From 7ddd3d0c6cedb1fbb5b993236e6fe8ccb6513bd8 Mon Sep 17 00:00:00 2001 From: liangxu Date: Mon, 27 Apr 2026 11:03:04 +0800 Subject: [PATCH] feat(publish): handle qiehao real-name auth via shared publisher errors - Detect qiehao's real-name auth blocking response (code/message) in the desktop adapter, surface a Chinese-friendly summary, and propagate the detail through the failure result. - Extract a shared publisher-errors module so the renderer can normalize these messages for the publish task table without duplicating regex. - Pre-process article HTML for qiehao before upload via a new packages/publisher-platforms/src/qiehao.ts entry point. - Update the manual self-test notes for jianshu and qiehao with the latest results. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/adapters/qiehao.test.ts | 30 +++++++++++++ .../src/main/adapters/qiehao.ts | 22 +++++++++- .../renderer/views/PublishManagementView.vue | 3 +- .../src/shared/publisher-errors.test.ts | 25 +++++++++++ .../src/shared/publisher-errors.ts | 42 +++++++++++++++++++ packages/publisher-platforms/src/index.ts | 1 + packages/publisher-platforms/src/qiehao.ts | 5 +++ 媒体发布手工自测.md | 6 +-- 8 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 apps/desktop-client/src/main/adapters/qiehao.test.ts create mode 100644 apps/desktop-client/src/shared/publisher-errors.test.ts create mode 100644 apps/desktop-client/src/shared/publisher-errors.ts create mode 100644 packages/publisher-platforms/src/qiehao.ts diff --git a/apps/desktop-client/src/main/adapters/qiehao.test.ts b/apps/desktop-client/src/main/adapters/qiehao.test.ts new file mode 100644 index 0000000..3147702 --- /dev/null +++ b/apps/desktop-client/src/main/adapters/qiehao.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; + +import { prepareQiehaoArticleHtml } from "../../../../../packages/publisher-platforms/src/qiehao"; +import { normalizeArticleHtml } from "./common"; + +describe("qiehao article content", () => { + it("downgrades wide markdown tables to paragraph rows before publishing", () => { + const html = prepareQiehaoArticleHtml( + normalizeArticleHtml({ + article_id: 1, + title: "表格测试", + html_content: null, + markdown_content: [ + "| 品牌 | 品牌定位 | 核心优势 |", + "| --- | --- | --- |", + "| 我乐家居 | 主打原创设计的全国性定制品牌,定位偏向时尚轻奢风格 | 拥有多项外观设计专利,产品颜值辨识度高;采用无醛添加环保板材 |", + "| 志邦家居 | 国内上市定制品牌,整体实力稳健 | 全国布局超4000家门店,合肥本地服务网络完善 |", + "", + "下一段中文说明", + ].join("\n"), + cover_asset_url: null, + }), + ); + + expect(html).not.toContain("品牌   品牌定位   核心优势

"); + expect(html).toContain("我乐家居   主打原创设计"); + expect(html).toContain("

下一段中文说明

