RSS bot: populate channel on first run so joiners see content

Previously the bot seeded all existing feed items on startup WITHOUT posting, so a
freshly-created channel stayed empty and new subscribers saw nothing (only items
appearing after start were posted). Now on first run it posts the latest items
(max 5) to fill the channel — recent history then shows them to joiners — and sets
an rss_populated flag so restarts don't replay. Existing (empty) bots get filled
once on next start. Update rss_test.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-06-05 22:02:15 +01:00
parent a43061e096
commit 7cda767408
2 changed files with 27 additions and 11 deletions

View File

@@ -492,8 +492,13 @@ async def _rss_ensure_channel(profile_id: int, b: "RunningBot", chat: Any, user_
return None return None
async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict, seed: bool) -> None: async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict,
"""Fetch the feed; on the seed run just record existing ids, otherwise broadcast new items.""" seed: bool, max_post: int | None = None) -> None:
"""Fetch the feed and broadcast items not seen before.
seed=True → only record ids (no posting), used on restart so we don't replay.
max_post=N → post at most the N latest new items (used to populate a fresh channel
without dumping the bot's entire backlog)."""
url = (config.get("feed_url") or "").strip() url = (config.get("feed_url") or "").strip()
if not url: if not url:
return return
@@ -505,18 +510,19 @@ async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict, s
return return
new = [e for e in entries if e["id"] not in b.rss_seen] new = [e for e in entries if e["id"] not in b.rss_seen]
for e in new: for e in new:
b.rss_seen.add(e["id"]) b.rss_seen.add(e["id"]) # mark all seen, even if we only post the latest few
if seed: if seed:
_append_log(b, f"RSS seeded {len(entries)} existing item(s)") _append_log(b, f"RSS seeded {len(entries)} existing item(s)")
return return
if not gid or not new: if not gid or not new:
return return
for e in reversed(new): # post oldest → newest to_post = new if max_post is None else new[:max_post] # new is newest-first
for e in reversed(to_post): # post oldest → newest
try: try:
await chat.api_send_text_message({"chatType": "group", "chatId": gid}, _rss_format(e)) await chat.api_send_text_message({"chatType": "group", "chatId": gid}, _rss_format(e))
except Exception: except Exception:
log.exception("rss: failed to post to channel") log.exception("rss: failed to post to channel")
_append_log(b, f"RSS posted {len(new)} new item(s)") _append_log(b, f"RSS posted {len(to_post)} item(s)")
async def _run_bot( async def _run_bot(
@@ -617,7 +623,16 @@ async def _run_bot(
rss_poll_s = float(config.get("poll_seconds", 300)) rss_poll_s = float(config.get("poll_seconds", 300))
if bot_type == "rss": if bot_type == "rss":
b.rss_gid = await _rss_ensure_channel(profile_id, b, chat, user_id, name, config) b.rss_gid = await _rss_ensure_channel(profile_id, b, chat, user_id, name, config)
await _rss_poll(b, chat, b.rss_gid, config, seed=True) if b.rss_gid and not config.get("rss_populated"):
# first run for this feed: fill the channel with the latest items so new
# subscribers see content (recent history). Done once, then flagged.
await _rss_poll(b, chat, b.rss_gid, config, seed=False, max_post=5)
config["rss_populated"] = True
import db as _db
_db.update_config(profile_id, config)
else:
# already populated on a previous run — just record current ids, don't replay
await _rss_poll(b, chat, b.rss_gid, config, seed=True)
b.rss_next_poll = time.time() + rss_poll_s b.rss_next_poll = time.time() + rss_poll_s
await refresh() await refresh()

View File

@@ -100,11 +100,12 @@ async def main() -> int:
print("channel created:", bool(gid), "gid", gid) print("channel created:", bool(gid), "gid", gid)
assert gid, "rss bot did not create a channel" assert gid, "rss bot did not create a channel"
# 2) the first item was seeded, not posted # 2) first run populates the channel with the existing item(s)
await asyncio.sleep(2) got_initial = await wait_until(
texts = await channel_texts(b.chat, gid) lambda: _contains(channel_texts(b.chat, gid), "First post"), timeout=20, every=2
assert not any("First post" in t for t in texts), "seeded item was wrongly broadcast" )
print("seed OK (first item not broadcast)") print("initial item populated to channel:", bool(got_initial))
assert got_initial, "channel was not populated on first run"
# 3) add a new item → it should be broadcast on the next poll # 3) add a new item → it should be broadcast on the next poll
FEED["items"].insert(0, ("Breaking news", "https://example.com/2")) FEED["items"].insert(0, ("Breaking news", "https://example.com/2"))