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 @@
-
+
+
-
@@ -39,9 +39,6 @@
-
-
-
{
"customColor": "",
"associatedIndex": 5
@@ -54,36 +51,60 @@
- {
+ "keyToString": {
+ "ASKED_ADD_EXTERNAL_FILES": "true",
+ "Python.123 (1).executor": "Run",
+ "Python.123.executor": "Run",
+ "Python.FlaskService.executor": "Run",
+ "Python.LogManager.executor": "Run",
+ "Python.Main.executor": "Run",
+ "Python.ScriptManager.executor": "Run",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "SHARE_PROJECT_CONFIGURATION_FILES": "true",
+ "git-widget-placeholder": "main",
+ "javascript.nodejs.core.library.configured.version": "20.17.0",
+ "javascript.nodejs.core.library.typings.version": "20.17.58",
+ "last_opened_file_path": "F:/company code/AI item/20250820/iOSAI",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "settings.editor.selected.configurable": "com.gitee.ui.GiteeSettingsConfigurable",
+ "vue.rearranger.settings.migration": "true"
}
-}]]>
+}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -201,10 +222,11 @@
+
+
-
@@ -252,7 +274,11 @@
-
+
+
+
+
+
@@ -262,7 +288,15 @@
1756303135240
-
+
+
+ 1756734696630
+
+
+
+ 1756734696630
+
+
@@ -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发送信息
# 获取主播的名称