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
37from __future__ import annotations
48
59import asyncio
610import logging
711from datetime import datetime
8- from typing import AsyncIterator
12+ from typing import AsyncIterator , Literal
913
1014from telegram import Update
1115from 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