Files
tamabox/dev/gen_changelog.py
T
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

130 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
gen_changelog.py — 生成 PocketBase 风格的 changelogs JSON(无需 PocketBase 服务)
用途:
你在本地维护一个 Markdown 源文件(按 `## <时间戳>` 分段写 changelog),
本脚本把它导出成前端 change-logs.html 所 fetch 的 tamabox.changelogs.json。
输出格式完全兼容 PocketBase list API 的返回结构,但无需部署任何后端服务,
因此不会在你的 1C1G 服务器上多占哪怕 1 MB 常驻内存。
输出位置:static/tamabox.changelogs.json —— 该文件被 static 包以 //go:embed 打入
二进制,由 flamego.Static 在 /static 路径同源提供,前端直接 fetch 同源路径,
不再依赖任何外部域名(去掉 api-direct.337845818.best 等外链,降低可用性/安全风险)。
源文件格式(dev/changelog-source.md 示例):
## 2024-03-04 21:45:55
新增:用户私信功能上线。
修复:移动端问题列表间距过窄。
## 2024-02-29 00:00:55
优化:首页加载速度提升约 30%。
注意:
- 时间戳支持 `YYYY-MM-DD HH:MM:SS` / `YYYY-MM-DD` / 带毫秒的 `...fffZ` / ISO 格式,
脚本会统一归一化为 PocketBase 风格的 `YYYY-MM-DD HH:MM:SS.fffZ`(UTC)。
- 正文里的换行默认会被转成 `<br>`,因为前端用 x-html 渲染;传 --no-br 可关闭。
- 导出时按时间倒序排列,最新一条排在最前(前端读 items[0] 即取最新)。
用法:
python dev/gen_changelog.py
python dev/gen_changelog.py 源文件.md 输出.json
python dev/gen_changelog.py 源文件.md 输出.json --no-br
"""
import json
import re
import sys
from datetime import datetime, timezone
COLLECTION_ID = "tamabox"
COLLECTION_NAME = "tamabox-changelogs"
DEFAULT_SRC = "dev/changelog-source.md"
DEFAULT_OUT = "static/tamabox.changelogs.json"
TS_LINE = re.compile(r"^##\s+(.+?)\s*$")
FULL_TS = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}Z$")
def normalize_ts(raw: str) -> str:
"""把用户输入的时间归一化为 PocketBase 风格时间戳:YYYY-MM-DD HH:MM:SS.fffZ(UTC)。"""
raw = raw.strip()
if FULL_TS.match(raw):
return raw
for fmt in ("%Y-%m-%d %H:%M:%S.%fZ", "%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M", "%Y-%m-%d"):
try:
dt = datetime.strptime(raw, fmt)
return dt.strftime("%Y-%m-%d %H:%M:%S") + ".000Z"
except ValueError:
continue
try:
dt = datetime.fromisoformat(raw.replace("Z", "+00:00"))
return dt.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + ".000Z"
except ValueError:
sys.exit(f"[错误] 无法解析时间戳: {raw!r}")
def parse_source(text: str):
"""按 `## <时间戳>` 分段,返回 [(ts, content), ...],顺序与源文件一致。"""
entries, cur_ts, buf = [], None, []
for line in text.splitlines():
m = TS_LINE.match(line)
if m:
if cur_ts is not None:
entries.append((cur_ts, "\n".join(buf).strip()))
cur_ts, buf = m.group(1).strip(), []
elif cur_ts is not None:
buf.append(line)
if cur_ts is not None:
entries.append((cur_ts, "\n".join(buf).strip()))
if not entries:
sys.exit("[错误] 源文件未找到任何 '## <时间戳>' 分段")
return entries
def build(entries, br=True):
"""把条目导出为 PocketBase list API 风格 JSON(最新在前)。"""
parsed = [(normalize_ts(ts), c) for ts, c in entries]
parsed.sort(key=lambda x: x[0], reverse=True) # 倒序:最新 published_at 在前
items = []
for ts, content in parsed:
items.append({
"collectionId": COLLECTION_ID,
"collectionName": COLLECTION_NAME,
"content": content.replace("\n", "<br>\n") if br else content,
"created": ts,
"id": "none",
"published_at": ts,
"updated": ts,
})
return {
"page": 1,
"perPage": len(items) or 1,
"totalItems": len(items),
"totalPages": 1,
"items": items,
}
def main():
args = sys.argv[1:]
br = "--no-br" not in args
paths = [a for a in args if not a.startswith("--")]
src = paths[0] if len(paths) > 0 else DEFAULT_SRC
out = paths[1] if len(paths) > 1 else DEFAULT_OUT
try:
with open(src, encoding="utf-8") as f:
text = f.read()
except FileNotFoundError:
sys.exit(f"[错误] 找不到源文件: {src}")
data = build(parse_source(text), br=br)
with open(out, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
latest = data["items"][0]["published_at"] if data["items"] else "N/A"
print(f"[完成] 已生成 {out}({data['totalItems']} 条记录,最新发布于 {latest})")
if __name__ == "__main__":
main()