Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions gateway/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ def load_gateway_config() -> GatewayConfig:
os.environ["DISCORD_FREE_RESPONSE_CHANNELS"] = str(frc)
if "auto_thread" in discord_cfg and not os.getenv("DISCORD_AUTO_THREAD"):
os.environ["DISCORD_AUTO_THREAD"] = str(discord_cfg["auto_thread"]).lower()

# Telegram settings → env vars (env vars take precedence)
telegram_cfg = yaml_cfg.get("telegram", {})
if isinstance(telegram_cfg, dict):
if "require_mention" in telegram_cfg and not os.getenv("TELEGRAM_REQUIRE_MENTION"):
os.environ["TELEGRAM_REQUIRE_MENTION"] = str(telegram_cfg["require_mention"]).lower()
except Exception:
pass

Expand Down
40 changes: 40 additions & 0 deletions gateway/platforms/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def __init__(self, config: PlatformConfig):
super().__init__(config, Platform.TELEGRAM)
self._app: Optional[Application] = None
self._bot: Optional[Bot] = None
self._bot_username: Optional[str] = None # Bot username for mention checking
self._require_mention: bool = os.getenv("TELEGRAM_REQUIRE_MENTION", "").lower() in ("true", "1", "yes", "on")
# Buffer rapid/album photo updates so Telegram image bursts are handled
# as a single MessageEvent instead of self-interrupting multiple turns.
self._media_batch_delay_seconds = float(os.getenv("HERMES_TELEGRAM_MEDIA_BATCH_DELAY_SECONDS", "0.8"))
Expand Down Expand Up @@ -228,6 +230,15 @@ async def connect(self) -> bool:
else:
raise
await self._app.start()

# Fetch bot username for mention checking
try:
bot_info = await self._bot.get_me()
self._bot_username = bot_info.username
logger.info("[%s] Bot username: @%s (require_mention=%s)", self.name, self._bot_username, self._require_mention)
except Exception as e:
logger.warning("[%s] Could not fetch bot username: %s", self.name, e)

loop = asyncio.get_running_loop()

def _polling_error_callback(error: Exception) -> None:
Expand Down Expand Up @@ -842,6 +853,25 @@ def _convert_header(m):

return text

def _is_bot_mentioned(self, message) -> bool:
"""Check if the bot is mentioned in the message (for group chats)."""
if not self._bot_username:
return False

# Check for @username mention in text
if message.text and f"@{self._bot_username}" in message.text:
return True

# Check for entity mentions (for users with usernames hidden)
if message.entities:
for entity in message.entities:
if entity.type == "mention":
mention_text = message.text[entity.offset:entity.offset + entity.length]
if mention_text == f"@{self._bot_username}":
return True

return False

async def _handle_text_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle incoming text messages.

Expand All @@ -852,6 +882,16 @@ async def _handle_text_message(self, update: Update, context: ContextTypes.DEFAU
if not update.message or not update.message.text:
return

# In group chats, if require_mention is enabled, only respond when mentioned
if self._require_mention and update.message.chat.type in (ChatType.GROUP, ChatType.SUPERGROUP):
if not self._is_bot_mentioned(update.message):
logger.debug(
"[%s] Ignoring group message without mention (chat_id=%s)",
self.name,
update.message.chat.id,
)
return

event = self._build_message_event(update.message, MessageType.TEXT)
self._enqueue_text_event(event)

Expand Down