チームパスワードが必要です

nia Reminderと同じチームパスワードを入力してください。

Specification

nia Reminder 仕様書

自然な日本語でLINE/Chatworkへリマインダーを送るWebアプリ「nia Reminder」のCloudflare版(現行本番)の詳細仕様書。実際のソースコードを精査して作成している。

対象 cloudflare-app (GAS版から完全移行済み) 本番URL nia-reminder.pages.dev ソース GitHub private repo: nia-reminder-cloudflare

0概要

スマートフォン/PCから自然文で「いつ・誰に・何を」送るかを入力するだけで、指定日時にLINEまたはChatworkへ自動でメッセージを送信するWebアプリ。元はGoogle Apps Script(GAS) + Google Sheetsで構築されていたが、現在はCloudflare Pages + D1へ完全移植されている。

  • 「明日の11:45にリーダーCWへ締め直しお願いします」のような自然文を解析し、送信先・日時・繰り返しを自動抽出する
  • 送信先(Destination)はLINE Group ID / Chatwork Room IDを直接持たず、必ず内部IDを経由する(グループ作り直し時もリマインダーの再作成が不要)
  • 送信失敗は自動リトライ(即時→1分後→5分後)し、3回失敗で確定的に失敗扱いとする
  • LINEグループの退出/解散は、Webhookのleaveイベントと6時間毎の定期ヘルスチェックの両方で検知する

1技術スタック

領域技術備考
フロントエンド素のHTML + CSS + JavaScriptフレームワーク不使用。public/index.html(9画面) + public/pending.html
バックエンドAPICloudflare Pages Functionsfunctions/api.js。/apiへの単一POSTエンドポイント(RPC方式)
データベースCloudflare D1(SQLite互換)5テーブル
定期実行・Webhook独立したCloudflare Worker(notify-worker/)1分毎cron・6時間毎cron・毎朝6:00 cron・LINE Webhook受信を1つに統合
認証Web Crypto API(HMAC-SHA256)チームパスワード + 署名付きトークン方式
外部連携LINE Messaging API / Chatwork APIトークンはCloudflare Secretsで管理

2アーキテクチャ

リクエストは3系統。(A) ブラウザ→Pages Functions→D1 のオンデマンドAPI、(B) notify-worker→D1/外部API の定期実行、(C) LINE→notify-worker→D1 のWebhook受信。全てD1(nia-reminder-db)を共有する。

(A) オンデマンドAPI

Browserpublic/index.html
→ fetch('/api') →
Pages Functionsfunctions/api.js
→
D1nia-reminder-db

(B) Scheduler(1分毎)/ HealthCheck(6時間毎)

Cron Trigger* * * * *
0 */6 * * *
→
notify-workerscheduled()
→
D1 + LINE/Chatwork API

(C) LINE Webhook

LINEjoin/leave
→
notify-worker/webhook(fetch)
→
D1WebhookEvents/Destinations
設計判断: GASのLockService(スクリプト全体ロック)は、D1の行単位CAS(UPDATE Reminders SET status='PROCESSING' WHERE status='SCHEDULED'、影響行数1件で成功とみなす)に置き換えている。二重送信しないという挙動は同じだが、GASより粒度が細かい。

3データモデル

D1(SQLite)上の5テーブル。ReminderからLINE Group ID / Chatwork Room IDを直接参照せず、必ずDestinationを経由する構造を維持している(最重要設計原則)。

Reminders

列内容
reminder_idPK。RMD_yyyyMMdd_XXXXXX
destination_id送信先(Destinations.destination_id)
scheduled_at / next_run_at+09:00固定のISO文字列。次回はrecurrenceから計算
statusDRAFT/SCHEDULED/PROCESSING/SENT/CANCELLED/FAILED/PAUSED_DESTINATION
recurrence_type / recurrence_ruleONCE/DAILY/WEEKDAYS/WEEKLY/MONTHLY。WEEKLY/MONTHLYはJSON文字列で詳細を持つ
failed_count連続失敗回数としてリトライ管理に使い回す(専用カラムを増やさない設計)
request_id二重登録防止用の一意キー

Destinations / DeliveryLogs / WebhookEvents / Settings

テーブル役割
Destinations送信先マスタ。channel(LINE/CHATWORK)・external_id・status(ACTIVE/DISCONNECTED/DISABLED)
DeliveryLogs追記専用の送信結果ログ(SUCCESS/FAILED/RETRY/SKIPPED)
WebhookEventsLINE Webhookイベントの記録。event_idで二重処理防止
Settings現行仕様では未使用(将来用に予約)

4認証・権限モデル

GAS版はGoogle Workspaceドメイン限定のWebアプリで保護されていたが、Cloudflareには相当機能が無いため、nia-shiftと同じ「チームパスワード + 署名付きトークン」方式(functions/lib/auth.js)に置き換えている。

  • 共有パスワード(ACCESS_PASSWORD)をverifyTeamAccessに渡し、成功するとHMAC-SHA256署名付きトークン(有効期限6時間)を発行
  • 全APIハンドラがteamTokenを必須引数として検証する(_assertTeamAccess)
  • public/pending.html・public/spec.html・public/infra-map.html・public/structure.htmlも同じゲートで保護
  • Ver.1は管理者利用前提(個人ログイン・RBACは無し)。将来USER/LEADER/MANAGER/OWNERへ拡張可能な構造にはしてある

5API リファレンス

POST /api に {"fn": "関数名", "args": [teamToken, ...]} を送るRPC方式。応答は{ok:true, result} / {ok:false, error:{code, message}}。

関数認可概要
verifyTeamAccess公開チームパスワード検証・トークン発行
parseReminderTextチーム自然文解析(RuleParser→AIParser)
previewReminderチーム保存せず確認画面表示用の整形のみ
createReminderチーム登録。request_idで二重登録防止
getReminder / listRemindersチーム取得・一覧(statusフィルタ)
updateReminder / cancelReminderチーム編集・キャンセル
listDestinations / getDestination / createDestinationチーム送信先の一覧・取得・手動登録
listPendingLineGroupsチーム未登録の新規LINEグループ一覧(pending.html用)
registerPendingLineGroupチーム検知済みグループへ名前を付けてDestination登録
getUsageStatusチームD1ストレージ使用量・LINE月間メッセージ枠の最新値を返す(保存済みの値を読むだけ)
getRawDumpチーム指定テーブルの生データ(先着50件)。設定画面のDBダンプ用
getRawDumpTablesチームダンプ可能なテーブル名一覧(許可リスト)

5.1使用量チェック(D1ストレージ・LINE月間メッセージ枠)

notify-workerの3つ目のcron(毎朝6:00 JST = 21:00 UTC、functions/lib/usageCheckService.js)が2つの値を定期チェックし、Settingsテーブルへ保存する。設定画面はこの保存済みの値をgetUsageStatus経由で読むだけで、外部API認証情報はPages Functions側には一切持たせない。

  • D1ストレージ: Cloudflare管理API(GET /accounts/{id}/d1/database/{id})のfile_sizeを取得し、5GB(想定の無料枠上限)に対する割合を計算する。専用のAPIトークン(Account>D1>Read権限のみ)をCF_API_TOKENとしてnotify-workerにのみ設定する
  • LINE月間メッセージ枠: GET /v2/bot/message/quota(上限。typeがlimitedならvalueに上限数)とGET /v2/bot/message/quota/consumption(当月の送信済み数)の2本を叩き、割合を計算する。従量課金プラン等で上限が無い(type: 'none')場合はパーセント表示をしない
どちらも取得に失敗しても例外を投げず、その回はスキップされるだけ(補助的な監視機能のため、本体機能を止めない設計)。

6自然言語Parser

