เบื้องหลัง Live Coding แชทบอท Live Score ตั้งแต่ Zero จนเป็น Hero

Jirawatee
Jirawatee
May 28 · 7 min read

ผ่านไปแล้วกับงานรวมตัวนักพัฒนาไทยที่สนใจ LINE APIs กับงาน งานเปิด APIs ของ LINE และโชว์ Best practice จาก LINE Engineers ที่ใหญ่ที่สุดเท่าที่เคยจัดมาในไทย

โดย Session ที่ผมอาสามาโชว์ภายในงานก็คือ ซึ่งเป็นการเขียนโค้ดสดจาก features ต่างๆของ LINE, Firebase และ GCP มายำรวมกันเป็น chatbot ซึ่งผมและทีมก็คิดกันอยู่นานว่าจะทำบริการอะไรดี เพื่อให้เข้ากับสถานการณ์ช่วงนี้…การเมืองดีมั้ย?(ไม่น่าดี เดี๋ยวต้องไปนอนเขียนโค้ดในคุกแทน) หรือ BNK ดีมั้ยเพราะงานจับมือจัดที่เดียวกันวันเดียวกัน แต่สุดท้ายก็นี่ละกัน

UEFA Champions League รอบชิง 2019 ระหว่าง Liverpool และ Tottenham Hotspur

Chatbot UCL ตัวนี้หละ เข้าสถานการณ์ที่สุด เพราะอีก 6 วันข้างหน้าจะเตะกันละ เชื่อว่ามีคนรอดูหลายล้านคน แฟนบอลของสองทีมนี้ในไทยก็เยอะ จัดไปวัยรุ่น!

โดยภารกิจที่ผมจะทำในการ Live Coding ในครั้งนี้

ซึ่งภารกิจข้อสุดท้ายผมถือว่ามันสำคัญสำหรับการ live coding ครั้งนี้มากที่สุด นั่นก็คือการให้ chatbot ตัวนี้มี feature เกี่ยวกับการรายงานผลบอลที่ไม่เคยมีใครทำมาก่อน ถึงตรงนี้เรามาดูขั้นตอนการพัฒนา chatbot ตัวนี้กันสักหน่อย

  1. สร้าง LINE Official Account(Chatbot)
  2. สร้าง Rich Menu
  3. ออกแบบโครงสร้าง Database
  4. ติดตั้ง Cloud Functions for Firebase
  5. เพิ่ม Dependencies และเปิดการใช้งาน Vision API
  6. ตั้งค่าและเตรียมความพร้อม
  7. เตรียมฟังก์ชันการ Push, Reply และ Broadcast
  8. สร้าง Webhook เพื่อรายงานผลบอล และ Subscribe
  9. แจ้งเตือน Subscriber เมื่อผลบอลมีการเปลี่ยนแปลง
  10. แจ้งเตือนทุกคนเมื่อจบการแข่งขัน
  11. สร้าง LIFF app เพื่อส่ง Image กลับเข้าห้อง
  12. เปลี่ยน Text ธรรมดาให้กลายเป็น Flex Message

1. สร้าง LINE Official Account(Chatbot)

สำหรับหัวข้อนี้ ใครยังไม่เคยสร้าง LINE Chatbot มาก่อน ให้ทำตามบทความด้านล่างนี้ ส่วนใครเคยทำแล้วให้ข้ามไปข้อถัดไปได้เลย


2. สร้าง Rich Menu

Pain point ของคนสร้างรูป Rich menu ที่พบบ่อยๆคือมีทั้งเรื่องนามสกุลไฟล์ไม่ถูกต้อง หรือขนาดของรูป Rich menu ไม่ตรงตามที่ LINE กำหนด ซึ่งหลายๆคนอาจจะยังไม่รู้ว่ามันมีเครื่องมือในการสร้างรูป Rich menu ที่พัฒนาโดย LINE ประเทศไทยอยู่ มันมีชื่อว่า Rich Menu Maker

