LINEチャットボット(3択クイズ➀)

LINEBOT

以下の点に注意してから作業を始めてください!

  • パソコンで作業をする
  • ブラウザの自動翻訳機能をオフにする

スプレッドシートの準備

1.ログイン

https://www.google.comを開き、ページ右上からログインします。

2.スプレッドシートを開く

ページ右上のをクリックし、メニューに表示されるスプレッシートをクリックします。

新しいスプレッドシートを作成の下にある、空白をクリックします。

3.スプレッシートの設定

スプレッドシート名シート名を変更します。

変更するには上側にある無題のスプレッシートをクリックし、自分の好きな名前に書き換えます。下側にあるシート名はダブルクリックをしてクイズに書き換えます。

左下にある+をクリックし、もう1枚スプレッドシートを作成します。出来たら同じようにダブルクリックをして、logに書き換えます。

4.クイズの準備

クイズの問題や3択、正解・不正解用の返信などを入力します。

下側のタブ(シート)がクイズに選択されている事を確認します。

  • B列2行目からクイズ内容
  • C列2行目から正解の選択肢
  • D列2行目から正解以外の選択肢1
  • E列2行目から正解以外の選択肢2
  • F列2行目から正解時のメッセージ
  • G列2行目から不正解時のメッセージ

logのページには何も記入しません。

日付を文字列として扱いたいときは先頭にシングルコーテーション「’」をつけましょう。

表計算ソフトでは日付や時間をシリアル値で扱います。解説はこちら。

Excelの日付や時刻の実態は「シリアル値」、1日を1として管理する
Excelの日付データは、ちょっと特殊なデータだ。「3/1」「3-1」「3月1日「令和4年3月1日」「2022年3月1日」など、どの形式で入力しても、日付データ「2022/3/1」としてセルに保存される。「シリアル値」という数値を使って管理...

Apps Scriptにプログラミング+公開設定

1.Apps Scriptを開く

ページ内の上側にある拡張機能をクリックし、下に表示されたApps Scriptをクリックします。

2.プログラムを書きこむ

元々書いてあるプログラムはすべて消し、下にあるプログラムをコピーして貼り付けます。チャネルアクセストークンスプレッドシートIDは自分の物を入れます。

*チャネルアクセストークンを忘れた場合はこちらを参照
*スプレッドシートIDを忘れた場合はこちらを参照

// LINE API エンドポイントの URL
const REPLY_API_URL = 'https://api.line.me/v2/bot/message/reply';
const PUSH_API_URL = 'https://api.line.me/v2/bot/message/push';
const PROFILE_API_URL = 'https://api.line.me/v2/bot/profile/';

// チャネルアクセストークン
const CHANNEL_ACCESS_TOKEN = '自分のトークン';

// スプレッドシートID
const SPREADSHEET_ID = '自分のシートID';

/**
 * Webhookからのリクエストを処理する
 * @param {Object} e - イベントオブジェクト
 */
function doPost(e) {
    const event = getEventFromLineWebhook(e);
    if (!event || !event.replyToken) return; // イベントが無効な場合は終了

    const userId = event.source.userId;
    const userProfile = getUserProfile(userId); // ユーザープロフィールを取得
    const eventType = event.type;
    const messageText = getMessageTextFromEvent(event); // メッセージ本文を取得

    // スプレッドシートを開く
    const spreadsheet = SpreadsheetApp.openById(SPREADSHEET_ID);
    const logSheet = spreadsheet.getSheetByName('log');
    const quizSheet = spreadsheet.getSheetByName('クイズ');

    let responseMessages = [];

    // イベントタイプに応じた処理
    if (eventType === 'postback') {
        // クイズの解答を処理
        responseMessages = handleQuizAnswer(event, quizSheet);
    } else if (messageText) {
        // メッセージに応じた応答を生成
        responseMessages = generateResponseMessages(messageText, quizSheet);
    }

    // 応答メッセージを送信
    replyMessages(event.replyToken, responseMessages);

    // ログを記録
    logSheet.appendRow([
        new Date(),
        userId,
        userProfile.displayName,
        eventType,
        event.postback ? event.postback.data : messageText,
        responseMessages.map(msg => msg.text).join('')
    ]);
}

/**
 * Line Webhookからイベントオブジェクトを取得する
 * @param {Object} e - イベントオブジェクト
 * @returns {Object|null} イベントオブジェクト、または null
 */
function getEventFromLineWebhook(e) {
    const events = JSON.parse(e.postData.contents).events;
    if (!events || events.length === 0) return null;
    return events[0];
}

/**
 * イベントからメッセージ本文を取得する
 * @param {Object} event - イベントオブジェクト
 * @returns {string|null} メッセージ本文、または null
 */
function getMessageTextFromEvent(event) {
    if (event.message && event.message.type === 'text') {
        return event.message.text;
    }
    return null;
}

/**
 * ユーザープロフィールを取得する
 * @param {string} userId - ユーザーID
 * @returns {Object} ユーザープロフィール
 */
function getUserProfile(userId) {
    const url = `${PROFILE_API_URL}${userId}`;
    const response = UrlFetchApp.fetch(url, {
        headers: { Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}` }
    });
    return JSON.parse(response);
}

/**
 * クイズの解答を処理する
 * @param {Object} event - イベントオブジェクト
 * @param {Object} quizSheet - クイズシート
 * @returns {Array} 応答メッセージの配列
 */
function handleQuizAnswer(event, quizSheet) {
    const answer = event.postback.data.slice(0, 1); // 'C4' -> 'C'
    const rowNumber = Number(event.postback.data.slice(1)); // 'C4' -> 4

    const answerMessage = answer === 'C'
        ? quizSheet.getRange(`F${rowNumber}`).getValue() // 正解メッセージ
        : quizSheet.getRange(`G${rowNumber}`).getValue(); // 不正解メッセージ

    return [{ type: 'text', text: answerMessage }];
}

/**
 * メッセージに応じた応答メッセージを生成する
 * @param {string} messageText - メッセージ本文
 * @param {Object} quizSheet - クイズシート
 * @returns {Array} 応答メッセージの配列
 */
function generateResponseMessages(messageText, quizSheet) {
    if (messageText.match(/確認/)) {
        return [{ type: 'text', text: 'クイズが出来ます。' }];
    } else if (messageText.match(/クイズ/)) {
        return generateQuizMessages(quizSheet);
    } else {
        return [{ type: 'text', text: '認識できませんでした、「クイズ」と入力してください。' }];
    }
}

/**
 * クイズメッセージを生成する
 * @param {Object} quizSheet - クイズシート
 * @returns {Array} クイズメッセージの配列
 */
function generateQuizMessages(quizSheet) {
    const lastRow = quizSheet.getLastRow();
    const randomRow = getRandomNumber(2, lastRow - 1); // 2行目から最終行までの範囲でランダムな行番号を取得
    const question = quizSheet.getRange(`B${randomRow}`).getValue(); // クイズ問題
    const choices = shuffleArray(['C', 'D', 'E']).map((col, i) => ({
        type: 'postback',
        label: quizSheet.getRange(`${col}${randomRow}`).getValue(),
        data: `${col}${randomRow}`,
        displayText: quizSheet.getRange(`${col}${randomRow}`).getValue()
    }));

    return [
        {
            type: 'template',
            altText: 'postback',
            template: {
                type: 'buttons',
                thumbnailImageUrl: 'https://illustmint.com/wp-content/uploads/2019/10/hyoujou_question_163-768x768.png',
                imageAspectRatio: 'rectangle',
                imageSize: 'cover',
                imageBackgroundColor: '#FFFFFF',
                title: 'クイズ',
                text: question,
                defaultAction: {
                    type: 'uri',
                    label: 'View detail',
                    uri: 'https://nksg.net/sit/lab/wiki/'
                },
                actions: choices
            }
        }
    ];
}

/**
 * 指定された範囲でランダムな数値を取得する
 * @param {number} min - 最小値
 * @param {number} max - 最大値
 * @returns {number} ランダムな数値
 */
function getRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

/**
 * 配列の要素をシャッフルする
 * @param {Array} array - 配列
 * @returns {Array} シャッフルされた配列
 */
function shuffleArray(array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
}

/**
 * Line APIにメッセージを送信する
 * @param {string} replyToken - 返信トークン
 * @param {Array} messages - 送信するメッセージの配列
 */
function replyMessages(replyToken, messages) {
    const options = {
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`
        },
        method: 'POST',
        payload: JSON.stringify({ replyToken, messages })
    };
    UrlFetchApp.fetch(REPLY_API_URL, options);
}

3.デプロイとウェブアプリ公開設定

*デプロイ方法を忘れてしまった場合はこちらを参照

4.実際にLINEで実行