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
+88
-21
No files matched your search
@@ -11,17 +11,17 @@ MaimaiNet.py —— 舞萌DX 网络核心层(纯 HTTP 复刻)
|
|||||||
|
|
||||||
1) 服务器基址与 API 路径
|
1) 服务器基址与 API 路径
|
||||||
source/Assembly-CSharp/Manager/OperationManager.cs:225,338
|
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
|
source/Assembly-CSharp/Net.Packet/Packet.cs:116
|
||||||
Client = NetHttpClient.Create(BaseUrl + Obfuscator(query.Api))
|
Client = NetHttpClient.Create(BaseUrl + Obfuscator(query.Api))
|
||||||
source/Assembly-CSharp/Net.Packet/Packet.cs:485
|
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
|
source/Assembly-CSharp/Net.VO/NetQuery.cs:14
|
||||||
Api = api + "MaimaiChn"
|
Api = api + "MaimaiChn"
|
||||||
|
|
||||||
=> 最终 URL = BASE_URL + md5(逻辑接口名 + "MaimaiChn" + "<REDACTED>")
|
=> 最终 URL = BASE_URL + md5(逻辑接口名 + "MaimaiChn" + "<OBFUSCATE_PARAM>")
|
||||||
|
|
||||||
已用真实日志验证:md5("GetGameSettingApiMaimaiChn" + "<REDACTED>")
|
已用真实日志验证:md5("GetGameSettingApiMaimaiChn" + "<OBFUSCATE_PARAM>")
|
||||||
= 83a5d5a20b062c5c2ad817460b8f7f76
|
= 83a5d5a20b062c5c2ad817460b8f7f76
|
||||||
(source/2026-10-05.log:1 的请求就是打到这个尾段)
|
(source/2026-10-05.log:1 的请求就是打到这个尾段)
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ from config import CLIENT_ID, MAI_ENCODING, TIMEOUT_LONG_MS, TIMEOUT_MS
|
|||||||
|
|
||||||
#: 游戏服务器(Maimai2Servlet)
|
#: 游戏服务器(Maimai2Servlet)
|
||||||
#: 依据 source/Assembly-CSharp/Manager/OperationManager.cs:225,338
|
#: 依据 source/Assembly-CSharp/Manager/OperationManager.cs:225,338
|
||||||
#: _operationData.ServerUri = Auth.GameServerUri + MovieServerUri("<REDACTED>/")
|
#: _operationData.ServerUri = Auth.GameServerUri + MovieServerUri("<OBFUSCATE_PARAM>/")
|
||||||
BASE_URL = ""
|
BASE_URL = ""
|
||||||
|
|
||||||
#: AES 参数,依据 source/Assembly-CSharp/Net/CipherAES.cs:11-12
|
#: 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)。
|
对逻辑接口名做 MD5 混淆,得到 URL 尾段(对应 Packet.Obfuscator)。
|
||||||
|
|
||||||
逻辑名 "GetUserPreviewApi" -> "GetUserPreviewApiMaimaiChn"
|
逻辑名 "GetUserPreviewApi" -> "GetUserPreviewApiMaimaiChn"
|
||||||
-> md5(上面 + "<REDACTED>")
|
-> md5(上面 + "<OBFUSCATE_PARAM>")
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
api_name: 逻辑接口名,**不要**自己带 MaimaiChn 后缀
|
api_name: 逻辑接口名,**不要**自己带 MaimaiChn 后缀
|
||||||
|
|||||||
+51
-15
@@ -912,36 +912,72 @@ def api_update_gh():
|
|||||||
repo=repo)
|
repo=repo)
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE_SITE = "https://git.fragrance.moe"
|
||||||
|
UPDATE_REPO = "shouko/maipulse"
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/update-check")
|
@app.get("/api/update-check")
|
||||||
@guarded
|
|
||||||
def api_update_check():
|
def api_update_check():
|
||||||
"""拉远端 version.json 与本地比较。远端格式:
|
"""检查更新。默认走自建 Gitea 的 Release(公开仓库,不用 token)。
|
||||||
{"version": "1.0.1", "url": "下载地址", "notes": "更新说明", "published": "..."}
|
|
||||||
|
asset 用 browser_download_url —— 那是 /releases/download/<tag>/<name> 的稳定地址,
|
||||||
|
比 attachments/uuid 可靠。给了 source 参数则按老的远端 version.json 走。
|
||||||
"""
|
"""
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
src = (request.args.get("source") or "").strip()
|
src = (request.args.get("source") or "").strip()
|
||||||
if not src:
|
|
||||||
return fail("还没填更新源地址")
|
def _v(text):
|
||||||
|
nums = [int(x) for x in re.findall(r"\d+", str(text))[:3]]
|
||||||
|
while len(nums) < 3:
|
||||||
|
nums.append(0)
|
||||||
|
return tuple(nums)
|
||||||
|
|
||||||
|
if src:
|
||||||
if not re.match(r"^https?://", src):
|
if not re.match(r"^https?://", src):
|
||||||
return fail("更新源必须是 http(s) 地址")
|
return fail("更新源必须是 http(s) 地址")
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(src, headers={"User-Agent": "maipulse/" + APP_VERSION})
|
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(urllib.request.urlopen(req, timeout=20).read().decode("utf-8"))
|
||||||
remote = json.loads(raw)
|
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
return fail("拉取更新源失败:%s" % exc)
|
return fail("拉取更新源失败:%s" % exc)
|
||||||
|
|
||||||
ver = str(remote.get("version") or "")
|
ver = str(remote.get("version") or "")
|
||||||
if not ver:
|
if not ver:
|
||||||
return fail("更新源里没有 version 字段")
|
return fail("更新源里没有 version 字段")
|
||||||
|
return ok(current=APP_VERSION, remote=ver, newer=_v(ver) > _v(APP_VERSION),
|
||||||
def _v(text):
|
|
||||||
return tuple(int(x) for x in re.findall(r"\d+", text)[:3])
|
|
||||||
|
|
||||||
newer = _v(ver) > _v(APP_VERSION)
|
|
||||||
return ok(current=APP_VERSION, remote=ver, newer=newer,
|
|
||||||
url=remote.get("url"), notes=remote.get("notes"),
|
url=remote.get("url"), notes=remote.get("notes"),
|
||||||
published=remote.get("published"))
|
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" onclick="updateMaster()">更新曲库</button>
|
||||||
<button class="ghost" style="margin:0;flex:0 0 auto" onclick="loadVersion()">刷新</button>
|
<button class="ghost" style="margin:0;flex:0 0 auto" onclick="loadVersion()">刷新</button>
|
||||||
</div>
|
</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>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
@@ -1284,6 +1290,31 @@ function importConf(file){
|
|||||||
/* ---------- 更新 ---------- *//* ---------- 更新 ---------- */
|
/* ---------- 更新 ---------- *//* ---------- 更新 ---------- */
|
||||||
const UP_SRC_KEY = 'maipulse.updatesrc';
|
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(){
|
async function loadVersion(){
|
||||||
const r = await get('/api/version');
|
const r = await get('/api/version');
|
||||||
if(!r.ok) return;
|
if(!r.ok) return;
|
||||||
|
|||||||
Reference in new issue
Block a user