4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リマインドを無視すると鬼になるLINE Botを作った

4
Posted at

はじめに

iPhoneのリマインダーを使っている。細かくリマインドを設定している。そして毎回無視している。

問題はリマインダーそのものではなく、「無視するコストがゼロ」であることだと気づいた。通知を見て、スワイプして消す。この動作が0.3秒で完了してしまう。抵抗がなさすぎる。

ならば、無視するたびに鬱陶しさがエスカレートしていくBotを作ればいいのでは、と思った。タスクをやるより無視し続けるほうが辛い設計にする。行動経済学でいう「ナッジ」の考え方だ。

この記事では、LINE Messaging APIとNode.js(TypeScript)を使ってそのBotを作った過程を書く。詰まったところも正直に書く。


作ったもの

LINEでタスクを登録すると、設定した時刻に通知が来る。無視すると30分後にまた来る。さらに無視すると口調がだんだん強くなっていく。「やった」と送ると完了になり、翌日リセットされる。

ユーザー → 「追加: 筋トレ 毎朝8時」
Bot     → 「筋トレを登録しました。最初の通知:2026/5/13 8:00」

--- 翌朝8時 ---
Bot     → 「筋トレの時間です。やりましたか?」
ユーザー → (無視)

--- 30分後 ---
Bot     → 「筋トレ、まだですか?」

--- さらに30分後 ---
Bot     → 「いい加減にしてください。筋トレやってください。」

ユーザー → 「やった」
Bot     → 「筋トレ完了!お疲れ様です。次回は明日また声をかけます。」

技術スタック

役割 技術
サーバー Node.js + Express + TypeScript
DB Turso(SQLiteのクラウド版)
ORM Prisma 6
通知 LINE Messaging API
定期実行 node-cron
ホスティング Render(無料プラン)

Claude APIでメッセージ生成することも考えたが、APIの課金が必要だったので今回はエスカレーションメッセージを固定文にした。十分鬱陶しい。


ディレクトリ構成

nag-bot/
├── prisma/
│   └── schema.prisma
├── src/
│   ├── index.ts        # エントリーポイント
│   ├── webhook.ts      # LINEのWebhook処理
│   ├── scheduler.ts    # 定期チェック
│   ├── notifier.ts     # LINE通知送信
│   └── taskParser.ts   # コマンドのパース
├── .env
├── package.json
└── tsconfig.json

実装

DBスキーマ

model Task {
  id             Int       @id @default(autoincrement())
  userId         String
  title          String
  cronExpression String
  ignoreCount    Int       @default(0)
  nextNotifyAt   DateTime
  completedAt    DateTime?
  createdAt      DateTime  @default(now())
}

ignoreCountがエスカレーションのキー。無視するたびにインクリメントされ、メッセージの強度を決める。

エスカレーションロジック

const NAG_MESSAGES = [
  (title: string) => `「${title}」の時間です。やりましたか?`,
  (title: string) => `「${title}」、まだですか?`,
  (title: string) => `いい加減にしてください。「${title}」やってください。`,
  (title: string) => `本当にやる気あるんですか。「${title}」。`,
  (title: string) => `…(呆れ)。「${title}」。早くしてください。`,
];

const ESCALATION_INTERVALS = [30, 30, 60, 120]; // 分

1分ごとにcronで未完了タスクをチェックし、nextNotifyAtを過ぎていたら通知を送る。送るたびにignoreCountを増やし、次回通知時刻を設定する。

Tursoへの接続

import { PrismaLibSql } from "@prisma/adapter-libsql";
import { PrismaClient } from "@prisma/client";

const adapter = new PrismaLibSql({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
});

export const prisma = new PrismaClient({ adapter });

コマンドのパース

LINEで「追加: 筋トレ 毎朝8時」と送るとタスクが登録される。自然言語をcron式に変換している。

export function parseAddCommand(text: string): ParseResult {
  const match = text.match(/^追加[::]\s*(.+?)\s+(.+)$/);
  if (!match) return null;

  const title = match[1];
  const scheduleText = match[2];
  const cron = parseCronFromText(scheduleText);
  if (!cron) return null;

  return { title, cronExpression: cron.expression, nextNotifyAt: cron.nextDate };
}

現在対応しているのは「毎朝HH時」「毎晩HH時」「毎日HH時」の形式。


コマンド一覧

コマンド 動作
追加: タスク名 毎朝8時 タスク登録
やった 直近タスクを完了
削除: タスク名 タスクを削除
リスト 未完了タスク一覧

詰まったポイント

1. LINE Developers ConsoleのUI変更

以前はLINE Developers ConsoleからMessaging APIチャネルを直接作れたが、現在はLINE公式アカウントを先に作り、そこからMessaging APIを有効化する手順に変わっている。

手順:

  1. LINE Official Account Manager でアカウント作成
  2. 設定 → Messaging API → 「Messaging APIを利用する」
  3. するとLINE Developers Consoleにチャネルができるのでそこでトークンを発行

ドキュメントが追いついていない部分があるので注意。

2. Prisma 7の破壊的変更

npm install prismaすると最新のPrisma 7が入るが、Prisma 7ではschema.prisma内のurl指定が廃止されている。prisma.config.tsという新しいファイルで設定する必要がある。

今回はPrisma 6を明示的に指定してこの問題を回避した。

npm install prisma@6 @prisma/client@6

3. RenderでのTypeScript型エラー

ローカルでは@types/node@types/expressdevDependenciesに入っていても動くが、Renderのビルド時はdevDependenciesをインストールしないため型エラーになる。

package.jsonを直接編集してdependenciesに移動した。

4. LINE公式アカウントの自動応答

Botに友達追加した直後、メッセージを送ると「このアカウントでは個別のお問い合わせは受け付けておりません」という自動応答が返ってくる。

LINE Official Account Managerの「応答設定」→「応答メッセージ」をオフにすることで解決する。

5. RenderのSQLiteはサーバー再起動でリセットされる

Renderの無料プランはサーバーが再起動するたびにファイルシステムがリセットされる。SQLiteのDBファイルも消えるため、タスクが全部消えてしまう。

これを解決するためにTursoというSQLiteのクラウド版に移行した。Tursoは無料プランがあり、接続もPrismaのアダプター経由で簡単にできる。

# Turso CLIのインストール
brew install tursodatabase/tap/turso

# ログインとDB作成
turso auth login
turso db create nag-bot

# 接続情報の取得
turso db show nag-bot      # URL
turso db tokens create nag-bot  # 認証トークン

PrismaのアダプターはPrisma 6の@prisma/adapter-libsqlを使う。

npm install @prisma/adapter-libsql @libsql/client

注意点として、TursoはPrismaのmigrate deployコマンドが使えない。初回のテーブル作成はmigration.sqlを直接流す必要がある。

turso db shell nag-bot < prisma/migrations/XXXXXX_init/migration.sql

Renderのstart commandもnpx prisma migrate deploy && npm startからnpm startに変更する。

6. @prisma/adapter-libsqlをdependenciesに入れる

@prisma/adapter-libsql@libsql/clientをdevDependenciesに入れてしまうとRenderのビルド時に除外されてエラーになる。他の@types系パッケージと同様にdependenciesに入れること。


設計について

このBotの本質は「意志力に頼らない設計」だ。

一般的なリマインダーは「やるべきことを思い出させる」ことしかしない。それだけでは行動は変わらない。人間は面倒なことを後回しにするようにできている。

このBotは「無視し続けることの方が面倒」という状況を作り出す。ペインドリブンな設計だ。タスクをやるより無視する方が辛いなら、やった方が楽になる。

完璧な設計ではないし、Botをブロックすれば終わりだ。でも少なくとも自分には効いている。


今後やりたいこと

  • 週次サボりレポート:「今週の無視回数ランキング」を月曜朝に送る
  • Claude APIの導入:無視回数に応じて動的にメッセージを生成する
  • 複数ユーザー対応:チームで使えるようにする
  • スケジュール形式の拡充:「毎週月曜」「毎月1日」などに対応する

おわりに

自分の実際の困りごとを題材にした。
作ってみると意外と詰まるところが多く大変だった。
こういう躓きも糧にしていきたい。
今回はClaudeにだいぶ助けられた。
なんならこの記事も書いてもらっている。
AIってすごいですね。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?