การสร้าง Facebook Messenger ChatBot และ Python ตอนที่ 2

[ตอนที่ 1] [ตอนที่ 3] [ตอนที่ 4]


หมายเหตุ เนื่องจากความแตกต่างในรายละเอียดปลีกย่อยของการใช้คำสั่งบางคำสั่งระหว่างระบบปฏิบัติการ หากต้องให้รายละเอียดทุกระบบปฏิบัติการจะทำให้เนื้อหาเยิ่นเย้อเกินไป จึงขอใช้สภาพแวดล้อมของผู้เขียนคือ Linux


พฤติกรรมที่เราจะสร้างให้กับ Chatbot  ของเราคือการนำความที่เข้าส่งเข้ามาตอบกลับออกไปหาผู้ส่งหรือที่เรียกว่า Echo Server นั่นเอง ถือว่าเป็นพฤติกรรมที่ง่ายที่สุดและนิยมทำกันเป็นตัวอย่าง

1. สร้างสภาพแวดล้อมการทำงานด้วย VirtualEnv 


1.1 สร้าง Folder สำหรับทำงานขึ้นมาก่อน สมมุติให้ชื่อว่า fb-chatbot

mkdir fb-chatbot

1.2 สร้างสภาพแวดล้อมด้วยคำสั่ง

virtualenv fb-chatbot

ในขั้นตอนนี้ จะมีการสำเนาสิ่งที่จำเป็นในการทำงานจาก Python2.7 มาไว้ใน fb-chatbot เมื่อเข้าไปดุภายในจะเห็นโครงสร้างดังภาพ


1.3 เปิดใช้งาน virtualenv

cd fb-chatbot

ในระบบที่เป็น Linux สามารถใช้คำสั่ง

source bin/activate 

หรือ

bin/activate

สำหรับระบบปฏิบัติการอื่นโปรดอ่านขั้นตอนตามเอกสารนี้ https://virtualenv.pypa.io/en/stable/userguide/


2. ติดตั้งซอฟต์แวร์


pip install Flask gunicorn requests

  • Flask คือ Python Web Framework ที่เราจะใช้ในการสร้างซอฟต์แวร์
  • Gunicorn คือ Python WSGI HTTP Server  ซึ่งเราต้องนำมาใช้เพื่อให้ Flask สามารถทำงานบน Heroku ได้
  • requests คือ utility ใช้ในการส่งความต้องการไปยัง Facebook Messenger 

3. เรียนรู้ Flask และ Gunicorn สักนิด

เรามาทดลองสร้าง Web Application ง่าย ๆ ด้วย Flask กันดูนะครับ โดยผมจะลอกมาจากตัวอย่างของ Flask เลย

from flask import Flask,request
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run(host='')

เร่ิมต้นด้วยการกำหนดตัวแปร app ให้เป็น instance ของ Flask  โดย app จะทำหน้าที่ในการจับคู่ระหว่างความต้องการ (request) ในรูปแบบของ URL ที่ส่งเข้ามาจากผู้ใช้กับ Python Code เพื่อทำการประมวลผล

ในบรรทัด @app.route('/') ตีความได้ว่า request ที่ส่งเข้ามาใน URL ที่เขียนย่อด้วย "/" จะถูกส่งไปยังฟังก์ชั่น  hello_world()  ซึ่งทำหน้าที่เดียวคือ ส่งข้อความ "Hello World" กลับไปยังผู้ใช้

หมายเหตุ เครื่องหมาย @ที่วางไว้ข้างหน้า app นั้นหมายถึงการทำ decoration ให้กับฟังก์ชั่น route ด้วยฟังก์ชั่น hello_world()

ในบรรทัด app.run(host='') คือการสั่งให้ Flask เริ่มการทำงาน โดยจะมีเปิดพอร์ตสื่อสารเลขที่ 5000 (default port)  host='' มีความหมายเดียวกับ host="0.0.0.0" ซึ่งเป็นอนุญาตให้ผู้ใช้สามารถใช้คอมพิวเตอร์เครื่องอื่น (ที่ไม่ใช่เครื่องที่ Flask ทำงาน) ติดต่อเข้ามาได้

ครับ นี่คือหลักการทำงานแบบคร่าว ๆ ของ Flask  ครับ ให้ท่านทำการสำเนา code นี้แล้วบันทึกไว้ในไฟล์ชื่อ hello.py แล้วเรียกใช้งานด้วยคำสั่ง

python hello.py

ท่านควรจะได้ผลลัพธ์ตามภาพ ซึ่งหมายถึงท่านสร้าง Python Web Application  ด้วย Flask สำเร็จแล้ว




และหากท่านใช้ Web Browser เปิดดูด้วย URL นี้ http://<ip addressของเครื่่องที่ใช้งาน>:5000/



ที่นี้มาดูการใช้ประโยชน์จาก Gunicorn กันบ้าง ครับ ทำไมเราต้องพึง Gunicorn ก็เพราะว่า Heroku ต้องติดต่อ client และ service หลากหลาย (เขาเป็น Clound ) ดังนั้น Service ที่ทำงานบน Heroku ก็ควรจะรองรับการทำงานแบบนั้นด้วย ข้อจำกัดของ Flask หรือ DJango คือรองรับการเรียกใช้บริการ request by request  (เพราะมันคือ application) หากให้ Heroku ส่ง request ไปหา Flask หรือ DJango โดยตรง มันจะเกิดการรองานเกิดขึ้น ดังนั้นเราจึงต้องมีตัวกลาวคือ Gunicorn มารับหน้าเสื่อแทนนั้นเอง  การเรียกใช้ Gunicorn ให้พิมพ์คำสั่งดังนี้

gunicorn -w 1 -b 0.0.0.0:5000 hello:app


-w 1 หมายถึง สร้าง web process ขึ้นมา 1 process
-b 0.0.0.0:5000 หมายถึง อนุญาตให้เครื่องอื่นติดต่อเข้ามาผ่าน port เบอร์ 5000
hello:app หมายถึง Application ที่ทำงานคือ app ที่อยู่ใน hello.py

มาถึงตรงนี้ ท่านก็ได้ทราบวิธีการสร้าง Python Web Application ด้วย Flask และการเรียกใช้งานผ่าน Gunicorn แล้วนะครับ

4. สร้าง Echo Bot

Facebook Messenger ได้กำหนดวิธีการติดต่อกับ Chatbot ของเราโดยการส่ง HTTP Requests พร้อมกับข้อมูล เพื่อให้รองรับการทำงานร่วมกับ Facebook Messenger ได้ เราต้องมีการปรับปรุงชุดคำสั่งให้สอดคล้องกับที่ Facebook กำหนดมาดังนี้ (อ่านเพิ่มเติม https://developers.facebook.com/docs/graph-api/webhooks )

4.1 )  Facebook Messenger จะส่ง "GET" request เข้ามาเพื่อทำการตรวจสอบและยืนยันการมีตัวตน โดยข้อมูลที่ถูกส่งมาประกอบด้วย
  • hub.mode
  • hub.challenge
  • hub.verify_token

สิิ่งที่เราต้องทำคือ
1. ตรวจดูว่า hub.verify_token นั้นตรงกับที่เราได้กำหนดไว้เมื่อสมัครใช้งานกับ Facebook Messenger (จะกล่าวถึงต่อไป)

2. ทำการส่ง hub.challenge กลับไปให้ Facebook Messenger

จะใช้ชุดคำสั่งดังนี้

@app.route('/',methods=['GET'])
def verification_handle():
     verify_token = request.args.get('hub.verify_token','')
     challenge = request.args.get('hub.challenge', '') 
     if verify_token == 'xxxxxx' :
         return challenge
     else :
         return "Wrong validation"


หมายเหตุ :  'XXXXX'  คือ ข้อมูลที่เราต้องให้ไว้กับ Facebook ในขั้นตอนการสมัครใช้ Chatbot


4.2 )  เมื่อมีผู้ส่งข้อความเข้ามาทาง Facebook Messenger จะทำการส่งข้อมูลของผู้ส่ง มาให้ Chatbot ในรูปแบบของ POST request (อ่านเพิ่มเติม https://developers.facebook.com/docs/messenger-platform/webhook-reference) สิ่งที่เราต้องทำคือ

    4.2.1 รับข้อมูลแล้วทำการตอบรับกลับไปยัง Facebook Messenger ด้วยสถานะ 200 OK ตามมาตรฐานของ  HTTP Status ไม่เช่นนั้นทาง Facebook Messenger จะถือว่าการส่งข้อความครั้งนั้นล้มเหลว

     4.2.2 ทำการแยกแยะข้อมูลต่าง ๆ ประมวลผล  แล้วทำการส่งผลการทำงานของเราผ่านไปที่ Facebook Messenger โดยทำตามวิธีการ send request

ขอให้พิจารณาดูโครงสร้างข้อมูลที่ทาง Facebook ทำการส่งมาให้จะอยู่ในรูปแบบของ JSON

{
  "object":"page",
  "entry":[
    {
      "id":"PAGE_ID",
      "time":1458692752478,
      "messaging":[
        {
          "sender":{
            "id":"USER_ID"
          },
          "recipient":{
            "id":"PAGE_ID"
          },
          "message":{
            "text":"...."
          },
          ...
        }
      ]
    }
  ]
}   


ข้อมูลที่เราจะให้ความสนใจในตอนนี้คือ
sender .id คือ user id ของผู้ที่ส่งข้อความมาหา Chatbot
message.text คือ ข้อความที่ส่งเข้ามา


@app.route('/',methods=['POST'])
def incoming_message_handle():
    #get data from request
    payload = request.get_data()
    
    #turn payload to json
    json_data = json.loads(payload)

    #extract entry, entry is an array
    entry = json_data['entry']

    #extract messaging from entry, messaging is an array
    messaging = entry[0]["messaging"]

    @app.route('/',methods=['POST'])
def incoming_message_handle():
    #get data from request
    payload = request.get_data()
    
    #turn payload to json
    json_data = json.loads(payload)

    #extract entry, entry is an array
    entry = json_data['entry']

    #extract messaging from entry, messaging is an array
    messaging = entry[0]["messaging"]

    for item in messaging :
      if "message" in item and "text" in item["message"] :

        #extract sender from messaging
        sender = item["sender"]

        #extract incoming message from messaging
        msg = item["message"]

        #echo back to sender
        echo_to_sender(sender["id"],msg["text"].encode('unicode_escape'))


    #tell Facebook that every is alright
    return json.dumps({'success':True}),200,{'Content-Type':'application/json'}


def echo_to_sender(sender_id,msg_txt):
    # get this from Facebook manual
        req = requests.post(
      "https://graph.facebook.com/v2.6/me/messages",
      params = {"access_token":access_token},
      data = json.dumps({
          "recipient":sender_id,
          "message": msg_txt
         }), 
      headers = {'Content-Type':'application/json'} )



ค่อนข้างยาวหน่อย แต่ค่อย ๆ ทำความเข้าใจไปครับ ไม่ยาก ต่อไปเราก็ทำการประกอบ Code เข้าด้วยกันเพื่อให้กลายเป็น Web Application ตามแบบของ Flask


from flask import Flask,request
import requests
import json

app = Flask(__name__)

@app.route('/',methods=['GET'])
def verification_handle():
     verify_token = request.args.get('hub.verify_token','')
     challenge = request.args.get('hub.challenge', '') 
     if verify_token == 'xxxxxx' :
         return challenge
     else :
         return "Wrong validation"


@app.route('/',methods=['POST'])
def incoming_message_handle():
    #get data from request
    payload = request.get_data()
    
    #turn payload to json
    json_data = json.loads(payload)

    #extract entry, entry is an array
    entry = json_data['entry']

    #extract messaging from entry, messaging is an array
    messaging = entry[0]["messaging"]

@app.route('/',methods=['POST'])
def incoming_message_handle():
    #get data from request
    payload = request.get_data()
    
    #turn payload to json
    json_data = json.loads(payload)

    #extract entry, entry is an array
    entry = json_data['entry']

    #extract messaging from entry, messaging is an array
    messaging = entry[0]["messaging"]

    for item in messaging :
      if "message" in item and "text" in item["message"] :

        #extract sender from messaging
        sender = item["sender"]

        #extract incoming message from messaging
        msg = item["message"]

        #echo back to sender
        echo_to_sender(sender["id"],msg["text"].encode('unicode_escape'))

    #tell Facebook that every is alright
    return json.dumps({'success':True}),200,{'Content-Type':'application/json'}


def echo_to_sender(sender_id,msg_txt):
    # get this from Facebook manual
    req = requests.post(
      "https://graph.facebook.com/v2.6/me/messages",
      params = {"access_token":access_token},
      data = json.dumps({
          "recipient":sender_id,
          "message": msg_txt
         }), 
      headers = {'Content-Type':'application/json'} )



สรุปแล้ว ในตอนที่ 2 นีได้กล่าวถึงสิ่งที่เราต้องทำให้กับ Chatbot ของเราตามที่ Facebook Messenger ได้กำหนดไว้  มีรายละเอียดที่ต้องอ่านเพิ่มเติมพอสมควรเพื่อให้เข้าใจว่าทำอะไร ไปเพื่ออะไร  ในตอนต่อไปจะกล่าวถึงการนำเอาสิ่งที่เราในตอนนี้ไปวางไว้บน Heroku ครับ


[ตอนที่ 1] [ตอนที่ 3] [ตอนที่ 4]
Previous
Next Post »