Skip to content

Commit bc5777d

Browse files
committed
Merge pull request #68 from hindessm/google-calendar-query
Add google calendar query node.
2 parents 5b46058 + df6462a commit bc5777d

File tree

2 files changed

+206
-1
lines changed

2 files changed

+206
-1
lines changed

google/calendar.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,50 @@
1414
limitations under the License.
1515
-->
1616

17+
<script type="text/x-red" data-template-name="google calendar">
18+
<div class="form-row">
19+
<label for="node-input-google"><i class="fa fa-user"></i> Google</label>
20+
<input type="text" id="node-input-google">
21+
</div>
22+
<div class="form-row">
23+
<label for="node-input-calendar"><i class="fa fa-tag"></i> Calendar</label>
24+
<input type="text" id="node-input-calendar">
25+
</div>
26+
<div class="form-row">
27+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
28+
<input type="text" id="node-input-name">
29+
</div>
30+
</script>
31+
32+
<script type="text/x-red" data-help-name="google calendar">
33+
<p>Create an entry in a <a href="https://www.google.com/calendar">Google Calendar</a>.</p>
34+
<p>The incoming message can provide the following properties:
35+
<ul>
36+
<li><b>payload</b> - an object containing the query parameters described in <a href="https://developers.google.com/google-apps/calendar/v3/reference/events/list">event list API documentation</a></li>
37+
<li><b>calendar</b> - the calendar to add the event to (optional, defaults to the node calendar property or the users primary calendar)</li>
38+
</ul>
39+
</p>
40+
</script>
41+
42+
<script type="text/javascript">
43+
RED.nodes.registerType('google calendar',{
44+
category: 'social',
45+
color:"#C0DEED",
46+
defaults: {
47+
google: {type:"google-credentials",required:true},
48+
name: {value:""},
49+
calendar: {value:""}
50+
},
51+
inputs:1,
52+
outputs:1,
53+
icon: "google-calendar.png",
54+
align: "right",
55+
label: function() {
56+
return this.name||"Google Calendar";
57+
}
58+
});
59+
</script>
60+
1761
<script type="text/x-red" data-template-name="google calendar out">
1862
<div class="form-row">
1963
<label for="node-input-google"><i class="fa fa-user"></i> Google</label>

google/calendar.js

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,168 @@
1717
module.exports = function(RED) {
1818
"use strict";
1919

20+
function GoogleCalendarQueryNode(n) {
21+
RED.nodes.createNode(this,n);
22+
this.google = RED.nodes.getNode(n.google);
23+
this.calendar = n.calendar || 'primary';
24+
25+
this.calendars = {};
26+
if (!this.google || !this.google.credentials.accessToken) {
27+
this.warn("Missing google credentials");
28+
return;
29+
}
30+
31+
var node = this;
32+
node.status({fill:"blue",shape:"dot",text:"querying"});
33+
this.google.request('https://www.googleapis.com/calendar/v3/users/me/calendarList', function(err, data) {
34+
if (err) {
35+
node.error("failed to fetch calendar list: " + err.toString());
36+
node.status({fill:"red",shape:"ring",text:"failed"});
37+
return;
38+
}
39+
if (data.error) {
40+
node.error("failed to fetch calendar list: " +
41+
data.error.message);
42+
node.status({fill:"red",shape:"ring",text:"failed"});
43+
return;
44+
}
45+
for (var i = 0; i < data.items.length; i++) {
46+
var cal = data.items[i];
47+
if (cal.primary) {
48+
node.calendars.primary = cal;
49+
}
50+
node.calendars[cal.id] = cal;
51+
}
52+
node.status({});
53+
54+
node.on('input', function(msg) {
55+
node.status({fill:"blue",shape:"dot",text:"querying"});
56+
var cal = node.calendars[msg.calendar] || node.calendarByName(msg.calendar) || node.calendars[node.calendar] || node.calendarByName(node.calendar);
57+
if (!cal) {
58+
node.status({fill:"red",shape:"ring",text:"invalid calendar"});
59+
return;
60+
}
61+
var request = {
62+
url: 'https://www.googleapis.com/calendar/v3/calendars/'+cal.id+'/events',
63+
};
64+
var now = new Date();
65+
request.qs = {
66+
maxResults: 10,
67+
orderBy: 'startTime',
68+
singleEvents: true,
69+
timeMin: now.toISOString()
70+
};
71+
if (msg.payload) {
72+
request.qs.q = RED.util.ensureString(msg.payload);
73+
}
74+
node.google.request(request, function(err, data) {
75+
if (err) {
76+
node.error("Error: " + err.toString());
77+
node.status({fill:"red",shape:"ring",text:"failed"});
78+
} else if (data.error) {
79+
node.error("Error " + data.error.code + ": " +
80+
JSON.stringify(data.error.message));
81+
node.status({fill:"red",shape:"ring",text:"failed"});
82+
} else {
83+
var payload = msg.payload = {};
84+
var ev;
85+
/* 0 - 10 events ending after now ordered by startTime
86+
* so we find the first that starts after now to
87+
* give us the "next" event
88+
*/
89+
for (var i = 0; i<data.items.length; i++) {
90+
ev = data.items[i];
91+
var start = getEventDate(ev);
92+
if (start && start.getTime() > now.getTime()) {
93+
payload.start = start;
94+
break;
95+
}
96+
ev = undefined;
97+
}
98+
if (!ev) {
99+
delete msg.data;
100+
node.send(msg);
101+
node.status({fill:"red",shape:"ring",text:"no event"});
102+
return;
103+
}
104+
if (ev.summary) {
105+
payload.title = msg.title = ev.summary;
106+
}
107+
if (ev.description) {
108+
payload.description = msg.description = ev.description;
109+
} else {
110+
delete msg.description;
111+
}
112+
if (ev.location) {
113+
/* intentionally the same object so that
114+
* if a node modifies msg.location (for
115+
* example by looking up
116+
* msg.location.description and adding
117+
* msg.location.{lat,lon} then both copies
118+
* will be updated.
119+
*/
120+
payload.location = msg.location = {
121+
description: ev.location
122+
};
123+
} else {
124+
delete msg.location;
125+
}
126+
if (ev.start && ev.start.date) {
127+
payload.allDayEvent = true;
128+
}
129+
var end = getEventDate(ev, 'end');
130+
if (end) {
131+
payload.end = end;
132+
}
133+
if (ev.creator) {
134+
payload.creator = {
135+
name: ev.creator.displayName,
136+
email: ev.creator.email,
137+
};
138+
}
139+
if (ev.attendees) {
140+
payload.attendees = [];
141+
ev.attendees.forEach(function (a) {
142+
payload.attendees.push({
143+
name: a.displayName,
144+
email: a.email
145+
});
146+
});
147+
}
148+
msg.data = ev;
149+
node.send(msg);
150+
node.status({});
151+
}
152+
});
153+
});
154+
});
155+
}
156+
RED.nodes.registerType("google calendar", GoogleCalendarQueryNode);
157+
158+
function getEventDate(ev, type) {
159+
if (typeof type === 'undefined') {
160+
type = 'start';
161+
}
162+
if (ev[type] && ev[type].dateTime) {
163+
return new Date(ev[type].dateTime);
164+
} else if (ev.start && ev.start.date) {
165+
return new Date(ev[type].date);
166+
} else {
167+
return null;
168+
}
169+
}
170+
171+
GoogleCalendarQueryNode.prototype.calendarByName = function(name) {
172+
for (var cal in this.calendars) {
173+
if (this.calendars.hasOwnProperty(cal)) {
174+
if (this.calendars[cal].summary === name) {
175+
return this.calendars[cal];
176+
}
177+
}
178+
}
179+
return;
180+
};
181+
20182
function GoogleCalendarOutNode(n) {
21183
RED.nodes.createNode(this,n);
22184
this.google = RED.nodes.getNode(n.google);
@@ -49,7 +211,6 @@ module.exports = function(RED) {
49211
}
50212
node.calendars[cal.id] = cal;
51213
}
52-
//console.log("Calendars: "+require('util').inspect(node.calendars));
53214
node.status({});
54215

55216
node.on('input', function(msg) {

0 commit comments

Comments
 (0)