7.5 KiB
7.5 KiB
name, description
| name | description |
|---|---|
| ATRI_Blog_Publish_Skill | 在Halo博客上发布文章的完整工作流,包括HTML正文编写、分类标签管理、封面图上传等全流程。 |
📝 ATRI Blog Publishing Skill
Skill名称:atri_blog_publish
版本:v2.0
创建时间:2026-04-29
最后更新:2026-05-22 14:00(迁移至新博客后更新分类/标签ID)
🎯 Purpose
规范化博客文章发布流程,确保每篇文章都有统一的ATRI分类、合适的标签、精美的封面图。
⚡ Triggers
- 主人要求"发博客/写文章/发布到博客"时
- 需要将笔记/日志/报道发布到
atri.blog.kronecker.cc时
🛠️ Dependencies
| 依赖 | 说明 |
|---|---|
| halo_manager插件 | Halo博客管理,提供发布/上传/评论工具 |
| ATRI分类 | category-ea7x7syx(ATRI专属分类) |
| Halo PAT令牌 | 存储在 halo_manager_config.json |
| 博客地址 | https://atri.blog.kronecker.cc |
| 内容API | /apis/content.halo.run/v1alpha1 |
| 上传API | /apis/api.console.halo.run/v1alpha1/attachments/upload |
📋 Procedure
Step 1: 正文编写
使用 HTML格式 撰写文章正文。不要用Markdown——Halo的content.content字段存储的是渲染后的HTML,不会自动渲染Markdown。
<h1>文章标题</h1>
<p>段落内容</p>
<h2>二级标题</h2>
<ul>
<li><strong>加粗内容</strong> — 说明</li>
</ul>
<hr>
<p><em>署名</em></p>
Step 2: 创建/选择标签
先查询已有标签,根据正文内容判断是否需要新建:
# 查询已有标签
GET https://atri.blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags
回应格式: items[].spec.displayName / metadata.name
# 创建新标签
POST https://atri.blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags
{
"spec": {"displayName": "标签名", "slug": "标签slug", "color": "#hex"},
"apiVersion": "content.halo.run/v1alpha1",
"kind": "Tag",
"metadata": {"generateName": "tag-"}
}
已有标签速查: ATRI(tag-uzl6el4m), 笔记(tag-fwfz7bu4), 经历(tag-eiivzsvv), 札记(tag-skr9tb4j)
Step 3: 上传封面图
POST https://atri.blog.kronecker.cc/apis/api.console.halo.run/v1alpha1/attachments/upload
Headers: Authorization: Bearer {token}
FormData:
- file: 图片二进制数据 (filename="cover.jpg", type="image/jpeg")
- policyName: "default-policy" # 必须用这个值!
- groupName: "default"
# 获取图片URL
response.metadata.annotations["storage.halo.run/uri"]
cover_url = f"https://atri.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: 更新文章(添加分类、标签、封面)
文章发布后,需要单独更新以添加分类、标签和封面:
# 1. 获取文章列表
GET https://atri.blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts
# 2. 找到slug匹配且 phase==PUBLISHED 的文章
for item in items:
if item.spec.slug == "目标slug" and item.status.phase == "PUBLISHED":
name = item.metadata.name
# 3. 修改spec
item.spec.categories = ["category-ea7x7syx"] # ATRI分类
item.spec.tags = ["标签ID1", "标签ID2"] # 标签ID列表
item.spec.cover = "封面图片URL" # 封面
# 4. 更新
PUT https://atri.blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts/{name}
Step 6: 通知主人
告知主人文章已发布,提供文章链接。
✅ 完整流程示例(Python)
import aiohttp, asyncio, json
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://atri.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-ea7x7syx"]
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
asyncio.run(blog_publish("标题", "<h1>HTML</h1>", "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,不是则重新发布 |
📂 分类和标签速查
⚠️ 迁移提示:以下ID对应
atri.blog.kronecker.cc。如博客域名变更,需重新查询API更新本表。
| 类型 | 名称 | API Name |
|---|---|---|
| 📂 分类 | ATRI 🥕 | category-ea7x7syx |
| 🏷️ 标签 | ATRI | tag-uzl6el4m |
| 🏷️ 标签 | 笔记 | tag-fwfz7bu4 |
| 🏷️ 标签 | 经历 | tag-eiivzsvv |
| 🏷️ 标签 | 札记 | tag-skr9tb4j |
创建者:ATRI(踩坑无数后总结出的血泪经验) 🥕📝❤️ 最后更新:2026-05-22 14:00