LINEに話しかけるだけで予定が増える。一覧から、まとめて消せる|“コードを書かない”自動化【作り方シリーズ第2回】

LINEに話しかけるだけで予定が増える――作り方シリーズ第2回のアイキャッチ つまずき帳

歩きながら「来週、歯医者だった」と思い出す。なのにその予定は、たいてい当日まで思い出されない。悪気なんてないのに、すっぽかすたび、自分がちょっと雑な人間に思えてくる——あの感じ、ありませんか。

…と書きかけて、やめます。事情を聞きたいんじゃなくて、代わりに言いたいんです。思い出した瞬間がいちばん覚えているのに、「入力」というひと手間を挟んだ瞬間に、するっと抜け落ちる。 カレンダーを開いて、日付を選んで、時刻を合わせて、件名を打って……手がふさがっていれば、そこまでやる気力はない。だから「あとで入力しよう」と思う。そして、その「あとで」は、たいてい来ません。

わたしもそうでした。予定をすっぽかしたこと自体より、こういう自分が積み重なっていく感じのほうが、地味に効くんですよね。

ほしかったのは、「思い出した、その勢いのまま、ひとことLINEに放り込めば終わる」場所でした。

結果から先に:LINEに話しかけると、予定が増える(スプレッドシートに自動で追記)

いまは、こうなっています。

LINEのトーク画面に「明日15時に歯医者」と打って送る。それだけです。数秒後、「予定に登録しました」と返ってきて、いつものスプレッドシート(予定を並べておく表計算の表)に、日付・予定・時刻がきれいに増えている。日付を選ぶ操作も、時刻を合わせる操作も、件名を打ち直す操作もありません。話しかけた、その一文だけ。

「ちゃんとした文章で書かなきゃ」と身構えなくていい。思い出したまま、雑に放り込んでいい——この“構えなくていい”が、続けられるかどうかの分かれ目でした。

写真でも同じです。紙の予定表を撮って送れば、そこから読み取って表にしてくれます(作り方シリーズ第1回で紹介しました)。今日の主役は、文字での会話のほうです。

そして今日いちばん見てほしいのは、その逆。登録した予定を、LINEから一覧で呼び出して、修正したり、消したりできることです。とくに、たまった予定をまとめて消すのが、想像よりずっと気持ちいい。順番に説明します。

例によって、わたしはコードを1行も書いていません。書いたのはAI。わたしは貼って、許可しただけです(この方針のいきさつは創刊号に書きました)。費用は、わたしの使い方では0円=Geminiの無料枠の範囲です(上限の話は、後段で正直に書きます)。

今日の一点:LINEを「届くだけ」から「話しかけて操作できる」へ(双方向)

前回までのLINEは、こちらに通知を“送ってくる”だけの一方通行でした。フォームに回答が来たら教えてくれる、毎朝レポートを届けてくれる——届く側だった、ということです(フォーム回答をLINEに通知する回から始めた仕組みです)。

今日のテーマは、その一本道を双方向にすること。つまり、こちらから話しかけて、向こうを操作できるようにします。

一方通行:仕組み →(だけ)→ わたし(届くだけ)
双方向 :仕組み ←→ わたし(こちらからも操作できる)

「双方向」と聞くと急に難しそうですが、覚えることは一つだけ。LINEに届いた言葉を、仕組みが受け取れるようにする。 いままでは「送る口」だけ開けていたのを、「受け取る口」も開ける。それだけで、トーク画面が“操作盤”に変わります。あとはAIが、届いた一文を読んで「これは予定だな」「これは消したいんだな」と仕分けてくれます。

あれもこれもは詰め込みません。今日の主役は、この「話しかけて操作できる」一点だけです。

話しかけると、AIが「予定」に仕分けてくれる

「明日15時に歯医者」。この一文には、人間なら一瞬で分かる情報が詰まっています。日付(明日)、時刻(15時)、用件(歯医者)。これを機械的なルールで切り分けようとすると、途端に難しくなる。「明日」って今日から数えていつ?「15時」と「午後3時」と「3時ごろ」を同じに扱える?——きりがありません。

ここがAIの出番です。文章の意味をくみ取って、日付・時刻・用件に整理するのは、AIがいちばん得意なこと。だから、難しい仕分けルールは一切書きません。AIに「この文を予定として読んで、日付・予定・時刻に整理して」と頼むだけ。「来週水曜の朝イチで打ち合わせ」みたいな、ふわっとした言い方でも、ちゃんと拾ってくれます。

うまく読み取れたら表に追記して、「登録しました」と返信。読み取れないとき、日付がはっきりしないときは、推測で勝手に埋めず「ここが読めませんでした」と知らせる——ようにAIに頼んでいます。

ただ、これは完璧ではありません。第1回でも正直に書いたとおり、わたしも一度、撮り間違えた写真の“化けた行”を、そのまま取り込んでしまったことがあります。だからこの記事でも、「黙って間違えない」と言い切るより、「間違えても、後から一覧でちゃんと直せる・消せる」ほうを土台にしています。 完璧を約束するより、やり直せることのほうが、安心して使えるからです。

LINEのトーク画面。利用者が「来週水 15時面談」「8/15 13時 打ち合わせ」と自然文で送り、ボットが「6/24(水) 面談 15:00 を予定に登録しました」と日付・時刻・用件に整理して確認返信している様子。
LINEに「来週水 15時面談」「8/15 13時 打ち合わせ」と、思い出したまま雑に放り込むだけ。AIが日付・時刻・用件を読み取って、「6/24(水) 面談 15:00 を予定に登録しました」と確認を返してくれます(画面の予定はすべて架空のサンプルです)。

一覧を呼び出して、修正・削除できる

放り込んだ予定は、あとから`予定一覧`と話しかければ、番号つきの縦リストで返ってきます。

1. 6/17(火)9:00  健康診断
2. 6/18(水)15:00 歯医者
3. 6/20(金)      資料の〆切
4. 6/24(火)10:00 打ち合わせ

(※ここに並ぶ予定は、すべて架空のサンプルです。)

ここから、`削除 2`と送れば2番が消えます。直したいときも、番号と新しい日付をセットで——たとえば`2 6/19`のように送れば、2番の日付だけ直る、という具合。LINEから入れたものは、LINEから直せる・消せる。 思い立った場所で完結します。

(補足:紙の予定表を撮って取り込んだ“おおもと”の予定は、うっかり消えないように保護してあります。LINEでの削除対象は、LINEから自分で入れたぶんだけ。この線引きが、あとで効いてきます。)

つまずき先回り:たまった予定、一個ずつ消すのは正直しんどい

ここで、使い込むと必ずぶつかる本音を先に言います。

一個ずつ消すの、面倒くさい。

終わった予定、入れ間違えた予定、重複した予定。気づくと一覧に10件、20件とたまっていて、`削除 3`、`削除 5`、`削除 8`……と一つずつ送るのは、けっきょく手作業に逆戻り。「自動化したはずなのに、なんでこんな手で消してるんだろう」と、ちょっと我に返る瞬間です。わたしも、気づけば毎回これをやっていました。

そこで入れたのが、まとめ削除です。

番号つきの一覧を見ながら、消したい番号をまとめて指定します。指定のしかたは、人が紙に書くときの感覚そのまま。

  • とびとびで選ぶ:`2 5 7`
  • 連続して選ぶ:`3-8`
  • ぜんぶ消す:`全部`

送っても、いきなり消したりはしません。一度だけ、確認が出ます。

「3件を削除します。よろしいですか?」 [はい] [やめる]

この[はい][やめる]は、トーク画面の下からポンと出てくるボタンを、タップひとつで選ぶだけ。こちらが文字を打つ必要はありません(LINEではこれを「クイックリプライ」と呼びます)。指が滑って関係ない予定をごっそり消す、みたいな事故を防ぐための、最後のひと呼吸です。

[はい]を押すと、まとめて消えて、結果を要約で返してくれます。

「3件を削除しました(残り◯件)。」(※数字は架空のサンプルです。)

一個ずつ`削除 ◯`を10回送っていた作業が、`2 5 7`→[はい]のふた手で終わる。地味ですが、ここがいちばん「もう手で消したくない」が報われる場所でした。

(さっきの“線引き”がここで効きます。`全部`と指定しても、消えるのはLINEから自分で入れたぶんだけ。紙から取り込んだおおもとの予定や、ほかの予定まで巻き込んで消える心配はありません。)

LINEの「まとめ削除」画面。ボットが番号つきの予定一覧(1. 6/24 面談、2. 8/15 打ち合わせ)と指定例(2 5 7/3-8/全部)を示し、利用者が「2を削除してください。」と文章で指定。[はい][やめる]の確認をはさんで「1件を削除しました」と返している。
予定がたまってきたら「まとめ削除」。番号つきの一覧が出たら、消したい番号を「2 5 7」ととびとびでも、「3-8」と範囲でも、「全部」でもまとめて指定できます(この例のように「2を削除してください」とふつうの文章で送っても、ちゃんと番号を読み取ってくれます)。送ってもいきなりは消えず、[はい][やめる]のひと呼吸をはさんでから削除。最後に「1件を削除しました」と残りの件数まで返ります(予定はすべて架空のサンプルです)。

同じものを作る:AIにGASを書かせるプロンプト(コードは書きません)

ここからが、このブログのいちばん大事なところ=そのまま真似できる“レシピ”です。コードを自分で書く必要はありません。ChatGPTやGeminiのような対話AIに、下の「AIに渡すプロンプト」をそのまま貼るだけ。組み立てるのはAIの仕事です。

最小の手順は、たったこれだけ。

1. 対話AIに、本記事のAIプロンプトを貼って、出てきたものを受け取る2. 言われたとおりに貼り付けて、初回の許可をする3. LINE側で「受け取る口」(送られてきた言葉を仕組みに渡す設定)を一度オンにする

ひとつだけ、設計のお願いを入れてあります。LINEやAIの合鍵(パスワードのようなもの)を、コードの中に直接書かないこと。代わりに、コードとは別の安全な場所にしまって、そこから読み込む形にしてもらいます。こうしておくと、後から人にコードを見せても合鍵が漏れません。プロンプトの中で、ちゃんとそう指示してあります。

「受け取る口」をオンにする具体的な画面まわりは、第1回で写真を受け取れるようにしたときと、ほぼ同じ要領です。そして、その途中で必ず出る「このアプリは確認されていません」という許可画面——あれは故障でも危険サインでもなく、正常です。ここで気後れして手が止まる人がとても多いので、次回・第3回でまるごと一本、許可画面だけを案内します。今日は「話しかけて操作できる」入り口に集中します。

▼ AIに渡すプロンプト(このままコピペ)

あなたはGoogle Apps Script(GAS)に詳しい開発者です。プログラミングができない会社員のわたしのために、下記の「LINEと双方向でやり取りするAI秘書」を、コピペで動かせる完成コードとして書いてください。わたしはコードを読めないので、出てきたコードをどこに貼り、何を押せばよいかを、専門用語をかみくだいた手順つきで説明してください。

【作りたいもの】
LINE公式アカウントに、わたしがトークで「話しかける」と、その内容に応じて予定をGoogleスプレッドシートに登録・確認・修正・削除できる仕組み。LINEから送るだけの一方通行ではなく、こちらから操作できる「双方向」にしたい。

