สร้าง LINE Bot ด้วย Messaging API และ Cloud Functions for Firebase

ยุคนี้หลายคนคงรู้จัก Bot หรือ Chatbot บนแพลตฟอร์มของ LINE กันแล้ว เพราะทาง LINE ได้เปิด Messaging API ให้เราใช้งานมาตั้งแต่ปี 2016 และมีนักพัฒนาในไทยสนใจสร้าง Bot เพิ่มขึ้นเรื่อยๆ

ในการพัฒนา LINE Bot เราจำเป็นต้องมี Webhook ที่เป็น SSL หรือ HTTPS ซึ่งหลายบทความที่เคยอ่านมา ก็จะใช้ Heroku และ ngrok ซะส่วนใหญ่ แต่บทความนี้ผมจะเลือกใช้ Cloud Functions for Firebase มาสร้าง เพราะ…ผมชอบ เอ้ย! เพราะสะดวกในการเชื่อมต่อ database ทั้ง Cloud Firestore หรือ Realtime Database และที่สำคัญคือเชื่อมั่นได้กับ Google infrastructure ที่พร้อม auto scale เมื่อ Bot เรามีผู้ใช้เพิ่มมากขึ้น

ขั้นตอนในการพัฒนา

  1. สร้าง Provider และ Channel
  2. ติดตั้ง Cloud Functions for Firebase
  3. พัฒนาฟังก์ชันการตอบกลับอัตโนมัติ (Reply)
  4. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:1 (Push)
  5. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:Many (Multicast)
  6. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:All (Broadcast)
  7. ตัวอย่างการคำนวนโควต้าข้อความที่ส่งออกจากการ Reply + Push
  8. แนะนำการตั้ง Cron Job แบบฟรีๆ (End credit)

1. สร้าง Provider และ Channel

สำหรับใครที่ยังไม่เคยสร้าง Provider และ Channel มาก่อน แนะนำให้อ่านบทความปฐมบทด้านล่างนี้ แต่ถ้าใครเคยละ ให้ข้ามไปข้อ 2 ได้เลยจ้า


2. ติดตั้ง Cloud Functions for Firebase

2.1 สร้างโปรเจคใน Firebase

ขั้นตอนนี้ ถ้าใครมีโปรเจคกับ Firebase อยู่แล้วให้ข้ามขั้นตอนนี้ไป

ส่วนใครที่ยังไม่เคยมีหรือต้องกาสร้างใหม่ให้ไปที่ Firebase Console จากนั้นกด add project ระบุรายละเอียด และกด Creat project

เมื่อกดเข้าไปในโปรเจคที่เราสร้าง สิ่งที่แรกที่ต้องทำคือ…ให้ไปเปลี่ยนแพลนจาก Spark เป็นเป็น Blaze(Pay as you go) ก่อน เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google จำเป็นจะต้องใช้แพลนนี้(เราจะต้องไปเรียก Messaging API ของ LINE)

เมื่อใช้แพลน Blaze แล้ว โควต้าฟรีจากแพลน Spark ก็สมทบให้เราเหมือนเดิม

2.2 ติดตั้ง Node.js และ npm

ขั้นตอนนี้ใครลงทั้ง 2 ตัวแล้วให้ข้ามไป หรือตรวจสอบให้แน่ใจอีกครั้งผ่าน command line

node --version // แนะนำ v6.14.0 และสูงกว่า
npm --version

สำหรับใครที่ยังไม่ได้ลง หรือไม่พบเลขเวอร์ชัน ให้ไปที่ https://nodejs.org ดาวน์โหลดและติดตั้ง Node.js ตามแต่ละ OS ที่เราใช้งานให้เรียบร้อย (การติดตั้ง Node.js จะได้ npm มาด้วยอัตโนมัติ)

2.3 ติดตั้ง Firebase CLI

เนื่องด้วยการ deploy และ Test ตัว Cloud Functions for Firebase ทั้งหมดต้องทำผ่าน Firebase CLI ดังนั้นเอา npm ที่ได้มา ติดตั้งโลด

npm install -g firebase-tools
// กรณีติดปัญหา permission ให้ sudo แล้วใส่ password แบบด้านล่างนี้
// sudo npm install -g firebase-tools

ตรวจสอบสักหน่อยว่าเราติดตั้ง Firebase CLI เรียบร้อยด้วยคำสั่ง

firebase --version

2.4 Initial โปรเจค

ให้ run คำส่ัง login โดยจะมี browser เด้งมาให้เรา login ก็ให้เรา login ด้วย account ที่เราใช้สร้างโปรเจคใน Firebase

