Devahoy Logo
PublishedAt

NodeJS

ทดลองดึงข้อมูล Calendar ด้วย Google Calendar API บน Nodejs

ทดลองดึงข้อมูล Calendar ด้วย Google Calendar API บน Nodejs

พอดีได้ลองเล่นตัว Google Calendar API ก็เลยนำมาเขียนเป็นบทความแชร์ไว้ ซึ่ง Tutorial ทั้งหมดผมก็อ่านจาก Google Calendar API Quickstart ของมันแหละครับ ซึ่งข้อมูลอย่างละเอียดแนะนำอ่าน Guides,Quickstart เพิ่มเติมเอานะครับ ส่วน Source Code ทั้งหมดดูได้บน Github

Step 1 : Create Project

เริ่มต้นสร้างโปรเจ็ค ผมทำการสร้างโปรเจ็ค ด้วย

1
npm init

จากนั้นก็ทำการติดตั้ง dependencies ของ google

1
npm install googleapis googlepauth-library --save

ต่อมาผมติดตั้ง hapi ซึ่งเอาไว้รัน web serve (หรือใครถนัดใช้ express ก็เปลี่ยนได้ครับ)

1
npm install hapi --save

และทำการสร้าง server ง่ายๆขึ้นมาตัวนึง ชื่อไฟล์ server.js

1
'use strict'
2
3
const Hapi = require('hapi')
4
const server = new Hapi.Server()
5
6
server.connection({
7
host: 'localhost',
8
port: 2345
9
})
10
11
server.route({
12
method: 'GET',
13
path: '/',
14
handler: (request, reply) => {
15
reply({ message: 'Hello World' })
16
}
17
})
18
19
server.start(() => {
20
console.log(`Server is running at ${server.info.uri}`)
21
})

เพิ่ม scripts เข้าไปในไฟล์ package.json

1
"scripts": {
2
"start": "node server.js"
3
}

แล้วสั่งรัน

1
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 จะมีข้อมูลของแอพเราอยู่ประมาณนี้

1
{
2
"installed": {
3
"client_id": "xxxxxxx.apps.googleusercontent.com",
4
"project_id": "project_id_1234",
5
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
6
"token_uri": "https://accounts.google.com/o/oauth2/token",
7
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
8
"client_secret": "client_secret123456",
9
"redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"]
10
}
11
}

Step 3 : Run quickstart

ทำการดาวน์โหลดไฟล์จาก quickstart.js

1
var fs = require('fs')
2
var readline = require('readline')
3
var google = require('googleapis')
4
var googleAuth = require('google-auth-library')
5
6
// If modifying these scopes, delete your previously saved credentials
7
// at ~/.credentials/calendar-nodejs-quickstart.json
8
var SCOPES = ['https://www.googleapis.com/auth/calendar']
9
var TOKEN_DIR =
10
(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + '/.credentials/'
11
var TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json'
12
13
// Load client secrets from a local file.
14
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
15
if (err) {
16
console.log('Error loading client secret file: ' + err)
17
return
18
}
19
// Authorize a client with the loaded credentials, then call the
20
// Google Calendar API.
21
authorize(JSON.parse(content), listEvents)
22
})
23
24
/**
25
* Create an OAuth2 client with the given credentials, and then execute the
26
* given callback function.
27
*
28
* @param {Object} credentials The authorization client credentials.
29
* @param {function} callback The callback to call with the authorized client.
30
*/
31
function authorize(credentials, callback) {
32
var clientSecret = credentials.installed.client_secret
33
var clientId = credentials.installed.client_id
34
var redirectUrl = credentials.installed.redirect_uris[0]
35
var auth = new googleAuth()
36
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl)
37
38
// Check if we have previously stored a token.
39
fs.readFile(TOKEN_PATH, function (err, token) {
40
if (err) {
41
getNewToken(oauth2Client, callback)
42
} else {
43
oauth2Client.credentials = JSON.parse(token)
44
callback(oauth2Client)
45
}
46
})
47
}
48
49
/**
50
* Get and store new token after prompting for user authorization, and then
51
* execute the given callback with the authorized OAuth2 client.
52
*
53
* @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
54
* @param {getEventsCallback} callback The callback to call with the authorized
55
* client.
56
*/
57
function getNewToken(oauth2Client, callback) {
58
var authUrl = oauth2Client.generateAuthUrl({
59
access_type: 'offline',
60
scope: SCOPES
61
})
62
console.log('Authorize this app by visiting this url: ', authUrl)
63
var rl = readline.createInterface({
64
input: process.stdin,
65
output: process.stdout
66
})
67
rl.question('Enter the code from that page here: ', function (code) {
68
rl.close()
69
oauth2Client.getToken(code, function (err, token) {
70
if (err) {
71
console.log('Error while trying to retrieve access token', err)
72
return
73
}
74
oauth2Client.credentials = token
75
storeToken(token)
76
callback(oauth2Client)
77
})
78
})
79
}
80
81
/**
82
* Store token to disk be used in later program executions.
83
*
84
* @param {Object} token The token to store to disk.
85
*/
86
function storeToken(token) {
87
try {
88
fs.mkdirSync(TOKEN_DIR)
89
} catch (err) {
90
if (err.code != 'EEXIST') {
91
throw err
92
}
93
}
94
fs.writeFile(TOKEN_PATH, JSON.stringify(token))
95
console.log('Token stored to ' + TOKEN_PATH)
96
}
97
98
/**
99
* Lists the next 10 events on the user's primary calendar.
100
*
101
* @param {google.auth.OAuth2} auth An authorized OAuth2 client.
102
*/
103
function listEvents(auth) {
104
var calendar = google.calendar('v3')
105
calendar.events.list(
106
{
107
auth: auth,
108
calendarId: 'primary',
109
timeMin: new Date().toISOString(),
110
maxResults: 10,
111
singleEvents: true,
112
orderBy: 'startTime'
113
},
114
function (err, response) {
115
if (err) {
116
console.log('The API returned an error: ' + err)
117
return
118
}
119
var events = response.items
120
if (events.length == 0) {
121
console.log('No upcoming events found.')
122
} else {
123
console.log('Upcoming 10 events:')
124
for (var i = 0; i < events.length; i++) {
125
var event = events[i]
126
var start = event.start.dateTime || event.start.date
127
console.log('%s - %s', start, event.summary)
128
}
129
}
130
}
131
)
132
}

