Files
geo/apps/browser-extension/src/adapters/qiehao.ts
T
root 134dd063c3 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.
2026-04-03 17:48:30 +08:00

243 lines
6.5 KiB
TypeScript

import type { StoredPlatformState } from "../platforms";
import {
connectedPlatform,
disconnectedPlatform,
fetchImageBlob,
fetchJson,
getCookieString,
manageUrlResult,
normalizeArticleHtml,
sleep,
uploadHtmlImages,
} from "./common";
import type { AdapterContext, PlatformAdapter, PlatformPublishResult } from "./types";
const QIEHAO_APP_ID = "LA6zXi1lWzAioIzdiAD6iM10aHarlHF6";
type QiehaoBasicInfoResponse = {
data?: {
cpInfo?: {
mediaId?: string | number;
mediaName?: string;
header?: string;
};
};
};
type QiehaoPublishResponse = {
response?: {
code?: number;
msg?: string;
};
data?: {
articleId?: string | number;
};
msg?: string;
};
type QiehaoArticleListResponse = {
data?: {
articles?: Array<{
article_id?: string | number;
url?: string;
}>;
};
};
type QiehaoUploadStepOneResponse = {
data?: {
url?: {
size?: Record<string, { imageUrl?: string }>;
};
};
};
type QiehaoUploadStepTwoResponse = {
data?: {
url?: string;
};
};
async function fetchBasicInfo() {
const cookie = await getCookieString("om.qq.com");
return fetchJson<QiehaoBasicInfoResponse>("https://om.qq.com/maccountsetting/basicinfo?relogin=1", {
headers: {
accept: "application/json, text/plain, */*",
...(cookie ? { cookie } : {}),
},
}).catch(() => null);
}
async function detectQiehao(platform: StoredPlatformState): Promise<StoredPlatformState> {
try {
const cpInfo = (await fetchBasicInfo())?.data?.cpInfo;
const uid = cpInfo?.mediaId != null ? String(cpInfo.mediaId) : "";
if (!uid || !cpInfo?.mediaName) {
return disconnectedPlatform(platform, "未检测到企鹅号登录态");
}
return connectedPlatform(platform, uid, cpInfo.mediaName, cpInfo.header ?? null);
} catch (error) {
return disconnectedPlatform(platform, error instanceof Error ? error.message : "企鹅号登录检测失败");
}
}
async function uploadImage(sourceUrl: string, includeCoverMeta: boolean): Promise<string | null> {
const blob = await fetchImageBlob(sourceUrl);
if (!blob) {
return null;
}
const form = new FormData();
form.append("file", blob, "image.png");
form.append("appid", QIEHAO_APP_ID);
form.append("isUpOrg", "1");
form.append("endpoint", "1");
form.append("isRetImgAttr", "1");
form.append("opCode", "151");
const first = await fetchJson<QiehaoUploadStepOneResponse>("https://image.om.qq.com/cpom_pimage/ArchacaleUploadViaFile", {
method: "POST",
body: form,
}).catch(() => null);
const imageUrl = first?.data?.url?.size?.["641"]?.imageUrl ?? null;
if (!imageUrl) {
return null;
}
if (!includeCoverMeta) {
return imageUrl;
}
const second = await fetchJson<QiehaoUploadStepTwoResponse>("https://image.om.qq.com/cpom_pimage/ListImageUploadViaUrl", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
auth: {
appid: QIEHAO_APP_ID,
endpoint: 1,
},
reqData: {
appid: QIEHAO_APP_ID,
isUpOrg: 1,
endpoint: 1,
isRetImgAttr: 1,
opCode: 151,
imageUrl,
},
relogin: 1,
}),
}).catch(() => null);
return second?.data?.url ?? imageUrl;
}
async function publishQiehao(context: AdapterContext): Promise<PlatformPublishResult> {
const cpInfo = (await fetchBasicInfo())?.data?.cpInfo;
const mediaId = cpInfo?.mediaId != null ? String(cpInfo.mediaId) : "";
if (!mediaId) {
throw new Error("qiehao_not_logged_in");
}
const cookie = await getCookieString("om.qq.com");
if (!cookie) {
throw new Error("qiehao_cookie_missing");
}
const content = normalizeArticleHtml(context.article);
const processed = await uploadHtmlImages(content, async (sourceUrl) => uploadImage(sourceUrl, false));
const coverImages = context.article.cover_asset_url?.trim()
? [await uploadImage(context.article.cover_asset_url.trim(), true)].filter(Boolean)
: [];
const uploadedImageList = Array.from(processed.uploaded.values());
const resourceMarkInfo = uploadedImageList.reduce<Record<string, Record<string, unknown>>>((result, imageUrl) => {
result[imageUrl] = {
image_url: imageUrl,
id: imageUrl,
resource_type: "image",
can_modify: 1,
aigc_user_statement: 2,
};
return result;
}, {});
const endpoint =
context.article.publish_type === "publish"
? "https://om.qq.com/marticlepublish/omPublish"
: "https://om.qq.com/marticlepublish/omSave";
const response = await fetchJson<QiehaoPublishResponse>(endpoint, {
method: "POST",
headers: {
"content-type": "application/json",
cookie,
},
body: JSON.stringify({
title: context.article.title,
content: processed.html,
imgurl_ext: JSON.stringify(coverImages),
cover_type: "1",
imgurlsrc: "custom",
orignal: 0,
user_original: 0,
mediaId,
needpub: 1,
type: 0,
relogin: 1,
self_declare: '{"id":7,"desc":"作者声明:该文章由AI辅助创作"}',
resource_aigc_mark_info: JSON.stringify(resourceMarkInfo),
}),
});
const articleId = response.data?.articleId != null ? String(response.data.articleId) : "";
if (response.response?.code !== 0 || !articleId) {
throw new Error(response.msg || response.response?.msg || "qiehao_publish_failed");
}
await sleep(1500);
const list = await fetchJson<QiehaoArticleListResponse>(
"https://om.qq.com/marticle/article/list?category=&search=&source=&startDate=&endDate=&num=20&ftype=&readChannel=qb&dstChannel=&isPartDst=0&refreshField=&relogin=1",
{
headers: {
accept: "application/json, text/plain, */*",
cookie,
},
},
).catch(() => null);
const article = list?.data?.articles?.find((item) => String(item.article_id) === articleId);
const articleUrl = article?.url?.trim() || null;
const externalArticleId = articleUrl ? articleUrl.split("/").filter(Boolean).pop() || articleId : articleId;
if (context.article.publish_type === "draft") {
return manageUrlResult(
true,
"pending_review",
externalArticleId,
"https://om.qq.com/article/manage",
"企鹅号草稿保存成功。",
articleUrl,
);
}
return manageUrlResult(
true,
"success",
externalArticleId,
"https://om.qq.com/article/manage",
"企鹅号发布成功。",
articleUrl,
);
}
export const qiehaoAdapter: PlatformAdapter = {
platformId: "qiehao",
detect: detectQiehao,
publish: publishQiehao,
};