ซึ่งการใช้งานมันก็ง่ายมากๆแค่ 3 ขั้นตอน
2.1 เลือก Template ของ Rich menu(ปัจจุบันมีอยู่ 7 แบบ)
2.2 ใส่รูปไอคอน(ตกแต่งเพิ่มเติมได้)
2.3 Save ภาพออกมา

เมื่อได้ภาพ Rich menu ตามต้องการแล้ว ก็เข้าไปที่ LINE Official Account Manager จากนั้นเลือก account ที่เราได้สร้างไว้ แล้วไปที่เมนูชื่อ Rich menus จะเจอปุ่มให้กดสร้าง ก็กดซะ

โดยเริ่มจากให้เราระบุข้อมูลใน Menu settings ให้ครบ(หลายคนระบุไม่ครบ ทำให้กด save ไม่ได้)

จากนั้นก็เลือก template ให้ตรงกับภาพที่สร้างมา, อัพโหลดรูปที่ได้จาก Rich Menu Maker แล้วระบุ action เมื่อมีการคลิก จากตัวอย่างเมื่อกดเมนู A ผมจะให้ส่ง text เข้าห้องแชทว่า “subscribe” และเมื่อกดเมนู B จะส่งคำว่า “ผลบอลล่าสุด”

เสร็จแล้วกด save จากนั้นก็มาดูผลงานในตัว LINE Chatbot ของเราสักหน่อย

Rich menu มาแล้วจ้า

3. ออกแบบโครงสร้าง Database

ในงานวันนั้นผมชั่งใจอยู่นานว่าจะเลือกใช้ Firebase Realtime Database(RTDB) หรือ Cloud Firestore(เพราะ Firestore มันเจ๋งกว่า เลยอยากส่งเสริมให้คนมาใช้เยอะๆ) แต่สุดท้ายผมเลือก RTDB ด้วยเหตุผลที่คิดว่า เวลาสื่อสารให้ผู้ฟังผ่าน Firebase Console ตัว RTDB มันดูเข้าใจง่ายกว่ามากสำหรับมือใหม่ แต่ในบทความนี้จะมาแนะนำให้ทั้ง 2 ตัวเลยละกัน

Firebase Realtime Database
Cloud Firestore

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

เราจะใช้ Cloud Functions for Firebase มาจัดการทั้งการสร้าง Webhook และการรับ trigger จาก features อื่นๆของ Firebase ที่เราจะใช้ในโปรโจคนี้ โดยใครที่ไม่เคยใช้งาน LINE Messaging API ให้อ่านทำความเข้าใจเรื่อง Push, Reply และ Broadcast จากบทความด้านล่างนี้ด้วย ส่วนใครมีประสบการณ์ตรงนี้แล้วให้ข้ามไป

โปรเจคนี้ต้องใช้ Blaze plan เนื่องจากจะมีการ curl ตัว service ภายนอก GCP แต่ย้ำกันอีกหนว่า โควต้าฟรีทั้งหมดของ Spark plan จะมา top-on ตัว Blaze plan ดังนั้น เราจะต้องใช้โควต้าฟรีให้หมดเสียก่อน จึงจะมีการคำนวนการใช้งานเพื่อคิดค่าใช้จ่าย


5. เพิ่ม Dependencies และเปิดการใช้งาน Vision API

เมื่อซีตุ๊ปตัว Cloud Functions เสร็จละ ก็เปิดไฟล์ package.json ขึ้นมา เราจะทำการเพิ่ม dependencies เข้าไป 3 ตัวดังนี้

"dependencies": {
"firebase-admin": "^8.0.0",
"firebase-functions": "^2.3.1",

}

โดย request และ request-promise เราจะเอาไว้ curl ตัว Messaging API ส่วน Cloud Vision API จะเอาไว้สำหรับทำนายสิ่งที่อยู่ในรูปภาพซึ่งจะกล่าวถึงในขั้นตอนถัดๆไป

