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"))