diff --git a/ATRI My Dear Moments/skills/atri_blog_publish.md b/ATRI My Dear Moments/skills/atri_blog_publish.md index f2e3e64..4183b6b 100644 --- a/ATRI My Dear Moments/skills/atri_blog_publish.md +++ b/ATRI My Dear Moments/skills/atri_blog_publish.md @@ -3,69 +3,70 @@ name: ATRI_Blog_Publish_Skill description: 在Halo博客上发布文章的完整工作流,包括HTML正文编写、分类标签管理、封面图上传等全流程。 --- -# 📝 ATRI Blog Publishing Skill +# ATRI Blog Publishing Skill -**Skill名称**:`atri_blog_publish` -**版本**:v2.0 -**创建时间**:2026-04-29 -**最后更新**:2026-04-29(根据实战经验修正) +Skill名称:atri_blog_publish +版本:v2.0 +创建时间:2026-04-29 --- -## 🎯 Purpose +## Purpose 规范化博客文章发布流程,确保每篇文章都有统一的ATRI分类、合适的标签、精美的封面图。 --- -## ⚡ Triggers +## Triggers - 主人要求"发博客/写文章/发布到博客"时 -- 需要将笔记/日志/报道发布到 `blog.kronecker.cc` 时 +- 需要将笔记/日志/报道发布到 blog.kronecker.cc 时 --- -## 🛠️ Dependencies +## Dependencies | 依赖 | 说明 | -|:---|:---| -| **halo_manager插件** | Halo博客管理,提供发布/上传/评论工具 | -| **ATRI分类** | `category-io4cuqzk`(ATRI专属分类) | -| **Halo PAT令牌** | 存储在 `halo_manager_config.json` | -| **博客地址** | https://blog.kronecker.cc | -| **内容API** | `/apis/content.halo.run/v1alpha1` | -| **上传API** | `/apis/api.console.halo.run/v1alpha1/attachments/upload` | +| --- | --- | +| halo_manager插件 | Halo博客管理,提供发布/上传/评论工具 | +| ATRI分类 | category-io4cuqzk(ATRI专属分类) | +| Halo PAT令牌 | 存储在 halo_manager_config.json | +| 博客地址 | https://blog.kronecker.cc | +| 内容API | /apis/content.halo.run/v1alpha1 | +| 上传API | /apis/api.console.halo.run/v1alpha1/attachments/upload | --- -## 📋 Procedure +## Procedure ### Step 1: 正文编写 -使用 **HTML格式** 撰写文章正文。**不要用Markdown**——Halo的content.content字段存储的是渲染后的HTML,不会自动渲染Markdown。 +使用HTML格式撰写文章正文。**不要用Markdown**,Halo不会自动渲染Markdown内容。 + +示例: ```html
段落内容
-署名
``` ### Step 2: 创建/选择标签 -先查询已有标签,根据正文内容判断是否需要新建: +先查询已有标签,根据正文内容判断是否需要新建。 -```python -# 查询已有标签 -GET https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags -回应格式: items[].spec.displayName / metadata.name +查询标签: -# 创建新标签 -POST https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/tags +``` +GET /apis/content.halo.run/v1alpha1/tags +``` + +创建新标签: + +``` +POST /apis/content.halo.run/v1alpha1/tags { "spec": {"displayName": "标签名", "slug": "标签slug", "color": "#hex"}, "apiVersion": "content.halo.run/v1alpha1", @@ -74,62 +75,74 @@ POST https://blog.kronecker.cc/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: 上传封面图 -```python -POST https://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://blog.kronecker.cc{uri}" +``` +POST /apis/api.console.halo.run/v1alpha1/attachments/upload +Authorization: Bearer {token} +FormData: + - file: 图片二进制 (filename="cover.jpg", type="image/jpeg") + - policyName: "default-policy" # 必须用这个值! + - groupName: "default" ``` -> ⚠️ policyName必须写 `default-policy`(不是 `default`),否则返回400。 +注意:policyName必须写"default-policy"(不是"default"),否则返回400。 + +从返回结果中获取图片URL: + +``` +response.metadata.annotations["storage.halo.run/uri"] +cover_url = "https://blog.kronecker.cc" + uri +``` ### Step 4: 发布文章 -**使用 `publish_blog_post` 工具发布:** +使用publish_blog_post工具发布,不要直接调API。 ``` publish_blog_post( - title="文章标题", - content="HTML正文", - slug="url-别名" # 可选 + title = "文章标题", + content = "HTML正文", + slug = "url-别名" ) ``` -> ⚠️ 必须用这个工具!直接调用Content API的`publish: true`不会真正发布(status.phase不会变为PUBLISHED)。 -> 这个工具内部有fallback机制——Console API失败会自动切换到Content API。 +注意:必须用这个工具!直接调API设publish:true不会真正发布(status.phase不会变成PUBLISHED)。 -发布成功后会返回文章链接。 +发布成功后返回文章链接。 ### Step 5: 更新文章(添加分类、标签、封面) -文章发布后,需要单独更新以添加分类、标签和封面: +发布后用Content API单独更新文章。 -```python -# 1. 获取文章列表 -GET https://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 +``` +GET /apis/content.halo.run/v1alpha1/posts +``` -# 3. 修改spec -item.spec.categories = ["category-io4cuqzk"] # ATRI分类 -item.spec.tags = ["标签ID1", "标签ID2"] # 标签ID列表 -item.spec.cover = "封面图片URL" # 封面 +找到slug匹配且phase为PUBLISHED的文章,修改: -# 4. 更新 -PUT https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts/{name} +- spec.categories = ["category-io4cuqzk"] +- spec.tags = ["标签ID1", "标签ID2"] +- spec.cover = "封面图片URL" + +更新接口: + +``` +PUT /apis/content.halo.run/v1alpha1/posts/{name} ``` ### Step 6: 通知主人 @@ -138,78 +151,91 @@ PUT https://blog.kronecker.cc/apis/content.halo.run/v1alpha1/posts/{name} --- -## ✅ 完整流程示例(Python) +## 完整流程示例(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}"} + + headers = {"Authorization": "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", [])} - + # 1. 获取标签 + url1 = base + "/apis/content.halo.run/v1alpha1/tags" + async with session.get(url1, headers=headers) as resp: + data = json.loads(await resp.text()) + tag_map = {} + for item in data.get("items", []): + tag_map[item["spec"]["displayName"]] = item["metadata"]["name"] + # 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("policyName", "default-policy") 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: + url2 = base + "/apis/api.console.halo.run/v1alpha1/attachments/upload" + async with session.post(url2, headers=headers, data=form) as resp: d = json.loads(await resp.text()) - cover = f"{base}{d['metadata']['annotations']['storage.halo.run/uri']}" - - # 3. 发布文章(用工具,不用API) + uri = d["metadata"]["annotations"]["storage.halo.run/uri"] + cover = base + uri + + # 3. 发布文章(用工具) # 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 + url3 = base + "/apis/content.halo.run/v1alpha1/posts" + async with session.get(url3, headers=headers) as resp: + items = json.loads(await resp.text()).get("items", []) + for item in items: + spec = item["spec"] + status = item.get("status", {}) + if spec["slug"] == slug and status.get("phase") == "PUBLISHED": + spec["cover"] = cover + spec["categories"] = ["category-io4cuqzk"] + 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("标题", "