12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

kintoneAdvent Calendar 2021

Day 9

【kintone×LINE Bot】餃子検定[非公式]を作ってみた

Last updated at Posted at 2021-12-08

はじめに

本格的に寒くていよいよ暖房を入れました、私です!
アドベントカレンダー = ネタ披露 という脳内変換がされたので、書きに来ました!

何を作ったのか

私は、餃子を食べることを「自主練」と呼んでいるのですが、今年は自主練が月1の時もあり、
「そんな自主練の頻度で、本当に餃子好きと言えるのか?」「ビジネス餃子ではないのか?」と周囲の方々からのありがたい手厳しいフィードバックを頂きまして、「私の餃子好きが分かるものを作るぞ!」ということでLINE BOTを作成してみました!

自主練はサボり気味ですが、
日々、餃子の知識をインプットしている私の作った最強の(?)餃子検定を受験して下さい。
みなさま、対戦よろしくお願いします!

デモ

まずは、何はともあれ触ってみると早いかと思います!
早速ですが、良かったらLINE BOTと友達になって餃子検定問題に回答して頂けると嬉しいです。
10問解答すると、自分の餃子レベルが分かります。
https://lin.ee/znPnwXn
M_gainfriends_qr.png

餃子検定

全体像

構成図

構成図.png

開発に必要なもの

  • 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から送信されたデータを受け取り、関数内のコードを実行していきます。

index.js
// 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);
      })
  }
}

②「餃子検定を受験する」を受け取った場合

「餃子検定を受験する」を受け取った場合

「餃子検定を受験する」文字列を受け取った場合は、

  1. event.message'message'
  2. event.message.type'text'
  3. 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を利用してデータを作成してから、コーディングすると簡単に実装することができます。
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 を忘れずに!

index.js
// 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を利用して、餃子検定(クイズ)アプリを作成しました。
一見、クソアプリに見えるかもしれませんが、
勉強や資格の問題を貯めておいて暇な時に使ったり、複数のメンバーで問題を作成し合って使う...
なんてことができたら、面白いかもしれませんね?

皆様も、良き餃子ライフで今年を締めくくりましょう!

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?