mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-10-20 05:15:31 +00:00
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:
parent
552adbe082
commit
736ffb3bc1
8 changed files with 250 additions and 161 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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]'")
|
||||
|
|
|
|||
|
|
@ -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().
|
||||
|
|
|
|||
3
plugins/platforms/telegram/__init__.py
Normal file
3
plugins/platforms/telegram/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .adapter import register
|
||||
|
||||
__all__ = ["register"]
|
||||
|
|
@ -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,
|
||||
)
|
||||
35
plugins/platforms/telegram/plugin.yaml
Normal file
35
plugins/platforms/telegram/plugin.yaml
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue