LoginSignup
4
2

【Linebot+GAS】単語の一問一答ができるボットの完成まで(1)

Last updated at Posted at 2024-05-05

作成した経緯

友人がITパスポートの勉強をしていると聞いたので、アプリではなくLinebotで一問一答を作成し、スプレッドシートで単語を管理して自分だけの一問一答ができるのではないかと考え…という建前の元、何か作りたかっただけ(笑)

使用技術

  • Google App Script
  • LINE Messaging API

機能一覧

まだ未実装のものもありますが、ざっと基本的なところまで

1. 問題出題

スプレッドシートから単語を取得して、その単語の意味を出題してくれる機能です。

IMG_0909.png

2. 選択肢

答えの選択肢を ダミー(2) + 正答(1) で出してくれる機能です。

IMG_0910.png

3. 答え

問題の答えとその参考URLを出してくれる機能です。

IMG_0911.png

4. 終了

問題の回答を終了する機能(?)これ、いらないな…

IMG_0912.png

5. ジャンル変更

出題される単語のジャンルを変更する機能。ITパスポートはストラテジ系, マネジメント系, テクノロジ系があったはずだからその変更に使います。まだ実装できていませんが^^;

6. リマインダー

定時でリマインドする機能。こちらもまだできていません^^;

各機能のコード

各機能をGAS(Google Apps Script)で作成したのでその説明

メッセージに対する分岐

Userが送ってきた各メッセージに対する分岐の部分。

var ACCESSTOKEN = 'YOUR_ACCESS_TOKEN';
const URL = 'YOUR_LINEBOT_REPLY_URL';
const PUSH_URL = 'YOUR_LINEBOT_PUSH_URL';
const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';
const SpreadsheetName = "問題用のスプレッドシート";
const ManegerSpreadsheetName = "管理用のスプレッドシート";


function doPost(e) {
  let data = JSON.parse(e.postData.contents)
  var event = data.events[0];
  let targetColumn = getColumn(event);
  switch (event.message.text) {
    case '出題':
      let answer = replyGame(event);
      insertData(answer, targetColumn);
      break;
    case '終了':
      quitReply(event, targetColumn);
      deleteData(targetColumn);
      break;
    case '答え':
      correctReply(event, targetColumn);
      break;
    case '増やす':
      let options = makeOption(event, "ごめん!その機能は今実装中だから待って!");
      UrlFetchApp.fetch(URL, options);
      break;
    case 'リマインド':
      let options2 = makeOption(event, "ごめん!その機能は今実装中だから待って!");
      UrlFetchApp.fetch(URL, options2);
      break;
    case '選択肢':
      choices(event, targetColumn);
      break;
    default:
      let options4 = makeOption(event, "メニューから言葉を選んでね!");
      UrlFetchApp.fetch(URL, options4);
      break;
  }
}

コードの解説

この2行はおまじない

let data = JSON.parse(e.postData.contents)
var event = data.events[0];

これは今回のLinebotの仕様上、登録されているユーザー識別列が必要になるので回収しているだけ

let targetColumn = getColumn(event);

event.message.textにUserのテキストメッセージが代入されるのでそれをSwitchで分岐

