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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user