Googleフォーム回答を毎朝自動集計してLINEに通知【コピペOK】

朝日の差す部屋で、ロボットが朝のレポート通知が光るスマホを女性に見せているイラスト つまずき帳

朝、出勤してPCを立ち上げて、まずスプレッドシートを開く。Googleフォームの回答が何件たまったか、いちばん下までスクロールして行番号を見る。昨日の最後が何行目だったか思い出せなくて、タイムスタンプを目でさかのぼる。

わたしはずっとそうでした。場所は職員室。会議の直前に「で、いま何件?」と聞かれて、慌ててもう一度シートを開いて数え直すところまで、毎朝セットです。数字を知りたいだけなのに、自分が「人間カウンター」になっている。

しかも、です。前回(記事02:フォーム回答をLINEに即時通知)で「回答が来た瞬間に通知」をつくったら、今度は新しい本音が顔を出しました。通知は来る。来たことは分かる。でも「結局、全部で何件?」「昨日は何件増えた?」という全体像は、シートを開かないと分からない。

というわけで今回は、記事02の末尾で予告した「たまっていく回答を“見る・まとめる”側」です。ゴールはこれ。

毎朝決まった時間帯に、「累計件数・昨日の新着数・選んだ質問の内訳」が1通のLINEになって、勝手に届く。

例によってコードは全部AIに書かせました(この方針のいきさつは創刊号に)。わたしがやったのは、貼って、押して、寝ただけです。

完成形:毎朝、フォーム回答の集計レポートがLINEに届く

LINEのトーク画面。前夜のテストに続き、翌朝8:48に自動で朝レポが届いた
↑ 完成形。前夜のテストのあと、翌朝8:48に“勝手に”届いた実際のログ(昨日+1件のカウントも正確)。

これは、わたしのLINEに実際に残っているログです。

  • 前夜23:21:「テスト送信」を手で実行 →「☀️ フォーム回答レポート(6/10)・累計:2件(昨日 +0件)」
  • 翌朝8:48:何もしていないのに自動で着信 →「☀️ フォーム回答レポート(6/11)・累計:2件(昨日 +1件)」

翌朝この「+1」を見て、ちょっと笑ってしまいました。わたしが寝ている間に、ちゃんと「昨日」を数えている。6/10の朝に送っておいたテスト回答1件を、翌朝のレポートが「昨日の新着」として正しく数えてくれた。つまり、日付をまたぐ集計がちゃんと動いている証拠です。

もうひとつ。翌朝のスクショ、電波表示が4Gなんです。自宅のWi-Fiの外、出かけた先のポケットの中に届いていました。シートを開かないどころか、家にすらいないのに、数字のほうから届く。

正直に書いておきます。「数を数えるだけ」なら、COUNTIFのような関数でもできます。この仕組みの本当の価値は集計そのものではなく、「こちらが開かなくても、毎朝決まった時間帯に、1通に整形されて、向こうから届く」ことです。シートを開く習慣ごと、手放せます。

作戦会議:時間主導型トリガー=“目覚まし時計”を仕掛ける

これまでは「玄関チャイム」型でした

このシリーズでこれまで使ってきた仕掛け(フォーム回答をメール通知(記事01)フォーム回答をLINEに即時通知(記事02))は、どちらも「フォームが送信された瞬間に動く」タイプでした。来客があったら鳴る、玄関チャイム型です。

今回はシリーズ初登場の「時間主導型トリガー」を使います(トリガー=プログラムを自動で動かす「きっかけ」の予約。Apps Scriptの画面上では「時間ベース」と表示されます)。誰も何もしなくても、「毎日この時間帯になったら動け」と命令しておけるもの。つまり目覚まし時計型です。チャイムと目覚まし時計、両方そろえば「来た瞬間に気づく」+「毎朝の定点観測」の二段構えになります。

正直ポイント:時間トリガーは“8時台のどこか”に動く

コードで「8時」と指定しても、届くのは「8時台のどこか」です。これはGoogle側の仕様で、分単位でぴったり指定することはできません。わたしの実測では8:48に届きました。「8:00きっかりに会議で読み上げたい」みたいな使い方には向きません。でも「朝のうちに数字が手元にある」が目的なら、まったく問題ありません。

