首次提交: OnebotCatalog 项目代码与文档(含 NX 按需生成服务二期、后台、一键启动)

This commit is contained in:
wangruiguo
2026-09-03 17:55:45 +08:00
commit fafa86d3a6
241 changed files with 78656 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""
gen-manual-placeholders.py —— 为新系列生成手册占位文件夹与占位图片 (尺寸图窗口占位用)
背景: 老 11 个系列有手册拆页 (catalog\\manual\\<系列>\\pXX.png), 新 6 个系列
(UCBM/ULP/USP/UGP/UAGP/LAE) 暂无手册 → 尺寸图窗口空白。
本脚本为每个新系列生成一张占位 PNG (A4 比例, 灰框+系列代码大字) 并写入
series\\<系列>.meta.json 的 manual 附件引用。真实手册到货后用
render-manual-pages.py 重新拆页覆盖 p01.png 即可。
纯标准库实现 (zlib+struct 写 PNG, 5x7 点阵字体), 无任何第三方依赖。
用法: python gen-manual-placeholders.py
"""
import json
import os
import struct
import zlib
BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
MANUAL_DIR = os.path.join(BASE, 'catalog', 'manual')
SERIES_DIR = os.path.join(BASE, 'catalog', 'series')
NEW_SERIES = ['UCBM', 'ULP', 'USP', 'UGP', 'UAGP', 'LAE']
# 5x7 点阵字体 (每字符 7 字节, 位=像素)
FONT = {
'A': [0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11], 'B': [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E],
'C': [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E], 'D': [0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C],
'E': [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F], 'F': [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10],
'G': [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F], 'H': [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
'I': [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E], 'J': [0x07, 0x02, 0x02, 0x02, 0x12, 0x12, 0x0C],
'K': [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11], 'L': [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F],
'M': [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11], 'N': [0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11],
'O': [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], 'P': [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10],
'Q': [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D], 'R': [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11],
'S': [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E], 'T': [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
'U': [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E], 'V': [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04],
'W': [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11], 'X': [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
'Y': [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04], 'Z': [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F],
'0': [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E], '1': [0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E],
'2': [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F], '3': [0x1F, 0x02, 0x04, 0x06, 0x01, 0x11, 0x0E],
'4': [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02], '5': [0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E],
'6': [0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E], '7': [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08],
'8': [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E], '9': [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C],
'/': [0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10], '-': [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00],
'.': [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C], ':': [0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00],
' ': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
W, H = 794, 1123 # A4 @96dpi 近似
def draw_text(buf, text, x0, y0, scale, color):
"""把 text 按 5x7 点阵放大 scale 倍画进 buf (0xFFFFFF 背景)"""
for ch in text:
glyph = FONT.get(ch.upper(), FONT[' '])
for row in range(7):
bits = glyph[row]
for col in range(5):
if bits & (0x10 >> col):
for dy in range(scale):
for dx in range(scale):
px = (x0 + col * scale + dx) * 3
py = y0 + row * scale + dy
buf[py * W * 3 + px] = color[0]
buf[py * W * 3 + px + 1] = color[1]
buf[py * W * 3 + px + 2] = color[2]
x0 += 6 * scale
def make_png(path, series):
buf = bytearray([0xF5, 0xF6, 0xF8] * (W * H)) # 浅灰底
gray = (0x9A, 0xA2, 0xAC)
dark = (0x3A, 0x45, 0x52)
# 图纸外框 + 四角标
for x in range(40, W - 40):
for y in (38, 39, H - 40, H - 39):
i = (y * W + x) * 3
buf[i], buf[i + 1], buf[i + 2] = gray
for y in range(38, H - 38):
for x in (40, 41, W - 42, W - 41):
i = (y * W + x) * 3
buf[i], buf[i + 1], buf[i + 2] = gray
draw_text(buf, series, 60, 90, 10, dark) # 系列代码大字
draw_text(buf, 'DRAWING PENDING', 60, 230, 3, gray) # 占位说明
draw_text(buf, 'SERIES: ' + series, 60, 270, 2, gray)
draw_text(buf, 'REPLACE THIS PAGE', 60, 300, 2, gray)
draw_text(buf, 'WITH REAL MANUAL', 60, 324, 2, gray)
# 对角辅助线 (工程图纸感)
for t in range(0, 300):
for x in (t, t + 1):
i = ((90 + t) * W + 60 + x) * 3
buf[i], buf[i + 1], buf[i + 2] = 0xE2, 0xE6, 0xEA
# 写 PNG (无压缩优化, 标准库)
def chunk(tag, data):
c = struct.pack('>I', len(data)) + tag + data
c += struct.pack('>I', zlib.crc32(tag + data) & 0xFFFFFFFF)
return c
raw = b''.join(b'\x00' + bytes(buf[y * W * 3:(y + 1) * W * 3]) for y in range(H))
png = (b'\x89PNG\r\n\x1a\n'
+ chunk(b'IHDR', struct.pack('>IIBBBBB', W, H, 8, 2, 0, 0, 0))
+ chunk(b'IDAT', zlib.compress(raw, 6))
+ chunk(b'IEND', b''))
with open(path, 'wb') as f:
f.write(png)
def update_meta(series):
meta_path = os.path.join(SERIES_DIR, series + '.meta.json')
with open(meta_path, 'r', encoding='utf-8') as f:
obj = json.load(f)
obj['attachments'] = [{'kind': 'manual', 'lang': '', 'source': 'manual/%s/p01.png' % series, 'model': None}]
with open(meta_path, 'w', encoding='utf-8') as f:
json.dump(obj, f, ensure_ascii=False)
def main():
made = 0
for s in NEW_SERIES:
d = os.path.join(MANUAL_DIR, s)
if not os.path.isdir(d):
os.makedirs(d)
make_png(os.path.join(d, 'p01.png'), s)
update_meta(s)
made += 1
print('OK %s: manual/%s/p01.png + meta 附件引用' % (s, s))
print('完成 %d 个系列' % made)
print('提示: 真实手册到货后跑 render-manual-pages.py 覆盖, 或直接替换 p01.png 同名文件')
if __name__ == '__main__':
main()