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