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:
@@ -0,0 +1,242 @@
|
||||
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,
|
||||
};
|
||||
Reference in New Issue
Block a user