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:
2026-04-27 11:03:04 +08:00
parent dbce8515e7
commit 7ddd3d0c6c
8 changed files with 129 additions and 5 deletions
@@ -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>品牌&nbsp;&nbsp;&nbsp;品牌定位&nbsp;&nbsp;&nbsp;核心优势</p>");
expect(html).toContain("我乐家居&nbsp;&nbsp;&nbsp;主打原创设计");
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");
}