firebase login

จากนั้นสร้าง directory ว่างๆมาสักอัน(ตัวอย่าง directory ผมชื่อ bot) แล้วให้ shell เข้าไปในนั้นซะ

mkdir bot
cd bot

จากนั้นเริ่มการ init ตัว Cloud Functions for Firebase แบบที่แท้ทรูได้

firebase init functions

จะเจอหน้าตาใน terminal แบบนี้ ให้เลือกโปรเจคที่เราต้องการใช้งาน(กรณีมีหลายโปรเจค) จากนั้นกด enter เบาๆ

จากนั้นจะมีคำถามมาอีก 3 คำถามคือ

  • จะใช้ Javascript หรือ TypeScript (ตัวอย่างผมใช้ javascript)
  • จะใช้ ESLint เพื่อตรวจสอบ bug และ code style ไหม (option)
  • ลง dependencies ทันทีเลยไหม (จัดไปอย่าให้เสีย)

ซึ่งเมื่อตอบคำถามครบ 3 ข้อ ก็ถือว่าเสร็จขั้นตอน Initial ละ โดย Structure ของโปรเจคที่เราได้จะมีหน้าตาแบบนี้

หลักๆคือเราจะเขียน code ผ่าน index.js เนี่ยหละ

แถม

การอัพเดททั้ง Firebase CLI และ firebase-functions SDK นั้นถือเป็นเรื่องสำคัญ ทั้งการใช้งาน feature ใหม่และการแก้ bug ดังนั้นเราจะควรหมั่นอัพเดททั้งคู่ให้เป็นปัจจุบันอยู่เสมอ และก่อนจะ run คำสั่งด้านล่างนี้ อย่าลืม shell ไปที่ directory ชื่อ functions/ ก่อนนะจ๊ะ

npm install firebase-functions@latest firebase-admin@latest --save
npm install -g firebase-tools

3. พัฒนาฟังก์ชันการตอบกลับอัตโนมัติ (Reply)

ขั้นตอนนี้เราจะพัฒนาระบบให้ตอบสนองกับผู้ใช้ เมื่อผู้ใช้ได้ส่งข้อความมาให้ Bot ก็จะมี Message event วิ่งเข้ามาที่ Webhook พร้อมกับก้อน Object ซึ่งภายในนั้นจะมี replyToken ที่ไว้สำหรับการตอบกลับได้ทั้งแบบ 1:1, group และ room

เริ่มต้นเปิดไฟล์ index.js ขึ้นมา ภารกิจแรกของเราคือ การสร้าง URL ที่มี HTTPS ขึ้นมา ด้วย code ตัวอย่างด้านล่างนี้

  • LineBot คือชื่อ path ที่ต่อจาก URL ของ Cloud Functions
  • https.onRequest() เป็นการสร้าง Web API ของ Cloud Functions for Firebase

ต่อไปก็ deploy โดยการ shell ไปที่ directory ชื่อ functions/ แล้ว run คำสั่งนี้

firebase deploy --only functions

รอ deploy สัก 10–20 วินาที เราจะได้ URL คืนมาใน terminal เลย ซึ่งมี HTTPS อยู่ และ path แรกก็ชื่อ LineBot ด้วย ซึ่งหากไม่ได้ copy ไว้ ก็ยังสามารถเข้าไปที่ Firebase Console เมนูชื่อ Functions จะเจอชื่อ Function และ URL ใน Dashboard

ทดสอบเอา URL มาเปิดซะหน่อย จ๊ะเอ๋ Hello Wolrd!

เมื่อได้ URL มาอย่างสมใจปอง ก็เอา URL นี้ไปกรอกใน LINE Developer Console ในช่องของ Webhook URL ที่เราได้ปล่อยว่างไปในขั้นตอนที่ 1 จากนั้นกด Update และ Verify ตามลำดับ

เอาหละ ก่อนที่เราจะเขียนโค้ดเพิ่มเติม เรามาศึกษาโครงสร้างของ object ที่เมื่อผู้ใช้ส่ง text หา Bot ในกรณีที่เป็น 1:1 กันก่อน

จากโครงสร้างด้านบน จะมีจุดที่ต้องสนใจ 3 จุดเพื่อนำไป coding ดังนี้

  • replyToken: Token สำหรับการตอบกลับ สามารถใช้ได้ครั้งเดียว
  • message->type: ประเภทของข้อความ เช่น text, sticker, image และอื่นๆ
  • message->text: ข้อความจากผู้ใช้

รายละเอียดสำหรับการพัฒนา

