@gobing-ai/knowledge-kit 0.0.36 → 0.1.0
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/board/collect/index.css +654 -16
- package/dist/board/collect/index.js +191 -64
- package/dist/board/daily-voice/index.css +679 -12
- package/dist/board/daily-voice/index.js +185 -66
- package/dist/board/itc/index.css +679 -13
- package/dist/board/itc/index.js +188 -66
- package/dist/index.js +2878 -152
- package/package.json +3 -2
- package/plugins/generations/content-gen/dist/index.js +158 -12
- package/plugins/generations/core-facts-gen/dist/index.js +158 -12
- package/plugins/generations/daily-article-gen/dist/index.js +158 -12
- package/plugins/generations/dailynews-gen/dist/index.js +180 -12
- package/plugins/generations/dailynews-gen/src/script-plan.ts +32 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +158 -12
- package/plugins/generations/gemini-tts/README.md +4 -3
- package/plugins/generations/gemini-tts/dist/index.js +158 -12
- package/plugins/generations/image-gen/dist/index.js +158 -12
- package/plugins/generations/news-report-gen/dist/index.js +158 -12
- package/plugins/generations/omni-voice-gen/README.md +15 -4
- package/plugins/generations/omni-voice-gen/profiles.json +1 -2
- package/plugins/generations/voice-gen/dist/index.js +158 -12
- package/plugins/generations/voxcpm-tts/README.md +21 -1
- package/plugins/generations/voxcpm-tts/dist/index.js +173 -13
- package/plugins/generations/voxcpm-tts/scripts/audit-speaker-consistency.py +109 -0
- package/plugins/generations/voxcpm-tts/src/voxcpm-client.ts +28 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +158 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +158 -12
- package/plugins/ingestions/last30days-ingest/dist/index.js +158 -12
- package/plugins/ingestions/web-search/dist/index.js +158 -12
- package/plugins/kk/commands/workflow-run.md +56 -18
- package/plugins/kk/decisions/kk-itc.yaml +46 -0
- package/plugins/kk/decisions/kk-seo-keyword.yaml +57 -0
- package/plugins/kk/decisions/kk-seo-page.yaml +230 -0
- package/plugins/kk/decisions/kk-seo-pair.yaml +21 -0
- package/plugins/kk/decisions/kk-seo-site.yaml +90 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/chinese-article.json +14 -0
- package/plugins/kk/publish-presets/technical-article.json +14 -0
- package/plugins/kk/references/smart-seo-attribution.md +85 -0
- package/plugins/kk/scripts/daily-stages.ts +42 -2
- package/plugins/kk/scripts/daily-voice-style.zh.md +1 -1
- package/plugins/kk/scripts/itc-stages.ts +25 -118
- package/plugins/kk/scripts/kk-workflow-stages.ts +38 -1
- package/plugins/kk/scripts/seo-decisions.ts +477 -0
- package/plugins/kk/scripts/seo-enrich.ts +728 -0
- package/plugins/kk/scripts/seo-facts.ts +610 -0
- package/plugins/kk/scripts/seo-fetch.ts +766 -0
- package/plugins/kk/scripts/seo-report.ts +460 -0
- package/plugins/kk/scripts/seo-rules.ts +1882 -0
- package/plugins/kk/scripts/seo-stages.ts +1044 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +2 -2
- package/plugins/kk/skills/smart-seo/SKILL.md +183 -0
- package/plugins/kk/skills/smart-seo/references/method.md +126 -0
- package/plugins/kk/skills/smart-seo/references/narrative.md +125 -0
- package/plugins/kk/skills/smart-seo/references/operations.md +202 -0
- package/plugins/kk/skills/smart-seo/references/seo-geo-sources.md +90 -0
- package/plugins/kk/workflows/feature-verification.yaml +28 -24
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +86 -18
- package/plugins/kk/workflows/kk-itc-author.yaml +406 -0
- package/plugins/kk/workflows/kk-itc.yaml +208 -682
- package/plugins/kk/workflows/kk-smart-seo.yaml +251 -0
- package/plugins/publishings/bilibili-pub/dist/index.js +21782 -0
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +20410 -0
- package/plugins/publishings/bilibili-pub/package.json +20 -0
- package/plugins/publishings/bilibili-pub/plugin.json +12 -0
- package/plugins/publishings/bilibili-pub/src/flow.ts +1296 -0
- package/plugins/publishings/bilibili-pub/src/index.ts +139 -0
- package/plugins/publishings/bilibili-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/bilibili-pub/src/selectors.ts +222 -0
- package/plugins/publishings/bilibili-pub/tsconfig.json +4 -0
- package/plugins/publishings/cnblogs-pub/dist/index.js +21383 -0
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +20412 -0
- package/plugins/publishings/cnblogs-pub/package.json +19 -0
- package/plugins/publishings/cnblogs-pub/plugin.json +12 -0
- package/plugins/publishings/cnblogs-pub/src/flow.ts +708 -0
- package/plugins/publishings/cnblogs-pub/src/index.ts +172 -0
- package/plugins/publishings/cnblogs-pub/src/profile-check.ts +24 -0
- package/plugins/publishings/cnblogs-pub/src/selectors.ts +104 -0
- package/plugins/publishings/cnblogs-pub/tsconfig.json +4 -0
- package/plugins/publishings/csdn-pub/dist/index.js +21552 -0
- package/plugins/publishings/csdn-pub/dist/profile-check.js +20963 -0
- package/plugins/publishings/csdn-pub/package.json +19 -0
- package/plugins/publishings/csdn-pub/plugin.json +12 -0
- package/plugins/publishings/csdn-pub/src/index.ts +1178 -0
- package/plugins/publishings/csdn-pub/src/profile-check.ts +23 -0
- package/plugins/publishings/csdn-pub/tsconfig.json +4 -0
- package/plugins/publishings/emdash-pub/dist/index.js +158 -12
- package/plugins/publishings/emdash-pub/plugin.json +22 -1
- package/plugins/publishings/infoq-pub/dist/index.js +160 -12
- package/plugins/publishings/infoq-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/infoq-pub/plugin.json +21 -0
- package/plugins/publishings/jike-pub/dist/index.js +21379 -0
- package/plugins/publishings/jike-pub/dist/profile-check.js +20951 -0
- package/plugins/publishings/jike-pub/package.json +19 -0
- package/plugins/publishings/jike-pub/plugin.json +12 -0
- package/plugins/publishings/jike-pub/src/index.ts +800 -0
- package/plugins/publishings/jike-pub/src/profile-check.ts +21 -0
- package/plugins/publishings/jike-pub/tsconfig.json +4 -0
- package/plugins/publishings/juejin-pub/dist/index.js +160 -12
- package/plugins/publishings/juejin-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/juejin-pub/plugin.json +21 -0
- package/plugins/publishings/medium-pub/dist/index.js +158 -12
- package/plugins/publishings/medium-pub/plugin.json +22 -1
- package/plugins/publishings/podcast-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/dist/index.js +158 -12
- package/plugins/publishings/qiita-pub/plugin.json +24 -1
- package/plugins/publishings/shipinhao-pub/dist/index.js +21715 -0
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +20431 -0
- package/plugins/publishings/shipinhao-pub/package.json +19 -0
- package/plugins/publishings/shipinhao-pub/plugin.json +12 -0
- package/plugins/publishings/shipinhao-pub/src/flow.ts +1323 -0
- package/plugins/publishings/shipinhao-pub/src/index.ts +173 -0
- package/plugins/publishings/shipinhao-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/shipinhao-pub/src/selectors.ts +241 -0
- package/plugins/publishings/shipinhao-pub/tsconfig.json +4 -0
- package/plugins/publishings/sspai-pub/dist/index.js +21371 -0
- package/plugins/publishings/sspai-pub/dist/profile-check.js +20414 -0
- package/plugins/publishings/sspai-pub/package.json +19 -0
- package/plugins/publishings/sspai-pub/plugin.json +12 -0
- package/plugins/publishings/sspai-pub/src/flow.ts +624 -0
- package/plugins/publishings/sspai-pub/src/index.ts +167 -0
- package/plugins/publishings/sspai-pub/src/profile-check.ts +25 -0
- package/plugins/publishings/sspai-pub/src/selectors.ts +122 -0
- package/plugins/publishings/sspai-pub/tsconfig.json +4 -0
- package/plugins/publishings/substack-pub/dist/index.js +160 -12
- package/plugins/publishings/substack-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/substack-pub/plugin.json +21 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +158 -12
- package/plugins/publishings/surfdash-pub/plugin.json +22 -1
- package/plugins/publishings/threads-pub/dist/index.js +158 -12
- package/plugins/publishings/wechatmp-pub/dist/index.js +1669 -502
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +204 -21
- package/plugins/publishings/wechatmp-pub/plugin.json +32 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-draft.ts +540 -0
- package/plugins/publishings/wechatmp-pub/src/cgi-media.ts +380 -0
- package/plugins/publishings/wechatmp-pub/src/cgi.ts +485 -0
- package/plugins/publishings/wechatmp-pub/src/flow.ts +104 -0
- package/plugins/publishings/weibo-pub/dist/index.js +160 -12
- package/plugins/publishings/weibo-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/weibo-pub/plugin.json +35 -0
- package/plugins/publishings/x-pub/dist/index.js +160 -12
- package/plugins/publishings/x-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/x-pub/plugin.json +40 -0
- package/plugins/publishings/xhs-pub/dist/index.js +160 -12
- package/plugins/publishings/xhs-pub/dist/profile-check.js +160 -12
- package/plugins/publishings/xhs-pub/plugin.json +37 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +21491 -0
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +20417 -0
- package/plugins/publishings/xiaoyuzhou-pub/package.json +19 -0
- package/plugins/publishings/xiaoyuzhou-pub/plugin.json +12 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/flow.ts +826 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/index.ts +174 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/profile-check.ts +26 -0
- package/plugins/publishings/xiaoyuzhou-pub/src/selectors.ts +125 -0
- package/plugins/publishings/xiaoyuzhou-pub/tsconfig.json +4 -0
- package/plugins/publishings/zenn-pub/dist/index.js +158 -12
- package/plugins/publishings/zenn-pub/plugin.json +22 -1
- package/plugins/generations/omni-voice-gen/voices/robin-news.wav +0 -0
|
@@ -19831,6 +19831,83 @@ var init_runtime_node_bun = __esm(() => {
|
|
|
19831
19831
|
// ../../plugins/publishings/wechatmp-pub/src/index.ts
|
|
19832
19832
|
import { parseArgs } from "util";
|
|
19833
19833
|
|
|
19834
|
+
// ../../packages/kk-core/src/content-limits.ts
|
|
19835
|
+
var PLATFORM_CONTENT_LIMITS = Object.freeze({
|
|
19836
|
+
schemaVersion: 5,
|
|
19837
|
+
tagLimits: Object.freeze({
|
|
19838
|
+
default: 7,
|
|
19839
|
+
imageText: Object.freeze({
|
|
19840
|
+
instagram: 5,
|
|
19841
|
+
douyin: 5,
|
|
19842
|
+
kuaishou: 4,
|
|
19843
|
+
weixin: 1,
|
|
19844
|
+
xiaohongshu: 10
|
|
19845
|
+
}),
|
|
19846
|
+
shortVideo: Object.freeze({
|
|
19847
|
+
douyin: 5,
|
|
19848
|
+
kuaishou: 4,
|
|
19849
|
+
xiaohongshu: 10,
|
|
19850
|
+
bilibili: 10,
|
|
19851
|
+
zhihu: 5,
|
|
19852
|
+
tiktok: 20,
|
|
19853
|
+
youtube: 60
|
|
19854
|
+
})
|
|
19855
|
+
}),
|
|
19856
|
+
titleCapabilities: Object.freeze({
|
|
19857
|
+
dynamic: Object.freeze({
|
|
19858
|
+
bilibili: Object.freeze({ max: 20, unit: "utf16_code_units" }),
|
|
19859
|
+
zhihu: Object.freeze({ max: 50, unit: "utf16_code_units" }),
|
|
19860
|
+
douban: Object.freeze({ max: 70, unit: "utf16_code_units" }),
|
|
19861
|
+
x: null,
|
|
19862
|
+
threads: null,
|
|
19863
|
+
weibo: null,
|
|
19864
|
+
jike: null,
|
|
19865
|
+
zsxq: null,
|
|
19866
|
+
toutiao: null,
|
|
19867
|
+
juejin: null
|
|
19868
|
+
}),
|
|
19869
|
+
imageText: Object.freeze({
|
|
19870
|
+
xiaohongshu: Object.freeze({ max: 20, unit: "xiaohongshu_title_weighted" }),
|
|
19871
|
+
weixin: Object.freeze({ max: 20, unit: "utf16_code_units" }),
|
|
19872
|
+
douyin: Object.freeze({ max: 20, unit: "utf16_code_units" }),
|
|
19873
|
+
shipinhao: Object.freeze({ max: 22, unit: "utf16_code_units" }),
|
|
19874
|
+
reddit: Object.freeze({ max: 300, unit: "utf16_code_units" })
|
|
19875
|
+
}),
|
|
19876
|
+
article: Object.freeze({
|
|
19877
|
+
juejin: Object.freeze({ max: 100, unit: "utf16_code_units" }),
|
|
19878
|
+
csdn: Object.freeze({ max: 100, unit: "utf16_code_units" }),
|
|
19879
|
+
cnblogs: Object.freeze({ max: 128, unit: "utf16_code_units" }),
|
|
19880
|
+
weibo: Object.freeze({ max: 30, unit: "utf16_code_units" }),
|
|
19881
|
+
sspai: Object.freeze({ max: 80, unit: "utf16_code_units" })
|
|
19882
|
+
}),
|
|
19883
|
+
shortVideo: Object.freeze({
|
|
19884
|
+
shipinhao: Object.freeze({ max: 16, unit: "utf16_code_units" }),
|
|
19885
|
+
douyin: Object.freeze({ max: 30, unit: "utf16_code_units" }),
|
|
19886
|
+
bilibili: Object.freeze({ max: 80, unit: "utf16_code_units" })
|
|
19887
|
+
})
|
|
19888
|
+
}),
|
|
19889
|
+
bodyCapabilities: Object.freeze({
|
|
19890
|
+
dynamic: Object.freeze({
|
|
19891
|
+
threads: Object.freeze({ max: 500, unit: "unicode_code_points", behavior: "split_thread" }),
|
|
19892
|
+
x: Object.freeze({ max: 280, unit: "weighted", behavior: "split_thread" }),
|
|
19893
|
+
weibo: Object.freeze({ max: 2000, unit: "unicode_code_points" }),
|
|
19894
|
+
jike: Object.freeze({ max: 1000, unit: "unicode_code_points" })
|
|
19895
|
+
}),
|
|
19896
|
+
imageText: Object.freeze({
|
|
19897
|
+
xiaohongshu: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
|
|
19898
|
+
douyin: Object.freeze({ max: 1000, unit: "unicode_code_points" })
|
|
19899
|
+
}),
|
|
19900
|
+
article: Object.freeze({
|
|
19901
|
+
wechatmp: Object.freeze({ max: 50000, unit: "unicode_code_points" }),
|
|
19902
|
+
juejin: Object.freeze({ max: 50000, unit: "unicode_code_points" })
|
|
19903
|
+
}),
|
|
19904
|
+
shortVideo: Object.freeze({
|
|
19905
|
+
douyin: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
|
|
19906
|
+
kuaishou: Object.freeze({ max: 1000, unit: "unicode_code_points" }),
|
|
19907
|
+
shipinhao: Object.freeze({ max: 1000, unit: "unicode_code_points" })
|
|
19908
|
+
})
|
|
19909
|
+
})
|
|
19910
|
+
});
|
|
19834
19911
|
// ../../packages/kk-core/src/kinds.ts
|
|
19835
19912
|
init_zod();
|
|
19836
19913
|
|
|
@@ -19921,6 +19998,67 @@ var BrowserSessionDescriptorSchema = object({
|
|
|
19921
19998
|
}
|
|
19922
19999
|
}).optional()
|
|
19923
20000
|
}).strict();
|
|
20001
|
+
var ContentCountUnitSchema = _enum([
|
|
20002
|
+
"utf16_code_units",
|
|
20003
|
+
"unicode_code_points",
|
|
20004
|
+
"weighted",
|
|
20005
|
+
"xiaohongshu_title_weighted"
|
|
20006
|
+
]);
|
|
20007
|
+
var LimitEntrySchema = object({
|
|
20008
|
+
max: number2().int().positive(),
|
|
20009
|
+
unit: ContentCountUnitSchema
|
|
20010
|
+
}).strict();
|
|
20011
|
+
var PublishingProfileLimitsSchema = object({
|
|
20012
|
+
title: LimitEntrySchema.optional(),
|
|
20013
|
+
body: LimitEntrySchema.optional()
|
|
20014
|
+
}).strict();
|
|
20015
|
+
var PublishingProfileRequiresSchema = object({
|
|
20016
|
+
cover: boolean2().optional(),
|
|
20017
|
+
orderedImages: boolean2().optional()
|
|
20018
|
+
}).strict();
|
|
20019
|
+
var PublishingProfileModeBindingSchema = object({
|
|
20020
|
+
metadata: record(string2(), unknown())
|
|
20021
|
+
}).strict();
|
|
20022
|
+
var PublishingProfileModesSchema = object({
|
|
20023
|
+
draft: PublishingProfileModeBindingSchema.optional(),
|
|
20024
|
+
live: PublishingProfileModeBindingSchema.optional()
|
|
20025
|
+
}).strict().superRefine((modes, ctx) => {
|
|
20026
|
+
if (modes.draft === undefined && modes.live === undefined) {
|
|
20027
|
+
ctx.addIssue({
|
|
20028
|
+
code: "custom",
|
|
20029
|
+
message: 'modes must define at least one of "draft" or "live"'
|
|
20030
|
+
});
|
|
20031
|
+
}
|
|
20032
|
+
});
|
|
20033
|
+
var PublishingProfileSchema = object({
|
|
20034
|
+
format: string2().min(1),
|
|
20035
|
+
locale: string2().regex(/^(source|[a-z]{2,3}(-[A-Za-z0-9]+)*)$/, {
|
|
20036
|
+
message: 'locale must be "source" or a valid language tag'
|
|
20037
|
+
}),
|
|
20038
|
+
limits: PublishingProfileLimitsSchema.optional(),
|
|
20039
|
+
requires: PublishingProfileRequiresSchema.optional(),
|
|
20040
|
+
modes: PublishingProfileModesSchema
|
|
20041
|
+
}).strict();
|
|
20042
|
+
var PublishingCapabilitiesSchema = object({
|
|
20043
|
+
defaultProfile: string2().min(1),
|
|
20044
|
+
profiles: record(string2().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/), PublishingProfileSchema)
|
|
20045
|
+
}).strict().superRefine((data, ctx) => {
|
|
20046
|
+
const keys = Object.keys(data.profiles);
|
|
20047
|
+
if (keys.length === 0) {
|
|
20048
|
+
ctx.addIssue({
|
|
20049
|
+
code: "custom",
|
|
20050
|
+
path: ["profiles"],
|
|
20051
|
+
message: "profiles record must be nonempty"
|
|
20052
|
+
});
|
|
20053
|
+
}
|
|
20054
|
+
if (!(data.defaultProfile in data.profiles)) {
|
|
20055
|
+
ctx.addIssue({
|
|
20056
|
+
code: "custom",
|
|
20057
|
+
path: ["defaultProfile"],
|
|
20058
|
+
message: `defaultProfile "${data.defaultProfile}" must reference a declared profile`
|
|
20059
|
+
});
|
|
20060
|
+
}
|
|
20061
|
+
});
|
|
19924
20062
|
var PluginManifestSchemaBase = looseObject({
|
|
19925
20063
|
name: string2().min(1),
|
|
19926
20064
|
kind: PluginKindSchema,
|
|
@@ -19929,23 +20067,31 @@ var PluginManifestSchemaBase = looseObject({
|
|
|
19929
20067
|
version: string2().optional(),
|
|
19930
20068
|
description: string2().optional(),
|
|
19931
20069
|
operations: record(string2(), PluginOperationSchema).optional(),
|
|
19932
|
-
browserSession: BrowserSessionDescriptorSchema.optional()
|
|
20070
|
+
browserSession: BrowserSessionDescriptorSchema.optional(),
|
|
20071
|
+
publishing: PublishingCapabilitiesSchema.optional()
|
|
19933
20072
|
});
|
|
19934
20073
|
var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
|
|
19935
|
-
if (manifest.browserSession
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
}
|
|
20074
|
+
if (manifest.browserSession !== undefined) {
|
|
20075
|
+
if (manifest.kind !== "publish") {
|
|
20076
|
+
ctx.addIssue({
|
|
20077
|
+
code: "custom",
|
|
20078
|
+
path: ["browserSession"],
|
|
20079
|
+
message: `browserSession requires kind "publish", not "${manifest.kind}"`
|
|
20080
|
+
});
|
|
20081
|
+
}
|
|
20082
|
+
if (manifest.check === undefined) {
|
|
20083
|
+
ctx.addIssue({
|
|
20084
|
+
code: "custom",
|
|
20085
|
+
path: ["browserSession"],
|
|
20086
|
+
message: "browserSession requires a check probe entry that owns the session routines"
|
|
20087
|
+
});
|
|
20088
|
+
}
|
|
19943
20089
|
}
|
|
19944
|
-
if (manifest.
|
|
20090
|
+
if (manifest.publishing !== undefined && manifest.kind !== "publish") {
|
|
19945
20091
|
ctx.addIssue({
|
|
19946
20092
|
code: "custom",
|
|
19947
|
-
path: ["
|
|
19948
|
-
message:
|
|
20093
|
+
path: ["publishing"],
|
|
20094
|
+
message: `publishing requires kind "publish", not "${manifest.kind}"`
|
|
19949
20095
|
});
|
|
19950
20096
|
}
|
|
19951
20097
|
});
|
|
@@ -20484,6 +20630,8 @@ async function ensureProfileDir2(profileDir, headless) {
|
|
|
20484
20630
|
throw new BrowserProfileMissingError(profileDir);
|
|
20485
20631
|
await mkdir2(profileDir, { recursive: true, mode: 448 });
|
|
20486
20632
|
}
|
|
20633
|
+
// ../../packages/publish-harness/src/browser-anti-detection.ts
|
|
20634
|
+
var MAX_QUEUED_BYTES = 8 * 1024 * 1024;
|
|
20487
20635
|
// ../../packages/publish-harness/src/browser.ts
|
|
20488
20636
|
var DEFAULT_STEP_TIMEOUT_MS = 30000;
|
|
20489
20637
|
var DEFAULT_POLL_MS = 500;
|
|
@@ -20687,380 +20835,344 @@ function editorTextScript(selector) {
|
|
|
20687
20835
|
// ../../plugins/publishings/wechatmp-pub/src/index.ts
|
|
20688
20836
|
init_dist();
|
|
20689
20837
|
|
|
20690
|
-
// ../../plugins/publishings/wechatmp-pub/src/
|
|
20691
|
-
var
|
|
20692
|
-
var
|
|
20693
|
-
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20838
|
+
// ../../plugins/publishings/wechatmp-pub/src/cgi.ts
|
|
20839
|
+
var MP_ORIGIN = "https://mp.weixin.qq.com";
|
|
20840
|
+
var FILETRANSFER_URL = `${MP_ORIGIN}/cgi-bin/filetransfer`;
|
|
20841
|
+
var CROPIMAGE_URL = `${MP_ORIGIN}/cgi-bin/cropimage`;
|
|
20842
|
+
var MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
20843
|
+
var REQUEST_TIMEOUT_MS = 30000;
|
|
20844
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
20845
|
+
var WIDE_COVER_ASPECT = 2.35;
|
|
20846
|
+
var SQUARE_COVER_ASPECT = 1;
|
|
20847
|
+
var EXTENSION_MIME = {
|
|
20848
|
+
".png": "image/png",
|
|
20849
|
+
".jpg": "image/jpeg",
|
|
20850
|
+
".jpeg": "image/jpeg",
|
|
20851
|
+
".webp": "image/webp"
|
|
20852
|
+
};
|
|
20853
|
+
|
|
20854
|
+
class WechatCgiError extends Error {
|
|
20855
|
+
reason;
|
|
20856
|
+
constructor(reason, message) {
|
|
20857
|
+
super(message);
|
|
20858
|
+
this.reason = reason;
|
|
20859
|
+
this.name = "WechatCgiError";
|
|
20699
20860
|
}
|
|
20700
20861
|
}
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
if (attached > 0) {
|
|
20707
|
-
found = selector;
|
|
20708
|
-
return true;
|
|
20709
|
-
}
|
|
20710
|
-
}
|
|
20862
|
+
function isAllowedCdnUrl(candidate) {
|
|
20863
|
+
try {
|
|
20864
|
+
const url = new URL(candidate);
|
|
20865
|
+
return url.protocol === "https:" && url.host === "mmbiz.qpic.cn";
|
|
20866
|
+
} catch {
|
|
20711
20867
|
return false;
|
|
20712
|
-
}
|
|
20713
|
-
return found;
|
|
20868
|
+
}
|
|
20714
20869
|
}
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20870
|
+
function centerCropRect(width, height, aspect) {
|
|
20871
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width < 1 || height < 1) {
|
|
20872
|
+
throw new WechatCgiError("invalid-dimensions", `image dimensions must be positive numbers, got ${width}x${height}`);
|
|
20873
|
+
}
|
|
20874
|
+
if (!Number.isFinite(aspect) || aspect <= 0) {
|
|
20875
|
+
throw new WechatCgiError("invalid-dimensions", `crop aspect must be positive, got ${aspect}`);
|
|
20876
|
+
}
|
|
20877
|
+
let cropWidth = width;
|
|
20878
|
+
let cropHeight = height;
|
|
20879
|
+
if (width / height > aspect) {
|
|
20880
|
+
cropWidth = height * aspect;
|
|
20881
|
+
} else {
|
|
20882
|
+
cropHeight = width / aspect;
|
|
20883
|
+
}
|
|
20884
|
+
const x1 = Math.floor((width - cropWidth) / 2);
|
|
20885
|
+
const y1 = Math.floor((height - cropHeight) / 2);
|
|
20886
|
+
return {
|
|
20887
|
+
x1,
|
|
20888
|
+
y1,
|
|
20889
|
+
x2: Math.min(width, x1 + Math.round(cropWidth)),
|
|
20890
|
+
y2: Math.min(height, y1 + Math.round(cropHeight))
|
|
20891
|
+
};
|
|
20726
20892
|
}
|
|
20727
|
-
|
|
20728
|
-
const
|
|
20729
|
-
|
|
20730
|
-
|
|
20893
|
+
function extensionMime(name) {
|
|
20894
|
+
const lower = name.toLowerCase();
|
|
20895
|
+
const dot = lower.lastIndexOf(".");
|
|
20896
|
+
const mime = dot === -1 ? undefined : EXTENSION_MIME[lower.slice(dot)];
|
|
20897
|
+
if (mime === undefined) {
|
|
20898
|
+
throw new WechatCgiError("unsupported-extension", `accepted image extensions are PNG/JPEG/WebP, got '${name}'`);
|
|
20731
20899
|
}
|
|
20732
|
-
return
|
|
20900
|
+
return mime;
|
|
20733
20901
|
}
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
throw new Error(`${what}: none of [${selectors.join(" | ")}] attached within ${timeoutMs}ms`);
|
|
20902
|
+
function validateImageBytes(bytes, name) {
|
|
20903
|
+
if (bytes.byteLength === 0) {
|
|
20904
|
+
throw new WechatCgiError("empty-image", `image '${name}' is empty`);
|
|
20738
20905
|
}
|
|
20739
|
-
|
|
20906
|
+
if (bytes.byteLength > MAX_IMAGE_BYTES) {
|
|
20907
|
+
throw new WechatCgiError("image-too-large", `image '${name}' exceeds the ${MAX_IMAGE_BYTES}-byte bound`);
|
|
20908
|
+
}
|
|
20909
|
+
return { bytes, name, mime: extensionMime(name) };
|
|
20740
20910
|
}
|
|
20741
|
-
async function
|
|
20742
|
-
const
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20911
|
+
async function readLocalImageBytes(path) {
|
|
20912
|
+
const name = path.replaceAll("\\", "/").split("/").pop() ?? path;
|
|
20913
|
+
extensionMime(name);
|
|
20914
|
+
const file = Bun.file(path);
|
|
20915
|
+
let stat;
|
|
20916
|
+
try {
|
|
20917
|
+
stat = await file.stat();
|
|
20918
|
+
} catch {
|
|
20919
|
+
throw new WechatCgiError("unreadable-file", `image is not a readable file: ${path}`);
|
|
20920
|
+
}
|
|
20921
|
+
if (!stat.isFile() || stat.size === 0) {
|
|
20922
|
+
throw new WechatCgiError("unreadable-file", `image is not a nonempty regular file: ${path}`);
|
|
20923
|
+
}
|
|
20924
|
+
if (stat.size > MAX_IMAGE_BYTES) {
|
|
20925
|
+
throw new WechatCgiError("image-too-large", `image '${path}' exceeds the ${MAX_IMAGE_BYTES}-byte bound`);
|
|
20926
|
+
}
|
|
20927
|
+
return validateImageBytes(new Uint8Array(await file.arrayBuffer()), name);
|
|
20747
20928
|
}
|
|
20748
|
-
|
|
20749
|
-
|
|
20929
|
+
function cgiFetchExpression(spec) {
|
|
20930
|
+
return `(async () => {
|
|
20931
|
+
const spec = ${JSON.stringify(spec)};
|
|
20932
|
+
const controller = new AbortController();
|
|
20933
|
+
const timer = setTimeout(() => controller.abort(), spec.timeoutMs);
|
|
20934
|
+
try {
|
|
20935
|
+
const form = new FormData();
|
|
20936
|
+
for (const key of Object.keys(spec.fields)) form.append(key, spec.fields[key]);
|
|
20937
|
+
if (spec.file) {
|
|
20938
|
+
const binary = atob(spec.file.base64);
|
|
20939
|
+
const bytes = new Uint8Array(binary.length);
|
|
20940
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
20941
|
+
form.append(spec.file.field, new Blob([bytes], { type: spec.file.mime }), spec.file.filename);
|
|
20942
|
+
}
|
|
20943
|
+
const response = await fetch(spec.url, {
|
|
20944
|
+
method: 'POST',
|
|
20945
|
+
body: form,
|
|
20946
|
+
credentials: 'include',
|
|
20947
|
+
redirect: 'error',
|
|
20948
|
+
signal: controller.signal,
|
|
20949
|
+
});
|
|
20950
|
+
if (!response.ok) return JSON.stringify({ kind: 'http-status', status: response.status });
|
|
20951
|
+
if (!response.body) return JSON.stringify({ kind: 'error', detail: 'response has no body stream' });
|
|
20952
|
+
const reader = response.body.getReader();
|
|
20953
|
+
let total = 0;
|
|
20954
|
+
const chunks = [];
|
|
20955
|
+
for (;;) {
|
|
20956
|
+
const { done, value } = await reader.read();
|
|
20957
|
+
if (done) break;
|
|
20958
|
+
total += value.byteLength;
|
|
20959
|
+
if (total > spec.maxBytes) {
|
|
20960
|
+
controller.abort();
|
|
20961
|
+
return JSON.stringify({ kind: 'too-large' });
|
|
20962
|
+
}
|
|
20963
|
+
chunks.push(value);
|
|
20964
|
+
}
|
|
20965
|
+
const text = await new Blob(chunks).text();
|
|
20966
|
+
return JSON.stringify({ kind: 'ok', status: response.status, text });
|
|
20967
|
+
} catch (error) {
|
|
20968
|
+
return JSON.stringify({ kind: 'error', detail: String(error) });
|
|
20969
|
+
} finally {
|
|
20970
|
+
clearTimeout(timer);
|
|
20971
|
+
}
|
|
20972
|
+
})()`;
|
|
20750
20973
|
}
|
|
20751
|
-
|
|
20752
|
-
|
|
20974
|
+
function stripQuery(detail) {
|
|
20975
|
+
return detail.replace(/\?[^'"\\\s]*/g, "?\u2026");
|
|
20753
20976
|
}
|
|
20754
|
-
function
|
|
20755
|
-
|
|
20756
|
-
|
|
20757
|
-
|
|
20758
|
-
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
first: () => {
|
|
20763
|
-
const loc = pw.locator(selector).first();
|
|
20764
|
-
return {
|
|
20765
|
-
count: () => loc.count(),
|
|
20766
|
-
isVisible: (options) => loc.isVisible(options),
|
|
20767
|
-
fill: (text, options) => loc.fill(text, options),
|
|
20768
|
-
click: (options) => loc.click(options),
|
|
20769
|
-
type: (text, options) => loc.pressSequentially(text, options),
|
|
20770
|
-
waitFor: (options) => loc.waitFor(options),
|
|
20771
|
-
textContent: async () => await loc.textContent() ?? "",
|
|
20772
|
-
inputValue: () => loc.inputValue()
|
|
20773
|
-
};
|
|
20774
|
-
}
|
|
20775
|
-
}),
|
|
20776
|
-
setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
|
|
20777
|
-
url: () => pw.url(),
|
|
20778
|
-
evaluate: (expression) => pw.evaluate(expression),
|
|
20779
|
-
waitForTimeout: (ms) => pw.waitForTimeout(ms),
|
|
20780
|
-
press: (key) => pw.keyboard.press(key),
|
|
20781
|
-
frames: () => {
|
|
20782
|
-
const source = raw;
|
|
20783
|
-
return (source.frames?.() ?? []).map((frame) => ({
|
|
20784
|
-
url: () => frame.url(),
|
|
20785
|
-
locator: (selector) => ({ count: () => frame.locator(selector).count() })
|
|
20786
|
-
}));
|
|
20977
|
+
async function postCgiForm(page, spec) {
|
|
20978
|
+
let verdict;
|
|
20979
|
+
const raw = await page.evaluate(cgiFetchExpression(spec));
|
|
20980
|
+
if (typeof raw === "string") {
|
|
20981
|
+
try {
|
|
20982
|
+
verdict = JSON.parse(raw);
|
|
20983
|
+
} catch {
|
|
20984
|
+
verdict = undefined;
|
|
20787
20985
|
}
|
|
20788
|
-
}
|
|
20986
|
+
}
|
|
20987
|
+
if (verdict?.kind === "http-status") {
|
|
20988
|
+
throw new WechatCgiError("http-status", `CGI call returned HTTP ${verdict.status}`);
|
|
20989
|
+
}
|
|
20990
|
+
if (verdict?.kind === "too-large") {
|
|
20991
|
+
throw new WechatCgiError("response-too-large", `CGI response exceeded the ${MAX_RESPONSE_BYTES}-byte bound`);
|
|
20992
|
+
}
|
|
20993
|
+
if (verdict?.kind !== "ok") {
|
|
20994
|
+
const detail = verdict?.kind === "error" ? stripQuery(verdict.detail) : "page returned no usable verdict";
|
|
20995
|
+
throw new WechatCgiError("network-error", `CGI call failed before a usable response: ${detail}`);
|
|
20996
|
+
}
|
|
20997
|
+
return { status: verdict.status, text: verdict.text };
|
|
20789
20998
|
}
|
|
20790
|
-
function
|
|
20791
|
-
|
|
20792
|
-
const cache = new WeakMap;
|
|
20793
|
-
const wrap = (page) => {
|
|
20794
|
-
const key = page;
|
|
20795
|
-
const cached = cache.get(key);
|
|
20796
|
-
if (cached !== undefined)
|
|
20797
|
-
return cached;
|
|
20798
|
-
const wrapped = wrapPlaywrightPage(page);
|
|
20799
|
-
cache.set(key, wrapped);
|
|
20800
|
-
return wrapped;
|
|
20801
|
-
};
|
|
20802
|
-
return {
|
|
20803
|
-
page: wrap(entryPage),
|
|
20804
|
-
pages: () => context.pages().map(wrap),
|
|
20805
|
-
newPage: async () => wrap(await context.newPage()),
|
|
20806
|
-
close: () => context.close()
|
|
20807
|
-
};
|
|
20999
|
+
function isRecord(value) {
|
|
21000
|
+
return typeof value === "object" && value !== null;
|
|
20808
21001
|
}
|
|
20809
|
-
|
|
20810
|
-
|
|
20811
|
-
|
|
20812
|
-
|
|
20813
|
-
}
|
|
20814
|
-
|
|
20815
|
-
|
|
20816
|
-
if (
|
|
21002
|
+
function parseJsonBody(text, what) {
|
|
21003
|
+
let body;
|
|
21004
|
+
try {
|
|
21005
|
+
body = JSON.parse(text);
|
|
21006
|
+
} catch {
|
|
21007
|
+
throw new WechatCgiError("bad-receipt", `${what}: response is not JSON`);
|
|
21008
|
+
}
|
|
21009
|
+
if (!isRecord(body)) {
|
|
21010
|
+
throw new WechatCgiError("bad-receipt", `${what}: response is not a JSON object`);
|
|
21011
|
+
}
|
|
21012
|
+
return body;
|
|
21013
|
+
}
|
|
21014
|
+
function assertBaseResp(body, what) {
|
|
21015
|
+
const base = body.base_resp;
|
|
21016
|
+
if (base === undefined)
|
|
20817
21017
|
return;
|
|
20818
|
-
|
|
21018
|
+
if (!isRecord(base)) {
|
|
21019
|
+
throw new WechatCgiError("bad-receipt", `${what}: malformed base_resp`);
|
|
21020
|
+
}
|
|
21021
|
+
if (base.ret !== undefined && base.ret !== 0) {
|
|
21022
|
+
throw new WechatCgiError("bad-receipt", `${what}: base_resp.ret=${String(base.ret)}`);
|
|
21023
|
+
}
|
|
21024
|
+
if (base.err_msg !== undefined && base.err_msg !== "ok") {
|
|
21025
|
+
throw new WechatCgiError("bad-receipt", `${what}: err_msg=${String(base.err_msg)}`);
|
|
21026
|
+
}
|
|
21027
|
+
}
|
|
21028
|
+
function parseFiletransferReceipt(text) {
|
|
21029
|
+
const body = parseJsonBody(text, "filetransfer");
|
|
21030
|
+
assertBaseResp(body, "filetransfer");
|
|
21031
|
+
const content = body.content;
|
|
21032
|
+
if (typeof content !== "string" || !/^\d+$/.test(content)) {
|
|
21033
|
+
throw new WechatCgiError("bad-receipt", "filetransfer: content file ID is missing or not numeric");
|
|
21034
|
+
}
|
|
21035
|
+
const cdnUrl = body.cdn_url;
|
|
21036
|
+
if (typeof cdnUrl !== "string" || !isAllowedCdnUrl(cdnUrl)) {
|
|
21037
|
+
throw new WechatCgiError("bad-receipt", "filetransfer: cdn_url is missing or outside the allowed HTTPS CDN host");
|
|
21038
|
+
}
|
|
21039
|
+
const width = body.width;
|
|
21040
|
+
const height = body.height;
|
|
21041
|
+
if (typeof width !== "number" || typeof height !== "number" || width < 1 || height < 1) {
|
|
21042
|
+
throw new WechatCgiError("bad-receipt", "filetransfer: receipt did not return usable image dimensions");
|
|
21043
|
+
}
|
|
21044
|
+
return { fileId: content, url: cdnUrl, width, height };
|
|
21045
|
+
}
|
|
21046
|
+
function parseCropMultiReceipt(text, crops) {
|
|
21047
|
+
const body = parseJsonBody(text, "crop_multi");
|
|
21048
|
+
assertBaseResp(body, "crop_multi");
|
|
21049
|
+
const entries = Array.isArray(body.result) ? body.result : [];
|
|
21050
|
+
const entryCount = entries.length;
|
|
21051
|
+
if (entryCount !== 2) {
|
|
21052
|
+
throw new WechatCgiError("bad-receipt", `crop_multi: expected exactly two result entries, got ${entryCount}`);
|
|
21053
|
+
}
|
|
21054
|
+
let wide;
|
|
21055
|
+
let square;
|
|
21056
|
+
entries.forEach((entry, index) => {
|
|
21057
|
+
const dims = index === 0 ? crops[0] : crops[1];
|
|
21058
|
+
if (!isRecord(entry)) {
|
|
21059
|
+
throw new WechatCgiError("bad-receipt", `crop_multi: result entry ${index} is not an object`);
|
|
21060
|
+
}
|
|
21061
|
+
const cdnurl = entry.cdnurl;
|
|
21062
|
+
const fileId = entry.file_id;
|
|
21063
|
+
if (typeof cdnurl !== "string" || !isAllowedCdnUrl(cdnurl)) {
|
|
21064
|
+
throw new WechatCgiError("bad-receipt", `crop_multi: entry ${index} cdnurl is outside the allowed HTTPS CDN host`);
|
|
21065
|
+
}
|
|
21066
|
+
if (typeof fileId !== "string" || fileId === "") {
|
|
21067
|
+
throw new WechatCgiError("bad-receipt", `crop_multi: entry ${index} file_id is missing`);
|
|
21068
|
+
}
|
|
21069
|
+
const image = {
|
|
21070
|
+
fileId,
|
|
21071
|
+
url: cdnurl,
|
|
21072
|
+
width: Math.max(1, dims.x2 - dims.x1),
|
|
21073
|
+
height: Math.max(1, dims.y2 - dims.y1)
|
|
21074
|
+
};
|
|
21075
|
+
if (index === 0) {
|
|
21076
|
+
wide = image;
|
|
21077
|
+
} else {
|
|
21078
|
+
square = image;
|
|
21079
|
+
}
|
|
21080
|
+
});
|
|
21081
|
+
if (wide === undefined || square === undefined) {
|
|
21082
|
+
throw new WechatCgiError("bad-receipt", "crop_multi: receipt did not fill both crop entries");
|
|
21083
|
+
}
|
|
21084
|
+
return [wide, square];
|
|
20819
21085
|
}
|
|
20820
|
-
|
|
20821
|
-
|
|
20822
|
-
var DIALOG = ".weui-desktop-dialog:visible";
|
|
20823
|
-
var CONFIRM = `${DIALOG} button:has-text("\u786E\u8BA4"), ${DIALOG} button:has-text("\u786E\u5B9A")`;
|
|
20824
|
-
var ROW_ORIGINAL = [
|
|
20825
|
-
".setting-group__switch.js_original_apply.js_edit_ori",
|
|
20826
|
-
".setting-group__switch.js_edit_ori",
|
|
20827
|
-
".js_edit_ori",
|
|
20828
|
-
"text=\u539F\u521B"
|
|
20829
|
-
];
|
|
20830
|
-
var ROW_REWARD = [
|
|
20831
|
-
".setting-group__switch.js_reward_open",
|
|
20832
|
-
".js_reward_open",
|
|
20833
|
-
".js_reward_allow_click",
|
|
20834
|
-
".setting-group__switch.js_can_reward",
|
|
20835
|
-
"text=\u8D5E\u8D4F"
|
|
20836
|
-
];
|
|
20837
|
-
var ROW_COLLECTION = [".allow_click_opr.js_article_tags_label", "text=\u5408\u96C6"];
|
|
20838
|
-
var ROW_URL = [".allow_click_opr.js_url_checkbox", "text=\u539F\u6587\u94FE\u63A5"];
|
|
20839
|
-
var ROW_CLAIM = ["#js_claim_source_area .frm_checkbox_label", "text=\u521B\u4F5C\u6765\u6E90"];
|
|
20840
|
-
var DIALOG_CONFIRM = CONFIRM;
|
|
20841
|
-
var TAG_INPUT = 'input[placeholder*="\u5408\u96C6"]';
|
|
20842
|
-
var REWARD_ACCOUNT = [
|
|
20843
|
-
`${DIALOG} input[placeholder*="\u8D5E\u8D4F"]`,
|
|
20844
|
-
`${DIALOG} input[placeholder*="\u8D26\u6237"]`,
|
|
20845
|
-
`${DIALOG} input.frm_input`
|
|
20846
|
-
];
|
|
20847
|
-
var URL_INPUT = [`${DIALOG} input[type="url"]`, `${DIALOG} input[placeholder*="\u94FE\u63A5"]`, `${DIALOG} input.frm_input`];
|
|
20848
|
-
var ORIGINAL_DECLARATION = `${DIALOG} label:has(.js_original_type_radio):has-text("\u6587\u5B57\u539F\u521B")`;
|
|
20849
|
-
var ORIGINAL_AGREEMENT = `${DIALOG} .original_agreement`;
|
|
20850
|
-
var COLLECTION_SETTLE_MS = 1200;
|
|
20851
|
-
var STEP_RETRY_SETTLE_MS = 1000;
|
|
20852
|
-
var SWEEP_SETTLE_MS = 400;
|
|
20853
|
-
|
|
20854
|
-
class CapabilityError extends Error {
|
|
21086
|
+
function buildUrl(base, params) {
|
|
21087
|
+
return `${base}?${new URLSearchParams(params).toString()}`;
|
|
20855
21088
|
}
|
|
20856
|
-
function
|
|
20857
|
-
return
|
|
21089
|
+
function randomRequestSalt() {
|
|
21090
|
+
return Math.floor(Math.random() * 1e9);
|
|
20858
21091
|
}
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
|
|
20876
|
-
|
|
20877
|
-
return { enabled, text };
|
|
20878
|
-
})()`;
|
|
20879
|
-
async function readChecked(page, expression) {
|
|
21092
|
+
function filetransferUrl(session) {
|
|
21093
|
+
return buildUrl(FILETRANSFER_URL, {
|
|
21094
|
+
action: "upload_material",
|
|
21095
|
+
f: "json",
|
|
21096
|
+
scene: "8",
|
|
21097
|
+
writetype: "doublewrite",
|
|
21098
|
+
groupid: "1",
|
|
21099
|
+
ticket_id: session.userName,
|
|
21100
|
+
ticket: session.ticket,
|
|
21101
|
+
svr_time: String(Math.floor(Date.now() / 1000)),
|
|
21102
|
+
token: session.token,
|
|
21103
|
+
lang: "zh_CN",
|
|
21104
|
+
seq: String(randomRequestSalt()),
|
|
21105
|
+
t: String(randomRequestSalt())
|
|
21106
|
+
});
|
|
21107
|
+
}
|
|
21108
|
+
function assertMpOrigin(page) {
|
|
21109
|
+
let origin;
|
|
20880
21110
|
try {
|
|
20881
|
-
|
|
20882
|
-
return typeof raw === "boolean" ? raw : null;
|
|
21111
|
+
origin = new URL(page.url()).origin;
|
|
20883
21112
|
} catch {
|
|
20884
|
-
|
|
20885
|
-
}
|
|
20886
|
-
|
|
20887
|
-
|
|
20888
|
-
|
|
20889
|
-
|
|
20890
|
-
|
|
20891
|
-
|
|
20892
|
-
|
|
20893
|
-
|
|
20894
|
-
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
const state = await modalState(page);
|
|
20901
|
-
if (state === "open") {
|
|
20902
|
-
for (const close of [".weui-desktop-dialog__close-btn", ".weui-desktop-dialog__close"]) {
|
|
20903
|
-
const control = await tryFindSelector(page, [close], 1500);
|
|
20904
|
-
if (control === undefined)
|
|
20905
|
-
continue;
|
|
20906
|
-
try {
|
|
20907
|
-
await clickSelector(page, control, 2000);
|
|
20908
|
-
} catch {}
|
|
20909
|
-
break;
|
|
20910
|
-
}
|
|
20911
|
-
return await modalState(page) === "closed" ? "closed" : "unknown";
|
|
20912
|
-
}
|
|
20913
|
-
return state === "closed" ? "closed" : "unknown";
|
|
21113
|
+
throw new WechatCgiError("wrong-origin", `page URL is not usable for CGI dispatch: ${page.url()}`);
|
|
21114
|
+
}
|
|
21115
|
+
if (origin !== MP_ORIGIN) {
|
|
21116
|
+
throw new WechatCgiError("wrong-origin", `CGI dispatch requires page origin ${MP_ORIGIN}, got ${origin}`);
|
|
21117
|
+
}
|
|
21118
|
+
}
|
|
21119
|
+
async function uploadCoverImage(page, session, input) {
|
|
21120
|
+
assertMpOrigin(page);
|
|
21121
|
+
const image = "path" in input ? await readLocalImageBytes(input.path) : validateImageBytes(input.bytes, input.name);
|
|
21122
|
+
const now = Date.now();
|
|
21123
|
+
const fields = {
|
|
21124
|
+
type: image.mime,
|
|
21125
|
+
id: `kk_${now}_${randomRequestSalt()}`,
|
|
21126
|
+
name: image.name,
|
|
21127
|
+
lastModifiedDate: new Date(now).toString(),
|
|
21128
|
+
size: String(image.bytes.byteLength)
|
|
20914
21129
|
};
|
|
20915
|
-
const
|
|
20916
|
-
|
|
20917
|
-
|
|
20918
|
-
|
|
20919
|
-
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20923
|
-
|
|
20924
|
-
|
|
20925
|
-
|
|
20926
|
-
|
|
20927
|
-
|
|
20928
|
-
|
|
20929
|
-
|
|
20930
|
-
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
20936
|
-
|
|
20937
|
-
|
|
20938
|
-
await fn();
|
|
20939
|
-
return "ok";
|
|
20940
|
-
} catch (error) {
|
|
20941
|
-
warnings.push(`${name}: ${(error instanceof Error ? error.message : String(error)).slice(0, 120)}`);
|
|
20942
|
-
await sweep();
|
|
20943
|
-
return "failed";
|
|
20944
|
-
}
|
|
20945
|
-
};
|
|
20946
|
-
const residual = async (name) => {
|
|
20947
|
-
const state = await modalState(page);
|
|
20948
|
-
if (state === "closed")
|
|
20949
|
-
return false;
|
|
20950
|
-
warnings.push(state === "open" ? `${name}: dialog did not close \u2014 check the draft` : `${name}: dialog state unknown after cleanup \u2014 a residual overlay may block later steps`);
|
|
20951
|
-
return true;
|
|
21130
|
+
const { text } = await postCgiForm(page, {
|
|
21131
|
+
url: filetransferUrl(session),
|
|
21132
|
+
fields,
|
|
21133
|
+
file: {
|
|
21134
|
+
field: "file",
|
|
21135
|
+
filename: image.name,
|
|
21136
|
+
mime: image.mime,
|
|
21137
|
+
base64: Buffer.from(image.bytes).toString("base64")
|
|
21138
|
+
},
|
|
21139
|
+
maxBytes: MAX_RESPONSE_BYTES,
|
|
21140
|
+
timeoutMs: REQUEST_TIMEOUT_MS
|
|
21141
|
+
});
|
|
21142
|
+
return parseFiletransferReceipt(text);
|
|
21143
|
+
}
|
|
21144
|
+
async function cropCoverMulti(page, session, imageUrl, crops) {
|
|
21145
|
+
assertMpOrigin(page);
|
|
21146
|
+
const fields = {
|
|
21147
|
+
imgurl: imageUrl,
|
|
21148
|
+
size_count: "2",
|
|
21149
|
+
token: session.token,
|
|
21150
|
+
lang: "zh_CN",
|
|
21151
|
+
f: "json",
|
|
21152
|
+
ajax: "1"
|
|
20952
21153
|
};
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
|
|
20965
|
-
|
|
20966
|
-
|
|
20967
|
-
|
|
20968
|
-
|
|
20969
|
-
|
|
20970
|
-
|
|
20971
|
-
|
|
20972
|
-
|
|
20973
|
-
|
|
20974
|
-
|
|
20975
|
-
const blocked = await tryFindSelector(page, [`${DIALOG} :has-text("\u524D\u5F80\u5F00\u901A")`, `${DIALOG} :has-text("\u65E0\u6CD5\u5F00\u542F")`], 1500);
|
|
20976
|
-
if (blocked !== undefined)
|
|
20977
|
-
throw new CapabilityError("\u9700\u8981\u8D5E\u8D4F\u8D26\u6237\uFF08\u8FD0\u8425\u8005\u5148\u5728\u5E73\u53F0\u5F00\u901A\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u81EA\u52A8\u586B\u5199");
|
|
20978
|
-
const author = str("author");
|
|
20979
|
-
if (author !== "") {
|
|
20980
|
-
for (const selector of REWARD_ACCOUNT) {
|
|
20981
|
-
const found = await trySelectors(page, [selector]);
|
|
20982
|
-
if (!found.found)
|
|
20983
|
-
continue;
|
|
20984
|
-
const locator = page.locator(selector).first();
|
|
20985
|
-
if (typeof locator.inputValue !== "function")
|
|
20986
|
-
break;
|
|
20987
|
-
const empty = norm(await locator.inputValue().catch(() => null)) === "";
|
|
20988
|
-
if (empty)
|
|
20989
|
-
await fillSelector(page, selector, author);
|
|
20990
|
-
break;
|
|
20991
|
-
}
|
|
20992
|
-
}
|
|
20993
|
-
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
20994
|
-
});
|
|
20995
|
-
if (outcome !== "capability" && await readChecked(page, COMMITTED.reward) !== true && !await residual(name)) {
|
|
20996
|
-
warnings.push(`${name}: \u8D5E\u8D4F\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E`);
|
|
20997
|
-
}
|
|
20998
|
-
}
|
|
20999
|
-
}
|
|
21000
|
-
const collection = str("collection");
|
|
21001
|
-
if (collection !== "") {
|
|
21002
|
-
const name = "\u5408\u96C6";
|
|
21003
|
-
await step(name, async () => {
|
|
21004
|
-
await openRow(ROW_COLLECTION, "\u5408\u96C6");
|
|
21005
|
-
await waitForVisible(page, TAG_INPUT, 8000);
|
|
21006
|
-
await fillSelector(page, TAG_INPUT, collection);
|
|
21007
|
-
await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
|
|
21008
|
-
await clickSelector(page, `${DIALOG} :text("${collection}")`, 8000);
|
|
21009
|
-
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
21010
|
-
});
|
|
21011
|
-
await residual(name);
|
|
21012
|
-
}
|
|
21013
|
-
const originalUrl = str("originalUrl");
|
|
21014
|
-
if (originalUrl !== "") {
|
|
21015
|
-
const name = "\u539F\u6587\u94FE\u63A5";
|
|
21016
|
-
const urlRow = await tryFindSelector(page, ROW_URL, 2000);
|
|
21017
|
-
if (urlRow === undefined) {
|
|
21018
|
-
warnings.push(`${name}: \u884C\u4E0D\u5B58\u5728\uFF08\u5F53\u524D\u516C\u4F17\u53F7\u4E0D\u652F\u6301\uFF09\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u6216\u79FB\u9664 originalUrl`);
|
|
21019
|
-
} else {
|
|
21020
|
-
await step(name, async () => {
|
|
21021
|
-
await openRow(ROW_URL, "\u539F\u6587\u94FE\u63A5");
|
|
21022
|
-
const field = await trySelectors(page, URL_INPUT);
|
|
21023
|
-
if (!field.found)
|
|
21024
|
-
throw new Error("no URL field in the \u539F\u6587\u94FE\u63A5 dialog");
|
|
21025
|
-
await fillSelector(page, field.selector ?? URL_INPUT.join(", "), originalUrl);
|
|
21026
|
-
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
21027
|
-
});
|
|
21028
|
-
await residual(name);
|
|
21029
|
-
const locator = page.locator(URL_INPUT[0] ?? "").first();
|
|
21030
|
-
const committed = typeof locator.inputValue === "function" ? await locator.inputValue().catch(() => null) : null;
|
|
21031
|
-
if (norm(committed) !== norm(originalUrl))
|
|
21032
|
-
warnings.push(`${name}: \u5DF2\u63D0\u4EA4\u4F46\u672A\u8BFB\u53D6\u5230\u5DF2\u4FDD\u5B58\u7684\u94FE\u63A5\uFF0C\u8BF7\u6838\u5BF9`);
|
|
21033
|
-
}
|
|
21034
|
-
}
|
|
21035
|
-
const claimSource = str("claimSource");
|
|
21036
|
-
if (claimSource !== "") {
|
|
21037
|
-
const name = "\u521B\u4F5C\u6765\u6E90";
|
|
21038
|
-
const claimMatched = async () => {
|
|
21039
|
-
try {
|
|
21040
|
-
const raw = await page.evaluate(CLAIM_COMMITTED);
|
|
21041
|
-
if (raw === null || typeof raw !== "object")
|
|
21042
|
-
return null;
|
|
21043
|
-
return raw.enabled === true && norm(raw.text) === norm(claimSource);
|
|
21044
|
-
} catch {
|
|
21045
|
-
return null;
|
|
21046
|
-
}
|
|
21047
|
-
};
|
|
21048
|
-
if (await claimMatched() !== true) {
|
|
21049
|
-
await step(name, async () => {
|
|
21050
|
-
await openRow(ROW_CLAIM, "\u521B\u4F5C\u6765\u6E90");
|
|
21051
|
-
const dialog = await waitForVisible(page, DIALOG, 3000, { required: false });
|
|
21052
|
-
if (dialog !== undefined) {
|
|
21053
|
-
await clickSelector(page, `${DIALOG} :text("${claimSource}")`, 8000);
|
|
21054
|
-
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
21055
|
-
}
|
|
21056
|
-
});
|
|
21057
|
-
}
|
|
21058
|
-
if (await claimMatched() !== true && !await residual(name)) {
|
|
21059
|
-
warnings.push(`${name}: \u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E`);
|
|
21060
|
-
}
|
|
21061
|
-
}
|
|
21062
|
-
await sweep();
|
|
21063
|
-
return warnings;
|
|
21154
|
+
crops.forEach((crop, index) => {
|
|
21155
|
+
fields[`size${index}_x1`] = String(crop.x1);
|
|
21156
|
+
fields[`size${index}_y1`] = String(crop.y1);
|
|
21157
|
+
fields[`size${index}_x2`] = String(crop.x2);
|
|
21158
|
+
fields[`size${index}_y2`] = String(crop.y2);
|
|
21159
|
+
});
|
|
21160
|
+
const { text } = await postCgiForm(page, {
|
|
21161
|
+
url: buildUrl(CROPIMAGE_URL, { action: "crop_multi" }),
|
|
21162
|
+
fields,
|
|
21163
|
+
maxBytes: MAX_RESPONSE_BYTES,
|
|
21164
|
+
timeoutMs: REQUEST_TIMEOUT_MS
|
|
21165
|
+
});
|
|
21166
|
+
return parseCropMultiReceipt(text, crops);
|
|
21167
|
+
}
|
|
21168
|
+
async function uploadAndCropCover(page, session, input) {
|
|
21169
|
+
const uploaded = await uploadCoverImage(page, session, input);
|
|
21170
|
+
const crops = [
|
|
21171
|
+
centerCropRect(uploaded.width, uploaded.height, WIDE_COVER_ASPECT),
|
|
21172
|
+
centerCropRect(uploaded.width, uploaded.height, SQUARE_COVER_ASPECT)
|
|
21173
|
+
];
|
|
21174
|
+
const [wide, square] = await cropCoverMulti(page, session, uploaded.url, crops);
|
|
21175
|
+
return { wide, square, crops };
|
|
21064
21176
|
}
|
|
21065
21177
|
|
|
21066
21178
|
// ../../plugins/publishings/wechatmp-pub/src/modes.ts
|
|
@@ -21164,156 +21276,1132 @@ async function resolveBodyImages(content) {
|
|
|
21164
21276
|
if (typeof path !== "string" || path.length === 0) {
|
|
21165
21277
|
return { error: `images validation: images[${index}] carries no path` };
|
|
21166
21278
|
}
|
|
21167
|
-
candidates.push({ path, what: `metadata.images[${index}]` });
|
|
21279
|
+
candidates.push({ path, what: `metadata.images[${index}]` });
|
|
21280
|
+
}
|
|
21281
|
+
const paths = [];
|
|
21282
|
+
for (const { path, what } of candidates) {
|
|
21283
|
+
if (!isAbsolute2(path)) {
|
|
21284
|
+
return { error: `images validation: ${what} must be absolute, got '${path}'` };
|
|
21285
|
+
}
|
|
21286
|
+
if (!IMAGE_EXTENSIONS.test(path)) {
|
|
21287
|
+
return {
|
|
21288
|
+
error: `images validation: ${what} has unsupported extension (expected ${IMAGE_LIST}): '${path}'`
|
|
21289
|
+
};
|
|
21290
|
+
}
|
|
21291
|
+
if (!await createNodeFileSystem().exists(path)) {
|
|
21292
|
+
return { error: `images validation: ${what} does not exist: '${path}'` };
|
|
21293
|
+
}
|
|
21294
|
+
paths.push(path);
|
|
21295
|
+
}
|
|
21296
|
+
return { paths };
|
|
21297
|
+
}
|
|
21298
|
+
function deriveDigest(content) {
|
|
21299
|
+
const explicit = content.metadata?.digest;
|
|
21300
|
+
if (typeof explicit === "string")
|
|
21301
|
+
return explicit;
|
|
21302
|
+
const body = content.body ?? "";
|
|
21303
|
+
const first = body.split(`
|
|
21304
|
+
`).map((line) => line.trim()).find((line) => line !== "");
|
|
21305
|
+
return first === undefined ? undefined : first.slice(0, DERIVED_DIGEST_MAX);
|
|
21306
|
+
}
|
|
21307
|
+
function validatePublishStatus(content) {
|
|
21308
|
+
const status = content.metadata?.publishStatus;
|
|
21309
|
+
if (status !== undefined && status !== "draft" && status !== "public") {
|
|
21310
|
+
return {
|
|
21311
|
+
error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
|
|
21312
|
+
};
|
|
21313
|
+
}
|
|
21314
|
+
return {};
|
|
21315
|
+
}
|
|
21316
|
+
function validateTitle(content, operation) {
|
|
21317
|
+
const limits = MODE_LIMITS[operation];
|
|
21318
|
+
const title = content.title ?? "";
|
|
21319
|
+
if (limits.titleRequired === true && title.trim().length === 0) {
|
|
21320
|
+
return {
|
|
21321
|
+
error: `title validation: Content.title is required for ${operation} (the editor's placeholder is \u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898)`
|
|
21322
|
+
};
|
|
21323
|
+
}
|
|
21324
|
+
if (limits.titleMax !== undefined && title.length > limits.titleMax) {
|
|
21325
|
+
return {
|
|
21326
|
+
error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${limits.titleMax}`
|
|
21327
|
+
};
|
|
21328
|
+
}
|
|
21329
|
+
return {};
|
|
21330
|
+
}
|
|
21331
|
+
function validateDigest(content, operation) {
|
|
21332
|
+
const max = MODE_LIMITS[operation].digestMax;
|
|
21333
|
+
if (max === undefined)
|
|
21334
|
+
return {};
|
|
21335
|
+
const digest = deriveDigest(content);
|
|
21336
|
+
if (digest !== undefined && digest.length > max) {
|
|
21337
|
+
return { error: `digest validation: the \u6458\u8981 is ${digest.length} chars but ${operation} accepts \u2264${max}` };
|
|
21338
|
+
}
|
|
21339
|
+
return digest === undefined ? {} : { digest };
|
|
21340
|
+
}
|
|
21341
|
+
function validateAuthor(content, operation) {
|
|
21342
|
+
const max = MODE_LIMITS[operation].authorMax;
|
|
21343
|
+
const author = content.metadata?.author;
|
|
21344
|
+
if (typeof author !== "string" || author.length === 0)
|
|
21345
|
+
return {};
|
|
21346
|
+
if (max !== undefined && author.length > max) {
|
|
21347
|
+
return {
|
|
21348
|
+
error: `author validation: Content.metadata.author is ${author.length} chars but ${operation} accepts \u2264${max}`
|
|
21349
|
+
};
|
|
21350
|
+
}
|
|
21351
|
+
return { author };
|
|
21352
|
+
}
|
|
21353
|
+
async function validateModeInput(operation, content) {
|
|
21354
|
+
const status = validatePublishStatus(content);
|
|
21355
|
+
if (status.error)
|
|
21356
|
+
return { error: status.error };
|
|
21357
|
+
const title = validateTitle(content, operation);
|
|
21358
|
+
if (title.error)
|
|
21359
|
+
return { error: title.error };
|
|
21360
|
+
const author = validateAuthor(content, operation);
|
|
21361
|
+
if (author.error)
|
|
21362
|
+
return { error: author.error };
|
|
21363
|
+
switch (operation) {
|
|
21364
|
+
case "\u6587\u7AE0": {
|
|
21365
|
+
if ((content.body ?? "").trim().length === 0) {
|
|
21366
|
+
return { error: "body validation: Content.body is required for \u6587\u7AE0" };
|
|
21367
|
+
}
|
|
21368
|
+
const digest = validateDigest(content, operation);
|
|
21369
|
+
if (digest.error)
|
|
21370
|
+
return { error: digest.error };
|
|
21371
|
+
const images = await resolveBodyImages(content);
|
|
21372
|
+
if (images.error)
|
|
21373
|
+
return { error: images.error };
|
|
21374
|
+
if ((images.paths ?? []).length === 0) {
|
|
21375
|
+
return {
|
|
21376
|
+
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[]"
|
|
21377
|
+
};
|
|
21378
|
+
}
|
|
21379
|
+
return {
|
|
21380
|
+
input: {
|
|
21381
|
+
operation,
|
|
21382
|
+
images: images.paths ?? [],
|
|
21383
|
+
...digest.digest === undefined ? {} : { digest: digest.digest }
|
|
21384
|
+
}
|
|
21385
|
+
};
|
|
21386
|
+
}
|
|
21387
|
+
case "\u8D34\u56FE": {
|
|
21388
|
+
const images = await resolveBodyImages(content);
|
|
21389
|
+
if (images.error)
|
|
21390
|
+
return { error: images.error };
|
|
21391
|
+
if ((images.paths ?? []).length === 0) {
|
|
21392
|
+
return {
|
|
21393
|
+
error: "image validation: \u8D34\u56FE requires at least one image (metadata.images[] or metadata.wechatmp.coverPath)"
|
|
21394
|
+
};
|
|
21395
|
+
}
|
|
21396
|
+
return { input: { operation, images: images.paths ?? [] } };
|
|
21397
|
+
}
|
|
21398
|
+
case "\u89C6\u9891": {
|
|
21399
|
+
const video = await resolveMediaPath(content, {
|
|
21400
|
+
key: "video",
|
|
21401
|
+
label: "video validation",
|
|
21402
|
+
extensions: VIDEO_EXTENSIONS,
|
|
21403
|
+
list: VIDEO_LIST
|
|
21404
|
+
});
|
|
21405
|
+
if (video.error)
|
|
21406
|
+
return { error: video.error };
|
|
21407
|
+
return { input: { operation, video: video.path ?? "" } };
|
|
21408
|
+
}
|
|
21409
|
+
case "\u64AD\u5BA2": {
|
|
21410
|
+
const audio = await resolveMediaPath(content, {
|
|
21411
|
+
key: "audio",
|
|
21412
|
+
label: "audio validation",
|
|
21413
|
+
extensions: AUDIO_EXTENSIONS,
|
|
21414
|
+
list: AUDIO_LIST
|
|
21415
|
+
});
|
|
21416
|
+
if (audio.error)
|
|
21417
|
+
return { error: audio.error };
|
|
21418
|
+
const cover = await resolveMediaPath(content, {
|
|
21419
|
+
key: "coverImage",
|
|
21420
|
+
label: "cover validation",
|
|
21421
|
+
extensions: IMAGE_EXTENSIONS,
|
|
21422
|
+
list: IMAGE_LIST
|
|
21423
|
+
});
|
|
21424
|
+
if (cover.error)
|
|
21425
|
+
return { error: cover.error };
|
|
21426
|
+
return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
|
|
21427
|
+
}
|
|
21428
|
+
}
|
|
21429
|
+
}
|
|
21430
|
+
|
|
21431
|
+
// ../../plugins/publishings/wechatmp-pub/src/cgi-draft.ts
|
|
21432
|
+
var MP_ORIGIN2 = "https://mp.weixin.qq.com";
|
|
21433
|
+
var CREATE_URL = `${MP_ORIGIN2}/cgi-bin/operate_appmsg`;
|
|
21434
|
+
var APPMSG_URL = `${MP_ORIGIN2}/cgi-bin/appmsg`;
|
|
21435
|
+
var MAX_RESPONSE_BYTES2 = 2 * 1024 * 1024;
|
|
21436
|
+
var REQUEST_TIMEOUT_MS2 = 30000;
|
|
21437
|
+
var MAX_SNAPSHOT_CHARS = 2 * 1024 * 1024;
|
|
21438
|
+
var DEFAULT_OPERATION = "\u6587\u7AE0";
|
|
21439
|
+
|
|
21440
|
+
class WechatCgiDraftError extends Error {
|
|
21441
|
+
code;
|
|
21442
|
+
stage;
|
|
21443
|
+
metadata;
|
|
21444
|
+
constructor(code, stage, message, metadata = {}) {
|
|
21445
|
+
super(message);
|
|
21446
|
+
this.code = code;
|
|
21447
|
+
this.stage = stage;
|
|
21448
|
+
this.metadata = metadata;
|
|
21449
|
+
this.name = "WechatCgiDraftError";
|
|
21450
|
+
}
|
|
21451
|
+
}
|
|
21452
|
+
var ALLOWED_METADATA_KEYS = new Set([
|
|
21453
|
+
"operation",
|
|
21454
|
+
"publishStatus",
|
|
21455
|
+
"title",
|
|
21456
|
+
"body",
|
|
21457
|
+
"digest",
|
|
21458
|
+
"author",
|
|
21459
|
+
"contentId",
|
|
21460
|
+
"source",
|
|
21461
|
+
"sourceUrl",
|
|
21462
|
+
"tags",
|
|
21463
|
+
"categories",
|
|
21464
|
+
"coverPath",
|
|
21465
|
+
"images"
|
|
21466
|
+
]);
|
|
21467
|
+
function isNonemptyString(value) {
|
|
21468
|
+
return typeof value === "string" && value.length > 0;
|
|
21469
|
+
}
|
|
21470
|
+
function isRecord2(value) {
|
|
21471
|
+
return typeof value === "object" && value !== null;
|
|
21472
|
+
}
|
|
21473
|
+
function certifyCgiDraft(content) {
|
|
21474
|
+
const metadata = content.metadata ?? {};
|
|
21475
|
+
function refuse(detail) {
|
|
21476
|
+
throw new WechatCgiDraftError("ineligible", "eligibility", `content is not eligible for the CGI draft path: ${detail}`);
|
|
21477
|
+
}
|
|
21478
|
+
const operation = metadata.operation ?? DEFAULT_OPERATION;
|
|
21479
|
+
if (operation !== DEFAULT_OPERATION) {
|
|
21480
|
+
refuse(`operation is not ${DEFAULT_OPERATION}`);
|
|
21481
|
+
}
|
|
21482
|
+
if (metadata.publishStatus !== undefined && metadata.publishStatus !== "draft") {
|
|
21483
|
+
refuse("publishStatus is not draft");
|
|
21484
|
+
}
|
|
21485
|
+
if (!isNonemptyString(content.title)) {
|
|
21486
|
+
refuse("title is missing or empty");
|
|
21487
|
+
}
|
|
21488
|
+
if (!isNonemptyString(content.body)) {
|
|
21489
|
+
refuse("body is missing or empty");
|
|
21490
|
+
}
|
|
21491
|
+
for (const key of ["digest", "author"]) {
|
|
21492
|
+
if (metadata[key] !== undefined && typeof metadata[key] !== "string") {
|
|
21493
|
+
refuse(`${key} is present but not a string`);
|
|
21494
|
+
}
|
|
21495
|
+
}
|
|
21496
|
+
for (const key of Object.keys(metadata)) {
|
|
21497
|
+
if (!ALLOWED_METADATA_KEYS.has(key)) {
|
|
21498
|
+
refuse(`metadata key '${key}' is outside the plain-article set`);
|
|
21499
|
+
}
|
|
21500
|
+
}
|
|
21501
|
+
return {
|
|
21502
|
+
operation: DEFAULT_OPERATION,
|
|
21503
|
+
publishStatus: "draft",
|
|
21504
|
+
title: content.title,
|
|
21505
|
+
body: content.body,
|
|
21506
|
+
digest: typeof metadata.digest === "string" ? metadata.digest : "",
|
|
21507
|
+
author: typeof metadata.author === "string" ? metadata.author : ""
|
|
21508
|
+
};
|
|
21509
|
+
}
|
|
21510
|
+
function assertEligibleCgiDraft(content) {
|
|
21511
|
+
certifyCgiDraft(content);
|
|
21512
|
+
}
|
|
21513
|
+
function stripQuery2(detail) {
|
|
21514
|
+
return detail.replace(/\?[^'"\\\s]*/g, "?\u2026");
|
|
21515
|
+
}
|
|
21516
|
+
function assertMpOrigin2(page) {
|
|
21517
|
+
let origin;
|
|
21518
|
+
try {
|
|
21519
|
+
origin = new URL(page.url()).origin;
|
|
21520
|
+
} catch {
|
|
21521
|
+
throw new WechatCgiDraftError("request-failed", "assert-origin", `page URL is not usable for CGI dispatch: ${page.url()}`);
|
|
21522
|
+
}
|
|
21523
|
+
if (origin !== MP_ORIGIN2) {
|
|
21524
|
+
throw new WechatCgiDraftError("request-failed", "assert-origin", `CGI dispatch requires page origin ${MP_ORIGIN2}, got ${origin}`);
|
|
21525
|
+
}
|
|
21526
|
+
}
|
|
21527
|
+
function extractSessionExpression() {
|
|
21528
|
+
return `(function () {
|
|
21529
|
+
const fields = {};
|
|
21530
|
+
const urlToken = new URLSearchParams(location.search).get('token');
|
|
21531
|
+
if (urlToken !== null && /^\\d{4,32}$/.test(urlToken)) fields.token = urlToken;
|
|
21532
|
+
const snapshot = document.documentElement.outerHTML.slice(0, ${MAX_SNAPSHOT_CHARS});
|
|
21533
|
+
const first = (regex) => {
|
|
21534
|
+
const match = regex.exec(snapshot);
|
|
21535
|
+
return match === null ? undefined : match[1];
|
|
21536
|
+
};
|
|
21537
|
+
// URL token wins, but ticket/user_name/uin are scan-only: a URL-token page still needs them,
|
|
21538
|
+
// so only the token lookup is skipped when the URL already supplied it (task 0290 chunk C bugfix).
|
|
21539
|
+
if (!fields.token) {
|
|
21540
|
+
const token = first(/\\btoken\\s*=\\s*['"](\\d{4,32})['"]/);
|
|
21541
|
+
if (token !== undefined) fields.token = token;
|
|
21542
|
+
}
|
|
21543
|
+
const ticketId = first(/\\bticket_id\\s*=\\s*['"]([A-Za-z0-9]{8,128})['"]/);
|
|
21544
|
+
if (ticketId !== undefined) fields.ticketId = ticketId;
|
|
21545
|
+
const ticket = first(/\\bticket\\s*=\\s*['"]([A-Za-z0-9]{8,128})['"]/);
|
|
21546
|
+
if (ticket !== undefined) fields.ticket = ticket;
|
|
21547
|
+
const userName = first(/\\buser_name\\s*=\\s*['"](@[A-Za-z0-9_-]{1,63}|gh_[A-Za-z0-9_-]{1,61})['"]/);
|
|
21548
|
+
if (userName !== undefined) fields.userName = userName;
|
|
21549
|
+
const uin = first(/\\buin\\s*=\\s*['"](\\d{1,32})['"]/);
|
|
21550
|
+
if (uin !== undefined) fields.uin = uin;
|
|
21551
|
+
return JSON.stringify({ kind: 'ok', ...fields });
|
|
21552
|
+
})()`;
|
|
21553
|
+
}
|
|
21554
|
+
function readSessionFields(raw) {
|
|
21555
|
+
if (typeof raw !== "string")
|
|
21556
|
+
return {};
|
|
21557
|
+
let parsed;
|
|
21558
|
+
try {
|
|
21559
|
+
parsed = JSON.parse(raw);
|
|
21560
|
+
} catch {
|
|
21561
|
+
return {};
|
|
21562
|
+
}
|
|
21563
|
+
if (!isRecord2(parsed))
|
|
21564
|
+
return {};
|
|
21565
|
+
const field = (key) => {
|
|
21566
|
+
const value = parsed[key];
|
|
21567
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
21568
|
+
};
|
|
21569
|
+
return {
|
|
21570
|
+
token: field("token"),
|
|
21571
|
+
ticket: field("ticket"),
|
|
21572
|
+
ticketId: field("ticketId"),
|
|
21573
|
+
userName: field("userName"),
|
|
21574
|
+
uin: field("uin")
|
|
21575
|
+
};
|
|
21576
|
+
}
|
|
21577
|
+
async function extractWechatCgiSession(page) {
|
|
21578
|
+
assertMpOrigin2(page);
|
|
21579
|
+
let raw;
|
|
21580
|
+
try {
|
|
21581
|
+
raw = await page.evaluate(extractSessionExpression());
|
|
21582
|
+
} catch (error) {
|
|
21583
|
+
throw new WechatCgiDraftError("session-missing", "extract-session", `session extraction failed inside the console page: ${stripQuery2(String(error))}`);
|
|
21584
|
+
}
|
|
21585
|
+
const fields = readSessionFields(raw);
|
|
21586
|
+
const userName = fields.userName ?? fields.ticketId;
|
|
21587
|
+
const { token, ticket } = fields;
|
|
21588
|
+
if (token === undefined || ticket === undefined || userName === undefined) {
|
|
21589
|
+
const missing = [
|
|
21590
|
+
...token === undefined ? ["token"] : [],
|
|
21591
|
+
...ticket === undefined ? ["ticket"] : [],
|
|
21592
|
+
...userName === undefined ? ["user_name"] : []
|
|
21593
|
+
];
|
|
21594
|
+
throw new WechatCgiDraftError("session-missing", "extract-session", `authenticated session field(s) missing on the console page: ${missing.join(", ")}`);
|
|
21595
|
+
}
|
|
21596
|
+
return { token, ticket, userName, uin: fields.uin };
|
|
21597
|
+
}
|
|
21598
|
+
function buildUrl2(base, params) {
|
|
21599
|
+
return `${base}?${new URLSearchParams(params).toString()}`;
|
|
21600
|
+
}
|
|
21601
|
+
function randomHex16() {
|
|
21602
|
+
const bytes = crypto.getRandomValues(new Uint8Array(8));
|
|
21603
|
+
let hex = "";
|
|
21604
|
+
for (const byte of bytes)
|
|
21605
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
21606
|
+
return hex;
|
|
21607
|
+
}
|
|
21608
|
+
function buildCreateArticlePayload(session, content, media, opts) {
|
|
21609
|
+
const metadata = content.metadata ?? {};
|
|
21610
|
+
const plain = (key) => {
|
|
21611
|
+
const value = metadata[key];
|
|
21612
|
+
return typeof value === "string" ? value : "";
|
|
21613
|
+
};
|
|
21614
|
+
const fields = {
|
|
21615
|
+
token: session.token,
|
|
21616
|
+
lang: "zh_CN",
|
|
21617
|
+
f: "json",
|
|
21618
|
+
ajax: "1",
|
|
21619
|
+
random: opts?.random ?? randomHex16(),
|
|
21620
|
+
AppMsgId: "",
|
|
21621
|
+
count: "1",
|
|
21622
|
+
data_seq: "0",
|
|
21623
|
+
operate_from: "Chrome",
|
|
21624
|
+
isnew: "0",
|
|
21625
|
+
type: "77",
|
|
21626
|
+
title0: content.title ?? "",
|
|
21627
|
+
author0: plain("author"),
|
|
21628
|
+
digest0: deriveDigest(content) ?? "",
|
|
21629
|
+
auto_gen_digest0: "0",
|
|
21630
|
+
content0: media.html,
|
|
21631
|
+
sourceurl0: "",
|
|
21632
|
+
cdn_url0: media.cover.wide.url,
|
|
21633
|
+
cdn_235_1_url0: media.cover.wide.url,
|
|
21634
|
+
cdn_1_1_url0: media.cover.square.url,
|
|
21635
|
+
cdn_url_back0: media.cover.wide.url,
|
|
21636
|
+
crop_list0: JSON.stringify(media.cover.crops),
|
|
21637
|
+
share_page_type0: "0",
|
|
21638
|
+
share_imageinfo0: JSON.stringify({ list: [] }),
|
|
21639
|
+
categories_list0: "[]",
|
|
21640
|
+
compose_info0: JSON.stringify({ list: "" })
|
|
21641
|
+
};
|
|
21642
|
+
return {
|
|
21643
|
+
url: buildUrl2(CREATE_URL, {
|
|
21644
|
+
t: "ajax-response",
|
|
21645
|
+
sub: "create",
|
|
21646
|
+
type: "77",
|
|
21647
|
+
token: session.token,
|
|
21648
|
+
lang: "zh_CN"
|
|
21649
|
+
}),
|
|
21650
|
+
fields
|
|
21651
|
+
};
|
|
21652
|
+
}
|
|
21653
|
+
function cgiFetchExpression2(spec) {
|
|
21654
|
+
return `(async () => {
|
|
21655
|
+
const spec = ${JSON.stringify(spec)};
|
|
21656
|
+
try {
|
|
21657
|
+
const init = {
|
|
21658
|
+
method: spec.method,
|
|
21659
|
+
credentials: 'include',
|
|
21660
|
+
redirect: 'error',
|
|
21661
|
+
signal: AbortSignal.timeout(spec.timeoutMs),
|
|
21662
|
+
};
|
|
21663
|
+
if (spec.method === 'POST') {
|
|
21664
|
+
const form = new FormData();
|
|
21665
|
+
for (const key of Object.keys(spec.fields)) form.append(key, spec.fields[key]);
|
|
21666
|
+
init.body = form;
|
|
21667
|
+
}
|
|
21668
|
+
const response = await fetch(spec.url, init);
|
|
21669
|
+
if (!response.ok) return JSON.stringify({ kind: 'http-status', status: response.status });
|
|
21670
|
+
if (!response.body) return JSON.stringify({ kind: 'error', detail: 'response has no body stream' });
|
|
21671
|
+
const reader = response.body.getReader();
|
|
21672
|
+
let total = 0;
|
|
21673
|
+
const chunks = [];
|
|
21674
|
+
for (;;) {
|
|
21675
|
+
const { done, value } = await reader.read();
|
|
21676
|
+
if (done) break;
|
|
21677
|
+
total += value.byteLength;
|
|
21678
|
+
if (total > spec.maxBytes) return JSON.stringify({ kind: 'too-large' });
|
|
21679
|
+
chunks.push(value);
|
|
21680
|
+
}
|
|
21681
|
+
const text = await new Blob(chunks).text();
|
|
21682
|
+
return JSON.stringify({ kind: 'ok', status: response.status, text });
|
|
21683
|
+
} catch (error) {
|
|
21684
|
+
return JSON.stringify({ kind: 'error', detail: String(error) });
|
|
21685
|
+
}
|
|
21686
|
+
})()`;
|
|
21687
|
+
}
|
|
21688
|
+
async function evaluateCgiVerdict(page, expression) {
|
|
21689
|
+
const raw = await page.evaluate(expression);
|
|
21690
|
+
if (typeof raw !== "string")
|
|
21691
|
+
return;
|
|
21692
|
+
try {
|
|
21693
|
+
return JSON.parse(raw);
|
|
21694
|
+
} catch {
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
}
|
|
21698
|
+
function ambiguousSubmission(stage, detail) {
|
|
21699
|
+
return new WechatCgiDraftError("ambiguous-submission", stage, detail, { ambiguousSubmission: true });
|
|
21700
|
+
}
|
|
21701
|
+
async function createWechatArticleDraft(page, session, content, media) {
|
|
21702
|
+
assertEligibleCgiDraft(content);
|
|
21703
|
+
assertMpOrigin2(page);
|
|
21704
|
+
const payload = buildCreateArticlePayload(session, content, media);
|
|
21705
|
+
let createVerdict;
|
|
21706
|
+
try {
|
|
21707
|
+
createVerdict = await evaluateCgiVerdict(page, cgiFetchExpression2({
|
|
21708
|
+
url: payload.url,
|
|
21709
|
+
method: "POST",
|
|
21710
|
+
fields: payload.fields,
|
|
21711
|
+
maxBytes: MAX_RESPONSE_BYTES2,
|
|
21712
|
+
timeoutMs: REQUEST_TIMEOUT_MS2
|
|
21713
|
+
}));
|
|
21714
|
+
} catch (error) {
|
|
21715
|
+
throw ambiguousSubmission("create-dispatch", `operate_appmsg create ended before a receipt returned: ${stripQuery2(String(error))}`);
|
|
21716
|
+
}
|
|
21717
|
+
if (createVerdict?.kind === "http-status") {
|
|
21718
|
+
throw ambiguousSubmission("create-dispatch", `operate_appmsg create returned HTTP ${createVerdict.status}`);
|
|
21719
|
+
}
|
|
21720
|
+
if (createVerdict?.kind === "too-large") {
|
|
21721
|
+
throw ambiguousSubmission("create-dispatch", `create response exceeded the ${MAX_RESPONSE_BYTES2}-byte bound`);
|
|
21722
|
+
}
|
|
21723
|
+
if (createVerdict?.kind !== "ok") {
|
|
21724
|
+
const detail = createVerdict?.kind === "error" ? stripQuery2(createVerdict.detail) : "page returned no usable verdict";
|
|
21725
|
+
throw ambiguousSubmission("create-dispatch", `create failed before a usable response: ${detail}`);
|
|
21726
|
+
}
|
|
21727
|
+
let body;
|
|
21728
|
+
try {
|
|
21729
|
+
body = JSON.parse(createVerdict.text);
|
|
21730
|
+
} catch {
|
|
21731
|
+
throw ambiguousSubmission("create-receipt", "create receipt is not JSON");
|
|
21732
|
+
}
|
|
21733
|
+
if (!isRecord2(body)) {
|
|
21734
|
+
throw ambiguousSubmission("create-receipt", "create receipt is not a JSON object");
|
|
21735
|
+
}
|
|
21736
|
+
if (body.ret !== undefined && body.ret !== 0) {
|
|
21737
|
+
throw new WechatCgiDraftError("receipt-invalid", "create-receipt", `operate_appmsg create refused: ret=${String(body.ret)}`);
|
|
21738
|
+
}
|
|
21739
|
+
const receipt = isRecord2(body.appMsgInfo) ? body.appMsgInfo : body;
|
|
21740
|
+
const rawId = receipt.appMsgId;
|
|
21741
|
+
const appMsgId = typeof rawId === "string" || typeof rawId === "number" ? String(rawId) : undefined;
|
|
21742
|
+
if (appMsgId === undefined || !/^-?\d{1,12}$/.test(appMsgId)) {
|
|
21743
|
+
throw ambiguousSubmission("create-receipt", "create receipt did not return a safe numeric appMsgId");
|
|
21744
|
+
}
|
|
21745
|
+
const listUrl = buildUrl2(APPMSG_URL, {
|
|
21746
|
+
action: "list_ex",
|
|
21747
|
+
begin: "0",
|
|
21748
|
+
count: "5",
|
|
21749
|
+
token: session.token,
|
|
21750
|
+
lang: "zh_CN",
|
|
21751
|
+
f: "json"
|
|
21752
|
+
});
|
|
21753
|
+
let listVerdict;
|
|
21754
|
+
try {
|
|
21755
|
+
listVerdict = await evaluateCgiVerdict(page, cgiFetchExpression2({
|
|
21756
|
+
url: listUrl,
|
|
21757
|
+
method: "GET",
|
|
21758
|
+
fields: {},
|
|
21759
|
+
maxBytes: MAX_RESPONSE_BYTES2,
|
|
21760
|
+
timeoutMs: REQUEST_TIMEOUT_MS2
|
|
21761
|
+
}));
|
|
21762
|
+
} catch (error) {
|
|
21763
|
+
throw new WechatCgiDraftError("readback-failed", "readback", `draft list readback ended before a receipt returned: ${stripQuery2(String(error))}`);
|
|
21764
|
+
}
|
|
21765
|
+
if (listVerdict?.kind !== "ok") {
|
|
21766
|
+
const detail = listVerdict?.kind === "http-status" ? `HTTP ${listVerdict.status}` : listVerdict?.kind === "too-large" ? `${MAX_RESPONSE_BYTES2}-byte bound exceeded` : listVerdict?.kind === "error" ? stripQuery2(listVerdict.detail) : "page returned no usable verdict";
|
|
21767
|
+
throw new WechatCgiDraftError("readback-failed", "readback", `draft list readback failed: ${detail}`);
|
|
21768
|
+
}
|
|
21769
|
+
let list;
|
|
21770
|
+
try {
|
|
21771
|
+
list = JSON.parse(listVerdict.text);
|
|
21772
|
+
} catch {
|
|
21773
|
+
throw new WechatCgiDraftError("readback-failed", "readback", "draft list receipt is not JSON");
|
|
21774
|
+
}
|
|
21775
|
+
const items = isRecord2(list) && Array.isArray(list.app_msg_list) ? list.app_msg_list : [];
|
|
21776
|
+
const confirmed = items.some((item) => isRecord2(item) && item.title === content.title && (String(item.app_id) === appMsgId || String(item.appmsgid) === appMsgId));
|
|
21777
|
+
if (!confirmed) {
|
|
21778
|
+
throw new WechatCgiDraftError("readback-failed", "readback", "draft list did not confirm an item matching the created appMsgId and title");
|
|
21779
|
+
}
|
|
21780
|
+
return {
|
|
21781
|
+
appMsgId,
|
|
21782
|
+
editPath: `/cgi-bin/appmsg?t=media/appmsg_edit&action=edit&isNew=0&type=77&appmsgid=${appMsgId}`
|
|
21783
|
+
};
|
|
21784
|
+
}
|
|
21785
|
+
|
|
21786
|
+
// ../../plugins/publishings/wechatmp-pub/src/cgi-media.ts
|
|
21787
|
+
var MP_ORIGIN3 = "https://mp.weixin.qq.com";
|
|
21788
|
+
var UPLOADIMG2CDN_URL = `${MP_ORIGIN3}/cgi-bin/uploadimg2cdn`;
|
|
21789
|
+
var MAX_RESPONSE_BYTES3 = 2 * 1024 * 1024;
|
|
21790
|
+
var REQUEST_TIMEOUT_MS3 = 30000;
|
|
21791
|
+
var TOTAL_DEADLINE_MS = 180000;
|
|
21792
|
+
var MAX_AGGREGATE_BYTES = 20 * 1024 * 1024;
|
|
21793
|
+
var MAX_BODY_SOURCES = 30;
|
|
21794
|
+
var MAX_SOURCE_DETAIL_LENGTH = 120;
|
|
21795
|
+
|
|
21796
|
+
class WechatMediaError extends Error {
|
|
21797
|
+
reason;
|
|
21798
|
+
constructor(reason, message) {
|
|
21799
|
+
super(message);
|
|
21800
|
+
this.reason = reason;
|
|
21801
|
+
this.name = "WechatMediaError";
|
|
21802
|
+
}
|
|
21803
|
+
}
|
|
21804
|
+
function safeSource(source) {
|
|
21805
|
+
const bare = source.replace(/[?#].*$/, "");
|
|
21806
|
+
return bare.length > MAX_SOURCE_DETAIL_LENGTH ? `${bare.slice(0, MAX_SOURCE_DETAIL_LENGTH)}\u2026` : bare;
|
|
21807
|
+
}
|
|
21808
|
+
function stripQuery3(detail) {
|
|
21809
|
+
return detail.replace(/\?[^'"\\\s]*/g, "?\u2026");
|
|
21810
|
+
}
|
|
21811
|
+
function assertMpOrigin3(page) {
|
|
21812
|
+
let origin;
|
|
21813
|
+
try {
|
|
21814
|
+
origin = new URL(page.url()).origin;
|
|
21815
|
+
} catch {
|
|
21816
|
+
throw new WechatMediaError("wrong-origin", `page URL is not usable for media preparation: ${page.url()}`);
|
|
21817
|
+
}
|
|
21818
|
+
if (origin !== MP_ORIGIN3) {
|
|
21819
|
+
throw new WechatMediaError("wrong-origin", `media preparation requires page origin ${MP_ORIGIN3}, got ${origin}`);
|
|
21820
|
+
}
|
|
21821
|
+
}
|
|
21822
|
+
function assertDeadline(deadlineAt) {
|
|
21823
|
+
if (Date.now() > deadlineAt) {
|
|
21824
|
+
throw new WechatMediaError("deadline-exceeded", `article media preparation exceeded the ${TOTAL_DEADLINE_MS}ms deadline`);
|
|
21825
|
+
}
|
|
21826
|
+
}
|
|
21827
|
+
function assertPublicHttpSource(source) {
|
|
21828
|
+
let url;
|
|
21829
|
+
try {
|
|
21830
|
+
url = new URL(source);
|
|
21831
|
+
} catch {
|
|
21832
|
+
throw new WechatMediaError("unmapped-source", `body image source is not absolute and has no localImages mapping: ${safeSource(source)}`);
|
|
21833
|
+
}
|
|
21834
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
21835
|
+
throw new WechatMediaError("unmapped-source", `body image source scheme ${url.protocol} requires a localImages mapping: ${safeSource(source)}`);
|
|
21836
|
+
}
|
|
21837
|
+
if (url.username !== "" || url.password !== "") {
|
|
21838
|
+
throw new WechatMediaError("unmapped-source", `body image source carries credentials and is not transferable: ${safeSource(source)}`);
|
|
21839
|
+
}
|
|
21840
|
+
if (isLocalHostLiteral(url.hostname)) {
|
|
21841
|
+
throw new WechatMediaError("unmapped-source", `body image source is a local-IP literal and is not transferable: ${safeSource(source)}`);
|
|
21842
|
+
}
|
|
21843
|
+
}
|
|
21844
|
+
function isLocalHostLiteral(hostname) {
|
|
21845
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
21846
|
+
return host === "localhost" || host === "::1" || host === "0.0.0.0" || /^127\./.test(host) || /^10\./.test(host) || /^192\.168\./.test(host) || /^169\.254\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host) || /^(f[cd]|fe80)/.test(host);
|
|
21847
|
+
}
|
|
21848
|
+
function parseArticleExpression(html) {
|
|
21849
|
+
return `(function () {
|
|
21850
|
+
const doc = new DOMParser().parseFromString(${JSON.stringify(html)}, 'text/html');
|
|
21851
|
+
const dangerous = (value) => {
|
|
21852
|
+
const compact = String(value).toLowerCase().replace(/\\s+/g, '');
|
|
21853
|
+
return ['javascript:', 'data:', 'vbscript:'].some((scheme) => compact.startsWith(scheme));
|
|
21854
|
+
};
|
|
21855
|
+
doc.body.querySelectorAll('*').forEach((el) => {
|
|
21856
|
+
[...el.attributes].forEach((attr) => {
|
|
21857
|
+
const name = attr.name.toLowerCase();
|
|
21858
|
+
if (name.startsWith('on') || dangerous(attr.value)) el.removeAttribute(attr.name);
|
|
21859
|
+
});
|
|
21860
|
+
});
|
|
21861
|
+
doc.body.querySelectorAll('a[href]').forEach((a) => {
|
|
21862
|
+
const span = doc.createElement('span');
|
|
21863
|
+
while (a.firstChild) span.appendChild(a.firstChild);
|
|
21864
|
+
a.replaceWith(span);
|
|
21865
|
+
});
|
|
21866
|
+
const sources = [];
|
|
21867
|
+
const seen = new Set();
|
|
21868
|
+
doc.body.querySelectorAll('img').forEach((img) => {
|
|
21869
|
+
const src = img.getAttribute('src') || '';
|
|
21870
|
+
if (src !== '' && !seen.has(src)) {
|
|
21871
|
+
seen.add(src);
|
|
21872
|
+
sources.push(src);
|
|
21873
|
+
}
|
|
21874
|
+
});
|
|
21875
|
+
return JSON.stringify({ kind: 'ok', sources, html: doc.body.innerHTML });
|
|
21876
|
+
})()`;
|
|
21877
|
+
}
|
|
21878
|
+
function applyReplacementsExpression(html, replacements) {
|
|
21879
|
+
return `(function () {
|
|
21880
|
+
const doc = new DOMParser().parseFromString(${JSON.stringify(html)}, 'text/html');
|
|
21881
|
+
const map = ${JSON.stringify(replacements)};
|
|
21882
|
+
doc.body.querySelectorAll('img').forEach((img) => {
|
|
21883
|
+
const src = img.getAttribute('src') || '';
|
|
21884
|
+
if (map[src]) img.setAttribute('src', map[src]);
|
|
21885
|
+
});
|
|
21886
|
+
return JSON.stringify({ kind: 'ok', html: doc.body.innerHTML });
|
|
21887
|
+
})()`;
|
|
21888
|
+
}
|
|
21889
|
+
function transferExpression(spec) {
|
|
21890
|
+
return `(async () => {
|
|
21891
|
+
const spec = ${JSON.stringify(spec)};
|
|
21892
|
+
const controller = new AbortController();
|
|
21893
|
+
const timer = setTimeout(() => controller.abort(), ${REQUEST_TIMEOUT_MS3});
|
|
21894
|
+
try {
|
|
21895
|
+
const form = new FormData();
|
|
21896
|
+
for (const key of Object.keys(spec.fields)) form.append(key, spec.fields[key]);
|
|
21897
|
+
const response = await fetch(spec.url, {
|
|
21898
|
+
method: 'POST',
|
|
21899
|
+
body: form,
|
|
21900
|
+
credentials: 'include',
|
|
21901
|
+
redirect: 'error',
|
|
21902
|
+
signal: controller.signal,
|
|
21903
|
+
});
|
|
21904
|
+
if (!response.ok) return JSON.stringify({ kind: 'http-status', status: response.status });
|
|
21905
|
+
if (!response.body) return JSON.stringify({ kind: 'error', detail: 'response has no body stream' });
|
|
21906
|
+
const reader = response.body.getReader();
|
|
21907
|
+
let total = 0;
|
|
21908
|
+
const chunks = [];
|
|
21909
|
+
for (;;) {
|
|
21910
|
+
const { done, value } = await reader.read();
|
|
21911
|
+
if (done) break;
|
|
21912
|
+
total += value.byteLength;
|
|
21913
|
+
if (total > spec.maxBytes) {
|
|
21914
|
+
controller.abort();
|
|
21915
|
+
return JSON.stringify({ kind: 'too-large' });
|
|
21916
|
+
}
|
|
21917
|
+
chunks.push(value);
|
|
21918
|
+
}
|
|
21919
|
+
const text = await new Blob(chunks).text();
|
|
21920
|
+
return JSON.stringify({ kind: 'ok', text });
|
|
21921
|
+
} catch (error) {
|
|
21922
|
+
return JSON.stringify({ kind: 'error', detail: String(error) });
|
|
21923
|
+
} finally {
|
|
21924
|
+
clearTimeout(timer);
|
|
21925
|
+
}
|
|
21926
|
+
})()`;
|
|
21927
|
+
}
|
|
21928
|
+
async function evaluateJson(page, expression, what) {
|
|
21929
|
+
const raw = await page.evaluate(expression);
|
|
21930
|
+
if (typeof raw !== "string") {
|
|
21931
|
+
throw new WechatMediaError("transfer-failed", `${what}: page returned no usable verdict`);
|
|
21932
|
+
}
|
|
21933
|
+
try {
|
|
21934
|
+
return JSON.parse(raw);
|
|
21935
|
+
} catch {
|
|
21936
|
+
throw new WechatMediaError("transfer-failed", `${what}: page verdict is not JSON`);
|
|
21937
|
+
}
|
|
21938
|
+
}
|
|
21939
|
+
function isRecord3(value) {
|
|
21940
|
+
return typeof value === "object" && value !== null;
|
|
21941
|
+
}
|
|
21942
|
+
function parseTransferReceipt(text, source) {
|
|
21943
|
+
let body;
|
|
21944
|
+
try {
|
|
21945
|
+
body = JSON.parse(text);
|
|
21946
|
+
} catch {
|
|
21947
|
+
throw new WechatMediaError("bad-transfer-receipt", `uploadimg2cdn: response is not JSON for ${safeSource(source)}`);
|
|
21948
|
+
}
|
|
21949
|
+
if (!isRecord3(body) || body.errcode !== 0) {
|
|
21950
|
+
throw new WechatMediaError("bad-transfer-receipt", `uploadimg2cdn: errcode=${String(isRecord3(body) ? body.errcode : body)} for ${safeSource(source)}`);
|
|
21951
|
+
}
|
|
21952
|
+
const fileId = body.file_id;
|
|
21953
|
+
const url = body.url;
|
|
21954
|
+
if (typeof fileId !== "string" || fileId === "" || typeof url !== "string" || !isAllowedCdnUrl(url)) {
|
|
21955
|
+
throw new WechatMediaError("bad-transfer-receipt", `uploadimg2cdn: file_id/url missing or outside the allowed HTTPS CDN host for ${safeSource(source)}`);
|
|
21956
|
+
}
|
|
21957
|
+
return { fileId, url, width: 0, height: 0 };
|
|
21958
|
+
}
|
|
21959
|
+
async function transferPublicImage(page, session, source) {
|
|
21960
|
+
const query = new URLSearchParams({
|
|
21961
|
+
lang: "zh_CN",
|
|
21962
|
+
token: session.token,
|
|
21963
|
+
t: String(Math.floor(Math.random() * 1e9))
|
|
21964
|
+
});
|
|
21965
|
+
const verdict = await evaluateJson(page, transferExpression({
|
|
21966
|
+
url: `${UPLOADIMG2CDN_URL}?${query.toString()}`,
|
|
21967
|
+
fields: {
|
|
21968
|
+
imgurl: source,
|
|
21969
|
+
t: "ajax-editor-upload-img",
|
|
21970
|
+
token: session.token,
|
|
21971
|
+
lang: "zh_CN",
|
|
21972
|
+
f: "json",
|
|
21973
|
+
ajax: "1"
|
|
21974
|
+
},
|
|
21975
|
+
maxBytes: MAX_RESPONSE_BYTES3
|
|
21976
|
+
}), "uploadimg2cdn");
|
|
21977
|
+
if (verdict.kind === "http-status") {
|
|
21978
|
+
throw new WechatMediaError("transfer-failed", `uploadimg2cdn returned HTTP ${verdict.status}`);
|
|
21979
|
+
}
|
|
21980
|
+
if (verdict.kind === "too-large") {
|
|
21981
|
+
throw new WechatMediaError("transfer-failed", `uploadimg2cdn response exceeded the ${MAX_RESPONSE_BYTES3}-byte bound`);
|
|
21982
|
+
}
|
|
21983
|
+
if (verdict.kind !== "ok" || typeof verdict.text !== "string") {
|
|
21984
|
+
const detail = verdict.kind === "error" ? stripQuery3(verdict.detail) : "page returned no usable verdict";
|
|
21985
|
+
throw new WechatMediaError("transfer-failed", `uploadimg2cdn failed before a usable response: ${detail}`);
|
|
21986
|
+
}
|
|
21987
|
+
return parseTransferReceipt(verdict.text, source);
|
|
21988
|
+
}
|
|
21989
|
+
async function prepareArticleMedia(page, session, input) {
|
|
21990
|
+
assertMpOrigin3(page);
|
|
21991
|
+
const deadlineAt = Date.now() + TOTAL_DEADLINE_MS;
|
|
21992
|
+
const parsed = await evaluateJson(page, parseArticleExpression(input.html), "article body parse");
|
|
21993
|
+
if (parsed.kind !== "ok" || !Array.isArray(parsed.sources) || typeof parsed.html !== "string") {
|
|
21994
|
+
throw new WechatMediaError("transfer-failed", "article body parse did not return a usable verdict");
|
|
21995
|
+
}
|
|
21996
|
+
if (parsed.sources.length > MAX_BODY_SOURCES) {
|
|
21997
|
+
throw new WechatMediaError("too-many-sources", `article body has ${parsed.sources.length} distinct image sources, bound is ${MAX_BODY_SOURCES}`);
|
|
21998
|
+
}
|
|
21999
|
+
assertDeadline(deadlineAt);
|
|
22000
|
+
const cover = await uploadAndCropCover(page, session, { path: input.coverPath });
|
|
22001
|
+
const replacements = {};
|
|
22002
|
+
const images = [];
|
|
22003
|
+
let aggregateBytes = 0;
|
|
22004
|
+
for (const source of parsed.sources) {
|
|
22005
|
+
assertDeadline(deadlineAt);
|
|
22006
|
+
if (isAllowedCdnUrl(source))
|
|
22007
|
+
continue;
|
|
22008
|
+
const mappedPath = input.localImages[source];
|
|
22009
|
+
let uploaded;
|
|
22010
|
+
if (mappedPath !== undefined) {
|
|
22011
|
+
const image = await readLocalImageBytes(mappedPath);
|
|
22012
|
+
aggregateBytes += image.bytes.byteLength;
|
|
22013
|
+
if (aggregateBytes > MAX_AGGREGATE_BYTES) {
|
|
22014
|
+
throw new WechatMediaError("aggregate-too-large", `local body images exceed the ${MAX_AGGREGATE_BYTES}-byte aggregate bound`);
|
|
22015
|
+
}
|
|
22016
|
+
uploaded = await uploadCoverImage(page, session, { path: mappedPath });
|
|
22017
|
+
} else {
|
|
22018
|
+
assertPublicHttpSource(source);
|
|
22019
|
+
uploaded = await transferPublicImage(page, session, source);
|
|
22020
|
+
}
|
|
22021
|
+
replacements[source] = uploaded.url;
|
|
22022
|
+
images.push(uploaded);
|
|
22023
|
+
}
|
|
22024
|
+
const applied = await evaluateJson(page, applyReplacementsExpression(parsed.html, replacements), "CDN replacement");
|
|
22025
|
+
if (applied.kind !== "ok" || typeof applied.html !== "string") {
|
|
22026
|
+
throw new WechatMediaError("transfer-failed", "CDN replacement did not return a usable verdict");
|
|
22027
|
+
}
|
|
22028
|
+
return { html: applied.html, cover, images };
|
|
22029
|
+
}
|
|
22030
|
+
|
|
22031
|
+
// ../../plugins/publishings/wechatmp-pub/src/page.ts
|
|
22032
|
+
var POLL_MS = 500;
|
|
22033
|
+
var STEP_TIMEOUT_MS = 30000;
|
|
22034
|
+
async function modalState(page) {
|
|
22035
|
+
try {
|
|
22036
|
+
const raw = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog__wrp, .weui-desktop-dialog')].some((d) => d.offsetWidth)`);
|
|
22037
|
+
return raw === true ? "open" : raw === false ? "closed" : "unknown";
|
|
22038
|
+
} catch {
|
|
22039
|
+
return "unknown";
|
|
22040
|
+
}
|
|
22041
|
+
}
|
|
22042
|
+
async function tryFindAttachedSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
|
|
22043
|
+
let found;
|
|
22044
|
+
await waitUntil(async () => {
|
|
22045
|
+
for (const selector of selectors) {
|
|
22046
|
+
const attached = await page.locator(selector).count().catch(() => 0);
|
|
22047
|
+
if (attached > 0) {
|
|
22048
|
+
found = selector;
|
|
22049
|
+
return true;
|
|
22050
|
+
}
|
|
22051
|
+
}
|
|
22052
|
+
return false;
|
|
22053
|
+
}, timeoutMs);
|
|
22054
|
+
return found;
|
|
22055
|
+
}
|
|
22056
|
+
async function tryFindSelector(page, selectors, timeoutMs = STEP_TIMEOUT_MS) {
|
|
22057
|
+
let found;
|
|
22058
|
+
await waitUntil(async () => {
|
|
22059
|
+
const result = await trySelectors(page, selectors).catch(() => ({ found: false, selector: null }));
|
|
22060
|
+
if (result.found && result.selector) {
|
|
22061
|
+
found = result.selector;
|
|
22062
|
+
return true;
|
|
22063
|
+
}
|
|
22064
|
+
return false;
|
|
22065
|
+
}, timeoutMs);
|
|
22066
|
+
return found;
|
|
22067
|
+
}
|
|
22068
|
+
async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
|
|
22069
|
+
const found = await tryFindSelector(page, selectors, timeoutMs);
|
|
22070
|
+
if (found === undefined) {
|
|
22071
|
+
throw new Error(`${what}: none of [${selectors.join(" | ")}] visible within ${timeoutMs}ms`);
|
|
22072
|
+
}
|
|
22073
|
+
return found;
|
|
22074
|
+
}
|
|
22075
|
+
async function findAttachedSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
|
|
22076
|
+
const found = await tryFindAttachedSelector(page, selectors, timeoutMs);
|
|
22077
|
+
if (found === undefined) {
|
|
22078
|
+
throw new Error(`${what}: none of [${selectors.join(" | ")}] attached within ${timeoutMs}ms`);
|
|
22079
|
+
}
|
|
22080
|
+
return found;
|
|
22081
|
+
}
|
|
22082
|
+
async function followNewPage(pages, before, timeoutMs) {
|
|
22083
|
+
const opened = await waitUntil(async () => pages().length > before, timeoutMs);
|
|
22084
|
+
if (!opened)
|
|
22085
|
+
return;
|
|
22086
|
+
const all = pages();
|
|
22087
|
+
return all[all.length - 1];
|
|
22088
|
+
}
|
|
22089
|
+
async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
|
|
22090
|
+
await page.locator(selector).first().click({ timeout: timeoutMs });
|
|
22091
|
+
}
|
|
22092
|
+
async function fillSelector(page, selector, value) {
|
|
22093
|
+
await page.locator(selector).first().fill(value);
|
|
22094
|
+
}
|
|
22095
|
+
function wrapPlaywrightPage(raw) {
|
|
22096
|
+
const pw = raw;
|
|
22097
|
+
return {
|
|
22098
|
+
goto: (url, options) => pw.goto(url, { waitUntil: "domcontentloaded", ...options }).then(() => {
|
|
22099
|
+
return;
|
|
22100
|
+
}),
|
|
22101
|
+
locator: (selector) => ({
|
|
22102
|
+
count: () => pw.locator(selector).count(),
|
|
22103
|
+
first: () => {
|
|
22104
|
+
const loc = pw.locator(selector).first();
|
|
22105
|
+
return {
|
|
22106
|
+
count: () => loc.count(),
|
|
22107
|
+
isVisible: (options) => loc.isVisible(options),
|
|
22108
|
+
fill: (text, options) => loc.fill(text, options),
|
|
22109
|
+
click: (options) => loc.click(options),
|
|
22110
|
+
type: (text, options) => loc.pressSequentially(text, options),
|
|
22111
|
+
waitFor: (options) => loc.waitFor(options),
|
|
22112
|
+
textContent: async () => await loc.textContent() ?? "",
|
|
22113
|
+
inputValue: () => loc.inputValue()
|
|
22114
|
+
};
|
|
22115
|
+
}
|
|
22116
|
+
}),
|
|
22117
|
+
setInputFiles: (selector, paths) => pw.setInputFiles(selector, paths),
|
|
22118
|
+
url: () => pw.url(),
|
|
22119
|
+
evaluate: (expression) => pw.evaluate(expression),
|
|
22120
|
+
waitForTimeout: (ms) => pw.waitForTimeout(ms),
|
|
22121
|
+
press: (key) => pw.keyboard.press(key),
|
|
22122
|
+
frames: () => {
|
|
22123
|
+
const source = raw;
|
|
22124
|
+
return (source.frames?.() ?? []).map((frame) => ({
|
|
22125
|
+
url: () => frame.url(),
|
|
22126
|
+
locator: (selector) => ({ count: () => frame.locator(selector).count() })
|
|
22127
|
+
}));
|
|
22128
|
+
}
|
|
22129
|
+
};
|
|
22130
|
+
}
|
|
22131
|
+
function wrapPlaywrightContext(raw, entryPage) {
|
|
22132
|
+
const context = raw;
|
|
22133
|
+
const cache = new WeakMap;
|
|
22134
|
+
const wrap = (page) => {
|
|
22135
|
+
const key = page;
|
|
22136
|
+
const cached = cache.get(key);
|
|
22137
|
+
if (cached !== undefined)
|
|
22138
|
+
return cached;
|
|
22139
|
+
const wrapped = wrapPlaywrightPage(page);
|
|
22140
|
+
cache.set(key, wrapped);
|
|
22141
|
+
return wrapped;
|
|
22142
|
+
};
|
|
22143
|
+
return {
|
|
22144
|
+
page: wrap(entryPage),
|
|
22145
|
+
pages: () => context.pages().map(wrap),
|
|
22146
|
+
newPage: async () => wrap(await context.newPage()),
|
|
22147
|
+
close: () => context.close()
|
|
22148
|
+
};
|
|
22149
|
+
}
|
|
22150
|
+
async function waitForVisible(page, selector, timeoutMs, opts = {}) {
|
|
22151
|
+
const visible = await waitUntil(async () => {
|
|
22152
|
+
const found = await trySelectors(page, [selector]).catch(() => ({ found: false, selector: null }));
|
|
22153
|
+
return found.found;
|
|
22154
|
+
}, timeoutMs, POLL_MS);
|
|
22155
|
+
if (visible)
|
|
22156
|
+
return selector;
|
|
22157
|
+
if (opts.required === false)
|
|
22158
|
+
return;
|
|
22159
|
+
throw new Error(`${selector}: not visible within ${timeoutMs}ms`);
|
|
22160
|
+
}
|
|
22161
|
+
|
|
22162
|
+
// ../../plugins/publishings/wechatmp-pub/src/enrich.ts
|
|
22163
|
+
var DIALOG = ".weui-desktop-dialog:visible";
|
|
22164
|
+
var CONFIRM = `${DIALOG} button:has-text("\u786E\u8BA4"), ${DIALOG} button:has-text("\u786E\u5B9A")`;
|
|
22165
|
+
var ROW_ORIGINAL = [
|
|
22166
|
+
".setting-group__switch.js_original_apply.js_edit_ori",
|
|
22167
|
+
".setting-group__switch.js_edit_ori",
|
|
22168
|
+
".js_edit_ori",
|
|
22169
|
+
"text=\u539F\u521B"
|
|
22170
|
+
];
|
|
22171
|
+
var ROW_REWARD = [
|
|
22172
|
+
".setting-group__switch.js_reward_open",
|
|
22173
|
+
".js_reward_open",
|
|
22174
|
+
".js_reward_allow_click",
|
|
22175
|
+
".setting-group__switch.js_can_reward",
|
|
22176
|
+
"text=\u8D5E\u8D4F"
|
|
22177
|
+
];
|
|
22178
|
+
var ROW_COLLECTION = [".allow_click_opr.js_article_tags_label", "text=\u5408\u96C6"];
|
|
22179
|
+
var ROW_URL = [".allow_click_opr.js_url_checkbox", "text=\u539F\u6587\u94FE\u63A5"];
|
|
22180
|
+
var ROW_CLAIM = ["#js_claim_source_area .frm_checkbox_label", "text=\u521B\u4F5C\u6765\u6E90"];
|
|
22181
|
+
var DIALOG_CONFIRM = CONFIRM;
|
|
22182
|
+
var TAG_INPUT = 'input[placeholder*="\u5408\u96C6"]';
|
|
22183
|
+
var REWARD_ACCOUNT = [
|
|
22184
|
+
`${DIALOG} input[placeholder*="\u8D5E\u8D4F"]`,
|
|
22185
|
+
`${DIALOG} input[placeholder*="\u8D26\u6237"]`,
|
|
22186
|
+
`${DIALOG} input.frm_input`
|
|
22187
|
+
];
|
|
22188
|
+
var URL_INPUT = [`${DIALOG} input[type="url"]`, `${DIALOG} input[placeholder*="\u94FE\u63A5"]`, `${DIALOG} input.frm_input`];
|
|
22189
|
+
var ORIGINAL_DECLARATION = `${DIALOG} label:has(.js_original_type_radio):has-text("\u6587\u5B57\u539F\u521B")`;
|
|
22190
|
+
var ORIGINAL_AGREEMENT = `${DIALOG} .original_agreement`;
|
|
22191
|
+
var COLLECTION_SETTLE_MS = 1200;
|
|
22192
|
+
var STEP_RETRY_SETTLE_MS = 1000;
|
|
22193
|
+
var SWEEP_SETTLE_MS = 400;
|
|
22194
|
+
|
|
22195
|
+
class CapabilityError extends Error {
|
|
22196
|
+
}
|
|
22197
|
+
function norm(value) {
|
|
22198
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
22199
|
+
}
|
|
22200
|
+
var COMMITTED = {
|
|
22201
|
+
original: `(() => {
|
|
22202
|
+
const el = document.querySelector('#js_original, .js_ori_setting_checkbox');
|
|
22203
|
+
return el ? el.checked === true : null;
|
|
22204
|
+
})()`,
|
|
22205
|
+
reward: `(() => {
|
|
22206
|
+
const el = document.querySelector('#js_reward_setting_area .js_reward_setting_checkbox') ||
|
|
22207
|
+
document.querySelector('.js_reward_allow_click_cell .js_reward_setting');
|
|
22208
|
+
return el ? el.checked === true : null;
|
|
22209
|
+
})()`
|
|
22210
|
+
};
|
|
22211
|
+
var CLAIM_COMMITTED = `(() => {
|
|
22212
|
+
const area = document.querySelector('#js_claim_source_area');
|
|
22213
|
+
if (!area) return null;
|
|
22214
|
+
const label = area.querySelector('.js_claim_source_selected');
|
|
22215
|
+
const text = (label ? (label.textContent ?? '') : '').replace(/\\s+/g, ' ').trim();
|
|
22216
|
+
const checkbox = area.querySelector('.js_claim_source, input[type=checkbox]');
|
|
22217
|
+
const enabled = checkbox ? checkbox.checked === true : null;
|
|
22218
|
+
return { enabled, text };
|
|
22219
|
+
})()`;
|
|
22220
|
+
async function readChecked(page, expression) {
|
|
22221
|
+
try {
|
|
22222
|
+
const raw = await page.evaluate(expression);
|
|
22223
|
+
return typeof raw === "boolean" ? raw : null;
|
|
22224
|
+
} catch {
|
|
22225
|
+
return null;
|
|
22226
|
+
}
|
|
22227
|
+
}
|
|
22228
|
+
async function enrichArticle(page, meta) {
|
|
22229
|
+
const warnings = [];
|
|
22230
|
+
const str = (key) => typeof meta[key] === "string" ? meta[key] : "";
|
|
22231
|
+
const on = (key) => meta[key] === true;
|
|
22232
|
+
const sweep = async () => {
|
|
22233
|
+
const cancel = await trySelectors(page, ['button:has-text("\u53D6\u6D88")']);
|
|
22234
|
+
if (cancel.found) {
|
|
22235
|
+
try {
|
|
22236
|
+
await page.locator(cancel.selector ?? "").first().click({ timeout: 2000 });
|
|
22237
|
+
} catch {}
|
|
22238
|
+
}
|
|
22239
|
+
await page.press?.("Escape");
|
|
22240
|
+
await page.waitForTimeout?.(SWEEP_SETTLE_MS);
|
|
22241
|
+
const state = await modalState(page);
|
|
22242
|
+
if (state === "open") {
|
|
22243
|
+
for (const close of [".weui-desktop-dialog__close-btn", ".weui-desktop-dialog__close"]) {
|
|
22244
|
+
const control = await tryFindSelector(page, [close], 1500);
|
|
22245
|
+
if (control === undefined)
|
|
22246
|
+
continue;
|
|
22247
|
+
try {
|
|
22248
|
+
await clickSelector(page, control, 2000);
|
|
22249
|
+
} catch {}
|
|
22250
|
+
break;
|
|
22251
|
+
}
|
|
22252
|
+
return await modalState(page) === "closed" ? "closed" : "unknown";
|
|
22253
|
+
}
|
|
22254
|
+
return state === "closed" ? "closed" : "unknown";
|
|
22255
|
+
};
|
|
22256
|
+
const openRow = async (row, what) => {
|
|
22257
|
+
for (const selector of row) {
|
|
22258
|
+
const found = await trySelectors(page, [selector]);
|
|
22259
|
+
if (found.found) {
|
|
22260
|
+
await clickSelector(page, selector, 8000);
|
|
22261
|
+
return;
|
|
22262
|
+
}
|
|
22263
|
+
}
|
|
22264
|
+
throw new Error(`no ${what} row control among [${row.join(" | ")}]`);
|
|
22265
|
+
};
|
|
22266
|
+
const step = async (name, fn) => {
|
|
22267
|
+
try {
|
|
22268
|
+
await fn();
|
|
22269
|
+
return "ok";
|
|
22270
|
+
} catch (error) {
|
|
22271
|
+
if (error instanceof CapabilityError) {
|
|
22272
|
+
warnings.push(`${name}: ${error.message}`);
|
|
22273
|
+
return "capability";
|
|
22274
|
+
}
|
|
22275
|
+
}
|
|
22276
|
+
await sweep();
|
|
22277
|
+
await page.waitForTimeout?.(STEP_RETRY_SETTLE_MS);
|
|
22278
|
+
try {
|
|
22279
|
+
await fn();
|
|
22280
|
+
return "ok";
|
|
22281
|
+
} catch (error) {
|
|
22282
|
+
warnings.push(`${name}: ${(error instanceof Error ? error.message : String(error)).slice(0, 120)}`);
|
|
22283
|
+
await sweep();
|
|
22284
|
+
return "failed";
|
|
22285
|
+
}
|
|
22286
|
+
};
|
|
22287
|
+
const residual = async (name) => {
|
|
22288
|
+
const state = await modalState(page);
|
|
22289
|
+
if (state === "closed")
|
|
22290
|
+
return false;
|
|
22291
|
+
warnings.push(state === "open" ? `${name}: dialog did not close \u2014 check the draft` : `${name}: dialog state unknown after cleanup \u2014 a residual overlay may block later steps`);
|
|
22292
|
+
return true;
|
|
22293
|
+
};
|
|
22294
|
+
if (on("original")) {
|
|
22295
|
+
const name = "\u539F\u521B\u58F0\u660E";
|
|
22296
|
+
if (await readChecked(page, COMMITTED.original) !== true) {
|
|
22297
|
+
const outcome = await step(name, async () => {
|
|
22298
|
+
await openRow(ROW_ORIGINAL, "\u539F\u521B\u58F0\u660E");
|
|
22299
|
+
await clickSelector(page, ORIGINAL_DECLARATION, 5000);
|
|
22300
|
+
const agreement = await trySelectors(page, [ORIGINAL_AGREEMENT]);
|
|
22301
|
+
if (agreement.found) {
|
|
22302
|
+
await clickSelector(page, ORIGINAL_AGREEMENT, 3000);
|
|
22303
|
+
}
|
|
22304
|
+
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
22305
|
+
});
|
|
22306
|
+
if (outcome !== "capability" && await readChecked(page, COMMITTED.original) !== true && !await residual(name)) {
|
|
22307
|
+
warnings.push(`${name}: \u539F\u521B\u58F0\u660E\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E`);
|
|
22308
|
+
}
|
|
22309
|
+
}
|
|
21168
22310
|
}
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
if (
|
|
21172
|
-
|
|
21173
|
-
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
22311
|
+
if (on("reward")) {
|
|
22312
|
+
const name = "\u8D5E\u8D4F";
|
|
22313
|
+
if (await readChecked(page, COMMITTED.reward) !== true) {
|
|
22314
|
+
const outcome = await step(name, async () => {
|
|
22315
|
+
await openRow(ROW_REWARD, "\u8D5E\u8D4F");
|
|
22316
|
+
const blocked = await tryFindSelector(page, [`${DIALOG} :has-text("\u524D\u5F80\u5F00\u901A")`, `${DIALOG} :has-text("\u65E0\u6CD5\u5F00\u542F")`], 1500);
|
|
22317
|
+
if (blocked !== undefined)
|
|
22318
|
+
throw new CapabilityError("\u9700\u8981\u8D5E\u8D4F\u8D26\u6237\uFF08\u8FD0\u8425\u8005\u5148\u5728\u5E73\u53F0\u5F00\u901A\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u81EA\u52A8\u586B\u5199");
|
|
22319
|
+
const author = str("author");
|
|
22320
|
+
if (author !== "") {
|
|
22321
|
+
for (const selector of REWARD_ACCOUNT) {
|
|
22322
|
+
const found = await trySelectors(page, [selector]);
|
|
22323
|
+
if (!found.found)
|
|
22324
|
+
continue;
|
|
22325
|
+
const locator = page.locator(selector).first();
|
|
22326
|
+
if (typeof locator.inputValue !== "function")
|
|
22327
|
+
break;
|
|
22328
|
+
const empty = norm(await locator.inputValue().catch(() => null)) === "";
|
|
22329
|
+
if (empty)
|
|
22330
|
+
await fillSelector(page, selector, author);
|
|
22331
|
+
break;
|
|
22332
|
+
}
|
|
22333
|
+
}
|
|
22334
|
+
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
22335
|
+
});
|
|
22336
|
+
if (outcome !== "capability" && await readChecked(page, COMMITTED.reward) !== true && !await residual(name)) {
|
|
22337
|
+
warnings.push(`${name}: \u8D5E\u8D4F\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E`);
|
|
22338
|
+
}
|
|
21181
22339
|
}
|
|
21182
|
-
paths.push(path);
|
|
21183
|
-
}
|
|
21184
|
-
return { paths };
|
|
21185
|
-
}
|
|
21186
|
-
function deriveDigest(content) {
|
|
21187
|
-
const explicit = content.metadata?.digest;
|
|
21188
|
-
if (typeof explicit === "string")
|
|
21189
|
-
return explicit;
|
|
21190
|
-
const body = content.body ?? "";
|
|
21191
|
-
const first = body.split(`
|
|
21192
|
-
`).map((line) => line.trim()).find((line) => line !== "");
|
|
21193
|
-
return first === undefined ? undefined : first.slice(0, DERIVED_DIGEST_MAX);
|
|
21194
|
-
}
|
|
21195
|
-
function validatePublishStatus(content) {
|
|
21196
|
-
const status = content.metadata?.publishStatus;
|
|
21197
|
-
if (status !== undefined && status !== "draft" && status !== "public") {
|
|
21198
|
-
return {
|
|
21199
|
-
error: `publishStatus validation: unknown publishStatus '${String(status)}' (expected 'draft' or 'public')`
|
|
21200
|
-
};
|
|
21201
|
-
}
|
|
21202
|
-
return {};
|
|
21203
|
-
}
|
|
21204
|
-
function validateTitle(content, operation) {
|
|
21205
|
-
const limits = MODE_LIMITS[operation];
|
|
21206
|
-
const title = content.title ?? "";
|
|
21207
|
-
if (limits.titleRequired === true && title.trim().length === 0) {
|
|
21208
|
-
return {
|
|
21209
|
-
error: `title validation: Content.title is required for ${operation} (the editor's placeholder is \u8BF7\u5728\u8FD9\u91CC\u8F93\u5165\u6807\u9898)`
|
|
21210
|
-
};
|
|
21211
|
-
}
|
|
21212
|
-
if (limits.titleMax !== undefined && title.length > limits.titleMax) {
|
|
21213
|
-
return {
|
|
21214
|
-
error: `title validation: Content.title is ${title.length} chars but ${operation} accepts \u2264${limits.titleMax}`
|
|
21215
|
-
};
|
|
21216
22340
|
}
|
|
21217
|
-
|
|
21218
|
-
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21222
|
-
|
|
21223
|
-
|
|
21224
|
-
|
|
21225
|
-
|
|
22341
|
+
const collection = str("collection");
|
|
22342
|
+
if (collection !== "") {
|
|
22343
|
+
const name = "\u5408\u96C6";
|
|
22344
|
+
await step(name, async () => {
|
|
22345
|
+
await openRow(ROW_COLLECTION, "\u5408\u96C6");
|
|
22346
|
+
await waitForVisible(page, TAG_INPUT, 8000);
|
|
22347
|
+
await fillSelector(page, TAG_INPUT, collection);
|
|
22348
|
+
await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
|
|
22349
|
+
await clickSelector(page, `${DIALOG} :text("${collection}")`, 8000);
|
|
22350
|
+
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
22351
|
+
});
|
|
22352
|
+
await residual(name);
|
|
21226
22353
|
}
|
|
21227
|
-
|
|
21228
|
-
|
|
21229
|
-
|
|
21230
|
-
|
|
21231
|
-
|
|
21232
|
-
|
|
21233
|
-
|
|
21234
|
-
|
|
21235
|
-
|
|
21236
|
-
|
|
21237
|
-
|
|
22354
|
+
const originalUrl = str("originalUrl");
|
|
22355
|
+
if (originalUrl !== "") {
|
|
22356
|
+
const name = "\u539F\u6587\u94FE\u63A5";
|
|
22357
|
+
const urlRow = await tryFindSelector(page, ROW_URL, 2000);
|
|
22358
|
+
if (urlRow === undefined) {
|
|
22359
|
+
warnings.push(`${name}: \u884C\u4E0D\u5B58\u5728\uFF08\u5F53\u524D\u516C\u4F17\u53F7\u4E0D\u652F\u6301\uFF09\uFF0C\u8BF7\u624B\u52A8\u5904\u7406\u6216\u79FB\u9664 originalUrl`);
|
|
22360
|
+
} else {
|
|
22361
|
+
await step(name, async () => {
|
|
22362
|
+
await openRow(ROW_URL, "\u539F\u6587\u94FE\u63A5");
|
|
22363
|
+
const field = await trySelectors(page, URL_INPUT);
|
|
22364
|
+
if (!field.found)
|
|
22365
|
+
throw new Error("no URL field in the \u539F\u6587\u94FE\u63A5 dialog");
|
|
22366
|
+
await fillSelector(page, field.selector ?? URL_INPUT.join(", "), originalUrl);
|
|
22367
|
+
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
22368
|
+
});
|
|
22369
|
+
await residual(name);
|
|
22370
|
+
const locator = page.locator(URL_INPUT[0] ?? "").first();
|
|
22371
|
+
const committed = typeof locator.inputValue === "function" ? await locator.inputValue().catch(() => null) : null;
|
|
22372
|
+
if (norm(committed) !== norm(originalUrl))
|
|
22373
|
+
warnings.push(`${name}: \u5DF2\u63D0\u4EA4\u4F46\u672A\u8BFB\u53D6\u5230\u5DF2\u4FDD\u5B58\u7684\u94FE\u63A5\uFF0C\u8BF7\u6838\u5BF9`);
|
|
22374
|
+
}
|
|
21238
22375
|
}
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
21249
|
-
|
|
21250
|
-
return { error: author.error };
|
|
21251
|
-
switch (operation) {
|
|
21252
|
-
case "\u6587\u7AE0": {
|
|
21253
|
-
if ((content.body ?? "").trim().length === 0) {
|
|
21254
|
-
return { error: "body validation: Content.body is required for \u6587\u7AE0" };
|
|
21255
|
-
}
|
|
21256
|
-
const digest = validateDigest(content, operation);
|
|
21257
|
-
if (digest.error)
|
|
21258
|
-
return { error: digest.error };
|
|
21259
|
-
const images = await resolveBodyImages(content);
|
|
21260
|
-
if (images.error)
|
|
21261
|
-
return { error: images.error };
|
|
21262
|
-
if ((images.paths ?? []).length === 0) {
|
|
21263
|
-
return {
|
|
21264
|
-
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[]"
|
|
21265
|
-
};
|
|
22376
|
+
const claimSource = str("claimSource");
|
|
22377
|
+
if (claimSource !== "") {
|
|
22378
|
+
const name = "\u521B\u4F5C\u6765\u6E90";
|
|
22379
|
+
const claimMatched = async () => {
|
|
22380
|
+
try {
|
|
22381
|
+
const raw = await page.evaluate(CLAIM_COMMITTED);
|
|
22382
|
+
if (raw === null || typeof raw !== "object")
|
|
22383
|
+
return null;
|
|
22384
|
+
return raw.enabled === true && norm(raw.text) === norm(claimSource);
|
|
22385
|
+
} catch {
|
|
22386
|
+
return null;
|
|
21266
22387
|
}
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
22388
|
+
};
|
|
22389
|
+
if (await claimMatched() !== true) {
|
|
22390
|
+
await step(name, async () => {
|
|
22391
|
+
await openRow(ROW_CLAIM, "\u521B\u4F5C\u6765\u6E90");
|
|
22392
|
+
const dialog = await waitForVisible(page, DIALOG, 3000, { required: false });
|
|
22393
|
+
if (dialog !== undefined) {
|
|
22394
|
+
await clickSelector(page, `${DIALOG} :text("${claimSource}")`, 8000);
|
|
22395
|
+
await clickSelector(page, DIALOG_CONFIRM, 8000);
|
|
21272
22396
|
}
|
|
21273
|
-
};
|
|
21274
|
-
}
|
|
21275
|
-
case "\u8D34\u56FE": {
|
|
21276
|
-
const images = await resolveBodyImages(content);
|
|
21277
|
-
if (images.error)
|
|
21278
|
-
return { error: images.error };
|
|
21279
|
-
if ((images.paths ?? []).length === 0) {
|
|
21280
|
-
return {
|
|
21281
|
-
error: "image validation: \u8D34\u56FE requires at least one image (metadata.images[] or metadata.wechatmp.coverPath)"
|
|
21282
|
-
};
|
|
21283
|
-
}
|
|
21284
|
-
return { input: { operation, images: images.paths ?? [] } };
|
|
21285
|
-
}
|
|
21286
|
-
case "\u89C6\u9891": {
|
|
21287
|
-
const video = await resolveMediaPath(content, {
|
|
21288
|
-
key: "video",
|
|
21289
|
-
label: "video validation",
|
|
21290
|
-
extensions: VIDEO_EXTENSIONS,
|
|
21291
|
-
list: VIDEO_LIST
|
|
21292
22397
|
});
|
|
21293
|
-
if (video.error)
|
|
21294
|
-
return { error: video.error };
|
|
21295
|
-
return { input: { operation, video: video.path ?? "" } };
|
|
21296
22398
|
}
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
key: "audio",
|
|
21300
|
-
label: "audio validation",
|
|
21301
|
-
extensions: AUDIO_EXTENSIONS,
|
|
21302
|
-
list: AUDIO_LIST
|
|
21303
|
-
});
|
|
21304
|
-
if (audio.error)
|
|
21305
|
-
return { error: audio.error };
|
|
21306
|
-
const cover = await resolveMediaPath(content, {
|
|
21307
|
-
key: "coverImage",
|
|
21308
|
-
label: "cover validation",
|
|
21309
|
-
extensions: IMAGE_EXTENSIONS,
|
|
21310
|
-
list: IMAGE_LIST
|
|
21311
|
-
});
|
|
21312
|
-
if (cover.error)
|
|
21313
|
-
return { error: cover.error };
|
|
21314
|
-
return { input: { operation, audio: audio.path ?? "", cover: cover.path ?? "" } };
|
|
22399
|
+
if (await claimMatched() !== true && !await residual(name)) {
|
|
22400
|
+
warnings.push(`${name}: \u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u8BBE\u7F6E`);
|
|
21315
22401
|
}
|
|
21316
22402
|
}
|
|
22403
|
+
await sweep();
|
|
22404
|
+
return warnings;
|
|
21317
22405
|
}
|
|
21318
22406
|
|
|
21319
22407
|
// ../../plugins/publishings/wechatmp-pub/src/flow.ts
|
|
@@ -21981,7 +23069,86 @@ async function openDialogInventory(page) {
|
|
|
21981
23069
|
const raw = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog, .weui-desktop-dialog__wrp')]` + `.filter((d) => d.offsetWidth).map((d) => (d.className || '') + ' :: ' + ` + `(d.innerText || '').replace(/\\s+/g, ' ').slice(0, 160)).join(' || ')`).catch(() => "");
|
|
21982
23070
|
return typeof raw === "string" ? raw.slice(0, 600) : "";
|
|
21983
23071
|
}
|
|
23072
|
+
async function runCgiDraft(input, deps) {
|
|
23073
|
+
if (input.operation !== "\u6587\u7AE0")
|
|
23074
|
+
return;
|
|
23075
|
+
try {
|
|
23076
|
+
assertEligibleCgiDraft(deps.content);
|
|
23077
|
+
} catch (error) {
|
|
23078
|
+
if (error instanceof WechatCgiDraftError && error.code === "ineligible")
|
|
23079
|
+
return;
|
|
23080
|
+
throw error;
|
|
23081
|
+
}
|
|
23082
|
+
const metadata = deps.content.metadata ?? {};
|
|
23083
|
+
const bodyImages = input.images;
|
|
23084
|
+
if (bodyImages.length === 0)
|
|
23085
|
+
return;
|
|
23086
|
+
const cover = typeof metadata.coverPath === "string" && metadata.coverPath.length > 0 ? metadata.coverPath : bodyImages[0] ?? "";
|
|
23087
|
+
try {
|
|
23088
|
+
await readLocalImageBytes(cover);
|
|
23089
|
+
} catch {
|
|
23090
|
+
return;
|
|
23091
|
+
}
|
|
23092
|
+
const localImages = {};
|
|
23093
|
+
for (const path of bodyImages)
|
|
23094
|
+
localImages[path] = path;
|
|
23095
|
+
const imageBlock = bodyImages.map((path) => `<img src="${path}">`).join("");
|
|
23096
|
+
try {
|
|
23097
|
+
const session = await extractWechatCgiSession(deps.entry);
|
|
23098
|
+
let media;
|
|
23099
|
+
try {
|
|
23100
|
+
media = await prepareArticleMedia(deps.entry, session, {
|
|
23101
|
+
html: imageBlock + markdownToWechatHtml(bodyOf(deps.content)),
|
|
23102
|
+
coverPath: cover,
|
|
23103
|
+
localImages
|
|
23104
|
+
});
|
|
23105
|
+
} catch (error) {
|
|
23106
|
+
if (error instanceof WechatCgiDraftError)
|
|
23107
|
+
throw error;
|
|
23108
|
+
return ResultSchema.parse({
|
|
23109
|
+
ok: false,
|
|
23110
|
+
target: TARGET,
|
|
23111
|
+
error: error instanceof Error ? error.message : String(error),
|
|
23112
|
+
metadata: { operation: input.operation, publishStatus: deps.publishStatus, stage: "media-preparation" }
|
|
23113
|
+
});
|
|
23114
|
+
}
|
|
23115
|
+
const draft = await createWechatArticleDraft(deps.entry, session, deps.content, media);
|
|
23116
|
+
return ResultSchema.parse({
|
|
23117
|
+
ok: true,
|
|
23118
|
+
target: TARGET,
|
|
23119
|
+
id: draft.appMsgId,
|
|
23120
|
+
url: `https://mp.weixin.qq.com${draft.editPath}`,
|
|
23121
|
+
metadata: {
|
|
23122
|
+
operation: input.operation,
|
|
23123
|
+
publishStatus: deps.publishStatus,
|
|
23124
|
+
stage: "draft-created",
|
|
23125
|
+
appMsgId: draft.appMsgId,
|
|
23126
|
+
coverFileIds: [media.cover.wide.fileId, media.cover.square.fileId],
|
|
23127
|
+
cdnMigratedImages: media.images.length
|
|
23128
|
+
}
|
|
23129
|
+
});
|
|
23130
|
+
} catch (error) {
|
|
23131
|
+
if (!(error instanceof WechatCgiDraftError))
|
|
23132
|
+
throw error;
|
|
23133
|
+
return ResultSchema.parse({
|
|
23134
|
+
ok: false,
|
|
23135
|
+
target: TARGET,
|
|
23136
|
+
error: error.message,
|
|
23137
|
+
metadata: {
|
|
23138
|
+
operation: input.operation,
|
|
23139
|
+
publishStatus: deps.publishStatus,
|
|
23140
|
+
stage: error.stage,
|
|
23141
|
+
...error.metadata.ambiguousSubmission === true ? { ambiguousSubmission: true } : {}
|
|
23142
|
+
}
|
|
23143
|
+
});
|
|
23144
|
+
}
|
|
23145
|
+
}
|
|
21984
23146
|
async function runMode(input, deps) {
|
|
23147
|
+
if (input.operation === "\u6587\u7AE0" && deps.publishStatus === "draft") {
|
|
23148
|
+
const cgi = await runCgiDraft(input, deps);
|
|
23149
|
+
if (cgi !== undefined)
|
|
23150
|
+
return cgi;
|
|
23151
|
+
}
|
|
21985
23152
|
const entry = await enterMode(deps, input.operation);
|
|
21986
23153
|
const composed = await compose(input, deps, entry);
|
|
21987
23154
|
const diagnostics = [...composed.warnings, ...composed.enrichWarnings];
|