diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 0790e72..bb25dd2 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -6,15 +6,11 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -45,6 +41,9 @@
+
+
+
{
"customColor": "",
"associatedIndex": 5
@@ -65,6 +64,8 @@
"Python.123.executor": "Run",
"Python.Main.executor": "Run",
"Python.Test.executor": "Run",
+ "Python.test (1).executor": "Run",
+ "Python.test (2).executor": "Run",
"Python.test.executor": "Run",
"Python.tidevice_entry.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
@@ -74,7 +75,7 @@
"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": "C:/Users/zhangkai/Desktop/20250916ios/iOSAI/resources",
+ "last_opened_file_path": "C:/Users/zhangkai/Desktop/20250916ios/iOSAI/Utils",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
@@ -86,6 +87,8 @@
}]]>
+
+
@@ -94,29 +97,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -186,6 +166,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -211,17 +237,19 @@
+
+
-
-
+
+
@@ -287,6 +315,10 @@
+
+
+
+
@@ -328,7 +360,15 @@
1757587781103
-
+
+
+ 1758121742405
+
+
+
+ 1758121742405
+
+
@@ -357,7 +397,7 @@
-
+
@@ -365,9 +405,11 @@
+
-
+
+
\ No newline at end of file
diff --git a/Module/FlaskService.py b/Module/FlaskService.py
index 54e2a56..02358ec 100644
--- a/Module/FlaskService.py
+++ b/Module/FlaskService.py
@@ -92,6 +92,7 @@ def start_socket_listener():
LogManager.error(f"[ERROR]Socket服务启动失败: {e}")
print(f"[ERROR]Socket服务启动失败: {e}")
+
def _handle_conn(conn: socket.socket, addr):
try:
with conn:
@@ -99,16 +100,16 @@ def _handle_conn(conn: socket.socket, addr):
buffer = ""
while True:
data = conn.recv(1024)
- if not data: # 对端关闭
+ if not data: # 对端关闭
break
buffer += data.decode('utf-8', errors='ignore')
# 2. 尝试切出完整 JSON(简单按行,也可按长度头、分隔符)
while True:
line, sep, buffer = buffer.partition('\n')
- if not sep: # 没找到完整行
+ if not sep: # 没找到完整行
break
line = line.strip()
- if not line: # 空行跳过
+ if not line: # 空行跳过
continue
try:
obj = json.loads(line)
@@ -275,12 +276,14 @@ def stopScript():
@app.route('/passAnchorData', methods=['POST'])
def passAnchorData():
try:
- LogManager.method_info("关注打招呼","关注打招呼")
+ LogManager.method_info("关注打招呼", "关注打招呼")
data: Dict[str, Any] = request.get_json()
# 设备列表
idList = data.get("deviceList", [])
# 主播列表
acList = data.get("anchorList", [])
+ LogManager.info(f"[INFO] 获取数据: {idList} {acList}")
+
AiUtils.save_aclist_flat_append(acList)
# 是否需要回复
@@ -401,6 +404,70 @@ def queryAnchorList():
return ResultData(data=data).toJson()
+# 修改当前的主播列表数据
+
+@app.route("/updateAnchorList", methods=['POST'])
+def updateAnchorList():
+ """
+ invitationType: 1 普票 2 金票
+ state: 1 通行(True) / 0 不通行(False)
+ """
+ data = request.get_json(force=True, silent=True) or {}
+ invitationType = data.get("invitationType")
+ state = bool(data.get("state")) # 转成布尔
+
+ # 要更新成的值
+ new_status = 1 if state else 0
+
+ # 用工具类解析路径,避免 cwd 影响
+ file_path = AiUtils._resolve_path("Module/log/acList.json")
+
+ # 加载 JSON
+ try:
+ doc = json.loads(file_path.read_text(encoding="utf-8-sig"))
+ except Exception as e:
+ LogManager.error(f"[updateAnchorList] 读取失败: {e}")
+ return ResultData(code=500, massage=f"读取失败: {e}").toJson()
+
+ # 定位 anchorList
+ if isinstance(doc, list):
+ acList = doc
+ wrapper = None
+ elif isinstance(doc, dict) and isinstance(doc.get("anchorList"), list):
+ acList = doc["anchorList"]
+ wrapper = doc
+ else:
+ return ResultData(code=500, massage="文件格式不合法").toJson()
+
+ # 遍历并更新
+ updated = 0
+ for item in acList:
+ if isinstance(item, dict) and item.get("invitationType") == invitationType:
+ item["status"] = new_status
+ updated += 1
+
+ # 写回(保持原始结构)
+ try:
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+ to_write = wrapper if wrapper is not None else acList
+ file_path.write_text(json.dumps(to_write, ensure_ascii=False, indent=2), encoding="utf-8")
+ except Exception as e:
+ LogManager.error(f"[updateAnchorList] 写入失败: {e}")
+ return ResultData(code=500, massage=f"写入失败: {e}").toJson()
+
+ if updated:
+ return ResultData(data=updated, massage=f"已更新 {updated} 条记录").toJson()
+ else:
+ return ResultData(data=0, massage="未找到符合条件的记录").toJson()
+
+
+
+
+
+
+
+
+
# 删除主播
@app.route("/deleteAnchorWithIds", methods=['POST'])
def deleteAnchorWithIds():
diff --git a/Utils/AiUtils.py b/Utils/AiUtils.py
index 5cedf6d..4aeeaf1 100644
--- a/Utils/AiUtils.py
+++ b/Utils/AiUtils.py
@@ -614,22 +614,55 @@ class AiUtils(object):
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
+ # @staticmethod
+ # def _normalize_anchor_items(items):
+ # """
+ # 规范化输入为 [{anchorId, country}] 的列表:
+ # - 允许传入:单个对象、对象列表、字符串(当 anchorId 用)
+ # - 过滤不合规项
+ # """
+ # result = []
+ # if items is None:
+ # return result
+ #
+ # if isinstance(items, dict):
+ # # 单个对象
+ # aid = items.get("anchorId")
+ # if aid:
+ # result.append({"anchorId": str(aid), "country": items.get("country", "")})
+ # return result
+ #
+ # if isinstance(items, list):
+ # for it in items:
+ # if isinstance(it, dict):
+ # aid = it.get("anchorId")
+ # if aid:
+ # result.append({"anchorId": str(aid), "country": it.get("country", "")})
+ # elif isinstance(it, str):
+ # result.append({"anchorId": it, "country": ""})
+ # return result
+ #
+ # if isinstance(items, str):
+ # result.append({"anchorId": items, "country": ""})
+ # return result
+
@staticmethod
def _normalize_anchor_items(items):
"""
- 规范化输入为 [{anchorId, country}] 的列表:
- - 允许传入:单个对象、对象列表、字符串(当 anchorId 用)
- - 过滤不合规项
+ 规范化为 [{...}]:
+ - 允许传入:单个对象、对象数组、字符串(当 anchorId 用)
+ - 保留原有字段,不限制只存 anchorId/country
+ - 字符串输入 → {"anchorId": xxx}
"""
result = []
if items is None:
return result
if isinstance(items, dict):
- # 单个对象
aid = items.get("anchorId")
if aid:
- result.append({"anchorId": str(aid), "country": items.get("country", "")})
+ obj = dict(items) # 保留所有字段
+ result.append(obj)
return result
if isinstance(items, list):
@@ -637,15 +670,17 @@ class AiUtils(object):
if isinstance(it, dict):
aid = it.get("anchorId")
if aid:
- result.append({"anchorId": str(aid), "country": it.get("country", "")})
+ obj = dict(it)
+ result.append(obj)
elif isinstance(it, str):
- result.append({"anchorId": it, "country": ""})
+ result.append({"anchorId": it})
return result
if isinstance(items, str):
- result.append({"anchorId": items, "country": ""})
+ result.append({"anchorId": items})
return result
+
# -------- 追加(对象数组平铺追加) --------
@classmethod
def save_aclist_flat_append(cls, acList, filename="log/acList.json"):
@@ -657,6 +692,8 @@ class AiUtils(object):
{"anchorId": "tianliang30", "country": ""}
]
"""
+
+
file_path = Path(filename)
data = cls._read_json_list(file_path)
@@ -690,6 +727,97 @@ class AiUtils(object):
cls._write_json_list(file_path, data)
return first
+ @classmethod
+ def bulk_update_anchors(cls, updates, filename="log/acList.json", case_insensitive=False):
+ """
+ 批量修改(文件根必须是数组,沿用 _read_json_list 的约定)
+ - updates:
+ dict: {"id1": {...}, "id2": {...}}
+ list[dict]: [{"anchorId":"id1", ...}, {"anchorId":"id2", ...}]
+ - case_insensitive: True 时用小写比较 anchorId
+ 返回: {"updated": , "missing": [ids...], "file": "<实际命中的路径>"}
+ """
+
+ def norm_id(x: str) -> str:
+ s = str(x).strip()
+ return s.lower() if case_insensitive else s
+
+ # ✅ 关键:使用你已有的 _resolve_path,避免受 cwd 影响
+ file_path = cls._resolve_path(filename)
+
+ data = cls._read_json_list(file_path)
+ if not data:
+ return {"updated": 0, "missing": cls._collect_all_ids(updates), "file": str(file_path)}
+
+ # 1) 归一化 updates -> map[normalized_id] = patch
+ upd_map = {}
+ raw_ids = [] # 保留原始传入 id,用于返回 missing 时回显
+ if isinstance(updates, dict):
+ for aid, patch in updates.items():
+ if aid and isinstance(patch, dict):
+ key = norm_id(aid)
+ raw_ids.append(str(aid))
+ patch = {k: v for k, v in patch.items() if k != "anchorId"}
+ if patch:
+ upd_map[key] = {**upd_map.get(key, {}), **patch}
+ elif isinstance(updates, list):
+ for it in updates:
+ if isinstance(it, dict) and it.get("anchorId"):
+ rid = str(it["anchorId"])
+ key = norm_id(rid)
+ raw_ids.append(rid)
+ patch = {k: v for k, v in it.items() if k != "anchorId"}
+ if patch:
+ upd_map[key] = {**upd_map.get(key, {}), **patch}
+
+ if not upd_map:
+ return {"updated": 0, "missing": [], "file": str(file_path)}
+
+ # 2) 建索引:map[normalized_id] -> item
+ index = {}
+ for item in data:
+ if isinstance(item, dict) and "anchorId" in item:
+ key = norm_id(item.get("anchorId", ""))
+ if key:
+ index[key] = item
+
+ # 3) 执行更新
+ updated, seen = 0, set()
+ for key, patch in upd_map.items():
+ target = index.get(key)
+ if target is not None:
+ target.update(patch)
+ updated += 1
+ seen.add(key)
+
+ # 4) 写回
+ if updated > 0:
+ cls._write_json_list(file_path, data)
+
+ # 5) 计算未命中(按传入原始 ID 回显)
+ missing = []
+ for rid in raw_ids:
+ if norm_id(rid) not in seen:
+ missing.append(rid)
+
+ return {"updated": updated, "missing": missing, "file": str(file_path)}
+
+ @staticmethod
+ def _collect_all_ids(updates):
+ ids = []
+ if isinstance(updates, dict):
+ ids = [str(k) for k in updates.keys()]
+ elif isinstance(updates, list):
+ for it in updates:
+ if isinstance(it, dict) and it.get("anchorId"):
+ ids.append(str(it["anchorId"]))
+ return ids
+
+
+
+
+
+
@classmethod
def delete_anchors_by_ids(cls, ids: list[str], filename="log/acList.json") -> int:
"""
@@ -718,6 +846,39 @@ class AiUtils(object):
LogManager.error(f"[delete_anchors_by_ids] 写入失败: {e}")
return deleted
+ # -------- 查看第一个(取出但不删除) --------
+ @staticmethod
+ def _resolve_path(p) -> Path:
+ p = Path(p)
+ if p.is_absolute():
+ return p
+ # 以项目根目录 (iOSAI) 为基准 —— script 的上一级
+ base = Path(__file__).resolve().parents[1]
+ return (base / p).resolve()
+
+ @classmethod
+ def peek_aclist_first(cls, filename="Module/log/acList.json"):
+ file_path = cls._resolve_path(filename)
+ if not file_path.exists():
+ print(f"[peek] 文件不存在: {file_path}")
+ return None
+ try:
+ raw = file_path.read_text(encoding="utf-8-sig").strip()
+ if not raw:
+ return None
+ data = json.loads(raw)
+ arr = data if isinstance(data, list) else data.get("anchorList") if isinstance(data, dict) else None
+ if not arr:
+ return None
+ first = arr[0]
+ norm = cls._normalize_anchor_items(first)
+ return norm[0] if norm else None
+ except Exception as e:
+ print(f"[peek] 读取失败: {e}")
+ return None
+
+
+
@staticmethod
def run_tidevice_command(udid, action, bundle_id, timeout=30):
"""
diff --git a/Utils/ControlUtils.py b/Utils/ControlUtils.py
index 103fe2f..9a42eea 100644
--- a/Utils/ControlUtils.py
+++ b/Utils/ControlUtils.py
@@ -189,7 +189,7 @@ class ControlUtils(object):
def userClickProfile(cls, session, aid):
try:
user_btn = session.xpath("(//XCUIElementTypeButton[@name='用户' and @visible='true'])[1]")
- if user_btn:
+ if user_btn.exists:
user_btn.click()
time.sleep(3)
follow_btn = session.xpath(
diff --git a/script/ScriptManager.py b/script/ScriptManager.py
index 4a98825..9855034 100644
--- a/script/ScriptManager.py
+++ b/script/ScriptManager.py
@@ -61,10 +61,6 @@ class ScriptManager():
session.appium_settings({"snapshotMaxDepth": 15})
# 判断当前页面上是否有推荐按钮
- # el = session(xpath='//XCUIElementTypeButton[@name="top_tabs_recomend"]')
- # node = session.xpath(
- # '//XCUIElementTypeButton[@name="top_tabs_recomend" or @name="推荐" or @label="推荐"]'
- # )
el = session.xpath(
'//XCUIElementTypeButton[@name="top_tabs_recomend" or @name="推荐" or @label="推荐"]'
@@ -278,10 +274,7 @@ class ScriptManager():
retries = 0
while not event.is_set():
try:
- # AiUtils.pop_aclist_first()
- # anchor = AiUtils.pop_aclist_first()
- # if not anchor:
- # break
+
self.greetNewFollowers(udid, needReply, event)
except Exception as e:
@@ -301,11 +294,13 @@ class ScriptManager():
LogManager.method_info(f"是否要自动回复消息:{needReply}", "关注打招呼", udid)
# 先关闭Tik Tok
+
ControlUtils.closeTikTok(session, udid)
time.sleep(1)
# 重新打开Tik Tok
ControlUtils.openTikTok(session, udid)
+
time.sleep(3)
LogManager.method_info(f"重启tiktok", "关注打招呼", udid)
# 设置查找深度
@@ -369,7 +364,6 @@ class ScriptManager():
input.set_text(f"{aid or '暂无数据'}\n")
# 定位 "关注" 按钮 通过关注按钮的位置点击主播首页
-
session.appium_settings({"snapshotMaxDepth": 25})
try:
@@ -499,10 +493,9 @@ class ScriptManager():
chatInput = session.xpath("//TextView")
if chatInput.exists:
chatInput.click()
+ chatInput.set_text(f"{msg or '暂无数据'}\n")
time.sleep(2)
# 发送消息
- chatInput.set_text(f"{msg or '暂无数据'}\n")
-
# input.set_text(f"{aid or '暂无数据'}\n")
time.sleep(1)
@@ -603,6 +596,7 @@ class ScriptManager():
time.sleep(3)
continue # 重新进入 while 循环,调用 monitorMessages
+
# 检查未读消息并回复
def monitorMessages(self, session, udid):
diff --git a/tidevice_entry.py b/tidevice_entry.py
index 2cc9028..402d915 100644
--- a/tidevice_entry.py
+++ b/tidevice_entry.py
@@ -15,4 +15,4 @@ if __name__ == "__main__":
with open(os.path.expanduser("~/tidevice_crash.log"), "a", encoding="utf-8") as f:
traceback.print_exc(file=f)
# 静默退出,**返回码 1**(父进程只认 returncode)
- sys.exit(1)
\ No newline at end of file
+ sys.exit(1)