Files
gitea/tools/issue.sh
T
jiantw83andClaude Opus 5 f8123e829b feat(gitea): 新增 wiki 轉議題技能
What:新增 wiki-to-issue 技能,把一頁 wiki 轉成同一個存取庫的議題;配套新增 tools/gitea-link.sh 解析連結、tools/issue.sh 讀寫議題。

Why:wiki 頁要變成可追蹤的工作時,原本得自己拼 API:組 JSON、查標籤 id、挑回應欄位,跳脫一錯就把對外的議題內容寫壞。

How:連結是唯一入口,gitea-link.sh 解不出 wiki 頁就中止,不猜存取庫也不猜頁名;標籤只從既有的挑;站台沒有專案看板 API 時據實回報請使用者手動拖,不假裝關聯成功。

Who:使用 jsc 技能組、需要把 wiki 內容交辦出去的人。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:19:08 +08:00

158 lines
5.9 KiB
Bash
Executable File
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 sh
# issue.sh — Gitea 議題的讀取與建立(供 jsc-gitea:wiki-to-issue、html-export 使用)。
#
# 為什麼要有這支腳本:建議題要組 JSON、要把標籤名換成標籤 id、要把回應裡的編號與網址挑出來,
# 全是固定的輸入輸出。交給模型每次自己拼 curl,跳脫字元一錯就把議題內容寫壞,而議題是對外的。
#
# 用法:
# issue.sh labels <owner>/<repo> 列出既有標籤:id<TAB>name
# issue.sh label-ids <owner>/<repo> <name>[,<name>] 標籤名換成 id(逗號分隔);有名字對不到就 exit 4
# issue.sh projects <owner>/<repo> 列出專案看板:id<TAB>title;站台沒有這個 API 就 exit 3
# issue.sh title <owner>/<repo> <index> 印出議題標題
# issue.sh body <owner>/<repo> <index> 印出議題正文(markdown)
# issue.sh labels-of <owner>/<repo> <index> 印出議題目前的標籤名,一行一個
# issue.sh create <owner>/<repo> <title> <body-file> [--labels <id>[,<id>]] [--milestone <id>]
# 建立議題。輸出兩行: index=<編號>、url=<議題網址>
#
# 環境變數: 同 gitea.sh(GITEA_HOST、GITEA_TOKEN;token 缺或被拒時退回 tea 登入金鑰)。
# 結束碼: 0=成功 1=API 失敗 2=用法錯誤 3=站台不支援該 API 4=名稱對不到 id
#
# 陷阱:
# - 正文一律用檔案傳進來,內容維持 UTF-8 與真實換行;不要在參數裡塞 \n。
# - Gitea 1.27 沒有公開的專案看板 API,projects 會 exit 3。呼叫端要據實回報「請手動把議題拖到看板」,
# 不要假裝已經關聯好——關聯不上跟關聯成功看起來一樣,是最容易被當成完成的一種失敗。
set -eu
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
GITEA="$script_dir/gitea.sh"
usage() {
cat >&2 <<'EOF'
用法:
issue.sh labels <owner>/<repo> 列出既有標籤:id<TAB>name
issue.sh label-ids <owner>/<repo> <name>[,<name>] 標籤名換成 id
issue.sh projects <owner>/<repo> 列出專案看板;站台不支援就 exit 3
issue.sh title|body|labels-of <owner>/<repo> <index>
issue.sh create <owner>/<repo> <title> <body-file> [--labels <id>[,<id>]] [--milestone <id>]
結束碼: 0=成功 1=API 失敗 2=用法錯誤 3=站台不支援 4=名稱對不到 id
EOF
exit 2
}
[ -f "$GITEA" ] || { echo "[jsc][議題][ERR]:找不到 $GITEA。" >&2; exit 1; }
valid_repo() {
case "${1:-}" in
*/*/*|/*|*/) return 1 ;;
*/*) return 0 ;;
*) return 1 ;;
esac
}
cmd="${1:-}"; [ -n "$cmd" ] || usage
shift || true
repo="${1:-}"
valid_repo "$repo" || { echo "[jsc][議題][ERR]:存取庫須為 {owner}/{repo},收到「${repo:-空值}」。" >&2; exit 2; }
shift
case "$cmd" in
labels)
"$GITEA" api GET "/repos/$repo/labels?limit=100" | python3 -c '
import json,sys
for l in json.load(sys.stdin):
print("%s\t%s" % (l["id"], l["name"]))
' ;;
label-ids)
names="${1:-}"; [ -n "$names" ] || usage
"$GITEA" api GET "/repos/$repo/labels?limit=100" | python3 -c '
import json,sys
want=[n.strip() for n in sys.argv[1].split(",") if n.strip()]
have={l["name"]: l["id"] for l in json.load(sys.stdin)}
miss=[n for n in want if n not in have]
if miss:
sys.stderr.write("[jsc][議題][ERR]:這些標籤在存取庫裡找不到:" + "、".join(miss) + "\n")
sys.exit(4)
print(",".join(str(have[n]) for n in want))
' "$names" ;;
projects)
if out=$("$GITEA" api GET "/repos/$repo/projects" 2>/dev/null); then
printf '%s' "$out" | python3 -c '
import json,sys
try:
d=json.load(sys.stdin)
except Exception:
sys.exit(3)
if not isinstance(d, list):
sys.exit(3)
for p in d:
print("%s\t%s" % (p.get("id",""), p.get("title") or p.get("name","")))
' || exit 3
else
host="${GITEA_HOST:-}"
case "$host" in http://*|https://*) : ;; *) host="https://$host" ;; esac
echo "[jsc][議題][WARN]:這個 Gitea 站台沒有專案看板 API,程式關聯不了。請開 ${host%/}/$repo/projects 手動把議題拖進看板。" >&2
exit 3
fi ;;
title)
idx="${1:-}"; [ -n "$idx" ] || usage
"$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c '
import json,sys
print(json.load(sys.stdin).get("title",""))
' ;;
body)
idx="${1:-}"; [ -n "$idx" ] || usage
"$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c '
import json,sys
sys.stdout.write(json.load(sys.stdin).get("body","") or "")
' ;;
labels-of)
idx="${1:-}"; [ -n "$idx" ] || usage
"$GITEA" api GET "/repos/$repo/issues/$idx" | python3 -c '
import json,sys
for l in json.load(sys.stdin).get("labels") or []:
print(l.get("name",""))
' ;;
create)
title="${1:-}"; body_file="${2:-}"
[ -n "$title" ] || usage
[ -n "$body_file" ] || usage
[ -f "$body_file" ] || { echo "[jsc][議題][ERR]:找不到正文檔「$body_file」。" >&2; exit 2; }
shift 2
labels=''
milestone=''
while [ "$#" -gt 0 ]; do
case "$1" in
--labels) [ "$#" -ge 2 ] || usage; labels="$2"; shift 2 ;;
--milestone) [ "$#" -ge 2 ] || usage; milestone="$2"; shift 2 ;;
*) echo "[jsc][議題][ERR]:不認得的選項「$1」。" >&2; usage ;;
esac
done
payload=$(mktemp)
trap 'rm -f "$payload"' EXIT
python3 -c '
import json,sys
title, body_file, labels, milestone, out = sys.argv[1:6]
d={"title": title, "body": open(body_file, encoding="utf-8").read()}
if labels:
d["labels"]=[int(x) for x in labels.split(",") if x.strip()]
if milestone:
d["milestone"]=int(milestone)
with open(out, "w", encoding="utf-8") as f:
json.dump(d, f, ensure_ascii=False)
' "$title" "$body_file" "$labels" "$milestone" "$payload"
"$GITEA" api POST "/repos/$repo/issues" "$payload" | python3 -c '
import json,sys
d=json.load(sys.stdin)
print("index=%s" % (d.get("number") or d.get("index") or ""))
print("url=%s" % (d.get("html_url") or ""))
' ;;
*) usage ;;
esac