Repository: sebbo2002/ical-generator

Issue:
Organizer EMAIL= in addition to mailto:
I would like to create an .ics file where the `organizer` has an `EMAIL` attribute in addition to the `mailto:` value.

For example, when I send an invitation with Apple Calendar, the `organizer` line in the .ics file will look like this:

```
ORGANIZER;CN="<Name>";EMAIL=<EMAIL>:mailto:<SOMEID>@imip.me.com
```

where `<Name>` is my name, `<EMAIL>` is my actual email address (e.g. example@gmail.com) and `<SOMEID>` is a seemingly random sequence of characters.

The point is that this way, the organizer's actual email address can be displayed, while the `mailto:` value is different and can point to our server, so that we get notified when the invitation is accepted.

Does `ical-generator` currently support that? If not, would you consider adding this functionality?

Hints from issue comments:
@sebbo2002: No, this feature is not implemented yet. But it sounds to me like that should change. I'll leave this here and implement it if I can find the time. Otherwise, of course, pull requests are also welcome. To do this, `Attendee` would have to be extended by a `mailto` method and the updated generation code.@VSchlattinger: I created a pull request for this: https://github.com/sebbo2002/ical-generator/pull/120

Please take a look and tell me if there is something I should change.@visiongeist: @VSchlattinger I know out of scope.. but stupid question, which server are you using for synchronisation?@github-actions: This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related issues and reference this one.

Solution patch:
diff --git a/README.md b/README.md
index 93e2bdd..74beb47 100755
--- a/README.md
+++ b/README.md
@@ -365,16 +365,25 @@ Appointment location
 Appointment organizer
 
 ```javascript
-cal.organizer({
+event.organizer({
     name: 'Organizer\'s Name',
     email: 'organizer@example.com'
 });
 
 // OR
 
-cal.organizer('Organizer\'s Name <organizer@example.com>');
+event.organizer('Organizer\'s Name <organizer@example.com>');
 ```
 
+You can also add an explicit `mailto` email address.
+
+```javascript
+event.organizer({
+    name: 'Organizer\'s Name',
+    email: 'organizer@example.com',
+    mailto: 'explicit@mailto.com'
+})
+```
 
 #### createAttendee([_Object_ options])
 
@@ -385,7 +394,7 @@ Calling this method without options will create an empty attendee.
 const ical = require('ical-generator');
 const cal = ical();
 const event = cal.createEvent();
-const attendee = event.createAttendee({email: 'hui@example.com', 'name': 'Hui'});
+const attendee = event.createAttendee({email: 'hui@example.com', name: 'Hui'});
 
 // overwrite attendee's email address
 attendee.email('hui@example.net');
@@ -394,6 +403,15 @@ attendee.email('hui@example.net');
 event.createAttendee('Buh <buh@example.net>');
 ```
 
+As with the organizer, you can also add an explicit `mailto` address.
+
+```javascript
+event.createAttendee({email: 'hui@example.com', name: 'Hui', mailto: 'another@mailto.com'});
+
+// overwrite an attendee's mailto address
+attendee.mailto('another@mailto.net');
+```
+
 
 #### attendees([_Object_ attendees])
 
diff --git a/index.d.ts b/index.d.ts
index 00b3464..dbcb862 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -90,6 +90,7 @@ declare module 'ical-generator' {
     interface PersonData {
       name: string;
       email: string;
+      mailto?: string;
     }
 
     interface AttendeeData extends PersonData {
diff --git a/src/attendee.js b/src/attendee.js
index d0727aa..eac3655 100755
--- a/src/attendee.js
+++ b/src/attendee.js
@@ -14,6 +14,7 @@ class ICalAttendee {
         this._data = {
             name: null,
             email: null,
+            mailto: null,
             status: null,
             role: 'REQ-PARTICIPANT',
             rsvp: null,
@@ -24,6 +25,7 @@ class ICalAttendee {
         this._attributes = [
             'name',
             'email',
+            'mailto',
             'role',
             'rsvp',
             'status',
@@ -110,6 +112,22 @@ class ICalAttendee {
         return this;
     }
 
+    /**
+     * Set/Get the attendee's email address
+     *
+     * @param {String} [mailto] Email address
+     * @since x.x.x TODO: set correct version number
+     * @returns {ICalAttendee|String}
+     */
+    mailto(mailto) {
+        if (mailto === undefined) {
+            return this._data.mailto;
+        }
+
+        this._data.mailto = mailto || null;
+        return this;
+    }
+
 
     /**
      * Set/Get attendee's role
@@ -334,10 +352,15 @@ class ICalAttendee {
 
         // CN / Name
         if (this._data.name) {
-            g += ';CN="' + this._data.name + '"';
+            g += ';CN="' + ICalTools.escape(this._data.name) + '"';
+        }
+
+        // EMAIL
+        if (this._data.email && this._data.mailto) {
+            g += ';EMAIL=' + ICalTools.escape(this._data.email);
         }
 
-        g += ':MAILTO:' + this._data.email + '\r\n';
+        g += ':MAILTO:' + ICalTools.escape(this._data.mailto || this._data.email) + '\r\n';
         return g;
     }
 }
diff --git a/src/event.js b/src/event.js
index 71475b4..6237882 100755
--- a/src/event.js
+++ b/src/event.js
@@ -535,6 +535,7 @@ class ICalEvent {
      * @param {String|Object} [organizer]
      * @param {String} [organizer.name]
      * @param {String} [organizer.email]
+     * @param {String} [organizer.mailto]
      * @since 0.2.0
      * @returns {ICalEvent|Object}
      */
@@ -562,7 +563,8 @@ class ICalEvent {
         else if (typeof _organizer === 'object') {
             organizer = {
                 name: _organizer.name,
-                email: _organizer.email
+                email: _organizer.email,
+                mailto: _organizer.mailto
             };
         }
 
@@ -590,6 +592,11 @@ class ICalEvent {
             name: organizer.name,
             email: organizer.email
         };
+
+        if (organizer.mailto) {
+            this._data.organizer.mailto = organizer.mailto;
+        }
+
         return this;
     }
 
@@ -958,7 +965,11 @@ class ICalEvent {
 
         // ORGANIZER
         if (this._data.organizer) {
-            g += 'ORGANIZER;CN="' + ICalTools.escape(this._data.organizer.name) + '":mailto:' + ICalTools.escape(this._data.organizer.email) + '\r\n';
+            g += 'ORGANIZER;CN="' + ICalTools.escape(this._data.organizer.name) + '"';
+            if (this._data.organizer.email && this._data.organizer.mailto) {
+                g += ';EMAIL=' + ICalTools.escape(this._data.organizer.email);
+            }
+            g += ':mailto:' + ICalTools.escape(this._data.organizer.mailto || this._data.organizer.email) + '\r\n';
         }
 
         // ATTENDEES



Test patch:
diff --git a/test/results/generate_09.ics b/test/results/generate_09.ics
new file mode 100755
index 0000000..44bba89
--- /dev/null
+++ b/test/results/generate_09.ics
@@ -0,0 +1,17 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//sebbo.net//ical-generator.tests//EN
+METHOD:REQUEST
+BEGIN:VEVENT
+UID:123@sebbo.net
+SEQUENCE:0
+DTSTAMP:20131004T233453Z
+DTSTART:20131004T223930Z
+DTEND:20131004T231500Z
+SUMMARY:Sample Event
+ORGANIZER;CN="Sebastian Pekarek";EMAIL=mail@sebbo.net:mailto:mail2@example
+ 2.com
+ATTENDEE;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP
+ =TRUE:MAILTO:mail@example.com
+END:VEVENT
+END:VCALENDAR
\ No newline at end of file
diff --git a/test/test_attendee.js b/test/test_attendee.js
index 4d00174..580e00f 100644
--- a/test/test_attendee.js
+++ b/test/test_attendee.js
@@ -57,6 +57,31 @@ describe('ical-generator Attendee', function () {
         });
     });
 
+    describe('mailto()', function () {
+        it('getter should return value', function () {
+            const a = new ICalAttendee(null, new ICalEvent(null, new ICalCalendar()));
+            assert.equal(a.mailto(), null);
+
+            a._data.mailto = 'foo@example.com';
+            assert.equal(a.mailto(), 'foo@example.com');
+        });
+
+        it('setter should return this', function () {
+            const a = new ICalAttendee(null, new ICalEvent(null, new ICalCalendar()));
+            assert.deepEqual(a, a.mailto(null));
+            assert.deepEqual(a, a.mailto('foo@example.com'));
+        });
+
+        it('should change mailto and keep email if present', function () {
+            const a = new ICalAttendee({email: 'mail@example.com'}, new ICalEvent(null, new ICalCalendar()));
+            a.mailto('mail2@example2.com');
+            assert.ok(
+                a._generate().indexOf('EMAIL=mail@example.com') > -1 &&
+                a._generate().indexOf('MAILTO:mail2@example2.com') > -1
+            );
+        });
+    });
+
     describe('role()', function () {
         it('setter should return this', function () {
             const a = new ICalAttendee(null, new ICalEvent(null, new ICalCalendar()));
diff --git a/test/test_cases.js b/test/test_cases.js
index 577b546..7d27113 100644
--- a/test/test_cases.js
+++ b/test/test_cases.js
@@ -253,4 +253,34 @@ describe('ical-generator Cases', function() {
         const json = JSON.stringify(cal.toJSON());
         assert.equal(ical(json).toString(), string);
     });
+
+    it('case #9 (organizer with mailto)', function() {
+        const cal = ical({domain: 'sebbo.net', method: 'request', prodId: '//sebbo.net//ical-generator.tests//EN'});
+        cal.createEvent({
+            id: '123',
+            start: new Date('Fr Oct 04 2013 22:39:30 UTC'),
+            end: new Date('Fr Oct 04 2013 23:15:00 UTC'),
+            stamp: new Date('Fr Oct 04 2013 23:34:53 UTC'),
+            summary: 'Sample Event',
+            organizer: {
+                name: 'Sebastian Pekarek',
+                email: 'mail@sebbo.net',
+                mailto: 'mail2@example2.com'
+            },
+            attendees: [{
+                type: 'individual',
+                role: 'req-participant',
+                status: 'needs-action',
+                email: 'mail@example.com',
+                rsvp: 'true'
+            }]
+        });
+
+        /*jslint stupid: true */
+        const string = cal.toString();
+        assert.equal(string, fs.readFileSync(__dirname + '/results/generate_09.ics', 'utf8'), 'toString');
+
+        const json = JSON.stringify(cal.toJSON());
+        assert.equal(ical(json).toString(), string, 'toJSON / toString()');
+    });
 });
\ No newline at end of file
diff --git a/test/test_event.js b/test/test_event.js
index bbd4fa8..b1215c0 100644
--- a/test/test_event.js
+++ b/test/test_event.js
@@ -897,6 +897,11 @@ describe('ical-generator Event', function () {
             e._data.organizer = {name: 'Sebastian Pekarek', email: 'mail@example.com'};
             assert.equal('Sebastian Pekarek', e.organizer().name);
             assert.equal('mail@example.com', e.organizer().email);
+
+            e._data.organizer = {name: 'Sebastian Pekarek', email: 'mail@example.com', mailto: 'mail2@example2.com'};
+            assert.equal('Sebastian Pekarek', e.organizer().name);
+            assert.equal('mail@example.com', e.organizer().email);
+            assert.equal('mail2@example2.com', e.organizer().mailto);
         });
 
         it('setter should return this', function () {
@@ -913,6 +918,9 @@ describe('ical-generator Event', function () {
 
             event.organizer({name: 'Sebastian Pekarek', email: 'mail@example.com'});
             assert.deepEqual(event._data.organizer, {name: 'Sebastian Pekarek', email: 'mail@example.com'});
+
+            event.organizer({name: 'Sebastian Pekarek', email: 'mail@example.com', mailto: 'mail2@example2.com'});
+            assert.deepEqual(event._data.organizer, {name: 'Sebastian Pekarek', email: 'mail@example.com', mailto: 'mail2@example2.com'});
         });
 
         it('should work with valid strings', function () {