feat(desktop-client/wangyihao): persist session cookies and recover bound partitions
Snapshot 163.com/126.net cookies on bind/refresh to a userData vault and restore them when reattaching a session, recover orphan partitions by matching the detected mediaInfo, verify the bind window via the console state instead of a generic profile probe, and route token/draft fetches through the https API origin while opening console pages over http to match the live editor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,9 +69,11 @@ type WangyiPublishDetail = {
|
||||
newProminentFlag: string;
|
||||
};
|
||||
|
||||
const WANGYI_ORIGIN = "https://mp.163.com";
|
||||
const WANGYI_HOME_URL = `${WANGYI_ORIGIN}/subscribe_v4/index.html`;
|
||||
const WANGYI_MANAGE_URL = `${WANGYI_ORIGIN}/subscribe_v4/index.html#/article-manage`;
|
||||
const WANGYI_API_ORIGIN = "https://mp.163.com";
|
||||
const WANGYI_CONSOLE_ORIGIN = "http://mp.163.com";
|
||||
const WANGYI_API_REFERER = `${WANGYI_API_ORIGIN}/subscribe_v4/index.html`;
|
||||
const WANGYI_HOME_URL = `${WANGYI_CONSOLE_ORIGIN}/subscribe_v4/index.html`;
|
||||
const WANGYI_MANAGE_URL = `${WANGYI_CONSOLE_ORIGIN}/subscribe_v4/index.html#/article-manage`;
|
||||
const WANGYI_CREATIVE_STATEMENT = "个人原创,仅供参考";
|
||||
const WANGYI_PUBLISH_DELAY_MS = 2_000;
|
||||
const WANGYI_TOKEN_WAIT_MS = 12_000;
|
||||
@@ -155,8 +157,8 @@ async function wangyiHeaders(
|
||||
(await sessionCookieHeader(context.session, "163.com").catch(() => ""));
|
||||
return {
|
||||
accept: "application/json, text/plain, */*",
|
||||
origin: WANGYI_ORIGIN,
|
||||
referer: WANGYI_HOME_URL,
|
||||
origin: WANGYI_API_ORIGIN,
|
||||
referer: WANGYI_API_REFERER,
|
||||
...(cookie ? { cookie } : {}),
|
||||
...extra,
|
||||
};
|
||||
@@ -165,7 +167,7 @@ async function wangyiHeaders(
|
||||
async function fetchAccount(context: PublishAdapterContext): Promise<WangyiAccount | null> {
|
||||
const response = await sessionFetchJson<WangyiInfoResponse>(
|
||||
context.session,
|
||||
`${WANGYI_ORIGIN}/wemedia/info.do`,
|
||||
`${WANGYI_API_ORIGIN}/wemedia/info.do`,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: await wangyiHeaders(context),
|
||||
@@ -191,7 +193,7 @@ async function fetchPublishDetail(
|
||||
newProminentFlag: "",
|
||||
};
|
||||
|
||||
const url = new URL(`${WANGYI_ORIGIN}/wemedia/article/postpage.do`);
|
||||
const url = new URL(`${WANGYI_API_ORIGIN}/wemedia/article/postpage.do`);
|
||||
url.searchParams.set("_", String(Date.now()));
|
||||
url.searchParams.set("wemediaId", account.wemediaId);
|
||||
url.searchParams.set("mediaId", account.wemediaId);
|
||||
@@ -224,13 +226,24 @@ async function getPublishToken(context: PublishAdapterContext): Promise<string>
|
||||
throw new Error("wangyihao_playwright_missing");
|
||||
}
|
||||
|
||||
await page.goto(WANGYI_HOME_URL, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
await page.goto(WANGYI_HOME_URL, {
|
||||
waitUntil: attempt === 0 ? "domcontentloaded" : "load",
|
||||
timeout: 20_000,
|
||||
}).catch(() => null);
|
||||
await page.waitForLoadState("networkidle", { timeout: 3_000 }).catch(() => null);
|
||||
if (attempt > 0) {
|
||||
await sleep(800, context.signal);
|
||||
}
|
||||
|
||||
// 网易号的 neg 有时是顶层绑定,不一定挂在 window 上;这里保持和旧版插件一致的读取方式。
|
||||
const token = await readPublishTokenFromPage(page);
|
||||
return token.trim();
|
||||
// 网易号的 neg 有时是顶层绑定,不一定挂在 window 上;这里保持和旧版插件一致的读取方式。
|
||||
const token = (await readPublishTokenFromPage(page)).trim();
|
||||
if (token) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
async function readPublishTokenFromPage(page: Page): Promise<string> {
|
||||
@@ -415,7 +428,7 @@ async function uploadImage(
|
||||
form.append("from", "neteasecode_mp");
|
||||
form.append("logotext", account.name);
|
||||
|
||||
const url = new URL(`${WANGYI_ORIGIN}/api/v3/upload/picupload`);
|
||||
const url = new URL(`${WANGYI_API_ORIGIN}/api/v3/upload/picupload`);
|
||||
url.searchParams.set("_", String(Date.now()));
|
||||
url.searchParams.set("wemediaId", account.wemediaId);
|
||||
url.searchParams.set("realUserId", account.realUserId);
|
||||
@@ -444,7 +457,7 @@ async function saveDraft(
|
||||
html: string,
|
||||
token: string,
|
||||
): Promise<string> {
|
||||
const url = new URL(`${WANGYI_ORIGIN}/wemedia/article/status/api/publishV2.do`);
|
||||
const url = new URL(`${WANGYI_API_ORIGIN}/wemedia/article/status/api/publishV2.do`);
|
||||
url.searchParams.set("_", String(Date.now()));
|
||||
|
||||
const body = buildDraftBody(context, account, detail, html, token);
|
||||
@@ -772,7 +785,7 @@ async function publishDraft(context: PublishAdapterContext, draftId: string): Pr
|
||||
throw new Error("wangyihao_playwright_missing");
|
||||
}
|
||||
|
||||
const draftUrl = `https://mp.163.com/subscribe_v4/index.html#/article-publish/${encodeURIComponent(draftId)}?option=editDraft`;
|
||||
const draftUrl = `${WANGYI_CONSOLE_ORIGIN}/subscribe_v4/index.html#/article-publish/${encodeURIComponent(draftId)}?option=editDraft`;
|
||||
await page.goto(draftUrl, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user