feat: 更新源改到自建 Gitea
- /api/update-check 走 git.fragrance.moe 的 Release API(公开仓库,无需 token) - 设置页加「检查更新 / 下载安装」,下载走 assets 的 browser_download_url - asset.rev 由打包脚本生成,覆盖安装必定重建资源
This commit is contained in:
1 parent
8421898626
commit
4223b0cd19
3 files changed
+95
-28
No files matched your search
@@ -11,17 +11,17 @@ MaimaiNet.py —— 舞萌DX 网络核心层(纯 HTTP 复刻)
|
||||
|
||||
1) 服务器基址与 API 路径
|
||||
source/Assembly-CSharp/Manager/OperationManager.cs:225,338
|
||||
ServerUri = Auth.GameServerUri + MovieServerUri("<REDACTED>/")
|
||||
ServerUri = Auth.GameServerUri + MovieServerUri("<OBFUSCATE_PARAM>/")
|
||||
source/Assembly-CSharp/Net.Packet/Packet.cs:116
|
||||
Client = NetHttpClient.Create(BaseUrl + Obfuscator(query.Api))
|
||||
source/Assembly-CSharp/Net.Packet/Packet.cs:485
|
||||
Obfuscator(s) = md5_hex_lower(s + "<REDACTED>")
|
||||
Obfuscator(s) = md5_hex_lower(s + "<OBFUSCATE_PARAM>")
|
||||
source/Assembly-CSharp/Net.VO/NetQuery.cs:14
|
||||
Api = api + "MaimaiChn"
|
||||
|
||||
=> 最终 URL = BASE_URL + md5(逻辑接口名 + "MaimaiChn" + "<REDACTED>")
|
||||
=> 最终 URL = BASE_URL + md5(逻辑接口名 + "MaimaiChn" + "<OBFUSCATE_PARAM>")
|
||||
|
||||
已用真实日志验证:md5("GetGameSettingApiMaimaiChn" + "<REDACTED>")
|
||||
已用真实日志验证:md5("GetGameSettingApiMaimaiChn" + "<OBFUSCATE_PARAM>")
|
||||
= 83a5d5a20b062c5c2ad817460b8f7f76
|
||||
(source/2026-10-05.log:1 的请求就是打到这个尾段)
|
||||
|
||||
@@ -121,7 +121,7 @@ from config import CLIENT_ID, MAI_ENCODING, TIMEOUT_LONG_MS, TIMEOUT_MS
|
||||
|
||||
#: 游戏服务器(Maimai2Servlet)
|
||||
#: 依据 source/Assembly-CSharp/Manager/OperationManager.cs:225,338
|
||||
#: _operationData.ServerUri = Auth.GameServerUri + MovieServerUri("<REDACTED>/")
|
||||
#: _operationData.ServerUri = Auth.GameServerUri + MovieServerUri("<OBFUSCATE_PARAM>/")
|
||||
BASE_URL = ""
|
||||
|
||||
#: AES 参数,依据 source/Assembly-CSharp/Net/CipherAES.cs:11-12
|
||||
@@ -162,7 +162,7 @@ def obfuscate_api_name(api_name: str) -> str:
|
||||
对逻辑接口名做 MD5 混淆,得到 URL 尾段(对应 Packet.Obfuscator)。
|
||||
|
||||
逻辑名 "GetUserPreviewApi" -> "GetUserPreviewApiMaimaiChn"
|
||||
-> md5(上面 + "<REDACTED>")
|
||||
-> md5(上面 + "<OBFUSCATE_PARAM>")
|
||||
|
||||
参数:
|
||||
api_name: 逻辑接口名,**不要**自己带 MaimaiChn 后缀
|
||||
|
||||
+58
-22
@@ -912,36 +912,72 @@ def api_update_gh():
|
||||
repo=repo)
|
||||
|
||||
|
||||
UPDATE_SITE = "https://git.fragrance.moe"
|
||||
UPDATE_REPO = "shouko/maipulse"
|
||||
|
||||
|
||||
@app.get("/api/update-check")
|
||||
@guarded
|
||||
def api_update_check():
|
||||
"""拉远端 version.json 与本地比较。远端格式:
|
||||
{"version": "1.0.1", "url": "下载地址", "notes": "更新说明", "published": "..."}
|
||||
"""检查更新。默认走自建 Gitea 的 Release(公开仓库,不用 token)。
|
||||
|
||||
asset 用 browser_download_url —— 那是 /releases/download/<tag>/<name> 的稳定地址,
|
||||
比 attachments/uuid 可靠。给了 source 参数则按老的远端 version.json 走。
|
||||
"""
|
||||
import urllib.request
|
||||
src = (request.args.get("source") or "").strip()
|
||||
if not src:
|
||||
return fail("还没填更新源地址")
|
||||
if not re.match(r"^https?://", src):
|
||||
return fail("更新源必须是 http(s) 地址")
|
||||
try:
|
||||
req = urllib.request.Request(src, headers={"User-Agent": "maipulse/" + APP_VERSION})
|
||||
raw = urllib.request.urlopen(req, timeout=20).read().decode("utf-8")
|
||||
remote = json.loads(raw)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return fail("拉取更新源失败:%s" % exc)
|
||||
|
||||
ver = str(remote.get("version") or "")
|
||||
if not ver:
|
||||
return fail("更新源里没有 version 字段")
|
||||
src = (request.args.get("source") or "").strip()
|
||||
|
||||
def _v(text):
|
||||
return tuple(int(x) for x in re.findall(r"\d+", text)[:3])
|
||||
nums = [int(x) for x in re.findall(r"\d+", str(text))[:3]]
|
||||
while len(nums) < 3:
|
||||
nums.append(0)
|
||||
return tuple(nums)
|
||||
|
||||
newer = _v(ver) > _v(APP_VERSION)
|
||||
return ok(current=APP_VERSION, remote=ver, newer=newer,
|
||||
url=remote.get("url"), notes=remote.get("notes"),
|
||||
published=remote.get("published"))
|
||||
if src:
|
||||
if not re.match(r"^https?://", src):
|
||||
return fail("更新源必须是 http(s) 地址")
|
||||
try:
|
||||
req = urllib.request.Request(src, headers={"User-Agent": "maipulse/" + APP_VERSION})
|
||||
remote = json.loads(urllib.request.urlopen(req, timeout=20).read().decode("utf-8"))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return fail("拉取更新源失败:%s" % exc)
|
||||
ver = str(remote.get("version") or "")
|
||||
if not ver:
|
||||
return fail("更新源里没有 version 字段")
|
||||
return ok(current=APP_VERSION, remote=ver, newer=_v(ver) > _v(APP_VERSION),
|
||||
url=remote.get("url"), notes=remote.get("notes"),
|
||||
published=remote.get("published"), repo="(自填)")
|
||||
|
||||
api = "%s/api/v1/repos/%s/releases/latest" % (UPDATE_SITE, UPDATE_REPO)
|
||||
try:
|
||||
req = urllib.request.Request(api, headers={"User-Agent": "maipulse/" + APP_VERSION})
|
||||
rel = json.loads(urllib.request.urlopen(req, timeout=20).read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as exc:
|
||||
return fail("Release 接口 HTTP %s(仓库或 Release 不存在)" % exc.code)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return fail("连不上更新仓库:%s" % exc)
|
||||
|
||||
tag = str(rel.get("tag_name") or "").strip()
|
||||
if not tag:
|
||||
return fail("Release 没有 tag")
|
||||
ver = re.sub(r"^[vV]", "", tag)
|
||||
|
||||
assets = rel.get("assets") or []
|
||||
apk = next((a for a in assets if str(a.get("name", "")).lower().endswith(".apk")), None)
|
||||
if apk:
|
||||
url = apk.get("browser_download_url") or ""
|
||||
size = apk.get("size") or 0
|
||||
name = apk.get("name") or "maipulse.apk"
|
||||
else:
|
||||
url = "%s/%s/releases/download/%s/maipulse.apk" % (UPDATE_SITE, UPDATE_REPO, tag)
|
||||
size = 0
|
||||
name = "maipulse.apk"
|
||||
return ok(current=APP_VERSION, remote=ver, newer=_v(ver) > _v(APP_VERSION),
|
||||
url=url, name=name, size=size, tag=tag,
|
||||
notes=(rel.get("body") or "")[:500],
|
||||
published=rel.get("published_at"),
|
||||
page="%s/%s/releases/tag/%s" % (UPDATE_SITE, UPDATE_REPO, tag),
|
||||
repo="%s/%s" % (UPDATE_REPO.split("/")[0], UPDATE_REPO.split("/")[-1]))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 配置
|
||||
|
||||
@@ -657,6 +657,12 @@
|
||||
<button class="ghost" style="margin:0" onclick="updateMaster()">更新曲库</button>
|
||||
<button class="ghost" style="margin:0;flex:0 0 auto" onclick="loadVersion()">刷新</button>
|
||||
</div>
|
||||
<div class="kv"><b>最新版本</b><span id="verRemote">—</span></div>
|
||||
<div class="row">
|
||||
<button class="ghost" style="margin:0" onclick="checkUpdate()">检查更新</button>
|
||||
<button class="ghost" style="margin:0;flex:0 0 auto;display:none" id="verDl" onclick="downloadUpdate()">下载安装</button>
|
||||
</div>
|
||||
<p class="desc" id="verNote" style="display:none"></p>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1284,6 +1290,31 @@ function importConf(file){
|
||||
/* ---------- 更新 ---------- *//* ---------- 更新 ---------- */
|
||||
const UP_SRC_KEY = 'maipulse.updatesrc';
|
||||
|
||||
|
||||
var _upd = null;
|
||||
async function checkUpdate(){
|
||||
var box = document.getElementById('verRemote');
|
||||
box.textContent = '检查中…';
|
||||
try{
|
||||
var r = await (await fetch('/api/update-check')).json();
|
||||
if(!r.ok){ box.textContent = '检查失败'; log('检查更新:' + r.error, 1); return; }
|
||||
_upd = r;
|
||||
box.textContent = r.remote + (r.newer ? '(有新版)' : '(已是最新)');
|
||||
var note = document.getElementById('verNote');
|
||||
if(r.notes){ note.style.display = ''; note.textContent = r.notes; }
|
||||
document.getElementById('verDl').style.display = r.newer ? '' : 'none';
|
||||
}catch(e){ box.textContent = '检查失败'; log('检查更新:' + e, 1); }
|
||||
}
|
||||
function downloadUpdate(){
|
||||
if(!_upd || !_upd.url){ log('没有可下载的地址', 1); return; }
|
||||
// 安卓壳里有原生下载器,没有就新开标签
|
||||
if(window.Maipulse && window.Maipulse.installApk){
|
||||
window.Maipulse.installApk(_upd.url);
|
||||
}else{
|
||||
window.open(_upd.url, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadVersion(){
|
||||
const r = await get('/api/version');
|
||||
if(!r.ok) return;
|
||||
|
||||
Reference in new issue
Block a user