チャットワークへスケジュールをリマインドしてくれるBOT


  • 2017/10/22追記
    • 終日のスケジュール入力に対応しました(Ver2.0.0へ変更)

前提

依頼されたタスクや参加する会議の日程、その他雑務など、仕事をしているとスケジュール管理が重要になってくる場面がたくさんでてくると思います。
自分のスケジュール管理ならできても、それだけではチームの状況は良くならないことも多いので(1人のミスは最終的には全員のミスになるので)、全員が幸せになれるようにリマインダーBOTを作ってみました。

できること

スケジュールを一括で簡単に登録でき、その時刻になったらチャットワークにTo付きで指定のメンバーにリマインドすることができます

メリット

  • Googleカレンダーでスケジュールのデータを管理しているので非エンジニアでも使いやすい
  • Googleカレンダーを使っているので定期スケジュールの登録、スケジュールの途中変更、削除などもしやすい
  • リマインドできるグループチャットや対象者をスケジュールごとに設定可能

環境

  • GoogleAppsScript(通称:GAS)
  • スプレッドシート
  • チャットワーク
  • Googleカレンダー

配布

使い方

  • ツール ⇒ スクリプトエディタを開き、定数部分を自分の環境にあったものに修正して下さい(SCRIPT_NAMEは変更不要です)
    • SPREADSHEET_URL:スプレッドシートのURL
    • CHATWORK_TOKEN:チャットワークのAPIトークン
    • CALENDAR_ID:GoogleカレンダーのID(基本的にはGmailアドレスと同じです)
    • ROOM_ID:一日一回送信される全体スケジュールの発言対象ルームID
  • ツール ⇒ スクリプトエディタを開き、スクリプトの実行タイマー(時計マーク)を下記に設定して下さい(以下は入力例です)
    • カレンダーとデータを連携する必要性があるため、設定時に承認を求められる場合があります
    • setTodaySchedule - 時間主導型 - 日タイマー - 午前4時~5時
      • この時間にその日のスケジュールを一括でスプレッドシートにコピペしてきます
    • sendSchedule - 時間主導型 - 分タイマー - 5分ごと
    • sendTodaySchedule - 時間主導型 - 日タイマー - 午前8時~9時
  • リマインドしたい内容を登録フォームに入力します。入力が終わったらツールバーの「スケジュールBOT」を実行して下さい
    • 終了日時は必要があれば手動で入力して下さい(終了日時を入力しておくとカレンダーが見やすいです)
    • スケジュールは複数同時登録可能です。複数登録する場合は、複数行入力して下さい
    • リマインドは開始日時に設定した時間に送られます
    • 発言対象、対象メンバーは適時VlookSheetを編集して追加して下さい
    • 行の追加は可能ですが、列を追加すると機能が正常に動かなくなります。列追加する場合はスクリプトも修正して下さい
    • 「登録リスト」「デイリー発言リスト」リートは編集しないで下さい
  • スケジュールの登録時に時間の部分を0:00で登録すると「終日」のスケジュールになります
  • 毎日1回だけ1日の全体スケジュールを特定のルームに送信することができます
  • スケジュール設定が「終日」の予定に関してはリマインドは行われません(全体スケジュールの時のみ予定に含まれます)
  • 定期的なスケジュール(リマインド)を登録したい場合は1つだけ登録した後、Googleカレンダーを開いて手動で定期登録に切り替えて下さい

プログラムの説明(エンジニア向け)

  • 「登録フォーム」シートに入力してある行を1行ずつ参照し、その内容をカレンダーに登録しています
  • GoogleAppsScriptのタイマー機能を使って、早朝に当日分の全スケジュールをデイリー発言リストにコピーしてきます
  • GoogleAppsScriptのタイマー機能を使って、一定時間ごとにスケジュールの時間が過ぎていないかどうか判定しています
  • スケジュールの時間になったらチャットワークの所定のグループへBOT発言します
  • リマインドは1回のみでいいので、発言したかどうかの判定を「デイリー発言リスト」のE列で行っています

ソースコード

// 定数指定
SPREADSHEET_URL = "";
CHATWORK_TOKEN = "";
CALENDAR_ID = "";
SCRIPT_NAME = "カレンダーへ登録";

// 本日の予定を送るチャットワークのルームID
ROOM_ID = 9999999999;

/**
 * スプレッドシート読み込み時に起動される
 */
function onOpen()
{
    var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
    var menuFunction = [{
        name: SCRIPT_NAME,
        functionName: "createSchedule"
    }];

    spreadSheet.addMenu("スケジュールBOT", menuFunction);
}

/**
 * 登録リストの内容をカレンダーに登録する
 */
function createSchedule()
{
    // スプレッドシートを操作するための準備
    var url = SPREADSHEET_URL;
    var spreadsheet = SpreadsheetApp.openByUrl(url);
    var sheet = spreadsheet.getSheetByName("登録リスト");

    //シートの最終行番号、最終列番号を取得
    var startRow = 1;
    var startColumn = 1;
    var lastRow = sheet.getLastRow();
    var lastColumn = sheet.getLastColumn();

    // シートの縦横すべてを取得
    var sheetData = sheet.getSheetValues(startRow, startColumn, lastRow, lastColumn);

    // カレンダーへの登録
    for (var i = 1; i < lastRow; i++) {
        if (sheetData[i][0] != "") {
            createEvent(sheetData[i][0], sheetData[i][1], sheetData[i][2], sheetData[i][3], sheetData[i][4], sheetData[i][5]);
            // 連続して作成するとエラーを返すので1秒ウェイト
            Utilities.sleep(1000);
        }
    }
}

