ทดลองดึงข้อมูล Calendar ด้วย Google Calendar API บน Nodejs
พอดีได้ลองเล่นตัว Google Calendar API ก็เลยนำมาเขียนเป็นบทความแชร์ไว้ ซึ่ง Tutorial ทั้งหมดผมก็อ่านจาก Google Calendar API Quickstart ของมันแหละครับ ซึ่งข้อมูลอย่างละเอียดแนะนำอ่าน Guides,Quickstart เพิ่มเติมเอานะครับ ส่วน Source Code ทั้งหมดดูได้บน Github
Step 1 : Create Project
เริ่มต้นสร้างโปรเจ็ค ผมทำการสร้างโปรเจ็ค ด้วย
npm init
จากนั้นก็ทำการติดตั้ง dependencies ของ google
npm install googleapis googlepauth-library --save
ต่อมาผมติดตั้ง hapi
ซึ่งเอาไว้รัน web serve (หรือใครถนัดใช้ express ก็เปลี่ยนได้ครับ)
npm install hapi --save
และทำการสร้าง server ง่ายๆขึ้นมาตัวนึง ชื่อไฟล์ server.js
'use strict'
const Hapi = require('hapi')
const server = new Hapi.Server()
server.connection({
host: 'localhost',
port: 2345
})
server.route({
method: 'GET',
path: '/',
handler: (request, reply) => {
reply({ message: 'Hello World' })
}
})
server.start(() => {
console.log(`Server is running at ${server.info.uri}`)
})
เพิ่ม scripts เข้าไปในไฟล์ package.json
"scripts": {
"start": "node server.js"
}
แล้วสั่งรัน
npm start
จะได้ server ที่รันอยู่บน port 2345 แล้ว
Step 2 : Enable Calendar API
ต่อมาก่อนที่เราจะใช้งาน Calendar API นั้น ให้ทำการ enable ตัว Google Calendar API ก่อน เพื่อให้มีสิทธิ์ในการเรียกใช้งาน API โดยเข้าไปที่ Enable Google Calendar API (เลือก Project ที่เราต้องการ หรือไม่ก็สร้างขึ้นมาใหม่)
จากนั้นเลือกแท็ป Credentials ซ้ายมือ แล้วเลือก Oauth consent screen จากนั้นกรอก email และ Product Name
จากนั้นเลือก Credentials =>
Create credentials =>
OAuth client ID เลือก Others และตั้งชื่อตามที่ต้องการ
เมื่อสร้างเสร็จ ก็จะได้ไฟล์ JSON ทำการดาวน์โหลดมามาไว้ในโปรเจ็คของเรา ตั้งชื่อว่า client_secret.json
ตัวไฟล์ client_secret.json
จะมีข้อมูลของแอพเราอยู่ประมาณนี้
{
"installed": {
"client_id": "xxxxxxx.apps.googleusercontent.com",
"project_id": "project_id_1234",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "client_secret123456",
"redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"]
}
}
Step 3 : Run quickstart
ทำการดาวน์โหลดไฟล์จาก quickstart.js
var fs = require('fs')
var readline = require('readline')
var google = require('googleapis')
var googleAuth = require('google-auth-library')
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/calendar-nodejs-quickstart.json
var SCOPES = ['https://www.googleapis.com/auth/calendar']
var TOKEN_DIR =
(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) +
'/.credentials/'
var TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json'
// Load client secrets from a local file.
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err)
return
}
// Authorize a client with the loaded credentials, then call the
// Google Calendar API.
authorize(JSON.parse(content), listEvents)
})
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* @param {Object} credentials The authorization client credentials.
* @param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret
var clientId = credentials.installed.client_id
var redirectUrl = credentials.installed.redirect_uris[0]
var auth = new googleAuth()
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl)
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
getNewToken(oauth2Client, callback)
} else {
oauth2Client.credentials = JSON.parse(token)
callback(oauth2Client)
}
})
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
})
console.log('Authorize this app by visiting this url: ', authUrl)
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question('Enter the code from that page here: ', function (code) {
rl.close()
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err)
return
}
oauth2Client.credentials = token
storeToken(token)
callback(oauth2Client)
})
})
}
/**
* Store token to disk be used in later program executions.
*
* @param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR)
} catch (err) {
if (err.code != 'EEXIST') {
throw err
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token))
console.log('Token stored to ' + TOKEN_PATH)
}
/**
* Lists the next 10 events on the user's primary calendar.
*
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listEvents(auth) {
var calendar = google.calendar('v3')
calendar.events.list(
{
auth: auth,
calendarId: 'primary',
timeMin: new Date().toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime'
},
function (err, response) {
if (err) {
console.log('The API returned an error: ' + err)
return
}
var events = response.items
if (events.length == 0) {
console.log('No upcoming events found.')
} else {
console.log('Upcoming 10 events:')
for (var i = 0; i < events.length; i++) {
var event = events[i]
var start = event.start.dateTime || event.start.date
console.log('%s - %s', start, event.summary)
}
}
}
)
}
โดยได้ทำการเปลี่ยนแค่ SCOPES
จาก
var SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
เป็น
var SCOPES = ['https://www.googleapis.com/auth/calendar']
สำหรับเพิ่ม permission เอาไว้จัดการ เพิ่มหรือลบ event ได้ ทดลองสั่งรัน
node quickstart.js
Authorize this app by visiting this url: https://accounts.google.com/o/oauth2/auth?access_type=offline&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly&response_type=code&client_id=12234.apps.googleusercontent.com&redirect_uri=xxxxx
Enter the code from that page here:
จะได้ output ออกมา (ไม่เหมือนกัน ขึ้นอยู่กับชื่อ project, clientId) ให้ก็อปไปวางไว้บน browser แล้วก็ทำการ Authorized จากนั้นก็ใส่ code กลับมาที่ terminal อีกรอบ
ไฟล์ autrorized จะถูกเซฟไว้ที่ ~/.credentials/calendar-nodejs-quickstart.json
ซึ่งมาถึงตรงนี้ หากใครแสดงรายชื่อ events บน Calendar ได้แล้ว แสดงว่าสามารถเรียกใช้ API ได้ เป็นอันจบ
แต่....เดี๋ยวก่อน ผมไม่อยากใช้การ รัน node quickstart.js
แบบ Tutorial ก็เลยทำเป็น library ซะเลย
Step 4 : Create library
ขั้นตอนนี้ผมจะนำไฟล์ quickstart.js
มาแปลงเป็น library เพื่อให้เรียกใช้งานง่ายๆผ่าน Server hapi พร้อมทั้งเพิ่มการสร้าง event เข้าไปใน Calendar ได้
สร้างไฟล์ lib/index.js
ขึ้นมา ในไฟล์ library ผมแปลงจาก quickstart.js
เป็น library แบบนี้
'use strict'
const google = require('googleapis')
const googleAuth = require('google-auth-library')
const calendar = google.calendar('v3')
const fs = require('fs')
const SCOPES = [process.env.SCOPES]
const TOKEN_DIR =
(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) +
'/.credentials/'
const TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json'
module.exports = {
authorize: callback => {
fs.readFile('client_secret.json', function processClientSecrets(
err,
content
) {
if (err) {
console.log('Error loading client secret file: ' + err)
return
}
let credentials = JSON.parse(content)
var clientSecret = credentials.installed.client_secret
var clientId = credentials.installed.client_id
var redirectUrl = credentials.installed.redirect_uris[0]
var auth = new googleAuth()
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl)
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
return callback(err)
} else {
oauth2Client.credentials = JSON.parse(token)
return callback(null, oauth2Client)
}
})
})
},
getNewToken: (oauth2Client, callback) => {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
})
console.log('Authorize this app by visiting this url: ', authUrl)
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question('Enter the code from that page here: ', function (code) {
rl.close()
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err)
return
}
oauth2Client.credentials = token
storeToken(token)
callback(oauth2Client)
})
})
},
listEvents: (auth, callback) => {
calendar.events.list(
{
auth: auth,
calendarId: process.env.CALENDAR_ID,
timeMin: new Date().toISOString(),
maxResults: 50,
singleEvents: true,
orderBy: 'startTime'
},
(err, response) => {
if (err) {
return callback(err)
}
return callback(null, response)
}
)
},
createEvent: (auth, event, callback) => {
calendar.events.insert(
{
auth: auth,
calendarId: process.env.CALENDAR_ID,
resource: event
},
(err, event) => {
if (err) {
return callback(
'There was an error contacting the Calendar service: ' + err
)
}
return callback(null, event.htmlLink)
}
)
}
}
ซึ่งเวลาเรียกใช้งานผ่านทาง express ผมก็แค่
const lib = require('./lib')
lib.authorize()
lib.listEvents()
lib.createEvent()
แต่ว่าทำเป็น callback ไม่ได้ใช้ Promise ซึ่งเวลาเรียกใช้งานจำเป็นที่จะต้องส่ง Callback ไปด้วย
Step 5 : Implement Server Side
ต่อมาในส่วน hapi server ผมอยากใช้มี route 2 อัน คือ
- GET
/events
: สำหรับแสดง events จาก google calendar - POST
/events
: สำหรับสร้าง events แบบเดียวกับสร้างในหน้า Calendar
แน่นอน ทั้งหมดผมทำเป็น API ไม่ได้มีหน้า UI ฉะนั้นการรับส่งข้อมูลก็จะเป็นแค่ JSON นะครับ
สร้างไฟล์ routes.js
ขึ้นมาดังนี้
'use strict'
const controller = require('./controller')
module.exports = [
{
method: 'GET',
path: '/',
config: controller.index
},
{
method: 'GET',
path: '/events',
config: controller.events
},
{
method: 'POST',
path: '/events',
config: controller.create
}
]
สำหรับ hapi js เล็กน้อย ในเรื่อง routing เราสามารถกำหนดได้ทั้ง
handler: function(request, reply)
หรือจะเป็นconfig: Object
ก็ได้ครับ รายละเอียดเพิ่มเติม http://hapijs.com/tutorials/routing
ต่อมาผมสร้างไฟล์ controller.js
ขึ้นมา
'use strict'
const validate = require('./validate')
const lib = require('./lib')
module.exports = {
index: {
handler: (request, reply) => {
reply({
message: 'Google Calendar API',
endpoint: {
listEvents: 'GET /events',
createEvent: 'POST /events'
}
})
}
},
create: {
validate: validate.create,
handler: (request, reply) => {
let payload = request.payload
let summary = payload.summary
let description = payload.description
let email = payload.email
let startDate = payload.startDate
let endDate = payload.endDate
lib.authorize((err, auth) => {
if (err) return reply(err)
let options = lib.eventBuilder(payload)
options.auth = auth
lib.createEvent(options, (err, result) => {
if (err) return reply(err)
return reply(result)
})
})
}
},
events: {
handler: (request, reply) => {
lib.authorize((err, auth) => {
if (err) return reply(err)
lib.listEvents(auth, (err, response) => {
if (err) return reply(err)
return reply(response)
})
})
}
}
}
ผมเพิ่ม validate.js
เล็กน้อย เวลาที่ส่ง parameters สำหรับ POST method
จะได้ไม่มีปัญหา
'use strict'
const Joi = require('joi')
module.exports = {
create: {
payload: {
email: Joi.string().email().required(),
title: Joi.string().required(),
description: Joi.string().required(),
startDate: Joi.string().required(),
endDate: Joi.string().required()
}
}
}
สุดท้ายไฟล์ server.js
'use strict'
const Hapi = require('hapi')
const server = new Hapi.Server()
const routes = require('./routes')
server.connection({
host: 'localhost',
port: 2345
})
server.route(routes)
server.start(() => {
console.log(`Server is running at ${server.info.uri}`)
})
ทดลองสั่งรัน server
node server.js
เข้าหน้าเว็บ http://localhost:2345/ เป็นอันเรียบร้อย
สรุป
ตัวอย่างทั้งหมดอันนี้ผมก็ศึกษาจาก Google Calendar API และลองทำเล่นๆเท่านั้น จะเห็นว่าไม่มีหน้า UI เนื่องจากผมกะเอาไปใช้งานร่วมกับคำสั่งพวก slash command ของ Slack หรือว่า bot service อื่นๆ ดูในอนาคตครับ
สุดท้าย Source Code ครับ
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit