iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
1

查詢口罩數量 Chatbot

今天我們來完成透過Cahtbot 輸入經緯度搜尋附近藥局口罩數量的功能
https://ithelp.ithome.com.tw/upload/images/20200925/20108281k58sgTVpc1.png

首先要處理來自Line的 Location Webhook Event

{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "message",
      "mode": "active",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      },
      "message": {
        "id": "325708",
        "type": "location",
        "title": "my location",
        "address": "〒150-0002 東京都渋谷区渋谷2丁目21−1",
        "latitude": 35.65910807942215,
        "longitude": 139.70372892916203
      }
    }
  ]
}

首先修改index.ts

import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import { WebhookEvent, validateSignature } from "@line/bot-sdk"
const intercept = require('azure-function-log-intercept');

import { LINE } from "./config"
import * as Dispatcher from './Chatbot/Dispatcher'

const lineWebhook: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    intercept(context);// console.log works now!

    const signature: string = req.headers["x-line-signature"]
    if (validateSignature(JSON.stringify(req.body), LINE.channelSecret, signature)) {
        const events = req.body.events as Array<WebhookEvent>
        for (const event of events) {
            await Dispatcher.eventDispatcher(event)
        }
    }

    context.res = { status: 200, body: 'ok' };
};

export default lineWebhook;

我們新增一支Dispatcher.ts程式來解析Webhook Event,並且根據文字,座標等不同的輸入進行不同處理

import { WebhookEvent, EventMessage } from "@line/bot-sdk"
import * as lineService from "./lineService"
import * as workflow from "./Workflow"

export const eventDispatcher = async(event: WebhookEvent) => {

    let replyToken: string
    let userId: string
    switch (event.type) {
        case "follow":
            const replyMessage = "歡迎加入口罩查詢,你可以輸入定位地點查詢附近健保特約機構口罩剩餘數量"
            replyToken = event.replyToken
            await lineService.replyMessage(replyMessage, replyToken)
            break;

        case "message":
            const message: EventMessage = event.message
            replyToken = event.replyToken
            userId = event.source.userId
            await messageDispatcher(message, replyToken, userId)
            break

        default:
            break
    }
}

const messageDispatcher = async(message: EventMessage, replyToken: string, userId: string) => {
    switch (message.type) {
        case "text":
            const phrase = message.text
             workflow.textUnderstanding(phrase, replyToken)
            break

        case "location":
            console.log('location')
            const GPS = {
                lat: message.latitude,
                lon: message.longitude
            }
            await workflow.findMaskStore(GPS, userId) 
            break

        default:
            break
    }
}

口罩剩餘數量公開資料

想做到上述功能的Chatbot 我們可以使用政府公開資料集-健保特約機構口罩剩餘數量明細清單來取得藥局口罩剩餘數量的資料
https://ithelp.ithome.com.tw/upload/images/20200924/20108281Zq1JglIopp.png

使用postman取得資料測試:
https://ithelp.ithome.com.tw/upload/images/20200924/20108281f7N5PLhUPZ.png

再使用Google Map API轉換經緯度(但這要收費/images/emoticon/emoticon04.gif)

這邊要感謝口罩供需資訊平台 共筆kiang提供轉好經緯度的資料:https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json

使用postman取得資料測試:
https://ithelp.ithome.com.tw/upload/images/20200925/20108281Ql45NA2cn0.jpg

處理定位-計算經緯度距離

我們新增一支Workflow.ts程式來計算最接近用戶的藥局資料

import * as lineService from "./lineService"
import axios from "axios";

export const findMaskStore = async (GPS: any, userId: string) => {

    let nearMaskStore = []
    const maskSnapshot = await axios.get('https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json')
    let maskData = maskSnapshot.data.features
    
    for (let element of maskData) {
        const storeGPS = element.geometry.coordinates

        let R = 6371 // Radius of the earth in km
        let dLon = (storeGPS[0] - GPS.lon) * (Math.PI / 180)
        let dLat = (storeGPS[1] - GPS.lat) * (Math.PI / 180)  // deg2rad below

        let a =
            Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos((GPS.lat) * (Math.PI / 180)) * Math.cos((GPS.lat) * (Math.PI / 180)) *
            Math.sin(dLon / 2) * Math.sin(dLon / 2)

        let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
        let d = R * c // Distance in km
        if (d < 0.4) {
            nearMaskStore.push(element)
        }
    }

    for (const store of nearMaskStore) {
        const maskMessage = lineService.toMaskMessage(store)
        lineService.pushMessage(maskMessage, userId)
    }
}

找出距離輸入經緯度最接近的幾組資料推播給使用者,推播方式可參考Day [9] Azure Functions-Line Chatbot實作(ㄧ)
並且為了方便轉換口罩查詢結果的回應訊息我們在lineService.ts程式中新增一隻function

export function toMaskMessage(nearMaskStore: any) {
    const maskMessage = 
           `${nearMaskStore.properties.name}\n` +
            `${nearMaskStore.properties.phone}\n` +
            `${nearMaskStore.properties.address}\n` +
            `成人口罩:${nearMaskStore.properties.mask_adult}\n` +
            `兒童口罩:${nearMaskStore.properties.mask_child}`
    return maskMessage
}

修改後記得部署Azure Functions就完成嘍
完成後的成品:


上一篇
Day [9] Azure Functions-Line Chatbot實作(ㄧ)
下一篇
Day [11] Why Azure Cache for Redis ?
系列文
白眼狼的30天Azure跳槽計畫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言