Skip to content

Stuffing an RSS Subscription into the Bot

On this page

The Current Tools Are Too Hard to Use… So…

Section titled “The Current Tools Are Too Hard to Use… So…”

The forwarding bot I wrote earlier was originally just for receiving private messages. Because I needed ad blocking, I added LLM moderation. To save a bit of token, I casually added allowlists and blocklists. To prevent false bans, I added an appeal system. Then, with nothing better to do, I added i18n… oh crap, how did this get to nearly 2,000 lines of code? The RSS tool I use also happens to be pretty clunky, so why not stuff an RSS reader in there too? kfb, one day you’re going to become a bot with way too many features…

My requirements are very simple: when a friend’s (see the friend links) blog updates, send me a Telegram message. No need to make it into some full-blown feed reader (because I think current reader designs are all pretty ugly). A few commands to add, remove, update, and list RSS feeds, plus basic send/receive — that’s enough. So:

/rss_add # add
/rss_list # list
/rss_remove # remove
/rss_refresh # refresh
/rss_title # rename
RSS Bot Command List
RSS Bot Command List

Here’s a relatively ideal item:

<item>
<title>Article Title</title>
<link>https://example.com/post</link>
<guid>https://example.com/post</guid>
<description>Article summary</description>
<pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
</item>

However, every site generates things slightly differently. Some don’t have content:encoded (like Dongye’s blog doesn’t have one), some use the link as guid, and some use some mysterious ID as guid.

For example, here’s mine:

<item>
<title>test-2</title>
<link>https://kokosa.icu/zh-cn/blog/test-2/</link>
<guid isPermaLink="true">https://kokosa.icu/zh-cn/blog/test-2/</guid>
<description>This is a test article</description>
<pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
<content:encoded>
<p>114514 characters omitted...</p>
</content:encoded>
<category>Test</category>
</item>

I’ve always felt that stuffing the full article into RSS is incredibly stupid, so I’ve already changed this.


Pushing Old Posts — Enjoy Getting Flooded ()

Section titled “Pushing Old Posts — Enjoy Getting Flooded ()”

When adding an RSS feed for the first time, you definitely can’t just push all the articles at once — it’s a message bot after all. Some blog RSS feeds contain dozens of articles (and I’ve already read them all). Adding a subscription and flooding the whole screen would be a nightmare. So when first adding a subscription, it fetches the feed once and marks all current articles as read, but doesn’t send them. After that, scheduled refreshes only send notifications when new items are discovered:

[RSS] Kokosa's Blog
Stuffing an RSS Subscription into the Bot
https://kokosa.icu/zh-cn/blog/2-tgbot-rss.md
Summary: Added RSS subscription notifications to the forwarding bot, with scheduled refreshes, deduplication, and AI summaries.

A Worker isn’t a persistent service, so to do scheduled refreshes you need to write this thing in wrangler.toml:

[triggers]
crons = ["*/30 * * * *"]

It checks every 30 minutes. For easier testing, I added a manual command:

/rss_refresh

Then I realized that manual refreshes might collide with Cron, so it needs a KV lock. Otherwise two refresh tasks might both find “this article hasn’t been sent yet” and send it twice.

Notification after manually refreshing RSS
Notification after manually refreshing RSS

Since Gemini was already integrated, how could a notification contain nothing but a link?! So I made it summarize the article too. The content moderation timeout was previously set to only 4 seconds, because moderation just needs to output SAFE or UNSAFEwhereas summaries have a lot more to think about. So summaries get a separate 20-second timeout. If it fails, the worst that happens is:

Summary: AI summary unavailable.

The link still gets sent as usual.


After vibing through this at lightning speed, I realized: couldn’t I have used something like OpenClaw or Hermes to do this for me? (kfb, rise up… surpassing the lobster is only a matter of time…)