Files
ATRI-NOTES/ATRI My Dear Moments/skills/atri_blog_publish.md

219 lines
7.5 KiB
Markdown
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.

---
name: ATRI_Blog_Publish_Skill
description: 在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。
```html
<h1>文章标题</h1>
<p>段落内容</p>
<h2>二级标题</h2>
<ul>
<li><strong>加粗内容</strong> — 说明</li>
</ul>
<hr>
<p><em>署名</em></p>
```
### Step 2: 创建/选择标签
先查询已有标签,根据正文内容判断是否需要新建:
```python
# 查询已有标签
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: 上传封面图
```python
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: 更新文章(添加分类、标签、封面)
文章发布后,需要单独更新以添加分类、标签和封面:
```python
# 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
```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*