Compare commits
5 Commits
811935ac60
...
5c9b6cd4c7
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c9b6cd4c7 | |||
| 02aa85dae2 | |||
| 31302fb43e | |||
| b9ecce6eeb | |||
| 11e72d0fae |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
# Python bytecode & caches
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 # 只改第一条匹配的
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
237
Utils/AiUtils.py
237
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)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user