記事02の「合鍵」をそのまま使い回します(メール派もOK)

LINE側の準備は、追加でやることがゼロです。記事02で作ったLINE公式アカウント(ボット)と合鍵(チャネルアクセストークン)を、そのまま再利用します。まだ作っていない方は記事02の前半をどうぞ。

そして、LINEはまだいいかな……という方も置いていきません。合鍵を空欄のままにすると、同じレポートが自分宛てのメールで届きます。記事01のメール派の方も、同じコードでそのまま使えます。

費用はゼロ円

GAS(Google Apps Script)の時間トリガーは無料枠の範囲内。LINEの無料枠は月200通で、毎朝1通なら月30通。余裕です。

手順:コードを貼って押すだけ(7ステップ)

手順1:回答先のスプレッドシートからApps Scriptを開く

フォームの回答先スプレッドシートを開き、メニューの「拡張機能」→「Apps Script」。開き方の基礎は記事01にあります。

手順2:コードを丸ごと貼り替える

もとから入っているコードを全部消して、この記事のコードを丸ごと貼り付けます。コードはこの少し下、「貼り付けるコード」の章にあります。

※記事02のコードを使っている方は、消す前に CHANNEL_ACCESS_TOKEN = '...''' の中身(合鍵)をコピーして、メモ帳などに控えておいてください。次の手順ですぐ使います。

手順3:設定4行を自分用に書き換える

コードのいちばん上に、設定が4行あります。その1つ目、CHANNEL_ACCESS_TOKEN = '' と書かれた行の '' の間に、記事02で発行した合鍵を貼ります(メール派は空欄のままでOK)。

ついでに 内訳を出す列 = ['ご用件'] の「ご用件」を、自分のフォームの質問名に書き換えてください。質問名が一字でも違うと、エラーは出ずに内訳だけ表示されません。書き方の詳細は、コードの章にまとめてあります。

手順4:保存する

フロッピーのアイコン(プロジェクトを保存)を押します(Ctrl+S / ⌘SでもOK)。保存するまでは、次の手順の「テスト送信」が関数メニューに出てこないので、ここを飛ばすと詰まります。

手順5:「テスト送信」を実行する

画面上部、「実行」「デバッグ」ボタンの並びにある関数名のメニューで「テスト送信」を選び、「実行」を押します。初回は「承認が必要です」の許可画面が出ます。進み方は記事01・02とまったく同じ「すべて選択→続行」です。

Apps Scriptの初回実行で表示される「承認が必要です」の画面
↑ 初回実行で出る「承認が必要です」。記事01・02と同じ道です。

LINEに「今すぐ」朝レポが1通届けば成功。合鍵を空欄にした方は、自分宛てのメールが届いていれば成功です。

「テスト送信」を実行した直後、PC版LINEに朝レポの通知が届いた画面
↑ 「テスト送信」を実行すると、今すぐ1通届きます(PC版LINEの通知)。

手順6:「初期設定」を実行する

関数を「初期設定」に切り替えて、1回だけ実行します。スプレッドシート側に「セットアップ完了!毎朝8時ごろにレポートが届きます。」と表示されたら、毎朝のスイッチONです(時刻の数字は、設定に合わせて表示されます)。

スプレッドシート右下に「セットアップ完了!毎朝8時ごろにレポートが届きます。」と表示されたところ
↑ 「初期設定」でスプレッドシート側にこの表示。毎朝のスイッチONです。

手順7:あとは寝るだけ

翌朝、自動で届きます。ちゃんと予約されたか不安な方は、Apps Script左メニューの時計アイコン(トリガー)を開いてみてください。「毎朝レポート/時間ベース」の行が見えていれば成功です。

Apps Scriptのトリガー一覧に「毎朝レポート・時間ベース」の行が表示されている画面
↑ 時計アイコン(トリガー)に「毎朝レポート/時間ベース」が増えていれば成功。これが“目覚まし時計”の正体。

貼り付けるコード(完全版・即時通知も同梱)

