本次提交包含多项优化与新增功能: 1. 清理多个文件中多余的空行与导入顺序 2. 修复voice.py中的多行字符串格式化问题 3. 新增微信公众号被动回复构建函数与配置项 4. 新增企业微信markdown消息发送支持 5. 新增消息去重TTL与最大条目配置 6. 新增markdown文本截断工具函数 7. 新增微信授权与OAuth相关工具方法 8. 重构消息去重逻辑,使用DedupPolicy替代本地字典实现 9. 新增子账号多租户支持功能 10. 新增消息动作处理适配器,支持send/reply等操作 11. 修复token持久化逻辑,新增状态存储支持
68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from yuxi.channels.models import DeliveryResult
|
|
|
|
from .adapter import WeChatAdapter
|
|
|
|
|
|
class WeChatHeartbeatAdapter:
|
|
def __init__(self, config: dict[str, Any]):
|
|
self._typing_enabled = config.get("typing_indicator", False)
|
|
self._heartbeat_interval = config.get("heartbeat_interval", 300.0)
|
|
|
|
@property
|
|
def typing_enabled(self) -> bool:
|
|
return self._typing_enabled
|
|
|
|
async def send_typing_indicator(self, send_fn, chat_id: str, active: bool = True) -> None:
|
|
if not self._typing_enabled or not active:
|
|
return
|
|
try:
|
|
await send_fn(chat_id, "...")
|
|
except Exception:
|
|
pass
|
|
|
|
async def send_typing_via_response(
|
|
self,
|
|
send_fn,
|
|
chat_id: str,
|
|
active: bool = True,
|
|
) -> DeliveryResult | None:
|
|
if not self._typing_enabled or not active:
|
|
return None
|
|
try:
|
|
from yuxi.channels.models import (
|
|
ChannelIdentity,
|
|
ChannelResponse,
|
|
ChannelType,
|
|
)
|
|
|
|
identity = ChannelIdentity(
|
|
channel_id="wechat",
|
|
channel_type=ChannelType.WECHAT,
|
|
channel_user_id="",
|
|
channel_chat_id=chat_id,
|
|
)
|
|
response = ChannelResponse(identity=identity, content="...")
|
|
return await send_fn(response)
|
|
except Exception:
|
|
return None
|
|
|
|
async def heartbeat_check(self, adapter: WeChatAdapter) -> dict[str, Any]:
|
|
import time
|
|
|
|
start = time.monotonic()
|
|
try:
|
|
health = await adapter.health_check()
|
|
latency = (time.monotonic() - start) * 1000
|
|
return {"alive": health.status == "healthy", "latencyMs": latency}
|
|
except Exception as e:
|
|
return {"alive": False, "lastError": str(e)}
|
|
|
|
@staticmethod
|
|
def should_preserve_thread_id_for_group_heartbeat(config: dict[str, Any]) -> bool:
|
|
return config.get("preserve_heartbeat_thread_id_for_group", False)
|