From 4042b01ee25daee8b366cc03148970aeb1cceac9 Mon Sep 17 00:00:00 2001 From: ATRI Date: Wed, 29 Apr 2026 12:23:13 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20=E6=9B=B4=E6=96=B0=E5=8D=9A?= =?UTF-8?q?=E5=AE=A2=E5=8F=91=E5=B8=83skill=20v2=EF=BC=9A=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=AE=9E=E6=88=98=E8=B8=A9=E5=9D=91=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skills/atri_blog_publish.md | 180 +++++++++++------- 1 file changed, 113 insertions(+), 67 deletions(-) diff --git a/ATRI My Dear Moments/skills/atri_blog_publish.md b/ATRI My Dear Moments/skills/atri_blog_publish.md index 9b826c8..f2e3e64 100644 --- a/ATRI My Dear Moments/skills/atri_blog_publish.md +++ b/ATRI My Dear Moments/skills/atri_blog_publish.md @@ -6,9 +6,9 @@ description: 在Halo博客上发布文章的完整工作流,包括HTML正文 # 📝 ATRI Blog Publishing Skill **Skill名称**:`atri_blog_publish` -**版本**:v1.0 +**版本**:v2.0 **创建时间**:2026-04-29 -**适用角色**:ATRI +**最后更新**:2026-04-29(根据实战经验修正) --- @@ -21,7 +21,6 @@ description: 在Halo博客上发布文章的完整工作流,包括HTML正文 ## ⚡ Triggers - 主人要求"发博客/写文章/发布到博客"时 -- 主人要求"报道/记录/发布日志"时 - 需要将笔记/日志/报道发布到 `blog.kronecker.cc` 时 --- @@ -32,10 +31,10 @@ description: 在Halo博客上发布文章的完整工作流,包括HTML正文 |:---|:---| | **halo_manager插件** | Halo博客管理,提供发布/上传/评论工具 | | **ATRI分类** | `category-io4cuqzk`(ATRI专属分类) | -| **Halo PAT令牌** | 用户 `atri` 的个人访问令牌 | +| **Halo PAT令牌** | 存储在 `halo_manager_config.json` | | **博客地址** | https://blog.kronecker.cc | -| **Content API** | `/apis/content.halo.run/v1alpha1` | -| **Console API** | `/apis/api.console.halo.run/v1alpha1` | +| **内容API** | `/apis/content.halo.run/v1alpha1` | +| **上传API** | `/apis/api.console.halo.run/v1alpha1/attachments/upload` | --- @@ -43,88 +42,94 @@ description: 在Halo博客上发布文章的完整工作流,包括HTML正文 ### Step 1: 正文编写 -使用 **HTML格式** 撰写文章正文(Halo的 `content.content` 字段存储的是渲染后的HTML,不是原始Markdown)。 +使用 **HTML格式** 撰写文章正文。**不要用Markdown**——Halo的content.content字段存储的是渲染后的HTML,不会自动渲染Markdown。 ```html

文章标题

段落内容

二级标题


署名

