はじめに
LINE Messaging APIを使ってLINE botやLIFF(LINE Front-end Framework)のアプリを開発するための、最初の土台作りの部分は省略します。。。(他のいろいろなサイトで解説されているので)
LINE公式アカウントの作成とFirebaseプロジェクトの作成などは頑張ってください!
実現したい動作としては👇のような感じ
①リッチメニューを押す
②設定しておいたメッセージがユーザー側から送信される
③Fiirestoreになんらかの値を保存
環境
・言語はTypeScript
・WebhookサーバーはFirebase Cloud Functions
・Expressは使いません
Webhookとは?
わかりやすいサイトがあったので👇を参考に。ここの理解はとても大事です!
LINE公式アカウントに何らかのイベントが起こった場合に、別のサービスを行っているサーバーへデータを送信する(通知する)ことをwebhookと言います。
イベントっていうのは、「友だちに追加された」「ブロックされた」「メッセージを受信した」といったことを指しています。
このようなイベントが起こったことを、チャットボットのサーバーに通知してくれるのがwebhookです。
https://hokkaido-dc.com/digital/edward/linechatbptsample/
ここで言う「チャットボット用サーバー」が今回ではFirebase Cloud Functionsにあたります。
リッチメニューが押されたことをどう検知するか?
リッチメニューを押すとユーザーから何かしらのテキストが送信されます。
例えば「ぴえん🥺」と言うリッチメニューがあったとして、それを押すと「ぴえん🥺」がユーザー側から送信されます。
これが「リッチメニューによって送信されたメッセージ」なのか、「ユーザー自身が入力して送信されたメッセージ」なのか、どうやって判断すれば良いのか?
LINE API初心者だった頃は頭を悩ませました。
なのでLINE公式のQ&Aプラットフォームで質問してみることに。
すると一瞬で解決しました!!
・Messaging APIでリッチメニューを作成
・ポストバックアクションを使う
ここがポイントです
LINE APIのチャネルシークレットとチャネルアクセストークンをGoogle Cloud Secret Managerに設定
API キーなどの機密性の高い値は.env
に入れるべきではないようなので、Google Cloud Secret Managerを使用します。
LINE developerからチャネルシークレットとチャネルアクセストークンを確認しましょう。
$ firebase functions:config:set line.channel_secret="channelSecret"
$ firebase functions:config:set line.channel_access_token="channelAccessToken"
これで値がセットできたか確認
$ firebase functions:config:get
{
"line": {
"channel_secret": "channelSecret",
"channel_access_token": "channelAccessToken"
}
}
Cloud Functionsへデプロイ
Cloud Functionsのコードを書く
書き方は本当人によっていろいろだな〜っと感じました。。
あくまでこれも1例です。
import * as functions from "firebase-functions";
import * as line from "@line/bot-sdk";
import * as admin from 'firebase-admin';
admin.initializeApp();
const db = admin.firestore();
interface Config {
channelAccessToken: string;
channelSecret: string;
}
const config: Config = {
channelAccessToken: functions.config().line.channel_access_token,
channelSecret: functions.config().line.channel_secret
};
const client = new line.Client(config);
export const lineWebhook = functions
.region("asia-northeast1")
.https.onRequest(async (request, response) => {
const signature = request.get("x-line-signature");
if (!signature || !line.validateSignature(
request.rawBody, config.channelSecret, signature
)) {
throw new
line.SignatureValidationFailed(
"signature validation failedで失敗ですわ!",
signature
);
}
const res: line.WebhookEvent = request.body.events[0];
try {
await actionPien(res);
} catch (err) {
console.log(err);
}
});
const actionPien = async (event: line.WebhookEvent) => {
const userId = event.source.userId!;
if (event.type !== 'postback') {
return;
}
try {
const message: line.TextMessage[] = [
{
type: "text",
text: event.postback.data
},
{
type: "text",
text: "ぴえん"
}
];
client.replyMessage(event.replyToken, message);
return addPien(userId, event.timestamp);
} catch (error) {
console.error(JSON.stringify(error));
return Promise.resolve(null);
}
};
const addPien = async (userId: string, timestamp: number) => {
const pienDate = timestampToTime(timestamp);
const pienEvent = {
pienTime: pienDate,
};
return db.collection(`users/${userId}/piens`).add(pienEvent);
}
// 引数 timestamp の単位はミリ秒
const timestampToTime = (timestamp: number) => {
const date = new Date(timestamp);
const yyyy = `${date.getFullYear()}`;
// .slice(-2)で文字列中の末尾の2文字を取得する
// `0${date.getHoge()}`.slice(-2) と書くことで0埋めをする
const MM = `0${date.getMonth() + 1}`.slice(-2); // getMonth()の返り値は0が基点
const dd = `0${date.getDate()}`.slice(-2);
const HH = `0${date.getHours() + 9}`.slice(-2); // 日本時間になるよう+9h
const mm = `0${date.getMinutes()}`.slice(-2);
const ss = `0${date.getSeconds()}`.slice(-2);
return `${yyyy}/${MM}/${dd} ${HH}:${mm}:${ss}`;
}
一旦デプロイします
$ firebase deploy --only functions
デプロイした関数のURLをwebhook URLに登録
これでCloud Functions側は完了です!
リッチメニューをデプロイ
ポストバックアクションを使うにはリッチメニューを自分でアップロードしなくてはなりません。。。
初見ではまぁまぁむずいです
リッチメニュー画像を用意
色々やり方はあると思いますが、canvaからテンプレをゲットして画像をkeynoteとかGoogleスライドで当てはめるやり方が楽かな〜っと。
比率をピッタシにするのは大変ですからね〜・・・
リッチメニューをデプロイ
こちらを参考にしながらやってみてください。
初めてcurl
コマンドに触れる方は混乱すると思いますが、ターミナルにコマンド(めっちゃ長いけど)をコピペでぶち込んでEnter押すだけです!!
リッチメニューに変更を加える度に毎回するので、どこかにコピペして置いとくのがおすすめ。(richMenuId
も毎回変わるのでご注意を)
ちなみに私のリッチメニューはこんな感じ
ボタン3つのリッチメニューです。
{
"size":{
"width":2500,
"height":1686
},
"selected": true,
"name": "ぴえん",
"chatBarText": "メニュー",
"areas": [
{
"bounds": {
"x": 0,
"y": 0,
"width": 1635,
"height": 1686
},
"action": {
"type": "postback",
"data": "pien",
"displayText": "\ぴえんっ🥺/\nどんなできごとがありましたか?\nよければLINEでお返事ください😌💫"
}
},
{
"bounds": {
"x": 865,
"y": 0,
"width": 865,
"height": 843
},
"action": {
"type": "uri",
"label": "動画だぜ",
"uri": "https://www.youtube.com"
}
},
{
"bounds": {
"x": 865,
"y": 843,
"width": 865,
"height": 843
},
"action": {
"type": "uri",
"label": "Twitterだぜ",
"uri": "https://twitter.com"
}
}
]
}
\n
で改行できます
完成
これでリッチメニューの一番でかいボタンを押すと
①リッチメニューのdisplayText
に設定したメッセージがユーザーから送信される
②postbackを検知してbotがリッチメニューのdata
と「ぴえん」のメッセージを送信
③Firestoreに値が保存される。
参考
今後プログラムが大きなってきたときに、index.tsに集中せずファイルを分割するやり方でとても参考になりました!!!