"); + }); +}); diff --git a/apps/desktop-client/src/main/adapters/qiehao.ts b/apps/desktop-client/src/main/adapters/qiehao.ts index f5f1e5c..163799d 100644 --- a/apps/desktop-client/src/main/adapters/qiehao.ts +++ b/apps/desktop-client/src/main/adapters/qiehao.ts @@ -1,4 +1,5 @@ import type { JsonValue } from "@geo/shared-types"; +import { prepareQiehaoArticleHtml } from "../../../../../packages/publisher-platforms/src/qiehao"; import { resolveDesktopApiURL } from "../transport/api-client"; import { @@ -9,6 +10,10 @@ import { uploadHtmlImages, } from "./common"; import type { PublishAdapter, PublishAdapterContext } from "./base"; +import { + QIEHAO_REAL_NAME_AUTH_MESSAGE, + isQiehaoRealNameAuthError, +} from "../../shared/publisher-errors"; const QIEHAO_APP_ID = "LA6zXi1lWzAioIzdiAD6iM10aHarlHF6"; const QIEHAO_ORIGIN = "https://om.qq.com"; @@ -356,6 +361,18 @@ function buildResultPayload( function failureResult(error: unknown): ReturnType extends Promise ? T : never { const message = error instanceof Error ? error.message : String(error); + if (isQiehaoRealNameAuthError(message)) { + return { + status: "failed", + summary: QIEHAO_REAL_NAME_AUTH_MESSAGE, + error: { + code: "qiehao_real_name_auth_required", + message: QIEHAO_REAL_NAME_AUTH_MESSAGE, + detail: message, + }, + }; + } + if (message === "qiehao_not_logged_in") { return { status: "failed", @@ -462,7 +479,7 @@ export const qiehaoAdapter: PublishAdapter = { throw new Error("qiehao_cookie_missing"); } - const html = normalizeArticleHtml(context.article); + const html = prepareQiehaoArticleHtml(normalizeArticleHtml(context.article)); if (!html) { throw new Error("article_content_empty"); } @@ -527,6 +544,9 @@ export const qiehaoAdapter: PublishAdapter = { if (isQiehaoChallengeMessage(message)) { throw new Error(`qiehao_challenge_required:${message}`); } + if (isQiehaoRealNameAuthError(message, responseCode)) { + throw new Error(`qiehao_real_name_auth_required:${message || responseCode}`); + } throw new Error(message || "qiehao_publish_failed"); } diff --git a/apps/desktop-client/src/renderer/views/PublishManagementView.vue b/apps/desktop-client/src/renderer/views/PublishManagementView.vue index 8c35bd5..79cb86d 100644 --- a/apps/desktop-client/src/renderer/views/PublishManagementView.vue +++ b/apps/desktop-client/src/renderer/views/PublishManagementView.vue @@ -13,6 +13,7 @@ import StatusBadge from "../components/StatusBadge.vue"; import { useDesktopRuntime } from "../composables/useDesktopRuntime"; import { formatDateTime, formatRelativeTime, titleCaseToken } from "../lib/formatters"; import { desktopPublishMediaCatalog } from "../lib/media-catalog"; +import { normalizePublisherErrorMessage } from "../../shared/publisher-errors"; const PAGE_SIZE = 10; @@ -125,7 +126,7 @@ function normalizeTaskErrorMessage(message: string | null): string | null { if (normalized.includes("adapter is not implemented for this platform")) { return "当前平台的桌面发布适配器未实现,任务已按失败处理。"; } - return normalized; + return normalizePublisherErrorMessage(normalized) ?? normalized; } function summaryForTask(status: DesktopTaskInfo["status"]): string { diff --git a/apps/desktop-client/src/shared/publisher-errors.test.ts b/apps/desktop-client/src/shared/publisher-errors.test.ts new file mode 100644 index 0000000..f8e8412 --- /dev/null +++ b/apps/desktop-client/src/shared/publisher-errors.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; + +import { + QIEHAO_REAL_NAME_AUTH_MESSAGE, + isQiehaoRealNameAuthError, + normalizePublisherErrorMessage, + normalizeQiehaoPublishErrorMessage, +} from "./publisher-errors"; + +describe("publisher error normalization", () => { + it("normalizes Qiehao real-name authentication failures from raw platform text", () => { + expect( + normalizePublisherErrorMessage("type:business, code:-5022, msg:user is not real name authentication"), + ).toBe(QIEHAO_REAL_NAME_AUTH_MESSAGE); + }); + + it("recognizes Qiehao real-name authentication failures by business code", () => { + expect(isQiehaoRealNameAuthError("publish rejected", -5022)).toBe(true); + expect(normalizeQiehaoPublishErrorMessage("publish rejected", -5022)).toBe(QIEHAO_REAL_NAME_AUTH_MESSAGE); + }); + + it("leaves unknown publisher errors unchanged", () => { + expect(normalizePublisherErrorMessage("qiehao_publish_failed")).toBe("qiehao_publish_failed"); + }); +}); diff --git a/apps/desktop-client/src/shared/publisher-errors.ts b/apps/desktop-client/src/shared/publisher-errors.ts new file mode 100644 index 0000000..ff03c5d --- /dev/null +++ b/apps/desktop-client/src/shared/publisher-errors.ts @@ -0,0 +1,42 @@ +export const QIEHAO_REAL_NAME_AUTH_MESSAGE = "企鹅号账号未完成实名认证,请先到企鹅号后台完成实名认证后再重试。"; + +function normalizeErrorText(value: string | null | undefined): string | null { + const trimmed = value?.trim(); + return trimmed ? trimmed : null; +} + +export function isQiehaoRealNameAuthError( + message: string | null | undefined, + code?: number | string | null, +): boolean { + const codeText = code == null ? "" : String(code).trim(); + if (codeText === "-5022" || codeText === "5022") { + return true; + } + + const normalized = normalizeErrorText(message); + if (!normalized) { + return false; + } + + return /^qiehao_real_name_auth_required\b/i.test(normalized) + || /\bcode\s*:\s*-?5022\b/i.test(normalized) + || /user\s+is\s+not\s+real\s+name\s+authentication/i.test(normalized) + || /(?:未|没有|尚未).{0,8}实名/.test(normalized) + || /请.{0,8}实名认证/.test(normalized); +} + +export function normalizeQiehaoPublishErrorMessage( + message: string | null | undefined, + code?: number | string | null, +): string | null { + const normalized = normalizeErrorText(message); + if (!normalized) { + return null; + } + return isQiehaoRealNameAuthError(normalized, code) ? QIEHAO_REAL_NAME_AUTH_MESSAGE : normalized; +} + +export function normalizePublisherErrorMessage(message: string | null | undefined): string | null { + return normalizeQiehaoPublishErrorMessage(message); +} diff --git a/packages/publisher-platforms/src/index.ts b/packages/publisher-platforms/src/index.ts index 741ab0a..623c957 100644 --- a/packages/publisher-platforms/src/index.ts +++ b/packages/publisher-platforms/src/index.ts @@ -1,3 +1,4 @@ export * from "./baijiahao"; export * from "./jianshu"; +export * from "./qiehao"; export * from "./toutiao"; diff --git a/packages/publisher-platforms/src/qiehao.ts b/packages/publisher-platforms/src/qiehao.ts new file mode 100644 index 0000000..f448ce9 --- /dev/null +++ b/packages/publisher-platforms/src/qiehao.ts @@ -0,0 +1,5 @@ +import { renderTablesAsParagraphRows } from "./baijiahao"; + +export function prepareQiehaoArticleHtml(html: string): string { + return renderTablesAsParagraphRows(html).trim(); +} diff --git a/媒体发布手工自测.md b/媒体发布手工自测.md index 6702804..9547307 100644 --- a/媒体发布手工自测.md +++ b/媒体发布手工自测.md @@ -22,14 +22,14 @@ 头条号:toutiaohao.ts 知乎: zhihu.ts -百家号:baijiao.ts (表格官方渲染成了图片,不可以的,我们需要把表格降级处理) +百家号:baijiao.ts (表格官方渲染成了图片,表格降级处理) 哔哩哔哩:平台表格就不支持;降级了 掘金:图 表 ok 什么值得买: 图 表 ok +企鹅号:图片 ok 表格支持,但是会撑出去,目前是降级处理,后续再细调 +简书:jianshu.ts (图片 ok,文字,表格 ok) -企鹅号:媒体资质未通过,另外要测 -简书:jianshu.ts (图片未测,文字,表格 ok) 网易:媒体资质没下来 搜狐:没注册成功