@gobing-ai/knowledge-kit 0.0.31 → 0.0.33

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 (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +32320 -15021
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +10 -9
  5. package/plugins/generations/core-facts-gen/dist/index.js +13 -12
  6. package/plugins/generations/daily-article-gen/dist/index.js +14 -13
  7. package/plugins/generations/daily-article-gen/src/index.ts +1 -1
  8. package/plugins/generations/dailynews-gen/dist/index.js +10 -9
  9. package/plugins/generations/episode-plan-gen/dist/index.js +58 -15
  10. package/plugins/generations/episode-plan-gen/src/index.ts +76 -6
  11. package/plugins/generations/gemini-tts/README.md +11 -0
  12. package/plugins/generations/gemini-tts/dist/index.js +10 -9
  13. package/plugins/generations/image-gen/dist/index.js +154 -151
  14. package/plugins/generations/image-gen/src/compose/chromium.ts +60 -42
  15. package/plugins/generations/news-report-gen/dist/index.js +13 -12
  16. package/plugins/generations/voice-gen/dist/index.js +10 -9
  17. package/plugins/ingestions/aihot-ingest/dist/index.js +13 -12
  18. package/plugins/ingestions/horizon-ingest/dist/index.js +13 -12
  19. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  20. package/plugins/ingestions/last30days-ingest/dist/index.js +13 -12
  21. package/plugins/ingestions/web-search/dist/index.js +11 -10
  22. package/plugins/kk/README.md +38 -0
  23. package/plugins/kk/decisions/extract/ai-news.yaml +21 -0
  24. package/plugins/kk/decisions/kk-items.yaml +113 -0
  25. package/plugins/kk/plugin.json +1 -1
  26. package/plugins/kk/schedules/cron.tmpl +15 -0
  27. package/plugins/kk/schedules/launchd.plist.tmpl +77 -0
  28. package/plugins/kk/scripts/daily-stages.ts +1533 -0
  29. package/plugins/kk/scripts/kk-workflow-stages.ts +268 -1228
  30. package/plugins/kk/skills/content-judge/SKILL.md +12 -2
  31. package/plugins/kk/skills/content-judge/references/rubrics.md +8 -2
  32. package/plugins/kk/skills/content-safety/SKILL.md +60 -18
  33. package/plugins/kk/skills/web-automation/SKILL.md +100 -0
  34. package/plugins/kk/skills/web-automation/references/browser-launch.md +86 -0
  35. package/plugins/kk/workflows/kk-collect.yaml +258 -0
  36. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +74 -11
  37. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +655 -0
  38. package/plugins/kk/workflows/kk-daily-safety.yaml +170 -0
  39. package/plugins/publishings/emdash-pub/dist/index.js +10 -9
  40. package/plugins/publishings/infoq-pub/dist/index.js +8695 -211
  41. package/plugins/publishings/infoq-pub/package.json +3 -1
  42. package/plugins/publishings/infoq-pub/plugin.json +2 -1
  43. package/plugins/publishings/infoq-pub/src/index.ts +358 -29
  44. package/plugins/publishings/infoq-pub/src/profile-check.ts +21 -0
  45. package/plugins/publishings/juejin-pub/dist/index.js +8746 -211
  46. package/plugins/publishings/juejin-pub/package.json +3 -1
  47. package/plugins/publishings/juejin-pub/plugin.json +2 -1
  48. package/plugins/publishings/juejin-pub/src/index.ts +345 -29
  49. package/plugins/publishings/juejin-pub/src/profile-check.ts +19 -0
  50. package/plugins/publishings/medium-pub/dist/index.js +10 -9
  51. package/plugins/publishings/podcast-pub/dist/index.js +10 -9
  52. package/plugins/publishings/qiita-pub/dist/index.js +10 -9
  53. package/plugins/publishings/substack-pub/dist/index.js +8687 -211
  54. package/plugins/publishings/substack-pub/package.json +3 -1
  55. package/plugins/publishings/substack-pub/plugin.json +2 -1
  56. package/plugins/publishings/substack-pub/src/index.ts +302 -29
  57. package/plugins/publishings/substack-pub/src/profile-check.ts +76 -0
  58. package/plugins/publishings/surfdash-pub/dist/index.js +10 -9
  59. package/plugins/publishings/wechatmp-pub/dist/index.js +1315 -352
  60. package/plugins/publishings/wechatmp-pub/plugin.json +1 -0
  61. package/plugins/publishings/wechatmp-pub/src/enrich.ts +229 -0
  62. package/plugins/publishings/wechatmp-pub/src/flow.ts +896 -0
  63. package/plugins/publishings/wechatmp-pub/src/index.ts +223 -520
  64. package/plugins/publishings/wechatmp-pub/src/modes.ts +403 -0
  65. package/plugins/publishings/wechatmp-pub/src/page.ts +260 -0
  66. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +84 -38
  67. package/plugins/publishings/x-pub/dist/index.js +8707 -223
  68. package/plugins/publishings/x-pub/package.json +3 -1
  69. package/plugins/publishings/x-pub/plugin.json +2 -1
  70. package/plugins/publishings/x-pub/src/index.ts +315 -23
  71. package/plugins/publishings/x-pub/src/profile-check.ts +19 -0
  72. package/plugins/publishings/xhs-pub/dist/index.js +897 -354
  73. package/plugins/publishings/xhs-pub/plugin.json +2 -1
  74. package/plugins/publishings/xhs-pub/src/flow.ts +572 -0
  75. package/plugins/publishings/xhs-pub/src/index.ts +108 -596
  76. package/plugins/publishings/xhs-pub/src/modes.ts +300 -0
  77. package/plugins/publishings/xhs-pub/src/page.ts +254 -0
  78. package/plugins/publishings/xhs-pub/src/profile-check.ts +29 -17
  79. package/plugins/publishings/zenn-pub/dist/index.js +10 -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.18/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.18/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.18/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.18/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.18/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.18/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.18/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.18/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/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.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29598
29598
  function getNodeFileSystem() {
29599
29599
  if (!_nodeFileSystem)
29600
29600
  _nodeFileSystem = createNodeFileSystem();
@@ -29740,15 +29740,16 @@ var PluginManifestSchema = exports_external.looseObject({
29740
29740
  name: exports_external.string().min(1),
29741
29741
  kind: PluginKindSchema,
29742
29742
  entry: exports_external.string().min(1),
29743
+ check: exports_external.string().min(1).optional(),
29743
29744
  version: exports_external.string().optional(),
29744
29745
  description: exports_external.string().optional(),
29745
29746
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
29746
29747
  });
29747
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
29748
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
29748
29749
  init_file_system_node();
29749
29750
  init_config();
29750
29751
 
29751
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/path.js
29752
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/path.js
29752
29753
  function normalizeSeparators(path6) {
29753
29754
  return path6.replaceAll("\\", "/");
29754
29755
  }
@@ -29806,7 +29807,7 @@ function getProcessCwd() {
29806
29807
  return globalThis.process?.cwd?.() ?? "/";
29807
29808
  }
29808
29809
 
29809
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/index.js
29810
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
29810
29811
  init_runtime_node_bun();
29811
29812
 
29812
29813
  // ../../packages/kk-core/src/render-md.ts
@@ -29897,8 +29898,32 @@ function resolveBrowserProfileDir(channel, override) {
29897
29898
  }
29898
29899
  return join(homedir(), ".config", "kk", "browser-profiles", channel);
29899
29900
  }
29901
+ async function trySelectors(page, selectors, options = {}) {
29902
+ const timeout = options.timeout ?? 2000;
29903
+ const visible = options.visible ?? true;
29904
+ for (const selector of selectors) {
29905
+ try {
29906
+ const locator = page.locator(selector).first();
29907
+ const attached = await locator.count() > 0;
29908
+ if (!attached) {
29909
+ continue;
29910
+ }
29911
+ if (visible) {
29912
+ const isVisible = await locator.isVisible({ timeout }).catch(() => false);
29913
+ if (!isVisible) {
29914
+ continue;
29915
+ }
29916
+ }
29917
+ return { found: true, selector };
29918
+ } catch {}
29919
+ }
29920
+ return { found: false, selector: null };
29921
+ }
29922
+
29923
+ // ../../packages/publish-harness/src/browser.ts
29924
+ import { resolve as resolve2 } from "path";
29900
29925
 
29901
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29926
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29902
29927
  class BrowserAutomationError extends Error {
29903
29928
  constructor(message, options) {
29904
29929
  super(message, options);
@@ -29933,10 +29958,10 @@ class PlaywrightUnavailableError extends BrowserAutomationError {
29933
29958
  this.name = "PlaywrightUnavailableError";
29934
29959
  }
29935
29960
  }
29936
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29961
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29937
29962
  import { mkdir } from "fs/promises";
29938
29963
 
29939
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29964
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29940
29965
  function resolveProfileDir(profileDir) {
29941
29966
  if (profileDir.trim() === "") {
29942
29967
  throw new InvalidProfileDirError;
@@ -29948,7 +29973,7 @@ function resolveProfileDir(profileDir) {
29948
29973
  return resolved;
29949
29974
  }
29950
29975
 
29951
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29976
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29952
29977
  var PROFILE_DIR_MODE = 448;
29953
29978
  var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
29954
29979
  var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
@@ -29999,7 +30024,7 @@ function createPersistentLauncher(options = {}) {
29999
30024
  };
30000
30025
  }
30001
30026
 
30002
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30027
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30003
30028
  var defaultLauncher;
30004
30029
  function getDefaultLauncher() {
30005
30030
  if (defaultLauncher === undefined)
@@ -30012,34 +30037,1157 @@ async function openPersistentBrowser(options) {
30012
30037
  headless: options.headless
30013
30038
  });
30014
30039
  }
30040
+ // ../../packages/publish-harness/src/profile-dir.ts
30041
+ import { mkdir as mkdir2, stat } from "fs/promises";
30042
+ async function ensureProfileDir2(profileDir, headless) {
30043
+ const existing = await stat(profileDir).catch(() => null);
30044
+ if (existing) {
30045
+ if (!existing.isDirectory())
30046
+ throw new InvalidProfileDirError(`profileDir is not a directory: "${profileDir}".`);
30047
+ return;
30048
+ }
30049
+ if (headless)
30050
+ throw new BrowserProfileMissingError(profileDir);
30051
+ await mkdir2(profileDir, { recursive: true, mode: 448 });
30052
+ }
30053
+
30054
+ // ../../packages/publish-harness/src/browser.ts
30055
+ var DEFAULT_STEP_TIMEOUT_MS = 30000;
30056
+ var DEFAULT_POLL_MS = 500;
30057
+ var ATTACH_POLL_MS = 100;
30058
+ function createKkBrowserLauncher(loadPlaywright = () => import("playwright")) {
30059
+ return {
30060
+ async launch(request) {
30061
+ if (request.profileDir.trim() === "")
30062
+ throw new InvalidProfileDirError;
30063
+ const profileDir = resolve2(request.profileDir);
30064
+ await ensureProfileDir2(profileDir, request.headless);
30065
+ let playwright;
30066
+ try {
30067
+ playwright = await loadPlaywright();
30068
+ } catch (error51) {
30069
+ throw new PlaywrightUnavailableError('The "playwright" dependency is not installed. Run `bunx playwright install chromium`.', { cause: error51 });
30070
+ }
30071
+ const channel = process.env.KK_BROWSER_CHANNEL?.trim() || "chromium";
30072
+ let context2;
30073
+ try {
30074
+ context2 = await playwright.chromium.launchPersistentContext(profileDir, {
30075
+ headless: request.headless,
30076
+ channel,
30077
+ args: ["--disable-blink-features=AutomationControlled"],
30078
+ ignoreDefaultArgs: ["--enable-automation"]
30079
+ });
30080
+ } catch (error51) {
30081
+ const message = errorMessage2(error51);
30082
+ if (/singleton|already in use|in use by another/i.test(message)) {
30083
+ throw new BrowserProfileBusyError(profileDir, error51);
30084
+ }
30085
+ if (/playwright install|executable doesn't exist/i.test(message)) {
30086
+ throw new PlaywrightUnavailableError(`Browser channel '${channel}' is not installed. Run \`bunx playwright install chromium\` (or install Google Chrome for KK_BROWSER_CHANNEL=chrome).`, { cause: error51 });
30087
+ }
30088
+ throw error51;
30089
+ }
30090
+ await context2.addInitScript(() => {
30091
+ Object.defineProperty(navigator, "webdriver", { get: () => {
30092
+ return;
30093
+ } });
30094
+ });
30095
+ return context2;
30096
+ }
30097
+ };
30098
+ }
30099
+ var kkBrowserLauncher = createKkBrowserLauncher();
30100
+ async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
30101
+ const deadline = Date.now() + timeoutMs;
30102
+ while (Date.now() < deadline) {
30103
+ if (await predicate()) {
30104
+ return true;
30105
+ }
30106
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
30107
+ }
30108
+ return predicate();
30109
+ }
30110
+ async function pasteHtml(page, selector, html, opts = {}) {
30111
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30112
+ await requireAttached(page, selector, "pasteHtml", timeoutMs);
30113
+ await page.evaluate(pasteScript(selector, html));
30114
+ const filled = await waitUntil(async () => String(await page.evaluate(editorTextScript(selector))).length > 0, timeoutMs, ATTACH_POLL_MS);
30115
+ if (!filled) {
30116
+ throw new Error(`pasteHtml: ${selector}: editor text is empty ${timeoutMs}ms after the paste`);
30117
+ }
30118
+ }
30119
+ async function uploadFile(page, selector, filePath, opts = {}) {
30120
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30121
+ await requireAttached(page, selector, "uploadFile", timeoutMs);
30122
+ await page.setInputFiles(selector, filePath);
30123
+ }
30124
+ async function expectOutcome(probe, opts) {
30125
+ let evidence = "";
30126
+ let lastProbeError = null;
30127
+ const observed = await waitUntil(async () => {
30128
+ let result;
30129
+ try {
30130
+ result = await probe();
30131
+ } catch (error51) {
30132
+ lastProbeError = error51 instanceof Error ? error51.message : String(error51);
30133
+ return false;
30134
+ }
30135
+ if (result === null || result.length === 0) {
30136
+ return false;
30137
+ }
30138
+ evidence = result;
30139
+ return true;
30140
+ }, opts.timeoutMs, opts.pollMs ?? DEFAULT_POLL_MS);
30141
+ if (observed && evidence.length > 0) {
30142
+ return { ok: true, evidence };
30143
+ }
30144
+ const detail = lastProbeError === null ? "" : ` (last probe error: ${lastProbeError})`;
30145
+ return { ok: false, error: `${opts.label}: no outcome observed within ${opts.timeoutMs}ms${detail}` };
30146
+ }
30147
+ var DEFAULT_SIGNAL_CONTAINERS = [
30148
+ '[class*="toast" i]',
30149
+ '[class*="notification" i]',
30150
+ '[role="alert"]',
30151
+ '[class*="message" i]'
30152
+ ];
30153
+ async function readScopedSignal(page, texts, containers = DEFAULT_SIGNAL_CONTAINERS) {
30154
+ for (const text of texts) {
30155
+ for (const container of containers) {
30156
+ const locator = page.locator(`${container} :text("${text}")`).first();
30157
+ if (await locator.count().catch(() => 0) === 0)
30158
+ continue;
30159
+ if (!await locator.isVisible().catch(() => false))
30160
+ continue;
30161
+ const observed = (await locator.textContent().catch(() => null))?.trim();
30162
+ return observed !== undefined && observed.length > 0 ? observed : text;
30163
+ }
30164
+ }
30165
+ return null;
30166
+ }
30167
+ async function requireAttached(page, selector, action, timeoutMs) {
30168
+ await resolveSelector(page, [selector], { timeoutMs, label: action });
30169
+ }
30170
+ async function resolveSelector(page, selectors, opts = {}) {
30171
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30172
+ const found = { selector: null };
30173
+ const attached = await waitUntil(async () => {
30174
+ const result = await trySelectors(page, selectors, { visible: false });
30175
+ found.selector = result.selector;
30176
+ return result.found && result.selector !== null;
30177
+ }, timeoutMs, ATTACH_POLL_MS);
30178
+ const selector = found.selector;
30179
+ if (!attached || selector === null) {
30180
+ throw new Error(`${opts.label ?? "selector"}: ${selectors.join(" | ")} not attached within ${timeoutMs}ms`);
30181
+ }
30182
+ return selector;
30183
+ }
30184
+ function errorMessage2(error51) {
30185
+ return error51 instanceof Error ? error51.message : String(error51);
30186
+ }
30187
+ function pasteScript(selector, html) {
30188
+ const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
30189
+ return `(() => {
30190
+ const el = document.querySelector(${JSON.stringify(selector)});
30191
+ if (!el) return false;
30192
+ const data = new DataTransfer();
30193
+ data.setData('text/html', ${JSON.stringify(html)});
30194
+ data.setData('text/plain', ${JSON.stringify(text)});
30195
+ el.focus();
30196
+ el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
30197
+ return true;
30198
+ })()`;
30199
+ }
30200
+ function editorTextScript(selector) {
30201
+ return `(() => {
30202
+ const el = document.querySelector(${JSON.stringify(selector)});
30203
+ return el ? (el.textContent ?? '').trim() : '';
30204
+ })()`;
30205
+ }
30206
+
30015
30207
  // ../../plugins/publishings/wechatmp-pub/src/index.ts
30016
30208
  init_dist();
30209
+
30210
+ // ../../plugins/publishings/wechatmp-pub/src/page.ts
30211
+ var POLL_MS = 500;
30212
+ var STEP_TIMEOUT_MS = 30000;
30213
+ async function tryFindAttachedSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
30214
+ let found;
30215
+ await waitUntil(async () => {
30216
+ for (const selector of selectors) {
30217
+ const attached = await page.locator(selector).count().catch(() => 0);
30218
+ if (attached > 0) {
30219
+ found = selector;
30220
+ return true;
30221
+ }
30222
+ }
30223
+ return false;
30224
+ }, timeoutMs);
30225
+ return found;
30226
+ }
30227
+ async function tryFindSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
30228
+ let found;
30229
+ await waitUntil(async () => {
30230
+ const result = await trySelectors(page, selectors).catch(() => ({ found: false, selector: null }));
30231
+ if (result.found && result.selector) {
30232
+ found = result.selector;
30233
+ return true;
30234
+ }
30235
+ return false;
30236
+ }, timeoutMs);
30237
+ return found;
30238
+ }
30239
+ async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30240
+ const found = await tryFindSelector(page, selectors, timeoutMs);
30241
+ if (found === undefined) {
30242
+ throw new Error(`${what}: none of [${selectors.join(" | ")}] visible within ${timeoutMs}ms`);
30243
+ }
30244
+ return found;
30245
+ }
30246
+ async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30247
+ const found = await tryFindAttachedSelector(page, selectors, timeoutMs);
30248
+ if (found === undefined) {
30249
+ throw new Error(`${what}: none of [${selectors.join(" | ")}] attached within ${timeoutMs}ms`);
30250
+ }
30251
+ return found;
30252
+ }
30253
+ async function followNewPage(pages, before, timeoutMs) {
30254
+ const opened = await waitUntil(async () => pages().length > before, timeoutMs);
30255
+ if (!opened)
30256
+ return;
30257
+ const all = pages();
30258
+ return all[all.length - 1];
30259
+ }
30260
+ async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
30261
+ await page.locator(selector).first().click({ timeout: timeoutMs });
30262
+ }
30263
+ async function fillSelector(page, selector, value) {
30264
+ await page.locator(selector).first().fill(value);
30265
+ }
30266
+ function wrapPlaywrightPage(raw) {
30267
+ const pw = raw;
30268
+ return {
30269
+ goto: (url2, options) => pw.goto(url2, { waitUntil: "domcontentloaded", ...options }).then(() => {
30270
+ return;
30271
+ }),
30272
+ locator: (selector) => ({
30273
+ count: () => pw.locator(selector).count(),
30274
+ first: () => {
30275
+ const loc = pw.locator(selector).first();
30276
+ return {
30277
+ count: () => loc.count(),
30278
+ isVisible: (options) => loc.isVisible(options),
30279
+ fill: (text, options) => loc.fill(text, options),
30280
+ click: (options) => loc.click(options),
30281
+ type: (text, options) => loc.pressSequentially(text, options),
30282
+ waitFor: (options) => loc.waitFor(options),
30283
+ textContent: async () => await loc.textContent() ?? "",
30284
+ inputValue: () => loc.inputValue()
30285
+ };
30286
+ }
30287
+ }),
30288
+ setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
30289
+ url: () => pw.url(),
30290
+ evaluate: (expression) => pw.evaluate(expression),
30291
+ waitForTimeout: (ms) => pw.waitForTimeout(ms),
30292
+ press: (key) => pw.keyboard.press(key),
30293
+ frames: () => {
30294
+ const source = raw;
30295
+ return (source.frames?.() ?? []).map((frame) => ({
30296
+ url: () => frame.url(),
30297
+ locator: (selector) => ({ count: () => frame.locator(selector).count() })
30298
+ }));
30299
+ }
30300
+ };
30301
+ }
30302
+ function wrapPlaywrightContext(raw, entryPage) {
30303
+ const context2 = raw;
30304
+ const cache = new WeakMap;
30305
+ const wrap = (page) => {
30306
+ const key = page;
30307
+ const cached2 = cache.get(key);
30308
+ if (cached2 !== undefined)
30309
+ return cached2;
30310
+ const wrapped = wrapPlaywrightPage(page);
30311
+ cache.set(key, wrapped);
30312
+ return wrapped;
30313
+ };
30314
+ return {
30315
+ page: wrap(entryPage),
30316
+ pages: () => context2.pages().map(wrap),
30317
+ newPage: async () => wrap(await context2.newPage()),
30318
+ close: () => context2.close()
30319
+ };
30320
+ }
30321
+
30322
+ // ../../plugins/publishings/wechatmp-pub/src/enrich.ts
30323
+ var DIALOG = ".weui-desktop-dialog:visible";
30324
+ var CONFIRM = `${DIALOG} button:has-text("\u786E\u8BA4"), ${DIALOG} button:has-text("\u786E\u5B9A")`;
30325
+ var ORIGINAL_SWITCH = ".setting-group__switch.js_edit_ori";
30326
+ var TAG_ROW = ".allow_click_opr.js_article_tags_label";
30327
+ var TAG_INPUT = 'input[placeholder*="\u5408\u96C6"]';
30328
+ var CLAIM_SOURCE = "#js_claim_source_area .frm_checkbox_label";
30329
+ var AUDIO_MENU = "li.js_insertaudio";
30330
+ var AUDIO_DIALOG = ".audio_dialog";
30331
+ var AUDIO_DIALOG_TIMEOUT_MS = 15000;
30332
+ var AUDIO_ROW_RETRIES = 2;
30333
+ var AUDIO_ROW_SETTLE_MS = 1200;
30334
+ var COLLECTION_SETTLE_MS = 1200;
30335
+ var STEP_RETRY_SETTLE_MS = 1000;
30336
+ var SWEEP_SETTLE_MS = 400;
30337
+ async function enrichArticle(page, meta3) {
30338
+ const warnings = [];
30339
+ const str = (key) => typeof meta3[key] === "string" ? meta3[key] : "";
30340
+ const on6 = (key) => meta3[key] === true;
30341
+ const sweep = async () => {
30342
+ const cancel = await trySelectors(page, ['button:has-text("\u53D6\u6D88")']);
30343
+ if (cancel.found)
30344
+ await page.locator(cancel.selector ?? "").first().click({ timeout: 2000 }).catch(() => {
30345
+ return;
30346
+ });
30347
+ await page.press?.("Escape");
30348
+ await page.waitForTimeout?.(SWEEP_SETTLE_MS);
30349
+ };
30350
+ const step = async (name, fn) => {
30351
+ try {
30352
+ await fn();
30353
+ return;
30354
+ } catch {}
30355
+ await sweep();
30356
+ await page.waitForTimeout?.(STEP_RETRY_SETTLE_MS);
30357
+ try {
30358
+ await fn();
30359
+ } catch (error51) {
30360
+ warnings.push(`${name}: ${(error51 instanceof Error ? error51.message : String(error51)).slice(0, 120)}`);
30361
+ await sweep();
30362
+ }
30363
+ };
30364
+ if (on6("original")) {
30365
+ await step("\u539F\u521B\u58F0\u660E", async () => {
30366
+ await clickSelector(page, ORIGINAL_SWITCH, 1e4);
30367
+ await clickSelector(page, `${DIALOG} :text("\u6587\u5B57\u539F\u521B")`, 8000);
30368
+ const author = str("author");
30369
+ if (author !== "")
30370
+ await fillSelector(page, `${DIALOG} input[placeholder*="\u4F5C\u8005"]`, author);
30371
+ await clickSelector(page, CONFIRM, 8000);
30372
+ });
30373
+ const committed = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`);
30374
+ if (committed === false) {
30375
+ 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");
30376
+ await sweep();
30377
+ }
30378
+ }
30379
+ const collection = str("collection");
30380
+ if (collection !== "") {
30381
+ await step("\u5408\u96C6", async () => {
30382
+ await clickSelector(page, TAG_ROW, 8000);
30383
+ await waitForVisible(page, TAG_INPUT, 8000);
30384
+ await fillSelector(page, TAG_INPUT, collection);
30385
+ await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
30386
+ await clickSelector(page, `${DIALOG} :text("${collection}")`, 8000);
30387
+ await clickSelector(page, CONFIRM, 8000);
30388
+ });
30389
+ }
30390
+ if (on6("aiGenerated")) {
30391
+ await step("\u521B\u4F5C\u6765\u6E90", async () => {
30392
+ await clickSelector(page, CLAIM_SOURCE, 8000);
30393
+ const dialog = await waitForVisible(page, DIALOG, 3000, { required: false });
30394
+ if (dialog !== undefined) {
30395
+ await clickSelector(page, `${DIALOG} :text("\u5185\u5BB9\u7531AI\u751F\u6210")`, 8000);
30396
+ await clickSelector(page, CONFIRM, 8000);
30397
+ }
30398
+ });
30399
+ const checked = await page.evaluate(`!!document.querySelector('#js_claim_source_area input:checked')`);
30400
+ if (checked === false)
30401
+ warnings.push("\u521B\u4F5C\u6765\u6E90: \u52FE\u9009\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E");
30402
+ }
30403
+ await sweep();
30404
+ return warnings;
30405
+ }
30406
+ async function insertAudio(page, title) {
30407
+ const CARET = `(() => {
30408
+ const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30409
+ if (!pm || !pm.firstChild) return;
30410
+ const r = document.createRange();
30411
+ r.setStart(pm.firstChild, 0);
30412
+ r.collapse(true);
30413
+ const sel = window.getSelection();
30414
+ sel?.removeAllRanges();
30415
+ sel?.addRange(r);
30416
+ })()`;
30417
+ const DEDUPE = `(() => {
30418
+ const want = ${JSON.stringify(title)}.replace(/\\s+/g, '');
30419
+ const hit = [...document.querySelectorAll('label.frm_checkbox_label.audio_item')]
30420
+ .find((l) => (l.textContent ?? '').replace(/\\s+/g, '').includes(want));
30421
+ if (!hit) return 'missing';
30422
+ const cb = (hit.closest('li') ?? hit.parentElement)?.querySelector('input[type=checkbox]');
30423
+ if (!cb) return 'nobox';
30424
+ cb.click();
30425
+ return 'checked';
30426
+ })()`;
30427
+ const INSERT = `(() => {
30428
+ const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u63D2\u5165' && b.offsetWidth > 0);
30429
+ if (!btn) return false;
30430
+ btn.click();
30431
+ return true;
30432
+ })()`;
30433
+ const CANCEL = `(() => {
30434
+ const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '\u53D6\u6D88' && b.offsetWidth > 0);
30435
+ btn?.click();
30436
+ })()`;
30437
+ const VERIFY = `(() => {
30438
+ const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
30439
+ if (!pm) return 'noeditor';
30440
+ const node = pm.querySelector('mpvoice, [class*="audio"]');
30441
+ if (!node) return 'absent';
30442
+ const first = pm.firstElementChild;
30443
+ return first && (first === node || first.contains(node)) ? 'top' : 'notop';
30444
+ })()`;
30445
+ try {
30446
+ await page.evaluate(CARET);
30447
+ await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
30448
+ await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
30449
+ let dedupe = String(await page.evaluate(DEDUPE));
30450
+ for (let i2 = 0;dedupe === "missing" && i2 < AUDIO_ROW_RETRIES; i2 += 1) {
30451
+ await page.waitForTimeout?.(AUDIO_ROW_SETTLE_MS);
30452
+ dedupe = String(await page.evaluate(DEDUPE));
30453
+ }
30454
+ if (dedupe !== "checked") {
30455
+ await page.evaluate(CANCEL);
30456
+ 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";
30457
+ }
30458
+ if (await page.evaluate(INSERT) !== true) {
30459
+ await page.evaluate(CANCEL);
30460
+ return "\u97F3\u9891: \u63D2\u5165\u6309\u94AE\u672A\u627E\u5230\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30461
+ }
30462
+ await page.waitForTimeout?.(2000);
30463
+ const state = String(await page.evaluate(VERIFY));
30464
+ if (state === "absent" || state === "noeditor")
30465
+ return "\u97F3\u9891: \u63D2\u5165\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5";
30466
+ if (state === "notop")
30467
+ return "\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u672A\u5728\u6587\u9996\uFF0C\u8BF7\u624B\u52A8\u8C03\u6574\u4F4D\u7F6E";
30468
+ return "";
30469
+ } catch {
30470
+ return "\u97F3\u9891: \u81EA\u52A8\u63D2\u5165\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
30471
+ }
30472
+ }
30473
+ async function waitForVisible(page, selector, timeoutMs, opts = {}) {
30474
+ const visible = await waitUntil(async () => {
30475
+ const found = await trySelectors(page, [selector]).catch(() => ({ found: false, selector: null }));
30476
+ return found.found;
30477
+ }, timeoutMs, POLL_MS);
30478
+ if (visible)
30479
+ return selector;
30480
+ if (opts.required === false)
30481
+ return;
30482
+ throw new Error(`${selector}: not visible within ${timeoutMs}ms`);
30483
+ }
30484
+
30485
+ // ../../plugins/publishings/wechatmp-pub/src/modes.ts
30486
+ import { isAbsolute } from "path";
30017
30487
  var TARGET = "wechatmp";
30018
30488
  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"
30489
+ var CONSOLE_HOME_FRAGMENT = "/cgi-bin/home";
30490
+ var WECHATMP_OPERATIONS = ["\u6587\u7AE0", "\u8D34\u56FE", "\u89C6\u9891", "\u64AD\u5BA2"];
30491
+ var DEFAULT_WECHATMP_OPERATION = "\u6587\u7AE0";
30492
+ var CREATE_TYPES = {
30493
+ \u{6587}\u{7ae0}: 0,
30494
+ \u{8d34}\u{56fe}: 8,
30495
+ \u{89c6}\u{9891}: 5,
30496
+ \u{64ad}\u{5ba2}: 7
30035
30497
  };
30036
- var CONSOLE_READY_TIMEOUT_MS = 30000;
30037
- var TOAST_TIMEOUT_MS = 15000;
30038
- function fail(message) {
30039
- throw new Error(message);
30498
+ var EDITOR_ROUTE = "/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=77";
30499
+ var CONSOLE_TOKEN_PATTERN = /[?&]token=([0-9]+)/;
30500
+ function readConsoleToken(url2) {
30501
+ return CONSOLE_TOKEN_PATTERN.exec(url2)?.[1];
30502
+ }
30503
+ function editorUrl(operation, token, timestamp = Date.now()) {
30504
+ const base2 = `${CONSOLE_URL.replace(/\/$/, "")}${EDITOR_ROUTE}`;
30505
+ return `${base2}&createType=${CREATE_TYPES[operation]}&token=${encodeURIComponent(token)}&lang=zh_CN&timestamp=${timestamp}`;
30506
+ }
30507
+ function readCreateType(url2) {
30508
+ const found = /[?&]createType=([0-9]+)/.exec(url2)?.[1];
30509
+ return found === undefined ? undefined : Number(found);
30510
+ }
30511
+ function resolveWechatmpOperation(options, content) {
30512
+ const raw = options?.operation ?? content?.metadata?.operation;
30513
+ if (raw === undefined)
30514
+ return { operation: DEFAULT_WECHATMP_OPERATION };
30515
+ if (typeof raw !== "string" || !WECHATMP_OPERATIONS.includes(raw)) {
30516
+ return {
30517
+ error: `unsupported operation '${String(raw)}' for ${TARGET}: expected one of ${WECHATMP_OPERATIONS.join(", ")}`
30518
+ };
30519
+ }
30520
+ return { operation: raw };
30521
+ }
30522
+ var IMAGE_EXTENSIONS = /\.(png|jpe?g|gif|webp|svg)$/i;
30523
+ var IMAGE_LIST = "png|jpg|jpeg|gif|webp|svg";
30524
+ var VIDEO_EXTENSIONS = /\.(mp4|mov|flv|f4v|mkv|m4v|mpg|mpeg|ts)$/i;
30525
+ var VIDEO_LIST = "mp4|mov|flv|f4v|mkv|m4v|mpg|mpeg|ts";
30526
+ var AUDIO_EXTENSIONS = /\.(mp3|wav|aac|flac|m4a)$/i;
30527
+ var AUDIO_LIST = "mp3|wav|aac|flac|m4a";
30528
+ var DERIVED_DIGEST_MAX = 54;
30529
+ var MODE_LIMITS = {
30530
+ \u{6587}\u{7ae0}: { titleRequired: true, titleMax: 64, digestMax: 120, authorMax: 8 },
30531
+ \u{8d34}\u{56fe}: { authorMax: 8 },
30532
+ \u{89c6}\u{9891}: { titleMax: 20, authorMax: 8 },
30533
+ \u{64ad}\u{5ba2}: { titleMax: 20, authorMax: 8 }
30534
+ };
30535
+ function artifactPaths(input) {
30536
+ switch (input.operation) {
30537
+ case "\u6587\u7AE0":
30538
+ return input.images;
30539
+ case "\u8D34\u56FE":
30540
+ return input.images;
30541
+ case "\u89C6\u9891":
30542
+ return [input.video];
30543
+ case "\u64AD\u5BA2":
30544
+ return [input.audio, input.cover];
30545
+ }
30546
+ }
30547
+ function coverPath(input) {
30548
+ return input.operation === "\u6587\u7AE0" || input.operation === "\u8D34\u56FE" ? input.images[0] : undefined;
30549
+ }
30550
+ async function resolveMediaPath(content, spec) {
30551
+ const raw = content.metadata?.[spec.key];
30552
+ const path7 = typeof raw === "string" ? raw : typeof raw?.path === "string" ? raw.path : undefined;
30553
+ if (path7 === undefined || path7.length === 0) {
30554
+ return {
30555
+ error: `${spec.label}: Content.metadata.${spec.key} is required (absolute path to an existing ${spec.list} file)`
30556
+ };
30557
+ }
30558
+ if (!isAbsolute(path7)) {
30559
+ return { error: `${spec.label}: Content.metadata.${spec.key} must be absolute, got '${path7}'` };
30560
+ }
30561
+ if (!spec.extensions.test(path7)) {
30562
+ return {
30563
+ error: `${spec.label}: Content.metadata.${spec.key} has unsupported extension (expected ${spec.list}): '${path7}'`
30564
+ };
30565
+ }
30566
+ if (!await createNodeFileSystem().exists(path7)) {
30567
+ return { error: `${spec.label}: Content.metadata.${spec.key} does not exist: '${path7}'` };
30568
+ }
30569
+ return { path: path7 };
30040
30570
  }
30041
- function escapeHtml(text) {
30042
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
30571
+ async function resolveBodyImages(content) {
30572
+ const ordered = content.metadata?.images;
30573
+ if (ordered !== undefined && !Array.isArray(ordered)) {
30574
+ return { error: "images validation: metadata.images must be an array" };
30575
+ }
30576
+ const descriptors2 = Array.isArray(ordered) ? ordered : [];
30577
+ const cover = typeof content.metadata?.wechatmp === "object" && content.metadata.wechatmp !== null ? content.metadata.wechatmp.coverPath : undefined;
30578
+ const candidates = [];
30579
+ if (typeof cover === "string" && cover.length > 0)
30580
+ candidates.push({ path: cover, what: "metadata.wechatmp.coverPath" });
30581
+ for (const [index, image] of descriptors2.entries()) {
30582
+ const path7 = typeof image === "string" ? image : image?.path;
30583
+ if (typeof path7 !== "string" || path7.length === 0) {
30584
+ return { error: `images validation: images[${index}] carries no path` };
30585
+ }
30586
+ candidates.push({ path: path7, what: `metadata.images[${index}]` });
30587
+ }
30588
+ const paths = [];
30589
+ for (const { path: path7, what } of candidates) {
30590
+ if (!isAbsolute(path7)) {
30591
+ return { error: `images validation: ${what} must be absolute, got '${path7}'` };
30592
+ }
30593
+ if (!IMAGE_EXTENSIONS.test(path7)) {
30594
+ return {
30595
+ error: `images validation: ${what} has unsupported extension (expected ${IMAGE_LIST}): '${path7}'`
30596
+ };
30597
+ }
30598
+ if (!await createNodeFileSystem().exists(path7)) {
30599
+ return { error: `images validation: ${what} does not exist: '${path7}'` };
30600
+ }
30601
+ paths.push(path7);
30602
+ }
30603
+ return { paths };
30604
+ }
30605
+ function deriveDigest(content) {
30606
+ const explicit = content.metadata?.digest;
30607
+ if (typeof explicit === "string")
30608
+ return explicit;
30609
+ const body = content.body ?? "";
30610
+ const first = body.split(`
30611
+ `).map((line) => line.trim()).find((line) => line !== "");
30612
+ return first === undefined ? undefined : first.slice(0, DERIVED_DIGEST_MAX);
30613
+ }
30614
+ function validatePublishStatus(content) {
30615
+ const status = content.metadata?.publishStatus;
30616
+ if (status !== undefined && status !== "draft" && status !== "public") {
30617
+ return {
30618
+ error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
30619
+ };
30620
+ }
30621
+ return {};
30622
+ }
30623
+ function validateTitle(content, operation) {
30624
+ const limits = MODE_LIMITS[operation];
30625
+ const title = content.title ?? "";
30626
+ if (limits.titleRequired === true && title.trim().length === 0) {
30627
+ return {
30628
+ error: `title validation: Content.title is required for ${operation} (the editor's placeholder is \u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898)`
30629
+ };
30630
+ }
30631
+ if (limits.titleMax !== undefined && title.length > limits.titleMax) {
30632
+ return {
30633
+ error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${limits.titleMax}`
30634
+ };
30635
+ }
30636
+ return {};
30637
+ }
30638
+ function validateDigest(content, operation) {
30639
+ const max = MODE_LIMITS[operation].digestMax;
30640
+ if (max === undefined)
30641
+ return {};
30642
+ const digest = deriveDigest(content);
30643
+ if (digest !== undefined && digest.length > max) {
30644
+ return { error: `digest validation: the \u6458\u8981 is ${digest.length} chars but ${operation} accepts \u2264${max}` };
30645
+ }
30646
+ return digest === undefined ? {} : { digest };
30647
+ }
30648
+ function validateAuthor(content, operation) {
30649
+ const max = MODE_LIMITS[operation].authorMax;
30650
+ const author = content.metadata?.author;
30651
+ if (typeof author !== "string" || author.length === 0)
30652
+ return {};
30653
+ if (max !== undefined && author.length > max) {
30654
+ return {
30655
+ error: `author validation: Content.metadata.author is ${author.length} chars but ${operation} accepts \u2264${max}`
30656
+ };
30657
+ }
30658
+ return { author };
30659
+ }
30660
+ async function validateModeInput(operation, content) {
30661
+ const status = validatePublishStatus(content);
30662
+ if (status.error)
30663
+ return { error: status.error };
30664
+ const title = validateTitle(content, operation);
30665
+ if (title.error)
30666
+ return { error: title.error };
30667
+ const author = validateAuthor(content, operation);
30668
+ if (author.error)
30669
+ return { error: author.error };
30670
+ switch (operation) {
30671
+ case "\u6587\u7AE0": {
30672
+ if ((content.body ?? "").trim().length === 0) {
30673
+ return { error: "body validation: Content.body is required for \u6587\u7AE0" };
30674
+ }
30675
+ const digest = validateDigest(content, operation);
30676
+ if (digest.error)
30677
+ return { error: digest.error };
30678
+ const images = await resolveBodyImages(content);
30679
+ if (images.error)
30680
+ return { error: images.error };
30681
+ if ((images.paths ?? []).length === 0) {
30682
+ return {
30683
+ 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[]"
30684
+ };
30685
+ }
30686
+ return {
30687
+ input: {
30688
+ operation,
30689
+ images: images.paths ?? [],
30690
+ ...digest.digest === undefined ? {} : { digest: digest.digest }
30691
+ }
30692
+ };
30693
+ }
30694
+ case "\u8D34\u56FE": {
30695
+ const images = await resolveBodyImages(content);
30696
+ if (images.error)
30697
+ return { error: images.error };
30698
+ if ((images.paths ?? []).length === 0) {
30699
+ return {
30700
+ error: "image validation: \u8D34\u56FE requires at least one image (metadata.images[] or metadata.wechatmp.coverPath)"
30701
+ };
30702
+ }
30703
+ return { input: { operation, images: images.paths ?? [] } };
30704
+ }
30705
+ case "\u89C6\u9891": {
30706
+ const video = await resolveMediaPath(content, {
30707
+ key: "video",
30708
+ label: "video validation",
30709
+ extensions: VIDEO_EXTENSIONS,
30710
+ list: VIDEO_LIST
30711
+ });
30712
+ if (video.error)
30713
+ return { error: video.error };
30714
+ return { input: { operation, video: video.path ?? "" } };
30715
+ }
30716
+ case "\u64AD\u5BA2": {
30717
+ const audio = await resolveMediaPath(content, {
30718
+ key: "audio",
30719
+ label: "audio validation",
30720
+ extensions: AUDIO_EXTENSIONS,
30721
+ list: AUDIO_LIST
30722
+ });
30723
+ if (audio.error)
30724
+ return { error: audio.error };
30725
+ const cover = await resolveMediaPath(content, {
30726
+ key: "coverImage",
30727
+ label: "cover validation",
30728
+ extensions: IMAGE_EXTENSIONS,
30729
+ list: IMAGE_LIST
30730
+ });
30731
+ if (cover.error)
30732
+ return { error: cover.error };
30733
+ return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
30734
+ }
30735
+ }
30736
+ }
30737
+
30738
+ // ../../plugins/publishings/wechatmp-pub/src/flow.ts
30739
+ var CREATION_MENU_ITEM = "div.new-creation__menu-item";
30740
+ var CREATION_MENU_OPEN = ["text=\u65B0\u7684\u521B\u4F5C", 'div.new-creation__menu-title:has-text("\u65B0\u7684\u521B\u4F5C")'];
30741
+ var TITLE_BLOCK = [
30742
+ 'div.ProseMirror[data-placeholder^="\u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898"]',
30743
+ 'div.ProseMirror[data-placeholder*="\u6807\u9898"]'
30744
+ ];
30745
+ var TITLE_MIRROR = ["textarea#title", "textarea.js_title"];
30746
+ var BODY_BLOCK = [
30747
+ "div.ProseMirror.ProseMirror-focused:not([data-placeholder])",
30748
+ "div.ProseMirror:not([data-placeholder])",
30749
+ 'div.ProseMirror[contenteditable="true"]'
30750
+ ];
30751
+ var DIGEST_INPUT = ["textarea#js_description", "textarea.js_desc"];
30752
+ var AUTHOR_INPUT = ["input#author", "input.js_author"];
30753
+ var IMAGE_FILE_INPUT = ['input[type="file"][accept*="image/"]', 'input[type="file"][accept*=".png"]'];
30754
+ var LAYOUT_CONTROL = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
30755
+ var LAYOUT_TARGET = ['div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")', 'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'];
30756
+ var COVER_PANEL_OPEN = ["a.js_selectCoverFromContent", ".js_cover_btn_area", "text=\u6362\u4E00\u5F20", "text=\u4FEE\u6539"];
30757
+ var COVER_FROM_CONTENT = ["a.js_selectCoverFromContent", "text=\u4ECE\u6B63\u6587\u9009\u62E9"];
30758
+ var COVER_ITEM = [
30759
+ 'div[class*="cover" i] li',
30760
+ 'div[class*="imgList" i] li',
30761
+ 'div[class*="weui-desktop-img-picker" i] li'
30762
+ ];
30763
+ var COVER_CONFIRM = ['button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u8BA4")', 'button:has-text("\u5B8C\u6210")'];
30764
+ var VIDEO_LOCAL_UPLOAD = ["text=\u672C\u5730\u4E0A\u4F20", 'button:has-text("\u672C\u5730\u4E0A\u4F20")'];
30765
+ var VIDEO_FILE_INPUT = [
30766
+ 'div.weui-desktop-dialog input[type="file"][accept*="video"]',
30767
+ 'input[type="file"][accept*="video"]',
30768
+ 'input[type="file"][accept*=".mp4"]',
30769
+ 'div.weui-desktop-dialog input[type="file"]'
30770
+ ];
30771
+ var VIDEO_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
30772
+ var PODCAST_UPLOAD = ["text=\u4E0A\u4F20\u97F3\u9891", 'button:has-text("\u4E0A\u4F20\u97F3\u9891")'];
30773
+ var PODCAST_AUDIO_INPUT = [
30774
+ 'div.weui-desktop-dialog input[type="file"][accept*="audio"]',
30775
+ 'div.weui-desktop-dialog input[type="file"][accept*=".mp3"]',
30776
+ 'input[type="file"][accept*="audio"]',
30777
+ 'input[type="file"][accept*=".mp3"]'
30778
+ ];
30779
+ var PODCAST_COVER_INPUT = [
30780
+ 'div.weui-desktop-dialog input[type="file"][accept*="image/"]',
30781
+ 'div.weui-desktop-dialog input[type="file"][accept*=".png"]',
30782
+ 'div.weui-desktop-dialog input[type="file"][accept*=".jpg"]'
30783
+ ];
30784
+ var PODCAST_INSERT = ['div.weui-desktop-dialog button:has-text("\u63D2\u5165")', 'button:has-text("\u63D2\u5165")'];
30785
+ var SAVE_DRAFT = ['button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")', "text=\u4FDD\u5B58\u4E3A\u8349\u7A3F"];
30786
+ var PUBLISH_SEND = ["button.mass_send", ".mass_send", 'button:has-text("\u53D1\u8868")'];
30787
+ var PUBLISH_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
30788
+ var PUBLISH_SUCCESS = ["\u53D1\u8868\u6210\u529F", "\u5DF2\u53D1\u8868"];
30789
+ 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"];
30790
+ var DRAFT_STAMP = "\u66F4\u65B0\u4E8E";
30791
+ var CONFIRM_TIMEOUT_MS = 5000;
30792
+ var TOKEN_LINK_SCRIPT = `(() => {
30793
+ const link = [...document.querySelectorAll('a[href*="token="]')]
30794
+ .map((a) => a.href)
30795
+ .find((href) => /[?&]token=\\d+/.test(href));
30796
+ return link ? (new URL(link).searchParams.get('token') ?? '') : '';
30797
+ })()`;
30798
+ function fail(error51) {
30799
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30800
+ }
30801
+ function sleep(ms) {
30802
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
30803
+ }
30804
+ function titleOf(content) {
30805
+ return (content.title ?? "").trim();
30806
+ }
30807
+ function bodyOf(content) {
30808
+ return content.body ?? "";
30809
+ }
30810
+ function asPlaywrightPage(page) {
30811
+ return page;
30812
+ }
30813
+ function draftRowContainers(title) {
30814
+ const keyed = title.length > 0 ? `:has-text(${JSON.stringify(title)})` : "";
30815
+ return [
30816
+ `.weui-desktop-card${keyed}`,
30817
+ `.publish_card_container${keyed}`,
30818
+ `[class*="draft" i]${keyed}`,
30819
+ `[class*="appmsg" i]${keyed}`,
30820
+ `.weui-desktop-dialog${keyed}`
30821
+ ];
30822
+ }
30823
+ var draftBoxUrl = (token) => `https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=${encodeURIComponent(token)}`;
30824
+ async function openDraftBox(page, title, timeoutMs, pollMs) {
30825
+ try {
30826
+ const token = await resolveConsoleToken(page);
30827
+ if (token === undefined)
30828
+ return false;
30829
+ await page.goto(draftBoxUrl(token), { timeout: timeoutMs });
30830
+ const ready = await expectOutcome(async () => {
30831
+ for (const container of draftRowContainers(title)) {
30832
+ const selector = `${container} :text("${DRAFT_STAMP}")`;
30833
+ if (await page.locator(selector).count() > 0)
30834
+ return selector;
30835
+ }
30836
+ return null;
30837
+ }, {
30838
+ timeoutMs,
30839
+ label: `\u8349\u7A3F\u7BB1 cards on the console home (:text("${DRAFT_STAMP}") inside the row containers)`,
30840
+ pollMs
30841
+ });
30842
+ return ready.ok;
30843
+ } catch {
30844
+ return false;
30845
+ }
30846
+ }
30847
+ async function describeFrames(page, limit) {
30848
+ const frames = typeof page.frames === "function" ? page.frames() : [];
30849
+ const parts = [];
30850
+ for (const frame of frames.slice(0, limit)) {
30851
+ const stamped = await frame.locator(`:text("${DRAFT_STAMP}")`).count().catch(() => -1);
30852
+ parts.push(`${frame.url().slice(0, 60)}:${stamped}`);
30853
+ }
30854
+ return parts.join(" | ");
30855
+ }
30856
+ async function readDraftStamp(page, title) {
30857
+ return readScopedSignal(asPlaywrightPage(page), [DRAFT_STAMP], draftRowContainers(title));
30858
+ }
30859
+ async function readDraftBaseline(page, title) {
30860
+ return { stamp: await readDraftStamp(page, title) };
30861
+ }
30862
+ async function resolveConsoleToken(page) {
30863
+ const fromUrl = readConsoleToken(page.url());
30864
+ if (fromUrl !== undefined)
30865
+ return fromUrl;
30866
+ const fromLink = String(await page.evaluate(TOKEN_LINK_SCRIPT)).trim();
30867
+ return fromLink.length > 0 ? fromLink : undefined;
30868
+ }
30869
+ function creationItemSelectors(operation) {
30870
+ return [
30871
+ `${CREATION_MENU_ITEM}:has-text("${operation}")`,
30872
+ `${CREATION_MENU_ITEM} .new-creation__menu-title:has-text("${operation}")`
30873
+ ];
30874
+ }
30875
+ async function enterViaMenu(deps, operation) {
30876
+ const selectors = creationItemSelectors(operation);
30877
+ let item = await tryFindSelector(deps.entry, selectors, Math.min(deps.stepTimeoutMs, 5000));
30878
+ if (item === undefined) {
30879
+ const opener = await tryFindSelector(deps.entry, CREATION_MENU_OPEN, deps.stepTimeoutMs);
30880
+ if (opener === undefined)
30881
+ return;
30882
+ await clickSelector(deps.entry, opener, deps.stepTimeoutMs);
30883
+ item = await tryFindSelector(deps.entry, selectors, deps.stepTimeoutMs);
30884
+ if (item === undefined)
30885
+ return;
30886
+ }
30887
+ const before = deps.pages().length;
30888
+ await clickSelector(deps.entry, item, deps.stepTimeoutMs);
30889
+ return followNewPage(deps.pages, before, deps.stepTimeoutMs);
30890
+ }
30891
+ function assertEditorMode(editor, operation, path7) {
30892
+ const expected = CREATE_TYPES[operation];
30893
+ const createType = readCreateType(editor.url());
30894
+ if (createType !== undefined && createType !== expected) {
30895
+ 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()})`);
30896
+ }
30897
+ return { editor, path: path7, createType: createType ?? null };
30898
+ }
30899
+ async function enterMode(deps, operation) {
30900
+ const viaMenu = await enterViaMenu(deps, operation);
30901
+ if (viaMenu !== undefined)
30902
+ return assertEditorMode(viaMenu, operation, "menu");
30903
+ const token = await resolveConsoleToken(deps.entry);
30904
+ if (token === undefined) {
30905
+ 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()})`);
30906
+ }
30907
+ const editor = await deps.newPage();
30908
+ await editor.goto(editorUrl(operation, token), { timeout: deps.stepTimeoutMs });
30909
+ return assertEditorMode(editor, operation, "route");
30910
+ }
30911
+ async function attachImages(page, images, deps) {
30912
+ const selector = await findAttachedSelector(page, IMAGE_FILE_INPUT, "image file input", deps.stepTimeoutMs);
30913
+ await page.setInputFiles(selector, images);
30914
+ }
30915
+ async function typeTitle(page, deps) {
30916
+ const title = titleOf(deps.content);
30917
+ if (title.length === 0)
30918
+ return "absent";
30919
+ const selector = await findSelector(page, TITLE_BLOCK, "title block", deps.stepTimeoutMs);
30920
+ const block = page.locator(selector).first();
30921
+ await block.click({ timeout: deps.stepTimeoutMs });
30922
+ if (typeof block.type === "function")
30923
+ await block.type(title, { delay: 5 });
30924
+ else
30925
+ await block.fill(title);
30926
+ const mirror = await readTitleMirror(page);
30927
+ if (mirror.observed && mirror.value !== title) {
30928
+ 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)`);
30929
+ }
30930
+ return mirror.observed ? "mirror-agreed" : "mirror-unread";
30931
+ }
30932
+ async function readTitleMirror(page) {
30933
+ const found = await trySelectors(page, TITLE_MIRROR, { visible: false }).catch(() => ({
30934
+ found: false,
30935
+ selector: null
30936
+ }));
30937
+ if (!found.found || !found.selector)
30938
+ return { observed: false, value: "" };
30939
+ const locator = page.locator(found.selector).first();
30940
+ if (typeof locator.inputValue !== "function")
30941
+ return { observed: false, value: "" };
30942
+ try {
30943
+ return { observed: true, value: await locator.inputValue() };
30944
+ } catch {
30945
+ return { observed: false, value: "" };
30946
+ }
30947
+ }
30948
+ async function insertBody(page, deps) {
30949
+ const body = bodyOf(deps.content);
30950
+ if (body.trim().length === 0)
30951
+ return;
30952
+ const selector = await findSelector(page, BODY_BLOCK, "article body", deps.stepTimeoutMs);
30953
+ await pasteHtml(asPlaywrightPage(page), selector, markdownToWechatHtml(body), { timeoutMs: deps.stepTimeoutMs });
30954
+ }
30955
+ async function applyLayout(page, deps) {
30956
+ try {
30957
+ const control = await findSelector(page, LAYOUT_CONTROL, "\u4E00\u952E\u6392\u7248 control", deps.stepTimeoutMs);
30958
+ await clickSelector(page, control, deps.stepTimeoutMs);
30959
+ const option = await findSelector(page, LAYOUT_TARGET, "\u5339\u914D\u76EE\u6807\u683C\u5F0F option", 1500).catch(() => null);
30960
+ if (option !== null)
30961
+ await clickSelector(page, option, 1500);
30962
+ } catch {}
30963
+ }
30964
+ async function fillOptional(page, selectors, value, what, deps) {
30965
+ if (value.length === 0)
30966
+ return null;
30967
+ const selector = await tryFindSelector(page, selectors, Math.min(deps.stepTimeoutMs, 5000));
30968
+ if (selector === undefined)
30969
+ return `optional field '${what}': none of [${selectors.join(" | ")}] visible`;
30970
+ try {
30971
+ await fillSelector(page, selector, value);
30972
+ return null;
30973
+ } catch (error51) {
30974
+ return `optional field '${what}': ${error51 instanceof Error ? error51.message : String(error51)}`;
30975
+ }
30976
+ }
30977
+ async function setCoverFromBody(page, deps) {
30978
+ const open = await tryFindSelector(page, COVER_PANEL_OPEN, Math.min(deps.stepTimeoutMs, 5000));
30979
+ if (open === undefined)
30980
+ 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";
30981
+ try {
30982
+ await clickSelector(page, open, deps.stepTimeoutMs);
30983
+ const pick2 = await tryFindSelector(page, COVER_FROM_CONTENT, Math.min(deps.stepTimeoutMs, 5000));
30984
+ if (pick2 === undefined)
30985
+ return "cover: \u4ECE\u6B63\u6587\u9009\u62E9 was not offered by the open cover panel";
30986
+ await clickSelector(page, pick2, deps.stepTimeoutMs);
30987
+ const item = await tryFindSelector(page, COVER_ITEM, Math.min(deps.stepTimeoutMs, 5000));
30988
+ if (item === undefined)
30989
+ return "cover: the panel listed no body image to choose (its list is not <img>-based, \xA75.2)";
30990
+ await clickSelector(page, item, deps.stepTimeoutMs);
30991
+ const confirm = await tryFindSelector(page, COVER_CONFIRM, Math.min(deps.stepTimeoutMs, 5000));
30992
+ if (confirm === undefined)
30993
+ return "cover: the choice panel offered no confirm control";
30994
+ await clickSelector(page, confirm, deps.stepTimeoutMs);
30995
+ return null;
30996
+ } catch (error51) {
30997
+ return `cover: ${error51 instanceof Error ? error51.message : String(error51)}`;
30998
+ }
30999
+ }
31000
+ async function enrichArticleDraft(page, content) {
31001
+ const enrichMeta = content.metadata?.wechatmp;
31002
+ if (enrichMeta === undefined || typeof enrichMeta !== "object" || Array.isArray(enrichMeta))
31003
+ return [];
31004
+ const warnings = await enrichArticle(page, enrichMeta);
31005
+ const audioTitle = enrichMeta.audioTitle;
31006
+ if (typeof audioTitle === "string" && audioTitle.trim() !== "") {
31007
+ const audioWarning = await insertAudio(page, audioTitle.trim());
31008
+ if (audioWarning !== "")
31009
+ warnings.push(audioWarning);
31010
+ }
31011
+ return warnings;
31012
+ }
31013
+ async function composeArticle(input, deps, entry) {
31014
+ const page = entry.editor;
31015
+ const warnings = [];
31016
+ await attachImages(page, input.images, deps);
31017
+ const titlePath = await typeTitle(page, deps);
31018
+ await insertBody(page, deps);
31019
+ await applyLayout(page, deps);
31020
+ for (const warning of [
31021
+ await fillOptional(page, DIGEST_INPUT, input.digest ?? "", "\u6458\u8981", deps),
31022
+ await fillOptional(page, AUTHOR_INPUT, authorOf(deps.content), "\u4F5C\u8005", deps)
31023
+ ]) {
31024
+ if (warning !== null)
31025
+ warnings.push(warning);
31026
+ }
31027
+ const cover = await setCoverFromBody(page, deps);
31028
+ if (cover !== null)
31029
+ warnings.push(cover);
31030
+ return { warnings, titlePath, enrichWarnings: await enrichArticleDraft(page, deps.content) };
31031
+ }
31032
+ async function composeSticker(input, deps, entry) {
31033
+ const page = entry.editor;
31034
+ await attachImages(page, input.images, deps);
31035
+ const titlePath = await typeTitle(page, deps);
31036
+ await insertBody(page, deps);
31037
+ return { warnings: [], titlePath, enrichWarnings: [] };
31038
+ }
31039
+ async function composeVideo(input, deps, entry) {
31040
+ const page = entry.editor;
31041
+ const upload = await findSelector(page, VIDEO_LOCAL_UPLOAD, "\u672C\u5730\u4E0A\u4F20 control", deps.stepTimeoutMs);
31042
+ await clickSelector(page, upload, deps.stepTimeoutMs);
31043
+ const fileInput = await findAttachedSelector(page, VIDEO_FILE_INPUT, "video file input", deps.stepTimeoutMs);
31044
+ await uploadFile(asPlaywrightPage(page), fileInput, input.video, { timeoutMs: deps.stepTimeoutMs });
31045
+ const confirm = await findSelector(page, VIDEO_CONFIRM, "video media confirm", deps.stepTimeoutMs);
31046
+ await clickSelector(page, confirm, deps.stepTimeoutMs);
31047
+ await awaitSaveBar(page, deps);
31048
+ const titlePath = await typeTitle(page, deps);
31049
+ await insertBody(page, deps);
31050
+ return { warnings: [], titlePath, enrichWarnings: [] };
31051
+ }
31052
+ async function composePodcast(input, deps, entry) {
31053
+ const page = entry.editor;
31054
+ const upload = await findSelector(page, PODCAST_UPLOAD, "\u4E0A\u4F20\u97F3\u9891 control", deps.stepTimeoutMs);
31055
+ await clickSelector(page, upload, deps.stepTimeoutMs);
31056
+ const audioInput = await findAttachedSelector(page, PODCAST_AUDIO_INPUT, "audio file input", deps.stepTimeoutMs);
31057
+ await uploadFile(asPlaywrightPage(page), audioInput, input.audio, { timeoutMs: deps.stepTimeoutMs });
31058
+ const coverInput = await findAttachedSelector(page, PODCAST_COVER_INPUT, "cover file input", deps.stepTimeoutMs);
31059
+ await uploadFile(asPlaywrightPage(page), coverInput, input.cover, { timeoutMs: deps.stepTimeoutMs });
31060
+ const insert = await findSelector(page, PODCAST_INSERT, "\u63D2\u5165 control", deps.stepTimeoutMs);
31061
+ await clickSelector(page, insert, deps.stepTimeoutMs);
31062
+ await awaitSaveBar(page, deps);
31063
+ const titlePath = await typeTitle(page, deps);
31064
+ await insertBody(page, deps);
31065
+ return { warnings: [], titlePath, enrichWarnings: [] };
31066
+ }
31067
+ async function awaitSaveBar(page, deps) {
31068
+ await findSelector(page, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control (it appears only after the media is attached)", deps.stepTimeoutMs);
31069
+ }
31070
+ async function compose(input, deps, entry) {
31071
+ switch (input.operation) {
31072
+ case "\u6587\u7AE0":
31073
+ return composeArticle(input, deps, entry);
31074
+ case "\u8D34\u56FE":
31075
+ return composeSticker(input, deps, entry);
31076
+ case "\u89C6\u9891":
31077
+ return composeVideo(input, deps, entry);
31078
+ case "\u64AD\u5BA2":
31079
+ return composePodcast(input, deps, entry);
31080
+ }
31081
+ }
31082
+ function authorOf(content) {
31083
+ const author = content.metadata?.author;
31084
+ return typeof author === "string" ? author : "";
31085
+ }
31086
+ function baseMetadata(input, deps, entry) {
31087
+ const cover = coverPath(input);
31088
+ return {
31089
+ operation: input.operation,
31090
+ driver: deps.driver,
31091
+ publishStatus: deps.publishStatus,
31092
+ profileDir: deps.profileDir,
31093
+ artifactPaths: artifactPaths(input),
31094
+ entryPath: entry.path,
31095
+ editorCreateType: entry.createType,
31096
+ editorUrl: entry.editor.url(),
31097
+ ...cover === undefined ? {} : { coverPath: cover }
31098
+ };
31099
+ }
31100
+ async function saveDraft(input, deps, entry, composed) {
31101
+ const title = titleOf(deps.content);
31102
+ let control = null;
31103
+ let signal = null;
31104
+ let attempts = 0;
31105
+ let stampAfter = deps.draftBaseline.stamp;
31106
+ while (signal === null && attempts < deps.submitAttempts) {
31107
+ attempts += 1;
31108
+ await sleep(deps.submitSettleMs);
31109
+ control = await findSelector(entry.editor, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control", deps.stepTimeoutMs);
31110
+ await clickSelector(entry.editor, control, deps.stepTimeoutMs);
31111
+ await deps.entry.goto(deps.entry.url(), { timeout: deps.stepTimeoutMs });
31112
+ await openDraftBox(deps.entry, title, deps.stepTimeoutMs, deps.pollMs);
31113
+ const outcome = await expectOutcome(async () => {
31114
+ const stamp = await readDraftStamp(deps.entry, title);
31115
+ stampAfter = stamp;
31116
+ return stamp !== null && stamp !== deps.draftBaseline.stamp ? stamp : null;
31117
+ }, {
31118
+ timeoutMs: deps.draftTimeoutMs,
31119
+ label: `draft confirmation '${control}'`,
31120
+ pollMs: deps.pollMs
31121
+ });
31122
+ if (outcome.ok)
31123
+ signal = outcome.evidence;
31124
+ }
31125
+ if (signal === null || control === null) {
31126
+ 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)})`);
31127
+ }
31128
+ return ResultSchema.parse({
31129
+ ok: true,
31130
+ target: TARGET,
31131
+ message: `${TARGET} ${input.operation} draft saved (unpublished) in profile ${deps.profileDir} via '${control}'`,
31132
+ metadata: {
31133
+ ...baseMetadata(input, deps, entry),
31134
+ draftControl: control,
31135
+ draftAttempts: attempts,
31136
+ draftSignal: `\u8349\u7A3F\u7BB1:${signal}`,
31137
+ draftStampBefore: deps.draftBaseline.stamp,
31138
+ draftStampAfter: signal,
31139
+ titlePath: composed.titlePath,
31140
+ ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
31141
+ ...composed.enrichWarnings.length > 0 ? { enrichWarnings: composed.enrichWarnings } : {}
31142
+ }
31143
+ });
31144
+ }
31145
+ async function publishOutcome(page) {
31146
+ const success2 = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
31147
+ if (success2 !== null)
31148
+ return `sent:${success2}`;
31149
+ const refusal = await readScopedSignal(asPlaywrightPage(page), PLATFORM_REFUSAL);
31150
+ if (refusal !== null)
31151
+ return `refused:${refusal}`;
31152
+ return null;
31153
+ }
31154
+ async function publishContent(input, deps, entry, composed) {
31155
+ await sleep(deps.submitSettleMs);
31156
+ const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
31157
+ const urlBefore = entry.editor.url();
31158
+ await clickSelector(entry.editor, control, deps.stepTimeoutMs);
31159
+ const confirm = await tryFindSelector(entry.editor, PUBLISH_CONFIRM, CONFIRM_TIMEOUT_MS);
31160
+ if (confirm !== undefined)
31161
+ await clickSelector(entry.editor, confirm, deps.stepTimeoutMs);
31162
+ const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
31163
+ timeoutMs: deps.stepTimeoutMs,
31164
+ label: `publish confirmation '${control}'`,
31165
+ pollMs: deps.pollMs
31166
+ });
31167
+ if (!outcome.ok) {
31168
+ 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.`);
31169
+ }
31170
+ if (outcome.evidence.startsWith("refused:")) {
31171
+ return fail(`publish refused by ${TARGET}: ${outcome.evidence.slice("refused:".length)}`);
31172
+ }
31173
+ return ResultSchema.parse({
31174
+ ok: true,
31175
+ target: TARGET,
31176
+ message: `${TARGET} ${input.operation} published`,
31177
+ metadata: {
31178
+ ...baseMetadata(input, deps, entry),
31179
+ publishControl: control,
31180
+ publishSignal: outcome.evidence,
31181
+ titlePath: composed.titlePath,
31182
+ ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
31183
+ ...composed.enrichWarnings.length > 0 ? { enrichWarnings: composed.enrichWarnings } : {}
31184
+ }
31185
+ });
31186
+ }
31187
+ async function runMode(input, deps) {
31188
+ const entry = await enterMode(deps, input.operation);
31189
+ const composed = await compose(input, deps, entry);
31190
+ return deps.publishStatus === "public" ? publishContent(input, deps, entry, composed) : saveDraft(input, deps, entry, composed);
30043
31191
  }
