🌟

Deno + Deno DeployでBlueskyへZennトレンドを定時投稿するbotを作成

2025/02/08に公開
  • JavaScript/TypeScriptランタイムのDenoには組み込みでcron機能がありスケジュール処理が容易に書くことができます。
  • また、記載されたコードをDeno Deployにデプロイしたら自動で設定した時刻に実行してくれます。
  • 今回はこの機能とBluesky APIを利用して、定時にBlueskyへZennトレンドを投稿するbotを作成する方法を記録します。

環境

  • macOS 13.6.9
  • deno 2.1.9

結果

  • Blueskyに以下のように定時に以下の投稿がされる。

image

手順

Bluesky情報の取得

Denoプロジェクトの構築

  • 任意のディレクトリで以下のコマンドで、Denoプロジェクトの作成およびgitの初期化を行います。
mkdir post-zenn-bluesky
git init
touch main.ts .env .gitignore
  • .gitignoreを以下の内容にします。
.env
  • .envファイルに先ほどメモしたBlueskyの情報を書き込みます。
BLUESKY_IDENTIFIER=xxxx.bsky.social
BLUESKY_PASSWORD=xxxx-xxxx-xxxx-xxxx
  • 以下のコマンドで必要なパッケージをインストールします。
deno add npm:@atproto/api jsr:@std/dotenv npm:fast-xml-parser

コードの実装

  • main.tsを以下の内容にしてください。
import AtprotoAPI from "@atproto/api";
import "@std/dotenv/load";
import { XMLParser } from "fast-xml-parser";

// 記事の型(必要部分のみ)
type Article = {
  title: string;
  link: string;
};

// レスポンスの型
type RSSResponse = {
  rss: {
    channel: {
      item: Article[];
    };
  };
};

// 定数
const CONFIG = {
  BLUESKY_SERVICE: "https://bsky.social",
  ZENN_FEED_URL: "https://zenn.dev/feed",
  CRON_SCHEDULE: "0 23 * * *", // 毎日8時
} as const;

const { AtpAgent, RichText } = AtprotoAPI;

// エージェント初期化
async function initAgent() {
  const agent = new AtpAgent({
    service: CONFIG.BLUESKY_SERVICE,
  });
  const identifier = Deno.env.get("BLUESKY_IDENTIFIER");
  const password = Deno.env.get("BLUESKY_PASSWORD");

  if (!identifier || !password) {
    throw new Error("必要な環境変数が設定されていません");
  }

  await agent.login({ identifier, password });
  return agent;
}

// トレンド記事取得
async function fetchZennTrends() {
  const res = await fetch(CONFIG.ZENN_FEED_URL);
  if (!res.ok) {
    throw new Error("Zenn記事取得できませんでした。");
  }
  const content = await res.text();
  const parser = new XMLParser();
  const data = parser.parse(content) as RSSResponse;
  return data.rss.channel.item;
}

// 投稿
async function createPost(agent: AtprotoAPI.AtpAgent, text: string) {
  const richText = new RichText({ text });
  await richText.detectFacets(agent);
  await agent.post({
    text: richText.text,
    facets: richText.facets,
    createdAt: new Date().toISOString(),
  });
}

// 投稿(毎日8時)
Deno.cron("post zenn trends", CONFIG.CRON_SCHEDULE, async () => {
  try {
    const agent = await initAgent();
    const trends = await fetchZennTrends();
    const firstTrends = trends.flatMap((trend) => [trend.title, trend.link])
      .slice(
        0,
        6,
      );

    const template = "今日のZennトレンド\n\n";
    const text = template + firstTrends.join("\n");
    await createPost(agent, text);
  } catch (e) {
    console.error(`投稿エラー: ${e}`);
    Deno.exit(1);
  }
});

ローカルでの実行

  • コード記述後、投稿できるか確認するため、上記のコードのDeno.cronの部分を近い時間帯にして、以下のコマンドでローカルでの確認をしてみます。
deno --unstable-cron --allow-read --allow-env --allow-net main.ts
  • 自身のBlueskyを開き、以下のように投稿されていることを確認します。

image

Githubへプッシュ

  • 確認後、Deno Deployでも実行するためGithubへプッシュします。
    • Deno DeployではGithubと連携すればリポジトリにプッシュしたら自動的にデプロイまで行ってくれます。
git add .
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/自身のユーザー名/リポジトリ名.git
git push -u origin main

Deno Deployへのデプロイ

  • Deno DeployにアクセスしてGithubアカウントでログインします。
  • ログイン後、「New Project」を開き、先ほどプッシュしたリポジトリと連携設定をして選択します。

image

  • 選択後、Entrypointにmain.tsを選び「Deploy Project」をクリックします。

image

  • 少し待ちチェックマークがつくと、デプロイ成功です。

実行時間の確認

  • デプロイ後、プロジェクトページの「Cron」ページを開き、コード内で設定した時間が自動的に設定されていることを確認します。

image

環境変数の設定

  • Deno Deploy側でも環境変数の設定が必要なので「Settings」を開きます。
  • 「Environment Variables」に.envファイルに記載の内容を設定します。

image

結果の確認

  • 設定した時間にBlueskyの自身のアカウントを開き、投稿されていることが確認できたら完了です。

image

まとめ

  • TypeScriptやDenoやモダンなクラウドシステムを活用することで、効率的で保守性の高い自動投稿システムを構築することができると感じました。

参考

Discussion