From b555712749aca20beef774331a4ff77773235681 Mon Sep 17 00:00:00 2001 From: zhangkai <2403741920@qq.com> Date: Wed, 3 Sep 2025 19:03:34 +0800 Subject: [PATCH] =?UTF-8?q?ai=20=E5=BC=80=E5=A7=8B=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 101 ++++++++++++++++++++---------- Entity/Variables.py | 5 +- Module/FlaskService.py | 2 +- Utils/AiUtils.py | 133 +++++++++++++++++++++++++++++++++++++--- Utils/ControlUtils.py | 12 ++-- Utils/LogManager.py | 19 +++++- script/ScriptManager.py | 43 ++++++++----- 7 files changed, 251 insertions(+), 64 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8bd1452..c43bfc4 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,11 +6,11 @@ - + + - - + + + + + + - @@ -252,7 +274,11 @@ - + + + + + - @@ -278,10 +312,11 @@ + - - + + \ No newline at end of file diff --git a/Entity/Variables.py b/Entity/Variables.py index 9b350d2..6592343 100644 --- a/Entity/Variables.py +++ b/Entity/Variables.py @@ -15,15 +15,18 @@ commentsList = [] # 存储主播名和session_id的字典 anchorWithSession = {} +token = '' + + # 安全删除数据 def removeModelFromAnchorList(model: AnchorModel): with anchorListLock: anchorList.remove(model) + # 添加数据 def addModelToAnchorList(models: list[Dict[str, Any]]): with anchorListLock: for dic in models: obj = AnchorModel.dictToModel(dic) anchorList.append(obj) - diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 65cb5a0..9cc70bd 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -314,7 +314,7 @@ def monitorMessages(): @app.route("/upLoadLogFile", methods=['POST']) def upLoadLogFile(): ok = LogManager.upload_all_logs( - server_url="http://127.0.0.1:6000/upload", + server_url="http://47.79.98.113:8101/api/log/upload", extra_data={"project": "TikTokAuto", "env": "dev"} ) if ok: diff --git a/Utils/AiUtils.py b/Utils/AiUtils.py index be40b86..9072c7b 100644 --- a/Utils/AiUtils.py +++ b/Utils/AiUtils.py @@ -124,7 +124,6 @@ class AiUtils(object): height, width, channels = heart_icon.shape roi = np.zeros((height, width, channels), dtype=np.uint8) - # 将爱心图标粘贴到透明背景上 for c in range(channels): roi[:, :, c] = np.where(mask_inv == 255, heart_icon[:, :, c], roi[:, :, c]) @@ -187,8 +186,6 @@ class AiUtils(object): LogManager.info(f"目录 {udid_dir} 已存在,跳过创建", udid) print(f"目录 {udid_dir} 已存在,跳过创建") - - # 查找首页按钮 # uuid 设备id # click 是否点击该按钮 @@ -242,8 +239,7 @@ class AiUtils(object): def getFollowButton(cls, session: Client): # followButton = session.xpath("//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[2]/Other[1]/Other[1]/Other[3]/Other[1]/Other[1]/Button[1]") - followButton = session.xpath( - '//XCUIElementTypeOther[@name="cta_social_interaction"]//XCUIElementTypeButton[@name="关注"]') + followButton = session.xpath('//XCUIElementTypeButton[@name="关注" or @label="关注"]') if followButton.exists: print("2.关注找到了") @@ -259,8 +255,14 @@ class AiUtils(object): def getSendMesageButton(cls, session: Client): # msgButton = session.xpath("//Window[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[2]/Other[1]/Other[1]/Other[3]/Other[1]/Other[1]") + # msgButton = session.xpath( + # '//XCUIElementTypeButton[@name="发消息" or @label="发消息"]') + msgButton = session.xpath( - '//XCUIElementTypeOther[@name="cta_social_interaction"]//XCUIElementTypeButton[@name="发消息"]') + '//XCUIElementTypeButton[' + '(@name="发消息" or @label="发消息" or @name="发送 👋" or @label="发送 👋")' + ' and @visible="true"]' + ) if msgButton.exists: print("3.发消息按钮找到了") LogManager.info("3.发消息按钮找到了") @@ -294,6 +296,109 @@ class AiUtils(object): return cls.findNumber(btn.label) # 获取聊天页面的聊天信息 + # @classmethod + # def extract_messages_from_xml(cls, xml: str): + # """ + # 仅返回当前屏幕中“可见的”聊天内容(含时间分隔) + # """ + # from lxml import etree + # root = etree.fromstring(xml.encode("utf-8")) + # items = [] + # + # # 判断是否是聊天页面 + # is_chat_page = False + # if root.xpath('//XCUIElementTypeStaticText[contains(@traits, "Header")]'): + # is_chat_page = True + # elif root.xpath('//XCUIElementTypeCell//XCUIElementTypeOther[@name or @label]'): + # is_chat_page = True + # + # if not is_chat_page: + # raise Exception("请先进入聊天页面") + # + # # 屏幕宽度 + # app = root.xpath('/XCUIElementTypeApplication') + # + # screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0 + # + # # 找 Table 的可见范围 + # table = root.xpath('//XCUIElementTypeTable') + # if table: + # table = table[0] + # table_top = cls.parse_float(table, 'y', 0.0) + # table_h = cls.parse_float(table, 'height', 0.0) + # table_bottom = table_top + table_h + # else: + # table_top, table_bottom = 0.0, cls.parse_float(app[0], 'height', 736.0) if app else 736.0 + # + # def in_view(el) -> bool: + # """元素在聊天区内并且可见""" + # if el.get('visible') != 'true': + # return False + # y = cls.parse_float(el, 'y', -1e9) + # h = cls.parse_float(el, 'height', 0.0) + # by = y + h + # return not (by <= table_top or y >= table_bottom) + # + # # 时间分隔 + # for t in root.xpath('//XCUIElementTypeStaticText[contains(@traits, "Header")]'): + # if not in_view(t): + # continue + # txt = (t.get('label') or t.get('name') or t.get('value') or '').strip() + # if txt: + # items.append({'type': 'time', 'text': txt, 'y': cls.parse_float(t, 'y')}) + # + # # 消息气泡 + # EXCLUDES = {'Heart', 'Lol', 'ThumbsUp', '分享发布内容', '视频贴纸标签页', '双击发送表情'} + # + # msg_nodes = table.xpath( + # './/XCUIElementTypeCell[@visible="true"]' + # '//XCUIElementTypeOther[@visible="true" and (@name or @label) and not(ancestor::XCUIElementTypeCollectionView)]' + # ) if table is not None else [] + # + # for o in msg_nodes: + # text = (o.get('label') or o.get('name') or '').strip() + # if not text or text in EXCLUDES: + # continue + # if not in_view(o): + # continue + # + # # 找所在 Cell + # cell = o.getparent() + # while cell is not None and cell.get('type') != 'XCUIElementTypeCell': + # cell = cell.getparent() + # + # x = cls.parse_float(o, 'x') + # y = cls.parse_float(o, 'y') + # w = cls.parse_float(o, 'width') + # right_edge = x + w + # + # direction = None + # # 头像位置判定 + # if cell is not None: + # avatar_btns = cell.xpath( + # './/XCUIElementTypeButton[@visible="true" and (@name="图片头像" or @label="图片头像")]') + # if avatar_btns: + # ax = cls.parse_float(avatar_btns[0], 'x') + # direction = 'in' if ax < (screen_w / 2) else 'out' + # # 右对齐兜底 + # if direction is None: + # direction = 'out' if right_edge > (screen_w - 20) else 'in' + # + # items.append({'type': 'msg', 'dir': direction, 'text': text, 'y': y}) + # + # # 排序 & 清理 + # items.sort(key=lambda i: i['y']) + # for it in items: + # it.pop('y', None) + # return items + # + # @classmethod + # def parse_float(cls, el, attr, default=0.0): + # try: + # return float(el.get(attr, default)) + # except Exception: + # return default + @classmethod def extract_messages_from_xml(cls, xml: str): """ @@ -315,7 +420,6 @@ class AiUtils(object): # 屏幕宽度 app = root.xpath('/XCUIElementTypeApplication') - screen_w = cls.parse_float(app[0], 'width', 414.0) if app else 414.0 # 找 Table 的可见范围 @@ -348,15 +452,28 @@ class AiUtils(object): # 消息气泡 EXCLUDES = {'Heart', 'Lol', 'ThumbsUp', '分享发布内容', '视频贴纸标签页', '双击发送表情'} + # —— 新增:系统横幅/提示卡片过滤(只文本判断,最小改动)—— + SYSTEM_BANNER_PATTERNS = [ + r"回复时接收通知", r"开启私信通知", r"开启通知", + r"Turn on (DM|message|direct message)?\s*notifications", + r"Enable notifications", + r"Get notified when .* replies", + ] + SYSTEM_BANNER_REGEX = re.compile("|".join(SYSTEM_BANNER_PATTERNS), re.IGNORECASE) + msg_nodes = table.xpath( './/XCUIElementTypeCell[@visible="true"]' '//XCUIElementTypeOther[@visible="true" and (@name or @label) and not(ancestor::XCUIElementTypeCollectionView)]' ) if table is not None else [] for o in msg_nodes: - text = (o.get('label') or o.get('name') or '').strip() + # 这里补上 value,避免少数节点只在 value 上有文本时漏读 + text = (o.get('label') or o.get('name') or o.get('value') or '').strip() if not text or text in EXCLUDES: continue + # 命中 TikTok 自带的“开启通知/回复时接收通知”类提示 → 直接剔除 + if SYSTEM_BANNER_REGEX.search(text): + continue if not in_view(o): continue diff --git a/Utils/ControlUtils.py b/Utils/ControlUtils.py index 81e19b9..009fd30 100644 --- a/Utils/ControlUtils.py +++ b/Utils/ControlUtils.py @@ -55,7 +55,12 @@ class ControlUtils(object): @classmethod def clickBack(cls, session: Client): try: - back = session.xpath("//*[@label='返回']") + back = session.xpath( + "//*[@label='返回']" + " | " + "//XCUIElementTypeButton[@visible='true' and @name='TTKProfileNavBarBaseItemComponent' and @label='IconChevronLeftOffsetLTR']" + ) + if back.exists: back.click() return True @@ -118,8 +123,7 @@ class ControlUtils(object): # '//Window/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[1]/Other[2]/Other[1]/ScrollView[1]/Other[1]/CollectionView[1]/Cell[2]') videoCell = session.xpath( - '(//XCUIElementTypeCollectionView//XCUIElementTypeCell[.//XCUIElementTypeImage[@name="profile_video"]])[1]').get( - timeout=5) + '(//XCUIElementTypeCollectionView//XCUIElementTypeCell[.//XCUIElementTypeImage[@name="profile_video"]])[1]') tab = session.xpath('//XCUIElementTypeButton[@name="TTKProfileTabVideoButton_0"]').get(timeout=2) # 某些版本 tab.value 可能就是数量;或者 tab.label 类似 “作品 7” @@ -132,7 +136,7 @@ class ControlUtils(object): num = int(m.group()) print("作品数量为:", num) - if videoCell is not None: + if videoCell.exists: videoCell.click() # 点击视频 print("找到主页的第一个视频") diff --git a/Utils/LogManager.py b/Utils/LogManager.py index 2788f05..59b27d9 100644 --- a/Utils/LogManager.py +++ b/Utils/LogManager.py @@ -91,6 +91,8 @@ from pathlib import Path import requests +from Entity.Variables import token + class LogManager: # 运行根目录:打包后取 exe 目录;源码运行取项目目录 @@ -271,12 +273,25 @@ class LogManager: for file in log_path.rglob("*.log"): # 递归找到所有 .log 文件 try: + files = {"file": open(file, "rb")} - data = {"relative_path": str(file.relative_to(log_path))} + headers = { + "Authorization": f"Bearer {token}", + } + + data = { + # "relative_path": str(file.relative_to(log_path)) + "tenantId": 1, + "userId": 1, + } + if extra_data: data.update(extra_data) + resp = requests.post(server_url, files=files, data=data, headers=headers ,timeout=15) + + print(resp.text) + print(resp.status_code) - resp = requests.post(server_url, files=files, data=data, timeout=15) if resp.status_code == 200: success_files.append(file.name) else: diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 201bd39..17c3622 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -123,7 +123,7 @@ class ScriptManager(): session = client.session() session.appium_settings({"snapshotMaxDepth": 0}) - if needLike < 3: + if needLike < 23: LogManager.method_info("进行点赞", "养号", udid) ControlUtils.clickLike(session, udid) @@ -288,6 +288,7 @@ class ScriptManager(): client = wda.USBClient(udid) session = client.session() + print(f"是否要自动回复消息:{needReply}") LogManager.method_info(f"是否要自动回复消息:{needReply}", "关注打招呼", udid) @@ -426,6 +427,7 @@ class ScriptManager(): followButton.click() else: LogManager.method_info("没找到关注按钮", "关注打招呼", udid) + time.sleep(1) goBack(3) session.appium_settings({"snapshotMaxDepth": 15}) continue @@ -485,22 +487,25 @@ class ScriptManager(): LogManager.method_info("即将要回复消息", "关注打招呼", udid) if needReply: print("如果需要回复主播消息。走此逻辑") - if AiUtils.getUnReadMsgCount(session) > 0: - print("监控回复消息") + print("----------------------------------------------------------") - # 执行回复消息逻辑 - self.monitorMessages(session, udid) + # if AiUtils.getUnReadMsgCount(session) > 0: - homeButton = AiUtils.findHomeButton(udid) - if homeButton.exists: - homeButton.click() - else: - ControlUtils.closeTikTok(session, udid) - time.sleep(2) + print("监控回复消息") - ControlUtils.openTikTok(session, udid) - time.sleep(3) + # 执行回复消息逻辑 + self.monitorMessages(session, udid) + + homeButton = AiUtils.findHomeButton(udid) + if homeButton.exists: + homeButton.click() + else: + ControlUtils.closeTikTok(session, udid) + time.sleep(2) + + ControlUtils.openTikTok(session, udid) + time.sleep(3) print("重新创建wda会话 防止wda会话失效") @@ -510,7 +515,7 @@ class ScriptManager(): # 执行完成之后。继续点击搜索 session.appium_settings({"snapshotMaxDepth": 15}) # 点击搜索按钮 - ControlUtils.clickSearch(session) + # ControlUtils.clickSearch(session) else: session.appium_settings({"snapshotMaxDepth": 15}) @@ -641,7 +646,15 @@ class ScriptManager(): xml = session.source() msgs = AiUtils.extract_messages_from_xml(xml) # 检测出对方发的最后一条信息 - last_msg_text = next(item['text'] for item in reversed(msgs) if item['type'] == 'msg') + # last_msg_text = next(item['text'] for item in reversed(msgs) if item['type'] == 'msg') + + text_list = ['What do you think of my live stream?', + 'What do you think makes my streams special?', + 'Do you think I’m one of the most engaging streamers you’ve seen?'] + + last_msg_text = next((item['text'] for item in reversed(msgs) if item['type'] == 'msg'), + random.choice(text_list)) + # 向ai发送信息 # 获取主播的名称