switch (event.message.text) {
    case '出題':
      let answer = replyGame(event);
      insertData(answer, targetColumn);
      
      (以下略)

問題出題機能

まずは使っている関数をざっと

先ほど紹介した分岐処理のdoPost

doPost
    case '出題':
      let answer = replyGame(event);
      insertData(answer, targetColumn);
      break;

スプレッドシートを読み込み、単語を一つ抽出して返答を作成するreplyGame

replyGame
function replyGame(event) {
  var Spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
  let sheet = Spreadsheet.getSheetByName(SpreadsheetName);
  let questionNumber = generateRandomNumArray(35, 1); // (※1)
  var question = sheet.getRange(questionNumber[0], 2).getValue();
  var answer = sheet.getRange(questionNumber[0], 1).getValue();
  let options = makeQuestionOption(event, question); // (※2)
  UrlFetchApp.fetch(URL, options);
  return answer
}

コードの解説

1. スプレッドシートを取得

  var Spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
  let sheet = Spreadsheet.getSheetByName(SpreadsheetName);

2. 問題の決定

generateRandomNumArrayでランダムな整数を一つ取得。今回は試験用として最大値の第1引数に35を渡しています(1~35までの数が取得可能)。ですが、本来なら単語の総数を代入。
スプシでは、1列目が単語、2列目が単語の意味になっているので、questionNumber行目の単語、単語の意味をそれぞれanswerquestionに代入。

  let questionNumber = generateRandomNumArray(35, 1); // (※1)
  var question = sheet.getRange(questionNumber[0], 2).getValue();
  var answer = sheet.getRange(questionNumber[0], 1).getValue();

(※1) ランダムな整数を配列にして返してくれるgenerateRandomNumArray

generateRandomNumArray
/** 
ランダム整数を生成して配列に格納するクラス
引き数1:最大値
引き数2:配列に格納するランダムな数字の数
**/
function generateRandomNumArray(maxNum, generateArrayLength) {
  let generateArray = []; //ランダム格納用配列
  let numberArray = []; // ランダム生成用配列

  //(2~maxNum)ランダム生成用配列を作成
  for (let i = 2; i <= maxNum; i++) {
    numberArray[i - 2] = i;
  }

  //ランダム格納用配列にランダム整数を格納
  for (let j = 0, len = numberArray.length; j < generateArrayLength; j++, len--) {
    let rndNum = Math.floor(Math.random() * len);
    generateArray.push(numberArray[rndNum]);
    numberArray[rndNum] = numberArray[len - 1];
  }

  return generateArray;
}

3. 返答の作成

返答用のoptionsを作成して2行目で返答

let options = makeQuestionOption(event, question); // (※2)
UrlFetchApp.fetch(URL, options);

(※2) 返答用のFlexMessageを作成するmakeQuestionOption

makeQuestionOption
//問題用のFlexMessageOption作成
function makeQuestionOption(event, message) {
  HEADERS = {
    "Content-Type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + ACCESSTOKEN,
  }
  postData = {
    "replyToken": event.replyToken,
    "messages" : [
      {
        'type':'flex',
        'altText':'問題',
        'contents': 
        {
          "type": "bubble",
          "body": {
            "type": "box",
            "layout": "vertical",
            "contents": [
              {
                "type": "text",
                "text": "問題",
                "weight": "bold",
                "color": "#4F6F52",
                "size": "sm",
                "align": "center"
              },
              {
                "type": "separator",
                "color": "#4F6F52",
              },
              {
                "type": "text",
                "text": message,
                "weight": "bold",
                "size": "lg",
                "margin": "md",
                "wrap": true
              }
            ],
            "backgroundColor": '#E8DFCA'
          },
          "styles": {
            "footer": {
              "separator": true
            }
          }
        }
      }
    ]
  }
  let options = {
    "headers": HEADERS,
    "method": "post",
    "payload": JSON.stringify(postData),
  };
  return options;
}

"type": "box""layout": "vertical"で垂直レイアウト方向を定義。
"type": "text"でテキストコンポーネントを作成
"type": "separator"で分割線を作成
wrapプロパティをtrueにすることでテキストを折り返し表示指定
FlexMessageのコンポーネントについてより知りたい方はこちら
FlexMessageのレイアウトについてより知りたい方はこちら

4. 回答履歴の保存

insertDataによって出題した単語(answer)をスプシに保存。(これは回答を出力するときに使用します)
具体的には、管理用スプシにおける解答Userの列を引数であるcolumnで特定済みなので、あとはその最終行targetRowanswerを書き込む

insertData
//管理用シートの最終行に回答状況を登録する
function insertData(value = '', column) {
  var Spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
  let sheet = Spreadsheet.getSheetByName(ManegerSpreadsheetName);
  const targetRow = sheet.getRange(1, column).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow() + 1;
  sheet.getRange(targetRow, column).setValue(value);
}

次回に続く

参考サイト

4
2
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
4
2