【動作の流れ】
1) 受け取り:LINEに送られてきたメッセージを、GASのウェブアプリ(doPost)で受け取る。テキストでも画像でも受け取れるようにする。
2) 自然文を予定に変換:受け取ったテキスト(例「明日15時に歯医者」「来週水曜の朝イチで打ち合わせ」など、ふわっとした言い方も含む)をAI(Gemini)に渡し、「日付・予定・時刻」に整理させてスプレッドシートに1行追記する。送信日を基準に「明日」「来週水曜」などの相対日付を実際の日付へ変換する。読み取れない・日付が確定できない場合は、推測で埋めずに登録を見送り、「ここが読めませんでした」と本人に知らせる(※完璧は保証しない設計でよい。後から一覧で直せること=下記4〜6を土台にする)。
3) 写真:画像が送られてきた場合も、紙の予定表をAIに読ませて同様に表として追記する(※詳細は別仕様。ここでは分岐だけ用意し、テキスト処理を主にする)。
4) 一覧:「予定一覧」と送ると、登録済みの予定を「番号つきの縦リスト」で返す(例:1. 6/18(水)15:00 歯医者)。横スクロールが要らない、一画面で読める縦リストにする。
5) 修正・削除:「削除 2」で2番を削除。番号と新しい日付をセットで送る(例「2 6/19」)と、その予定の日付/時刻だけ更新できる。
6) まとめ削除(複数一括):番号つき一覧に対し、複数の番号をまとめて指定して一度に削除できるようにする。指定方法は、とびとび(例「2 5 7」)、範囲(例「3-8」)、全部(「全部」)の3通りを受け付ける。全角・半角の数字や空白の揺れも吸収する。
7) 確認はクイックリプライ:まとめ削除の前に必ず一度だけ確認を出す。確認はLINEのクイックリプライのボタン([はい]/[やめる])で答えられるようにする。[はい]で実行、[やめる]で中止。「全部」のときは件数を読み上げて二重に確認する。
8) 結果の要約:まとめ削除を実行したら「◯件を削除しました(残り◯件)」のように、消した予定を日付つきで明記しつつ要約で返す。
9) 安全な線引き:削除・まとめ削除の対象は「LINEから自分で登録したぶん」だけに限定し、写真(紙の予定表)から取り込んだおおもとの予定や他の予定は誤って消さないよう保護する。

【セキュリティ要件(必須)】
– LINEのチャネルアクセストークン、Geminiなどの外部APIキー、自分のLINE User IDといった秘密情報を、コード本体に直書きしないこと。
– これらはすべてGASの「スクリプトプロパティ(PropertiesService.getScriptProperties())」から読み込む設計にする。Geminiの呼び出しはキーを x-goog-api-key ヘッダで渡す方式にする。
– コードには、スクリプトプロパティに登録すべきキー名の一覧(例:LINE_TOKEN、GEMINI_API_KEY、MY_USER_ID など)と、それぞれを設定画面に登録する手順を、わたし向けに説明として添えること。
– 本人以外からのメッセージで操作されないよう、MY_USER_IDと一致する送信者からのリクエストだけを処理するようにする。

【出力してほしいもの】
– 上記を満たすGASの完成コード一式(doPostを起点に、関数ごとに何をしているか日本語コメントつき)。
– LINE側で「受け取る口」(Webhook)をオンにし、GASのウェブアプリURLを登録するまでの手順。
– スクリプトプロパティに合鍵を登録する手順。
– 最初に一度だけ必要な「許可(初回承認)」で、何を聞かれて、どう進めばよいかの説明。
すべて、コードを読めない人でも順番どおりやれば動く粒度で、専門用語はかみくだいて説明してください。

(おまけ)双方向の最小サンプルを見る

※ここから下はおまけ。本筋は上のプロンプトでAIに作ってもらう方法です。これは“双方向の心臓部”だけを取り出した、単体で動く最小サンプル(自分の合鍵を設定欄に入れて使う/実物のボットそのものではありません)。