// HTTP Request
POST
https://api.line.me/v2/bot/message/reply
// Headers
Content-Type: application/json
Authorization: Bearer (CHANNEL ACCESS TOKEN จากขั้นตอนที่ 1)
// Request Body
replyToken: xxx
messages:
(Array ของ Message object สูงสุดได้ 5 object)
// Message object ประเภท text
type: text
text: ข้อความยาวไม่เกิน 2,000 ตัวอักษร และใส่ emoji ที่เป็น unicode ได้

เนื่องจากเราจะต้องมีการ curl ตัว Messaging API ดังนั้นให้เราไปเพิ่ม dependencies ชื่อ request และ request-promise ในไฟล์ package.json ก่อนเป็นอันดับแรก

"dependencies": {
"firebase-admin": "~7.3.0",
"firebase-functions": "^2.2.1",
"request": "^2.88.0",
"request-promise": "^4.2.4"

}

กลับมาที่ไฟล์ index.js ให้เรา import ตัว dependency เข้ามา

const functions = require('firebase-functions');
const request = require('request-promise');

มือปืนลงมือได้ โดยอ้างอิงจากสเปคที่ได้มา เขียนในรูปแบบ Node.js โลด ซึ่งจากโค้ดด้านล่างผมจะเช็คก่อนว่า message->type เป็น text หรือไม่ ถ้าไม่ใช่ text ตัว Bot ก็จะไม่โต้ตอบใดๆกลับไป แต่หากเป็น text ก็จะตอบข้อความเดียวกับที่ผู้ใช้พิมพ์มากลับไป หรือที่เขาเรียกกันว่า Echo Bot นั่นเอง

อย่าลืมเปลี่ยน xxxxx เป็น Channel Access Token จากขั้นตอนที่ 1 ถ้าเรียบร้อยละ จะรอรัยอะ deploy ยาวๆไป

firebase deploy --only functions

ทดสอบคุยกับตัวเองหน่อยซิ

Response จากการ reply จะเป็นค่าว่าง และมี status = 200 (กรณีไม่มีปัญหา)

4. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:1 (Push)

ขั้นตอนนี้เราจะพัฒนาระบบส่งข้อความไปหาผู้ใช้แบบ 1 shot ต่อ 1 คน โดยการส่งจะต้องระบุ User ID ของผู้ใช้คนนั้นๆ และสำหรับตัวอย่างนี้ผมจะไปดึงข้อมูลสภาพอากาศจาก OpenWeatherMap ด้วยพิกัดจากรหัสไปรษณี ดังนั้นให้ทุกคนไปสมัครสมาชิกแล้ว copy ตัว API Key ออกมา

รายละเอียดสำหรับการพัฒนา

// OpenWeatherMap HTTP Request
GET https://api.openweathermap.org/data/2.5/weather?units=metric&type=accurate&zip=10330,th&appid=(API KEY)
// LINE HTTP Request
POST
https://api.line.me/v2/bot/message/push
// Headers
Content-Type: application/json
Authorization: Bearer (CHANNEL ACCESS TOKEN จากขั้นตอนที่ 1)
// Request Body
to: xxx (User ID)
messages:
(Array ของ Message object สูงสุดได้ 5 object)
// Message object ประเภท text
type: text
text: ข้อความยาวไม่เกิน 2,000 ตัวอักษร และใส่ emoji ที่เป็น unicode ได้

การได้มาซึ่ง User ID ปกติจะเก็บมาจาก 2 เหตุการณ์

  • Follow event ผู้ใช้เพิ่ม Bot เราเป็นเพื่อน
  • Message event ผู้ใช้ส่งข้อความ

ตัว userId จะอยู่ภายใน object ชื่อ source ที่มากับ event ซึ่งจะมีลักษณะประมาณนี้

"source": {
"type": "user",
"userId": "U4af4980629..."
}

ให้เราแอบเก็บ userId นี้ไว้ในฐานข้อมูลกรณีที่ต้องการส่งข้อความหาผู้ใช้ในภายโอกาสต่อไป

อย่าลืมแก้ค่า x, y, z จากตัวอย่างด้วย

  • xxxxx ให้แทนด้วย Channel Access Token จากขั้นตอนที่ 1
  • yyyyy ให้แทนด้วย API Key จาก OpenWeatherMap
  • zzzzz ให้แทนที่ด้วย User ID ของผู้ใช้เป้าหมาย

เสร็จแล้วก็ deploy โลด

firebase deploy --only functions

เมื่อ deploy ไปรอบได้จะได้ URL ใหม่ที่ต่อท้ายด้วย /LineBotPush ให้เอา URL ดังกล่าวไป request ซะ แล้วรอดูผลลัพธ์

