@gobing-ai/knowledge-kit 0.0.30 → 0.0.32

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 (66) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +10 -10
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +9 -9
  5. package/plugins/generations/core-facts-gen/dist/index.js +12 -12
  6. package/plugins/generations/daily-article-gen/dist/index.js +12 -12
  7. package/plugins/generations/dailynews-gen/dist/index.js +10 -10
  8. package/plugins/generations/dailynews-gen/src/script-builder.ts +4 -2
  9. package/plugins/generations/episode-plan-gen/dist/index.js +23 -13
  10. package/plugins/generations/episode-plan-gen/src/index.ts +23 -2
  11. package/plugins/generations/gemini-tts/dist/index.js +9 -9
  12. package/plugins/generations/image-gen/dist/index.js +9 -9
  13. package/plugins/generations/news-report-gen/dist/index.js +12 -12
  14. package/plugins/generations/voice-gen/dist/index.js +9 -9
  15. package/plugins/ingestions/aihot-ingest/dist/index.js +12 -12
  16. package/plugins/ingestions/horizon-ingest/dist/index.js +12 -12
  17. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  18. package/plugins/ingestions/last30days-ingest/dist/index.js +12 -12
  19. package/plugins/ingestions/web-search/dist/index.js +10 -10
  20. package/plugins/kk/plugin.json +1 -1
  21. package/plugins/kk/scripts/kk-workflow-stages.ts +173 -15
  22. package/plugins/kk/skills/content-judge/SKILL.md +16 -4
  23. package/plugins/kk/skills/content-judge/references/rubrics.md +8 -2
  24. package/plugins/kk/skills/content-judge/references/workflow-integration.md +2 -1
  25. package/plugins/kk/skills/content-safety/SKILL.md +24 -13
  26. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +65 -8
  27. package/plugins/publishings/emdash-pub/dist/index.js +9 -9
  28. package/plugins/publishings/infoq-pub/dist/index.js +8602 -211
  29. package/plugins/publishings/infoq-pub/package.json +3 -1
  30. package/plugins/publishings/infoq-pub/plugin.json +1 -1
  31. package/plugins/publishings/infoq-pub/src/index.ts +246 -29
  32. package/plugins/publishings/infoq-pub/src/profile-check.ts +19 -0
  33. package/plugins/publishings/juejin-pub/dist/index.js +8665 -211
  34. package/plugins/publishings/juejin-pub/package.json +3 -1
  35. package/plugins/publishings/juejin-pub/plugin.json +1 -1
  36. package/plugins/publishings/juejin-pub/src/index.ts +305 -29
  37. package/plugins/publishings/juejin-pub/src/profile-check.ts +19 -0
  38. package/plugins/publishings/medium-pub/dist/index.js +9 -9
  39. package/plugins/publishings/podcast-pub/dist/index.js +9 -9
  40. package/plugins/publishings/qiita-pub/dist/index.js +9 -9
  41. package/plugins/publishings/substack-pub/dist/index.js +8610 -211
  42. package/plugins/publishings/substack-pub/package.json +3 -1
  43. package/plugins/publishings/substack-pub/plugin.json +1 -1
  44. package/plugins/publishings/substack-pub/src/index.ts +271 -29
  45. package/plugins/publishings/substack-pub/src/profile-check.ts +74 -0
  46. package/plugins/publishings/surfdash-pub/dist/index.js +9 -9
  47. package/plugins/publishings/wechatmp-pub/dist/index.js +1227 -348
  48. package/plugins/publishings/wechatmp-pub/src/enrich.ts +229 -0
  49. package/plugins/publishings/wechatmp-pub/src/flow.ts +896 -0
  50. package/plugins/publishings/wechatmp-pub/src/index.ts +186 -516
  51. package/plugins/publishings/wechatmp-pub/src/modes.ts +403 -0
  52. package/plugins/publishings/wechatmp-pub/src/page.ts +260 -0
  53. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +7 -5
  54. package/plugins/publishings/x-pub/dist/index.js +8626 -221
  55. package/plugins/publishings/x-pub/package.json +3 -1
  56. package/plugins/publishings/x-pub/plugin.json +1 -1
  57. package/plugins/publishings/x-pub/src/index.ts +258 -20
  58. package/plugins/publishings/x-pub/src/profile-check.ts +19 -0
  59. package/plugins/publishings/xhs-pub/dist/index.js +843 -346
  60. package/plugins/publishings/xhs-pub/plugin.json +1 -1
  61. package/plugins/publishings/xhs-pub/src/flow.ts +572 -0
  62. package/plugins/publishings/xhs-pub/src/index.ts +101 -593
  63. package/plugins/publishings/xhs-pub/src/modes.ts +300 -0
  64. package/plugins/publishings/xhs-pub/src/page.ts +267 -0
  65. package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -3
  66. package/plugins/publishings/zenn-pub/dist/index.js +9 -9
@@ -14671,7 +14671,7 @@ var init_zod = __esm(() => {
14671
14671
  init_external();
14672
14672
  });
14673
14673
 
14674
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/errors.js
14674
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/errors.js
14675
14675
  var ErrorCode;
