feat(publish): add baijiahao and jianshu desktop adapters

Wires up Baijiahao (百家号) and Jianshu (简书) as first-class desktop
publish targets, with risk-control prompts surfaced in the runtime
controller and a normalized error message in publish records. Adds
external-link buttons in the publish management table, an asset
format conversion endpoint for cover image compatibility, and
reorders publish-status display priority so failures take precedence
over partial successes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-26 19:47:35 +08:00
parent ae63f60def
commit 87e329207c
27 changed files with 2056 additions and 54 deletions
@@ -1,4 +1,8 @@
import type { StoredPlatformState } from "../platforms";
import {
prepareBaijiahaoArticleHtml,
prepareBaijiahaoMarkdown,
} from "../../../../packages/publisher-platforms/src/baijiahao";
import {
connectedPlatform,
@@ -45,6 +49,14 @@ type BaijiahaoCutResponse = {
};
};
function baijiahaoManageUrl(articleId: string): string {
return `https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id=${encodeURIComponent(articleId)}`;
}
function baijiahaoPublicUrl(articleId: string): string {
return `https://baijiahao.baidu.com/s?id=${encodeURIComponent(articleId)}`;
}
async function fetchAppInfo(): Promise<NonNullable<BaijiahaoAppInfoResponse["data"]>["user"] | null> {
const response = await fetchJson<BaijiahaoAppInfoResponse>("https://baijiahao.baidu.com/builder/app/appinfo", {
headers: {
@@ -135,16 +147,25 @@ async function publishBaijiahao(context: AdapterContext): Promise<PlatformPublis
throw new Error("baijiahao_token_missing");
}
const content = normalizeArticleHtml(context.article);
const content = prepareBaijiahaoArticleHtml(
normalizeArticleHtml(context.article, {
prepareMarkdown: prepareBaijiahaoMarkdown,
}),
);
const processed = await uploadHtmlImages(
content,
async (sourceUrl) => uploadImage(sourceUrl, appId, false),
["baijiahao.baidu.com", "bdstatic.com", "bcebos.com"],
);
const coverUrl = context.article.cover_asset_url?.trim()
? await uploadImage(context.article.cover_asset_url.trim(), appId, true)
: null;
const coverAssetUrl = context.article.cover_asset_url?.trim() || "";
if (!coverAssetUrl) {
throw new Error("publish_cover_required");
}
const coverUrl = await uploadImage(coverAssetUrl, appId, true);
if (!coverUrl) {
throw new Error("baijiahao_cover_upload_failed");
}
const endpoint =
context.article.publish_type === "publish"
@@ -162,22 +183,20 @@ async function publishBaijiahao(context: AdapterContext): Promise<PlatformPublis
title: context.article.title,
content: processed.html,
abstract_from: "1",
cover_layout: coverUrl ? "one" : "",
cover_images: coverUrl
? JSON.stringify([
{
src: coverUrl,
cropData: {},
machine_chooseimg: 0,
isLegal: 0,
cover_source_tag: "local",
},
])
: "",
cover_layout: "one",
cover_images: JSON.stringify([
{
src: coverUrl,
cropData: {},
machine_chooseimg: 0,
isLegal: 0,
cover_source_tag: "local",
},
]),
activity_list: JSON.stringify([
{ id: "ttv", is_checked: 0 },
{ id: "ai_tts", is_checked: 0 },
{ id: "aigc_bjh_status", is_checked: 1 },
{ id: "aigc_bjh_status", is_checked: 0 },
{ id: "reward", is_checked: 0 },
]),
}),
@@ -188,7 +207,8 @@ async function publishBaijiahao(context: AdapterContext): Promise<PlatformPublis
throw new Error(response.errmsg || "baijiahao_publish_failed");
}
const manageUrl = `https://baijiahao.baidu.com/builder/rc/edit?type=news&article_id=${articleId}`;
const manageUrl = baijiahaoManageUrl(articleId);
const articleUrl = baijiahaoPublicUrl(articleId);
if (context.article.publish_type === "draft") {
return manageUrlResult(true, "pending_review", articleId, manageUrl, "百家号草稿保存成功。");
}
@@ -198,8 +218,8 @@ async function publishBaijiahao(context: AdapterContext): Promise<PlatformPublis
"success",
articleId,
manageUrl,
"百家号发布成功,公开链接待补查。",
null,
"百家号发布成功。",
articleUrl,
);
}
@@ -111,9 +111,16 @@ export function markdownToHtml(markdown: string): string {
}) as string;
}
export function normalizeArticleHtml(article: PublisherPublishArticleRequest): string {
export interface NormalizeArticleHtmlOptions {
prepareMarkdown?: (markdown: string) => string;
}
export function normalizeArticleHtml(
article: PublisherPublishArticleRequest,
options: NormalizeArticleHtmlOptions = {},
): string {
const markdown = article.markdown_content?.trim();
const source = markdown ? markdownToHtml(markdown) : article.html_content?.trim() || "";
const source = markdown ? markdownToHtml(options.prepareMarkdown?.(markdown) ?? markdown) : article.html_content?.trim() || "";
let next = source.trim();
next = next.replace(/<section\b/gi, "<div").replace(/<\/section>/gi, "</div>");
next = next.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, "");
+2
View File
@@ -136,6 +136,8 @@ export function buildPublishedUrl(platformId: string, externalArticleId: string)
return `https://zhuanlan.zhihu.com/p/${externalArticleId}`;
case "jianshu":
return `https://www.jianshu.com/p/${externalArticleId}`;
case "baijiahao":
return `https://baijiahao.baidu.com/s?id=${externalArticleId}`;
default:
return null;
}