"""Reply eligibility from session identity only, never from message contents.""" import re import unicodedata APPLICATION_REASON = '企小码系统会话不参与自动回复,已跳过' GROUP_REASON = '群聊不参与自动回复,已跳过' APPLICATION_NAMES = frozenset({'企小码', '企小码会话', '企小码会话管理', '企小碼', '企小碼會話管理'}) GROUP_KINDS = frozenset({'group', 'group_chat', 'groupchat', 'chatroom', 'room', '群聊', '群会话'}) def normalize_session_name(value): value = unicodedata.normalize('NFKC', str(value or '')) return re.sub(r'[\s\u200b-\u200d\ufeff]+', '', value).strip() def _true(value): return value is True or value == 1 or isinstance(value, str) and value.strip().lower() in {'true', '1', 'yes'} def excluded_session_reason(name='', conv_id='', metadata=None): """Only inspect recognized metadata keys. Free text is intentionally ignored.""" metadata = metadata if isinstance(metadata, dict) else {} records = [metadata] # Persisted visual DB identities are nested; do not recurse into messages. event = metadata.get('database_event') if isinstance(event, dict): records.append(event) names = [name] ids = [conv_id] for record in records: names.extend(record.get(key) for key in ('display_name', 'conv_name', 'conversation_name', 'session_name')) ids.extend(record.get(key) for key in ('conv_id', 'conversation_id', 'conversationId')) if any(normalize_session_name(value) in APPLICATION_NAMES for value in names if value): return APPLICATION_REASON for value in ids: value = str(value or '').strip() if value.upper().startswith('R:') or value.lower().endswith('@chatroom'): return GROUP_REASON for record in records: if any(_true(record.get(key)) for key in ('is_group', 'is_group_chat', 'isGroup')): return GROUP_REASON for key in ('kind', 'chat_type', 'conversation_type', 'session_type'): if str(record.get(key) or '').strip().lower() in GROUP_KINDS: return GROUP_REASON return '' def visual_group_title_reason(title, name=''): """Match a member count added to the opened title, not a row unread badge. A personal nickname ending in '(2)' appears unchanged in the list and title; it is not a group. Bare numeric counts require a separately known base name. """ title = normalize_session_name(title) name = normalize_session_name(name) if not title or '@' in title or name == title: return '' match = re.fullmatch(r'(.+?)\(([0-9]{1,6})(人|位成员|成员)?\)', title) if not match or int(match[2]) < 2: return '' if match[3] or name and match[1] == name: return GROUP_REASON return ''