Skip to content

Commit

Permalink
release v0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-virkus committed Jul 20, 2021
1 parent 6c3d4fd commit 7dccbe2
Show file tree
Hide file tree
Showing 11 changed files with 625 additions and 1 deletion.
76 changes: 76 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
pubspec.lock

# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java

# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*

# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
10 changes: 10 additions & 0 deletions .metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
channel: stable

project_type: package
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## 0.1.0
* Initial release with iMIP support.
105 changes: 104 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,105 @@
# enough_mail_icalendar
iCalendar support for email / mime. Compatible with iCalendar Message-Based Interoperability Protocol (iMIP) RFC 6047.
iCalendar support for email / mime. Compatible with the iCalendar Message-Based Interoperability Protocol (iMIP) [RFC 6047](https://datatracker.ietf.org/doc/html/rfc6047).

## Installation
Add this dependency your pubspec.yaml file:

```
dependencies:
enough_mail_icalendar: ^0.1.0
enough_mail: latest
enough_icalendar: latest
```
The latest version or `enough_mail_icalendar` is [![enough_mail_icalendar version](https://img.shields.io/pub/v/enough_mail_icalendar.svg)](https://pub.dartlang.org/packages/enough_mail_icalendar).



## API Documentation
Check out the full API documentation at https://pub.dev/documentation/enough_mail_icalendar/latest/

## Usage

Use `enough_mail_icalendar` to generate and send MIME email messages for iCalendar requests.

### Import

```dart
import 'package:enough_icalendar/enough_icalendar.dart';
import 'package:enough_mail/enough_mail.dart';
import 'package:enough_mail_icalendar/enough_mail_icalendar.dart';
```
### Generate a MimeMessage for a VCalendar
With `VMessageBuilder.prepareFromCalendar(...)` create a MIME message builder for a given `VCalendar` object.

```dart
void buildCalendarInviteMessage(VCalendar invite) {
final builder = VMessageBuilder.prepareFromCalendar(invite);
final mimeMessage = builder.buildMimeMessage();
print(mimeMessage);
// you can now send the MimeMessage as any other message, e.g. with `MailClient.sendMessage(mimeMessage)`
}
```
### Generate a Reply MimeMessage for a Received VCalendar
Use `VMessageBuilder.prepareCalendarReply(...)` to create a reply MIME message for a received VCalendar.
In the following example the invite is accepted.
```dart
void buildAcceptReplyMessage(VCalendar invite) {
final me = MailAddress('Donna Strickland', '[email protected]');
final acceptMessageBuilder = VMessageBuilder.prepareCalendarReply(
invite,
ParticipantStatus.accepted,
me,
);
final mimeMessage = acceptMessageBuilder.buildMimeMessage();
print(mimeMessage);
}
```
### Send a Reply directly for a Received VCalendar
Send a reply directly with the `MailClient.sendCalendarReply()` instance method. This will generate the
mime message, send it and update the originating message's flags, when the message is specified and when the
mail service supports arbitrary message flags.
```dart
Future sendCalendarReply(
VCalendar calendar,
ParticipantStatus participantStatus,
MimeMessage originatingMessage,
MailClient mailClient,
) {
// generate reply email message, send it, set message flags:
return mailClient.sendCalendarReply(calendar, participantStatus,
originatingMessage: originatingMessage);
}
```

### Check if a Reply has been Send for a MimeMessage
Use the `calendarParticipantStatus` getter on a `MimeMessage` instance to check for participation status flags that have been set earlier.
```dart
ParticipantStatus? getParticipantStatus(MimeMessage message) {
// the ParticipantStatus can be detected from the message flags when
//the flag was added successfully before
final participantStatus = message.calendarParticipantStatus;
if (participantStatus != null) {
print(
'detected ${participantStatus.name} through flag ${participantStatus.flag}');
} else {
print('no participant status flag detected in ${message.flags}');
}
return participantStatus;
}
```

## Features and bugs

`enough_mail_icalendar` should be fully compliant with the iCalendar Message-Based Interoperability Protocol (iMIP) [RFC 6047](https://datatracker.ietf.org/doc/html/rfc6047).


Please file feature requests and bugs at the [issue tracker][tracker].

[tracker]: https://github.com/Enough-Software/enough_mail_icalendar/issues

## Null-Safety
`enough_mail_icalendar` is null-safe.

## License
`enough_mail_icalendar` is licensed under the commercial friendly [Mozilla Public License 2.0](LICENSE)

1 change: 1 addition & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package:lints/recommended.yaml
77 changes: 77 additions & 0 deletions example/enough_mail_icalendar_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:enough_icalendar/enough_icalendar.dart';
import 'package:enough_mail/enough_mail.dart';
import 'package:enough_mail_icalendar/enough_mail_icalendar.dart';

void main() {
final invite = createInvite();
buildCalendarInviteMessage(invite);
buildAcceptReplyMessage(invite);
}

void buildCalendarInviteMessage(VCalendar invite) {
final builder = VMessageBuilder.prepareFromCalendar(invite);
final mimeMessage = builder.buildMimeMessage();
print('==========================');
print('invite message:');
print('==========================');
print(mimeMessage);
// you can now send the MimeMessage as any other message, e.g. with `MailClient.sendMessage(mimeMessage)`
}

void buildAcceptReplyMessage(VCalendar invite) {
final me = MailAddress('Donna Strickland', '[email protected]');
final acceptMessageBuilder = VMessageBuilder.prepareCalendarReply(
invite,
ParticipantStatus.accepted,
me,
);
final mimeMessage = acceptMessageBuilder.buildMimeMessage();
print('\n==========================');
print('reply message:');
print('==========================');
print(mimeMessage);
}

Future sendCalendarReply(
VCalendar calendar,
ParticipantStatus participantStatus,
MimeMessage originatingMessage,
MailClient mailClient,
) {
// generate reply email message, send it, set message flags:
return mailClient.sendCalendarReply(calendar, participantStatus,
originatingMessage: originatingMessage);
}

ParticipantStatus? getParticipantStatus(MimeMessage message) {
// the ParticipantStatus can be detected from the message flags when
//the flag was added successfully before
final participantStatus = message.calendarParticipantStatus;
if (participantStatus != null) {
print(
'detected ${participantStatus.name} through flag ${participantStatus.flag}');
} else {
print('no participant status flag detected in ${message.flags}');
}
return participantStatus;
}

VCalendar createInvite() {
final me = MailAddress('Andrea Ghez', '[email protected]');
final invitees = [
MailAddress('Andrea Ghez', '[email protected]'),
MailAddress('Donna Strickland', '[email protected]'),
MailAddress('Maria Goeppert Mayer', '[email protected]'),
MailAddress('Marie Curie, née Sklodowska', '[email protected]'),
];
final invite = VCalendar.createEvent(
start: DateTime(2021, 08, 01, 11, 00),
duration: IsoDuration(hours: 1),
organizer: me.organizer,
attendees: invitees.map((address) => address.attendee).toList(),
location: 'Stockholm',
summary: 'Physics Winners',
description: 'Let\'s discuss what to research next.',
);
return invite;
}
7 changes: 7 additions & 0 deletions lib/enough_mail_icalendar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// A library for handling iCalendar invites in email.
///
/// Compatible with the iCalendar Message-Based Interoperability Protocol (iMIP), [RFC 6047](https://datatracker.ietf.org/doc/html/rfc6047)
library enough_mail_icalendar;

export 'src/builder.dart';
export 'src/extensions.dart';
93 changes: 93 additions & 0 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'package:enough_icalendar/enough_icalendar.dart';
import 'package:enough_mail/enough_mail.dart';
import 'extensions.dart';

class VMessageBuilder {
VMessageBuilder._();

/// Prepares a message builder with a request for the specified [calendar].
static MessageBuilder prepareFromCalendar(
VCalendar calendar, {
List<MailAddress> toRecipients = const [],
List<MailAddress> ccRecipients = const [],
List<MailAddress> bccRecipients = const [],
String? plaintTextPart,
MailAddress? from,
String filename = 'invite.ics',
String? subject,
}) {
final organizer = calendar.organizer;
if (from == null && (organizer == null || organizer.email == null)) {
throw StateError(
'Either the [from] parameter must be set or the calendar requires a child with an [organizer] set.');
}

final builder = MessageBuilder.prepareMultipartMixedMessage();
builder.subject = subject ?? calendar.summary ?? 'Invite';
final fromSender = from ?? calendar.organizerMailAddress!;
builder.from = [fromSender];
if (toRecipients.isEmpty && ccRecipients.isEmpty && bccRecipients.isEmpty) {
final attendees = calendar.attendees;
if (attendees == null || attendees.isEmpty) {
throw StateError(
'Warning: neither recipients specified nor attendees found in calendar');
}
builder.to = calendar.attendeeMailAddresses;
} else {
builder.to = toRecipients;
builder.cc = ccRecipients;
builder.bcc = bccRecipients;
}
final text = plaintTextPart ??
calendar.description ??
calendar.summary ??
'This message contains an calendar invite';
builder.addTextPlain(text);
final calendarPart = builder.addText(
calendar.toString(),
mediaType: MediaSubtype.textCalendar.mediaType,
disposition: ContentDispositionHeader.from(
ContentDisposition.attachment,
filename: filename,
),
);
if (calendar.method != null) {
final contentType = calendarPart.contentType!;
contentType.parameters['method'] = calendar.method!.name;
}
return builder;
}

static MessageBuilder prepareCalendarReply(
VCalendar calendar,
ParticipantStatus participantStatus,
MailAddress from, {
String? comment,
String productId = 'enough_mail with enough_icalendar',
String icsFilename = 'reply.ics',
}) {
final organizer = calendar.organizer;
if (organizer == null || organizer.email == null) {
throw StateError(
'VCALENDAR has no organizer or the organizer has no email: $organizer');
}
final reply = calendar.replyWithParticipantStatus(
participantStatus,
attendeeEmail: from.email,
comment: comment,
productId: productId,
);
final subject =
MessageBuilder.createReplySubject(calendar.summary ?? 'Invite');
final messageBuilder = prepareFromCalendar(
reply,
from: from,
toRecipients: [organizer.mailAddress!],
filename: icsFilename,
plaintTextPart: comment ??
'"${calendar.summary}" is ${participantStatus.name} by $from.',
subject: subject,
);
return messageBuilder;
}
}
Loading

0 comments on commit 7dccbe2

Please sign in to comment.