""" 抖音控制技能 - Agent端实现 功能模块: 1. 消息管理 - 发送/获取私信 2. 粉丝管理 - 获取粉丝列表、关注用户 3. 评论管理 - 获取评论、回复评论 4. 视频互动 - 点赞、收藏、分享 @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 DouyinSkill(BaseSkill): """ 抖音控制技能 支持的操作: - send_message: 发送私信 - get_messages: 获取私信列表 - get_fans: 获取粉丝列表 - follow_user: 关注用户 - unfollow_user: 取消关注 - get_comments: 获取视频评论 - reply_comment: 回复评论 - like_video: 点赞视频 - collect_video: 收藏视频 - search_user: 搜索用户 - batch_send_message: 批量发送私信 """ PACKAGE = "com.ss.android.ugc.aweme" 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): 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("发送") logger.info(f"抖音私信发送成功: {to_id}") return { "success": True, "message_id": f"dy_{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树解析消息 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, video_id: str = None, limit: int = 50) -> Dict[str, Any]: """ 获取视频评论 Args: video_id: 视频ID(如果不指定,获取当前视频的评论) limit: 评论数量 """ try: self.launch() self.wait_for_app_ready() if video_id and str(video_id).strip(): self.click_text("搜索") or self.click_desc("搜索") self.sleep(0.8) self.input_text(str(video_id).strip()[:50]) self.sleep(1.5) if self.click_contains(str(video_id).strip()[:20]) or self.click_text("搜索"): self.sleep(1) # 点击评论按钮 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, video_id: str, comment_id: str, content: str ) -> Dict[str, Any]: """ 回复评论 Args: video_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_video(self, video_id: str = None) -> Dict[str, Any]: """ 点赞视频 Args: video_id: 视频ID(不指定则点赞当前视频) """ try: self.launch() self.wait_for_app_ready() # 双击屏幕点赞 info = self.d.info center_x = info['displayWidth'] // 2 center_y = info['displayHeight'] // 2 self.d.double_click(center_x, center_y) return {"success": True} except Exception as e: return {"success": False, "error": str(e)} def collect_video(self, video_id: str = None) -> Dict[str, Any]: """ 收藏视频 Args: video_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_video(self, video_id: str = None, platform: str = "wechat") -> Dict[str, Any]: """ 分享视频 Args: video_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_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}