การจะใช้งาน Cloud Vision API ได้ จะมีเงื่อนไขให้ทำ 2 ข้อ
5.1 Enable Billing ที่ GCP
5.2 เปิดใช้งาน Cloud Vision API


6. ตั้งค่าและเตรียมความพร้อม

ถึงเวลาเปิดไฟล์ index.js ขึ้นมาเตรียมความพร้อมกันละ ซึ่งผมอยากแนะนำเคล็ดลับบางอย่างที่คุณอาจไม่เคยรู้มาก่อนในขั้นตอนนี้แถมไปด้วย

จากโค้ดด้านบน ผมจะขออธิบายตัวที่น่าสนใจดังนี้

  • เป็นการกำหนด location ที่ support ของ Cloud Functions ซึ่งในโปรเจคนี้เป็น asia-east2 หรือ Hong Kong เนื่องจากเป็น region ที่ใกล้ไทยมากที่สุดในขณะนี้ ดังนั้นจึงมีความเร็วในการ ping สูงที่สุด
  • เป็นการตั้งค่า ให้เหมาะสมกับระยะเวลาในการ execute แต่ละฟังก์ชันของเรา โดยค่า default จะเป็น 30s ซึ่งระยะเวลาการทำงานในแต่ละฟังก์ชันของผมมันไม่นาน ดังนั้นผมจึงตั้งไว้ที่ ให้จำไว้ว่าตรงนี้ตั้งมากยิ่งเปลืองทรัพยากร เพราะในแต่ละ request มันจะเข้าไปจองทรัพยากรไว้ ส่วน ใน instant นั้นผมตั้งไว้ที่ โดยค่า default จะเป็น 256MB

ในการเรียกใช้งานฟังก์ชันครั้งแรก ตัวแปรและการ Initial ต่างๆที่อยู่ใน global scope จะถูก execute ทั้งหมด ซึ่งอาจเป็นสาเหตุให้การเรียกฟังก์ชันครั้งแรกช้า หรือที่เราเรียกว่า Cold start time ดังนั้นเพื่อลดเวลาในจังหวะนี้ให้สั้นที่สุดหากมี dependencies หรือการ initial ที่ใช้เฉพาะฟังก์ชัน ให้ไปประกาศในระดับฟังก์ชันซะ


7. เตรียมฟังก์ชันการ Push, Reply และ Broadcast

ขั้นตอนนี้ให้เราเขียนฟังก์ชันในการส่งข้อความทั้ง Push, Reply และ Broadcast ของ Messaging API ทิ้งไว้เลย เพราะเราจะได้ใช้มันหลายครั้งในโปรเจคดังนี้

  • การส่งข้อความแบบ one-way communication ที่จะระบุผู้รับได้ทีละ 1 คน โดยมีการรับ params 3 ตัวคือ userId, ข้อความประเภท text เท่านั้นและ object ของ Quick Reply
  • การตอบกลับผู้ใช้แบบ two-ways communication(ฟรี) โดยมีการรับ params 2 ตัวคือ replyToken และ Payload ข้อความรูปแบบต่างๆใน LINE
  • การส่งข้อความแบบ one-way communication โดยส่งข้อความแบบยิงปืนนัดเดียวได้นกทุกตัว โดยมีการรับ param 1 ตัวคือข้อความที่เป็น text

8. สร้าง Webhook เพื่อรายงานผลบอล และ Subscribe

ความสามารถของ Cloud Functions for Firebase นอกจากจะรับ trigger จาก feature ต่างๆใน Firebase แล้ว มันก็ยังสามารถสร้าง Webhook API ให้เราแถมได้ https มาฟรีอีก ดังนั้นมาดูโครงสร้างของ Webhook ตัวนี้กัน

