Files
replypier/backend/app/services/telegram_webhooks.py
WangShen007 e0baca15a9 feat(modules): 帮助中心、工单、工作流与多渠道接入
补齐 FAQ、帮助中心、工单、可视化工作流及 Telegram/WhatsApp/Slack 等渠道模块。
2026-10-17 22:05:00 +08:00

84 lines
3.4 KiB
Python

"""
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Registering ReplyPier's webhook with a Telegram bot, with the updates it
must receive. Done on connect, and again for an agent's bots when its reply
buttons are turned on: a bot connected before buttons existed was registered
without button taps, and Telegram keeps a bot's last registration.
"""
import asyncio
import json
from typing import Optional, Tuple
from uuid import UUID
from sqlalchemy.orm import Session
from app.channels import telegram as telegram_api
from app.core.config import settings
from app.core.security import decrypt_api_key
from app.core.logger import get_logger
from app.models.channels import ChannelAccount, ChannelType
from app.repositories.channels import AgentChannelConfigRepository, ChannelAccountRepository
logger = get_logger(__name__)
def webhook_url(account_id) -> str:
return f"{settings.BACKEND_URL.rstrip('/')}{settings.API_V1_STR}/webhooks/telegram/{account_id}"
async def register(account: ChannelAccount, bot_token: str) -> Tuple[bool, str]:
return await telegram_api.set_webhook(bot_token, webhook_url(account.id), account.webhook_secret)
async def reregister_for_agent(db: Session, agent_id: UUID) -> None:
"""Register again, at once, every active Telegram bot routed to this agent
— its buttons were just turned on."""
try:
accounts = ChannelAccountRepository(db)
bots = [account for account in (accounts.get_by_id(config.channel_account_id)
for config in AgentChannelConfigRepository(db).list_by_agent(agent_id))
if _is_active_bot(account)]
except Exception:
# The agent is already saved; only the re-registration is lost
logger.exception(f"Could not list agent {agent_id}'s Telegram bots to re-register")
return
await asyncio.gather(*(_reregister(account) for account in bots))
async def reregister_if_buttons_on(account: ChannelAccount, agent) -> None:
"""A bot just routed to an agent whose buttons are already on."""
if _is_active_bot(account) and agent.channel_quick_replies:
await _reregister(account)
async def _reregister(account: ChannelAccount) -> None:
"""Best effort: a bot that can't be registered keeps working — only its
button taps don't arrive until it is (turn the agent's buttons off and on
again, or reconnect the bot)."""
try:
bot_token = json.loads(decrypt_api_key(account.encrypted_credentials))["bot_token"]
ok, error = await register(account, bot_token)
except Exception as e:
ok, error = False, f"{type(e).__name__}: {e}"
if not ok:
logger.warning(f"Could not re-register Telegram bot {account.id} for button taps: {error}")
def _is_active_bot(account: Optional[ChannelAccount]) -> bool:
return (account is not None and account.is_active
and account.channel_type == ChannelType.TELEGRAM.value)