Files
maipulse/maipulse/core/accounts.py
T
shouko 8421898626 maipulse: 脱敏存档
- Python 后端 + 单页前端 + Android(Chaquopy) 壳
- 曲库 1343 首 / 称号 2800 个
- 机台身份与店铺信息已抹除,字段留空
- 打包脚本含 16KB 页对齐与自动脱敏
2026-10-28 22:27:46 +08:00

161 lines
4.6 KiB
Python

# -*- coding: utf-8 -*-
"""多账号:把扫码换来的 userId + token 存本机,之后直接用它重新登录。
为什么能省掉扫码:二维码只是一次性入口,扫出来的 token 才是账号凭证。
`session.login(state, user_id, token)` 每天都用它做 UserLogin——只要机台上
没有重新扫码把旧凭证顶掉,同一个 token 可以反复登录。
★ token 属于敏感信息:
· 只写 `data/accounts.json`(本机,不进任何仓库、不上传);
· 接口返回给前端的只有打码 userId 与昵称,token 不出这个模块。
"""
from __future__ import annotations
import hashlib
import json
import os
import time
from pathlib import Path
_STORE = Path(__file__).resolve().parent.parent / "data" / "accounts.json"
def mask_user_id(user_id) -> str:
"""12345678901 -> 123****01(和面板其它地方同一口径)。"""
s = str(user_id or "")
if not s:
return "-"
if len(s) <= 6:
return s[:1] + "***"
return s[:3] + "****" + s[-2:] # 固定 4 星,不泄露位数
def ref_of(user_id) -> str:
"""对外引用:userId 的短哈希。
前端拿到的永远是打码 id 与这个 ref,切换账号时只回传 ref,
完整 userId 与 token 都不出后端。
"""
return hashlib.sha256(str(int(user_id)).encode("utf-8")).hexdigest()[:12]
def get_by_ref(ref):
ref = str(ref or "")
if not ref:
return None
for row in _load():
if ref_of(row["userId"]) == ref:
return row
return None
def _load() -> list:
try:
raw = json.loads(_STORE.read_text(encoding="utf-8"))
except (OSError, ValueError):
return []
rows = raw.get("accounts") if isinstance(raw, dict) else raw
out = []
for row in (rows or []):
if not isinstance(row, dict):
continue
try:
uid = int(row.get("userId") or 0)
except (TypeError, ValueError):
continue
token = str(row.get("token") or "")
if uid <= 0 or not token:
continue
out.append({
"userId": uid,
"token": token,
"name": str(row.get("name") or ""),
"savedAt": str(row.get("savedAt") or ""),
"lastUsed": str(row.get("lastUsed") or ""),
})
return out
def _save(rows: list) -> None:
_STORE.parent.mkdir(parents=True, exist_ok=True)
tmp = _STORE.with_suffix(".tmp")
payload = {"accounts": rows}
tmp.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
os.replace(tmp, _STORE) # 原子替换,写一半断电不会把存档写坏
def _now() -> str:
return time.strftime("%Y-%m-%d %H:%M:%S")
def list_accounts(current_user_id=None) -> list:
"""给前端看的列表:没有 token,只有打码 id / 昵称 / 时间。"""
cur = str(current_user_id or "")
rows = _load()
rows.sort(key=lambda r: r.get("lastUsed") or "", reverse=True)
return [{
"ref": ref_of(r["userId"]),
"userId": mask_user_id(r["userId"]),
"name": r["name"] or "(未命名)",
"lastUsed": r["lastUsed"] or r["savedAt"] or "-",
"current": str(r["userId"]) == cur,
} for r in rows]
def upsert(user_id, token, name: str = "") -> None:
"""登录成功后保存 / 刷新这个账号。"""
uid = int(user_id)
token = str(token or "")
if uid <= 0 or not token:
return
rows = _load()
for row in rows:
if row["userId"] == uid:
row["token"] = token
row["lastUsed"] = _now()
if name:
row["name"] = name
break
else:
rows.append({"userId": uid, "token": token, "name": name or "",
"savedAt": _now(), "lastUsed": _now()})
_save(rows)
def get(user_id):
uid = int(user_id)
for row in _load():
if row["userId"] == uid:
return row
return None
def touch(user_id) -> None:
uid = int(user_id)
rows = _load()
for row in rows:
if row["userId"] == uid:
row["lastUsed"] = _now()
_save(rows)
return
def rename(user_id, name: str) -> bool:
uid = int(user_id)
rows = _load()
for row in rows:
if row["userId"] == uid:
row["name"] = str(name or "")[:40]
_save(rows)
return True
return False
def remove(user_id) -> bool:
uid = int(user_id)
rows = _load()
left = [r for r in rows if r["userId"] != uid]
if len(left) == len(rows):
return False
_save(left)
return True