feat: Add platform adapters for various content publishing platforms

- Implemented Dongchedi adapter for user detection and publishing.
- Implemented Jianshu adapter for user detection and publishing.
- Implemented Juejin adapter for user detection and publishing.
- Implemented Qiehao adapter for user detection and publishing.
- Implemented Smzdm adapter for user detection and publishing.
- Implemented Sohuhao adapter for user detection and publishing.
- Implemented Toutiaohao adapter for user detection and publishing.
- Implemented Wangyihao adapter for user detection and publishing.
- Implemented Weixin Gzh adapter for user detection and publishing.
- Implemented Zol adapter for user detection and publishing.
- Added documentation for manual testing of media publishing.
This commit is contained in:
2026-04-03 17:48:30 +08:00
parent 32d6a462cd
commit 134dd063c3
33 changed files with 2722 additions and 457 deletions
+123 -101
View File
@@ -28,6 +28,30 @@ function normalizeText(value?: string | null): string | null {
return trimmed ? trimmed : null;
}
function logPublishRuntime(level: "info" | "warn" | "error", event: string, payload: Record<string, unknown>): void {
const prefix = `[geo-publisher] ${event}`;
if (level === "error") {
console.error(prefix, payload);
return;
}
if (level === "warn") {
console.warn(prefix, payload);
return;
}
console.info(prefix, payload);
}
function normalizeUrl(value?: string | null, platformId?: string | null, externalArticleId?: string | null): string | null {
const explicit = normalizeText(value);
if (explicit) {
return explicit;
}
if (!platformId || !externalArticleId) {
return null;
}
return normalizeText(buildPublishedUrl(platformId, externalArticleId));
}
async function postCallback<T>(
baseUrl: string,
path: string,
@@ -51,25 +75,78 @@ async function postCallback<T>(
return body.data;
}
function nextExternalArticleId(taskId: number): string {
return `${Date.now()}${String(taskId).slice(-4)}`;
}
function asFailedAdapterResult(error: unknown) {
const message = error instanceof Error ? error.message : "publisher_adapter_failed";
return {
success: false,
status: "failed" as const,
externalArticleId: null,
externalArticleUrl: null,
externalManageUrl: null,
message: error instanceof Error ? error.message : "publisher_adapter_failed",
message,
responsePayload: {
mode: "platform-adapter",
error: error instanceof Error ? error.message : "publisher_adapter_failed",
error: message,
stack: error instanceof Error ? error.stack ?? null : null,
},
};
}
async function postPublishCallback(
callbackBaseUrl: string,
payload: PublisherPublishArticleRequest,
task: PublisherPublishArticleRequest["tasks"][number],
adapterResult: {
success: boolean;
status: PublisherPublishTaskResult["status"];
externalArticleId?: string | null;
externalArticleUrl?: string | null;
externalManageUrl?: string | null;
message?: string | null;
responsePayload?: Record<string, unknown>;
},
) {
const externalArticleId = normalizeText(adapterResult.externalArticleId);
const externalArticleUrl = normalizeUrl(adapterResult.externalArticleUrl, task.platform_id, externalArticleId);
const externalManageUrl = normalizeText(adapterResult.externalManageUrl);
await postCallback(callbackBaseUrl, "/api/callback/plugin/publish", {
plugin_session_id: task.plugin_session_id,
session_token: task.session_token,
article_id: payload.article_id,
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
status: adapterResult.status,
external_article_id: externalArticleId,
external_article_url: externalArticleUrl,
external_manage_url: externalManageUrl,
published_at: adapterResult.success ? new Date().toISOString() : null,
error_message: adapterResult.success ? null : adapterResult.message,
client_version: browser.runtime.getManifest().version,
request_payload: {
title: payload.title,
cover_asset_url: normalizeText(payload.cover_asset_url),
publish_type: payload.publish_type,
},
response_payload: adapterResult.responsePayload ?? {
mode: "platform-adapter",
},
});
return {
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
success: adapterResult.success,
status: adapterResult.status,
external_article_id: externalArticleId,
external_article_url: externalArticleUrl,
external_manage_url: externalManageUrl,
message: adapterResult.message ?? null,
} satisfies PublisherPublishTaskResult;
}
export async function handlePublisherAction(
action: PublisherAction,
payload?: unknown,
@@ -193,110 +270,55 @@ async function handlePublishArticle(payload: PublisherPublishArticleRequest): Pr
}).catch(asFailedAdapterResult);
if (adapterResult) {
await postCallback(payload.callback_base_url, "/api/callback/plugin/publish", {
plugin_session_id: task.plugin_session_id,
session_token: task.session_token,
article_id: payload.article_id,
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
status: adapterResult.status,
external_article_id: normalizeText(adapterResult.externalArticleId),
external_article_url: normalizeText(adapterResult.externalArticleUrl),
external_manage_url: normalizeText(adapterResult.externalManageUrl),
published_at: adapterResult.success ? new Date().toISOString() : null,
error_message: adapterResult.success ? null : adapterResult.message,
client_version: browser.runtime.getManifest().version,
request_payload: {
title: payload.title,
cover_asset_url: normalizeText(payload.cover_asset_url),
publish_type: payload.publish_type,
},
response_payload: adapterResult.responsePayload ?? {
mode: "platform-adapter",
},
});
results.push({
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
success: adapterResult.success,
status: adapterResult.status,
external_article_id: normalizeText(adapterResult.externalArticleId),
external_article_url: normalizeText(adapterResult.externalArticleUrl),
external_manage_url: normalizeText(adapterResult.externalManageUrl),
message: adapterResult.message ?? null,
});
if (!adapterResult.success) {
logPublishRuntime("error", "publish-task-failed", {
article_id: payload.article_id,
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
message: adapterResult.message ?? null,
response_payload: adapterResult.responsePayload ?? null,
});
}
results.push(await postPublishCallback(payload.callback_base_url, payload, task, adapterResult));
continue;
}
if (!platform?.connected) {
await postCallback(payload.callback_base_url, "/api/callback/plugin/publish", {
plugin_session_id: task.plugin_session_id,
session_token: task.session_token,
article_id: payload.article_id,
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
status: "failed",
error_message: "Local platform session is missing in the extension runtime.",
client_version: browser.runtime.getManifest().version,
request_payload: {
title: payload.title,
publish_type: payload.publish_type,
},
});
const fallbackFailure = !platform?.connected
? {
success: false,
status: "failed" as const,
externalArticleId: null,
externalArticleUrl: null,
externalManageUrl: normalizeText(platform?.login_url),
message: "Local platform session is missing in the extension runtime.",
responsePayload: {
mode: "missing-session",
installation_key: state.installation_key,
},
}
: {
success: false,
status: "failed" as const,
externalArticleId: null,
externalArticleUrl: null,
externalManageUrl: normalizeText(platform.login_url),
message: "No direct-publish adapter is available for this platform yet.",
responsePayload: {
mode: "missing-adapter",
installation_key: state.installation_key,
},
};
results.push({
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
success: false,
status: "failed",
message: "Local platform session is missing.",
});
continue;
}
const externalArticleId = nextExternalArticleId(task.publish_record_id);
const externalArticleUrl = buildPublishedUrl(task.platform_id, externalArticleId);
await postCallback(payload.callback_base_url, "/api/callback/plugin/publish", {
plugin_session_id: task.plugin_session_id,
session_token: task.session_token,
logPublishRuntime("warn", "publish-task-rejected", {
article_id: payload.article_id,
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
status: "success",
external_article_id: externalArticleId,
external_article_url: externalArticleUrl,
external_manage_url: normalizeText(platform.login_url),
published_at: new Date().toISOString(),
client_version: browser.runtime.getManifest().version,
request_payload: {
title: payload.title,
cover_asset_url: normalizeText(payload.cover_asset_url),
publish_type: payload.publish_type,
},
response_payload: {
mode: "mock-adapter",
installation_key: state.installation_key,
},
});
results.push({
publish_record_id: task.publish_record_id,
platform_account_id: task.platform_account_id,
platform_id: task.platform_id,
success: true,
status: "success",
external_article_id: externalArticleId,
external_article_url: externalArticleUrl,
external_manage_url: normalizeText(platform.login_url),
message: "Published through the mock adapter.",
reason: fallbackFailure.message,
response_payload: fallbackFailure.responsePayload,
});
results.push(await postPublishCallback(payload.callback_base_url, payload, task, fallbackFailure));
}
const successCount = results.filter((item) => item.success).length;