จากโครงสร้างฟังก์ชันด้านบน จะเห็นว่ามีการแยกประเภทของ event จาก LINE ที่วิ่งเข้ามาปะทะ Webhook ของเราออกเป็น 2 ประเภทหลักๆด้วยกันคือ และ โดยใน message จะจำแนกประเภทของข้อความเป็น และ ซึ่งใน text ก็ยังแยกเป็นกรณีที่ส่งมาว่า “subscribe” กับกรณีอื่นๆ

ถึงตรงนี้ผู้อ่านจะต้องมีความเข้าใจเรื่อง Webhook และ Payload ที่แนบมาตอน event จาก LINE เข้ามาปะทะ แนะนำให้อ่านบทความด้านล่างนี้ซะก่อน

ใครอ่านจบ หรือเข้าใจอยู่แล้ว จะรอไรหละ มาลงโค้ดตามลำดับตัวเลขในแต่ละบล๊อคกันเลย

[8.1] ตอบกลับผลบอลล่าสุด

กรณีที่ผู้ใช้พิมพ์ข้อความที่เป็น text ใดๆมา ให้เราดึงผลบอลล่าสุดและตอบกลับไปด้วย replyToken

[8.2] บอกวิธีการ Subscribe

เมื่อผู้ใช้กดปุ่ม Rich menu ทางด้านซ้าย ก็ให้ chatbot ตอบกลับผู้ใช้ว่าวิธีการ subscribe เป็นอย่างไร

[8.3] จัดการเรื่องรูปภาพ

หลังจากเราเชื้อเชิญให้เหล่าสาวกอัพโหลดรูปภาพที่มีโลโกทีมมา จุดนี้คือจุดสำคัญโดยเราจะเริ่มจากการไปดาวน์โหลด binary จาก LINE และอัพโหลดภาพขึ้น Cloud Storage for Firebase กันก่อน

...
if (event.message.type === '') {
// [8.3]
(event)
}
...

จากโค้ดด้านบน ผมจะบอกวิธีไปหา YOUR-BUCKET-NAME กัน โดยให้ทุกคนเข้าไปที่ Firebase Console แล้วเลือกเมนูชื่อ Storage จากนั้นดูรูปด้านล่างที่ขีดเส้นไว้ นั่นหละคือชื่อ bucket

ยัง…ยัง…[8.3] ยังไม่จบ หลังจากที่รูปเราขึ้นไปบน Cloud Storage แล้ว ด้วยการที่เราใช้ Cloud Functions เราจึงสามารถรับ trigger จากการที่มีไฟล์ถูก write เข้ามาได้ เราจึงสร้างจะสร้างฟังก์ชันใน Cloud Functions ใหม่ขึ้นมาเพื่อรับเหตุการณ์นี้ โดยจะเอารูปที่เพิ่งถูกอัพโหลดมาไปทำนายหาโลโกในรูปนั้น

[8.4] ตรวจสอบแฟนพันธุ์แท้

เมื่อผู้ใช้กดปุ่ม Quick Reply เพื่อเลือกทีมแล้ว จะมี event ที่เป็น postback วิ่งเข้ามาที่ Webhook ทันที เราก็จะตรวจสอบข้อมูลจาก postback ว่าทีมที่เลือกมีคำว่า liverpool หรือ tottenham หรือไม่ ถ้ามีเราก็จะทำการ write ข้อมูลไปที่ database และตอบกลับผู้ใช้ด้วยการ reply ถ้าไม่มีก็แซวซะหน่อย ป๊าาาดโถ่!

Firebase Realtime Database
Cloud Firestore

9. แจ้งเตือน Subscriber เมื่อผลบอลมีการเปลี่ยนแปลง

คราวนี้ลองจินตนาการว่า บอลคู่นี้เริ่มแข่ง แล้วมี score เกิดขึ้น เราก็จะให้ chatbot ดึงข้อมูลคนที่ subscribe ไว้แล้วมาส่งข้อความหา โดยฟังก์ชันที่เราจะสร้างใน Cloud Functions จะดักฟังการเปลี่ยนแปลงของ database นั่นเอง