30044
31192
  function markdownToWechatHtml(markdown) {
30045
31193
  const lines = markdown.replace(/\r\n/g, `
@@ -30111,92 +31259,64 @@ function markdownToWechatHtml(markdown) {
30111
31259
  `))}</code></pre>`);
30112
31260
  return out.join("");
30113
31261
  }
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");
31262
+ function escapeHtml(text) {
31263
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
30118
31264
  }
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;
31265
+
31266
+ // ../../plugins/publishings/wechatmp-pub/src/index.ts
31267
+ var CHANNEL = TARGET;
31268
+ var CONSOLE_READY_TIMEOUT_MS = 30000;
31269
+ var SUBMIT_SETTLE_MS = 1500;
31270
+ var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
31271
+ var SUBMIT_ATTEMPTS = 2;
31272
+ function fail2(error51) {
31273
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
31274
+ }
31275
+ function isConsoleReady(page) {
31276
+ return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
30126
31277
  }
30127
31278
  function isWechatLoggedIn(page) {
30128
- return page.url().includes(SEL.loggedInFragment);
31279
+ return page.url().includes(CONSOLE_HOME_FRAGMENT);
30129
31280
  }
30130
31281
  function storageStatePath(profileDir) {
30131
31282
  return `${profileDir}/storage-state.json`;
30132
31283
  }
30133
- async function checkWechatSession(profileDir) {
30134
- const { page, context: context2 } = await openWithState(profileDir, true);
31284
+ async function checkWechatSession(profileDir, opts = {}) {
31285
+ const { page, context: context2 } = await openWithState(profileDir, opts.headless ?? true);
30135
31286
  try {
30136
- await pollLogin(page);
31287
+ await pollLogin(page, opts.timeoutMs);
30137
31288
  } finally {
30138
31289
  await context2.close();
30139
31290
  }
30140
31291
  }
30141
- 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
- }
31292
+ async function pollLogin(page, timeoutMs) {
31293
+ const deadline = timeoutMs ?? CONSOLE_READY_TIMEOUT_MS;
31294
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(page)), deadline, 1000);
31295
+ if (ready)
31296
+ return;
31297
+ throw new Error(`Authentication was not confirmed within ${deadline}ms \u2014 the saved session state has ` + "likely expired: re-run `bun src/profile-check.ts login` to sign in again.");
30149
31298
  }
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
- };
31299
+ async function openWechatLogin(opts) {
31300
+ const timeoutMs = opts.timeoutMs ?? 180000;
31301
+ const context2 = await openPersistentBrowser({
31302
+ profileDir: opts.profileDir,
31303
+ headless: false,
31304
+ launcher: kkBrowserLauncher
31305
+ });
31306
+ try {
31307
+ const page = context2.pages()[0] ?? await context2.newPage();
31308
+ await page.goto(CONSOLE_URL);
31309
+ const ready = await waitUntil(() => Promise.resolve(page.url().includes(CONSOLE_HOME_FRAGMENT)), timeoutMs, 1000);
31310
+ if (!ready)
31311
+ throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
31312
+ await page.waitForTimeout(1e4);
31313
+ await context2.storageState({ path: storageStatePath(opts.profileDir) });
31314
+ } finally {
31315
+ await context2.close();
31316
+ }
30197
31317
  }
30198
31318
  async function openWithState(profileDir, headless) {
30199
- const context2 = await openPersistentBrowser({ profileDir, headless });
31319
+ const context2 = await openPersistentBrowser({ profileDir, headless, launcher: kkBrowserLauncher });
30200
31320
  try {
30201
31321
  const stateFile = storageStatePath(profileDir);
30202
31322
  let state;
@@ -30211,6 +31331,18 @@ async function openWithState(profileDir, headless) {
30211
31331
  }
30212
31332
  await context2.addCookies(cookies);
30213
31333
  const page = context2.pages()[0] ?? await context2.newPage();
31334
+ const origins = state?.origins ?? [];
31335
+ for (const entry of origins) {
31336
+ const items = entry.localStorage ?? [];
31337
+ if (entry.origin === undefined || entry.origin.length === 0 || items.length === 0)
31338
+ continue;
31339
+ await page.goto(entry.origin).catch(() => {
31340
+ return;
31341
+ });
31342
+ await page.evaluate("(pairs) => { for (const pair of pairs) { window.localStorage.setItem(pair.name, pair.value); } }", items).catch(() => {
31343
+ return;
31344
+ });
31345
+ }
30214
31346
  await page.goto(CONSOLE_URL);
30215
31347
  return { page, context: context2 };
30216
31348
  } catch (error51) {
@@ -30218,274 +31350,99 @@ async function openWithState(profileDir, headless) {
30218
31350
  throw error51;
30219
31351
  }
30220
31352
  }
30221
- async function defaultOpenPage(profileDir) {
31353
+ async function defaultOpenConsole(profileDir) {
30222
31354
  const { page, context: context2 } = await openWithState(profileDir, true);
30223
- return wrapPlaywrightPage(page, context2);
31355
+ return wrapPlaywrightContext(context2, page);
30224
31356
  }
30225
31357
 
30226
31358
  class WechatmpBrowserTransport {
30227
31359
  kind = "browser";
31360
+ driver;
30228
31361
  openPage;
30229
31362
  checkSession;
31363
+ consoleReadyTimeoutMs;
31364
+ stepTimeoutMs;
31365
+ pollMs;
31366
+ submitSettleMs;
31367
+ draftTimeoutMs;
31368
+ submitAttempts;
30230
31369
  constructor(options = {}) {
30231
- this.openPage = options.openPage ?? defaultOpenPage;
31370
+ this.driver = options.driver ?? "script";
31371
+ this.openPage = options.openPage ?? defaultOpenConsole;
30232
31372
  this.checkSession = options.checkSession ?? ((profileDir) => checkWechatSession(profileDir));
31373
+ this.consoleReadyTimeoutMs = options.consoleReadyTimeoutMs ?? CONSOLE_READY_TIMEOUT_MS;
31374
+ this.stepTimeoutMs = options.stepTimeoutMs ?? STEP_TIMEOUT_MS;
31375
+ this.pollMs = options.pollMs ?? POLL_MS;
31376
+ this.submitSettleMs = options.submitSettleMs ?? SUBMIT_SETTLE_MS;
31377
+ this.draftTimeoutMs = options.draftTimeoutMs ?? DRAFT_SIGNAL_TIMEOUT_MS;
31378
+ this.submitAttempts = options.submitAttempts ?? SUBMIT_ATTEMPTS;
30233
31379
  }
30234
31380
  async publish(payload) {
30235
31381
  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
- }
31382
+ if (this.driver !== "script") {
31383
+ return fail2(`unsupported driver '${this.driver}' for ${TARGET}: the console modes (${WECHATMP_OPERATIONS.join(", ")}) require the script driver (Playwright setInputFiles)`);
31384
+ }
31385
+ const requested = resolveWechatmpOperation(options, content);
31386
+ if (requested.error !== undefined)
31387
+ return fail2(requested.error);
31388
+ const validation = await validateModeInput(requested.operation, content);
31389
+ if (validation.error !== undefined)
31390
+ return fail2(validation.error);
30244
31391
  const profileDir = resolveBrowserProfileDir(TARGET, process.env.WECHATMP_PROFILE_DIR);
31392
+ const publishStatus = content.metadata?.publishStatus === "public" ? "public" : "draft";
30245
31393
  try {
30246
31394
  await this.checkSession(profileDir);
30247
31395
  } catch (error51) {
30248
31396
  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}`);
