"""
微信控制技能 - Agent端实现

功能模块：
1. 消息管理 - 发送/获取消息、批量发送
2. 好友管理 - 添加/通过好友、设置备注、删除好友
3. 群聊管理 - 创建群/邀请入群/群发消息/设置群欢迎语
4. 标签管理 - 添加/删除/查询标签
5. 朋友圈管理 - 发布/点赞/评论

@author 卡若
@version 3.0.0
"""

import time
import logging
import re
import shlex
import sys
import os
from typing import Dict, Any, List, Optional
_agent_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if _agent_dir not in sys.path:
    sys.path.insert(0, _agent_dir)
from skills.base import BaseSkill
from error_handler import retry, with_error_handling, ErrorHandler

logger = logging.getLogger(__name__)

# 截屏 + AI 视觉（每次操作都先看屏再决定）
class _VisionFallback(Exception):
    """视觉流程失败，回退到规则流程"""
    pass


def _vision_step(self, goal: str, step_hint: str = "") -> dict:
    """截屏 → AI 看屏 → 返回下一步动作"""
    try:
        from vision_helper import ask_vision
        img = self.screenshot()
        return ask_vision(img, goal, step_hint)
    except Exception as e:
        logger.warning(f"视觉调用失败: {e}")
        return {"action": "error", "error": str(e)}


class WechatSkill(BaseSkill):
    """
    微信控制技能 — 96个action全覆盖

    控制通道（按优先级）：
    1. Frida Hook — 直接调用微信内部方法/读SQLite, 50-200ms
    2. u2 UI自动化 — uiautomator2模拟点击/输入, 2-15s（本文件实现）
    3. ADB Shell — adb命令+坐标点击, 1-10s（wechat_adb_engine.py）

    功能模块（29个模块 / 96个action）:
    消息: send_message, get_messages, forward_message, recall_message, send_card, batch_send_message
    好友: add_friend, accept_friend, set_remark, delete_friend, get_contacts, search_contact,
          get_friend_info, batch_add_friend
    群聊: create_group, invite_to_group, remove_from_group, set_group_notice, set_group_name,
          send_group_message, set_group_welcome, get_groups, get_group_members, quit_group
    标签: add_tag, remove_tag, create_tag, delete_tag, get_tags, get_users_by_tag
    朋友圈: post_moments, like_moments, comment_moments, get_moments, delete_moments,
            set_moments_cover, set_moments_privacy, forward_moments_link
    个人: get_profile, set_nickname, set_signature, set_avatar, set_gender, set_region
    安全: check_account_status, unblock_account, safety_center, change_password,
          unblock_self, unblock_appeal, check_restrictions, appeal_restriction, unblock_with_sms
    支付: send_red_packet, transfer, show_payment_code, receive_payment, view_wallet,
          view_transactions, receive_red_packet
    聊天: set_chat_top, set_mute_chat, clear_chat_history
    收藏: add_to_favorites, get_favorites
    小程序: open_mini_program | 公众号: follow_official_account
    视频号: open_video_channel, get_video_list, like_video, comment_video,
            follow_video_creator, share_video
    扫码: scan_qr_code, scan_add_friend, show_my_qr, extract_qr_from_image
    通话: voice_call, video_call
    群发: mass_send | 搜索: wechat_search | 发现: top_stories
    运动: get_steps, like_steps
    位置: send_location, share_real_time_location
    表情: send_emoji, get_sticker_list
    语音: send_voice_message | 文件: send_file_from_chat, download_file
    设置: toggle_do_not_disturb, clear_cache, check_for_update, logout, switch_account
    截图: screenshot
    """
    
    PACKAGE = "com.tencent.mm"
    NAME = "微信"
    SEND_MESSAGE_TIMEOUT = 45
    MAX_TEST_PAYMENT_AMOUNT = 1.0

    def _payment_guard(self, amount: str = "", confirm: bool = False, **kwargs) -> Dict[str, Any]:
        """资金类动作保护：默认只导航到页面；真确认必须显式授权且金额在测试上限内。"""
        try:
            amt = float(str(amount or "0").strip() or "0")
        except Exception:
            return {"pass": False, "error": "amount 必须是数字"}
        if amt < 0:
            return {"pass": False, "error": "amount 不能为负数"}
        if not confirm:
            return {
                "pass": True,
                "dry_run": True,
                "confirm_required": True,
                "amount": amt,
                "note": "已按内部测试保护策略停止在最终确认前",
            }
        if amt > self.MAX_TEST_PAYMENT_AMOUNT:
            return {
                "pass": False,
                "error": f"测试金额超过上限 {self.MAX_TEST_PAYMENT_AMOUNT}",
                "amount": amt,
            }
        if not kwargs.get("test_whitelist_ok", False):
            return {
                "pass": False,
                "error": "资金动作需要 test_whitelist_ok=true",
                "amount": amt,
            }
        return {"pass": True, "dry_run": False, "confirm_required": False, "amount": amt}

    def send_message_with_vision(self, to_id: str, content: str, msg_type: str = "text") -> Dict[str, Any]:
        """
        发送消息（以截屏+AI为主，每次操作都先看屏再决定）
        """
        try:
            start = time.time()
            self.launch()
            self.sleep(0.8)
            goal = f"给 {to_id} 发消息：{content}"
            max_steps = 15
            for step in range(max_steps):
                if time.time() - start > self.SEND_MESSAGE_TIMEOUT:
                    return {"success": False, "error": "timeout"}
                hint = f"步骤 {step+1}/{max_steps}。"
                act = _vision_step(self, goal, hint)
                a = act.get("action", "")
                if a == "done":
                    logger.info(f"微信消息发送成功(视觉): {to_id}")
                    return {"success": True, "message_id": f"wx_{int(time.time() * 1000)}"}
                if a == "error":
                    logger.warning(f"视觉返回错误: {act.get('error')}，回退到规则流程")
                    raise _VisionFallback()
                if a == "click":
                    x, y = int(act.get("x", 540)), int(act.get("y", 1200))
                    self.click(x, y)
                    self.sleep(0.3)
                elif a == "input":
                    t = act.get("text", "")
                    if t:
                        self.input_text(t, clear=(step < 2))
                    self.sleep(0.2)
                elif a == "back":
                    self.back()
                    self.sleep(0.3)
            raise _VisionFallback()
        except _VisionFallback:
            raise
        except Exception as e:
            logger.error(f"视觉发送失败: {e}")
            raise _VisionFallback()

    @with_error_handling("发送微信消息")
    @retry(max_retries=1, delay=1)
    def send_message(self, to_id: str, content: str, msg_type: str = "text") -> Dict[str, Any]:
        """
        发送消息（坐标导航 + accessibility 双通道）。
        微信的自定义 View 不暴露给 accessibility tree，核心流程用坐标点击。
        """
        self.natural_behavior_before("send_message")
        start = time.time()
        info = self.d.info
        W, H = info['displayWidth'], info['displayHeight']

        try:
            # ── 1. 确保在微信主界面 ──
            curr = self.d.app_current()
            if curr.get("package") != self.PACKAGE:
                self.launch()
                self.sleep(2)
            elif "ChattingUI" in (curr.get("activity") or ""):
                self.press("back")
                self.sleep(0.5)

            for _ in range(3):
                curr = self.d.app_current()
                act = curr.get("activity", "")
                if act.endswith(".LauncherUI"):
                    break
                self.press("back")
                self.sleep(0.5)
            else:
                self.launch()
                self.sleep(2)

            # ── 2. 打开搜索（坐标：标题栏放大镜，在 + 号左侧） ──
            search_x = int(W * 0.82)
            search_y = int(H * 0.06)
            self.click(search_x, search_y)
            self.sleep(1.5)

            curr = self.d.app_current()
            if "FTSMainUI" not in (curr.get("activity") or ""):
                self.click(search_x - 60, search_y)
                self.sleep(1)

            # ── 3. 输入联系人 ──
            self.click(int(W * 0.5), int(H * 0.035))
            self.sleep(0.3)
            try:
                self.d.set_input_ime(True)
            except Exception as e:
                logger.warning(f"ADBKeyboard 启用失败，使用 root input text 兜底: {e}")
            self.sleep(0.2)
            self.input_text(to_id, clear=False)
            self.sleep(2.5)

            # ── 4. 点击搜索结果（微信自定义控件不在 a11y tree，用坐标） ──
            result_x = int(W * 0.3)
            entered_chat = False
            for result_y in [int(H * 0.20), int(H * 0.22), int(H * 0.18), int(H * 0.25)]:
                self.click(result_x, result_y)
                self.sleep(2)
                curr = self.d.app_current()
                if "ChattingUI" in (curr.get("activity") or ""):
                    entered_chat = True
                    break
                if "FTSMainUI" not in (curr.get("activity") or ""):
                    self.press("back")
                    self.sleep(0.5)

            if not entered_chat:
                if self.click_text(to_id, timeout=2) or self.click_contains(to_id, timeout=2):
                    self.sleep(2)
                    curr = self.d.app_current()
                    entered_chat = "ChattingUI" in (curr.get("activity") or "")

            if not entered_chat:
                return {"success": False, "error": f"未找到联系人: {to_id}"}

            if time.time() - start > self.SEND_MESSAGE_TIMEOUT:
                return {"success": False, "error": "timeout"}

            # ── 5. 聚焦输入框 ──
            input_y = int(H * 0.955)
            self.click(int(W * 0.3), input_y)
            self.sleep(0.5)

            # ── 6. 输入消息 ──
            try:
                self.d.set_input_ime(True)
            except Exception as e:
                logger.warning(f"ADBKeyboard 启用失败，使用 root input text 兜底: {e}")
            self.sleep(0.2)
            self.input_text(content, clear=False)
            self.sleep(0.8)

            # ── 7. 点击发送按钮（先尝试 xpath/text，再坐标兜底） ──
            sent = False
            try:
                if self.d.xpath('//*[@text="发送"]').exists:
                    self.d.xpath('//*[@text="发送"]').click()
                    sent = True
                elif self.d(text="发送").exists(timeout=1):
                    self.d(text="发送").click()
                    sent = True
            except Exception:
                pass

            if not sent:
                send_x = int(W * 0.91)
                self.click(send_x, input_y)
                self.sleep(0.3)

            self.sleep(0.5)
            logger.info(f"微信消息发送成功: {to_id}")
            self.say(f"已发消息给 {to_id}", data={"to_id": to_id, "content_preview": content[:30]})
            return {"success": True, "message_id": f"wx_{int(time.time() * 1000)}"}
        except Exception as e:
            logger.error(f"微信发送失败: {e}")
            return {"success": False, "error": str(e)}


    def get_messages(self, limit: int = 20, conversation_id: str = None, count: int = None, **kwargs) -> Dict[str, Any]:
        """
        获取消息列表
        
        Args:
            limit: 消息数量限制
            count: limit 的别名（AI Brain 兼容）
            conversation_id: 会话ID（联系人名称），如果指定则获取该会话的消息
            
        Returns:
            消息列表
        """
        if count is not None and limit == 20:
            limit = count
        try:
            self.launch()
            self.wait_for_app_ready()
            
            messages = []
            
            # 如果指定了会话，先进入
            if conversation_id:
                # 使用搜索进入会话
                if self.click_text("搜索") or self.click_desc("搜索"):
                    self.sleep(0.5)
                    self.input_text(conversation_id)
                    self.sleep(1.5)
                    
                    # 点击搜索结果
                    if not self.click_text(conversation_id):
                        if not self.click_contains(conversation_id):
                            return {"success": False, "error": f"未找到会话: {conversation_id}", "messages": []}
                    
                    self.sleep(1)
            
            # 获取UI树分析消息
            ui_tree = self.d.dump_hierarchy()
            
            # 解析XML提取消息
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                
                # 查找消息元素（通常在聊天界面）
                # 微信消息通常有特定的resource-id或class
                for elem in root.iter():
                    # 查找消息文本
                    text = elem.get("text", "")
                    resource_id = elem.get("resource-id", "")
                    bounds = elem.get("bounds", "")
                    
                    if text and len(text) > 0:
                        # 判断是否是消息内容（排除按钮、输入框等）
                        if "发送" not in text and "输入" not in text:
                            # 提取坐标判断位置（消息通常在屏幕中间）
                            if bounds:
                                try:
                                    coords = bounds.replace("[", "").replace("]", "").split(",")
                                    if len(coords) >= 4:
                                        y = int(coords[1])
                                        # 消息通常在屏幕上半部分
                                        info = self.d.info
                                        if y < info['displayHeight'] * 0.8:
                                            messages.append({
                                                "text": text,
                                                "bounds": bounds,
                                                "timestamp": int(time.time() * 1000)
                                            })
                                except:
                                    pass
                
                # 限制数量
                messages = messages[:limit]
                
            except Exception as e:
                logger.warning(f"解析UI树失败: {e}")
            
            return {
                "success": True,
                "messages": messages,
                "count": len(messages)
            }
        
        except Exception as e:
            logger.error(f"获取消息失败: {e}")
            return {"success": False, "error": str(e), "messages": []}
    
    def get_contacts(self, limit: int = 100) -> Dict[str, Any]:
        """
        获取联系人列表
        
        Args:
            limit: 联系人数量限制
            
        Returns:
            联系人列表
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 点击通讯录
            if not self.click_text("通讯录"):
                return {"success": False, "error": "无法进入通讯录", "contacts": []}
            
            self.sleep(1)
            
            contacts = []
            
            # 获取UI树
            ui_tree = self.d.dump_hierarchy()
            
            # 解析联系人
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                
                # 查找联系人元素
                for elem in root.iter():
                    text = elem.get("text", "")
                    resource_id = elem.get("resource-id", "")
                    
                    # 微信联系人通常有特定的resource-id或class
                    if text and len(text) > 0:
                        # 排除系统元素
                        if "通讯录" not in text and "搜索" not in text:
                            # 检查是否是联系人（通常有头像或特定布局）
                            parent = elem
                            for _ in range(3):  # 向上查找3层
                                if parent is not None:
                                    parent_id = parent.get("resource-id", "")
                                    if "contact" in parent_id.lower() or "item" in parent_id.lower():
                                        contacts.append({
                                            "name": text,
                                            "resource_id": resource_id
                                        })
                                        break
                                    parent = parent.getparent() if hasattr(parent, 'getparent') else None
                
                # 去重
                seen = set()
                unique_contacts = []
                for contact in contacts:
                    name = contact["name"]
                    if name not in seen:
                        seen.add(name)
                        unique_contacts.append(contact)
                
                contacts = unique_contacts[:limit]
                
            except Exception as e:
                logger.warning(f"解析联系人失败: {e}")
                # 降级方案：使用搜索功能获取常用联系人
                # 可以通过搜索历史记录获取
            
            return {
                "success": True,
                "contacts": contacts,
                "count": len(contacts)
            }
        
        except Exception as e:
            logger.error(f"获取联系人失败: {e}")
            return {"success": False, "error": str(e), "contacts": []}
    
    def add_friend(self, user_id: str, message: str = "") -> Dict[str, Any]:
        """添加好友"""
        self.natural_behavior_before("add_friend")
        try:
            self.launch()
            self.sleep(2)
            
            # 点击+号
            self.click_desc("+") or self.click_text("+")
            self.sleep(0.5)
            
            # 添加朋友
            self.click_text("添加朋友")
            self.sleep(0.5)
            
            # 输入微信号
            self.input_text(user_id)
            self.sleep(1)
            
            # 搜索
            self.click_text("搜索") or self.click_contains("搜索")
            self.sleep(2)
            
            # 添加
            if self.click_text("添加到通讯录"):
                if message:
                    self.sleep(0.5)
                    self.input_text(message)
                self.click_text("发送")
                return {"success": True}
            
            return {"success": False, "error": "未找到用户或已是好友"}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def accept_friend(self, user_id: str = None, **kwargs) -> Dict[str, Any]:
        """通过好友请求 [控制形式: u2 UI自动化, 耗时~5s]"""
        try:
            self.launch()
            self.sleep(2)
            self.click_text("通讯录")
            self.sleep(1)
            self.click_text("新的朋友")
            self.sleep(1)

            if user_id:
                ui = self.d.dump_hierarchy()
                import xml.etree.ElementTree as ET
                root = ET.fromstring(ui)
                found = False
                for elem in root.iter():
                    if elem.get("text", "") == user_id:
                        bounds = elem.get("bounds", "")
                        if bounds:
                            coords = bounds.replace("[", "").replace("]", ",").split(",")
                            y_center = (int(coords[1]) + int(coords[4])) // 2
                            accept_x = int(self.d.info["displayWidth"] * 0.85)
                            self.click(accept_x, y_center)
                            self.sleep(1)
                            found = True
                            break
                if not found:
                    if self.click_text("接受"):
                        return {"success": True}
                    return {"success": False, "error": f"未找到用户 {user_id} 的好友请求"}
                return {"success": True, "user_id": user_id}

            if self.click_text("接受"):
                return {"success": True}
            return {"success": False, "error": "无待处理的好友请求"}
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    # ==========================================================================
    # 三、群聊管理
    # ==========================================================================
    
    def create_group(self, group_name: str, member_ids: List[str]) -> Dict[str, Any]:
        """
        创建群聊
        
        Args:
            group_name: 群名称
            member_ids: 成员名称列表（至少2人）
            
        Returns:
            执行结果
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 点击右上角+号
            self.click_desc("+") or self.click_text("+")
            self.sleep(0.5)
            
            # 发起群聊
            self.click_text("发起群聊")
            self.sleep(1)
            
            # 选择群成员
            for member_id in member_ids:
                # 搜索成员
                if self.click_text("搜索") or self.click_desc("搜索"):
                    self.sleep(0.3)
                    self.input_text(member_id)
                    self.sleep(1)
                    
                    # 选中成员
                    self.click_text(member_id) or self.click_contains(member_id)
                    self.sleep(0.5)
                    
                    # 清空搜索框
                    self.back()
                    self.sleep(0.3)
            
            # 确定创建
            self.click_text("完成") or self.click_text("确定")
            self.sleep(2)
            
            # 修改群名
            if group_name:
                # 点击群名区域
                self.click_text("群聊") or self.click_contains("的群聊")
                self.sleep(0.5)
                self.input_text(group_name, clear=True)
                self.click_text("完成") or self.click_text("确定")
            
            logger.info(f"创建群聊成功: {group_name}")
            return {
                "success": True,
                "group_name": group_name,
                "member_count": len(member_ids) + 1
            }
        
        except Exception as e:
            logger.error(f"创建群聊失败: {e}")
            return {"success": False, "error": str(e)}
    
    def invite_to_group(self, group_id: str, member_ids: List[str]) -> Dict[str, Any]:
        """
        邀请入群
        
        Args:
            group_id: 群名称
            member_ids: 要邀请的成员名称列表
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 搜索进入群聊
            if self.click_text("搜索") or self.click_desc("搜索"):
                self.sleep(0.5)
                self.input_text(group_id)
                self.sleep(1.5)
                self.click_text(group_id) or self.click_contains(group_id)
                self.sleep(1)
            
            # 点击右上角群设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3) or \
                self.click_text("...")
            self.sleep(1)
            
            # 点击+号添加成员
            self.click_desc("+") or self.click_text("+")
            self.sleep(1)
            
            # 选择成员
            invited_count = 0
            for member_id in member_ids:
                if self.click_text("搜索") or self.click_desc("搜索"):
                    self.sleep(0.3)
                    self.input_text(member_id)
                    self.sleep(1)
                    
                    if self.click_text(member_id) or self.click_contains(member_id):
                        invited_count += 1
                    
                    self.back()
                    self.sleep(0.3)
            
            # 确定
            self.click_text("完成") or self.click_text("确定")
            
            return {
                "success": True,
                "invited_count": invited_count
            }
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def remove_from_group(self, group_id: str, member_ids: List[str]) -> Dict[str, Any]:
        """移出群聊"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入群聊
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            # 进入群设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 点击-号移除成员
            self.click_desc("-")
            self.sleep(1)
            
            removed_count = 0
            for member_id in member_ids:
                if self.click_text(member_id):
                    removed_count += 1
            
            self.click_text("完成") or self.click_text("删除")
            
            return {"success": True, "removed_count": removed_count}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def send_group_message(
        self,
        group_id: str,
        content: str,
        msg_type: str = "text",
        at_all: bool = False,
        at_list: List[str] = None
    ) -> Dict[str, Any]:
        """
        发送群消息
        
        Args:
            group_id: 群名称
            content: 消息内容
            msg_type: 消息类型
            at_all: 是否@所有人
            at_list: @的成员列表
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 搜索进入群聊
            self.search(group_id)
            self.sleep(1)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            # 处理@
            if at_all:
                content = "@所有人 " + content
            elif at_list:
                at_str = " ".join([f"@{name}" for name in at_list])
                content = at_str + " " + content
            
            # 输入消息
            self.input_text(content, clear=False)
            self.sleep(0.3)
            
            # 发送
            self.click_text("发送")
            
            return {
                "success": True,
                "message_id": f"wx_group_{int(time.time() * 1000)}"
            }
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def set_group_notice(self, group_id: str, notice: str) -> Dict[str, Any]:
        """设置群公告"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入群聊
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            # 进入群设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 群公告
            self.click_text("群公告")
            self.sleep(1)
            
            # 输入公告
            self.input_text(notice, clear=True)
            
            # 发布
            self.click_text("完成") or self.click_text("发布")
            
            return {"success": True}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def set_group_name(self, group_id: str, group_name: str) -> Dict[str, Any]:
        """设置群名"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入群聊
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            # 进入群设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 点击群名
            self.click_text("群聊名称") or self.click_contains("群名")
            self.sleep(0.5)
            
            # 输入新群名
            self.input_text(group_name, clear=True)
            
            # 确定
            self.click_text("完成") or self.click_text("确定")
            
            return {"success": True}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def set_group_welcome(self, group_id: str, welcome_text: str, welcome_image: str = None) -> Dict[str, Any]:
        """设置群欢迎语（需要群主/管理员权限）"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入群聊设置
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 群管理
            if not self.click_text("群管理"):
                return {"success": False, "error": "未找到群管理选项，可能不是群主/管理员"}
            self.sleep(1)
            
            # 入群欢迎语
            self.click_text("入群欢迎语") or self.click_contains("欢迎语")
            self.sleep(1)
            
            # 输入欢迎语
            self.input_text(welcome_text, clear=True)
            
            # 保存
            self.click_text("完成") or self.click_text("保存")
            
            return {"success": True}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def get_groups(self, limit: int = 100) -> Dict[str, Any]:
        """获取群聊列表"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 通讯录 -> 群聊
            self.click_text("通讯录")
            self.sleep(1)
            
            self.click_text("群聊")
            self.sleep(1)
            
            # 获取UI树解析群列表
            groups = []
            ui_tree = self.d.dump_hierarchy()
            
            # 解析群名（简化实现）
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                for elem in root.iter():
                    text = elem.get("text", "")
                    if text and "群聊" not in text and "通讯录" not in text:
                        # 可能是群名
                        groups.append({"name": text})
            except:
                pass
            
            return {
                "success": True,
                "groups": groups[:limit],
                "count": len(groups[:limit])
            }
        
        except Exception as e:
            return {"success": False, "error": str(e), "groups": []}
    
    def get_group_members(self, group_id: str) -> Dict[str, Any]:
        """获取群成员列表"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入群聊
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            
            # 进入群设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 点击查看全部成员
            self.click_contains("查看全部") or self.click_text("全部群成员")
            self.sleep(1)
            
            # 获取成员列表
            members = []
            ui_tree = self.d.dump_hierarchy()
            
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                for elem in root.iter():
                    text = elem.get("text", "")
                    if text and len(text) > 0:
                        if "群成员" not in text and "搜索" not in text:
                            members.append({"name": text})
            except:
                pass
            
            return {
                "success": True,
                "members": members,
                "count": len(members)
            }
        
        except Exception as e:
            return {"success": False, "error": str(e), "members": []}
    
    # ==========================================================================
    # 四、标签管理
    # ==========================================================================
    
    def add_tag(self, user_id: str, tags: List[str]) -> Dict[str, Any]:
        """
        给好友添加标签
        
        Args:
            user_id: 好友名称
            tags: 标签列表
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 搜索好友
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            
            # 进入聊天界面后点击头像进入详情
            # 或者直接在搜索结果点击头像
            
            # 点击更多设置
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3) or \
                self.click_text("...")
            self.sleep(1)
            
            # 设置标签
            self.click_text("设置标签") or self.click_contains("标签")
            self.sleep(1)
            
            # 选择/创建标签
            for tag in tags:
                if self.exists(tag):
                    self.click_text(tag)
                else:
                    # 创建新标签
                    self.click_text("添加标签") or self.click_text("新建标签")
                    self.sleep(0.5)
                    self.input_text(tag)
                    self.click_text("完成") or self.click_text("确定")
                self.sleep(0.3)
            
            # 保存
            self.click_text("完成") or self.click_text("保存")
            
            return {"success": True, "tags_added": tags}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def remove_tag(self, user_id: str, tags: List[str]) -> Dict[str, Any]:
        """移除好友标签"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入好友详情
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 设置标签
            self.click_text("设置标签") or self.click_contains("标签")
            self.sleep(1)
            
            # 取消选择标签
            for tag in tags:
                if self.exists(tag):
                    self.click_text(tag)  # 再次点击取消选择
                self.sleep(0.3)
            
            # 保存
            self.click_text("完成") or self.click_text("保存")
            
            return {"success": True, "tags_removed": tags}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def create_tag(self, tag_name: str) -> Dict[str, Any]:
        """创建标签"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 通讯录 -> 标签
            self.click_text("通讯录")
            self.sleep(1)
            
            self.click_text("标签")
            self.sleep(1)
            
            # 新建标签
            self.click_text("新建") or self.click_text("添加标签")
            self.sleep(0.5)
            
            self.input_text(tag_name)
            self.click_text("完成") or self.click_text("保存")
            
            return {"success": True, "tag_name": tag_name}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def delete_tag(self, tag_name: str) -> Dict[str, Any]:
        """删除标签"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 通讯录 -> 标签
            self.click_text("通讯录")
            self.sleep(1)
            
            self.click_text("标签")
            self.sleep(1)
            
            # 找到并长按标签
            if self.exists(tag_name):
                # 长按删除
                element = self.d(text=tag_name)
                if element.exists:
                    element.long_click()
                    self.sleep(0.5)
                    self.click_text("删除")
                    self.click_text("确定")
                    return {"success": True}
            
            return {"success": False, "error": f"未找到标签: {tag_name}"}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def get_tags(self) -> Dict[str, Any]:
        """获取标签列表"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 通讯录 -> 标签
            self.click_text("通讯录")
            self.sleep(1)
            
            self.click_text("标签")
            self.sleep(1)
            
            # 解析标签列表
            tags = []
            ui_tree = self.d.dump_hierarchy()
            
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                for elem in root.iter():
                    text = elem.get("text", "")
                    if text and "标签" not in text and "新建" not in text:
                        # 提取标签名（格式可能是"标签名(数量)"）
                        match = re.match(r'^(.+?)(?:\(\d+\))?$', text)
                        if match:
                            tags.append(match.group(1))
            except:
                pass
            
            return {"success": True, "tags": tags}
        
        except Exception as e:
            return {"success": False, "error": str(e), "tags": []}
    
    def get_users_by_tag(self, tag_name: str, limit: int = 100) -> Dict[str, Any]:
        """根据标签获取好友列表"""
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 通讯录 -> 标签
            self.click_text("通讯录")
            self.sleep(1)
            
            self.click_text("标签")
            self.sleep(1)
            
            # 点击标签
            self.click_text(tag_name) or self.click_contains(tag_name)
            self.sleep(1)
            
            # 解析好友列表
            users = []
            ui_tree = self.d.dump_hierarchy()
            
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                for elem in root.iter():
                    text = elem.get("text", "")
                    if text and len(text) > 0:
                        if tag_name not in text and "人" not in text:
                            users.append({"name": text})
            except:
                pass
            
            return {
                "success": True,
                "users": users[:limit],
                "count": len(users[:limit])
            }
        
        except Exception as e:
            return {"success": False, "error": str(e), "users": []}
    
    # ==========================================================================
    # 五、朋友圈管理
    # ==========================================================================
    
    def post_moments(
        self,
        content: str,
        images: List[str] = None,
        video_url: str = None,
        location: str = None,
        visible_list: List[str] = None,
        invisible_list: List[str] = None
    ) -> Dict[str, Any]:
        """
        发布朋友圈
        
        Args:
            content: 文字内容
            images: 图片URL列表（需要先下载到本地）
            video_url: 视频URL
            location: 位置
            visible_list: 可见名单
            invisible_list: 不可见名单
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 发现 -> 朋友圈
            self.click_text("发现")
            self.sleep(1)
            self.click_text("朋友圈")
            self.sleep(2)
            
            # 点击相机图标发布
            camera = self.d.xpath('//*[@content-desc="拍照分享"]')
            if camera.exists:
                if images or video_url:
                    # 短按：选择图片/视频
                    camera.click()
                else:
                    # 长按：纯文字
                    camera.long_click()
            self.sleep(1)
            
            if images:
                # 从相册选择
                self.click_text("从相册选择")
                self.sleep(1)
                # TODO: 选择图片（需要图片已在相册中）
                # 这里简化处理，假设选择第一张
                self.click_text("完成") or self.click_text("发送")
            
            self.sleep(1)
            
            # 输入文字内容
            if self.click_contains("这一刻的想法") or self.click_contains("说点什么"):
                self.input_text(content)
            else:
                self.input_text(content)
            
            # 设置位置
            if location:
                self.click_text("所在位置")
                self.sleep(1)
                self.input_text(location)
                self.sleep(1)
                self.click_text(location) or self.click_contains(location)
            
            # 设置可见范围
            if visible_list or invisible_list:
                self.click_text("谁可以看")
                self.sleep(0.5)
                if visible_list:
                    self.click_text("部分可见")
                    # TODO: 选择可见的人
                elif invisible_list:
                    self.click_text("不给谁看")
                    # TODO: 选择不可见的人
                self.click_text("完成")
            
            # 发表
            self.click_text("发表")
            self.sleep(2)
            
            return {
                "success": True,
                "post_id": f"moments_{int(time.time() * 1000)}"
            }
        
        except Exception as e:
            logger.error(f"发布朋友圈失败: {e}")
            return {"success": False, "error": str(e)}
    
    def like_moments(self, user_id: str, post_index: int = 0) -> Dict[str, Any]:
        """
        点赞朋友圈
        
        Args:
            user_id: 好友名称
            post_index: 第几条朋友圈（0表示最新一条）
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入好友朋友圈
            self._enter_user_moments(user_id)
            
            # 滚动到指定位置
            for _ in range(post_index):
                self.swipe("up", scale=0.3)
                self.sleep(0.5)
            
            # 点击评论按钮
            self.click_desc("评论") or self.d.xpath('//*[@content-desc="评论"]').click()
            self.sleep(0.5)
            
            # 点击赞
            self.click_text("赞")
            
            return {"success": True}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def comment_moments(
        self,
        user_id: str,
        comment: str,
        post_index: int = 0,
        reply_to: str = None
    ) -> Dict[str, Any]:
        """
        评论朋友圈
        
        Args:
            user_id: 好友名称
            comment: 评论内容
            post_index: 第几条朋友圈
            reply_to: 回复某人
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入好友朋友圈
            self._enter_user_moments(user_id)
            
            # 滚动到指定位置
            for _ in range(post_index):
                self.swipe("up", scale=0.3)
                self.sleep(0.5)
            
            # 点击评论按钮
            self.click_desc("评论") or self.d.xpath('//*[@content-desc="评论"]').click()
            self.sleep(0.5)
            
            # 点击评论
            self.click_text("评论")
            self.sleep(0.3)
            
            # 如果要回复某人
            if reply_to:
                # 找到该人的评论并点击
                self.click_text(reply_to)
                self.sleep(0.3)
            
            # 输入评论
            self.input_text(comment)
            
            # 发送
            self.click_text("发送")
            
            return {"success": True}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def get_moments(self, user_id: str = None, limit: int = 10) -> Dict[str, Any]:
        """
        获取朋友圈列表
        
        Args:
            user_id: 好友名称，不指定则获取自己的朋友圈
            limit: 获取数量
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            if user_id:
                self._enter_user_moments(user_id)
            else:
                # 进入自己的朋友圈
                self.click_text("发现")
                self.sleep(1)
                self.click_text("朋友圈")
                self.sleep(2)
                # 点击自己的头像
                # TODO: 进入个人相册
            
            # 获取朋友圈内容
            moments = []
            ui_tree = self.d.dump_hierarchy()
            
            # 简化解析
            import xml.etree.ElementTree as ET
            try:
                root = ET.fromstring(ui_tree)
                for elem in root.iter():
                    text = elem.get("text", "")
                    if text and len(text) > 10:  # 可能是朋友圈内容
                        moments.append({"content": text[:100]})
            except:
                pass
            
            return {
                "success": True,
                "moments": moments[:limit],
                "count": len(moments[:limit])
            }
        
        except Exception as e:
            return {"success": False, "error": str(e), "moments": []}
    
    def _enter_user_moments(self, user_id: str):
        """进入好友的朋友圈（内部方法）"""
        # 搜索好友
        self.search(user_id)
        self.click_text(user_id) or self.click_contains(user_id)
        self.sleep(1)
        
        # 进入好友详情
        self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
        self.sleep(1)
        
        # 点击头像进入详情页
        # 然后点击朋友圈
        self.click_text("朋友圈") or self.click_contains("个人相册")
        self.sleep(2)
    
    # ==========================================================================
    # 六、批量操作
    # ==========================================================================
    
    def batch_send_message(
        self,
        to_ids: List[str],
        content: str,
        msg_type: str = "text",
        interval: float = 2.0
    ) -> Dict[str, Any]:
        """
        批量发送消息
        
        Args:
            to_ids: 接收者ID列表
            content: 消息内容
            msg_type: 消息类型
            interval: 发送间隔(秒)
            
        Returns:
            批量发送结果
        """
        results = []
        success_count = 0
        failed_count = 0
        
        for to_id in to_ids:
            try:
                result = self.send_message(to_id, content, msg_type)
                
                if result.get("success"):
                    success_count += 1
                else:
                    failed_count += 1
                
                results.append({
                    "to_id": to_id,
                    "success": result.get("success", False),
                    "error": result.get("error"),
                    "message_id": result.get("message_id")
                })
                
                # 发送间隔
                time.sleep(interval)
                
            except Exception as e:
                failed_count += 1
                results.append({
                    "to_id": to_id,
                    "success": False,
                    "error": str(e)
                })
        
        return {
            "success": True,
            "success_count": success_count,
            "failed_count": failed_count,
            "results": results
        }
    
    def batch_add_friend(
        self,
        user_ids: List[str],
        message: str = "",
        interval: float = 5.0
    ) -> Dict[str, Any]:
        """
        批量添加好友
        
        Args:
            user_ids: 用户ID列表
            message: 验证消息
            interval: 添加间隔(秒)
        """
        results = []
        success_count = 0
        failed_count = 0
        
        for user_id in user_ids:
            try:
                result = self.add_friend(user_id, message)
                
                if result.get("success"):
                    success_count += 1
                else:
                    failed_count += 1
                
                results.append({
                    "user_id": user_id,
                    "success": result.get("success", False),
                    "error": result.get("error")
                })
                
                time.sleep(interval)
                
            except Exception as e:
                failed_count += 1
                results.append({
                    "user_id": user_id,
                    "success": False,
                    "error": str(e)
                })
        
        return {
            "success": True,
            "success_count": success_count,
            "failed_count": failed_count,
            "results": results
        }
    
    # ==========================================================================
    # 七、好友管理扩展
    # ==========================================================================
    
    def set_remark(self, user_id: str, remark: str) -> Dict[str, Any]:
        """
        设置好友备注
        
        Args:
            user_id: 好友名称
            remark: 新备注
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 进入好友详情
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            
            # 设置备注和标签
            self.click_text("设置备注和标签") or self.click_contains("备注")
            self.sleep(0.5)
            
            # 输入备注
            self.input_text(remark, clear=True)
            
            # 保存
            self.click_text("完成") or self.click_text("保存")
            
            return {"success": True, "remark": remark}
        
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    def delete_friend(self, user_id: str, **kwargs) -> Dict[str, Any]:
        """删除好友 [控制形式: u2 UI自动化, 耗时~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            W = self.d.info["displayWidth"]
            self.click(int(W * 0.2), int(self.d.info["displayHeight"] * 0.12))
            self.sleep(1.5)
            self.click_text("...") or self.click_desc("更多")
            self.sleep(0.5)
            self.click_text("删除")
            self.sleep(0.5)
            self.click_text("删除") or self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}
    
    # ==========================================================================
    # 八、辅助方法
    # ==========================================================================
    
    def send_message_with_search(self, to_id: str, content: str) -> Dict[str, Any]:
        """
        通过搜索发送消息（更可靠）
        
        Args:
            to_id: 联系人名称/备注
            content: 消息内容
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            
            # 使用搜索功能
            search_result = self.search(to_id)
            if not search_result.get("success"):
                return {"success": False, "error": "搜索联系人失败"}
            
            self.sleep(1)
            
            # 点击第一个搜索结果
            if not self.click_text(to_id):
                if not self.click_contains(to_id):
                    return {"success": False, "error": f"未找到联系人: {to_id}"}
            
            self.sleep(1)
            
            # 确保在聊天界面
            if not self.exists("发送") and not self.exists("按住 说话"):
                return {"success": False, "error": "未能进入聊天界面"}
            
            # 输入并发送消息
            self.input_text(content, clear=False)
            self.sleep(0.3)
            self.click_text("发送")
            
            return {
                "success": True,
                "message_id": f"wx_{int(time.time() * 1000)}"
            }
            
        except Exception as e:
            logger.error(f"微信发送失败: {e}")
            return {"success": False, "error": str(e)}
    
    def execute_compound_task(self, task_description: str) -> Dict[str, Any]:
        """
        执行复合任务（如"打开微信给张三发消息：下午开会"）
        
        Args:
            task_description: 任务描述
            
        Returns:
            执行结果
        """
        try:
            # 解析任务
            # 提取联系人
            contact_match = re.search(r'给(.+?)发', task_description)
            if not contact_match:
                return {"success": False, "error": "无法解析联系人"}
            
            contact = contact_match.group(1).strip()
            
            # 提取消息内容
            content_match = re.search(r'发消息[：:](.+)', task_description)
            if not content_match:
                content_match = re.search(r'发(.+)', task_description)
            
            if not content_match:
                return {"success": False, "error": "无法解析消息内容"}
            
            content = content_match.group(1).strip()
            
            # 执行发送
            return self.send_message_with_search(contact, content)
            
        except Exception as e:
            logger.error(f"执行复合任务失败: {e}")
            return {"success": False, "error": str(e)}
    
    def get_current_screen_info(self) -> Dict[str, Any]:
        """获取当前屏幕信息（调试用）"""
        try:
            ui_tree = self.d.dump_hierarchy()
            info = self.d.info
            return {
                "success": True,
                "display_width": info.get("displayWidth"),
                "display_height": info.get("displayHeight"),
                "current_package": info.get("currentPackageName"),
                "ui_tree_length": len(ui_tree)
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 九、消息高级操作（新增）
    # 控制形式: u2 UI自动化 | Hook可加速(forward/recall走Frida RPC更快)
    # ======================================================================

    def forward_message(self, to_id: str = "", content: str = "", **kwargs) -> Dict[str, Any]:
        """转发消息 [u2, ~6s]"""
        try:
            self.launch()
            self.sleep(1)
            self.long_click(int(self.d.info["displayWidth"] * 0.5),
                              int(self.d.info["displayHeight"] * 0.5), duration=1.0)
            self.sleep(0.5)
            if not (self.click_text("转发") or self.click_text("逐条转发")):
                return {"success": False, "error": "未找到转发选项"}
            self.sleep(1)
            if to_id:
                self.click_text("搜索") or self.click_desc("搜索")
                self.sleep(0.3)
                self.input_text(to_id)
                self.sleep(1)
                self.click_text(to_id) or self.click_contains(to_id)
                self.sleep(0.5)
            self.click_text("发送") or self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def forward_multiple(self, msg_svr_ids: List[str] = None, to_id: str = "", **kwargs) -> Dict[str, Any]:
        """合并/逐条转发多条消息 [u2, 默认基于当前聊天上下文进入转发流程]"""
        result = self.forward_message(to_id=to_id, **kwargs)
        if result.get("success"):
            result["msg_svr_ids"] = msg_svr_ids or []
            result.setdefault("note", "已进入转发流程；多选依赖当前聊天上下文")
        return result

    def recall_message(self, **kwargs) -> Dict[str, Any]:
        """撤回最近一条消息 [u2, ~3s]"""
        try:
            self.launch()
            self.sleep(0.5)
            H = self.d.info["displayHeight"]
            W = self.d.info["displayWidth"]
            self.long_click(int(W * 0.7), int(H * 0.85), duration=1.0)
            self.sleep(0.5)
            if self.click_text("撤回"):
                self.sleep(0.5)
                self.click_text("确定")
                return {"success": True}
            return {"success": False, "error": "未找到撤回选项"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def send_card(self, to_id: str = "", card_wxid: str = "", **kwargs) -> Dict[str, Any]:
        """发送名片 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(to_id)
            self.click_text(to_id) or self.click_contains(to_id)
            self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("名片") or self.click_contains("名片")
            self.sleep(1)
            if card_wxid:
                self.click_text("搜索") or self.click_desc("搜索")
                self.sleep(0.3)
                self.input_text(card_wxid)
                self.sleep(1)
                self.click_text(card_wxid) or self.click_contains(card_wxid)
            self.sleep(0.5)
            self.click_text("发送") or self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十、好友管理扩展（新增）
    # 控制形式: u2 UI自动化 | Hook可读(search_contact/get_friend_info走SQLite)
    # ======================================================================

    def search_contact(self, keyword: str = "", **kwargs) -> Dict[str, Any]:
        """搜索联系人 [Hook优先(SQLite), u2降级, ~2s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(keyword)
            self.sleep(1.5)
            results = []
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            skip = {"搜索", "取消", "清除", keyword}
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and len(t) > 0 and t not in skip:
                    results.append({"name": t})
            return {"success": True, "contacts": results[:20]}
        except Exception as e:
            return {"success": False, "error": str(e), "contacts": []}

    def get_friend_info(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """获取好友详细资料 [Hook优先(SQLite rcontact), u2降级, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            self.click(int(W * 0.2), int(H * 0.12))
            self.sleep(1.5)
            info = {}
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            texts = [e.get("text", "") for e in root.iter() if e.get("text", "").strip()]
            info["raw_texts"] = texts[:20]
            for t in texts:
                if "微信号" in t:
                    info["wxid"] = t.replace("微信号：", "").replace("微信号:", "").strip()
                if "地区" in t or "Region" in t:
                    info["region"] = t
            info["nickname"] = user_id
            return {"success": True, **info}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十一、群聊扩展（新增）
    # ======================================================================

    def quit_group(self, group_id: str = "", **kwargs) -> Dict[str, Any]:
        """退出群聊 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            for _ in range(5):
                self.swipe("up", scale=0.5)
                self.sleep(0.5)
            if self.click_text("删除并退出") or self.click_text("退出群聊"):
                self.sleep(0.5)
                self.click_text("确定") or self.click_text("退出")
                return {"success": True}
            return {"success": False, "error": "未找到退出群聊按钮"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十二、朋友圈增强（新增）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def delete_moments(self, post_index: int = 0, **kwargs) -> Dict[str, Any]:
        """删除朋友圈 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("朋友圈")
            self.sleep(2)
            self.click(int(self.d.info["displayWidth"] * 0.15),
                         int(self.d.info["displayHeight"] * 0.08))
            self.sleep(2)
            for _ in range(post_index):
                self.swipe("up", scale=0.3)
                self.sleep(0.5)
            self.long_click(int(self.d.info["displayWidth"] * 0.5),
                              int(self.d.info["displayHeight"] * 0.4), duration=1.0)
            self.sleep(0.5)
            if self.click_text("删除"):
                self.sleep(0.3)
                self.click_text("确定") or self.click_text("删除")
                return {"success": True}
            return {"success": False, "error": "未找到删除选项"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_moments_cover(self, **kwargs) -> Dict[str, Any]:
        """设置朋友圈封面 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("朋友圈")
            self.sleep(2)
            self.click(int(self.d.info["displayWidth"] * 0.5),
                         int(self.d.info["displayHeight"] * 0.15))
            self.sleep(1)
            self.click_text("换封面") or self.click_text("更换封面") or self.click_contains("封面")
            self.sleep(1)
            self.click_text("从手机相册选择") or self.click_text("相册")
            self.sleep(1)
            return {"success": True, "note": "已打开相册选择页面"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_moments_privacy(self, days: int = 180, **kwargs) -> Dict[str, Any]:
        """设置朋友圈可见天数 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("朋友权限") or self.click_text("隐私")
            self.sleep(1)
            self.click_text("朋友圈") or self.click_contains("允许朋友查看")
            self.sleep(1)
            day_map = {3: "最近三天", 30: "最近一个月", 180: "最近半年", 0: "全部"}
            target = day_map.get(days, "最近半年")
            if self.click_text(target) or self.click_contains(target):
                return {"success": True, "days": days}
            return {"success": False, "error": f"未找到选项: {target}"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def forward_moments_link(self, url: str = "", title: str = "", **kwargs) -> Dict[str, Any]:
        """分享链接到朋友圈 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("朋友圈")
            self.sleep(2)
            camera = self.d.xpath('//*[@content-desc="拍照分享"]')
            if camera.exists:
                camera.click()
            self.sleep(1)
            if title:
                self.input_text(f"{title}\n{url}" if url else title)
            elif url:
                self.input_text(url)
            self.sleep(0.5)
            self.click_text("发表")
            self.sleep(2)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十三、个人设置（新增6个）
    # 控制形式: u2 UI自动化 | Hook只能读(get_profile走SQLite)
    # ======================================================================

    def _goto_personal_info(self):
        """进入个人信息页（内部方法）"""
        self.click_text("我")
        self.sleep(1)
        W = self.d.info["displayWidth"]
        self.click(int(W * 0.3), int(self.d.info["displayHeight"] * 0.12))
        self.sleep(1.5)

    def get_profile(self, **kwargs) -> Dict[str, Any]:
        """获取当前微信账号资料 [Hook优先(SQLite userinfo, ~50ms), u2降级~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            texts = [e.get("text", "").strip() for e in root.iter() if e.get("text", "").strip()]
            profile = {"raw_texts": texts[:30]}
            for t in texts:
                if "微信号" in t:
                    profile["wxid"] = t.split("：")[-1].split(":")[-1].strip()
                if "地区" in t:
                    profile["region"] = t
            if texts:
                profile["nickname"] = texts[0]
            return {"success": True, **profile}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_nickname(self, nickname: str = "", **kwargs) -> Dict[str, Any]:
        """修改微信昵称 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            self.click_text("名字") or self.click_contains("昵称")
            self.sleep(0.5)
            self.input_text(nickname, clear=True)
            self.sleep(0.3)
            self.click_text("保存") or self.click_text("完成")
            return {"success": True, "nickname": nickname}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_signature(self, signature: str = "", **kwargs) -> Dict[str, Any]:
        """修改个性签名 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            if not (self.click_text("个性签名") or self.click_contains("签名")):
                self.click_text("更多信息")
                self.sleep(0.5)
                self.click_text("个性签名")
            self.sleep(0.5)
            self.input_text(signature, clear=True)
            self.sleep(0.3)
            self.click_text("保存") or self.click_text("完成")
            return {"success": True, "signature": signature}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_status(self, status: str = "", **kwargs) -> Dict[str, Any]:
        """设置微信状态 [u2, 默认打开状态入口]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("+状态") or self.click_text("状态") or self.click_contains("状态")
            self.sleep(1)
            if status and bool(kwargs.get("confirm")):
                self.input_text(status)
                self.sleep(0.3)
                self.click_text("就这样") or self.click_text("完成")
            return {
                "success": True,
                "status": status,
                "dry_run": not bool(kwargs.get("confirm")),
                "confirm_required": bool(status) and not bool(kwargs.get("confirm")),
                "note": "已打开微信状态入口",
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_avatar(self, image_path: str = None, **kwargs) -> Dict[str, Any]:
        """修改头像 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            self.click_text("头像")
            self.sleep(1)
            self.click_text("从手机相册选择") or self.click_text("相册")
            self.sleep(1)
            return {"success": True, "note": "已打开相册选择页面"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_gender(self, gender: str = "", **kwargs) -> Dict[str, Any]:
        """设置性别 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            if not (self.click_text("性别") or self.click_contains("性别")):
                self.click_text("更多信息")
                self.sleep(0.5)
                self.click_text("性别")
            self.sleep(0.5)
            target = "男" if gender.lower() in ("male", "男", "m") else "女"
            self.click_text(target)
            self.sleep(0.3)
            self.click_text("保存") or self.click_text("完成")
            return {"success": True, "gender": target}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_region(self, region: str = "", **kwargs) -> Dict[str, Any]:
        """设置地区 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._goto_personal_info()
            if not (self.click_text("地区") or self.click_contains("地区")):
                self.click_text("更多信息")
                self.sleep(0.5)
                self.click_text("地区")
            self.sleep(0.5)
            parts = region.split(" ") if region else ["中国", "福建", "厦门"]
            for part in parts:
                self.click_text(part) or self.click_contains(part)
                self.sleep(0.5)
            self.click_text("完成") or self.click_text("确定")
            return {"success": True, "region": region}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十四、账号安全（新增9个）
    # 控制形式: u2 UI自动化 | Hook仅check_account_status可读
    # ======================================================================

    def check_account_status(self, **kwargs) -> Dict[str, Any]:
        """检查账号状态 [Hook优先(SQLite, ~50ms), u2降级~4s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            curr = self.d.app_current()
            act = curr.get("activity", "")
            logged_in = "LauncherUI" in act or "ChattingUI" in act
            return {
                "success": True,
                "logged_in": logged_in,
                "activity": act,
                "status": "normal" if logged_in else "unknown"
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    def unblock_account(self, helper_wxid: str = None, **kwargs) -> Dict[str, Any]:
        """微信解封 [u2, ~10s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            if self.click_text("微信安全中心") or self.click_contains("安全中心"):
                self.sleep(1)
                self.click_text("解封账号") or self.click_contains("解封")
                self.sleep(1)
                return {"success": True, "note": "已进入解封页面"}
            return {"success": False, "error": "未找到安全中心"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def safety_center(self, **kwargs) -> Dict[str, Any]:
        """打开微信安全中心 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("微信安全中心") or self.click_contains("安全中心")
            self.sleep(1)
            return {"success": True, "note": "已打开安全中心"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def change_password(self, old_pwd: str = "", new_pwd: str = "", **kwargs) -> Dict[str, Any]:
        """修改微信密码 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("微信密码") or self.click_contains("密码")
            self.sleep(1)
            return {"success": True, "note": "已进入密码修改页面"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def bind_phone(self, phone: str = "", **kwargs) -> Dict[str, Any]:
        """绑定手机号 [u2, 默认打开手机号设置入口]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("手机号") or self.click_contains("手机")
            self.sleep(1)
            return {"success": True, "phone": phone, "note": "已打开手机号设置入口"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def unbind_phone(self, **kwargs) -> Dict[str, Any]:
        """解绑银行卡式手机号入口 [u2, 默认只导航]"""
        return self.bind_phone(**kwargs)

    def get_login_devices(self, **kwargs) -> Dict[str, Any]:
        """查看登录设备 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("登录设备管理") or self.click_contains("登录设备")
            self.sleep(1)
            items = []
            try:
                import xml.etree.ElementTree as ET
                root = ET.fromstring(self.d.dump_hierarchy())
                for elem in root.iter():
                    t = elem.get("text", "").strip()
                    if t and t not in ("登录设备管理", "当前设备"):
                        items.append(t[:80])
            except Exception:
                pass
            return {"success": True, "devices": items[:30], "count": len(items[:30])}
        except Exception as e:
            return {"success": False, "error": str(e), "devices": []}

    def remove_login_device(self, device_id: str = "", **kwargs) -> Dict[str, Any]:
        """移除登录设备 [u2, 默认停在列表/确认前]"""
        res = self.get_login_devices(**kwargs)
        if not res.get("success") or not bool(kwargs.get("confirm")):
            res.update({"dry_run": True, "confirm_required": True, "target_device_id": device_id})
            return res
        try:
            if device_id:
                self.click_text(device_id) or self.click_contains(device_id)
                self.sleep(0.5)
            self.click_text("删除") or self.click_text("移除")
            self.sleep(0.5)
            self.click_text("确定") or self.click_text("删除")
            return {"success": True, "device_id": device_id}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def enable_fingerprint(self, enable: bool = True, **kwargs) -> Dict[str, Any]:
        """开启/关闭指纹支付入口 [u2, 默认只导航]"""
        return self._open_payment_settings("指纹", enable=enable)

    def set_account_protection(self, enable: bool = True, **kwargs) -> Dict[str, Any]:
        """账号保护入口 [u2, 默认只导航]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("账号保护") or self.click_contains("保护")
            self.sleep(1)
            return {"success": True, "enable": enable, "note": "已打开账号保护入口"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def _open_payment_settings(self, keyword: str = "", **kwargs) -> Dict[str, Any]:
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("服务") or self.click_text("支付")
            self.sleep(1)
            self.click_text("钱包")
            self.sleep(1)
            self.click_text("支付设置") or self.click_contains("支付设置")
            self.sleep(1)
            if keyword:
                self.click_text(keyword) or self.click_contains(keyword)
                self.sleep(0.5)
            return {"success": True, "keyword": keyword, "note": "已打开支付设置入口"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def unblock_self(self, **kwargs) -> Dict[str, Any]:
        """自助解封 [u2, ~8s]"""
        return self.unblock_account(**kwargs)

    def unblock_appeal(self, **kwargs) -> Dict[str, Any]:
        """申诉解封 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("账号与安全") or self.click_text("帐号与安全")
            self.sleep(1)
            self.click_text("微信安全中心") or self.click_contains("安全中心")
            self.sleep(1)
            self.click_text("申诉") or self.click_contains("申诉")
            return {"success": True, "note": "已进入申诉页面"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def check_restrictions(self, **kwargs) -> Dict[str, Any]:
        """检查当前功能限制 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            curr = self.d.app_current()
            return {
                "success": True,
                "activity": curr.get("activity", ""),
                "restricted": False,
                "note": "功能限制需在安全中心查看"
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    def appeal_restriction(self, **kwargs) -> Dict[str, Any]:
        """申诉功能限制 [u2, ~8s]"""
        return self.unblock_appeal(**kwargs)

    def check_login_state(self, **kwargs) -> Dict[str, Any]:
        """检测微信是否已登录 [u2]"""
        try:
            from .wechat_auto_login import WeChatAutoLogin
            return WeChatAutoLogin(self).check_login_state()
        except Exception as exc:
            return {"logged_in": False, "state": "error", "error": str(exc)}

    def login_by_password(
        self,
        account: str = "",
        phone: str = "",
        password: str = "",
        **kwargs,
    ) -> Dict[str, Any]:
        """微信号/手机号 + 密码登录 [u2]"""
        try:
            from .wechat_auto_login import WeChatAutoLogin
            acct = (account or phone or kwargs.get("username") or "").strip()
            pwd = password or kwargs.get("pwd") or ""
            return WeChatAutoLogin(self).login_by_password(acct, pwd)
        except Exception as exc:
            return {"success": False, "error": str(exc)}

    def ensure_logged_in(
        self,
        account: str = "",
        phone: str = "",
        password: str = "",
        **kwargs,
    ) -> Dict[str, Any]:
        """未登录则按配置/参数自动登录 [u2]"""
        acct = (account or phone or kwargs.get("username") or "").strip()
        pwd = password or kwargs.get("pwd") or ""
        if not acct or not pwd:
            try:
                import os
                import yaml
                cfg_path = os.path.join(
                    os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))),
                    "config", "wechat_login.yaml",
                )
                if os.path.isfile(cfg_path):
                    with open(cfg_path, "r", encoding="utf-8") as f:
                        cfg = yaml.safe_load(f) or {}
                    dev_id = getattr(getattr(self, "d", None), "serial", None) or ""
                    dev_cfg = (cfg.get("devices") or {}).get(dev_id) or {}
                    default = cfg.get("default") or {}
                    acct = acct or dev_cfg.get("account") or default.get("account") or ""
                    pwd = pwd or dev_cfg.get("password") or default.get("password") or ""
            except Exception as exc:
                logger.warning(f"[ensure_logged_in] 读配置失败: {exc}")
        try:
            from .wechat_auto_login import WeChatAutoLogin
            return WeChatAutoLogin(self).ensure_logged_in(acct, pwd)
        except Exception as exc:
            return {"success": False, "error": str(exc)}

    def unblock_with_sms(self, phone: str = "", **kwargs) -> Dict[str, Any]:
        """短信验证解封 [u2, ~8s]"""
        return self.unblock_account(**kwargs)

    def unblock_via_customer_service(
        self,
        reason: str = "",
        phone: str = "",
        wxid: str = "",
        max_rounds: int = 20,
        chat_interval_sec: int = 8,
        use_web_search: bool = True,
        **kwargs,
    ) -> Dict[str, Any]:
        """
        联系客服解封（自动化全链路 + AI 持续对话）[u2 + AI Brain]

        链路：设置 → 账号与安全 → 微信安全中心 → 联系客服 → 客服会话/小程序
              → 抓客服消息 → AI 生成回复 → 发送 → 循环直到解封/拒绝/超时

        参数:
            reason: 账号被封原因（用户自述，会带入 AI 上下文）
            phone / wxid: 申诉时可能用到的账号信息
            max_rounds: 最大对话轮次（默认 20）
            chat_interval_sec: 每轮等待客服回复时间（默认 8s）
            use_web_search: 是否在开始前联网搜「类似申诉经验」作为参考

        返回: {
            "success": bool,
            "status": "success|reject|timeout|fallback|error",
            "message": "客服最终消息",
            "session": { 完整对话上下文 }
        }
        """
        import asyncio
        try:
            from .unblock_customer_service import (
                UnblockCustomerServiceFlow,
                UnblockSession,
            )
        except Exception as exc:
            return {"success": False, "error": f"加载解封模块失败: {exc}"}

        # 从 agent 反向取 AIBrain（可选，缺省时使用兜底话术）
        ai_brain = None
        agent = getattr(self, "agent", None) or getattr(self, "_agent", None)
        if agent is not None:
            ai_brain = getattr(agent, "ai_brain", None)

        session = UnblockSession(
            reason=reason,
            phone=phone,
            wxid=wxid,
            max_rounds=max(1, min(int(max_rounds or 20), 50)),
            chat_interval_sec=max(2, min(int(chat_interval_sec or 8), 60)),
            use_web_search=bool(use_web_search),
        )
        flow = UnblockCustomerServiceFlow(self, ai_brain)

        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                # 在已有事件循环中（Agent 内部）— 用 run_coroutine_threadsafe 或同步降级
                import concurrent.futures
                fut = asyncio.run_coroutine_threadsafe(flow.run(session), loop)
                return fut.result(timeout=max_rounds * (chat_interval_sec + 4) + 60)
        except RuntimeError:
            pass

        return asyncio.run(flow.run(session))

    # ======================================================================
    # 十五、支付（新增7个）
    # 控制形式: u2 UI自动化（支付类操作不走Hook，安全考虑）
    # ======================================================================

    def send_red_packet(self, to_id: str = "", amount: str = "", message: str = "恭喜发财", **kwargs) -> Dict[str, Any]:
        """发红包 [u2, ~10s]"""
        try:
            guard = self._payment_guard(amount, bool(kwargs.get("confirm")), **kwargs)
            if not guard.get("pass"):
                return {"success": False, **guard}
            if guard.get("dry_run"):
                return {
                    "success": True,
                    "action": "send_red_packet",
                    "to_id": to_id,
                    "message": message,
                    **guard,
                }
            self.launch()
            self.wait_for_app_ready()
            self.search(to_id)
            self.click_text(to_id) or self.click_contains(to_id)
            self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("红包")
            self.sleep(1)
            if amount:
                self.input_text(amount)
            if not guard.get("dry_run") and (self.click_text("塞钱进红包") or self.click_contains("塞钱")):
                return {"success": True, "note": "已进入红包支付确认", **guard}
            return {"success": True, "note": "已打开红包页面", **guard}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def transfer(self, to_id: str = "", amount: str = "", message: str = "", **kwargs) -> Dict[str, Any]:
        """转账 [u2, ~10s]"""
        try:
            guard = self._payment_guard(amount, bool(kwargs.get("confirm")), **kwargs)
            if not guard.get("pass"):
                return {"success": False, **guard}
            if guard.get("dry_run"):
                return {
                    "success": True,
                    "action": "transfer",
                    "to_id": to_id,
                    "message": message,
                    **guard,
                }
            self.launch()
            self.wait_for_app_ready()
            self.search(to_id)
            self.click_text(to_id) or self.click_contains(to_id)
            self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("转账")
            self.sleep(1)
            if amount:
                self.input_text(amount)
            if not guard.get("dry_run") and (self.click_text("转账") or self.click_text("确认转账")):
                return {"success": True, "note": "已进入转账支付确认", **guard}
            return {"success": True, "note": "已打开转账页面", **guard}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def send_transfer(self, to_id: str = "", amount: str = "", description: str = "", **kwargs) -> Dict[str, Any]:
        """SDK 标准动作名：转账 [u2, 默认停在确认前]"""
        return self.transfer(to_id=to_id, amount=amount, message=description, **kwargs)

    def show_payment_code(self, **kwargs) -> Dict[str, Any]:
        """显示付款码 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("服务") or self.click_text("支付")
            self.sleep(1)
            self.click_text("收付款") or self.click_contains("付款码")
            self.sleep(1)
            return {"success": True, "note": "已显示付款码"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def receive_payment(self, amount: str = "", desc: str = "", **kwargs) -> Dict[str, Any]:
        """收款 [u2, ~5s]"""
        try:
            guard = self._payment_guard(amount or "0", bool(kwargs.get("confirm")), **kwargs)
            if not guard.get("pass"):
                return {"success": False, **guard}
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("服务") or self.click_text("支付")
            self.sleep(1)
            self.click_text("收付款") or self.click_contains("付款码")
            self.sleep(1)
            self.click_text("二维码收款") or self.click_contains("收款")
            self.sleep(1)
            return {"success": True, "note": "已显示收款码", **guard}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def view_wallet(self, **kwargs) -> Dict[str, Any]:
        """查看钱包 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("服务") or self.click_text("支付")
            self.sleep(1)
            self.click_text("钱包")
            self.sleep(1)
            return {"success": True, "note": "已打开钱包"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def view_transactions(self, limit: int = 20, **kwargs) -> Dict[str, Any]:
        """查看账单 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("服务") or self.click_text("支付")
            self.sleep(1)
            self.click_text("钱包")
            self.sleep(1)
            self.click_text("账单") or self.click_contains("账单")
            self.sleep(1)
            return {"success": True, "note": "已打开账单页面"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def receive_red_packet(self, from_id: str = "", **kwargs) -> Dict[str, Any]:
        """领取红包 [u2, ~5s]"""
        try:
            if not bool(kwargs.get("confirm")):
                return {
                    "success": True,
                    "dry_run": True,
                    "confirm_required": True,
                    "note": "领取红包默认不自动点击“开”，需 confirm=true",
                }
            self.launch()
            self.sleep(1)
            if self.click_text("微信红包") or self.click_contains("红包"):
                self.sleep(0.5)
                self.click_text("开") or self.click_contains("开")
                self.sleep(1)
                return {"success": True}
            return {"success": False, "error": "未找到红包"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def receive_transfer(self, msg_svr_id: str = "", from_id: str = "", **kwargs) -> Dict[str, Any]:
        """领取转账 [u2, 默认停在确认前]"""
        if not bool(kwargs.get("confirm")):
            return {
                "success": True,
                "dry_run": True,
                "confirm_required": True,
                "note": "领取转账默认不自动确认收款，需 confirm=true",
            }
        try:
            self.launch()
            self.sleep(1)
            if self.click_text("转账") or self.click_contains("转账"):
                self.sleep(0.5)
                self.click_text("确认收款") or self.click_contains("收款")
                self.sleep(1)
                return {"success": True}
            return {"success": False, "error": "未找到转账消息"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十六、聊天设置（新增3个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def set_chat_top(self, user_id: str = "", enable: bool = True, **kwargs) -> Dict[str, Any]:
        """置顶/取消置顶聊天 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            self.click_text("置顶聊天") or self.click_contains("置顶")
            self.sleep(0.5)
            return {"success": True, "top": enable}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_mute_chat(self, user_id: str = "", enable: bool = True, **kwargs) -> Dict[str, Any]:
        """消息免打扰 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            self.click_text("消息免打扰") or self.click_contains("免打扰")
            self.sleep(0.5)
            return {"success": True, "muted": enable}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def clear_chat_history(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """清空聊天记录 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            self.click_text("清空聊天记录") or self.click_contains("清空")
            self.sleep(0.5)
            self.click_text("确定") or self.click_text("清空")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 十七、收藏管理（新增2个）
    # 控制形式: u2 UI自动化 | Hook可读(get_favorites走SQLite Favorite.db)
    # ======================================================================

    def add_to_favorites(self, content_desc: str = None, **kwargs) -> Dict[str, Any]:
        """收藏消息 [u2, ~4s]"""
        try:
            H = self.d.info["displayHeight"]
            W = self.d.info["displayWidth"]
            self.long_click(int(W * 0.5), int(H * 0.5), duration=1.0)
            self.sleep(0.5)
            if self.click_text("收藏"):
                return {"success": True}
            return {"success": False, "error": "未找到收藏选项"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def delete_favorite(self, local_id: str = "", **kwargs) -> Dict[str, Any]:
        """删除收藏 [u2, 默认进入收藏列表并停在确认前]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("收藏")
            self.sleep(1.5)
            if not bool(kwargs.get("confirm")):
                return {
                    "success": True,
                    "dry_run": True,
                    "confirm_required": True,
                    "note": "已打开收藏列表，删除收藏需 confirm=true",
                    "local_id": local_id,
                }
            H = self.d.info["displayHeight"]
            W = self.d.info["displayWidth"]
            self.long_click(int(W * 0.5), int(H * 0.25), duration=1.0)
            self.sleep(0.5)
            self.click_text("删除") or self.click_contains("删除")
            self.sleep(0.5)
            self.click_text("删除") or self.click_text("确定")
            return {"success": True, "local_id": local_id}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def get_favorites(self, limit: int = 20, **kwargs) -> Dict[str, Any]:
        """获取收藏列表 [Hook优先(SQLite Favorite.db, ~100ms), u2降级~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("收藏")
            self.sleep(1.5)
            items = []
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and len(t) > 2 and t not in ("收藏", "搜索", "最近使用"):
                    items.append({"content": t[:100]})
            return {"success": True, "favorites": items[:limit]}
        except Exception as e:
            return {"success": False, "error": str(e), "favorites": []}

    # ======================================================================
    # 十八、小程序 & 公众号（新增2个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def open_mini_program(self, name: str = "", **kwargs) -> Dict[str, Any]:
        """打开小程序 [u2, ~8s]"""
        try:
            name = name or kwargs.get("app_id", "")
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("小程序")
            self.sleep(1)
            if name:
                self.click_text("搜索小程序") or self.click_desc("搜索")
                self.sleep(0.5)
                self.input_text(name)
                self.sleep(1.5)
                self.click_text(name) or self.click_contains(name)
                self.sleep(2)
            return {"success": True, "name": name}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def get_recent_mini_programs(self, limit: int = 20, **kwargs) -> Dict[str, Any]:
        """获取最近小程序列表 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("小程序")
            self.sleep(1.5)
            items = []
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and t not in ("小程序", "搜索", "最近使用", "我的小程序"):
                    items.append({"name": t[:80]})
            return {"success": True, "mini_programs": items[:limit], "count": len(items[:limit])}
        except Exception as e:
            return {"success": False, "error": str(e), "mini_programs": []}

    def share_mini_program(self, app_id: str = "", to_id: str = "", title: str = "", **kwargs) -> Dict[str, Any]:
        """分享小程序 [u2, 默认打开小程序，不自动选择接收人]"""
        if not bool(kwargs.get("confirm")):
            try:
                self.launch()
                self.wait_for_app_ready()
                self.click_text("发现")
                self.sleep(1)
                self.click_text("小程序")
                self.sleep(1)
                return {
                    "success": True,
                    "dry_run": True,
                    "confirm_required": True,
                    "note": "已打开小程序入口，分享需 confirm=true 后搜索并发送",
                    "app_id": app_id,
                }
            except Exception as e:
                return {"success": False, "error": str(e)}
        res = self.open_mini_program(name=app_id or title, **kwargs)
        if not res.get("success"):
            return res
        if not to_id or not bool(kwargs.get("confirm")):
            return {
                "success": True,
                "dry_run": True,
                "confirm_required": True,
                "note": "已打开小程序，分享需 to_id + confirm=true 后继续",
                "app_id": app_id,
            }
        try:
            self.click_desc("更多") or self.click_text("...")
            self.sleep(0.5)
            self.click_text("转发") or self.click_text("发送给朋友")
            self.sleep(0.8)
            self.click_text(to_id) or self.click_contains(to_id)
            self.sleep(0.5)
            self.click_text("发送") or self.click_text("确定")
            return {"success": True, "to_id": to_id, "app_id": app_id}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def follow_official_account(self, account_name: str = "", **kwargs) -> Dict[str, Any]:
        """关注公众号 [u2, ~8s]"""
        try:
            account_name = account_name or kwargs.get("account_id", "")
            self.launch()
            self.wait_for_app_ready()
            self.search(account_name)
            self.sleep(1)
            self.click_text(account_name) or self.click_contains(account_name)
            self.sleep(2)
            if self.click_text("关注") or self.click_contains("关注"):
                return {"success": True, "account": account_name}
            return {"success": True, "note": "可能已关注"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def unfollow_official_account(self, account_id: str = "", **kwargs) -> Dict[str, Any]:
        """取消关注公众号 [u2, 默认导航到公众号资料页，人工/二次确认后取消]"""
        account_name = account_id or kwargs.get("account_name", "")
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(account_name)
            self.sleep(1)
            self.click_text(account_name) or self.click_contains(account_name)
            self.sleep(1)
            if not bool(kwargs.get("confirm")):
                return {
                    "success": True,
                    "dry_run": True,
                    "confirm_required": True,
                    "note": "已打开公众号页面，取消关注需 confirm=true",
                    "account": account_name,
                }
            self.click_desc("更多") or self.click_text("...")
            self.sleep(0.5)
            self.click_text("不再关注") or self.click_contains("取消关注")
            self.sleep(0.5)
            self.click_text("不再关注") or self.click_text("确定")
            return {"success": True, "account": account_name}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def get_official_account_articles(self, account_id: str = "", limit: int = 10, **kwargs) -> Dict[str, Any]:
        """获取/打开公众号文章列表 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(account_id)
            self.sleep(1)
            self.click_text(account_id) or self.click_contains(account_id)
            self.sleep(2)
            articles = []
            try:
                import xml.etree.ElementTree as ET
                root = ET.fromstring(self.d.dump_hierarchy())
                for elem in root.iter():
                    t = elem.get("text", "").strip()
                    if t and t not in ("消息", "服务", "发消息"):
                        articles.append({"title": t[:120]})
            except Exception:
                pass
            return {
                "success": True,
                "account_id": account_id,
                "articles": articles[:limit],
                "count": len(articles[:limit]),
            }
        except Exception as e:
            return {"success": False, "error": str(e), "articles": []}

    # ======================================================================
    # 十九、视频号（新增6个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def _enter_video_channel(self):
        """进入视频号（内部方法）"""
        self.click_text("发现")
        self.sleep(1)
        self.click_text("视频号") or self.click_contains("视频号")
        self.sleep(2)

    def open_video_channel(self, **kwargs) -> Dict[str, Any]:
        """打开视频号 [u2, ~3s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def get_video_list(self, limit: int = 10, **kwargs) -> Dict[str, Any]:
        """获取视频号列表 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            videos = []
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and len(t) > 3:
                    videos.append({"title": t[:100]})
            return {"success": True, "videos": videos[:limit]}
        except Exception as e:
            return {"success": False, "error": str(e), "videos": []}

    def like_video(self, index: int = 0, **kwargs) -> Dict[str, Any]:
        """点赞视频 [u2, ~4s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            for _ in range(index):
                self.swipe("up", scale=0.8)
                self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            self.click(int(W * 0.93), int(H * 0.45))
            self.sleep(0.5)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def comment_video(self, index: int = 0, comment: str = "", **kwargs) -> Dict[str, Any]:
        """评论视频 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            for _ in range(index):
                self.swipe("up", scale=0.8)
                self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            self.click(int(W * 0.93), int(H * 0.55))
            self.sleep(1)
            if comment:
                self.input_text(comment)
                self.sleep(0.3)
                self.click_text("发送")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def reply_comment(self, video_id: str = "", comment_id: str = "", content: str = "", **kwargs) -> Dict[str, Any]:
        """回复当前评论 [u2, 默认打开评论区并停在发送前]"""
        try:
            res = self.comment_video(index=int(kwargs.get("index", 0) or 0), comment=content if bool(kwargs.get("confirm")) else "")
            if res.get("success") and not bool(kwargs.get("confirm")):
                res.update({
                    "dry_run": True,
                    "confirm_required": bool(content),
                    "video_id": video_id,
                    "comment_id": comment_id,
                    "note": "已打开评论区，回复发送需 confirm=true",
                })
            return res
        except Exception as e:
            return {"success": False, "error": str(e)}

    def follow_video_creator(self, index: int = 0, **kwargs) -> Dict[str, Any]:
        """关注视频号创作者 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            for _ in range(index):
                self.swipe("up", scale=0.8)
                self.sleep(1)
            self.click(int(self.d.info["displayWidth"] * 0.08),
                         int(self.d.info["displayHeight"] * 0.85))
            self.sleep(1)
            self.click_text("关注") or self.click_contains("关注")
            self.sleep(0.5)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def unfollow_video_creator(self, index: int = 0, **kwargs) -> Dict[str, Any]:
        """取消关注视频号创作者 [u2, 默认停在确认前]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            for _ in range(index):
                self.swipe("up", scale=0.8)
                self.sleep(1)
            self.click(int(self.d.info["displayWidth"] * 0.08),
                         int(self.d.info["displayHeight"] * 0.85))
            self.sleep(1)
            if not bool(kwargs.get("confirm")):
                return {
                    "success": True,
                    "dry_run": True,
                    "confirm_required": True,
                    "note": "已打开视频号创作者入口，取消关注需 confirm=true",
                }
            self.click_text("已关注") or self.click_contains("已关注")
            self.sleep(0.5)
            self.click_text("不再关注") or self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def share_video(self, index: int = 0, to_id: str = "", **kwargs) -> Dict[str, Any]:
        """分享视频 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self._enter_video_channel()
            for _ in range(index):
                self.swipe("up", scale=0.8)
                self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            self.click(int(W * 0.93), int(H * 0.65))
            self.sleep(1)
            if to_id:
                self.click_text(to_id) or self.click_contains(to_id)
                self.sleep(0.5)
                self.click_text("发送") or self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十、扫一扫（新增4个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def scan_qr_code(self, **kwargs) -> Dict[str, Any]:
        """扫描二维码 [u2, ~3s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_desc("+") or self.click_text("+")
            self.sleep(0.5)
            self.click_text("扫一扫")
            self.sleep(2)
            return {"success": True, "note": "已打开扫一扫"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def scan_add_friend(self, **kwargs) -> Dict[str, Any]:
        """扫码加好友 [u2, ~3s]"""
        return self.scan_qr_code(**kwargs)

    def show_my_qr(self, **kwargs) -> Dict[str, Any]:
        """显示我的二维码并截图返回 [u2, ~6s]

        WX-13：不仅导航到二维码页，还截图返回完整 base64 PNG（真正的二维码素材，
        供 SDK/hub 展示或下游识别）。截图失败不影响导航成功标记。
        """
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self._goto_personal_info()
            self.click_text("二维码名片") or self.click_text("我的二维码") or self.click_contains("二维码")
            self.sleep(1.5)
            result = {"success": True, "note": "已显示二维码"}
            try:
                import base64
                from io import BytesIO
                img = self.d.screenshot()
                buf = BytesIO()
                img.save(buf, format="PNG")
                result["qr_image_b64"] = base64.b64encode(buf.getvalue()).decode()
                result["qr_image_format"] = "png"
            except Exception as _se:
                result["qr_image_error"] = str(_se)
            return result
        except Exception as e:
            return {"success": False, "error": str(e)}

    def show_group_qr(self, group_id: str = "", **kwargs) -> Dict[str, Any]:
        """显示群二维码并截图返回 [u2, ~8s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(group_id)
            self.click_text(group_id) or self.click_contains(group_id)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            self.click_text("群二维码") or self.click_contains("二维码")
            self.sleep(1.5)
            result = {"success": True, "note": "已显示群二维码", "group_id": group_id}
            try:
                import base64
                from io import BytesIO
                img = self.d.screenshot()
                buf = BytesIO()
                img.save(buf, format="PNG")
                result["qr_image_b64"] = base64.b64encode(buf.getvalue()).decode()
                result["qr_image_format"] = "png"
            except Exception as _se:
                result["qr_image_error"] = str(_se)
            return result
        except Exception as e:
            return {"success": False, "error": str(e)}

    def _save_qr_image_to_album(self, image_base64: str = "", image_url: str = "") -> str:
        """WP-WX-04：二维码图落盘相册并触发媒体扫描，失败返回空串。"""
        import base64
        import shutil
        import subprocess
        import tempfile

        dst = f"/sdcard/Pictures/wp_qr_{int(time.time())}.png"
        try:
            self.d.shell(f"mkdir -p {shlex.quote(os.path.dirname(dst))}", timeout=8)

            def adb_push_fallback(local_path: str) -> bool:
                adb_bin = shutil.which("adb")
                serial = (
                    getattr(self.d, "serial", "")
                    or getattr(self, "device_id", "")
                    or os.environ.get("ANDROID_SERIAL", "")
                )
                if not adb_bin or not serial:
                    return False
                try:
                    subprocess.run(
                        [adb_bin, "-s", serial, "push", local_path, dst],
                        check=True,
                        capture_output=True,
                        text=True,
                        timeout=30,
                    )
                    return True
                except Exception as adb_err:
                    logger.warning("save_qr_image_to_album adb push fallback failed: %s", adb_err)
                    return False

            if image_url:
                self.d.shell(
                    f"curl -sL -o {shlex.quote(dst)} {shlex.quote(image_url)}",
                    timeout=30,
                )
            elif image_base64:
                b64 = image_base64.split(",", 1)[-1].strip()
                raw = base64.b64decode(b64)
                with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f:
                    f.write(raw)
                    local = f.name
                try:
                    pushed = False
                    try:
                        self.d.push(local, dst)
                        pushed = True
                    except Exception as push_err:
                        logger.warning("save_qr_image_to_album u2 push failed: %s", push_err)
                    if not pushed and not adb_push_fallback(local):
                        return ""
                finally:
                    os.unlink(local)
            else:
                return ""
            size_out = (self.d.shell(f"wc -c < {shlex.quote(dst)}").output or "").strip()
            if not size_out or size_out == "0":
                return ""
            self.d.shell(
                "am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE "
                f"-d file://{dst}",
                timeout=8,
            )
            self.sleep(1)
            return dst
        except Exception as e:
            logger.warning("save_qr_image_to_album failed: %s", e)
            return ""

    def _open_scan_pick_album_first(self) -> Dict[str, Any]:
        """微信扫一扫 → 相册 → 点第一张图。

        真机验证（2026-06-27 Redmi K70 1080x2400）：
        - u2 dump_hierarchy 不到微信节点（SurfaceView/自绘限制）
        - u2 click 被 MIUI INJECT_EVENTS 拦截
        - 改用 root am start + root input tap 坐标点击
        - 权限弹窗与 AlbumPreviewUI 用多坐标兜底
        """
        try:
            def current_focus() -> str:
                try:
                    return self.d.shell("dumpsys window | grep mCurrentFocus").output or ""
                except Exception:
                    return ""

            def tap(x: int, y: int, wait_sec: float = 1.5) -> None:
                self.d.shell(f"su -c 'input tap {x} {y}'")
                self.sleep(wait_sec)

            # 1. root am start 直接打开扫一扫（绕过 u2 click "+" 菜单）
            self.d.shell("su -c 'am start -n com.tencent.mm/.plugin.scanner.ui.BaseScanUI'")
            self.sleep(2)

            # 2. 点相册按钮（937,2012 · 真机验证）
            tap(937, 2012, 2)

            # 3. 处理 MIUI/微信自有权限弹窗；焦点有时不叫 GrantPermissions
            focus = current_focus()
            focus_norm = focus.lower()
            if any(key in focus_norm for key in ("grantpermissions", "permission", "permis")):
                for allow_x, allow_y in ((830, 1710), (540, 1710)):
                    tap(allow_x, allow_y, 1.5)
                    self.d.shell("su -c 'am start -n com.tencent.mm/.plugin.scanner.ui.BaseScanUI'")
                    self.sleep(1.5)
                    tap(937, 2012, 2)
                    focus_norm = current_focus().lower()
                    if "gallery" in focus_norm or "album" in focus_norm:
                        break

            # 4. 检查是否进入相册（焦点应含 AlbumPreviewUI / AlbumPickerUI / gallery）
            focus2 = current_focus()
            focus2_norm = focus2.lower()
            if "gallery" not in focus2_norm and "album" not in focus2_norm:
                return {"success": False, "error": f"未进入相册界面（焦点={focus2[:120]})"}

            # 5. 点第一张图（270,400 · 3列网格第一张 · 真机验证）
            tap(270, 400, 2)

            # 6. 在 AlbumPreviewUI 点"完成"按钮
            ok = self.click_text("完成") or self.click_contains("完成")
            if not ok:
                # `/tmp/find_btn_out.txt` 像素分析显示底部中心高亮明显，优先点底部中心，再试右下/右上。
                for finish_x, finish_y in ((528, 2330), (950, 2300), (950, 120)):
                    tap(finish_x, finish_y, 1.5)
                    xml_try = self.d.dump_hierarchy(compressed=True)
                    if any(flag in xml_try for flag in ("添加到通讯录", "发消息", "二维码已过期", "二维码无效")):
                        ok = True
                        break

            xml = self.d.dump_hierarchy(compressed=True)
            texts = re.findall(r'text="([^"]+)"', xml)[:15]
            if any(flag in xml for flag in ("添加到通讯录", "发消息", "二维码已过期", "二维码无效")):
                return {"success": True, "note": "已从相册选取图片识别二维码", "page_texts": texts}
            return {
                "success": False,
                "error": "已选取图片但未进入二维码结果页",
                "page_texts": texts,
            }
        except Exception as e:
            return {"success": False, "error": f"扫一扫相册流程异常: {e}"}

    def extract_qr_from_image(
        self, image_base64: str = "", image_url: str = "", **kwargs
    ) -> Dict[str, Any]:
        """从图片识别二维码（支持 image_base64 / image_url 落盘后再扫） [u2, ~8s]"""
        try:
            saved = ""
            if image_base64 or image_url:
                saved = self._save_qr_image_to_album(
                    image_base64=image_base64, image_url=image_url
                )
                if not saved:
                    return {
                        "success": False,
                        "error": "图片落盘相册失败（image_base64/image_url 无效或写入失败）",
                    }
            result = self._open_scan_pick_album_first()
            if saved:
                result["saved_path"] = saved
            return result
        except Exception as e:
            return {"success": False, "error": str(e)}

    def add_friend_from_image(
        self,
        image_base64: str = "",
        image_url: str = "",
        verify_message: str = "",
        **kwargs,
    ) -> Dict[str, Any]:
        """WP-WX-04：二维码图 → 扫一扫相册识别 → 发送好友申请。"""
        try:
            ext = self.extract_qr_from_image(
                image_base64=image_base64, image_url=image_url
            )
            if not ext.get("success"):
                return ext
            self.sleep(2)
            xml = self.d.dump_hierarchy(compressed=True)
            if "添加到通讯录" in xml:
                self.click_text("添加到通讯录") or self.click_contains("添加到通讯录")
                self.sleep(1)
                if verify_message:
                    xml2 = self.d.dump_hierarchy(compressed=True)
                    if "发送添加朋友申请" in xml2 or "申请添加朋友" in xml2:
                        (
                            self.click_text("发送添加朋友申请")
                            or self.click_contains("申请添加朋友")
                        )
                        self.sleep(0.5)
                        self.human_type(verify_message, clear=True)
                        self.sleep(0.3)
                self.click_text("发送") or self.click_text("完成")
                return {
                    "success": True,
                    "note": "已识别二维码并发送好友请求",
                    "verified": True,
                    "verify_message": verify_message,
                }
            if "该二维码已过期" in xml or "二维码无效" in xml:
                return {"success": False, "error": "二维码已过期/无效"}
            if "已是好友" in xml or ("发消息" in xml and "添加到通讯录" not in xml):
                return {"success": True, "note": "对方已是好友", "already_friend": True}
            texts = re.findall(r'text="([^"]+)"', xml)[:10]
            return {
                "success": False,
                "error": "未识别到好友二维码或未进入添加页",
                "page_texts": texts,
            }
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十一、通话（新增2个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def voice_call(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """语音通话 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("语音通话") or self.click_contains("语音通话")
            self.sleep(1)
            return {"success": True, "note": "已发起语音通话"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def video_call(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """视频通话 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(user_id)
            self.click_text(user_id) or self.click_contains(user_id)
            self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("视频通话") or self.click_contains("视频通话")
            self.sleep(1)
            return {"success": True, "note": "已发起视频通话"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十二、群发/搜索/发现（新增3个）
    # 控制形式: u2 UI自动化 | Hook可加速(wechat_search走SQLite)
    # ======================================================================

    def mass_send(self, content: str = "", user_ids: List[str] = None, **kwargs) -> Dict[str, Any]:
        """群发消息（微信自带群发助手）[u2, ~10s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("通用")
            self.sleep(1)
            self.click_text("辅助功能") or self.click_contains("辅助")
            self.sleep(1)
            self.click_text("群发助手")
            self.sleep(1)
            self.click_text("新建群发") or self.click_text("开始群发")
            self.sleep(1)
            if user_ids:
                for uid in user_ids:
                    self.click_text(uid) or self.click_contains(uid)
                    self.sleep(0.3)
                self.click_text("下一步") or self.click_text("完成")
                self.sleep(1)
            if content:
                self.input_text(content)
                self.sleep(0.3)
                self.click_text("发送")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def wechat_search(self, keyword: str = "", **kwargs) -> Dict[str, Any]:
        """搜一搜 [Hook优先(SQLite多表联查, ~100ms), u2降级~3s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("搜一搜") or self.click_contains("搜索")
            self.sleep(1)
            if keyword:
                self.input_text(keyword)
                self.sleep(2)
            return {"success": True, "keyword": keyword}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def top_stories(self, **kwargs) -> Dict[str, Any]:
        """看一看 [u2, ~3s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("发现")
            self.sleep(1)
            self.click_text("看一看") or self.click_contains("看一看")
            self.sleep(2)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十三、微信运动（新增2个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def get_steps(self, **kwargs) -> Dict[str, Any]:
        """获取步数 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search("微信运动")
            self.click_text("微信运动") or self.click_contains("微信运动")
            self.sleep(2)
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            steps = None
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and t.isdigit() and len(t) >= 2:
                    steps = int(t)
                    break
            return {"success": True, "steps": steps}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def like_steps(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """点赞步数 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search("微信运动")
            self.click_text("微信运动") or self.click_contains("微信运动")
            self.sleep(2)
            self.click_text("步数排行榜") or self.click_contains("排行")
            self.sleep(1)
            if user_id:
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(0.5)
            self.click_text("赞") or self.click_contains("赞")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十四、位置分享（新增2个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def send_location(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """发送位置 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if user_id:
                self.search(user_id)
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("位置") or self.click_contains("位置")
            self.sleep(0.5)
            self.click_text("发送位置")
            self.sleep(2)
            self.click_text("发送")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def share_real_time_location(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """共享实时位置 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if user_id:
                self.search(user_id)
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("位置") or self.click_contains("位置")
            self.sleep(0.5)
            self.click_text("共享实时位置")
            self.sleep(1)
            return {"success": True, "note": "已发起实时位置共享"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十五、表情管理（新增2个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def send_emoji(self, user_id: str = "", emoji_name: str = "", **kwargs) -> Dict[str, Any]:
        """发送表情 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if user_id:
                self.search(user_id)
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            self.click(int(W * 0.12), int(H * 0.955))
            self.sleep(0.5)
            if emoji_name:
                self.click_text(emoji_name) or self.click_contains(emoji_name)
            else:
                self.click(int(W * 0.1), int(H * 0.88))
            self.sleep(0.3)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def add_custom_emoji(self, image_path: str = "", **kwargs) -> Dict[str, Any]:
        """添加自定义表情 [u2, 默认打开添加入口]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("表情")
            self.sleep(1)
            self.click_text("设置") or self.click_desc("设置")
            self.sleep(0.8)
            self.click_text("添加的单个表情") or self.click_contains("单个表情")
            self.sleep(0.8)
            self.click_desc("+") or self.click_text("+")
            self.sleep(1)
            return {"success": True, "note": "已打开添加自定义表情入口", "image_path": image_path}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def get_sticker_list(self, **kwargs) -> Dict[str, Any]:
        """获取表情包列表 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("表情")
            self.sleep(1)
            stickers = []
            ui = self.d.dump_hierarchy()
            import xml.etree.ElementTree as ET
            root = ET.fromstring(ui)
            for elem in root.iter():
                t = elem.get("text", "").strip()
                if t and len(t) > 0 and t not in ("表情", "搜索", "我的表情", "更多"):
                    stickers.append({"name": t})
            return {"success": True, "stickers": stickers[:50]}
        except Exception as e:
            return {"success": False, "error": str(e), "stickers": []}

    # ======================================================================
    # 二十六、语音/文件（新增3个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def send_voice_message(self, user_id: str = "", duration: int = 3, **kwargs) -> Dict[str, Any]:
        """发送语音消息 [u2, ~duration+3s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if user_id:
                self.search(user_id)
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(1)
            W = self.d.info["displayWidth"]
            H = self.d.info["displayHeight"]
            if self.click_text("按住 说话") or self.click_contains("按住"):
                pass
            else:
                self.click(int(W * 0.06), int(H * 0.955))
                self.sleep(0.5)
            btn_x = int(W * 0.5)
            btn_y = int(H * 0.955)
            self.long_click(btn_x, btn_y, duration=float(duration))
            self.sleep(0.5)
            return {"success": True, "duration": duration}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def send_file_from_chat(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """发送文件 [u2, ~6s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if user_id:
                self.search(user_id)
                self.click_text(user_id) or self.click_contains(user_id)
                self.sleep(1)
            self.click_text("+") or self.click_desc("更多功能")
            self.sleep(0.5)
            self.click_text("文件") or self.click_contains("文件")
            self.sleep(1)
            return {"success": True, "note": "已打开文件选择"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def download_file(self, user_id: str = "", **kwargs) -> Dict[str, Any]:
        """下载文件 [u2, ~5s]"""
        try:
            self.launch()
            self.sleep(1)
            if self.click_text("文件") or self.click_contains("[文件]"):
                self.sleep(1)
                return {"success": True, "note": "已点击文件"}
            return {"success": False, "error": "未找到可下载的文件"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十七、设置管理（新增5个）
    # 控制形式: u2 UI自动化
    # ======================================================================

    def toggle_do_not_disturb(self, enable: bool = True, **kwargs) -> Dict[str, Any]:
        """勿扰模式 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("新消息通知") or self.click_text("消息通知")
            self.sleep(1)
            self.click_text("勿扰模式") or self.click_contains("勿扰")
            self.sleep(0.5)
            return {"success": True, "dnd": enable}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def add_to_float(self, wxid: str = "", **kwargs) -> Dict[str, Any]:
        """添加聊天浮窗 [u2, 默认进入聊天更多菜单]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.search(wxid)
            self.click_text(wxid) or self.click_contains(wxid)
            self.sleep(1)
            self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
            self.sleep(1)
            self.click_text("浮窗") or self.click_contains("浮窗")
            return {"success": True, "wxid": wxid}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def remove_from_float(self, wxid: str = "", **kwargs) -> Dict[str, Any]:
        """移除浮窗 [u2]"""
        return self.add_to_float(wxid=wxid, **kwargs)

    def set_notification(self, type: str = "all", enable: bool = True, **kwargs) -> Dict[str, Any]:
        """设置新消息通知 [u2]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("新消息通知") or self.click_text("消息通知")
            self.sleep(1)
            return {"success": True, "type": type, "enable": enable, "note": "已打开新消息通知设置"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def set_chat_background(self, wxid: str = "", image_path: str = "", **kwargs) -> Dict[str, Any]:
        """设置聊天背景 [u2, 默认打开背景设置入口]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            if wxid:
                self.search(wxid)
                self.click_text(wxid) or self.click_contains(wxid)
                self.sleep(1)
                self.d.xpath('//*[@content-desc="聊天信息"]').click_exists(timeout=3)
                self.sleep(1)
            self.click_text("设置当前聊天背景") or self.click_contains("聊天背景")
            self.sleep(1)
            return {"success": True, "wxid": wxid, "image_path": image_path, "note": "已打开聊天背景入口"}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def clear_cache(self, **kwargs) -> Dict[str, Any]:
        """清理缓存 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("通用")
            self.sleep(1)
            self.click_text("存储空间") or self.click_contains("存储")
            self.sleep(2)
            self.click_text("清理") or self.click_contains("清理")
            self.sleep(0.5)
            self.click_text("确定")
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def check_for_update(self, **kwargs) -> Dict[str, Any]:
        """检查更新 [u2, ~5s]"""
        try:
            self.launch()
            self.wait_for_app_ready()
            self.click_text("我")
            self.sleep(1)
            self.click_text("设置")
            self.sleep(1)
            self.click_text("关于微信")
            self.sleep(1)
            self.click_text("检查新版本") or self.click_contains("版本")
            self.sleep(2)
            return {"success": True}
        except Exception as e:
            return {"success": False, "error": str(e)}

    def logout(self, **kwargs) -> Dict[str, Any]:
        """退出登录 [root am start 深链 SettingsUI + root tap]

        WeChat 8.0.69 UI 非无障碍（click_text 失效），故：
        1. `su -c am start SettingsUI` 直达设置页（绕非无障碍底栏 tab 导航）；
        2. root swipe 滚到底（退出登录在底部）；
        3. 截图返回（qr/screenshot 供人工/下游确认 退出登录 位置）；
        4. **默认非破坏**：仅当 confirm=True 才 root tap 退出登录 + 确认（真退登录，需再登凭证）。
        """
        import base64
        from io import BytesIO
        confirm = bool(kwargs.get("confirm"))
        try:
            # 1) root 深链直达设置页
            self.d.shell("su -c 'am start -n com.tencent.mm/.plugin.setting.ui.setting.SettingsUI'")
            self.sleep(2.0)
            cur = ""
            try:
                cur = self.d.app_current().get("activity", "")
            except Exception:
                pass
            if "SettingsUI" not in cur:
                # 回退：常规我→设置（老版本/可访问时）
                self.launch(); self.wait_for_app_ready()
                self.click_text("我"); self.sleep(1); self.click_text("设置"); self.sleep(1)
            # 2) 滚到底（退出登录在底部）
            info = self.d.info
            W, H = info.get("displayWidth", 1080), info.get("displayHeight", 2400)
            for _ in range(4):
                if self._root_input_available():
                    self._root_swipe(W // 2, int(H * 0.8), W // 2, int(H * 0.25), 250)
                else:
                    self.swipe("up", scale=0.6)
                self.sleep(0.4)
            # 3) 截图返回（供确认 退出登录 位置）
            result = {"success": True, "navigated_to": "SettingsUI", "confirm": confirm}
            try:
                img = self.d.screenshot(); buf = BytesIO(); img.save(buf, format="PNG")
                result["screenshot_b64"] = base64.b64encode(buf.getvalue()).decode()
            except Exception as _se:
                result["screenshot_error"] = str(_se)
            # 4) 默认非破坏；confirm=True 才真退
            if not confirm:
                result["note"] = "已到设置底部(退出登录区域)，未点击退出（confirm=False，非破坏）；传 confirm=true 真退（需再登凭证）"
                return result
            # 真退：root tap「退出」（底部按钮，截图校准 ~H*0.89）+ 确认对话框
            if self._root_input_available():
                self._root_tap(W // 2, int(H * 0.89))  # 8.0.69 设置底部「退出」按钮（截图校准）
                self.sleep(1.2)
                self._root_tap(W // 2, int(H * 0.60))  # 确认对话框「退出登录」按钮（居中偏下）
            else:
                self.click_text("退出登录") or self.click_text("退出")
                self.sleep(0.5)
                self.click_text("退出登录") or self.click_text("确定")
            self.sleep(2.0)
            result["note"] = "已尝试退出登录（confirm=True）"
            return result
        except Exception as e:
            return {"success": False, "error": str(e)}

    def switch_account(self, **kwargs) -> Dict[str, Any]:
        """切换账号 [root am start 深链 SettingsUI + root tap「切换账号」]

        WeChat 8.0.69 非无障碍：root 深链直达设置 + root swipe 滚到底 + root tap
        「切换账号」(截图校准 ~H*0.79)。打开账号选择页（非破坏，不实际切换）。
        """
        import base64
        from io import BytesIO
        try:
            self.d.shell("su -c 'am start -n com.tencent.mm/.plugin.setting.ui.setting.SettingsUI'")
            self.sleep(2.0)
            cur = ""
            try:
                cur = self.d.app_current().get("activity", "")
            except Exception:
                pass
            if "SettingsUI" not in cur:
                self.launch(); self.wait_for_app_ready()
                self.click_text("我"); self.sleep(1); self.click_text("设置"); self.sleep(1)
            info = self.d.info
            W, H = info.get("displayWidth", 1080), info.get("displayHeight", 2400)
            for _ in range(4):
                if self._root_input_available():
                    self._root_swipe(W // 2, int(H * 0.8), W // 2, int(H * 0.25), 250)
                else:
                    self.swipe("up", scale=0.6)
                self.sleep(0.4)
            if self._root_input_available():
                self._root_tap(W // 2, int(H * 0.79))  # 设置底部「切换账号」按钮（截图校准）
            else:
                self.click_text("切换账号") or self.click_contains("切换")
            self.sleep(1.5)
            result = {"success": True, "note": "已 root 深链导航并点击「切换账号」(打开账号选择页)"}
            try:
                img = self.d.screenshot(); buf = BytesIO(); img.save(buf, format="PNG")
                result["screenshot_b64"] = base64.b64encode(buf.getvalue()).decode()
            except Exception:
                pass
            return result
        except Exception as e:
            return {"success": False, "error": str(e)}

    # ======================================================================
    # 二十八、截图（新增1个）
    # 控制形式: u2直接调用设备截图API
    # ======================================================================

    def screenshot(self, **kwargs) -> Dict[str, Any]:
        """截图 [u2直接API, ~500ms]"""
        try:
            import base64
            from io import BytesIO
            img = self.d.screenshot()
            buf = BytesIO()
            img.save(buf, format="PNG")
            b64 = base64.b64encode(buf.getvalue()).decode()
            return {"success": True, "screenshot": b64[:100] + "...", "size": len(b64)}
        except Exception as e:
            return {"success": False, "error": str(e)}
