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) <noreply@anthropic.com>
This commit is contained in:
@@ -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("<table");
|
||||
expect(html).toContain("<p>品牌 品牌定位 核心优势</p>");
|
||||
expect(html).toContain("我乐家居 主打原创设计");
|
||||
expect(html).toContain("<p>下一段中文说明</p>");
|
||||
});
|
||||
});
|
||||
@@ -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<PublishAdapter["publish"]> extends Promise<infer T> ? 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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user