あなたが触るのは、いちばん上の設定4行だけです。

  • CHANNEL_ACCESS_TOKEN:記事02で発行したLINEの合鍵。空欄のままなら自分宛てメールに切り替わります
  • 通知時刻:レポートが届く時間帯(0〜23)。初期値は8(=朝8時台のどこか)
  • 内訳を出す列:内訳を数えたい質問名。フォームの質問の見出しと一字一句同じ文字にしてください。書き換えるのは '' の中身だけで、['ご用件']['お名前'] のように。複数の質問を数えたいときは ['ご用件', '学年'] とカンマ区切りに。内訳が不要なら [] だけに。''[] の記号は消さずにそのまま使ってください
  • 対象シート名:通常は空欄でOK。ひとつのスプレッドシートに複数フォームをつないでいる場合だけ、シート名(=スプレッドシート画面の下に並ぶタブの名前)を指定
// === 設定(ここだけ変えればOK)===
const CHANNEL_ACCESS_TOKEN = '';   // 記事02で発行したLINEの合鍵。空のままなら自分にメールで届きます
const 通知時刻   = 8;              // 毎朝レポートが届く時間帯(0〜23。例: 8 → 朝8時台のどこか)
const 内訳を出す列 = ['ご用件'];     // 内訳を数えたい質問名(フォームの見出しと同じ文字で。不要なら [] に)
const 対象シート名 = '';            // 通常は空でOK。複数フォームがつながっている場合だけシート名を指定

// 毎朝“自動で”動く本体(テスト送信からも呼ばれる)
function 毎朝レポート() {
  const ss    = SpreadsheetApp.getActive();
  const sheet = (対象シート名 && ss.getSheetByName(対象シート名))
             || ss.getSheets().find(s => s.getName().startsWith('フォームの回答'))
             || ss.getSheets()[0];
  const data    = sheet.getDataRange().getValues();
  const headers = data[0];
  const rows    = data.slice(1).filter(r => r[0]);   // 空行は除く

  const total = rows.length;

  // 昨日ぶんの新着(タイムスタンプ列で判定)
  const now        = new Date();
  const today0     = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const yesterday0 = new Date(today0.getTime() - 24 * 60 * 60 * 1000);
  const 昨日 = rows.filter(r => r[0] >= yesterday0 && r[0] < today0).length;

  // 選んだ列の内訳(累計・多い順)
  let 内訳 = '';
  for (const 列名 of 内訳を出す列) {
    const i = headers.indexOf(列名);
    if (i < 0) continue;
    const counts = {};
    rows.forEach(r => { const v = String(r[i] || '(未記入)'); counts[v] = (counts[v] || 0) + 1; });
    const lines = Object.entries(counts).sort((a, b) => b[1] - a[1])
      .map(([v, c]) => ` ${v}:${c}件`).join('\n');
    内訳 += `\n■「${列名}」の内訳\n${lines}`;
  }

  const 日付 = Utilities.formatDate(now, 'Asia/Tokyo', 'M/d');
  const text = `☀️ フォーム回答レポート(${日付})\n・累計:${total}件(昨日 +${昨日}件)${内訳}`;
  sendLineBroadcast(text, `【朝レポ】フォーム回答 累計${total}件(昨日+${昨日})`);
}

// 回答が来た“瞬間”の通知(記事02と同じもの・同梱)
function onFormSubmit(e) {
  const sheet   = e.range.getSheet();
  const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
  const values  = e.values;
  const total   = sheet.getLastRow() - 1;
  const lines   = headers.map((h, i) => `${h}:${values[i] || ''}`);
  sendLineBroadcast(`🔔 新しい回答が届きました(これで ${total} 件目)\n\n` + lines.join('\n'),
    `【新着】フォーム回答が届きました(${total}件目)`);
}

