|
16 | 16 | from astrbot.core import logger |
17 | 17 | from astrbot.core.astrbot_config_mgr import AstrBotConfigManager |
18 | 18 | from astrbot.core.message.utils import ( |
19 | | - build_event_content_dedup_key, |
20 | | - build_event_message_id_dedup_key, |
| 19 | + build_content_dedup_key, |
| 20 | + build_message_id_dedup_key, |
21 | 21 | ) |
22 | 22 | from astrbot.core.pipeline.scheduler import PipelineScheduler |
23 | 23 | from astrbot.core.utils.number_utils import safe_positive_float |
|
26 | 26 | from .platform import AstrMessageEvent |
27 | 27 |
|
28 | 28 |
|
29 | | -class EventDeduplicator: |
30 | | - """Event deduplicator using TTL-based registry. |
31 | | -
|
32 | | - This class handles deduplication of events based on content fingerprint |
33 | | - and message ID, with configurable TTL window. |
34 | | - """ |
| 29 | +class EventBus: |
| 30 | + """用于处理事件的分发和处理""" |
35 | 31 |
|
36 | | - def __init__(self, ttl_seconds: float = 0.5) -> None: |
37 | | - self._registry = TTLKeyRegistry(ttl_seconds) |
| 32 | + def __init__( |
| 33 | + self, |
| 34 | + event_queue: Queue, |
| 35 | + pipeline_scheduler_mapping: dict[str, PipelineScheduler], |
| 36 | + astrbot_config_mgr: AstrBotConfigManager, |
| 37 | + ) -> None: |
| 38 | + self.event_queue = event_queue # 事件队列 |
| 39 | + # abconf uuid -> scheduler |
| 40 | + self.pipeline_scheduler_mapping = pipeline_scheduler_mapping |
| 41 | + self.astrbot_config_mgr = astrbot_config_mgr |
| 42 | + dedup_ttl_seconds = safe_positive_float( |
| 43 | + self.astrbot_config_mgr.g( |
| 44 | + None, |
| 45 | + "event_bus_dedup_ttl_seconds", |
| 46 | + 0.5, |
| 47 | + ), |
| 48 | + default=0.5, |
| 49 | + ) |
| 50 | + self._dedup_registry = TTLKeyRegistry(ttl_seconds=dedup_ttl_seconds) |
| 51 | + |
| 52 | + @staticmethod |
| 53 | + def _build_event_content_key(event: AstrMessageEvent) -> str: |
| 54 | + return build_content_dedup_key( |
| 55 | + platform_id=str(event.get_platform_id() or ""), |
| 56 | + unified_msg_origin=str(event.unified_msg_origin or ""), |
| 57 | + sender_id=str(event.get_sender_id() or ""), |
| 58 | + text=str(event.get_message_str() or ""), |
| 59 | + components=event.get_messages(), |
| 60 | + ) |
38 | 61 |
|
39 | | - def is_duplicate(self, event: AstrMessageEvent) -> bool: |
40 | | - """Check if the event is a duplicate. |
| 62 | + @staticmethod |
| 63 | + def _build_event_message_id_key(event: AstrMessageEvent) -> str | None: |
| 64 | + message_id = getattr(event.message_obj, "message_id", "") or getattr( |
| 65 | + event.message_obj, |
| 66 | + "id", |
| 67 | + "", |
| 68 | + ) |
| 69 | + return build_message_id_dedup_key( |
| 70 | + platform_id=str(event.get_platform_id() or ""), |
| 71 | + unified_msg_origin=str(event.unified_msg_origin or ""), |
| 72 | + message_id=str(message_id or ""), |
| 73 | + ) |
41 | 74 |
|
42 | | - Returns False immediately if TTL is 0 (deduplication disabled). |
43 | | - Short-circuits on message_id key to avoid expensive attachment signature computation. |
44 | | - """ |
45 | | - # TTL of 0 means deduplication is disabled |
46 | | - if self._registry.ttl_seconds == 0: |
| 75 | + def _is_duplicate(self, event: AstrMessageEvent) -> bool: |
| 76 | + if self._dedup_registry.ttl_seconds == 0: |
47 | 77 | return False |
48 | 78 |
|
49 | | - # Short-circuit: check message_id first (cheap) before computing full content key (expensive) |
50 | | - message_id_key = build_event_message_id_dedup_key(event) |
| 79 | + message_id_key = self._build_event_message_id_key(event) |
51 | 80 | if message_id_key is not None: |
52 | | - if self._registry.contains(message_id_key): |
| 81 | + if self._dedup_registry.contains(message_id_key): |
53 | 82 | logger.debug( |
54 | 83 | "Skip duplicate event in event_bus (by message_id): umo=%s, sender=%s", |
55 | 84 | event.unified_msg_origin, |
56 | 85 | event.get_sender_id(), |
57 | 86 | ) |
58 | 87 | return True |
59 | | - # Register message_id key since we'll process the event |
60 | | - self._registry.add(message_id_key) |
| 88 | + self._dedup_registry.add(message_id_key) |
61 | 89 |
|
62 | | - # Only compute full content key if we get past message_id check |
63 | | - content_key = build_event_content_dedup_key(event) |
64 | | - if self._registry.contains(content_key): |
| 90 | + content_key = self._build_event_content_key(event) |
| 91 | + if self._dedup_registry.contains(content_key): |
65 | 92 | logger.debug( |
66 | 93 | "Skip duplicate event in event_bus (by content): umo=%s, sender=%s", |
67 | 94 | event.unified_msg_origin, |
68 | 95 | event.get_sender_id(), |
69 | 96 | ) |
70 | | - # If content duplicate, also remove message_id to preserve existing behavior |
71 | 97 | if message_id_key is not None: |
72 | | - self._registry.discard(message_id_key) |
| 98 | + self._dedup_registry.discard(message_id_key) |
73 | 99 | return True |
74 | 100 |
|
75 | | - # Register content key |
76 | | - self._registry.add(content_key) |
| 101 | + self._dedup_registry.add(content_key) |
77 | 102 | return False |
78 | 103 |
|
79 | | - |
80 | | -class EventBus: |
81 | | - """用于处理事件的分发和处理""" |
82 | | - |
83 | | - def __init__( |
84 | | - self, |
85 | | - event_queue: Queue, |
86 | | - pipeline_scheduler_mapping: dict[str, PipelineScheduler], |
87 | | - astrbot_config_mgr: AstrBotConfigManager, |
88 | | - ) -> None: |
89 | | - self.event_queue = event_queue # 事件队列 |
90 | | - # abconf uuid -> scheduler |
91 | | - self.pipeline_scheduler_mapping = pipeline_scheduler_mapping |
92 | | - self.astrbot_config_mgr = astrbot_config_mgr |
93 | | - dedup_ttl_seconds = safe_positive_float( |
94 | | - self.astrbot_config_mgr.g( |
95 | | - None, |
96 | | - "event_bus_dedup_ttl_seconds", |
97 | | - 0.5, |
98 | | - ), |
99 | | - default=0.5, |
100 | | - ) |
101 | | - self._deduplicator = EventDeduplicator(ttl_seconds=dedup_ttl_seconds) |
102 | | - |
103 | 104 | async def dispatch(self) -> None: |
104 | 105 | # event_queue 由单一消费者处理;去重结构不是线程安全的,按设计仅在此循环中使用。 |
105 | 106 | while True: |
106 | 107 | event: AstrMessageEvent = await self.event_queue.get() |
107 | | - if self._deduplicator.is_duplicate(event): |
108 | | - logger.debug( |
109 | | - "Skip duplicate event in event_bus, umo=%s, sender=%s", |
110 | | - event.unified_msg_origin, |
111 | | - event.get_sender_id(), |
112 | | - ) |
| 108 | + if self._is_duplicate(event): |
113 | 109 | continue |
114 | 110 | conf_info = self.astrbot_config_mgr.get_conf_info(event.unified_msg_origin) |
115 | 111 | conf_id = conf_info["id"] |
|
0 commit comments