@gobing-ai/knowledge-kit 0.1.10 → 0.1.12

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.
Files changed (158) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +2478 -1737
  3. package/package.json +1 -1
  4. package/plugins/generations/image-gen/README.md +108 -0
  5. package/plugins/generations/image-gen/assets/sample/cover__clean-webapp-ui.jpg +0 -0
  6. package/plugins/generations/image-gen/assets/sample/cover__cute.jpg +0 -0
  7. package/plugins/generations/image-gen/assets/sample/cover__daily-news.jpg +0 -0
  8. package/plugins/generations/image-gen/assets/sample/cover__editorial.jpg +0 -0
  9. package/plugins/generations/image-gen/assets/sample/cover__fresh.jpg +0 -0
  10. package/plugins/generations/image-gen/assets/sample/cover__minimalist.jpg +0 -0
  11. package/plugins/generations/image-gen/assets/sample/cover__photorealistic.jpg +0 -0
  12. package/plugins/generations/image-gen/assets/sample/cover__sketch.jpg +0 -0
  13. package/plugins/generations/image-gen/assets/sample/cover__technical-diagram.jpg +0 -0
  14. package/plugins/generations/image-gen/assets/sample/cover__vibrant.jpg +0 -0
  15. package/plugins/generations/image-gen/assets/sample/cover__warm.jpg +0 -0
  16. package/plugins/generations/image-gen/assets/sample/cover__xhs-knowledge.jpg +0 -0
  17. package/plugins/generations/image-gen/assets/sample/daily-news-cover__clean-webapp-ui.jpg +0 -0
  18. package/plugins/generations/image-gen/assets/sample/daily-news-cover__cute.jpg +0 -0
  19. package/plugins/generations/image-gen/assets/sample/daily-news-cover__daily-news.jpg +0 -0
  20. package/plugins/generations/image-gen/assets/sample/daily-news-cover__editorial.jpg +0 -0
  21. package/plugins/generations/image-gen/assets/sample/daily-news-cover__fresh.jpg +0 -0
  22. package/plugins/generations/image-gen/assets/sample/daily-news-cover__minimalist.jpg +0 -0
  23. package/plugins/generations/image-gen/assets/sample/daily-news-cover__photorealistic.jpg +0 -0
  24. package/plugins/generations/image-gen/assets/sample/daily-news-cover__sketch.jpg +0 -0
  25. package/plugins/generations/image-gen/assets/sample/daily-news-cover__technical-diagram.jpg +0 -0
  26. package/plugins/generations/image-gen/assets/sample/daily-news-cover__vibrant.jpg +0 -0
  27. package/plugins/generations/image-gen/assets/sample/daily-news-cover__warm.jpg +0 -0
  28. package/plugins/generations/image-gen/assets/sample/daily-news-cover__xhs-knowledge.jpg +0 -0
  29. package/plugins/generations/image-gen/assets/sample/free__clean-webapp-ui.jpg +0 -0
  30. package/plugins/generations/image-gen/assets/sample/free__cute.jpg +0 -0
  31. package/plugins/generations/image-gen/assets/sample/free__daily-news.jpg +0 -0
  32. package/plugins/generations/image-gen/assets/sample/free__editorial.jpg +0 -0
  33. package/plugins/generations/image-gen/assets/sample/free__fresh.jpg +0 -0
  34. package/plugins/generations/image-gen/assets/sample/free__minimalist.jpg +0 -0
  35. package/plugins/generations/image-gen/assets/sample/free__photorealistic.jpg +0 -0
  36. package/plugins/generations/image-gen/assets/sample/free__sketch.jpg +0 -0
  37. package/plugins/generations/image-gen/assets/sample/free__technical-diagram.jpg +0 -0
  38. package/plugins/generations/image-gen/assets/sample/free__vibrant.jpg +0 -0
  39. package/plugins/generations/image-gen/assets/sample/free__warm.jpg +0 -0
  40. package/plugins/generations/image-gen/assets/sample/free__xhs-knowledge.jpg +0 -0
  41. package/plugins/generations/image-gen/assets/sample/illustration__clean-webapp-ui.jpg +0 -0
  42. package/plugins/generations/image-gen/assets/sample/illustration__cute.jpg +0 -0
  43. package/plugins/generations/image-gen/assets/sample/illustration__daily-news.jpg +0 -0
  44. package/plugins/generations/image-gen/assets/sample/illustration__editorial.jpg +0 -0
  45. package/plugins/generations/image-gen/assets/sample/illustration__fresh.jpg +0 -0
  46. package/plugins/generations/image-gen/assets/sample/illustration__minimalist.jpg +0 -0
  47. package/plugins/generations/image-gen/assets/sample/illustration__photorealistic.jpg +0 -0
  48. package/plugins/generations/image-gen/assets/sample/illustration__sketch.jpg +0 -0
  49. package/plugins/generations/image-gen/assets/sample/illustration__technical-diagram.jpg +0 -0
  50. package/plugins/generations/image-gen/assets/sample/illustration__vibrant.jpg +0 -0
  51. package/plugins/generations/image-gen/assets/sample/illustration__warm.jpg +0 -0
  52. package/plugins/generations/image-gen/assets/sample/illustration__xhs-knowledge.jpg +0 -0
  53. package/plugins/generations/image-gen/assets/sample/xhs-card__clean-webapp-ui.jpg +0 -0
  54. package/plugins/generations/image-gen/assets/sample/xhs-card__cute.jpg +0 -0
  55. package/plugins/generations/image-gen/assets/sample/xhs-card__daily-news.jpg +0 -0
  56. package/plugins/generations/image-gen/assets/sample/xhs-card__editorial.jpg +0 -0
  57. package/plugins/generations/image-gen/assets/sample/xhs-card__fresh.jpg +0 -0
  58. package/plugins/generations/image-gen/assets/sample/xhs-card__minimalist.jpg +0 -0
  59. package/plugins/generations/image-gen/assets/sample/xhs-card__photorealistic.jpg +0 -0
  60. package/plugins/generations/image-gen/assets/sample/xhs-card__sketch.jpg +0 -0
  61. package/plugins/generations/image-gen/assets/sample/xhs-card__technical-diagram.jpg +0 -0
  62. package/plugins/generations/image-gen/assets/sample/xhs-card__vibrant.jpg +0 -0
  63. package/plugins/generations/image-gen/assets/sample/xhs-card__warm.jpg +0 -0
  64. package/plugins/generations/image-gen/assets/sample/xhs-card__xhs-knowledge.jpg +0 -0
  65. package/plugins/generations/image-gen/dist/index.js +102 -58
  66. package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +5 -21
  67. package/plugins/generations/image-gen/scripts/gen-samples.ts +208 -0
  68. package/plugins/generations/image-gen/src/compose/chromium.ts +2 -3
  69. package/plugins/generations/image-gen/src/presets.ts +6 -3
  70. package/plugins/generations/image-gen/src/providers/errors.ts +16 -1
  71. package/plugins/generations/image-gen/src/providers/minimax.ts +46 -17
  72. package/plugins/ingestions/web-search/src/index.ts +1 -1
  73. package/plugins/kk/README.md +18 -0
  74. package/plugins/kk/commands/image-extract.md +1 -0
  75. package/plugins/kk/commands/image-generate.md +1 -0
  76. package/plugins/kk/commands/tell-me.md +1 -0
  77. package/plugins/kk/commands/workflow-run.md +7 -1
  78. package/plugins/kk/decisions/extract/it-topics.yaml +24 -0
  79. package/plugins/kk/decisions/kk-itc.yaml +6 -4
  80. package/plugins/kk/plugin.json +1 -1
  81. package/plugins/kk/publish-presets/daily-ai-voice-migration.json +146 -0
  82. package/plugins/kk/publish-presets/daily-ai-voice.json +11 -3
  83. package/plugins/kk/schedules/kk-collect.grok-bot.md.tmpl +69 -0
  84. package/plugins/kk/schedules/kk-daily-ai-voice3.cron.tmpl +18 -0
  85. package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +15 -5
  86. package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +9 -4
  87. package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
  88. package/plugins/kk/scripts/daily-stages.ts +1403 -156
  89. package/plugins/kk/scripts/kk-workflow-stages.ts +266 -10
  90. package/plugins/kk/scripts/safety-artifact-check.ts +277 -0
  91. package/plugins/kk/scripts/voice3-publish-window.ts +202 -0
  92. package/plugins/kk/skills/article-adapt/SKILL.md +1 -0
  93. package/plugins/kk/skills/audio-authoring/SKILL.md +1 -0
  94. package/plugins/kk/skills/content-judge/SKILL.md +1 -0
  95. package/plugins/kk/skills/content-safety/SKILL.md +1 -0
  96. package/plugins/kk/skills/explain-things/SKILL.md +1 -0
  97. package/plugins/kk/skills/image-authoring/SKILL.md +1 -0
  98. package/plugins/kk/skills/itc-generating/SKILL.md +1 -0
  99. package/plugins/kk/skills/publish/SKILL.md +1 -0
  100. package/plugins/kk/skills/smart-seo/SKILL.md +1 -0
  101. package/plugins/kk/skills/storm-research/SKILL.md +1 -0
  102. package/plugins/kk/skills/taste-unslop/SKILL.md +1 -0
  103. package/plugins/kk/skills/topic/SKILL.md +1 -0
  104. package/plugins/kk/skills/web-automation/SKILL.md +1 -0
  105. package/plugins/kk/skills/xhs-images/SKILL.md +1 -0
  106. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +177 -128
  107. package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +161 -61
  108. package/plugins/kk/workflows/kk-daily-publish3.yaml +82 -26
  109. package/plugins/kk/workflows/kk-daily-safety.yaml +156 -69
  110. package/plugins/kk/workflows/kk-itc-author.yaml +75 -0
  111. package/plugins/kk/workflows/kk-itc-publish-tail.yaml +25 -3
  112. package/plugins/kk/workflows/kk-itc.yaml +36 -4
  113. package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
  114. package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
  115. package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
  116. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
  117. package/plugins/publishings/csdn-pub/dist/index.js +2 -1
  118. package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
  119. package/plugins/publishings/infoq-pub/dist/index.js +2 -1
  120. package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
  121. package/plugins/publishings/jike-pub/dist/index.js +2 -1
  122. package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
  123. package/plugins/publishings/juejin-pub/dist/index.js +2 -1
  124. package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
  125. package/plugins/publishings/linkedin-pub/dist/index.js +21333 -0
  126. package/plugins/publishings/linkedin-pub/dist/profile-check.js +21005 -0
  127. package/plugins/publishings/linkedin-pub/package.json +19 -0
  128. package/plugins/publishings/linkedin-pub/plugin.json +12 -0
  129. package/plugins/publishings/linkedin-pub/src/index.ts +705 -0
  130. package/plugins/publishings/linkedin-pub/src/profile-check.ts +17 -0
  131. package/plugins/publishings/linkedin-pub/tsconfig.json +4 -0
  132. package/plugins/publishings/podcast-pub/dist/index.js +46 -8
  133. package/plugins/publishings/podcast-pub/src/index.ts +62 -5
  134. package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
  135. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
  136. package/plugins/publishings/sspai-pub/dist/index.js +2 -1
  137. package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
  138. package/plugins/publishings/substack-pub/dist/index.js +2 -1
  139. package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
  140. package/plugins/publishings/surfdash-pub/dist/index.js +461 -53
  141. package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
  142. package/plugins/publishings/surfdash-pub/src/index.ts +592 -65
  143. package/plugins/publishings/threads-pub/dist/index.js +7 -0
  144. package/plugins/publishings/threads-pub/src/index.ts +12 -0
  145. package/plugins/publishings/wechatmp-pub/dist/index.js +936 -98
  146. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
  147. package/plugins/publishings/wechatmp-pub/src/flow.ts +1134 -86
  148. package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
  149. package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
  150. package/plugins/publishings/weibo-pub/dist/index.js +2 -1
  151. package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
  152. package/plugins/publishings/x-pub/dist/index.js +9 -1
  153. package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
  154. package/plugins/publishings/x-pub/src/index.ts +11 -0
  155. package/plugins/publishings/xhs-pub/dist/index.js +2 -1
  156. package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
  157. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
  158. 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 = ['div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")', 'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'];