Response จากการ pushจะเป็นค่าว่าง และมี status = 200 (กรณีไม่มีปัญหา)

5. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:Many (Multicast)

ขั้นตอนนี้เราจะพัฒนาระบบส่งข้อความไปหาผู้ใช้แบบ shot เดียวหลายคน สูงสุด 150 คน โดยการส่งจะต้องระบุ User ID ของกลุ่มผู้ใช้เป้าหมายลงใน array

รายละเอียดสำหรับการพัฒนา

// LINE HTTP Request
POST
https://api.line.me/v2/bot/message/multicast
// Headers
Content-Type: application/json
Authorization: Bearer (CHANNEL ACCESS TOKEN จากขั้นตอนที่ 1)
// Request Body
to: [xxx, yyy, zzz] (Array ของ User ID สูงสุด 150 userId)
messages:
(Array ของ Message object สูงสุดได้ 5 object)
// Message object ประเภท text
type: text
text: ข้อความยาวไม่เกิน 2,000 ตัวอักษร และใส่ emoji ที่เป็น unicode ได้

การได้มาซึ่ง User ID ก็เหมือนกับในข้อที่ 4 เด๊ะ จากนั้นก็มาลงโค้ดกัน โดยตัวอย่างนี้ผมจะรับค่า query string ที่ชื่อว่า text แล้วตรวจสอบว่ามีข้อความส่งมาจริง จากนั้นจึงจะนำข้อความนั้นไปส่งแบบ multicast ให้ผู้ใช้ 2 คนพร้อมกัน ซึ่งจะได้ response กลับมาเป็น JSON

อย่าลืมแก้ค่า x, y, z จากตัวอย่างด้วย

  • xxxxx ให้แทนด้วย Channel Access Token จากขั้นตอนที่ 1
  • yyyyy และ zzzzzให้แทนด้วย User ID ของผู้ใช้เป้าหมาย

เสร็จแล้วก็ deploy โลด

firebase deploy --only functions

เมื่อ deploy เรียบร้อยแล้วจะได้ URL ใหม่ที่ต่อท้ายด้วย /LineBotMulticast ให้เอา URL ดังกล่าวไป request ซะ แล้วรอดูผลลัพธ์ได้เลย

Response จากการ multicast จะเป็นค่าว่าง และมี status = 200 (กรณีไม่มีปัญหา)

6. พัฒนาฟังก์ชันการส่งข้อความแบบ 1:All (Broadcast)

ขั้นตอนนี้เราจะพัฒนาระบบส่งข้อความไปหาผู้ใช้แบบ shot เดียวโดนทุกคน โดยไม่ต้องระบุ User ID ของกลุ่มผู้ใช้เป้าหมายเลย สะด๊วก สะดวก

หมายเหตุ: เฉพาะ LINE Official Account ที่สามารถใช้งาน Broadcast ได้

รายละเอียดสำหรับการพัฒนา

// LINE HTTP Request
POST
https://api.line.me/v2/bot/message/broadcast
// Headers
Content-Type: application/json
Authorization: Bearer (CHANNEL ACCESS TOKEN จากขั้นตอนที่ 1)
// Request Body
messages:
(Array ของ Message object สูงสุดได้ 5 object)
// Message object ประเภท text
type: text
text: ข้อความยาวไม่เกิน 2,000 ตัวอักษร และใส่ emoji ที่เป็น unicode ได้

โดยตัวอย่างนี้ผมจะรับค่า query string ที่ชื่อว่า text แล้วตรวจสอบว่ามีข้อความส่งมาจริง จากนั้นจึงจะนำข้อความนั้นไปส่งแบบ broadcastให้ผู้ใช้ทุกคนพร้อมกัน ซึ่งจะได้ response กลับมาเป็น JSON

อย่าลืมเปลี่ยน xxxxx เป็น Channel Access Token จากขั้นตอนที่ 1 ถ้าเรียบร้อยละ จะรอรัยอะ deploy ยาวๆไป

firebase deploy --only functions

เมื่อ deploy เรียบร้อยแล้วจะได้ URL ใหม่ที่ต่อท้ายด้วย /LineBotBroadcast ให้เอา URL ดังกล่าวไป request ซะ แล้วรอดูผลลัพธ์ได้เลย

Response จากการ broadcast จะเป็นค่าว่าง และมี status = 200 (กรณีไม่มีปัญหา)

7. ตัวอย่างการคำนวนโควต้าข้อความที่ส่งออกจากการ Reply + Push