functions/lib/ruleParserService.js。信頼度スコア(初期値0.5)を各段階で加減点しながら、繰り返し→日付→時刻→午前午後→送信先の順に抽出する。

  • 日付: 今日/明日/明後日(各+0.15)、○月○日(+0.2、過去日付は翌年に繰り上げ)、曜日指定(来週/今週プレフィックス対応、+0.18)
  • 時刻: HH:mm・○時○分(+0.25)、曖昧語「朝/昼/夕方/夜」(+0.05、警告付きで仮確定)、特定不可ならデフォルト9:00(-0.2)
  • 送信先: 文中に登録済み送信先名が複数マッチしたら最長一致を優先(+0.2)。同点複数なら警告付きで仮確定(-0.1)
  • 信頼度がPARSER_CONFIDENCE_THRESHOLD(0.6)未満だとAIParserへフォールバック(現状スタブのみ、未実装)
曜日変換はruleParserService.js(JS Date基準 0=日〜6=土)とrecurrenceService.js(ISO基準 1=月〜7=日)で別実装。意図的に統一していない(GAS版からの移行時に挙動を変えないため)。

7Scheduler・Retry

functions/lib/schedulerService.js。notify-workerの1分毎cronからrunScheduler(db, env)が呼ばれる。

  1. findDueReminders: status=SCHEDULED AND next_run_at<=現在時刻
  2. 各リマインダーをD1 CASでPROCESSINGへ(競合時はスキップ)
  3. 送信先ACTIVE確認 → 送信 → 結果に応じてSENT/SCHEDULED(次回計算)/リトライ/FAILED
  4. 送信先レベルのエラー(LINE_TARGET_ERROR/CHATWORK_PERMISSION_ERROR)はdisconnectDestinationを自動発火

リトライ: failed_countを連続失敗回数として使い回す。1回目失敗→1分後、2回目失敗→5分後、3回目でFAILED確定。

8Webhook・HealthCheck

  • POST /webhook: x-line-signatureをHMAC-SHA256で検証(functions/lib/lineSignature.js)。失敗イベントは処理しない
  • join: グループ名取得を試み、WebhookEventsへ記録。Chatwork通知(任意設定)
  • leave: 対象DestinationをDISCONNECTED化し、関連するSCHEDULEDなReminderをPAUSED_DESTINATIONへ
  • 6時間毎のrunHealthCheck: ACTIVEな送信先へ生死確認APIを叩き、不健全ならdisconnectDestinationと同じ処理を実行(Webhookに依存しない補完的検知)

9フロントエンド

public/index.html(1ファイルSPA)。9画面: ホーム/解析確認/登録完了/予定一覧/詳細/編集/送信先一覧/送信先追加/設定。送信先セレクトはLINE/Chatworkでoptgroupにグループ化。public/pending.htmlは独立ページ(Chatwork通知からの導線を素早く保つため)。

「設定」画面には、未登録LINEグループ・仕様書/運用基盤マップ/ソースコード構成へのリンクに加え、使用量(D1ストレージ・LINE月間メッセージ枠。色分け表示、70%で黄・90%で赤)とDBダンプ(テーブル選択→先着50件を表形式で表示)を配置している。

10開発の経緯

GAS + Google Sheets(Ver.1完成) → 一部機能(LINE Webhook)のみCloudflare Workers化 → 今回、GASを完全廃止してCloudflare Pages + D1へ全面移行。観測可能な挙動(API入出力・parserの解析結果・retry/recurrenceの計算)は変えず、GAS特有API(LockService・PropertiesService)のみ同等のCloudflare実装に置き換えた。テストはGAS版のほぼ全ケースを移植(108件、Node標準テストランナーのみで実行可能)。

11既知の制限

  • AIParserServiceは骨組みのみ(実プロバイダ未実装)。RuleParserの信頼度が低い場合は警告を付けてそのまま返す
  • MONTHLY繰り返しは1〜28日のみ許可(29〜31日は月によって存在しないため)
  • 個人ログイン・RBACは未実装(Ver.1は管理者利用前提)
  • Settingsテーブルは現行仕様でも未使用

本仕様書は実際のソースコード(functions/, public/, notify-worker/)を精査して作成。コードと差異がある場合はコードを正とする。関連: 運用基盤マップ / ソースコード構成