@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +32320 -15021
- package/package.json +4 -4
- package/plugins/generations/content-gen/dist/index.js +10 -9
- package/plugins/generations/core-facts-gen/dist/index.js +13 -12
- package/plugins/generations/daily-article-gen/dist/index.js +14 -13
- package/plugins/generations/daily-article-gen/src/index.ts +1 -1
- package/plugins/generations/dailynews-gen/dist/index.js +10 -9
- package/plugins/generations/episode-plan-gen/dist/index.js +58 -15
- package/plugins/generations/episode-plan-gen/src/index.ts +76 -6
- package/plugins/generations/gemini-tts/README.md +11 -0
- package/plugins/generations/gemini-tts/dist/index.js +10 -9
- package/plugins/generations/image-gen/dist/index.js +154 -151
- package/plugins/generations/image-gen/src/compose/chromium.ts +60 -42
- package/plugins/generations/news-report-gen/dist/index.js +13 -12
- package/plugins/generations/voice-gen/dist/index.js +10 -9
- package/plugins/ingestions/aihot-ingest/dist/index.js +13 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +13 -12
- package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
- package/plugins/ingestions/last30days-ingest/dist/index.js +13 -12
- package/plugins/ingestions/web-search/dist/index.js +11 -10
- package/plugins/kk/README.md +38 -0
- package/plugins/kk/decisions/extract/ai-news.yaml +21 -0
- package/plugins/kk/decisions/kk-items.yaml +113 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/schedules/cron.tmpl +15 -0
- package/plugins/kk/schedules/launchd.plist.tmpl +77 -0
- package/plugins/kk/scripts/daily-stages.ts +1533 -0
- package/plugins/kk/scripts/kk-workflow-stages.ts +268 -1228
- package/plugins/kk/skills/content-judge/SKILL.md +12 -2
- package/plugins/kk/skills/content-judge/references/rubrics.md +8 -2
- package/plugins/kk/skills/content-safety/SKILL.md +60 -18
- package/plugins/kk/skills/web-automation/SKILL.md +100 -0
- package/plugins/kk/skills/web-automation/references/browser-launch.md +86 -0
- package/plugins/kk/workflows/kk-collect.yaml +258 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +74 -11
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +655 -0
- package/plugins/kk/workflows/kk-daily-safety.yaml +170 -0
- package/plugins/publishings/emdash-pub/dist/index.js +10 -9
- package/plugins/publishings/infoq-pub/dist/index.js +8695 -211
- package/plugins/publishings/infoq-pub/package.json +3 -1
- package/plugins/publishings/infoq-pub/plugin.json +2 -1
- package/plugins/publishings/infoq-pub/src/index.ts +358 -29
- package/plugins/publishings/infoq-pub/src/profile-check.ts +21 -0
- package/plugins/publishings/juejin-pub/dist/index.js +8746 -211
- package/plugins/publishings/juejin-pub/package.json +3 -1
- package/plugins/publishings/juejin-pub/plugin.json +2 -1
- package/plugins/publishings/juejin-pub/src/index.ts +345 -29
- package/plugins/publishings/juejin-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/medium-pub/dist/index.js +10 -9
- package/plugins/publishings/podcast-pub/dist/index.js +10 -9
- package/plugins/publishings/qiita-pub/dist/index.js +10 -9
- package/plugins/publishings/substack-pub/dist/index.js +8687 -211
- package/plugins/publishings/substack-pub/package.json +3 -1
- package/plugins/publishings/substack-pub/plugin.json +2 -1
- package/plugins/publishings/substack-pub/src/index.ts +302 -29
- package/plugins/publishings/substack-pub/src/profile-check.ts +76 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +10 -9
- package/plugins/publishings/wechatmp-pub/dist/index.js +1315 -352
- package/plugins/publishings/wechatmp-pub/plugin.json +1 -0
- package/plugins/publishings/wechatmp-pub/src/enrich.ts +229 -0
- package/plugins/publishings/wechatmp-pub/src/flow.ts +896 -0
- package/plugins/publishings/wechatmp-pub/src/index.ts +223 -520
- package/plugins/publishings/wechatmp-pub/src/modes.ts +403 -0
- package/plugins/publishings/wechatmp-pub/src/page.ts +260 -0
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +84 -38
- package/plugins/publishings/x-pub/dist/index.js +8707 -223
- package/plugins/publishings/x-pub/package.json +3 -1
- package/plugins/publishings/x-pub/plugin.json +2 -1
- package/plugins/publishings/x-pub/src/index.ts +315 -23
- package/plugins/publishings/x-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/xhs-pub/dist/index.js +897 -354
- package/plugins/publishings/xhs-pub/plugin.json +2 -1
- package/plugins/publishings/xhs-pub/src/flow.ts +572 -0
- package/plugins/publishings/xhs-pub/src/index.ts +108 -596
- package/plugins/publishings/xhs-pub/src/modes.ts +300 -0
- package/plugins/publishings/xhs-pub/src/page.ts +254 -0
- package/plugins/publishings/xhs-pub/src/profile-check.ts +29 -17
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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();
|
|
@@ -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
|
|
29658
|
+
import { dirname as dirname2 } from "path";
|
|
29659
29659
|
import { parseArgs } from "util";
|
|
29660
29660
|
|
|
29661
29661
|
// ../../packages/kk-core/src/kinds.ts
|
|
@@ -29741,15 +29741,16 @@ var PluginManifestSchema = exports_external.looseObject({
|
|
|
29741
29741
|
name: exports_external.string().min(1),
|
|
29742
29742
|
kind: PluginKindSchema,
|
|
29743
29743
|
entry: exports_external.string().min(1),
|
|
29744
|
+
check: exports_external.string().min(1).optional(),
|
|
29744
29745
|
version: exports_external.string().optional(),
|
|
29745
29746
|
description: exports_external.string().optional(),
|
|
29746
29747
|
operations: exports_external.record(exports_external.string(), PluginOperationSchema).optional()
|
|
29747
29748
|
});
|
|
29748
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
29749
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
|
|
29749
29750
|
init_file_system_node();
|
|
29750
29751
|
init_config();
|
|
29751
29752
|
|
|
29752
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
29753
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/path.js
|
|
29753
29754
|
function normalizeSeparators(path6) {
|
|
29754
29755
|
return path6.replaceAll("\\", "/");
|
|
29755
29756
|
}
|
|
@@ -29807,7 +29808,7 @@ function getProcessCwd() {
|
|
|
29807
29808
|
return globalThis.process?.cwd?.() ?? "/";
|
|
29808
29809
|
}
|
|
29809
29810
|
|
|
29810
|
-
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.
|
|
29811
|
+
// ../../node_modules/.bun/@gobing-ai+ts-runtime@0.5.18+ea28d47622d670ff/node_modules/@gobing-ai/ts-runtime/dist/index.js
|
|
29811
29812
|
init_runtime_node_bun();
|
|
29812
29813
|
|
|
29813
29814
|
// ../../packages/kk-core/src/render-md.ts
|
|
@@ -29919,7 +29920,11 @@ async function trySelectors(page, selectors, options = {}) {
|
|
|
29919
29920
|
}
|
|
29920
29921
|
return { found: false, selector: null };
|
|
29921
29922
|
}
|
|
29922
|
-
|
|
29923
|
+
|
|
29924
|
+
// ../../packages/publish-harness/src/browser.ts
|
|
29925
|
+
import { resolve as resolve2 } from "path";
|
|
29926
|
+
|
|
29927
|
+
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/errors.js
|
|
29923
29928
|
class BrowserAutomationError extends Error {
|
|
29924
29929
|
constructor(message, options) {
|
|
29925
29930
|
super(message, options);
|
|
@@ -29968,11 +29973,10 @@ class OperationAbortedError extends BrowserAutomationError {
|
|
|
29968
29973
|
this.name = "OperationAbortedError";
|
|
29969
29974
|
}
|
|
29970
29975
|
}
|
|
29971
|
-
|
|
29972
|
-
// ../../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
|
|
29973
29977
|
import { mkdir } from "fs/promises";
|
|
29974
29978
|
|
|
29975
|
-
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.
|
|
29979
|
+
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/profile-dir.js
|
|
29976
29980
|
function resolveProfileDir(profileDir) {
|
|
29977
29981
|
if (profileDir.trim() === "") {
|
|
29978
29982
|
throw new InvalidProfileDirError;
|
|
@@ -29984,7 +29988,7 @@ function resolveProfileDir(profileDir) {
|
|
|
29984
29988
|
return resolved;
|
|
29985
29989
|
}
|
|
29986
29990
|
|
|
29987
|
-
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.
|
|
29991
|
+
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/launcher.js
|
|
29988
29992
|
var PROFILE_DIR_MODE = 448;
|
|
29989
29993
|
var BUSY_PROFILE_PATTERN = /singleton|already in use|in use by another/i;
|
|
29990
29994
|
var CHROMIUM_MISSING_PATTERN = /playwright install|executable doesn't exist/i;
|
|
@@ -30035,7 +30039,7 @@ function createPersistentLauncher(options = {}) {
|
|
|
30035
30039
|
};
|
|
30036
30040
|
}
|
|
30037
30041
|
|
|
30038
|
-
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.
|
|
30042
|
+
// ../../node_modules/.bun/@gobing-ai+ts-browser-automation@0.5.18+1f5782d4d327a230/node_modules/@gobing-ai/ts-browser-automation/dist/operations.js
|
|
30039
30043
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
30040
30044
|
var POLL_INTERVAL_MS = 250;
|
|
30041
30045
|
var defaultLauncher;
|
|
@@ -30044,12 +30048,6 @@ function getDefaultLauncher() {
|
|
|
30044
30048
|
defaultLauncher = createPersistentLauncher();
|
|
30045
30049
|
return defaultLauncher;
|
|
30046
30050
|
}
|
|
30047
|
-
async function openPersistentBrowser(options) {
|
|
30048
|
-
return (options.launcher ?? getDefaultLauncher()).launch({
|
|
30049
|
-
profileDir: resolveProfileDir(options.profileDir),
|
|
30050
|
-
headless: options.headless
|
|
30051
|
-
});
|
|
30052
|
-
}
|
|
30053
30051
|
async function withProfilePage(options, callback) {
|
|
30054
30052
|
assertNotAborted(options.signal);
|
|
30055
30053
|
if (options.isAuthenticated !== undefined)
|
|
@@ -30120,19 +30118,270 @@ async function pollPredicate(page, predicate, timeoutMs, signal) {
|
|
|
30120
30118
|
function sleep(ms) {
|
|
30121
30119
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
30122
30120
|
}
|
|
30121
|
+
// ../../packages/publish-harness/src/profile-dir.ts
|
|
30122
|
+
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
30123
|
+
async function ensureProfileDir2(profileDir, headless) {
|
|
30124
|
+
const existing = await stat(profileDir).catch(() => null);
|
|
30125
|
+
if (existing) {
|
|
30126
|
+
if (!existing.isDirectory())
|
|
30127
|
+
throw new InvalidProfileDirError(`profileDir is not a directory: "${profileDir}".`);
|
|
30128
|
+
return;
|
|
30129
|
+
}
|
|
30130
|
+
if (headless)
|
|
30131
|
+
throw new BrowserProfileMissingError(profileDir);
|
|
30132
|
+
await mkdir2(profileDir, { recursive: true, mode: 448 });
|
|
30133
|
+
}
|
|
30134
|
+
|
|
30135
|
+
// ../../packages/publish-harness/src/browser.ts
|
|
30136
|
+
var DEFAULT_CHECK_TIMEOUT_MS = 30000;
|
|
30137
|
+
var DEFAULT_STEP_TIMEOUT_MS = 30000;
|
|
30138
|
+
var DEFAULT_POLL_MS = 500;
|
|
30139
|
+
var ATTACH_POLL_MS = 100;
|
|
30140
|
+
function createKkBrowserLauncher(loadPlaywright = () => import("playwright")) {
|
|
30141
|
+
return {
|
|
30142
|
+
async launch(request) {
|
|
30143
|
+
if (request.profileDir.trim() === "")
|
|
30144
|
+
throw new InvalidProfileDirError;
|
|
30145
|
+
const profileDir = resolve2(request.profileDir);
|
|
30146
|
+
await ensureProfileDir2(profileDir, request.headless);
|
|
30147
|
+
let playwright;
|
|
30148
|
+
try {
|
|
30149
|
+
playwright = await loadPlaywright();
|
|
30150
|
+
} catch (error51) {
|
|
30151
|
+
throw new PlaywrightUnavailableError('The "playwright" dependency is not installed. Run `bunx playwright install chromium`.', { cause: error51 });
|
|
30152
|
+
}
|
|
30153
|
+
const channel = process.env.KK_BROWSER_CHANNEL?.trim() || "chromium";
|
|
30154
|
+
let context2;
|
|
30155
|
+
try {
|
|
30156
|
+
context2 = await playwright.chromium.launchPersistentContext(profileDir, {
|
|
30157
|
+
headless: request.headless,
|
|
30158
|
+
channel,
|
|
30159
|
+
args: ["--disable-blink-features=AutomationControlled"],
|
|
30160
|
+
ignoreDefaultArgs: ["--enable-automation"]
|
|
30161
|
+
});
|
|
30162
|
+
} catch (error51) {
|
|
30163
|
+
const message = errorMessage2(error51);
|
|
30164
|
+
if (/singleton|already in use|in use by another/i.test(message)) {
|
|
30165
|
+
throw new BrowserProfileBusyError(profileDir, error51);
|
|
30166
|
+
}
|
|
30167
|
+
if (/playwright install|executable doesn't exist/i.test(message)) {
|
|
30168
|
+
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 });
|
|
30169
|
+
}
|
|
30170
|
+
throw error51;
|
|
30171
|
+
}
|
|
30172
|
+
await context2.addInitScript(() => {
|
|
30173
|
+
Object.defineProperty(navigator, "webdriver", { get: () => {
|
|
30174
|
+
return;
|
|
30175
|
+
} });
|
|
30176
|
+
});
|
|
30177
|
+
return context2;
|
|
30178
|
+
}
|
|
30179
|
+
};
|
|
30180
|
+
}
|
|
30181
|
+
var kkBrowserLauncher = createKkBrowserLauncher();
|
|
30182
|
+
function resolveChannelProfileDir(channel, override) {
|
|
30183
|
+
const tiers = [override, process.env[`${channel.toUpperCase()}_PROFILE_DIR`], process.env.KK_BROWSER_PROFILE_DIR];
|
|
30184
|
+
const chosen = tiers.find((tier) => tier !== undefined && tier.trim().length > 0);
|
|
30185
|
+
return resolveBrowserProfileDir(channel, chosen);
|
|
30186
|
+
}
|
|
30187
|
+
async function checkChannelSession(config3, opts) {
|
|
30188
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
30189
|
+
const passes = opts.headedOnly ? [false] : opts.headedFallback === false ? [true] : [true, false];
|
|
30190
|
+
let lastDetail = `${config3.channel} readiness was never probed`;
|
|
30191
|
+
for (const headless of passes) {
|
|
30192
|
+
try {
|
|
30193
|
+
await withProfilePage({
|
|
30194
|
+
profileDir: opts.profileDir,
|
|
30195
|
+
headless,
|
|
30196
|
+
url: config3.homeUrl,
|
|
30197
|
+
isAuthenticated: config3.isAuthenticated,
|
|
30198
|
+
timeoutMs,
|
|
30199
|
+
launcher: opts.launcher ?? kkBrowserLauncher
|
|
30200
|
+
}, async () => true);
|
|
30201
|
+
return {
|
|
30202
|
+
status: "verified",
|
|
30203
|
+
profileDir: opts.profileDir,
|
|
30204
|
+
detail: `authenticated ${config3.channel} session observed ${headless ? "headlessly" : "via a headed pass"} via profile ${opts.profileDir} (read-only)`
|
|
30205
|
+
};
|
|
30206
|
+
} catch (error51) {
|
|
30207
|
+
if (error51 instanceof BrowserProfileMissingError) {
|
|
30208
|
+
return {
|
|
30209
|
+
status: "refused",
|
|
30210
|
+
profileDir: opts.profileDir,
|
|
30211
|
+
detail: `no ${config3.channel} browser profile at ${opts.profileDir} \u2014 run the login command first`
|
|
30212
|
+
};
|
|
30213
|
+
}
|
|
30214
|
+
lastDetail = `${headless ? "headless" : "headed"} ${config3.channel} readiness refused: ${errorMessage2(error51)}`;
|
|
30215
|
+
}
|
|
30216
|
+
}
|
|
30217
|
+
return { status: "refused", profileDir: opts.profileDir, detail: lastDetail };
|
|
30218
|
+
}
|
|
30219
|
+
async function waitUntil(predicate, timeoutMs, pollMs = DEFAULT_POLL_MS) {
|
|
30220
|
+
const deadline = Date.now() + timeoutMs;
|
|
30221
|
+
while (Date.now() < deadline) {
|
|
30222
|
+
if (await predicate()) {
|
|
30223
|
+
return true;
|
|
30224
|
+
}
|
|
30225
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollMs));
|
|
30226
|
+
}
|
|
30227
|
+
return predicate();
|
|
30228
|
+
}
|
|
30229
|
+
async function pasteHtml(page, selector, html, opts = {}) {
|
|
30230
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
|
|
30231
|
+
await requireAttached(page, selector, "pasteHtml", timeoutMs);
|
|
30232
|
+
await page.evaluate(pasteScript(selector, html));
|
|
30233
|
+
const filled = await waitUntil(async () => String(await page.evaluate(editorTextScript(selector))).length > 0, timeoutMs, ATTACH_POLL_MS);
|
|
30234
|
+
if (!filled) {
|
|
30235
|
+
throw new Error(`pasteHtml: ${selector}: editor text is empty ${timeoutMs}ms after the paste`);
|
|
30236
|
+
}
|
|
30237
|
+
}
|
|
30238
|
+
async function uploadFile(page, selector, filePath, opts = {}) {
|
|
30239
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
|
|
30240
|
+
await requireAttached(page, selector, "uploadFile", timeoutMs);
|
|
30241
|
+
await page.setInputFiles(selector, filePath);
|
|
30242
|
+
}
|
|
30243
|
+
async function expectOutcome(probe, opts) {
|
|
30244
|
+
let evidence = "";
|
|
30245
|
+
let lastProbeError = null;
|
|
30246
|
+
const observed = await waitUntil(async () => {
|
|
30247
|
+
let result;
|
|
30248
|
+
try {
|
|
30249
|
+
result = await probe();
|
|
30250
|
+
} catch (error51) {
|
|
30251
|
+
lastProbeError = error51 instanceof Error ? error51.message : String(error51);
|
|
30252
|
+
return false;
|
|
30253
|
+
}
|
|
30254
|
+
if (result === null || result.length === 0) {
|
|
30255
|
+
return false;
|
|
30256
|
+
}
|
|
30257
|
+
evidence = result;
|
|
30258
|
+
return true;
|
|
30259
|
+
}, opts.timeoutMs, opts.pollMs ?? DEFAULT_POLL_MS);
|
|
30260
|
+
if (observed && evidence.length > 0) {
|
|
30261
|
+
return { ok: true, evidence };
|
|
30262
|
+
}
|
|
30263
|
+
const detail = lastProbeError === null ? "" : ` (last probe error: ${lastProbeError})`;
|
|
30264
|
+
return { ok: false, error: `${opts.label}: no outcome observed within ${opts.timeoutMs}ms${detail}` };
|
|
30265
|
+
}
|
|
30266
|
+
var DEFAULT_SIGNAL_CONTAINERS = [
|
|
30267
|
+
'[class*="toast" i]',
|
|
30268
|
+
'[class*="notification" i]',
|
|
30269
|
+
'[role="alert"]',
|
|
30270
|
+
'[class*="message" i]'
|
|
30271
|
+
];
|
|
30272
|
+
async function readScopedSignal(page, texts, containers = DEFAULT_SIGNAL_CONTAINERS) {
|
|
30273
|
+
for (const text of texts) {
|
|
30274
|
+
for (const container of containers) {
|
|
30275
|
+
const locator = page.locator(`${container} :text("${text}")`).first();
|
|
30276
|
+
if (await locator.count().catch(() => 0) === 0)
|
|
30277
|
+
continue;
|
|
30278
|
+
if (!await locator.isVisible().catch(() => false))
|
|
30279
|
+
continue;
|
|
30280
|
+
const observed = (await locator.textContent().catch(() => null))?.trim();
|
|
30281
|
+
return observed !== undefined && observed.length > 0 ? observed : text;
|
|
30282
|
+
}
|
|
30283
|
+
}
|
|
30284
|
+
return null;
|
|
30285
|
+
}
|
|
30286
|
+
var cdpNodeText = (node) => node.nodeName === "#text" ? node.nodeValue ?? "" : (node.children ?? []).map(cdpNodeText).join("");
|
|
30287
|
+
async function findShadowControl(session, labels) {
|
|
30288
|
+
const document = await session.send("DOM.getDocument", { depth: -1, pierce: true });
|
|
30289
|
+
if (!document.root)
|
|
30290
|
+
return null;
|
|
30291
|
+
const wanted = new Set(labels);
|
|
30292
|
+
const findNode = (node, insideShadow) => {
|
|
30293
|
+
if (insideShadow && node.nodeName === "BUTTON" && typeof node.backendNodeId === "number") {
|
|
30294
|
+
const label = cdpNodeText(node).replace(/\s+/g, "");
|
|
30295
|
+
if (wanted.has(label))
|
|
30296
|
+
return { label, backendNodeId: node.backendNodeId };
|
|
30297
|
+
}
|
|
30298
|
+
for (const shadowRoot of node.shadowRoots ?? []) {
|
|
30299
|
+
const hit2 = findNode(shadowRoot, true);
|
|
30300
|
+
if (hit2)
|
|
30301
|
+
return hit2;
|
|
30302
|
+
}
|
|
30303
|
+
for (const child of node.children ?? []) {
|
|
30304
|
+
const hit2 = findNode(child, insideShadow);
|
|
30305
|
+
if (hit2)
|
|
30306
|
+
return hit2;
|
|
30307
|
+
}
|
|
30308
|
+
return null;
|
|
30309
|
+
};
|
|
30310
|
+
const hit = findNode(document.root, false);
|
|
30311
|
+
if (hit === null)
|
|
30312
|
+
return null;
|
|
30313
|
+
const box = await session.send("DOM.getBoxModel", { backendNodeId: hit.backendNodeId });
|
|
30314
|
+
const quad = box.model?.content;
|
|
30315
|
+
if (quad === undefined || quad.length < 8)
|
|
30316
|
+
return null;
|
|
30317
|
+
const [x1 = 0, y1 = 0, x3 = 0, , , y4 = 0] = quad;
|
|
30318
|
+
return { label: hit.label, x: Math.round((x1 + x3) / 2), y: Math.round((y1 + y4) / 2) };
|
|
30319
|
+
}
|
|
30320
|
+
async function createShadowFinder(context2, page) {
|
|
30321
|
+
if (typeof context2.newCDPSession !== "function")
|
|
30322
|
+
return;
|
|
30323
|
+
try {
|
|
30324
|
+
const session = await context2.newCDPSession(page);
|
|
30325
|
+
await session.send("DOM.enable");
|
|
30326
|
+
return (labels) => findShadowControl(session, labels);
|
|
30327
|
+
} catch {
|
|
30328
|
+
return;
|
|
30329
|
+
}
|
|
30330
|
+
}
|
|
30331
|
+
var markdownToHtml = (markdown) => Bun.markdown.html(markdown);
|
|
30332
|
+
async function requireAttached(page, selector, action, timeoutMs) {
|
|
30333
|
+
await resolveSelector(page, [selector], { timeoutMs, label: action });
|
|
30334
|
+
}
|
|
30335
|
+
async function resolveSelector(page, selectors, opts = {}) {
|
|
30336
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
|
|
30337
|
+
const found = { selector: null };
|
|
30338
|
+
const attached = await waitUntil(async () => {
|
|
30339
|
+
const result = await trySelectors(page, selectors, { visible: false });
|
|
30340
|
+
found.selector = result.selector;
|
|
30341
|
+
return result.found && result.selector !== null;
|
|
30342
|
+
}, timeoutMs, ATTACH_POLL_MS);
|
|
30343
|
+
const selector = found.selector;
|
|
30344
|
+
if (!attached || selector === null) {
|
|
30345
|
+
throw new Error(`${opts.label ?? "selector"}: ${selectors.join(" | ")} not attached within ${timeoutMs}ms`);
|
|
30346
|
+
}
|
|
30347
|
+
return selector;
|
|
30348
|
+
}
|
|
30349
|
+
function errorMessage2(error51) {
|
|
30350
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
30351
|
+
}
|
|
30352
|
+
function pasteScript(selector, html) {
|
|
30353
|
+
const text = html.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
30354
|
+
return `(() => {
|
|
30355
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
30356
|
+
if (!el) return false;
|
|
30357
|
+
const data = new DataTransfer();
|
|
30358
|
+
data.setData('text/html', ${JSON.stringify(html)});
|
|
30359
|
+
data.setData('text/plain', ${JSON.stringify(text)});
|
|
30360
|
+
el.focus();
|
|
30361
|
+
el.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true }));
|
|
30362
|
+
return true;
|
|
30363
|
+
})()`;
|
|
30364
|
+
}
|
|
30365
|
+
function editorTextScript(selector) {
|
|
30366
|
+
return `(() => {
|
|
30367
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
30368
|
+
return el ? (el.textContent ?? '').trim() : '';
|
|
30369
|
+
})()`;
|
|
30370
|
+
}
|
|
30371
|
+
|
|
30123
30372
|
// ../../plugins/publishings/xhs-pub/src/index.ts
|
|
30124
30373
|
init_dist();
|
|
30125
30374
|
|
|
30126
30375
|
// ../../plugins/publishings/xhs-pub/src/automation-guard.ts
|
|
30127
30376
|
import { homedir as homedir2 } from "os";
|
|
30128
|
-
import { join as join2, resolve as
|
|
30377
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
30129
30378
|
var fs = createNodeFileSystem();
|
|
30130
30379
|
var CAP_WINDOW_MS = 2 * 60 * 60 * 1000;
|
|
30131
30380
|
var AUTOMATION_STATE_ENV = "KK_XHS_AUTOMATION_STATE";
|
|
30132
30381
|
function resolveAutomationStatePath(override) {
|
|
30133
30382
|
const explicit = override ?? process.env[AUTOMATION_STATE_ENV];
|
|
30134
30383
|
if (explicit !== undefined && explicit !== "")
|
|
30135
|
-
return
|
|
30384
|
+
return resolve3(explicit);
|
|
30136
30385
|
return join2(process.env.HOME ?? homedir2(), ".config", "kk", "xhs-automation.json");
|
|
30137
30386
|
}
|
|
30138
30387
|
function resetHint(path7) {
|
|
@@ -30195,48 +30444,86 @@ function createAutomationGuard(options) {
|
|
|
30195
30444
|
};
|
|
30196
30445
|
}
|
|
30197
30446
|
|
|
30198
|
-
// ../../plugins/publishings/xhs-pub/src/
|
|
30199
|
-
|
|
30447
|
+
// ../../plugins/publishings/xhs-pub/src/modes.ts
|
|
30448
|
+
import { isAbsolute } from "path";
|
|
30200
30449
|
var TARGET = "xhs";
|
|
30201
30450
|
var PUBLISH_URL = "https://creator.xiaohongshu.com/publish/publish";
|
|
30202
30451
|
var PUBLISHED_MARKER = "published=true";
|
|
30203
|
-
var
|
|
30204
|
-
var
|
|
30205
|
-
|
|
30206
|
-
|
|
30207
|
-
|
|
30208
|
-
|
|
30209
|
-
|
|
30210
|
-
|
|
30211
|
-
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30219
|
-
|
|
30220
|
-
|
|
30221
|
-
|
|
30222
|
-
|
|
30223
|
-
var
|
|
30224
|
-
|
|
30225
|
-
|
|
30452
|
+
var XHS_OPERATIONS = ["\u4E0A\u4F20\u56FE\u6587", "\u4E0A\u4F20\u89C6\u9891", "\u5199\u957F\u6587", "\u53D1\u64AD\u5BA2"];
|
|
30453
|
+
var DEFAULT_XHS_OPERATION = "\u4E0A\u4F20\u56FE\u6587";
|
|
30454
|
+
function resolveXhsOperation(options, content) {
|
|
30455
|
+
const raw = options?.operation ?? content?.metadata?.operation;
|
|
30456
|
+
if (raw === undefined)
|
|
30457
|
+
return { operation: DEFAULT_XHS_OPERATION };
|
|
30458
|
+
if (typeof raw !== "string" || !XHS_OPERATIONS.includes(raw)) {
|
|
30459
|
+
return {
|
|
30460
|
+
error: `unsupported operation '${String(raw)}' for ${TARGET}: expected one of ${XHS_OPERATIONS.join(", ")}`
|
|
30461
|
+
};
|
|
30462
|
+
}
|
|
30463
|
+
return { operation: raw };
|
|
30464
|
+
}
|
|
30465
|
+
var IMAGE_EXTENSIONS = /\.(png|jpe?g|webp)$/i;
|
|
30466
|
+
var IMAGE_LIST = "png|jpg|jpeg|webp";
|
|
30467
|
+
var VIDEO_EXTENSIONS = /\.(mp4|mov|flv|f4v|mkv|rm|rmvb|m4v|mpg|mpeg|ts)$/i;
|
|
30468
|
+
var VIDEO_LIST = "mp4|mov|flv|f4v|mkv|rm|rmvb|m4v|mpg|mpeg|ts";
|
|
30469
|
+
var AUDIO_EXTENSIONS = /\.(mp3|wav|aac|flac|m4a)$/i;
|
|
30470
|
+
var AUDIO_LIST = "mp3|wav|aac|flac|m4a";
|
|
30471
|
+
var XHS_IMAGE_MAX = 18;
|
|
30472
|
+
var XHS_NOTE_TITLE_MAX = 20;
|
|
30473
|
+
var XHS_NOTE_BODY_MAX = 1000;
|
|
30474
|
+
var MODE_LIMITS = {
|
|
30475
|
+
\u{4e0a}\u{4f20}\u{56fe}\u{6587}: { imageMax: XHS_IMAGE_MAX, titleMax: XHS_NOTE_TITLE_MAX, bodyMax: XHS_NOTE_BODY_MAX },
|
|
30476
|
+
\u{4e0a}\u{4f20}\u{89c6}\u{9891}: { titleMax: XHS_NOTE_TITLE_MAX },
|
|
30477
|
+
\u{5199}\u{957f}\u{6587}: {},
|
|
30478
|
+
\u{53d1}\u{64ad}\u{5ba2}: {}
|
|
30226
30479
|
};
|
|
30227
|
-
function
|
|
30228
|
-
|
|
30480
|
+
function artifactPaths(input) {
|
|
30481
|
+
switch (input.operation) {
|
|
30482
|
+
case "\u4E0A\u4F20\u56FE\u6587":
|
|
30483
|
+
return input.images;
|
|
30484
|
+
case "\u4E0A\u4F20\u89C6\u9891":
|
|
30485
|
+
return [input.video];
|
|
30486
|
+
case "\u5199\u957F\u6587":
|
|
30487
|
+
return [];
|
|
30488
|
+
case "\u53D1\u64AD\u5BA2":
|
|
30489
|
+
return [input.audio, input.cover];
|
|
30490
|
+
}
|
|
30229
30491
|
}
|
|
30230
|
-
async function
|
|
30231
|
-
const
|
|
30232
|
-
|
|
30233
|
-
|
|
30492
|
+
async function resolveMediaPath(content, spec) {
|
|
30493
|
+
const raw = content.metadata?.[spec.key];
|
|
30494
|
+
const path7 = typeof raw === "string" ? raw : typeof raw?.path === "string" ? raw.path : undefined;
|
|
30495
|
+
if (path7 === undefined || path7.length === 0) {
|
|
30496
|
+
return {
|
|
30497
|
+
error: `${spec.label}: Content.metadata.${spec.key} is required (absolute path to an existing ${spec.list} file)`
|
|
30498
|
+
};
|
|
30234
30499
|
}
|
|
30500
|
+
if (!isAbsolute(path7)) {
|
|
30501
|
+
return { error: `${spec.label}: Content.metadata.${spec.key} must be absolute, got '${path7}'` };
|
|
30502
|
+
}
|
|
30503
|
+
if (!spec.extensions.test(path7)) {
|
|
30504
|
+
return {
|
|
30505
|
+
error: `${spec.label}: Content.metadata.${spec.key} has unsupported extension (expected ${spec.list}): '${path7}'`
|
|
30506
|
+
};
|
|
30507
|
+
}
|
|
30508
|
+
if (!await createNodeFileSystem().exists(path7)) {
|
|
30509
|
+
return { error: `${spec.label}: Content.metadata.${spec.key} does not exist: '${path7}'` };
|
|
30510
|
+
}
|
|
30511
|
+
return { path: path7 };
|
|
30512
|
+
}
|
|
30513
|
+
async function validatePublishInput(content) {
|
|
30514
|
+
const status = validatePublishStatus(content);
|
|
30515
|
+
if (status.error)
|
|
30516
|
+
return { error: status.error };
|
|
30235
30517
|
const images = content.metadata?.images;
|
|
30236
30518
|
if (!Array.isArray(images) || images.length === 0) {
|
|
30237
30519
|
return { error: "images validation: metadata.images must be a non-empty ordered array" };
|
|
30238
30520
|
}
|
|
30239
|
-
const
|
|
30521
|
+
const max = XHS_IMAGE_MAX;
|
|
30522
|
+
if (images.length > max) {
|
|
30523
|
+
return {
|
|
30524
|
+
error: `images validation: metadata.images holds ${images.length} entries but the \u4E0A\u4F20\u56FE\u6587 grid accepts \u2264${max}`
|
|
30525
|
+
};
|
|
30526
|
+
}
|
|
30240
30527
|
const paths = [];
|
|
30241
30528
|
for (const [i2, image] of images.entries()) {
|
|
30242
30529
|
const path7 = image?.path;
|
|
@@ -30246,20 +30533,154 @@ async function validatePublishInput(content) {
|
|
|
30246
30533
|
if (!isAbsolute(path7)) {
|
|
30247
30534
|
return { error: `images validation: images[${i2}].path must be absolute, got '${path7}'` };
|
|
30248
30535
|
}
|
|
30249
|
-
if (
|
|
30536
|
+
if (!IMAGE_EXTENSIONS.test(path7)) {
|
|
30250
30537
|
return {
|
|
30251
|
-
error: `images validation: images[${i2}].path has unsupported extension (expected
|
|
30538
|
+
error: `images validation: images[${i2}].path has unsupported extension (expected ${IMAGE_LIST}): '${path7}'`
|
|
30252
30539
|
};
|
|
30253
30540
|
}
|
|
30254
|
-
if (!await
|
|
30541
|
+
if (!await createNodeFileSystem().exists(path7)) {
|
|
30255
30542
|
return { error: `images validation: images[${i2}].path does not exist: '${path7}'` };
|
|
30256
30543
|
}
|
|
30257
30544
|
paths.push(path7);
|
|
30258
30545
|
}
|
|
30259
30546
|
return { paths };
|
|
30260
30547
|
}
|
|
30548
|
+
function validatePublishStatus(content) {
|
|
30549
|
+
const status = content.metadata?.publishStatus;
|
|
30550
|
+
if (status !== undefined && status !== "draft" && status !== "public") {
|
|
30551
|
+
return {
|
|
30552
|
+
error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
|
|
30553
|
+
};
|
|
30554
|
+
}
|
|
30555
|
+
return {};
|
|
30556
|
+
}
|
|
30557
|
+
function validateTitle(content, operation) {
|
|
30558
|
+
const max = MODE_LIMITS[operation].titleMax;
|
|
30559
|
+
if (max === undefined)
|
|
30560
|
+
return {};
|
|
30561
|
+
const title = content.title ?? "";
|
|
30562
|
+
if (title.length > max) {
|
|
30563
|
+
return {
|
|
30564
|
+
error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${max}`
|
|
30565
|
+
};
|
|
30566
|
+
}
|
|
30567
|
+
return {};
|
|
30568
|
+
}
|
|
30569
|
+
function validateBody(content, operation) {
|
|
30570
|
+
const max = MODE_LIMITS[operation].bodyMax;
|
|
30571
|
+
if (max === undefined)
|
|
30572
|
+
return {};
|
|
30573
|
+
if (content.body.length > max) {
|
|
30574
|
+
return {
|
|
30575
|
+
error: `body validation: Content.body is ${content.body.length} chars but ${operation} accepts \u2264${max}`
|
|
30576
|
+
};
|
|
30577
|
+
}
|
|
30578
|
+
return {};
|
|
30579
|
+
}
|
|
30580
|
+
async function validateModeInput(operation, content) {
|
|
30581
|
+
const status = validatePublishStatus(content);
|
|
30582
|
+
if (status.error)
|
|
30583
|
+
return { error: status.error };
|
|
30584
|
+
switch (operation) {
|
|
30585
|
+
case "\u4E0A\u4F20\u56FE\u6587": {
|
|
30586
|
+
const title = validateTitle(content, operation);
|
|
30587
|
+
if (title.error)
|
|
30588
|
+
return { error: title.error };
|
|
30589
|
+
const body = validateBody(content, operation);
|
|
30590
|
+
if (body.error)
|
|
30591
|
+
return { error: body.error };
|
|
30592
|
+
const images = await validatePublishInput(content);
|
|
30593
|
+
if (images.error)
|
|
30594
|
+
return { error: images.error };
|
|
30595
|
+
return { input: { operation, images: images.paths ?? [] } };
|
|
30596
|
+
}
|
|
30597
|
+
case "\u4E0A\u4F20\u89C6\u9891": {
|
|
30598
|
+
const title = validateTitle(content, operation);
|
|
30599
|
+
if (title.error)
|
|
30600
|
+
return { error: title.error };
|
|
30601
|
+
const video = await resolveMediaPath(content, {
|
|
30602
|
+
key: "video",
|
|
30603
|
+
label: "video validation",
|
|
30604
|
+
extensions: VIDEO_EXTENSIONS,
|
|
30605
|
+
list: VIDEO_LIST
|
|
30606
|
+
});
|
|
30607
|
+
if (video.error)
|
|
30608
|
+
return { error: video.error };
|
|
30609
|
+
return { input: { operation, video: video.path ?? "" } };
|
|
30610
|
+
}
|
|
30611
|
+
case "\u5199\u957F\u6587": {
|
|
30612
|
+
if (content.body.trim().length === 0) {
|
|
30613
|
+
return { error: "body validation: Content.body must be non-empty for \u5199\u957F\u6587 (authored in place)" };
|
|
30614
|
+
}
|
|
30615
|
+
return { input: { operation } };
|
|
30616
|
+
}
|
|
30617
|
+
case "\u53D1\u64AD\u5BA2": {
|
|
30618
|
+
const audio = await resolveMediaPath(content, {
|
|
30619
|
+
key: "audio",
|
|
30620
|
+
label: "audio validation",
|
|
30621
|
+
extensions: AUDIO_EXTENSIONS,
|
|
30622
|
+
list: AUDIO_LIST
|
|
30623
|
+
});
|
|
30624
|
+
if (audio.error)
|
|
30625
|
+
return { error: audio.error };
|
|
30626
|
+
const cover = await resolveMediaPath(content, {
|
|
30627
|
+
key: "coverImage",
|
|
30628
|
+
label: "cover validation",
|
|
30629
|
+
extensions: IMAGE_EXTENSIONS,
|
|
30630
|
+
list: IMAGE_LIST
|
|
30631
|
+
});
|
|
30632
|
+
if (cover.error)
|
|
30633
|
+
return { error: cover.error };
|
|
30634
|
+
return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
|
|
30635
|
+
}
|
|
30636
|
+
}
|
|
30637
|
+
}
|
|
30638
|
+
|
|
30639
|
+
// ../../plugins/publishings/xhs-pub/src/page.ts
|
|
30640
|
+
var POLL_MS = 500;
|
|
30641
|
+
var STEP_TIMEOUT_MS = 30000;
|
|
30642
|
+
var CREATOR_TAB_BAR = "div.creator-tab";
|
|
30643
|
+
var CREATOR_TAB_COUNT = 4;
|
|
30644
|
+
function creatorTabSelector(label) {
|
|
30645
|
+
return `${CREATOR_TAB_BAR}:has-text("${label}")`;
|
|
30646
|
+
}
|
|
30647
|
+
async function clickCreatorTab(page, label, opts = {}) {
|
|
30648
|
+
const timeoutMs = opts.timeoutMs ?? STEP_TIMEOUT_MS;
|
|
30649
|
+
const pollMs = opts.pollMs ?? POLL_MS;
|
|
30650
|
+
const onScreenCount = () => page.countOnScreen(CREATOR_TAB_BAR).catch(() => 0);
|
|
30651
|
+
const mounted = await waitUntil(() => onScreenCount().then((count2) => count2 >= CREATOR_TAB_COUNT), timeoutMs, pollMs);
|
|
30652
|
+
if (!mounted) {
|
|
30653
|
+
const observed = await onScreenCount();
|
|
30654
|
+
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`);
|
|
30655
|
+
}
|
|
30656
|
+
if (!page.clickInViewport) {
|
|
30657
|
+
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`);
|
|
30658
|
+
}
|
|
30659
|
+
const clicked = await page.clickInViewport(creatorTabSelector(label));
|
|
30660
|
+
if (!clicked) {
|
|
30661
|
+
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`);
|
|
30662
|
+
}
|
|
30663
|
+
}
|
|
30261
30664
|
function wrapPlaywrightPage(raw, shadow) {
|
|
30262
30665
|
const pw = raw;
|
|
30666
|
+
const onScreen = async (selector) => {
|
|
30667
|
+
const viewport = pw.viewportSize?.() ?? null;
|
|
30668
|
+
if (viewport === null)
|
|
30669
|
+
return [];
|
|
30670
|
+
const total = await pw.locator(selector).count();
|
|
30671
|
+
const found = [];
|
|
30672
|
+
for (let index = 0;index < total; index += 1) {
|
|
30673
|
+
const candidate = pw.locator(selector).nth(index);
|
|
30674
|
+
const box = await candidate.boundingBox().catch(() => null);
|
|
30675
|
+
if (!box || box.width === 0 || box.height === 0)
|
|
30676
|
+
continue;
|
|
30677
|
+
const outside = box.x < 0 || box.y < 0 || box.x >= viewport.width || box.y >= viewport.height;
|
|
30678
|
+
if (outside)
|
|
30679
|
+
continue;
|
|
30680
|
+
found.push({ index, box });
|
|
30681
|
+
}
|
|
30682
|
+
return found;
|
|
30683
|
+
};
|
|
30263
30684
|
return {
|
|
30264
30685
|
goto: (url2, options) => pw.goto(url2, options),
|
|
30265
30686
|
locator: (selector) => ({
|
|
@@ -30279,86 +30700,386 @@ function wrapPlaywrightPage(raw, shadow) {
|
|
|
30279
30700
|
setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
|
|
30280
30701
|
waitForSelector: (selector, options) => pw.waitForSelector(selector, options),
|
|
30281
30702
|
url: () => pw.url(),
|
|
30703
|
+
evaluate: (expression) => pw.evaluate(expression),
|
|
30704
|
+
countOnScreen: async (selector) => (await onScreen(selector)).length,
|
|
30282
30705
|
clickInViewport: async (selector) => {
|
|
30283
|
-
const
|
|
30284
|
-
if (
|
|
30706
|
+
const [candidate] = await onScreen(selector);
|
|
30707
|
+
if (candidate === undefined)
|
|
30285
30708
|
return false;
|
|
30286
|
-
|
|
30287
|
-
|
|
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;
|
|
30709
|
+
await pw.locator(selector).nth(candidate.index).click({ force: true });
|
|
30710
|
+
return true;
|
|
30299
30711
|
},
|
|
30300
30712
|
findShadowControl: async (labels) => shadow ? shadow.findControl(labels) : null,
|
|
30301
30713
|
clickAt: (x, y) => pw.mouse.click(x, y)
|
|
30302
30714
|
};
|
|
30303
30715
|
}
|
|
30304
|
-
async function
|
|
30305
|
-
const
|
|
30306
|
-
|
|
30307
|
-
|
|
30308
|
-
|
|
30309
|
-
|
|
30310
|
-
|
|
30311
|
-
|
|
30312
|
-
|
|
30313
|
-
|
|
30314
|
-
|
|
30315
|
-
|
|
30316
|
-
|
|
30317
|
-
|
|
30318
|
-
|
|
30319
|
-
|
|
30320
|
-
|
|
30321
|
-
if (wanted.has(label) && typeof node.backendNodeId === "number") {
|
|
30322
|
-
hits.push({ label, backendNodeId: node.backendNodeId });
|
|
30323
|
-
return;
|
|
30324
|
-
}
|
|
30716
|
+
async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
|
|
30717
|
+
const deadline = Date.now() + timeoutMs;
|
|
30718
|
+
while (Date.now() < deadline) {
|
|
30719
|
+
const found = await trySelectors(page, selectors);
|
|
30720
|
+
if (found.found && found.selector)
|
|
30721
|
+
return found.selector;
|
|
30722
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_MS));
|
|
30723
|
+
}
|
|
30724
|
+
throw new Error(`${what}: no matching element observed`);
|
|
30725
|
+
}
|
|
30726
|
+
async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
|
|
30727
|
+
const deadline = Date.now() + timeoutMs;
|
|
30728
|
+
while (Date.now() < deadline) {
|
|
30729
|
+
for (const selector of selectors) {
|
|
30730
|
+
const attached = await page.locator(selector).count().catch(() => 0);
|
|
30731
|
+
if (attached > 0)
|
|
30732
|
+
return selector;
|
|
30325
30733
|
}
|
|
30326
|
-
|
|
30327
|
-
|
|
30328
|
-
|
|
30329
|
-
|
|
30734
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_MS));
|
|
30735
|
+
}
|
|
30736
|
+
throw new Error(`${what}: no matching element observed`);
|
|
30737
|
+
}
|
|
30738
|
+
|
|
30739
|
+
// ../../plugins/publishings/xhs-pub/src/flow.ts
|
|
30740
|
+
var NOTE_TITLE = [
|
|
30741
|
+
'input.d-text[placeholder="\u586B\u5199\u6807\u9898\u4F1A\u6709\u66F4\u591A\u8D5E\u54E6"]',
|
|
30742
|
+
'input[placeholder*="\u6807\u9898"]',
|
|
30743
|
+
"#title-textarea input"
|
|
30744
|
+
];
|
|
30745
|
+
var NOTE_BODY = [
|
|
30746
|
+
'div.tiptap[contenteditable="true"]',
|
|
30747
|
+
'div[contenteditable="true"].ql-editor',
|
|
30748
|
+
"#post-textarea",
|
|
30749
|
+
".ql-editor"
|
|
30750
|
+
];
|
|
30751
|
+
var NOTE_THUMBNAIL = ["img.preivew-image", ".img-preview img"];
|
|
30752
|
+
var IMAGE_FILE_INPUT = [
|
|
30753
|
+
'input[type="file"][accept*=".jpg"]',
|
|
30754
|
+
'input[type="file"].upload-input',
|
|
30755
|
+
'input[type="file"]'
|
|
30756
|
+
];
|
|
30757
|
+
var VIDEO_FILE_INPUT = ['input[type="file"][accept*=".mp4"]', 'input[type="file"].upload-input'];
|
|
30758
|
+
var VIDEO_EDITOR = ["div.tiptap.ProseMirror", 'div.tiptap[contenteditable="true"]', ".ProseMirror"];
|
|
30759
|
+
var ARTICLE_NEW = ["text=\u65B0\u7684\u521B\u4F5C", 'button:has-text("\u65B0\u7684\u521B\u4F5C")'];
|
|
30760
|
+
var ARTICLE_EDITOR = ["div.tiptap.ProseMirror.ProseMirror-focused", "div.tiptap.ProseMirror", ".ProseMirror"];
|
|
30761
|
+
var ARTICLE_LAYOUT = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
|
|
30762
|
+
var PODCAST_OPEN = ["text=\u4E0A\u4F20\u97F3\u9891", 'button:has-text("\u4E0A\u4F20\u97F3\u9891")'];
|
|
30763
|
+
var PODCAST_MODAL = ["div.creator-modal-sty", ".d-modal.d-modal-centered", ".d-modal"];
|
|
30764
|
+
var PODCAST_AUDIO_INPUT = ['input[type="file"][accept*=".mp3"]', 'input[type="file"][accept*=".wav"]'];
|
|
30765
|
+
var PODCAST_COVER_INPUT = ['input[type="file"][accept*=".jpg"]', 'input[type="file"][accept*=".png"]'];
|
|
30766
|
+
var PODCAST_MODAL_ADVANCE = ["\u53BB\u53D1\u5E03"];
|
|
30767
|
+
var DRAFT_LABELS = ["\u6682\u5B58\u79BB\u5F00", "\u5B58\u8349\u7A3F", "\u4FDD\u5B58\u8349\u7A3F"];
|
|
30768
|
+
var PUBLISH_LABELS = ["\u53D1\u5E03"];
|
|
30769
|
+
var PUBLISH_CONFIRM = ["\u53D1\u5E03\u6210\u529F", "\u5DF2\u53D1\u5E03"];
|
|
30770
|
+
var DRAFT_TOASTS = ["\u4FDD\u5B58\u6210\u529F"];
|
|
30771
|
+
var SHADOW_BAR = "the xhs publish bar (closed shadow root)";
|
|
30772
|
+
var MODE_ACTION_PATHS = {
|
|
30773
|
+
\u{4e0a}\u{4f20}\u{56fe}\u{6587}: ["shadow"],
|
|
30774
|
+
\u{4e0a}\u{4f20}\u{89c6}\u{9891}: ["shadow"],
|
|
30775
|
+
\u{5199}\u{957f}\u{6587}: ["light", "shadow"],
|
|
30776
|
+
\u{53d1}\u{64ad}\u{5ba2}: ["shadow"]
|
|
30777
|
+
};
|
|
30778
|
+
function fail(error51) {
|
|
30779
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
|
|
30780
|
+
}
|
|
30781
|
+
function sleep2(ms) {
|
|
30782
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
30783
|
+
}
|
|
30784
|
+
function asPlaywrightPage(page) {
|
|
30785
|
+
return page;
|
|
30786
|
+
}
|
|
30787
|
+
async function enterMode(deps, label) {
|
|
30788
|
+
await clickCreatorTab(deps.page, label, { timeoutMs: deps.stepTimeoutMs });
|
|
30789
|
+
}
|
|
30790
|
+
async function clickShadowControl(page, labels) {
|
|
30791
|
+
if (!page.findShadowControl || !page.clickAt) {
|
|
30792
|
+
return {
|
|
30793
|
+
ok: false,
|
|
30794
|
+
error: `submit control: ${SHADOW_BAR} and this driver has no CDP access to resolve it`
|
|
30795
|
+
};
|
|
30796
|
+
}
|
|
30797
|
+
const control = await page.findShadowControl(labels);
|
|
30798
|
+
if (!control) {
|
|
30799
|
+
return {
|
|
30800
|
+
ok: false,
|
|
30801
|
+
error: `submit control: none of [${labels.join(", ")}] found in ${SHADOW_BAR}`
|
|
30802
|
+
};
|
|
30803
|
+
}
|
|
30804
|
+
await page.clickAt(control.x, control.y);
|
|
30805
|
+
return { ok: true, label: control.label, x: control.x, y: control.y };
|
|
30806
|
+
}
|
|
30807
|
+
async function clickLightControl(page, labels, timeoutMs, what) {
|
|
30808
|
+
const candidates = labels.flatMap((label2) => [`button:has-text("${label2}")`, `text=${label2}`]);
|
|
30809
|
+
let selector;
|
|
30810
|
+
try {
|
|
30811
|
+
selector = await findSelector(page, candidates, `${what} (light DOM) none of [${labels.join(", ")}]`, timeoutMs);
|
|
30812
|
+
} catch (error51) {
|
|
30813
|
+
return { ok: false, error: error51 instanceof Error ? error51.message : String(error51) };
|
|
30814
|
+
}
|
|
30815
|
+
const label = labels.find((candidate) => selector.includes(candidate)) ?? labels[0];
|
|
30816
|
+
await page.locator(selector).first().click({ timeout: timeoutMs });
|
|
30817
|
+
return { ok: true, label: label ?? "", selector };
|
|
30818
|
+
}
|
|
30819
|
+
async function clickActionControl(operation, deps, labels, what) {
|
|
30820
|
+
let lastError = "";
|
|
30821
|
+
for (const path7 of MODE_ACTION_PATHS[operation]) {
|
|
30822
|
+
if (path7 === "shadow") {
|
|
30823
|
+
const clicked2 = await clickShadowControl(deps.page, labels);
|
|
30824
|
+
if (clicked2.ok)
|
|
30825
|
+
return { ok: true, path: path7, label: clicked2.label, click: `${clicked2.x},${clicked2.y}` };
|
|
30826
|
+
lastError = clicked2.error;
|
|
30827
|
+
continue;
|
|
30330
30828
|
}
|
|
30331
|
-
|
|
30332
|
-
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
|
|
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
|
-
};
|
|
30829
|
+
const clicked = await clickLightControl(deps.page, labels, deps.stepTimeoutMs, what);
|
|
30830
|
+
if (clicked.ok)
|
|
30831
|
+
return { ok: true, path: path7, label: clicked.label, click: `selector:${clicked.selector}` };
|
|
30832
|
+
lastError = clicked.error;
|
|
30833
|
+
}
|
|
30834
|
+
return { ok: false, error: lastError };
|
|
30346
30835
|
}
|
|
30347
|
-
async function
|
|
30348
|
-
|
|
30836
|
+
async function readDraftCount(page) {
|
|
30837
|
+
for (const selector of ["text=/\u8349\u7A3F\u7BB1\\(\\d+\\)/", "text=\u8349\u7A3F\u7BB1"]) {
|
|
30838
|
+
const attached = await page.locator(selector).count().catch(() => 0);
|
|
30839
|
+
if (attached === 0)
|
|
30840
|
+
continue;
|
|
30841
|
+
const locator = page.locator(selector).first();
|
|
30842
|
+
if (!locator.textContent)
|
|
30843
|
+
continue;
|
|
30844
|
+
const text = await locator.textContent().catch(() => "");
|
|
30845
|
+
const match = /\u8349\u7A3F\u7BB1\s*[\uFF08(]\s*(\d+)\s*[\uFF09)]/.exec(text);
|
|
30846
|
+
if (match?.[1] !== undefined)
|
|
30847
|
+
return Number(match[1]);
|
|
30848
|
+
}
|
|
30849
|
+
return null;
|
|
30850
|
+
}
|
|
30851
|
+
function draftSavedStamps(now = new Date) {
|
|
30852
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
30853
|
+
return [0, 1, 2].map((minutesAgo) => {
|
|
30854
|
+
const stamp = new Date(now.getTime() - minutesAgo * 60000);
|
|
30855
|
+
return `\u4FDD\u5B58\u4E8E${stamp.getFullYear()}-${pad(stamp.getMonth() + 1)}-${pad(stamp.getDate())} ${pad(stamp.getHours())}:${pad(stamp.getMinutes())}`;
|
|
30856
|
+
});
|
|
30857
|
+
}
|
|
30858
|
+
async function readDraftStamps(page) {
|
|
30859
|
+
const present = [];
|
|
30860
|
+
for (const stamp of draftSavedStamps()) {
|
|
30861
|
+
const attached = await page.locator(`text=${stamp}`).count().catch(() => 0);
|
|
30862
|
+
if (attached > 0)
|
|
30863
|
+
present.push(stamp);
|
|
30864
|
+
}
|
|
30865
|
+
return present;
|
|
30866
|
+
}
|
|
30867
|
+
async function readDraftBaseline(page) {
|
|
30868
|
+
return { count: await readDraftCount(page), stamps: await readDraftStamps(page) };
|
|
30869
|
+
}
|
|
30870
|
+
async function isDraftRowSaved(page, baseline) {
|
|
30871
|
+
const stamps = await readDraftStamps(page);
|
|
30872
|
+
if (stamps.some((stamp) => !baseline.stamps.includes(stamp)))
|
|
30873
|
+
return true;
|
|
30874
|
+
const after = await readDraftCount(page);
|
|
30875
|
+
if (baseline.count === null || after === null)
|
|
30876
|
+
return false;
|
|
30877
|
+
return after > baseline.count;
|
|
30878
|
+
}
|
|
30879
|
+
async function draftSignalEvidence(page, baseline) {
|
|
30880
|
+
const toast = await readScopedSignal(asPlaywrightPage(page), DRAFT_TOASTS);
|
|
30881
|
+
if (toast !== null)
|
|
30882
|
+
return `toast:${toast}`;
|
|
30883
|
+
if (await isDraftRowSaved(page, baseline))
|
|
30884
|
+
return "draft-row";
|
|
30885
|
+
return null;
|
|
30886
|
+
}
|
|
30887
|
+
async function applyNoteTitle(deps) {
|
|
30888
|
+
const selector = await findSelector(deps.page, NOTE_TITLE, "title input", deps.stepTimeoutMs);
|
|
30889
|
+
await deps.page.locator(selector).first().fill(deps.content.title ?? "");
|
|
30890
|
+
}
|
|
30891
|
+
async function typeNoteBody(deps, selectors, what) {
|
|
30892
|
+
const selector = await findSelector(deps.page, selectors, what, deps.stepTimeoutMs);
|
|
30893
|
+
const body = deps.page.locator(selector).first();
|
|
30894
|
+
const text = deps.content.body ?? "";
|
|
30895
|
+
if (typeof body.type === "function") {
|
|
30896
|
+
await body.click();
|
|
30897
|
+
await body.type(text, { delay: 5 });
|
|
30349
30898
|
return;
|
|
30899
|
+
}
|
|
30900
|
+
await body.fill(text);
|
|
30901
|
+
}
|
|
30902
|
+
async function confirmThumbnails(deps, expected) {
|
|
30903
|
+
const selector = await findSelector(deps.page, NOTE_THUMBNAIL, "thumbnail wait", deps.stepTimeoutMs);
|
|
30904
|
+
const matches = await waitUntil(() => deps.page.locator(selector).count().then((count2) => count2 >= expected), deps.stepTimeoutMs);
|
|
30905
|
+
const observed = await deps.page.locator(selector).count();
|
|
30906
|
+
if (!matches || observed !== expected) {
|
|
30907
|
+
throw new Error(`thumbnail count: expected ${expected}, observed ${observed}`);
|
|
30908
|
+
}
|
|
30909
|
+
}
|
|
30910
|
+
async function composeImageText(input, deps) {
|
|
30911
|
+
await enterMode(deps, input.operation);
|
|
30912
|
+
const fileSelector = await findAttachedSelector(deps.page, IMAGE_FILE_INPUT, "image file input", deps.stepTimeoutMs);
|
|
30913
|
+
await deps.page.setInputFiles(fileSelector, input.images);
|
|
30914
|
+
await confirmThumbnails(deps, input.images.length);
|
|
30915
|
+
await applyNoteTitle(deps);
|
|
30916
|
+
await typeNoteBody(deps, NOTE_BODY, "note body");
|
|
30917
|
+
}
|
|
30918
|
+
async function composeVideo(input, deps) {
|
|
30919
|
+
await enterMode(deps, input.operation);
|
|
30920
|
+
const fileSelector = await findAttachedSelector(deps.page, VIDEO_FILE_INPUT, "video file input", deps.stepTimeoutMs);
|
|
30921
|
+
await uploadFile(asPlaywrightPage(deps.page), fileSelector, input.video, { timeoutMs: deps.stepTimeoutMs });
|
|
30922
|
+
await findSelector(deps.page, VIDEO_EDITOR, "video note editor", deps.stepTimeoutMs);
|
|
30923
|
+
await applyNoteTitle(deps);
|
|
30924
|
+
await typeNoteBody(deps, NOTE_BODY, "note body");
|
|
30925
|
+
}
|
|
30926
|
+
async function composeArticle(deps) {
|
|
30927
|
+
await enterMode(deps, "\u5199\u957F\u6587");
|
|
30928
|
+
const newArticle = await findAttachedSelector(deps.page, ARTICLE_NEW, "\u65B0\u7684\u521B\u4F5C control", deps.stepTimeoutMs);
|
|
30929
|
+
await deps.page.locator(newArticle).first().click();
|
|
30930
|
+
const editor = await findSelector(deps.page, ARTICLE_EDITOR, "long-article editor", deps.stepTimeoutMs);
|
|
30931
|
+
await pasteHtml(asPlaywrightPage(deps.page), editor, markdownToHtml(deps.content.body), {
|
|
30932
|
+
timeoutMs: deps.stepTimeoutMs
|
|
30933
|
+
});
|
|
30350
30934
|
try {
|
|
30351
|
-
const
|
|
30352
|
-
await
|
|
30353
|
-
|
|
30354
|
-
|
|
30935
|
+
const layout = await findSelector(deps.page, ARTICLE_LAYOUT, "\u4E00\u952E\u6392\u7248 control", deps.stepTimeoutMs);
|
|
30936
|
+
await deps.page.locator(layout).first().click();
|
|
30937
|
+
} catch {}
|
|
30938
|
+
}
|
|
30939
|
+
async function isPodcastModalOpen(page) {
|
|
30940
|
+
return trySelectors(page, PODCAST_MODAL).then((found) => found.found).catch(() => false);
|
|
30941
|
+
}
|
|
30942
|
+
async function leavePodcastModal(deps) {
|
|
30943
|
+
const closed = () => isPodcastModalOpen(deps.page).then((open) => !open);
|
|
30944
|
+
if (await waitUntil(closed, deps.stepTimeoutMs))
|
|
30355
30945
|
return;
|
|
30946
|
+
if (deps.publishStatus === "public") {
|
|
30947
|
+
const advance = await clickLightControl(deps.page, PODCAST_MODAL_ADVANCE, deps.stepTimeoutMs, "podcast modal advance");
|
|
30948
|
+
if (!advance.ok)
|
|
30949
|
+
throw new Error(advance.error);
|
|
30950
|
+
if (await waitUntil(closed, deps.stepTimeoutMs))
|
|
30951
|
+
return;
|
|
30952
|
+
}
|
|
30953
|
+
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)`);
|
|
30954
|
+
}
|
|
30955
|
+
async function composePodcast(input, deps) {
|
|
30956
|
+
await enterMode(deps, input.operation);
|
|
30957
|
+
const open = await findAttachedSelector(deps.page, PODCAST_OPEN, "\u4E0A\u4F20\u97F3\u9891 control", deps.stepTimeoutMs);
|
|
30958
|
+
await deps.page.locator(open).first().click();
|
|
30959
|
+
await findSelector(deps.page, PODCAST_MODAL, "podcast modal", deps.stepTimeoutMs);
|
|
30960
|
+
const audioInput = await findAttachedSelector(deps.page, PODCAST_AUDIO_INPUT, "podcast audio input", deps.stepTimeoutMs);
|
|
30961
|
+
await uploadFile(asPlaywrightPage(deps.page), audioInput, input.audio, { timeoutMs: deps.stepTimeoutMs });
|
|
30962
|
+
const coverInput = await findAttachedSelector(deps.page, PODCAST_COVER_INPUT, "podcast cover input", deps.stepTimeoutMs);
|
|
30963
|
+
await uploadFile(asPlaywrightPage(deps.page), coverInput, input.cover, { timeoutMs: deps.stepTimeoutMs });
|
|
30964
|
+
await leavePodcastModal(deps);
|
|
30965
|
+
}
|
|
30966
|
+
async function compose(input, deps) {
|
|
30967
|
+
switch (input.operation) {
|
|
30968
|
+
case "\u4E0A\u4F20\u56FE\u6587":
|
|
30969
|
+
await composeImageText(input, deps);
|
|
30970
|
+
return;
|
|
30971
|
+
case "\u4E0A\u4F20\u89C6\u9891":
|
|
30972
|
+
await composeVideo(input, deps);
|
|
30973
|
+
return;
|
|
30974
|
+
case "\u5199\u957F\u6587":
|
|
30975
|
+
await composeArticle(deps);
|
|
30976
|
+
return;
|
|
30977
|
+
case "\u53D1\u64AD\u5BA2":
|
|
30978
|
+
await composePodcast(input, deps);
|
|
30979
|
+
return;
|
|
30356
30980
|
}
|
|
30357
30981
|
}
|
|
30982
|
+
function baseMetadata(input, deps) {
|
|
30983
|
+
return {
|
|
30984
|
+
operation: input.operation,
|
|
30985
|
+
driver: deps.driver,
|
|
30986
|
+
publishStatus: deps.publishStatus,
|
|
30987
|
+
profileDir: deps.profileDir,
|
|
30988
|
+
artifactPaths: artifactPaths(input),
|
|
30989
|
+
...input.operation === "\u4E0A\u4F20\u56FE\u6587" ? { imageCount: input.images.length } : {}
|
|
30990
|
+
};
|
|
30991
|
+
}
|
|
30992
|
+
async function saveDraft(input, deps) {
|
|
30993
|
+
let clicked = null;
|
|
30994
|
+
let signal = null;
|
|
30995
|
+
let attempts = 0;
|
|
30996
|
+
while (signal === null && attempts < deps.submitAttempts) {
|
|
30997
|
+
attempts += 1;
|
|
30998
|
+
await sleep2(deps.submitSettleMs);
|
|
30999
|
+
const control = await clickActionControl(input.operation, deps, DRAFT_LABELS, "draft control");
|
|
31000
|
+
if (!control.ok)
|
|
31001
|
+
return fail(control.error);
|
|
31002
|
+
clicked = control;
|
|
31003
|
+
const outcome = await expectOutcome(() => draftSignalEvidence(deps.page, deps.draftBaseline), {
|
|
31004
|
+
timeoutMs: deps.submitSettleMs,
|
|
31005
|
+
label: `draft confirmation '${control.label}'`,
|
|
31006
|
+
pollMs: POLL_MS
|
|
31007
|
+
});
|
|
31008
|
+
if (outcome.ok)
|
|
31009
|
+
signal = outcome.evidence;
|
|
31010
|
+
}
|
|
31011
|
+
const draftCountAfter = await readDraftCount(deps.page);
|
|
31012
|
+
if (signal === null || clicked === null) {
|
|
31013
|
+
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)})`);
|
|
31014
|
+
}
|
|
31015
|
+
return ResultSchema.parse({
|
|
31016
|
+
ok: true,
|
|
31017
|
+
target: TARGET,
|
|
31018
|
+
message: `${TARGET} draft saved (unpublished) in profile ${deps.profileDir} via '${clicked.label}' (${input.operation})`,
|
|
31019
|
+
metadata: {
|
|
31020
|
+
...baseMetadata(input, deps),
|
|
31021
|
+
draftControl: clicked.label,
|
|
31022
|
+
draftClick: clicked.click,
|
|
31023
|
+
draftPath: clicked.path,
|
|
31024
|
+
draftAttempts: attempts,
|
|
31025
|
+
draftSignal: signal,
|
|
31026
|
+
draftCountBefore: deps.draftBaseline.count,
|
|
31027
|
+
draftCountAfter
|
|
31028
|
+
}
|
|
31029
|
+
});
|
|
31030
|
+
}
|
|
31031
|
+
async function publishNote(input, deps) {
|
|
31032
|
+
await sleep2(deps.submitSettleMs);
|
|
31033
|
+
const submitted = await clickActionControl(input.operation, deps, PUBLISH_LABELS, "publish control");
|
|
31034
|
+
if (!submitted.ok)
|
|
31035
|
+
return fail(submitted.error);
|
|
31036
|
+
const urlBeforeSubmit = deps.page.url();
|
|
31037
|
+
const confirmed = await waitUntil(async () => {
|
|
31038
|
+
if (deps.page.url().includes(PUBLISHED_MARKER))
|
|
31039
|
+
return true;
|
|
31040
|
+
return await readScopedSignal(asPlaywrightPage(deps.page), PUBLISH_CONFIRM) !== null;
|
|
31041
|
+
}, deps.stepTimeoutMs);
|
|
31042
|
+
if (!confirmed) {
|
|
31043
|
+
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.`);
|
|
31044
|
+
}
|
|
31045
|
+
const url2 = deps.page.url();
|
|
31046
|
+
return ResultSchema.parse({
|
|
31047
|
+
ok: true,
|
|
31048
|
+
target: TARGET,
|
|
31049
|
+
...url2 !== PUBLISH_URL ? { url: url2 } : {},
|
|
31050
|
+
message: `${TARGET} ${input.operation} post published`,
|
|
31051
|
+
metadata: {
|
|
31052
|
+
...baseMetadata(input, deps),
|
|
31053
|
+
publishControl: submitted.label,
|
|
31054
|
+
publishPath: submitted.path,
|
|
31055
|
+
publishClick: submitted.click,
|
|
31056
|
+
publishSignal: url2.includes(PUBLISHED_MARKER) ? `url:${PUBLISHED_MARKER}` : "toast"
|
|
31057
|
+
}
|
|
31058
|
+
});
|
|
31059
|
+
}
|
|
31060
|
+
async function runMode(input, deps) {
|
|
31061
|
+
await compose(input, deps);
|
|
31062
|
+
return deps.publishStatus === "public" ? publishNote(input, deps) : saveDraft(input, deps);
|
|
31063
|
+
}
|
|
31064
|
+
|
|
31065
|
+
// ../../plugins/publishings/xhs-pub/src/index.ts
|
|
31066
|
+
var CHANNEL = "xhs";
|
|
31067
|
+
var LOGIN_TIMEOUT_MS = 120000;
|
|
31068
|
+
var PREFLIGHT_TIMEOUT_MS = 30000;
|
|
31069
|
+
var SUBMIT_SETTLE_MS = 2500;
|
|
31070
|
+
var SUBMIT_ATTEMPTS = 3;
|
|
31071
|
+
var AUTH_GATE = ["div.creator-tab", "text=\u4E0A\u4F20\u56FE\u6587"];
|
|
31072
|
+
function fail2(error51) {
|
|
31073
|
+
return ResultSchema.parse({ ok: false, target: TARGET, error: error51 });
|
|
31074
|
+
}
|
|
31075
|
+
async function createShadowProbe(context2, page) {
|
|
31076
|
+
const find = await createShadowFinder(context2, page);
|
|
31077
|
+
return find === undefined ? undefined : { findControl: (labels) => find(labels) };
|
|
31078
|
+
}
|
|
30358
31079
|
async function isXhsConsoleReady(page) {
|
|
30359
|
-
return trySelectors(page,
|
|
31080
|
+
return trySelectors(page, AUTH_GATE).then((r) => r.found);
|
|
30360
31081
|
}
|
|
30361
|
-
var defaultLauncher2 =
|
|
31082
|
+
var defaultLauncher2 = kkBrowserLauncher.launch;
|
|
30362
31083
|
function makeDefaultOpenPage(launch = defaultLauncher2) {
|
|
30363
31084
|
return async (profileDir) => {
|
|
30364
31085
|
let context2;
|
|
@@ -30379,67 +31100,17 @@ function makeDefaultOpenPage(launch = defaultLauncher2) {
|
|
|
30379
31100
|
};
|
|
30380
31101
|
}
|
|
30381
31102
|
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
31103
|
async function checkXhsProfileSession(opts) {
|
|
30414
|
-
|
|
30415
|
-
|
|
30416
|
-
|
|
30417
|
-
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
|
|
30421
|
-
|
|
30422
|
-
|
|
30423
|
-
|
|
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");
|
|
31104
|
+
return checkChannelSession({
|
|
31105
|
+
channel: CHANNEL,
|
|
31106
|
+
homeUrl: PUBLISH_URL,
|
|
31107
|
+
isAuthenticated: (page) => isXhsConsoleReady(wrapPlaywrightPage(page))
|
|
31108
|
+
}, {
|
|
31109
|
+
profileDir: opts.profileDir,
|
|
31110
|
+
launcher: opts.launcher,
|
|
31111
|
+
timeoutMs: opts.timeoutMs ?? PREFLIGHT_TIMEOUT_MS,
|
|
31112
|
+
headedOnly: opts.headedOnly === true
|
|
31113
|
+
});
|
|
30443
31114
|
}
|
|
30444
31115
|
|
|
30445
31116
|
class XhsBrowserTransport {
|
|
@@ -30462,91 +31133,34 @@ class XhsBrowserTransport {
|
|
|
30462
31133
|
this.automation = opts?.automation ?? createAutomationGuard();
|
|
30463
31134
|
this.preflight = opts?.preflight ?? ((profileDir) => checkXhsProfileSession({ profileDir }));
|
|
30464
31135
|
}
|
|
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
31136
|
async publish(payload) {
|
|
30526
31137
|
if (this.driver !== "script") {
|
|
30527
|
-
return
|
|
30528
|
-
}
|
|
30529
|
-
const
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
|
|
30533
|
-
|
|
30534
|
-
|
|
31138
|
+
return fail2(`unsupported driver '${this.driver}' for ${TARGET}: the console modes (${XHS_OPERATIONS.join(", ")}) require the script driver (Playwright setInputFiles)`);
|
|
31139
|
+
}
|
|
31140
|
+
const requested = resolveXhsOperation(payload.options, payload.content);
|
|
31141
|
+
if (requested.error !== undefined)
|
|
31142
|
+
return fail2(requested.error);
|
|
31143
|
+
const operation = requested.operation;
|
|
31144
|
+
const validation = await validateModeInput(operation, payload.content);
|
|
31145
|
+
if (validation.error !== undefined)
|
|
31146
|
+
return fail2(validation.error);
|
|
31147
|
+
const input = validation.input;
|
|
31148
|
+
const override = typeof payload.options?.profileDir === "string" ? payload.options.profileDir : undefined;
|
|
31149
|
+
const profileDir = resolveChannelProfileDir(CHANNEL, override);
|
|
30535
31150
|
const publishStatus = payload.content.metadata?.publishStatus === "public" ? "public" : "draft";
|
|
30536
|
-
const base2 = { driver: this.driver, publishStatus, profileDir, imageCount: paths.length };
|
|
30537
31151
|
const cap = this.automation.allow("publish");
|
|
30538
31152
|
if (!cap.allowed)
|
|
30539
|
-
return
|
|
31153
|
+
return fail2(`automation cap: ${cap.reason}`);
|
|
30540
31154
|
const session = await this.preflight(profileDir);
|
|
30541
31155
|
if (session.status !== "verified") {
|
|
30542
|
-
return
|
|
31156
|
+
return fail2(`session preflight refused (no headed visit attempted): ${session.detail}`);
|
|
30543
31157
|
}
|
|
30544
31158
|
this.automation.record("publish");
|
|
30545
31159
|
let opened;
|
|
30546
31160
|
try {
|
|
30547
31161
|
opened = await this.openPage(profileDir);
|
|
30548
31162
|
} catch (error51) {
|
|
30549
|
-
return
|
|
31163
|
+
return fail2(error51 instanceof Error ? error51.message : String(error51));
|
|
30550
31164
|
}
|
|
30551
31165
|
const { page, close } = opened;
|
|
30552
31166
|
const act = async (what, fn) => {
|
|
@@ -30560,99 +31174,22 @@ class XhsBrowserTransport {
|
|
|
30560
31174
|
await act("creator page goto", () => page.goto(PUBLISH_URL, { timeout: this.loginTimeoutMs }));
|
|
30561
31175
|
const gated = await waitUntil(() => isXhsConsoleReady(page), this.loginTimeoutMs);
|
|
30562
31176
|
if (!gated) {
|
|
30563
|
-
return
|
|
30564
|
-
}
|
|
30565
|
-
const
|
|
30566
|
-
|
|
30567
|
-
|
|
30568
|
-
|
|
30569
|
-
|
|
30570
|
-
|
|
30571
|
-
|
|
30572
|
-
|
|
30573
|
-
|
|
30574
|
-
|
|
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
|
-
}
|
|
31177
|
+
return fail2(`login timeout after ${this.loginTimeoutMs}ms: no authenticated ${CHANNEL} creator console in ${profileDir}`);
|
|
31178
|
+
}
|
|
31179
|
+
const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(page) : { count: null, stamps: [] };
|
|
31180
|
+
return await runMode(input, {
|
|
31181
|
+
page,
|
|
31182
|
+
content: payload.content,
|
|
31183
|
+
publishStatus,
|
|
31184
|
+
profileDir,
|
|
31185
|
+
driver: this.driver,
|
|
31186
|
+
stepTimeoutMs: this.stepTimeoutMs,
|
|
31187
|
+
submitSettleMs: this.submitSettleMs,
|
|
31188
|
+
submitAttempts: this.submitAttempts,
|
|
31189
|
+
draftBaseline
|
|
30653
31190
|
});
|
|
30654
31191
|
} catch (error51) {
|
|
30655
|
-
return
|
|
31192
|
+
return fail2(error51 instanceof Error ? error51.message : String(error51));
|
|
30656
31193
|
} finally {
|
|
30657
31194
|
await close();
|
|
30658
31195
|
}
|
|
@@ -30682,7 +31219,7 @@ async function processPublishIO(inputPath, outputPath, transport = getPublishTra
|
|
|
30682
31219
|
}
|
|
30683
31220
|
} catch (error51) {
|
|
30684
31221
|
const message = `Invalid Content input: ${error51 instanceof Error ? error51.message : String(error51)}`;
|
|
30685
|
-
await writePublishResult(outputPath,
|
|
31222
|
+
await writePublishResult(outputPath, fail2(message));
|
|
30686
31223
|
throw new Error(message);
|
|
30687
31224
|
}
|
|
30688
31225
|
const result = ResultSchema.parse(await transport.publish({ content, options }));
|
|
@@ -30723,10 +31260,16 @@ export {
|
|
|
30723
31260
|
getPublishTransport,
|
|
30724
31261
|
defaultOpenPage,
|
|
30725
31262
|
defaultLauncher2 as defaultLauncher,
|
|
31263
|
+
creatorTabSelector,
|
|
30726
31264
|
createShadowProbe,
|
|
31265
|
+
clickCreatorTab,
|
|
30727
31266
|
checkXhsProfileSession,
|
|
30728
31267
|
XhsBrowserTransport,
|
|
31268
|
+
XHS_OPERATIONS,
|
|
30729
31269
|
PUBLISH_URL,
|
|
31270
|
+
MODE_LIMITS,
|
|
30730
31271
|
LOGIN_TIMEOUT_MS,
|
|
31272
|
+
CREATOR_TAB_COUNT,
|
|
31273
|
+
CREATOR_TAB_BAR,
|
|
30731
31274
|
CHANNEL
|
|
30732
31275
|
};
|