// LINEへ送る(合鍵が空ならメールで送る)
function sendLineBroadcast(text, mailSubject) {
  if (CHANNEL_ACCESS_TOKEN) {
    UrlFetchApp.fetch('https://api.line.me/v2/bot/message/broadcast', {
      method: 'post',
      contentType: 'application/json',
      headers: { 'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN },
      payload: JSON.stringify({ messages: [{ type: 'text', text: text }] }),
      muteHttpExceptions: true
    });
  } else {
    MailApp.sendEmail(Session.getActiveUser().getEmail(), mailSubject || 'フォーム通知', text);
  }
}

// 動作テスト:これを実行すると“今すぐ”朝レポが1通届く
function テスト送信() { 毎朝レポート(); }

// 最初に1回だけ実行:毎朝の自動実行+回答時の即時通知をまとめてセット
function 初期設定() {
  ScriptApp.getProjectTriggers()
    .filter(t => ['毎朝レポート', 'onFormSubmit'].includes(t.getHandlerFunction()))
    .forEach(t => ScriptApp.deleteTrigger(t));
  ScriptApp.newTrigger('毎朝レポート').timeBased().everyDays(1).atHour(通知時刻).create();
  ScriptApp.newTrigger('onFormSubmit').forSpreadsheet(SpreadsheetApp.getActive()).onFormSubmit().create();
  SpreadsheetApp.getActive().toast(`セットアップ完了!毎朝${通知時刻}時ごろにレポートが届きます。`);
}

このコードには、記事02の即時通知(onFormSubmit)も同梱してあります。だから記事02を使っている方も、遠慮なく丸ごと貼り替えてOK。「初期設定」を実行すると、古いトリガーを掃除したうえで、「毎朝レポート(時間ベース)」と「onFormSubmit(フォーム送信時)」の2つをまとめて張り直してくれます。即時通知と朝レポが、これ1本で両立します。

※本文のスクショとコードで行番号が少しずれて見える箇所がありますが、撮影後にコードを完全版へ改良したためです。掲載しているのが最新版で、動作はこの版で検証しています。

わたしがやらかした3つの事故(先回りしておきます)

きれいな手順だけ書いて終わるのは不誠実なので、わたしが実際に踏んだ地雷を3つ、先に共有します。

事故1:追記したら「関数の中」に貼ってしまった

最初、既存のコードに新しい関数を「追記」しようとして、初期設定関数の閉じカッコ }手前に貼ってしまいました。すると追記した関数が「関数の中の関数」になってしまい、トリガーからも実行メニューからも見えなくなります。エラーすら出ないので、何が悪いのか分からず固まりました。

直し方はシンプルで、追記は必ず、いちばん下の } のさらに後ろ(=関数の外)に。実行メニューの関数一覧に追記したはずの関数名が出てこなかったら、まずこの事故を疑ってください。

事故2:丸ごと貼り替えたら、前のコードが消えた

次に、コードを丸ごと貼り替えたら、当然ながら記事02の即時通知(onFormSubmit)が消えました。ところが古いトリガーだけが、Apps Script側(時計アイコンの一覧)に残っていました。そのせいで回答が来るたびにエラーになり、「関数が見つかりません」のエラーメールがGoogleから届く、という気まずい状態に。

この記事のコードは、その反省から即時通知も同梱した完全版にしてあります。なので対処は「この記事のコードを丸ごと貼り替えて、初期設定をもう一度実行する」だけ。トリガーの掃除と張り直しまで、初期設定が面倒を見ます。

事故3:“完成イメージ図”を、注釈ごとコピペした

3つ目は、いちばん恥ずかしいやつです。AIとのチャットで見せられた「完成イメージはこんな感じです」という図を、行番号や注釈ごとコードエディタに貼ってしまったことがあります。当然動きません。コピペするのは「コードブロックの中身だけ」。この記事なら、上のグレーの枠の中身だけです。

まとめ:シートを開いて数える朝が消えた

わたしの朝から、「シートを開いて数える」という工程が消えました。外出先のポケットの中で1回震えて、画面を見れば、累計と昨日の増分と内訳が1通に並んでいる。「で、いま何件?」には、LINEを見せれば終わりです。

記事02と合わせると、役割分担はこうなります。

  • 記事02:来た瞬間に気づく(即時通知・玄関チャイム)
  • 記事03:毎朝の定点観測(まとめ・目覚まし時計)

数字の報告のために自分がカウンターになる必要は、もうありません。数字は、向こうから出勤してきます。

次回予告

次回は、「同じ連絡を、一人ひとり名前だけ変えて送る」あの地味に時間を食う作業——メールの差し込み一斉送信を自動化する予定です。

それでは、定時で帰りましょう。

このシリーズのこれまで:創刊号(入口)記事01:フォーム回答をメールで受け取る記事02:フォーム回答をLINEに即時通知

コメント

タイトルとURLをコピーしました