เอาหละหลังจากเรารู้จักทั้งการ Reply และการ Push แล้ว เรามาลองดูสถานการณ์จริงกันหน่อยว่าทาง LINE จะคำนวนโควต้าข้อความที่ส่งออก(one-way-communication) กันอย่างไร มาดูกัน

จากรูปด้านบนมีลำดับเหตุการณ์ดังนี้

  1. กค้า: กด SNAP & SHOP
  2. Bot: ส่งข้อความแจ้งให้ลูกค้าส่งรูปสินค้าเข้ามา (Reply)
  3. ลูกค้า: อัพโหลดรูปสินค้าให้
  4. Bot: บอกให้รอซักครู่ (Reply)
  5. Bot: แสดงผลลัพธ์การค้นหา (Push)

ถึงเวลาคำนวนละว่าเหตุการณ์ตัวอย่างนี้ จะนับโควต้าข้อความที่ส่งออกเท่าไร
ซึ่งคำตอบก็คือ

นับแค่ 1 ข้อความเท่านั้นคร้าบ จุดนี้ถ้าใครเข้าใจเรื่อง Reply + Push แล้ว ทาง LINE จะนับ 1 แค่ข้อความสุดท้าย(“ผลลัพธ์การค้นหา”) เท่านั้น เนื่องจากข้อความสุดท้ายที่ส่งออกมาไม่มี replyToken เพื่อใช้ในการตอบกลับนั่นเอง

จากตัวอย่างนี้มันจะมีโอกาส ฟรี เลยได้ไหม คำตอบคือ ได้ แค่ไม่ให้ Bot ตอบว่า “รอสักครู่นะคะ” ทุกอย่างก็ไม่มีการคำนวนโควต้าแล้ว ว้าววว!!!

หมายเหตุ: ถ้าเหตุการแชท 1-on-1 นี้เกิดขึ้นบน CMS จะไม่มีค่าใช้จ่ายใดๆทิ้งสิ้น

8. แนะนำการตั้ง Cron Job แบบฟรีๆ

ขั้นตอนนี้ผมจะขอแนะนำวิธีการตั้งเวลาให้ Bot ทำงานตามที่เรากำหนด…ขอแนะนำให้รู้จัก Cron-Job.org บริการที่จะทำให้เราตั้งเวลา curl ตัว API ได้อัตโนมัติและ FREE(ทีม Firebase ยังแนะนำ)

เข้าไปที่เว็บ https://cron-job.org แล้วสมัครสมาชิกให้เรียบร้อย จากนั้นเข้าไปที่เมนู Members -> Cronjobs -> Create cronjob

ระบุ API ที่ต้องการให้ไป curl ได้เลย หาก API ที่เตรียมมาต้องมีการทำ HTTP authentication ก็ใส่ username และ password ได้ ถัดมาก็ตั้งเวลาละ จากตัวอย่างผมตั้งให้ curl ทุกวันตอน 1 ทุ่ม นอกจากนี้ยังสามารถส่งอีเมลกรณีที่ Cronjob fail และบันทึก response ได้อีกด้วย

คราวนี้ก็ลองนึก use case กับ Bot ของเราดูว่า จะตั้งเวลาให้ Bot ไปหาข้อมูลอะไรมาให้เราบ้าง เช่น ค่าน้ำมัน, ราคาทอง, อัตราแลกเปลี่ยน และอื่นๆอีกล้านแปด

ของดีและฟรีมีอยู่จริงบนโลก ชอบมะ ถ้าชอบก็ให้แม่มาขอเสะ เอ้ย ถ้าชอบก็ใช้ซะ!


สรุป

Source code ฉบับสมบูรณ์ทั้ง Reply, Push, Multicast และ Broadcast ของบทความนี้สามารถ clone ไปเล่นกันได้ที่

สำหรับบทความนี้ก็เหมาะมากสำหรับผู้เริ่มต้นที่สนใจพัฒนา Chatbot บนแพลตฟอร์มของ LINE ซึ่งมีผู้ใช้ในไทยกว่า 44 ล้านคน หลังจากนี้ผมจะปล่อยบทความที่เป็น series ของ Messaging API ออกมาเรื่อยๆ ขอให้รอติดตามชมกัน ก่อนจากไปวันนี้ขอฝากคำคมไว้สักหน่อย…มีด…คมมะ…ตึ่งโป๊ะ ไปเล่นที่บ้านไป๊…เอาจริงๆ “การแพ้ที่น่ากลัวที่สุด ก็คือการแพ้ใจตัวเอง” ราตรีสวัสดิ์พี่น้องชาวไทย