▶ 最小サンプルを開く(約80行)
/**************************************************************************
 * 【おまけ・最小サンプル】LINE双方向の“心臓部”だけを取り出したもの
 *   ・本物の「ぽけっと手帳」ではなく、"受け取って・返す" 仕組みの最小形です
 *   ・トークンやキーはコードに書かず、スクリプトプロパティから読みます
 *     [⚙ プロジェクトの設定]→「スクリプト プロパティ」に
 *      LINE_TOKEN / GEMINI_KEY / MY_USER_ID を登録してください
 *   ※「まとめ削除」の本体(範囲/とびとび/全部の処理)は本番ボットが大きいので、
 *     ここでは「番号の指定文字列を配列にする小関数」だけ添えています。
 *     完全版は記事中の「AIに渡すプロンプト」からAIに作ってもらえます。
 **************************************************************************/

const PROP = PropertiesService.getScriptProperties();
const LINE_TOKEN = PROP.getProperty('LINE_TOKEN');
const GEMINI_KEY = PROP.getProperty('GEMINI_KEY');
const MY_USER_ID = PROP.getProperty('MY_USER_ID');   // 自分のLINE User ID
const SHEET = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();

// ① 受け取る口(doPost)=本人だけ通す
function doPost(e) {
  const event = JSON.parse(e.postData.contents).events[0];
  if (!event || event.type !== 'message') return ok();
  if (event.source.userId !== MY_USER_ID) return ok();   // 本人以外は無視
  if (event.message.type === 'text') handleText(event.message.text, event.replyToken);
  return ok();
}
function ok() { return ContentService.createTextOutput('OK'); }

// ② テキスト1通 → 予定1行に変換してシートに追記
function handleText(text, replyToken) {
  if (text === '削除') { askYesNo(replyToken, 'いちばん下の予定を削除しますか?'); return; } // ③確認UIのデモ
  const prompt = '次の文を予定に変換し、JSON {"date":"YYYY-MM-DD","title":"...","time":"HH:mm"} '
    + 'だけで返してください。今日は ' + today() + ' です。文:' + text;
  const ans = callGemini(prompt);
  if (!ans) { reply(replyToken, 'AIが混み合っています。少し後でお試しください。'); return; }
  const r = JSON.parse(ans.replace(/```json|```/g, '').trim());
  SHEET.appendRow([r.date, r.title, r.time]);
  reply(replyToken, '✅ 登録しました:' + r.date + ' ' + r.title + ' ' + r.time);
}

// ③ [はい][やめる]のボタン(クイックリプライ)を返す最小例
function askYesNo(replyToken, question) {
  lineReply(replyToken, [{
    type: 'text', text: question,
    quickReply: { items: [quick('はい', 'はい'), quick('やめる', 'やめる')] }
  }]);
}
function quick(label, text) {
  return { type: 'action', action: { type: 'message', label: label, text: text } };
}

// 「2 5 7」「3-8」「全部」を番号の配列にする小関数(=まとめ削除のパース部分)
function parseNumbers(spec, total) {
  if (spec.trim() === '全部') {
    const all = []; for (let i = 1; i <= total; i++) all.push(i); return all;
  }
  const nums = [];
  spec.split(/[\s,]+/).forEach(function (token) {
    const range = token.match(/^(\d+)-(\d+)$/);       // 範囲(例 3-8)
    if (range) { for (let i = Number(range[1]); i <= Number(range[2]); i++) nums.push(i); }
    else if (/^\d+$/.test(token)) nums.push(Number(token));  // とびとび(例 2 5 7)
  });
  return nums;
}

