返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

zblog-article-publisher

通过对话把文章发布到你的 Z-BlogPHP 博客。任何支持 SKILL.md 规范的 AI 编程助手(Claude Code、Trae、Codex 等)都可使用。

person作者: ytecnkinghubgithub

zblog-article-publisher - 文章发布任务路由器

最高优先级要求

本文件不是完整执行手册,只负责判断文章发布任务类型,并选择对应的小技能文件。

收到文章发布任务后,必须先完成任务分流,然后只读取同目录下对应的一个小技能文件,严格按照该小技能文件执行。

禁止只根据本文件自行发布文章。

禁止把两类任务流程混用。

禁止把两个小技能全部读入上下文后再选择。判断任务类型只能根据用户输入和本文件规则完成。

每个发布任务开始时的第一步:先检查同目录 config.local.md 是否存在且包含 SITE / USERNAME / PASSWORD 三项必填值。不满足时立即执行「全局配置」章节的「首次配置流程」,向用户确认站点信息后再继续任务,禁止跳过检查直接发布,禁止用猜测值或示例值充当配置。

全局配置(唯一配置点)

站点与账号配置存放在技能目录下的 config.local.md(私有文件,不随技能公开分发),所有子技能和脚本从这里读取,任何文件中不得写死站点地址、用户名、密码。