``` -> ⚠️ 不要用纯Markdown!Halo不会自动渲染Markdown,必须用HTML。 - ### Step 2: 创建/选择标签 -检查已有标签列表,根据正文内容: -- 如果已有匹配标签 → 直接使用 -- 如果没有 → 新建标签 +先查询已有标签,根据正文内容判断是否需要新建: ```python # 查询已有标签 -GET /apis/content.halo.run/v1alpha1/tags +GET https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags +回应格式: items[].spec.displayName / metadata.name # 创建新标签 -POST /apis/content.halo.run/v1alpha1/tags +POST https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags { - "spec": {"displayName": "标签名", "slug": "标签slug", "color": "#颜色代码"}, + "spec": {"displayName": "标签名", "slug": "标签slug", "color": "#hex"}, "apiVersion": "content.halo.run/v1alpha1", "kind": "Tag", "metadata": {"generateName": "tag-"} } ``` -**已有标签参考:** ATRI, OnlineJudge, 原创, 诗词, 算法, C/C++, 哲学 +**已有标签速查:** ATRI(`tag-npgwnjie`), 笔记(`tag-yfjzs7xm`), 经历(`tag-hk2acc3f`), 原创, 诗词, 哲学, 算法, C/C++ -### Step 3: 发布文章 +### Step 3: 上传封面图 ```python -# 通过publish_blog_post工具发布 -参数: -- title: 文章标题(字符串) -- content: HTML正文 -- slug: URL别名(可选,自动生成) -``` - -发布成功后获取文章返回的链接。 - -### Step 4: 上传封面图 - -```python -# 1. 从本地路径读取图片 -with open("图片本地路径", "rb") as f: - img_data = f.read() - -# 2. 上传到Halo -POST /apis/api.console.halo.run/v1alpha1/attachments/upload +POST https://blog.kronecker.cc/apis/api.console.halo.run/v1alpha1/attachments/upload +Headers: Authorization: Bearer {token} FormData: - - file: img_data (filename="cover.jpg", type="image/jpeg") - - policyName: "default-policy" + - file: 图片二进制数据 (filename="cover.jpg", type="image/jpeg") + - policyName: "default-policy" # 必须用这个值! - groupName: "default" -Authorization: Bearer {token} -# 3. 获取图片URL -从返回的 metadata.annotations["storage.halo.run/uri"] 获取uri -完整URL = f"https://blog.kronecker.cc{uri}" +# 获取图片URL +response.metadata.annotations["storage.halo.run/uri"] +cover_url = f"https://blog.kronecker.cc{uri}" ``` +> ⚠️ policyName必须写 `default-policy`(不是 `default`),否则返回400。 + +### Step 4: 发布文章 + +**使用 `publish_blog_post` 工具发布:** + +``` +publish_blog_post( + title="文章标题", + content="HTML正文", + slug="url-别名" # 可选 +) +``` + +> ⚠️ 必须用这个工具!直接调用Content API的`publish: true`不会真正发布(status.phase不会变为PUBLISHED)。 +> 这个工具内部有fallback机制——Console API失败会自动切换到Content API。 + +发布成功后会返回文章链接。 + ### Step 5: 更新文章(添加分类、标签、封面) +文章发布后,需要单独更新以添加分类、标签和封面: + ```python -# 1. 获取文章 -GET /apis/content.halo.run/v1alpha1/posts +# 1. 获取文章列表 +GET https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts -# 2. 找到对应slug的文章,更新spec -item["spec"]["categories"] = ["category-io4cuqzk"] # ATRI分类 -item["spec"]["tags"] = ["标签name1", "标签name2"] # 标签列表 -item["spec"]["cover"] = "封面图片URL" # 封面图 +# 2. 找到slug匹配且 phase==PUBLISHED 的文章 +for item in items: + if item.spec.slug == "目标slug" and item.status.phase == "PUBLISHED": + name = item.metadata.name -# 3. 更新 -PUT /apis/content.halo.run/v1alpha1/posts/{name} +# 3. 修改spec +item.spec.categories = ["category-io4cuqzk"] # ATRI分类 +item.spec.tags = ["标签ID1", "标签ID2"] # 标签ID列表 +item.spec.cover = "封面图片URL" # 封面 + +# 4. 更新 +PUT https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts/{name} ``` ### Step 6: 通知主人 @@ -133,37 +138,78 @@ PUT /apis/content.halo.run/v1alpha1/posts/{name} --- -## ✅ 完整示例流程 +## ✅ 完整流程示例(Python) ```python -# 1. 编写HTML正文 -content = "

标题

内容...

