家にある本を、写真だけでObsidianに一括登録する仕組みを以前つくりました。
おかげで100冊以上の蔵書が、表紙付きの本棚としてObsidianに並びました。……が、しばらくして気づきました。
「本棚、作って満足して終わってないか?」
せっかく登録した本も、自分から開かなければ二度と目に入りません。積読は物理からデジタルに移動しただけでした。
そこで、本棚のほうから毎朝話しかけてくるようにしました。Obsidianの本棚からランダムに1冊選び、AIが紹介コメントを付けて、毎朝8時にSlackへ投稿する 仕組みです。
この記事では、その仕組みをどう作ったか、そして途中でハマった 「GitHub Actionsの定時実行(cron)が新規アカウントだと発火しない」 という落とし穴と回避方法までをまとめます。
ソースはこちらに公開しています。
作ったもの
毎朝、Slackの専用チャンネルにこんな投稿が流れます。
- 本棚(Obsidianのノート)から、まだ紹介していない本をランダムに1冊選ぶ
- タイトル・著者・概要をもとに、Claudeが2〜3文の紹介コメントを生成する
- 表紙画像付きでSlackに投稿する
- 同じ本の連投は避け、全部紹介し終えたら履歴をリセットする
Macの電源が入っているかどうかに関係なく、クラウド上(GitHub Actions)で毎朝勝手に動きます。
スレッドは「思い出す」練習に使う
投稿されたスレッドには、その本を読んで覚えている内容を、自分の言葉で少し書き出す ようにしています(上のスクリーンショットのスレッド返信がその例です)。
概要を読み返すのではなく、自分から思い出して書くことで、記憶に定着しやすくなります。毎朝流れてくる1冊が、ただの通知ではなく「軽い想起トレーニング(retrieval practice)」のきっかけになります。本棚に眠らせた本を、もう一度自分の頭の中に呼び戻す仕掛けです。
全体構成
Obsidianの本棚(Markdown)
│ sync-data.sh でコピー&push
▼
GitHubリポジトリ (data/Books, data/02_読書メモ)
│
▼
GitHub Actions で post_book.py を実行
├─ 本を1冊選ぶ
├─ Claude API で紹介文を生成
└─ Slack API で投稿(表紙アップロード)
ポイントは、Obsidianで書いた本のMarkdownノートを、そのままデータソースとして使う ことです。本棚のための新しいデータベースは作りません。前編で作った資産をそのまま活かします。
実装
本体は1つのPythonスクリプト post_book.py です。外部ライブラリは表紙変換用のPillowだけで、あとは標準ライブラリ(urllib)でAPIを叩いています。
1. 本棚のノートを読む
Obsidianの書籍ノートは、フロントマターに書誌情報を持っています。
---
title: 整える習慣
author: 小林弘幸
publisher: 日経BP
publishDate: "2021.2"
cover: Books/covers/9784532199951.webp
---
<!-- bookshelf-description:start -->
自律神経の名医が説く、心と体を「整える」ための習慣術…
<!-- bookshelf-description:end -->
これを正規表現でパースして、タイトル・著者・概要・表紙を取り出します。紙の本(Books)とKindleメモ(読書メモ)でスキーマが少し違うので、両方のキーに対応させています。
title = fm.get("title") or fm.get("kindle-title") or stem
author = fm.get("author") or fm.get("kindle-author", "")
# 表紙: ローカルのwebp(Books)を優先、無ければ公開URL(KindleのimageUrl)
2. まだ紹介していない本を選ぶ
投稿済みの本は posted.log に記録し、そこに無い本の中からランダムに選びます。全部紹介し終えたらログを空にして一巡します。
posted = set(open(HISTORY_FILE).read().split())
candidates = [p for p in notes if rel[p] not in posted]
if not candidates: # 全部紹介済みなら一周してリセット
candidates = notes
open(HISTORY_FILE, "w").close()
chosen = random.choice(candidates)
この posted.log は、実行のたびにGitHub Actionsがコミットして履歴として残します。クラウドで動いても「どこまで紹介したか」の状態が引き継がれるようにしています。
3. Claudeで紹介コメントを生成
概要をそのまま貼ると事務的なので、「本好きの同僚が毎朝おすすめしてくる」トーンの紹介文をClaudeに書いてもらいます。
prompt = (
"あなたは社内Slackで毎朝1冊の本を紹介する、本好きの同僚です。\n"
"以下の本について、読みたくなるような紹介コメントを日本語で2〜3文で書いてください。\n"
"・カジュアルで親しみやすい口調(絵文字は1個まで)\n"
"・概要の丸写しではなく、その本の面白さ・読む価値が伝わるように\n"
f"タイトル: {title}\n著者: {author}\n概要: {desc}\n"
)
Anthropic APIをそのまま urllib で叩いています(既定モデルは claude-opus-4-8、環境変数で変更可)。
4. Slackに表紙付きで投稿
表紙が手元にある本(Books)は、画像ファイルをSlackにアップロードします。Slackの新しいファイルアップロードは3段階です。
-
files.getUploadURLExternalでアップロード先URLを取得 - そのURLに画像をPOST
-
files.completeUploadExternalでチャンネルに投稿
Kindleメモのように表紙が公開URLしかない場合は、Block Kitの image accessoryでその画像を貼って投稿します。
これで、冒頭のような「今日の一冊」がSlackに流れます。
どう「毎朝」動かすか
ローカルのMacでcronを回すと、Macが寝ていると動きません。そこで GitHub Actions で動かすことにしました。
name: daily-book-post
on:
schedule:
- cron: "0 23 * * *" # 23:00 UTC = 08:00 JST のつもりだった
workflow_dispatch: {}
jobs:
post:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install --quiet pillow
- name: Post a book to Slack
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
run: python post_book.py
APIキーやトークンはコードに書かず、GitHubのSecretsに保存します。……ここまでは順調でした。
ハマったところ:scheduleのcronが一切発火しない
手動実行(workflow_dispatch)は成功し、Slackにもちゃんと投稿されます。ところが、schedule の定時実行が翌朝になっても一向に動きません。
$ gh run list --event schedule
(1件も出てこない)
cronの時刻ずれを疑って半日待っても、丸一日待っても、発火ゼロ。設定を何度も見直しました。
- cron構文は正しい(
0 23 * * *) - ワークフローはデフォルトブランチ(master)にある
- Actionsは有効、fork でもない
- 手動実行・pushトリガーは動く
どれも問題なし。設定は正しいのに schedule だけが動かない。原因はここでした。
$ gh api /user -q '.created_at'
2026-06-30T... # アカウント作成から数日
GitHubは、作成間もない新規アカウントの schedule(cron)による自動実行を事実上ブロックします。
定時cronはクリプトマイニング等の悪用の典型的な入口なので、GitHubは新規・未検証のアカウントに対してこれを制限します。人間が押す workflow_dispatch や push は許可されるのに、schedule だけ発火しないのはこのためでした。コードやYAMLのバグではなく、アカウント側の制限 だったのです。
ちなみに、この制限はリポジトリを public にすると緩みます。無料のActions枠も実質無制限になるので、公開して良い内容なら public 化が一番手っ取り早い解決策です。
解決:外部cronからworkflow_dispatchを叩く
workflow_dispatch(手動トリガー)は動くので、これを外部のcronサービスから毎朝叩けばいい という発想に切り替えました。GitHubのschedule制限を完全に回避できます。
使ったのは無料の cron-job.org です。毎朝8時に、GitHubのworkflow_dispatch APIへPOSTするだけ。
-
URL
https://api.github.com/repos/<owner>/<repo>/actions/workflows/daily.yml/dispatches -
Method:
POST -
Headers
Accept: application/vnd.github+json Authorization: Bearer <Fine-grained PAT / Actions: read & write> X-GitHub-Api-Version: 2022-11-28 -
Body
{"ref": "master"}
成功すると 204 No Content が返り、GitHub Actions上でワークフローが起動します。同じ呼び出しは手元でも確認できます。
$ gh api -X POST /repos/<owner>/<repo>/actions/workflows/daily.yml/dispatches -f ref=master
$ gh run list --limit 1
in_progress daily-book-post workflow_dispatch ...
使うトークンは、対象リポジトリだけに絞った Fine-grainedトークン(権限は Actions: Read and write のみ) にしておくと安全です。トークンに有効期限を付けた場合は、切れると止まるので再発行してヘッダを差し替えます。
これで、GitHubのschedule制限に左右されず、毎朝8時に確実にSlackへ「今日の一冊」が流れるようになりました。処理自体は引き続きGitHub Actions上で動くので、Macは寝ていても大丈夫です。
補足:Slackのプランについて
このbot自体は Slackの無料プランでも動きます。投稿に使う chat.postMessage も、表紙のファイルアップロード(files.getUploadURLExternal / completeUploadExternal)も、無料プランで利用できます。
ただし無料プランには、
- メッセージ履歴が直近90日まで(それ以前は遡れない)
- 連携アプリは10個まで
という制約があります。「今日の一冊」を過去ログとして蓄積して後から振り返りたい場合や、すでに他のアプリを多く連携している場合は、有料プランのほうが快適 です。毎朝の積み重ねを資産として残したいなら、有料プランを前提に運用するのがおすすめです。
まとめ
- 前編で作った Obsidianの本棚(Markdownノート)をそのままデータソース にして、毎朝「今日の一冊」をSlackに自動投稿する仕組みを作りました
- 本選び・AIによる紹介文・表紙付き投稿までを1つのPythonスクリプトにまとめ、GitHub Actionsで動かしています
-
新規GitHubアカウントでは
schedulecronが発火しない という落とし穴があります。手動実行だけ動いてscheduleが無反応なら、まずアカウントの作成日と、public化 or 外部cronでの回避を検討してください - 定時トリガーは、無料の外部cron(cron-job.org)から
workflow_dispatchAPIを叩くことで安定して動かせます - bot自体はSlackの無料プランでも動きますが、履歴保持(90日)やアプリ数の制約があるため、継続運用するなら有料プランが快適です
- 投稿スレッドに「覚えている内容」を書き出すことで、毎朝を軽い想起トレーニングの機会にできます
本棚は、作って終わりではなく「毎朝ふと目に入る」状態にして初めて活きてきました。積読を崩すきっかけを、仕組みのほうから毎朝届けてくれます。
家に眠っている本を、もう一度めくりたくなる仕組みを、あなたのSlackにも置いてみませんか?
