Compare commits

...

5 Commits

Author SHA1 Message Date
5c9b6cd4c7 20250904-初步功能已完成 2025-09-18 21:45:31 +08:00
02aa85dae2 保存本地修改 2025-09-18 21:37:50 +08:00
31302fb43e 20250918-新增主播库功能 2025-09-18 21:36:51 +08:00
b9ecce6eeb 20250918-新增主播库功能 2025-09-18 21:31:56 +08:00
11e72d0fae 20250918-新增主播库功能 2025-09-18 20:15:07 +08:00
23 changed files with 314 additions and 37 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,9 @@
# Byte-compiled / optimized / DLL files
__pycache__/
# Python bytecode & caches
*.pyc
*.pyo
*.pyd
*.py[cod]
*$py.class

View File

@@ -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:
@@ -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)
# 是否需要回复
@@ -402,6 +405,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["state"] = 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():
@@ -446,7 +513,7 @@ def update_last_message():
updated_count = JsonUtils.update_json_items(
match={"sender": sender, "text": text}, # 匹配条件
patch={"status": 1}, # 修改内容
patch={"state": 1}, # 修改内容
filename="log/last_message.json", # 要修改的文件
multi=False # 只改第一条匹配的
)

View File

@@ -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)
@@ -671,25 +708,162 @@ class AiUtils(object):
LogManager.info(f"[acList] 已追加 {len(to_add)} 条,当前总数={len(data)} -> {file_path}")
# -------- 弹出(取一个删一个) --------
# @classmethod
# def pop_aclist_first(cls, filename="log/acList.json"):
# """
# 从 JSON 数组中取出第一个 anchor 对象,并删除它;为空或文件不存在返回 None。
# 返回形如:{"anchorId": "...", "country": "..."}
# """
# file_path = Path(filename)
# data = cls._read_json_list(file_path)
# if not data:
# return None
#
# first = data.pop(0)
# # 兜底保证结构
# norm = cls._normalize_anchor_items(first)
# first = norm[0] if norm else None
#
# cls._write_json_list(file_path, data)
# return first
@classmethod
def pop_aclist_first(cls, filename="log/acList.json"):
def pop_aclist_first(cls, filename="Module/log/acList.json", mode="pop"):
"""
从 JSON 数组中取出第一个 anchor 对象,并删除它;为空或文件不存在返回 None
返回形如:{"anchorId": "...", "country": "..."}
从 JSON 数组/对象(anchorList)中取出第一个 anchor 对象。
- mode="pop" : 取出并删除
- mode="move" : 取出并放到列表尾部
返回形如:{"anchorId": "...", "country": "...", ...}
"""
file_path = Path(filename)
data = cls._read_json_list(file_path)
file_path = cls._resolve_path(filename)
if not file_path.exists():
return None
try:
raw = json.loads(file_path.read_text(encoding="utf-8-sig"))
except Exception as e:
LogManager.error(f"[pop_aclist_first] 读取失败: {e}")
return None
# 支持两种格式list 或 dict{anchorList:[...]}
if isinstance(raw, list):
data, wrapper = raw, None
elif isinstance(raw, dict) and isinstance(raw.get("anchorList"), list):
data, wrapper = raw["anchorList"], raw
else:
return None
if not data:
return None
# 取第一个
first = data.pop(0)
# 兜底保证结构
norm = cls._normalize_anchor_items(first)
first = norm[0] if norm else None
cls._write_json_list(file_path, data)
if first and mode == "move":
# 放到尾部
data.append(first)
# 写回
to_write = wrapper if wrapper is not None else data
file_path.write_text(
json.dumps(to_write, ensure_ascii=False, indent=2),
encoding="utf-8"
)
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": <int>, "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 +892,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):
"""

View File

@@ -188,7 +188,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(

View File

@@ -60,12 +60,6 @@ class ScriptManager():
# 设置手机的节点深度为15,判断该页面是否正确
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 +272,6 @@ 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:
@@ -330,8 +320,18 @@ class ScriptManager():
# 循环条件。1、 循环关闭 2、 数据处理完毕
while not event.is_set():
# 获取一个主播,并删除
# 获取一个主播,
result = AiUtils.peek_aclist_first()
state = result.get("state",0)
if not state:
LogManager.method_info(f"当前主播的状态是:{state} 不通行,取出数据移到列表尾部 继续下一个", "关注打招呼", udid)
AiUtils.pop_aclist_first(mode="move")
continue
# 并删除
anchor = AiUtils.pop_aclist_first()
LogManager.method_info(f"当前主播的状态是:{state} 通行,取出数据删除", "关注打招呼", udid)
if not anchor:
LogManager.method_info(f"数据库中的数据不足", "关注打招呼", udid)
time.sleep(30)
@@ -499,10 +499,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)