前回に引き続き、ソロインスタンスのローカルタイムラインを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 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にいろいろ聞いた。 理解しきれていないので、カスタマイズしつつ理解を深めていきたい。