From 890772f70e2a197b9389569eea36a0430ae05c11 Mon Sep 17 00:00:00 2001 From: z060142 Date: Thu, 15 May 2025 02:16:24 +0800 Subject: [PATCH] Add message deduplication system and UI fallback handling for updated game states - Implemented `MessageDeduplication` class to suppress duplicate bot replies: - Normalizes sender and message content for reliable comparison. - Tracks processed messages with timestamp-based expiry (default 1 hour). - Integrated into `run_ui_monitoring_loop()` with support for F7/F8-based history resets. - Periodic cleanup thread purges expired entries every 10 minutes. - Added new UI fallback handling logic to address post-update game state changes: - Detects `chat_option.png` overlay before bubble detection and presses ESC to dismiss. - Detects `update_confirm.png` when chat room state is unavailable and clicks it to proceed. - Both behaviors improve UI stability following game version changes. - Updated `essential_templates` dictionary and constants with the two new template paths: - `chat_option.png` - `update_confirm.png` These improvements reduce redundant bot responses and enhance UI resilience against inconsistent or obstructed states in the latest game versions. --- main.py | 28 ++++++- templates/chat_option.png | Bin 0 -> 3393 bytes templates/update_confirm.png | Bin 0 -> 5152 bytes ui_interaction.py | 137 +++++++++++++++++++++++++++++++---- 4 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 templates/chat_option.png create mode 100644 templates/update_confirm.png diff --git a/main.py b/main.py index 81081be..bd42f58 100644 --- a/main.py +++ b/main.py @@ -16,6 +16,8 @@ from mcp import ClientSession, StdioServerParameters, types # --- Keyboard Imports --- import threading import time +# Import MessageDeduplication from ui_interaction +from ui_interaction import MessageDeduplication try: import keyboard # Needs pip install keyboard except ImportError: @@ -483,9 +485,12 @@ async def run_main_with_exit_stack(): # 5. Start UI Monitoring in a separate thread print("\n--- Starting UI monitoring thread ---") - # Use the new monitoring loop function, passing both queues + # 5c. Create MessageDeduplication instance + deduplicator = MessageDeduplication(expiry_seconds=3600) # Default 1 hour + + # Use the new monitoring loop function, passing both queues and the deduplicator monitor_task = loop.create_task( - asyncio.to_thread(ui_interaction.run_ui_monitoring_loop, trigger_queue, command_queue), # Pass command_queue + asyncio.to_thread(ui_interaction.run_ui_monitoring_loop, trigger_queue, command_queue, deduplicator), # Pass command_queue and deduplicator name="ui_monitor" ) ui_monitor_task = monitor_task # Store task reference for shutdown @@ -493,6 +498,25 @@ async def run_main_with_exit_stack(): # 5b. Game Window Monitoring is now handled by Setup.py + # 5d. Start Periodic Cleanup Timer for Deduplicator + def periodic_cleanup(): + if not shutdown_requested: # Only run if not shutting down + print("Main Thread: Running periodic deduplicator cleanup...") + deduplicator.purge_expired() + # Reschedule the timer + cleanup_timer = threading.Timer(600, periodic_cleanup) # 10 minutes + cleanup_timer.daemon = True + cleanup_timer.start() + else: + print("Main Thread: Shutdown requested, not rescheduling deduplicator cleanup.") + + print("\n--- Starting periodic deduplicator cleanup timer (10 min interval) ---") + initial_cleanup_timer = threading.Timer(600, periodic_cleanup) + initial_cleanup_timer.daemon = True + initial_cleanup_timer.start() + # Note: This timer will run in a separate thread. + # Ensure it's handled correctly on shutdown if it holds resources. + # Since it's a daemon thread and reschedules itself, it should exit when the main program exits. # 6. Start the main processing loop (non-blocking check on queue) print("\n--- Wolfhart chatbot has started (waiting for triggers) ---") diff --git a/templates/chat_option.png b/templates/chat_option.png new file mode 100644 index 0000000000000000000000000000000000000000..943862a1b712d579c3e7e9618ea23685809d4167 GIT binary patch literal 3393 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 X&5f(`a}lU{00000NkvXXu0mjfpNW29 literal 0 HcmV?d00001 diff --git a/templates/update_confirm.png b/templates/update_confirm.png new file mode 100644 index 0000000000000000000000000000000000000000..95cd87bc625f872929011929e25e7f34d8e17a2a GIT binary patch literal 5152 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 O0000 100: + clean_content = clean_content[:100] + + return f"{clean_sender}:{clean_content}" + + def is_duplicate(self, sender, content): + """Check if the message is a duplicate within the expiry period.""" + if not sender or not content: + return False # Missing necessary info, treat as new message + + key = self.create_key(sender, content) + current_time = time.time() + + # Check if duplicate and not expired + if key in self.processed_messages: + last_time = self.processed_messages[key] + if current_time - last_time < self.expiry_seconds: + print(f"Deduplicator: Detected duplicate message: {sender} - {content[:20]}...") + return True + + # Update processing time + self.processed_messages[key] = current_time + return False + + def purge_expired(self): + """Remove expired message records.""" + current_time = time.time() + expired_keys = [k for k, t in self.processed_messages.items() + if current_time - t >= self.expiry_seconds] + + for key in expired_keys: + del self.processed_messages[key] + + if expired_keys: # Log only if something was purged + print(f"Deduplicator: Purged {len(expired_keys)} expired message records.") + return len(expired_keys) + + def clear_all(self): + """Clear all recorded messages (for F7/F8 functionality).""" + count = len(self.processed_messages) + self.processed_messages.clear() + if count > 0: # Log only if something was cleared + print(f"Deduplicator: Cleared all {count} message records.") + return count # --- Global Pause Flag --- # Using a simple mutable object (list) for thread-safe-like access without explicit lock @@ -142,6 +200,9 @@ PROFILE_OPTION_IMG = os.path.join(TEMPLATE_DIR, "profile_option.png") COPY_NAME_BUTTON_IMG = os.path.join(TEMPLATE_DIR, "copy_name_button.png") SEND_BUTTON_IMG = os.path.join(TEMPLATE_DIR, "send_button.png") CHAT_INPUT_IMG = os.path.join(TEMPLATE_DIR, "chat_input.png") +# 新增的模板路徑 +CHAT_OPTION_IMG = os.path.join(TEMPLATE_DIR, "chat_option.png") +UPDATE_CONFIRM_IMG = os.path.join(TEMPLATE_DIR, "update_confirm.png") # State Detection PROFILE_NAME_PAGE_IMG = os.path.join(TEMPLATE_DIR, "Profile_Name_page.png") PROFILE_PAGE_IMG = os.path.join(TEMPLATE_DIR, "Profile_page.png") @@ -1629,7 +1690,7 @@ def perform_state_cleanup(detector: DetectionModule, interactor: InteractionModu # --- UI Monitoring Loop Function (To be run in a separate thread) --- -def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queue): +def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queue, deduplicator: 'MessageDeduplication'): """ Continuously monitors the UI, detects triggers, performs interactions, puts trigger data into trigger_queue, and processes commands from command_queue. @@ -1667,7 +1728,9 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu 'page_sec': PAGE_SEC_IMG, 'page_str': PAGE_STR_IMG, 'dismiss_button': DISMISS_BUTTON_IMG, 'confirm_button': CONFIRM_BUTTON_IMG, 'close_button': CLOSE_BUTTON_IMG, 'back_arrow': BACK_ARROW_IMG, - 'reply_button': REPLY_BUTTON_IMG + 'reply_button': REPLY_BUTTON_IMG, + # 添加新模板 + 'chat_option': CHAT_OPTION_IMG, 'update_confirm': UPDATE_CONFIRM_IMG, } legacy_templates = { # Deprecated Keywords (for legacy method fallback) @@ -1773,13 +1836,15 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu elif action == 'clear_history': # Added for F7 print("UI Thread: Processing clear_history command.") recent_texts.clear() - print("UI Thread: recent_texts cleared.") + deduplicator.clear_all() # Simultaneously clear deduplication records + print("UI Thread: recent_texts and deduplicator records cleared.") elif action == 'reset_state': # Added for F8 resume print("UI Thread: Processing reset_state command.") recent_texts.clear() last_processed_bubble_info = None - print("UI Thread: recent_texts cleared and last_processed_bubble_info reset.") + deduplicator.clear_all() # Simultaneously clear deduplication records + print("UI Thread: recent_texts, last_processed_bubble_info, and deduplicator records reset.") else: print(f"UI Thread: Received unknown command: {action}") @@ -1804,6 +1869,19 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu # --- If not paused, proceed with UI Monitoring --- # print("[DEBUG] UI Loop: Monitoring is active. Proceeding...") # DEBUG REMOVED + # --- 添加檢查 chat_option 狀態 --- + try: + chat_option_locs = detector._find_template('chat_option', confidence=0.8) + if chat_option_locs: + print("UI Thread: Detected chat_option overlay. Pressing ESC to dismiss...") + interactor.press_key('esc') + time.sleep(0.2) # 給一點時間讓界面響應 + print("UI Thread: Pressed ESC to dismiss chat_option. Continuing...") + continue # 重新開始循環以確保界面已清除 + except Exception as chat_opt_err: + print(f"UI Thread: Error checking for chat_option: {chat_opt_err}") + # 繼續執行,不要中斷主流程 + # --- Check for Main Screen Navigation --- # print("[DEBUG] UI Loop: Checking for main screen navigation...") # DEBUG REMOVED try: @@ -1842,8 +1920,19 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu # Use a slightly lower confidence maybe, or state_confidence chat_room_locs = detector._find_template('chat_room', confidence=detector.state_confidence) if not chat_room_locs: - print("UI Thread: Not in chat room state before bubble detection. Attempting cleanup...") - # Call the existing cleanup function to try and return + print("UI Thread: Not in chat room state before bubble detection. Checking for update confirm...") + + # 檢查是否存在更新確認按鈕 + update_confirm_locs = detector._find_template('update_confirm', confidence=0.8) + if update_confirm_locs: + print("UI Thread: Detected update_confirm button. Clicking to proceed...") + interactor.click_at(update_confirm_locs[0][0], update_confirm_locs[0][1]) + time.sleep(0.5) # 給更新過程一些時間 + print("UI Thread: Clicked update_confirm button. Continuing...") + continue # 重新開始循環以重新檢查狀態 + + # 沒有找到更新確認按鈕,繼續原有的清理邏輯 + print("UI Thread: No update_confirm button found. Attempting cleanup...") perform_state_cleanup(detector, interactor) # Regardless of cleanup success, restart the loop to re-evaluate state from the top print("UI Thread: Continuing loop after attempting chat room cleanup.") @@ -2010,16 +2099,6 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu perform_state_cleanup(detector, interactor) # Attempt cleanup continue # Skip to next bubble - # Check recent text history - # print("[DEBUG] UI Loop: Checking recent text history...") # DEBUG REMOVED - if bubble_text in recent_texts: - print(f"Content '{bubble_text[:30]}...' in recent history, skipping this bubble.") - continue # Skip to next bubble - - print(">>> New trigger event <<<") - # Add to recent texts *before* potentially long interaction - recent_texts.append(bubble_text) - # 5. Interact: Get Sender Name (uses re-location internally via retrieve_sender_name_interaction) # print("[DEBUG] UI Loop: Retrieving sender name...") # DEBUG REMOVED sender_name = None @@ -2097,6 +2176,32 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu print("Error: Could not get sender name for this bubble, skipping.") continue # Skip to next bubble + # --- Deduplication Check --- + # This is the new central point for deduplication and recent_texts logic + if sender_name and bubble_text: # Ensure both are valid before deduplication + if deduplicator.is_duplicate(sender_name, bubble_text): + print(f"UI Thread: Skipping duplicate message via Deduplicator: {sender_name} - {bubble_text[:30]}...") + # Cleanup UI state as interaction might have occurred during sender_name retrieval + perform_state_cleanup(detector, interactor) + continue # Skip this bubble + + # If not a duplicate by deduplicator, then check recent_texts (original safeguard) + if bubble_text in recent_texts: + print(f"UI Thread: Content '{bubble_text[:30]}...' in recent_texts history, skipping.") + perform_state_cleanup(detector, interactor) # Cleanup as we are skipping + continue + + # If not a duplicate by any means, add to recent_texts and proceed + print(">>> New trigger event (passed deduplication) <<<") + recent_texts.append(bubble_text) + else: + # This case implies sender_name or bubble_text was None/empty, + # which should have been caught by earlier checks. + # If somehow reached, log and skip. + print(f"Warning: sender_name ('{sender_name}') or bubble_text ('{bubble_text[:30]}...') is invalid before deduplication check. Skipping.") + perform_state_cleanup(detector, interactor) + continue + # --- Attempt to activate reply context --- # print("[DEBUG] UI Loop: Attempting to activate reply context...") # DEBUG REMOVED reply_context_activated = False