22453
- var COVER_PANEL_OPEN = ["a.js_selectCoverFromContent", ".js_cover_btn_area", "text=\u6362\u4E00\u5F20", "text=\u4FEE\u6539"];
22454
- var COVER_FROM_CONTENT = ["a.js_selectCoverFromContent", "text=\u4ECE\u6B63\u6587\u9009\u62E9"];
22455
- var COVER_FROM_LIBRARY = ["a.js_imagedialog", "text=\u4ECE\u56FE\u7247\u5E93\u9009\u62E9"];
22456
- var COVER_LIBRARY_NEXT = ['button:has-text("\u4E0B\u4E00\u6B65")'];
22457
- var COVER_CONFIRM = ['button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u8BA4")', 'button:has-text("\u5B8C\u6210")'];
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 = ['.weui-desktop-dialog button:has-text("\u4FDD\u5B58")', 'button:has-text("\u4FDD\u5B58")'];
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 PUBLISH_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
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 PLATFORM_REFUSAL = ["\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u53D1\u8868", "\u8BF7\u8865\u5145\u5C01\u9762\u56FE", "\u672A\u8BBE\u7F6E\u5934\u50CF\u548C\u540D\u79F0", "\u516C\u4F17\u53F7\u5C1A\u672A\u5B9E\u540D"];
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
- const option = await tryFindSelector(page, LAYOUT_TARGET, 5000);
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
- return "\u4E00\u952E\u6392\u7248: the control exists but this build renders no \u5339\u914D\u76EE\u6807\u683C\u5F0F option (measured 2026-10-21 with a body in place) \u2014 layout skipped, apply \u4E00\u952E\u6392\u7248 by hand before publishing";
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(page, option, 5000);
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 pick = await tryFindSelector(page, picker, Math.min(deps.stepTimeoutMs, 8000));
22775
- if (pick === undefined)
22776
- return await warn(`cover: ${what} was not offered by the open cover panel`);
22777
- await clickSelector(page, pick, deps.stepTimeoutMs);
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()).find((v) => v.length > 0) ?? '';
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) === w));
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.querySelector('input[type=checkbox], [role=checkbox]')?.click();
22795
- row.click();
23102
+ if (!row.classList.contains('selected')) row.click();
22796
23103
  const checked = row.querySelector('input[type=checkbox], [role=checkbox]');