โดยได้ทำการเปลี่ยนแค่ SCOPES จาก

1
var SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']

เป็น

1
var SCOPES = ['https://www.googleapis.com/auth/calendar']

สำหรับเพิ่ม permission เอาไว้จัดการ เพิ่มหรือลบ event ได้ ทดลองสั่งรัน

1
node quickstart.js
1
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
2
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 แบบนี้

1
'use strict'
2
3
const google = require('googleapis')
4
const googleAuth = require('google-auth-library')
5
const calendar = google.calendar('v3')
6
7
const fs = require('fs')
8
9
const SCOPES = [process.env.SCOPES]
10
const TOKEN_DIR =
11
(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) + '/.credentials/'
12
const TOKEN_PATH = TOKEN_DIR + 'calendar-nodejs-quickstart.json'
13
14
module.exports = {
15
authorize: (callback) => {
16
fs.readFile('client_secret.json', function processClientSecrets(err, content) {
17
if (err) {
18
console.log('Error loading client secret file: ' + err)
19
return
20
}
21
22
let credentials = JSON.parse(content)
23
var clientSecret = credentials.installed.client_secret
24
var clientId = credentials.installed.client_id
25
var redirectUrl = credentials.installed.redirect_uris[0]
26
var auth = new googleAuth()
27
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl)
28
29
// Check if we have previously stored a token.
30
fs.readFile(TOKEN_PATH, function (err, token) {
31
if (err) {
32
return callback(err)
33
} else {
34
oauth2Client.credentials = JSON.parse(token)
35
return callback(null, oauth2Client)
36
}
37
})
38
})
39
},
40
41
getNewToken: (oauth2Client, callback) => {
42
var authUrl = oauth2Client.generateAuthUrl({
43
access_type: 'offline',
44
scope: SCOPES
45
})
46
console.log('Authorize this app by visiting this url: ', authUrl)
47
var rl = readline.createInterface({
48
input: process.stdin,
49
output: process.stdout
50
})
51
rl.question('Enter the code from that page here: ', function (code) {
52
rl.close()
53
oauth2Client.getToken(code, function (err, token) {
54
if (err) {
55
console.log('Error while trying to retrieve access token', err)
56
return
57
}
58
oauth2Client.credentials = token
59
storeToken(token)
60
callback(oauth2Client)
61
})
62
})
63
},
64
65
listEvents: (auth, callback) => {
66
calendar.events.list(
67
{
68
auth: auth,
69
calendarId: process.env.CALENDAR_ID,
70
timeMin: new Date().toISOString(),
71
maxResults: 50,
72
singleEvents: true,
73
orderBy: 'startTime'
74
},
75
(err, response) => {
76
if (err) {
77
return callback(err)
78
}
79
return callback(null, response)
80
}
81
)
82
},
83
84
createEvent: (auth, event, callback) => {
85
calendar.events.insert(
86
{
87
auth: auth,
88
calendarId: process.env.CALENDAR_ID,
89
resource: event
90
},
91
(err, event) => {
92
if (err) {
93
return callback('There was an error contacting the Calendar service: ' + err)
94
}
95
return callback(null, event.htmlLink)
96
}
97
)
98
}
99
}

