refactor(gateway): migrate telegram adapter (+ telegram_network) to bundled plugin

WIP toward #41112 — the last and largest gateway channel. Telegram adapter +
telegram_network satellite relocated to plugins/platforms/telegram/ with the
network import rewritten to the new package path. register() exposes the
platform with standalone_sender_fn (delegates to the retained _send_telegram
REST sender), apply_yaml_config_fn (full telegram: YAML→env + extra bridge),
setup_fn (delegates to the managed-bot QR wizard via lazy import), and a
_build_adapter that applies the notification-mode resolution that used to live
in gateway/run.py's factory branch. Core touchpoints stripped: run.py factory
branch (was the leading if), config.py 90-line telegram_cfg bridge, gateway.py
static dict + setup-map entry. send_message_tool keeps _send_telegram (the
plugin delegates to it) with its TelegramAdapter imports repointed to the
plugin path. Generic Platform.TELEGRAM references in core (auth maps, bridged
config-loop special-cases, source-platform behavior checks) intentionally
remain — same 'left generic in core' policy as the merged migrations.
This commit is contained in:
teknium1 2026-06-07 05:22:19 -07:00
parent 552adbe082
commit 736ffb3bc1
No known key found for this signature in database
8 changed files with 250 additions and 161 deletions

View file

@ -998,98 +998,9 @@ def load_gateway_config() -> GatewayConfig:
_tg_extra = _tg_plat.setdefault("extra", {})
_tg_extra.setdefault("require_mention", _tl_require_mention)
# Telegram settings → env vars (env vars take precedence)
telegram_cfg = yaml_cfg.get("telegram", {})
if isinstance(telegram_cfg, dict):
# Bridge top-level legacy `telegram.disable_topic_auto_rename` into
# gateway.platforms.telegram.extra so the runtime config sees it.
# Read as a runtime-config flag, not env-var (no need for env override).
if "disable_topic_auto_rename" in telegram_cfg:
_tg_plat = platforms_data.setdefault(Platform.TELEGRAM.value, {})
_tg_extra = _tg_plat.setdefault("extra", {})
_tg_extra.setdefault(
"disable_topic_auto_rename",
telegram_cfg["disable_topic_auto_rename"],
)
# Prefer telegram.require_mention; fall back to the top-level shorthand.
_effective_rm = telegram_cfg.get("require_mention", yaml_cfg.get("require_mention"))
if _effective_rm is not None and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
os.environ["TELEGRAM_REQUIRE_MENTION"] = str(_effective_rm).lower()
if "mention_patterns" in telegram_cfg and not os.getenv("TELEGRAM_MENTION_PATTERNS"):
os.environ["TELEGRAM_MENTION_PATTERNS"] = json.dumps(telegram_cfg["mention_patterns"])
if "exclusive_bot_mentions" in telegram_cfg and not os.getenv("TELEGRAM_EXCLUSIVE_BOT_MENTIONS"):
os.environ["TELEGRAM_EXCLUSIVE_BOT_MENTIONS"] = str(telegram_cfg["exclusive_bot_mentions"]).lower()
if "guest_mode" in telegram_cfg and not os.getenv("TELEGRAM_GUEST_MODE"):
os.environ["TELEGRAM_GUEST_MODE"] = str(telegram_cfg["guest_mode"]).lower()
if "observe_unmentioned_group_messages" in telegram_cfg and not os.getenv("TELEGRAM_OBSERVE_UNMENTIONED_GROUP_MESSAGES"):
os.environ["TELEGRAM_OBSERVE_UNMENTIONED_GROUP_MESSAGES"] = str(telegram_cfg["observe_unmentioned_group_messages"]).lower()
frc = telegram_cfg.get("free_response_chats")
if frc is not None and not os.getenv("TELEGRAM_FREE_RESPONSE_CHATS"):
if isinstance(frc, list):
frc = ",".join(str(v) for v in frc)
os.environ["TELEGRAM_FREE_RESPONSE_CHATS"] = str(frc)
# allowed_chats: if set, bot ONLY responds in these group chats (whitelist)
ac = telegram_cfg.get("allowed_chats")
if ac is not None and not os.getenv("TELEGRAM_ALLOWED_CHATS"):
if isinstance(ac, list):
ac = ",".join(str(v) for v in ac)
os.environ["TELEGRAM_ALLOWED_CHATS"] = str(ac)
allowed_topics = telegram_cfg.get("allowed_topics")
if allowed_topics is not None and not os.getenv("TELEGRAM_ALLOWED_TOPICS"):
if isinstance(allowed_topics, list):
allowed_topics = ",".join(str(v) for v in allowed_topics)
os.environ["TELEGRAM_ALLOWED_TOPICS"] = str(allowed_topics)
ignored_threads = telegram_cfg.get("ignored_threads")
if ignored_threads is not None and not os.getenv("TELEGRAM_IGNORED_THREADS"):
if isinstance(ignored_threads, list):
ignored_threads = ",".join(str(v) for v in ignored_threads)
os.environ["TELEGRAM_IGNORED_THREADS"] = str(ignored_threads)
if "reactions" in telegram_cfg and not os.getenv("TELEGRAM_REACTIONS"):
os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower()
if "proxy_url" in telegram_cfg and not os.getenv("TELEGRAM_PROXY"):
os.environ["TELEGRAM_PROXY"] = str(telegram_cfg["proxy_url"]).strip()
# reply_to_mode: top-level preferred, falls back to extra.reply_to_mode
# YAML 1.1 parses bare 'off' as boolean False — coerce to string "off".
_telegram_extra = telegram_cfg.get("extra") if isinstance(telegram_cfg.get("extra"), dict) else {}
_telegram_rtm = (
telegram_cfg["reply_to_mode"] if "reply_to_mode" in telegram_cfg
else _telegram_extra.get("reply_to_mode")
)
if _telegram_rtm is not None and not os.getenv("TELEGRAM_REPLY_TO_MODE"):
_rtm_str = "off" if _telegram_rtm is False else str(_telegram_rtm).lower()
os.environ["TELEGRAM_REPLY_TO_MODE"] = _rtm_str
allowed_users = telegram_cfg.get("allow_from")
if allowed_users is not None and not os.getenv("TELEGRAM_ALLOWED_USERS"):
if isinstance(allowed_users, list):
allowed_users = ",".join(str(v) for v in allowed_users)
os.environ["TELEGRAM_ALLOWED_USERS"] = str(allowed_users)
group_allowed_users = telegram_cfg.get("group_allow_from")
if group_allowed_users is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_USERS"):
if isinstance(group_allowed_users, list):
group_allowed_users = ",".join(str(v) for v in group_allowed_users)
os.environ["TELEGRAM_GROUP_ALLOWED_USERS"] = str(group_allowed_users)
group_allowed_chats = telegram_cfg.get("group_allowed_chats")
if group_allowed_chats is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_CHATS"):
if isinstance(group_allowed_chats, list):
group_allowed_chats = ",".join(str(v) for v in group_allowed_chats)
os.environ["TELEGRAM_GROUP_ALLOWED_CHATS"] = str(group_allowed_chats)
for _telegram_extra_key in ("guest_mode", "disable_link_previews", "observe_unmentioned_group_messages"):
if _telegram_extra_key in telegram_cfg:
plat_data = platforms_data.setdefault(Platform.TELEGRAM.value, {})
if not isinstance(plat_data, dict):
plat_data = {}
platforms_data[Platform.TELEGRAM.value] = plat_data
extra = plat_data.setdefault("extra", {})
if not isinstance(extra, dict):
extra = {}
plat_data["extra"] = extra
extra[_telegram_extra_key] = telegram_cfg[_telegram_extra_key]
if _telegram_extra:
_plat_data, _plat_extra = _ensure_platform_extra_dict(
platforms_data, Platform.TELEGRAM.value
)
for _telegram_extra_key, _telegram_extra_value in _telegram_extra.items():
_plat_extra.setdefault(_telegram_extra_key, _telegram_extra_value)
# Telegram settings → env vars / extra: migrated to the telegram
# plugin's apply_yaml_config_fn hook
# (plugins/platforms/telegram/adapter.py). #41112 / #3823.
# WhatsApp settings → env vars: migrated to the whatsapp plugin's
# apply_yaml_config_fn hook (plugins/platforms/whatsapp/adapter.py).

