Skip to content

Commit aa3863e

Browse files
gambletanclaude
andcommitted
fix: sync telegram adapter with webhook support
TelegramAdapter was missing mode/webhook_url params that tests expected. Synced from dev to include webhook mode support. 327/327 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9a6fe04 commit aa3863e

File tree

1 file changed

+69
-6
lines changed

1 file changed

+69
-6
lines changed

unified_channel/adapters/telegram.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
"""Telegram adapter — bridges python-telegram-bot to UnifiedMessage."""
1+
"""Telegram adapter — bridges python-telegram-bot to UnifiedMessage.
2+
3+
Supports both polling (default) and webhook modes. Webhook mode uses
4+
python-telegram-bot's built-in webhook support via an aiohttp server.
5+
"""
26

37
from __future__ import annotations
48

59
import asyncio
610
import logging
711
from datetime import datetime
8-
from typing import AsyncIterator
12+
from typing import AsyncIterator, Literal
913

1014
from telegram import Update
1115
from telegram.ext import (
@@ -34,22 +38,45 @@ class TelegramAdapter(ChannelAdapter):
3438
"""
3539
Telegram channel adapter using python-telegram-bot.
3640
41+
Supports two modes:
42+
- ``"polling"`` (default): long polling via ``getUpdates``
43+
- ``"webhook"``: starts an HTTP server and registers a webhook URL
44+
3745
This is the ONLY file needed to add Telegram support.
3846
Compare with openclaw's 125-file Telegram implementation —
3947
all routing/session/middleware logic lives in the shared layer.
4048
"""
4149

4250
channel_id = "telegram"
4351

44-
def __init__(self, token: str, *, parse_mode: str = "Markdown") -> None:
52+
def __init__(
53+
self,
54+
token: str,
55+
*,
56+
parse_mode: str = "Markdown",
57+
mode: Literal["polling", "webhook"] = "polling",
58+
webhook_url: str | None = None,
59+
port: int = 8443,
60+
listen: str = "0.0.0.0",
61+
url_path: str = "/telegram-webhook",
62+
) -> None:
4563
self._token = token
4664
self._parse_mode = parse_mode
65+
self._mode: Literal["polling", "webhook"] = mode
66+
self._webhook_url = webhook_url
67+
self._port = port
68+
self._listen = listen
69+
self._url_path = url_path
4770
self._app: Application | None = None # type: ignore[type-arg]
4871
self._queue: asyncio.Queue[UnifiedMessage] = asyncio.Queue()
4972
self._connected = False
5073
self._last_activity: datetime | None = None
5174
self._bot_username: str | None = None
5275

76+
@property
77+
def mode(self) -> str:
78+
return self._mode
79+
5380
async def connect(self) -> None:
5481
self._app = (
5582
Application.builder()
@@ -72,16 +99,23 @@ async def connect(self) -> None:
7299

73100
await self._app.initialize()
74101
await self._app.start()
75-
await self._app.updater.start_polling(drop_pending_updates=True) # type: ignore[union-attr]
102+
103+
if self._mode == "webhook":
104+
await self._start_webhook()
105+
else:
106+
await self._app.updater.start_polling(drop_pending_updates=True) # type: ignore[union-attr]
76107

77108
me = await self._app.bot.get_me()
78109
self._bot_username = me.username
79110
self._connected = True
80-
logger.info("telegram connected: @%s", self._bot_username)
111+
logger.info("telegram connected (%s mode): @%s", self._mode, self._bot_username)
81112

82113
async def disconnect(self) -> None:
83114
if self._app:
84-
await self._app.updater.stop() # type: ignore[union-attr]
115+
if self._mode == "webhook":
116+
await self._stop_webhook()
117+
else:
118+
await self._app.updater.stop() # type: ignore[union-attr]
85119
await self._app.stop()
86120
await self._app.shutdown()
87121
self._connected = False
@@ -136,6 +170,35 @@ async def get_status(self) -> ChannelStatus:
136170
last_activity=self._last_activity,
137171
)
138172

173+
# -- Webhook management --
174+
175+
async def _start_webhook(self) -> None:
176+
if not self._webhook_url:
177+
raise ValueError("webhook_url is required for webhook mode")
178+
179+
full_url = self._webhook_url.rstrip("/") + self._url_path
180+
181+
# Use python-telegram-bot's built-in webhook support
182+
await self._app.updater.start_webhook( # type: ignore[union-attr]
183+
listen=self._listen,
184+
port=self._port,
185+
url_path=self._url_path,
186+
webhook_url=full_url,
187+
drop_pending_updates=True,
188+
)
189+
logger.info("telegram webhook started on %s:%d%s", self._listen, self._port, self._url_path)
190+
191+
async def _stop_webhook(self) -> None:
192+
try:
193+
await self._app.updater.stop() # type: ignore[union-attr]
194+
except Exception:
195+
pass
196+
try:
197+
await self._app.bot.delete_webhook() # type: ignore[union-attr]
198+
except Exception:
199+
pass
200+
logger.info("telegram webhook stopped")
201+
139202
# -- internal handlers: convert platform events to UnifiedMessage --
140203

141204
async def _on_command(

0 commit comments

Comments
 (0)