From 82a568efc56feb4c6b76bff25a270536aed6da96 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:03:32 +0200 Subject: [PATCH] fix(bot): post the version notice in the group when no admin can be reached in private (0.6.1) --- docs/DESIGN.md | 7 ++++--- supercycletracker/__init__.py | 2 +- supercycletracker/bot.py | 14 ++++++++------ tests/test_bot.py | 11 ++++++++++- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 75ebd6f..7be8163 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1105,9 +1105,10 @@ with the same `{placeholders}`; every Spanish string containing `?` also contain user_id=)`; either one (privacy mode off, or status `administrator`/`creator`) turns on the privacy-off rule (4.3), and the log line says which. - After the menu, a version the KV's `announced_version` does not hold yet is announced in private to - each admin of the profile (the group when it names none): «Nueva versión en marcha: 0.6.0 (antes - 0.5.0).». The version is stored once Telegram took at least one of those messages, so a failed send - is tried again at the next start and a restart on the same version says nothing. + each admin of the profile: «Nueva versión en marcha: 0.6.0 (antes 0.5.0).». It goes to the group + when the profile names no admin or Telegram refuses every private message (403 for an admin who never + opened a private chat with the bot). The version is stored once Telegram took a message, so a failed + send is tried again at the next start and a restart on the same version says nothing. - `download(file_path, limit)` fetches a file `getFile` returned from `https://api.telegram.org/file/bot/`. Like `call`, its errors are `TelegramError("download", code, description)` with no URL in them; more than `limit` bytes raises diff --git a/supercycletracker/__init__.py b/supercycletracker/__init__.py index 2cb063a..96ba45e 100644 --- a/supercycletracker/__init__.py +++ b/supercycletracker/__init__.py @@ -1,3 +1,3 @@ """SuperCycleTracker: a menstrual cycle tracker that lives in a Telegram group.""" -__version__ = "0.6.0" +__version__ = "0.6.1" diff --git a/supercycletracker/bot.py b/supercycletracker/bot.py index 6062532..c5b7d70 100644 --- a/supercycletracker/bot.py +++ b/supercycletracker/bot.py @@ -641,10 +641,11 @@ def group_admin(self) -> bool: return isinstance(member, Mapping) and member.get("status") in ADMIN_STATUSES def announce_version(self, version: str = __version__) -> None: - """Tell the admins in private, or the group when the profile names none, that a new - version runs. The version is kept in the KV only once Telegram took at least one message, - so a failed send is tried again at the next start and a restart on the same version - says nothing.""" + """Tell the admins in private that a new version runs, or the group when the profile + names none or Telegram refuses every private message (an admin who never opened a + private chat with the bot gets 403). The version is kept in the KV only once Telegram + took a message, so a failed send is tried again at the next start and a restart on the + same version says nothing.""" previous = self.kv.get("announced_version") if previous == version: return @@ -652,8 +653,9 @@ def announce_version(self, version: str = __version__) -> None: text = self.t("version_new_from", version=version, previous=previous) else: text = self.t("version_new", version=version) - chats = list(self.profile.admins) or [self.chat_id] - sent = [c for c in chats if c is not None and self.post(c, text) is not None] + sent = [a for a in self.profile.admins if self.post(a, text) is not None] + if not sent and self.chat_id is not None and self.post(self.chat_id, text) is not None: + sent = [self.chat_id] if sent: self.kv.set("announced_version", version) log.info("version %s announced to %d chat(s)", version, len(sent)) diff --git a/tests/test_bot.py b/tests/test_bot.py index 3e5fdf1..2eb76ef 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -1412,10 +1412,19 @@ def test_a_new_version_names_the_one_before(make_bot): def test_a_failed_announcement_is_not_stored_and_goes_out_at_the_next_start(make_bot): bot = make_bot() bot.tg.fail_next("sendMessage", 403, "Forbidden: bot can't initiate conversation with a user") + bot.tg.fail_next("sendMessage", 502, "Bad Gateway") bot.announce_version("1.2.3") assert bot.kv.get("announced_version") is None bot.announce_version("1.2.3") - assert bot.tg.texts() == ["Nueva versión en marcha: 1.2.3."] * 2 # refused, then taken + assert [m["chat_id"] for m in bot.tg.of("sendMessage")] == [ADMIN, GROUP, ADMIN] + assert bot.kv.get("announced_version") == "1.2.3" + + +def test_an_admin_the_bot_cannot_reach_in_private_gets_it_in_the_group(make_bot): + bot = make_bot() + bot.tg.fail_next("sendMessage", 403, "Forbidden: bot can't initiate conversation with a user") + bot.announce_version("1.2.3") + assert [m["chat_id"] for m in bot.tg.of("sendMessage")] == [ADMIN, GROUP] assert bot.kv.get("announced_version") == "1.2.3"