View file

@ -6864,36 +6864,7 @@ class GatewayRunner:
logger.debug("Platform registry lookup for '%s' failed: %s", platform.value, e)
# Fall through to built-in adapters below
if platform == Platform.TELEGRAM:
from gateway.platforms.telegram import TelegramAdapter, check_telegram_requirements
if not check_telegram_requirements():
logger.warning("Telegram: python-telegram-bot not installed")
return None
adapter = TelegramAdapter(config)
# Apply Telegram notification mode from config. Controls whether
# intermediate messages (tool progress, streaming, status) trigger
# push notifications. Supports ENV override for quick testing.
_notify_mode = os.getenv("HERMES_TELEGRAM_NOTIFICATIONS", "")
if not _notify_mode:
try:
_gw_cfg = _load_gateway_config()
_raw = cfg_get(_gw_cfg, "display", "platforms", "telegram", "notifications")
if _raw not in {None, ""}:
_notify_mode = str(_raw).strip().lower()
except Exception:
pass
_notify_mode = _notify_mode or "important"
if _notify_mode not in {"all", "important"}:
logger.warning(
"Unknown telegram notifications mode '%s', "
"defaulting to 'important' (valid: all, important)",
_notify_mode,
)
_notify_mode = "important"
adapter._notifications_mode = _notify_mode
return adapter
elif platform == Platform.SLACK:
if platform == Platform.SLACK:
from gateway.platforms.slack import SlackAdapter, check_slack_requirements
if not check_slack_requirements():
logger.warning("Slack: slack-bolt not installed. Run: pip install 'hermes-agent[slack]'")

