完成 Ruff 全量清理
This commit is contained in:
@@ -2,21 +2,16 @@
|
|||||||
|
|
||||||
## Ruff lint 清理
|
## Ruff lint 清理
|
||||||
|
|
||||||
记录日期:2026-08-30
|
状态:已完成
|
||||||
|
|
||||||
`uv run ruff check .` 当前发现 1263 条 lint 诊断。该事项暂不在本次处理,后续按批次清理并逐批运行 pytest、Pyright 和 Ruff 检查。
|
完成日期:2026-08-31
|
||||||
|
|
||||||
建议顺序:
|
本轮完成了 Ruff lint 全量清理,包括未使用导入与变量、导入排序、类型注解现代化、FastAPI 依赖声明、异常处理标注、时区处理及其他规则。FastAPI 路由参数中的依赖调用通过 `web/backend/**/*.py` 的 B008 配置例外保留框架惯用写法;非 FastAPI 的可变默认对象已改为函数内惰性初始化。
|
||||||
|
|
||||||
1. `F401`、`F841`:未使用导入和变量。
|
最终基线:
|
||||||
2. `BLE001`、`S110`、`S112`:异常处理质量。
|
|
||||||
3. `B008`:FastAPI 依赖声明模式,区分真实问题与框架惯用写法。
|
|
||||||
4. `I001`:导入排序。
|
|
||||||
5. `UP045`、`UP007`、`UP017`、`UP035`、`UP006`:类型注解和 Python 版本语法现代化。
|
|
||||||
|
|
||||||
当前基线:
|
- Ruff lint:0 条
|
||||||
|
- Ruff format:208 个文件全部通过
|
||||||
- Ruff lint:1263 条
|
- pytest:105 passed
|
||||||
- Ruff format:207 个文件全部通过
|
|
||||||
- pytest:104 passed
|
|
||||||
- Pyright:0 errors / 0 warnings
|
- Pyright:0 errors / 0 warnings
|
||||||
|
- Python compileall:通过
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class CookieEnricher:
|
|||||||
self.generate_acf_ccn_cookie()
|
self.generate_acf_ccn_cookie()
|
||||||
logger.info("补CK完成")
|
logger.info("补CK完成")
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_error = e
|
last_error = e
|
||||||
logger.warning(f"补CK失败 {attempt}/{max_attempts}: {e}")
|
logger.warning(f"补CK失败 {attempt}/{max_attempts}: {e}")
|
||||||
if attempt < max_attempts:
|
if attempt < max_attempts:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -68,8 +68,8 @@ class EmailVerifier:
|
|||||||
f"{self.roundcube_url}?_task=logout",
|
f"{self.roundcube_url}?_task=logout",
|
||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"Roundcube logout failed: {exc}")
|
||||||
self._rc_session = None
|
self._rc_session = None
|
||||||
self._rc_logged_in = False
|
self._rc_logged_in = False
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ class EmailVerifier:
|
|||||||
logger.debug(f"Roundcube: {password_name}登录成功 ({self.username})")
|
logger.debug(f"Roundcube: {password_name}登录成功 ({self.username})")
|
||||||
return True, False
|
return True, False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.warning(f"Roundcube: 登录异常: {e}")
|
logger.warning(f"Roundcube: 登录异常: {e}")
|
||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ class EmailVerifier:
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.warning(f"Roundcube: 获取邮件列表失败: {e}")
|
logger.warning(f"Roundcube: 获取邮件列表失败: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ class EmailVerifier:
|
|||||||
)
|
)
|
||||||
return resp.text
|
return resp.text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.warning(f"Roundcube: 读取邮件 {uid} 失败: {e}")
|
logger.warning(f"Roundcube: 读取邮件 {uid} 失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ class EmailVerifier:
|
|||||||
- "2026-06-20" → 直接解析
|
- "2026-06-20" → 直接解析
|
||||||
- "06-20" → 今年的该日期
|
- "06-20" → 今年的该日期
|
||||||
"""
|
"""
|
||||||
now = datetime.now()
|
now = datetime.now(UTC)
|
||||||
|
|
||||||
# 今天
|
# 今天
|
||||||
if date_str.startswith("今天"):
|
if date_str.startswith("今天"):
|
||||||
@@ -322,32 +322,36 @@ class EmailVerifier:
|
|||||||
|
|
||||||
# 日期格式 2026-06-20
|
# 日期格式 2026-06-20
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(date_str.strip(), "%Y-%m-%d")
|
return datetime.strptime(date_str.strip(), "%Y-%m-%d").replace(tzinfo=UTC)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 日期格式 2026-06-20 14:30
|
# 日期格式 2026-06-20 14:30
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(date_str.strip(), "%Y-%m-%d %H:%M")
|
return datetime.strptime(date_str.strip(), "%Y-%m-%d %H:%M").replace(
|
||||||
|
tzinfo=UTC
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 日期格式 2026-06-20 14:30:00
|
# 日期格式 2026-06-20 14:30:00
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(date_str.strip(), "%Y-%m-%d %H:%M:%S")
|
return datetime.strptime(date_str.strip(), "%Y-%m-%d %H:%M:%S").replace(
|
||||||
|
tzinfo=UTC
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 日期格式 06-20
|
# 日期格式 06-20
|
||||||
try:
|
try:
|
||||||
dt = datetime.strptime(date_str.strip(), "%m-%d")
|
dt = datetime.strptime(date_str.strip(), "%m-%d").replace(tzinfo=UTC)
|
||||||
return dt.replace(year=now.year)
|
return dt.replace(year=now.year)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 日期格式 06-20 14:30
|
# 日期格式 06-20 14:30
|
||||||
try:
|
try:
|
||||||
dt = datetime.strptime(date_str.strip(), "%m-%d %H:%M")
|
dt = datetime.strptime(date_str.strip(), "%m-%d %H:%M").replace(tzinfo=UTC)
|
||||||
return dt.replace(year=now.year)
|
return dt.replace(year=now.year)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
@@ -395,7 +399,7 @@ class EmailVerifier:
|
|||||||
return code
|
return code
|
||||||
except EmailLoginError:
|
except EmailLoginError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_error = str(e)
|
last_error = str(e)
|
||||||
logger.warning(f"Roundcube 读邮件异常: {e}")
|
logger.warning(f"Roundcube 读邮件异常: {e}")
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -398,7 +398,7 @@ class DouyuLogin:
|
|||||||
return LoginResult(
|
return LoginResult(
|
||||||
success=False, message=str(e), code="email_login_failed"
|
success=False, message=str(e), code="email_login_failed"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
elapsed = time.monotonic() - start_time
|
elapsed = time.monotonic() - start_time
|
||||||
if self.max_login_retries > 0:
|
if self.max_login_retries > 0:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -475,7 +475,7 @@ class DouyuLogin:
|
|||||||
return LoginResult(
|
return LoginResult(
|
||||||
success=False, message=str(e), code="email_login_failed"
|
success=False, message=str(e), code="email_login_failed"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
elapsed = time.monotonic() - start_time
|
elapsed = time.monotonic() - start_time
|
||||||
if self.max_login_retries > 0:
|
if self.max_login_retries > 0:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -901,7 +901,7 @@ class DouyuLogin:
|
|||||||
|
|
||||||
if response2.status_code == 200:
|
if response2.status_code == 200:
|
||||||
logger.info("WebLogin成功")
|
logger.info("WebLogin成功")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.warning(f"WebLogin请求失败(不影响登录): {e}")
|
logger.warning(f"WebLogin请求失败(不影响登录): {e}")
|
||||||
|
|
||||||
# 登录成功后补齐 Web 侧 Cookie。补 CK 失败只影响完整度,不回滚已成功的登录态。
|
# 登录成功后补齐 Web 侧 Cookie。补 CK 失败只影响完整度,不回滚已成功的登录态。
|
||||||
@@ -915,7 +915,7 @@ class DouyuLogin:
|
|||||||
).enrich_with_retry()
|
).enrich_with_retry()
|
||||||
except InterruptedError:
|
except InterruptedError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._cookie_enrich_error = self._truncate_error(str(e), 160)
|
self._cookie_enrich_error = self._truncate_error(str(e), 160)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"补CK最终失败,本次仍按登录成功保存基础CK: {self._cookie_enrich_error}"
|
f"补CK最终失败,本次仍按登录成功保存基础CK: {self._cookie_enrich_error}"
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class BaseWhitelistAdapter(ABC):
|
|||||||
|
|
||||||
return False, f"白名单添加失败,API响应: {resp}"
|
return False, f"白名单添加失败,API响应: {resp}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
msg = f"白名单同步失败: {e}"
|
msg = f"白名单同步失败: {e}"
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
return False, msg
|
return False, msg
|
||||||
@@ -165,7 +165,8 @@ def _get_local_exit_ip() -> str | None:
|
|||||||
match = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", text)
|
match = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", text)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug(f"本机出口 IP 查询失败: {url}: {exc}")
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -197,6 +198,7 @@ def get_exit_ip_via_proxy(proxy: str) -> str | None:
|
|||||||
match = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", text)
|
match = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", text)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1)
|
return match.group(1)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug(f"代理出口 IP 查询失败: {url}: {exc}")
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class XiequAdapter(BaseWhitelistAdapter):
|
|||||||
if r.get("IP") or r.get("ip")
|
if r.get("IP") or r.get("ip")
|
||||||
]
|
]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.error(f"获取白名单失败: {e}")
|
logger.error(f"获取白名单失败: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ class XiequAdapter(BaseWhitelistAdapter):
|
|||||||
logger.warning(f"白名单添加结果: {text}")
|
logger.warning(f"白名单添加结果: {text}")
|
||||||
return False, text
|
return False, text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.error(f"添加白名单失败: {e}")
|
logger.error(f"添加白名单失败: {e}")
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ class XiequAdapter(BaseWhitelistAdapter):
|
|||||||
logger.warning(f"白名单删除结果: {text}")
|
logger.warning(f"白名单删除结果: {text}")
|
||||||
return False, text
|
return False, text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.error(f"删除白名单失败: {e}")
|
logger.error(f"删除白名单失败: {e}")
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ class XiequAdapter(BaseWhitelistAdapter):
|
|||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
return True, msg
|
return True, msg
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
msg = f"连接失败: {e}"
|
msg = f"连接失败: {e}"
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class XkdailiAdapter(BaseWhitelistAdapter):
|
|||||||
logger.warning(f"星空白名单添加失败: {msg}")
|
logger.warning(f"星空白名单添加失败: {msg}")
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.error(f"星空添加白名单失败: {e}")
|
logger.error(f"星空添加白名单失败: {e}")
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ class XkdailiAdapter(BaseWhitelistAdapter):
|
|||||||
logger.warning(f"星空白名单删除失败: {msg}")
|
logger.warning(f"星空白名单删除失败: {msg}")
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.error(f"星空删除白名单失败: {e}")
|
logger.error(f"星空删除白名单失败: {e}")
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
@@ -159,5 +159,5 @@ class XkdailiAdapter(BaseWhitelistAdapter):
|
|||||||
# 其他错误(如无效IP),说明认证通过了
|
# 其他错误(如无效IP),说明认证通过了
|
||||||
return True, f"连接成功,API可访问: {msg}"
|
return True, f"连接成功,API可访问: {msg}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False, f"连接失败: {e}"
|
return False, f"连接失败: {e}"
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class ProxyResolver:
|
|||||||
f"代理预检 {attempt}/{max_attempts}: {last_error}: {text[:80]}",
|
f"代理预检 {attempt}/{max_attempts}: {last_error}: {text[:80]}",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_error = f"代理API请求失败: {exc}"
|
last_error = f"代理API请求失败: {exc}"
|
||||||
self._log("warning", f"代理预检 {attempt}/{max_attempts}: {last_error}")
|
self._log("warning", f"代理预检 {attempt}/{max_attempts}: {last_error}")
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def verify_proxy_url(proxy_url: str, timeout: tuple = (3, 5)) -> tuple[bool, str
|
|||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return True, "代理可用 → 斗鱼可达"
|
return True, "代理可用 → 斗鱼可达"
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
err_msg = str(exc)
|
err_msg = str(exc)
|
||||||
if "Tunnel connection failed" in err_msg or "503" in err_msg:
|
if "Tunnel connection failed" in err_msg or "503" in err_msg:
|
||||||
detail = "代理拒绝连接(白名单可能未生效)"
|
detail = "代理拒绝连接(白名单可能未生效)"
|
||||||
@@ -75,7 +75,8 @@ def verify_proxies_concurrent(
|
|||||||
ok, _ = future.result()
|
ok, _ = future.result()
|
||||||
if ok:
|
if ok:
|
||||||
available.append(future_map[future])
|
available.append(future_map[future])
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug(f"代理并发验证失败: {future_map[future]}: {exc}")
|
||||||
continue
|
continue
|
||||||
if available:
|
if available:
|
||||||
logger.success(
|
logger.success(
|
||||||
@@ -99,7 +100,8 @@ def verify_proxies_concurrent(
|
|||||||
if item != future:
|
if item != future:
|
||||||
item.cancel()
|
item.cancel()
|
||||||
return proxy_url, msg
|
return proxy_url, msg
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug(f"代理并发验证失败: {proxy_url}: {exc}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return None, f"共 {len(proxy_urls)} 个代理均不可用"
|
return None, f"共 {len(proxy_urls)} 个代理均不可用"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""代理模块使用的白名单适配器。"""
|
"""代理模块使用的白名单适配器。"""
|
||||||
|
|
||||||
|
|
||||||
from .proxy_platforms import create_adapter
|
from .proxy_platforms import create_adapter
|
||||||
from .proxy_platforms.base import BaseWhitelistAdapter, _get_local_exit_ip
|
from .proxy_platforms.base import BaseWhitelistAdapter, _get_local_exit_ip
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ def get_challenge_gt_bak() -> tuple[str, str]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"t": str(int(round(time.time() * 1000))),
|
"t": str(round(time.time() * 1000)),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
@@ -133,7 +133,7 @@ def get_js_address(gt: str, proxies: Mapping[str, str] | None = None) -> dict:
|
|||||||
|
|
||||||
params = {
|
params = {
|
||||||
"gt": gt,
|
"gt": gt,
|
||||||
"callback": "geetest_" + str(int(round(time.time() * 1000))),
|
"callback": "geetest_" + str(round(time.time() * 1000)),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = _get(
|
response = _get(
|
||||||
@@ -173,7 +173,7 @@ def get_c_s(
|
|||||||
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
||||||
+ w
|
+ w
|
||||||
+ "&callback=geetest_"
|
+ "&callback=geetest_"
|
||||||
+ str(int(round(time.time() * 1000))),
|
+ str(round(time.time() * 1000)),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
proxies=proxies,
|
proxies=proxies,
|
||||||
)
|
)
|
||||||
@@ -210,7 +210,7 @@ def req_fullpage_validate(
|
|||||||
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
||||||
+ w
|
+ w
|
||||||
+ "&callback=geetest_"
|
+ "&callback=geetest_"
|
||||||
+ str(int(round(time.time() * 1000))),
|
+ str(round(time.time() * 1000)),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
proxies=proxies,
|
proxies=proxies,
|
||||||
)
|
)
|
||||||
@@ -241,7 +241,7 @@ def req_slide(gt: str, challenge: str, w2: str) -> None:
|
|||||||
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
+ "&lang=zh-cn&pt=0&client_type=web&w="
|
||||||
+ w2
|
+ w2
|
||||||
+ "&callback=geetest_"
|
+ "&callback=geetest_"
|
||||||
+ str(int(round(time.time() * 1000))),
|
+ str(round(time.time() * 1000)),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=REQUEST_TIMEOUT,
|
timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -277,7 +277,7 @@ def get_picture(gt: str, challenge: str) -> tuple[str, str, list[int], str, str,
|
|||||||
"isPC": "true",
|
"isPC": "true",
|
||||||
"autoReset": "true",
|
"autoReset": "true",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"callback": "geetest_" + str(int(round(time.time() * 1000))),
|
"callback": "geetest_" + str(round(time.time() * 1000)),
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
@@ -343,7 +343,7 @@ def req_end(gt: str, challenge: str, w: str) -> dict:
|
|||||||
+ "&lang=zh-cn&%24_BCm=0&client_type=web&w="
|
+ "&lang=zh-cn&%24_BCm=0&client_type=web&w="
|
||||||
+ w
|
+ w
|
||||||
+ "&callback=geetest_"
|
+ "&callback=geetest_"
|
||||||
+ str(int(round(time.time() * 1000))),
|
+ str(round(time.time() * 1000)),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=REQUEST_TIMEOUT,
|
timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -592,7 +592,7 @@ def H(t: int, e: str) -> str:
|
|||||||
n = 36 * r[0] + r[1]
|
n = 36 * r[0] + r[1]
|
||||||
|
|
||||||
# 计算目标值
|
# 计算目标值
|
||||||
a = round(t) + n
|
a = (t) + n
|
||||||
|
|
||||||
# 构建字符池
|
# 构建字符池
|
||||||
_ = [[], [], [], [], []]
|
_ = [[], [], [], [], []]
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def get_w2(gt: str, challenge: str, c: list[int], s: str, str_16: str) -> str:
|
|||||||
"u": fake_timing["loadEventEnd"],
|
"u": fake_timing["loadEventEnd"],
|
||||||
}
|
}
|
||||||
|
|
||||||
first_time = int(round(time.time() * 1000)) # 伪造脚本开始运行时间
|
first_time = round(time.time() * 1000) # 伪造脚本开始运行时间
|
||||||
|
|
||||||
guiji_yuanshu_shuzu = generate_realistic_trajectory(
|
guiji_yuanshu_shuzu = generate_realistic_trajectory(
|
||||||
start_x=random.randint(400, 600), # 起始位置随机
|
start_x=random.randint(400, 600), # 起始位置随机
|
||||||
@@ -99,7 +99,7 @@ def get_w2(gt: str, challenge: str, c: list[int], s: str, str_16: str) -> str:
|
|||||||
compressed = compress_trajectory(trajectory)
|
compressed = compress_trajectory(trajectory)
|
||||||
tt = encrypt_string(compressed, c, s)
|
tt = encrypt_string(compressed, c, s)
|
||||||
|
|
||||||
passtime = str(int(round(time.time() * 1000)) - first_time)
|
passtime = str(round(time.time() * 1000) - first_time)
|
||||||
|
|
||||||
rp = simple_md5(gt + challenge + passtime)
|
rp = simple_md5(gt + challenge + passtime)
|
||||||
|
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ class UserPrizeRecordItem(TafStruct):
|
|||||||
self.exchangeDate = ins.read_int64(21, default=self.exchangeDate)
|
self.exchangeDate = ins.read_int64(21, default=self.exchangeDate)
|
||||||
while True:
|
while True:
|
||||||
pos = ins.buf.tell()
|
pos = ins.buf.tell()
|
||||||
tag, dtype = ins.read_head()
|
_tag, dtype = ins.read_head()
|
||||||
if dtype == TafType.STRUCT_END:
|
if dtype == TafType.STRUCT_END:
|
||||||
ins.buf.seek(pos)
|
ins.buf.seek(pos)
|
||||||
return
|
return
|
||||||
@@ -406,7 +406,7 @@ class ActTaskPrizeInfo(TafStruct):
|
|||||||
self.extra = ins.read_map(12)
|
self.extra = ins.read_map(12)
|
||||||
while True:
|
while True:
|
||||||
pos = ins.buf.tell()
|
pos = ins.buf.tell()
|
||||||
tag, dtype = ins.read_head()
|
_tag, dtype = ins.read_head()
|
||||||
if dtype == TafType.STRUCT_END:
|
if dtype == TafType.STRUCT_END:
|
||||||
ins.buf.seek(pos)
|
ins.buf.seek(pos)
|
||||||
return
|
return
|
||||||
@@ -494,7 +494,7 @@ class ActTaskDetailItem(TafStruct):
|
|||||||
self.endTime = ins.read_string(26, default=self.endTime)
|
self.endTime = ins.read_string(26, default=self.endTime)
|
||||||
while True:
|
while True:
|
||||||
pos = ins.buf.tell()
|
pos = ins.buf.tell()
|
||||||
tag, dtype = ins.read_head()
|
_tag, dtype = ins.read_head()
|
||||||
if dtype == TafType.STRUCT_END:
|
if dtype == TafType.STRUCT_END:
|
||||||
ins.buf.seek(pos)
|
ins.buf.seek(pos)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ def solve_safe_auth(
|
|||||||
result = solver.solve(risk_url)
|
result = solver.solve(risk_url)
|
||||||
except HuyaQrAuthRequiredError:
|
except HuyaQrAuthRequiredError:
|
||||||
raise
|
raise
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_err = exc
|
last_err = exc
|
||||||
logger.warning(f"[safe_auth] 第 {attempt + 1} 次过验异常: {exc}")
|
logger.warning(f"[safe_auth] 第 {attempt + 1} 次过验异常: {exc}")
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
@@ -425,8 +425,8 @@ class HuyaAppPasswordLogin:
|
|||||||
from .device_profile import record_login
|
from .device_profile import record_login
|
||||||
|
|
||||||
record_login(self.username, result.success, result.message)
|
record_login(self.username, result.success, result.message)
|
||||||
except Exception: # 元数据记录失败不影响登录结果
|
except Exception as exc: # noqa: BLE001 # 元数据记录失败不影响登录结果
|
||||||
pass
|
logger.debug(f"登录元数据记录失败: {exc}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _login_impl(self) -> HuyaLoginResult:
|
def _login_impl(self) -> HuyaLoginResult:
|
||||||
@@ -450,7 +450,7 @@ class HuyaAppPasswordLogin:
|
|||||||
message=str(exc),
|
message=str(exc),
|
||||||
code="QR_AUTH_REQUIRED",
|
code="QR_AUTH_REQUIRED",
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaLoginResult(
|
return HuyaLoginResult(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"App 登录凭证获取失败: {exc}",
|
message=f"App 登录凭证获取失败: {exc}",
|
||||||
@@ -478,7 +478,7 @@ class HuyaAppPasswordLogin:
|
|||||||
if env.uid != uid:
|
if env.uid != uid:
|
||||||
struct.pack_into(">Q", raw, env.uid_off, uid)
|
struct.pack_into(">Q", raw, env.uid_off, uid)
|
||||||
wup = base64.b64encode(bytes(raw)).decode("ascii")
|
wup = base64.b64encode(bytes(raw)).decode("ascii")
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaLoginResult(
|
return HuyaLoginResult(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"证书铸造/信封补丁失败: {exc}",
|
message=f"证书铸造/信封补丁失败: {exc}",
|
||||||
@@ -603,7 +603,7 @@ class HuyaAppPasswordLogin:
|
|||||||
sdid=sdid,
|
sdid=sdid,
|
||||||
context=pc.context,
|
context=pc.context,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaLoginResult(
|
return HuyaLoginResult(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"扫码绑定兑换 Cookie 失败: {exc}",
|
message=f"扫码绑定兑换 Cookie 失败: {exc}",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from core.sms_provider import SmsLine, SmsProviderClient
|
from core.sms_provider import SmsLine, SmsProviderClient
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ def register_huya_with_sms_line(
|
|||||||
sms_url=item.url,
|
sms_url=item.url,
|
||||||
)
|
)
|
||||||
|
|
||||||
sent_at = datetime.now()
|
sent_at = datetime.now(UTC)
|
||||||
try:
|
try:
|
||||||
code_result = send_huya_sms_code(phone=phone, proxies=proxies)
|
code_result = send_huya_sms_code(phone=phone, proxies=proxies)
|
||||||
except HuyaLoginError as exc:
|
except HuyaLoginError as exc:
|
||||||
@@ -89,7 +89,7 @@ def register_huya_with_sms_line(
|
|||||||
normalized_phone=normalized_phone,
|
normalized_phone=normalized_phone,
|
||||||
sms_url=item.url,
|
sms_url=item.url,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaAutoRegisterResult(
|
return HuyaAutoRegisterResult(
|
||||||
phone=phone,
|
phone=phone,
|
||||||
provider=item.provider,
|
provider=item.provider,
|
||||||
@@ -155,7 +155,7 @@ def register_huya_with_sms_line(
|
|||||||
sms_url=item.url,
|
sms_url=item.url,
|
||||||
attempts=attempts,
|
attempts=attempts,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return HuyaAutoRegisterResult(
|
return HuyaAutoRegisterResult(
|
||||||
phone=phone,
|
phone=phone,
|
||||||
provider=item.provider,
|
provider=item.provider,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import UTC, datetime
|
||||||
from urllib.parse import quote, urlsplit, urlunsplit
|
from urllib.parse import quote, urlsplit, urlunsplit
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -496,7 +496,7 @@ def change_huya_password_with_sms_line(
|
|||||||
) -> HuyaChangePasswordResult:
|
) -> HuyaChangePasswordResult:
|
||||||
"""使用同一手机号接码链接完成改密短信验证。"""
|
"""使用同一手机号接码链接完成改密短信验证。"""
|
||||||
changer = HuyaPasswordChanger(uid=uid, cookie=cookie, proxies=proxies)
|
changer = HuyaPasswordChanger(uid=uid, cookie=cookie, proxies=proxies)
|
||||||
sent_at = datetime.now()
|
sent_at = datetime.now(UTC)
|
||||||
code_result = changer.send_code()
|
code_result = changer.send_code()
|
||||||
if not code_result.success or not code_result.session_data:
|
if not code_result.success or not code_result.session_data:
|
||||||
return HuyaChangePasswordResult(
|
return HuyaChangePasswordResult(
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import random
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent.parent.parent
|
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
DATA_DIR = ROOT / "data"
|
DATA_DIR = ROOT / "data"
|
||||||
PRIMARY_PROFILE_DB = DATA_DIR / "huya_device_profiles.json"
|
PRIMARY_PROFILE_DB = DATA_DIR / "huya_device_profiles.json"
|
||||||
@@ -119,13 +121,13 @@ def _load_db() -> dict:
|
|||||||
if PRIMARY_PROFILE_DB.exists():
|
if PRIMARY_PROFILE_DB.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(PRIMARY_PROFILE_DB.read_text("utf-8"))
|
return json.loads(PRIMARY_PROFILE_DB.read_text("utf-8"))
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"读取主设备画像库失败: {exc}")
|
||||||
if FALLBACK_PROFILE_DB.exists():
|
if FALLBACK_PROFILE_DB.exists():
|
||||||
try:
|
try:
|
||||||
return json.loads(FALLBACK_PROFILE_DB.read_text("utf-8"))
|
return json.loads(FALLBACK_PROFILE_DB.read_text("utf-8"))
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"读取备用设备画像库失败: {exc}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -135,8 +137,8 @@ def _save_db(db: dict) -> None:
|
|||||||
PRIMARY_PROFILE_DB.write_text(
|
PRIMARY_PROFILE_DB.write_text(
|
||||||
json.dumps(db, indent=2, ensure_ascii=False), encoding="utf-8"
|
json.dumps(db, indent=2, ensure_ascii=False), encoding="utf-8"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"保存设备画像库失败: {exc}")
|
||||||
|
|
||||||
|
|
||||||
def _enrich_profile(profile: dict) -> tuple[dict, bool]:
|
def _enrich_profile(profile: dict) -> tuple[dict, bool]:
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import json
|
|||||||
import struct
|
import struct
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
INT8, INT16, INT32, INT64 = 0x00, 0x01, 0x02, 0x03
|
||||||
STRING1, STRING4 = 0x06, 0x07
|
STRING1, STRING4 = 0x06, 0x07
|
||||||
MAP, LIST = 0x08, 0x09
|
MAP, LIST = 0x08, 0x09
|
||||||
@@ -131,8 +133,8 @@ class Envelope:
|
|||||||
if candidate.exists():
|
if candidate.exists():
|
||||||
try:
|
try:
|
||||||
return cls._load_from_path(candidate)
|
return cls._load_from_path(candidate)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"加载证书信封候选文件失败: {candidate}: {exc}")
|
||||||
return cls(base64.b64decode(DEFAULT_QURL_B64))
|
return cls(base64.b64decode(DEFAULT_QURL_B64))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ TAF/WUP 帧解码器 — 将二进制帧转为可读摘要,用于日志输出
|
|||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from .taf_protocol import TafInputStream, TafType
|
from .taf_protocol import TafInputStream, TafType
|
||||||
from .wup_protocol import normalize_wup_payload
|
from .wup_protocol import normalize_wup_payload
|
||||||
|
|
||||||
@@ -101,13 +103,13 @@ def _decode_taf_struct(ins: TafInputStream, depth: int = 0) -> dict:
|
|||||||
if depth < 5:
|
if depth < 5:
|
||||||
try:
|
try:
|
||||||
val = _decode_taf_value(ins, dtype, depth)
|
val = _decode_taf_value(ins, dtype, depth)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
val = f"<decode_err:0x{dtype:02x}>"
|
val = f"<decode_err:0x{dtype:02x}>"
|
||||||
else:
|
else:
|
||||||
val = "<...>"
|
val = "<...>"
|
||||||
try:
|
try:
|
||||||
ins.skip_field(dtype)
|
ins.skip_field(dtype)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
break
|
break
|
||||||
key = f"tag{tag}"
|
key = f"tag{tag}"
|
||||||
if key in fields:
|
if key in fields:
|
||||||
@@ -195,19 +197,20 @@ def _decode_wup_body(body: bytes) -> dict:
|
|||||||
if v:
|
if v:
|
||||||
try:
|
try:
|
||||||
tins = TafInputStream(v)
|
tins = TafInputStream(v)
|
||||||
ttag, tdt = tins.peek_head()
|
_ttag, tdt = tins.peek_head()
|
||||||
if tdt == TafType.STRUCT_BEGIN:
|
if tdt == TafType.STRUCT_BEGIN:
|
||||||
tins.read_head()
|
tins.read_head()
|
||||||
result[k] = _decode_taf_struct(tins)
|
result[k] = _decode_taf_struct(tins)
|
||||||
else:
|
else:
|
||||||
result[k] = f"<{len(v)}B>"
|
result[k] = f"<{len(v)}B>"
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug(f"TAF 嵌套结构解码失败: {exc}")
|
||||||
result[k] = f"<{len(v)}B>"
|
result[k] = f"<{len(v)}B>"
|
||||||
else:
|
else:
|
||||||
result[k] = _decode_taf_value(sins, vt)
|
result[k] = _decode_taf_value(sins, vt)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"TAF 字段解码失败: {exc}")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
result["err"] = str(e)
|
result["err"] = str(e)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -286,7 +289,7 @@ def format_wss_log(body: bytes, cmd: int, seq: int, direction: str) -> str:
|
|||||||
try:
|
try:
|
||||||
ins = TafInputStream(clean)
|
ins = TafInputStream(clean)
|
||||||
# 看第一个 head
|
# 看第一个 head
|
||||||
tag, dtype = ins.peek_head()
|
_tag, dtype = ins.peek_head()
|
||||||
if dtype == TafType.STRUCT_BEGIN:
|
if dtype == TafType.STRUCT_BEGIN:
|
||||||
ins.read_head()
|
ins.read_head()
|
||||||
fields = _decode_taf_struct(ins)
|
fields = _decode_taf_struct(ins)
|
||||||
@@ -314,6 +317,6 @@ def format_wss_log(body: bytes, cmd: int, seq: int, direction: str) -> str:
|
|||||||
if fields:
|
if fields:
|
||||||
return f"{prefix} {cmd_name} {_fmt_fields(cast(dict[str, Any], _truncate(fields)))}"
|
return f"{prefix} {cmd_name} {_fmt_fields(cast(dict[str, Any], _truncate(fields)))}"
|
||||||
return f"{prefix} {cmd_name}"
|
return f"{prefix} {cmd_name}"
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
cmd_name = CMD_NAMES.get(cmd, f"0x{cmd:02x}")
|
||||||
return f"{prefix} {cmd_name} ({len(body)}B)"
|
return f"{prefix} {cmd_name} ({len(body)}B)"
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class WSConnectParaInfo(TafStruct):
|
|||||||
|
|
||||||
def _gen_trace_id() -> str:
|
def _gen_trace_id() -> str:
|
||||||
"""生成 sTraceId (格式 hex8:hex8:0:0,HAR 实证)"""
|
"""生成 sTraceId (格式 hex8:hex8:0:0,HAR 实证)"""
|
||||||
h = "%016x" % random.getrandbits(64)
|
h = f"{random.getrandbits(64):016x}"
|
||||||
return f"{h}:{h}:0:0"
|
return f"{h}:{h}:0:0"
|
||||||
|
|
||||||
|
|
||||||
@@ -294,7 +294,7 @@ class HuyaHttpClient:
|
|||||||
import gzip
|
import gzip
|
||||||
|
|
||||||
resp_data = gzip.decompress(resp_data)
|
resp_data = gzip.decompress(resp_data)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[HTTP] ❌ 请求失败: {type(e).__name__}: {e}")
|
self.logger(f"[HTTP] ❌ 请求失败: {type(e).__name__}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -611,13 +611,13 @@ class HuyaHttpClient:
|
|||||||
or resp.headers.get("Content-Encoding") == "gzip"
|
or resp.headers.get("Content-Encoding") == "gzip"
|
||||||
):
|
):
|
||||||
resp_data = gzip.decompress(resp_data)
|
resp_data = gzip.decompress(resp_data)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[LIVELINK] ❌ 小程序码请求失败: {type(e).__name__}: {e}")
|
self.logger(f"[LIVELINK] ❌ 小程序码请求失败: {type(e).__name__}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(resp_data.decode("utf-8", "replace"))
|
data = json.loads(resp_data.decode("utf-8", "replace"))
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[LIVELINK] ❌ 小程序码响应解析失败: {type(e).__name__}: {e}")
|
self.logger(f"[LIVELINK] ❌ 小程序码响应解析失败: {type(e).__name__}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -679,13 +679,13 @@ class HuyaHttpClient:
|
|||||||
or resp.headers.get("Content-Encoding") == "gzip"
|
or resp.headers.get("Content-Encoding") == "gzip"
|
||||||
):
|
):
|
||||||
resp_data = gzip.decompress(resp_data)
|
resp_data = gzip.decompress(resp_data)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[LIVELINK] ❌ 二维码状态请求失败: {type(e).__name__}: {e}")
|
self.logger(f"[LIVELINK] ❌ 二维码状态请求失败: {type(e).__name__}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(resp_data.decode("utf-8", "replace"))
|
data = json.loads(resp_data.decode("utf-8", "replace"))
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(
|
self.logger(
|
||||||
f"[LIVELINK] ❌ 二维码状态响应解析失败: {type(e).__name__}: {e}"
|
f"[LIVELINK] ❌ 二维码状态响应解析失败: {type(e).__name__}: {e}"
|
||||||
)
|
)
|
||||||
@@ -972,7 +972,7 @@ class HuyaHttpClient:
|
|||||||
import gzip
|
import gzip
|
||||||
|
|
||||||
resp_data = gzip.decompress(resp_data)
|
resp_data = gzip.decompress(resp_data)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[HTTP] ❌ payOrderSubmitV5 失败: {e}")
|
self.logger(f"[HTTP] ❌ payOrderSubmitV5 失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -380,8 +380,7 @@ class HuyaPasswordLogin:
|
|||||||
|
|
||||||
last_exc: Exception | None = None
|
last_exc: Exception | None = None
|
||||||
for attempt in range(max_ip_retries + 1):
|
for attempt in range(max_ip_retries + 1):
|
||||||
if attempt > 0:
|
if attempt > 0 and not self._swap_proxy():
|
||||||
if not self._swap_proxy():
|
|
||||||
logger.warning("无可用代理可切换,停止换 IP 重试")
|
logger.warning("无可用代理可切换,停止换 IP 重试")
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
来源:m-shop.yaoguo.com 的 jce/ShopFacade.js、api/orderui.ts、api/mall/PlayMallNewHome.ts
|
来源:m-shop.yaoguo.com 的 jce/ShopFacade.js、api/orderui.ts、api/mall/PlayMallNewHome.ts
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from .taf_protocol import TafInputStream, TafOutputStream, TafStruct, TafType
|
from .taf_protocol import TafInputStream, TafOutputStream, TafStruct, TafType
|
||||||
|
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ def _skip_to_struct_end(ins: TafInputStream):
|
|||||||
"""跳过当前结构里未解析的尾部字段,停在 STRUCT_END 前。"""
|
"""跳过当前结构里未解析的尾部字段,停在 STRUCT_END 前。"""
|
||||||
while True:
|
while True:
|
||||||
pos = ins.buf.tell()
|
pos = ins.buf.tell()
|
||||||
tag, dtype = ins.read_head()
|
_tag, dtype = ins.read_head()
|
||||||
if dtype == TafType.STRUCT_END:
|
if dtype == TafType.STRUCT_END:
|
||||||
ins.buf.seek(pos)
|
ins.buf.seek(pos)
|
||||||
return
|
return
|
||||||
@@ -427,7 +426,7 @@ class GoodsPriceInfo(TafStruct):
|
|||||||
def first_sku_id(self) -> int:
|
def first_sku_id(self) -> int:
|
||||||
if not self.skuMap:
|
if not self.skuMap:
|
||||||
return 0
|
return 0
|
||||||
return sorted(self.skuMap.keys())[0]
|
return min(self.skuMap.keys())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sku_list(self) -> list[dict]:
|
def sku_list(self) -> list[dict]:
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ class TafInputStream:
|
|||||||
|
|
||||||
def _read_int_len(self) -> int:
|
def _read_int_len(self) -> int:
|
||||||
"""读 map/list 长度(int32 带优化)"""
|
"""读 map/list 长度(int32 带优化)"""
|
||||||
tag, dtype = self.read_head()
|
_tag, dtype = self.read_head()
|
||||||
return self._read_int_value(dtype)
|
return self._read_int_value(dtype)
|
||||||
|
|
||||||
def _read_int_value(self, dtype: int) -> int:
|
def _read_int_value(self, dtype: int) -> int:
|
||||||
@@ -273,7 +273,7 @@ class TafInputStream:
|
|||||||
|
|
||||||
def _skip_struct(self):
|
def _skip_struct(self):
|
||||||
while True:
|
while True:
|
||||||
tag, dtype = self.read_head()
|
_tag, dtype = self.read_head()
|
||||||
if dtype == TafType.STRUCT_END:
|
if dtype == TafType.STRUCT_END:
|
||||||
break
|
break
|
||||||
self.skip_field(dtype)
|
self.skip_field(dtype)
|
||||||
@@ -441,7 +441,7 @@ class TafInputStream:
|
|||||||
obj = struct_class()
|
obj = struct_class()
|
||||||
obj.read_from(self)
|
obj.read_from(self)
|
||||||
# 消费 STRUCT_END
|
# 消费 STRUCT_END
|
||||||
t, dt = self.read_head()
|
_t, dt = self.read_head()
|
||||||
if dt != TafType.STRUCT_END:
|
if dt != TafType.STRUCT_END:
|
||||||
raise ValueError(f"期望 STRUCT_END, 实际 0x{dt:02x}")
|
raise ValueError(f"期望 STRUCT_END, 实际 0x{dt:02x}")
|
||||||
return obj
|
return obj
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ class HuyaCaptchaOcr:
|
|||||||
target["cropped_image"],
|
target["cropped_image"],
|
||||||
char["cropped_image"],
|
char["cropped_image"],
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
score_matrix[target_index][char_index] = 1e6
|
score_matrix[target_index][char_index] = 1e6
|
||||||
|
|
||||||
row_ind, col_ind = linear_sum_assignment(score_matrix)
|
row_ind, col_ind = linear_sum_assignment(score_matrix)
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class HuyaVerificationSolver:
|
|||||||
max_width = max(bg.shape[1], tip.shape[1])
|
max_width = max(bg.shape[1], tip.shape[1])
|
||||||
|
|
||||||
def pad_right(img, target_width):
|
def pad_right(img, target_width):
|
||||||
height, width = img.shape[:2]
|
_height, width = img.shape[:2]
|
||||||
if width >= target_width:
|
if width >= target_width:
|
||||||
return img
|
return img
|
||||||
return cv2.copyMakeBorder(
|
return cv2.copyMakeBorder(
|
||||||
@@ -299,8 +299,8 @@ class HuyaVerificationSolver:
|
|||||||
"虎牙登录风控strategys完整结构: {}",
|
"虎牙登录风控strategys完整结构: {}",
|
||||||
json.dumps(strategies, ensure_ascii=False)[:1200],
|
json.dumps(strategies, ensure_ascii=False)[:1200],
|
||||||
)
|
)
|
||||||
except Exception: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug(f"风控策略日志序列化失败: {exc}")
|
||||||
strategy_url_lower = strategy_url.lower()
|
strategy_url_lower = strategy_url.lower()
|
||||||
# 判定依据是 URL 路径,不是 strategy 数值。
|
# 判定依据是 URL 路径,不是 strategy 数值。
|
||||||
# 实测(2026-08-25): strategy=64 时 pt_auth.html 是滑块、qr_auth.html 才是扫码,
|
# 实测(2026-08-25): strategy=64 时 pt_auth.html 是滑块、qr_auth.html 才是扫码,
|
||||||
|
|||||||
+14
-12
@@ -76,7 +76,7 @@ class WssMessage:
|
|||||||
def decode(cls, data: bytes) -> "WssMessage":
|
def decode(cls, data: bytes) -> "WssMessage":
|
||||||
if len(data) < 6:
|
if len(data) < 6:
|
||||||
raise ValueError(f"消息太短: {len(data)} bytes")
|
raise ValueError(f"消息太短: {len(data)} bytes")
|
||||||
version, command = struct.unpack(">BB", data[0:2])
|
_version, command = struct.unpack(">BB", data[0:2])
|
||||||
sequence = struct.unpack(">I", data[2:6])[0]
|
sequence = struct.unpack(">I", data[2:6])[0]
|
||||||
body = data[6:]
|
body = data[6:]
|
||||||
return cls(command=command, sequence=sequence, body=body)
|
return cls(command=command, sequence=sequence, body=body)
|
||||||
@@ -256,7 +256,7 @@ class HuyaWssClient:
|
|||||||
format_wss_log(msg.body, msg.command, msg.sequence, "收")
|
format_wss_log(msg.body, msg.command, msg.sequence, "收")
|
||||||
)
|
)
|
||||||
await self._handle_message(msg)
|
await self._handle_message(msg)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(
|
self.logger(
|
||||||
f"[WSS] 解析消息失败: {e} raw_hex={raw_bytes[:50].hex()}"
|
f"[WSS] 解析消息失败: {e} raw_hex={raw_bytes[:50].hex()}"
|
||||||
)
|
)
|
||||||
@@ -264,7 +264,7 @@ class HuyaWssClient:
|
|||||||
pass
|
pass
|
||||||
except websockets.exceptions.ConnectionClosed as e:
|
except websockets.exceptions.ConnectionClosed as e:
|
||||||
self.logger(f"[WSS] 连接关闭: code={e.code} reason={e.reason}")
|
self.logger(f"[WSS] 连接关闭: code={e.code} reason={e.reason}")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[WSS] 接收循环异常: {type(e).__name__}: {e}")
|
self.logger(f"[WSS] 接收循环异常: {type(e).__name__}: {e}")
|
||||||
|
|
||||||
async def _handle_message(self, msg: WssMessage):
|
async def _handle_message(self, msg: WssMessage):
|
||||||
@@ -289,7 +289,7 @@ class HuyaWssClient:
|
|||||||
await self.send_heartbeat()
|
await self.send_heartbeat()
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[WSS] 心跳循环异常: {e}")
|
self.logger(f"[WSS] 心跳循环异常: {e}")
|
||||||
|
|
||||||
async def send_heartbeat(self):
|
async def send_heartbeat(self):
|
||||||
@@ -399,7 +399,7 @@ class HuyaWssClient:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
ins = TafInputStream(treq)
|
ins = TafInputStream(treq)
|
||||||
tag, dtype = ins.peek_head()
|
_tag, dtype = ins.peek_head()
|
||||||
if dtype != 0x0A: # STRUCT_BEGIN
|
if dtype != 0x0A: # STRUCT_BEGIN
|
||||||
self.logger(f"[RPC] wsLaunch tRsp 非结构体 dtype=0x{dtype:02x}")
|
self.logger(f"[RPC] wsLaunch tRsp 非结构体 dtype=0x{dtype:02x}")
|
||||||
return
|
return
|
||||||
@@ -432,7 +432,7 @@ class HuyaWssClient:
|
|||||||
self.logger(
|
self.logger(
|
||||||
f"[RPC] wsLaunch 解析: guid={self._launch_guid} ip={self._launch_ip}"
|
f"[RPC] wsLaunch 解析: guid={self._launch_guid} ip={self._launch_ip}"
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self.logger(f"[RPC] wsLaunch 响应解析失败: {e}")
|
self.logger(f"[RPC] wsLaunch 响应解析失败: {e}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -610,15 +610,17 @@ class HuyaWssClient:
|
|||||||
if data and isinstance(data, bytes) and len(data) > 0:
|
if data and isinstance(data, bytes) and len(data) > 0:
|
||||||
try:
|
try:
|
||||||
ins = TafInputStream(data)
|
ins = TafInputStream(data)
|
||||||
tag, dtype = ins.peek_head()
|
_tag, dtype = ins.peek_head()
|
||||||
if dtype == TafType.STRUCT_BEGIN:
|
if dtype == TafType.STRUCT_BEGIN:
|
||||||
ins.read_head()
|
ins.read_head()
|
||||||
decoded = _decode_taf_struct(ins)
|
decoded = _decode_taf_struct(ins)
|
||||||
self.logger(
|
self.logger(
|
||||||
f"[←] {service}.{method} {key}: {_truncate(decoded)}"
|
f"[←] {service}.{method} {key}: {_truncate(decoded)}"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
self.logger(
|
||||||
|
f"[debug] WUP 响应字段解码失败: {service}.{method}.{key}: {exc}"
|
||||||
|
)
|
||||||
|
|
||||||
if rsp_class is None:
|
if rsp_class is None:
|
||||||
return body
|
return body
|
||||||
@@ -794,13 +796,13 @@ class HuyaWssClient:
|
|||||||
if data and isinstance(data, bytes) and len(data) > 0:
|
if data and isinstance(data, bytes) and len(data) > 0:
|
||||||
try:
|
try:
|
||||||
ins = TafInputStream(data)
|
ins = TafInputStream(data)
|
||||||
tag, dtype = ins.peek_head()
|
_tag, dtype = ins.peek_head()
|
||||||
if dtype == TafType.STRUCT_BEGIN:
|
if dtype == TafType.STRUCT_BEGIN:
|
||||||
ins.read_head()
|
ins.read_head()
|
||||||
decoded = _decode_taf_struct(ins)
|
decoded = _decode_taf_struct(ins)
|
||||||
self.logger(f"[←] payOrderSubmitV5 {key}: {_truncate(decoded)}")
|
self.logger(f"[←] payOrderSubmitV5 {key}: {_truncate(decoded)}")
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
self.logger(f"[debug] WUP 支付响应字段解码失败: {key}: {exc}")
|
||||||
result = wup_resp.readStruct("tRsp", PayOrderRes)
|
result = wup_resp.readStruct("tRsp", PayOrderRes)
|
||||||
if result is None:
|
if result is None:
|
||||||
result = wup_resp.readStruct("tResp", PayOrderRes)
|
result = wup_resp.readStruct("tResp", PayOrderRes)
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ class WupResponse:
|
|||||||
_, vt = ins.read_head()
|
_, vt = ins.read_head()
|
||||||
val = _read_bytes_value(ins, vt)
|
val = _read_bytes_value(ins, vt)
|
||||||
self.newdata[key] = val
|
self.newdata[key] = val
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
print(f"[WupResponse] 解析 newdata 失败: {e}")
|
print(f"[WupResponse] 解析 newdata 失败: {e}")
|
||||||
|
|
||||||
def readStruct(self, key: str, struct_class=None):
|
def readStruct(self, key: str, struct_class=None):
|
||||||
@@ -236,13 +236,13 @@ class WupResponse:
|
|||||||
ins = TafInputStream(data)
|
ins = TafInputStream(data)
|
||||||
# newdata 里的结构体以 STRUCT_BEGIN 开头
|
# newdata 里的结构体以 STRUCT_BEGIN 开头
|
||||||
try:
|
try:
|
||||||
tag, dtype = ins.peek_head()
|
_tag, dtype = ins.peek_head()
|
||||||
if dtype == TafType.STRUCT_BEGIN:
|
if dtype == TafType.STRUCT_BEGIN:
|
||||||
ins.read_head() # 消费 STRUCT_BEGIN
|
ins.read_head() # 消费 STRUCT_BEGIN
|
||||||
obj = struct_class()
|
obj = struct_class()
|
||||||
obj.read_from(ins)
|
obj.read_from(ins)
|
||||||
return obj
|
return obj
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
print(f"[WupResponse] 解析 {struct_class.__name__} 失败: {e}")
|
print(f"[WupResponse] 解析 {struct_class.__name__} 失败: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import UTC, datetime
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@@ -98,7 +98,7 @@ def _parse_sms8_time(value: str) -> datetime | None:
|
|||||||
return None
|
return None
|
||||||
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S"):
|
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S"):
|
||||||
try:
|
try:
|
||||||
return datetime.strptime(text, fmt)
|
return datetime.strptime(text, fmt).replace(tzinfo=UTC)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ packages = ["core", "utils", "web"]
|
|||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
# FastAPI evaluates dependency metadata in route parameter defaults by design.
|
||||||
|
"web/backend/**/*.py" = ["B008"]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"pyright>=1.1.411",
|
"pyright>=1.1.411",
|
||||||
|
|||||||
@@ -225,6 +225,6 @@ def main() -> int:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
print(f"迁移失败:{exc}", file=sys.stderr)
|
print(f"迁移失败:{exc}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
Regular → Executable
+12
-8
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
@@ -49,6 +50,8 @@ from pyvm.protocol import (
|
|||||||
)
|
)
|
||||||
from pyvm.session import SessionState, load_session
|
from pyvm.session import SessionState, load_session
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
REPLAY = ROOT / "replay"
|
REPLAY = ROOT / "replay"
|
||||||
DEFAULT_APPID = PAY_APPID
|
DEFAULT_APPID = PAY_APPID
|
||||||
DEFAULT_SAVE_URL = f"https://api.unipay.qq.com/v1/r/{DEFAULT_APPID}/web_save"
|
DEFAULT_SAVE_URL = f"https://api.unipay.qq.com/v1/r/{DEFAULT_APPID}/web_save"
|
||||||
@@ -103,7 +106,8 @@ def _load_cap(path: Path) -> dict:
|
|||||||
d = attempt()
|
d = attempt()
|
||||||
if isinstance(d, dict) and "C" in d:
|
if isinstance(d, dict) and "C" in d:
|
||||||
return d
|
return d
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
|
logger.debug("deepCap 候选格式解析失败: %s", exc)
|
||||||
continue
|
continue
|
||||||
raise ValueError(f"无法解析 deepCap 文件: {path}(前 80 字符: {raw[:80]!r})")
|
raise ValueError(f"无法解析 deepCap 文件: {path}(前 80 字符: {raw[:80]!r})")
|
||||||
|
|
||||||
@@ -122,8 +126,8 @@ def parse_plaintext(path: str | Path) -> dict:
|
|||||||
d = json.loads(raw)
|
d = json.loads(raw)
|
||||||
if isinstance(d, dict):
|
if isinstance(d, dict):
|
||||||
return {str(k): str(v) for k, v in d.items()}
|
return {str(k): str(v) for k, v in d.items()}
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug("订单响应 JSON 解析失败: %s", exc)
|
||||||
fields: dict[str, str] = {}
|
fields: dict[str, str] = {}
|
||||||
for kv in raw.split("&"):
|
for kv in raw.split("&"):
|
||||||
k, _, v = kv.partition("=")
|
k, _, v = kv.partition("=")
|
||||||
@@ -249,7 +253,7 @@ def cmd_submit(args) -> int:
|
|||||||
return 0
|
return 0
|
||||||
print(f"❌ web_save ret:{ret}({js.get('err_code', '')})—— 见 case 踩坑记录")
|
print(f"❌ web_save ret:{ret}({js.get('err_code', '')})—— 见 case 踩坑记录")
|
||||||
return 1
|
return 1
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@@ -399,8 +403,8 @@ def cmd_mall_submit(args) -> int:
|
|||||||
js = json.loads(raw)
|
js = json.loads(raw)
|
||||||
if isinstance(js, dict):
|
if isinstance(js, dict):
|
||||||
ret = js.get("ret", js.get("result_code", js.get("code")))
|
ret = js.get("ret", js.get("result_code", js.get("code")))
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug("支付响应 JSON 解析失败: %s", exc)
|
||||||
ok = ret in (0, "0")
|
ok = ret in (0, "0")
|
||||||
if ok:
|
if ok:
|
||||||
_write_private_text(out, raw)
|
_write_private_text(out, raw)
|
||||||
@@ -999,8 +1003,8 @@ def cmd_mall_pay(args) -> int:
|
|||||||
print("\n ══ 微信扫码支付(终端二维码)══")
|
print("\n ══ 微信扫码支付(终端二维码)══")
|
||||||
try:
|
try:
|
||||||
qr.terminal(compact=False)
|
qr.terminal(compact=False)
|
||||||
except Exception: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
logger.debug("终端二维码输出失败: %s", exc)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
|
import random
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ def _ic(x):
|
|||||||
try:
|
try:
|
||||||
f = float(x)
|
f = float(x)
|
||||||
return 0 if math.isnan(f) else int(f)
|
return 0 if math.isnan(f) else int(f)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ def js_typeof(v):
|
|||||||
return "number"
|
return "number"
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
return "string"
|
return "string"
|
||||||
if isinstance(v, JSFunction) or isinstance(v, HostFunction):
|
if isinstance(v, (JSFunction, HostFunction)):
|
||||||
return "function"
|
return "function"
|
||||||
return "object"
|
return "object"
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ def js_truthy(v):
|
|||||||
def _nan_ok(x):
|
def _nan_ok(x):
|
||||||
try:
|
try:
|
||||||
return not (isinstance(x, float) and math.isnan(x))
|
return not (isinstance(x, float) and math.isnan(x))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ def js_num(v):
|
|||||||
return v
|
return v
|
||||||
try:
|
try:
|
||||||
return float(v)
|
return float(v)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return float("nan")
|
return float("nan")
|
||||||
|
|
||||||
|
|
||||||
@@ -226,12 +227,12 @@ def js_eq(a, b):
|
|||||||
if isinstance(a, (int, float)) and isinstance(b, str):
|
if isinstance(a, (int, float)) and isinstance(b, str):
|
||||||
try:
|
try:
|
||||||
return a == float(b)
|
return a == float(b)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
if isinstance(b, (int, float)) and isinstance(a, str):
|
if isinstance(b, (int, float)) and isinstance(a, str):
|
||||||
try:
|
try:
|
||||||
return float(a) == b
|
return float(a) == b
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
return a == b
|
return a == b
|
||||||
|
|
||||||
@@ -355,9 +356,7 @@ def js_set(obj, key, val):
|
|||||||
pass
|
pass
|
||||||
if isinstance(obj, str):
|
if isinstance(obj, str):
|
||||||
raise TypeError("Cannot assign to read only property")
|
raise TypeError("Cannot assign to read only property")
|
||||||
cur = getattr(
|
cur = getattr(__import__("pyvm.algorithm", fromlist=["x"]).VM, "_last_u", None)
|
||||||
__import__("pyvm.algorithm", fromlist=["x"]).VM, "_last_u", None
|
|
||||||
)
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"invalid set target obj={type(obj).__name__} key={key!r} val={type(val).__name__} u={cur}"
|
f"invalid set target obj={type(obj).__name__} key={key!r} val={type(val).__name__} u={cur}"
|
||||||
)
|
)
|
||||||
@@ -736,7 +735,7 @@ def h_parsefloat(this, s):
|
|||||||
def h_isnan(this, x):
|
def h_isnan(this, x):
|
||||||
try:
|
try:
|
||||||
return math.isnan(float(x))
|
return math.isnan(float(x))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -896,7 +895,7 @@ class VM:
|
|||||||
for x in v:
|
for x in v:
|
||||||
try:
|
try:
|
||||||
parts.append("%02x" % (int(x) & 255))
|
parts.append("%02x" % (int(x) & 255))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
parts.append("??")
|
parts.append("??")
|
||||||
self.out_hex = "".join(parts)
|
self.out_hex = "".join(parts)
|
||||||
# 仅供离线排查 webSave 输出缓冲何时变化,生产路径默认不启用。
|
# 仅供离线排查 webSave 输出缓冲何时变化,生产路径默认不启用。
|
||||||
@@ -1527,9 +1526,7 @@ class VM:
|
|||||||
if not (
|
if not (
|
||||||
isinstance(fn, (JSFunction, HostFunction)) or callable(fn)
|
isinstance(fn, (JSFunction, HostFunction)) or callable(fn)
|
||||||
):
|
):
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"op97 non-function at u={u} fn={fn!r}")
|
||||||
"op97 non-function at u=%s fn=%r" % (u, fn)
|
|
||||||
)
|
|
||||||
js_set(C, dest, js_apply(fn, thisv, f))
|
js_set(C, dest, js_apply(fn, thisv, f))
|
||||||
elif op == 98:
|
elif op == 98:
|
||||||
a = o[u + 1]
|
a = o[u + 1]
|
||||||
@@ -1647,10 +1644,10 @@ class VM:
|
|||||||
u += 1
|
u += 1
|
||||||
js_set(C, a, None)
|
js_set(C, a, None)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("unknown opcode %s at %s" % (op, u))
|
raise RuntimeError(f"unknown opcode {op} at {u}")
|
||||||
except _VMThrow as e:
|
except _VMThrow as e:
|
||||||
if not d:
|
if not d:
|
||||||
raise RuntimeError("VM uncaught throw: %s" % (e.value,))
|
raise RuntimeError(f"VM uncaught throw: {e.value}")
|
||||||
l = e.value
|
l = e.value
|
||||||
u = d.pop()
|
u = d.pop()
|
||||||
continue
|
continue
|
||||||
@@ -1658,9 +1655,8 @@ class VM:
|
|||||||
if not d:
|
if not d:
|
||||||
raise
|
raise
|
||||||
if not isinstance(d, list):
|
if not isinstance(d, list):
|
||||||
raise RuntimeError(
|
raise TypeError(
|
||||||
"d corrupted: %r (type %s) at trace %s"
|
f"d corrupted: {d!r} (type {type(d).__name__}) at trace {self._trace[-3:]}"
|
||||||
% (d, type(d).__name__, self._trace[-3:])
|
|
||||||
)
|
)
|
||||||
l = e
|
l = e
|
||||||
u = d.pop()
|
u = d.pop()
|
||||||
@@ -1746,14 +1742,14 @@ def decode_d(v):
|
|||||||
vs = p[ci + 1 :]
|
vs = p[ci + 1 :]
|
||||||
try:
|
try:
|
||||||
obj.set(key, decode_d(json.loads(vs)))
|
obj.set(key, decode_d(json.loads(vs)))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
obj.set(key, decode_d(vs))
|
obj.set(key, decode_d(vs))
|
||||||
return obj
|
return obj
|
||||||
if v.lstrip("-").isdigit():
|
if v.lstrip("-").isdigit():
|
||||||
return int(v)
|
return int(v)
|
||||||
try:
|
try:
|
||||||
return float(v)
|
return float(v)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -72,8 +72,12 @@ class MallSession:
|
|||||||
f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}"
|
f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}"
|
||||||
)
|
)
|
||||||
mid = self.transform_input[10]
|
mid = self.transform_input[10]
|
||||||
if isinstance(mid, list) and mid and isinstance(mid[0], list):
|
if (
|
||||||
if len(mid[0]) != 624:
|
isinstance(mid, list)
|
||||||
|
and mid
|
||||||
|
and isinstance(mid[0], list)
|
||||||
|
and len(mid[0]) != 624
|
||||||
|
):
|
||||||
raise ValueError(f"624B 中间态长度 != 624: {len(mid[0])}")
|
raise ValueError(f"624B 中间态长度 != 624: {len(mid[0])}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ def get_official_orders(cookies: dict[str, str], count: int = 20) -> dict[str, A
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise RuntimeError("订单状态查询返回非 JSON") from exc
|
raise RuntimeError("订单状态查询返回非 JSON") from exc
|
||||||
if not isinstance(document, dict):
|
if not isinstance(document, dict):
|
||||||
raise RuntimeError("订单状态查询响应格式异常")
|
raise TypeError("订单状态查询响应格式异常")
|
||||||
if document.get("ret_code") not in (None, 0, "0"):
|
if document.get("ret_code") not in (None, 0, "0"):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"订单状态查询失败: {document.get('ret_code')} {document.get('ret_msg', '')}"
|
f"订单状态查询失败: {document.get('ret_code')} {document.get('ret_msg', '')}"
|
||||||
)
|
)
|
||||||
if not isinstance(document.get("list", []), list):
|
if not isinstance(document.get("list", []), list):
|
||||||
raise RuntimeError("订单状态查询响应缺少 list")
|
raise TypeError("订单状态查询响应缺少 list")
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ JS 语义辅助复用 algorithm.py(_ic/i32/js_add/js_index/JSObject/JSFunction
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from .algorithm import (
|
from .algorithm import (
|
||||||
UNDEF,
|
UNDEF,
|
||||||
@@ -20,6 +22,7 @@ from .algorithm import (
|
|||||||
JSDate,
|
JSDate,
|
||||||
JSFunction,
|
JSFunction,
|
||||||
JSObject,
|
JSObject,
|
||||||
|
Window,
|
||||||
_ic,
|
_ic,
|
||||||
h_decodeuri,
|
h_decodeuri,
|
||||||
h_decodeuricomponent,
|
h_decodeuricomponent,
|
||||||
@@ -57,6 +60,8 @@ from .algorithm import (
|
|||||||
ushr,
|
ushr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
REPLAY = Path(__file__).resolve().parent.parent / "replay"
|
||||||
|
|
||||||
__all__ = ["REPLAY", "PagedooVM", "run_frame"]
|
__all__ = ["REPLAY", "PagedooVM", "run_frame"]
|
||||||
|
|
||||||
|
|
||||||
@@ -66,28 +71,28 @@ __all__ = ["REPLAY", "PagedooVM", "run_frame"]
|
|||||||
def _cmp_lt(a, b):
|
def _cmp_lt(a, b):
|
||||||
try:
|
try:
|
||||||
return a < b
|
return a < b
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _cmp_le(a, b):
|
def _cmp_le(a, b):
|
||||||
try:
|
try:
|
||||||
return a <= b
|
return a <= b
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _cmp_gt(a, b):
|
def _cmp_gt(a, b):
|
||||||
try:
|
try:
|
||||||
return a > b
|
return a > b
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _cmp_ge(a, b):
|
def _cmp_ge(a, b):
|
||||||
try:
|
try:
|
||||||
return a >= b
|
return a >= b
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -122,8 +127,8 @@ def h_arr_slice(this, a=None, b=None):
|
|||||||
a = 0
|
a = 0
|
||||||
if b is None or b is UNDEF:
|
if b is None or b is UNDEF:
|
||||||
b = n
|
b = n
|
||||||
a = int(a) if a == a else 0
|
a = int(a) if not isinstance(a, float) or not math.isnan(a) else 0
|
||||||
b = int(b) if b == b else n
|
b = int(b) if not isinstance(b, float) or not math.isnan(b) else n
|
||||||
if a < 0:
|
if a < 0:
|
||||||
a = max(0, n + a)
|
a = max(0, n + a)
|
||||||
if b < 0:
|
if b < 0:
|
||||||
@@ -303,8 +308,7 @@ def _pg_index(obj, key):
|
|||||||
return HostFunction(
|
return HostFunction(
|
||||||
lambda this: this.t if isinstance(this, JSDate) else obj.t, "valueOf"
|
lambda this: this.t if isinstance(this, JSDate) else obj.t, "valueOf"
|
||||||
)
|
)
|
||||||
if isinstance(obj, str):
|
if isinstance(obj, str) and isinstance(key, str):
|
||||||
if isinstance(key, str):
|
|
||||||
if key == "length":
|
if key == "length":
|
||||||
return len(obj)
|
return len(obj)
|
||||||
if key == "charCodeAt":
|
if key == "charCodeAt":
|
||||||
@@ -486,15 +490,7 @@ class PagedooVM:
|
|||||||
and len(self._host_log) < 2000
|
and len(self._host_log) < 2000
|
||||||
):
|
):
|
||||||
# 记录调用目标(简化)
|
# 记录调用目标(简化)
|
||||||
_tgt = (
|
_tgt = o[u + 2]
|
||||||
o[u + 2]
|
|
||||||
if op in (4, 11, 44, 50)
|
|
||||||
else (
|
|
||||||
o[u + 2]
|
|
||||||
if op in (0, 18, 23, 26, 43, 48, 84, 107)
|
|
||||||
else o[u + 2]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
_tv = C[_tgt] if 0 <= _tgt < len(C) else UNDEF
|
_tv = C[_tgt] if 0 <= _tgt < len(C) else UNDEF
|
||||||
_tr = (
|
_tr = (
|
||||||
@@ -505,7 +501,7 @@ class PagedooVM:
|
|||||||
else repr(_tv)[:30]
|
else repr(_tv)[:30]
|
||||||
)
|
)
|
||||||
self._host_log.append((op, u, _tr))
|
self._host_log.append((op, u, _tr))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._host_log.append((op, u, "?"))
|
self._host_log.append((op, u, "?"))
|
||||||
if (
|
if (
|
||||||
getattr(self, "_trace", None) is not None
|
getattr(self, "_trace", None) is not None
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def validate_goods_materials(
|
|||||||
def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
|
def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
|
||||||
"""校验 mall 固定槽模板;动态槽仍由当前会话的 GetPayToken 填充。"""
|
"""校验 mall 固定槽模板;动态槽仍由当前会话的 GetPayToken 填充。"""
|
||||||
if not isinstance(transform_fixed, dict):
|
if not isinstance(transform_fixed, dict):
|
||||||
raise ValueError("mall transform-fixed 必须是对象")
|
raise TypeError("mall transform-fixed 必须是对象")
|
||||||
required = {
|
required = {
|
||||||
str(index) for index in (1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17)
|
str(index) for index in (1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17)
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
|
|||||||
raise ValueError(f"mall transform-fixed 缺少槽: {', '.join(missing)}")
|
raise ValueError(f"mall transform-fixed 缺少槽: {', '.join(missing)}")
|
||||||
for index in required:
|
for index in required:
|
||||||
if not isinstance(transform_fixed[index], list):
|
if not isinstance(transform_fixed[index], list):
|
||||||
raise ValueError(f"mall transform-fixed 槽 {index} 不是数组")
|
raise TypeError(f"mall transform-fixed 槽 {index} 不是数组")
|
||||||
|
|
||||||
|
|
||||||
def goods_material_diagnostics(
|
def goods_material_diagnostics(
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
Regular → Executable
+5
-1
@@ -81,7 +81,10 @@ def _safe_log(job: dict, line: str) -> None:
|
|||||||
clean = re.sub(r"HOLD_[A-Za-z0-9_-]+", "[订单已隐藏]", clean)
|
clean = re.sub(r"HOLD_[A-Za-z0-9_-]+", "[订单已隐藏]", clean)
|
||||||
clean = re.sub(r"(订单\s*[::]\s*)\S+", r"\1[订单已隐藏]", clean)
|
clean = re.sub(r"(订单\s*[::]\s*)\S+", r"\1[订单已隐藏]", clean)
|
||||||
clean = re.sub(
|
clean = re.sub(
|
||||||
r"(?:token|openid|openkey|cookie)=\S+", "[敏感字段已隐藏]", clean, flags=re.IGNORECASE
|
r"(?:token|openid|openkey|cookie)=\S+",
|
||||||
|
"[敏感字段已隐藏]",
|
||||||
|
clean,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
)
|
)
|
||||||
clean = re.sub(
|
clean = re.sub(
|
||||||
r"(?:pay_token|web_token|anti_token|session_id|sessionid)\s*[:= ]\s*\S+",
|
r"(?:pay_token|web_token|anti_token|session_id|sessionid)\s*[:= ]\s*\S+",
|
||||||
@@ -282,6 +285,7 @@ def _check_payment_once(job_id: str) -> int:
|
|||||||
text=True,
|
text=True,
|
||||||
env=environment,
|
env=environment,
|
||||||
timeout=90,
|
timeout=90,
|
||||||
|
check=False,
|
||||||
)
|
)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
_safe_log(
|
_safe_log(
|
||||||
|
|||||||
@@ -175,18 +175,21 @@ class TestHuyaAppLogin:
|
|||||||
side_effect=DfpRegistrationError("注册链超时"),
|
side_effect=DfpRegistrationError("注册链超时"),
|
||||||
) as m_reg,
|
) as m_reg,
|
||||||
patch("core.huya.app_login.requests.post") as m_post,
|
patch("core.huya.app_login.requests.post") as m_post,
|
||||||
|
pytest.raises(HuyaAppLoginError),
|
||||||
):
|
):
|
||||||
with pytest.raises(HuyaAppLoginError):
|
|
||||||
wup_password_login_raw("300023887", "pw")
|
wup_password_login_raw("300023887", "pw")
|
||||||
m_reg.assert_called_once()
|
m_reg.assert_called_once()
|
||||||
m_post.assert_not_called()
|
m_post.assert_not_called()
|
||||||
|
|
||||||
def test_login_cred_flow_registration_failure_is_explicit(self):
|
def test_login_cred_flow_registration_failure_is_explicit(self):
|
||||||
"""login_cred_with_flow 注册失败同样包装为 HuyaAppLoginError 显式失败。"""
|
"""login_cred_with_flow 注册失败同样包装为 HuyaAppLoginError 显式失败。"""
|
||||||
with patch(
|
with (
|
||||||
|
patch(
|
||||||
"core.huya.app_login.register_device",
|
"core.huya.app_login.register_device",
|
||||||
side_effect=DfpRegistrationError("注册链 HTTP 500"),
|
side_effect=DfpRegistrationError("注册链 HTTP 500"),
|
||||||
), pytest.raises(HuyaAppLoginError, match="注册失败"):
|
),
|
||||||
|
pytest.raises(HuyaAppLoginError, match="注册失败"),
|
||||||
|
):
|
||||||
login_cred_with_flow("300023887", "pw")
|
login_cred_with_flow("300023887", "pw")
|
||||||
|
|
||||||
def test_router_functions(self):
|
def test_router_functions(self):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
import tempfile
|
import tempfile
|
||||||
from datetime import datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from utils.logger import (
|
from utils.logger import (
|
||||||
@@ -40,7 +40,8 @@ class TestLogger:
|
|||||||
|
|
||||||
archives = list(Path(tmpdir).glob("app-2026-08-28.log.*.gz"))
|
archives = list(Path(tmpdir).glob("app-2026-08-28.log.*.gz"))
|
||||||
assert len(archives) == 1
|
assert len(archives) == 1
|
||||||
archived_content = gzip.open(archives[0], "rt", encoding="utf-8").read()
|
with gzip.open(archives[0], "rt", encoding="utf-8") as archive:
|
||||||
|
archived_content = archive.read()
|
||||||
current_content = log_path.read_text(encoding="utf-8")
|
current_content = log_path.read_text(encoding="utf-8")
|
||||||
combined = archived_content + current_content
|
combined = archived_content + current_content
|
||||||
assert "super-secret" not in combined
|
assert "super-secret" not in combined
|
||||||
@@ -50,7 +51,7 @@ class TestLogger:
|
|||||||
|
|
||||||
def test_daily_log_switches_to_a_new_dated_file(self):
|
def test_daily_log_switches_to_a_new_dated_file(self):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
today = datetime.now().date()
|
today = datetime.now(UTC).date()
|
||||||
old_path = (
|
old_path = (
|
||||||
Path(tmpdir) / f"app-{(today - timedelta(days=1)).isoformat()}.log"
|
Path(tmpdir) / f"app-{(today - timedelta(days=1)).isoformat()}.log"
|
||||||
)
|
)
|
||||||
|
|||||||
+11
-9
@@ -8,7 +8,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import UTC, datetime, timedelta
|
||||||
from logging.handlers import BaseRotatingHandler
|
from logging.handlers import BaseRotatingHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -35,7 +35,9 @@ def _parse_positive_int(value: str | None, default: int) -> int:
|
|||||||
|
|
||||||
def _parse_size(value: str | None, default: int = _DEFAULT_ROTATION_SIZE) -> int:
|
def _parse_size(value: str | None, default: int = _DEFAULT_ROTATION_SIZE) -> int:
|
||||||
"""解析 50M、1GiB 等易读大小;非法值保持安全默认值。"""
|
"""解析 50M、1GiB 等易读大小;非法值保持安全默认值。"""
|
||||||
matched = re.fullmatch(r"\s*(\d+)\s*([kmgt]?i?b?)?\s*", str(value or ""), re.IGNORECASE)
|
matched = re.fullmatch(
|
||||||
|
r"\s*(\d+)\s*([kmgt]?i?b?)?\s*", str(value or ""), re.IGNORECASE
|
||||||
|
)
|
||||||
if not matched:
|
if not matched:
|
||||||
return default
|
return default
|
||||||
amount = int(matched.group(1))
|
amount = int(matched.group(1))
|
||||||
@@ -101,7 +103,7 @@ class _SizeAndDayRotatingFileHandler(BaseRotatingHandler):
|
|||||||
super().__init__(str(filename), "a", encoding="utf-8", delay=True)
|
super().__init__(str(filename), "a", encoding="utf-8", delay=True)
|
||||||
self.max_bytes = max_bytes
|
self.max_bytes = max_bytes
|
||||||
self.retention_days = retention_days
|
self.retention_days = retention_days
|
||||||
self._active_day = datetime.now().date()
|
self._active_day = datetime.now(UTC).date()
|
||||||
path = Path(filename)
|
path = Path(filename)
|
||||||
self._log_dir = path.parent
|
self._log_dir = path.parent
|
||||||
self._suffix = path.suffix
|
self._suffix = path.suffix
|
||||||
@@ -113,7 +115,7 @@ class _SizeAndDayRotatingFileHandler(BaseRotatingHandler):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def shouldRollover(self, record: logging.LogRecord) -> bool:
|
def shouldRollover(self, record: logging.LogRecord) -> bool:
|
||||||
if datetime.now().date() != self._active_day:
|
if datetime.now(UTC).date() != self._active_day:
|
||||||
return True
|
return True
|
||||||
if self.stream is None:
|
if self.stream is None:
|
||||||
self.stream = self._open()
|
self.stream = self._open()
|
||||||
@@ -126,7 +128,7 @@ class _SizeAndDayRotatingFileHandler(BaseRotatingHandler):
|
|||||||
self.stream.close()
|
self.stream.close()
|
||||||
self.stream = None
|
self.stream = None
|
||||||
source = Path(self.baseFilename)
|
source = Path(self.baseFilename)
|
||||||
current_day = datetime.now().date()
|
current_day = datetime.now(UTC).date()
|
||||||
if current_day != self._active_day:
|
if current_day != self._active_day:
|
||||||
# 每日文件本身已带日期,跨日时直接切换到新文件,无需再移动旧文件。
|
# 每日文件本身已带日期,跨日时直接切换到新文件,无需再移动旧文件。
|
||||||
self.baseFilename = os.fspath(self._path_for_day(current_day).resolve())
|
self.baseFilename = os.fspath(self._path_for_day(current_day).resolve())
|
||||||
@@ -134,7 +136,7 @@ class _SizeAndDayRotatingFileHandler(BaseRotatingHandler):
|
|||||||
self._delete_expired_archives()
|
self._delete_expired_archives()
|
||||||
return
|
return
|
||||||
if source.exists() and source.stat().st_size:
|
if source.exists() and source.stat().st_size:
|
||||||
stamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
stamp = datetime.now(UTC).strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
archive = source.with_name(f"{source.name}.{stamp}.{os.getpid()}.gz")
|
archive = source.with_name(f"{source.name}.{stamp}.{os.getpid()}.gz")
|
||||||
sequence = 1
|
sequence = 1
|
||||||
while archive.exists():
|
while archive.exists():
|
||||||
@@ -148,10 +150,10 @@ class _SizeAndDayRotatingFileHandler(BaseRotatingHandler):
|
|||||||
self._delete_expired_archives()
|
self._delete_expired_archives()
|
||||||
|
|
||||||
def _delete_expired_archives(self) -> None:
|
def _delete_expired_archives(self) -> None:
|
||||||
cutoff = datetime.now() - timedelta(days=self.retention_days)
|
cutoff = datetime.now(UTC) - timedelta(days=self.retention_days)
|
||||||
for archive in self._log_dir.glob(f"{self._filename_prefix}-*.log*.gz"):
|
for archive in self._log_dir.glob(f"{self._filename_prefix}-*.log*.gz"):
|
||||||
try:
|
try:
|
||||||
if datetime.fromtimestamp(archive.stat().st_mtime) < cutoff:
|
if datetime.fromtimestamp(archive.stat().st_mtime, UTC) < cutoff:
|
||||||
archive.unlink()
|
archive.unlink()
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
@@ -226,7 +228,7 @@ def setup_logger(
|
|||||||
if log_file:
|
if log_file:
|
||||||
file_path = Path(log_file).expanduser()
|
file_path = Path(log_file).expanduser()
|
||||||
elif log_dir:
|
elif log_dir:
|
||||||
file_path = Path(log_dir).expanduser() / f"app-{datetime.now():%Y-%m-%d}.log"
|
file_path = Path(log_dir).expanduser() / f"app-{datetime.now(UTC):%Y-%m-%d}.log"
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def decrypt_value(value: str | None) -> str | None:
|
|||||||
for candidate in _key_candidates():
|
for candidate in _key_candidates():
|
||||||
try:
|
try:
|
||||||
return _decrypt_with_candidate(value, candidate)
|
return _decrypt_with_candidate(value, candidate)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_error = exc
|
last_error = exc
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
||||||
@@ -124,7 +124,7 @@ def decrypt_value_with_key_name(value: str) -> tuple[str, str]:
|
|||||||
for candidate in _key_candidates():
|
for candidate in _key_candidates():
|
||||||
try:
|
try:
|
||||||
return _decrypt_with_candidate(value, candidate), candidate.name
|
return _decrypt_with_candidate(value, candidate), candidate.name
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
last_error = exc
|
last_error = exc
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
"敏感字段解密失败,请确认 APP_ENCRYPTION_KEY 是否正确"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""FastAPI 依赖注入"""
|
"""FastAPI 依赖注入"""
|
||||||
|
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException, Request, WebSocket, status
|
from fastapi import Depends, HTTPException, Request, WebSocket, status
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from jose import JWTError
|
from jose import JWTError
|
||||||
|
|||||||
@@ -483,7 +483,7 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
results.append(future.result())
|
results.append(future.result())
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
task = futures[future]
|
task = futures[future]
|
||||||
results.append(
|
results.append(
|
||||||
{
|
{
|
||||||
@@ -623,7 +623,7 @@ def _start_relogin_tasks(
|
|||||||
def run_relogin_batch():
|
def run_relogin_batch():
|
||||||
try:
|
try:
|
||||||
runner.run()
|
runner.run()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
runner._push_log("error", f"批量重新登录异常: {exc}")
|
runner._push_log("error", f"批量重新登录异常: {exc}")
|
||||||
message = f"重新登录异常: {exc}(旧 Cookie 已保留)"
|
message = f"重新登录异常: {exc}(旧 Cookie 已保留)"
|
||||||
for task_id in task_ids:
|
for task_id in task_ids:
|
||||||
|
|||||||
@@ -852,12 +852,14 @@ def stop_auto_register_batch(
|
|||||||
)
|
)
|
||||||
def retry_auto_register_batch(
|
def retry_auto_register_batch(
|
||||||
batch_id: str,
|
batch_id: str,
|
||||||
req: HuyaAutoRegisterRetryRequest = HuyaAutoRegisterRetryRequest.model_construct(),
|
req: HuyaAutoRegisterRetryRequest | None = None,
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
current: User = Depends(get_current_user),
|
current: User = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
"""继续批次:默认从停止处往下跑(跳过成功与已失败);可传 mode 改行为。"""
|
"""继续批次:默认从停止处往下跑(跳过成功与已失败);可传 mode 改行为。"""
|
||||||
_require_huya_perm(current, "huya:import")
|
_require_huya_perm(current, "huya:import")
|
||||||
|
if req is None:
|
||||||
|
req = HuyaAutoRegisterRetryRequest.model_construct()
|
||||||
use_proxy = req.use_proxy
|
use_proxy = req.use_proxy
|
||||||
# 先看历史批次是否用过代理
|
# 先看历史批次是否用过代理
|
||||||
snapshot = huya_register_registry.get_snapshot(batch_id)
|
snapshot = huya_register_registry.get_snapshot(batch_id)
|
||||||
@@ -1075,7 +1077,7 @@ def password_login_selected_accounts(
|
|||||||
"account": _account_out(account, include_cookie=include_cookie),
|
"account": _account_out(account, include_cookie=include_cookie),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
account.status = "login_failed"
|
account.status = "login_failed"
|
||||||
account.updated_at = datetime.now(UTC)
|
account.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -1239,7 +1241,7 @@ def app_password_login_selected_accounts(
|
|||||||
"account": _account_out(account, include_cookie=include_cookie),
|
"account": _account_out(account, include_cookie=include_cookie),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
account.status = "login_failed"
|
account.status = "login_failed"
|
||||||
account.updated_at = datetime.now(UTC)
|
account.updated_at = datetime.now(UTC)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|||||||
@@ -58,8 +58,10 @@ async def create_batch(
|
|||||||
acc = db.query(Account).filter(Account.id == aid).first()
|
acc = db.query(Account).filter(Account.id == aid).first()
|
||||||
if not acc:
|
if not acc:
|
||||||
continue
|
continue
|
||||||
if not user_has_permission(current, "login:view_all"):
|
if (
|
||||||
if acc.assigned_to != current.id:
|
not user_has_permission(current, "login:view_all")
|
||||||
|
and acc.assigned_to != current.id
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
valid_ids.append(aid)
|
valid_ids.append(aid)
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def hash_password(password: str) -> str:
|
|||||||
def verify_password(plain: str, hashed: str) -> bool:
|
def verify_password(plain: str, hashed: str) -> bool:
|
||||||
try:
|
try:
|
||||||
return bcrypt.checkpw(plain.encode("utf-8")[:72], hashed.encode("utf-8"))
|
return bcrypt.checkpw(plain.encode("utf-8")[:72], hashed.encode("utf-8"))
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ class AccountCheckRunner:
|
|||||||
stop_event=self._stop,
|
stop_event=self._stop,
|
||||||
api_strategy=WgapiLoginAPI(),
|
api_strategy=WgapiLoginAPI(),
|
||||||
).check_account()
|
).check_account()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._set_item(
|
self._set_item(
|
||||||
index, status="error", message=f"检测异常: {exc}", finished_at=_now()
|
index, status="error", message=f"检测异常: {exc}", finished_at=_now()
|
||||||
)
|
)
|
||||||
@@ -319,7 +319,7 @@ class AccountCheckRunner:
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
items = list(self.batch.items)
|
items = list(self.batch.items)
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||||
for status in EXPORT_STATUS_ORDER:
|
for status in EXPORT_STATUS_ORDER:
|
||||||
@@ -367,7 +367,7 @@ class AccountCheckRunner:
|
|||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._set_batch(
|
self._set_batch(
|
||||||
status="error", message=f"批次执行异常: {exc}", finished_at=_now()
|
status="error", message=f"批次执行异常: {exc}", finished_at=_now()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def check_douyu_cookie(cookie: str) -> dict:
|
|||||||
if isinstance(fish_data, dict)
|
if isinstance(fish_data, dict)
|
||||||
else "响应异常"
|
else "响应异常"
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
fish_msg = f"请求失败: {exc}"
|
fish_msg = f"请求失败: {exc}"
|
||||||
|
|
||||||
level_ok = False
|
level_ok = False
|
||||||
@@ -83,7 +83,7 @@ def check_douyu_cookie(cookie: str) -> dict:
|
|||||||
if isinstance(level_data, dict)
|
if isinstance(level_data, dict)
|
||||||
else "响应异常"
|
else "响应异常"
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
level_msg = f"请求失败: {exc}"
|
level_msg = f"请求失败: {exc}"
|
||||||
|
|
||||||
valid = fish_ok and level_ok
|
valid = fish_ok and level_ok
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class DouyuBatchRunner(
|
|||||||
if "task" in locals() and task:
|
if "task" in locals() and task:
|
||||||
self._mark_task(worker_db, task, "failed", str(exc))
|
self._mark_task(worker_db, task, "failed", str(exc))
|
||||||
self._push_log("warning", f"斗鱼任务失败: {exc}")
|
self._push_log("warning", f"斗鱼任务失败: {exc}")
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
if "task" in locals() and task:
|
if "task" in locals() and task:
|
||||||
self._mark_task(worker_db, task, "error", str(exc))
|
self._mark_task(worker_db, task, "error", str(exc))
|
||||||
self._push_log("error", f"斗鱼任务异常: {exc}")
|
self._push_log("error", f"斗鱼任务异常: {exc}")
|
||||||
@@ -177,7 +177,7 @@ class DouyuBatchRunner(
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("error", f"Worker 异常: {exc}")
|
self._push_log("error", f"Worker 异常: {exc}")
|
||||||
|
|
||||||
if self._stop.is_set():
|
if self._stop.is_set():
|
||||||
|
|||||||
@@ -386,9 +386,9 @@ class BindMixin:
|
|||||||
]
|
]
|
||||||
if not bound:
|
if not bound:
|
||||||
return None
|
return None
|
||||||
return sorted(
|
return min(
|
||||||
bound, key=lambda info: prefer.index(str(info.get("act_alias") or ""))
|
bound, key=lambda info: prefer.index(str(info.get("act_alias") or ""))
|
||||||
)[0]
|
)
|
||||||
|
|
||||||
def _pick_baseline_bind_info(
|
def _pick_baseline_bind_info(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ class DouyuBatchRunnerCore:
|
|||||||
if proxy_url:
|
if proxy_url:
|
||||||
return {"http": proxy_url, "https": proxy_url}
|
return {"http": proxy_url, "https": proxy_url}
|
||||||
self._push_log("warning", "代理 API 未返回可用代理, 本任务降级直连")
|
self._push_log("warning", "代理 API 未返回可用代理, 本任务降级直连")
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"取代理失败, 本任务降级直连: {exc}")
|
self._push_log("warning", f"取代理失败, 本任务降级直连: {exc}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ class DouyuBatchRunnerCore:
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
payload = douyu_task_payload(task)
|
payload = douyu_task_payload(task)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
logger.exception("[douyu] 推送任务状态失败: task_id={}", task.id)
|
logger.exception("[douyu] 推送任务状态失败: task_id={}", task.id)
|
||||||
return
|
return
|
||||||
event = {
|
event = {
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ class DonateMixin:
|
|||||||
)
|
)
|
||||||
baseline_points = baseline["esports_points"]
|
baseline_points = baseline["esports_points"]
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"赠送{gift_name}前刷新电竞积分失败: {exc}")
|
self._push_log("warning", f"赠送{gift_name}前刷新电竞积分失败: {exc}")
|
||||||
|
|
||||||
result = client.donate_esports_gift(
|
result = client.donate_esports_gift(
|
||||||
@@ -158,7 +158,7 @@ class DonateMixin:
|
|||||||
refresh_errors = []
|
refresh_errors = []
|
||||||
try:
|
try:
|
||||||
result.update(self._refresh_account_gold_balance(client, account))
|
result.update(self._refresh_account_gold_balance(client, account))
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
refresh_errors.append(f"鱼翅余额: {exc}")
|
refresh_errors.append(f"鱼翅余额: {exc}")
|
||||||
try:
|
try:
|
||||||
points_result = self._refresh_esports_handbook(
|
points_result = self._refresh_esports_handbook(
|
||||||
@@ -171,7 +171,7 @@ class DonateMixin:
|
|||||||
and points_result["esports_points"] is not None
|
and points_result["esports_points"] is not None
|
||||||
and points_result["esports_points"] != baseline_points
|
and points_result["esports_points"] != baseline_points
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
refresh_errors.append(f"电竞积分: {exc}")
|
refresh_errors.append(f"电竞积分: {exc}")
|
||||||
if refresh_errors:
|
if refresh_errors:
|
||||||
result["refresh_errors"] = refresh_errors
|
result["refresh_errors"] = refresh_errors
|
||||||
@@ -240,7 +240,7 @@ class DonateMixin:
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
baseline_points = baseline_result["points"]
|
baseline_points = baseline_result["points"]
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"赠送精英令前刷新积分失败: {exc}")
|
self._push_log("warning", f"赠送精英令前刷新积分失败: {exc}")
|
||||||
result = client.donate_elite_gift(
|
result = client.donate_elite_gift(
|
||||||
gift_count=gift_count,
|
gift_count=gift_count,
|
||||||
@@ -252,7 +252,7 @@ class DonateMixin:
|
|||||||
refresh_errors = []
|
refresh_errors = []
|
||||||
try:
|
try:
|
||||||
result.update(self._refresh_account_gold_balance(client, account))
|
result.update(self._refresh_account_gold_balance(client, account))
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
refresh_errors.append(f"鱼翅余额: {exc}")
|
refresh_errors.append(f"鱼翅余额: {exc}")
|
||||||
try:
|
try:
|
||||||
result.update(
|
result.update(
|
||||||
@@ -268,7 +268,7 @@ class DonateMixin:
|
|||||||
gift_count,
|
gift_count,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
refresh_errors.append(f"积分: {exc}")
|
refresh_errors.append(f"积分: {exc}")
|
||||||
if refresh_errors:
|
if refresh_errors:
|
||||||
result["refresh_errors"] = refresh_errors
|
result["refresh_errors"] = refresh_errors
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class GoldMixin:
|
|||||||
f"鱼翅支付码已生成,等待到账(当前鱼翅 {last_gold if last_gold is not None else '-'})",
|
f"鱼翅支付码已生成,等待到账(当前鱼翅 {last_gold if last_gold is not None else '-'})",
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
poll_count += 1
|
poll_count += 1
|
||||||
result["payment_poll_count"] = poll_count
|
result["payment_poll_count"] = poll_count
|
||||||
result["payment_poll_error"] = str(exc)
|
result["payment_poll_error"] = str(exc)
|
||||||
@@ -260,7 +260,7 @@ class GoldMixin:
|
|||||||
baseline = self._refresh_account_gold_balance(client, account)
|
baseline = self._refresh_account_gold_balance(client, account)
|
||||||
baseline_gold = baseline["gold_balance"]
|
baseline_gold = baseline["gold_balance"]
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"生成鱼翅码前刷新余额失败: {exc}")
|
self._push_log("warning", f"生成鱼翅码前刷新余额失败: {exc}")
|
||||||
result = client.create_gold_qr(
|
result = client.create_gold_qr(
|
||||||
amount=amount, pay_type=int(config["gold_pay_type"])
|
amount=amount, pay_type=int(config["gold_pay_type"])
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ class GoodsMixin:
|
|||||||
result.update(
|
result.update(
|
||||||
self._refresh_account_points(client, account, cookie, ctn=ctn)
|
self._refresh_account_points(client, account, cookie, ctn=ctn)
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"支付锁单后刷新积分失败: {exc}")
|
self._push_log("warning", f"支付锁单后刷新积分失败: {exc}")
|
||||||
self._mark_task(
|
self._mark_task(
|
||||||
db,
|
db,
|
||||||
@@ -493,7 +493,7 @@ class GoodsMixin:
|
|||||||
points_refresh = self._refresh_account_points(
|
points_refresh = self._refresh_account_points(
|
||||||
client, account, cookie, ctn=ctn
|
client, account, cookie, ctn=ctn
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"兑换后刷新积分失败: {exc}")
|
self._push_log("warning", f"兑换后刷新积分失败: {exc}")
|
||||||
self._mark_task(
|
self._mark_task(
|
||||||
db,
|
db,
|
||||||
@@ -549,7 +549,7 @@ class GoodsMixin:
|
|||||||
)
|
)
|
||||||
baseline_points = baseline["esports_points"]
|
baseline_points = baseline["esports_points"]
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"兑换电竞皮肤前刷新积分失败: {exc}")
|
self._push_log("warning", f"兑换电竞皮肤前刷新积分失败: {exc}")
|
||||||
|
|
||||||
result = client.exchange_esports_goods(
|
result = client.exchange_esports_goods(
|
||||||
@@ -571,7 +571,7 @@ class GoodsMixin:
|
|||||||
)
|
)
|
||||||
result.update(points_result)
|
result.update(points_result)
|
||||||
result["esports_points_after_exchange"] = points_result["esports_points"]
|
result["esports_points_after_exchange"] = points_result["esports_points"]
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
result["esports_points_refresh_error"] = str(exc)
|
result["esports_points_refresh_error"] = str(exc)
|
||||||
|
|
||||||
account.esports_bind_status = "esports_goods_exchanged"
|
account.esports_bind_status = "esports_goods_exchanged"
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class ManualMixin:
|
|||||||
f"精英宝典支付码已生成,等待开通到账(当前积分 {last_points if last_points is not None else '-'})",
|
f"精英宝典支付码已生成,等待开通到账(当前积分 {last_points if last_points is not None else '-'})",
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
poll_count += 1
|
poll_count += 1
|
||||||
result["payment_poll_count"] = poll_count
|
result["payment_poll_count"] = poll_count
|
||||||
result["payment_poll_error"] = str(exc)
|
result["payment_poll_error"] = str(exc)
|
||||||
@@ -184,7 +184,7 @@ class ManualMixin:
|
|||||||
f"积分 {last_manual_score if last_manual_score is not None else '-'})",
|
f"积分 {last_manual_score if last_manual_score is not None else '-'})",
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
poll_count += 1
|
poll_count += 1
|
||||||
result["payment_poll_count"] = poll_count
|
result["payment_poll_count"] = poll_count
|
||||||
result["payment_poll_error"] = str(exc)
|
result["payment_poll_error"] = str(exc)
|
||||||
@@ -276,7 +276,7 @@ class ManualMixin:
|
|||||||
baseline_manual_type = baseline_result["esports_manual_type"]
|
baseline_manual_type = baseline_result["esports_manual_type"]
|
||||||
baseline_manual_score = baseline_result["esports_manual_score"]
|
baseline_manual_score = baseline_result["esports_manual_score"]
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("warning", f"生成电竞手册支付码前查询活动状态失败: {exc}")
|
self._push_log("warning", f"生成电竞手册支付码前查询活动状态失败: {exc}")
|
||||||
|
|
||||||
if baseline_manual_type is not None and baseline_manual_type >= 1:
|
if baseline_manual_type is not None and baseline_manual_type >= 1:
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class XpdMixin:
|
|||||||
result["before_role_name"] = str(before.get("role_name") or "")
|
result["before_role_name"] = str(before.get("role_name") or "")
|
||||||
result["before_area_name"] = str(before.get("area_name") or "")
|
result["before_area_name"] = str(before.get("area_name") or "")
|
||||||
result["before_plat_name"] = str(before.get("plat_name") or "")
|
result["before_plat_name"] = str(before.get("plat_name") or "")
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
result["before_bound"] = False
|
result["before_bound"] = False
|
||||||
result["before_role_name"] = ""
|
result["before_role_name"] = ""
|
||||||
result["bind_polling"] = True
|
result["bind_polling"] = True
|
||||||
@@ -208,7 +208,7 @@ class XpdMixin:
|
|||||||
f"等待扫码绑定(第 {poll_count} 次)",
|
f"等待扫码绑定(第 {poll_count} 次)",
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
poll_count += 1
|
poll_count += 1
|
||||||
result["bind_poll_count"] = poll_count
|
result["bind_poll_count"] = poll_count
|
||||||
result["bind_poll_error"] = str(exc)
|
result["bind_poll_error"] = str(exc)
|
||||||
@@ -259,7 +259,7 @@ class XpdMixin:
|
|||||||
else:
|
else:
|
||||||
account.xpd_game_name = role_name
|
account.xpd_game_name = role_name
|
||||||
account.updated_at = datetime.now(UTC)
|
account.updated_at = datetime.now(UTC)
|
||||||
except Exception:
|
except Exception: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
account.xpd_game_name = role_name
|
account.xpd_game_name = role_name
|
||||||
account.updated_at = datetime.now(UTC)
|
account.updated_at = datetime.now(UTC)
|
||||||
account.xpd_bind_status = "xpd_bound"
|
account.xpd_bind_status = "xpd_bound"
|
||||||
@@ -409,8 +409,8 @@ class XpdMixin:
|
|||||||
plat = str(role_plat) if role_plat not in (None, "") else plat
|
plat = str(role_plat) if role_plat not in (None, "") else plat
|
||||||
areaid = str(role_area) or areaid
|
areaid = str(role_area) or areaid
|
||||||
self._apply_xpd_role_to_account(account, role, role_area)
|
self._apply_xpd_role_to_account(account, role, role_area)
|
||||||
except Exception:
|
except Exception as exc: # noqa: BLE001
|
||||||
pass
|
self._push_log("debug", f"小店角色信息补全失败: {exc}")
|
||||||
if not openid or not roleid:
|
if not openid or not roleid:
|
||||||
self._mark_task(
|
self._mark_task(
|
||||||
db, task, "failed", "未获取到小店绑定角色,请先生成二维码扫码绑定"
|
db, task, "failed", "未获取到小店绑定角色,请先生成二维码扫码绑定"
|
||||||
|
|||||||
@@ -690,7 +690,7 @@ class HuyaRegisterRunner:
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
account_id, username, uid = self._save_success(index, result)
|
account_id, username, uid = self._save_success(index, result)
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
status = "error"
|
status = "error"
|
||||||
cookie = ""
|
cookie = ""
|
||||||
full_cookie = ""
|
full_cookie = ""
|
||||||
@@ -779,7 +779,7 @@ class HuyaRegisterRunner:
|
|||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self.batch.status = "error"
|
self.batch.status = "error"
|
||||||
self.batch.message = f"批次执行异常: {exc}"
|
self.batch.message = f"批次执行异常: {exc}"
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class HuyaBatchRunner(
|
|||||||
self._push_log("success", f"[{current}] {name} {task.message}")
|
self._push_log("success", f"[{current}] {name} {task.message}")
|
||||||
else:
|
else:
|
||||||
self._push_log("error", f"[{current}] {name} {task.message}")
|
self._push_log("error", f"[{current}] {name} {task.message}")
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._mark_task(worker_db, task, "error", f"执行异常: {exc}")
|
self._mark_task(worker_db, task, "error", f"执行异常: {exc}")
|
||||||
self._push_log("error", f"[{current}] {name} 执行异常: {exc}")
|
self._push_log("error", f"[{current}] {name} 执行异常: {exc}")
|
||||||
finally:
|
finally:
|
||||||
@@ -196,12 +196,12 @@ class HuyaBatchRunner(
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("error", f"虎牙 Worker 异常: {exc}")
|
self._push_log("error", f"虎牙 Worker 异常: {exc}")
|
||||||
|
|
||||||
self._push_log("info", f"虎牙批次 {self.batch_id} 完成")
|
self._push_log("info", f"虎牙批次 {self.batch_id} 完成")
|
||||||
self._push_log("result", "")
|
self._push_log("result", "")
|
||||||
except Exception as exc:
|
except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("error", f"虎牙批次执行异常: {exc}")
|
self._push_log("error", f"虎牙批次执行异常: {exc}")
|
||||||
self._push_log("result", "")
|
self._push_log("result", "")
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class HuyaBatchRunnerCore:
|
|||||||
def _format_local_time(timestamp: int) -> str:
|
def _format_local_time(timestamp: int) -> str:
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
return ""
|
return ""
|
||||||
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
return datetime.fromtimestamp(timestamp, UTC).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_scheduled_time(value) -> datetime | None:
|
def _parse_scheduled_time(value) -> datetime | None:
|
||||||
|
|||||||
@@ -267,8 +267,11 @@ class GoodsMixin:
|
|||||||
if self.payload.get("scheduled_at") and scheduled_at is None:
|
if self.payload.get("scheduled_at") and scheduled_at is None:
|
||||||
self._mark_task(worker_db, task, "failed", "定时兑换时间格式无效")
|
self._mark_task(worker_db, task, "failed", "定时兑换时间格式无效")
|
||||||
return
|
return
|
||||||
if scheduled_at and scheduled_at.timestamp() > time.time():
|
if (
|
||||||
if not self._wait_until(scheduled_at, uid):
|
scheduled_at
|
||||||
|
and scheduled_at.timestamp() > time.time()
|
||||||
|
and not self._wait_until(scheduled_at, uid)
|
||||||
|
):
|
||||||
self._mark_task(worker_db, task, "stopped", "兑换任务已停止")
|
self._mark_task(worker_db, task, "stopped", "兑换任务已停止")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -348,8 +348,7 @@ def cleanup_orphan_huya_tasks(
|
|||||||
query = db.query(HuyaTask).filter(HuyaTask.status.in_(statuses))
|
query = db.query(HuyaTask).filter(HuyaTask.status.in_(statuses))
|
||||||
if batch_id:
|
if batch_id:
|
||||||
query = query.filter(HuyaTask.batch_id == batch_id)
|
query = query.filter(HuyaTask.batch_id == batch_id)
|
||||||
elif active_batch_ids is not None:
|
elif active_batch_ids is not None and active_batch_ids:
|
||||||
if active_batch_ids:
|
|
||||||
query = query.filter(~HuyaTask.batch_id.in_(list(active_batch_ids)))
|
query = query.filter(~HuyaTask.batch_id.in_(list(active_batch_ids)))
|
||||||
# active_batch_ids is None 且未指定 batch_id:清理全部匹配状态
|
# active_batch_ids is None 且未指定 batch_id:清理全部匹配状态
|
||||||
|
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ class LoginBatchRunner:
|
|||||||
f"[{current}] {acc_info['username']} {action_name}失败: {result.message}",
|
f"[{current}] {acc_info['username']} {action_name}失败: {result.message}",
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
if self.mode == "relogin":
|
if self.mode == "relogin":
|
||||||
task.status = "relogin_failed"
|
task.status = "relogin_failed"
|
||||||
task.message = f"重新登录异常: {e}(旧 Cookie 已保留)"
|
task.message = f"重新登录异常: {e}(旧 Cookie 已保留)"
|
||||||
@@ -462,8 +462,10 @@ class LoginBatchRunner:
|
|||||||
if not acc:
|
if not acc:
|
||||||
self._push_log("warning", f"跳过无账号的任务 #{task_id}")
|
self._push_log("warning", f"跳过无账号的任务 #{task_id}")
|
||||||
continue
|
continue
|
||||||
if "login:view_all" not in self.creator_permissions:
|
if (
|
||||||
if acc.assigned_to != self.created_by:
|
"login:view_all" not in self.creator_permissions
|
||||||
|
and acc.assigned_to != self.created_by
|
||||||
|
):
|
||||||
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||||
continue
|
continue
|
||||||
_append_task_info(task, acc)
|
_append_task_info(task, acc)
|
||||||
@@ -482,8 +484,10 @@ class LoginBatchRunner:
|
|||||||
if not acc:
|
if not acc:
|
||||||
continue
|
continue
|
||||||
# 权限检查:客服只能跑分配给自己的
|
# 权限检查:客服只能跑分配给自己的
|
||||||
if "login:view_all" not in self.creator_permissions:
|
if (
|
||||||
if acc.assigned_to != self.created_by:
|
"login:view_all" not in self.creator_permissions
|
||||||
|
and acc.assigned_to != self.created_by
|
||||||
|
):
|
||||||
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
self._push_log("warning", f"跳过无权账号: {acc.username}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -563,7 +567,7 @@ class LoginBatchRunner:
|
|||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
try:
|
try:
|
||||||
future.result()
|
future.result()
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
self._push_log("error", f"Worker 异常: {e}")
|
self._push_log("error", f"Worker 异常: {e}")
|
||||||
|
|
||||||
self._push_log("info", f"批量{action_name}任务 {batch_id} 完成")
|
self._push_log("info", f"批量{action_name}任务 {batch_id} 完成")
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ class ProxyService:
|
|||||||
|
|
||||||
push("error", "未配置代理地址或API")
|
push("error", "未配置代理地址或API")
|
||||||
push("result", "")
|
push("result", "")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
push("error", f"测试异常: {e}")
|
push("error", f"测试异常: {e}")
|
||||||
push("result", "")
|
push("result", "")
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ class ProxyService:
|
|||||||
if whitelist_ip:
|
if whitelist_ip:
|
||||||
local_ip = whitelist_ip
|
local_ip = whitelist_ip
|
||||||
push("info", f"从代理API获取到本机IP: {local_ip}")
|
push("info", f"从代理API获取到本机IP: {local_ip}")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
push("warning", f"代理API请求失败: {e}")
|
push("warning", f"代理API请求失败: {e}")
|
||||||
|
|
||||||
if not local_ip:
|
if not local_ip:
|
||||||
@@ -293,7 +293,7 @@ class ProxyService:
|
|||||||
push("success" if sync_ok else "error", f"白名单同步: {sync_msg}")
|
push("success" if sync_ok else "error", f"白名单同步: {sync_msg}")
|
||||||
|
|
||||||
push("result", "")
|
push("result", "")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底
|
||||||
push("error", f"测试异常: {e}")
|
push("error", f"测试异常: {e}")
|
||||||
push("result", "")
|
push("result", "")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user