1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Obsidianの本棚を活かして、毎朝Slackに「今日の一冊」を自動で流す仕組みを作った

1
Posted at

家にある本を、写真だけでObsidianに一括登録する仕組みを以前つくりました。

おかげで100冊以上の蔵書が、表紙付きの本棚としてObsidianに並びました。……が、しばらくして気づきました。

「本棚、作って満足して終わってないか?」

せっかく登録した本も、自分から開かなければ二度と目に入りません。積読は物理からデジタルに移動しただけでした。

そこで、本棚のほうから毎朝話しかけてくるようにしました。Obsidianの本棚からランダムに1冊選び、AIが紹介コメントを付けて、毎朝8時にSlackへ投稿する 仕組みです。

この記事では、その仕組みをどう作ったか、そして途中でハマった 「GitHub Actionsの定時実行(cron)が新規アカウントだと発火しない」 という落とし穴と回避方法までをまとめます。

ソースはこちらに公開しています。

作ったもの

毎朝、Slackの専用チャンネルにこんな投稿が流れます。

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段階です。

  1. files.getUploadURLExternal でアップロード先URLを取得
  2. そのURLに画像をPOST
  3. 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_dispatchpush は許可されるのに、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アカウントでは schedule cronが発火しない という落とし穴があります。手動実行だけ動いてscheduleが無反応なら、まずアカウントの作成日と、public化 or 外部cronでの回避を検討してください
  • 定時トリガーは、無料の外部cron(cron-job.org)から workflow_dispatch APIを叩くことで安定して動かせます
  • bot自体はSlackの無料プランでも動きますが、履歴保持(90日)やアプリ数の制約があるため、継続運用するなら有料プランが快適です
  • 投稿スレッドに「覚えている内容」を書き出すことで、毎朝を軽い想起トレーニングの機会にできます

本棚は、作って終わりではなく「毎朝ふと目に入る」状態にして初めて活きてきました。積読を崩すきっかけを、仕組みのほうから毎朝届けてくれます。

家に眠っている本を、もう一度めくりたくなる仕組みを、あなたのSlackにも置いてみませんか?

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?