23
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

n8nで「あとで読む/見る」をLINEで管理する仕組みを作ってみた

23
Posted at

はじめに

あとで読みたい記事、こんなことありませんか?

  • とりあえずURLだけ保存して埋もれる
  • ブックマークしても見返さない
  • YouTubeもnoteもバラバラ

LINEに送るだけで管理できる仕組みを作りました。


作ったもの

LINEにURLを送るだけで

  • 保存が一瞬で終わる
  • 見返すきっかけが自動で来る
  • 読み終わったら一言で整理できる

ようにしました。


完成イメージ

登録

LINEで下記のようにURLを送るだけです。

https://note.com/xxx


するとスプレッドシートに登録され、下記のようなメッセージが返信されます。

登録しました。
管理番号:001
登録日時:2026/04/10 19:30:00
URL:https://note.com/xxx

完了したら「001完了」と送ってください。

定期通知

毎日12時に、対象URLのリマインドがLINEに届きます。

こちらどうですか?
登録日時:2026/04/10 19:30:00
管理番号:001
URL:https://note.com/xxx

読み終わったら「001完了」と送ってください。

完了処理

URLを読み終わって管理対象から外したい場合は、下記のように「管理番号 + 完了」と送ります。

001完了


すると登録情報のステータスが更新され、完了メッセージが返信されます。
更新されたURLは、以後リマインドの対象外になります。

完了に更新しました。
管理番号:001
完了日時:2026/04/10 21:00:00

使用技術

今回使用した技術は以下の通りです。

  • n8n
  • LINE Messaging API
  • Google Sheets

全体構成

LINE → Webhook → n8n
    ↓
・URLなら登録
・完了なら更新
    ↓
Google Sheetsに保存または更新
    ↓
LINE返信

+

Schedule Trigger → ランダム通知

スプレッドシート設計

管理番号 URL 登録日時 ステータス 完了日時
001 https://... 2026/04/10 19:30:00 todo

実装(n8n)

今回作成したワークフローはこちら👇
image.png


① LINE受信

Webhookで受信したLINEイベントから、必要な情報だけを取り出します。

ポイント

  • replyToken
  • userId
  • text

を取り出す

const event = $json.body?.events?.[0] ?? $json.events?.[0];

if (!event) {
  return [];
}

return [{
  json: {
    replyToken: event.replyToken ?? "",
    userId: event.source?.userId ?? "",
    text: (event.message?.text ?? "").trim()
  }
}];

② URL or 完了の判定

受信したテキストがURL登録なのか、完了更新なのかを判定して後続処理を分岐します。

const text = ($json.text || "").trim();

const urlMatch = text.match(/https?:\/\/[^\s]+/i);
const completeMatch = text.match(/^(\d{1,})\s*完了$/);

let mode = "unknown";
let url = "";
let managementId = "";

if (urlMatch) {
  mode = "register";
  url = urlMatch[0];
} else if (completeMatch) {
  mode = "complete";
  managementId = completeMatch[1].padStart(3, '0');
}

return [{
  json: {
    ...$json,
    mode,
    url,
    managementId
  }
}];

③ Switchノード

  • register → 登録フロー
  • complete → 完了フロー

image.png

▼ 登録フロー

④ 登録済み情報取得(Google Sheets)

登録済みの情報を検索条件なしで全件取得します。
このあと管理番号を連番で採番するために使います。

⑤ 登録情報作成(Codeノード)

const items = $input.all();

let max = 0;

for (const item of items) {
  const num = Number(item.json["管理番号"]);
  if (Number.isFinite(num) && num > max) {
    max = num;
  }
}

const next = max + 1;
const managementId = String(next).padStart(3, '0');

const registeredAt = new Date().toLocaleString("ja-JP", {
  timeZone: "Asia/Tokyo",
  hour12: false
});

return [{
  json: {
    managementId,
    url: $('URL or 完了の判定').first().json.url,
    registeredAt,
    status: "todo",
    doneAt: ""
  }
}];

⑥ スプレッドシートに保存

スプレッドシートの下記カラムに情報を登録します。

  • 管理番号
  • URL
  • 登録日時
  • ステータス = todo

image.png


⑦ LINE返信

{
  "replyToken": "{{ $('URL or 完了の判定').item.json.replyToken }}",
  "messages": [
    {
      "type": "text",
      "text": "登録しました。\n管理番号:{{$json.managementId}}\n登録日時:{{$json.registeredAt}}\nURL:{{$json.url}}\n完了したら「{{$json.managementId}}完了」と送ってください。"
    }
  ]
}

▼ 完了フロー

⑧ 管理番号を条件に対象データを取得(Google Sheets)

完了メッセージで送られてきた管理番号をもとに、対象の行を取得します。
image.png

⑨ 更新情報作成(Codeノード)

const input = $input.first().json;

const doneAt = new Date().toLocaleString("ja-JP", {
  timeZone: "Asia/Tokyo",
  hour12: false
});

return [{
  json: {
    ...input,
    status: "done",
    doneAt
  }
}];

⑩ ステータス更新

管理番号を条件にスプレッドシートの下記カラムを更新します。

  • ステータス → done
  • 完了日時

image.png


⑪ LINE返信

{
  "replyToken": "{{ $('URL or 完了の判定').item.json.replyToken }}",
  "messages": [
    {
      "type": "text",
      "text": "完了に更新しました。\n管理番号:{{$json.管理番号}}\n完了日時:{{$json.doneAt}}"
    }
  ]
}

▼ 定期通知フロー

⑫ Schedule Trigger

Schedule Triggerで毎日12時に実行します。

image.png

⑬ todoのみ取得(Google Sheets)

ステータスが todo のデータだけを取得します。
image.png

⑭ ランダム抽出(Codeノード)

const items = $input.all();

if (!items.length) {
  return [];
}

const randomIndex = Math.floor(Math.random() * items.length);

return [items[randomIndex]];

⑮ LINE push送信

ランダム抽出したURLを、指定の文言でリマインドします。

{
  "to": "ユーザーID",
  "messages": [
    {
      "type": "text",
      "text": "こちらどうですか?\n登録日時:{{$json['登録日時']}}\n管理番号:{{$json['管理番号']}}\nURL:{{$json['URL']}}"
    }
  ]
}

工夫したポイント

① 管理番号は短く(重要)

長いIDは打ち込むのがめんどくさいため

001
002
003

にしました。


② UX最優先

ユーザー操作はこれだけ

  • URL送る
  • 001完了 と送る

③ ランダム通知で「見る機会」を作る

ただ保存するだけだと見ないので定期的にリマインドするようにしました


ハマりポイント

JSONエラー

LINE APIはJSONが厳密なので

  • ダブルクォート抜け
  • カンマ抜け

ですぐエラーになります。

まとめ

  • ユーザーの操作をシンプルにすることで、使いやすくしました。
  • 複数のサービスのURLを一元管理でき、自動リマインドによって見返すきっかけも作れます

おまけ(今後やりたい)

シンプルで最低限を心がけましたが、下記の追加機能はあってもよいと感じました。

  • 「未完了一覧」コマンド
  • 重複URLチェック

23
7
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
23
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?