@gobing-ai/knowledge-kit 0.1.9 → 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 +132 -25
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +4 -0
- package/plugins/generations/core-facts-gen/dist/index.js +4 -0
- package/plugins/generations/daily-article-gen/dist/index.js +4 -0
- package/plugins/generations/dailynews-gen/dist/index.js +4 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +102 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +2 -4
- package/plugins/generations/gemini-tts/dist/index.js +4 -0
- package/plugins/generations/image-gen/dist/index.js +4 -0
- package/plugins/generations/news-report-gen/dist/index.js +4 -0
- package/plugins/generations/voice-gen/dist/index.js +4 -0
- package/plugins/generations/voxcpm-tts/dist/index.js +4 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +4 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +4 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +11 -3
- package/plugins/ingestions/last30days-ingest/src/index.ts +14 -3
- package/plugins/ingestions/web-search/dist/index.js +4 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1083 -49
- package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
- package/plugins/kk/scripts/sentences.ts +134 -0
- package/plugins/kk/scripts/series-names.ts +124 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +194 -113
- package/plugins/kk/workflows/kk-daily-safety.yaml +160 -36
- package/plugins/publishings/bilibili-pub/dist/index.js +6 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +6 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +6 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/emdash-pub/dist/index.js +4 -0
- package/plugins/publishings/infoq-pub/dist/index.js +6 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/jike-pub/dist/index.js +6 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/juejin-pub/dist/index.js +6 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/medium-pub/dist/index.js +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +151 -16
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/podcast-pub/src/seo.ts +2 -4
- package/plugins/publishings/podcast-pub/src/show-notes.ts +3 -8
- package/plugins/publishings/qiita-pub/dist/index.js +4 -0
- package/plugins/publishings/shipinhao-pub/dist/index.js +6 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +6 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +6 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +691 -63
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +284 -0
- package/plugins/publishings/surfdash-pub/src/index.ts +656 -84
- package/plugins/publishings/surfdash-pub/src/patch.ts +44 -7
- package/plugins/publishings/threads-pub/dist/index.js +4 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +937 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +64 -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 +6 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/x-pub/dist/index.js +6 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/xhs-pub/dist/index.js +6 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +6 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/zenn-pub/dist/index.js +4 -0
|
@@ -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
|
|
@@ -20162,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20162
20163
|
|
|
20163
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20164
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20165
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20166
20171
|
import { homedir } from "os";
|
|
20167
20172
|
import { join, resolve } from "path";
|
|
@@ -20369,9 +20374,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20369
20374
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20370
20375
|
import {
|
|
20371
20376
|
chmodSync,
|
|
20377
|
+
closeSync,
|
|
20372
20378
|
copyFileSync,
|
|
20373
20379
|
lstatSync,
|
|
20374
20380
|
mkdirSync as mkdirSync2,
|
|
20381
|
+
openSync,
|
|
20375
20382
|
readdirSync as readdirSync2,
|
|
20376
20383
|
realpathSync as realpathSync2,
|
|
20377
20384
|
rmdirSync
|
|
@@ -20412,6 +20419,9 @@ function rmdir(path) {
|
|
|
20412
20419
|
function chmod(path, mode) {
|
|
20413
20420
|
chmodSync(path, mode);
|
|
20414
20421
|
}
|
|
20422
|
+
function createExclusiveFile(path) {
|
|
20423
|
+
closeSync(openSync(path, "wx", 384));
|
|
20424
|
+
}
|
|
20415
20425
|
|
|
20416
20426
|
// ../../packages/publish-harness/src/bot-transaction.ts
|
|
20417
20427
|
import { homedir as homedir2 } from "os";
|
|
@@ -20616,7 +20626,6 @@ class BotError extends Error {
|
|
|
20616
20626
|
this.code = code;
|
|
20617
20627
|
}
|
|
20618
20628
|
}
|
|
20619
|
-
|
|
20620
20629
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20621
20630
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20622
20631
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -20835,6 +20844,11 @@ function editorTextScript(selector) {
|
|
|
20835
20844
|
// ../../plugins/publishings/wechatmp-pub/src/index.ts
|
|
20836
20845
|
init_dist();
|
|
20837
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
|
+
|
|
20838
20852
|
// ../../plugins/publishings/wechatmp-pub/src/cgi.ts
|
|
20839
20853
|
var MP_ORIGIN = "https://mp.weixin.qq.com";
|
|
20840
20854
|
var FILETRANSFER_URL = `${MP_ORIGIN}/cgi-bin/filetransfer`;
|
|
@@ -22035,6 +22049,9 @@ async function prepareArticleMedia(page, session, input) {
|
|
|
22035
22049
|
// ../../plugins/publishings/wechatmp-pub/src/page.ts
|
|
22036
22050
|
var POLL_MS = 500;
|
|
22037
22051
|
var STEP_TIMEOUT_MS = 30000;
|
|
22052
|
+
function sanitizeSessionEvidence(value) {
|
|
22053
|
+
return value.replace(/([?&](?:token|ticket|authkey)=)[^&\s"'<>)]*/gi, "$1[REDACTED]");
|
|
22054
|
+
}
|
|
22038
22055
|
async function modalState(page) {
|
|
22039
22056
|
try {
|
|
22040
22057
|
const raw = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog__wrp, .weui-desktop-dialog')].some((d) => d.offsetWidth)`);
|
|
@@ -22096,9 +22113,89 @@ async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
|
|
|
22096
22113
|
async function fillSelector(page, selector, value) {
|
|
22097
22114
|
await page.locator(selector).first().fill(value);
|
|
22098
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
|
+
}
|
|
22099
22195
|
function wrapPlaywrightPage(raw) {
|
|
22100
22196
|
const pw = raw;
|
|
22101
22197
|
return {
|
|
22198
|
+
saveDraftAndWait: (selector, timeoutMs) => saveNativeDraft(raw, selector, timeoutMs),
|
|
22102
22199
|
goto: (url, options) => pw.goto(url, { waitUntil: "domcontentloaded", ...options }).then(() => {
|
|
22103
22200
|
return;
|
|
22104
22201
|
}),
|
|
@@ -22445,12 +22542,37 @@ var DIGEST_INPUT = ["textarea#js_description", "textarea.js_desc"];
|
|
|
22445
22542
|
var AUTHOR_INPUT = ["input#author", "input.js_author"];
|
|
22446
22543
|
var IMAGE_FILE_INPUT = ['input[type="file"][accept*="image/"]', 'input[type="file"][accept*=".png"]'];
|
|
22447
22544
|
var LAYOUT_CONTROL = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
|
|
22448
|
-
var LAYOUT_TARGET = [
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
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
|
+
];
|
|
22454
22576
|
var DIALOG_DISMISS = [
|
|
22455
22577
|
'.weui-desktop-dialog:visible button:has-text("\u53D6\u6D88")',
|
|
22456
22578
|
".weui-desktop-dialog__close",
|
|
@@ -22537,19 +22659,58 @@ var AUDIO_COVER_INPUT = [
|
|
|
22537
22659
|
'.audio_dialog input[type="file"][accept*="image/"]',
|
|
22538
22660
|
'input[type="file"][accept*=".png"]',
|
|
22539
22661
|
'.weui-desktop-dialog input[type="file"][accept*="image"]',
|
|
22540
|
-
'.audio_dialog input[type="file"][accept*="image"]'
|
|
22662
|
+
'.audio_dialog input[type="file"][accept*="image"]',
|
|
22663
|
+
'input[type="file"][accept*="image/bmp"]'
|
|
22541
22664
|
];
|
|
22542
22665
|
var AUDIO_TITLE_INPUT = [
|
|
22543
22666
|
'.weui-desktop-dialog input[placeholder*="\u6807\u9898"]',
|
|
22544
22667
|
'.audio_dialog input[placeholder*="\u6807\u9898"]',
|
|
22545
22668
|
".audio_dialog input.frm_input"
|
|
22546
22669
|
];
|
|
22547
|
-
var UPLOAD_SAVE = ['.
|
|
22670
|
+
var UPLOAD_SAVE = ['.audio-upload-dialog button:text-is("\u4FDD\u5B58")', '.weui-desktop-dialog button:text-is("\u4FDD\u5B58")'];
|
|
22548
22671
|
var SAVE_DRAFT = ['button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")', "text=\u4FDD\u5B58\u4E3A\u8349\u7A3F"];
|
|
22549
22672
|
var PUBLISH_SEND = ["button.mass_send", ".mass_send", 'button:has-text("\u53D1\u8868")'];
|
|
22550
|
-
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
|
+
];
|
|
22551
22696
|
var PUBLISH_SUCCESS = ["\u53D1\u8868\u6210\u529F", "\u5DF2\u53D1\u8868"];
|
|
22552
|
-
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
|
+
];
|
|
22553
22714
|
var DRAFT_STAMP = "\u66F4\u65B0\u4E8E";
|
|
22554
22715
|
var CONFIRM_TIMEOUT_MS = 5000;
|
|
22555
22716
|
var TOKEN_LINK_SCRIPT = `(() => {
|
|
@@ -22559,7 +22720,7 @@ var TOKEN_LINK_SCRIPT = `(() => {
|
|
|
22559
22720
|
return link ? (new URL(link).searchParams.get('token') ?? '') : '';
|
|
22560
22721
|
})()`;
|
|
22561
22722
|
function fail(error) {
|
|
22562
|
-
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
22723
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
|
|
22563
22724
|
}
|
|
22564
22725
|
function sleep(ms) {
|
|
22565
22726
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -22715,21 +22876,154 @@ async function insertBody(page, deps) {
|
|
|
22715
22876
|
const selector = await findSelector(page, BODY_BLOCK, "article body", deps.stepTimeoutMs);
|
|
22716
22877
|
await pasteHtml(asPlaywrightPage(page), selector, markdownToWechatHtml(body), { timeoutMs: deps.stepTimeoutMs });
|
|
22717
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
|
+
}
|
|
22718
22980
|
async function applyLayout(page, deps) {
|
|
22719
22981
|
const control = await tryFindSelector(page, LAYOUT_CONTROL, Math.min(deps.stepTimeoutMs, 5000));
|
|
22720
22982
|
if (control === undefined)
|
|
22721
22983
|
return null;
|
|
22984
|
+
const before = deps.pages().length;
|
|
22722
22985
|
try {
|
|
22723
22986
|
await clickSelector(page, control, deps.stepTimeoutMs);
|
|
22724
22987
|
} catch (error) {
|
|
22725
22988
|
return `\u4E00\u952E\u6392\u7248: click failed \u2014 ${error instanceof Error ? error.message : String(error)}`;
|
|
22726
22989
|
}
|
|
22727
|
-
|
|
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
|
+
}
|
|
22728
23020
|
if (option === undefined) {
|
|
22729
|
-
|
|
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");
|
|
22730
23024
|
}
|
|
22731
23025
|
try {
|
|
22732
|
-
await clickSelector(
|
|
23026
|
+
await clickSelector(layoutPage, option, 5000);
|
|
22733
23027
|
return null;
|
|
22734
23028
|
} catch (error) {
|
|
22735
23029
|
return `\u4E00\u952E\u6392\u7248: \u5339\u914D\u76EE\u6807\u683C\u5F0F not applied \u2014 ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -22751,6 +23045,20 @@ async function fillOptional(page, selectors, value, what, deps) {
|
|
|
22751
23045
|
function baseName(path) {
|
|
22752
23046
|
return path.split(/[\\/]/).pop() ?? path;
|
|
22753
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
|
+
}
|
|
22754
23062
|
async function setCover(page, source, deps, identity) {
|
|
22755
23063
|
const picker = source === "library" ? COVER_FROM_LIBRARY : COVER_FROM_CONTENT;
|
|
22756
23064
|
const what = source === "library" ? "\u4ECE\u56FE\u7247\u5E93\u9009\u62E9" : "\u4ECE\u6B63\u6587\u9009\u62E9";
|
|
@@ -22759,18 +23067,22 @@ async function setCover(page, source, deps, identity) {
|
|
|
22759
23067
|
...identity.binding ? [baseName(identity.binding)] : []
|
|
22760
23068
|
].map((name) => name.trim()).filter((name) => name.length > 0);
|
|
22761
23069
|
const warn = async (message) => {
|
|
23070
|
+
const inventory = await openDialogInventory(page);
|
|
22762
23071
|
await dismissOpenDialog(page);
|
|
22763
|
-
return message;
|
|
23072
|
+
return inventory ? `${message} (dialogs: ${inventory})` : message;
|
|
22764
23073
|
};
|
|
22765
23074
|
try {
|
|
22766
23075
|
const open = await tryFindSelector(page, COVER_PANEL_OPEN, Math.min(deps.stepTimeoutMs, 5000));
|
|
22767
23076
|
if (open === undefined)
|
|
22768
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");
|
|
22769
23078
|
await clickSelector(page, open, deps.stepTimeoutMs);
|
|
22770
|
-
const
|
|
22771
|
-
if (
|
|
22772
|
-
|
|
22773
|
-
|
|
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
|
+
}
|
|
22774
23086
|
if (wanted.length === 0)
|
|
22775
23087
|
return await warn(`cover: no requested cover file identity to match against \u2014 ${what} not driven`);
|
|
22776
23088
|
const IDENTIFY = `(() => {
|
|
@@ -22781,19 +23093,43 @@ async function setCover(page, source, deps, identity) {
|
|
|
22781
23093
|
const el = row.querySelector('[title], [data-name], [data-file_name]') ?? row;
|
|
22782
23094
|
const attrs = [el.getAttribute?.('title'), el.getAttribute?.('data-name'), el.getAttribute?.('data-file_name')];
|
|
22783
23095
|
const text = [...row.querySelectorAll('[class*="name" i], [class*="title" i]')].map((n) => n.textContent ?? '');
|
|
22784
|
-
return [...attrs, ...text].map((v) => (v ?? '').trim()).
|
|
23096
|
+
return [...attrs, ...text].map((v) => (v ?? '').trim()).filter((v) => v.length > 0);
|
|
22785
23097
|
};
|
|
22786
|
-
const hits = rows.filter((row) => wanted.some((w) => nameOf(row)
|
|
23098
|
+
const hits = rows.filter((row) => wanted.some((w) => nameOf(row).includes(w)));
|
|
22787
23099
|
if (hits.length === 0) return 'missing';
|
|
22788
23100
|
if (hits.length > 1) return 'ambiguous';
|
|
22789
23101
|
const row = hits[0];
|
|
22790
|
-
row.
|
|
22791
|
-
row.click();
|
|
23102
|
+
if (!row.classList.contains('selected')) row.click();
|
|
22792
23103
|
const checked = row.querySelector('input[type=checkbox], [role=checkbox]');
|
|
22793
|
-
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;
|
|
22794
23107
|
return JSON.stringify({ picked: true, src });
|
|
22795
23108
|
})()`;
|
|
22796
|
-
|
|
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
|
+
}
|
|
22797
23133
|
if (rawIdentify === "missing")
|
|
22798
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`);
|
|
22799
23135
|
if (rawIdentify === "ambiguous")
|
|
@@ -22810,33 +23146,49 @@ async function setCover(page, source, deps, identity) {
|
|
|
22810
23146
|
const confirm = await tryFindSelector(page, COVER_CONFIRM, Math.min(deps.stepTimeoutMs, 8000));
|
|
22811
23147
|
if (confirm === undefined)
|
|
22812
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
|
+
})()`));
|
|
22813
23160
|
await clickSelector(page, confirm, deps.stepTimeoutMs);
|
|
22814
23161
|
const READBACK = `(() => {
|
|
22815
23162
|
const area = document.querySelector('#js_cover_area');
|
|
22816
23163
|
if (!area) return 'noarea';
|
|
22817
|
-
if (area.querySelector('.icon20_common.add_cover') && area.querySelector('.icon20_common.add_cover').offsetWidth > 0) return 'empty';
|
|
22818
23164
|
const preview = area.querySelector('.js_cover_preview_new');
|
|
22819
|
-
if (!preview || preview.offsetWidth === 0)
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
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 || '';
|
|
22823
23171
|
return src ? src : 'unknown';
|
|
22824
23172
|
})()`;
|
|
22825
23173
|
let committed = "";
|
|
22826
|
-
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
22831
|
-
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
if (
|
|
22837
|
-
return await warn("cover: \u5DF2\u786E\u8BA4\u4F46\u65E0\u6CD5\u8BFB\u53D6\u5DF2\u63D0\u4EA4\u5C01\u9762\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9");
|
|
22838
|
-
|
|
22839
|
-
|
|
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)
|
|
22840
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`);
|
|
22841
23193
|
return null;
|
|
22842
23194
|
} catch (error) {
|
|
@@ -22862,7 +23214,7 @@ function audioBlockOf(content) {
|
|
|
22862
23214
|
coverPath: typeof m.audioCoverPath === "string" ? m.audioCoverPath : ""
|
|
22863
23215
|
};
|
|
22864
23216
|
}
|
|
22865
|
-
async function enrichArticleDraft(page, content) {
|
|
23217
|
+
async function enrichArticleDraft(page, content, stepTimeoutMs) {
|
|
22866
23218
|
const enrichMeta = content.metadata?.wechatmp;
|
|
22867
23219
|
if (enrichMeta === undefined || typeof enrichMeta !== "object" || Array.isArray(enrichMeta))
|
|
22868
23220
|
return { enrichWarnings: [], coverWarnings: [] };
|
|
@@ -22882,7 +23234,7 @@ async function enrichArticleDraft(page, content) {
|
|
|
22882
23234
|
if (audioFailed) {
|
|
22883
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");
|
|
22884
23236
|
} else {
|
|
22885
|
-
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) });
|
|
22886
23238
|
if (cover !== null)
|
|
22887
23239
|
coverWarnings.push(cover);
|
|
22888
23240
|
}
|
|
@@ -22895,9 +23247,6 @@ async function composeArticle(input, deps, entry) {
|
|
|
22895
23247
|
await attachImages(page, input.images, deps);
|
|
22896
23248
|
const titlePath = await typeTitle(page, deps);
|
|
22897
23249
|
await insertBody(page, deps);
|
|
22898
|
-
const layout = await applyLayout(page, deps);
|
|
22899
|
-
if (layout !== null)
|
|
22900
|
-
warnings.push(layout);
|
|
22901
23250
|
for (const warning of [
|
|
22902
23251
|
await fillOptional(page, DIGEST_INPUT, input.digest ?? "", "\u6458\u8981", deps),
|
|
22903
23252
|
await fillOptional(page, AUTHOR_INPUT, authorOf(deps.content), "\u4F5C\u8005", deps)
|
|
@@ -22914,8 +23263,11 @@ async function composeArticle(input, deps, entry) {
|
|
|
22914
23263
|
if (cover !== null)
|
|
22915
23264
|
warnings.push(cover);
|
|
22916
23265
|
}
|
|
22917
|
-
const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content);
|
|
23266
|
+
const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content, deps.stepTimeoutMs);
|
|
22918
23267
|
warnings.push(...coverWarnings);
|
|
23268
|
+
const layout = await applyLayout(page, deps);
|
|
23269
|
+
if (layout !== null)
|
|
23270
|
+
warnings.push(layout);
|
|
22919
23271
|
return { warnings, titlePath, enrichWarnings };
|
|
22920
23272
|
}
|
|
22921
23273
|
async function composeSticker(input, deps, entry) {
|
|
@@ -22986,7 +23338,7 @@ function baseMetadata(input, deps, entry) {
|
|
|
22986
23338
|
artifactPaths: artifactPaths(input),
|
|
22987
23339
|
entryPath: entry.path,
|
|
22988
23340
|
editorCreateType: entry.createType,
|
|
22989
|
-
editorUrl: entry.editor.url(),
|
|
23341
|
+
editorUrl: sanitizeSessionEvidence(entry.editor.url()),
|
|
22990
23342
|
...cover === undefined ? {} : { coverPath: cover }
|
|
22991
23343
|
};
|
|
22992
23344
|
}
|
|
@@ -23051,6 +23403,272 @@ async function saveDraft(input, deps, entry, composed) {
|
|
|
23051
23403
|
}
|
|
23052
23404
|
});
|
|
23053
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
|
+
}
|
|
23054
23672
|
async function publishOutcome(page) {
|
|
23055
23673
|
const success = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
|
|
23056
23674
|
if (success !== null)
|
|
@@ -23060,14 +23678,112 @@ async function publishOutcome(page) {
|
|
|
23060
23678
|
return `refused:${refusal}`;
|
|
23061
23679
|
return null;
|
|
23062
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
|
+
}
|
|
23063
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;
|
|
23064
23763
|
await sleep(deps.submitSettleMs);
|
|
23065
23764
|
const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
|
|
23066
23765
|
const urlBefore = entry.editor.url();
|
|
23067
23766
|
await clickSelector(entry.editor, control, deps.stepTimeoutMs);
|
|
23068
|
-
|
|
23069
|
-
if (
|
|
23070
|
-
|
|
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
|
+
}
|
|
23071
23787
|
const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
|
|
23072
23788
|
timeoutMs: deps.stepTimeoutMs,
|
|
23073
23789
|
label: `publish confirmation '${control}'`,
|
|
@@ -23085,7 +23801,16 @@ async function publishContent(input, deps, entry, composed) {
|
|
|
23085
23801
|
message: `${TARGET} ${input.operation} published`,
|
|
23086
23802
|
metadata: {
|
|
23087
23803
|
...baseMetadata(input, deps, entry),
|
|
23804
|
+
...saved === undefined ? {} : {
|
|
23805
|
+
draftSaveControl: saved.saveControl,
|
|
23806
|
+
savedEditorUrl: finalSavedEditorUrl ?? saved.canonicalUrlForMetadata,
|
|
23807
|
+
draftSaveSignal: saved.saveSignal,
|
|
23808
|
+
staleVersionRecoveryCount
|
|
23809
|
+
},
|
|
23088
23810
|
publishControl: control,
|
|
23811
|
+
finalPublishControl: confirmation.finalSelector,
|
|
23812
|
+
notifySubscribers: confirmation.notifySubscribers,
|
|
23813
|
+
notifySubscribersPolicy: confirmation.notifySubscribersPolicy,
|
|
23089
23814
|
publishSignal: outcome.evidence,
|
|
23090
23815
|
titlePath: composed.titlePath,
|
|
23091
23816
|
...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
|
|
@@ -23181,7 +23906,11 @@ async function runMode(input, deps) {
|
|
|
23181
23906
|
const composed = await compose(input, deps, entry);
|
|
23182
23907
|
const diagnostics = [...composed.warnings, ...composed.enrichWarnings];
|
|
23183
23908
|
try {
|
|
23184
|
-
|
|
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;
|
|
23185
23914
|
} catch (error) {
|
|
23186
23915
|
const message = error instanceof Error ? error.message : String(error);
|
|
23187
23916
|
const blocked = await openDialogInventory(entry.editor);
|
|
@@ -23269,15 +23998,20 @@ function escapeHtml(text) {
|
|
|
23269
23998
|
}
|
|
23270
23999
|
async function insertAudioMaterial(page, input) {
|
|
23271
24000
|
const { title, audioPath, coverPath } = input;
|
|
23272
|
-
const CARET = `(() => {
|
|
24001
|
+
const CARET = `(async () => {
|
|
23273
24002
|
const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
|
|
23274
24003
|
if (!pm || !pm.firstChild) return;
|
|
24004
|
+
pm.focus();
|
|
23275
24005
|
const r = document.createRange();
|
|
23276
|
-
|
|
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);
|
|
23277
24009
|
r.collapse(true);
|
|
23278
24010
|
const sel = window.getSelection();
|
|
23279
24011
|
sel?.removeAllRanges();
|
|
23280
24012
|
sel?.addRange(r);
|
|
24013
|
+
document.dispatchEvent(new Event('selectionchange'));
|
|
24014
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
23281
24015
|
})()`;
|
|
23282
24016
|
const norm = (value) => typeof value === "string" ? value.replace(/[\s\u00a0]+/g, " ").trim() : "";
|
|
23283
24017
|
const MATCH = `(() => {
|
|
@@ -23299,8 +24033,8 @@ async function insertAudioMaterial(page, input) {
|
|
|
23299
24033
|
return JSON.stringify({ state: 'checked', identity });
|
|
23300
24034
|
})()`;
|
|
23301
24035
|
const AGREE = `(() => {
|
|
23302
|
-
const node = [...document.querySelectorAll('label,
|
|
23303
|
-
.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]'));
|
|
23304
24038
|
if (!node) return 'no-label';
|
|
23305
24039
|
const scope = node.closest('label') ?? node.parentElement;
|
|
23306
24040
|
const cb = scope?.querySelector('input[type=checkbox]') ?? document.querySelector('input[type=checkbox].js_agree');
|
|
@@ -23323,9 +24057,10 @@ async function insertAudioMaterial(page, input) {
|
|
|
23323
24057
|
if (!pm) return JSON.stringify({ state: 'noeditor' });
|
|
23324
24058
|
const first = pm.firstElementChild;
|
|
23325
24059
|
if (!first) return JSON.stringify({ state: 'absent' });
|
|
23326
|
-
const
|
|
23327
|
-
|
|
23328
|
-
|
|
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') ??
|
|
23329
24064
|
node.getAttribute('data-src') ?? node.getAttribute('src') ?? '';
|
|
23330
24065
|
return JSON.stringify({ state: 'top', identity });
|
|
23331
24066
|
})()`;
|
|
@@ -23334,7 +24069,7 @@ async function insertAudioMaterial(page, input) {
|
|
|
23334
24069
|
if (!uploadControl.found)
|
|
23335
24070
|
return "\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog";
|
|
23336
24071
|
await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? "text=\u4E0A\u4F20\u97F3\u9891", 8000);
|
|
23337
|
-
const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT);
|
|
24072
|
+
const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
|
|
23338
24073
|
if (!audioInput.found)
|
|
23339
24074
|
return "\u97F3\u9891: no audio file input appeared";
|
|
23340
24075
|
await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(", "), audioPath, {
|
|
@@ -23343,11 +24078,63 @@ async function insertAudioMaterial(page, input) {
|
|
|
23343
24078
|
const coverControl = await trySelectors(page, UPLOAD_COVER);
|
|
23344
24079
|
if (coverControl.found) {
|
|
23345
24080
|
await clickSelector(page, coverControl.selector ?? UPLOAD_COVER[0] ?? "text=\u4E0A\u4F20\u5C01\u9762", 8000);
|
|
23346
|
-
const coverInput = await trySelectors(page, AUDIO_COVER_INPUT);
|
|
23347
|
-
if (coverInput.found)
|
|
23348
|
-
|
|
23349
|
-
|
|
23350
|
-
|
|
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
|
+
}
|
|
23351
24138
|
}
|
|
23352
24139
|
const titleInput = await trySelectors(page, AUDIO_TITLE_INPUT);
|
|
23353
24140
|
if (titleInput.found)
|
|
@@ -23355,10 +24142,8 @@ async function insertAudioMaterial(page, input) {
|
|
|
23355
24142
|
const agreed = String(await page.evaluate(AGREE));
|
|
23356
24143
|
if (agreed !== "checked")
|
|
23357
24144
|
return `\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`;
|
|
23358
|
-
const save = await
|
|
23359
|
-
|
|
23360
|
-
return "\u97F3\u9891: \u4E0A\u4F20\u8868\u5355\u7684\u4FDD\u5B58\u6309\u94AE\u672A\u627E\u5230";
|
|
23361
|
-
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);
|
|
23362
24147
|
return null;
|
|
23363
24148
|
};
|
|
23364
24149
|
const matchOnce = async () => {
|
|
@@ -23378,6 +24163,7 @@ async function insertAudioMaterial(page, input) {
|
|
|
23378
24163
|
};
|
|
23379
24164
|
try {
|
|
23380
24165
|
await page.evaluate(CARET);
|
|
24166
|
+
await page.press?.(process.platform === "darwin" ? "Meta+ArrowUp" : "Control+Home");
|
|
23381
24167
|
await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_TOOLBAR_TRIGGER)})?.click()`);
|
|
23382
24168
|
await page.waitForTimeout?.(300);
|
|
23383
24169
|
await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
|
|
@@ -23445,7 +24231,7 @@ var SUBMIT_SETTLE_MS = 1500;
|
|
|
23445
24231
|
var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
|
|
23446
24232
|
var SUBMIT_ATTEMPTS = 2;
|
|
23447
24233
|
function fail2(error) {
|
|
23448
|
-
return ResultSchema.parse({ ok: false, target: TARGET, error });
|
|
24234
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
|
|
23449
24235
|
}
|
|
23450
24236
|
function isConsoleReady(page) {
|
|
23451
24237
|
return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
|
|
@@ -23456,6 +24242,25 @@ function isWechatLoggedIn(page) {
|
|
|
23456
24242
|
function storageStatePath(profileDir) {
|
|
23457
24243
|
return `${profileDir}/storage-state.json`;
|
|
23458
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
|
+
}
|
|
23459
24264
|
async function checkWechatSession(profileDir, opts = {}) {
|
|
23460
24265
|
const { page, context } = await openWithState(profileDir, opts.headless ?? true);
|
|
23461
24266
|
try {
|
|
@@ -23478,6 +24283,8 @@ async function openWechatLogin(opts) {
|
|
|
23478
24283
|
headless: false,
|
|
23479
24284
|
launcher: kkBrowserLauncher
|
|
23480
24285
|
});
|
|
24286
|
+
let failed = false;
|
|
24287
|
+
let failure;
|
|
23481
24288
|
try {
|
|
23482
24289
|
const page = context.pages()[0] ?? await context.newPage();
|
|
23483
24290
|
await page.goto(CONSOLE_URL);
|
|
@@ -23485,10 +24292,23 @@ async function openWechatLogin(opts) {
|
|
|
23485
24292
|
if (!ready)
|
|
23486
24293
|
throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
|
|
23487
24294
|
await page.waitForTimeout(1e4);
|
|
23488
|
-
|
|
23489
|
-
|
|
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 {
|
|
23490
24305
|
await context.close();
|
|
24306
|
+
} catch {
|
|
24307
|
+
if (!failed)
|
|
24308
|
+
echoError("wechatmp browser context close failed after authenticated session snapshot; saved state preserved");
|
|
23491
24309
|
}
|
|
24310
|
+
if (failed)
|
|
24311
|
+
throw failure;
|
|
23492
24312
|
}
|
|
23493
24313
|
async function openWechatSession(profileDir, opts = {}) {
|
|
23494
24314
|
const { context } = await openWithState(profileDir, opts.headless ?? false, opts.url ?? CONSOLE_URL);
|
|
@@ -23534,7 +24354,10 @@ async function openWithState(profileDir, headless, url = CONSOLE_URL) {
|
|
|
23534
24354
|
}
|
|
23535
24355
|
async function defaultOpenConsole(profileDir) {
|
|
23536
24356
|
const { page, context } = await openWithState(profileDir, true);
|
|
23537
|
-
return
|
|
24357
|
+
return {
|
|
24358
|
+
...wrapPlaywrightContext(context, page),
|
|
24359
|
+
persistSession: () => persistWechatSessionState(context, profileDir)
|
|
24360
|
+
};
|
|
23538
24361
|
}
|
|
23539
24362
|
|
|
23540
24363
|
class WechatmpBrowserTransport {
|
|
@@ -23584,33 +24407,48 @@ class WechatmpBrowserTransport {
|
|
|
23584
24407
|
} catch (error) {
|
|
23585
24408
|
return fail2(error instanceof Error ? error.message : String(error));
|
|
23586
24409
|
}
|
|
24410
|
+
let result;
|
|
23587
24411
|
try {
|
|
23588
24412
|
const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
|
|
23589
24413
|
if (!ready) {
|
|
23590
|
-
|
|
23591
|
-
}
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
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
|
+
}
|
|
23609
24434
|
} catch (error) {
|
|
23610
|
-
|
|
24435
|
+
result = fail2(error instanceof Error ? error.message : String(error));
|
|
23611
24436
|
} finally {
|
|
23612
|
-
|
|
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
|
+
}
|
|
23613
24450
|
}
|
|
24451
|
+
return result;
|
|
23614
24452
|
}
|
|
23615
24453
|
}
|
|
23616
24454
|
function getPublishTransport(driver = "script") {
|
|
@@ -23663,6 +24501,7 @@ export {
|
|
|
23663
24501
|
markdownToWechatHtml,
|
|
23664
24502
|
openWechatLogin,
|
|
23665
24503
|
openWechatSession,
|
|
24504
|
+
persistWechatSessionState,
|
|
23666
24505
|
processPublishIO,
|
|
23667
24506
|
runMode,
|
|
23668
24507
|
storageStatePath,
|