31397
+ return fail2(`${TARGET}-pub: ${why}`);
30250
31398
  }
30251
- const page = await this.openPage(profileDir);
30252
- let enrichWarnings = [];
31399
+ let opened;
30253
31400
  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
- };
31401
+ opened = await this.openPage(profileDir);
30301
31402
  } 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
- });
31403
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
30388
31404
  }
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
31405
  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";
31406
+ const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
31407
+ if (!ready) {
31408
+ 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})`);
31409
+ }
31410
+ const title = typeof content.title === "string" ? content.title.trim() : "";
31411
+ const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
31412
+ return await runMode(validation.input, {
31413
+ entry: opened.page,
31414
+ pages: opened.pages,
31415
+ newPage: opened.newPage,
31416
+ content,
31417
+ publishStatus,
31418
+ profileDir,
31419
+ driver: this.driver,
31420
+ stepTimeoutMs: this.stepTimeoutMs,
31421
+ pollMs: this.pollMs,
31422
+ submitSettleMs: this.submitSettleMs,
31423
+ draftTimeoutMs: this.draftTimeoutMs,
31424
+ submitAttempts: this.submitAttempts,
31425
+ draftBaseline
31426
+ });
31427
+ } catch (error51) {
31428
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
31429
+ } finally {
31430
+ await opened.close();
30461
31431
  }
30462
31432
  }
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
31433
  }
30479
- function getPublishTransport() {
31434
+ function getPublishTransport(driver = "script") {
30480
31435
  if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === "fake")
30481
31436
  return new FakeFileCliTransport;
30482
- return new WechatmpBrowserTransport;
31437
+ return new WechatmpBrowserTransport({ driver });
30483
31438
  }
30484
31439
  async function processPublishIO(inPath, outPath, transport) {
30485
31440
  const fs = createNodeFileSystem();
30486
31441
  const raw = JSON.parse(await fs.readFile(inPath));
30487
31442
  const content = ContentSchema.parse(raw);
30488
- const result = await (transport ?? getPublishTransport()).publish({ content, options: { target: TARGET } });
31443
+ const provided = raw.options;
31444
+ const options = provided !== undefined && typeof provided === "object" && provided !== null && !Array.isArray(provided) ? { target: TARGET, ...provided } : { target: TARGET };
31445
+ const result = await (transport ?? getPublishTransport()).publish({ content, options });
30489
31446
  await fs.writeFile(outPath, JSON.stringify(ResultSchema.parse(result), null, 2));
30490
31447
  }
30491
31448
  async function main2(argv = process.argv.slice(2)) {
@@ -30514,11 +31471,17 @@ if (import.meta.main) {
30514
31471
  export {
30515
31472
  wrapPlaywrightPage,
30516
31473
  storageStatePath,
31474
+ runMode,
30517
31475
  processPublishIO,
31476
+ openWechatLogin,
30518
31477
  markdownToWechatHtml,
30519
31478
  main2 as main,
30520
31479
  isWechatLoggedIn,
31480
+ isConsoleReady,
30521
31481
  getPublishTransport,
30522
31482
  checkWechatSession,
30523
- WechatmpBrowserTransport
31483
+ WechatmpBrowserTransport,
31484
+ WECHATMP_OPERATIONS,
31485
+ CONSOLE_URL,
31486
+ CHANNEL
30524
31487
  };