发布插件 - Publisher Plugins
发布插件是 ArtiPub 与各种发布平台之间的桥梁。它们处理平台特定的转换和 API 交互。
概述
ArtiPub 使用基于插件的架构,允许您:
- 同时发布到多个平台
- 为平台特定需求转换内容
- 跟踪已发布的文章以便后续更新
- 创建与任何平台的自定义集成
内置插件
ArtiPub 提供三个生产就绪的插件:
NotionPublisherPlugin
直接发布文章到 Notion 页面或数据库。
主要功能:
- 自动将 Markdown 转换为 Notion 块
- 支持嵌套页面和数据库
- 图片上传和嵌入
- 完整的格式保留
快速开始:
ts
import { NotionPublisherPlugin } from "@artipub/core";
publisher.addPlugin(
NotionPublisherPlugin({
api_key: process.env.NOTION_API_KEY!,
page_id: process.env.NOTION_PAGE_ID!,
})
);
DevToPublisherPlugin
发布文章到 Dev.to 和其他基于 Forem 的社区。
主要功能:
- 草稿和已发布状态
- 系列组织
- SEO 优化
- 组织发布
快速开始:
ts
import { DevToPublisherPlugin } from "@artipub/core";
publisher.addPlugin(
DevToPublisherPlugin({
api_key: process.env.DEVTO_API_KEY!,
published: false,
})
);
NativePublisherPlugin
将文章保存到本地文件系统,适用于静态站点生成器。
主要功能:
- 本地文件系统保存
- GitHub 图片的 CDN URL 转换
- UTF-8 编码
- 与静态站点生成器简单集成
快速开始:
ts
import { NativePublisherPlugin } from "@artipub/core";
publisher.addPlugin(
NativePublisherPlugin({
destination_path: "./content/posts",
})
);
插件接口
所有插件都实现 PublisherPlugin
接口:
ts
interface PublisherPlugin {
extendsParam?(extendsParam: ExtendsParam): PublisherPlugin; // 接收跟踪数据
process(articleTitle: string, visit: TVisitor, toMarkdown: ToMarkdown): Promise<PublishResult>; // 主处理函数
update?(article_id: string | undefined, articleTitle: string, content: string): Promise<void>; // 更新现有文章
name: string; // 插件标识符
isTraceUpdate?: boolean; // 启用更新跟踪
}
interface PublishResult {
pid?: string; // 平台文章 ID
name?: string; // 插件名称
success: boolean; // 发布状态
info?: string; // 状态消息
}
interface ExtendsParam {
pid?: string; // 跟踪中的现有文章 ID
}
type ToMarkdown = () => { content: string };
创建自定义插件
构建您自己的插件以集成任何平台:
ts
import { PublisherPlugin } from "@artipub/core";
function MyPlatformPlugin(options: MyOptions): PublisherPlugin {
return {
name: "MyPlatform",
isTraceUpdate: true,
async process(articleTitle, visit, toMarkdown) {
// 转换内容
visit("image", (node) => {
node.url = transformUrl(node.url);
});
// 获取最终内容
const { content } = toMarkdown();
// 发布到平台
const result = await publishToAPI(articleTitle, content);
return {
success: true,
info: `已发布: ${result.url}`,
pid: result.id,
};
},
};
}
使用插件
单个平台
ts
import { PublisherManager, NotionPublisherPlugin } from "@artipub/core";
const publisher = new PublisherManager(processedContent);
publisher.addPlugin(
NotionPublisherPlugin({
api_key: "your-api-key",
page_id: "your-page-id",
})
);
const results = await publisher.publish();
多个平台
ts
import { PublisherManager, NotionPublisherPlugin, DevToPublisherPlugin, NativePublisherPlugin } from "@artipub/core";
const publisher = new PublisherManager(processedContent);
// 添加多个插件
publisher.addPlugin(
NotionPublisherPlugin({
/* ... */
})
);
publisher.addPlugin(
DevToPublisherPlugin({
/* ... */
})
);
publisher.addPlugin(
NativePublisherPlugin({
/* ... */
})
);
// 同时发布到所有平台
const results = await publisher.publish();
// 处理结果
results.forEach((result) => {
console.log(`${result.name}: ${result.success ? "✅" : "❌"} ${result.info}`);
});
插件功能
内容转换
插件可以修改内容以满足平台特定的要求:
ts
process(articleTitle, visit, toMarkdown) {
// 删除不支持的元素
visit('html', (node, index, parent) => {
parent.children.splice(index, 1);
});
// 修改图片
visit('image', (node) => {
node.url = cdnUrl(node.url);
});
// 获取转换后的内容
const { content } = toMarkdown();
}
更新跟踪
设置 isTraceUpdate: true
的插件可以更新现有文章:
ts
{
name: "MyPlatform",
isTraceUpdate: true,
extendsParam(params) {
this.postId = params.pid; // 接收现有文章 ID
},
async process(/* ... */) {
if (this.postId) {
// 更新现有文章
} else {
// 创建新文章
}
}
}
错误处理
插件应始终返回结果,即使失败时也是如此:
ts
async process(articleTitle, visit, toMarkdown) {
try {
// 发布逻辑
return {
success: true,
info: "发布成功"
};
} catch (error) {
return {
success: false,
info: error.message
};
}
}
最佳实践
环境变量:安全地存储敏感数据
tsNotionPublisherPlugin({ api_key: process.env.NOTION_API_KEY!, page_id: process.env.NOTION_PAGE_ID!, });
错误恢复:优雅地处理失败
tsconst results = await publisher.publish(); const failed = results.filter((r) => !r.success); if (failed.length > 0) { // 重试或回退逻辑 }
速率限制:遵守 API 限制
tsfor (const article of articles) { await publisher.publish(); await delay(1000); // 防止速率限制 }
验证:发布前检查内容
tsif (!content || content.trim().length === 0) { throw new Error("内容为空"); }
插件开发资源
API 参考
平台 API
社区插件
与社区分享您的自定义插件!提交 PR 将您的插件添加到此列表。