@gobing-ai/knowledge-kit 0.1.10 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +128 -25
- package/package.json +1 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1057 -108
- package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +168 -121
- package/plugins/kk/workflows/kk-daily-safety.yaml +129 -50
- package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +2 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/infoq-pub/dist/index.js +2 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/jike-pub/dist/index.js +2 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/juejin-pub/dist/index.js +2 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/podcast-pub/dist/index.js +46 -8
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +2 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +2 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +460 -52
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
- package/plugins/publishings/surfdash-pub/src/index.ts +589 -64
- package/plugins/publishings/wechatmp-pub/dist/index.js +933 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +1125 -85
- package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
- package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
- package/plugins/publishings/weibo-pub/dist/index.js +2 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/dist/index.js +2 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xhs-pub/dist/index.js +2 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
|
@@ -19829,6 +19829,7 @@ var init_runtime_node_bun = __esm(() => {
|
|
|
19829
19829
|
});
|
|
19830
19830
|
|
|
19831
19831
|
// ../../plugins/publishings/wechatmp-pub/src/index.ts
|
|
19832
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
19832
19833
|
import { parseArgs } from "util";
|
|
19833
19834
|
|
|
19834
19835
|
// ../../packages/kk-core/src/content-limits.ts
|
|
@@ -20373,9 +20374,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20373
20374
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20374
20375
|
import {
|
|
20375
20376
|
chmodSync,
|
|
20377
|
+
closeSync,
|
|
20376
20378
|
copyFileSync,
|
|
20377
20379
|
lstatSync,
|
|
20378
20380
|
mkdirSync as mkdirSync2,
|
|
20381
|
+
openSync,
|
|
20379
20382
|
readdirSync as readdirSync2,
|
|
20380
20383
|
realpathSync as realpathSync2,
|
|
20381
20384
|
rmdirSync
|
|
@@ -20416,6 +20419,9 @@ function rmdir(path) {
|
|
|
20416
20419
|
function chmod(path, mode) {
|
|
20417
20420
|
chmodSync(path, mode);
|
|
20418
20421
|
}
|
|
20422
|
+
function createExclusiveFile(path) {
|
|
20423
|
+
closeSync(openSync(path, "wx", 384));
|
|
20424
|
+
}
|
|
20419
20425
|
|
|
20420
20426
|
// ../../packages/publish-harness/src/bot-transaction.ts
|
|
20421
20427
|
import { homedir as homedir2 } from "os";
|
|
@@ -20620,7 +20626,6 @@ class BotError extends Error {
|
|
|
20620
20626
|
this.code = code;
|
|
20621
20627
|
}
|
|
20622
20628
|
}
|
|
20623
|
-
|
|
20624
20629
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20625
20630
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20626
20631
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -20839,6 +20844,11 @@ function editorTextScript(selector) {
|
|
|
20839
20844
|
// ../../plugins/publishings/wechatmp-pub/src/index.ts
|
|
20840
20845
|
init_dist();
|
|
20841
20846
|
|
|
20847
|
+
// ../../plugins/publishings/wechatmp-pub/src/flow.ts
|
|
20848
|
+
import { randomUUID } from "crypto";
|
|
20849
|
+
import { tmpdir } from "os";
|
|
20850
|
+
import { extname, join as join6 } from "path";
|
|
20851
|
+
|
|
20842
20852
|
// ../../plugins/publishings/wechatmp-pub/src/cgi.ts
|
|
20843
20853
|
var MP_ORIGIN = "https://mp.weixin.qq.com";
|
|
20844
20854
|
var FILETRANSFER_URL = `${MP_ORIGIN}/cgi-bin/filetransfer`;
|
|
@@ -22039,6 +22049,9 @@ async function prepareArticleMedia(page, session, input) {
|
|
|
22039
22049
|
// ../../plugins/publishings/wechatmp-pub/src/page.ts
|
|
22040
22050
|
var POLL_MS = 500;
|
|
22041
22051
|
var STEP_TIMEOUT_MS = 30000;
|
|
22052
|
+
function sanitizeSessionEvidence(value) {
|
|
22053
|
+
return value.replace(/([?&](?:token|ticket|authkey)=)[^&\s"'<>)]*/gi, "$1[REDACTED]");
|
|
22054
|
+
}
|
|
22042
22055
|
async function modalState(page) {
|
|
22043
22056
|
try {
|
|
22044
22057
|
const raw = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog__wrp, .weui-desktop-dialog')].some((d) => d.offsetWidth)`);
|
|
@@ -22100,9 +22113,89 @@ async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
|
|
|
22100
22113
|
async function fillSelector(page, selector, value) {
|
|
22101
22114
|
await page.locator(selector).first().fill(value);
|
|
22102
22115
|
}
|
|
22116
|
+
function isArticleSave(request, origin) {
|
|
22117
|
+
if (request.method() !== "POST")
|
|
22118
|
+
return false;
|
|
22119
|
+
const url = new URL(request.url());
|
|
22120
|
+
if (url.origin !== origin || url.pathname !== "/cgi-bin/operate_appmsg")
|
|
22121
|
+
return false;
|
|
22122
|
+
if (!["create", "update", "edit"].includes(url.searchParams.get("sub") ?? ""))
|
|
22123
|
+
return false;
|
|
22124
|
+
const data = request.postData() ?? "";
|
|
22125
|
+
const form = new URLSearchParams(data);
|
|
22126
|
+
return form.has("content0") && form.has("title0") || data.includes('name="content0"') && data.includes('name="title0"');
|
|
22127
|
+
}
|
|
22128
|
+
function saveResponseSucceeded(value) {
|
|
22129
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
22130
|
+
return false;
|
|
22131
|
+
const body = value;
|
|
22132
|
+
const base = body.base_resp !== null && typeof body.base_resp === "object" ? body.base_resp : {};
|
|
22133
|
+
const codes = [body.ret, body.errcode, base.ret].filter((code) => code !== undefined);
|
|
22134
|
+
if (codes.some((code) => code !== 0 && code !== "0"))
|
|
22135
|
+
return false;
|
|
22136
|
+
if (codes.length > 0)
|
|
22137
|
+
return true;
|
|
22138
|
+
const info = body.appMsgInfo !== null && typeof body.appMsgInfo === "object" ? body.appMsgInfo : body;
|
|
22139
|
+
return typeof info.appMsgId === "string" || typeof info.appMsgId === "number" ? /^\d{1,12}$/.test(String(info.appMsgId)) : false;
|
|
22140
|
+
}
|
|
22141
|
+
async function saveNativeDraft(raw, selector, timeoutMs) {
|
|
22142
|
+
const origin = new URL(raw.url()).origin;
|
|
22143
|
+
const marker = `__kkNativeSave_${crypto.randomUUID()}`;
|
|
22144
|
+
const button = raw.locator(selector).first();
|
|
22145
|
+
await button.evaluate((element, key) => {
|
|
22146
|
+
const state = { clickedAt: 0 };
|
|
22147
|
+
const handler = () => {
|
|
22148
|
+
state.clickedAt = Date.now();
|
|
22149
|
+
};
|
|
22150
|
+
globalThis[key] = { state, element, handler };
|
|
22151
|
+
element.addEventListener("click", handler, { capture: true, once: true });
|
|
22152
|
+
}, marker);
|
|
22153
|
+
let deadline;
|
|
22154
|
+
const responseComplete = raw.waitForRequest(async (request) => {
|
|
22155
|
+
if (!isArticleSave(request, origin))
|
|
22156
|
+
return false;
|
|
22157
|
+
const clickedAt = await raw.evaluate(`globalThis[${JSON.stringify(marker)}]?.state?.clickedAt ?? 0`).catch(() => 0);
|
|
22158
|
+
if (typeof clickedAt !== "number" || clickedAt <= 0)
|
|
22159
|
+
return false;
|
|
22160
|
+
if (!await request.response())
|
|
22161
|
+
return false;
|
|
22162
|
+
return request.timing().startTime >= clickedAt;
|
|
22163
|
+
}, { timeout: timeoutMs }).then(async (request) => {
|
|
22164
|
+
const response = await request.response();
|
|
22165
|
+
if (response === null || response.status() < 200 || response.status() >= 300)
|
|
22166
|
+
throw new Error("HTTP refusal");
|
|
22167
|
+
if (await response.finished())
|
|
22168
|
+
throw new Error("Incomplete response");
|
|
22169
|
+
if (!saveResponseSucceeded(await response.json()))
|
|
22170
|
+
throw new Error("Save refused");
|
|
22171
|
+
});
|
|
22172
|
+
try {
|
|
22173
|
+
await Promise.race([
|
|
22174
|
+
Promise.all([responseComplete, button.click({ timeout: timeoutMs })]),
|
|
22175
|
+
new Promise((_resolve, reject) => {
|
|
22176
|
+
deadline = setTimeout(() => reject(new Error("Save timeout")), timeoutMs);
|
|
22177
|
+
})
|
|
22178
|
+
]);
|
|
22179
|
+
} catch {
|
|
22180
|
+
throw new Error("Native article save was not acknowledged successfully before the deadline");
|
|
22181
|
+
} finally {
|
|
22182
|
+
if (deadline !== undefined)
|
|
22183
|
+
clearTimeout(deadline);
|
|
22184
|
+
await raw.evaluate(`(() => {
|
|
22185
|
+
const key = ${JSON.stringify(marker)};
|
|
22186
|
+
const records = globalThis;
|
|
22187
|
+
const value = records[key];
|
|
22188
|
+
if (value?.element && value.handler) value.element.removeEventListener('click', value.handler, true);
|
|
22189
|
+
delete records[key];
|
|
22190
|
+
})()`).catch(() => {
|
|
22191
|
+
return;
|
|
22192
|
+
});
|
|
22193
|
+
}
|
|
22194
|
+
}
|
|
22103
22195
|
function wrapPlaywrightPage(raw) {
|
|
22104
22196
|
const pw = raw;
|
|
22105
22197
|
return {
|
|
22198
|
+
saveDraftAndWait: (selector, timeoutMs) => saveNativeDraft(raw, selector, timeoutMs),
|
|
22106
22199
|
goto: (url, options) => pw.goto(url, { waitUntil: "domcontentloaded", ...options }).then(() => {
|
|
22107
22200
|
return;
|
|
22108
22201
|
}),
|
|
@@ -22449,12 +22542,37 @@ var DIGEST_INPUT = ["textarea#js_description", "textarea.js_desc"];
|
|
|
22449
22542
|
var AUTHOR_INPUT = ["input#author", "input.js_author"];
|
|
22450
22543
|
var IMAGE_FILE_INPUT = ['input[type="file"][accept*="image/"]', 'input[type="file"][accept*=".png"]'];
|
|
22451
22544
|
var LAYOUT_CONTROL = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
|
|
22452
|
-
var LAYOUT_TARGET = [
|
|
22453
|
-
|
|
22454
|
-
|
|
22455
|
-
|
|
22456
|
-
|
|
22457
|
-
var
|
|
22545
|
+
var LAYOUT_TARGET = [
|
|
22546
|
+
"div.edui-popup:visible .edui-pastecontainer > .edui-button > div.edui-default:nth-child(2)",
|
|
22547
|
+
'div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")',
|
|
22548
|
+
'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'
|
|
22549
|
+
];
|
|
22550
|
+
var LAYOUT_PREVIEW_APPLY = ['button:has-text("\u4F7F\u7528\u6B64\u6392\u7248")', 'a:has-text("\u4F7F\u7528\u6B64\u6392\u7248")', "text=\u4F7F\u7528\u6B64\u6392\u7248"];
|
|
22551
|
+
var LAYOUT_TARGET_MARKER = "__KK_WECHAT_LAYOUT_TARGET__";
|
|
22552
|
+
var LAYOUT_SAVE_STATE_MARKER = "__KK_WECHAT_LAYOUT_SAVE_STATE__";
|
|
22553
|
+
var LEGACY_EDITOR_READY_MARKER = "__KK_WECHAT_LEGACY_EDITOR_READY__";
|
|
22554
|
+
var COVER_PANEL_OPEN = [
|
|
22555
|
+
".js_cover_btn_area:visible",
|
|
22556
|
+
"text=\u6362\u4E00\u5F20",
|
|
22557
|
+
"text=\u4FEE\u6539",
|
|
22558
|
+
"a.js_selectCoverFromContent:visible"
|
|
22559
|
+
];
|
|
22560
|
+
var COVER_FROM_CONTENT = [
|
|
22561
|
+
"#js_cover_area a.js_selectCoverFromContent:visible",
|
|
22562
|
+
"a.js_selectCoverFromContent:visible"
|
|
22563
|
+
];
|
|
22564
|
+
var COVER_FROM_LIBRARY = ["#js_cover_area a.js_imagedialog:visible", "a.js_imagedialog:visible"];
|
|
22565
|
+
var COVER_LIBRARY_NEXT = [
|
|
22566
|
+
'.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) button:has-text("\u4E0B\u4E00\u6B65")',
|
|
22567
|
+
'button:has-text("\u4E0B\u4E00\u6B65")'
|
|
22568
|
+
];
|
|
22569
|
+
var COVER_CONFIRM = [
|
|
22570
|
+
'.weui-desktop-dialog:visible:has(h3:has-text("\u7F16\u8F91\u5C01\u9762")) button:text-is("\u786E\u8BA4")',
|
|
22571
|
+
'.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)',
|
|
22572
|
+
'button:has-text("\u786E\u5B9A")',
|
|
22573
|
+
'button:has-text("\u786E\u8BA4")',
|
|
22574
|
+
'button:has-text("\u5B8C\u6210")'
|
|
22575
|
+
];
|
|
22458
22576
|
var DIALOG_DISMISS = [
|
|
22459
22577
|
'.weui-desktop-dialog:visible button:has-text("\u53D6\u6D88")',
|
|
22460
22578
|
".weui-desktop-dialog__close",
|
|
@@ -22541,19 +22659,58 @@ var AUDIO_COVER_INPUT = [
|
|
|
22541
22659
|
'.audio_dialog input[type="file"][accept*="image/"]',
|
|
22542
22660
|
'input[type="file"][accept*=".png"]',
|
|
22543
22661
|
'.weui-desktop-dialog input[type="file"][accept*="image"]',
|
|
22544
|
-
'.audio_dialog input[type="file"][accept*="image"]'
|
|
22662
|
+
'.audio_dialog input[type="file"][accept*="image"]',
|
|
22663
|
+
'input[type="file"][accept*="image/bmp"]'
|
|
22545
22664
|
];
|
|
22546
22665
|
var AUDIO_TITLE_INPUT = [
|
|
22547
22666
|
'.weui-desktop-dialog input[placeholder*="\u6807\u9898"]',
|
|
22548
22667
|
'.audio_dialog input[placeholder*="\u6807\u9898"]',
|
|
22549
22668
|
".audio_dialog input.frm_input"
|
|
22550
22669
|
];
|
|
22551
|
-
var UPLOAD_SAVE = ['.
|
|
22670
|
+
var UPLOAD_SAVE = ['.audio-upload-dialog button:text-is("\u4FDD\u5B58")', '.weui-desktop-dialog button:text-is("\u4FDD\u5B58")'];
|
|
22552
22671
|
var SAVE_DRAFT = ['button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")', "text=\u4FDD\u5B58\u4E3A\u8349\u7A3F"];
|
|
22553
22672
|
var PUBLISH_SEND = ["button.mass_send", ".mass_send", 'button:has-text("\u53D1\u8868")'];
|
|
22554
|
-
var
|
|
22673
|
+
var PUBLISH_DIALOG = ['div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868"))'];
|
|
22674
|
+
var PUBLISH_NOTIFY_TOGGLE = [
|
|
22675
|
+
'div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868")) .mass_send__notify .weui-desktop-switch__input:visible'
|
|
22676
|
+
];
|
|
22677
|
+
var PUBLISH_NOTIFY_STATE_SCRIPT = `(() => {
|
|
22678
|
+
const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find((candidate) => {
|
|
22679
|
+
const title = candidate.querySelector('h3.weui-desktop-dialog__title')?.textContent?.trim();
|
|
22680
|
+
const rect = candidate.getBoundingClientRect();
|
|
22681
|
+
return title === '\u53D1\u8868' && rect.width > 0 && rect.height > 0;
|
|
22682
|
+
});
|
|
22683
|
+
const input = dialog?.querySelector('.mass_send__notify input.weui-desktop-switch__input');
|
|
22684
|
+
return input instanceof HTMLInputElement ? input.checked : null;
|
|
22685
|
+
})()`;
|
|
22686
|
+
var PUBLISH_FINAL = [
|
|
22687
|
+
'div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868")) .mass-send__footer button.weui-desktop-btn_primary:text-is("\u53D1\u8868")'
|
|
22688
|
+
];
|
|
22689
|
+
var PUBLISH_VIEW_LATEST = [
|
|
22690
|
+
'div.version_fail_dialog:visible a.btn_primary.js_btn:text-is("\u67E5\u770B\u65B0\u8349\u7A3F"):visible',
|
|
22691
|
+
'div.weui-desktop-dialog:visible button:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
|
|
22692
|
+
'div.weui-desktop-dialog:visible a:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
|
|
22693
|
+
'div.weui-desktop-dialog:visible [role="button"]:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
|
|
22694
|
+
'div.weui-desktop-dialog:visible button:has-text("\u67E5\u770B\u65B0\u8349\u7A3F")'
|
|
22695
|
+
];
|
|
22555
22696
|
var PUBLISH_SUCCESS = ["\u53D1\u8868\u6210\u529F", "\u5DF2\u53D1\u8868"];
|
|
22556
|
-
var
|
|
22697
|
+
var PUBLIC_SAVE_REFUSAL = ["\u5F53\u524D\u7F16\u8F91\u7684\u8349\u7A3F\u4E0D\u662F\u6700\u65B0\u7248\u672C"];
|
|
22698
|
+
var PUBLIC_SAVE_REFUSAL_CONTAINERS = [
|
|
22699
|
+
...DEFAULT_SIGNAL_CONTAINERS,
|
|
22700
|
+
"div.version_fail_dialog:visible",
|
|
22701
|
+
"div.weui-desktop-dialog:visible"
|
|
22702
|
+
];
|
|
22703
|
+
async function readStaleVersionRefusal(page) {
|
|
22704
|
+
return readScopedSignal(page, PUBLIC_SAVE_REFUSAL, PUBLIC_SAVE_REFUSAL_CONTAINERS);
|
|
22705
|
+
}
|
|
22706
|
+
var PLATFORM_REFUSAL = [
|
|
22707
|
+
"\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u53D1\u8868",
|
|
22708
|
+
"\u8BF7\u8865\u5145\u5C01\u9762\u56FE",
|
|
22709
|
+
"\u5FC5\u987B\u63D2\u5165\u4E00\u5F20\u56FE\u7247",
|
|
22710
|
+
"\u672A\u8BBE\u7F6E\u5934\u50CF\u548C\u540D\u79F0",
|
|
22711
|
+
"\u516C\u4F17\u53F7\u5C1A\u672A\u5B9E\u540D",
|
|
22712
|
+
"\u5F53\u524D\u7F16\u8F91\u7684\u8349\u7A3F\u4E0D\u662F\u6700\u65B0\u7248\u672C"
|
|
22713
|
+
];
|
|
22557
22714
|
var DRAFT_STAMP = "\u66F4\u65B0\u4E8E";
|
|
22558
22715
|
var CONFIRM_TIMEOUT_MS = 5000;
|
|
22559
22716
|
var TOKEN_LINK_SCRIPT = `(() => {
|
|
@@ -22563,7 +22720,7 @@ var TOKEN_LINK_SCRIPT = `(() => {
|
|
|
22563
22720
|
return link ? (new URL(link).searchParams.get('token') ?? '') : '';
|
|
22564
22721
|
})()`;
|
|
22565
22722
|
function fail(error) {
|
|
22566
|
-
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
22723
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
|
|
22567
22724
|
}
|
|
22568
22725
|
function sleep(ms) {
|
|
22569
22726
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -22719,21 +22876,154 @@ async function insertBody(page, deps) {
|
|
|
22719
22876
|
const selector = await findSelector(page, BODY_BLOCK, "article body", deps.stepTimeoutMs);
|
|
22720
22877
|
await pasteHtml(asPlaywrightPage(page), selector, markdownToWechatHtml(body), { timeoutMs: deps.stepTimeoutMs });
|
|
22721
22878
|
}
|
|
22879
|
+
function nativeLayoutTargetScript() {
|
|
22880
|
+
return `/* ${LAYOUT_TARGET_MARKER} */ (() => {
|
|
22881
|
+
const popups = [...document.querySelectorAll('.edui-popup')].filter((popup) => {
|
|
22882
|
+
const rect = popup.getBoundingClientRect();
|
|
22883
|
+
const style = getComputedStyle(popup);
|
|
22884
|
+
return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
|
|
22885
|
+
});
|
|
22886
|
+
const targets = popups
|
|
22887
|
+
.flatMap((popup) => [...popup.querySelectorAll('.edui-pastecontainer > .edui-button > div')])
|
|
22888
|
+
.filter((candidate) => candidate.textContent?.trim() === '\u5339\u914D\u76EE\u6807\u683C\u5F0F');
|
|
22889
|
+
if (targets.length !== 1) return JSON.stringify({ state: targets.length === 0 ? 'missing' : 'ambiguous' });
|
|
22890
|
+
const target = targets[0];
|
|
22891
|
+
if (!(target instanceof HTMLElement)) return JSON.stringify({ state: 'missing' });
|
|
22892
|
+
target.click();
|
|
22893
|
+
return JSON.stringify({ state: 'clicked' });
|
|
22894
|
+
})()`;
|
|
22895
|
+
}
|
|
22896
|
+
function nativeLayoutSaveStateScript() {
|
|
22897
|
+
return `/* ${LAYOUT_SAVE_STATE_MARKER} */ (() => {
|
|
22898
|
+
const container = document.querySelector('#auto_save_container');
|
|
22899
|
+
if (!container) return 'unknown';
|
|
22900
|
+
if (container.textContent?.includes('\u81EA\u52A8\u4FDD\u5B58\u4E2D')) return 'saving';
|
|
22901
|
+
if (container.querySelector('.save_done')?.textContent?.includes('\u5DF2\u4FDD\u5B58')) return 'saved';
|
|
22902
|
+
return 'unknown';
|
|
22903
|
+
})()`;
|
|
22904
|
+
}
|
|
22905
|
+
function nativeLegacyEditorReadyScript() {
|
|
22906
|
+
return `/* ${LEGACY_EDITOR_READY_MARKER} */ (() => {
|
|
22907
|
+
const current = new URL(window.location.href);
|
|
22908
|
+
const canonicalLegacyRoute =
|
|
22909
|
+
current.origin === 'https://mp.weixin.qq.com' &&
|
|
22910
|
+
current.pathname === '/cgi-bin/appmsg' &&
|
|
22911
|
+
current.searchParams.get('t') === 'media/appmsg_edit' &&
|
|
22912
|
+
current.searchParams.get('action') === 'edit' &&
|
|
22913
|
+
(current.searchParams.get('appmsgid') ?? '').trim() !== '' &&
|
|
22914
|
+
current.searchParams.get('isNew') !== '1';
|
|
22915
|
+
if (!canonicalLegacyRoute) return 'unknown';
|
|
22916
|
+
const autoSaveContainer = document.querySelector('#auto_save_container');
|
|
22917
|
+
const legacyAutoSave = document.querySelector('#js_autosave');
|
|
22918
|
+
const saveSuccessMarkers = [...document.querySelectorAll(
|
|
22919
|
+
'#js_save_success, #js_save_success_with_ad, #js_save_success_with_ad_op',
|
|
22920
|
+
)];
|
|
22921
|
+
if (!autoSaveContainer || !legacyAutoSave || saveSuccessMarkers.length === 0) return 'unknown';
|
|
22922
|
+
const hidden = (node) => {
|
|
22923
|
+
const style = window.getComputedStyle(node);
|
|
22924
|
+
const rect = node.getBoundingClientRect();
|
|
22925
|
+
return style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0;
|
|
22926
|
+
};
|
|
22927
|
+
const visibleSaveControls = [...document.querySelectorAll('button, a')].filter((node) => {
|
|
22928
|
+
const text = (node.textContent ?? '').replace(/[\\s\\u00a0]+/g, '').trim();
|
|
22929
|
+
if (text !== '\u4FDD\u5B58\u4E3A\u8349\u7A3F') return false;
|
|
22930
|
+
const style = window.getComputedStyle(node);
|
|
22931
|
+
const rect = node.getBoundingClientRect();
|
|
22932
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
22933
|
+
});
|
|
22934
|
+
return autoSaveContainer.textContent?.trim() === '' &&
|
|
22935
|
+
hidden(legacyAutoSave) &&
|
|
22936
|
+
saveSuccessMarkers.every(hidden) &&
|
|
22937
|
+
visibleSaveControls.length === 1
|
|
22938
|
+
? 'ready'
|
|
22939
|
+
: 'unknown';
|
|
22940
|
+
})()`;
|
|
22941
|
+
}
|
|
22942
|
+
async function clickNativeLayoutTarget(page, deps) {
|
|
22943
|
+
let state = "missing";
|
|
22944
|
+
const observed = await waitUntil(async () => {
|
|
22945
|
+
const raw = await page.evaluate(nativeLayoutTargetScript()).catch(() => null);
|
|
22946
|
+
if (typeof raw !== "string")
|
|
22947
|
+
return false;
|
|
22948
|
+
try {
|
|
22949
|
+
const parsed = JSON.parse(raw);
|
|
22950
|
+
if (parsed.state === "ambiguous") {
|
|
22951
|
+
state = "ambiguous";
|
|
22952
|
+
return true;
|
|
22953
|
+
}
|
|
22954
|
+
if (parsed.state === "clicked") {
|
|
22955
|
+
state = "clicked";
|
|
22956
|
+
return true;
|
|
22957
|
+
}
|
|
22958
|
+
} catch {
|
|
22959
|
+
state = "missing";
|
|
22960
|
+
}
|
|
22961
|
+
return false;
|
|
22962
|
+
}, Math.min(deps.stepTimeoutMs, 5000), deps.pollMs);
|
|
22963
|
+
if (!observed)
|
|
22964
|
+
return false;
|
|
22965
|
+
if (state === "ambiguous")
|
|
22966
|
+
throw new Error("\u4E00\u952E\u6392\u7248: \u5339\u914D\u76EE\u6807\u683C\u5F0F was ambiguous in the visible native menu \u2014 composition stopped before submission");
|
|
22967
|
+
return state === "clicked";
|
|
22968
|
+
}
|
|
22969
|
+
async function waitForNativeLayoutSave(page, deps) {
|
|
22970
|
+
let sawSaving = false;
|
|
22971
|
+
const saved = await waitUntil(async () => {
|
|
22972
|
+
const state = await page.evaluate(nativeLayoutSaveStateScript()).catch(() => "unknown");
|
|
22973
|
+
if (state === "saving")
|
|
22974
|
+
sawSaving = true;
|
|
22975
|
+
return sawSaving && state === "saved";
|
|
22976
|
+
}, Math.min(deps.stepTimeoutMs, 15000), deps.pollMs);
|
|
22977
|
+
if (!saved)
|
|
22978
|
+
throw new Error("\u4E00\u952E\u6392\u7248: native auto-save did not transition from \u81EA\u52A8\u4FDD\u5B58\u4E2D... to \u5DF2\u4FDD\u5B58 within the layout deadline \u2014 composition stopped before submission");
|
|
22979
|
+
}
|
|
22722
22980
|
async function applyLayout(page, deps) {
|
|
22723
22981
|
const control = await tryFindSelector(page, LAYOUT_CONTROL, Math.min(deps.stepTimeoutMs, 5000));
|
|
22724
22982
|
if (control === undefined)
|
|
22725
22983
|
return null;
|
|
22984
|
+
const before = deps.pages().length;
|
|
22726
22985
|
try {
|
|
22727
22986
|
await clickSelector(page, control, deps.stepTimeoutMs);
|
|
22728
22987
|
} catch (error) {
|
|
22729
22988
|
return `\u4E00\u952E\u6392\u7248: click failed \u2014 ${error instanceof Error ? error.message : String(error)}`;
|
|
22730
22989
|
}
|
|
22731
|
-
|
|
22990
|
+
if (await clickNativeLayoutTarget(page, deps)) {
|
|
22991
|
+
await waitForNativeLayoutSave(page, deps);
|
|
22992
|
+
return null;
|
|
22993
|
+
}
|
|
22994
|
+
let layoutPage = page;
|
|
22995
|
+
let option;
|
|
22996
|
+
await waitUntil(async () => {
|
|
22997
|
+
for (const candidate of [...deps.pages().slice(before), page]) {
|
|
22998
|
+
const found = await trySelectors(candidate, [...LAYOUT_PREVIEW_APPLY, ...LAYOUT_TARGET]);
|
|
22999
|
+
if (found.found && found.selector) {
|
|
23000
|
+
layoutPage = candidate;
|
|
23001
|
+
option = found.selector;
|
|
23002
|
+
return true;
|
|
23003
|
+
}
|
|
23004
|
+
}
|
|
23005
|
+
return false;
|
|
23006
|
+
}, Math.min(deps.stepTimeoutMs, 15000));
|
|
23007
|
+
if (option !== undefined && LAYOUT_PREVIEW_APPLY.includes(option)) {
|
|
23008
|
+
const selected = option;
|
|
23009
|
+
await clickSelector(layoutPage, selected, deps.stepTimeoutMs);
|
|
23010
|
+
const closed = await waitUntil(async () => {
|
|
23011
|
+
if (!deps.pages().includes(layoutPage))
|
|
23012
|
+
return true;
|
|
23013
|
+
const control = layoutPage.locator(selected).first();
|
|
23014
|
+
return !await control.isVisible();
|
|
23015
|
+
}, Math.min(deps.stepTimeoutMs, 5000));
|
|
23016
|
+
if (!closed)
|
|
23017
|
+
throw new Error("\u4E00\u952E\u6392\u7248: \u4F7F\u7528\u6B64\u6392\u7248 did not close the preview \u2014 composition stopped before submission");
|
|
23018
|
+
return null;
|
|
23019
|
+
}
|
|
22732
23020
|
if (option === undefined) {
|
|
22733
|
-
|
|
23021
|
+
if (deps.pages().length > before)
|
|
23022
|
+
throw new Error("\u4E00\u952E\u6392\u7248: a new preview tab opened without a recognized commit action \u2014 composition stopped before submission");
|
|
23023
|
+
throw new Error("\u4E00\u952E\u6392\u7248: the visible control exposed no recognized commit action \u2014 composition stopped before submission");
|
|
22734
23024
|
}
|
|
22735
23025
|
try {
|
|
22736
|
-
await clickSelector(
|
|
23026
|
+
await clickSelector(layoutPage, option, 5000);
|
|
22737
23027
|
return null;
|
|
22738
23028
|
} catch (error) {
|
|
22739
23029
|
return `\u4E00\u952E\u6392\u7248: \u5339\u914D\u76EE\u6807\u683C\u5F0F not applied \u2014 ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -22755,6 +23045,20 @@ async function fillOptional(page, selectors, value, what, deps) {
|
|
|
22755
23045
|
function baseName(path) {
|
|
22756
23046
|
return path.split(/[\\/]/).pop() ?? path;
|
|
22757
23047
|
}
|
|
23048
|
+
async function coverLibraryName(path) {
|
|
23049
|
+
if (!await Bun.file(path).exists())
|
|
23050
|
+
return baseName(path);
|
|
23051
|
+
const hash = new Bun.CryptoHasher("sha256").update(new Uint8Array(await Bun.file(path).arrayBuffer())).digest("hex");
|
|
23052
|
+
return `kk-cover-${hash.slice(0, 32)}${extname(path).toLowerCase()}`;
|
|
23053
|
+
}
|
|
23054
|
+
function coverAssetIdentity(value) {
|
|
23055
|
+
try {
|
|
23056
|
+
const url = new URL(value);
|
|
23057
|
+
return url.hostname === "mmbiz.qpic.cn" ? `${url.hostname}${url.pathname.replace(/\/(?:0|300|640|1080)$/, "")}` : value;
|
|
23058
|
+
} catch {
|
|
23059
|
+
return value;
|
|
23060
|
+
}
|
|
23061
|
+
}
|
|
22758
23062
|
async function setCover(page, source, deps, identity) {
|
|
22759
23063
|
const picker = source === "library" ? COVER_FROM_LIBRARY : COVER_FROM_CONTENT;
|
|
22760
23064
|
const what = source === "library" ? "\u4ECE\u56FE\u7247\u5E93\u9009\u62E9" : "\u4ECE\u6B63\u6587\u9009\u62E9";
|
|
@@ -22763,18 +23067,22 @@ async function setCover(page, source, deps, identity) {
|
|
|
22763
23067
|
...identity.binding ? [baseName(identity.binding)] : []
|
|
22764
23068
|
].map((name) => name.trim()).filter((name) => name.length > 0);
|
|
22765
23069
|
const warn = async (message) => {
|
|
23070
|
+
const inventory = await openDialogInventory(page);
|
|
22766
23071
|
await dismissOpenDialog(page);
|
|
22767
|
-
return message;
|
|
23072
|
+
return inventory ? `${message} (dialogs: ${inventory})` : message;
|
|
22768
23073
|
};
|
|
22769
23074
|
try {
|
|
22770
23075
|
const open = await tryFindSelector(page, COVER_PANEL_OPEN, Math.min(deps.stepTimeoutMs, 5000));
|
|
22771
23076
|
if (open === undefined)
|
|
22772
23077
|
return await warn("cover: the panel offers none of \u4ECE\u6B63\u6587\u9009\u62E9 / \u4ECE\u56FE\u7247\u5E93\u9009\u62E9 \u2014 the platform default (\u9ED8\u8BA4\u9996\u56FE\u4E3A\u5C01\u9762) is all this run has");
|
|
22773
23078
|
await clickSelector(page, open, deps.stepTimeoutMs);
|
|
22774
|
-
const
|
|
22775
|
-
if (
|
|
22776
|
-
|
|
22777
|
-
|
|
23079
|
+
const alreadyOpen = source === "library" && await tryFindSelector(page, ['.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247"))'], Math.min(deps.stepTimeoutMs, 1000)) !== undefined;
|
|
23080
|
+
if (!alreadyOpen) {
|
|
23081
|
+
const pick = await tryFindSelector(page, picker, Math.min(deps.stepTimeoutMs, 8000));
|
|
23082
|
+
if (pick === undefined)
|
|
23083
|
+
return await warn(`cover: ${what} was not offered by the open cover panel`);
|
|
23084
|
+
await clickSelector(page, pick, deps.stepTimeoutMs);
|
|
23085
|
+
}
|
|
22778
23086
|
if (wanted.length === 0)
|
|
22779
23087
|
return await warn(`cover: no requested cover file identity to match against \u2014 ${what} not driven`);
|
|
22780
23088
|
const IDENTIFY = `(() => {
|
|
@@ -22785,19 +23093,43 @@ async function setCover(page, source, deps, identity) {
|
|
|
22785
23093
|
const el = row.querySelector('[title], [data-name], [data-file_name]') ?? row;
|
|
22786
23094
|
const attrs = [el.getAttribute?.('title'), el.getAttribute?.('data-name'), el.getAttribute?.('data-file_name')];
|
|
22787
23095
|
const text = [...row.querySelectorAll('[class*="name" i], [class*="title" i]')].map((n) => n.textContent ?? '');
|
|
22788
|
-
return [...attrs, ...text].map((v) => (v ?? '').trim()).
|
|
23096
|
+
return [...attrs, ...text].map((v) => (v ?? '').trim()).filter((v) => v.length > 0);
|
|
22789
23097
|
};
|
|
22790
|
-
const hits = rows.filter((row) => wanted.some((w) => nameOf(row)
|
|
23098
|
+
const hits = rows.filter((row) => wanted.some((w) => nameOf(row).includes(w)));
|
|
22791
23099
|
if (hits.length === 0) return 'missing';
|
|
22792
23100
|
if (hits.length > 1) return 'ambiguous';
|
|
22793
23101
|
const row = hits[0];
|
|
22794
|
-
row.
|
|
22795
|
-
row.click();
|
|
23102
|
+
if (!row.classList.contains('selected')) row.click();
|
|
22796
23103
|
const checked = row.querySelector('input[type=checkbox], [role=checkbox]');
|
|
22797
|
-
const
|
|
23104
|
+
const css = row.querySelector('[style*="background-image"]')?.style.backgroundImage ?? '';
|
|
23105
|
+
const backgroundUrl = css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
|
|
23106
|
+
const src = row.querySelector('img')?.src || backgroundUrl;
|
|
22798
23107
|
return JSON.stringify({ picked: true, src });
|
|
22799
23108
|
})()`;
|
|
22800
|
-
|
|
23109
|
+
let rawIdentify = "missing";
|
|
23110
|
+
await waitUntil(async () => {
|
|
23111
|
+
rawIdentify = String(await page.evaluate(IDENTIFY));
|
|
23112
|
+
return rawIdentify !== "missing";
|
|
23113
|
+
}, Math.min(deps.stepTimeoutMs, 8000));
|
|
23114
|
+
const paginationScript = `(() => {
|
|
23115
|
+
const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find(d => d.offsetWidth > 0 && d.querySelector('h3')?.textContent?.includes('\u9009\u62E9\u56FE\u7247'));
|
|
23116
|
+
const labels = dialog?.querySelectorAll('.weui-desktop-pagination__num');
|
|
23117
|
+
return JSON.stringify({ page: Number(labels?.[0]?.textContent ?? 0), total: Number(labels?.[1]?.textContent ?? 0) });
|
|
23118
|
+
})()`;
|
|
23119
|
+
for (let scanned = 0;source === "library" && rawIdentify === "missing" && scanned < 50; scanned += 1) {
|
|
23120
|
+
const pagination = JSON.parse(String(await page.evaluate(paginationScript)));
|
|
23121
|
+
if (!(Number(pagination?.page) > 0 && Number(pagination?.page) < Number(pagination?.total)))
|
|
23122
|
+
break;
|
|
23123
|
+
const nextPage = await tryFindSelector(page, [
|
|
23124
|
+
'.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) a:has-text("\u4E0B\u4E00\u9875"):visible:not(.weui-desktop-btn_disabled)'
|
|
23125
|
+
], 1000);
|
|
23126
|
+
if (nextPage === undefined)
|
|
23127
|
+
break;
|
|
23128
|
+
await clickSelector(page, nextPage, deps.stepTimeoutMs);
|
|
23129
|
+
await page.waitForTimeout?.(500);
|
|
23130
|
+
await waitUntil(async () => Boolean(await page.evaluate(`![...document.querySelectorAll('.weui-desktop-dialog .weui-desktop-loading')].some(node => node.offsetWidth > 0)`)), Math.min(deps.stepTimeoutMs, 8000));
|
|
23131
|
+
rawIdentify = String(await page.evaluate(IDENTIFY));
|
|
23132
|
+
}
|
|
22801
23133
|
if (rawIdentify === "missing")
|
|
22802
23134
|
return await warn(`cover: ${what} \u672A\u6807\u51FA\u4E0E\u8BF7\u6C42\u6587\u4EF6\u540C\u540D\u7684\u7D20\u6750\uFF0C\u672A\u9009\u62E9\uFF08\u4E0D\u6309\u9996\u56FE\u731C\u6D4B\uFF09`);
|
|
22803
23135
|
if (rawIdentify === "ambiguous")
|
|
@@ -22814,33 +23146,49 @@ async function setCover(page, source, deps, identity) {
|
|
|
22814
23146
|
const confirm = await tryFindSelector(page, COVER_CONFIRM, Math.min(deps.stepTimeoutMs, 8000));
|
|
22815
23147
|
if (confirm === undefined)
|
|
22816
23148
|
return await warn("cover: the choice panel offered no confirm control");
|
|
23149
|
+
const selectedSrc = (selected.src ?? "").trim();
|
|
23150
|
+
const cropSources = JSON.parse(String(await page.evaluate(`(() => {
|
|
23151
|
+
const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find(d => d.offsetWidth > 0 && d.querySelector('h3')?.textContent?.trim() === '\u7F16\u8F91\u5C01\u9762');
|
|
23152
|
+
return JSON.stringify([...(dialog?.querySelectorAll('img.crop-img') ?? [])]
|
|
23153
|
+
.filter(img => img.offsetWidth > 0 && getComputedStyle(img).visibility !== 'hidden').map(img => img.src));
|
|
23154
|
+
})()`)));
|
|
23155
|
+
const cropBound = selectedSrc !== "" && Array.isArray(cropSources) && cropSources.some((src) => typeof src === "string" && coverAssetIdentity(src) === coverAssetIdentity(selectedSrc));
|
|
23156
|
+
const beforeCommit = String(await page.evaluate(`(() => {
|
|
23157
|
+
const css = document.querySelector('#js_cover_area .js_cover_preview_new')?.style.backgroundImage ?? '';
|
|
23158
|
+
return css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
|
|
23159
|
+
})()`));
|
|
22817
23160
|
await clickSelector(page, confirm, deps.stepTimeoutMs);
|
|
22818
23161
|
const READBACK = `(() => {
|
|
22819
23162
|
const area = document.querySelector('#js_cover_area');
|
|
22820
23163
|
if (!area) return 'noarea';
|
|
22821
|
-
if (area.querySelector('.icon20_common.add_cover') && area.querySelector('.icon20_common.add_cover').offsetWidth > 0) return 'empty';
|
|
22822
23164
|
const preview = area.querySelector('.js_cover_preview_new');
|
|
22823
|
-
if (!preview || preview.offsetWidth === 0)
|
|
22824
|
-
|
|
22825
|
-
|
|
22826
|
-
|
|
23165
|
+
if (!preview || preview.offsetWidth === 0) {
|
|
23166
|
+
return area.querySelector('.icon20_common.add_cover')?.offsetWidth > 0 ? 'empty' : 'nopreview';
|
|
23167
|
+
}
|
|
23168
|
+
const css = preview.style.backgroundImage || preview.querySelector('[style*="background-image"]')?.style.backgroundImage || '';
|
|
23169
|
+
const backgroundUrl = css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
|
|
23170
|
+
const src = preview.querySelector('img')?.src || backgroundUrl || preview.querySelector('input[type=hidden]')?.value || '';
|
|
22827
23171
|
return src ? src : 'unknown';
|
|
22828
23172
|
})()`;
|
|
22829
23173
|
let committed = "";
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
22838
|
-
|
|
22839
|
-
|
|
22840
|
-
if (
|
|
22841
|
-
return await warn("cover: \u5DF2\u786E\u8BA4\u4F46\u65E0\u6CD5\u8BFB\u53D6\u5DF2\u63D0\u4EA4\u5C01\u9762\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9");
|
|
22842
|
-
|
|
22843
|
-
|
|
23174
|
+
let lastRead = "";
|
|
23175
|
+
const observed = await waitUntil(async () => {
|
|
23176
|
+
lastRead = String(await page.evaluate(READBACK).catch(() => "error"));
|
|
23177
|
+
if (["", "nopreview", "noarea", "empty", "unknown", "error"].includes(lastRead))
|
|
23178
|
+
return false;
|
|
23179
|
+
if (cropBound && lastRead === beforeCommit && coverAssetIdentity(lastRead) !== coverAssetIdentity(selectedSrc))
|
|
23180
|
+
return false;
|
|
23181
|
+
committed = lastRead;
|
|
23182
|
+
return true;
|
|
23183
|
+
}, Math.min(deps.stepTimeoutMs, 15000), 200);
|
|
23184
|
+
if (!observed)
|
|
23185
|
+
return await warn(lastRead === "empty" ? "cover: \u786E\u8BA4\u540E\u9762\u677F\u4ECD\u663E\u793A\u672A\u8BBE\u7F6E\u5C01\u9762\uFF0C\u63D0\u4EA4\u672A\u751F\u6548" : "cover: \u5DF2\u786E\u8BA4\u4F46\u65E0\u6CD5\u8BFB\u53D6\u5DF2\u63D0\u4EA4\u5C01\u9762\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9");
|
|
23186
|
+
let derivedFromBoundCrop = false;
|
|
23187
|
+
try {
|
|
23188
|
+
const url = new URL(committed);
|
|
23189
|
+
derivedFromBoundCrop = cropBound && committed !== beforeCommit && url.hostname === "mmbiz.qpic.cn" && ["http:", "https:"].includes(url.protocol);
|
|
23190
|
+
} catch {}
|
|
23191
|
+
if (selectedSrc === "" || coverAssetIdentity(committed) !== coverAssetIdentity(selectedSrc) && !derivedFromBoundCrop)
|
|
22844
23192
|
return await warn(`cover: \u5DF2\u63D0\u4EA4\u5C01\u9762\u7684\u7D20\u6750\u8EAB\u4EFD\u4E0E\u6240\u9009\u4E0D\u4E00\u81F4\uFF08selected=${selectedSrc || "\u672A\u8BFB\u5230"} committed=${committed.slice(0, 80)}\uFF09`);
|
|
22845
23193
|
return null;
|
|
22846
23194
|
} catch (error) {
|
|
@@ -22866,7 +23214,7 @@ function audioBlockOf(content) {
|
|
|
22866
23214
|
coverPath: typeof m.audioCoverPath === "string" ? m.audioCoverPath : ""
|
|
22867
23215
|
};
|
|
22868
23216
|
}
|
|
22869
|
-
async function enrichArticleDraft(page, content) {
|
|
23217
|
+
async function enrichArticleDraft(page, content, stepTimeoutMs) {
|
|
22870
23218
|
const enrichMeta = content.metadata?.wechatmp;
|
|
22871
23219
|
if (enrichMeta === undefined || typeof enrichMeta !== "object" || Array.isArray(enrichMeta))
|
|
22872
23220
|
return { enrichWarnings: [], coverWarnings: [] };
|
|
@@ -22886,7 +23234,7 @@ async function enrichArticleDraft(page, content) {
|
|
|
22886
23234
|
if (audioFailed) {
|
|
22887
23235
|
coverWarnings.push("cover: \u97F3\u9891\u4E0A\u4F20\u672A\u6210\u529F\uFF0C\u56FE\u7247\u5E93\u4E0D\u4FDD\u8BC1\u5DF2\u6709\u8BF7\u6C42\u5C01\u9762\uFF0C\u5DF2\u8DF3\u8FC7\u56FE\u7247\u5E93\u5C01\u9762");
|
|
22888
23236
|
} else {
|
|
22889
|
-
const cover = await setCover(page, "library", { stepTimeoutMs: 8000 }, { binding: audio.coverPath === "" ? undefined : audio.coverPath });
|
|
23237
|
+
const cover = await setCover(page, "library", { stepTimeoutMs: Math.min(stepTimeoutMs, 8000) }, { binding: audio.coverPath === "" ? undefined : await coverLibraryName(audio.coverPath) });
|
|
22890
23238
|
if (cover !== null)
|
|
22891
23239
|
coverWarnings.push(cover);
|
|
22892
23240
|
}
|
|
@@ -22899,9 +23247,6 @@ async function composeArticle(input, deps, entry) {
|
|
|
22899
23247
|
await attachImages(page, input.images, deps);
|
|
22900
23248
|
const titlePath = await typeTitle(page, deps);
|
|
22901
23249
|
await insertBody(page, deps);
|
|
22902
|
-
const layout = await applyLayout(page, deps);
|
|
22903
|
-
if (layout !== null)
|
|
22904
|
-
warnings.push(layout);
|
|
22905
23250
|
for (const warning of [
|
|
22906
23251
|
await fillOptional(page, DIGEST_INPUT, input.digest ?? "", "\u6458\u8981", deps),
|
|
22907
23252
|
await fillOptional(page, AUTHOR_INPUT, authorOf(deps.content), "\u4F5C\u8005", deps)
|
|
@@ -22918,8 +23263,11 @@ async function composeArticle(input, deps, entry) {
|
|
|
22918
23263
|
if (cover !== null)
|
|
22919
23264
|
warnings.push(cover);
|
|
22920
23265
|
}
|
|
22921
|
-
const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content);
|
|
23266
|
+
const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content, deps.stepTimeoutMs);
|
|
22922
23267
|
warnings.push(...coverWarnings);
|
|
23268
|
+
const layout = await applyLayout(page, deps);
|
|
23269
|
+
if (layout !== null)
|
|
23270
|
+
warnings.push(layout);
|
|
22923
23271
|
return { warnings, titlePath, enrichWarnings };
|
|
22924
23272
|
}
|
|
22925
23273
|
async function composeSticker(input, deps, entry) {
|
|
@@ -22990,7 +23338,7 @@ function baseMetadata(input, deps, entry) {
|
|
|
22990
23338
|
artifactPaths: artifactPaths(input),
|
|
22991
23339
|
entryPath: entry.path,
|
|
22992
23340
|
editorCreateType: entry.createType,
|
|
22993
|
-
editorUrl: entry.editor.url(),
|
|
23341
|
+
editorUrl: sanitizeSessionEvidence(entry.editor.url()),
|
|
22994
23342
|
...cover === undefined ? {} : { coverPath: cover }
|
|
22995
23343
|
};
|
|
22996
23344
|
}
|
|
@@ -23055,6 +23403,272 @@ async function saveDraft(input, deps, entry, composed) {
|
|
|
23055
23403
|
}
|
|
23056
23404
|
});
|
|
23057
23405
|
}
|
|
23406
|
+
function canonicalSavedEditorUrl(value) {
|
|
23407
|
+
try {
|
|
23408
|
+
const url = new URL(value);
|
|
23409
|
+
if (url.origin !== "https://mp.weixin.qq.com" || url.pathname !== "/cgi-bin/appmsg")
|
|
23410
|
+
return null;
|
|
23411
|
+
if (url.searchParams.get("action") !== "edit" || url.searchParams.get("isNew") === "1")
|
|
23412
|
+
return null;
|
|
23413
|
+
if ((url.searchParams.get("appmsgid") ?? "").trim() === "")
|
|
23414
|
+
return null;
|
|
23415
|
+
return url.toString();
|
|
23416
|
+
} catch {
|
|
23417
|
+
return null;
|
|
23418
|
+
}
|
|
23419
|
+
}
|
|
23420
|
+
function savedEditorUrlForMetadata(value) {
|
|
23421
|
+
return sanitizeSessionEvidence(value);
|
|
23422
|
+
}
|
|
23423
|
+
var PUBLIC_DRAFT_SNAPSHOT_SCRIPT = `/* __KK_WECHAT_PUBLISH_SNAPSHOT__ */ (() => {
|
|
23424
|
+
const norm = (value) => (value ?? '').replace(/[\\s\\u00a0]+/g, ' ').trim();
|
|
23425
|
+
const titleNode = document.querySelector('textarea#title, textarea.js_title');
|
|
23426
|
+
const title = titleNode?.value ?? [...document.querySelectorAll('.ProseMirror[data-placeholder*="\u6807\u9898"]')]
|
|
23427
|
+
.map((node) => node.textContent ?? '').join(' ');
|
|
23428
|
+
const body = [...document.querySelectorAll('.ProseMirror:not([data-placeholder])')]
|
|
23429
|
+
.map((node) => norm(node.textContent)).join('\\n');
|
|
23430
|
+
const media = [
|
|
23431
|
+
...document.querySelectorAll(
|
|
23432
|
+
'.ProseMirror mp-common-mpaudio, .ProseMirror mpvoice, .ProseMirror [data-mediaid], .ProseMirror audio, .ProseMirror video, .ProseMirror img',
|
|
23433
|
+
),
|
|
23434
|
+
]
|
|
23435
|
+
.map((node) => node.getAttribute('data-mediaid') ?? node.getAttribute('data-id') ??
|
|
23436
|
+
node.getAttribute('voice_encode_fileid') ?? node.getAttribute('data-src') ?? node.getAttribute('src') ??
|
|
23437
|
+
norm(node.textContent))
|
|
23438
|
+
.map(norm).filter((value) => value.length > 0);
|
|
23439
|
+
const area = document.querySelector('#js_cover_area');
|
|
23440
|
+
const coverNode = area?.querySelector('.js_cover_preview_new img, img, [style*="background-image"], input[type="hidden"]');
|
|
23441
|
+
const cover = coverNode?.getAttribute('src') ?? coverNode?.getAttribute('value') ??
|
|
23442
|
+
coverNode?.style.backgroundImage ?? '';
|
|
23443
|
+
const urlArea = document.querySelector('#js_article_url_area');
|
|
23444
|
+
const originalUrl = urlArea?.querySelector('input.js_url_checkbox')?.checked === true
|
|
23445
|
+
? norm(urlArea.querySelector('.article_url_setting')?.textContent) : '';
|
|
23446
|
+
return JSON.stringify({ title: norm(title), body, media, cover: norm(cover), originalUrl });
|
|
23447
|
+
})()`;
|
|
23448
|
+
function normalizeSnapshotText(value) {
|
|
23449
|
+
return value.replace(/[\s\u00a0]+/g, " ").trim();
|
|
23450
|
+
}
|
|
23451
|
+
async function readPublicDraftSnapshot(page) {
|
|
23452
|
+
try {
|
|
23453
|
+
const raw = await page.evaluate(PUBLIC_DRAFT_SNAPSHOT_SCRIPT);
|
|
23454
|
+
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
23455
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
23456
|
+
return null;
|
|
23457
|
+
const value = parsed;
|
|
23458
|
+
if (typeof value.title !== "string" || typeof value.body !== "string" || !Array.isArray(value.media) || value.media.some((item) => typeof item !== "string") || typeof value.cover !== "string")
|
|
23459
|
+
return null;
|
|
23460
|
+
return {
|
|
23461
|
+
title: normalizeSnapshotText(value.title),
|
|
23462
|
+
body: normalizeSnapshotText(value.body),
|
|
23463
|
+
media: value.media.map((item) => normalizeSnapshotText(item)),
|
|
23464
|
+
cover: normalizeSnapshotText(value.cover),
|
|
23465
|
+
originalUrl: typeof value.originalUrl === "string" ? normalizeSnapshotText(value.originalUrl) : ""
|
|
23466
|
+
};
|
|
23467
|
+
} catch {
|
|
23468
|
+
return null;
|
|
23469
|
+
}
|
|
23470
|
+
}
|
|
23471
|
+
function publicSnapshotDifferences(before, after) {
|
|
23472
|
+
const differences = [];
|
|
23473
|
+
if (before.title !== after.title)
|
|
23474
|
+
differences.push("title");
|
|
23475
|
+
if (before.body !== after.body)
|
|
23476
|
+
differences.push("body");
|
|
23477
|
+
const beforeMedia = before.media.map(snapshotMediaIdentity);
|
|
23478
|
+
const afterMedia = after.media.map(snapshotMediaIdentity);
|
|
23479
|
+
if (JSON.stringify(beforeMedia) !== JSON.stringify(afterMedia))
|
|
23480
|
+
differences.push("media");
|
|
23481
|
+
if (before.cover !== after.cover)
|
|
23482
|
+
differences.push("cover");
|
|
23483
|
+
if (before.originalUrl !== after.originalUrl)
|
|
23484
|
+
differences.push("originalUrl");
|
|
23485
|
+
return differences;
|
|
23486
|
+
}
|
|
23487
|
+
function snapshotMediaIdentity(value) {
|
|
23488
|
+
try {
|
|
23489
|
+
const url = new URL(value);
|
|
23490
|
+
if (url.hostname !== "mmbiz.qpic.cn")
|
|
23491
|
+
return value;
|
|
23492
|
+
const path = url.pathname.replace(/\/(?:0|300|640|1080)$/, "");
|
|
23493
|
+
return `${url.origin}${path}${url.search}${url.hash}`;
|
|
23494
|
+
} catch {
|
|
23495
|
+
return value;
|
|
23496
|
+
}
|
|
23497
|
+
}
|
|
23498
|
+
function snapshotValueIsEmpty(value) {
|
|
23499
|
+
const normalized = normalizeSnapshotText(value).toLowerCase();
|
|
23500
|
+
return normalized === "" || normalized === 'url("")' || normalized === "url('')" || normalized === "url()";
|
|
23501
|
+
}
|
|
23502
|
+
function publicSnapshotIsIncomplete(expected, actual) {
|
|
23503
|
+
if (!snapshotValueIsEmpty(expected.title) && snapshotValueIsEmpty(actual.title))
|
|
23504
|
+
return true;
|
|
23505
|
+
if (!snapshotValueIsEmpty(expected.body) && snapshotValueIsEmpty(actual.body))
|
|
23506
|
+
return true;
|
|
23507
|
+
if (expected.media.length > actual.media.length)
|
|
23508
|
+
return true;
|
|
23509
|
+
if (!snapshotValueIsEmpty(expected.cover) && snapshotValueIsEmpty(actual.cover))
|
|
23510
|
+
return true;
|
|
23511
|
+
if (expected.originalUrl !== "" && actual.originalUrl === "")
|
|
23512
|
+
return true;
|
|
23513
|
+
return false;
|
|
23514
|
+
}
|
|
23515
|
+
var PUBLIC_SAVED_EDITOR_RELOAD_LIMIT = 6;
|
|
23516
|
+
function appmsgIdFromCanonical(value) {
|
|
23517
|
+
const canonical = canonicalSavedEditorUrl(value);
|
|
23518
|
+
if (canonical === null)
|
|
23519
|
+
return null;
|
|
23520
|
+
try {
|
|
23521
|
+
const appmsgId = new URL(canonical).searchParams.get("appmsgid")?.trim();
|
|
23522
|
+
return appmsgId && appmsgId.length > 0 ? appmsgId : null;
|
|
23523
|
+
} catch {
|
|
23524
|
+
return null;
|
|
23525
|
+
}
|
|
23526
|
+
}
|
|
23527
|
+
async function waitForPublicEditorReadiness(page, deps) {
|
|
23528
|
+
let consecutiveSaved = 0;
|
|
23529
|
+
return waitUntil(async () => {
|
|
23530
|
+
const state = await page.evaluate(nativeLayoutSaveStateScript()).catch(() => "unknown");
|
|
23531
|
+
const legacyState = state === "saved" ? "not-needed" : await page.evaluate(nativeLegacyEditorReadyScript()).catch(() => "unknown");
|
|
23532
|
+
if (state === "saved" || legacyState === "ready") {
|
|
23533
|
+
consecutiveSaved += 1;
|
|
23534
|
+
return consecutiveSaved >= 2;
|
|
23535
|
+
}
|
|
23536
|
+
consecutiveSaved = 0;
|
|
23537
|
+
return false;
|
|
23538
|
+
}, Math.min(deps.stepTimeoutMs, 15000), deps.pollMs);
|
|
23539
|
+
}
|
|
23540
|
+
async function savePublicDraft(deps, entry) {
|
|
23541
|
+
const composed = await readPublicDraftSnapshot(entry.editor);
|
|
23542
|
+
if (composed === null)
|
|
23543
|
+
return fail("public save readiness: the composed editor snapshot (title/body/media/cover) was unreadable; no publish click attempted");
|
|
23544
|
+
const platformMetadata = deps.content.metadata?.wechatmp;
|
|
23545
|
+
const requestedUrl = platformMetadata !== null && typeof platformMetadata === "object" && "originalUrl" in platformMetadata ? platformMetadata.originalUrl : undefined;
|
|
23546
|
+
if (typeof requestedUrl === "string" && normalizeSnapshotText(requestedUrl) !== composed.originalUrl)
|
|
23547
|
+
return fail("public save readiness: requested \u539F\u6587\u94FE\u63A5 was not committed; no publish click attempted");
|
|
23548
|
+
const canonicalUrlBeforeSave = canonicalSavedEditorUrl(entry.editor.url());
|
|
23549
|
+
await sleep(deps.submitSettleMs);
|
|
23550
|
+
if (await closeOpenDialogs(entry.editor) !== "closed")
|
|
23551
|
+
return fail("public save readiness: a residual editor dialog blocked \u4FDD\u5B58\u4E3A\u8349\u7A3F; no publish click attempted");
|
|
23552
|
+
const saveControl = await findSelector(entry.editor, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control", deps.stepTimeoutMs);
|
|
23553
|
+
if (!entry.editor.saveDraftAndWait)
|
|
23554
|
+
return fail("public save readiness: the native \u4FDD\u5B58\u4E3A\u8349\u7A3F acknowledgement probe was unavailable; no publish click attempted");
|
|
23555
|
+
try {
|
|
23556
|
+
await entry.editor.saveDraftAndWait(saveControl, deps.draftTimeoutMs);
|
|
23557
|
+
} catch {
|
|
23558
|
+
return fail("public save readiness: native \u4FDD\u5B58\u4E3A\u8349\u7A3F was not acknowledged successfully; no publish click attempted");
|
|
23559
|
+
}
|
|
23560
|
+
let canonicalUrl = null;
|
|
23561
|
+
let refusal = null;
|
|
23562
|
+
const saved = await waitUntil(async () => {
|
|
23563
|
+
refusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
|
|
23564
|
+
if (refusal !== null)
|
|
23565
|
+
return true;
|
|
23566
|
+
canonicalUrl = canonicalSavedEditorUrl(entry.editor.url());
|
|
23567
|
+
return canonicalUrl !== null;
|
|
23568
|
+
}, deps.draftTimeoutMs, deps.pollMs);
|
|
23569
|
+
if (refusal !== null)
|
|
23570
|
+
return fail(`public save refused by ${TARGET}: ${refusal}; no publish click attempted`);
|
|
23571
|
+
if (!saved || canonicalUrl === null) {
|
|
23572
|
+
return fail(`public save readiness: \u4FDD\u5B58\u4E3A\u8349\u7A3F did not expose a canonical saved editor URL (action=edit with appmsgid, not isNew=1) within ${deps.draftTimeoutMs}ms; no publish click attempted`);
|
|
23573
|
+
}
|
|
23574
|
+
const savedCanonicalUrl = canonicalUrl;
|
|
23575
|
+
await sleep(Math.min(Math.max(deps.pollMs, 25), 500));
|
|
23576
|
+
await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
|
|
23577
|
+
let reloaded = null;
|
|
23578
|
+
let reloadRefusal = null;
|
|
23579
|
+
let reloads = 0;
|
|
23580
|
+
let lastReloadAt = Date.now();
|
|
23581
|
+
const reloadIntervalMs = Math.min(Math.max(deps.pollMs * 4, 50), 2000);
|
|
23582
|
+
const canonicalUrlWasPreexisting = canonicalUrlBeforeSave === savedCanonicalUrl;
|
|
23583
|
+
await waitUntil(async () => {
|
|
23584
|
+
reloadRefusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
|
|
23585
|
+
if (reloadRefusal !== null)
|
|
23586
|
+
return true;
|
|
23587
|
+
if (canonicalSavedEditorUrl(entry.editor.url()) === null)
|
|
23588
|
+
return false;
|
|
23589
|
+
reloaded = await readPublicDraftSnapshot(entry.editor);
|
|
23590
|
+
if (reloaded === null)
|
|
23591
|
+
return false;
|
|
23592
|
+
const differences = publicSnapshotDifferences(composed, reloaded);
|
|
23593
|
+
if (differences.length === 0)
|
|
23594
|
+
return true;
|
|
23595
|
+
if (!publicSnapshotIsIncomplete(composed, reloaded))
|
|
23596
|
+
return true;
|
|
23597
|
+
if (reloads >= PUBLIC_SAVED_EDITOR_RELOAD_LIMIT)
|
|
23598
|
+
return true;
|
|
23599
|
+
if (Date.now() - lastReloadAt < reloadIntervalMs)
|
|
23600
|
+
return false;
|
|
23601
|
+
reloads += 1;
|
|
23602
|
+
lastReloadAt = Date.now();
|
|
23603
|
+
await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
|
|
23604
|
+
return false;
|
|
23605
|
+
}, deps.draftTimeoutMs, deps.pollMs);
|
|
23606
|
+
if (reloadRefusal !== null)
|
|
23607
|
+
return fail(`public saved-editor refusal by ${TARGET}: ${reloadRefusal}; no publish click attempted`);
|
|
23608
|
+
if (reloaded === null)
|
|
23609
|
+
return fail(`public saved-editor readiness: the canonical editor reopened but its title/body/media/cover snapshot was unreadable within ${deps.draftTimeoutMs}ms; no publish click attempted`);
|
|
23610
|
+
const differences = publicSnapshotDifferences(composed, reloaded);
|
|
23611
|
+
if (differences.length > 0)
|
|
23612
|
+
return fail(`public saved editor content mismatch before publication (${differences.join(", ")}); no publish click attempted`);
|
|
23613
|
+
const autoSaveSettled = await waitForPublicEditorReadiness(entry.editor, deps);
|
|
23614
|
+
if (!autoSaveSettled)
|
|
23615
|
+
return fail(`public saved-editor readiness: native \u4FDD\u5B58\u72B6\u6001 did not provide two consecutive recognized readiness probes within ${Math.min(deps.stepTimeoutMs, 15000)}ms; no publish click attempted`);
|
|
23616
|
+
return {
|
|
23617
|
+
saveControl,
|
|
23618
|
+
canonicalUrl: savedCanonicalUrl,
|
|
23619
|
+
canonicalUrlForMetadata: savedEditorUrlForMetadata(savedCanonicalUrl),
|
|
23620
|
+
saveSignal: canonicalUrlWasPreexisting ? "persisted-snapshot" : "canonical-url-transition+snapshot",
|
|
23621
|
+
snapshot: composed
|
|
23622
|
+
};
|
|
23623
|
+
}
|
|
23624
|
+
async function recoverStaleVersion(deps, entry, saved) {
|
|
23625
|
+
const expectedAppmsgId = appmsgIdFromCanonical(saved.canonicalUrl);
|
|
23626
|
+
if (expectedAppmsgId === null)
|
|
23627
|
+
return fail("public stale-version recovery: the saved editor identity was not canonical; no retry attempted");
|
|
23628
|
+
const latest = await findUniqueVisibleSelector(entry.editor, PUBLISH_VIEW_LATEST, Math.min(CONFIRM_TIMEOUT_MS, deps.stepTimeoutMs), deps.pollMs);
|
|
23629
|
+
if (latest.selector === undefined)
|
|
23630
|
+
return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F was ${latest.reason}; no second \u53D1\u8868 click attempted`);
|
|
23631
|
+
const before = deps.pages().length;
|
|
23632
|
+
try {
|
|
23633
|
+
await clickSelector(entry.editor, latest.selector, deps.stepTimeoutMs);
|
|
23634
|
+
} catch {
|
|
23635
|
+
return fail("public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F could not be clicked; no second \u53D1\u8868 click attempted");
|
|
23636
|
+
}
|
|
23637
|
+
const latestEditor = await followNewPage(deps.pages, before, Math.min(deps.stepTimeoutMs, CONFIRM_TIMEOUT_MS)) ?? entry.editor;
|
|
23638
|
+
let latestCanonicalUrl = null;
|
|
23639
|
+
const canonicalReady = await waitUntil(async () => {
|
|
23640
|
+
latestCanonicalUrl = canonicalSavedEditorUrl(latestEditor.url());
|
|
23641
|
+
if (latestCanonicalUrl === null)
|
|
23642
|
+
return false;
|
|
23643
|
+
const refusal = await readStaleVersionRefusal(asPlaywrightPage(latestEditor)).catch(() => "__unreadable__");
|
|
23644
|
+
return refusal === null;
|
|
23645
|
+
}, deps.draftTimeoutMs, deps.pollMs);
|
|
23646
|
+
if (!canonicalReady || latestCanonicalUrl === null)
|
|
23647
|
+
return fail("public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F did not expose a canonical saved editor route; no second \u53D1\u8868 click attempted");
|
|
23648
|
+
const actualAppmsgId = appmsgIdFromCanonical(latestCanonicalUrl);
|
|
23649
|
+
if (actualAppmsgId !== expectedAppmsgId)
|
|
23650
|
+
return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F opened appmsgid=${actualAppmsgId ?? "unknown"}, expected appmsgid=${expectedAppmsgId}; no second \u53D1\u8868 click attempted`);
|
|
23651
|
+
let latestSnapshot = null;
|
|
23652
|
+
let differences = [];
|
|
23653
|
+
const snapshotSettled = await waitUntil(async () => {
|
|
23654
|
+
latestSnapshot = await readPublicDraftSnapshot(latestEditor);
|
|
23655
|
+
if (latestSnapshot === null)
|
|
23656
|
+
return false;
|
|
23657
|
+
differences = publicSnapshotDifferences(saved.snapshot, latestSnapshot);
|
|
23658
|
+
return differences.length === 0 || !publicSnapshotIsIncomplete(saved.snapshot, latestSnapshot);
|
|
23659
|
+
}, deps.draftTimeoutMs, deps.pollMs);
|
|
23660
|
+
if (!snapshotSettled || latestSnapshot === null)
|
|
23661
|
+
return fail("public stale-version recovery: latest editor snapshot was unreadable; no second \u53D1\u8868 click attempted");
|
|
23662
|
+
if (differences.length > 0)
|
|
23663
|
+
return fail(`public stale-version recovery: latest editor content mismatch (${differences.join(", ")}); no second \u53D1\u8868 click attempted`);
|
|
23664
|
+
if (!await waitForPublicEditorReadiness(latestEditor, deps))
|
|
23665
|
+
return fail("public stale-version recovery: latest editor did not provide two consecutive recognized readiness probes; no second \u53D1\u8868 click attempted");
|
|
23666
|
+
return {
|
|
23667
|
+
editor: latestEditor,
|
|
23668
|
+
canonicalUrl: latestCanonicalUrl,
|
|
23669
|
+
canonicalUrlForMetadata: savedEditorUrlForMetadata(latestCanonicalUrl)
|
|
23670
|
+
};
|
|
23671
|
+
}
|
|
23058
23672
|
async function publishOutcome(page) {
|
|
23059
23673
|
const success = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
|
|
23060
23674
|
if (success !== null)
|
|
@@ -23064,14 +23678,112 @@ async function publishOutcome(page) {
|
|
|
23064
23678
|
return `refused:${refusal}`;
|
|
23065
23679
|
return null;
|
|
23066
23680
|
}
|
|
23681
|
+
async function findUniqueVisibleSelector(page, selectors, timeoutMs, pollMs) {
|
|
23682
|
+
let reason = "missing";
|
|
23683
|
+
let selector;
|
|
23684
|
+
const found = await waitUntil(async () => {
|
|
23685
|
+
for (const candidate of selectors) {
|
|
23686
|
+
const locator = page.locator(candidate);
|
|
23687
|
+
const count = await locator.count().catch(() => 0);
|
|
23688
|
+
if (count > 1) {
|
|
23689
|
+
reason = "ambiguous";
|
|
23690
|
+
return true;
|
|
23691
|
+
}
|
|
23692
|
+
if (count !== 1)
|
|
23693
|
+
continue;
|
|
23694
|
+
const visible = await locator.first().isVisible().catch(() => false);
|
|
23695
|
+
if (!visible)
|
|
23696
|
+
continue;
|
|
23697
|
+
selector = candidate;
|
|
23698
|
+
return true;
|
|
23699
|
+
}
|
|
23700
|
+
return false;
|
|
23701
|
+
}, timeoutMs, pollMs);
|
|
23702
|
+
return found && selector !== undefined ? { selector, reason } : { reason };
|
|
23703
|
+
}
|
|
23704
|
+
async function readPublishNotifySubscribers(page) {
|
|
23705
|
+
try {
|
|
23706
|
+
const value = await page.evaluate(PUBLISH_NOTIFY_STATE_SCRIPT);
|
|
23707
|
+
return typeof value === "boolean" ? value : null;
|
|
23708
|
+
} catch {
|
|
23709
|
+
return null;
|
|
23710
|
+
}
|
|
23711
|
+
}
|
|
23712
|
+
function shouldNotifySubscribers(content) {
|
|
23713
|
+
const wechatmp = content.metadata?.wechatmp;
|
|
23714
|
+
if (wechatmp === null || typeof wechatmp !== "object" || Array.isArray(wechatmp))
|
|
23715
|
+
return true;
|
|
23716
|
+
return wechatmp.notifySubscribers !== false;
|
|
23717
|
+
}
|
|
23718
|
+
async function confirmPublicPublish(page, notifySubscribers, stepTimeoutMs, pollMs) {
|
|
23719
|
+
const confirmationTimeoutMs = Math.min(CONFIRM_TIMEOUT_MS, stepTimeoutMs);
|
|
23720
|
+
const dialog = await findUniqueVisibleSelector(page, PUBLISH_DIALOG, confirmationTimeoutMs, pollMs);
|
|
23721
|
+
if (dialog.selector === undefined) {
|
|
23722
|
+
if (dialog.reason === "missing") {
|
|
23723
|
+
const refusal = await readStaleVersionRefusal(asPlaywrightPage(page)).catch(() => null);
|
|
23724
|
+
if (refusal !== null)
|
|
23725
|
+
return { kind: "stale-version", refusal };
|
|
23726
|
+
}
|
|
23727
|
+
return fail(`public confirmation: the \u53D1\u8868 dialog was ${dialog.reason}; no final publish click attempted`);
|
|
23728
|
+
}
|
|
23729
|
+
let observedNotifySubscribers = null;
|
|
23730
|
+
if (!notifySubscribers) {
|
|
23731
|
+
const toggle = await findUniqueVisibleSelector(page, PUBLISH_NOTIFY_TOGGLE, confirmationTimeoutMs, pollMs);
|
|
23732
|
+
if (toggle.selector === undefined)
|
|
23733
|
+
return fail(`public confirmation: \u7FA4\u53D1\u901A\u77E5 switch was ${toggle.reason === "missing" ? "unavailable" : "ambiguous"}; no final publish click attempted`);
|
|
23734
|
+
const before = await readPublishNotifySubscribers(page);
|
|
23735
|
+
if (before === null)
|
|
23736
|
+
return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch state was unreadable; no final publish click attempted");
|
|
23737
|
+
if (before)
|
|
23738
|
+
await clickSelector(page, toggle.selector, stepTimeoutMs);
|
|
23739
|
+
const off = await waitUntil(async () => await readPublishNotifySubscribers(page) === false, confirmationTimeoutMs, pollMs);
|
|
23740
|
+
if (!off)
|
|
23741
|
+
return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not verify OFF; no final publish click attempted");
|
|
23742
|
+
}
|
|
23743
|
+
const final = await findUniqueVisibleSelector(page, PUBLISH_FINAL, confirmationTimeoutMs, pollMs);
|
|
23744
|
+
if (final.selector === undefined)
|
|
23745
|
+
return fail(`public confirmation: final \u53D1\u8868 button was ${final.reason}; no final publish click attempted`);
|
|
23746
|
+
observedNotifySubscribers = await readPublishNotifySubscribers(page);
|
|
23747
|
+
if (!notifySubscribers && observedNotifySubscribers !== false)
|
|
23748
|
+
return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not remain verifiably OFF; no final publish click attempted");
|
|
23749
|
+
await clickSelector(page, final.selector, stepTimeoutMs);
|
|
23750
|
+
return {
|
|
23751
|
+
finalSelector: final.selector,
|
|
23752
|
+
notifySubscribers: observedNotifySubscribers,
|
|
23753
|
+
notifySubscribersPolicy: notifySubscribers ? "preserve" : "disabled"
|
|
23754
|
+
};
|
|
23755
|
+
}
|
|
23067
23756
|
async function publishContent(input, deps, entry, composed) {
|
|
23757
|
+
const saved = input.operation === "\u6587\u7AE0" ? await savePublicDraft(deps, entry) : undefined;
|
|
23758
|
+
if (saved !== undefined && "ok" in saved)
|
|
23759
|
+
return saved;
|
|
23760
|
+
const notifySubscribers = shouldNotifySubscribers(deps.content);
|
|
23761
|
+
let staleVersionRecoveryCount = 0;
|
|
23762
|
+
let finalSavedEditorUrl = saved?.canonicalUrlForMetadata;
|
|
23068
23763
|
await sleep(deps.submitSettleMs);
|
|
23069
23764
|
const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
|
|
23070
23765
|
const urlBefore = entry.editor.url();
|
|
23071
23766
|
await clickSelector(entry.editor, control, deps.stepTimeoutMs);
|
|
23072
|
-
|
|
23073
|
-
if (
|
|
23074
|
-
|
|
23767
|
+
let confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
|
|
23768
|
+
if ("ok" in confirmation)
|
|
23769
|
+
return confirmation;
|
|
23770
|
+
if ("kind" in confirmation) {
|
|
23771
|
+
if (saved === undefined)
|
|
23772
|
+
return fail(`public confirmation: ${confirmation.refusal}; no canonical saved-editor evidence was available for recovery; no second \u53D1\u8868 click attempted`);
|
|
23773
|
+
const recovered = await recoverStaleVersion(deps, entry, saved);
|
|
23774
|
+
if ("ok" in recovered)
|
|
23775
|
+
return recovered;
|
|
23776
|
+
entry.editor = recovered.editor;
|
|
23777
|
+
finalSavedEditorUrl = recovered.canonicalUrlForMetadata;
|
|
23778
|
+
staleVersionRecoveryCount = 1;
|
|
23779
|
+
const retryControl = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control after \u67E5\u770B\u65B0\u8349\u7A3F", deps.stepTimeoutMs);
|
|
23780
|
+
await clickSelector(entry.editor, retryControl, deps.stepTimeoutMs);
|
|
23781
|
+
confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
|
|
23782
|
+
if ("ok" in confirmation)
|
|
23783
|
+
return confirmation;
|
|
23784
|
+
if ("kind" in confirmation)
|
|
23785
|
+
return fail(`public confirmation: ${confirmation.refusal} after bounded \u67E5\u770B\u65B0\u8349\u7A3F recovery; no final publish click attempted`);
|
|
23786
|
+
}
|
|
23075
23787
|
const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
|
|
23076
23788
|
timeoutMs: deps.stepTimeoutMs,
|
|
23077
23789
|
label: `publish confirmation '${control}'`,
|
|
@@ -23089,7 +23801,16 @@ async function publishContent(input, deps, entry, composed) {
|
|
|
23089
23801
|
message: `${TARGET} ${input.operation} published`,
|
|
23090
23802
|
metadata: {
|
|
23091
23803
|
...baseMetadata(input, deps, entry),
|
|
23804
|
+
...saved === undefined ? {} : {
|
|
23805
|
+
draftSaveControl: saved.saveControl,
|
|
23806
|
+
savedEditorUrl: finalSavedEditorUrl ?? saved.canonicalUrlForMetadata,
|
|
23807
|
+
draftSaveSignal: saved.saveSignal,
|
|
23808
|
+
staleVersionRecoveryCount
|
|
23809
|
+
},
|
|
23092
23810
|
publishControl: control,
|
|
23811
|
+
finalPublishControl: confirmation.finalSelector,
|
|
23812
|
+
notifySubscribers: confirmation.notifySubscribers,
|
|
23813
|
+
notifySubscribersPolicy: confirmation.notifySubscribersPolicy,
|
|
23093
23814
|
publishSignal: outcome.evidence,
|
|
23094
23815
|
titlePath: composed.titlePath,
|
|
23095
23816
|
...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
|
|
@@ -23185,7 +23906,11 @@ async function runMode(input, deps) {
|
|
|
23185
23906
|
const composed = await compose(input, deps, entry);
|
|
23186
23907
|
const diagnostics = [...composed.warnings, ...composed.enrichWarnings];
|
|
23187
23908
|
try {
|
|
23188
|
-
|
|
23909
|
+
const result = deps.publishStatus === "public" ? await publishContent(input, deps, entry, composed) : await saveDraft(input, deps, entry, composed);
|
|
23910
|
+
if (!result.ok && diagnostics.length > 0) {
|
|
23911
|
+
return { ...result, error: `${result.error} \u2014 compose diagnostics: ${diagnostics.join(" | ")}` };
|
|
23912
|
+
}
|
|
23913
|
+
return result;
|
|
23189
23914
|
} catch (error) {
|
|
23190
23915
|
const message = error instanceof Error ? error.message : String(error);
|
|
23191
23916
|
const blocked = await openDialogInventory(entry.editor);
|
|
@@ -23273,15 +23998,20 @@ function escapeHtml(text) {
|
|
|
23273
23998
|
}
|
|
23274
23999
|
async function insertAudioMaterial(page, input) {
|
|
23275
24000
|
const { title, audioPath, coverPath } = input;
|
|
23276
|
-
const CARET = `(() => {
|
|
24001
|
+
const CARET = `(async () => {
|
|
23277
24002
|
const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
|
|
23278
24003
|
if (!pm || !pm.firstChild) return;
|
|
24004
|
+
pm.focus();
|
|
23279
24005
|
const r = document.createRange();
|
|
23280
|
-
|
|
24006
|
+
// The first child can be a non-editable image atom. Select the boundary before it,
|
|
24007
|
+
// not a position inside that atom which ProseMirror discards on toolbar focus.
|
|
24008
|
+
r.setStart(pm, 0);
|
|
23281
24009
|
r.collapse(true);
|
|
23282
24010
|
const sel = window.getSelection();
|
|
23283
24011
|
sel?.removeAllRanges();
|
|
23284
24012
|
sel?.addRange(r);
|
|
24013
|
+
document.dispatchEvent(new Event('selectionchange'));
|
|
24014
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
23285
24015
|
})()`;
|
|
23286
24016
|
const norm = (value) => typeof value === "string" ? value.replace(/[\s\u00a0]+/g, " ").trim() : "";
|
|
23287
24017
|
const MATCH = `(() => {
|
|
@@ -23303,8 +24033,8 @@ async function insertAudioMaterial(page, input) {
|
|
|
23303
24033
|
return JSON.stringify({ state: 'checked', identity });
|
|
23304
24034
|
})()`;
|
|
23305
24035
|
const AGREE = `(() => {
|
|
23306
|
-
const node = [...document.querySelectorAll('label,
|
|
23307
|
-
.find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.
|
|
24036
|
+
const node = [...document.querySelectorAll('.weui-desktop-dialog label, .audio_dialog label')]
|
|
24037
|
+
.find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.querySelector('input[type=checkbox]'));
|
|
23308
24038
|
if (!node) return 'no-label';
|
|
23309
24039
|
const scope = node.closest('label') ?? node.parentElement;
|
|
23310
24040
|
const cb = scope?.querySelector('input[type=checkbox]') ?? document.querySelector('input[type=checkbox].js_agree');
|
|
@@ -23327,9 +24057,10 @@ async function insertAudioMaterial(page, input) {
|
|
|
23327
24057
|
if (!pm) return JSON.stringify({ state: 'noeditor' });
|
|
23328
24058
|
const first = pm.firstElementChild;
|
|
23329
24059
|
if (!first) return JSON.stringify({ state: 'absent' });
|
|
23330
|
-
const
|
|
23331
|
-
|
|
23332
|
-
|
|
24060
|
+
const selector = 'mp-common-mpaudio, mpvoice, [data-mediaid]';
|
|
24061
|
+
const node = first.matches(selector) ? first : first.querySelector(selector);
|
|
24062
|
+
if (!node) return JSON.stringify({ state: pm.querySelector(selector) ? 'not-top' : 'absent' });
|
|
24063
|
+
const identity = node.getAttribute('data-mediaid') ?? node.getAttribute('cover') ?? node.getAttribute('voice_encode_fileid') ?? node.getAttribute('data-appid') ??
|
|
23333
24064
|
node.getAttribute('data-src') ?? node.getAttribute('src') ?? '';
|
|
23334
24065
|
return JSON.stringify({ state: 'top', identity });
|
|
23335
24066
|
})()`;
|
|
@@ -23338,7 +24069,7 @@ async function insertAudioMaterial(page, input) {
|
|
|
23338
24069
|
if (!uploadControl.found)
|
|
23339
24070
|
return "\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog";
|
|
23340
24071
|
await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? "text=\u4E0A\u4F20\u97F3\u9891", 8000);
|
|
23341
|
-
const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT);
|
|
24072
|
+
const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
|
|
23342
24073
|
if (!audioInput.found)
|
|
23343
24074
|
return "\u97F3\u9891: no audio file input appeared";
|
|
23344
24075
|
await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(", "), audioPath, {
|
|
@@ -23347,11 +24078,63 @@ async function insertAudioMaterial(page, input) {
|
|
|
23347
24078
|
const coverControl = await trySelectors(page, UPLOAD_COVER);
|
|
23348
24079
|
if (coverControl.found) {
|
|
23349
24080
|
await clickSelector(page, coverControl.selector ?? UPLOAD_COVER[0] ?? "text=\u4E0A\u4F20\u5C01\u9762", 8000);
|
|
23350
|
-
const coverInput = await trySelectors(page, AUDIO_COVER_INPUT);
|
|
23351
|
-
if (coverInput.found)
|
|
23352
|
-
|
|
23353
|
-
|
|
23354
|
-
|
|
24081
|
+
const coverInput = await trySelectors(page, AUDIO_COVER_INPUT, { visible: false });
|
|
24082
|
+
if (coverInput.found) {
|
|
24083
|
+
const chooser = '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247"))';
|
|
24084
|
+
const hasChooser = await page.locator(chooser).count() > 0;
|
|
24085
|
+
const coverName = hasChooser ? await coverLibraryName(coverPath) : baseName(coverPath);
|
|
24086
|
+
let existing = 0;
|
|
24087
|
+
if (hasChooser) {
|
|
24088
|
+
const libraryReady = await waitUntil(async () => Boolean(await page.evaluate(`!document.querySelector('#js_image_dialog_list_wrp .weui-desktop-loading')`)), 5000);
|
|
24089
|
+
if (!libraryReady)
|
|
24090
|
+
return "\u97F3\u9891: \u5C01\u9762\u7D20\u6750\u5E93\u52A0\u8F7D\u672A\u5B8C\u6210\uFF0C\u672A\u4E0A\u4F20\u91CD\u590D\u5C01\u9762";
|
|
24091
|
+
existing = Number(await page.evaluate(`([...document.querySelectorAll('.weui-desktop-img-picker__item')].filter(row => row.offsetWidth > 0 && row.querySelector('.weui-desktop-img-picker__img-title')?.textContent?.trim() === ${JSON.stringify(coverName)})).length`));
|
|
24092
|
+
if (existing > 1)
|
|
24093
|
+
return "\u97F3\u9891: \u5185\u5BB9\u7ED1\u5B9A\u5C01\u9762\u5B58\u5728\u591A\u4E2A\u540C\u540D\u7D20\u6750\uFF0C\u672A\u731C\u6D4B\u9009\u62E9";
|
|
24094
|
+
}
|
|
24095
|
+
let uploadPath = coverPath;
|
|
24096
|
+
let temporaryDir;
|
|
24097
|
+
if (hasChooser && existing === 0) {
|
|
24098
|
+
temporaryDir = join6(tmpdir(), `kk-wechat-cover-${randomUUID()}`);
|
|
24099
|
+
if (!mkdirExclusive(temporaryDir))
|
|
24100
|
+
throw new Error("Private cover upload directory already exists");
|
|
24101
|
+
uploadPath = join6(temporaryDir, coverName);
|
|
24102
|
+
}
|
|
24103
|
+
try {
|
|
24104
|
+
if (temporaryDir !== undefined)
|
|
24105
|
+
await createNodeFileSystem().copy(coverPath, uploadPath);
|
|
24106
|
+
if (existing === 0) {
|
|
24107
|
+
await uploadFile(asPlaywrightPage(page), coverInput.selector ?? AUDIO_COVER_INPUT.join(", "), uploadPath, {
|
|
24108
|
+
timeoutMs: 60000
|
|
24109
|
+
});
|
|
24110
|
+
}
|
|
24111
|
+
if (hasChooser) {
|
|
24112
|
+
const selectedName = JSON.stringify(coverName);
|
|
24113
|
+
const selected = await waitUntil(async () => Boolean(await page.evaluate(`(() => {
|
|
24114
|
+
const rows = [...document.querySelectorAll('.weui-desktop-img-picker__item')]
|
|
24115
|
+
.filter(row => row.offsetWidth > 0 && row.querySelector('.weui-desktop-img-picker__img-title')?.textContent?.trim() === ${selectedName});
|
|
24116
|
+
if (rows.length !== 1) return false;
|
|
24117
|
+
const row = rows[0];
|
|
24118
|
+
if (!row.classList.contains('selected')) row.click();
|
|
24119
|
+
return row.classList.contains('selected');
|
|
24120
|
+
})()`)), 30000);
|
|
24121
|
+
if (!selected)
|
|
24122
|
+
return "\u97F3\u9891: \u4E0A\u4F20\u5C01\u9762\u672A\u51FA\u73B0\u552F\u4E00\u5DF2\u9009\u4E2D\u7684\u540C\u540D\u56FE\u7247\uFF0C\u672A\u63D0\u4EA4\u5C01\u9762";
|
|
24123
|
+
const next = await findSelector(page, [`${chooser} button:has-text("\u4E0B\u4E00\u6B65"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u4E0B\u4E00\u6B65", 30000);
|
|
24124
|
+
await clickSelector(page, next, 8000);
|
|
24125
|
+
const finish = await findSelector(page, [`${chooser} button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u5B8C\u6210", 30000);
|
|
24126
|
+
await clickSelector(page, finish, 8000);
|
|
24127
|
+
const closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 8000);
|
|
24128
|
+
if (!closed)
|
|
24129
|
+
return "\u97F3\u9891: \u5C01\u9762\u9009\u62E9\u5668\u5B8C\u6210\u540E\u672A\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58\u97F3\u9891";
|
|
24130
|
+
}
|
|
24131
|
+
} finally {
|
|
24132
|
+
if (temporaryDir !== undefined) {
|
|
24133
|
+
await createNodeFileSystem().deleteFile(uploadPath);
|
|
24134
|
+
rmdir(temporaryDir);
|
|
24135
|
+
}
|
|
24136
|
+
}
|
|
24137
|
+
}
|
|
23355
24138
|
}
|
|
23356
24139
|
const titleInput = await trySelectors(page, AUDIO_TITLE_INPUT);
|
|
23357
24140
|
if (titleInput.found)
|
|
@@ -23359,10 +24142,8 @@ async function insertAudioMaterial(page, input) {
|
|
|
23359
24142
|
const agreed = String(await page.evaluate(AGREE));
|
|
23360
24143
|
if (agreed !== "checked")
|
|
23361
24144
|
return `\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`;
|
|
23362
|
-
const save = await
|
|
23363
|
-
|
|
23364
|
-
return "\u97F3\u9891: \u4E0A\u4F20\u8868\u5355\u7684\u4FDD\u5B58\u6309\u94AE\u672A\u627E\u5230";
|
|
23365
|
-
await clickSelector(page, UPLOAD_SAVE.join(", "), 8000);
|
|
24145
|
+
const save = await findSelector(page, UPLOAD_SAVE.map((selector) => `${selector}:not(.weui-desktop-btn_disabled):not([disabled])`), "\u97F3\u9891\u4E0A\u4F20\u5B8C\u6210\u4E14\u5C01\u9762\u5DF2\u63D0\u4EA4\u540E\u7684\u4FDD\u5B58\u6309\u94AE", 90000);
|
|
24146
|
+
await clickSelector(page, save, 8000);
|
|
23366
24147
|
return null;
|
|
23367
24148
|
};
|
|
23368
24149
|
const matchOnce = async () => {
|
|
@@ -23382,6 +24163,7 @@ async function insertAudioMaterial(page, input) {
|
|
|
23382
24163
|
};
|
|
23383
24164
|
try {
|
|
23384
24165
|
await page.evaluate(CARET);
|
|
24166
|
+
await page.press?.(process.platform === "darwin" ? "Meta+ArrowUp" : "Control+Home");
|
|
23385
24167
|
await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_TOOLBAR_TRIGGER)})?.click()`);
|
|
23386
24168
|
await page.waitForTimeout?.(300);
|
|
23387
24169
|
await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
|
|
@@ -23449,7 +24231,7 @@ var SUBMIT_SETTLE_MS = 1500;
|
|
|
23449
24231
|
var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
|
|
23450
24232
|
var SUBMIT_ATTEMPTS = 2;
|
|
23451
24233
|
function fail2(error) {
|
|
23452
|
-
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
24234
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
|
|
23453
24235
|
}
|
|
23454
24236
|
function isConsoleReady(page) {
|
|
23455
24237
|
return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
|
|
@@ -23460,6 +24242,25 @@ function isWechatLoggedIn(page) {
|
|
|
23460
24242
|
function storageStatePath(profileDir) {
|
|
23461
24243
|
return `${profileDir}/storage-state.json`;
|
|
23462
24244
|
}
|
|
24245
|
+
async function persistWechatSessionState(context, profileDir) {
|
|
24246
|
+
const target = storageStatePath(profileDir);
|
|
24247
|
+
const temporary = `${target}.${process.pid}.${randomUUID2()}.tmp`;
|
|
24248
|
+
const fs = createNodeFileSystem();
|
|
24249
|
+
let reserved = false;
|
|
24250
|
+
try {
|
|
24251
|
+
createExclusiveFile(temporary);
|
|
24252
|
+
reserved = true;
|
|
24253
|
+
await context.storageState({ path: temporary });
|
|
24254
|
+
chmod(temporary, 384);
|
|
24255
|
+
await fs.rename(temporary, target);
|
|
24256
|
+
} finally {
|
|
24257
|
+
if (reserved) {
|
|
24258
|
+
await Promise.resolve().then(() => fs.deleteFile(temporary)).catch(() => {
|
|
24259
|
+
return;
|
|
24260
|
+
});
|
|
24261
|
+
}
|
|
24262
|
+
}
|
|
24263
|
+
}
|
|
23463
24264
|
async function checkWechatSession(profileDir, opts = {}) {
|
|
23464
24265
|
const { page, context } = await openWithState(profileDir, opts.headless ?? true);
|
|
23465
24266
|
try {
|
|
@@ -23482,6 +24283,8 @@ async function openWechatLogin(opts) {
|
|
|
23482
24283
|
headless: false,
|
|
23483
24284
|
launcher: kkBrowserLauncher
|
|
23484
24285
|
});
|
|
24286
|
+
let failed = false;
|
|
24287
|
+
let failure;
|
|
23485
24288
|
try {
|
|
23486
24289
|
const page = context.pages()[0] ?? await context.newPage();
|
|
23487
24290
|
await page.goto(CONSOLE_URL);
|
|
@@ -23489,10 +24292,23 @@ async function openWechatLogin(opts) {
|
|
|
23489
24292
|
if (!ready)
|
|
23490
24293
|
throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
|
|
23491
24294
|
await page.waitForTimeout(1e4);
|
|
23492
|
-
|
|
23493
|
-
|
|
24295
|
+
try {
|
|
24296
|
+
await persistWechatSessionState(context, opts.profileDir);
|
|
24297
|
+
} catch {
|
|
24298
|
+
throw new Error("wechatmp authenticated session snapshot failed");
|
|
24299
|
+
}
|
|
24300
|
+
} catch (error) {
|
|
24301
|
+
failed = true;
|
|
24302
|
+
failure = error;
|
|
24303
|
+
}
|
|
24304
|
+
try {
|
|
23494
24305
|
await context.close();
|
|
24306
|
+
} catch {
|
|
24307
|
+
if (!failed)
|
|
24308
|
+
echoError("wechatmp browser context close failed after authenticated session snapshot; saved state preserved");
|
|
23495
24309
|
}
|
|
24310
|
+
if (failed)
|
|
24311
|
+
throw failure;
|
|
23496
24312
|
}
|
|
23497
24313
|
async function openWechatSession(profileDir, opts = {}) {
|
|
23498
24314
|
const { context } = await openWithState(profileDir, opts.headless ?? false, opts.url ?? CONSOLE_URL);
|
|
@@ -23538,7 +24354,10 @@ async function openWithState(profileDir, headless, url = CONSOLE_URL) {
|
|
|
23538
24354
|
}
|
|
23539
24355
|
async function defaultOpenConsole(profileDir) {
|
|
23540
24356
|
const { page, context } = await openWithState(profileDir, true);
|
|
23541
|
-
return
|
|
24357
|
+
return {
|
|
24358
|
+
...wrapPlaywrightContext(context, page),
|
|
24359
|
+
persistSession: () => persistWechatSessionState(context, profileDir)
|
|
24360
|
+
};
|
|
23542
24361
|
}
|
|
23543
24362
|
|
|
23544
24363
|
class WechatmpBrowserTransport {
|
|
@@ -23588,33 +24407,48 @@ class WechatmpBrowserTransport {
|
|
|
23588
24407
|
} catch (error) {
|
|
23589
24408
|
return fail2(error instanceof Error ? error.message : String(error));
|
|
23590
24409
|
}
|
|
24410
|
+
let result;
|
|
23591
24411
|
try {
|
|
23592
24412
|
const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
|
|
23593
24413
|
if (!ready) {
|
|
23594
|
-
|
|
23595
|
-
}
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
24414
|
+
result = fail2(`no authenticated ${TARGET} console within ${this.consoleReadyTimeoutMs}ms \u2014 the preflight passed but the console did not report a session route (profile ${profileDir})`);
|
|
24415
|
+
} else {
|
|
24416
|
+
const title = typeof content.title === "string" ? content.title.trim() : "";
|
|
24417
|
+
const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
|
|
24418
|
+
result = await runMode(validation.input, {
|
|
24419
|
+
entry: opened.page,
|
|
24420
|
+
pages: opened.pages,
|
|
24421
|
+
newPage: opened.newPage,
|
|
24422
|
+
content,
|
|
24423
|
+
publishStatus,
|
|
24424
|
+
profileDir,
|
|
24425
|
+
driver: this.driver,
|
|
24426
|
+
stepTimeoutMs: this.stepTimeoutMs,
|
|
24427
|
+
pollMs: this.pollMs,
|
|
24428
|
+
submitSettleMs: this.submitSettleMs,
|
|
24429
|
+
draftTimeoutMs: this.draftTimeoutMs,
|
|
24430
|
+
submitAttempts: this.submitAttempts,
|
|
24431
|
+
draftBaseline
|
|
24432
|
+
});
|
|
24433
|
+
}
|
|
23613
24434
|
} catch (error) {
|
|
23614
|
-
|
|
24435
|
+
result = fail2(error instanceof Error ? error.message : String(error));
|
|
23615
24436
|
} finally {
|
|
23616
|
-
|
|
24437
|
+
if (opened.persistSession !== undefined) {
|
|
24438
|
+
try {
|
|
24439
|
+
if (isConsoleReady(opened.page))
|
|
24440
|
+
await opened.persistSession();
|
|
24441
|
+
} catch {
|
|
24442
|
+
echoError("wechatmp-pub: authenticated session snapshot failed; publication result preserved");
|
|
24443
|
+
}
|
|
24444
|
+
}
|
|
24445
|
+
try {
|
|
24446
|
+
await opened.close();
|
|
24447
|
+
} catch {
|
|
24448
|
+
echoError("wechatmp-pub: browser context close failed; publication result preserved");
|
|
24449
|
+
}
|
|
23617
24450
|
}
|
|
24451
|
+
return result;
|
|
23618
24452
|
}
|
|
23619
24453
|
}
|
|
23620
24454
|
function getPublishTransport(driver = "script") {
|
|
@@ -23667,6 +24501,7 @@ export {
|
|
|
23667
24501
|
markdownToWechatHtml,
|
|
23668
24502
|
openWechatLogin,
|
|
23669
24503
|
openWechatSession,
|
|
24504
|
+
persistWechatSessionState,
|
|
23670
24505
|
processPublishIO,
|
|
23671
24506
|
runMode,
|
|
23672
24507
|
storageStatePath,
|