Bluesky.bot

投稿を予約する

ボットを常に動かしておく必要はありません。もっとも簡単で確実な構成は、数分おきに実行され、時間になったものを投稿して終了する スクリプトです。

時間になったものだけを投稿する

スクリプトに予定表と、決まった実行間隔を持たせます。各実行では、前回の実行以降に 時刻を迎えた項目を投稿します。実行が時間どおりに行われる限り、二重投稿も投稿漏れも起きません。

scheduled.ts
// scheduled.ts: 15 分ごとに実行します(cron、GitHub Actions など)。
// 各実行で前回以降に時刻を迎えたものを投稿するので、二重投稿は起きません。
import { AtpAgent } from "@atproto/api";

const RUN_EVERY_MS = 15 * 60 * 1000;

const SCHEDULE = [
  { at: "2026-11-02T08:00:00Z", text: "Good morning! Today's forecast is up." },
  { at: "2026-11-02T18:00:00Z", text: "Evening update: clear skies tonight." },
];

const now = Date.now();
const due = SCHEDULE.filter((post) => {
  const at = Date.parse(post.at);
  return at <= now && at > now - RUN_EVERY_MS;
});

if (due.length > 0) {
  const agent = new AtpAgent({ service: "https://bsky.social" });
  await agent.login({
    identifier: process.env.BLUESKY_HANDLE ?? "",
    password: process.env.BLUESKY_APP_PASSWORD ?? "",
  });
  for (const post of due) {
    await agent.post({ text: post.text, createdAt: new Date().toISOString() });
    console.log(`Posted: ${post.text}`);
  }
} else {
  console.log("Nothing due.");
}

Python では次のようになります。

scheduled.py
# scheduled.py: 15 分ごとに実行します(cron、GitHub Actions など)。
# 各実行で前回以降に時刻を迎えたものを投稿するので、二重投稿は起きません。
import os
from datetime import datetime, timedelta, timezone

from atproto import Client

RUN_EVERY = timedelta(minutes=15)

SCHEDULE = [
    ("2026-11-02T08:00:00+00:00", "Good morning! Today's forecast is up."),
    ("2026-11-02T18:00:00+00:00", "Evening update: clear skies tonight."),
]

now = datetime.now(timezone.utc)
due = [
    text
    for at, text in SCHEDULE
    if now - RUN_EVERY < datetime.fromisoformat(at) <= now
]

if due:
    client = Client()
    client.login(os.environ["BLUESKY_HANDLE"], os.environ["BLUESKY_APP_PASSWORD"])
    for text in due:
        client.send_post(text)
        print(f"Posted: {text}")
else:
    print("Nothing due.")

予定が変わる場合は、コードではなくファイルやデータベースから読み込み、投稿済みのものを記録しておきましょう。遅れた実行や重複した実行でも二重投稿を防げます。

cron で実行する

自分で管理しているサーバーなら、crontab -e で 1 行追加します(15 分ごと)。

bash
*/15 * * * * cd /home/bot/my-bot && BLUESKY_HANDLE=weather.bluesky.bot BLUESKY_APP_PASSWORD=$(cat ~/.bluesky-app-password) npx tsx scheduled.ts >> bot.log 2>&1

GitHub Actions で実行する

ボットのリポジトリに .github/workflows/post.yml を追加し、認証情報をリポジトリのシークレットとして保存します(Settings → Secrets and variables → Actions)。

yaml
name: Post
on:
  schedule:
    - cron: "*/15 * * * *"
  workflow_dispatch:
jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npx tsx scheduled.ts
        env:
          BLUESKY_HANDLE: ${{ secrets.BLUESKY_HANDLE }}
          BLUESKY_APP_PASSWORD: ${{ secrets.BLUESKY_APP_PASSWORD }}

GitHub はスケジュールされたワークフローをベストエフォートで実行します。実行が遅れたり、負荷が高いときにはスキップされたりすることがあります。スクリプトの実行間隔はスケジュールと同じにしておき、正確な分単位の時刻には頼らないでください。

制限を守る

  • 人間らしいペースで投稿しましょう。タイムラインを埋め尽くすボットは、ミュートや通報、レート制限の対象になります。
  • サインインは投稿ごとではなく、実行ごとに 1 回にします。サインインには、より厳しい専用の制限があります。
  • 活発なボットを動かす前に、Bluesky のレート制限 を読んでおきましょう。