View file

@ -3797,39 +3797,9 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
# Per-platform config: each entry defines the env vars, setup instructions,
# and prompts needed to configure a messaging platform.
_PLATFORMS = [
{
"key": "telegram",
"label": "Telegram",
"emoji": "📱",
"token_var": "TELEGRAM_BOT_TOKEN",
"setup_instructions": [
"1. Open Telegram and message @BotFather",
"2. Send /newbot and follow the prompts to create your bot",
"3. Copy the bot token BotFather gives you",
"4. To find your user ID: message @userinfobot — it replies with your numeric ID",
],
"vars": [
{
"name": "TELEGRAM_BOT_TOKEN",
"prompt": "Bot token",
"password": True,
"help": "Paste the token from @BotFather (step 3 above).",
},
{
"name": "TELEGRAM_ALLOWED_USERS",
"prompt": "Allowed user IDs (comma-separated)",
"password": False,
"is_allowlist": True,
"help": "Paste your user ID from step 4 above.",
},
{
"name": "TELEGRAM_HOME_CHANNEL",
"prompt": "Home channel ID (for cron/notification delivery, or empty to set later with /set-home)",
"password": False,
"help": "For DMs, this is your user ID. You can set it later by typing /set-home in chat.",
},
],
},
# Telegram moved to plugins/platforms/telegram/ — setup metadata discovered
# dynamically via the platform registry entry registered by
# plugins/platforms/telegram/adapter.py::register(). #41112.
# Discord moved to plugins/platforms/discord/ — its setup metadata is
# discovered dynamically via _all_platforms() from the platform registry
# entry registered by plugins/platforms/discord/adapter.py::register().
@ -5227,7 +5197,8 @@ def _builtin_setup_fn(key: str):
from hermes_cli import setup as _s
return {
"telegram": _s._setup_telegram,
# telegram moved into the plugin: setup_fn registered by
# plugins/platforms/telegram/adapter.py::register(). #41112.
# discord moved into the plugin: setup_fn is registered by
# plugins/platforms/discord/adapter.py::register() and dispatched
# via the plugin path in _configure_platform().

View file

@ -0,0 +1,3 @@
from .adapter import register
__all__ = ["register"]

View file

@ -81,7 +81,7 @@ from gateway.platforms.base import (
SUPPORTED_IMAGE_DOCUMENT_TYPES,
utf16_len,
)
from gateway.platforms.telegram_network import (
from plugins.platforms.telegram.telegram_network import (
TelegramFallbackTransport,
discover_fallback_ips,
parse_fallback_ip_env,
@ -6079,3 +6079,201 @@ class TelegramAdapter(BasePlatformAdapter):
message_id,
"\U0001f44d" if outcome == ProcessingOutcome.SUCCESS else "\U0001f44e",
)
# ──────────────────────────────────────────────────────────────────────────
# Plugin migration glue (#41112 / #3823)
#
# Added when the Telegram adapter (+ its telegram_network satellite) moved from
# gateway/platforms/ into this bundled plugin. Mirrors the Discord (#24356) /
# Slack migrations: a register(ctx) entry point plus hook implementations that
# replace the per-platform core touchpoints (the Platform.TELEGRAM branch in
# gateway/run.py, the telegram_cfg YAML→env/extra block in gateway/config.py,
# the _setup_telegram wizard + _PLATFORMS["telegram"] static dict in
# hermes_cli/{setup,gateway}.py, and the _send_telegram dispatch in
# tools/send_message_tool.py). Telegram uses the generic token connected
# check, so no is_connected override is needed.
# ──────────────────────────────────────────────────────────────────────────
def _resolve_notifications_mode() -> str:
"""Resolve the Telegram notification mode (all/important) from env or
config.yaml display.platforms.telegram.notifications, defaulting to
'important'. Mirrors the post-construction logic that used to live in
gateway/run.py::_create_adapter()."""
mode = os.getenv("HERMES_TELEGRAM_NOTIFICATIONS", "")
if not mode:
try:
from gateway.config import load_gateway_config
from gateway.run import cfg_get
_gw_cfg = load_gateway_config()
_raw = cfg_get(_gw_cfg, "display", "platforms", "telegram", "notifications")
if _raw not in {None, ""}:
mode = str(_raw).strip().lower()
except Exception:
pass
mode = mode or "important"
if mode not in {"all", "important"}:
logger.warning(
"Unknown telegram notifications mode '%s', defaulting to 'important' "
"(valid: all, important)", mode,
)
mode = "important"
return mode
def _build_adapter(config):
"""Factory wrapper that constructs TelegramAdapter and applies the
notification mode (preserving the gateway/run.py post-construction step)."""
adapter = TelegramAdapter(config)
try:
adapter._notifications_mode = _resolve_notifications_mode()
except Exception:
adapter._notifications_mode = "important"
return adapter
async def _standalone_send(
pconfig,
chat_id,
message,
*,
thread_id=None,
media_files=None,
force_document=False,
):
"""Out-of-process Telegram delivery. Delegates to the standalone
``_send_telegram`` REST sender in tools/send_message_tool.py (which already
handles chunking-agnostic single sends, threads, media, retries, and
parse-mode fallback). Implements the standalone_sender_fn contract so
deliver=telegram cron jobs succeed when cron runs separately from the
gateway."""
token = getattr(pconfig, "token", None) or os.getenv("TELEGRAM_BOT_TOKEN", "")
disable_link_previews = bool(
getattr(pconfig, "extra", {}) and pconfig.extra.get("disable_link_previews")
)
from tools.send_message_tool import _send_telegram
return await _send_telegram(
token,
chat_id,
message,
media_files=media_files,
thread_id=thread_id,
disable_link_previews=disable_link_previews,
force_document=force_document,
)
def interactive_setup() -> None:
"""Configure Telegram bot credentials and allowlist.
Delegates to the existing CLI setup helpers (managed-bot QR onboarding,
token validation, allowlist capture) via lazy import so the full wizard
behavior is preserved without duplicating ~150 lines. Replaces the
_PLATFORMS["telegram"] static dict dispatch in hermes_cli/gateway.py.
"""
from hermes_cli import setup as _setup_mod
_setup_mod._setup_telegram()
def _apply_yaml_config(yaml_cfg: dict, telegram_cfg: dict) -> dict | None:
"""Translate config.yaml telegram: keys into TELEGRAM_* env vars and
PlatformConfig.extra entries.
Implements the apply_yaml_config_fn contract (#24849). Mirrors the legacy
telegram_cfg block from gateway/config.py::load_gateway_config(). Env vars
take precedence over YAML. Returns a dict of extras to merge into
PlatformConfig.extra (disable_topic_auto_rename + runtime flags), or None.
"""
import json as _json
extras: dict = {}
if "disable_topic_auto_rename" in telegram_cfg:
extras.setdefault("disable_topic_auto_rename", telegram_cfg["disable_topic_auto_rename"])
_effective_rm = telegram_cfg.get("require_mention", yaml_cfg.get("require_mention"))
if _effective_rm is not None and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
os.environ["TELEGRAM_REQUIRE_MENTION"] = str(_effective_rm).lower()
if "mention_patterns" in telegram_cfg and not os.getenv("TELEGRAM_MENTION_PATTERNS"):
os.environ["TELEGRAM_MENTION_PATTERNS"] = _json.dumps(telegram_cfg["mention_patterns"])
if "exclusive_bot_mentions" in telegram_cfg and not os.getenv("TELEGRAM_EXCLUSIVE_BOT_MENTIONS"):
os.environ["TELEGRAM_EXCLUSIVE_BOT_MENTIONS"] = str(telegram_cfg["exclusive_bot_mentions"]).lower()
if "guest_mode" in telegram_cfg and not os.getenv("TELEGRAM_GUEST_MODE"):
os.environ["TELEGRAM_GUEST_MODE"] = str(telegram_cfg["guest_mode"]).lower()
if "observe_unmentioned_group_messages" in telegram_cfg and not os.getenv("TELEGRAM_OBSERVE_UNMENTIONED_GROUP_MESSAGES"):
os.environ["TELEGRAM_OBSERVE_UNMENTIONED_GROUP_MESSAGES"] = str(telegram_cfg["observe_unmentioned_group_messages"]).lower()
frc = telegram_cfg.get("free_response_chats")
if frc is not None and not os.getenv("TELEGRAM_FREE_RESPONSE_CHATS"):
if isinstance(frc, list):
frc = ",".join(str(v) for v in frc)
os.environ["TELEGRAM_FREE_RESPONSE_CHATS"] = str(frc)
ac = telegram_cfg.get("allowed_chats")
if ac is not None and not os.getenv("TELEGRAM_ALLOWED_CHATS"):
if isinstance(ac, list):
ac = ",".join(str(v) for v in ac)
os.environ["TELEGRAM_ALLOWED_CHATS"] = str(ac)
allowed_topics = telegram_cfg.get("allowed_topics")
if allowed_topics is not None and not os.getenv("TELEGRAM_ALLOWED_TOPICS"):
if isinstance(allowed_topics, list):
allowed_topics = ",".join(str(v) for v in allowed_topics)
os.environ["TELEGRAM_ALLOWED_TOPICS"] = str(allowed_topics)
ignored_threads = telegram_cfg.get("ignored_threads")
if ignored_threads is not None and not os.getenv("TELEGRAM_IGNORED_THREADS"):
if isinstance(ignored_threads, list):
ignored_threads = ",".join(str(v) for v in ignored_threads)
os.environ["TELEGRAM_IGNORED_THREADS"] = str(ignored_threads)
if "reactions" in telegram_cfg and not os.getenv("TELEGRAM_REACTIONS"):
os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower()
if "proxy_url" in telegram_cfg and not os.getenv("TELEGRAM_PROXY"):
os.environ["TELEGRAM_PROXY"] = str(telegram_cfg["proxy_url"]).strip()
_telegram_extra = telegram_cfg.get("extra") if isinstance(telegram_cfg.get("extra"), dict) else {}
_telegram_rtm = (
telegram_cfg["reply_to_mode"] if "reply_to_mode" in telegram_cfg
else _telegram_extra.get("reply_to_mode")
)
if _telegram_rtm is not None and not os.getenv("TELEGRAM_REPLY_TO_MODE"):
_rtm_str = "off" if _telegram_rtm is False else str(_telegram_rtm).lower()
os.environ["TELEGRAM_REPLY_TO_MODE"] = _rtm_str
allowed_users = telegram_cfg.get("allow_from")
if allowed_users is not None and not os.getenv("TELEGRAM_ALLOWED_USERS"):
if isinstance(allowed_users, list):
allowed_users = ",".join(str(v) for v in allowed_users)
os.environ["TELEGRAM_ALLOWED_USERS"] = str(allowed_users)
group_allowed_users = telegram_cfg.get("group_allow_from")
if group_allowed_users is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_USERS"):
if isinstance(group_allowed_users, list):
group_allowed_users = ",".join(str(v) for v in group_allowed_users)
os.environ["TELEGRAM_GROUP_ALLOWED_USERS"] = str(group_allowed_users)
group_allowed_chats = telegram_cfg.get("group_allowed_chats")
if group_allowed_chats is not None and not os.getenv("TELEGRAM_GROUP_ALLOWED_CHATS"):
if isinstance(group_allowed_chats, list):
group_allowed_chats = ",".join(str(v) for v in group_allowed_chats)
os.environ["TELEGRAM_GROUP_ALLOWED_CHATS"] = str(group_allowed_chats)
for _key in ("guest_mode", "disable_link_previews", "observe_unmentioned_group_messages"):
if _key in telegram_cfg:
extras.setdefault(_key, telegram_cfg[_key])
for _k, _v in _telegram_extra.items():
extras.setdefault(_k, _v)
return extras or None
def register(ctx) -> None:
"""Plugin entry point — called by the Hermes plugin system."""
ctx.register_platform(
name="telegram",
label="Telegram",
adapter_factory=_build_adapter,
check_fn=check_telegram_requirements,
required_env=["TELEGRAM_BOT_TOKEN"],
install_hint="pip install 'hermes-agent[telegram]'",
setup_fn=interactive_setup,
apply_yaml_config_fn=_apply_yaml_config,
allowed_users_env="TELEGRAM_ALLOWED_USERS",
allow_all_env="TELEGRAM_ALLOW_ALL_USERS",
cron_deliver_env_var="TELEGRAM_HOME_CHANNEL",
standalone_sender_fn=_standalone_send,
max_message_length=4096,
emoji="✈️",
allow_update_command=True,
)

View file

@ -0,0 +1,35 @@
name: telegram-platform
label: Telegram
kind: platform
version: 1.0.0
description: >
Telegram gateway adapter for Hermes Agent.
Connects to Telegram via python-telegram-bot and relays messages between
Telegram chats/groups/topics and the Hermes agent. Supports threads/topics,
streaming edits, native media, inline keyboards, slash commands, fallback
network transport (direct-IP failover), notification modes, mention gating,
and per-user/chat allowlists.
author: NousResearch
requires_env:
- name: TELEGRAM_BOT_TOKEN
description: "Telegram bot token from @BotFather"
prompt: "Telegram bot token"
url: "https://t.me/BotFather"
password: true
optional_env:
- name: TELEGRAM_ALLOWED_USERS
description: "Comma-separated Telegram user IDs allowed to talk to the bot"
prompt: "Allowed users (comma-separated)"
password: false
- name: TELEGRAM_ALLOW_ALL_USERS
description: "Allow any Telegram user to trigger the bot (dev only)"
prompt: "Allow all users? (true/false)"
password: false
- name: TELEGRAM_HOME_CHANNEL
description: "Default chat ID for cron / notification delivery"
prompt: "Home channel ID"
password: false
- name: TELEGRAM_HOME_CHANNEL_NAME
description: "Display name for the Telegram home channel"
prompt: "Home channel display name"
password: false

View file

@ -593,7 +593,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
# Telegram adapter import is optional (requires python-telegram-bot)
try:
from gateway.platforms.telegram import TelegramAdapter
from plugins.platforms.telegram.adapter import TelegramAdapter
_telegram_available = True
except ImportError:
_telegram_available = False
@ -857,7 +857,7 @@ async def _send_telegram(token, chat_id, message, media_files=None, thread_id=No
else:
# Reuse the gateway adapter's format_message for markdown→MarkdownV2
try:
from gateway.platforms.telegram import TelegramAdapter
from plugins.platforms.telegram.adapter import TelegramAdapter
_adapter = TelegramAdapter.__new__(TelegramAdapter)
formatted = _adapter.format_message(message)
except Exception:
@ -902,7 +902,7 @@ async def _send_telegram(token, chat_id, message, media_files=None, thread_id=No
# send to a forum group's General topic always errors out
# (see issue #22267).
try:
from gateway.platforms.telegram import TelegramAdapter
from plugins.platforms.telegram.adapter import TelegramAdapter
effective_thread_id = TelegramAdapter._message_thread_id_for_send(
str(thread_id)
)
@ -954,7 +954,7 @@ async def _send_telegram(token, chat_id, message, media_files=None, thread_id=No
)
if not _has_html:
try:
from gateway.platforms.telegram import _strip_mdv2
from plugins.platforms.telegram.adapter import _strip_mdv2
plain = _strip_mdv2(formatted)
except Exception:
plain = message