feat: publish workphone SDK deployment and API docs

This commit is contained in:
Manus AI
2026-07-14 18:10:52 +08:00
commit 021d633cc1
534 changed files with 122391 additions and 0 deletions

View File

@@ -0,0 +1,735 @@
"""
小红书控制技能 - Agent端实现
功能模块:
1. 消息管理 - 发送/获取私信
2. 粉丝管理 - 获取粉丝列表、关注用户
3. 评论管理 - 获取评论、回复评论
4. 笔记互动 - 点赞、收藏、分享
5. 笔记发布 - 发布图文笔记
@author 卡若
@version 3.0.0
"""
import time
import logging
import re
from typing import Dict, Any, List, Optional
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 XhsSkill(BaseSkill):
"""
小红书控制技能
支持的操作:
- send_message: 发送私信
- get_messages: 获取私信列表
- get_fans: 获取粉丝列表
- follow_user: 关注用户
- unfollow_user: 取消关注
- get_comments: 获取笔记评论
- reply_comment: 回复评论
- like_note: 点赞笔记
- collect_note: 收藏笔记
- search_user: 搜索用户
- search_note: 搜索笔记
- batch_send_message: 批量发送私信
- post_note: 发布笔记
"""
PACKAGE = "com.xingin.xhs"
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_desc("搜索") or self.click_text("搜索"):
self.sleep(0.5)
self.input_text(to_id)
self.sleep(1.5)
# 点击搜索结果
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("发送") or self.click_desc("发送")
logger.info(f"小红书私信发送成功: {to_id}")
return {
"success": True,
"message_id": f"xhs_{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_desc("搜索") or self.click_text("搜索"):
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树解析消息
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:
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_fans(self, limit: int = 100) -> Dict[str, Any]:
"""
获取粉丝列表
Args:
limit: 获取数量
Returns:
粉丝列表
"""
try:
self.launch()
self.wait_for_app_ready()
# 进入我的页面
self.click_text("") or self.click_desc("")
self.sleep(1)
# 点击粉丝
self.click_text("粉丝")
self.sleep(1)
fans = []
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:
fans.append({"name": text})
except:
pass
return {
"success": True,
"fans": fans[:limit],
"count": len(fans[:limit])
}
except Exception as e:
return {"success": False, "error": str(e), "fans": []}
def follow_user(self, user_id: str) -> Dict[str, Any]:
"""
关注用户
Args:
user_id: 用户ID或昵称
"""
try:
self.launch()
self.wait_for_app_ready()
# 搜索用户
self._search_user(user_id)
# 点击关注
if self.click_text("关注"):
return {"success": True}
# 可能已关注
if self.exists("已关注") or 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("已关注") or 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, keyword: str, limit: int = 20) -> Dict[str, Any]:
"""
搜索用户
Args:
keyword: 搜索关键词
limit: 结果数量
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击搜索
self.click_desc("搜索")
self.sleep(0.5)
# 输入关键词
self.input_text(keyword)
self.sleep(1)
# 切换到用户标签
self.click_text("用户")
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 "用户" 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 get_comments(self, note_id: str = None, limit: int = 50) -> Dict[str, Any]:
"""
获取笔记评论
Args:
note_id: 笔记ID如果不指定获取当前笔记的评论
limit: 评论数量
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击评论按钮
self.click_desc("评论") or self.click_text("评论")
self.sleep(1)
comments = []
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) > 5:
comments.append({
"content": text,
"timestamp": int(time.time() * 1000)
})
except:
pass
return {
"success": True,
"comments": comments[:limit],
"count": len(comments[:limit])
}
except Exception as e:
return {"success": False, "error": str(e), "comments": []}
def reply_comment(
self,
note_id: str,
comment_id: str,
content: str
) -> Dict[str, Any]:
"""
回复评论
Args:
note_id: 笔记ID
comment_id: 评论ID或评论内容片段
content: 回复内容
"""
try:
self.launch()
self.wait_for_app_ready()
# 打开评论区
self.click_desc("评论") or self.click_text("评论")
self.sleep(1)
# 找到评论并点击回复
if self.click_contains(comment_id):
self.sleep(0.5)
self.click_text("回复")
self.sleep(0.3)
# 输入回复内容
self.input_text(content)
self.sleep(0.3)
# 发送
self.click_text("发送")
return {"success": True}
return {"success": False, "error": "未找到评论"}
except Exception as e:
return {"success": False, "error": str(e)}
# ==========================================================================
# 四、笔记互动
# ==========================================================================
def like_note(self, note_id: str = None) -> Dict[str, Any]:
"""
点赞笔记
Args:
note_id: 笔记ID不指定则点赞当前笔记
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击点赞按钮
self.click_desc("点赞") or self.click_text("点赞") or self.click_desc("")
return {"success": True}
except Exception as e:
return {"success": False, "error": str(e)}
def collect_note(self, note_id: str = None) -> Dict[str, Any]:
"""
收藏笔记
Args:
note_id: 笔记ID
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击收藏按钮
self.click_desc("收藏") or self.click_text("收藏")
return {"success": True}
except Exception as e:
return {"success": False, "error": str(e)}
def share_note(self, note_id: str = None, platform: str = "wechat") -> Dict[str, Any]:
"""
分享笔记
Args:
note_id: 笔记ID
platform: 分享平台 (wechat/qq/weibo)
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击分享按钮
self.click_desc("分享") or self.click_text("分享")
self.sleep(1)
# 选择平台
platform_map = {
"wechat": "微信",
"qq": "QQ",
"weibo": "微博"
}
target = platform_map.get(platform, platform)
self.click_text(target) or self.click_contains(target)
return {"success": True}
except Exception as e:
return {"success": False, "error": str(e)}
def search_note(self, keyword: str, limit: int = 20) -> Dict[str, Any]:
"""
搜索笔记
Args:
keyword: 搜索关键词
limit: 结果数量
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击搜索
self.click_desc("搜索")
self.sleep(0.5)
# 输入关键词
self.input_text(keyword)
self.sleep(1)
# 切换到笔记标签
self.click_text("笔记") or self.click_text("全部")
self.sleep(1)
# 获取搜索结果
notes = []
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:
notes.append({"title": text})
except:
pass
return {
"success": True,
"notes": notes[:limit],
"count": len(notes[:limit])
}
except Exception as e:
return {"success": False, "error": str(e), "notes": []}
# ==========================================================================
# 五、笔记发布
# ==========================================================================
def post_note(
self,
content: str,
images: List[str] = None,
title: str = None,
topics: List[str] = None,
location: str = None
) -> Dict[str, Any]:
"""
发布笔记
Args:
content: 笔记内容
images: 图片路径列表
title: 笔记标题
topics: 话题标签列表
location: 位置信息
Returns:
发布结果
"""
try:
self.launch()
self.wait_for_app_ready()
# 点击发布按钮(通常在底部中间)
self.click_text("+") or self.click_desc("发布")
self.sleep(1)
# 选择图文笔记
self.click_text("图文") or self.click_text("笔记")
self.sleep(1)
# 选择图片(如果有)
if images:
for img in images:
# TODO: 实现图片选择逻辑
pass
self.sleep(1)
self.click_text("下一步") or self.click_text("完成")
self.sleep(1)
# 输入标题(如果有)
if title:
self.click_text("标题") or self.click_text("添加标题")
self.input_text(title)
# 输入正文
self.click_text("正文") or self.click_text("添加正文")
self.input_text(content)
self.sleep(0.5)
# 添加话题标签
if topics:
for topic in topics:
self.input_text(f" #{topic}")
# 添加位置
if location:
self.click_text("位置") or self.click_text("添加位置")
self.sleep(0.5)
self.input_text(location)
self.sleep(1)
self.click_contains(location)
# 发布
self.click_text("发布笔记") or self.click_text("发布")
self.sleep(2)
return {
"success": True,
"note_id": f"xhs_note_{int(time.time() * 1000)}"
}
except Exception as e:
return {"success": False, "error": str(e)}
# ==========================================================================
# 六、辅助方法
# ==========================================================================
def _search_user(self, user_id: str):
"""搜索用户(内部方法)"""
# 点击搜索
self.click_desc("搜索")
self.sleep(0.5)
# 输入用户ID
self.input_text(user_id)
self.sleep(1)
# 切换到用户标签
self.click_text("用户")
self.sleep(1)
# 点击第一个结果
self.click_text(user_id) or self.click_contains(user_id)
self.sleep(1)
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:
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}