From 7cda767408da3422e7fa6a097e336ef238125dec Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 5 Jun 2026 22:02:15 +0100 Subject: [PATCH] RSS bot: populate channel on first run so joiners see content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- manager/profiles.py | 27 +++++++++++++++++++++------ manager/rss_test.py | 11 ++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/manager/profiles.py b/manager/profiles.py index 19f248a..6f63e52 100644 --- a/manager/profiles.py +++ b/manager/profiles.py @@ -492,8 +492,13 @@ async def _rss_ensure_channel(profile_id: int, b: "RunningBot", chat: Any, user_ return None -async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict, seed: bool) -> None: - """Fetch the feed; on the seed run just record existing ids, otherwise broadcast new items.""" +async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict, + 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() if not url: return @@ -505,18 +510,19 @@ async def _rss_poll(b: "RunningBot", chat: Any, gid: int | None, config: dict, s return new = [e for e in entries if e["id"] not in b.rss_seen] 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: _append_log(b, f"RSS seeded {len(entries)} existing item(s)") return if not gid or not new: 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: await chat.api_send_text_message({"chatType": "group", "chatId": gid}, _rss_format(e)) except Exception: 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( @@ -617,7 +623,16 @@ async def _run_bot( rss_poll_s = float(config.get("poll_seconds", 300)) if bot_type == "rss": 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 await refresh() diff --git a/manager/rss_test.py b/manager/rss_test.py index 5a4d799..06eda5f 100644 --- a/manager/rss_test.py +++ b/manager/rss_test.py @@ -100,11 +100,12 @@ async def main() -> int: print("channel created:", bool(gid), "gid", gid) assert gid, "rss bot did not create a channel" - # 2) the first item was seeded, not posted - await asyncio.sleep(2) - texts = await channel_texts(b.chat, gid) - assert not any("First post" in t for t in texts), "seeded item was wrongly broadcast" - print("seed OK (first item not broadcast)") + # 2) first run populates the channel with the existing item(s) + got_initial = await wait_until( + lambda: _contains(channel_texts(b.chat, gid), "First post"), timeout=20, every=2 + ) + 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 FEED["items"].insert(0, ("Breaking news", "https://example.com/2"))