Collect authenticated Qinglong scripts batch

This commit is contained in:
Hermes Agent
2026-05-24 03:53:42 +00:00
parent 0d80b33d25
commit 28d62fef03
86 changed files with 18420 additions and 5 deletions

View File

@@ -0,0 +1,413 @@
# Source: https://github.com/Small-tailqwq/ql_script/blob/master/zaimanhua/zaimanhua.py
# Raw: https://raw.githubusercontent.com/Small-tailqwq/ql_script/master/zaimanhua/zaimanhua.py
# Repo: Small-tailqwq/ql_script
# Path: zaimanhua/zaimanhua.py
# UploadedAt: 2026-03-02T11:07:04Z
# SHA256: d504877ad0b3be012794bec38536cdaeaab15f83fe8bafcee5d8d9121dce5b8d
# Category: web版/账密
# Evidence: web/H5关键词 + username/password/login
# -*- coding: utf-8 -*-
"""
cron: 20 7 * * *
new Env('再漫画自动签到');
"""
from __future__ import annotations
import hashlib
import json
import logging
import os
from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Optional, Tuple
import requests
try:
from sendNotify import send # type: ignore
except Exception: # noqa: BLE001
try:
from notify import send # type: ignore
except Exception:
send = None
API_BASE = "https://i.zaimanhua.com/lpi/v1"
DEFAULT_TIMEOUT = 15
DEBUG_ENABLED = os.getenv("ZAIMANHUA_DEBUG", "0").strip().lower() in {"1", "true", "on", "yes"}
def debug_dump(label: str, payload: Any) -> None:
if not DEBUG_ENABLED:
return
try:
fragment = json.dumps(payload, ensure_ascii=False)[:1500]
except Exception: # noqa: BLE001
fragment = str(payload)[:1500]
logging.debug("%s: %s", label, fragment)
@dataclass
class SignResult:
account: str
success: bool
message: str
class ZaiManHua:
name = "再漫画"
def __init__(self, username: str, password: str, alias: Optional[str] = None) -> None:
self.username = username.strip()
self.password = password.strip()
self.alias = alias.strip() if alias else ""
self.session = self._build_session()
@staticmethod
def _build_session() -> requests.Session:
session = requests.Session()
session.headers.update(
{
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0 Safari/537.36"
),
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Origin": "https://m.zaimanhua.com",
"Referer": "https://m.zaimanhua.com/",
}
)
return session
@staticmethod
def _md5(text: str) -> str:
return hashlib.md5(text.encode("utf-8")).hexdigest()
def _request_json(
self,
method: str,
endpoint: str,
*,
params: Optional[Dict[str, Any]] = None,
json_data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]:
url = endpoint if endpoint.startswith("http") else f"{API_BASE}/{endpoint.lstrip('/') }"
if DEBUG_ENABLED:
logging.debug(
"请求 %s %s params=%s json=%s headers=%s",
method,
url,
params,
json_data,
headers,
)
response = self.session.request(
method,
url,
params=params,
json=json_data,
headers=headers,
timeout=DEFAULT_TIMEOUT,
)
response.raise_for_status()
data = response.json()
if not isinstance(data, dict): # pragma: no cover - 防御
raise ValueError("接口返回数据格式异常")
debug_dump(f"响应 {method} {url}", data)
return data
@staticmethod
def _ensure_success(payload: Dict[str, Any], allowed_errno: Iterable[int] = (0,)) -> Tuple[int, str]:
errno = int(payload.get("errno", -1))
errmsg = str(payload.get("errmsg", ""))
if errno not in allowed_errno:
raise RuntimeError(f"接口返回错误 errno={errno}, errmsg={errmsg}")
return errno, errmsg
@staticmethod
def _extract_token(payload: Dict[str, Any]) -> Optional[str]:
def _search(node: Any) -> Optional[str]:
if isinstance(node, dict):
for key, value in node.items():
key_lower = str(key).lower()
if key_lower in {"token", "access_token"} and isinstance(value, str) and value:
return value
if isinstance(value, (dict, list)):
found = _search(value)
if found:
return found
elif isinstance(node, list):
for item in node:
found = _search(item)
if found:
return found
return None
return _search(payload)
@staticmethod
def _find_first(node: Any, target_keys: Iterable[str]) -> Optional[Any]:
target = {str(key).lower() for key in target_keys}
def _search(current: Any) -> Optional[Any]:
if isinstance(current, dict):
for key, value in current.items():
if str(key).lower() in target and value is not None:
return value
if isinstance(value, (dict, list)):
found = _search(value)
if found is not None:
return found
elif isinstance(current, list):
for item in current:
found = _search(item)
if found is not None:
return found
return None
return _search(node)
def _login(self) -> str:
payload = self._request_json(
"POST",
"login/passwd",
params={"username": self.username, "passwd": self._md5(self.password)},
)
self._ensure_success(payload)
token: Optional[str] = None
token_source = "未知"
data = payload.get("data")
if isinstance(data, dict):
token = self._extract_token(data)
if token:
token_source = "data"
if not token:
token = self._extract_token(payload)
if token:
token_source = "payload"
if not token and self.session.cookies:
token = self.session.cookies.get("token") or self.session.cookies.get("Authorization")
if token:
token_source = "cookies"
if not token:
snippet = str(payload)[:300]
raise RuntimeError(f"登录成功但未返回 token响应片段{snippet}")
logging.debug("登录成功token 来源=%s", token_source)
return str(token)
def _sign_in(self, token: str) -> str:
payload = self._request_json(
"POST",
"task/sign_in",
headers={"Authorization": f"Bearer {token}"},
)
_, errmsg = self._ensure_success(payload, allowed_errno=(0, 1))
return errmsg or "签到成功"
def _fetch_user_info(self, token: str) -> Tuple[str, Optional[int]]:
payload = self._request_json(
"POST",
"u_center/passport/message",
headers={"Authorization": f"Bearer {token}"},
)
self._ensure_success(payload)
data = payload.get("data")
debug_dump("用户信息 payload", payload)
if not isinstance(data, dict):
raise RuntimeError("用户信息数据格式异常")
user_info = data.get("userInfo") or data.get("user") or data
if not isinstance(user_info, dict):
user_info = {}
nickname = str(
user_info.get("nickname")
or user_info.get("username")
or data.get("nickname")
or ""
)
level_value = user_info.get("user_level") or data.get("user_level")
if level_value is None:
level_value = self._find_first(data, {"user_level", "level"})
try:
level = int(level_value) if level_value is not None else None
except (TypeError, ValueError):
level = None
return nickname, level
def _fetch_task_info(self, token: str) -> Tuple[Optional[int], Optional[int], Optional[int]]:
payload = self._request_json(
"GET",
"task/list",
headers={"Authorization": f"Bearer {token}"},
)
self._ensure_success(payload)
data = payload.get("data")
debug_dump("任务列表 payload", payload)
if not isinstance(data, dict):
raise RuntimeError("任务列表数据格式异常")
user_currency = data.get("userCurrency")
if not isinstance(user_currency, dict):
task_section = data.get("task")
if isinstance(task_section, dict):
user_currency = task_section.get("userCurrency")
credits = self._safe_int(user_currency, "credits") if isinstance(user_currency, dict) else None
if credits is None:
found = self._find_first(data, {"credits"})
try:
credits = int(found) if found is not None else None
except (TypeError, ValueError):
credits = None
sum_sign_task = (
data.get("sumSignTask")
or data.get("signTask")
or (data.get("task") or {}).get("sumSignTask")
or (data.get("task") or {}).get("signTask")
or {}
)
continuous_days = self._safe_int(sum_sign_task, "continuousSignDays")
history_days = self._safe_int(sum_sign_task, "sumSignDays")
if continuous_days is None:
value = self._find_first(data, {"continuousSignDays", "continuous_days"})
try:
continuous_days = int(value) if value is not None else None
except (TypeError, ValueError):
continuous_days = None
if history_days is None:
value = self._find_first(data, {"sumSignDays", "totalSignDays", "history_days"})
try:
history_days = int(value) if value is not None else None
except (TypeError, ValueError):
history_days = None
return credits, continuous_days, history_days
@staticmethod
def _safe_int(source: Any, key: str) -> Optional[int]:
value: Any = None
if isinstance(source, dict):
value = source.get(key)
try:
return int(value) if value is not None else None
except (TypeError, ValueError):
return None
@staticmethod
def _mask(text: str) -> str:
text = text or ""
if len(text) <= 2:
return f"{text[:1]}*" if text else "未知"
return f"{text[0]}***{text[-1]}"
def main(self) -> SignResult:
if not self.username or not self.password:
raise RuntimeError("账号或密码未配置")
token = self._login()
sign_msg = self._sign_in(token)
nickname, level = self._fetch_user_info(token)
credits, continuous_days, history_days = self._fetch_task_info(token)
display_name = nickname or self.alias or self._mask(self.username)
level_info = f"LV{level}" if level is not None else "未知"
credit_info = str(credits) if credits is not None else "未知"
continuous_info = f"{continuous_days}" if continuous_days is not None else "未知"
history_info = f"{history_days}" if history_days is not None else "未知"
lines = [
f"签到状态:{sign_msg}",
f"用户等级:{level_info}",
f"当前积分:{credit_info}",
f"连续签到:{continuous_info}",
f"历史签到:{history_info}",
]
return SignResult(
account=display_name,
success=True,
message="\n".join(lines),
)
def load_accounts() -> List[ZaiManHua]:
accounts: List[ZaiManHua] = []
raw_accounts = os.getenv("ZAIMANHUA_ACCOUNTS", "").strip()
if raw_accounts:
for line in raw_accounts.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split("#", 2)
if len(parts) < 2:
logging.warning("无效的账号配置行:%s,格式应为 用户名#密码[#别名]", line)
continue
username, password, *rest = parts
alias = rest[0] if rest else None
accounts.append(ZaiManHua(username=username, password=password, alias=alias))
username = os.getenv("ZAIMANHUA_USERNAME", "").strip()
password = os.getenv("ZAIMANHUA_PASSWORD", "").strip()
alias = os.getenv("ZAIMANHUA_ALIAS", "").strip()
if not accounts and username and password:
accounts.append(ZaiManHua(username=username, password=password, alias=alias))
return accounts
def format_report(results: List[SignResult]) -> str:
chunks = []
for item in results:
status = "✅ 成功" if item.success else "❌ 失败"
chunks.append(f"账号:{item.account} | {status}\n{item.message}")
return "\n\n".join(chunks)
def main() -> None:
logging.basicConfig(
level=logging.DEBUG if DEBUG_ENABLED else logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
if DEBUG_ENABLED:
logging.debug("调试模式已开启 (ZAIMANHUA_DEBUG=1)")
accounts = load_accounts()
if DEBUG_ENABLED and accounts:
masked_accounts = [
{
"username": ZaiManHua._mask(acc.username),
"alias": acc.alias,
}
for acc in accounts
]
debug_dump("已加载账号", masked_accounts)
if not accounts:
logging.error("未配置再漫画账号信息,需设置 ZAIMANHUA_ACCOUNTS 或 ZAIMANHUA_USERNAME/ZAIMANHUA_PASSWORD。")
if send:
send("再漫画签到", "未配置账号信息,任务未执行。")
return
results: List[SignResult] = []
for index, account in enumerate(accounts, start=1):
try:
logging.info("[%s] 正在执行账号 %s", index, account.alias or account.username)
results.append(account.main())
except Exception as exc: # noqa: BLE001
logging.exception("[%s] 账号 %s 执行失败:%s", index, account.alias or account.username, exc)
results.append(
SignResult(
account=account.alias or account.username,
success=False,
message=str(exc),
)
)
report = format_report(results)
logging.info("\n%s", report)
if send:
send("再漫画签到", report)
if __name__ == "__main__":
main()