22797
- const src = row.querySelector('img')?.src ?? (row.querySelector('[style*="background-image"]')?.style.backgroundImage.match(/url("?([^")]+)"?)/)?.[1] ?? '');
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
- const rawIdentify = String(await page.evaluate(IDENTIFY));
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) return 'nopreview';
22824
- const src = preview.querySelector('img')?.src ??
22825
- (preview.querySelector('[style*="background-image"]')?.style.backgroundImage.match(/url("?([^")]+)"?)/)?.[1] ?? '') ||
22826
- (preview.querySelector('input[type=hidden]')?.value ?? '');
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
- for (let read = 0;read < 5 && committed === ""; read += 1) {
22831
- const raw = String(await page.evaluate(READBACK).catch(() => "error"));
22832
- if (raw !== "" && raw !== "nopreview" && raw !== "noarea") {
22833
- committed = raw;
22834
- break;
22835
- }
22836
- await page.waitForTimeout?.(600);
22837
- }
22838
- if (committed === "empty")
22839
- return await warn("cover: \u786E\u8BA4\u540E\u9762\u677F\u4ECD\u663E\u793A\u672A\u8BBE\u7F6E\u5C01\u9762\uFF0C\u63D0\u4EA4\u672A\u751F\u6548");
22840
- if (committed === "" || committed === "noarea" || committed === "error")
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
- const selectedSrc = (selected.src ?? "").trim();
22843
- if (selectedSrc === "" || committed !== selectedSrc && !committed.includes(selectedSrc))
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,275 @@ 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 (actual.body.length < expected.body.length && expected.body.startsWith(actual.body))
23508
+ return true;
23509
+ if (expected.media.length > actual.media.length)
23510
+ return true;
23511
+ if (!snapshotValueIsEmpty(expected.cover) && snapshotValueIsEmpty(actual.cover))
23512
+ return true;
23513
+ if (expected.originalUrl !== "" && actual.originalUrl === "")
23514
+ return true;
23515
+ return false;
23516
+ }
23517
+ var PUBLIC_SAVED_EDITOR_RELOAD_LIMIT = 6;
23518
+ function appmsgIdFromCanonical(value) {
23519
+ const canonical = canonicalSavedEditorUrl(value);
23520
+ if (canonical === null)
23521
+ return null;
23522
+ try {
23523
+ const appmsgId = new URL(canonical).searchParams.get("appmsgid")?.trim();
23524
+ return appmsgId && appmsgId.length > 0 ? appmsgId : null;
23525
+ } catch {
23526
+ return null;
23527
+ }
23528
+ }
23529
+ async function waitForPublicEditorReadiness(page, deps) {
23530
+ let consecutiveSaved = 0;
23531
+ return waitUntil(async () => {
23532
+ const state = await page.evaluate(nativeLayoutSaveStateScript()).catch(() => "unknown");
23533
+ const legacyState = state === "saved" ? "not-needed" : await page.evaluate(nativeLegacyEditorReadyScript()).catch(() => "unknown");
23534
+ if (state === "saved" || legacyState === "ready") {
23535
+ consecutiveSaved += 1;
23536
+ return consecutiveSaved >= 2;
23537
+ }
23538
+ consecutiveSaved = 0;
23539
+ return false;
23540
+ }, Math.min(deps.stepTimeoutMs, 15000), deps.pollMs);
23541
+ }
23542
+ async function savePublicDraft(deps, entry) {
23543
+ const composed = await readPublicDraftSnapshot(entry.editor);
23544
+ if (composed === null)
23545
+ return fail("public save readiness: the composed editor snapshot (title/body/media/cover) was unreadable; no publish click attempted");
23546
+ const platformMetadata = deps.content.metadata?.wechatmp;
23547
+ const requestedUrl = platformMetadata !== null && typeof platformMetadata === "object" && "originalUrl" in platformMetadata ? platformMetadata.originalUrl : undefined;
23548
+ if (typeof requestedUrl === "string" && normalizeSnapshotText(requestedUrl) !== composed.originalUrl)
23549
+ return fail("public save readiness: requested \u539F\u6587\u94FE\u63A5 was not committed; no publish click attempted");
23550
+ const canonicalUrlBeforeSave = canonicalSavedEditorUrl(entry.editor.url());
23551
+ await sleep(deps.submitSettleMs);
23552
+ if (await closeOpenDialogs(entry.editor) !== "closed")
23553
+ return fail("public save readiness: a residual editor dialog blocked \u4FDD\u5B58\u4E3A\u8349\u7A3F; no publish click attempted");
23554
+ const saveControl = await findSelector(entry.editor, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control", deps.stepTimeoutMs);
23555
+ if (!entry.editor.saveDraftAndWait)
23556
+ return fail("public save readiness: the native \u4FDD\u5B58\u4E3A\u8349\u7A3F acknowledgement probe was unavailable; no publish click attempted");
23557
+ try {
23558
+ await entry.editor.saveDraftAndWait(saveControl, deps.draftTimeoutMs);
23559
+ } catch {
23560
+ return fail("public save readiness: native \u4FDD\u5B58\u4E3A\u8349\u7A3F was not acknowledged successfully; no publish click attempted");
23561
+ }
23562
+ let canonicalUrl = null;
23563
+ let refusal = null;
23564
+ const saved = await waitUntil(async () => {
23565
+ refusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
23566
+ if (refusal !== null)
23567
+ return true;
23568
+ canonicalUrl = canonicalSavedEditorUrl(entry.editor.url());
23569
+ return canonicalUrl !== null;
23570
+ }, deps.draftTimeoutMs, deps.pollMs);
23571
+ if (refusal !== null)
23572
+ return fail(`public save refused by ${TARGET}: ${refusal}; no publish click attempted`);
23573
+ if (!saved || canonicalUrl === null) {
23574
+ 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`);
23575
+ }
23576
+ const savedCanonicalUrl = canonicalUrl;
23577
+ await sleep(Math.min(Math.max(deps.pollMs, 25), 500));
23578
+ await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
23579
+ let reloaded = null;
23580
+ let reloadRefusal = null;
23581
+ let reloads = 0;
23582
+ let lastReloadAt = Date.now();
23583
+ const reloadIntervalMs = Math.min(Math.max(deps.pollMs * 4, 50), 2000);
23584
+ const canonicalUrlWasPreexisting = canonicalUrlBeforeSave === savedCanonicalUrl;
23585
+ await waitUntil(async () => {
23586
+ reloadRefusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
23587
+ if (reloadRefusal !== null)
23588
+ return true;
23589
+ if (canonicalSavedEditorUrl(entry.editor.url()) === null)
23590
+ return false;
23591
+ reloaded = await readPublicDraftSnapshot(entry.editor);
23592
+ if (reloaded === null)
23593
+ return false;
23594
+ const differences = publicSnapshotDifferences(composed, reloaded);
23595
+ if (differences.length === 0)
23596
+ return true;
23597
+ if (!publicSnapshotIsIncomplete(composed, reloaded))
23598
+ return true;
23599
+ if (reloads >= PUBLIC_SAVED_EDITOR_RELOAD_LIMIT)
23600
+ return true;
23601
+ if (Date.now() - lastReloadAt < reloadIntervalMs)
23602
+ return false;
23603
+ reloads += 1;
23604
+ lastReloadAt = Date.now();
23605
+ await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
23606
+ return false;
23607
+ }, deps.draftTimeoutMs, deps.pollMs);
23608
+ if (reloadRefusal !== null)
23609
+ return fail(`public saved-editor refusal by ${TARGET}: ${reloadRefusal}; no publish click attempted`);
23610
+ if (reloaded === null)
23611
+ 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`);
23612
+ const differences = publicSnapshotDifferences(composed, reloaded);
23613
+ if (differences.length > 0)
23614
+ return fail(`public saved editor content mismatch before publication (${differences.join(", ")}); no publish click attempted`);
23615
+ const autoSaveSettled = await waitForPublicEditorReadiness(entry.editor, deps);
23616
+ if (!autoSaveSettled)
23617
+ 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`);
23618
+ return {
23619
+ saveControl,
23620
+ canonicalUrl: savedCanonicalUrl,
23621
+ canonicalUrlForMetadata: savedEditorUrlForMetadata(savedCanonicalUrl),
23622
+ saveSignal: canonicalUrlWasPreexisting ? "persisted-snapshot" : "canonical-url-transition+snapshot",
23623
+ snapshot: composed
23624
+ };
23625
+ }
23626
+ async function recoverStaleVersion(deps, entry, saved) {
23627
+ const expectedAppmsgId = appmsgIdFromCanonical(saved.canonicalUrl);
23628
+ if (expectedAppmsgId === null)
23629
+ return fail("public stale-version recovery: the saved editor identity was not canonical; no retry attempted");
23630
+ const latest = await findUniqueVisibleSelector(entry.editor, PUBLISH_VIEW_LATEST, Math.min(CONFIRM_TIMEOUT_MS, deps.stepTimeoutMs), deps.pollMs);
23631
+ if (latest.selector === undefined)
23632
+ return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F was ${latest.reason}; no second \u53D1\u8868 click attempted`);
23633
+ const before = deps.pages().length;
23634
+ try {
23635
+ await clickSelector(entry.editor, latest.selector, deps.stepTimeoutMs);
23636
+ } catch {
23637
+ return fail("public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F could not be clicked; no second \u53D1\u8868 click attempted");
23638
+ }
23639
+ const latestEditor = await followNewPage(deps.pages, before, Math.min(deps.stepTimeoutMs, CONFIRM_TIMEOUT_MS)) ?? entry.editor;
23640
+ let latestCanonicalUrl = null;
23641
+ const canonicalReady = await waitUntil(async () => {
23642
+ latestCanonicalUrl = canonicalSavedEditorUrl(latestEditor.url());
23643
+ if (latestCanonicalUrl === null)
23644
+ return false;
23645
+ const refusal = await readStaleVersionRefusal(asPlaywrightPage(latestEditor)).catch(() => "__unreadable__");
23646
+ return refusal === null;
23647
+ }, deps.draftTimeoutMs, deps.pollMs);
23648
+ if (!canonicalReady || latestCanonicalUrl === null)
23649
+ 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");
23650
+ const actualAppmsgId = appmsgIdFromCanonical(latestCanonicalUrl);
23651
+ if (actualAppmsgId !== expectedAppmsgId)
23652
+ return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F opened appmsgid=${actualAppmsgId ?? "unknown"}, expected appmsgid=${expectedAppmsgId}; no second \u53D1\u8868 click attempted`);
23653
+ let latestSnapshot = null;
23654
+ let differences = [];
23655
+ const snapshotSettled = await waitUntil(async () => {
23656
+ latestSnapshot = await readPublicDraftSnapshot(latestEditor);
23657
+ if (latestSnapshot === null)
23658
+ return false;
23659
+ differences = publicSnapshotDifferences(saved.snapshot, latestSnapshot);
23660
+ return differences.length === 0 || !publicSnapshotIsIncomplete(saved.snapshot, latestSnapshot);
23661
+ }, deps.draftTimeoutMs, deps.pollMs);
23662
+ if (!snapshotSettled || latestSnapshot === null)
23663
+ return fail("public stale-version recovery: latest editor snapshot was unreadable; no second \u53D1\u8868 click attempted");
23664
+ const latestBody = latestSnapshot.body;
23665
+ if (differences.length > 0)
23666
+ return fail(`public stale-version recovery: latest editor content mismatch (${differences.join(", ")}; body ${latestBody.length}/${saved.snapshot.body.length} chars); no second \u53D1\u8868 click attempted`);
23667
+ if (!await waitForPublicEditorReadiness(latestEditor, deps))
23668
+ return fail("public stale-version recovery: latest editor did not provide two consecutive recognized readiness probes; no second \u53D1\u8868 click attempted");
23669
+ return {
23670
+ editor: latestEditor,
23671
+ canonicalUrl: latestCanonicalUrl,
23672
+ canonicalUrlForMetadata: savedEditorUrlForMetadata(latestCanonicalUrl)
23673
+ };
23674
+ }
23058
23675
  async function publishOutcome(page) {
23059
23676
  const success = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
23060
23677
  if (success !== null)
@@ -23064,14 +23681,112 @@ async function publishOutcome(page) {
23064
23681
  return `refused:${refusal}`;
23065
23682
  return null;
23066
23683
  }
23684
+ async function findUniqueVisibleSelector(page, selectors, timeoutMs, pollMs) {
23685
+ let reason = "missing";
23686
+ let selector;
23687
+ const found = await waitUntil(async () => {
23688
+ for (const candidate of selectors) {
23689
+ const locator = page.locator(candidate);
23690
+ const count = await locator.count().catch(() => 0);
23691
+ if (count > 1) {
23692
+ reason = "ambiguous";
23693
+ return true;
23694
+ }
23695
+ if (count !== 1)
23696
+ continue;
23697
+ const visible = await locator.first().isVisible().catch(() => false);
23698
+ if (!visible)
23699
+ continue;
23700
+ selector = candidate;
23701
+ return true;
23702
+ }
23703
+ return false;
23704
+ }, timeoutMs, pollMs);
23705
+ return found && selector !== undefined ? { selector, reason } : { reason };
23706
+ }
23707
+ async function readPublishNotifySubscribers(page) {
23708
+ try {
23709
+ const value = await page.evaluate(PUBLISH_NOTIFY_STATE_SCRIPT);
23710
+ return typeof value === "boolean" ? value : null;
23711
+ } catch {
23712
+ return null;
23713
+ }
23714
+ }
23715
+ function shouldNotifySubscribers(content) {
23716
+ const wechatmp = content.metadata?.wechatmp;
23717
+ if (wechatmp === null || typeof wechatmp !== "object" || Array.isArray(wechatmp))
23718
+ return true;
23719
+ return wechatmp.notifySubscribers !== false;
23720
+ }
23721
+ async function confirmPublicPublish(page, notifySubscribers, stepTimeoutMs, pollMs) {
23722
+ const confirmationTimeoutMs = stepTimeoutMs;
23723
+ const dialog = await findUniqueVisibleSelector(page, PUBLISH_DIALOG, confirmationTimeoutMs, pollMs);
23724
+ if (dialog.selector === undefined) {
23725
+ if (dialog.reason === "missing") {
23726
+ const refusal = await readStaleVersionRefusal(asPlaywrightPage(page)).catch(() => null);
23727
+ if (refusal !== null)
23728
+ return { kind: "stale-version", refusal };
23729
+ }
23730
+ return fail(`public confirmation: the \u53D1\u8868 dialog was ${dialog.reason}; no final publish click attempted`);
23731
+ }
23732
+ let observedNotifySubscribers = null;
23733
+ if (!notifySubscribers) {
23734
+ const toggle = await findUniqueVisibleSelector(page, PUBLISH_NOTIFY_TOGGLE, confirmationTimeoutMs, pollMs);
23735
+ if (toggle.selector === undefined)
23736
+ return fail(`public confirmation: \u7FA4\u53D1\u901A\u77E5 switch was ${toggle.reason === "missing" ? "unavailable" : "ambiguous"}; no final publish click attempted`);
23737
+ const before = await readPublishNotifySubscribers(page);
23738
+ if (before === null)
23739
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch state was unreadable; no final publish click attempted");
23740
+ if (before)
23741
+ await clickSelector(page, toggle.selector, stepTimeoutMs);
23742
+ const off = await waitUntil(async () => await readPublishNotifySubscribers(page) === false, confirmationTimeoutMs, pollMs);
23743
+ if (!off)
23744
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not verify OFF; no final publish click attempted");
23745
+ }
23746
+ const final = await findUniqueVisibleSelector(page, PUBLISH_FINAL, confirmationTimeoutMs, pollMs);
23747
+ if (final.selector === undefined)
23748
+ return fail(`public confirmation: final \u53D1\u8868 button was ${final.reason}; no final publish click attempted`);
23749
+ observedNotifySubscribers = await readPublishNotifySubscribers(page);
23750
+ if (!notifySubscribers && observedNotifySubscribers !== false)
23751
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not remain verifiably OFF; no final publish click attempted");
23752
+ await clickSelector(page, final.selector, stepTimeoutMs);
23753
+ return {
23754
+ finalSelector: final.selector,
23755
+ notifySubscribers: observedNotifySubscribers,
23756
+ notifySubscribersPolicy: notifySubscribers ? "preserve" : "disabled"
23757
+ };
23758
+ }
23067
23759
  async function publishContent(input, deps, entry, composed) {
23760
+ const saved = input.operation === "\u6587\u7AE0" ? await savePublicDraft(deps, entry) : undefined;
23761
+ if (saved !== undefined && "ok" in saved)
23762
+ return saved;
23763
+ const notifySubscribers = shouldNotifySubscribers(deps.content);
23764
+ let staleVersionRecoveryCount = 0;
23765
+ let finalSavedEditorUrl = saved?.canonicalUrlForMetadata;
23068
23766
  await sleep(deps.submitSettleMs);
23069
23767
  const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
23070
23768
  const urlBefore = entry.editor.url();
23071
23769
  await clickSelector(entry.editor, control, deps.stepTimeoutMs);
23072
- const confirm = await tryFindSelector(entry.editor, PUBLISH_CONFIRM, CONFIRM_TIMEOUT_MS);
23073
- if (confirm !== undefined)
23074
- await clickSelector(entry.editor, confirm, deps.stepTimeoutMs);
23770
+ let confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
23771
+ if ("ok" in confirmation)
23772
+ return confirmation;
23773
+ if ("kind" in confirmation) {
23774
+ if (saved === undefined)
23775
+ return fail(`public confirmation: ${confirmation.refusal}; no canonical saved-editor evidence was available for recovery; no second \u53D1\u8868 click attempted`);
23776
+ const recovered = await recoverStaleVersion(deps, entry, saved);
23777
+ if ("ok" in recovered)
23778
+ return recovered;
23779
+ entry.editor = recovered.editor;
23780
+ finalSavedEditorUrl = recovered.canonicalUrlForMetadata;
23781
+ staleVersionRecoveryCount = 1;
23782
+ const retryControl = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control after \u67E5\u770B\u65B0\u8349\u7A3F", deps.stepTimeoutMs);
23783
+ await clickSelector(entry.editor, retryControl, deps.stepTimeoutMs);
23784
+ confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
23785
+ if ("ok" in confirmation)
23786
+ return confirmation;
23787
+ if ("kind" in confirmation)
23788
+ return fail(`public confirmation: ${confirmation.refusal} after bounded \u67E5\u770B\u65B0\u8349\u7A3F recovery; no final publish click attempted`);
23789
+ }
23075
23790
  const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
23076
23791
  timeoutMs: deps.stepTimeoutMs,
23077
23792
  label: `publish confirmation '${control}'`,
@@ -23089,7 +23804,16 @@ async function publishContent(input, deps, entry, composed) {
23089
23804
  message: `${TARGET} ${input.operation} published`,
23090
23805
  metadata: {
23091
23806
  ...baseMetadata(input, deps, entry),
23807
+ ...saved === undefined ? {} : {
23808
+ draftSaveControl: saved.saveControl,
23809
+ savedEditorUrl: finalSavedEditorUrl ?? saved.canonicalUrlForMetadata,
23810
+ draftSaveSignal: saved.saveSignal,
23811
+ staleVersionRecoveryCount
23812
+ },
23092
23813
  publishControl: control,
23814
+ finalPublishControl: confirmation.finalSelector,
23815
+ notifySubscribers: confirmation.notifySubscribers,
23816
+ notifySubscribersPolicy: confirmation.notifySubscribersPolicy,
23093
23817
  publishSignal: outcome.evidence,
23094
23818
  titlePath: composed.titlePath,
23095
23819
  ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
@@ -23185,7 +23909,11 @@ async function runMode(input, deps) {
23185
23909
  const composed = await compose(input, deps, entry);
23186
23910
  const diagnostics = [...composed.warnings, ...composed.enrichWarnings];
23187
23911
  try {
23188
- return deps.publishStatus === "public" ? await publishContent(input, deps, entry, composed) : await saveDraft(input, deps, entry, composed);
23912
+ const result = deps.publishStatus === "public" ? await publishContent(input, deps, entry, composed) : await saveDraft(input, deps, entry, composed);
23913
+ if (!result.ok && diagnostics.length > 0) {
23914
+ return { ...result, error: `${result.error} \u2014 compose diagnostics: ${diagnostics.join(" | ")}` };
23915
+ }
23916
+ return result;
23189
23917
  } catch (error) {
23190
23918
  const message = error instanceof Error ? error.message : String(error);
23191
23919
  const blocked = await openDialogInventory(entry.editor);
@@ -23273,15 +24001,20 @@ function escapeHtml(text) {
23273
24001
  }
23274
24002
  async function insertAudioMaterial(page, input) {
23275
24003
  const { title, audioPath, coverPath } = input;
23276
- const CARET = `(() => {
24004
+ const CARET = `(async () => {
23277
24005
  const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
23278
24006
  if (!pm || !pm.firstChild) return;
24007
+ pm.focus();
23279
24008
  const r = document.createRange();
23280
- r.setStart(pm.firstChild, 0);
24009
+ // The first child can be a non-editable image atom. Select the boundary before it,
24010
+ // not a position inside that atom which ProseMirror discards on toolbar focus.
24011
+ r.setStart(pm, 0);
23281
24012
  r.collapse(true);
23282
24013
  const sel = window.getSelection();
23283
24014
  sel?.removeAllRanges();
23284
24015
  sel?.addRange(r);
24016
+ document.dispatchEvent(new Event('selectionchange'));
24017
+ await new Promise(resolve => setTimeout(resolve, 100));
23285
24018
  })()`;
23286
24019
  const norm = (value) => typeof value === "string" ? value.replace(/[\s\u00a0]+/g, " ").trim() : "";
23287
24020
  const MATCH = `(() => {
@@ -23303,8 +24036,8 @@ async function insertAudioMaterial(page, input) {
23303
24036
  return JSON.stringify({ state: 'checked', identity });
23304
24037
  })()`;
23305
24038
  const AGREE = `(() => {
23306
- const node = [...document.querySelectorAll('label, span, div')]
23307
- .find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.children.length === 0);
24039
+ const node = [...document.querySelectorAll('.weui-desktop-dialog label, .audio_dialog label')]
24040
+ .find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.querySelector('input[type=checkbox]'));
23308
24041
  if (!node) return 'no-label';
23309
24042
  const scope = node.closest('label') ?? node.parentElement;
23310
24043
  const cb = scope?.querySelector('input[type=checkbox]') ?? document.querySelector('input[type=checkbox].js_agree');
@@ -23327,9 +24060,10 @@ async function insertAudioMaterial(page, input) {
23327
24060
  if (!pm) return JSON.stringify({ state: 'noeditor' });
23328
24061
  const first = pm.firstElementChild;
23329
24062
  if (!first) return JSON.stringify({ state: 'absent' });
23330
- const node = first.matches('mpvoice, [data-mediaid]') ? first : first.querySelector('mpvoice, [data-mediaid]');
23331
- if (!node) return JSON.stringify({ state: 'absent' });
23332
- const identity = node.getAttribute('data-mediaid') ?? node.getAttribute('data-appid') ??
24063
+ const selector = 'mp-common-mpaudio, mpvoice, [data-mediaid]';
24064
+ const node = first.matches(selector) ? first : first.querySelector(selector);
24065
+ if (!node) return JSON.stringify({ state: pm.querySelector(selector) ? 'not-top' : 'absent' });
24066
+ const identity = node.getAttribute('data-mediaid') ?? node.getAttribute('cover') ?? node.getAttribute('voice_encode_fileid') ?? node.getAttribute('data-appid') ??
23333
24067
  node.getAttribute('data-src') ?? node.getAttribute('src') ?? '';
23334
24068
  return JSON.stringify({ state: 'top', identity });
23335
24069
  })()`;
@@ -23338,7 +24072,7 @@ async function insertAudioMaterial(page, input) {
23338
24072
  if (!uploadControl.found)
23339
24073
  return "\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog";
23340
24074
  await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? "text=\u4E0A\u4F20\u97F3\u9891", 8000);
23341
- const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT);
24075
+ const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
23342
24076
  if (!audioInput.found)
23343
24077
  return "\u97F3\u9891: no audio file input appeared";
23344
24078
  await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(", "), audioPath, {
@@ -23347,11 +24081,63 @@ async function insertAudioMaterial(page, input) {
23347
24081
  const coverControl = await trySelectors(page, UPLOAD_COVER);
23348
24082
  if (coverControl.found) {
23349
24083
  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
- await uploadFile(asPlaywrightPage(page), coverInput.selector ?? AUDIO_COVER_INPUT.join(", "), coverPath, {
23353
- timeoutMs: 60000
23354
- });
24084
+ const coverInput = await trySelectors(page, AUDIO_COVER_INPUT, { visible: false });
24085
+ if (coverInput.found) {
24086
+ const chooser = '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247"))';
24087
+ const hasChooser = await page.locator(chooser).count() > 0;
24088
+ const coverName = hasChooser ? await coverLibraryName(coverPath) : baseName(coverPath);
24089
+ let existing = 0;
24090
+ if (hasChooser) {
24091
+ const libraryReady = await waitUntil(async () => Boolean(await page.evaluate(`!document.querySelector('#js_image_dialog_list_wrp .weui-desktop-loading')`)), 5000);
24092
+ if (!libraryReady)
24093
+ return "\u97F3\u9891: \u5C01\u9762\u7D20\u6750\u5E93\u52A0\u8F7D\u672A\u5B8C\u6210\uFF0C\u672A\u4E0A\u4F20\u91CD\u590D\u5C01\u9762";
24094
+ 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`));
24095
+ if (existing > 1)
24096
+ return "\u97F3\u9891: \u5185\u5BB9\u7ED1\u5B9A\u5C01\u9762\u5B58\u5728\u591A\u4E2A\u540C\u540D\u7D20\u6750\uFF0C\u672A\u731C\u6D4B\u9009\u62E9";
24097
+ }
24098
+ let uploadPath = coverPath;
24099
+ let temporaryDir;
24100
+ if (hasChooser && existing === 0) {
24101
+ temporaryDir = join6(tmpdir(), `kk-wechat-cover-${randomUUID()}`);
24102
+ if (!mkdirExclusive(temporaryDir))
24103
+ throw new Error("Private cover upload directory already exists");
24104
+ uploadPath = join6(temporaryDir, coverName);
24105
+ }
24106
+ try {
24107
+ if (temporaryDir !== undefined)
24108
+ await createNodeFileSystem().copy(coverPath, uploadPath);
24109
+ if (existing === 0) {
24110
+ await uploadFile(asPlaywrightPage(page), coverInput.selector ?? AUDIO_COVER_INPUT.join(", "), uploadPath, {
24111
+ timeoutMs: 60000
24112
+ });
24113
+ }
24114
+ if (hasChooser) {
24115
+ const selectedName = JSON.stringify(coverName);
24116
+ const selected = await waitUntil(async () => Boolean(await page.evaluate(`(() => {
24117
+ const rows = [...document.querySelectorAll('.weui-desktop-img-picker__item')]
24118
+ .filter(row => row.offsetWidth > 0 && row.querySelector('.weui-desktop-img-picker__img-title')?.textContent?.trim() === ${selectedName});
24119
+ if (rows.length !== 1) return false;
24120
+ const row = rows[0];
24121
+ if (!row.classList.contains('selected')) row.click();
24122
+ return row.classList.contains('selected');
24123
+ })()`)), 30000);
24124
+ if (!selected)
24125
+ 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";
24126
+ 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);
24127
+ await clickSelector(page, next, 8000);
24128
+ const finish = await findSelector(page, [`${chooser} button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u5B8C\u6210", 30000);
24129
+ await clickSelector(page, finish, 8000);
24130
+ const closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 8000);
24131
+ if (!closed)
24132
+ return "\u97F3\u9891: \u5C01\u9762\u9009\u62E9\u5668\u5B8C\u6210\u540E\u672A\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58\u97F3\u9891";
24133
+ }
24134
+ } finally {
24135
+ if (temporaryDir !== undefined) {
24136
+ await createNodeFileSystem().deleteFile(uploadPath);
24137
+ rmdir(temporaryDir);
24138
+ }
24139
+ }
24140
+ }
23355
24141
  }
23356
24142
  const titleInput = await trySelectors(page, AUDIO_TITLE_INPUT);
23357
24143
  if (titleInput.found)
@@ -23359,10 +24145,8 @@ async function insertAudioMaterial(page, input) {
23359
24145
  const agreed = String(await page.evaluate(AGREE));
23360
24146
  if (agreed !== "checked")
23361
24147
  return `\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`;
23362
- const save = await trySelectors(page, UPLOAD_SAVE);
23363
- if (!save.found)
23364
- return "\u97F3\u9891: \u4E0A\u4F20\u8868\u5355\u7684\u4FDD\u5B58\u6309\u94AE\u672A\u627E\u5230";
23365
- await clickSelector(page, UPLOAD_SAVE.join(", "), 8000);
24148
+ 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);
24149
+ await clickSelector(page, save, 8000);
23366
24150
  return null;
23367
24151
  };
23368
24152
  const matchOnce = async () => {
@@ -23382,6 +24166,7 @@ async function insertAudioMaterial(page, input) {
23382
24166
  };
23383
24167
  try {
23384
24168
  await page.evaluate(CARET);
24169
+ await page.press?.(process.platform === "darwin" ? "Meta+ArrowUp" : "Control+Home");
23385
24170
  await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_TOOLBAR_TRIGGER)})?.click()`);
23386
24171
  await page.waitForTimeout?.(300);
23387
24172
  await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
@@ -23449,7 +24234,7 @@ var SUBMIT_SETTLE_MS = 1500;
23449
24234
  var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
23450
24235
  var SUBMIT_ATTEMPTS = 2;
23451
24236
  function fail2(error) {
23452
- return ResultSchema.parse({ ok: false, target: TARGET, error });
24237
+ return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
23453
24238
  }
23454
24239
  function isConsoleReady(page) {
23455
24240
  return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
@@ -23460,6 +24245,25 @@ function isWechatLoggedIn(page) {
23460
24245
  function storageStatePath(profileDir) {
23461
24246
  return `${profileDir}/storage-state.json`;
23462
24247
  }
24248
+ async function persistWechatSessionState(context, profileDir) {
24249
+ const target = storageStatePath(profileDir);
24250
+ const temporary = `${target}.${process.pid}.${randomUUID2()}.tmp`;
24251
+ const fs = createNodeFileSystem();
24252
+ let reserved = false;
24253
+ try {
24254
+ createExclusiveFile(temporary);
24255
+ reserved = true;
24256
+ await context.storageState({ path: temporary });
24257
+ chmod(temporary, 384);
24258
+ await fs.rename(temporary, target);
24259
+ } finally {
24260
+ if (reserved) {
24261
+ await Promise.resolve().then(() => fs.deleteFile(temporary)).catch(() => {
24262
+ return;
24263
+ });
24264
+ }
24265
+ }
24266
+ }
23463
24267
  async function checkWechatSession(profileDir, opts = {}) {
23464
24268
  const { page, context } = await openWithState(profileDir, opts.headless ?? true);
23465
24269
  try {
@@ -23482,6 +24286,8 @@ async function openWechatLogin(opts) {
23482
24286
  headless: false,
23483
24287
  launcher: kkBrowserLauncher
23484
24288
  });
24289
+ let failed = false;
24290
+ let failure;
23485
24291
  try {
23486
24292
  const page = context.pages()[0] ?? await context.newPage();
23487
24293
  await page.goto(CONSOLE_URL);
@@ -23489,10 +24295,23 @@ async function openWechatLogin(opts) {
23489
24295
  if (!ready)
23490
24296
  throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
23491
24297
  await page.waitForTimeout(1e4);
23492
- await context.storageState({ path: storageStatePath(opts.profileDir) });
23493
- } finally {
24298
+ try {
24299
+ await persistWechatSessionState(context, opts.profileDir);
24300
+ } catch {
24301
+ throw new Error("wechatmp authenticated session snapshot failed");
24302
+ }
24303
+ } catch (error) {
24304
+ failed = true;
24305
+ failure = error;
24306
+ }
24307
+ try {
23494
24308
  await context.close();
24309
+ } catch {
24310
+ if (!failed)
24311
+ echoError("wechatmp browser context close failed after authenticated session snapshot; saved state preserved");
23495
24312
  }
24313
+ if (failed)
24314
+ throw failure;
23496
24315
  }
23497
24316
  async function openWechatSession(profileDir, opts = {}) {
23498
24317
  const { context } = await openWithState(profileDir, opts.headless ?? false, opts.url ?? CONSOLE_URL);
@@ -23538,7 +24357,10 @@ async function openWithState(profileDir, headless, url = CONSOLE_URL) {
23538
24357
  }
23539
24358
  async function defaultOpenConsole(profileDir) {
23540
24359
  const { page, context } = await openWithState(profileDir, true);
23541
- return wrapPlaywrightContext(context, page);
24360
+ return {
24361
+ ...wrapPlaywrightContext(context, page),
24362
+ persistSession: () => persistWechatSessionState(context, profileDir)
24363
+ };
23542
24364
  }
23543
24365
 
23544
24366
  class WechatmpBrowserTransport {
@@ -23588,33 +24410,48 @@ class WechatmpBrowserTransport {
23588
24410
  } catch (error) {
23589
24411
  return fail2(error instanceof Error ? error.message : String(error));
23590
24412
  }
24413
+ let result;
23591
24414
  try {
23592
24415
  const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
23593
24416
  if (!ready) {
23594
- return fail2(`no authenticated ${TARGET} console within ${this.consoleReadyTimeoutMs}ms \u2014 the preflight passed but the console did not report a session route (profile ${profileDir})`);
23595
- }
23596
- const title = typeof content.title === "string" ? content.title.trim() : "";
23597
- const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
23598
- return await runMode(validation.input, {
23599
- entry: opened.page,
23600
- pages: opened.pages,
23601
- newPage: opened.newPage,
23602
- content,
23603
- publishStatus,
23604
- profileDir,
23605
- driver: this.driver,
23606
- stepTimeoutMs: this.stepTimeoutMs,
23607
- pollMs: this.pollMs,
23608
- submitSettleMs: this.submitSettleMs,
23609
- draftTimeoutMs: this.draftTimeoutMs,
23610
- submitAttempts: this.submitAttempts,
23611
- draftBaseline
23612
- });
24417
+ 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})`);
24418
+ } else {
24419
+ const title = typeof content.title === "string" ? content.title.trim() : "";
24420
+ const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
24421
+ result = await runMode(validation.input, {
24422
+ entry: opened.page,
24423
+ pages: opened.pages,
24424
+ newPage: opened.newPage,
24425
+ content,
24426
+ publishStatus,
24427
+ profileDir,
24428
+ driver: this.driver,
24429
+ stepTimeoutMs: this.stepTimeoutMs,
24430
+ pollMs: this.pollMs,
24431
+ submitSettleMs: this.submitSettleMs,
24432
+ draftTimeoutMs: this.draftTimeoutMs,
24433
+ submitAttempts: this.submitAttempts,
24434
+ draftBaseline
24435
+ });
24436
+ }
23613
24437
  } catch (error) {
23614
- return fail2(error instanceof Error ? error.message : String(error));
24438
+ result = fail2(error instanceof Error ? error.message : String(error));
23615
24439
  } finally {
23616
- await opened.close();
24440
+ if (opened.persistSession !== undefined) {
24441
+ try {
24442
+ if (isConsoleReady(opened.page))
24443
+ await opened.persistSession();
24444
+ } catch {
24445
+ echoError("wechatmp-pub: authenticated session snapshot failed; publication result preserved");
24446
+ }
24447
+ }
24448
+ try {
24449
+ await opened.close();
24450
+ } catch {
24451
+ echoError("wechatmp-pub: browser context close failed; publication result preserved");
24452
+ }
23617
24453
  }
24454
+ return result;
23618
24455
  }
23619
24456
  }
23620
24457
  function getPublishTransport(driver = "script") {
@@ -23667,6 +24504,7 @@ export {
23667
24504
  markdownToWechatHtml,
23668
24505
  openWechatLogin,
23669
24506
  openWechatSession,
24507
+ persistWechatSessionState,
23670
24508
  processPublishIO,
23671
24509
  runMode,
23672
24510
  storageStatePath,