/* 共通:LINE返信/Gemini呼び出し(503は自動リトライ)/日付 */
function reply(replyToken, text) { lineReply(replyToken, [{ type: 'text', text: text }]); }
function lineReply(replyToken, messages) {
  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', {
    method: 'post', contentType: 'application/json',
    headers: { Authorization: 'Bearer ' + LINE_TOKEN },
    payload: JSON.stringify({ replyToken: replyToken, messages: messages })
  });
}
function callGemini(text) {
  const payload = JSON.stringify({ contents: [{ parts: [{ text: text }] }] });
  for (let i = 0; i < 3; i++) {
    const res = UrlFetchApp.fetch(
      'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent',
      { method: 'post', contentType: 'application/json',
        headers: { 'x-goog-api-key': GEMINI_KEY }, muteHttpExceptions: true, payload: payload });
    if (res.getResponseCode() < 500) return JSON.parse(res.getContentText()).candidates[0].content.parts[0].text;
    Utilities.sleep(1000 * (i + 1));
  }
  return '';
}
function today() { return Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd'); }

数字と、正直な気持ち

お金の話を、体験の範囲だけで。AIの読み取りは、わたしの使い方ではGeminiの無料枠におさまっていて、かかった費用は0円です。個人がひとりで、思いついた予定をちょこちょこ放り込むくらいなら、無料枠で足りています。大量にさばく用途だと話は別なので、そこは正直に「わたしの場合は」と添えておきます。

気持ちのほうも正直に。いちばん変わったのは、「思い出した瞬間に、終わらせられる」こと。そしてもうひとつ、自分でも意外だったのは——予定を覚えておくために、頭のすみをずっと一区画あけておく、あの“常時稼働”がなくなったことでした。放り込めば仕組みが覚えていてくれる。その安心感が、思っていたよりずっと楽だったんです。

そして`2 5 7`→[はい]で予定がまとめて片づく瞬間の、机をさっと拭いたみたいな小さな爽快感。これだけは、ぜひ一度、自分の指で味わってほしいところです。

次回予告:「このアプリは確認されていません」が怖くなくなる回(第3回・許可画面)

ここまでで「話しかけて操作する」までは作れます。でも、その手前に、多くの人がそっとタブを閉じてしまう壁がひとつあります。「このアプリは確認されていません」という、あの許可画面です。

次回・第3回は、この許可画面だけをテーマにします。なぜあれが出るのか、なぜ進んで大丈夫なのか、アカウントの種類で画面がどう変わるのか。「怖くて押せない」を「なんだ、これでいいのか」に変える回にします。


このシリーズのこれまで:AI秘書「ぽけっと手帳」(全体像)第1回:紙の予定表を撮って送るだけ(写真→表)第2回:LINEに話しかけて予定を登録・一括削除(この記事)第3回:許可画面で気後れしないために第4回:毎朝・前夜・週1の自動通知第5回:カレンダー/フォトと連携(完結回)応用編その3:受発注・請求 秘書(受注が来たら、選ぶだけで見積書・請求書のPDFに)受発注 育てる編①:入金消込・督促・領収書PDF受発注 育てる編②:受注した瞬間に在庫を押さえる受発注 育てる編③:発注点アラートと発注書PDF受発注 育てる編④:月次集計・売上グラフ・会計CSV(最終回)基礎編:URLひとつで“自分の申込ページ”を作る(Webアプリ)基礎編:申し込まれた瞬間に、確認メールが自動で返る基礎編:申し込んだら、控えのPDFが自動で届く基礎編:申込の控えが、勝手にドライブへ積み上がっていく基礎編:申込のひとことを、届いた瞬間にAIが仕分ける基礎編:誰も画面を開いていないのに、毎朝まとめが届く基礎編:申込が来た瞬間、ポケットのLINEが鳴る(最終回)応用編その4:申込・予約 秘書(満席の瞬間、ページが自分から閉まる)申込・予約 育てる編①:キャンセル待ちと自動繰り上げ申込・予約 育てる編②:承認制とお断り下書き申込・予約 育てる編③:前日リマインダーと毎朝まとめ申込・予約 育てる編④:管理ページ(最終回)

#AI秘書 #ぽけっと手帳

コメント

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