| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- """事件处理 + 转发核心:白名单过滤、防死循环、去重、调用 Forward API。
- lark.ws.Client 在独立线程中运行事件循环并回调 handler。
- 本模块的事件回调把任务投递到 asyncio.Queue(跨线程安全),
- 由 ratelimit.Worker 在主事件循环中匀速消费。
- """
- from __future__ import annotations
- import asyncio
- import logging
- import threading
- from typing import Optional
- import lark_oapi as lark
- from lark_oapi.api.im.v1 import (
- ForwardMessageRequest,
- ForwardMessageRequestBody,
- )
- from config import Config
- from dedup import MessageDedup
- from ratelimit import ForwardTask, RateLimitedWorker
- logger = logging.getLogger(__name__)
- class Forwarder:
- """绑定配置、去重器、限流 Worker,并提供事件回调。"""
- def __init__(
- self,
- cfg: Config,
- client: lark.Client,
- dedup: MessageDedup,
- worker: RateLimitedWorker,
- loop: asyncio.AbstractEventLoop,
- ) -> None:
- self._cfg = cfg
- self._client = client
- self._dedup = dedup
- self._worker = worker
- self._loop = loop
- self._source_set = set(cfg.source_chat_ids)
- # ---------- 事件回调(lark SDK 在子线程调用) ----------
- def on_message_receive(self, data: lark.im.v1.P2ImMessageReceiveV1) -> None:
- try:
- self._handle_event(data)
- except Exception as e:
- logger.exception("事件处理异常: %s", e)
- def _handle_event(self, data: lark.im.v1.P2ImMessageReceiveV1) -> None:
- event = data.event
- if event is None:
- return
- message = getattr(event, "message", None)
- sender = getattr(event, "sender", None)
- if message is None:
- return
- message_id: str = message.message_id or ""
- chat_id: str = message.chat_id or ""
- sender_type: str = (sender.sender_type if sender else "") or ""
- # 1) 防死循环:丢弃机器人自己发出的消息
- if sender_type == "bot":
- logger.debug("跳过 bot 消息 message_id=%s", message_id)
- return
- # 2) 白名单:只处理源群
- if chat_id not in self._source_set:
- logger.debug("跳过非白名单群 chat_id=%s", chat_id)
- return
- # 3) 去重
- if not self._dedup.check_and_mark(message_id):
- logger.debug("重复消息已丢弃 message_id=%s", message_id)
- return
- logger.info("接收消息 message_id=%s chat_id=%s type=%s",
- message_id, chat_id, message.message_type)
- # 4) 投递转发任务(每个目标群一个)
- for target in self._cfg.target_chat_ids:
- if target == chat_id:
- # 源==目标,跳过避免无效转发
- continue
- task = ForwardTask(
- message_id=message_id,
- target_chat_id=target,
- source_chat_id=chat_id,
- )
- # 跨线程安全投递到主事件循环的队列
- asyncio.run_coroutine_threadsafe(
- self._worker.enqueue(task), self._loop
- )
- # ---------- 实际转发(Worker 调用,运行在主事件循环) ----------
- async def forward(self, task: ForwardTask) -> bool:
- """调用飞书 Forward API。返回 True 表示成功。"""
- req = (
- ForwardMessageRequest.builder()
- .message_id(task.message_id)
- .receive_id_type("chat_id")
- .request_body(
- ForwardMessageRequestBody.builder()
- .receive_id(task.target_chat_id)
- .build()
- )
- .build()
- )
- def _call() -> bool:
- try:
- resp = self._client.im.v1.message.forward(req)
- except Exception as e:
- logger.error("Forward API 异常 message_id=%s -> %s: %s",
- task.message_id, task.target_chat_id, e)
- return False
- if not resp.success():
- logger.error("Forward API 失败 message_id=%s -> %s code=%s msg=%s",
- task.message_id, task.target_chat_id,
- resp.code, resp.msg)
- return False
- logger.info("转发成功 message_id=%s -> %s",
- task.message_id, task.target_chat_id)
- return True
- # lark-oapi 的 Client 是同步阻塞调用,放到默认 executor 执行
- return await asyncio.get_event_loop().run_in_executor(None, _call)
- def build_event_handler(forwarder: Forwarder):
- """构造 lark EventDispatcherHandler,注册 im.message.receive_v1。"""
- return (
- lark.EventDispatcherHandler.builder("", "")
- .register_p2_im_message_receive_v1(forwarder.on_message_receive)
- .build()
- )
|