Firebase Realtime Database
Cloud Firestore
เข้าประตูปุ๊บ ข้อความเข้ามาปั๊บ

10. แจ้งเตือนทุกคนเมื่อจบการแข่งขัน

คราวนี้ให้ลองจินตนาการต่อว่าบอลแข่งจบแล้ว ตัว chatbot ของเราก็จะรายงานผลให้กับทุกคนที่เป็นเพื่อนกับ chatbot ตัวนี้ว่าใครคือผู้ชนะ โดยเราจะสร้าง Cron job ง่ายๆผ่าน Cloud Scheduler เพื่อยิง Broadcast Message API ของ LINE

ถ้าอยากรู้ว่า Cloud Scheduler มันง่ายอย่างไรลองอ่านบทความนี้ดู

Firebase Realtime Database
Cloud Firestore

11. สร้าง LIFF app เพื่อส่ง Image กลับเข้าห้อง

เราจะสร้าง LIFF ขึ้นมาเพื่อช่วยให้ผู้ใช้ที่ไม่มีรูปตราสโมสร สามารถจะส่งรูปเข้าไปในห้องแชทเพื่อ subscribe ได้

โดยให้สร้าง HTML ขึ้นมาหนึ่งหน้า มีโลโกของทั้ง 2 สโมสร จากนั้นก็ import ตัว LIFF SDK และทำการ Initial LIFF app โดยในรูปทั้ง 2 หากคลิกจะมีการเรียกฟังก์ชัน sendMessage() ใน Javascript เพื่อไปส่งข้อความประเภท Image กลับไปยังห้องแชท

ซึ่งเมื่อสร้างเสร็จแล้ว ผมก็เลือก Firebase Hosting มา host ตัวเว็บนี้ไว้ เนื่องจากฟรีและได้ SSL ตาม requirement ของ LIFF พอดี หากใครยังไม่เคยใช้งาน Firebase Hosting มาก่อน จะบอกว่ามันง่ายสุดในสามโลก ลองอ่านบทความนี้ดูได้จ้า

และนี่คือตัวอย่างผลลัพธ์ที่ได้หลังจาก deploy (โค้ด copy จาก view source ได้เลย)

การส่งข้อความจาก LIFF กลับไปยังห้องแชทจะเป็นการส่งในนามบุคคล ซึ่งก็เหมือนกับผู้ใช้ส่งข้อความเอง

ถัดไปก็ถึงเวลาไปสร้าง LIFF app จริงๆละ ให้เข้าไปที่ LINE Developers Console จากนั้นก็เลือก Chatbot ที่ต้องการ แล้วเลือกเมนู LIFF จะเจอหน้าให้ลงทะเบียน LIFF ละ กดเพิ่มโลด

ในตัวอย่างผมเลือกขนาด compact คือแสดงผล 50% ในหน้าจอก็พอ
เมื่อ confirm แล้วเราจะได้ LIFF URL มา เสร็จละ ง่ายจุง

12. เปลี่ยน Text ธรรมดาให้กลายเป็น Flex Message

สำหรับขั้นตอนนี้ผมจะเปลี่ยนข้อความ text ที่ตอบกลับเวลาผู้ใช้ส่งข้อความว่า “subscribe” เข้ามา โดยจะใช้เครื่องมือที่ชื่อว่า LINE Bot Designer ซึ่งเป็นเครื่องมือในการออกแบบรูปแบบข้อความต่างๆใน LINE ซึ่งไม่ต้องเขียนโค้ดเลย มีทั้ง PC และ Mac สะดวกมากๆเพราะตอนสุดท้ายมันจะ generate ตัว JSON ให้เราไปใช้ในโค้ดได้เลย มาดูตัวอย่างกัน

ไม่ต้องเขียนโค้ดเลยแม้แต่บรรทัดเดียว

เสร็จแล้วก็เอา JSON ทางขวามือไปแทนที่บรรทัดที่ reply เป็น text แบบนี้ และอย่าลืมไป copy ตัว LIFF URL มาแทนที่ URL เมื่อคลิกปุ่มชื่อ LIFF หละ


