新增大量WhatsApp适配器相关代码,包括账号管理、会话处理、消息收发、验证授权、媒体处理、互动命令、审批流程、健康检测等完整功能模块,搭建基础的Baileys协议WhatsApp接入能力
105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from collections import OrderedDict
|
|
from typing import Any
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
def _normalize_jid(jid: str) -> str:
|
|
return jid.split("@")[0].replace("+", "").replace(" ", "").replace("-", "")
|
|
|
|
|
|
def _is_group_jid(jid: str) -> bool:
|
|
return "@g.us" in jid
|
|
|
|
|
|
class InboundMessageCache:
|
|
def __init__(self, max_size: int = 500, ttl_seconds: int = 600):
|
|
self._cache: OrderedDict[str, dict[str, Any]] = OrderedDict()
|
|
self._max_size = max_size
|
|
self._ttl = ttl_seconds
|
|
|
|
def put(self, msg_id: str, jid: str, content: str, metadata: dict[str, Any] | None = None) -> None:
|
|
if not msg_id:
|
|
return
|
|
self._cache[msg_id] = {
|
|
"jid": jid,
|
|
"content": content[:500],
|
|
"timestamp": time.time(),
|
|
"metadata": metadata or {},
|
|
}
|
|
self._cache.move_to_end(msg_id)
|
|
self._evict()
|
|
|
|
def get(self, msg_id: str) -> dict[str, Any] | None:
|
|
entry = self._cache.get(msg_id)
|
|
if entry is None:
|
|
return None
|
|
if time.time() - entry["timestamp"] > self._ttl:
|
|
self._cache.pop(msg_id, None)
|
|
return None
|
|
return entry
|
|
|
|
def lookup_inbound_meta(self, target_jid: str, target_msg_id: str | None = None) -> dict[str, Any] | None:
|
|
if target_msg_id:
|
|
exact = self.get(target_msg_id)
|
|
if exact:
|
|
return exact
|
|
|
|
target_is_group = _is_group_jid(target_jid)
|
|
candidates: list[tuple[str, dict[str, Any], float]] = []
|
|
|
|
now = time.time()
|
|
for msg_id, entry in self._cache.items():
|
|
if now - entry["timestamp"] > self._ttl:
|
|
continue
|
|
entry_jid = entry.get("jid", "")
|
|
if target_is_group != _is_group_jid(entry_jid):
|
|
continue
|
|
candidates.append((msg_id, entry, entry["timestamp"]))
|
|
if len(candidates) >= 10:
|
|
break
|
|
|
|
if not candidates:
|
|
return None
|
|
|
|
candidates.sort(key=lambda x: x[2], reverse=True)
|
|
best_msg_id, best_entry, _ = candidates[0]
|
|
logger.debug(
|
|
f"InboundMessageCache: fuzzy match for {target_jid} -> msg_id={best_msg_id}, candidates={len(candidates)}"
|
|
)
|
|
return best_entry
|
|
|
|
def resolve_quoted_message_key(self, target_jid: str, quoted_msg_id: str | None = None) -> dict[str, Any] | None:
|
|
meta = None
|
|
if quoted_msg_id:
|
|
meta = self.get(quoted_msg_id)
|
|
if meta is None:
|
|
meta = self.lookup_inbound_meta(target_jid, None)
|
|
if meta is None:
|
|
return None
|
|
|
|
msg_jid = meta.get("jid", target_jid)
|
|
return {
|
|
"remoteJid": msg_jid,
|
|
"fromMe": False,
|
|
"id": quoted_msg_id or "",
|
|
}
|
|
|
|
def _evict(self) -> None:
|
|
now = time.time()
|
|
expired = [k for k, v in self._cache.items() if now - v["timestamp"] > self._ttl]
|
|
for k in expired:
|
|
del self._cache[k]
|
|
while len(self._cache) > self._max_size:
|
|
oldest = next(iter(self._cache))
|
|
del self._cache[oldest]
|
|
|
|
def clear(self) -> None:
|
|
self._cache.clear()
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._cache)
|