👻

LINEのイベントを仕分けてチャネルトークへ送信してみる

2023/09/27に公開

はじめに

鉄血鉄血...
みなさんは鉄血していますか?僕はしています。

さて、以前書いた記事の続きのような感じなのですが、最近の開発で「webhookで受け取ったイベントごとに処理を分けたい」というニーズが生まれました。
具体的には、イベントによってチャネルトークへ送るもの、送らないものを区別する必要が出てきたんですね。

通常であればイベントを処理する関数の中で分岐させればいいだけなので何も難しいことはないのですが、今回はチャネルトークへ送る必要があるので、イベントを処理する関数よりさらに外側での処理が必要になります。
このような処理が必要になる理由は、LINEから送られてくるリクエストの仕様によるものなんです。

LINEから送られてくるリクエストの仕様

LINEから送られてくるリクエストは、複数のイベントがまとまった状態で来ることがあります。
ほとんど同じタイミングで送信されたイベントは、req.body.eventsの配列の中に一緒に格納されて送られてくるんですね。

チャネルトークへ送る場合はLINEから送られてきたreq全体を模倣しなければならないため、区別するべきイベントが複数混在した状態だと扱いづらいわけです。

解決策

ではどうするかというと、チャネルトークのwebhookへリクエストを送る前にeventを仕分けてしまえばいいんです。
今回はfirestoreでLINEのuserIdと紐づけたflagという変数をおいて、bool値で振り分けを管理してみます。

実装

まず、チャネルトークへ送信するイベント(true)としないイベント(false)を格納するための配列をそれぞれ用意しておいて、振り分け用の関数を作って格納していきます。

const trueEvents = []
const falseEvents = []

await Promise.all(req.body.events.map(async (event) => {
    const userId = event.source.userId
    const docRef = firestore.doc(firestore.db, 'users', userId)
    const userData = await firestore.getDoc(docRef).then(doc => doc.data())
    const flag = userData?.flag || false
    
    if(qaflag){
      trueEvents.push(event)
      console.log('trueEvents:', trueEvents)
    }
    else{
      falseEvents.push(event)
    }
}))

あとはこれをexpressの関数にブチ込んでいきます。
今回はtrueのイベントとfalseのイベントを処理する関数を別で用意しました。

index.js
import express from 'express'
import axios from 'axios'
import bodyParser from 'body-parser'
import { firestore } from './firestore.js'


const app = express()


app.use(bodyParser.json())


app.post('/webhook', async (req, res) => {
    const { method, url, headers, body } = req
    console.log('Event Details:', JSON.stringify(req.body.events, null, 2))

    const trueEvents = []
    const falseEvents = []

    await Promise.all(req.body.events.map(async (event) => {
        const userId = event.source.userId
        const docRef = firestore.doc(firestore.db, 'users', userId)
        const userData = await firestore.getDoc(docRef).then(doc => doc.data())
        const flag = userData?.flag || false
    
        switch (flag){
            case true:
                trueEvents.push(event)
                break
            case false:
                falseEvents.push(event)
                break
            default:
                break
        }
    }
    ))

    body.events = trueEvents

    try {
        if(trueEvents.length !== 0){
            const { 'host': _, ...filteredHeaders } = headers

            const axiosHeaders = {
                ...filteredHeaders,
                'content-type': 'application/json; charset=utf-8'
            }

            console.log('Data to be forwarded:', JSON.stringify(body, null, 2))

            const response = await axios({
                method: method,
                url: 'https://example.com/webhook',
                headers: axiosHeaders,
                data: body,
            })

            console.log('Forwarded Data:', response.data)
            console.log('HTTP Status Code:', response.status)

            await Promise.all(trueEvents.map(handleTrueEvent))
        }
        
        if(falseEvents.length !== 0) await Promise.all(falseEvents.map(handleFalseEvent))

        res.status(200).send('Success')

    } catch (error) {
        console.error('Error:', error.message)
        res.status(500).send('Failed to forward data')
    }
})

実装終わり

あとがき

当初はなかなか難しい実装になるかなと思ったのですが、"本質"を理解していれば意外と簡単でした。
あぶね〜笑。

Discussion