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 @@ - - - - - - - - - + + + + + @@ -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)