ซึ่งเวลาเรียกใช้งานผ่านทาง express ผมก็แค่

1
const lib = require('./lib')
2
3
lib.authorize()
4
lib.listEvents()
5
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 ขึ้นมาดังนี้

1
'use strict'
2
3
const controller = require('./controller')
4
5
module.exports = [
6
{
7
method: 'GET',
8
path: '/',
9
config: controller.index
10
},
11
{
12
method: 'GET',
13
path: '/events',
14
config: controller.events
15
},
16
{
17
method: 'POST',
18
path: '/events',
19
config: controller.create
20
}
21
]
สำหรับ hapi js เล็กน้อย ในเรื่อง routing เราสามารถกำหนดได้ทั้ง handler: function(request, reply) {} หรือจะเป็น config: Object ก็ได้ครับ รายละเอียดเพิ่มเติม http://hapijs.com/tutorials/routing

ต่อมาผมสร้างไฟล์ controller.js ขึ้นมา

1
'use strict'
2
3
const validate = require('./validate')
4
const lib = require('./lib')
5
6
module.exports = {
7
index: {
8
handler: (request, reply) => {
9
reply({
10
message: 'Google Calendar API',
11
endpoint: {
12
listEvents: 'GET /events',
13
createEvent: 'POST /events'
14
}
15
})
16
}
17
},
18
19
create: {
20
validate: validate.create,
21
handler: (request, reply) => {
22
let payload = request.payload
23
24
let summary = payload.summary
25
let description = payload.description
26
let email = payload.email
27
let startDate = payload.startDate
28
let endDate = payload.endDate
29
30
lib.authorize((err, auth) => {
31
if (err) return reply(err)
32
33
let options = lib.eventBuilder(payload)
34
options.auth = auth
35
36
lib.createEvent(options, (err, result) => {
37
if (err) return reply(err)
38
39
return reply(result)
40
})
41
})
42
}
43
},
44
45
events: {
46
handler: (request, reply) => {
47
lib.authorize((err, auth) => {
48
if (err) return reply(err)
49
50
lib.listEvents(auth, (err, response) => {
51
if (err) return reply(err)
52
53
return reply(response)
54
})
55
})
56
}
57
}
58
}

ผมเพิ่ม validate.js เล็กน้อย เวลาที่ส่ง parameters สำหรับ POST method จะได้ไม่มีปัญหา

1
'use strict'
2
3
const Joi = require('joi')
4
5
module.exports = {
6
create: {
7
payload: {
8
email: Joi.string().email().required(),
9
title: Joi.string().required(),
10
description: Joi.string().required(),
11
startDate: Joi.string().required(),
12
endDate: Joi.string().required()
13
}
14
}
15
}

สุดท้ายไฟล์ server.js

1
'use strict'
2
3
const Hapi = require('hapi')
4
const server = new Hapi.Server()
5
const routes = require('./routes')
6
7
server.connection({
8
host: 'localhost',
9
port: 2345
10
})
11
12
server.route(routes)
13
14
server.start(() => {
15
console.log(`Server is running at ${server.info.uri}`)
16
})

ทดลองสั่งรัน server

1
node server.js

เข้าหน้าเว็บ http://localhost:2345/ เป็นอันเรียบร้อย

สรุป

ตัวอย่างทั้งหมดอันนี้ผมก็ศึกษาจาก Google Calendar API และลองทำเล่นๆเท่านั้น จะเห็นว่าไม่มีหน้า UI เนื่องจากผมกะเอาไปใช้งานร่วมกับคำสั่งพวก slash command ของ Slack หรือว่า bot service อื่นๆ ดูในอนาคตครับ

สุดท้าย Source Code ครับ

Authors
avatar

Chai Phonbopit

เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust

Related Posts