# 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化することができた。

Terminal window
// pm2のインストール
npm install -g pm2
// pm2でBotを起動
pm2 start {path}/index.js --env MASTODON_ACCESS_TOKEN={access_token}
// pm2で起動したBotを自動起動設定
pm2 startup
pm2 save

pm2 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にいろいろ聞いた。理解しきれていないので、カスタマイズしつつ理解を深めていきたい。


My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts

Comments