diff --git a/.gitignore b/.gitignore index 56d7dc2..d59b8ba 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ public themes/hugo-book/** resources/_gen *.log -assets/items/*.png \ No newline at end of file +assets/items/*.png +__pycache__/ diff --git a/Makefile b/Makefile index 741bb61..2eea786 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,18 @@ -# SFCraft 文档 · 构建 / 开发入口 (跨平台) +# SFCraft 文档 · 构建 / 开发入口 (跨平台, 脚本为 Python 3) # -# Linux / macOS: make build / make dev / make sync -# Windows(有 make 时): 同样可用, 自动调用 PowerShell 脚本 +# make build / make dev / make sync / make help # # 可附加 hugo 参数: make dev HUGO_ARGS="--port 1314" +# 可指定解释器: make PY=python3 build .PHONY: help sync build dev HUGO_ARGS ?= ifeq ($(OS),Windows_NT) - PS := powershell -NoProfile -ExecutionPolicy Bypass -File - BUILD_CMD := $(PS) scripts/build.ps1 - DEV_CMD := $(PS) scripts/dev.ps1 - SYNC_CMD := $(PS) scripts/sync-items.ps1 + PY ?= python else - BUILD_CMD := ./scripts/build.sh - DEV_CMD := ./scripts/dev.sh - SYNC_CMD := ./scripts/sync-items.sh + PY ?= python3 endif help: @@ -30,10 +25,10 @@ help: @echo 可附加 hugo 参数: make dev HUGO_ARGS="--port 1314" sync: - $(SYNC_CMD) + $(PY) scripts/sync-items.py build: - $(BUILD_CMD) $(HUGO_ARGS) + $(PY) scripts/build.py $(HUGO_ARGS) dev: - $(DEV_CMD) $(HUGO_ARGS) + $(PY) scripts/dev.py $(HUGO_ARGS) diff --git a/assets/items/README.md b/assets/items/README.md index eb92d39..83e420f 100644 --- a/assets/items/README.md +++ b/assets/items/README.md @@ -12,7 +12,7 @@ ## 自动同步(推荐) -构建 / 开发时由 `scripts/sync-items.ps1` 自动维护本目录: +构建 / 开发时由 `scripts/sync-items.py` 自动维护本目录: - sfcraft 自定义贴图(`amethyst_cauldron_blank`、`exp_totem`、`lunch_box`、`pearl_token` 等) 从 [sfcraft 仓库](https://github.com/saltedfishclub/sfcraft/tree/rev/26.2/src/main/resources/assets/sfcraft/textures/item) @@ -20,7 +20,7 @@ - 原版物品(`diamond`、`stick` 等)在本地缺失时从 Minecraft wiki 自动获取; - 手动放入的贴图不会被覆盖;找不到的贴图会报错并列出物品 id,阻止构建 / 开发。 -请使用 `scripts\build.ps1` / `scripts\dev.ps1` 而非直接运行 `hugo`,详见 `scripts/README.md`。 +请使用 `python scripts\build.py` / `python scripts\dev.py` 而非直接运行 `hugo`,详见 `scripts/README.md`。 ## 组件用法 diff --git a/scripts/README.md b/scripts/README.md index 7a4bcd8..a809637 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -2,50 +2,50 @@ ## 用途 -`sync-items.ps1` 会在构建和开发前自动保证物品贴图齐全: +`scripts/` 下全部为 Python 3 脚本(无其他语言): + +- `sync-items.py` —— 物品贴图自动同步(核心); +- `build.py` —— 同步贴图 + hugo 构建; +- `dev.py` —— 同步贴图 + hugo 开发服务器。 + +同步流程: 1. 下载 [sfcraft 仓库](https://github.com/saltedfishclub/sfcraft/tree/rev/26.2/src/main/resources/assets/sfcraft/textures/item) 的 全部自定义贴图到 `assets/items/`; -2. 扫描 `content/` 中所有 `crafting` 组件用到的物品 id; -3. 本地缺失的原版物品从 Minecraft wiki 自动获取(自动挑选最新版本渲染); +2. 扫描 `content/` 中所有 `crafting` 组件用到的物品 id(支持九格直填与 pattern 别名两种写法); +3. 本地缺失的原版物品从 Minecraft wiki 自动获取(自动挑选最新版本渲染); 4. 有任何物品无法找到时, 打印缺失清单并以非零码退出, 阻断构建 / 开发启动。 ## 使用 -```powershell -.\scripts\build.ps1 # 同步贴图 + hugo 构建 -.\scripts\dev.ps1 # 同步贴图 + hugo server -.\scripts\dev.ps1 -NoSync # 仅启动开发服务器, 不重新同步 -``` - -若 PowerShell 执行策略阻止脚本, 请用: +Windows: ```powershell -powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 +python scripts\build.py # 同步贴图 + hugo 构建 +python scripts\dev.py # 同步贴图 + hugo server +python scripts\dev.py --no-sync # 仅启动开发服务器, 不重新同步 +python scripts\sync-items.py -f # 仅同步贴图(强制刷新 sfcraft 贴图) ``` -脚本通过 `$env:HUGO` 或 PATH 查找 hugo, 找不到时也会探测 winget 安装位置。 - -## Linux / macOS 使用 - -Unix 环境下使用同名的 bash 脚本(同步逻辑为 Python 3 实现, 需要 `python3` 与 `hugo`): +Linux / macOS: ```bash -./scripts/build.sh # 同步贴图 + hugo 构建 -./scripts/dev.sh # 同步贴图 + hugo server -./scripts/dev.sh -NoSync # 仅启动开发服务器 +python3 scripts/build.py +python3 scripts/dev.py +python3 scripts/dev.py --no-sync ``` -也可以使用 Makefile(Windows / Unix 通用, 自动选择对应脚本): +也可以用 Makefile(Windows / Unix 通用): ```bash make build # 同步贴图 + hugo 构建 make dev # 同步贴图 + hugo server make sync # 仅同步贴图 make dev HUGO_ARGS="--port 1314" +make PY=python build # 指定 Python 解释器 ``` -首次使用前请确认脚本有执行权限: `chmod +x scripts/*.sh`。 +脚本按 `$HUGO` 环境变量、PATH、winget 安装目录的顺序查找 hugo。 ## 组件写法 @@ -62,7 +62,7 @@ make dev HUGO_ARGS="--port 1314" - 原版贴图: 只在本地缺失时下载, **不会覆盖**手动放入 `assets/items/` 的贴图; - 物品 id 即 `assets/items/` 下的文件名(不含 `.png`); - wiki 命名规则: `<英文名>_JE<版本>[_BE<版本>].png`, 特殊命名(如 `TNT`、`Flint_and_Steel`) - 由 `sync-items.ps1` / `sync-items.py` 中的变体生成与别名表处理。 + 由 `sync-items.py` 中的变体生成与别名表处理。 ## 网络要求 diff --git a/scripts/build.ps1 b/scripts/build.ps1 deleted file mode 100644 index 60b06b5..0000000 --- a/scripts/build.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -<# - SFCraft 文档构建入口: 先同步物品贴图, 再执行 hugo 构建。 - 贴图同步失败(有物品找不到)时终止构建。 - 用法: powershell -ExecutionPolicy Bypass -File scripts\build.ps1 [hugo 参数...] -#> -[CmdletBinding()] -param( - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$HugoArgs -) - -$ErrorActionPreference = 'Stop' -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path - -function Get-HugoPath { - if ($env:HUGO) { return $env:HUGO } - $cmd = Get-Command hugo -ErrorAction SilentlyContinue - if ($cmd) { return $cmd.Source } - $p = Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') -Recurse -Filter hugo.exe -ErrorAction SilentlyContinue | - Sort-Object LastWriteTime -Descending | Select-Object -First 1 - if ($p) { return $p.FullName } - throw '未找到 Hugo。请安装 Hugo, 或将 hugo 可执行文件路径写入环境变量 HUGO。' -} - -Write-Host "`n[1/2] 同步物品贴图" -ForegroundColor Cyan -& (Join-Path $scriptDir 'sync-items.ps1') -if ($LASTEXITCODE -ne 0) { - Write-Host "[构建终止] 物品贴图同步失败, 未执行 hugo 构建。" -ForegroundColor Red - exit $LASTEXITCODE -} - -Write-Host "`n[2/2] hugo 构建" -ForegroundColor Cyan -$hugo = Get-HugoPath -& $hugo @HugoArgs -exit $LASTEXITCODE diff --git a/scripts/build.py b/scripts/build.py new file mode 100644 index 0000000..79f961b --- /dev/null +++ b/scripts/build.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""SFCraft 文档构建入口: 先同步物品贴图, 再执行 hugo 构建。 + +贴图同步失败(有物品找不到)时终止构建。 + +用法: + python scripts/build.py [hugo 参数...] +""" + +import os +import subprocess +import sys +from pathlib import Path + +SCRIPTS = Path(__file__).resolve().parent + + +def find_hugo(): + """按 HUGO 环境变量、PATH、winget 安装目录依次查找 hugo。""" + env = os.environ.get("HUGO") + if env and Path(env).is_file(): + return env + + name = "hugo.exe" if os.name == "nt" else "hugo" + for d in os.environ.get("PATH", "").split(os.pathsep): + if d: + cand = Path(d) / name + if cand.is_file(): + return str(cand) + + local = os.environ.get("LOCALAPPDATA") + if local: + base = Path(local) / "Microsoft" / "WinGet" / "Packages" + if base.is_dir(): + for p in sorted(base.glob("*/hugo.exe")): + return str(p) + return None + + +def main(): + print("\n[1/2] 同步物品贴图") + r = subprocess.run([sys.executable, str(SCRIPTS / "sync-items.py")]) + if r.returncode != 0: + print("[构建终止] 物品贴图同步失败, 未执行 hugo 构建。", file=sys.stderr) + sys.exit(r.returncode) + + hugo = find_hugo() + if not hugo: + print( + "[错误] 未找到 hugo, 请安装 Hugo、将其加入 PATH, 或设置环境变量 HUGO。", + file=sys.stderr, + ) + sys.exit(1) + + print("\n[2/2] hugo 构建") + sys.exit(subprocess.run([hugo, *sys.argv[1:]]).returncode) + + +if __name__ == "__main__": + main() diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index 1f62105..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# SFCraft 文档构建入口 (Linux / macOS / Unix): 先同步贴图, 再执行 hugo 构建。 -# 贴图同步失败(有物品找不到)时终止构建。 -# 用法: ./scripts/build.sh [hugo 参数...] -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -echo -echo "[1/2] 同步物品贴图" -"$DIR/sync-items.sh" - -if ! command -v hugo >/dev/null 2>&1; then - HUGO_BIN="" - if [ -n "${HUGO:-}" ] && [ -x "$HUGO" ]; then - HUGO_BIN="$HUGO" - elif [ -n "${LOCALAPPDATA:-}" ]; then - HUGO_BIN="$(find "$LOCALAPPDATA/Microsoft/WinGet/Packages" -maxdepth 3 -name hugo.exe 2>/dev/null | head -n 1)" - fi - if [ -z "$HUGO_BIN" ]; then - echo "[错误] 未找到 hugo, 请安装 Hugo、将其加入 PATH, 或设置环境变量 HUGO。" >&2 - exit 1 - fi -else - HUGO_BIN="$(command -v hugo)" -fi - -echo -echo "[2/2] hugo 构建" -exec "$HUGO_BIN" "$@" diff --git a/scripts/dev.ps1 b/scripts/dev.ps1 deleted file mode 100644 index 6e0d9ae..0000000 --- a/scripts/dev.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -<# - SFCraft 文档开发服务器入口: 先同步物品贴图, 再启动 hugo server。 - 贴图同步失败(有物品找不到)时终止启动。 - 用法: powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 [-NoSync] [hugo server 参数...] -#> -[CmdletBinding()] -param( - [switch]$NoSync, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$HugoArgs -) - -$ErrorActionPreference = 'Stop' -$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path - -function Get-HugoPath { - if ($env:HUGO) { return $env:HUGO } - $cmd = Get-Command hugo -ErrorAction SilentlyContinue - if ($cmd) { return $cmd.Source } - $p = Get-ChildItem -Path (Join-Path $env:LOCALAPPDATA 'Microsoft\WinGet\Packages') -Recurse -Filter hugo.exe -ErrorAction SilentlyContinue | - Sort-Object LastWriteTime -Descending | Select-Object -First 1 - if ($p) { return $p.FullName } - throw '未找到 Hugo。请安装 Hugo, 或将 hugo 可执行文件路径写入环境变量 HUGO。' -} - -if (-not $NoSync) { - Write-Host "`n[1/2] 同步物品贴图" -ForegroundColor Cyan - & (Join-Path $scriptDir 'sync-items.ps1') - if ($LASTEXITCODE -ne 0) { - Write-Host "[启动终止] 物品贴图同步失败, 未启动 hugo server。" -ForegroundColor Red - exit $LASTEXITCODE - } -} else { - Write-Host "[跳过] 已指定 -NoSync, 不执行贴图同步" -ForegroundColor Yellow -} - -Write-Host "`n[2/2] 启动 hugo server" -ForegroundColor Cyan -$hugo = Get-HugoPath -$serverArgs = @('server') -if ($HugoArgs.Count -gt 0) { $serverArgs += $HugoArgs } -& $hugo @serverArgs -exit $LASTEXITCODE diff --git a/scripts/dev.py b/scripts/dev.py new file mode 100644 index 0000000..fb6a73e --- /dev/null +++ b/scripts/dev.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""SFCraft 文档开发服务器入口: 先同步物品贴图, 再启动 hugo server。 + +贴图同步失败(有物品找不到)时终止启动。 + +用法: + python scripts/dev.py [--no-sync] [hugo server 参数...] +""" + +import argparse +import os +import subprocess +import sys +from pathlib import Path + +SCRIPTS = Path(__file__).resolve().parent + + +def find_hugo(): + """按 HUGO 环境变量、PATH、winget 安装目录依次查找 hugo。""" + env = os.environ.get("HUGO") + if env and Path(env).is_file(): + return env + + name = "hugo.exe" if os.name == "nt" else "hugo" + for d in os.environ.get("PATH", "").split(os.pathsep): + if d: + cand = Path(d) / name + if cand.is_file(): + return str(cand) + + local = os.environ.get("LOCALAPPDATA") + if local: + base = Path(local) / "Microsoft" / "WinGet" / "Packages" + if base.is_dir(): + for p in sorted(base.glob("*/hugo.exe")): + return str(p) + return None + + +def main(): + parser = argparse.ArgumentParser(description="SFCraft 开发服务器") + parser.add_argument( + "--no-sync", "-NoSync", action="store_true", + help="跳过贴图同步", + ) + args, rest = parser.parse_known_args() + + if not args.no_sync: + print("\n[1/2] 同步物品贴图") + r = subprocess.run([sys.executable, str(SCRIPTS / "sync-items.py")]) + if r.returncode != 0: + print("[启动终止] 物品贴图同步失败, 未启动 hugo server。", file=sys.stderr) + sys.exit(r.returncode) + else: + print("[跳过] 已指定 --no-sync, 不执行贴图同步") + + hugo = find_hugo() + if not hugo: + print( + "[错误] 未找到 hugo, 请安装 Hugo、将其加入 PATH, 或设置环境变量 HUGO。", + file=sys.stderr, + ) + sys.exit(1) + + print("\n[2/2] 启动 hugo server") + sys.exit(subprocess.run([hugo, "server", *rest]).returncode) + + +if __name__ == "__main__": + main() diff --git a/scripts/dev.sh b/scripts/dev.sh deleted file mode 100755 index 0635200..0000000 --- a/scripts/dev.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash -# SFCraft 文档开发服务器入口 (Linux / macOS / Unix): 先同步贴图, 再启动 hugo server。 -# 贴图同步失败(有物品找不到)时终止启动。 -# 用法: ./scripts/dev.sh [-NoSync] [hugo server 参数...] -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -NOSYNC=0 -HUGO_ARGS=() -for arg in "$@"; do - case "$arg" in - -NoSync|--no-sync|-nosync) - NOSYNC=1 - ;; - *) - HUGO_ARGS+=("$arg") - ;; - esac -done - -if [ "$NOSYNC" -eq 0 ]; then - echo - echo "[1/2] 同步物品贴图" - "$DIR/sync-items.sh" -else - echo "[跳过] 已指定 -NoSync, 不执行贴图同步" -fi - -if ! command -v hugo >/dev/null 2>&1; then - HUGO_BIN="" - if [ -n "${HUGO:-}" ] && [ -x "$HUGO" ]; then - HUGO_BIN="$HUGO" - elif [ -n "${LOCALAPPDATA:-}" ]; then - HUGO_BIN="$(find "$LOCALAPPDATA/Microsoft/WinGet/Packages" -maxdepth 3 -name hugo.exe 2>/dev/null | head -n 1)" - fi - if [ -z "$HUGO_BIN" ]; then - echo "[错误] 未找到 hugo, 请安装 Hugo、将其加入 PATH, 或设置环境变量 HUGO。" >&2 - exit 1 - fi -else - HUGO_BIN="$(command -v hugo)" -fi - -echo -echo "[2/2] 启动 hugo server" -exec "$HUGO_BIN" server "${HUGO_ARGS[@]}" diff --git a/scripts/sync-items.ps1 b/scripts/sync-items.ps1 deleted file mode 100644 index 3fbe5a5..0000000 --- a/scripts/sync-items.ps1 +++ /dev/null @@ -1,248 +0,0 @@ -<# - SFCraft 文档 · 物品贴图自动同步 - - 流程: - 1. 将 sfcraft 仓库 textures/item 目录下所有贴图同步到 assets/items - 2. 扫描 content 中所有 crafting 组件, 收集用到的物品 id - 3. 对本地缺失的 id, 从 Minecraft wiki 自动获取(按命名规则挑选最新版本) - 4. 仍有无法获取的 id 时, 列出并退出(返回码 1), 阻断 build / dev - - 用法: - powershell -ExecutionPolicy Bypass -File scripts\sync-items.ps1 [-Force] - - 说明: - - sfcraft 自定义贴图每次与远端比对大小, 缺失或变更时更新 - - 原版贴图只在本地缺失时下载, 不会覆盖手动放入 assets/items 的贴图 - - 需要联网访问 api.github.com / zh.minecraft.wiki -#> -[CmdletBinding()] -param( - [switch]$Force -) - -$ErrorActionPreference = 'Stop' - -$repoRoot = Split-Path -Parent $PSScriptRoot -$itemsDir = Join-Path $repoRoot 'assets\items' -$contentDir = Join-Path $repoRoot 'content' -$ua = 'sfcraft-docs-sync/1.0 (https://github.com/saltedfishclub/sfcraft-docs)' - -New-Item -ItemType Directory -Path $itemsDir -Force | Out-Null - -# ---------- 数据源 ---------- -$sfOwner = 'saltedfishclub' -$sfRepo = 'sfcraft' -$sfRef = 'rev/26.2' -$sfDir = 'src/main/resources/assets/sfcraft/textures/item' -$sfListUrl = "https://api.github.com/repos/$sfOwner/$sfRepo/contents/$sfDir`?ref=$sfRef" -$wikiApi = 'https://zh.minecraft.wiki/api.php' -$wikiImg = 'https://zh.minecraft.wiki/images/' - -function Write-Step([string]$s) { Write-Host "`n== $s" -ForegroundColor Cyan } - -function Invoke-Get([string]$url, [int]$maxRetry = 2) { - for ($i = 0; ; $i++) { - try { - return Invoke-RestMethod -Uri $url -Headers @{ 'User-Agent' = $ua } -TimeoutSec 30 - } catch { - if ($i -ge $maxRetry) { throw } - Start-Sleep -Milliseconds 800 - } - } -} - -# ---------- 1. sfcraft 自定义贴图 ---------- -Write-Step '1/4 同步 sfcraft 自定义贴图' -$sfCount = 0 -$sfNew = 0 -try { - $list = Invoke-Get $sfListUrl - foreach ($f in $list) { - $target = Join-Path $itemsDir $f.name - $need = $Force -or - -not (Test-Path -LiteralPath $target) -or - ((Get-Item -LiteralPath $target).Length -ne [int64]$f.size) - if ($need) { - Invoke-WebRequest -Uri $f.download_url -Headers @{ 'User-Agent' = $ua } -OutFile $target -TimeoutSec 60 - $sfNew++ - } - $sfCount++ - } - Write-Host " $sfCount 个文件, 本次更新 $sfNew 个" -} catch { - Write-Host " [警告] 无法访问 GitHub, 跳过 sfcraft 贴图同步: $($_.Exception.Message)" -ForegroundColor Yellow -} - -# ---------- 2. 扫描 crafting 组件 ---------- -Write-Step '2/4 扫描组件物品 id' -$ids = @{} -$shortcodePat = [regex]'(?s){{<[ ]*crafting\b(.*?)}}' -$pairPat = [regex]'(\w+)="([^"]*)"' -$slotKeys = @('a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3', 'out') - -function Add-ItemId([string]$raw) { - $v = $raw.Trim() - if ($v.Length -gt 0) { - $id = ($v -split ':')[0].Trim() - if ($id.Length -gt 0) { $ids[$id] = $true } - } -} - -Get-ChildItem -Path $contentDir -Recurse -Filter '*.md' -File | ForEach-Object { - $text = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8 - foreach ($m in $shortcodePat.Matches($text)) { - $params = @{} - foreach ($p in $pairPat.Matches($m.Groups[1].Value)) { $params[$p.Groups[1].Value] = $p.Groups[2].Value } - foreach ($key in $slotKeys) { - if ($params.ContainsKey($key)) { Add-ItemId $params[$key] } - } - if ($params.ContainsKey('pattern')) { - $pat = $params['pattern'] -replace '\\n|;|\r?\n', ' ' - foreach ($tok in @($pat -split '\s+' | Where-Object { $_ })) { - if ($tok -eq '.') { continue } - if ($params.ContainsKey($tok)) { Add-ItemId $params[$tok] } - elseif ($tok -match '^[a-z0-9_]+$') { Add-ItemId $tok } - } - } - } -} - -$all = @($ids.Keys | Sort-Object) -$need = @($all | Where-Object { -not (Test-Path -LiteralPath (Join-Path $itemsDir "${_}.png")) }) -Write-Host " 使用 $($all.Count) 个物品 id, 本地已有 $($all.Count - $need.Count) 个" - -# ---------- 3. 从 Minecraft wiki 获取缺失贴图 ---------- -Write-Step '3/4 从 Minecraft wiki 获取缺失贴图' - -function Get-WikiCandidates([string]$name) { - $regex = '^' + [regex]::Escape($name) + '(_\(item\))?_JE\d+(\.\d+)*(_BE\d+)?\.png$' - # 两个查询互补: - # _ 宽前缀, 覆盖 (item) 等变体, 如 Diamond_Boots_(item)_JE3_BE2.png - # _JE 窄前缀, 在宽前缀因数量被截断时兜底(裸物品如 diamond 的 JE 渲染排序靠前) - $urls = @( - "$wikiApi`?action=query&list=allimages&aiprefix=$([uri]::EscapeDataString($name + '_'))&ailimit=500&format=json", - "$wikiApi`?action=query&list=allimages&aiprefix=$([uri]::EscapeDataString($name + '_JE'))&ailimit=200&format=json" - ) - $seen = @{} - $all = New-Object System.Collections.ArrayList - foreach ($u in $urls) { - try { - $r = Invoke-Get $u - if ($null -ne $r.query.allimages) { - foreach ($n in $r.query.allimages) { - if (-not $seen.ContainsKey($n.name)) { - [void]$seen.Add($n.name, $true) - [void]$all.Add($n.name) - } - } - } - } catch { - Write-Host " [警告] wiki 查询失败 ($name): $($_.Exception.Message)" -ForegroundColor Yellow - } - Start-Sleep -Milliseconds 250 - } - return @($all | Where-Object { $_ -match $regex }) -} - -function Select-BestCandidate([string]$name, [string[]]$candidates) { - $regex = '^' + [regex]::Escape($name) + '(_\(item\))?_JE(\d+)(\.\d+)*(_BE(\d+))?\.png$' - $best = $null - $bestKey = $null - foreach ($c in $candidates) { - $m = [regex]::Match($c, $regex) - if (-not $m.Success) { continue } - $key = @( - $(if ($m.Groups[1].Success) { 1 } else { 0 }), # 优先物品图标 (item) - [int]$m.Groups[2].Value, # JE 版本 - $(if ($m.Groups[5].Success) { [int]$m.Groups[5].Value } else { -1 }) # BE 版本 - ) - if ($null -eq $bestKey) { $best = $c; $bestKey = $key; continue } - for ($i = 0; $i -lt 3; $i++) { - if ($key[$i] -ne $bestKey[$i]) { - if ($key[$i] -gt $bestKey[$i]) { $best = $c; $bestKey = $key } - break - } - } - } - return $best -} - -function Get-NameVariants([string]$id) { - $words = @($id -split '_' | Where-Object { $_ }) - $title = @($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join '_' - - $connectors = @('and', 'of', 'the', 'for', 'with', 'to', 'from', 'by', 'in', 'on', 'at') - $mixed = @($words | ForEach-Object { - if ($connectors -contains $_) { $_ } else { $_.Substring(0, 1).ToUpper() + $_.Substring(1) } - }) -join '_' - - $variants = New-Object System.Collections.ArrayList - [void]$variants.Add($title) - if ($mixed -cne $title) { [void]$variants.Add($mixed) } - if ($id.Length -le 4 -and $id -notmatch '[aeiou]') { [void]$variants.Add($id.ToUpper()) } - $oneWord = @($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join '' - if ($oneWord -cne $title) { [void]$variants.Add($oneWord) } - - $aliases = @{ - 'tnt' = 'TNT' - 'tnt_minecart' = 'TNT_Minecart' - 'dragon_breath' = "Dragon's_Breath" - 'slime_ball' = 'Slime' - 'snow_golem' = 'Snow_Golem' - 'ocelot' = 'Ocelot' - } - if ($aliases.ContainsKey($id)) { [void]$variants.Insert(0, $aliases[$id]) } - return @($variants | Select-Object -Unique) -} - -$failed = @() -foreach ($id in $need) { - $file = $null - foreach ($name in Get-NameVariants $id) { - $cands = Get-WikiCandidates $name - if ($cands.Count -gt 0) { $file = Select-BestCandidate $name $cands; break } - } - - if ($file) { - $target = Join-Path $itemsDir "$id.png" - try { - Invoke-WebRequest -Uri ($wikiImg + [uri]::EscapeDataString($file)) -Headers @{ 'User-Agent' = $ua } -OutFile $target -TimeoutSec 60 - $fs = [System.IO.File]::OpenRead($target) - $sig = New-Object byte[] 8 - [void]$fs.Read($sig, 0, 8) - $fs.Close() - if (-not ($sig[0] -eq 137 -and $sig[1] -eq 80 -and $sig[2] -eq 78 -and $sig[3] -eq 71)) { - throw '下载内容不是有效 PNG' - } - Write-Host " ok $id <- $file" -ForegroundColor Green - continue - } catch { - Write-Host " fail $id 下载失败: $($_.Exception.Message)" -ForegroundColor Yellow - } - } else { - Write-Host " fail $id 在 wiki 上未找到" -ForegroundColor Yellow - } - $failed += $id -} - -# ---------- 4. 汇总错误 ---------- -if ($failed.Count -gt 0) { - Write-Host "`n[错误] 以下物品贴图无法自动获取:" -ForegroundColor Red - $failed | Sort-Object | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } - Write-Host @" - -可能原因: - 1. 物品 id 拼写错误(组件中填写的是 assets/items 下的文件名) - 2. 该物品是 sfcraft 自定义物品, 但 textures/item 目录中没有同名贴图 - 3. 原版物品在 Minecraft wiki 上的命名特殊, 未被自动规则覆盖 - -解决办法: - - 将贴图手动放入 assets/items/.png 后重新运行 - - 修正组件中的物品 id - - 若是 wiki 命名特例, 可在 scripts/sync-items.ps1 的 Get-NameVariants 别名表中补充 -"@ -ForegroundColor Yellow - exit 1 -} - -Write-Host "`n全部贴图就绪 ✓" -ForegroundColor Green -exit 0 diff --git a/scripts/sync-items.py b/scripts/sync-items.py index ff2c4a0..411e6d7 100644 --- a/scripts/sync-items.py +++ b/scripts/sync-items.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -"""SFCraft 文档 · 物品贴图自动同步 (Linux / macOS / Unix 版) +"""SFCraft 文档 · 物品贴图自动同步 (Python 3) -与 scripts/sync-items.ps1 逻辑一致: 1. 下载 sfcraft 仓库 textures/item 全部贴图到 assets/items 2. 扫描 content 中 crafting 组件用到的物品 id (支持九格直填与 pattern 别名两种写法) 3. 本地缺失的 id 从 Minecraft wiki 自动获取(自动挑选最新版本渲染) @@ -50,18 +49,19 @@ ALIASES = { } -def http_get_json(url, retries=2): - last = None +def http_get_json(url: str, retries: int = 2) -> dict: for i in range(retries + 1): try: req = urllib.request.Request(url, headers={"User-Agent": UA}) with urllib.request.urlopen(req, timeout=30) as resp: return json.load(resp) - except Exception as e: # noqa: BLE001 - last = e + except Exception: # noqa: BLE001 if i < retries: time.sleep(0.8) - raise last + else: + raise + # 理论上不可达(retries 为负时 range 为空才可能走到), 兜底避免隐式返回 None + raise RuntimeError(f"请求失败(无可用异常信息): {url}") def http_download(url, target): @@ -84,7 +84,7 @@ def add_item_id(ids, raw): def scan_item_ids(): """扫描 content 下所有 crafting 组件, 收集物品 id。""" - ids = set() + ids: set[str] = set() for path in sorted(CONTENT_DIR.rglob("*.md")): try: text = path.read_text(encoding="utf-8") diff --git a/scripts/sync-items.sh b/scripts/sync-items.sh deleted file mode 100755 index 995faf0..0000000 --- a/scripts/sync-items.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -# SFCraft 文档 · 物品贴图自动同步 (Linux / macOS / Unix) -# 用法: ./scripts/sync-items.sh [-f] -set -euo pipefail - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -PY="" -if command -v python3 >/dev/null 2>&1 && python3 -c "import sys" >/dev/null 2>&1; then - PY="python3" -elif command -v python >/dev/null 2>&1 && python -c "import sys" >/dev/null 2>&1; then - PY="python" -else - echo "[错误] 未找到可用的 python3 / python, 请先安装 Python 3。" >&2 - exit 1 -fi - -exec "$PY" "$DIR/sync-items.py" "$@"