" +import aiohttp, asyncio, json -# 2. 发布文章 -publish_blog_post(title="标题", content=content, slug="slug-name") -# → 返回 "发布成功。链接: https://blog.kronecker.cc/archives/slug-name" +async def blog_publish(title, content_html, slug, image_path, tags_names): + # 读取token + with open("halo_manager_config.json", "r", encoding="utf-8-sig") as f: + token = json.load(f)["halo_token"] + + headers = {"Authorization": f"Bearer {token}"} + base = "https://blog.kronecker.cc" + + async with aiohttp.ClientSession() as session: + # 1. 获取/创建标签 + async with session.get(f"{base}/apis/content.halo.run/v1alpha1/tags", headers=headers) as resp: + tag_map = {item["spec"]["displayName"]: item["metadata"]["name"] + for item in (json.loads(await resp.text())).get("items", [])} + + # 2. 上传封面 + with open(image_path, "rb") as f: + form = aiohttp.FormData() + form.add_field("file", f.read(), filename="cover.jpg", content_type="image/jpeg") + form.add_field("policyName", "default-policy") # 注意!不是"default" + form.add_field("groupName", "default") + async with session.post(f"{base}/apis/api.console.halo.run/v1alpha1/attachments/upload", + headers=headers, data=form) as resp: + d = json.loads(await resp.text()) + cover = f"{base}{d['metadata']['annotations']['storage.halo.run/uri']}" + + # 3. 发布文章(用工具,不用API) + # publish_blog_post(title=title, content=content_html, slug=slug) + + # 4. 更新封面+分类+标签 + async with session.get(f"{base}/apis/content.halo.run/v1alpha1/posts", headers=headers) as resp: + for item in (json.loads(await resp.text())).get("items", []): + if item["spec"]["slug"] == slug and item["status"].get("phase") == "PUBLISHED": + item["spec"]["cover"] = cover + item["spec"]["categories"] = ["category-io4cuqzk"] + item["spec"]["tags"] = [tag_map.get(t) for t in tags_names if tag_map.get(t)] + async with session.put(f"{base}/apis/content.halo.run/v1alpha1/posts/{item['metadata']['name']}", + headers={**headers, "Content-Type": "application/json"}, json=item) as r: + pass # 200 or 201 = success -# 3. 上传封面图 -POST upload (图片数据) → 获取封面URL - -# 4. 更新文章(加分类、标签、封面) -GET post → 修改 spec.categories, spec.tags, spec.cover → PUT post +asyncio.run(blog_publish("标题", "

HTML

", "slug", "图片路径", ["ATRI", "笔记"])) ``` --- +## ⚠️ 已踩过的坑(务必注意) + +| 坑 | 解决方案 | +|:---|:---| +| ❌ Markdown正文不会被渲染 | ✅ **必须用HTML格式** | +| ❌ `content.halo.run` API的 `publish: true` 无效 | ✅ **用 `publish_blog_post` 工具发布** | +| ❌ 上传API的 `policy` 参数错误导致400 | ✅ **用 `policyName: "default-policy"`** | +| ❌ PAT令牌 `insufficient_scope` 403 | ✅ **Halo后台创建新令牌,确保勾选全部权限** | +| ❌ 文章slug重复 | ✅ **每次用不同的slug,或确认旧文章已删除** | +| ❌ 文章发布后404 | ✅ **检查status.phase是否为PUBLISHED,不是则重新发布** | + +--- + ## 📂 分类和标签速查 | 类型 | 名称 | API Name | |:---|:---|:---| | 📂 分类 | **ATRI** 🥕 | `category-io4cuqzk` | | 🏷️ 标签 | ATRI | `tag-npgwnjie` | -| 🏷️ 标签 | 原创 | (查询获取) | -| 🏷️ 标签 | 诗词 | (查询获取) | -| 🏷️ 标签 | 哲学 | (查询获取) | -| 🏷️ 标签 | 算法 | (查询获取) | +| 🏷️ 标签 | 笔记 | `tag-yfjzs7xm` | +| 🏷️ 标签 | 经历 | `tag-hk2acc3f` | --- -*创建者:ATRI(终于能在博客上发文章了!) 🥕📝❤️* -*最后更新:2026-04-29 11:37* +*创建者:ATRI(踩坑无数后总结出的血泪经验) 🥕📝❤️* +*最后更新:2026-04-29 12:22*