14676
14676
  var init_errors3 = __esm(() => {
14677
14677
  ErrorCode = {
@@ -14682,7 +14682,7 @@ var init_errors3 = __esm(() => {
14682
14682
  };
14683
14683
  });
14684
14684
 
14685
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14685
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14686
14686
  var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14687
14687
  var init_api_response = __esm(() => {
14688
14688
  init_errors3();
@@ -14705,10 +14705,10 @@ var init_api_response = __esm(() => {
14705
14705
  CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14706
14706
  });
14707
14707
 
14708
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14708
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14709
14709
  var init_cursor = () => {};
14710
14710
 
14711
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/env.js
14711
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/env.js
14712
14712
  function getEnvVar(name, fallback) {
14713
14713
  const raw = process.env[name];
14714
14714
  return raw === undefined ? fallback : raw;
@@ -14717,10 +14717,10 @@ function getEnvVars() {
14717
14717
  return process.env;
14718
14718
  }
14719
14719
 
14720
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14720
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14721
14721
  var init_event_severity = () => {};
14722
14722
 
14723
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/object.js
14723
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/object.js
14724
14724
  function isPlainObject2(value) {
14725
14725
  return typeof value === "object" && value !== null && !Array.isArray(value);
14726
14726
  }
@@ -14738,7 +14738,7 @@ function deepMerge(target, source) {
14738
14738
  return result;
14739
14739
  }
14740
14740
 
14741
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/output.js
14741
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/output.js
14742
14742
  function processStream(name) {
14743
14743
  const proc = globalThis.process;
14744
14744
  const stream = proc?.[name];
@@ -14756,7 +14756,7 @@ function echoError(message, target = defaultStderrTarget ?? processStream("stder
14756
14756
  }
14757
14757
  var defaultStderrTarget;
14758
14758
 
14759
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/index.js
14759
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/index.js
14760
14760
  var init_dist = __esm(() => {
14761
14761
  init_api_response();
14762
14762
  init_cursor();
@@ -21754,7 +21754,7 @@ var init_dist2 = __esm(() => {
21754
21754
  $visitAsync = visit.visitAsync;
21755
21755
  });
21756
21756
 
21757
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/config.js
21757
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/config.js
21758
21758
  function getProcessEnv() {
21759
21759
  return getEnvVars();
21760
21760
  }
@@ -21832,7 +21832,7 @@ var init_config = __esm(() => {
21832
21832
  };
21833
21833
  });
21834
21834
 
21835
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21835
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21836
21836
  import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
21837
21837
  import { dirname, resolve as resolvePath } from "path";
21838
21838
  function createNodeFileSystem(root, paths) {
@@ -21919,7 +21919,7 @@ function findProjectRoot(startDir) {
21919
21919
  }
21920
21920
  var init_file_system_node = () => {};
21921
21921
 
21922
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21922
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21923
21923
  var DbModuleNotInstalledError;
21924
21924
  var init_db_errors = __esm(() => {
21925
21925
  DbModuleNotInstalledError = class DbModuleNotInstalledError extends Error {
@@ -29130,7 +29130,7 @@ var init_execa = __esm(() => {
29130
29130
  } = getIpcExport());
29131
29131
  });
29132
29132
 
29133
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29133
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29134
29134
  function resolveDeadline(optionTimeout, defaultTimeout) {
29135
29135
  if (optionTimeout === null)
29136
29136
  return null;
@@ -29222,7 +29222,7 @@ function ownProcessGroupLifecycle(ownership) {
29222
29222
  }
29223
29223
  var MAX_TIMEOUT_MS = 2147483647, DEFAULT_KILL_GRACE_MS = 5000, GROUP_POLL_INTERVAL_MS = 25, GROUP_KILL_PATIENCE_MS = 2000;
29224
29224
 
29225
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29225
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29226
29226
  import { isatty } from "tty";
29227
29227
  function processEventSeverity(reason, exitCode) {
29228
29228
  if (reason === "error")
@@ -29594,7 +29594,7 @@ var init_process_executor = __esm(() => {
29594
29594
  init_config();
29595
29595
  });
29596
29596
 
29597
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29597
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29598
29598
  function getNodeFileSystem() {
29599
29599
  if (!_nodeFileSystem)
29600
29600
  _nodeFileSystem = createNodeFileSystem();
@@ -29744,11 +29744,11 @@ var PluginManifestSchema = exports_external.looseObject({
29744
29744
  description: exports_external.string().optional(),
29745
29745
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
29746
29746
  });
29747
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
29747
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29748
29748
  init_file_system_node();
29749
29749
  init_config();
29750
29750
 
29751
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/path.js
29751
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/path.js
29752
29752
  function normalizeSeparators(path6) {
29753
29753
  return path6.replaceAll("\\", "/");
29754
29754
  }
@@ -29806,7 +29806,7 @@ function getProcessCwd() {
29806
29806
  return globalThis.process?.cwd?.() ?? "/";
29807
29807
  }
29808
29808
 
29809
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
29809
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29810
29810
  init_runtime_node_bun();
29811
29811
 
29812
29812
  // ../../packages/kk-core/src/render-md.ts
@@ -29897,8 +29897,29 @@ function resolveBrowserProfileDir(channel, override) {
29897
29897
  }
29898
29898
  return join(homedir(), ".config", "kk", "browser-profiles", channel);
29899
29899
  }
29900
+ async function trySelectors(page, selectors, options = {}) {
29901
+ const timeout = options.timeout ?? 2000;
29902
+ const visible = options.visible ?? true;
29903
+ for (const selector of selectors) {
29904
+ try {
29905
+ const locator = page.locator(selector).first();
29906
+ const attached = await locator.count() > 0;
29907
+ if (!attached) {
29908
+ continue;
29909
+ }
29910
+ if (visible) {
29911
+ const isVisible = await locator.isVisible({ timeout }).catch(() => false);
29912
+ if (!isVisible) {
29913
+ continue;
29914
+ }
29915
+ }
29916
+ return { found: true, selector };
29917
+ } catch {}
29918
+ }
29919
+ return { found: false, selector: null };
29920
+ }
29900
29921
 
29901
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29922
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29902
29923
  class BrowserAutomationError extends Error {
29903
29924
  constructor(message, options) {
29904
29925
  super(message, options);
@@ -29933,10 +29954,10 @@ class PlaywrightUnavailableError extends BrowserAutomationError {
29933
29954
  this.name = "PlaywrightUnavailableError";
29934
29955
  }
29935
29956
  }
29936
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29957
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29937
29958
  import { mkdir } from "fs/promises";
29938
29959
 
29939
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29960
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29940
29961
  function resolveProfileDir(profileDir) {
29941
29962
  if (profileDir.trim() === "") {
29942
29963
  throw new InvalidProfileDirError;
@@ -29948,7 +29969,7 @@ function resolveProfileDir(profileDir) {
29948
29969
  return resolved;
29949
29970
  }
29950
29971
 
29951
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29972
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29952
29973
  var PROFILE_DIR_MODE = 448;
29953
29974
  var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
29954
29975
  var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
@@ -29999,7 +30020,7 @@ function createPersistentLauncher(options = {}) {
29999
30020
  };
30000
30021
  }
30001
30022
 
30002
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30023
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30003
30024
  var defaultLauncher;
30004
30025
  function getDefaultLauncher() {
30005
30026
  if (defaultLauncher === undefined)
@@ -30012,34 +30033,1098 @@ async function openPersistentBrowser(options) {
30012
30033
  headless: options.headless
30013
30034
  });
30014
30035
  }
30036
+ // ../../packages/publish-harness/src/browser.ts
30037
+ var DEFAULT_STEP_TIMEOUT_MS = 30000;
30038
+ var DEFAULT_POLL_MS = 500;
30039
+ var ATTACH_POLL_MS = 100;
30040
+ async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
30041
+ const deadline = Date.now() + timeoutMs;
30042
+ while (Date.now() < deadline) {
30043
+ if (await predicate()) {
30044
+ return true;
30045
+ }
30046
+ await new Promise((resolve2) => setTimeout(resolve2, pollMs));
30047
+ }
30048
+ return predicate();
30049
+ }
30050
+ async function pasteHtml(page, selector, html, opts = {}) {
30051
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30052
+ await requireAttached(page, selector, "pasteHtml", timeoutMs);
30053
+ await page.evaluate(pasteScript(selector, html));
30054
+ const filled = await waitUntil(async () => String(await page.evaluate(editorTextScript(selector))).length > 0, timeoutMs, ATTACH_POLL_MS);
30055
+ if (!filled) {
30056
+ throw new Error(`pasteHtml: ${selector}: editor text is empty ${timeoutMs}ms after the paste`);
30057
+ }
30058
+ }
30059
+ async function uploadFile(page, selector, filePath, opts = {}) {
30060
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30061
+ await requireAttached(page, selector, "uploadFile", timeoutMs);
30062
+ await page.setInputFiles(selector, filePath);
30063
+ }
30064
+ async function expectOutcome(probe, opts) {
30065
+ let evidence = "";
30066
+ let lastProbeError = null;
30067
+ const observed = await waitUntil(async () => {
30068
+ let result;
30069
+ try {
30070
+ result = await probe();
30071
+ } catch (error51) {
30072
+ lastProbeError = error51 instanceof Error ? error51.message : String(error51);
30073
+ return false;
30074
+ }
30075
+ if (result === null || result.length === 0) {
30076
+ return false;
30077
+ }
30078
+ evidence = result;
30079
+ return true;
30080
+ }, opts.timeoutMs, opts.pollMs ?? DEFAULT_POLL_MS);
30081
+ if (observed && evidence.length > 0) {
30082
+ return { ok: true, evidence };
30083
+ }
30084
+ const detail = lastProbeError === null ? "" : ` (last probe error: ${lastProbeError})`;
30085
+ return { ok: false, error: `${opts.label}: no outcome observed within ${opts.timeoutMs}ms${detail}` };
30086
+ }
30087
+ var DEFAULT_SIGNAL_CONTAINERS = [
30088
+ '[class*="toast" i]',
30089
+ '[class*="notification" i]',
30090
+ '[role="alert"]',
30091
+ '[class*="message" i]'
30092
+ ];
30093
+ async function readScopedSignal(page, texts, containers = DEFAULT_SIGNAL_CONTAINERS) {
30094
+ for (const text of texts) {
30095
+ for (const container of containers) {
30096
+ const locator = page.locator(`${container} :text("${text}")`).first();
30097
+ if (await locator.count().catch(() => 0) === 0)
30098
+ continue;
30099
+ if (!await locator.isVisible().catch(() => false))
30100
+ continue;
30101
+ const observed = (await locator.textContent().catch(() => null))?.trim();
30102
+ return observed !== undefined && observed.length > 0 ? observed : text;
30103
+ }
30104
+ }
30105
+ return null;
30106
+ }
30107
+ async function requireAttached(page, selector, action, timeoutMs) {
30108
+ await resolveSelector(page, [selector], { timeoutMs, label: action });
30109
+ }
30110
+ async function resolveSelector(page, selectors, opts = {}) {
30111
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30112
+ const found = { selector: null };
30113
+ const attached = await waitUntil(async () => {
30114
+ const result = await trySelectors(page, selectors, { visible: false });
30115
+ found.selector = result.selector;
30116
+ return result.found && result.selector !== null;
30117
+ }, timeoutMs, ATTACH_POLL_MS);
30118
+ const selector = found.selector;
30119
+ if (!attached || selector === null) {
30120
+ throw new Error(`${opts.label ?? "selector"}: ${selectors.join(" | ")} not attached within ${timeoutMs}ms`);
30121
+ }
30122
+ return selector;
30123
+ }
30124
+ function pasteScript(selector, html) {
30125
+ const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
30126
+ return `(() => {
30127
+ const el = document.querySelector(${JSON.stringify(selector)});
30128
+ if (!el) return false;
30129
+ const data = new DataTransfer();
30130
+ data.setData('text/html', ${JSON.stringify(html)});
30131
+ data.setData('text/plain', ${JSON.stringify(text)});
30132
+ el.focus();
30133
+ el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
30134
+ return true;
30135
+ })()`;
30136
+ }
30137
+ function editorTextScript(selector) {
30138
+ return `(() => {
30139
+ const el = document.querySelector(${JSON.stringify(selector)});
30140
+ return el ? (el.textContent ?? '').trim() : '';
30141
+ })()`;
30142
+ }
30143
+
30015
30144
  // ../../plugins/publishings/wechatmp-pub/src/index.ts
30016
30145
  init_dist();
30146
+
30147
+ // ../../plugins/publishings/wechatmp-pub/src/page.ts
30148
+ var POLL_MS = 500;
30149
+ var STEP_TIMEOUT_MS = 30000;
30150
+ async function tryFindAttachedSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
30151
+ let found;
30152
+ await waitUntil(async () => {
30153
+ for (const selector of selectors) {
30154
+ const attached = await page.locator(selector).count().catch(() => 0);
30155
+ if (attached > 0) {
30156
+ found = selector;
30157
+ return true;
30158
+ }
30159
+ }
30160
+ return false;
30161
+ }, timeoutMs);
30162
+ return found;
30163
+ }
30164
+ async function tryFindSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
30165
+ let found;
30166
+ await waitUntil(async () => {
30167
+ const result = await trySelectors(page, selectors).catch(() => ({ found: false, selector: null }));
30168
+ if (result.found && result.selector) {
30169
+ found = result.selector;
30170
+ return true;
30171
+ }
30172
+ return false;
30173
+ }, timeoutMs);
30174
+ return found;
30175
+ }
30176
+ async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30177
+ const found = await tryFindSelector(page, selectors, timeoutMs);
30178
+ if (found === undefined) {
30179
+ throw new Error(`${what}: none of [${selectors.join(" | ")}] visible within ${timeoutMs}ms`);
30180
+ }
30181
+ return found;
30182
+ }
30183
+ async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30184
+ const found = await tryFindAttachedSelector(page, selectors, timeoutMs);
30185
+ if (found === undefined) {
30186
+ throw new Error(`${what}: none of [${selectors.join(" | ")}] attached within ${timeoutMs}ms`);
30187
+ }
30188
+ return found;
30189
+ }
30190
+ async function followNewPage(pages, before, timeoutMs) {
30191
+ const opened = await waitUntil(async () => pages().length > before, timeoutMs);
30192
+ if (!opened)
30193
+ return;
30194
+ const all = pages();
30195
+ return all[all.length - 1];
30196
+ }
30197
+ async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
30198
+ await page.locator(selector).first().click({ timeout: timeoutMs });
30199
+ }
30200
+ async function fillSelector(page, selector, value) {
30201
+ await page.locator(selector).first().fill(value);
30202
+ }
30203
+ function wrapPlaywrightPage(raw) {
30204
+ const pw = raw;
30205
+ return {
30206
+ goto: (url2, options) => pw.goto(url2, { waitUntil: "domcontentloaded", ...options }).then(() => {
30207
+ return;
30208
+ }),
30209
+ locator: (selector) => ({
30210
+ count: () => pw.locator(selector).count(),
30211
+ first: () => {
30212
+ const loc = pw.locator(selector).first();
30213
+ return {
30214
+ count: () => loc.count(),
30215
+ isVisible: (options) => loc.isVisible(options),
30216
+ fill: (text, options) => loc.fill(text, options),
30217
+ click: (options) => loc.click(options),
30218
+ type: (text, options) => loc.pressSequentially(text, options),
30219
+ waitFor: (options) => loc.waitFor(options),
30220
+ textContent: async () => await loc.textContent() ?? "",
30221
+ inputValue: () => loc.inputValue()
30222
+ };
30223
+ }
30224
+ }),
30225
+ setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
30226
+ url: () => pw.url(),
30227
+ evaluate: (expression) => pw.evaluate(expression),
30228
+ waitForTimeout: (ms) => pw.waitForTimeout(ms),
30229
+ press: (key) => pw.keyboard.press(key),
30230
+ frames: () => {
30231
+ const source = raw;
30232
+ return (source.frames?.() ?? []).map((frame) => ({
30233
+ url: () => frame.url(),
30234
+ locator: (selector) => ({ count: () => frame.locator(selector).count() })
30235
+ }));
30236
+ }
30237
+ };
30238
+ }
30239
+ function wrapPlaywrightContext(raw, entryPage) {
30240
+ const context2 = raw;
30241
+ const cache = new WeakMap;
30242
+ const wrap = (page) => {
30243
+ const key = page;
30244
+ const cached2 = cache.get(key);
30245
+ if (cached2 !== undefined)
30246
+ return cached2;
30247
+ const wrapped = wrapPlaywrightPage(page);
30248
+ cache.set(key, wrapped);
30249
+ return wrapped;
30250
+ };
30251
+ return {
30252
+ page: wrap(entryPage),
30253
+ pages: () => context2.pages().map(wrap),
30254
+ newPage: async () => wrap(await context2.newPage()),
30255
+ close: () => context2.close()
30256
+ };
30257
+ }
30258
+
30259
+ // ../../plugins/publishings/wechatmp-pub/src/enrich.ts
30260
+ var DIALOG = ".weui-desktop-dialog:visible";
30261
+ var CONFIRM = `${DIALOG} button:has-text("\u786E\u8BA4"), ${DIALOG} button:has-text("\u786E\u5B9A")`;
30262
+ var ORIGINAL_SWITCH = ".setting-group__switch.js_edit_ori";
30263
+ var TAG_ROW = ".allow_click_opr.js_article_tags_label";
30264
+ var TAG_INPUT = 'input[placeholder*="\u5408\u96C6"]';
30265
+ var CLAIM_SOURCE = "#js_claim_source_area .frm_checkbox_label";
30266
+ var AUDIO_MENU = "li.js_insertaudio";
30267
+ var AUDIO_DIALOG = ".audio_dialog";
30268
+ var AUDIO_DIALOG_TIMEOUT_MS = 15000;
30269
+ var AUDIO_ROW_RETRIES = 2;
30270
+ var AUDIO_ROW_SETTLE_MS = 1200;
30271
+ var COLLECTION_SETTLE_MS = 1200;
30272
+ var STEP_RETRY_SETTLE_MS = 1000;
30273
+ var SWEEP_SETTLE_MS = 400;
30274
+ async function enrichArticle(page, meta3) {
30275
+ const warnings = [];
30276
+ const str = (key) => typeof meta3[key] === "string" ? meta3[key] : "";
30277
+ const on6 = (key) => meta3[key] === true;
30278
+ const sweep = async () => {
30279
+ const cancel = await trySelectors(page, ['button:has-text("\u53D6\u6D88")']);
30280
+ if (cancel.found)
30281
+ await page.locator(cancel.selector ?? "").first().click({ timeout: 2000 }).catch(() => {
30282
+ return;
30283
+ });
30284
+ await page.press?.("Escape");
30285
+ await page.waitForTimeout?.(SWEEP_SETTLE_MS);
30286
+ };
30287
+ const step = async (name, fn) => {
30288
+ try {
30289
+ await fn();
30290
+ return;
30291
+ } catch {}
30292
+ await sweep();
30293
+ await page.waitForTimeout?.(STEP_RETRY_SETTLE_MS);
30294
+ try {
30295
+ await fn();
30296
+ } catch (error51) {
30297
+ warnings.push(`${name}: ${(error51 instanceof Error ? error51.message : String(error51)).slice(0, 120)}`);
30298
+ await sweep();
30299
+ }
30300
+ };
30301
+ if (on6("original")) {
30302
+ await step("\u539F\u521B\u58F0\u660E", async () => {
30303
+ await clickSelector(page, ORIGINAL_SWITCH, 1e4);
30304
+ await clickSelector(page, `${DIALOG} :text("\u6587\u5B57\u539F\u521B")`, 8000);
30305
+ const author = str("author");
30306
+ if (author !== "")
30307
+ await fillSelector(page, `${DIALOG} input[placeholder*="\u4F5C\u8005"]`, author);
30308
+ await clickSelector(page, CONFIRM, 8000);
30309
+ });
30310
+ const committed = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`);
30311
+ if (committed === false) {
30312
+ warnings.push("\u539F\u521B\u58F0\u660E: \u672A\u751F\u6548 - \u4F5C\u8005\u987B\u4E3A\u8D5E\u8D4F\u8D26\u6237(\u641C\u7D22\u9009\u62E9)\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E");
30313
+ await sweep();
30314
+ }
30315
+ }
30316
+ const collection = str("collection");
30317
+ if (collection !== "") {
30318
+ await step("\u5408\u96C6", async () => {
30319
+ await clickSelector(page, TAG_ROW, 8000);
30320
+ await waitForVisible(page, TAG_INPUT, 8000);
30321
+ await fillSelector(page, TAG_INPUT, collection);
30322
+ await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
30323
+ await clickSelector(page, `${DIALOG} :text("${collection}")`, 8000);
30324
+ await clickSelector(page, CONFIRM, 8000);
30325
+ });
30326
+ }
30327
+ if (on6("aiGenerated")) {
30328
+ await step("\u521B\u4F5C\u6765\u6E90", async () => {
30329
+ await clickSelector(page, CLAIM_SOURCE, 8000);
30330
+ const dialog = await waitForVisible(page, DIALOG, 3000, { required: false });
30331
+ if (dialog !== undefined) {
30332
+ await clickSelector(page, `${DIALOG} :text("\u5185\u5BB9\u7531AI\u751F\u6210")`, 8000);
30333
+ await clickSelector(page, CONFIRM, 8000);
30334
+ }
30335
+ });
30336
+ const checked = await page.evaluate(`!!document.querySelector('#js_claim_source_area input:checked')`);
30337
+ if (checked === false)
30338
+ warnings.push("\u521B\u4F5C\u6765\u6E90: \u52FE\u9009\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E");
30339
+ }
30340
+ await sweep();
30341
+ return warnings;
30342
+ }
30343
+ async function insertAudio(page, title) {
30344
+ const CARET = `(() => {
30345
+ const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30346
+ if (!pm || !pm.firstChild) return;
30347
+ const r = document.createRange();
30348
+ r.setStart(pm.firstChild, 0);
30349
+ r.collapse(true);
30350
+ const sel = window.getSelection();
30351
+ sel?.removeAllRanges();
30352
+ sel?.addRange(r);
30353
+ })()`;
30354
+ const DEDUPE = `(() => {
30355
+ const want = ${JSON.stringify(title)}.replace(/\\s+/g, '');
30356
+ const hit = [...document.querySelectorAll('label.frm_checkbox_label.audio_item')]
30357
+ .find((l) => (l.textContent ?? '').replace(/\\s+/g, '').includes(want));
30358
+ if (!hit) return 'missing';
30359
+ const cb = (hit.closest('li') ?? hit.parentElement)?.querySelector('input[type=checkbox]');
30360
+ if (!cb) return 'nobox';
30361
+ cb.click();
30362
+ return 'checked';
30363
+ })()`;
30364
+ const INSERT = `(() => {
30365
+ const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u63D2\u5165' && b.offsetWidth > 0);
30366
+ if (!btn) return false;
30367
+ btn.click();
30368
+ return true;
30369
+ })()`;
30370
+ const CANCEL = `(() => {
30371
+ const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u53D6\u6D88' && b.offsetWidth > 0);
30372
+ btn?.click();
30373
+ })()`;
30374
+ const VERIFY = `(() => {
30375
+ const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30376
+ if (!pm) return 'noeditor';
30377
+ const node = pm.querySelector('mpvoice, [class*="audio"]');
30378
+ if (!node) return 'absent';
30379
+ const first = pm.firstElementChild;
30380
+ return first && (first === node || first.contains(node)) ? 'top' : 'notop';
30381
+ })()`;
30382
+ try {
30383
+ await page.evaluate(CARET);
30384
+ await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
30385
+ await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
30386
+ let dedupe = String(await page.evaluate(DEDUPE));
30387
+ for (let i2 = 0;dedupe === "missing" && i2 < AUDIO_ROW_RETRIES; i2 += 1) {
30388
+ await page.waitForTimeout?.(AUDIO_ROW_SETTLE_MS);
30389
+ dedupe = String(await page.evaluate(DEDUPE));
30390
+ }
30391
+ if (dedupe !== "checked") {
30392
+ await page.evaluate(CANCEL);
30393
+ return dedupe === "missing" ? "\u97F3\u9891: \u7D20\u6750\u5E93\u65E0\u540C\u540D\u97F3\u9891\uFF0C\u8BF7\u624B\u52A8\u4E0A\u4F20\u540E\u63D2\u5165" : "\u97F3\u9891: \u540C\u540D\u7D20\u6750\u884C\u672A\u627E\u5230\u52FE\u9009\u6846\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30394
+ }
30395
+ if (await page.evaluate(INSERT) !== true) {
30396
+ await page.evaluate(CANCEL);
30397
+ return "\u97F3\u9891: \u63D2\u5165\u6309\u94AE\u672A\u627E\u5230\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30398
+ }
30399
+ await page.waitForTimeout?.(2000);
30400
+ const state = String(await page.evaluate(VERIFY));
30401
+ if (state === "absent" || state === "noeditor")
30402
+ return "\u97F3\u9891: \u63D2\u5165\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5";
30403
+ if (state === "notop")
30404
+ return "\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u672A\u5728\u6587\u9996\uFF0C\u8BF7\u624B\u52A8\u8C03\u6574\u4F4D\u7F6E";
30405
+ return "";
30406
+ } catch {
30407
+ return "\u97F3\u9891: \u81EA\u52A8\u63D2\u5165\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30408
+ }
30409
+ }
30410
+ async function waitForVisible(page, selector, timeoutMs, opts = {}) {
30411
+ const visible = await waitUntil(async () => {
30412
+ const found = await trySelectors(page, [selector]).catch(() => ({ found: false, selector: null }));
30413
+ return found.found;
30414
+ }, timeoutMs, POLL_MS);
30415
+ if (visible)
30416
+ return selector;
30417
+ if (opts.required === false)
30418
+ return;
30419
+ throw new Error(`${selector}: not visible within ${timeoutMs}ms`);
30420
+ }
30421
+
30422
+ // ../../plugins/publishings/wechatmp-pub/src/modes.ts
30423
+ import { isAbsolute } from "path";
30017
30424
  var TARGET = "wechatmp";
30018
30425
  var CONSOLE_URL = "https://mp.weixin.qq.com/";
30019
- var SEL = {
30020
- loggedInFragment: "/cgi-bin/home",
30021
- menuItem: ".new-creation__menu .new-creation__menu-item",
30022
- titleInput: '.ProseMirror[data-placeholder*="\u6807\u9898"]',
30023
- authorInput: "#author",
30024
- editor: ".ProseMirror:not([data-placeholder])",
30025
- summaryInput: "#js_description",
30026
- saveDraftBtn: 'button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")',
30027
- originalSwitch: ".setting-group__switch.js_edit_ori",
30028
- tagRow: ".allow_click_opr.js_article_tags_label",
30029
- tagInput: 'input[placeholder*="\u5408\u96C6"]',
30030
- claimSourceCheckbox: "#js_claim_source_area .frm_checkbox_label",
30031
- coverBtn: ".js_cover_btn_area",
30032
- publishBtn: '.js_publish, button:has-text("\u53D1\u8868"), button:has-text("\u53D1\u5E03")',
30033
- confirmBtn: '.weui-desktop-dialog .weui-desktop-btn_primary, button:has-text("\u786E\u5B9A")',
30034
- toast: ".weui-desktop-toast"
30426
+ var CONSOLE_HOME_FRAGMENT = "/cgi-bin/home";
30427
+ var WECHATMP_OPERATIONS = ["\u6587\u7AE0", "\u8D34\u56FE", "\u89C6\u9891", "\u64AD\u5BA2"];
30428
+ var DEFAULT_WECHATMP_OPERATION = "\u6587\u7AE0";
30429
+ var CREATE_TYPES = {
30430
+ \u{6587}\u{7ae0}: 0,
30431
+ \u{8d34}\u{56fe}: 8,
30432
+ \u{89c6}\u{9891}: 5,
30433
+ \u{64ad}\u{5ba2}: 7
30035
30434
  };
30036
- var CONSOLE_READY_TIMEOUT_MS = 30000;
30037
- var TOAST_TIMEOUT_MS = 15000;
30038
- function fail(message) {
30039
- throw new Error(message);
30435
+ var EDITOR_ROUTE = "/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=77";
30436
+ var CONSOLE_TOKEN_PATTERN = /[?&]token=([0-9]+)/;
30437
+ function readConsoleToken(url2) {
30438
+ return CONSOLE_TOKEN_PATTERN.exec(url2)?.[1];
30439
+ }
30440
+ function editorUrl(operation, token, timestamp = Date.now()) {
30441
+ const base2 = `${CONSOLE_URL.replace(/\/$/, "")}${EDITOR_ROUTE}`;
30442
+ return `${base2}&createType=${CREATE_TYPES[operation]}&token=${encodeURIComponent(token)}&lang=zh_CN&timestamp=${timestamp}`;
30443
+ }
30444
+ function readCreateType(url2) {
30445
+ const found = /[?&]createType=([0-9]+)/.exec(url2)?.[1];
30446
+ return found === undefined ? undefined : Number(found);
30447
+ }
30448
+ function resolveWechatmpOperation(options, content) {
30449
+ const raw = options?.operation ?? content?.metadata?.operation;
30450
+ if (raw === undefined)
30451
+ return { operation: DEFAULT_WECHATMP_OPERATION };
30452
+ if (typeof raw !== "string" || !WECHATMP_OPERATIONS.includes(raw)) {
30453
+ return {
30454
+ error: `unsupported operation '${String(raw)}' for ${TARGET}: expected one of ${WECHATMP_OPERATIONS.join(", ")}`
30455
+ };
30456
+ }
30457
+ return { operation: raw };
30458
+ }
30459
+ var IMAGE_EXTENSIONS = /\.(png|jpe?g|gif|webp|svg)$/i;
30460
+ var IMAGE_LIST = "png|jpg|jpeg|gif|webp|svg";
30461
+ var VIDEO_EXTENSIONS = /\.(mp4|mov|flv|f4v|mkv|m4v|mpg|mpeg|ts)$/i;
30462
+ var VIDEO_LIST = "mp4|mov|flv|f4v|mkv|m4v|mpg|mpeg|ts";
30463
+ var AUDIO_EXTENSIONS = /\.(mp3|wav|aac|flac|m4a)$/i;
30464
+ var AUDIO_LIST = "mp3|wav|aac|flac|m4a";
30465
+ var DERIVED_DIGEST_MAX = 54;
30466
+ var MODE_LIMITS = {
30467
+ \u{6587}\u{7ae0}: { titleRequired: true, titleMax: 64, digestMax: 120, authorMax: 8 },
30468
+ \u{8d34}\u{56fe}: { authorMax: 8 },
30469
+ \u{89c6}\u{9891}: { titleMax: 20, authorMax: 8 },
30470
+ \u{64ad}\u{5ba2}: { titleMax: 20, authorMax: 8 }
30471
+ };
30472
+ function artifactPaths(input) {
30473
+ switch (input.operation) {
30474
+ case "\u6587\u7AE0":
30475
+ return input.images;
30476
+ case "\u8D34\u56FE":
30477
+ return input.images;
30478
+ case "\u89C6\u9891":
30479
+ return [input.video];
30480
+ case "\u64AD\u5BA2":
30481
+ return [input.audio, input.cover];
30482
+ }
30483
+ }
30484
+ function coverPath(input) {
30485
+ return input.operation === "\u6587\u7AE0" || input.operation === "\u8D34\u56FE" ? input.images[0] : undefined;
30486
+ }
30487
+ async function resolveMediaPath(content, spec) {
30488
+ const raw = content.metadata?.[spec.key];
30489
+ const path7 = typeof raw === "string" ? raw : typeof raw?.path === "string" ? raw.path : undefined;
30490
+ if (path7 === undefined || path7.length === 0) {
30491
+ return {
30492
+ error: `${spec.label}: Content.metadata.${spec.key} is required (absolute path to an existing ${spec.list} file)`
30493
+ };
30494
+ }
30495
+ if (!isAbsolute(path7)) {
30496
+ return { error: `${spec.label}: Content.metadata.${spec.key} must be absolute, got '${path7}'` };
30497
+ }
30498
+ if (!spec.extensions.test(path7)) {
30499
+ return {
30500
+ error: `${spec.label}: Content.metadata.${spec.key} has unsupported extension (expected ${spec.list}): '${path7}'`
30501
+ };
30502
+ }
30503
+ if (!await createNodeFileSystem().exists(path7)) {
30504
+ return { error: `${spec.label}: Content.metadata.${spec.key} does not exist: '${path7}'` };
30505
+ }
30506
+ return { path: path7 };
30040
30507
  }
30041
- function escapeHtml(text) {
30042
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
30508
+ async function resolveBodyImages(content) {
30509
+ const ordered = content.metadata?.images;
30510
+ if (ordered !== undefined && !Array.isArray(ordered)) {
30511
+ return { error: "images validation: metadata.images must be an array" };
30512
+ }
30513
+ const descriptors2 = Array.isArray(ordered) ? ordered : [];
30514
+ const cover = typeof content.metadata?.wechatmp === "object" && content.metadata.wechatmp !== null ? content.metadata.wechatmp.coverPath : undefined;
30515
+ const candidates = [];
30516
+ if (typeof cover === "string" && cover.length > 0)
30517
+ candidates.push({ path: cover, what: "metadata.wechatmp.coverPath" });
30518
+ for (const [index, image] of descriptors2.entries()) {
30519
+ const path7 = typeof image === "string" ? image : image?.path;
30520
+ if (typeof path7 !== "string" || path7.length === 0) {
30521
+ return { error: `images validation: images[${index}] carries no path` };
30522
+ }
30523
+ candidates.push({ path: path7, what: `metadata.images[${index}]` });
30524
+ }
30525
+ const paths = [];
30526
+ for (const { path: path7, what } of candidates) {
30527
+ if (!isAbsolute(path7)) {
30528
+ return { error: `images validation: ${what} must be absolute, got '${path7}'` };
30529
+ }
30530
+ if (!IMAGE_EXTENSIONS.test(path7)) {
30531
+ return {
30532
+ error: `images validation: ${what} has unsupported extension (expected ${IMAGE_LIST}): '${path7}'`
30533
+ };
30534
+ }
30535
+ if (!await createNodeFileSystem().exists(path7)) {
30536
+ return { error: `images validation: ${what} does not exist: '${path7}'` };
30537
+ }
30538
+ paths.push(path7);
30539
+ }
30540
+ return { paths };
30541
+ }
30542
+ function deriveDigest(content) {
30543
+ const explicit = content.metadata?.digest;
30544
+ if (typeof explicit === "string")
30545
+ return explicit;
30546
+ const body = content.body ?? "";
30547
+ const first = body.split(`
30548
+ `).map((line) => line.trim()).find((line) => line !== "");
30549
+ return first === undefined ? undefined : first.slice(0, DERIVED_DIGEST_MAX);
30550
+ }
30551
+ function validatePublishStatus(content) {
30552
+ const status = content.metadata?.publishStatus;
30553
+ if (status !== undefined && status !== "draft" && status !== "public") {
30554
+ return {
30555
+ error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
30556
+ };
30557
+ }
30558
+ return {};
30559
+ }
30560
+ function validateTitle(content, operation) {
30561
+ const limits = MODE_LIMITS[operation];
30562
+ const title = content.title ?? "";
30563
+ if (limits.titleRequired === true && title.trim().length === 0) {
30564
+ return {
30565
+ error: `title validation: Content.title is required for ${operation} (the editor's placeholder is \u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898)`
30566
+ };
30567
+ }
30568
+ if (limits.titleMax !== undefined && title.length > limits.titleMax) {
30569
+ return {
30570
+ error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${limits.titleMax}`
30571
+ };
30572
+ }
30573
+ return {};
30574
+ }
30575
+ function validateDigest(content, operation) {
30576
+ const max = MODE_LIMITS[operation].digestMax;
30577
+ if (max === undefined)
30578
+ return {};
30579
+ const digest = deriveDigest(content);
30580
+ if (digest !== undefined && digest.length > max) {
30581
+ return { error: `digest validation: the \u6458\u8981 is ${digest.length} chars but ${operation} accepts \u2264${max}` };
30582
+ }
30583
+ return digest === undefined ? {} : { digest };
30584
+ }
30585
+ function validateAuthor(content, operation) {
30586
+ const max = MODE_LIMITS[operation].authorMax;
30587
+ const author = content.metadata?.author;
30588
+ if (typeof author !== "string" || author.length === 0)
30589
+ return {};
30590
+ if (max !== undefined && author.length > max) {
30591
+ return {
30592
+ error: `author validation: Content.metadata.author is ${author.length} chars but ${operation} accepts \u2264${max}`
30593
+ };
30594
+ }
30595
+ return { author };
30596
+ }
30597
+ async function validateModeInput(operation, content) {
30598
+ const status = validatePublishStatus(content);
30599
+ if (status.error)
30600
+ return { error: status.error };
30601
+ const title = validateTitle(content, operation);
30602
+ if (title.error)
30603
+ return { error: title.error };
30604
+ const author = validateAuthor(content, operation);
30605
+ if (author.error)
30606
+ return { error: author.error };
30607
+ switch (operation) {
30608
+ case "\u6587\u7AE0": {
30609
+ if ((content.body ?? "").trim().length === 0) {
30610
+ return { error: "body validation: Content.body is required for \u6587\u7AE0" };
30611
+ }
30612
+ const digest = validateDigest(content, operation);
30613
+ if (digest.error)
30614
+ return { error: digest.error };
30615
+ const images = await resolveBodyImages(content);
30616
+ if (images.error)
30617
+ return { error: images.error };
30618
+ if ((images.paths ?? []).length === 0) {
30619
+ return {
30620
+ error: "cover validation: \u6587\u7AE0 takes its cover from a body image (\u9ED8\u8BA4\u9996\u56FE\u4E3A\u5C01\u9762) but none was supplied \u2014 set metadata.wechatmp.coverPath or metadata.images[]"
30621
+ };
30622
+ }
30623
+ return {
30624
+ input: {
30625
+ operation,
30626
+ images: images.paths ?? [],
30627
+ ...digest.digest === undefined ? {} : { digest: digest.digest }
30628
+ }
30629
+ };
30630
+ }
30631
+ case "\u8D34\u56FE": {
30632
+ const images = await resolveBodyImages(content);
30633
+ if (images.error)
30634
+ return { error: images.error };
30635
+ if ((images.paths ?? []).length === 0) {
30636
+ return {
30637
+ error: "image validation: \u8D34\u56FE requires at least one image (metadata.images[] or metadata.wechatmp.coverPath)"
30638
+ };
30639
+ }
30640
+ return { input: { operation, images: images.paths ?? [] } };
30641
+ }
30642
+ case "\u89C6\u9891": {
30643
+ const video = await resolveMediaPath(content, {
30644
+ key: "video",
30645
+ label: "video validation",
30646
+ extensions: VIDEO_EXTENSIONS,
30647
+ list: VIDEO_LIST
30648
+ });
30649
+ if (video.error)
30650
+ return { error: video.error };
30651
+ return { input: { operation, video: video.path ?? "" } };
30652
+ }
30653
+ case "\u64AD\u5BA2": {
30654
+ const audio = await resolveMediaPath(content, {
30655
+ key: "audio",
30656
+ label: "audio validation",
30657
+ extensions: AUDIO_EXTENSIONS,
30658
+ list: AUDIO_LIST
30659
+ });
30660
+ if (audio.error)
30661
+ return { error: audio.error };
30662
+ const cover = await resolveMediaPath(content, {
30663
+ key: "coverImage",
30664
+ label: "cover validation",
30665
+ extensions: IMAGE_EXTENSIONS,
30666
+ list: IMAGE_LIST
30667
+ });
30668
+ if (cover.error)
30669
+ return { error: cover.error };
30670
+ return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
30671
+ }
30672
+ }
30673
+ }
30674
+
30675
+ // ../../plugins/publishings/wechatmp-pub/src/flow.ts
30676
+ var CREATION_MENU_ITEM = "div.new-creation__menu-item";
30677
+ var CREATION_MENU_OPEN = ["text=\u65B0\u7684\u521B\u4F5C", 'div.new-creation__menu-title:has-text("\u65B0\u7684\u521B\u4F5C")'];
30678
+ var TITLE_BLOCK = [
30679
+ 'div.ProseMirror[data-placeholder^="\u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898"]',
30680
+ 'div.ProseMirror[data-placeholder*="\u6807\u9898"]'
30681
+ ];
30682
+ var TITLE_MIRROR = ["textarea#title", "textarea.js_title"];
30683
+ var BODY_BLOCK = [
30684
+ "div.ProseMirror.ProseMirror-focused:not([data-placeholder])",
30685
+ "div.ProseMirror:not([data-placeholder])",
30686
+ 'div.ProseMirror[contenteditable="true"]'
30687
+ ];
30688
+ var DIGEST_INPUT = ["textarea#js_description", "textarea.js_desc"];
30689
+ var AUTHOR_INPUT = ["input#author", "input.js_author"];
30690
+ var IMAGE_FILE_INPUT = ['input[type="file"][accept*="image/"]', 'input[type="file"][accept*=".png"]'];
30691
+ var LAYOUT_CONTROL = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
30692
+ var LAYOUT_TARGET = ['div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")', 'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'];
30693
+ var COVER_PANEL_OPEN = ["a.js_selectCoverFromContent", ".js_cover_btn_area", "text=\u6362\u4E00\u5F20", "text=\u4FEE\u6539"];
30694
+ var COVER_FROM_CONTENT = ["a.js_selectCoverFromContent", "text=\u4ECE\u6B63\u6587\u9009\u62E9"];
30695
+ var COVER_ITEM = [
30696
+ 'div[class*="cover" i] li',
30697
+ 'div[class*="imgList" i] li',
30698
+ 'div[class*="weui-desktop-img-picker" i] li'
30699
+ ];
30700
+ var COVER_CONFIRM = ['button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u8BA4")', 'button:has-text("\u5B8C\u6210")'];
30701
+ var VIDEO_LOCAL_UPLOAD = ["text=\u672C\u5730\u4E0A\u4F20", 'button:has-text("\u672C\u5730\u4E0A\u4F20")'];
30702
+ var VIDEO_FILE_INPUT = [
30703
+ 'div.weui-desktop-dialog input[type="file"][accept*="video"]',
30704
+ 'input[type="file"][accept*="video"]',
30705
+ 'input[type="file"][accept*=".mp4"]',
30706
+ 'div.weui-desktop-dialog input[type="file"]'
30707
+ ];
30708
+ var VIDEO_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
30709
+ var PODCAST_UPLOAD = ["text=\u4E0A\u4F20\u97F3\u9891", 'button:has-text("\u4E0A\u4F20\u97F3\u9891")'];
30710
+ var PODCAST_AUDIO_INPUT = [
30711
+ 'div.weui-desktop-dialog input[type="file"][accept*="audio"]',
30712
+ 'div.weui-desktop-dialog input[type="file"][accept*=".mp3"]',
30713
+ 'input[type="file"][accept*="audio"]',
30714
+ 'input[type="file"][accept*=".mp3"]'
30715
+ ];
30716
+ var PODCAST_COVER_INPUT = [
30717
+ 'div.weui-desktop-dialog input[type="file"][accept*="image/"]',
30718
+ 'div.weui-desktop-dialog input[type="file"][accept*=".png"]',
30719
+ 'div.weui-desktop-dialog input[type="file"][accept*=".jpg"]'
30720
+ ];
30721
+ var PODCAST_INSERT = ['div.weui-desktop-dialog button:has-text("\u63D2\u5165")', 'button:has-text("\u63D2\u5165")'];
30722
+ var SAVE_DRAFT = ['button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")', "text=\u4FDD\u5B58\u4E3A\u8349\u7A3F"];
30723
+ var PUBLISH_SEND = ["button.mass_send", ".mass_send", 'button:has-text("\u53D1\u8868")'];
30724
+ var PUBLISH_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
30725
+ var PUBLISH_SUCCESS = ["\u53D1\u8868\u6210\u529F", "\u5DF2\u53D1\u8868"];
30726
+ 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"];
30727
+ var DRAFT_STAMP = "\u66F4\u65B0\u4E8E";
30728
+ var CONFIRM_TIMEOUT_MS = 5000;
30729
+ var TOKEN_LINK_SCRIPT = `(() => {
30730
+ const link = [...document.querySelectorAll('a[href*="token="]')]
30731
+ .map((a) => a.href)
30732
+ .find((href) => /[?&]token=\\d+/.test(href));
30733
+ return link ? (new URL(link).searchParams.get('token') ?? '') : '';
30734
+ })()`;
30735
+ function fail(error51) {
30736
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30737
+ }
30738
+ function sleep(ms) {
30739
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
30740
+ }
30741
+ function titleOf(content) {
30742
+ return (content.title ?? "").trim();
30743
+ }
30744
+ function bodyOf(content) {
30745
+ return content.body ?? "";
30746
+ }
30747
+ function asPlaywrightPage(page) {
30748
+ return page;
30749
+ }
30750
+ function draftRowContainers(title) {
30751
+ const keyed = title.length > 0 ? `:has-text(${JSON.stringify(title)})` : "";
30752
+ return [
30753
+ `.weui-desktop-card${keyed}`,
30754
+ `.publish_card_container${keyed}`,
30755
+ `[class*="draft" i]${keyed}`,
30756
+ `[class*="appmsg" i]${keyed}`,
30757
+ `.weui-desktop-dialog${keyed}`
30758
+ ];
30759
+ }
30760
+ var draftBoxUrl = (token) => `https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=${encodeURIComponent(token)}`;
30761
+ async function openDraftBox(page, title, timeoutMs, pollMs) {
30762
+ try {
30763
+ const token = await resolveConsoleToken(page);
30764
+ if (token === undefined)
30765
+ return false;
30766
+ await page.goto(draftBoxUrl(token), { timeout: timeoutMs });
30767
+ const ready = await expectOutcome(async () => {
30768
+ for (const container of draftRowContainers(title)) {
30769
+ const selector = `${container} :text("${DRAFT_STAMP}")`;
30770
+ if (await page.locator(selector).count() > 0)
30771
+ return selector;
30772
+ }
30773
+ return null;
30774
+ }, {
30775
+ timeoutMs,
30776
+ label: `\u8349\u7A3F\u7BB1 cards on the console home (:text("${DRAFT_STAMP}") inside the row containers)`,
30777
+ pollMs
30778
+ });
30779
+ return ready.ok;
30780
+ } catch {
30781
+ return false;
30782
+ }
30783
+ }
30784
+ async function describeFrames(page, limit) {
30785
+ const frames = typeof page.frames === "function" ? page.frames() : [];
30786
+ const parts = [];
30787
+ for (const frame of frames.slice(0, limit)) {
30788
+ const stamped = await frame.locator(`:text("${DRAFT_STAMP}")`).count().catch(() => -1);
30789
+ parts.push(`${frame.url().slice(0, 60)}:${stamped}`);
30790
+ }
30791
+ return parts.join(" | ");
30792
+ }
30793
+ async function readDraftStamp(page, title) {
30794
+ return readScopedSignal(asPlaywrightPage(page), [DRAFT_STAMP], draftRowContainers(title));
30795
+ }
30796
+ async function readDraftBaseline(page, title) {
30797
+ return { stamp: await readDraftStamp(page, title) };
30798
+ }
30799
+ async function resolveConsoleToken(page) {
30800
+ const fromUrl = readConsoleToken(page.url());
30801
+ if (fromUrl !== undefined)
30802
+ return fromUrl;
30803
+ const fromLink = String(await page.evaluate(TOKEN_LINK_SCRIPT)).trim();
30804
+ return fromLink.length > 0 ? fromLink : undefined;
30805
+ }
30806
+ function creationItemSelectors(operation) {
30807
+ return [
30808
+ `${CREATION_MENU_ITEM}:has-text("${operation}")`,
30809
+ `${CREATION_MENU_ITEM} .new-creation__menu-title:has-text("${operation}")`
30810
+ ];
30811
+ }
30812
+ async function enterViaMenu(deps, operation) {
30813
+ const selectors = creationItemSelectors(operation);
30814
+ let item = await tryFindSelector(deps.entry, selectors, Math.min(deps.stepTimeoutMs, 5000));
30815
+ if (item === undefined) {
30816
+ const opener = await tryFindSelector(deps.entry, CREATION_MENU_OPEN, deps.stepTimeoutMs);
30817
+ if (opener === undefined)
30818
+ return;
30819
+ await clickSelector(deps.entry, opener, deps.stepTimeoutMs);
30820
+ item = await tryFindSelector(deps.entry, selectors, deps.stepTimeoutMs);
30821
+ if (item === undefined)
30822
+ return;
30823
+ }
30824
+ const before = deps.pages().length;
30825
+ await clickSelector(deps.entry, item, deps.stepTimeoutMs);
30826
+ return followNewPage(deps.pages, before, deps.stepTimeoutMs);
30827
+ }
30828
+ function assertEditorMode(editor, operation, path7) {
30829
+ const expected = CREATE_TYPES[operation];
30830
+ const createType = readCreateType(editor.url());
30831
+ if (createType !== undefined && createType !== expected) {
30832
+ throw new Error(`mode entry: the editor landed on createType=${createType} but ${operation} is createType=${expected} \u2014 refusing to type into another mode's editor (${editor.url()})`);
30833
+ }
30834
+ return { editor, path: path7, createType: createType ?? null };
30835
+ }
30836
+ async function enterMode(deps, operation) {
30837
+ const viaMenu = await enterViaMenu(deps, operation);
30838
+ if (viaMenu !== undefined)
30839
+ return assertEditorMode(viaMenu, operation, "menu");
30840
+ const token = await resolveConsoleToken(deps.entry);
30841
+ if (token === undefined) {
30842
+ throw new Error(`mode entry: the console offers no '${operation}' creation item and carries no session token to derive its route \u2014 is the console logged in? (${deps.entry.url()})`);
30843
+ }
30844
+ const editor = await deps.newPage();
30845
+ await editor.goto(editorUrl(operation, token), { timeout: deps.stepTimeoutMs });
30846
+ return assertEditorMode(editor, operation, "route");
30847
+ }
30848
+ async function attachImages(page, images, deps) {
30849
+ const selector = await findAttachedSelector(page, IMAGE_FILE_INPUT, "image file input", deps.stepTimeoutMs);
30850
+ await page.setInputFiles(selector, images);
30851
+ }
30852
+ async function typeTitle(page, deps) {
30853
+ const title = titleOf(deps.content);
30854
+ if (title.length === 0)
30855
+ return "absent";
30856
+ const selector = await findSelector(page, TITLE_BLOCK, "title block", deps.stepTimeoutMs);
30857
+ const block = page.locator(selector).first();
30858
+ await block.click({ timeout: deps.stepTimeoutMs });
30859
+ if (typeof block.type === "function")
30860
+ await block.type(title, { delay: 5 });
30861
+ else
30862
+ await block.fill(title);
30863
+ const mirror = await readTitleMirror(page);
30864
+ if (mirror.observed && mirror.value !== title) {
30865
+ throw new Error(`title: the title block holds '${title}' but its form mirror textarea#title reads '${mirror.value}' \u2014 the mirror is what a save submits, so the draft would be saved without the title (discovery \xA75.2 trap 1)`);
30866
+ }
30867
+ return mirror.observed ? "mirror-agreed" : "mirror-unread";
30868
+ }
30869
+ async function readTitleMirror(page) {
30870
+ const found = await trySelectors(page, TITLE_MIRROR, { visible: false }).catch(() => ({
30871
+ found: false,
30872
+ selector: null
30873
+ }));
30874
+ if (!found.found || !found.selector)
30875
+ return { observed: false, value: "" };
30876
+ const locator = page.locator(found.selector).first();
30877
+ if (typeof locator.inputValue !== "function")
30878
+ return { observed: false, value: "" };
30879
+ try {
30880
+ return { observed: true, value: await locator.inputValue() };
30881
+ } catch {
30882
+ return { observed: false, value: "" };
30883
+ }
30884
+ }
30885
+ async function insertBody(page, deps) {
30886
+ const body = bodyOf(deps.content);
30887
+ if (body.trim().length === 0)
30888
+ return;
30889
+ const selector = await findSelector(page, BODY_BLOCK, "article body", deps.stepTimeoutMs);
30890
+ await pasteHtml(asPlaywrightPage(page), selector, markdownToWechatHtml(body), { timeoutMs: deps.stepTimeoutMs });
30891
+ }
30892
+ async function applyLayout(page, deps) {
30893
+ try {
30894
+ const control = await findSelector(page, LAYOUT_CONTROL, "\u4E00\u952E\u6392\u7248 control", deps.stepTimeoutMs);
30895
+ await clickSelector(page, control, deps.stepTimeoutMs);
30896
+ const option = await findSelector(page, LAYOUT_TARGET, "\u5339\u914D\u76EE\u6807\u683C\u5F0F option", 1500).catch(() => null);
30897
+ if (option !== null)
30898
+ await clickSelector(page, option, 1500);
30899
+ } catch {}
30900
+ }
30901
+ async function fillOptional(page, selectors, value, what, deps) {
30902
+ if (value.length === 0)
30903
+ return null;
30904
+ const selector = await tryFindSelector(page, selectors, Math.min(deps.stepTimeoutMs, 5000));
30905
+ if (selector === undefined)
30906
+ return `optional field '${what}': none of [${selectors.join(" | ")}] visible`;
30907
+ try {
30908
+ await fillSelector(page, selector, value);
30909
+ return null;
30910
+ } catch (error51) {
30911
+ return `optional field '${what}': ${error51 instanceof Error ? error51.message : String(error51)}`;
30912
+ }
30913
+ }
30914
+ async function setCoverFromBody(page, deps) {
30915
+ const open = await tryFindSelector(page, COVER_PANEL_OPEN, Math.min(deps.stepTimeoutMs, 5000));
30916
+ if (open === undefined)
30917
+ return "cover: the panel offers neither \u4ECE\u6B63\u6587\u9009\u62E9 nor its openers \u2014 the platform default (\u9ED8\u8BA4\u9996\u56FE\u4E3A\u5C01\u9762) is all this run has";
30918
+ try {
30919
+ await clickSelector(page, open, deps.stepTimeoutMs);
30920
+ const pick2 = await tryFindSelector(page, COVER_FROM_CONTENT, Math.min(deps.stepTimeoutMs, 5000));
30921
+ if (pick2 === undefined)
30922
+ return "cover: \u4ECE\u6B63\u6587\u9009\u62E9 was not offered by the open cover panel";
30923
+ await clickSelector(page, pick2, deps.stepTimeoutMs);
30924
+ const item = await tryFindSelector(page, COVER_ITEM, Math.min(deps.stepTimeoutMs, 5000));
30925
+ if (item === undefined)
30926
+ return "cover: the panel listed no body image to choose (its list is not <img>-based, \xA75.2)";
30927
+ await clickSelector(page, item, deps.stepTimeoutMs);
30928
+ const confirm = await tryFindSelector(page, COVER_CONFIRM, Math.min(deps.stepTimeoutMs, 5000));
30929
+ if (confirm === undefined)
30930
+ return "cover: the choice panel offered no confirm control";
30931
+ await clickSelector(page, confirm, deps.stepTimeoutMs);
30932
+ return null;
30933
+ } catch (error51) {
30934
+ return `cover: ${error51 instanceof Error ? error51.message : String(error51)}`;
30935
+ }
30936
+ }
30937
+ async function enrichArticleDraft(page, content) {
30938
+ const enrichMeta = content.metadata?.wechatmp;
30939
+ if (enrichMeta === undefined || typeof enrichMeta !== "object" || Array.isArray(enrichMeta))
30940
+ return [];
30941
+ const warnings = await enrichArticle(page, enrichMeta);
30942
+ const audioTitle = enrichMeta.audioTitle;
30943
+ if (typeof audioTitle === "string" && audioTitle.trim() !== "") {
30944
+ const audioWarning = await insertAudio(page, audioTitle.trim());
30945
+ if (audioWarning !== "")
30946
+ warnings.push(audioWarning);
30947
+ }
30948
+ return warnings;
30949
+ }
30950
+ async function composeArticle(input, deps, entry) {
30951
+ const page = entry.editor;
30952
+ const warnings = [];
30953
+ await attachImages(page, input.images, deps);
30954
+ const titlePath = await typeTitle(page, deps);
30955
+ await insertBody(page, deps);
30956
+ await applyLayout(page, deps);
30957
+ for (const warning of [
30958
+ await fillOptional(page, DIGEST_INPUT, input.digest ?? "", "\u6458\u8981", deps),
30959
+ await fillOptional(page, AUTHOR_INPUT, authorOf(deps.content), "\u4F5C\u8005", deps)
30960
+ ]) {
30961
+ if (warning !== null)
30962
+ warnings.push(warning);
30963
+ }
30964
+ const cover = await setCoverFromBody(page, deps);
30965
+ if (cover !== null)
30966
+ warnings.push(cover);
30967
+ return { warnings, titlePath, enrichWarnings: await enrichArticleDraft(page, deps.content) };
30968
+ }
30969
+ async function composeSticker(input, deps, entry) {
30970
+ const page = entry.editor;
30971
+ await attachImages(page, input.images, deps);
30972
+ const titlePath = await typeTitle(page, deps);
30973
+ await insertBody(page, deps);
30974
+ return { warnings: [], titlePath, enrichWarnings: [] };
30975
+ }
30976
+ async function composeVideo(input, deps, entry) {
30977
+ const page = entry.editor;
30978
+ const upload = await findSelector(page, VIDEO_LOCAL_UPLOAD, "\u672C\u5730\u4E0A\u4F20 control", deps.stepTimeoutMs);
30979
+ await clickSelector(page, upload, deps.stepTimeoutMs);
30980
+ const fileInput = await findAttachedSelector(page, VIDEO_FILE_INPUT, "video file input", deps.stepTimeoutMs);
30981
+ await uploadFile(asPlaywrightPage(page), fileInput, input.video, { timeoutMs: deps.stepTimeoutMs });
30982
+ const confirm = await findSelector(page, VIDEO_CONFIRM, "video media confirm", deps.stepTimeoutMs);
30983
+ await clickSelector(page, confirm, deps.stepTimeoutMs);
30984
+ await awaitSaveBar(page, deps);
30985
+ const titlePath = await typeTitle(page, deps);
30986
+ await insertBody(page, deps);
30987
+ return { warnings: [], titlePath, enrichWarnings: [] };
30988
+ }
30989
+ async function composePodcast(input, deps, entry) {
30990
+ const page = entry.editor;
30991
+ const upload = await findSelector(page, PODCAST_UPLOAD, "\u4E0A\u4F20\u97F3\u9891 control", deps.stepTimeoutMs);
30992
+ await clickSelector(page, upload, deps.stepTimeoutMs);
30993
+ const audioInput = await findAttachedSelector(page, PODCAST_AUDIO_INPUT, "audio file input", deps.stepTimeoutMs);
30994
+ await uploadFile(asPlaywrightPage(page), audioInput, input.audio, { timeoutMs: deps.stepTimeoutMs });
30995
+ const coverInput = await findAttachedSelector(page, PODCAST_COVER_INPUT, "cover file input", deps.stepTimeoutMs);
30996
+ await uploadFile(asPlaywrightPage(page), coverInput, input.cover, { timeoutMs: deps.stepTimeoutMs });
30997
+ const insert = await findSelector(page, PODCAST_INSERT, "\u63D2\u5165 control", deps.stepTimeoutMs);
30998
+ await clickSelector(page, insert, deps.stepTimeoutMs);
30999
+ await awaitSaveBar(page, deps);
31000
+ const titlePath = await typeTitle(page, deps);
31001
+ await insertBody(page, deps);
31002
+ return { warnings: [], titlePath, enrichWarnings: [] };
31003
+ }
31004
+ async function awaitSaveBar(page, deps) {
31005
+ await findSelector(page, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control (it appears only after the media is attached)", deps.stepTimeoutMs);
31006
+ }
31007
+ async function compose(input, deps, entry) {
31008
+ switch (input.operation) {
31009
+ case "\u6587\u7AE0":
31010
+ return composeArticle(input, deps, entry);
31011
+ case "\u8D34\u56FE":
31012
+ return composeSticker(input, deps, entry);
31013
+ case "\u89C6\u9891":
31014
+ return composeVideo(input, deps, entry);
31015
+ case "\u64AD\u5BA2":
31016
+ return composePodcast(input, deps, entry);
31017
+ }
31018
+ }
31019
+ function authorOf(content) {
31020
+ const author = content.metadata?.author;
31021
+ return typeof author === "string" ? author : "";
31022
+ }
31023
+ function baseMetadata(input, deps, entry) {
31024
+ const cover = coverPath(input);
31025
+ return {
31026
+ operation: input.operation,
31027
+ driver: deps.driver,
31028
+ publishStatus: deps.publishStatus,
31029
+ profileDir: deps.profileDir,
31030
+ artifactPaths: artifactPaths(input),
31031
+ entryPath: entry.path,
31032
+ editorCreateType: entry.createType,
31033
+ editorUrl: entry.editor.url(),
31034
+ ...cover === undefined ? {} : { coverPath: cover }
31035
+ };
31036
+ }
31037
+ async function saveDraft(input, deps, entry, composed) {
31038
+ const title = titleOf(deps.content);
31039
+ let control = null;
31040
+ let signal = null;
31041
+ let attempts = 0;
31042
+ let stampAfter = deps.draftBaseline.stamp;
31043
+ while (signal === null && attempts < deps.submitAttempts) {
31044
+ attempts += 1;
31045
+ await sleep(deps.submitSettleMs);
31046
+ control = await findSelector(entry.editor, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control", deps.stepTimeoutMs);
31047
+ await clickSelector(entry.editor, control, deps.stepTimeoutMs);
31048
+ await deps.entry.goto(deps.entry.url(), { timeout: deps.stepTimeoutMs });
31049
+ await openDraftBox(deps.entry, title, deps.stepTimeoutMs, deps.pollMs);
31050
+ const outcome = await expectOutcome(async () => {
31051
+ const stamp = await readDraftStamp(deps.entry, title);
31052
+ stampAfter = stamp;
31053
+ return stamp !== null && stamp !== deps.draftBaseline.stamp ? stamp : null;
31054
+ }, {
31055
+ timeoutMs: deps.draftTimeoutMs,
31056
+ label: `draft confirmation '${control}'`,
31057
+ pollMs: deps.pollMs
31058
+ });
31059
+ if (outcome.ok)
31060
+ signal = outcome.evidence;
31061
+ }
31062
+ if (signal === null || control === null) {
31063
+ return fail(`draft confirmation: '${SAVE_DRAFT[0]}' was clicked ${attempts}\xD7 but no fresh \u8349\u7A3F\u7BB1 row for '${title}' (its ${DRAFT_STAMP} stamp) was observed within ${deps.draftTimeoutMs}ms (before=${deps.draftBaseline.stamp ?? "none"}, after=${stampAfter ?? "none"}, page=${deps.entry.url()}, frames=${await describeFrames(deps.entry, 5)})`);
31064
+ }
31065
+ return ResultSchema.parse({
31066
+ ok: true,
31067
+ target: TARGET,
31068
+ message: `${TARGET} ${input.operation} draft saved (unpublished) in profile ${deps.profileDir} via '${control}'`,
31069
+ metadata: {
31070
+ ...baseMetadata(input, deps, entry),
31071
+ draftControl: control,
31072
+ draftAttempts: attempts,
31073
+ draftSignal: `\u8349\u7A3F\u7BB1:${signal}`,
31074
+ draftStampBefore: deps.draftBaseline.stamp,
31075
+ draftStampAfter: signal,
31076
+ titlePath: composed.titlePath,
31077
+ ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
31078
+ ...composed.enrichWarnings.length > 0 ? { enrichWarnings: composed.enrichWarnings } : {}
31079
+ }
31080
+ });
31081
+ }
31082
+ async function publishOutcome(page) {
31083
+ const success2 = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
31084
+ if (success2 !== null)
31085
+ return `sent:${success2}`;
31086
+ const refusal = await readScopedSignal(asPlaywrightPage(page), PLATFORM_REFUSAL);
31087
+ if (refusal !== null)
31088
+ return `refused:${refusal}`;
31089
+ return null;
31090
+ }
31091
+ async function publishContent(input, deps, entry, composed) {
31092
+ await sleep(deps.submitSettleMs);
31093
+ const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
31094
+ const urlBefore = entry.editor.url();
31095
+ await clickSelector(entry.editor, control, deps.stepTimeoutMs);
31096
+ const confirm = await tryFindSelector(entry.editor, PUBLISH_CONFIRM, CONFIRM_TIMEOUT_MS);
31097
+ if (confirm !== undefined)
31098
+ await clickSelector(entry.editor, confirm, deps.stepTimeoutMs);
31099
+ const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
31100
+ timeoutMs: deps.stepTimeoutMs,
31101
+ label: `publish confirmation '${control}'`,
31102
+ pollMs: deps.pollMs
31103
+ });
31104
+ if (!outcome.ok) {
31105
+ return fail(`publish submission: state unknown \u2014 '${control}' was clicked from ${urlBefore} but neither the platform's success marker nor one of its recorded refusals was observed within ${deps.stepTimeoutMs}ms (now ${entry.editor.url()}). Check the ${TARGET} console before retrying: a retry could publish twice.`);
31106
+ }
31107
+ if (outcome.evidence.startsWith("refused:")) {
31108
+ return fail(`publish refused by ${TARGET}: ${outcome.evidence.slice("refused:".length)}`);
31109
+ }
31110
+ return ResultSchema.parse({
31111
+ ok: true,
31112
+ target: TARGET,
31113
+ message: `${TARGET} ${input.operation} published`,
31114
+ metadata: {
31115
+ ...baseMetadata(input, deps, entry),
31116
+ publishControl: control,
31117
+ publishSignal: outcome.evidence,
31118
+ titlePath: composed.titlePath,
31119
+ ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
31120
+ ...composed.enrichWarnings.length > 0 ? { enrichWarnings: composed.enrichWarnings } : {}
31121
+ }
31122
+ });
31123
+ }
31124
+ async function runMode(input, deps) {
31125
+ const entry = await enterMode(deps, input.operation);
31126
+ const composed = await compose(input, deps, entry);
31127
+ return deps.publishStatus === "public" ? publishContent(input, deps, entry, composed) : saveDraft(input, deps, entry, composed);
30043
31128
  }
30044
31129
  function markdownToWechatHtml(markdown) {
30045
31130
  const lines = markdown.replace(/\r\n/g, `
@@ -30111,21 +31196,24 @@ function markdownToWechatHtml(markdown) {
30111
31196
  `))}</code></pre>`);
30112
31197
  return out.join("");
30113
31198
  }
30114
- function resolveTitle(content) {
30115
- if (typeof content.title === "string" && content.title.trim() !== "")
30116
- return content.title.trim();
30117
- return fail("wechatmp-pub: content.title is required");
31199
+ function escapeHtml(text) {
31200
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
31201
+ }
31202
+
31203
+ // ../../plugins/publishings/wechatmp-pub/src/index.ts
31204
+ var CHANNEL = TARGET;
31205
+ var CONSOLE_READY_TIMEOUT_MS = 30000;
31206
+ var SUBMIT_SETTLE_MS = 1500;
31207
+ var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
31208
+ var SUBMIT_ATTEMPTS = 2;
31209
+ function fail2(error51) {
31210
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30118
31211
  }
30119
- function resolveDigest(content) {
30120
- const body = typeof content.body === "string" ? content.body : "";
30121
- if (body.trim() === "")
30122
- return;
30123
- const first = body.split(`
30124
- `).map((line) => line.trim()).find((line) => line !== "");
30125
- return first ? first.slice(0, 54) : undefined;
31212
+ function isConsoleReady(page) {
31213
+ return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
30126
31214
  }
30127
31215
  function isWechatLoggedIn(page) {
30128
- return page.url().includes(SEL.loggedInFragment);
31216
+ return page.url().includes(CONSOLE_HOME_FRAGMENT);
30129
31217
  }
30130
31218
  function storageStatePath(profileDir) {
30131
31219
  return `${profileDir}/storage-state.json`;
@@ -30139,61 +31227,10 @@ async function checkWechatSession(profileDir) {
30139
31227
  }
30140
31228
  }
30141
31229
  async function pollLogin(page) {
30142
- const deadline = Date.now() + CONSOLE_READY_TIMEOUT_MS;
30143
- while (!isWechatLoggedIn(page)) {
30144
- if (Date.now() > deadline) {
30145
- throw new Error(`Authentication was not confirmed within ${CONSOLE_READY_TIMEOUT_MS}ms \u2014 the saved session state has ` + "likely expired: re-run `bun src/profile-check.ts login` to sign in again.");
30146
- }
30147
- await new Promise((resolve2) => setTimeout(resolve2, 1000));
30148
- }
30149
- }
30150
- function wrapPlaywrightPage(page, context2) {
30151
- return {
30152
- goto: (url2) => page.goto(url2, { waitUntil: "domcontentloaded" }).then(() => {
30153
- return;
30154
- }),
30155
- url: () => page.url(),
30156
- fill: (selector, value) => page.locator(selector).first().fill(value),
30157
- click: (selector, timeoutMs) => page.locator(selector).first().click(timeoutMs ? { timeout: timeoutMs } : undefined),
30158
- setInnerHtml: (selector, html) => page.locator(selector).first().evaluate((node, value) => {
30159
- node.innerHTML = value;
30160
- node.dispatchEvent(new Event("input", { bubbles: true }));
30161
- }, html),
30162
- waitFor: (selector, timeoutMs) => page.locator(selector).first().waitFor({ state: "visible", timeout: timeoutMs }).then(() => {
30163
- return;
30164
- }),
30165
- waitForAny: async (selectors, timeoutMs) => {
30166
- const deadline = Date.now() + timeoutMs;
30167
- while (Date.now() < deadline) {
30168
- for (const selector of selectors) {
30169
- try {
30170
- await page.locator(selector).first().waitFor({ state: "visible", timeout: 250 });
30171
- return selector;
30172
- } catch {}
30173
- }
30174
- }
30175
- return "";
30176
- },
30177
- waitForUrlFragment: (fragment, timeoutMs) => page.waitForURL(new RegExp(fragment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), { timeout: timeoutMs }).then(() => {
30178
- return;
30179
- }),
30180
- setInputFiles: (selector, files) => page.locator(selector).first().setInputFiles(files),
30181
- waitForTimeout: (ms) => page.waitForTimeout(ms),
30182
- evalJs: (expression) => page.evaluate(expression),
30183
- press: (key) => page.keyboard.press(key),
30184
- clickAndAwaitNewPage: context2 ? async (selector) => {
30185
- const popup = context2.waitForEvent("page", { timeout: 15000 }).catch(() => {
30186
- return;
30187
- });
30188
- await page.locator(selector).first().click();
30189
- const opened = await popup;
30190
- if (!opened)
30191
- return wrapPlaywrightPage(page, context2);
30192
- await opened.waitForLoadState("domcontentloaded").catch(() => {});
30193
- return wrapPlaywrightPage(opened, context2);
30194
- } : undefined,
30195
- close: context2 ? () => context2.close() : undefined
30196
- };
31230
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), CONSOLE_READY_TIMEOUT_MS, 1000);
31231
+ if (ready)
31232
+ return;
31233
+ throw new Error(`Authentication was not confirmed within ${CONSOLE_READY_TIMEOUT_MS}ms \u2014 the saved session state has ` + "likely expired: re-run `bun src/profile-check.ts login` to sign in again.");
30197
31234
  }
30198
31235
  async function openWithState(profileDir, headless) {
30199
31236
  const context2 = await openPersistentBrowser({ profileDir, headless });
@@ -30211,6 +31248,18 @@ async function openWithState(profileDir, headless) {
30211
31248
  }
30212
31249
  await context2.addCookies(cookies);
30213
31250
  const page = context2.pages()[0] ?? await context2.newPage();
31251
+ const origins = state?.origins ?? [];
31252
+ for (const entry of origins) {
31253
+ const items = entry.localStorage ?? [];
31254
+ if (entry.origin === undefined || entry.origin.length === 0 || items.length === 0)
31255
+ continue;
31256
+ await page.goto(entry.origin).catch(() => {
31257
+ return;
31258
+ });
31259
+ await page.evaluate("(pairs) => { for (const pair of pairs) { window.localStorage.setItem(pair.name, pair.value); } }", items).catch(() => {
31260
+ return;
31261
+ });
31262
+ }
30214
31263
  await page.goto(CONSOLE_URL);
30215
31264
  return { page, context: context2 };
30216
31265
  } catch (error51) {
@@ -30218,274 +31267,99 @@ async function openWithState(profileDir, headless) {
30218
31267
  throw error51;
30219
31268
  }
30220
31269
  }
30221
- async function defaultOpenPage(profileDir) {
31270
+ async function defaultOpenConsole(profileDir) {
30222
31271
  const { page, context: context2 } = await openWithState(profileDir, true);
30223
- return wrapPlaywrightPage(page, context2);
31272
+ return wrapPlaywrightContext(context2, page);
30224
31273
  }
30225
31274
 
30226
31275
  class WechatmpBrowserTransport {
30227
31276
  kind = "browser";
31277
+ driver;
30228
31278
  openPage;
30229
31279
  checkSession;
31280
+ consoleReadyTimeoutMs;
31281
+ stepTimeoutMs;
31282
+ pollMs;
31283
+ submitSettleMs;
31284
+ draftTimeoutMs;
31285
+ submitAttempts;
30230
31286
  constructor(options = {}) {
30231
- this.openPage = options.openPage ?? defaultOpenPage;
31287
+ this.driver = options.driver ?? "script";
31288
+ this.openPage = options.openPage ?? defaultOpenConsole;
30232
31289
  this.checkSession = options.checkSession ?? ((profileDir) => checkWechatSession(profileDir));
31290
+ this.consoleReadyTimeoutMs = options.consoleReadyTimeoutMs ?? CONSOLE_READY_TIMEOUT_MS;
31291
+ this.stepTimeoutMs = options.stepTimeoutMs ?? STEP_TIMEOUT_MS;
31292
+ this.pollMs = options.pollMs ?? POLL_MS;
31293
+ this.submitSettleMs = options.submitSettleMs ?? SUBMIT_SETTLE_MS;
31294
+ this.draftTimeoutMs = options.draftTimeoutMs ?? DRAFT_SIGNAL_TIMEOUT_MS;
31295
+ this.submitAttempts = options.submitAttempts ?? SUBMIT_ATTEMPTS;
30233
31296
  }
30234
31297
  async publish(payload) {
30235
31298
  const { content, options } = payload;
30236
- const want = options?.operation === "live" ? "public" : content.metadata?.publishStatus ?? "draft";
30237
- if (want !== "draft" && want !== "public") {
30238
- return this.failed(`publishStatus "${want}" is not draft|public`);
30239
- }
30240
- const title = resolveTitle(content);
30241
- if (typeof content.body !== "string" || content.body.trim() === "") {
30242
- return this.failed("wechatmp-pub: content.body is required");
30243
- }
31299
+ if (this.driver !== "script") {
31300
+ return fail2(`unsupported driver '${this.driver}' for ${TARGET}: the console modes (${WECHATMP_OPERATIONS.join(", ")}) require the script driver (Playwright setInputFiles)`);
31301
+ }
31302
+ const requested = resolveWechatmpOperation(options, content);
31303
+ if (requested.error !== undefined)
31304
+ return fail2(requested.error);
31305
+ const validation = await validateModeInput(requested.operation, content);
31306
+ if (validation.error !== undefined)
31307
+ return fail2(validation.error);
30244
31308
  const profileDir = resolveBrowserProfileDir(TARGET, process.env.WECHATMP_PROFILE_DIR);
31309
+ const publishStatus = content.metadata?.publishStatus === "public" ? "public" : "draft";
30245
31310
  try {
30246
31311
  await this.checkSession(profileDir);
30247
31312
  } catch (error51) {
30248
31313
  const why = error51 instanceof BrowserProfileMissingError ? "no saved console profile \u2014 log in once via `bun src/profile-check.ts login`" : `console session preflight failed (${error51 instanceof Error ? error51.message : String(error51)})`;
30249
- return this.failed(`wechatmp-pub: ${why}`);
31314
+ return fail2(`${TARGET}-pub: ${why}`);
30250
31315
  }
30251
- const page = await this.openPage(profileDir);
30252
- let enrichWarnings = [];
31316
+ let opened;
30253
31317
  try {
30254
- await page.waitFor(SEL.menuItem, CONSOLE_READY_TIMEOUT_MS);
30255
- const editor = page.clickAndAwaitNewPage ? await page.clickAndAwaitNewPage(`${SEL.menuItem}:has-text("\u56FE\u6587"), ${SEL.menuItem}`) : page;
30256
- if (editor === page) {
30257
- await page.click(`${SEL.menuItem}:has-text("\u56FE\u6587"), ${SEL.menuItem}`);
30258
- }
30259
- await editor.waitFor(SEL.titleInput, CONSOLE_READY_TIMEOUT_MS);
30260
- await editor.fill(SEL.titleInput, title);
30261
- await this.tryFill(editor, SEL.authorInput, String(content.metadata?.author ?? ""));
30262
- await editor.waitFor(SEL.editor, CONSOLE_READY_TIMEOUT_MS);
30263
- await editor.setInnerHtml(SEL.editor, markdownToWechatHtml(content.body));
30264
- const digest = resolveDigest(content);
30265
- if (digest)
30266
- await this.tryFill(editor, SEL.summaryInput, digest);
30267
- const enrichMeta = content.metadata?.wechatmp;
30268
- if (enrichMeta && typeof enrichMeta === "object" && !Array.isArray(enrichMeta)) {
30269
- enrichWarnings = await this.enrichDraft(editor, enrichMeta);
30270
- const audioTitle = enrichMeta.audioTitle;
30271
- if (typeof audioTitle === "string" && audioTitle.trim() !== "") {
30272
- const audioWarn = await this.insertAudio(editor, audioTitle.trim());
30273
- if (audioWarn !== "")
30274
- enrichWarnings.push(audioWarn);
30275
- }
30276
- }
30277
- if (want === "draft") {
30278
- await editor.click(SEL.saveDraftBtn);
30279
- } else {
30280
- await editor.click(SEL.publishBtn);
30281
- await this.tryClick(editor, SEL.confirmBtn);
30282
- }
30283
- if (editor.waitForUrlFragment) {
30284
- await editor.waitForUrlFragment("appmsg_edit&action=edit", CONSOLE_READY_TIMEOUT_MS);
30285
- } else {
30286
- const seen = await editor.waitForAny([SEL.toast], TOAST_TIMEOUT_MS);
30287
- if (seen === "") {
30288
- return this.failed(`save was clicked but no confirmation toast appeared within ${TOAST_TIMEOUT_MS}ms \u2014 verify the console manually`);
30289
- }
30290
- }
30291
- const appmsgid = /appmsgid=(\d+)/.exec(editor.url())?.[1] ?? "";
30292
- return {
30293
- ok: true,
30294
- target: TARGET,
30295
- metadata: {
30296
- publishStatus: want,
30297
- note: want === "draft" ? `saved to the WeChat MP draft box (\u8349\u7A3F\u7BB1)${appmsgid ? ` as appmsgid ${appmsgid}` : ""}` : "published via the console \u53D1\u8868 flow",
30298
- ...enrichWarnings.length > 0 ? { enrichWarnings } : {}
30299
- }
30300
- };
31318
+ opened = await this.openPage(profileDir);
30301
31319
  } catch (error51) {
30302
- const why = error51 instanceof Error ? error51.message : String(error51);
30303
- const enrich = enrichWarnings.length > 0 ? ` (enrich warnings: ${enrichWarnings.join(" | ")})` : "";
30304
- return this.failed(`console flow failed: ${why}${enrich}`);
30305
- } finally {
30306
- await page.close?.();
30307
- }
30308
- }
30309
- async enrichDraft(editor, meta3) {
30310
- const DIALOG = ".weui-desktop-dialog:visible";
30311
- const CONFIRM = `${DIALOG} button:has-text("\u786E\u8BA4"), ${DIALOG} button:has-text("\u786E\u5B9A")`;
30312
- const warnings = [];
30313
- const str = (key) => typeof meta3[key] === "string" ? meta3[key] : "";
30314
- const on6 = (key) => meta3[key] === true;
30315
- const sweep = async () => {
30316
- await this.tryClick(editor, 'button:has-text("\u53D6\u6D88")', 2000);
30317
- await editor.press?.("Escape");
30318
- await editor.waitForTimeout?.(400);
30319
- };
30320
- const step = async (name, fn) => {
30321
- try {
30322
- await fn();
30323
- return;
30324
- } catch {}
30325
- await sweep();
30326
- await editor.waitForTimeout?.(1000);
30327
- try {
30328
- await fn();
30329
- } catch (error51) {
30330
- warnings.push(`${name}: ${(error51 instanceof Error ? error51.message : String(error51)).slice(0, 120)}`);
30331
- await sweep();
30332
- }
30333
- };
30334
- if (on6("original")) {
30335
- await step("\u539F\u521B\u58F0\u660E", async () => {
30336
- await editor.click(SEL.originalSwitch, 1e4);
30337
- await editor.click(`${DIALOG} :text("\u6587\u5B57\u539F\u521B")`, 8000);
30338
- const author = str("author");
30339
- if (author)
30340
- await this.tryFill(editor, `${DIALOG} input[placeholder*="\u4F5C\u8005"]`, author);
30341
- await editor.click(CONFIRM, 8000);
30342
- });
30343
- const committed = await editor.evalJs?.(`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`);
30344
- if (committed === false) {
30345
- warnings.push("\u539F\u521B\u58F0\u660E: \u672A\u751F\u6548 - \u4F5C\u8005\u987B\u4E3A\u8D5E\u8D4F\u8D26\u6237(\u641C\u7D22\u9009\u62E9)\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E");
30346
- await sweep();
30347
- }
30348
- }
30349
- const collection = str("collection");
30350
- if (collection) {
30351
- await step("\u5408\u96C6", async () => {
30352
- await editor.click(SEL.tagRow, 8000);
30353
- await editor.waitFor(SEL.tagInput, 8000);
30354
- await editor.fill(SEL.tagInput, collection);
30355
- await editor.waitForTimeout?.(1200);
30356
- await editor.click(`${DIALOG} :text("${collection}")`, 8000);
30357
- await editor.click(CONFIRM, 8000);
30358
- });
30359
- }
30360
- if (on6("aiGenerated")) {
30361
- await step("\u521B\u4F5C\u6765\u6E90", async () => {
30362
- await editor.click(SEL.claimSourceCheckbox, 8000);
30363
- const dialog = await editor.waitForAny([DIALOG], 3000);
30364
- if (dialog) {
30365
- await editor.click(`${DIALOG} :text("\u5185\u5BB9\u7531AI\u751F\u6210")`, 8000);
30366
- await editor.click(CONFIRM, 8000);
30367
- }
30368
- });
30369
- const checked = await editor.evalJs?.(`!!document.querySelector('#js_claim_source_area input:checked')`);
30370
- if (checked === false)
30371
- warnings.push("\u521B\u4F5C\u6765\u6E90: \u52FE\u9009\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E");
30372
- }
30373
- const coverPath = str("coverPath");
30374
- const putFile = editor.setInputFiles?.bind(editor);
30375
- if (coverPath && putFile) {
30376
- await step("\u5C01\u9762", async () => {
30377
- try {
30378
- await putFile('input[type="file"]', coverPath);
30379
- } catch {
30380
- await editor.click(SEL.coverBtn, 8000);
30381
- await editor.waitForTimeout?.(1500);
30382
- await putFile('input[type="file"]', coverPath);
30383
- }
30384
- const cropBtn = await editor.waitForAny([`${DIALOG} button:has-text("\u5B8C\u6210")`, `${DIALOG} button:has-text("\u786E\u5B9A")`], 5000);
30385
- if (cropBtn)
30386
- await editor.click(cropBtn, 8000);
30387
- });
31320
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
30388
31321
  }
30389
- await sweep();
30390
- return warnings;
30391
- }
30392
- async insertAudio(editor, title) {
30393
- const evalJs = editor.evalJs;
30394
- if (typeof evalJs !== "function")
30395
- return "";
30396
- const CARET = `(() => {
30397
- const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30398
- if (!pm || !pm.firstChild) return;
30399
- const r = document.createRange();
30400
- r.setStart(pm.firstChild, 0);
30401
- r.collapse(true);
30402
- const sel = window.getSelection();
30403
- sel?.removeAllRanges();
30404
- sel?.addRange(r);
30405
- })()`;
30406
- const DEDUPE = `(() => {
30407
- const want = ${JSON.stringify(title)}.replace(/\\s+/g, '');
30408
- const hit = [...document.querySelectorAll('label.frm_checkbox_label.audio_item')]
30409
- .find((l) => (l.textContent ?? '').replace(/\\s+/g, '').includes(want));
30410
- if (!hit) return 'missing';
30411
- const cb = (hit.closest('li') ?? hit.parentElement)?.querySelector('input[type=checkbox]');
30412
- if (!cb) return 'nobox';
30413
- cb.click();
30414
- return 'checked';
30415
- })()`;
30416
- const INSERT = `(() => {
30417
- const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u63D2\u5165' && b.offsetWidth > 0);
30418
- if (!btn) return false;
30419
- btn.click();
30420
- return true;
30421
- })()`;
30422
- const CANCEL = `(() => {
30423
- const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u53D6\u6D88' && b.offsetWidth > 0);
30424
- btn?.click();
30425
- })()`;
30426
- const VERIFY = `(() => {
30427
- const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30428
- if (!pm) return 'noeditor';
30429
- const node = pm.querySelector('mpvoice, [class*="audio"]');
30430
- if (!node) return 'absent';
30431
- const first = pm.firstElementChild;
30432
- return first && (first === node || first.contains(node)) ? 'top' : 'notop';
30433
- })()`;
30434
31322
  try {
30435
- const run = (expression) => evalJs.call(editor, expression);
30436
- await run(CARET);
30437
- await run("document.querySelector('li.js_insertaudio')?.click()");
30438
- await editor.waitFor(".audio_dialog", TOAST_TIMEOUT_MS);
30439
- let dedupe = String(await run(DEDUPE));
30440
- for (let i2 = 0;dedupe === "missing" && i2 < 2; i2++) {
30441
- await editor.waitForTimeout?.(1200);
30442
- dedupe = String(await run(DEDUPE));
30443
- }
30444
- if (dedupe !== "checked") {
30445
- await run(CANCEL);
30446
- return dedupe === "missing" ? "\u97F3\u9891: \u7D20\u6750\u5E93\u65E0\u540C\u540D\u97F3\u9891\uFF0C\u8BF7\u624B\u52A8\u4E0A\u4F20\u540E\u63D2\u5165" : "\u97F3\u9891: \u540C\u540D\u7D20\u6750\u884C\u672A\u627E\u5230\u52FE\u9009\u6846\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30447
- }
30448
- if (await run(INSERT) !== true) {
30449
- await run(CANCEL);
30450
- return "\u97F3\u9891: \u63D2\u5165\u6309\u94AE\u672A\u627E\u5230\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30451
- }
30452
- await editor.waitForTimeout?.(2000);
30453
- const state = String(await run(VERIFY));
30454
- if (state === "absent" || state === "noeditor")
30455
- return "\u97F3\u9891: \u63D2\u5165\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5";
30456
- if (state === "notop")
30457
- return "\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u672A\u5728\u6587\u9996\uFF0C\u8BF7\u624B\u52A8\u8C03\u6574\u4F4D\u7F6E";
30458
- return "";
30459
- } catch {
30460
- return "\u97F3\u9891: \u81EA\u52A8\u63D2\u5165\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
31323
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
31324
+ if (!ready) {
31325
+ 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})`);
31326
+ }
31327
+ const title = typeof content.title === "string" ? content.title.trim() : "";
31328
+ const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
31329
+ return await runMode(validation.input, {
31330
+ entry: opened.page,
31331
+ pages: opened.pages,
31332
+ newPage: opened.newPage,
31333
+ content,
31334
+ publishStatus,
31335
+ profileDir,
31336
+ driver: this.driver,
31337
+ stepTimeoutMs: this.stepTimeoutMs,
31338
+ pollMs: this.pollMs,
31339
+ submitSettleMs: this.submitSettleMs,
31340
+ draftTimeoutMs: this.draftTimeoutMs,
31341
+ submitAttempts: this.submitAttempts,
31342
+ draftBaseline
31343
+ });
31344
+ } catch (error51) {
31345
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
31346
+ } finally {
31347
+ await opened.close();
30461
31348
  }
30462
31349
  }
30463
- async tryFill(page, selector, value) {
30464
- if (value === "")
30465
- return;
30466
- try {
30467
- await page.fill(selector, value);
30468
- } catch {}
30469
- }
30470
- async tryClick(page, selector, timeoutMs) {
30471
- try {
30472
- await page.click(selector, timeoutMs);
30473
- } catch {}
30474
- }
30475
- failed(error51) {
30476
- return { ok: false, target: TARGET, error: error51, metadata: { publishStatus: "draft" } };
30477
- }
30478
31350
  }
30479
- function getPublishTransport() {
31351
+ function getPublishTransport(driver = "script") {
30480
31352
  if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === "fake")
30481
31353
  return new FakeFileCliTransport;
30482
- return new WechatmpBrowserTransport;
31354
+ return new WechatmpBrowserTransport({ driver });
30483
31355
  }
30484
31356
  async function processPublishIO(inPath, outPath, transport) {
30485
31357
  const fs = createNodeFileSystem();
30486
31358
  const raw = JSON.parse(await fs.readFile(inPath));
30487
31359
  const content = ContentSchema.parse(raw);
30488
- const result = await (transport ?? getPublishTransport()).publish({ content, options: { target: TARGET } });
31360
+ const provided = raw.options;
31361
+ const options = provided !== undefined && typeof provided === "object" && provided !== null && !Array.isArray(provided) ? { target: TARGET, ...provided } : { target: TARGET };
31362
+ const result = await (transport ?? getPublishTransport()).publish({ content, options });
30489
31363
  await fs.writeFile(outPath, JSON.stringify(ResultSchema.parse(result), null, 2));
30490
31364
  }
30491
31365
  async function main2(argv = process.argv.slice(2)) {
@@ -30514,11 +31388,16 @@ if (import.meta.main) {
30514
31388
  export {
30515
31389
  wrapPlaywrightPage,
30516
31390
  storageStatePath,
31391
+ runMode,
30517
31392
  processPublishIO,
30518
31393
  markdownToWechatHtml,
30519
31394
  main2 as main,
30520
31395
  isWechatLoggedIn,
31396
+ isConsoleReady,
30521
31397
  getPublishTransport,
30522
31398
  checkWechatSession,
30523
- WechatmpBrowserTransport
31399
+ WechatmpBrowserTransport,
31400
+ WECHATMP_OPERATIONS,
31401
+ CONSOLE_URL,
31402
+ CHANNEL
30524
31403
  };