/**
 * カレンダー登録の本体
 */
function createEvent(title, startTime, endTime, location, menber, agenda)
{
    var calendar = CalendarApp.getCalendarById(CALENDAR_ID);

    if (HHmm(startTime) == "00:00") {
        calendar.createAllDayEvent(title,
            startTime,
            {
                description: menber + "\n" + agenda,
                location: location
            }
        );
    } else {
        calendar.createEvent(title,
            new Date(startTime),
            new Date(endTime),
            {
                description: menber + "\n" + agenda,
                location: location
            }
        );
    }
}

/**
 * 今日の予定をスプレッドシートにコピーする(毎日1度だけ呼ばれる)
 */
function setTodaySchedule()
{
    // スプレッドシートを操作するための準備
    var url = SPREADSHEET_URL;
    var spreadsheet = SpreadsheetApp.openByUrl(url);
    var sheet = spreadsheet.getSheetByName("デイリー発言リスト");

    //特定のIDのカレンダーを取得
    var calendar = CalendarApp.getCalendarById(CALENDAR_ID);

    //カレンダーの本日のイベントを取得
    var schedules = calendar.getEventsForDay(new Date());

    // 一旦、シートをクリアにする
    sheet.getRange("A2:E100").clearContent();

    // イベントの数だけ繰り返し
    for (var i = 0; i < schedules.length; i++) {
        var title = schedules[i].getTitle();
        var startTime = schedules[i].getStartTime();
        var location = schedules[i].getLocation();
        var agenda = schedules[i].getDescription();

        sheet.getRange(i + 2, 1).setValue(title);
        sheet.getRange(i + 2, 2).setValue(startTime);
        sheet.getRange(i + 2, 3).setValue(location);
        sheet.getRange(i + 2, 4).setValue(agenda);

        if (HHmm(startTime) == "00:00") {
            sheet.getRange(i + 2, 5).setValue("送信済");
        } else {
            sheet.getRange(i + 2, 5).setValue("未送信");
        }
    }
}

/**
 * 時間になったらチャットワークへ予定を発言する
 */
function sendSchedule()
{
    // スプレッドシートを操作するための準備
    var url = SPREADSHEET_URL;
    var spreadsheet = SpreadsheetApp.openByUrl(url);
    var sheet = spreadsheet.getSheetByName("デイリー発言リスト");

    //シートの最終行番号、最終列番号を取得
    var startRow = 1;
    var startColumn = 1;
    var lastRow = sheet.getLastRow();
    var lastColumn = sheet.getLastColumn();

    // シートの縦横すべてを取得
    var sheetData = sheet.getSheetValues(startRow, startColumn, lastRow, lastColumn);

    var nowTime = new Date();
    var honbun = "";

    for (var i = 1; i < lastRow; i++) {
        if (sheet.getRange(i + 1, 5).getValue() == "未送信" && sheet.getRange(i + 1, 2).getValue() <= nowTime) {
            honbun = "";
            honbun += "[info][title]" + sheetData[i][0] + " (roger)[/title]";
            honbun += sheetData[i][3];
            honbun += "[/info]";

            sheet.getRange(i + 1, 5).setValue("送信済");
            sendMessageToChatWork(sheetData[i][2], honbun);
        }
    }
}

/**
 * チャットワークへの発言プログラムの本体
 */
function sendMessageToChatWork(roomId, message)
{
    var token = CHATWORK_TOKEN;

    var parameter = {
        headers: {
            "X-ChatWorkToken": token
        },
        method: "post",
        payload: {
            body: message
        }
    };

    // 送信先情報
    var url = "https://api.chatwork.com/v2/rooms/" + roomId + "/messages";

    // メッセージ送信
    UrlFetchApp.fetch(url, parameter);
}

/**
 * 本日の予定をチャットワークに送る
 */
function sendTodaySchedule()
{
    // 本日の予定を送るチャットワークのルームID
    var roomId = ROOM_ID;

    //特定のIDのカレンダーを取得
    var calendar = CalendarApp.getCalendarById(CALENDAR_ID);

    //カレンダーの本日のイベントを取得
    var schedules = calendar.getEventsForDay(new Date());

    // チャットワークに送る文字列のヘッダー
    var honbun = "[info][title]本日の予定:" + Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd') + " (roger)[/title]";

    // イベントの数だけ繰り返し
    for (var i = 0; i < schedules.length; i++) {
        var strTitle = schedules[i].getTitle();
        var strStart = HHmm(schedules[i].getStartTime());
        var strEnd = HHmm(schedules[i].getEndTime());
        honbun += strStart + ' - ' + strEnd + " " + strTitle + '\n';
    }

    honbun += '[/info]';

    sendMessageToChatWork(roomId, honbun)
}

/**
 * 時刻の表記をHH:mmに変更
 */
function HHmm(str)
{
    return Utilities.formatDate(str, 'JST', 'HH:mm');
}

参考