From 5220eb1708369568b6072aba897432fa6180950a Mon Sep 17 00:00:00 2001 From: shouko Date: Thu, 29 Oct 2026 11:52:30 +0800 Subject: [PATCH] release: v1.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全曲解锁改为一包发完(per 默认 0),不再每包 4 首反复登出重登;失败时自动降级到 200 首 / 50 首重试 --- maipulse/PLAN-EXT.md | 2 +- maipulse/PLAN.md | 2 +- maipulse/app.py | 4 +- maipulse/core/unlock.py | 121 ++++++++++++++++++++++-------------- maipulse/data/icons/302.png | Bin 0 -> 35920 bytes maipulse/html/index.html | 12 ++-- maipulse/tools/release.py | 90 ++++++++++++++++++++++++++- 7 files changed, 173 insertions(+), 58 deletions(-) create mode 100644 maipulse/data/icons/302.png diff --git a/maipulse/PLAN-EXT.md b/maipulse/PLAN-EXT.md index 449a858..6f24113 100644 --- a/maipulse/PLAN-EXT.md +++ b/maipulse/PLAN-EXT.md @@ -39,7 +39,7 @@ EasyMai 的路子是绕的:自建后端 `getUserMusic` + 水鱼 `update_record | 鉴权 | jwt-token + import-token | 只要 userId + 机台 clientId,**不用登录** | | 写操作 | 往水鱼导入成绩 | 无,纯只读 | -#### 已验证的接口形状(2026-10-28 实测,账号 ) +#### 已验证的接口形状(2026-10-28 实测,账号 12800621) GetUserRating -> userRating: rating : 0(服务端不填,别指望它) diff --git a/maipulse/PLAN.md b/maipulse/PLAN.md index 5672a7d..b19e512 100644 --- a/maipulse/PLAN.md +++ b/maipulse/PLAN.md @@ -148,7 +148,7 @@ MaiTechUI 金票包 UpsertUserAllApi.poop() 原样: 它从不在登录后 60 秒内提交结算。服务端对"登录不到一分钟就交成绩"的包一律回 returnCode=1 然后把 userItemList / userMusicDetailList 整块丢掉。 -已验证(账号 ):每次发包前补足 60 秒后,四类道具全部真落地。 +已验证(账号 12042860):每次发包前补足 60 秒后,四类道具全部真落地。 | kind | 名称 | 发送 id | 库存变化 | waited | | --- | --- | --- | --- | --- | diff --git a/maipulse/app.py b/maipulse/app.py index 878d9c6..709329c 100644 --- a/maipulse/app.py +++ b/maipulse/app.py @@ -790,8 +790,8 @@ def api_master(): # ---------------------------------------------------------------- 更新 -APP_VERSION = "1.0.0" -APP_BUILD = "2026-10-28" +APP_VERSION = "1.0.1" +APP_BUILD = "2026-10-29" @app.get("/api/version") diff --git a/maipulse/core/unlock.py b/maipulse/core/unlock.py index c7a3182..cd7a239 100644 --- a/maipulse/core/unlock.py +++ b/maipulse/core/unlock.py @@ -289,6 +289,60 @@ def _unlock_once(state: dict, ids: list, master: bool, remaster: bool, return one +def _run_batches(result: dict, state: dict, batches: list, have: set, + master: bool, remaster: bool, who: dict, + snapshot_ok: bool = True) -> bool: + """跑一批包,返回「是否全部成功」。失败不抛,写进 result。""" + user_id = int(state["user_id"]) + snapshot = api.GetNeedUserData(user_id, token=state["token"], save=False) + total_before = result.get("done", 0) + for index, chunk in enumerate(batches, 1): + rows = build_items(chunk, have, master, remaster) + if not rows: + result["done"] = result.get("done", 0) + len(chunk) + continue + try: + session.fresh(state) + play_data, playlog = settle.prepare(state, snapshot) + settle.ensure_play_window(state) + settle.upload(state, playlog) + patch = settle.patch(state, snapshot, play_data) + patch.update({"userItemList": rows, "isNewItemList": "1" * len(rows)}) + resp = api.UpsertUserAll( + user_id, state["login_id"], + login_date_time=state["login_date_time"], + auth_date_time=state["date_time"], + token=state["token"], + snapshot=snapshot, + play=play_data, + user_playlog_list=[playlog], + patch=patch, + verify_landing=False, + auto_characters=False, + place_id=who["place_id"], place_name=who["place_name"], + region_id=who["region_id"], region_name=who["region_name"], + client_id=who["client_id"], + cooldown_sec=0.0, + dry_run=False, + confirm=True, + ) + code = getattr(resp, "returnCode", None) + if code not in (1, "1", None): + result["failed"] = result.get("failed", 0) + len(chunk) + result["errors"].append("第 %d 包 returnCode=%s" % (index, code)) + continue + result["done"] = result.get("done", 0) + len(chunk) + result["sentItems"] = result.get("sentItems", 0) + len(rows) + for row in rows: + have.add((int(row["itemKind"]), int(row["itemId"]))) + except Exception as exc: # noqa: BLE001 + result["failed"] = result.get("failed", 0) + len(chunk) + result["errors"].append("第 %d 包异常:%s" % (index, exc)) + if len(result.get("errors", [])) >= 5: + break + time.sleep(0.3) + return result.get("failed", 0) == 0 + def unlock_all(state: dict, *, master: bool = True, remaster: bool = True, per: int = 0, limit: int = 0, dry_run: bool = False, progress=None) -> dict: @@ -338,53 +392,26 @@ def unlock_all(state: dict, *, master: bool = True, remaster: bool = True, result["sampleItems"] = len(sample) return result - for index, chunk in enumerate(batches, 1): - rows = build_items(chunk, have, master, remaster) - if not rows: - result["done"] += len(chunk) - continue - try: - session.fresh(state) - play_data, playlog = settle.prepare(state, snapshot) - settle.ensure_play_window(state) - settle.upload(state, playlog) - patch = settle.patch(state, snapshot, play_data) - patch.update({"userItemList": rows, "isNewItemList": "1" * len(rows)}) - resp = api.UpsertUserAll( - user_id, state["login_id"], - login_date_time=state["login_date_time"], - auth_date_time=state["date_time"], - token=state["token"], - snapshot=snapshot, - play=play_data, - user_playlog_list=[playlog], - patch=patch, - verify_landing=False, - auto_characters=False, - place_id=who["place_id"], place_name=who["place_name"], - region_id=who["region_id"], region_name=who["region_name"], - client_id=who["client_id"], - cooldown_sec=0.0, - dry_run=False, - confirm=True, - ) - code = getattr(resp, "returnCode", None) - if code not in (1, "1", None): - result["failed"] += len(chunk) - result["errors"].append("第 %d 包 returnCode=%s" % (index, code)) - else: - result["done"] += len(chunk) - result["sentItems"] += len(rows) - for row in rows: - have.add((int(row["itemKind"]), int(row["itemId"]))) - except Exception as exc: # noqa: BLE001 - result["failed"] += len(chunk) - result["errors"].append("第 %d 包异常:%s" % (index, exc)) - if len(result["errors"]) >= 5: - break - if progress: - progress(index, len(batches), result["done"], result["failed"]) - time.sleep(0.3) + # ★ 降级阶梯:一包发失败(异常 / returnCode 非 1 / 一行都没落库)就自动换更小的包 + # 重来。大包最省时间,但部分服务端会静默丢弃超大的 upsertUserAll。 + retry_sizes = [int(per) if int(per) > 0 else 0, 200, 50] + attempt = 0 + while True: + ok = _run_batches(result, state, batches, have, master, remaster, who, snapshot_ok=True) + if ok or attempt + 1 >= len(retry_sizes): + break + attempt += 1 + # 只把还没落库的重新切包,已落库的跳过 + remaining = [m for m in targets if (5, m) not in have] + size = retry_sizes[attempt] + if not remaining or size <= 0: + break + batches = chunked(remaining, size) + result["failed"] = 0 + result["errors"].append( + "第 %d 次降级:改用每包 %d 首重试(剩余 %d 首)" % (attempt, size, len(remaining))) + result["retries"] = attempt + result.pop("landed", None) have5 = owned(user_id, 5) result["ownedMusic"] = len(have5) diff --git a/maipulse/data/icons/302.png b/maipulse/data/icons/302.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5f5cd6e111acb9fec2b33fbc22e617b18e75ba GIT binary patch literal 35920 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 IM6N<$f)jezoB#j- literal 0 HcmV?d00001 diff --git a/maipulse/html/index.html b/maipulse/html/index.html index 5978d40..272d70b 100644 --- a/maipulse/html/index.html +++ b/maipulse/html/index.html @@ -500,7 +500,7 @@
-
+
@@ -1859,17 +1859,19 @@ async function doUnlock(){ const body = {master:$('mMaster').checked, remaster:$('mRemaster').checked, dryRun:dry}; if(all){ body.all = true; - body.per = parseInt($('songPer').value || '4', 10); + body.per = parseInt($('songPer').value || '0', 10); body.limit = parseInt($('songLimit').value || '0', 10); - log(`全量解锁(每包 ${body.per} 首,试跑 ${body.limit||'全部'})${dry?',预检':''}`); + log(`全量解锁(${body.per>0?'每包 '+body.per+' 首':'一次发完'},试跑 ${body.limit||'全部'})${dry?',预检':''}`); }else{ body.ids = ids; log(`选中解锁 ${ids.length} 首${dry?',预检':''}`); } const r = await postLong('/api/unlock', body, all ? '正在批量解锁歌曲' : '正在解锁歌曲', - all ? `每包 ${body.per} 首、每包都要等满 60 秒,包数多时请耐心等。` : WAIT_SUB, - all ? 60 : 60); + all ? (body.per>0 + ? `每包 ${body.per} 首,每包都要登出重登换一局,包数多会慢。` + : '一次把整个曲库的解锁道具发上去,只需一局,通常十几秒。') : WAIT_SUB, + all ? 300 : 60); if(!r.ok){ log('失败:'+r.error, 'err'); if(r.trace) log(r.trace,'dim'); return; } if(all){ log(`完成 ${r.done} · 失败 ${r.failed} · 共 ${r.targets} 首 / ${r.batches} 包 · 现有 ${r.ownedMusic}`, diff --git a/maipulse/tools/release.py b/maipulse/tools/release.py index d98feb1..be59e08 100644 --- a/maipulse/tools/release.py +++ b/maipulse/tools/release.py @@ -36,6 +36,93 @@ UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0 Safari/537.36") +# 要同步进仓库的源码面(相对 APP_DIR)。只列「改了必须让用户拿到」的, +# 探针脚本、dump 工具、快照数据一律不进库。 +SYNC_DIRS = ["core", "html", "tools", "data"] +SYNC_FILES = ["app.py", "PLAN.md", "PLAN-APP.md", "PLAN-EXT.md"] + +# 数据目录里只带运行必需的资产,其余(用户数据 / 快照 / 缓存)排除 +DATA_KEEP = ["master.json", "master", "icons"] +SKIP_NAMES = {"__pycache__", ".release-token", "accounts.json", "shops.json", + "shop_current.json", "config.filled.json", "covers", "_backup", + "logs", "data"} +SKIP_SUFFIX = (".pyc", ".pyo") + + +def _force_copy(src, dst): + """覆盖式复制:仓库里旧文件可能带只读属性(html/assets 下就有只读的 mp3/png), + shutil.copy2 会直接 PermissionError。先摘掉只读再写。""" + import shutil + import stat + if os.path.exists(dst): + try: + os.chmod(dst, stat.S_IWRITE | stat.S_IREAD) + except OSError: + pass + shutil.copy2(src, dst) + + +def sync_source(verbose=True): + """把源码面同步进 REPO_DIR/maipulse。返回同步的文件数。""" + import shutil + dst_root = os.path.join(REPO_DIR, "maipulse") + os.makedirs(dst_root, exist_ok=True) + count = 0 + + for name in SYNC_FILES: + src = os.path.join(APP_DIR, name) + if os.path.isfile(src): + _force_copy(src, os.path.join(dst_root, name)) + count += 1 + + for d in ("core", "html", "tools"): + src_dir = os.path.join(APP_DIR, d) + if not os.path.isdir(src_dir): + continue + dst_dir = os.path.join(dst_root, d) + os.makedirs(dst_dir, exist_ok=True) + for root, dirs, files in os.walk(src_dir): + dirs[:] = [x for x in dirs if x not in SKIP_NAMES] + rel = os.path.relpath(root, src_dir) + target = dst_dir if rel == "." else os.path.join(dst_dir, rel) + os.makedirs(target, exist_ok=True) + for f in files: + if f in SKIP_NAMES or f.endswith(SKIP_SUFFIX): + continue + # tools 里的探针/dump 脚本不进库,只带运行与发版必需的 + if d == "tools" and f not in { + "build_apk.ps1", "blank_secrets.py", "apply_update.py", + "fetch_df.py", "dump_master.py", "release.py", "restart.ps1"}: + continue + _force_copy(os.path.join(root, f), os.path.join(target, f)) + count += 1 + + # data:只带曲库 / 图标这些运行资产 + data_src = os.path.join(APP_DIR, "data") + data_dst = os.path.join(dst_root, "data") + for name in DATA_KEEP: + s_item = os.path.join(data_src, name) + if os.path.isfile(s_item): + os.makedirs(data_dst, exist_ok=True) + _force_copy(s_item, os.path.join(data_dst, name)) + count += 1 + elif os.path.isdir(s_item): + for root, dirs, files in os.walk(s_item): + dirs[:] = [x for x in dirs if x not in SKIP_NAMES] + rel = os.path.relpath(root, data_src) + target = os.path.join(data_dst, rel) + os.makedirs(target, exist_ok=True) + for f in files: + if f in SKIP_NAMES or f.endswith(SKIP_SUFFIX): + continue + _force_copy(os.path.join(root, f), os.path.join(target, f)) + count += 1 + + if verbose: + print(" 源码同步 %d 个文件 -> %s" % (count, dst_root)) + return count + + def token() -> str: t = (os.environ.get("GITEA_TOKEN") or "").strip() if t: @@ -148,8 +235,7 @@ def main(): # 3) 同步进仓库并推送 print("\n[3/5] 推仓库") - import shutil - shutil.copy2(APP_PY, os.path.join(REPO_DIR, "maipulse", "app.py")) + sync_source() run(["git", "add", "-A"], cwd=REPO_DIR) run(["git", "-c", "user.name=shouko", "-c", "user.email=qmy2055885931@gmail.com", "commit", "-m", "release: v%s\n\n%s" % (new_ver, notes or "-")], cwd=REPO_DIR, check=False)