mirror of
https://gitee.com/xmz7891/ykxmz.git
synced 2026-10-18 12:30:44 +00:00
182 lines
6.2 KiB
Python
182 lines
6.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
r"""在 Gitee 上创建发行版(Release)并上传三个文件。
|
|
|
|
用法:
|
|
python _build/gitee_release.py <令牌> [版本号]
|
|
|
|
令牌只从命令行参数读,**不写进任何文件、不进仓库**。
|
|
|
|
Gitee 的两个接口:
|
|
建发行版 POST /api/v5/repos/{owner}/{repo}/releases
|
|
传附件 POST /api/v5/repos/{owner}/{repo}/releases/{id}/attach_files
|
|
|
|
SSH 密钥只能推代码,建发行版必须用这个令牌。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import mimetypes
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote")
|
|
OWNER = "xmz7891"
|
|
REPO = "ykxmz"
|
|
API = f"https://gitee.com/api/v5/repos/{OWNER}/{REPO}"
|
|
|
|
|
|
def api(url: str, payload: dict | None = None, method: str = "GET",
|
|
timeout: int = 60):
|
|
data = None
|
|
headers = {"User-Agent": "ykxmz-release"}
|
|
if payload is not None:
|
|
data = json.dumps(payload).encode("utf-8")
|
|
headers["Content-Type"] = "application/json;charset=UTF-8"
|
|
request = urllib.request.Request(url, data=data, headers=headers,
|
|
method=method)
|
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
body = response.read().decode("utf-8", "replace")
|
|
return response.status, (json.loads(body) if body.strip() else {})
|
|
|
|
|
|
def upload_file(url: str, path: Path, token: str, timeout: int = 600):
|
|
"""multipart 上传单个文件。"""
|
|
boundary = "----ykxmz" + uuid.uuid4().hex
|
|
content_type = (mimetypes.guess_type(path.name)[0]
|
|
or "application/octet-stream")
|
|
head = (
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="access_token"\r\n\r\n'
|
|
f"{token}\r\n"
|
|
f"--{boundary}\r\n"
|
|
f'Content-Disposition: form-data; name="file"; '
|
|
f'filename="{path.name}"\r\n'
|
|
f"Content-Type: {content_type}\r\n\r\n"
|
|
).encode("utf-8")
|
|
tail = f"\r\n--{boundary}--\r\n".encode("utf-8")
|
|
body = head + path.read_bytes() + tail
|
|
|
|
request = urllib.request.Request(
|
|
url, data=body, method="POST",
|
|
headers={
|
|
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
|
"Content-Length": str(len(body)),
|
|
"User-Agent": "ykxmz-release",
|
|
})
|
|
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
text = response.read().decode("utf-8", "replace")
|
|
return response.status, (json.loads(text) if text.strip() else {})
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 2:
|
|
print(" 用法: python _build/gitee_release.py <令牌> [版本号]")
|
|
return 1
|
|
token = sys.argv[1]
|
|
version = sys.argv[2] if len(sys.argv) > 2 else "1.6.0"
|
|
tag = f"v{version}"
|
|
|
|
files = [
|
|
ROOT / "dist" / f"ykxmz-agent-{version}.zip",
|
|
ROOT / "dist" / f"ykxmz-server-{version}.zip",
|
|
ROOT / "dist" / f"ykxmz-watch-{version}.apk",
|
|
]
|
|
for path in files:
|
|
if not path.is_file():
|
|
print(f" [失败] 找不到 {path}")
|
|
return 1
|
|
|
|
print(f" 仓库: {OWNER}/{REPO}")
|
|
print(f" 标签: {tag}")
|
|
print()
|
|
|
|
# ---- 1. 建发行版 ----
|
|
body = f"""## ykxmz {version}
|
|
|
|
教室电脑手表遥控。
|
|
|
|
老师抬一下手腕就能管教室里那台电脑 —— 放课件、放音频、投图、
|
|
看看谁在打游戏、下课关机,不用走回讲台。
|
|
|
|
### 这次下载哪个
|
|
|
|
| 你是 | 下这个 |
|
|
|---|---|
|
|
| 教室电脑装被控端 | `ykxmz-agent-{version}.zip` |
|
|
| 要跨网络(办公室管教室) | `ykxmz-server-{version}.zip` |
|
|
| 手表装控制端 | `ykxmz-watch-{version}.apk` |
|
|
|
|
只在一个教室用的话,第三件(中转服务器)可以先不装,
|
|
手表和电脑连同一个 WiFi 就能直连。
|
|
|
|
### 怎么用
|
|
|
|
装完看包里的 `README.txt`,或者仓库首页的说明。
|
|
|
|
### 这一版改了什么
|
|
|
|
- 重写说明文档,按老师 / 电教 / 开发者 / 普通用户分开讲
|
|
- 手表端内置根证书,修部分手表连中转时的证书校验失败
|
|
- 中转站加下载区
|
|
- 网络稳定性:修 MTU 问题,公网可用率从 48% 提到 95% 以上
|
|
"""
|
|
print(" 建发行版…")
|
|
try:
|
|
status, reply = api(
|
|
f"{API}/releases",
|
|
{"access_token": token, "tag_name": tag, "name": f"ykxmz {version}",
|
|
"body": body, "target_commitish": "main", "prerelease": False},
|
|
method="POST")
|
|
except urllib.error.HTTPError as exc:
|
|
detail = exc.read().decode("utf-8", "replace")[:400]
|
|
print(f" [失败] HTTP {exc.code}: {detail}")
|
|
return 1
|
|
|
|
if status not in (200, 201):
|
|
print(f" [失败] HTTP {status}: {reply}")
|
|
return 1
|
|
|
|
release_id = reply.get("id")
|
|
print(f" ✓ 发行版已建,id={release_id}")
|
|
print(f" {reply.get('html_url', '')}")
|
|
|
|
# 如果标签已经存在(重复发布),走"找已有发行版再传附件"的路
|
|
if not release_id:
|
|
print(" [提示] 没拿到 id,尝试按标签查已有的发行版…")
|
|
try:
|
|
_s, existing = api(f"{API}/releases/tags/{tag}?access_token={token}")
|
|
release_id = existing.get("id")
|
|
print(f" 已有发行版 id={release_id}")
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f" [失败] 查不到: {exc}")
|
|
return 1
|
|
|
|
# ---- 2. 传附件 ----
|
|
print()
|
|
print(" 上传文件:")
|
|
attach = f"{API}/releases/{release_id}/attach_files"
|
|
for path in files:
|
|
size_mb = path.stat().st_size / 1024 / 1024
|
|
try:
|
|
status, reply = upload_file(attach, path, token)
|
|
if status in (200, 201):
|
|
print(f" ✓ {path.name} ({size_mb:.2f} MB)")
|
|
else:
|
|
print(f" ✗ {path.name}: HTTP {status} {reply}")
|
|
except urllib.error.HTTPError as exc:
|
|
detail = exc.read().decode("utf-8", "replace")[:200]
|
|
print(f" ✗ {path.name}: HTTP {exc.code} {detail}")
|
|
except Exception as exc: # noqa: BLE001
|
|
print(f" ✗ {path.name}: {exc}")
|
|
|
|
print()
|
|
print(f" 完成:https://gitee.com/{OWNER}/{REPO}/releases/tag/{tag}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|