11from __future__ import annotations
22
33import asyncio
4- import hashlib
54import logging
65import os
76import random
2322 PlatformMetadata ,
2423)
2524from astrbot .core .message .components import BaseMessageComponent
25+ from astrbot .core .message .utils import build_sender_content_dedup_key
2626from astrbot .core .platform .astr_message_event import MessageSesion
2727from astrbot .core .utils .number_utils import safe_positive_float
2828from astrbot .core .utils .ttl_registry import TTLKeyRegistry
@@ -55,13 +55,22 @@ def _extract_sender_id(message) -> str:
5555 Returns:
5656 The sender ID as a string, or empty string if not found.
5757 """
58- if hasattr (message , "author" ) and hasattr (message .author , "user_openid" ):
59- return str (message .author .user_openid )
60- if hasattr (message , "author" ) and hasattr (message .author , "member_openid" ):
61- return str (message .author .member_openid )
62- if hasattr (message , "author" ) and hasattr (message .author , "id" ):
63- return str (message .author .id )
64- return ""
58+ author = getattr (message , "author" , None )
59+ if not author :
60+ return ""
61+
62+ sender_id = (
63+ getattr (author , "user_openid" , None )
64+ or getattr (author , "member_openid" , None )
65+ or getattr (author , "id" , None )
66+ )
67+ if sender_id is None :
68+ return ""
69+
70+ sender_id_str = str (sender_id ).strip ()
71+ if not sender_id_str :
72+ return ""
73+ return sender_id_str
6574
6675
6776class MessageDeduplicator :
@@ -82,10 +91,7 @@ def __init__(
8291 self ._lock = asyncio .Lock ()
8392
8493 def _build_content_key (self , content : str , sender_id : str ) -> str | None :
85- if not (content and sender_id ):
86- return None
87- content_hash = hashlib .sha1 (content .encode ("utf-8" )).hexdigest ()[:16 ]
88- return f"{ sender_id } :{ content_hash } "
94+ return build_sender_content_dedup_key (content , sender_id )
8995
9096 async def is_duplicate (
9197 self ,
@@ -94,24 +100,35 @@ async def is_duplicate(
94100 sender_id : str = "" ,
95101 ) -> bool :
96102 async with self ._lock :
97- # Bypass deduplication if TTL is 0 (disabled)
98- if self ._message_ids .ttl_seconds == 0 :
103+ id_dedup_enabled = self ._message_ids .ttl_seconds > 0 and bool (message_id )
104+ content_dedup_enabled = self ._content_keys .ttl_seconds > 0
105+
106+ if not id_dedup_enabled and not content_dedup_enabled :
99107 return False
100108
101109 # 1) ID-based dedup
102- if self ._message_ids .contains (message_id ):
103- logger .debug (
104- "[QQOfficial] Duplicate message detected (by ID): %s..." ,
105- message_id [:50 ],
106- )
107- return True
110+ if id_dedup_enabled :
111+ if self ._message_ids .contains (message_id ):
112+ logger .debug (
113+ "[QQOfficial] Duplicate message detected (by ID): %s..." ,
114+ message_id [:50 ],
115+ )
116+ return True
108117
109- self ._message_ids .add (message_id )
118+ self ._message_ids .add (message_id )
110119
111120 # 2) Content-based dedup
121+ if not content_dedup_enabled :
122+ logger .debug (
123+ "[QQOfficial] New message registered: %s..." , message_id [:50 ]
124+ )
125+ return False
126+
112127 content_key = self ._build_content_key (content , sender_id )
113128 if content_key is None :
114- logger .debug ("[QQOfficial] New message registered: %s..." , message_id [:50 ])
129+ logger .debug (
130+ "[QQOfficial] New message registered: %s..." , message_id [:50 ]
131+ )
115132 return False
116133
117134 if self ._content_keys .contains (content_key ):
@@ -120,7 +137,8 @@ async def is_duplicate(
120137 content_key ,
121138 )
122139 # Preserve existing behavior: do not keep message_id on content duplicates
123- self ._message_ids .discard (message_id )
140+ if id_dedup_enabled :
141+ self ._message_ids .discard (message_id )
124142 return True
125143
126144 self ._content_keys .add (content_key )
@@ -132,21 +150,9 @@ class botClient(Client):
132150 def set_platform (self , platform : QQOfficialPlatformAdapter ) -> None :
133151 self .platform = platform
134152
135- def _get_sender_id (self , message ) -> str :
136- """Extract sender ID from different message types.
137-
138- Delegates to the centralized _extract_sender_id function to avoid
139- precedence drift.
140- """
141- return _extract_sender_id (message )
142-
143- def _extract_dedup_key (self , message ) -> tuple [str , str ]:
144- sender_id = self ._get_sender_id (message )
145- content = getattr (message , "content" , "" ) or ""
146- return sender_id , content
147-
148153 async def _should_drop_message (self , message ) -> bool :
149- sender_id , content = self ._extract_dedup_key (message )
154+ sender_id = _extract_sender_id (message )
155+ content = getattr (message , "content" , "" ) or ""
150156 return await self .platform ._is_duplicate_message (message .id , content , sender_id )
151157
152158 # 收到群消息
@@ -596,11 +602,12 @@ def _parse_from_qqofficial(
596602 message ,
597603 botpy .message .C2CMessage ,
598604 ):
605+ sender_user_id = _extract_sender_id (message )
599606 if isinstance (message , botpy .message .GroupMessage ):
600- abm .sender = MessageMember (message . author . member_openid , "" )
607+ abm .sender = MessageMember (sender_user_id , "" )
601608 abm .group_id = message .group_openid
602609 else :
603- abm .sender = MessageMember (message . author . user_openid , "" )
610+ abm .sender = MessageMember (sender_user_id , "" )
604611 # Parse face messages to readable text
605612 abm .message_str = QQOfficialPlatformAdapter ._parse_face_message (
606613 message .content .strip ()
0 commit comments