""" 闲鱼控制技能 - Agent端实现 功能模块: 1. 消息管理 - 发送/获取私信 2. 联系人 - 获取最近聊天、关注用户 3. 商品/聊天 - 与买家/卖家沟通 @author 卡若 @version 3.0.0 """ import time import logging from typing import Dict, Any, List import sys import os _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 logger = logging.getLogger(__name__) class XianyuSkill(BaseSkill): """ 闲鱼控制技能 支持的操作: - send_message: 发送私信 - get_messages: 获取私信列表 - get_contacts: 获取最近联系人/聊天列表 - add_friend: 关注用户(闲鱼为「关注」) - batch_send_message: 批量发送私信 """ PACKAGE = "com.taobao.idlefish" NAME = "闲鱼" # ========================================================================== # 一、消息管理 # ========================================================================== @with_error_handling("发送闲鱼私信") @retry(max_retries=2, delay=0.5) def send_message(self, to_id: str, content: str, msg_type: str = "text") -> Dict[str, Any]: """ 发送私信 Args: to_id: 用户昵称或ID content: 消息内容 msg_type: 消息类型 (text/image) Returns: 执行结果 """ try: self.launch() self.wait_for_app_ready() # 进入消息页(闲鱼底部通常有「消息」) self.click_text("消息") or self.click_desc("消息") self.sleep(1) # 搜索用户或从列表进入 if self.click_text("搜索") or self.click_desc("搜索"): self.sleep(0.5) self.input_text(to_id) self.sleep(1.5) if not self.click_text(to_id) and not self.click_contains(to_id): return {"success": False, "error": f"未找到用户: {to_id}"} self.sleep(1) else: # 从聊天列表点击 if not self.click_contains(to_id) and not self.click_text(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("发送") or self.click_desc("发送") logger.info(f"闲鱼私信发送成功: {to_id}") return { "success": True, "message_id": f"xy_{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) -> Dict[str, Any]: """ 获取私信列表 Args: limit: 消息数量限制 conversation_id: 会话ID(用户昵称),指定则取该会话消息 Returns: 消息列表 """ try: self.launch() self.wait_for_app_ready() self.click_text("消息") or self.click_desc("消息") self.sleep(1) 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) self.click_text(conversation_id) or self.click_contains(conversation_id) self.sleep(1) 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: messages.append({ "text": text, "timestamp": int(time.time() * 1000) }) except Exception: pass return { "success": True, "messages": messages[:limit], "count": len(messages[:limit]) } except Exception as e: return {"success": False, "error": str(e), "messages": []} def batch_send_message( self, to_ids: List[str], content: str, interval: float = 3.0 ) -> Dict[str, Any]: """批量发送私信""" results = [] success_count = 0 failed_count = 0 for to_id in to_ids: try: result = self.send_message(to_id, content) 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 get_contacts(self, limit: int = 100) -> Dict[str, Any]: """获取最近聊天/联系人列表(与微信技能接口保持一致)""" try: self.launch() self.wait_for_app_ready() self.click_text("消息") or self.click_desc("消息") self.sleep(1) contacts = [] 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: contacts.append({"name": text}) except Exception: pass return { "success": True, "contacts": contacts[:limit], "count": len(contacts[:limit]) } except Exception as e: return {"success": False, "error": str(e), "contacts": []} def add_friend(self, user_id: str, message: str = "") -> Dict[str, Any]: """关注用户(闲鱼为「关注」)- 与微信技能接口保持一致""" return self.follow_user(user_id) def accept_friend(self, user_id: str = None) -> Dict[str, Any]: """通过好友请求 - 闲鱼无此入口,占位""" return {"success": False, "error": "闲鱼暂不支持通过好友请求"} def set_remark(self, user_id: str, remark: str) -> Dict[str, Any]: """设置备注 - 闲鱼无此能力,占位""" return {"success": False, "error": "闲鱼暂不支持设置备注"} def delete_friend(self, user_id: str) -> Dict[str, Any]: """删除好友 - 闲鱼为取消关注""" return self.unfollow_user(user_id) def batch_add_friend(self, user_ids: List[str], message: str = "", interval: float = 5.0) -> Dict[str, Any]: """批量关注用户""" results = [] success_count = 0 failed_count = 0 for uid in user_ids: try: r = self.add_friend(uid, message) ok = r.get("success", False) if ok: success_count += 1 else: failed_count += 1 results.append({"user_id": uid, "success": ok, "error": r.get("error")}) time.sleep(max(interval, 2.0)) except Exception as e: failed_count += 1 results.append({"user_id": uid, "success": False, "error": str(e)}) return {"success_count": success_count, "failed_count": failed_count, "results": results} def follow_user(self, user_id: str) -> Dict[str, Any]: """关注用户""" try: self.launch() self.wait_for_app_ready() self._search_user(user_id) if self.click_text("关注"): return {"success": True} if self.exists("已关注"): return {"success": True, "message": "已关注"} return {"success": False, "error": "关注失败"} except Exception as e: return {"success": False, "error": str(e)} def unfollow_user(self, user_id: str) -> Dict[str, Any]: """取消关注""" try: self.launch() self.wait_for_app_ready() self._search_user(user_id) 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 _search_user(self, user_id: str): """搜索用户(内部方法)""" self.click_desc("搜索") or self.click_text("搜索") self.sleep(0.5) self.input_text(user_id) self.sleep(1) self.click_text(user_id) or self.click_contains(user_id) self.sleep(1)