@gobing-ai/knowledge-kit 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +10 -10
  3. package/package.json +4 -4
  4. package/plugins/generations/content-gen/dist/index.js +9 -9
  5. package/plugins/generations/core-facts-gen/dist/index.js +12 -12
  6. package/plugins/generations/daily-article-gen/dist/index.js +12 -12
  7. package/plugins/generations/dailynews-gen/dist/index.js +10 -10
  8. package/plugins/generations/dailynews-gen/src/script-builder.ts +4 -2
  9. package/plugins/generations/episode-plan-gen/dist/index.js +23 -13
  10. package/plugins/generations/episode-plan-gen/src/index.ts +23 -2
  11. package/plugins/generations/gemini-tts/dist/index.js +9 -9
  12. package/plugins/generations/image-gen/dist/index.js +9 -9
  13. package/plugins/generations/news-report-gen/dist/index.js +12 -12
  14. package/plugins/generations/voice-gen/dist/index.js +9 -9
  15. package/plugins/ingestions/aihot-ingest/dist/index.js +12 -12
  16. package/plugins/ingestions/horizon-ingest/dist/index.js +12 -12
  17. package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
  18. package/plugins/ingestions/last30days-ingest/dist/index.js +12 -12
  19. package/plugins/ingestions/web-search/dist/index.js +10 -10
  20. package/plugins/kk/plugin.json +1 -1
  21. package/plugins/kk/scripts/kk-workflow-stages.ts +173 -15
  22. package/plugins/kk/skills/content-judge/SKILL.md +16 -4
  23. package/plugins/kk/skills/content-judge/references/rubrics.md +8 -2
  24. package/plugins/kk/skills/content-judge/references/workflow-integration.md +2 -1
  25. package/plugins/kk/skills/content-safety/SKILL.md +24 -13
  26. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +65 -8
  27. package/plugins/publishings/emdash-pub/dist/index.js +9 -9
  28. package/plugins/publishings/infoq-pub/dist/index.js +8602 -211
  29. package/plugins/publishings/infoq-pub/package.json +3 -1
  30. package/plugins/publishings/infoq-pub/plugin.json +1 -1
  31. package/plugins/publishings/infoq-pub/src/index.ts +246 -29
  32. package/plugins/publishings/infoq-pub/src/profile-check.ts +19 -0
  33. package/plugins/publishings/juejin-pub/dist/index.js +8665 -211
  34. package/plugins/publishings/juejin-pub/package.json +3 -1
  35. package/plugins/publishings/juejin-pub/plugin.json +1 -1
  36. package/plugins/publishings/juejin-pub/src/index.ts +305 -29
  37. package/plugins/publishings/juejin-pub/src/profile-check.ts +19 -0
  38. package/plugins/publishings/medium-pub/dist/index.js +9 -9
  39. package/plugins/publishings/podcast-pub/dist/index.js +9 -9
  40. package/plugins/publishings/qiita-pub/dist/index.js +9 -9
  41. package/plugins/publishings/substack-pub/dist/index.js +8610 -211
  42. package/plugins/publishings/substack-pub/package.json +3 -1
  43. package/plugins/publishings/substack-pub/plugin.json +1 -1
  44. package/plugins/publishings/substack-pub/src/index.ts +271 -29
  45. package/plugins/publishings/substack-pub/src/profile-check.ts +74 -0
  46. package/plugins/publishings/surfdash-pub/dist/index.js +9 -9
  47. package/plugins/publishings/wechatmp-pub/dist/index.js +1227 -348
  48. package/plugins/publishings/wechatmp-pub/src/enrich.ts +229 -0
  49. package/plugins/publishings/wechatmp-pub/src/flow.ts +896 -0
  50. package/plugins/publishings/wechatmp-pub/src/index.ts +186 -516
  51. package/plugins/publishings/wechatmp-pub/src/modes.ts +403 -0
  52. package/plugins/publishings/wechatmp-pub/src/page.ts +260 -0
  53. package/plugins/publishings/wechatmp-pub/src/profile-check.ts +7 -5
  54. package/plugins/publishings/x-pub/dist/index.js +8626 -221
  55. package/plugins/publishings/x-pub/package.json +3 -1
  56. package/plugins/publishings/x-pub/plugin.json +1 -1
  57. package/plugins/publishings/x-pub/src/index.ts +258 -20
  58. package/plugins/publishings/x-pub/src/profile-check.ts +19 -0
  59. package/plugins/publishings/xhs-pub/dist/index.js +843 -346
  60. package/plugins/publishings/xhs-pub/plugin.json +1 -1
  61. package/plugins/publishings/xhs-pub/src/flow.ts +572 -0
  62. package/plugins/publishings/xhs-pub/src/index.ts +101 -593
  63. package/plugins/publishings/xhs-pub/src/modes.ts +300 -0
  64. package/plugins/publishings/xhs-pub/src/page.ts +267 -0
  65. package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -3
  66. package/plugins/publishings/zenn-pub/dist/index.js +9 -9
@@ -14671,7 +14671,7 @@ var init_zod = __esm(() => {
14671
14671
  init_external();
14672
14672
  });
14673
14673
 
14674
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/errors.js
14674
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/errors.js
14675
14675
  var ErrorCode;
