ForcePilot/backend/package/yuxi/channels/adapters/telegram/lease.py
Kris 71fec609bb feat(telegram-adapter): 实现完整的Telegram适配器基础代码
新增了Telegram适配器的全套基础模块,包括:
1.  核心适配器入口与会话工具
2.  账号管理、认证与配置系统
3.  连接相关的轮询、Webhook、更新偏移管理
4.  话题路由、管理与缓存系统
5.  消息反抖动、超时配置与工具类
6.  响应式UI与命令交互系统
7.  反应表情与通知系统
8.  审批与安全审计模块
9.  健康检查与状态监控
10. 贴纸缓存与视觉工具
11. 流式响应与协作功能
12. 群组迁移与目标归一化处理
2026-05-12 00:49:52 +08:00

93 lines
2.9 KiB
Python

from __future__ import annotations
import asyncio
import hashlib
import os
from typing import Any
from yuxi.utils.logging_config import logger
class PollingLease:
def __init__(self, config: dict[str, Any] | None = None):
cfg = config or {}
self._token_hash = hashlib.sha256(cfg.get("bot_token", "").encode()).hexdigest()[:8]
self._instance_id = os.urandom(6).hex()
self._lease_ttl = cfg.get("polling_lease_ttl", 30)
self._acquired = False
self._renew_task: asyncio.Task | None = None
@property
def is_acquired(self) -> bool:
return self._acquired
async def acquire(self) -> bool:
try:
from yuxi.storage.redis import get_redis
redis = get_redis()
except ImportError:
logger.error(
"[Telegram] Redis not available - lease disabled. "
"Multiple instances may poll simultaneously, causing duplicate message processing."
)
self._acquired = True
return True
lease_key = f"telegram:polling_lease:{self._token_hash}"
try:
acquired = await redis.set(lease_key, self._instance_id, nx=True, ex=self._lease_ttl)
except Exception as e:
logger.error(
f"[Telegram] Redis lease check failed: {e}. "
"Proceeding without lease - multiple instances may poll simultaneously."
)
self._acquired = True
return True
if acquired:
self._acquired = True
self._renew_task = asyncio.create_task(self._renew_loop(lease_key))
logger.info(f"[Telegram] Polling lease acquired (instance={self._instance_id}, ttl={self._lease_ttl}s)")
return True
logger.debug("[Telegram] Polling lease held by another instance")
return False
async def release(self) -> None:
self._acquired = False
if self._renew_task and not self._renew_task.done():
self._renew_task.cancel()
try:
await self._renew_task
except asyncio.CancelledError:
pass
self._renew_task = None
try:
from yuxi.storage.redis import get_redis
redis = get_redis()
lease_key = f"telegram:polling_lease:{self._token_hash}"
await redis.delete(lease_key)
except Exception:
pass
async def _renew_loop(self, key: str) -> None:
try:
from yuxi.storage.redis import get_redis
redis = get_redis()
except ImportError:
return
while self._acquired:
await asyncio.sleep(self._lease_ttl * 0.5)
try:
await redis.expire(key, self._lease_ttl)
except asyncio.CancelledError:
break
except Exception:
pass