From be15463fd2620a8abea3ec152211944b82587667 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Fri, 24 Apr 2026 15:15:25 +0300 Subject: [PATCH] feat(telegram): add 'none' listener mode for bots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a third update_mode option alongside polling/webhook. 'none' disables both polling and webhook delivery — useful when another instance owns the listener or when the bot is send-only. Switching into 'none' now unschedules polling and unregisters any active webhook so Telegram stops delivering updates. New bots default to 'none' (safer when multiple bridges share a token). Existing bots upgraded from a pre-update_mode schema keep 'polling' so their behavior is unchanged. --- frontend/src/lib/i18n/en.json | 2 ++ frontend/src/lib/i18n/ru.json | 2 ++ .../src/routes/bots/TelegramBotTab.svelte | 23 ++++++++++++++++--- .../notify_bridge_server/api/telegram_bots.py | 13 ++++++++++- .../database/migrations.py | 5 +++- .../notify_bridge_server/database/models.py | 2 +- .../services/backup_schema.py | 2 +- 7 files changed, 42 insertions(+), 7 deletions(-) diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index ba41a39..ebd130e 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -433,6 +433,8 @@ "webhookRegistered": "Webhook registered", "webhookUnregistered": "Webhook unregistered", "updateMode": "Update mode", + "none": "None", + "noneActive": "Listener disabled", "polling": "Polling", "webhook": "Webhook", "webhookStatus": "Webhook status", diff --git a/frontend/src/lib/i18n/ru.json b/frontend/src/lib/i18n/ru.json index ee439a0..a0d96e5 100644 --- a/frontend/src/lib/i18n/ru.json +++ b/frontend/src/lib/i18n/ru.json @@ -433,6 +433,8 @@ "webhookRegistered": "Вебхук зарегистрирован", "webhookUnregistered": "Вебхук удалён", "updateMode": "Режим обновлений", + "none": "Откл.", + "noneActive": "Приём обновлений отключён", "polling": "Опрос", "webhook": "Вебхук", "webhookStatus": "Статус вебхука", diff --git a/frontend/src/routes/bots/TelegramBotTab.svelte b/frontend/src/routes/bots/TelegramBotTab.svelte index 2d14a33..5aabe50 100644 --- a/frontend/src/routes/bots/TelegramBotTab.svelte +++ b/frontend/src/routes/bots/TelegramBotTab.svelte @@ -334,10 +334,12 @@ @{bot.bot_username} {/if} - - {bot.update_mode === 'webhook' ? t('telegramBot.webhook') : t('telegramBot.polling')} + : (bot.update_mode || 'none') === 'polling' + ? 'bg-[var(--color-success-bg)] text-[var(--color-success-fg)]' + : 'bg-[var(--color-muted)] text-[var(--color-muted-foreground)]'}"> + {(bot.update_mode || 'none') === 'webhook' ? t('telegramBot.webhook') : (bot.update_mode || 'none') === 'polling' ? t('telegramBot.polling') : t('telegramBot.none')}

{bot.token_preview}

@@ -456,6 +458,14 @@

{t('telegramBot.updateMode')}

+
+ {#if (bot.update_mode || 'none') === 'none'} + + + {t('telegramBot.noneActive')} + + {/if} + {#if bot.update_mode === 'polling'} diff --git a/packages/server/src/notify_bridge_server/api/telegram_bots.py b/packages/server/src/notify_bridge_server/api/telegram_bots.py index f0370c4..d74e1e9 100644 --- a/packages/server/src/notify_bridge_server/api/telegram_bots.py +++ b/packages/server/src/notify_bridge_server/api/telegram_bots.py @@ -86,6 +86,11 @@ async def update_bot( bot.icon = body.icon # Handle mode switching if body.update_mode is not None and body.update_mode != bot.update_mode: + if body.update_mode not in ("none", "polling", "webhook"): + raise HTTPException( + status_code=400, + detail=f"Invalid update_mode: {body.update_mode!r}. Must be 'none', 'polling', or 'webhook'.", + ) if body.update_mode == "webhook": # Validate and register webhook BEFORE stopping polling base_url = await get_setting(session, "external_url") @@ -108,6 +113,12 @@ async def update_bot( # Switching to polling: unregister webhook, start polling await unregister_webhook(bot.token) schedule_bot_polling(bot.id) + elif body.update_mode == "none": + # Disable listener: stop polling and clear any webhook so Telegram + # stops delivering updates. This makes the bot send-only, which is + # safe when another instance owns the listener. + unschedule_bot_polling(bot.id) + await unregister_webhook(bot.token) bot.update_mode = body.update_mode session.add(bot) @@ -406,7 +417,7 @@ def _bot_response(b: TelegramBot) -> dict: "bot_username": b.bot_username, "bot_id": b.bot_id, "webhook_path_id": b.webhook_path_id, - "update_mode": b.update_mode or "polling", + "update_mode": b.update_mode or "none", "token_preview": f"{b.token[:8]}...{b.token[-4:]}" if len(b.token) > 12 else "***", "created_at": b.created_at.isoformat(), } diff --git a/packages/server/src/notify_bridge_server/database/migrations.py b/packages/server/src/notify_bridge_server/database/migrations.py index 84dbc50..884270c 100644 --- a/packages/server/src/notify_bridge_server/database/migrations.py +++ b/packages/server/src/notify_bridge_server/database/migrations.py @@ -144,7 +144,10 @@ async def migrate_schema(engine: AsyncEngine) -> None: if bots: logger.info("Backfilled webhook_path_id for %d existing bots", len(bots)) - # Add update_mode to telegram_bot if missing + # Add update_mode to telegram_bot if missing. + # Existing bots pre-date this feature and were implicitly polling; + # preserve that behavior. New bots default to "none" via the + # SQLModel field default on fresh schemas. if not await _has_column(conn, "telegram_bot", "update_mode"): await conn.execute( text("ALTER TABLE telegram_bot ADD COLUMN update_mode TEXT DEFAULT 'polling'") diff --git a/packages/server/src/notify_bridge_server/database/models.py b/packages/server/src/notify_bridge_server/database/models.py index 6f6a778..071733f 100644 --- a/packages/server/src/notify_bridge_server/database/models.py +++ b/packages/server/src/notify_bridge_server/database/models.py @@ -49,7 +49,7 @@ class TelegramBot(SQLModel, table=True): bot_username: str = Field(default="") bot_id: int = Field(default=0) webhook_path_id: str = Field(default_factory=lambda: uuid4().hex) - update_mode: str = Field(default="polling") # "polling" or "webhook" + update_mode: str = Field(default="none") # "none", "polling", or "webhook" # NOTE: commands_config column remains in the DB for backward compat, # but is no longer part of the SQLModel class. Data migrated to CommandConfig. created_at: datetime = Field(default_factory=_utcnow) diff --git a/packages/server/src/notify_bridge_server/services/backup_schema.py b/packages/server/src/notify_bridge_server/services/backup_schema.py index e3443e8..01ded51 100644 --- a/packages/server/src/notify_bridge_server/services/backup_schema.py +++ b/packages/server/src/notify_bridge_server/services/backup_schema.py @@ -173,7 +173,7 @@ class TelegramBotData(BaseModel): token: str = "" icon: str = "" bot_username: str = "" - update_mode: str = "polling" + update_mode: str = "none" class MatrixBotData(BaseModel):