14676
14676
  var init_errors3 = __esm(() => {
14677
14677
  ErrorCode = {
@@ -14682,7 +14682,7 @@ var init_errors3 = __esm(() => {
14682
14682
  };
14683
14683
  });
14684
14684
 
14685
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14685
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/api-response.js
14686
14686
  var API_ERROR_CODES, ERROR_CODE_TO_HTTP, CLIENT_SAFE_CODES;
14687
14687
  var init_api_response = __esm(() => {
14688
14688
  init_errors3();
@@ -14705,10 +14705,10 @@ var init_api_response = __esm(() => {
14705
14705
  CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
14706
14706
  });
14707
14707
 
14708
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14708
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/cursor.js
14709
14709
  var init_cursor = () => {};
14710
14710
 
14711
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/env.js
14711
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/env.js
14712
14712
  function getEnvVar(name, fallback) {
14713
14713
  const raw = process.env[name];
14714
14714
  return raw === undefined ? fallback : raw;
@@ -14717,10 +14717,10 @@ function getEnvVars() {
14717
14717
  return process.env;
14718
14718
  }
14719
14719
 
14720
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14720
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/event-severity.js
14721
14721
  var init_event_severity = () => {};
14722
14722
 
14723
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/object.js
14723
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/object.js
14724
14724
  function isPlainObject2(value) {
14725
14725
  return typeof value === "object" && value !== null && !Array.isArray(value);
14726
14726
  }
@@ -14738,7 +14738,7 @@ function deepMerge(target, source) {
14738
14738
  return result;
14739
14739
  }
14740
14740
 
14741
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/output.js
14741
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/output.js
14742
14742
  function processStream(name) {
14743
14743
  const proc = globalThis.process;
14744
14744
  const stream = proc?.[name];
@@ -14756,7 +14756,7 @@ function echoError(message, target = defaultStderrTarget ?? processStream("stder
14756
14756
  }
14757
14757
  var defaultStderrTarget;
14758
14758
 
14759
- // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.11/node_modules/@gobing-ai/ts-utils/dist/index.js
14759
+ // ../../node_modules/.bun/@gobing-ai+ts-utils@0.5.15/node_modules/@gobing-ai/ts-utils/dist/index.js
14760
14760
  var init_dist = __esm(() => {
14761
14761
  init_api_response();
14762
14762
  init_cursor();
@@ -21754,7 +21754,7 @@ var init_dist2 = __esm(() => {
21754
21754
  $visitAsync = visit.visitAsync;
21755
21755
  });
21756
21756
 
21757
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/config.js
21757
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/config.js
21758
21758
  function getProcessEnv() {
21759
21759
  return getEnvVars();
21760
21760
  }
@@ -21832,7 +21832,7 @@ var init_config = __esm(() => {
21832
21832
  };
21833
21833
  });
21834
21834
 
21835
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21835
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/file-system-node.js
21836
21836
  import { appendFileSync, cpSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "fs";
21837
21837
  import { dirname, resolve as resolvePath } from "path";
21838
21838
  function createNodeFileSystem(root, paths) {
@@ -21919,7 +21919,7 @@ function findProjectRoot(startDir) {
21919
21919
  }
21920
21920
  var init_file_system_node = () => {};
21921
21921
 
21922
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21922
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/db-errors.js
21923
21923
  var DbModuleNotInstalledError;
21924
21924
  var init_db_errors = __esm(() => {
21925
21925
  DbModuleNotInstalledError = class DbModuleNotInstalledError extends Error {
@@ -29130,7 +29130,7 @@ var init_execa = __esm(() => {
29130
29130
  } = getIpcExport());
29131
29131
  });
29132
29132
 
29133
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29133
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-group.js
29134
29134
  function resolveDeadline(optionTimeout, defaultTimeout) {
29135
29135
  if (optionTimeout === null)
29136
29136
  return null;
@@ -29222,7 +29222,7 @@ function ownProcessGroupLifecycle(ownership) {
29222
29222
  }
29223
29223
  var MAX_TIMEOUT_MS = 2147483647, DEFAULT_KILL_GRACE_MS = 5000, GROUP_POLL_INTERVAL_MS = 25, GROUP_KILL_PATIENCE_MS = 2000;
29224
29224
 
29225
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29225
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/process-executor.js
29226
29226
  import { isatty } from "tty";
29227
29227
  function processEventSeverity(reason, exitCode) {
29228
29228
  if (reason === "error")
@@ -29594,7 +29594,7 @@ var init_process_executor = __esm(() => {
29594
29594
  init_config();
29595
29595
  });
29596
29596
 
29597
- // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.11/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29597
+ // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.15/node_modules/@gobing-ai/ts-runtime/dist/runtime-node-bun.js
29598
29598
  function getNodeFileSystem() {
29599
29599
  if (!_nodeFileSystem)
29600
29600
  _nodeFileSystem = createNodeFileSystem();
@@ -29655,7 +29655,7 @@ var init_runtime_node_bun = __esm(() => {
29655
29655
  });
29656
29656
 
29657
29657
  // ../../plugins/publishings/xhs-pub/src/index.ts
29658
- import { dirname as dirname2, isAbsolute } from "path";
29658
+ import { dirname as dirname2 } from "path";
29659
29659
  import { parseArgs } from "util";
29660
29660
 
29661
29661
  // ../../packages/kk-core/src/kinds.ts
@@ -29745,11 +29745,11 @@ var PluginManifestSchema = exports_external.looseObject({
29745
29745
  description: exports_external.string().optional(),
29746
29746
  operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
29747
29747
  });
29748
- // ../../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.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29749
29749
  init_file_system_node();
29750
29750
  init_config();
29751
29751
 
29752
- // ../../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.15/node_modules/@gobing-ai/ts-runtime/dist/path.js
29753
29753
  function normalizeSeparators(path6) {
29754
29754
  return path6.replaceAll("\\", "/");
29755
29755
  }
@@ -29807,7 +29807,7 @@ function getProcessCwd() {
29807
29807
  return globalThis.process?.cwd?.() ?? "/";
29808
29808
  }
29809
29809
 
29810
- // ../../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.15/node_modules/@gobing-ai/ts-runtime/dist/index.js
29811
29811
  init_runtime_node_bun();
29812
29812
 
29813
29813
  // ../../packages/kk-core/src/render-md.ts
@@ -29919,7 +29919,8 @@ async function trySelectors(page, selectors, options = {}) {
29919
29919
  }
29920
29920
  return { found: false, selector: null };
29921
29921
  }
29922
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29922
+
29923
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
29923
29924
  class BrowserAutomationError extends Error {
29924
29925
  constructor(message, options) {
29925
29926
  super(message, options);
@@ -29968,11 +29969,10 @@ class OperationAbortedError extends BrowserAutomationError {
29968
29969
  this.name = "OperationAbortedError";
29969
29970
  }
29970
29971
  }
29971
-
29972
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29972
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29973
29973
  import { mkdir } from "fs/promises";
29974
29974
 
29975
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29975
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
29976
29976
  function resolveProfileDir(profileDir) {
29977
29977
  if (profileDir.trim() === "") {
29978
29978
  throw new InvalidProfileDirError;
@@ -29984,7 +29984,7 @@ function resolveProfileDir(profileDir) {
29984
29984
  return resolved;
29985
29985
  }
29986
29986
 
29987
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29987
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
29988
29988
  var PROFILE_DIR_MODE = 448;
29989
29989
  var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
29990
29990
  var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
@@ -30035,7 +30035,7 @@ function createPersistentLauncher(options = {}) {
30035
30035
  };
30036
30036
  }
30037
30037
 
30038
- // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.11+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30038
+ // ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.15+e28deaf1f2c5a2b1/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
30039
30039
  var DEFAULT_TIMEOUT_MS = 30000;
30040
30040
  var POLL_INTERVAL_MS = 250;
30041
30041
  var defaultLauncher;
@@ -30120,6 +30120,201 @@ async function pollPredicate(page, predicate, timeoutMs, signal) {
30120
30120
  function sleep(ms) {
30121
30121
  return new Promise((resolve2) => setTimeout(resolve2, ms));
30122
30122
  }
30123
+ // ../../packages/publish-harness/src/browser.ts
30124
+ var DEFAULT_CHECK_TIMEOUT_MS = 30000;
30125
+ var DEFAULT_STEP_TIMEOUT_MS = 30000;
30126
+ var DEFAULT_POLL_MS = 500;
30127
+ var ATTACH_POLL_MS = 100;
30128
+ function resolveChannelProfileDir(channel, override) {
30129
+ const tiers = [override, process.env[`${channel.toUpperCase()}_PROFILE_DIR`], process.env.KK_BROWSER_PROFILE_DIR];
30130
+ const chosen = tiers.find((tier) => tier !== undefined && tier.trim().length > 0);
30131
+ return resolveBrowserProfileDir(channel, chosen);
30132
+ }
30133
+ async function checkChannelSession(config3, opts) {
30134
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
30135
+ const passes = opts.headedFallback === false ? [true] : [true, false];
30136
+ let lastDetail = `${config3.channel} readiness was never probed`;
30137
+ for (const headless of passes) {
30138
+ try {
30139
+ await withProfilePage({
30140
+ profileDir: opts.profileDir,
30141
+ headless,
30142
+ url: config3.homeUrl,
30143
+ isAuthenticated: config3.isAuthenticated,
30144
+ timeoutMs,
30145
+ launcher: opts.launcher
30146
+ }, async () => true);
30147
+ return {
30148
+ status: "verified",
30149
+ profileDir: opts.profileDir,
30150
+ detail: `authenticated ${config3.channel} session observed ${headless ? "headlessly" : "via headed fallback"} via profile ${opts.profileDir} (read-only)`
30151
+ };
30152
+ } catch (error51) {
30153
+ if (error51 instanceof BrowserProfileMissingError) {
30154
+ return {
30155
+ status: "refused",
30156
+ profileDir: opts.profileDir,
30157
+ detail: `no ${config3.channel} browser profile at ${opts.profileDir} \u2014 run the login command first`
30158
+ };
30159
+ }
30160
+ lastDetail = `${headless ? "headless" : "headed"} ${config3.channel} readiness refused: ${errorMessage2(error51)}`;
30161
+ }
30162
+ }
30163
+ return { status: "refused", profileDir: opts.profileDir, detail: lastDetail };
30164
+ }
30165
+ async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
30166
+ const deadline = Date.now() + timeoutMs;
30167
+ while (Date.now() < deadline) {
30168
+ if (await predicate()) {
30169
+ return true;
30170
+ }
30171
+ await new Promise((resolve2) => setTimeout(resolve2, pollMs));
30172
+ }
30173
+ return predicate();
30174
+ }
30175
+ async function pasteHtml(page, selector, html, opts = {}) {
30176
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30177
+ await requireAttached(page, selector, "pasteHtml", timeoutMs);
30178
+ await page.evaluate(pasteScript(selector, html));
30179
+ const filled = await waitUntil(async () => String(await page.evaluate(editorTextScript(selector))).length > 0, timeoutMs, ATTACH_POLL_MS);
30180
+ if (!filled) {
30181
+ throw new Error(`pasteHtml: ${selector}: editor text is empty ${timeoutMs}ms after the paste`);
30182
+ }
30183
+ }
30184
+ async function uploadFile(page, selector, filePath, opts = {}) {
30185
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30186
+ await requireAttached(page, selector, "uploadFile", timeoutMs);
30187
+ await page.setInputFiles(selector, filePath);
30188
+ }
30189
+ async function expectOutcome(probe, opts) {
30190
+ let evidence = "";
30191
+ let lastProbeError = null;
30192
+ const observed = await waitUntil(async () => {
30193
+ let result;
30194
+ try {
30195
+ result = await probe();
30196
+ } catch (error51) {
30197
+ lastProbeError = error51 instanceof Error ? error51.message : String(error51);
30198
+ return false;
30199
+ }
30200
+ if (result === null || result.length === 0) {
30201
+ return false;
30202
+ }
30203
+ evidence = result;
30204
+ return true;
30205
+ }, opts.timeoutMs, opts.pollMs ?? DEFAULT_POLL_MS);
30206
+ if (observed && evidence.length > 0) {
30207
+ return { ok: true, evidence };
30208
+ }
30209
+ const detail = lastProbeError === null ? "" : ` (last probe error: ${lastProbeError})`;
30210
+ return { ok: false, error: `${opts.label}: no outcome observed within ${opts.timeoutMs}ms${detail}` };
30211
+ }
30212
+ var DEFAULT_SIGNAL_CONTAINERS = [
30213
+ '[class*="toast" i]',
30214
+ '[class*="notification" i]',
30215
+ '[role="alert"]',
30216
+ '[class*="message" i]'
30217
+ ];
30218
+ async function readScopedSignal(page, texts, containers = DEFAULT_SIGNAL_CONTAINERS) {
30219
+ for (const text of texts) {
30220
+ for (const container of containers) {
30221
+ const locator = page.locator(`${container} :text("${text}")`).first();
30222
+ if (await locator.count().catch(() => 0) === 0)
30223
+ continue;
30224
+ if (!await locator.isVisible().catch(() => false))
30225
+ continue;
30226
+ const observed = (await locator.textContent().catch(() => null))?.trim();
30227
+ return observed !== undefined && observed.length > 0 ? observed : text;
30228
+ }
30229
+ }
30230
+ return null;
30231
+ }
30232
+ var cdpNodeText = (node) => node.nodeName === "#text" ? node.nodeValue ?? "" : (node.children ?? []).map(cdpNodeText).join("");
30233
+ async function findShadowControl(session, labels) {
30234
+ const document = await session.send("DOM.getDocument", { depth: -1, pierce: true });
30235
+ if (!document.root)
30236
+ return null;
30237
+ const wanted = new Set(labels);
30238
+ const findNode = (node, insideShadow) => {
30239
+ if (insideShadow && node.nodeName === "BUTTON" && typeof node.backendNodeId === "number") {
30240
+ const label = cdpNodeText(node).replace(/\s+/g, "");
30241
+ if (wanted.has(label))
30242
+ return { label, backendNodeId: node.backendNodeId };
30243
+ }
30244
+ for (const shadowRoot of node.shadowRoots ?? []) {
30245
+ const hit2 = findNode(shadowRoot, true);
30246
+ if (hit2)
30247
+ return hit2;
30248
+ }
30249
+ for (const child of node.children ?? []) {
30250
+ const hit2 = findNode(child, insideShadow);
30251
+ if (hit2)
30252
+ return hit2;
30253
+ }
30254
+ return null;
30255
+ };
30256
+ const hit = findNode(document.root, false);
30257
+ if (hit === null)
30258
+ return null;
30259
+ const box = await session.send("DOM.getBoxModel", { backendNodeId: hit.backendNodeId });
30260
+ const quad = box.model?.content;
30261
+ if (quad === undefined || quad.length < 8)
30262
+ return null;
30263
+ const [x1 = 0, y1 = 0, x3 = 0, , , y4 = 0] = quad;
30264
+ return { label: hit.label, x: Math.round((x1 + x3) / 2), y: Math.round((y1 + y4) / 2) };
30265
+ }
30266
+ async function createShadowFinder(context2, page) {
30267
+ if (typeof context2.newCDPSession !== "function")
30268
+ return;
30269
+ try {
30270
+ const session = await context2.newCDPSession(page);
30271
+ await session.send("DOM.enable");
30272
+ return (labels) => findShadowControl(session, labels);
30273
+ } catch {
30274
+ return;
30275
+ }
30276
+ }
30277
+ var markdownToHtml = (markdown) => Bun.markdown.html(markdown);
30278
+ async function requireAttached(page, selector, action, timeoutMs) {
30279
+ await resolveSelector(page, [selector], { timeoutMs, label: action });
30280
+ }
30281
+ async function resolveSelector(page, selectors, opts = {}) {
30282
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
30283
+ const found = { selector: null };
30284
+ const attached = await waitUntil(async () => {
30285
+ const result = await trySelectors(page, selectors, { visible: false });
30286
+ found.selector = result.selector;
30287
+ return result.found && result.selector !== null;
30288
+ }, timeoutMs, ATTACH_POLL_MS);
30289
+ const selector = found.selector;
30290
+ if (!attached || selector === null) {
30291
+ throw new Error(`${opts.label ?? "selector"}: ${selectors.join(" | ")} not attached within ${timeoutMs}ms`);
30292
+ }
30293
+ return selector;
30294
+ }
30295
+ function errorMessage2(error51) {
30296
+ return error51 instanceof Error ? error51.message : String(error51);
30297
+ }
30298
+ function pasteScript(selector, html) {
30299
+ const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
30300
+ return `(() => {
30301
+ const el = document.querySelector(${JSON.stringify(selector)});
30302
+ if (!el) return false;
30303
+ const data = new DataTransfer();
30304
+ data.setData('text/html', ${JSON.stringify(html)});
30305
+ data.setData('text/plain', ${JSON.stringify(text)});
30306
+ el.focus();
30307
+ el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
30308
+ return true;
30309
+ })()`;
30310
+ }
30311
+ function editorTextScript(selector) {
30312
+ return `(() => {
30313
+ const el = document.querySelector(${JSON.stringify(selector)});
30314
+ return el ? (el.textContent ?? '').trim() : '';
30315
+ })()`;
30316
+ }
30317
+
30123
30318
  // ../../plugins/publishings/xhs-pub/src/index.ts
30124
30319
  init_dist();
30125
30320
 
@@ -30195,48 +30390,86 @@ function createAutomationGuard(options) {
30195
30390
  };
30196
30391
  }
30197
30392
 
30198
- // ../../plugins/publishings/xhs-pub/src/index.ts
30199
- var CHANNEL = "xhs";
30393
+ // ../../plugins/publishings/xhs-pub/src/modes.ts
30394
+ import { isAbsolute } from "path";
30200
30395
  var TARGET = "xhs";
30201
30396
  var PUBLISH_URL = "https://creator.xiaohongshu.com/publish/publish";
30202
30397
  var PUBLISHED_MARKER = "published=true";
30203
- var LOGIN_TIMEOUT_MS = 120000;
30204
- var STEP_TIMEOUT_MS = 30000;
30205
- var PREFLIGHT_TIMEOUT_MS = 30000;
30206
- var POLL_MS = 500;
30207
- var SUBMIT_SETTLE_MS = 2500;
30208
- var SUBMIT_ATTEMPTS = 3;
30209
- var SELECTORS = {
30210
- imageTab: ['div.creator-tab:has-text("\u56FE\u6587")', "text=\u4E0A\u4F20\u56FE\u6587"],
30211
- fileInput: ['input[type="file"][accept*=".jpg"]', 'input[type="file"]'],
30212
- thumbnail: ["img.preivew-image", ".img-preview img"],
30213
- title: ['input[placeholder*="\u6807\u9898"]', "#title-textarea input"],
30214
- body: [
30215
- 'div.tiptap[contenteditable="true"]',
30216
- 'div[contenteditable="true"].ql-editor',
30217
- "#post-textarea",
30218
- ".ql-editor"
30219
- ],
30220
- draftBox: ["text=/\u8349\u7A3F\u7BB1\\(\\d+\\)/", "text=\u8349\u7A3F\u7BB1"],
30221
- publishConfirm: ["text=\u53D1\u5E03\u6210\u529F", "text=\u5DF2\u53D1\u5E03"]
30222
- };
30223
- var SHADOW_CONTROLS = {
30224
- draft: ["\u6682\u5B58\u79BB\u5F00", "\u5B58\u8349\u7A3F", "\u4FDD\u5B58\u8349\u7A3F"],
30225
- publish: ["\u53D1\u5E03"]
30398
+ var XHS_OPERATIONS = ["\u4E0A\u4F20\u56FE\u6587", "\u4E0A\u4F20\u89C6\u9891", "\u5199\u957F\u6587", "\u53D1\u64AD\u5BA2"];
30399
+ var DEFAULT_XHS_OPERATION = "\u4E0A\u4F20\u56FE\u6587";
30400
+ function resolveXhsOperation(options, content) {
30401
+ const raw = options?.operation ?? content?.metadata?.operation;
30402
+ if (raw === undefined)
30403
+ return { operation: DEFAULT_XHS_OPERATION };
30404
+ if (typeof raw !== "string" || !XHS_OPERATIONS.includes(raw)) {
30405
+ return {
30406
+ error: `unsupported operation '${String(raw)}' for ${TARGET}: expected one of ${XHS_OPERATIONS.join(", ")}`
30407
+ };
30408
+ }
30409
+ return { operation: raw };
30410
+ }
30411
+ var IMAGE_EXTENSIONS = /\.(png|jpe?g|webp)$/i;
30412
+ var IMAGE_LIST = "png|jpg|jpeg|webp";
30413
+ var VIDEO_EXTENSIONS = /\.(mp4|mov|flv|f4v|mkv|rm|rmvb|m4v|mpg|mpeg|ts)$/i;
30414
+ var VIDEO_LIST = "mp4|mov|flv|f4v|mkv|rm|rmvb|m4v|mpg|mpeg|ts";
30415
+ var AUDIO_EXTENSIONS = /\.(mp3|wav|aac|flac|m4a)$/i;
30416
+ var AUDIO_LIST = "mp3|wav|aac|flac|m4a";
30417
+ var XHS_IMAGE_MAX = 18;
30418
+ var XHS_NOTE_TITLE_MAX = 20;
30419
+ var XHS_NOTE_BODY_MAX = 1000;
30420
+ var MODE_LIMITS = {
30421
+ \u{4e0a}\u{4f20}\u{56fe}\u{6587}: { imageMax: XHS_IMAGE_MAX, titleMax: XHS_NOTE_TITLE_MAX, bodyMax: XHS_NOTE_BODY_MAX },
30422
+ \u{4e0a}\u{4f20}\u{89c6}\u{9891}: { titleMax: XHS_NOTE_TITLE_MAX },
30423
+ \u{5199}\u{957f}\u{6587}: {},
30424
+ \u{53d1}\u{64ad}\u{5ba2}: {}
30226
30425
  };
30227
- function fail(error51) {
30228
- return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30426
+ function artifactPaths(input) {
30427
+ switch (input.operation) {
30428
+ case "\u4E0A\u4F20\u56FE\u6587":
30429
+ return input.images;
30430
+ case "\u4E0A\u4F20\u89C6\u9891":
30431
+ return [input.video];
30432
+ case "\u5199\u957F\u6587":
30433
+ return [];
30434
+ case "\u53D1\u64AD\u5BA2":
30435
+ return [input.audio, input.cover];
30436
+ }
30229
30437
  }
30230
- async function validatePublishInput(content) {
30231
- const status = content.metadata?.publishStatus;
30232
- if (status !== undefined && status !== "draft" && status !== "public") {
30233
- return { error: `images validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')` };
30438
+ async function resolveMediaPath(content, spec) {
30439
+ const raw = content.metadata?.[spec.key];
30440
+ const path7 = typeof raw === "string" ? raw : typeof raw?.path === "string" ? raw.path : undefined;
30441
+ if (path7 === undefined || path7.length === 0) {
30442
+ return {
30443
+ error: `${spec.label}: Content.metadata.${spec.key} is required (absolute path to an existing ${spec.list} file)`
30444
+ };
30445
+ }
30446
+ if (!isAbsolute(path7)) {
30447
+ return { error: `${spec.label}: Content.metadata.${spec.key} must be absolute, got '${path7}'` };
30448
+ }
30449
+ if (!spec.extensions.test(path7)) {
30450
+ return {
30451
+ error: `${spec.label}: Content.metadata.${spec.key} has unsupported extension (expected ${spec.list}): '${path7}'`
30452
+ };
30453
+ }
30454
+ if (!await createNodeFileSystem().exists(path7)) {
30455
+ return { error: `${spec.label}: Content.metadata.${spec.key} does not exist: '${path7}'` };
30234
30456
  }
30457
+ return { path: path7 };
30458
+ }
30459
+ async function validatePublishInput(content) {
30460
+ const status = validatePublishStatus(content);
30461
+ if (status.error)
30462
+ return { error: status.error };
30235
30463
  const images = content.metadata?.images;
30236
30464
  if (!Array.isArray(images) || images.length === 0) {
30237
30465
  return { error: "images validation: metadata.images must be a non-empty ordered array" };
30238
30466
  }
30239
- const fs2 = createNodeFileSystem();
30467
+ const max = XHS_IMAGE_MAX;
30468
+ if (images.length > max) {
30469
+ return {
30470
+ error: `images validation: metadata.images holds ${images.length} entries but the \u4E0A\u4F20\u56FE\u6587 grid accepts \u2264${max}`
30471
+ };
30472
+ }
30240
30473
  const paths = [];
30241
30474
  for (const [i2, image] of images.entries()) {
30242
30475
  const path7 = image?.path;
@@ -30246,20 +30479,163 @@ async function validatePublishInput(content) {
30246
30479
  if (!isAbsolute(path7)) {
30247
30480
  return { error: `images validation: images[${i2}].path must be absolute, got '${path7}'` };
30248
30481
  }
30249
- if (!/\.(png|jpe?g|webp)$/i.test(path7)) {
30482
+ if (!IMAGE_EXTENSIONS.test(path7)) {
30250
30483
  return {
30251
- error: `images validation: images[${i2}].path has unsupported extension (expected png|jpg|jpeg|webp): '${path7}'`
30484
+ error: `images validation: images[${i2}].path has unsupported extension (expected ${IMAGE_LIST}): '${path7}'`
30252
30485
  };
30253
30486
  }
30254
- if (!await fs2.exists(path7)) {
30487
+ if (!await createNodeFileSystem().exists(path7)) {
30255
30488
  return { error: `images validation: images[${i2}].path does not exist: '${path7}'` };
30256
30489
  }
30257
30490
  paths.push(path7);
30258
30491
  }
30259
30492
  return { paths };
30260
30493
  }
30494
+ function validatePublishStatus(content) {
30495
+ const status = content.metadata?.publishStatus;
30496
+ if (status !== undefined && status !== "draft" && status !== "public") {
30497
+ return {
30498
+ error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
30499
+ };
30500
+ }
30501
+ return {};
30502
+ }
30503
+ function validateTitle(content, operation) {
30504
+ const max = MODE_LIMITS[operation].titleMax;
30505
+ if (max === undefined)
30506
+ return {};
30507
+ const title = content.title ?? "";
30508
+ if (title.length > max) {
30509
+ return {
30510
+ error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${max}`
30511
+ };
30512
+ }
30513
+ return {};
30514
+ }
30515
+ function validateBody(content, operation) {
30516
+ const max = MODE_LIMITS[operation].bodyMax;
30517
+ if (max === undefined)
30518
+ return {};
30519
+ if (content.body.length > max) {
30520
+ return {
30521
+ error: `body validation: Content.body is ${content.body.length} chars but ${operation} accepts \u2264${max}`
30522
+ };
30523
+ }
30524
+ return {};
30525
+ }
30526
+ async function validateModeInput(operation, content) {
30527
+ const status = validatePublishStatus(content);
30528
+ if (status.error)
30529
+ return { error: status.error };
30530
+ switch (operation) {
30531
+ case "\u4E0A\u4F20\u56FE\u6587": {
30532
+ const title = validateTitle(content, operation);
30533
+ if (title.error)
30534
+ return { error: title.error };
30535
+ const body = validateBody(content, operation);
30536
+ if (body.error)
30537
+ return { error: body.error };
30538
+ const images = await validatePublishInput(content);
30539
+ if (images.error)
30540
+ return { error: images.error };
30541
+ return { input: { operation, images: images.paths ?? [] } };
30542
+ }
30543
+ case "\u4E0A\u4F20\u89C6\u9891": {
30544
+ const title = validateTitle(content, operation);
30545
+ if (title.error)
30546
+ return { error: title.error };
30547
+ const video = await resolveMediaPath(content, {
30548
+ key: "video",
30549
+ label: "video validation",
30550
+ extensions: VIDEO_EXTENSIONS,
30551
+ list: VIDEO_LIST
30552
+ });
30553
+ if (video.error)
30554
+ return { error: video.error };
30555
+ return { input: { operation, video: video.path ?? "" } };
30556
+ }
30557
+ case "\u5199\u957F\u6587": {
30558
+ if (content.body.trim().length === 0) {
30559
+ return { error: "body validation: Content.body must be non-empty for \u5199\u957F\u6587 (authored in place)" };
30560
+ }
30561
+ return { input: { operation } };
30562
+ }
30563
+ case "\u53D1\u64AD\u5BA2": {
30564
+ const audio = await resolveMediaPath(content, {
30565
+ key: "audio",
30566
+ label: "audio validation",
30567
+ extensions: AUDIO_EXTENSIONS,
30568
+ list: AUDIO_LIST
30569
+ });
30570
+ if (audio.error)
30571
+ return { error: audio.error };
30572
+ const cover = await resolveMediaPath(content, {
30573
+ key: "coverImage",
30574
+ label: "cover validation",
30575
+ extensions: IMAGE_EXTENSIONS,
30576
+ list: IMAGE_LIST
30577
+ });
30578
+ if (cover.error)
30579
+ return { error: cover.error };
30580
+ return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
30581
+ }
30582
+ }
30583
+ }
30584
+
30585
+ // ../../plugins/publishings/xhs-pub/src/page.ts
30586
+ var POLL_MS = 500;
30587
+ var STEP_TIMEOUT_MS = 30000;
30588
+ var CREATOR_TAB_BAR = "div.creator-tab";
30589
+ var CREATOR_TAB_COUNT = 4;
30590
+ function creatorTabSelector(label) {
30591
+ return `${CREATOR_TAB_BAR}:has-text("${label}")`;
30592
+ }
30593
+ async function clickCreatorTab(page, label, opts = {}) {
30594
+ const timeoutMs = opts.timeoutMs ?? STEP_TIMEOUT_MS;
30595
+ const pollMs = opts.pollMs ?? POLL_MS;
30596
+ const onScreenCount = () => page.countOnScreen(CREATOR_TAB_BAR).catch(() => 0);
30597
+ const mounted = await waitUntil2(() => onScreenCount().then((count2) => count2 >= CREATOR_TAB_COUNT), timeoutMs, pollMs);
30598
+ if (!mounted) {
30599
+ const observed = await onScreenCount();
30600
+ throw new Error(`mode entry: the console tab bar did not mount within ${timeoutMs}ms (${observed} of ${CREATOR_TAB_COUNT} on-screen '${CREATOR_TAB_BAR}' nodes) \u2014 every tab renders aria-hidden and off-viewport clones`);
30601
+ }
30602
+ if (!page.clickInViewport) {
30603
+ throw new Error(`mode entry: this driver cannot read viewport geometry, so the on-screen '${label}' tab cannot be told apart from its off-screen clones`);
30604
+ }
30605
+ const clicked = await page.clickInViewport(creatorTabSelector(label));
30606
+ if (!clicked) {
30607
+ throw new Error(`mode entry: no in-viewport '${label}' tab found (only aria-hidden or off-screen clones matched) \u2014 the mode was not entered`);
30608
+ }
30609
+ }
30610
+ async function waitUntil2(predicate, timeoutMs, pollMs = POLL_MS) {
30611
+ const deadline = Date.now() + timeoutMs;
30612
+ while (Date.now() < deadline) {
30613
+ if (await predicate())
30614
+ return true;
30615
+ await new Promise((resolve3) => setTimeout(resolve3, pollMs));
30616
+ }
30617
+ return predicate();
30618
+ }
30261
30619
  function wrapPlaywrightPage(raw, shadow) {
30262
30620
  const pw = raw;
30621
+ const onScreen = async (selector) => {
30622
+ const viewport = pw.viewportSize?.() ?? null;
30623
+ if (viewport === null)
30624
+ return [];
30625
+ const total = await pw.locator(selector).count();
30626
+ const found = [];
30627
+ for (let index = 0;index < total; index += 1) {
30628
+ const candidate = pw.locator(selector).nth(index);
30629
+ const box = await candidate.boundingBox().catch(() => null);
30630
+ if (!box || box.width === 0 || box.height === 0)
30631
+ continue;
30632
+ const outside = box.x < 0 || box.y < 0 || box.x >= viewport.width || box.y >= viewport.height;
30633
+ if (outside)
30634
+ continue;
30635
+ found.push({ index, box });
30636
+ }
30637
+ return found;
30638
+ };
30263
30639
  return {
30264
30640
  goto: (url2, options) => pw.goto(url2, options),
30265
30641
  locator: (selector) => ({
@@ -30279,84 +30655,384 @@ function wrapPlaywrightPage(raw, shadow) {
30279
30655
  setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
30280
30656
  waitForSelector: (selector, options) => pw.waitForSelector(selector, options),
30281
30657
  url: () => pw.url(),
30658
+ evaluate: (expression) => pw.evaluate(expression),
30659
+ countOnScreen: async (selector) => (await onScreen(selector)).length,
30282
30660
  clickInViewport: async (selector) => {
30283
- const viewport = pw.viewportSize?.() ?? null;
30284
- if (viewport === null)
30661
+ const [candidate] = await onScreen(selector);
30662
+ if (candidate === undefined)
30285
30663
  return false;
30286
- const total = await pw.locator(selector).count();
30287
- for (let index = 0;index < total; index += 1) {
30288
- const candidate = pw.locator(selector).nth(index);
30289
- const box = await candidate.boundingBox().catch(() => null);
30290
- if (!box || box.width === 0)
30291
- continue;
30292
- const outside = box.x < 0 || box.y < 0 || box.x >= viewport.width || box.y >= viewport.height;
30293
- if (outside)
30294
- continue;
30295
- await candidate.click({ force: true });
30296
- return true;
30297
- }
30298
- return false;
30664
+ await pw.locator(selector).nth(candidate.index).click({ force: true });
30665
+ return true;
30299
30666
  },
30300
30667
  findShadowControl: async (labels) => shadow ? shadow.findControl(labels) : null,
30301
30668
  clickAt: (x, y) => pw.mouse.click(x, y)
30302
30669
  };
30303
30670
  }
30304
- async function findControlViaCdp(session, labels) {
30305
- const document = await session.send("DOM.getDocument", { depth: -1, pierce: true });
30306
- if (!document.root)
30307
- return null;
30308
- const wanted = new Set(labels);
30309
- const text = (node) => node.nodeName === "#text" ? node.nodeValue ?? "" : (node.children ?? []).map(text).join("");
30310
- const hits = [];
30311
- const walk = (node) => {
30312
- if (hits.length > 0)
30313
- return;
30314
- for (const shadowRoot of node.shadowRoots ?? []) {
30315
- walk(shadowRoot);
30316
- if (hits.length > 0)
30317
- return;
30318
- }
30319
- if (node.nodeName === "BUTTON") {
30320
- const label = text(node).replace(/\s+/g, "");
30321
- if (wanted.has(label) && typeof node.backendNodeId === "number") {
30322
- hits.push({ label, backendNodeId: node.backendNodeId });
30323
- return;
30324
- }
30671
+ async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30672
+ const deadline = Date.now() + timeoutMs;
30673
+ while (Date.now() < deadline) {
30674
+ const found = await trySelectors(page, selectors);
30675
+ if (found.found && found.selector)
30676
+ return found.selector;
30677
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_MS));
30678
+ }
30679
+ throw new Error(`${what}: no matching element observed`);
30680
+ }
30681
+ async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30682
+ const deadline = Date.now() + timeoutMs;
30683
+ while (Date.now() < deadline) {
30684
+ for (const selector of selectors) {
30685
+ const attached = await page.locator(selector).count().catch(() => 0);
30686
+ if (attached > 0)
30687
+ return selector;
30325
30688
  }
30326
- for (const child of node.children ?? []) {
30327
- walk(child);
30328
- if (hits.length > 0)
30329
- return;
30689
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_MS));
30690
+ }
30691
+ throw new Error(`${what}: no matching element observed`);
30692
+ }
30693
+
30694
+ // ../../plugins/publishings/xhs-pub/src/flow.ts
30695
+ var NOTE_TITLE = [
30696
+ 'input.d-text[placeholder="\u586B\u5199\u6807\u9898\u4F1A\u6709\u66F4\u591A\u8D5E\u54E6"]',
30697
+ 'input[placeholder*="\u6807\u9898"]',
30698
+ "#title-textarea input"
30699
+ ];
30700
+ var NOTE_BODY = [
30701
+ 'div.tiptap[contenteditable="true"]',
30702
+ 'div[contenteditable="true"].ql-editor',
30703
+ "#post-textarea",
30704
+ ".ql-editor"
30705
+ ];
30706
+ var NOTE_THUMBNAIL = ["img.preivew-image", ".img-preview img"];
30707
+ var IMAGE_FILE_INPUT = [
30708
+ 'input[type="file"][accept*=".jpg"]',
30709
+ 'input[type="file"].upload-input',
30710
+ 'input[type="file"]'
30711
+ ];
30712
+ var VIDEO_FILE_INPUT = ['input[type="file"][accept*=".mp4"]', 'input[type="file"].upload-input'];
30713
+ var VIDEO_EDITOR = ["div.tiptap.ProseMirror", 'div.tiptap[contenteditable="true"]', ".ProseMirror"];
30714
+ var ARTICLE_NEW = ["text=\u65B0\u7684\u521B\u4F5C", 'button:has-text("\u65B0\u7684\u521B\u4F5C")'];
30715
+ var ARTICLE_EDITOR = ["div.tiptap.ProseMirror.ProseMirror-focused", "div.tiptap.ProseMirror", ".ProseMirror"];
30716
+ var ARTICLE_LAYOUT = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
30717
+ var PODCAST_OPEN = ["text=\u4E0A\u4F20\u97F3\u9891", 'button:has-text("\u4E0A\u4F20\u97F3\u9891")'];
30718
+ var PODCAST_MODAL = ["div.creator-modal-sty", ".d-modal.d-modal-centered", ".d-modal"];
30719
+ var PODCAST_AUDIO_INPUT = ['input[type="file"][accept*=".mp3"]', 'input[type="file"][accept*=".wav"]'];
30720
+ var PODCAST_COVER_INPUT = ['input[type="file"][accept*=".jpg"]', 'input[type="file"][accept*=".png"]'];
30721
+ var PODCAST_MODAL_ADVANCE = ["\u53BB\u53D1\u5E03"];
30722
+ var DRAFT_LABELS = ["\u6682\u5B58\u79BB\u5F00", "\u5B58\u8349\u7A3F", "\u4FDD\u5B58\u8349\u7A3F"];
30723
+ var PUBLISH_LABELS = ["\u53D1\u5E03"];
30724
+ var PUBLISH_CONFIRM = ["\u53D1\u5E03\u6210\u529F", "\u5DF2\u53D1\u5E03"];
30725
+ var DRAFT_TOASTS = ["\u4FDD\u5B58\u6210\u529F"];
30726
+ var SHADOW_BAR = "the xhs publish bar (closed shadow root)";
30727
+ var MODE_ACTION_PATHS = {
30728
+ \u{4e0a}\u{4f20}\u{56fe}\u{6587}: ["shadow"],
30729
+ \u{4e0a}\u{4f20}\u{89c6}\u{9891}: ["shadow"],
30730
+ \u{5199}\u{957f}\u{6587}: ["light", "shadow"],
30731
+ \u{53d1}\u{64ad}\u{5ba2}: ["shadow"]
30732
+ };
30733
+ function fail(error51) {
30734
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
30735
+ }
30736
+ function sleep2(ms) {
30737
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
30738
+ }
30739
+ function asPlaywrightPage(page) {
30740
+ return page;
30741
+ }
30742
+ async function enterMode(deps, label) {
30743
+ await clickCreatorTab(deps.page, label, { timeoutMs: deps.stepTimeoutMs });
30744
+ }
30745
+ async function clickShadowControl(page, labels) {
30746
+ if (!page.findShadowControl || !page.clickAt) {
30747
+ return {
30748
+ ok: false,
30749
+ error: `submit control: ${SHADOW_BAR} and this driver has no CDP access to resolve it`
30750
+ };
30751
+ }
30752
+ const control = await page.findShadowControl(labels);
30753
+ if (!control) {
30754
+ return {
30755
+ ok: false,
30756
+ error: `submit control: none of [${labels.join(", ")}] found in ${SHADOW_BAR}`
30757
+ };
30758
+ }
30759
+ await page.clickAt(control.x, control.y);
30760
+ return { ok: true, label: control.label, x: control.x, y: control.y };
30761
+ }
30762
+ async function clickLightControl(page, labels, timeoutMs, what) {
30763
+ const candidates = labels.flatMap((label2) => [`button:has-text("${label2}")`, `text=${label2}`]);
30764
+ let selector;
30765
+ try {
30766
+ selector = await findSelector(page, candidates, `${what} (light DOM) none of [${labels.join(", ")}]`, timeoutMs);
30767
+ } catch (error51) {
30768
+ return { ok: false, error: error51 instanceof Error ? error51.message : String(error51) };
30769
+ }
30770
+ const label = labels.find((candidate) => selector.includes(candidate)) ?? labels[0];
30771
+ await page.locator(selector).first().click({ timeout: timeoutMs });
30772
+ return { ok: true, label: label ?? "", selector };
30773
+ }
30774
+ async function clickActionControl(operation, deps, labels, what) {
30775
+ let lastError = "";
30776
+ for (const path7 of MODE_ACTION_PATHS[operation]) {
30777
+ if (path7 === "shadow") {
30778
+ const clicked2 = await clickShadowControl(deps.page, labels);
30779
+ if (clicked2.ok)
30780
+ return { ok: true, path: path7, label: clicked2.label, click: `${clicked2.x},${clicked2.y}` };
30781
+ lastError = clicked2.error;
30782
+ continue;
30330
30783
  }
30331
- };
30332
- walk(document.root);
30333
- const hit = hits[0];
30334
- if (!hit)
30335
- return null;
30336
- const box = await session.send("DOM.getBoxModel", { backendNodeId: hit.backendNodeId });
30337
- const quad = box.model?.content;
30338
- if (!quad || quad.length < 8)
30339
- return null;
30340
- const [x1 = 0, y1 = 0, x3 = 0, , , y4 = 0] = quad;
30341
- return {
30342
- label: hit.label,
30343
- x: Math.round((x1 + x3) / 2),
30344
- y: Math.round((y1 + y4) / 2)
30345
- };
30784
+ const clicked = await clickLightControl(deps.page, labels, deps.stepTimeoutMs, what);
30785
+ if (clicked.ok)
30786
+ return { ok: true, path: path7, label: clicked.label, click: `selector:${clicked.selector}` };
30787
+ lastError = clicked.error;
30788
+ }
30789
+ return { ok: false, error: lastError };
30346
30790
  }
30347
- async function createShadowProbe(context2, page) {
30348
- if (typeof context2.newCDPSession !== "function")
30791
+ async function readDraftCount(page) {
30792
+ for (const selector of ["text=/\u8349\u7A3F\u7BB1\\(\\d+\\)/", "text=\u8349\u7A3F\u7BB1"]) {
30793
+ const attached = await page.locator(selector).count().catch(() => 0);
30794
+ if (attached === 0)
30795
+ continue;
30796
+ const locator = page.locator(selector).first();
30797
+ if (!locator.textContent)
30798
+ continue;
30799
+ const text = await locator.textContent().catch(() => "");
30800
+ const match = /\u8349\u7A3F\u7BB1\s*[\uFF08(]\s*(\d+)\s*[\uFF09)]/.exec(text);
30801
+ if (match?.[1] !== undefined)
30802
+ return Number(match[1]);
30803
+ }
30804
+ return null;
30805
+ }
30806
+ function draftSavedStamps(now = new Date) {
30807
+ const pad = (value) => String(value).padStart(2, "0");
30808
+ return [0, 1, 2].map((minutesAgo) => {
30809
+ const stamp = new Date(now.getTime() - minutesAgo * 60000);
30810
+ return `\u4FDD\u5B58\u4E8E${stamp.getFullYear()}-${pad(stamp.getMonth() + 1)}-${pad(stamp.getDate())} ${pad(stamp.getHours())}:${pad(stamp.getMinutes())}`;
30811
+ });
30812
+ }
30813
+ async function readDraftStamps(page) {
30814
+ const present = [];
30815
+ for (const stamp of draftSavedStamps()) {
30816
+ const attached = await page.locator(`text=${stamp}`).count().catch(() => 0);
30817
+ if (attached > 0)
30818
+ present.push(stamp);
30819
+ }
30820
+ return present;
30821
+ }
30822
+ async function readDraftBaseline(page) {
30823
+ return { count: await readDraftCount(page), stamps: await readDraftStamps(page) };
30824
+ }
30825
+ async function isDraftRowSaved(page, baseline) {
30826
+ const stamps = await readDraftStamps(page);
30827
+ if (stamps.some((stamp) => !baseline.stamps.includes(stamp)))
30828
+ return true;
30829
+ const after = await readDraftCount(page);
30830
+ if (baseline.count === null || after === null)
30831
+ return false;
30832
+ return after > baseline.count;
30833
+ }
30834
+ async function draftSignalEvidence(page, baseline) {
30835
+ const toast = await readScopedSignal(asPlaywrightPage(page), DRAFT_TOASTS);
30836
+ if (toast !== null)
30837
+ return `toast:${toast}`;
30838
+ if (await isDraftRowSaved(page, baseline))
30839
+ return "draft-row";
30840
+ return null;
30841
+ }
30842
+ async function applyNoteTitle(deps) {
30843
+ const selector = await findSelector(deps.page, NOTE_TITLE, "title input", deps.stepTimeoutMs);
30844
+ await deps.page.locator(selector).first().fill(deps.content.title ?? "");
30845
+ }
30846
+ async function typeNoteBody(deps, selectors, what) {
30847
+ const selector = await findSelector(deps.page, selectors, what, deps.stepTimeoutMs);
30848
+ const body = deps.page.locator(selector).first();
30849
+ const text = deps.content.body ?? "";
30850
+ if (typeof body.type === "function") {
30851
+ await body.click();
30852
+ await body.type(text, { delay: 5 });
30349
30853
  return;
30854
+ }
30855
+ await body.fill(text);
30856
+ }
30857
+ async function confirmThumbnails(deps, expected) {
30858
+ const selector = await findSelector(deps.page, NOTE_THUMBNAIL, "thumbnail wait", deps.stepTimeoutMs);
30859
+ const matches = await waitUntil2(() => deps.page.locator(selector).count().then((count2) => count2 >= expected), deps.stepTimeoutMs);
30860
+ const observed = await deps.page.locator(selector).count();
30861
+ if (!matches || observed !== expected) {
30862
+ throw new Error(`thumbnail count: expected ${expected}, observed ${observed}`);
30863
+ }
30864
+ }
30865
+ async function composeImageText(input, deps) {
30866
+ await enterMode(deps, input.operation);
30867
+ const fileSelector = await findAttachedSelector(deps.page, IMAGE_FILE_INPUT, "image file input", deps.stepTimeoutMs);
30868
+ await deps.page.setInputFiles(fileSelector, input.images);
30869
+ await confirmThumbnails(deps, input.images.length);
30870
+ await applyNoteTitle(deps);
30871
+ await typeNoteBody(deps, NOTE_BODY, "note body");
30872
+ }
30873
+ async function composeVideo(input, deps) {
30874
+ await enterMode(deps, input.operation);
30875
+ const fileSelector = await findAttachedSelector(deps.page, VIDEO_FILE_INPUT, "video file input", deps.stepTimeoutMs);
30876
+ await uploadFile(asPlaywrightPage(deps.page), fileSelector, input.video, { timeoutMs: deps.stepTimeoutMs });
30877
+ await findSelector(deps.page, VIDEO_EDITOR, "video note editor", deps.stepTimeoutMs);
30878
+ await applyNoteTitle(deps);
30879
+ await typeNoteBody(deps, NOTE_BODY, "note body");
30880
+ }
30881
+ async function composeArticle(deps) {
30882
+ await enterMode(deps, "\u5199\u957F\u6587");
30883
+ const newArticle = await findAttachedSelector(deps.page, ARTICLE_NEW, "\u65B0\u7684\u521B\u4F5C control", deps.stepTimeoutMs);
30884
+ await deps.page.locator(newArticle).first().click();
30885
+ const editor = await findSelector(deps.page, ARTICLE_EDITOR, "long-article editor", deps.stepTimeoutMs);
30886
+ await pasteHtml(asPlaywrightPage(deps.page), editor, markdownToHtml(deps.content.body), {
30887
+ timeoutMs: deps.stepTimeoutMs
30888
+ });
30350
30889
  try {
30351
- const session = await context2.newCDPSession(page);
30352
- await session.send("DOM.enable");
30353
- return { findControl: (labels) => findControlViaCdp(session, labels) };
30354
- } catch {
30890
+ const layout = await findSelector(deps.page, ARTICLE_LAYOUT, "\u4E00\u952E\u6392\u7248 control", deps.stepTimeoutMs);
30891
+ await deps.page.locator(layout).first().click();
30892
+ } catch {}
30893
+ }
30894
+ async function isPodcastModalOpen(page) {
30895
+ return trySelectors(page, PODCAST_MODAL).then((found) => found.found).catch(() => false);
30896
+ }
30897
+ async function leavePodcastModal(deps) {
30898
+ const closed = () => isPodcastModalOpen(deps.page).then((open) => !open);
30899
+ if (await waitUntil2(closed, deps.stepTimeoutMs))
30355
30900
  return;
30901
+ if (deps.publishStatus === "public") {
30902
+ const advance = await clickLightControl(deps.page, PODCAST_MODAL_ADVANCE, deps.stepTimeoutMs, "podcast modal advance");
30903
+ if (!advance.ok)
30904
+ throw new Error(advance.error);
30905
+ if (await waitUntil2(closed, deps.stepTimeoutMs))
30906
+ return;
30356
30907
  }
30908
+ throw new Error(`podcast modal: the \u53D1\u64AD\u5BA2 modal was still open ${deps.stepTimeoutMs}ms after the audio and cover were attached; the only recorded advance control is '${PODCAST_MODAL_ADVANCE[0]}' and a draft run does not click it (it is publish-adjacent)`);
30909
+ }
30910
+ async function composePodcast(input, deps) {
30911
+ await enterMode(deps, input.operation);
30912
+ const open = await findAttachedSelector(deps.page, PODCAST_OPEN, "\u4E0A\u4F20\u97F3\u9891 control", deps.stepTimeoutMs);
30913
+ await deps.page.locator(open).first().click();
30914
+ await findSelector(deps.page, PODCAST_MODAL, "podcast modal", deps.stepTimeoutMs);
30915
+ const audioInput = await findAttachedSelector(deps.page, PODCAST_AUDIO_INPUT, "podcast audio input", deps.stepTimeoutMs);
30916
+ await uploadFile(asPlaywrightPage(deps.page), audioInput, input.audio, { timeoutMs: deps.stepTimeoutMs });
30917
+ const coverInput = await findAttachedSelector(deps.page, PODCAST_COVER_INPUT, "podcast cover input", deps.stepTimeoutMs);
30918
+ await uploadFile(asPlaywrightPage(deps.page), coverInput, input.cover, { timeoutMs: deps.stepTimeoutMs });
30919
+ await leavePodcastModal(deps);
30920
+ }
30921
+ async function compose(input, deps) {
30922
+ switch (input.operation) {
30923
+ case "\u4E0A\u4F20\u56FE\u6587":
30924
+ await composeImageText(input, deps);
30925
+ return;
30926
+ case "\u4E0A\u4F20\u89C6\u9891":
30927
+ await composeVideo(input, deps);
30928
+ return;
30929
+ case "\u5199\u957F\u6587":
30930
+ await composeArticle(deps);
30931
+ return;
30932
+ case "\u53D1\u64AD\u5BA2":
30933
+ await composePodcast(input, deps);
30934
+ return;
30935
+ }
30936
+ }
30937
+ function baseMetadata(input, deps) {
30938
+ return {
30939
+ operation: input.operation,
30940
+ driver: deps.driver,
30941
+ publishStatus: deps.publishStatus,
30942
+ profileDir: deps.profileDir,
30943
+ artifactPaths: artifactPaths(input),
30944
+ ...input.operation === "\u4E0A\u4F20\u56FE\u6587" ? { imageCount: input.images.length } : {}
30945
+ };
30946
+ }
30947
+ async function saveDraft(input, deps) {
30948
+ let clicked = null;
30949
+ let signal = null;
30950
+ let attempts = 0;
30951
+ while (signal === null && attempts < deps.submitAttempts) {
30952
+ attempts += 1;
30953
+ await sleep2(deps.submitSettleMs);
30954
+ const control = await clickActionControl(input.operation, deps, DRAFT_LABELS, "draft control");
30955
+ if (!control.ok)
30956
+ return fail(control.error);
30957
+ clicked = control;
30958
+ const outcome = await expectOutcome(() => draftSignalEvidence(deps.page, deps.draftBaseline), {
30959
+ timeoutMs: deps.submitSettleMs,
30960
+ label: `draft confirmation '${control.label}'`,
30961
+ pollMs: POLL_MS
30962
+ });
30963
+ if (outcome.ok)
30964
+ signal = outcome.evidence;
30965
+ }
30966
+ const draftCountAfter = await readDraftCount(deps.page);
30967
+ if (signal === null || clicked === null) {
30968
+ return fail(`draft confirmation: '${clicked?.label ?? DRAFT_LABELS[0]}' was clicked ${attempts}\xD7 but neither a fresh ${TARGET} save timestamp, a moved draft counter, nor a scoped '${DRAFT_TOASTS[0]}' toast was observed within ${deps.submitSettleMs}ms (before=${String(deps.draftBaseline.count)}, after=${String(draftCountAfter)})`);
30969
+ }
30970
+ return ResultSchema.parse({
30971
+ ok: true,
30972
+ target: TARGET,
30973
+ message: `${TARGET} draft saved (unpublished) in profile ${deps.profileDir} via '${clicked.label}' (${input.operation})`,
30974
+ metadata: {
30975
+ ...baseMetadata(input, deps),
30976
+ draftControl: clicked.label,
30977
+ draftClick: clicked.click,
30978
+ draftPath: clicked.path,
30979
+ draftAttempts: attempts,
30980
+ draftSignal: signal,
30981
+ draftCountBefore: deps.draftBaseline.count,
30982
+ draftCountAfter
30983
+ }
30984
+ });
30985
+ }
30986
+ async function publishNote(input, deps) {
30987
+ await sleep2(deps.submitSettleMs);
30988
+ const submitted = await clickActionControl(input.operation, deps, PUBLISH_LABELS, "publish control");
30989
+ if (!submitted.ok)
30990
+ return fail(submitted.error);
30991
+ const urlBeforeSubmit = deps.page.url();
30992
+ const confirmed = await waitUntil2(async () => {
30993
+ if (deps.page.url().includes(PUBLISHED_MARKER))
30994
+ return true;
30995
+ return await readScopedSignal(asPlaywrightPage(deps.page), PUBLISH_CONFIRM) !== null;
30996
+ }, deps.stepTimeoutMs);
30997
+ if (!confirmed) {
30998
+ return fail(`publish submission: state unknown \u2014 the '${submitted.label}' click (${submitted.click}) was sent from ${urlBeforeSubmit} but no success state was observed (now ${deps.page.url()}). Check the ${TARGET} creator note manager before retrying: a retry could duplicate the post.`);
30999
+ }
31000
+ const url2 = deps.page.url();
31001
+ return ResultSchema.parse({
31002
+ ok: true,
31003
+ target: TARGET,
31004
+ ...url2 !== PUBLISH_URL ? { url: url2 } : {},
31005
+ message: `${TARGET} ${input.operation} post published`,
31006
+ metadata: {
31007
+ ...baseMetadata(input, deps),
31008
+ publishControl: submitted.label,
31009
+ publishPath: submitted.path,
31010
+ publishClick: submitted.click,
31011
+ publishSignal: url2.includes(PUBLISHED_MARKER) ? `url:${PUBLISHED_MARKER}` : "toast"
31012
+ }
31013
+ });
31014
+ }
31015
+ async function runMode(input, deps) {
31016
+ await compose(input, deps);
31017
+ return deps.publishStatus === "public" ? publishNote(input, deps) : saveDraft(input, deps);
31018
+ }
31019
+
31020
+ // ../../plugins/publishings/xhs-pub/src/index.ts
31021
+ var CHANNEL = "xhs";
31022
+ var LOGIN_TIMEOUT_MS = 120000;
31023
+ var PREFLIGHT_TIMEOUT_MS = 30000;
31024
+ var SUBMIT_SETTLE_MS = 2500;
31025
+ var SUBMIT_ATTEMPTS = 3;
31026
+ var AUTH_GATE = ["div.creator-tab", "text=\u4E0A\u4F20\u56FE\u6587"];
31027
+ function fail2(error51) {
31028
+ return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
31029
+ }
31030
+ async function createShadowProbe(context2, page) {
31031
+ const find = await createShadowFinder(context2, page);
31032
+ return find === undefined ? undefined : { findControl: (labels) => find(labels) };
30357
31033
  }
30358
31034
  async function isXhsConsoleReady(page) {
30359
- return trySelectors(page, SELECTORS.imageTab).then((r) => r.found);
31035
+ return trySelectors(page, AUTH_GATE).then((r) => r.found);
30360
31036
  }
30361
31037
  var defaultLauncher2 = openPersistentBrowser;
30362
31038
  function makeDefaultOpenPage(launch = defaultLauncher2) {
@@ -30379,67 +31055,16 @@ function makeDefaultOpenPage(launch = defaultLauncher2) {
30379
31055
  };
30380
31056
  }
30381
31057
  var defaultOpenPage = makeDefaultOpenPage();
30382
- async function waitUntil(predicate, timeoutMs) {
30383
- const deadline = Date.now() + timeoutMs;
30384
- while (Date.now() < deadline) {
30385
- if (await predicate())
30386
- return true;
30387
- await new Promise((resolve3) => setTimeout(resolve3, POLL_MS));
30388
- }
30389
- return predicate();
30390
- }
30391
- async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30392
- const deadline = Date.now() + timeoutMs;
30393
- while (Date.now() < deadline) {
30394
- const found = await trySelectors(page, selectors);
30395
- if (found.found && found.selector)
30396
- return found.selector;
30397
- await new Promise((resolve3) => setTimeout(resolve3, POLL_MS));
30398
- }
30399
- throw new Error(`${what}: no matching element observed`);
30400
- }
30401
- async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
30402
- const deadline = Date.now() + timeoutMs;
30403
- while (Date.now() < deadline) {
30404
- for (const selector of selectors) {
30405
- const attached = await page.locator(selector).count().catch(() => 0);
30406
- if (attached > 0)
30407
- return selector;
30408
- }
30409
- await new Promise((resolve3) => setTimeout(resolve3, POLL_MS));
30410
- }
30411
- throw new Error(`${what}: no matching element observed`);
30412
- }
30413
31058
  async function checkXhsProfileSession(opts) {
30414
- const timeoutMs = opts.timeoutMs ?? PREFLIGHT_TIMEOUT_MS;
30415
- for (const headless of [true, false]) {
30416
- try {
30417
- await withProfilePage({
30418
- profileDir: opts.profileDir,
30419
- headless,
30420
- url: PUBLISH_URL,
30421
- isAuthenticated: (page) => isXhsConsoleReady(wrapPlaywrightPage(page)),
30422
- timeoutMs,
30423
- launcher: opts.launcher
30424
- }, async () => {
30425
- return;
30426
- });
30427
- return {
30428
- status: "verified",
30429
- profileDir: opts.profileDir,
30430
- detail: `authenticated ${CHANNEL} creator console observed ${headless ? "headlessly" : "via headed fallback"} via profile ${opts.profileDir} (read-only)`
30431
- };
30432
- } catch (error51) {
30433
- if (headless)
30434
- continue;
30435
- return {
30436
- status: "refused",
30437
- profileDir: opts.profileDir,
30438
- detail: `headless and headed readiness refused: ${error51 instanceof Error ? error51.message : String(error51)}`
30439
- };
30440
- }
30441
- }
30442
- throw new Error("unreachable: session check loop must return or throw");
31059
+ return checkChannelSession({
31060
+ channel: CHANNEL,
31061
+ homeUrl: PUBLISH_URL,
31062
+ isAuthenticated: (page) => isXhsConsoleReady(wrapPlaywrightPage(page))
31063
+ }, {
31064
+ profileDir: opts.profileDir,
31065
+ launcher: opts.launcher,
31066
+ timeoutMs: opts.timeoutMs ?? PREFLIGHT_TIMEOUT_MS
31067
+ });
30443
31068
  }
30444
31069
 
30445
31070
  class XhsBrowserTransport {
@@ -30462,91 +31087,34 @@ class XhsBrowserTransport {
30462
31087
  this.automation = opts?.automation ?? createAutomationGuard();
30463
31088
  this.preflight = opts?.preflight ?? ((profileDir) => checkXhsProfileSession({ profileDir }));
30464
31089
  }
30465
- async readDraftCount(page) {
30466
- for (const selector of SELECTORS.draftBox) {
30467
- const attached = await page.locator(selector).count().catch(() => 0);
30468
- if (attached === 0)
30469
- continue;
30470
- const locator = page.locator(selector).first();
30471
- if (!locator.textContent)
30472
- continue;
30473
- const text = await locator.textContent().catch(() => "");
30474
- const match = /\u8349\u7A3F\u7BB1\s*[\uFF08(]\s*(\d+)\s*[\uFF09)]/.exec(text);
30475
- if (match?.[1] !== undefined)
30476
- return Number(match[1]);
30477
- }
30478
- return null;
30479
- }
30480
- static draftSavedStamps(now = new Date) {
30481
- const pad = (value) => String(value).padStart(2, "0");
30482
- return [0, 1, 2].map((minutesAgo) => {
30483
- const stamp = new Date(now.getTime() - minutesAgo * 60000);
30484
- return `\u4FDD\u5B58\u4E8E${stamp.getFullYear()}-${pad(stamp.getMonth() + 1)}-${pad(stamp.getDate())} ${pad(stamp.getHours())}:${pad(stamp.getMinutes())}`;
30485
- });
30486
- }
30487
- async readDraftStamps(page) {
30488
- const present = [];
30489
- for (const stamp of XhsBrowserTransport.draftSavedStamps()) {
30490
- const attached = await page.locator(`text=${stamp}`).count().catch(() => 0);
30491
- if (attached > 0)
30492
- present.push(stamp);
30493
- }
30494
- return present;
30495
- }
30496
- async readDraftBaseline(page) {
30497
- return { count: await this.readDraftCount(page), stamps: await this.readDraftStamps(page) };
30498
- }
30499
- async isDraftSaved(page, baseline) {
30500
- const stamps = await this.readDraftStamps(page);
30501
- if (stamps.some((stamp) => !baseline.stamps.includes(stamp)))
30502
- return true;
30503
- const after = await this.readDraftCount(page);
30504
- if (baseline.count === null || after === null)
30505
- return false;
30506
- return after > baseline.count;
30507
- }
30508
- async clickShadowControl(page, labels) {
30509
- if (!page.findShadowControl || !page.clickAt) {
30510
- return {
30511
- ok: false,
30512
- error: `submit control: the ${CHANNEL} publish bar lives in a closed shadow root and this driver has no CDP access to resolve it`
30513
- };
30514
- }
30515
- const control = await page.findShadowControl(labels);
30516
- if (!control) {
30517
- return {
30518
- ok: false,
30519
- error: `submit control: none of [${labels.join(", ")}] found in the ${CHANNEL} publish bar (closed shadow root)`
30520
- };
30521
- }
30522
- await page.clickAt(control.x, control.y);
30523
- return { ok: true, label: control.label, x: control.x, y: control.y };
30524
- }
30525
31090
  async publish(payload) {
30526
31091
  if (this.driver !== "script") {
30527
- return fail(`unsupported driver '${this.driver}' for ${TARGET}: image posts require the script driver (Playwright setInputFiles)`);
30528
- }
30529
- const override = typeof payload.options?.profileDir === "string" ? payload.options.profileDir : process.env.XHS_PROFILE_DIR ?? process.env.KK_BROWSER_PROFILE_DIR;
30530
- const profileDir = resolveBrowserProfileDir(CHANNEL, override);
30531
- const validation = await validatePublishInput(payload.content);
30532
- if (validation.error)
30533
- return fail(validation.error);
30534
- const paths = validation.paths ?? [];
31092
+ return fail2(`unsupported driver '${this.driver}' for ${TARGET}: the console modes (${XHS_OPERATIONS.join(", ")}) require the script driver (Playwright setInputFiles)`);
31093
+ }
31094
+ const requested = resolveXhsOperation(payload.options, payload.content);
31095
+ if (requested.error !== undefined)
31096
+ return fail2(requested.error);
31097
+ const operation = requested.operation;
31098
+ const validation = await validateModeInput(operation, payload.content);
31099
+ if (validation.error !== undefined)
31100
+ return fail2(validation.error);
31101
+ const input = validation.input;
31102
+ const override = typeof payload.options?.profileDir === "string" ? payload.options.profileDir : undefined;
31103
+ const profileDir = resolveChannelProfileDir(CHANNEL, override);
30535
31104
  const publishStatus = payload.content.metadata?.publishStatus === "public" ? "public" : "draft";
30536
- const base2 = { driver: this.driver, publishStatus, profileDir, imageCount: paths.length };
30537
31105
  const cap = this.automation.allow("publish");
30538
31106
  if (!cap.allowed)
30539
- return fail(`automation cap: ${cap.reason}`);
31107
+ return fail2(`automation cap: ${cap.reason}`);
30540
31108
  const session = await this.preflight(profileDir);
30541
31109
  if (session.status !== "verified") {
30542
- return fail(`session preflight refused (no headed visit attempted): ${session.detail}`);
31110
+ return fail2(`session preflight refused (no headed visit attempted): ${session.detail}`);
30543
31111
  }
30544
31112
  this.automation.record("publish");
30545
31113
  let opened;
30546
31114
  try {
30547
31115
  opened = await this.openPage(profileDir);
30548
31116
  } catch (error51) {
30549
- return fail(error51 instanceof Error ? error51.message : String(error51));
31117
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
30550
31118
  }
30551
31119
  const { page, close } = opened;
30552
31120
  const act = async (what, fn) => {
@@ -30558,101 +31126,24 @@ class XhsBrowserTransport {
30558
31126
  };
30559
31127
  try {
30560
31128
  await act("creator page goto", () => page.goto(PUBLISH_URL, { timeout: this.loginTimeoutMs }));
30561
- const gated = await waitUntil(() => isXhsConsoleReady(page), this.loginTimeoutMs);
31129
+ const gated = await waitUntil2(() => isXhsConsoleReady(page), this.loginTimeoutMs);
30562
31130
  if (!gated) {
30563
- return fail(`login timeout after ${this.loginTimeoutMs}ms: no authenticated ${CHANNEL} creator console in ${profileDir}`);
30564
- }
30565
- const step = (selectors, what) => findSelector(page, selectors, what, this.stepTimeoutMs);
30566
- const draftBaseline = publishStatus === "draft" ? await this.readDraftBaseline(page) : { count: null, stamps: [] };
30567
- const tabSelector = await step(SELECTORS.imageTab, "image-post tab");
30568
- const clickInViewport = page.clickInViewport;
30569
- if (clickInViewport) {
30570
- const switched = await act("image-post tab click", () => clickInViewport.call(page, tabSelector));
30571
- if (!switched)
30572
- return fail("image-post tab click: no in-viewport image tab found");
30573
- } else {
30574
- await act("image-post tab click", () => page.locator(tabSelector).first().click({ force: true }));
30575
- }
30576
- const fileSelector = await findAttachedSelector(page, SELECTORS.fileInput, "image file input", this.stepTimeoutMs);
30577
- await act("image upload", () => page.setInputFiles(fileSelector, paths));
30578
- const thumbSelector = await step(SELECTORS.thumbnail, "thumbnail wait");
30579
- const countMatches = await waitUntil(() => act("thumbnail count read", () => page.locator(thumbSelector).count()).then((n2) => n2 >= paths.length), this.stepTimeoutMs);
30580
- const thumbCount = await act("thumbnail count read", () => page.locator(thumbSelector).count());
30581
- if (!countMatches || thumbCount !== paths.length) {
30582
- return fail(`thumbnail count: expected ${paths.length}, observed ${thumbCount}`);
30583
- }
30584
- const titleSelector = await step(SELECTORS.title, "title input");
30585
- await act("title fill", () => page.locator(titleSelector).first().fill(payload.content.title ?? ""));
30586
- const bodySelector = await step(SELECTORS.body, "note body");
30587
- await act("body fill", async () => {
30588
- const body = page.locator(bodySelector).first();
30589
- const text = payload.content.body ?? "";
30590
- if (typeof body.type === "function") {
30591
- await body.click();
30592
- await body.type(text, { delay: 5 });
30593
- return;
30594
- }
30595
- await body.fill(text);
30596
- });
30597
- if (publishStatus === "draft") {
30598
- let submitted2 = null;
30599
- let confirmed2 = false;
30600
- let attempts = 0;
30601
- while (!confirmed2 && attempts < this.submitAttempts) {
30602
- attempts += 1;
30603
- await new Promise((resolve3) => setTimeout(resolve3, this.submitSettleMs));
30604
- const clicked = await this.clickShadowControl(page, SHADOW_CONTROLS.draft);
30605
- if (!clicked.ok)
30606
- return fail(clicked.error);
30607
- submitted2 = clicked;
30608
- confirmed2 = await waitUntil(() => this.isDraftSaved(page, draftBaseline), this.submitSettleMs);
30609
- }
30610
- const draftCountAfter = await this.readDraftCount(page);
30611
- if (!confirmed2 || submitted2 === null) {
30612
- return fail(`draft confirmation: '${submitted2?.label ?? SHADOW_CONTROLS.draft[0]}' was clicked ${attempts}\xD7 but neither a fresh ${CHANNEL} save timestamp nor a moved draft counter was observed (before=${String(draftBaseline.count)}, after=${String(draftCountAfter)})`);
30613
- }
30614
- return ResultSchema.parse({
30615
- ok: true,
30616
- target: TARGET,
30617
- message: `${TARGET} draft saved (unpublished) in profile ${profileDir} via '${submitted2.label}'`,
30618
- metadata: {
30619
- ...base2,
30620
- draftControl: submitted2.label,
30621
- draftClick: `${submitted2.x},${submitted2.y}`,
30622
- draftAttempts: attempts,
30623
- draftCountBefore: draftBaseline.count,
30624
- draftCountAfter
30625
- }
30626
- });
30627
- }
30628
- await new Promise((resolve3) => setTimeout(resolve3, this.submitSettleMs));
30629
- const submitted = await this.clickShadowControl(page, SHADOW_CONTROLS.publish);
30630
- if (!submitted.ok)
30631
- return fail(submitted.error);
30632
- const urlBeforeSubmit = page.url();
30633
- const confirmed = await waitUntil(async () => {
30634
- if (page.url().includes(PUBLISHED_MARKER))
30635
- return true;
30636
- return trySelectors(page, SELECTORS.publishConfirm).then((r) => r.found).catch(() => false);
30637
- }, this.stepTimeoutMs);
30638
- if (!confirmed) {
30639
- return fail(`publish submission: state unknown \u2014 the '${submitted.label}' click at ${submitted.x},${submitted.y} was sent from ${urlBeforeSubmit} but no success state was observed (now ${page.url()}). Check the ${CHANNEL} creator note manager before retrying: a retry could duplicate the post.`);
30640
- }
30641
- const url2 = page.url();
30642
- return ResultSchema.parse({
30643
- ok: true,
30644
- target: TARGET,
30645
- ...url2 !== PUBLISH_URL ? { url: url2 } : {},
30646
- message: `${TARGET} post published`,
30647
- metadata: {
30648
- ...base2,
30649
- publishControl: submitted.label,
30650
- publishClick: `${submitted.x},${submitted.y}`,
30651
- publishSignal: url2.includes(PUBLISHED_MARKER) ? `url:${PUBLISHED_MARKER}` : "text"
30652
- }
31131
+ return fail2(`login timeout after ${this.loginTimeoutMs}ms: no authenticated ${CHANNEL} creator console in ${profileDir}`);
31132
+ }
31133
+ const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(page) : { count: null, stamps: [] };
31134
+ return await runMode(input, {
31135
+ page,
31136
+ content: payload.content,
31137
+ publishStatus,
31138
+ profileDir,
31139
+ driver: this.driver,
31140
+ stepTimeoutMs: this.stepTimeoutMs,
31141
+ submitSettleMs: this.submitSettleMs,
31142
+ submitAttempts: this.submitAttempts,
31143
+ draftBaseline
30653
31144
  });
30654
31145
  } catch (error51) {
30655
- return fail(error51 instanceof Error ? error51.message : String(error51));
31146
+ return fail2(error51 instanceof Error ? error51.message : String(error51));
30656
31147
  } finally {
30657
31148
  await close();
30658
31149
  }
@@ -30682,7 +31173,7 @@ async function processPublishIO(inputPath, outputPath, transport = getPublishTra
30682
31173
  }
30683
31174
  } catch (error51) {
30684
31175
  const message = `Invalid Content input: ${error51 instanceof Error ? error51.message : String(error51)}`;
30685
- await writePublishResult(outputPath, fail(message));
31176
+ await writePublishResult(outputPath, fail2(message));
30686
31177
  throw new Error(message);
30687
31178
  }
30688
31179
  const result = ResultSchema.parse(await transport.publish({ content, options }));
@@ -30723,10 +31214,16 @@ export {
30723
31214
  getPublishTransport,
30724
31215
  defaultOpenPage,
30725
31216
  defaultLauncher2 as defaultLauncher,
31217
+ creatorTabSelector,
30726
31218
  createShadowProbe,
31219
+ clickCreatorTab,
30727
31220
  checkXhsProfileSession,
30728
31221
  XhsBrowserTransport,
31222
+ XHS_OPERATIONS,
30729
31223
  PUBLISH_URL,
31224
+ MODE_LIMITS,
30730
31225
  LOGIN_TIMEOUT_MS,
31226
+ CREATOR_TAB_COUNT,
31227
+ CREATOR_TAB_BAR,
30731
31228
  CHANNEL
30732
31229
  };