Skip to content

Instantly share code, notes, and snippets.

@evalphobia
Created July 5, 2018 08:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save evalphobia/bd7265217351031ab19a3419e37eaadb to your computer and use it in GitHub Desktop.
Save evalphobia/bd7265217351031ab19a3419e37eaadb to your computer and use it in GitHub Desktop.
To fetch Google Calendar event. (Running on Firebase Functions for Dialogflow)
class Action {
constructor(req, res){
this.request = req;
this.response = res;
this.requestSource = (req.body.originalRequest) ? req.body.originalRequest.source : undefined;
this.app = new DialogflowApp({req, res});
console.log('api-v2')
}
sendResponse(text) {
const googleAssistantRequest = 'google';
if (this.requestSource === googleAssistantRequest) {
this._sendGoogleResponse(text)
return
}
this._sendResponse(text)
}
_sendResponse(text) {
if (typeof text === 'string') {
let responseJson = {};
responseJson.speech = text; // spoken response
responseJson.displayText = text; // displayed response
this.response.json(responseJson); // Send response to Dialogflow
return
}
let responseJson = {};
responseJson.speech = text.speech || text.displayText;
responseJson.displayText = text.displayText || text.speech;
this.response.json(responseJson); // Send response to Dialogflow
}
_sendGoogleResponse(text) {
if (typeof text === 'string') {
this.app.ask(text);
return
}
// If speech or displayText is defined use it to respond
let googleResponse = this.app.buildRichResponse().addSimpleResponse({
speech: text.speech || text.displayText,
displayText: text.displayText || text.speech
});
this.app.ask(googleResponse);
}
}
'use strict';
const google = require('googleapis');
// CalendarAction for fetch calendar event from Google Calendar.
class CalendarAction extends Action {
get CLIENT_ID(){ return '000000000000-foobar.apps.googleusercontent.com' }
get CLIENT_SECRET(){ return '<secret>' }
get REDIRECT_URL(){ return 'https://example.firebaseapp.com/__/auth/handler' }
get REFRESH_TOKEN(){ return '<secret>' }
constructor(req, res){
super(req, res)
const oauth2Client = new google.auth.OAuth2(this.CLIENT_ID, this.CLIENT_SECRET, this.REDIRECT_URL);
oauth2Client.credentials = {
refresh_token: this.REFRESH_TOKEN,
};
this.oauth2Client = oauth2Client;
this.locale = 'ja-JP'
this.timeZone = 'Asia/Tokyo'
}
doWhere(params) {
const fn = (err, resp) => {this.listCalendar(err, resp)}
this._do(params, fn)
}
_do(params, callback) {
this.oauth2Client.refreshAccessToken( (err, resp) => {
if (err) {
console.log('actionCalendar: token error=', err);
this.sendResponse('actionCalendar: token error');
return
}
const calendar = google.calendar('v3');
const requestParams = this._createParams(params)
calendar.events.list(requestParams, (err, resp) => { callback(err, resp) });
})
}
_createParams(params) {
let email = params['people-mail'] || params['email']
return {
auth: this.oauth2Client,
calendarId: email,
timeMin: (new Date()).toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime'
}
}
listCalendar(err, resp) {
console.log('exec listCalendar')
if (err) {
console.log('actionCalendar: list error=', err);
this.sendResponse('actionCalendar: list error');
return
}
const result = this._getCalendarEvent(resp.items)
console.log('result=', result)
if (!result) {
this.sendResponse('今はその辺にいそうです');
return
}
this.sendResponse(this._createMessage(result));
}
_getCalendarEvent(items) {
let now = new Date().getTime();
let result = {
currentList: [],
current: undefined,
next: undefined,
prev: undefined,
hasEvent: false,
allDay: undefined,
hasAllDay: false,
}
for (var i = 0, max = items.length; i < max; i++) {
let item = items[i]
// 未承認
if (item.status != 'confirmed') {continue;}
if (!item.start) {continue;}
console.log(`%%% summary=${item.summary}, start=${this._getLocalTime(item.start.dateTime)}, end=${this._getLocalTime(item.end.dateTime)} %%%`)
if (item.start.date && !item.start.dateTime) {
let startTime = new Date(item.start.date).getTime()
let endTime = new Date(item.end.date).getTime()
if (now < startTime) continue;
if (now > endTime) continue;
result.allDay = item;
result.hasAllDay = true
continue;
}
let startTime = new Date(item.start.dateTime).getTime()
let endTime = new Date(item.end.dateTime).getTime()
if (now > startTime && now < endTime) {
result.currentList.push(item)
result.hasEvent = true
continue
}
// 30分以内に終了したイベントを取得
if (now > startTime && now < (endTime + 60*30*1000)) {
result.prev = item
result.hasEvent = true
continue;
}
// 30分以内に始まるイベントを取得
if (now < endTime && now > (startTime - 60*30*1000)) {
result.next = item
result.hasEvent = true
continue;
}
}
// 重複イベントの処理
if (result.currentList.length == 1) {
result.current = result.currentList[0]
} else if (result.currentList.length > 1) {
result.prev = result.currentList[result.currentList.length - 2]
result.current = result.currentList[result.currentList.length - 1]
}
if (result.current || result.next || result.prev || result.allDay) {
return result
}
return false
}
_createMessage(result) {
var msg = [];
if (result.prev) {
msg.push(this._createSentence('前', result.prev))
}
if (result.current) {
msg.push(this._createSentence('今', result.current))
}
if (result.next) {
msg.push(this._createSentence('次', result.next))
}
// 終日イベントは、(1)「通常イベントが無い場合」、または(2)「全日イベントに場所がある場合」のみ使用
if (result.hasAllDay) {
if (result.allDay.location || !result.hasEvent) {
msg.push(this._createSentence('終日', result.allDay))
}
}
return msg.join("\n")
}
_createSentence(title, item) {
if (!item.location) {
item.location = 'どこか'
}
return `【${title}】 | ${item.location}で${item.summary}してそうです (${this._getLocalTime(item.start.dateTime)} - ${this._getLocalTime(item.end.dateTime)})`
}
_getLocalTime(str) {
return new Date(str).toLocaleTimeString(this.locale, {timeZone: this.timeZone})
}
}
@evalphobia
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment