Files
workphone-sdk/sdk/agent/skills/xianyu/skill.py

301 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
闲鱼控制技能 - 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)