# PM2を使ってMastodonのBotを作成した
4 min read
Table of Contents
前回に引き続き、ソロインスタンスのローカルタイムラインをRSS Feedとして活用したいという思いで、今回は追加したアカウンをBotとして機能するようにした。
Botをどこで動かすのか
Mastodonを触るうえでディレクトリ構成に悩む。今回もどこで動かすか迷ったうえ、/home/mastodon/以下に作成することにした。
投稿する仕組みを作成する
import axios from "axios";import Parser from "rss-parser";import dotenv from "dotenv";import fs from "fs";
dotenv.config();
const parser = new Parser();const MASTODON_API_URL = "https://instanceURL/api/v1/statuses";const ACCESS_TOKEN = process.env.MASTODON_ACCESS_TOKEN;
const rssUrls = ["https://blog.nove-b.dev/index.xml"];
const POSTED_URLS_FILE = "posted_urls.json";
// 投稿済みURLを読み込むfunction loadPostedUrls(): Set<string> { try { if (!fs.existsSync(POSTED_URLS_FILE)) { console.log( `File ${POSTED_URLS_FILE} does not exist. Creating a new one.`, ); fs.writeFileSync(POSTED_URLS_FILE, JSON.stringify([], null, 2), "utf8"); console.log(`${POSTED_URLS_FILE} successfully created.`); } const data = fs.readFileSync(POSTED_URLS_FILE, "utf8"); return new Set(JSON.parse(data)); } catch (error) { console.error("Error loading posted URLs:", error); return new Set(); }}
// 投稿済みURLを保存するfunction savePostedUrls(postedUrls: Set<string>) { try { fs.writeFileSync( POSTED_URLS_FILE, JSON.stringify([...postedUrls], null, 2), "utf8", ); console.log(`Updated ${POSTED_URLS_FILE}`); } catch (error) { console.error("Error saving posted URLs:", error); }}
async function fetchAndPost() { try { const postedUrls = loadPostedUrls();
for (const url of rssUrls) { const feed = await parser.parseURL(encodeURI(url)); if (feed.items.length === 0) { console.log(`No items found in RSS feed: ${url}`); continue; }
let hasNewPost = false;
for (const post of feed.items) { if (!post.link || postedUrls.has(post.link)) { continue; }
const status = `🎉 ${post.title} 🎉\n🔗 ${post.link}`; try { const response = await axios.post( MASTODON_API_URL, { status, visibility: "public" }, { headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, "Content-Type": "application/json", }, }, ); console.log(`Successfully posted: ${post.title}`, response.data); } catch (error: any) { console.error( "Error posting to Mastodon:", error.response?.data || error.message, ); } postedUrls.add(post.link); hasNewPost = true; }
if (hasNewPost) { savePostedUrls(postedUrls); } } } catch (error) { console.error("Error fetching or posting RSS:", error); }}
// 定期実行(30分ごと)setInterval(fetchAndPost, 30 * 60 * 1000);
// 初回実行fetchAndPost();npx tscでビルドして、node dist/index.jsで実行で投稿されることを確認した。
PM2で永続化し、Botとして機能させる
PM2とは?
PM2 は、アプリケーションを 24 時間 365 日オンラインで管理および維持するのに役立つデーモン プロセス マネージャーです。 https://pm2.keymetrics.io/
これで常時、jsを動かしてBot化することができた。
// pm2のインストールnpm install -g pm2// pm2でBotを起動pm2 start {path}/index.js --env MASTODON_ACCESS_TOKEN={access_token}// pm2で起動したBotを自動起動設定pm2 startuppm2 savepm2 listで起動しているか確認することもできる。
┌────┬─────────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │├────┼─────────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤│ 1 │ rss-mastodon-bot │ default │ 1.0.0 │ fork │ 141470 │ 69s │ 0 │ online │ 0% │ 60.7mb │ root │ disabled │ちなみに削除は、pm2 delete rss-mastodon-botで実行できる。
Chat GPTにお世話になりすぎている
今回もまたChat GPTにいろいろ聞いた。理解しきれていないので、カスタマイズしつつ理解を深めていきたい。