はじめに
本格的に寒くていよいよ暖房を入れました、私です!
アドベントカレンダー = ネタ披露
という脳内変換がされたので、書きに来ました!
何を作ったのか
私は、餃子を食べることを「自主練」と呼んでいるのですが、今年は自主練が月1の時もあり、
「そんな自主練の頻度で、本当に餃子好きと言えるのか?」「ビジネス餃子ではないのか?」と周囲の方々からのありがたい手厳しいフィードバックを頂きまして、「私の餃子好きが分かるものを作るぞ!」ということでLINE BOTを作成してみました!
自主練はサボり気味ですが、
日々、餃子の知識をインプットしている私の作った最強の(?)餃子検定を受験して下さい。
みなさま、対戦よろしくお願いします!
デモ
まずは、何はともあれ触ってみると早いかと思います!
早速ですが、良かったらLINE BOTと友達になって餃子検定問題に回答して頂けると嬉しいです。
10問解答すると、自分の餃子レベルが分かります。
https://lin.ee/znPnwXn
全体像
構成図
開発に必要なもの
- LINEアカウント
- Messaging APIの設定
- Herokuアカウント
- デプロイ先のServerに利用する
- kintoneアプリ
- 餃子検定問題[非公式]
- 問題、選択肢、解答を管理するアプリ
- 餃子検定ランク
- 正解数に合わせた餃子ランク、メッセージを管理するアプリ
- 餃子検定挑戦者
- 問題と回答を管理するアプリ
- 餃子検定問題[非公式]
- 餃子に関する問題
- 地味に問題作るのは時間がかかる...
ざっくり解説
1. LINE
BOTを作成する
- LINE Developer Consoleにログイン
- プロバイダーやMessaging APIを使用したチャネルを登録
- ※後で使う:
チャネルアクセストークン
- ※後で使う:
チャネルシークレット
- ※後で更新する:
Webhook URL
- ※後で使う:
リッチメニューを作成する
今回はLINE Official Account Manager 画面から作成しました。
画面上で、
- 表示期間
- メニューで表示したい画像
- メニューをクリックした時のアクション
を設定すれば完成。
2. kintone
各種アプリの作成と、REST APIで利用したいためトークンを発行しておく。
- kintoneアプリ
- 餃子検定問題集[非公式]
- 餃子検定ランク
- 餃子検定挑戦者
3. コード解説
①LINEからデータを受け取る
ユーザーがLINE上でアクションをすると、LINEのPlatformがアクションを検知し、設定したWebhook URLにデータが送信されます。データを受け取ったサーバー(今回はHeroku)は、handleEvent関数の引数(event)にLINEから送信されたデータを受け取り、関数内のコードを実行していきます。
// webhookのhandler設定
app.post('/callback', line.middleware(config), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});
async function handleEvent(event) {
let reply_msg;
switch (event.type){
// **************************
// メッセージイベント受信時に実行
// **************************
case "message":
reply_msg = await func.messageFunc(event);
console.log(reply_msg)
break;
// **************************
// 問題の返信(postback)イベント受信時に実行
// **************************
case "postback":
reply_msg = await func.postbackFunc(event);
break;
}
if (reply_msg !== undefined) {
return client
.replyMessage(event.replyToken, reply_msg)
.catch((err) => {
console.error(err);
})
}
}
②「餃子検定を受験する」を受け取った場合
「餃子検定を受験する」文字列を受け取った場合は、
-
event.message
が'message'
-
event.message.type
が'text'
-
event.message.text
が'餃子検定を受験する'
と判断されて、最終的に、getGyozaQuizFunc(関数)を実行します。
// **************************
// メッセージの内容毎に返信を変える
// **************************
async function messageFunc(event) {
let message;
switch (event.message.type) {
case 'text':
message = await messageTextFunc(event);
break;
case 'image':
case 'sticker':
message = {
type: 'sticker',
packageId: conf.package_id,
stickerId: conf.stickers[Math.floor(Math.random() * conf.stickers.length)]
};
break;
}
return message;
}
// **************************
// メッセージのテキストを判断して返信内容を変更する
// **************************
async function messageTextFunc(event) {
let message;
switch (event.message.text) {
case ('餃子検定を受験する'):
message = await getGyozaQuizFunc(event);
break;
case ('おすすめの餃子'):
console.log('おすすめの餃子');
message = { type: 'text', text: 'おすすめのお店を紹介します!' + conf.gyoza_stores[Math.floor(Math.random() * conf.gyoza_stores.length)] };
break;
//省略
//:
//:
default:
console.log(event);
message = { type: 'text', text: event.message.text };
break;
}
return message;
}
getGyozaQuizFunc関数は、kintoneの餃子検定問題アプリからランダムに問題を1問取得、
kintoneの餃子検定挑戦者アプリにデータを登録する仕組みになっています。
// **************************
// 餃子問題を取得する関数
// **************************
async function getGyozaQuizFunc(event) {
const min = 1;
const max = 10;
// ランダムで1問取得
const result = await kintone_client.record.getRecord({
app: kintoneGyozaQuizAppId,
id: Math.floor(Math.random() * (max + 1 - min)) + min
});
const current_user_data = await kintone_user_client.record.getRecords({
app: kintoneGyozaUserAppId,
query: `user_id="${event.source.userId}" order by $id desc limit 1`,
});
// ログインしているユーザーのクイズ数を確認
// 最新のレコードが0,10問の時は新規レコード,10問以内は更新
if (current_user_data.records[0].length === 0 || current_user_data.records[0].table.value.length === max) {
const postUser = await postGyozaQuizChallengerRecord(event, result.record);
} else {
let params_obj = [];
current_user_data.records[0].table.value.forEach(element => {
let table_id = {
id: element.id
};
params_obj.push(table_id);
});
const postUser = await updateGyozaQuizChallengerRecord(event, result.record, current_user_data.records[0].$id.value, params_obj);
}
const correct_arry = [];
const correct_radio = result.record.answer.value;
result.record.table.value.forEach(sub_table => {
let val = (correct_radio === sub_table.value.select_radio.value) ? '正解' : '-';
correct_arry.push(val);
});
let message = {
type: "flex",
altText: "解答を表示",
contents: {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "問題",
"weight": "bold",
"size": "xl"
},
{
"type": "text",
"text": result.record.question.value,
"size": "md",
"wrap": true
},
{
"type": "button",
"action": {
"type": "postback",
"label": "A:" + result.record.table.value[0].value.select_answer.value,
"data": correct_arry[0]
},
"height": "sm",
"margin": "sm",
"style": "primary",
"color": "#87ceeb"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "B:" + result.record.table.value[1].value.select_answer.value,
"data": correct_arry[1]
},
"height": "sm",
"margin": "sm",
"style": "primary",
"color": "#87ceeb"
},
{
"type": "button",
"action": {
"type": "postback",
"label": "C:" + result.record.table.value[2].value.select_answer.value,
"data": correct_arry[2]
},
"height": "sm",
"margin": "sm",
"style": "primary",
"color": "#87ceeb"
}
]
}
}
}
return message;
}
💡ポイント
Botの返信メッセージをmessage変数に入れていますが、今回のようにFLEX MESSAGEを利用したい場合は、FLEX MESSAGE SIMULATORを利用してデータを作成してから、コーディングすると簡単に実装することができます。
③Botの返信メッセージをreturnする
最終的にreply_msg
変数に、kintnoeから取得した問題データが代入され、Botの返信メッセージを送信する仕組みです。
if (reply_msg !== undefined) {
return client
.replyMessage(event.replyToken, reply_msg)
.catch((err) => {
console.error(err);
})
}
4. Heroku
最終的に、Herokuにデプロイする。
デプロイの詳細手順は、公式ドキュメントを参考にすると良いです!
Herokuでサンプルボットを作成する
デプロイが終わったら、LINE Developer ConsoleのWebhook URL
をデプロイした
https://~~~.herokuapp.com/callback
に修正する。
※今回は、Webhookのエンドポイントを/callbackとしているため、 /callback
を忘れずに!
// webhookのhandler設定
app.post('/callback', line.middleware(config), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});
ソースコード
全ての解説はしませんでしたが、全体がどのような実装になっているのか気になる方は、
GitHubで管理しているので、参考にしてみて下さい。
今後
- Lambdaに移行する
- 問題10問を重複しないようにしたい(現状はランダムのため重複してしまう)
- 「結果をシェアする」 アクションを実装?
- APIリクエストしすぎ問題
- 10問解き終わったら画像を返す
自分の中でも課題がまだあるので、時間を見て追加・改修して行けたら良いな〜
終わりに
これで本物の「餃子好き」と認めてもらえること間違いなしですね!?
問題も回答も自分で作成したので、当たり前の結果ですが...(笑)
ということで、餃子検定に出題して欲しい問題を大募集中です。
今回は、kintoneとLINE Botを利用して、餃子検定(クイズ)アプリを作成しました。
一見、クソアプリに見えるかもしれませんが、
勉強や資格の問題を貯めておいて暇な時に使ったり、複数のメンバーで問題を作成し合って使う...
なんてことができたら、面白いかもしれませんね?
皆様も、良き餃子ライフで今年を締めくくりましょう!