首次配置流程(config.local.md 不存在或缺少必填项时,作为任务第一步执行)

  1. 一次性向用户询问以下信息,不得猜测、不得使用示例值:
    • 站点地址(例如 https://example.com)
    • 用户名
    • 密码
    • 发布格式:html 或 md(用户未明确时默认 html)
  2. 将用户提供的值写入技能目录下的 config.local.md,格式:
| 配置项 | 变量名 | 值 |
|---|---|---|
| 站点地址 | SITE | <站点地址> |
| 用户名 | USERNAME | <用户名> |
| 密码 | PASSWORD | <密码> |
| 发布格式 | POST_FORMAT | html |
  1. 写入后继续执行本次任务

约定

  • API 地址固定为 {SITE}/zb_system/api.php
  • 子技能文档中的 {API} 代表 API 地址(即 {SITE}/zb_system/api.php),{SITE} 代表站点地址
  • 发布文章统一调用技能目录下的 publish.py,脚本自动从 config.local.md 读取配置(含 POST_FORMAT),代码里不得写具体网址和明文账号
  • 上传图片等手动 API 操作沿用「通用代码开头模板」中的 SITE / USERNAME / PASSWORD 常量,API 常量由 SITE 拼接
  • 发布格式 POST_FORMAT 只能是 htmlmd,发布时同样放入 Content 字段:
    • html(默认):按各子技能排版章节要求生成 HTML 正文
    • md:正文用 Markdown 语法书写(标题 ##、加粗 **、引用 >、列表、图片 ![描述](站内图片URL)),子技能排版章节中 HTML 专属的图片居中样式和版权说明样式不再强制
  • 修改站点、账号或发布格式时只改 config.local.md;用户说"重新配置"时按首次配置流程重新询问

API 操作规范(唯一技术手册)

所有子技能共用的 Z-Blog API 操作只在本节定义一次,子技能文件不得重复。子技能只补充本任务特有的发布前检查、配图/截图等任务要求。

通用要求

  • 所有 API 操作统一使用 Python requests.Session(),Session 自动管理 Cookie
  • HTTPS 请求默认走正常证书校验(verify=True);遇到 SSLError(自签名证书、本地代理环境)才降级 verify=False 重试,并屏蔽 InsecureRequestWarning。不要一开始就全局 verify=False(公网正常站点应正常校验),本地 http 站点无此问题
  • 除分类查询外,所有 API 请求的 token 都放在 POST body 中,不要放在 URL 参数中(URL 参数方式会返回 419"非法访问")
  • 密码必须用变量拼接,禁止在代码中出现明文密码或 *** 字符串
  • token 有时效性,过期后需重新登录

登录获取 token

POST {API}?mod=member&act=login
Content-Type: application/x-www-form-urlencoded
Body: username={USERNAME}&password={PASSWORD}(取值见上方全局配置)
返回: data.token

图片搜索与下载要求

  • 搜索图片统一用 agent-browser 打开搜索引擎(必应/搜狗/百度等),浏览器自带正常 User-Agent
  • 用 requests 直接下载候选图片或页面时,必须带浏览器 User-Agent 请求头,否则搜索引擎和图床会拒绝请求、返回验证页或异常内容:
UA = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'}
r = requests.get(图片URL, headers=UA, timeout=30)
  • 下载完成后再做真实图片格式识别(file 命令或 PIL),HTML/验证页内容不得当作图片使用

上传图片

统一调用技能目录下的 upload_image.py(登录、预处理、auto_rename、解析 URL 都由脚本完成):

python upload_image.py 图1.jpg 图2.png
  • 上传前自动预处理:宽度超过 600px 等比缩放到 600,统一转 WebP 格式(质量 80);已是 WebP 且宽度达标的不重编码
  • 依赖 Pillow(python -m pip install --user pillow
  • 输出每行"图片路径 -> 站内URL (尺寸, image/webp)",多张上传时逐行记录图片与段落的对应关系。API 细节:
POST {API}?mod=upload&act=post
Content-Type: multipart/form-data
Body: 字段名 File(传图片二进制数据,显式三元组指定图片 Content-Type 如 image/jpeg)+ auto_rename=on + token(放在 POST body 中)
返回: data.upload.Url 即为站内图片地址,写入文章 HTML 用
  • ⚠️ 必须显式指定图片 Content-Type(files={'File': (文件名, 文件对象, 'image/jpeg')} 三元组):本机 requests/urllib3 对两元组不自动补 type,缺失会导致后台附件"类型"列为空
  • 必须带 auto_rename=on,由 Z-Blog 自动重命名为"日期+时间戳+随机数"文件名,客户端不得自定义文件名
  • 不带 auto_rename 时按原文件名保存,同月内文件重名会返回 500"文件已存在"错误

通用代码开头模板(上传图片等手动 API 操作用)

分类查询、发布文章、上传图片已分别固化为技能目录下的 publish.pyupload_image.py(见下文),无需手写。仅其他手动 API 操作(如编辑文章)需要按此模板写代码:

import requests, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 全局配置常量(SITE/USERNAME/PASSWORD 取值见「全局配置」章节,API 由 SITE 拼接)
SITE = '<站点地址>'
API = SITE + '/zb_system/api.php'
USERNAME = '<用户名>'
PASSWORD = '<密码>'

# SSL 自适应:默认正常证书校验,SSLError(自签名证书/本地代理)才降级 verify=False 重试
def req(s, method, url, **kw):
    try:
        return s.request(method, url, verify=True, timeout=30, **kw)
    except requests.exceptions.SSLError:
        return s.request(method, url, verify=False, timeout=30, **kw)

# 标准开头:登录并获取 token
s = requests.Session()
lr = req(s, 'POST', f'{API}?mod=member&act=login',
         data={'username': USERNAME, 'password': PASSWORD})
token = lr.json()['data']['token']

分类查询

**每次发布前都必须确定分类,不使用硬编码的分类 ID。**分类查询已内置在 publish.py 中,发布时按以下方式传分类:

  • 用户已明确提供分类 ID → --cate-id
  • 用户已指定分类名 → --cate "分类名"(脚本自动查询并匹配,查不到时报错并列出所有现有分类)
  • 用户未指定分类 → 不传分类参数,脚本自动选「未分类」(不存在则选第一个分类)
  • 多个分类都可能匹配或没有明显匹配分类 → 先询问用户,再用 --cate / --cate-id 发布

发布文章

各任务的发布前检查清单见对应子技能文件,检查通过后统一调用技能目录下的 publish.py 发布(登录、分类查询、发布、前台自检都由脚本完成):

python publish.py --title "文章标题" --content-file body.html --cate "分类名" --tags "标签1,标签2" --intro "摘要"

参数:

  • --title(必需):文章标题
  • --content-file(必需):正文文件路径,内容为 HTML 或 Markdown 源码(按全局配置 POST_FORMAT)
  • --cate / --cate-id:分类名 / 分类 ID(见「分类查询」)
  • --tags:标签,英文逗号分隔
  • --intro:摘要
  • --format:html/md,不传则使用全局配置 POST_FORMAT

脚本成功后直接输出文章链接;失败时输出错误信息并退出。正文文件放本次任务的临时子目录,随清理删除。发布成功后按子技能要求做前台验证。

HTML 实体编码问题

通过 API(mod=post&act=get)获取文章内容时,返回的 HTML 是实体编码的(如 &lt;h2&gt; 而不是 <h2>)。如果需要基于现有内容做修改再提交,必须先用 html.unescape() 解码,否则会被双重编码导致页面显示异常。

# 获取文章内容后,必须先解码再使用
content = html.unescape(article['Content'])
# 如需多层解码(罕见情况),循环解码直到不再变化:
for _ in range(3):
    new = html.unescape(content)
    if new == content: break
    content = new

通用失败处理

  • 发布失败:记录错误 → 立即停止 → 不重试 → 告诉用户失败原因 → 等待用户决定是否修改后重试。发布失败后绝对不要自动重试,防止产生重复文章
  • 登录失败:停止发布 → 告诉用户 API 鉴权失败 → 不猜密码、不爆破

图片处理失败的处理方式因任务而异,见各子技能文件。

清理

发布完成后清理本次任务产生的临时文件。

优先为每次文章发布创建独立临时子目录,例如:

当前工作区的 downloads/liubo_YYYYMMDD_HHMMSS/

清理要求:

  • 只清理本次任务创建的文件或本次任务独立子目录
  • 不得直接清空整个当前工作区的 downloads/ 目录
  • 不得删除用户手动放入、其他任务产生、或无法确认归属的无关文件

默认清理:临时图片、临时HTML、临时脚本。

子技能文件

两个子技能文件必须和本文件放在同一个技能目录下。引用子技能时只使用同目录文件名,不写任何机器绝对路径。

  • 标题任务:同目录下的 biaotirenwu-SKILL.md
  • 一段文章任务:同目录下的 yi-duanwenzhang-SKILL.md

任务类型判定

1. 一段文章任务

如果用户直接粘贴一段正文,让你发布、整理后发布、编辑后发布,且没有要求原创扩写,判定为一段文章任务。

常见表达:

  • “把下面这段发到网站”
  • “整理一下发布”
  • “这篇文章帮我发出去”
  • “编辑后发布”
  • 用户直接粘贴正文内容

执行方式:只读取同目录下的 yi-duanwenzhang-SKILL.md,并严格按该文件执行。

2. 标题任务

如果用户只给标题、主题、关键词、写作方向,或者说“写一篇……发布到网站”,判定为标题任务。

常见表达:

  • “写一篇关于……的文章发布”
  • “帮我写一篇……”
  • “以……为主题写文章”
  • “关键词是……”
  • “写一篇……发到我的网站”

执行方式:只读取同目录下的 biaotirenwu-SKILL.md,并严格按该文件执行。

冲突处理规则

如果一条任务同时像多个类型,按以下优先级判断:

一段文章任务 > 标题任务

但如果用户明确提出特殊要求,以用户明确要求为准。

例如:

  • 用户给了正文,但说"在这个基础上扩写成一篇原创文章",应根据实际要求判定为标题任务或一段文章深度编辑任务;不确定时先问用户一次。

如果仍然无法判断,只允许问用户一次,不要自行猜测。

执行要求

判断出任务类型后,必须执行以下步骤:

  1. 只读取同目录下对应的一个小技能文件
  2. 禁止同时读取另一个小技能文件
  3. 禁止把两个小技能全部读入上下文后再选择
  4. 如果判断不清,只问用户一次,不要通过读取全部小技能来判断
  5. 读取对应小技能后,严格按该小技能文件里的完整流程执行
  6. 不得跳过小技能文件中的任何强制步骤
  7. 不得混用其他小技能流程
  8. 不得只根据本路由文件直接发布文章
  9. 发布、上传图片、编辑文章等网站数据变更,只在用户明确要求发布文章时执行
  10. 执行完成后,按小技能文件要求回复发布结果

文件缺失处理

如果对应的小技能文件不存在、无法读取,或内容明显不完整,必须停止执行,并告知用户缺少哪个文件。

不要凭记忆补流程,不要自行拼接其他技能流程。