この前のツイートで、こんな投稿をしました。
前置きとして雑ですが、今日はこの課題に対する対処をしていきたいと思います。

"なんちゃってプログラマ"の略でなんグラマです。プログラムを書いたり、ガジェットを紹介したりします。
・お問い合わせ
・プライバシーポリシー
▼以前作ったシフト希望のフォームを送信するプログラムはこちら▼
今回の仕様
①スプレッドシートの回答を確認する
- 設定ファイルにある名前が、スプレッドシートの回答シートにすべて記述されているか確認する
- 設定ファイルの名前がすべてない場合、②の仕様を実行する
②催促メッセージを送信する
- 「まだシフト希望の回答をしていない方は回答してください。」とLINEのトークルームに送信する
実装
コードを書く場所
"src"ディレクトリに、新しく「remainde.gas」を作成し、ここにコードを書いていきます。
ホーム
├ config/
│ └ Config ← 設定値を管理するスプレッドシート
├ form/ ← GAS が自動生成する Google フォームを格納
├ sheet/
│ └ shit ← フォーム回答が書き込まれるスプレッドシートを格納
└ src/
├ main.gs ← メイン(フォームの送信)
└ remainde.gs ← 回答を催促する機能のgasファイル ★新規作成


コード
// 設定を記述したスプレッドシートのファイルID
const CONFIG_SPREADSHEET_ID = 'スプレッドシートのファイルID';
// 設定を管理するシート名
const CONFIG_SHEET_NAME = 'config';
// フォルダ検索フラグ
const ROOT_DIR = 0;
const SPECIFIC_DIR = 1;
/**
* 未回答者がいる場合、催促のメッセージを送信する
*/
function remindUnanswered() {
const cfg = loadConfig(); // 既存の設定読込を流用
const ss = SpreadsheetApp.openById(cfg.spreadsheetId);
// 回答シートを取得("Form Responses" or "フォームの回答" を検出)
const responsesSheet = getResponsesSheet_(ss);
// 回答済みの名前一覧を収集
let respondedNames = [];
if (responsesSheet) {
const lastRow = responsesSheet.getLastRow();
if (lastRow >= 2) {
const headers = responsesSheet.getRange(1, 1, 1, responsesSheet.getLastColumn()).getValues()[0];
const nameCol = findNameColumn_(headers); // 「名前」列の位置
if (nameCol < 1) {
// フォームの設問タイトルが「名前」になっていない等、想定外の場合は送信せず終了
console.log('「名前」列が見つからないため、通知をスキップしました。');
return;
}
respondedNames = responsesSheet
.getRange(2, nameCol, lastRow - 1, 1)
.getValues()
.map(r => String(r[0]).trim())
.filter(v => v);
}
}
// 未回答者の有無だけ判定
const respondedSet = new Set(respondedNames);
const names = (cfg.names || []).map(n => String(n).trim()).filter(Boolean);
const notAnswered = names.filter(n => !respondedSet.has(n));
if (notAnswered.length === 0) {
console.log('未回答者なし:通知をスキップしました。');
return;
}
// 固定メッセージのみ送信(未回答者名は含めない)
const message = 'まだシフト希望の回答をしていない方は回答してください。';
sendTextViaLine_(message, cfg);
}
/**
* config シートから A列(Key)/B列(Value) を読み込み、
* オブジェクトを返す
*/
function loadConfig() {
Logger.log("START FUNCTION: loadConfig()");
const ss = SpreadsheetApp.openById(CONFIG_SPREADSHEET_ID);
const sheet = ss.getSheetByName(CONFIG_SHEET_NAME);
const rows = sheet.getRange('A2:B' + sheet.getLastRow()).getValues();
const cfg = {};
rows.forEach(function(r) {
const key = r[0], val = r[1];
if (key) cfg[key] = val;
});
// names はコンマ区切り文字列 → 配列に
cfg.names = cfg.names ? cfg.names.split(',').map(function(n){ return n.trim(); }) : [];
Logger.log("cfg: %s", JSON.stringify(cfg));
return cfg;
}
/**
* 回答シート(Form Responses / フォームの回答)を返す
*/
function getResponsesSheet_(ss) {
const sheets = ss.getSheets();
for (const s of sheets) {
const n = s.getName();
if (n.indexOf('Form Responses') !== -1 || n.indexOf('フォームの回答') !== -1) {
return s;
}
}
return null;
}
/**
* ヘッダー配列から「名前」列の 1-based インデックスを返す。見つからなければ 0
*/
function findNameColumn_(headers) {
// 完全一致を優先
let idx = headers.findIndex(h => String(h).trim() === '名前');
if (idx >= 0) return idx + 1;
// 念のため部分一致も最後にチェック(柔軟性確保・任意)
idx = headers.findIndex(h => String(h).indexOf('名前') !== -1);
if (idx >= 0) return idx + 1;
return 0;
}
/**
* LINEグループにテキストを送信
*/
function sendTextViaLine_(text, cfg) {
const payload = {
to: cfg.lineGroupId,
messages: [{
type: 'text',
text
}]
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
headers: { Authorization: 'Bearer ' + cfg.accessToken },
muteHttpExceptions: true
};
UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', options);
}
"スプレッドシートのファイルID"は、以下を参考にconfigファイルのURLから取得してください。

動作確認
トリガーを設定する
GASエディタ画面から「Triggers」をクリックする。

「Add Trigger」を押す。

以下のように設定し、「Save」を押す。
- Select type of time based trigger:「Week timer(毎週)」にする
- Select day of week:「Every Friday(毎週金曜日)」にする
- Select time of day:「5 pm to 6 pm(17時~18時の間)」にする
設定間隔はご自身の都合に合わせて調整してください
動作確認
コードの作成とトリガーを設定して迎えた、金曜日の17:00。例によって全員からの回答はありませんでした。LINEを確認してみると、グループトークに催促のメッセージが届いていました。