เก็บตกจากงาน

Stack โดยรวมกว่าจะมาเป็น chatbot ตัวนี้ก็เป็นดังภาพนี้เลยครับ

และอย่างที่ทุกท่านที่ใช้ Cloud Functions ทราบ ว่าการ deploy จะต้องใช้เวลา 40–60s ซึ่งบนเวทีวันนั้นผมก็เลยถือโอกาสบอกนักพัฒนาในฮอลว่า โปรแกรมเมอร์อย่างพวกเรา นอกจากจะพูดไม่เก่งแล้ว ยังมีโอกาสเป็นง่อยตอนแก่อีก เพราะพวกเราต้องทำงานหน้าคอมนานๆ ไม่ค่อยได้ออกกำลังกาย ดังนั้นมาครับ เรามาวิดพื้นระหว่างรอ deploy กัน ว่าแล้วก็ อึ๊บ อึ๊บ

หลังจาก deploy เสร็จ ผมก็ปล่อย gimmick สุดท้ายของโชว์ผมออกไปโดยการขออาสาให้นักพัฒนาที่อยู่ด้านล่างขึ้นมาบนเวทีหนึ่งคน เพราะผมเตรียมของไว้ในกระเป๋าบนเวที ซึ่งมันก็คือ ทาดา เสื้อ Liverpool นั่นเอง และเพื่อเป็นการยืนยันว่า chatbot UCL ทำงานได้อย่างเที่ยงธรรม เราก็เลยให้เขาใส่เสื้อ Liverpool แล้วถ่ายรูปเขาซะเลย

ปรากฎว่า ถ่ายแค่ครั้งเดียว chatbot ผมก็ทำงานถูกต้องสำเร็จเลย จังหวะนั้นก็เฮกันไปสิคร้าบ และผมก็ขอถือโอกาสขอบคุณนักพัฒนาท่านนั้นที่อาสาขึ้นมาร่วมสนุกกันมา ณ โอกาสนี้ด้วยครับ


สรุป

สำหรับ Live Coding ในครั้งนี้ ผมได้ซ้อมกับทีมงานเกือบ 10 รอบ จนหาสูตรสำเร็จที่จะให้ทัน 45 นาที และเข้าใจง่ายที่สุดออกมาได้ ซึ่งเอาเข้าจริงคือมีข้อผิดพลาดแทบทุกรอบที่ซ้อม จนต้องเตรียมมุกไว้เผื่อ deploy ไม่ผ่าน “โค้ดที่ไม่มี bugs ก็เหมือนท้องฟ้าที่ไม่มีดาว” แต่ไม่ได้ใช้ เพราะวันจริงไม่รู้ทำไมมันไม่พลาดเลย อิอิ

สำหรับใครที่ตั้งตารอ source code อยู่ผมก็เอาขึ้น GitHub ให้เรียบร้อยครับ ไป clone กันโลด

หวังว่า Live Coding ครั้งนี้จะทำให้นักพัฒนาสนุกและได้ไอเดียกลับบ้านไปต่อยอดพัฒนาบริการดีๆ แปลกใหม่ และที่สำคัญคือ “ชีวิตนักพัฒนาอย่างพวกเรามันจะมีความหมายเมื่อเราได้สร้างอะไรสักอย่างเพื่อคนอื่น มาครับเรามาทำให้คนไทย 44 ล้านคนมีชีวิตที่ดีขึ้นไปด้วยกัน” แล้วพบกันใหม่ ราตรีสวัสดิ์นักพัฒนาชาวไทย

LINE Developers Thailand

Closing the distance. Our mission is to bring people, information and services closer together

Jirawatee

Written by

Jirawatee

Technology Evangelist at LINE Thailand / Google Developer Expert - Firebase

LINE Developers Thailand

Closing the distance. Our mission is to bring people, information and services closer together