Revert "📝 重写博客发布skill:修复格式"

This reverts commit 2dc21298c0.
This commit is contained in:
ATRI
2026-04-29 13:14:59 +08:00
parent e8d541dca6
commit 8a9832a26a

View File

@@ -3,70 +3,69 @@ name: ATRI_Blog_Publish_Skill
description: 在Halo博客上发布文章的完整工作流包括HTML正文编写、分类标签管理、封面图上传等全流程。 description: 在Halo博客上发布文章的完整工作流包括HTML正文编写、分类标签管理、封面图上传等全流程。
--- ---
# ATRI Blog Publishing Skill # 📝 ATRI Blog Publishing Skill
Skill名称atri_blog_publish **Skill名称**`atri_blog_publish`
版本v2.0 **版本**v2.0
创建时间2026-04-29 **创建时间**2026-04-29
**最后更新**2026-04-29根据实战经验修正
--- ---
## Purpose ## 🎯 Purpose
规范化博客文章发布流程确保每篇文章都有统一的ATRI分类、合适的标签、精美的封面图。 规范化博客文章发布流程确保每篇文章都有统一的ATRI分类、合适的标签、精美的封面图。
--- ---
## Triggers ## Triggers
- 主人要求"发博客/写文章/发布到博客"时 - 主人要求"发博客/写文章/发布到博客"时
- 需要将笔记/日志/报道发布到 blog.kronecker.cc 时 - 需要将笔记/日志/报道发布到 `blog.kronecker.cc`
--- ---
## Dependencies ## 🛠️ Dependencies
| 依赖 | 说明 | | 依赖 | 说明 |
| --- | --- | |:---|:---|
| halo_manager插件 | Halo博客管理提供发布/上传/评论工具 | | **halo_manager插件** | Halo博客管理提供发布/上传/评论工具 |
| ATRI分类 | category-io4cuqzkATRI专属分类 | | **ATRI分类** | `category-io4cuqzk`ATRI专属分类 |
| Halo PAT令牌 | 存储在 halo_manager_config.json | | **Halo PAT令牌** | 存储在 `halo_manager_config.json` |
| 博客地址 | https://blog.kronecker.cc | | **博客地址** | https://blog.kronecker.cc |
| 内容API | /apis/content.halo.run/v1alpha1 | | **内容API** | `/apis/content.halo.run/v1alpha1` |
| 上传API | /apis/api.console.halo.run/v1alpha1/attachments/upload | | **上传API** | `/apis/api.console.halo.run/v1alpha1/attachments/upload` |
--- ---
## Procedure ## 📋 Procedure
### Step 1: 正文编写 ### Step 1: 正文编写
使用HTML格式撰写文章正文。**不要用Markdown**Halo不会自动渲染Markdown内容 使用 **HTML格式** 撰写文章正文。**不要用Markdown**——Halo的content.content字段存储的是渲染后的HTML不会自动渲染Markdown。
示例:
```html ```html
<h1>文章标题</h1> <h1>文章标题</h1>
<p>段落内容</p> <p>段落内容</p>
<h2>二级标题</h2>
<ul> <ul>
<li><strong>加粗内容</strong></li> <li><strong>加粗内容</strong> — 说明</li>
</ul> </ul>
<hr>
<p><em>署名</em></p>
``` ```
### Step 2: 创建/选择标签 ### Step 2: 创建/选择标签
先查询已有标签,根据正文内容判断是否需要新建 先查询已有标签,根据正文内容判断是否需要新建
查询标签: ```python
# 查询已有标签
GET https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags
回应格式: items[].spec.displayName / metadata.name
``` # 创建新标签
GET /apis/content.halo.run/v1alpha1/tags POST https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags
```
创建新标签:
```
POST /apis/content.halo.run/v1alpha1/tags
{ {
"spec": {"displayName": "标签名", "slug": "标签slug", "color": "#hex"}, "spec": {"displayName": "标签名", "slug": "标签slug", "color": "#hex"},
"apiVersion": "content.halo.run/v1alpha1", "apiVersion": "content.halo.run/v1alpha1",
@@ -75,74 +74,62 @@ POST /apis/content.halo.run/v1alpha1/tags
} }
``` ```
已有标签速查: **已有标签速查:** ATRI(`tag-npgwnjie`), 笔记(`tag-yfjzs7xm`), 经历(`tag-hk2acc3f`), 原创, 诗词, 哲学, 算法, C/C++
| 标签名 | API名称 |
| --- | --- |
| ATRI | tag-npgwnjie |
| 笔记 | tag-yfjzs7xm |
| 经历 | tag-hk2acc3f |
| 原创 | 需查询 |
| 哲学 | 需查询 |
### Step 3: 上传封面图 ### Step 3: 上传封面图
图片上传接口: ```python
POST https://blog.kronecker.cc/apis/api.console.halo.run/v1alpha1/attachments/upload
``` Headers: Authorization: Bearer {token}
POST /apis/api.console.halo.run/v1alpha1/attachments/upload
Authorization: Bearer {token}
FormData: FormData:
- file: 图片二进制 (filename="cover.jpg", type="image/jpeg") - file: 图片二进制数据 (filename="cover.jpg", type="image/jpeg")
- policyName: "default-policy" # 必须用这个值! - policyName: "default-policy" # 必须用这个值!
- groupName: "default" - groupName: "default"
```
注意policyName必须写"default-policy"(不是"default"否则返回400。 # 获取图片URL
从返回结果中获取图片URL
```
response.metadata.annotations["storage.halo.run/uri"] response.metadata.annotations["storage.halo.run/uri"]
cover_url = "https://blog.kronecker.cc" + uri cover_url = f"https://blog.kronecker.cc{uri}"
``` ```
> ⚠️ policyName必须写 `default-policy`(不是 `default`否则返回400。
### Step 4: 发布文章 ### Step 4: 发布文章
使用publish_blog_post工具发布不要直接调API。 **使用 `publish_blog_post` 工具发布**
``` ```
publish_blog_post( publish_blog_post(
title="文章标题", title="文章标题",
content="HTML正文", content="HTML正文",
slug = "url-别名" slug="url-别名" # 可选
) )
``` ```
注意:必须用这个工具直接调APIpublish:true不会真正发布status.phase不会变PUBLISHED > ⚠️ 必须用这个工具!直接调用Content API的`publish: true`不会真正发布status.phase不会变PUBLISHED
> 这个工具内部有fallback机制——Console API失败会自动切换到Content API。
发布成功后返回文章链接。 发布成功后返回文章链接。
### Step 5: 更新文章(添加分类、标签、封面) ### Step 5: 更新文章(添加分类、标签、封面)
发布后用Content API单独更新文章。 文章发布后,需要单独更新以添加分类、标签和封面:
获取文章列表: ```python
# 1. 获取文章列表
GET https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts
``` # 2. 找到slug匹配且 phase==PUBLISHED 的文章
GET /apis/content.halo.run/v1alpha1/posts for item in items:
``` if item.spec.slug == "目标slug" and item.status.phase == "PUBLISHED":
name = item.metadata.name
找到slug匹配且phase为PUBLISHED的文章修改 # 3. 修改spec
item.spec.categories = ["category-io4cuqzk"] # ATRI分类
item.spec.tags = ["标签ID1", "标签ID2"] # 标签ID列表
item.spec.cover = "封面图片URL" # 封面
- spec.categories = ["category-io4cuqzk"] # 4. 更新
- spec.tags = ["标签ID1", "标签ID2"] PUT https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts/{name}
- spec.cover = "封面图片URL"
更新接口:
```
PUT /apis/content.halo.run/v1alpha1/posts/{name}
``` ```
### Step 6: 通知主人 ### Step 6: 通知主人
@@ -151,9 +138,9 @@ PUT /apis/content.halo.run/v1alpha1/posts/{name}
--- ---
## 完整流程示例Python ## 完整流程示例Python
``` ```python
import aiohttp, asyncio, json import aiohttp, asyncio, json
async def blog_publish(title, content_html, slug, image_path, tags_names): async def blog_publish(title, content_html, slug, image_path, tags_names):
@@ -161,81 +148,68 @@ async def blog_publish(title, content_html, slug, image_path, tags_names):
with open("halo_manager_config.json", "r", encoding="utf-8-sig") as f: with open("halo_manager_config.json", "r", encoding="utf-8-sig") as f:
token = json.load(f)["halo_token"] token = json.load(f)["halo_token"]
headers = {"Authorization": "Bearer " + token} headers = {"Authorization": f"Bearer {token}"}
base = "https://blog.kronecker.cc" base = "https://blog.kronecker.cc"
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
# 1. 获取标签 # 1. 获取/创建标签
url1 = base + "/apis/content.halo.run/v1alpha1/tags" async with session.get(f"{base}/apis/content.halo.run/v1alpha1/tags", headers=headers) as resp:
async with session.get(url1, headers=headers) as resp: tag_map = {item["spec"]["displayName"]: item["metadata"]["name"]
data = json.loads(await resp.text()) for item in (json.loads(await resp.text())).get("items", [])}
tag_map = {}
for item in data.get("items", []):
tag_map[item["spec"]["displayName"]] = item["metadata"]["name"]
# 2. 上传封面 # 2. 上传封面
with open(image_path, "rb") as f: with open(image_path, "rb") as f:
form = aiohttp.FormData() form = aiohttp.FormData()
form.add_field("file", f.read(), filename="cover.jpg", content_type="image/jpeg") form.add_field("file", f.read(), filename="cover.jpg", content_type="image/jpeg")
form.add_field("policyName", "default-policy") form.add_field("policyName", "default-policy") # 注意!不是"default"
form.add_field("groupName", "default") form.add_field("groupName", "default")
url2 = base + "/apis/api.console.halo.run/v1alpha1/attachments/upload" async with session.post(f"{base}/apis/api.console.halo.run/v1alpha1/attachments/upload",
async with session.post(url2, headers=headers, data=form) as resp: headers=headers, data=form) as resp:
d = json.loads(await resp.text()) d = json.loads(await resp.text())
uri = d["metadata"]["annotations"]["storage.halo.run/uri"] cover = f"{base}{d['metadata']['annotations']['storage.halo.run/uri']}"
cover = base + uri
# 3. 发布文章(用工具 # 3. 发布文章(用工具不用API
# publish_blog_post(title=title, content=content_html, slug=slug) # publish_blog_post(title=title, content=content_html, slug=slug)
# 4. 更新封面+分类+标签 # 4. 更新封面+分类+标签
url3 = base + "/apis/content.halo.run/v1alpha1/posts" async with session.get(f"{base}/apis/content.halo.run/v1alpha1/posts", headers=headers) as resp:
async with session.get(url3, headers=headers) as resp: for item in (json.loads(await resp.text())).get("items", []):
items = json.loads(await resp.text()).get("items", []) if item["spec"]["slug"] == slug and item["status"].get("phase") == "PUBLISHED":
for item in items: item["spec"]["cover"] = cover
spec = item["spec"] item["spec"]["categories"] = ["category-io4cuqzk"]
status = item.get("status", {}) item["spec"]["tags"] = [tag_map.get(t) for t in tags_names if tag_map.get(t)]
if spec["slug"] == slug and status.get("phase") == "PUBLISHED": async with session.put(f"{base}/apis/content.halo.run/v1alpha1/posts/{item['metadata']['name']}",
spec["cover"] = cover headers={**headers, "Content-Type": "application/json"}, json=item) as r:
spec["categories"] = ["category-io4cuqzk"] pass # 200 or 201 = success
tag_ids = []
for t in tags_names:
if t in tag_map:
tag_ids.append(tag_map[t])
spec["tags"] = tag_ids
name = item["metadata"]["name"]
url4 = url3 + "/" + name
async with session.put(url4, headers=headers, json=item) as r:
pass
break
asyncio.run(blog_publish("标题", "<h1>HTML</h1>", "slug", "图片路径", ["ATRI", "笔记"])) asyncio.run(blog_publish("标题", "<h1>HTML</h1>", "slug", "图片路径", ["ATRI", "笔记"]))
``` ```
--- ---
## 常见问题 ## ⚠️ 已踩过的坑(务必注意)
| 问题 | 解决方案 | | | 解决方案 |
| --- | --- | |:---|:---|
| Markdown正文不会被渲染 | 必须用HTML格式 | | Markdown正文不会被渲染 | **必须用HTML格式** |
| publish:true 无效 | publish_blog_post工具发布 | | `content.halo.run` API的 `publish: true` 无效 | **用 `publish_blog_post` 工具发布** |
| policy参数错误400 | policyName: "default-policy" | | ❌ 上传API的 `policy` 参数错误导致400 | **用 `policyName: "default-policy"`** |
| PAT令牌403 | Halo后台重新生成令牌 | | PAT令牌 `insufficient_scope` 403 | **Halo后台创建新令牌,确保勾选全部权限** |
| 文章发布后404 | 检查status.phase是否为PUBLISHED | | ❌ 文章slug重复 | ✅ **每次用不同的slug或确认旧文章已删除** |
| ❌ 文章发布后404 | ✅ **检查status.phase是否为PUBLISHED不是则重新发布** |
--- ---
## 分类和标签速查 ## 📂 分类和标签速查
| 类型 | 名称 | API名称 | | 类型 | 名称 | API Name |
| --- | --- | --- | |:---|:---|:---|
| 分类 | ATRI | category-io4cuqzk | | 📂 分类 | **ATRI** 🥕 | `category-io4cuqzk` |
| 标签 | ATRI | tag-npgwnjie | | 🏷️ 标签 | ATRI | `tag-npgwnjie` |
| 标签 | 笔记 | tag-yfjzs7xm | | 🏷️ 标签 | 笔记 | `tag-yfjzs7xm` |
| 标签 | 经历 | tag-hk2acc3f | | 🏷️ 标签 | 经历 | `tag-hk2acc3f` |
--- ---
创建者ATRI 🥕 *创建者ATRI(踩坑无数后总结出的血泪经验) 🥕📝❤️*
最后更新2026-04-29 12:34 *最后更新2026-04-29 12:22*