启动器:游戏更新拆分为当前/历史双子页,新增关于我们页

- 游戏更新页改为「当前更新 / 历史更新」两个标签页,历史页显示条数
- 更新日志合并仓库 Release 与本地 更新日志.txt,按日期去重、仓库优先;断网时仅用本地记录
- 主导航新增「关于我们」,内容与游戏内控制台关于页一致(四行文案 + 赞助/公众号二维码)
- 新增 /qr/<name> 白名单路由读取启动器目录 qr\ 下图片,防止目录穿越
- 二维码支持点击放大(灯箱),图片缺失时显示占位而不是破图
- 打包 spec 补入 qr 三张图,静态响应统一 Cache-Control: no-store
This commit is contained in:
hanfuit committed 2026-11-05 16:05:00 +08:00
1 parent 1dd3775510
commit 6a4b7e58d3
8 files changed
+311 -36

No files matched your search

+124 -4
View File
@@ -16,6 +16,7 @@ import ctypes
import json
import os
import queue
import re
import shutil
import socket
import subprocess
@@ -145,6 +146,16 @@ SPONSOR_IMG = os.path.join(BASE_DIR, "sponsor.png")
if not os.path.exists(SPONSOR_IMG):
SPONSOR_IMG = os.path.join(RES_DIR, "sponsor.png")
# 关于我们页的三张二维码:优先 EXE 同目录 qr\(可自行替换),其次打包内资源
QR_FILES = {
"alipay.png": "image/png",
"wechat.png": "image/png",
"gzh.jpg": "image/jpeg",
}
# 断网时更新日志的兜底来源(EXE 同目录 / 服务端根目录的 更新日志.txt)
LOCAL_CHANGELOG_NAME = "更新日志.txt"
# 数据库与实例配置候选路径(基于运行目录解析出的 SERVER_ROOT)
def _instance_candidates():
out = []
@@ -373,6 +384,93 @@ def _release_summary(rel):
}
def _local_changelog():
"""断网时的兜底:解析本地 更新日志.txt,按日期切成若干条(最新在前)。
文件长这样(日期行之间是正文):
2026.11.05 更新:
1.xxx
2.xxx
"""
path = ""
for d in (BASE_DIR, SERVER_ROOT):
if not d:
continue
p = os.path.join(d, LOCAL_CHANGELOG_NAME)
if os.path.exists(p):
path = p
break
if not path:
return []
try:
with open(path, "r", encoding="utf-8", errors="ignore") as f:
lines = f.read().splitlines()
except Exception:
return []
items = []
cur = None
for ln in lines:
s = ln.strip()
m = re.match(r"^(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})", s)
if m:
if cur:
items.append(cur)
date = "%s.%s.%s" % (m.group(1), m.group(2).zfill(2), m.group(3).zfill(2))
cur = {
"tag": date,
"name": date + " 更新",
"body": "",
"published_at": "",
"prerelease": False,
"html_url": UPDATE_REPO_WEB,
"assets": [],
}
continue
if cur is not None and s:
cur["body"] = (cur["body"] + "\n" + s).strip()
if cur:
items.append(cur)
items.reverse() # 文件里是旧→新,接口要新→旧
return items
def _changelog_date_key(rel):
"""从 tag / name / 发布时间里抽 20261105 这样的排序键;取不到返回空串。"""
s = " ".join([rel.get("tag") or "", rel.get("name") or "", rel.get("published_at") or ""])
m = re.search(r"(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})", s)
if not m:
return ""
return "%s%02d%02d" % (m.group(1), int(m.group(2)), int(m.group(3)))
def _merge_changelog(remote, local):
"""仓库发布记录 + 本地更新日志合并:按日期去重(仓库优先,内容更全),新的在前。
仓库没跟上发版时,本地 更新日志.txt 里的新条目会自动出现在「当前更新」。
"""
out, seen = [], set()
for rel in remote or []:
k = _changelog_date_key(rel)
if k:
if k in seen:
continue
seen.add(k)
out.append(rel)
for rel in local or []:
k = _changelog_date_key(rel)
if k:
if k in seen:
continue
seen.add(k)
out.append(rel)
dated = [r for r in out if _changelog_date_key(r)]
undated = [r for r in out if not _changelog_date_key(r)]
dated.sort(key=_changelog_date_key, reverse=True)
return dated + undated
def flow_start_game(account_username=None):
"""一键开始游戏;account_username 为空时使用默认账号。"""
log("== 开始游戏 ==")
@@ -590,6 +688,8 @@ class Handler(BaseHTTPRequestHandler):
self._send_file(SPONSOR_IMG, "image/png")
else:
self._json({"error": "赞助二维码尚未提供"}, 404)
elif path.startswith("/qr/"):
self._send_qr(path[len("/qr/"):])
# 启动器接口
elif path == "/api/status":
self._api_status()
@@ -792,19 +892,39 @@ class Handler(BaseHTTPRequestHandler):
})
def _api_update_changelog(self, qs):
"""获取所有发布的更新日志。"""
"""获取所有发布的更新日志;连不上仓库时退回本地 更新日志.txt。"""
force = qs.get("force", ["0"])[0] == "1"
releases, err = _fetch_releases(force=force)
if err:
self._json({"ok": False, "error": err, "releases": []})
remote = [] if err else [_release_summary(r) for r in (releases or [])]
local = _local_changelog()
merged = _merge_changelog(remote, local)
if not merged:
self._json({"ok": False, "error": err or "没有任何更新记录", "releases": []})
return
self._json({
"ok": True,
"current": CURRENT_VERSION,
"releases": [_release_summary(r) for r in (releases or [])],
"releases": merged,
# local = 完全没连上仓库;merged = 仓库记录 + 本地补齐
"source": "remote" if remote else "local",
"local_count": len(local),
"warning": err,
"web_url": UPDATE_REPO_WEB,
})
def _send_qr(self, name):
"""关于我们页的二维码图片:EXE 同目录 qr\\ 优先,其次打包内资源。"""
ctype = QR_FILES.get(name)
if not ctype:
self._json({"error": "not found"}, 404)
return
for d in (os.path.join(BASE_DIR, "qr"), os.path.join(RES_DIR, "qr")):
p = os.path.join(d, name)
if os.path.exists(p):
self._send_file(p, ctype)
return
self._json({"error": "二维码未配置"}, 404)
def _api_logs(self):
logs = []
while True: