LINEチャットボット(3択クイズ④時間トリガー)

LINEBOT

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

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

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

1.Apps Scriptを開く

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

2.プログラムを書き換える

下記のコードをコピーしますチャネルアクセストークンスプレッドシートIDは自分の物を入れます。

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

// ライン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 = '自分のトークン';

// 自分のLINE ID
const MY_USER_ID = '自分のLINE ID(logに保存されています)';

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

// クイズの時間にプッシュ通知を送信する関数
function pushQuizReminder() {
    const url = PUSH_API_URL;
    const headers = {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': `Bearer ${CHANNEL_ACCESS_TOKEN}`
    };

    const postData = {
        'to': MY_USER_ID,
        'messages': [
            {
                'type': 'template',
                'altText': '毎日クイズ',
                'template': {
                    'type': 'confirm',
                    'text': 'おはようございます、毎日クイズの時間です!',
                    'actions': [
                        {
                            'type': 'message',
                            'label': '挑戦する',
                            'text': 'はい'
                        },
                        {
                            'type': 'message',
                            'label': 'お休みする',
                            'text': 'いいえ'
                        }
                    ]
                }
            }
        ]
    };

    const options = {
        'method': 'post',
        'headers': headers,
        'payload': JSON.stringify(postData)
    };

    UrlFetchApp.fetch(url, options);
}

/**
* 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);
    }

    // 応答メッセージを送信
    sendMessages(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(); // 不正解メッセージ

    const continueMessage = {
        type: 'template',
        altText: 'test',
        template: {
            type: 'confirm',
            text: 'クイズを続けますか?',
            actions: [
                {
                    type: 'message',
                    label: 'はい',
                    text: 'はい'
                },
                {
                    type: 'message',
                    label: 'いいえ',
                    text: 'いいえ'
                }
            ]
        }
    };

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

/**
* メッセージに応じた応答メッセージを生成する
* @param {string} messageText - メッセージ本文
* @param {Object} quizSheet - クイズシート
* @returns {Array} 応答メッセージの配列
*/
function generateResponseMessages(messageText, quizSheet) {
    if (messageText.match(/はい/)) {
        return generateQuizMessages(quizSheet);
    } else 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: 'bubble',
        hero: {
            type: 'box',
            layout: 'vertical',
            contents: [
                {
                    type: 'text',
                    text: `  選択肢${i + 1}`
                }
            ]
        },
        body: {
            type: 'box',
            layout: 'vertical',
            contents: [
                {
                    type: 'button',
                    action: {
                        type: 'postback',
                        label: quizSheet.getRange(`${col}${randomRow}`).getValue(),
                        data: `${col}${randomRow}`,
                        displayText: quizSheet.getRange(`${col}${randomRow}`).getValue()
                    }
                }
            ]
        }
    }));

    return [
        {
            type: 'text',
            text: question
        },
        {
            type: 'flex',
            altText: 'test',
            contents: {
                type: 'carousel',
                contents: 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 sendMessages(replyToken, messages) {
    const apiUrl = replyToken ? REPLY_API_URL : PUSH_API_URL;
    const body = replyToken
        ? { replyToken, messages }
        : { to: userId, messages };

    const options = {
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`
        },
        method: 'POST',
        payload: JSON.stringify(body)
    };

    UrlFetchApp.fetch(apiUrl, options);
}

3.デプロイを管理

デプロイをクリックし、表示されるメニューのデプロイを管理をクリックします。

右上のペンマークをクリックします。

バージョンの枠横にあるをクリックします。

一番上にある新バージョンをクリックします。

バージョンの枠内が新バージョンになっている事を確認したら、右下のデプロイをクリックします。

4.時間トリガー設定

指定した時間にプッシュされるように、時間トリガーの設定を行います。

左側にある目覚まし時計のアイコンをクリックします。

右下側にある「+トリガーの追加」をクリックします。

トリガーの追加設定画面が表示されます。

左側の各種設定を変えます
*例では動作を確認するために1分毎にメッセージが送られるので注意してください。

実行する関数pushQuizReminder
実行するデプロイを選択Head
イベントのソースを選択時間主導型
時間ベースのトリガーのタイプを選択分ベースのタイマー
時間の間隔を選択(分)1分おき

5.実際にLINE実行

6.追加部分説明

メッセージを送るにはユーザーIDが必要です、今回は個別に指定しています。
(次のブログで自動取得が実装されます)

// 自分のLINE ID
const MY_USER_ID = '自分のLINE ID(logに保存されています)';

関数「pushQuizReminder()」を先ほど設定した時間トリガーで実行しています。
「はい」をクリックするとtext「はい」が送られ関数function doPost(e)でクイズが始まります。

// クイズの時間にプッシュ通知を送信する関数
function pushQuizReminder() {
    const url = PUSH_API_URL;
    const headers = {
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': `Bearer ${CHANNEL_ACCESS_TOKEN}`
    };

    const postData = {
        'to': MY_USER_ID,
        'messages': [
            {
                'type': 'template',
                'altText': '毎日クイズ',
                'template': {
                    'type': 'confirm',
                    'text': 'おはようございます、毎日クイズの時間です!',
                    'actions': [
                        {
                            'type': 'message',
                            'label': '挑戦する',
                            'text': 'はい'
                        },
                        {
                            'type': 'message',
                            'label': 'お休みする',
                            'text': 'いいえ'
                        }
                    ]
                }
            }
        ]
    };

    const options = {
        'method': 'post',
        'headers': headers,
        'payload': JSON.stringify(postData)
    };

    UrlFetchApp.fetch(url, options);
}