@gobing-ai/knowledge-kit 0.0.33 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +25191 -24167
- package/package.json +4 -4
- package/plugins/generations/content-gen/dist/index.js +9 -9
- package/plugins/generations/core-facts-gen/dist/index.js +12 -12
- package/plugins/generations/daily-article-gen/dist/index.js +31 -23
- package/plugins/generations/daily-article-gen/src/index.ts +39 -14
- package/plugins/generations/dailynews-gen/dist/index.js +23 -21
- package/plugins/generations/dailynews-gen/src/script-builder.ts +30 -26
- package/plugins/generations/episode-plan-gen/dist/index.js +12 -12
- package/plugins/generations/gemini-tts/dist/index.js +13 -12
- package/plugins/generations/image-gen/dist/index.js +26 -11
- package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +16 -14
- package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -2
- package/plugins/generations/image-gen/src/compose/card.ts +17 -2
- package/plugins/generations/news-report-gen/dist/index.js +12 -12
- package/plugins/generations/omni-voice-gen/plugin.json +1 -1
- package/plugins/generations/voice-gen/dist/index.js +13 -12
- package/plugins/generations/voice-gen/src/index.ts +9 -3
- package/plugins/ingestions/aihot-ingest/dist/index.js +12 -12
- package/plugins/ingestions/horizon-ingest/dist/index.js +12 -12
- package/plugins/ingestions/karakeep-local/dist/index.js +13 -13
- package/plugins/ingestions/last30days-ingest/dist/index.js +12 -12
- package/plugins/ingestions/web-search/dist/index.js +10 -10
- package/plugins/kk/decisions/extract/ai-news.yaml +6 -0
- package/plugins/kk/decisions/kk-items.yaml +69 -3
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-stages.ts +834 -87
- package/plugins/kk/scripts/daily-voice-style.zh.md +15 -10
- package/plugins/kk/scripts/kk-workflow-stages.ts +46 -3
- package/plugins/kk/skills/content-safety/SKILL.md +1 -1
- package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +2 -2
- package/plugins/kk/skills/content-safety/references/platforms/wechatmp.md +2 -2
- package/plugins/kk/skills/content-safety/references/platforms/xiaoyuzhou.md +4 -2
- package/plugins/kk/workflows/kk-collect.yaml +7 -9
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +32 -7
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +169 -47
- package/plugins/kk/workflows/kk-daily-safety.yaml +9 -12
- package/plugins/publishings/emdash-pub/dist/index.js +9 -9
- package/plugins/publishings/infoq-pub/dist/index.js +22 -22
- package/plugins/publishings/infoq-pub/dist/profile-check.js +30336 -0
- package/plugins/publishings/infoq-pub/plugin.json +1 -1
- package/plugins/publishings/juejin-pub/dist/index.js +22 -22
- package/plugins/publishings/juejin-pub/dist/profile-check.js +30322 -0
- package/plugins/publishings/juejin-pub/plugin.json +1 -1
- package/plugins/publishings/medium-pub/dist/index.js +9 -9
- package/plugins/publishings/podcast-pub/dist/index.js +9 -9
- package/plugins/publishings/qiita-pub/dist/index.js +9 -9
- package/plugins/publishings/substack-pub/dist/index.js +22 -22
- package/plugins/publishings/substack-pub/dist/profile-check.js +30392 -0
- package/plugins/publishings/substack-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +9 -9
- package/plugins/publishings/wechatmp-pub/dist/index.js +329 -154
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +30271 -0
- package/plugins/publishings/wechatmp-pub/plugin.json +1 -1
- package/plugins/publishings/wechatmp-pub/src/enrich.ts +106 -145
- package/plugins/publishings/wechatmp-pub/src/flow.ts +283 -42
- package/plugins/publishings/wechatmp-pub/src/page.ts +20 -0
- package/plugins/publishings/x-pub/dist/index.js +22 -22
- package/plugins/publishings/x-pub/dist/profile-check.js +30328 -0
- package/plugins/publishings/x-pub/plugin.json +1 -1
- package/plugins/publishings/xhs-pub/dist/index.js +76 -27
- package/plugins/publishings/xhs-pub/dist/profile-check.js +30538 -0
- package/plugins/publishings/xhs-pub/plugin.json +1 -1
- package/plugins/publishings/xhs-pub/src/flow.ts +9 -3
- package/plugins/publishings/xhs-pub/src/index.ts +111 -5
- package/plugins/publishings/zenn-pub/dist/index.js +9 -9
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +0 -2
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +0 -6
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "wechatmp-pub",
|
|
3
3
|
"kind": "publish",
|
|
4
4
|
"entry": "./dist/index.js",
|
|
5
|
-
"check": "./
|
|
5
|
+
"check": "./dist/profile-check.js",
|
|
6
6
|
"version": "1.0.0",
|
|
7
7
|
"description": "Publish plugin for the WeChat MP console via ts-browser-automation: draft-box first (ADR-022), headed publish behind a session preflight"
|
|
8
8
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { trySelectors } from '@gobing-ai/publish-harness';
|
|
2
|
-
import {
|
|
3
|
-
import { clickSelector, fillSelector, POLL_MS, type WechatPage } from './page';
|
|
2
|
+
import { clickSelector, fillSelector, type WechatPage, waitForVisible } from './page';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* The 文章 operation's `metadata.wechatmp` enrichment rail (2026-09-29, ADR-026).
|
|
@@ -19,16 +18,28 @@ import { clickSelector, fillSelector, POLL_MS, type WechatPage } from './page';
|
|
|
19
18
|
|
|
20
19
|
const DIALOG = '.weui-desktop-dialog:visible';
|
|
21
20
|
const CONFIRM = `${DIALOG} button:has-text("确认"), ${DIALOG} button:has-text("确定")`;
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Every settings row follows one shape (operator walk 2026-10-10): a row control that opens a dialog,
|
|
23
|
+
* an action inside it, then 确定. The row's own label is listed alongside its `>` expander because both
|
|
24
|
+
* dispatch the same handler; the first selector that resolves wins.
|
|
25
|
+
*/
|
|
26
|
+
const ROW_ORIGINAL = ['.setting-group__switch.js_edit_ori', '.js_edit_ori', 'text=原创'];
|
|
27
|
+
const ROW_REWARD = ['.setting-group__switch.js_can_reward', '.js_can_reward', 'text=赞赏'];
|
|
28
|
+
const ROW_COLLECTION = ['.allow_click_opr.js_article_tags_label', 'text=合集'];
|
|
29
|
+
const ROW_URL = ['.allow_click_opr.js_url_checkbox', 'text=原文链接'];
|
|
30
|
+
const ROW_CLAIM = ['#js_claim_source_area .frm_checkbox_label', 'text=创作来源'];
|
|
31
|
+
const DIALOG_CONFIRM = CONFIRM;
|
|
24
32
|
const TAG_INPUT = 'input[placeholder*="合集"]';
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
/** The 赞赏 dialog's account field, filled only when the platform leaves it empty. */
|
|
34
|
+
const REWARD_ACCOUNT = [
|
|
35
|
+
`${DIALOG} input[placeholder*="赞赏"]`,
|
|
36
|
+
`${DIALOG} input[placeholder*="账户"]`,
|
|
37
|
+
`${DIALOG} input.frm_input`,
|
|
38
|
+
];
|
|
39
|
+
/** The 原文链接 dialog's URL field. */
|
|
40
|
+
const URL_INPUT = [`${DIALOG} input[type="url"]`, `${DIALOG} input[placeholder*="链接"]`, `${DIALOG} input.frm_input`];
|
|
41
|
+
/** The 原创 dialog's declaration choice; the dialog validates on 确定 alone for this account. */
|
|
42
|
+
const ORIGINAL_DECLARATION = `${DIALOG} :text("文字原创")`;
|
|
32
43
|
/** The 合集 picker's options load after the dialog opens (the pre-ADR-029 settle, kept). */
|
|
33
44
|
const COLLECTION_SETTLE_MS = 1_200;
|
|
34
45
|
/** The settings panel re-renders after a dialog confirm and can briefly detach rows. */
|
|
@@ -36,16 +47,15 @@ const STEP_RETRY_SETTLE_MS = 1_000;
|
|
|
36
47
|
const SWEEP_SETTLE_MS = 400;
|
|
37
48
|
|
|
38
49
|
/**
|
|
39
|
-
* Set the operator's draft metadata (
|
|
50
|
+
* Set the operator's draft metadata (原创/赞赏/合集/原文链接/创作来源) in the v2 editor.
|
|
40
51
|
*
|
|
41
52
|
* Runs on a FRESH draft only — the toggles are not state-aware (no idempotency for re-runs).
|
|
42
|
-
* Per-field failures are collected into warnings and surfaced in the `Result`; the draft still
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* so a non-closing dialog is swept and surfaced as a warning.
|
|
53
|
+
* Per-field failures are collected into warnings and surfaced in the `Result`; the draft still saves.
|
|
54
|
+
*
|
|
55
|
+
* Every step follows the operator's measured shape (2026-10-10): open the row, act in the dialog,
|
|
56
|
+
* click 确定. The previous rail modelled 原创 as "choose the declaration, then the author must resolve
|
|
57
|
+
* to the 赏 account" and skipped 赞赏 / 原文链接 entirely — which is why 作者, 原创 and 赞赏 all came
|
|
58
|
+
* back unset on the daily run.
|
|
49
59
|
*/
|
|
50
60
|
export async function enrichArticle(page: WechatPage, meta: Record<string, unknown>): Promise<string[]> {
|
|
51
61
|
const warnings: string[] = [];
|
|
@@ -54,17 +64,33 @@ export async function enrichArticle(page: WechatPage, meta: Record<string, unkno
|
|
|
54
64
|
// Sweep any leaked dialog so the next step (especially 保存为草稿) is never overlay-blocked.
|
|
55
65
|
const sweep = async () => {
|
|
56
66
|
const cancel = await trySelectors(page, ['button:has-text("取消")']);
|
|
57
|
-
if (cancel.found)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
if (cancel.found) {
|
|
68
|
+
try {
|
|
69
|
+
await page
|
|
70
|
+
.locator(cancel.selector ?? '')
|
|
71
|
+
.first()
|
|
72
|
+
.click({ timeout: 2_000 });
|
|
73
|
+
} catch {
|
|
74
|
+
// The dialog may already be gone; the sweep is best-effort.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
63
77
|
await page.press?.('Escape');
|
|
64
78
|
await page.waitForTimeout?.(SWEEP_SETTLE_MS);
|
|
65
79
|
};
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
/** Open a settings row: its label or `>` expander, whichever the build exposes. */
|
|
81
|
+
const openRow = async (row: readonly string[], what: string) => {
|
|
82
|
+
for (const selector of row) {
|
|
83
|
+
// `trySelectors` already absorbs a throwing probe and returns {found:false}, so a
|
|
84
|
+
// `.catch()` here would be unreachable.
|
|
85
|
+
const found = await trySelectors(page, [selector]);
|
|
86
|
+
if (found.found) {
|
|
87
|
+
await clickSelector(page, selector, 8_000);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`no ${what} row control among [${row.join(' | ')}]`);
|
|
92
|
+
};
|
|
93
|
+
/** One retry per step: the panel re-renders after a dialog confirm and can briefly detach rows. */
|
|
68
94
|
const step = async (name: string, fn: () => Promise<void>) => {
|
|
69
95
|
try {
|
|
70
96
|
await fn();
|
|
@@ -81,149 +107,84 @@ export async function enrichArticle(page: WechatPage, meta: Record<string, unkno
|
|
|
81
107
|
await sweep();
|
|
82
108
|
}
|
|
83
109
|
};
|
|
110
|
+
/** True once every dialog has closed — the platform's own validation gate on a settings dialog. */
|
|
111
|
+
const dialogsClosed = async () =>
|
|
112
|
+
(await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`)) !==
|
|
113
|
+
false;
|
|
84
114
|
|
|
85
115
|
if (on('original')) {
|
|
86
116
|
await step('原创声明', async () => {
|
|
87
|
-
await
|
|
88
|
-
|
|
117
|
+
await openRow(ROW_ORIGINAL, '原创声明');
|
|
118
|
+
// The declaration radio only exists on some builds; 确定 alone is the documented action.
|
|
119
|
+
const declaration = await trySelectors(page, [ORIGINAL_DECLARATION]).catch(() => ({ found: false }));
|
|
120
|
+
if (declaration.found) await clickSelector(page, ORIGINAL_DECLARATION, 5_000);
|
|
121
|
+
await clickSelector(page, DIALOG_CONFIRM, 8_000);
|
|
122
|
+
});
|
|
123
|
+
if (!(await dialogsClosed())) warnings.push('原创声明: dialog did not close — check the draft');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (on('reward')) {
|
|
127
|
+
await step('赞赏', async () => {
|
|
128
|
+
await openRow(ROW_REWARD, '赞赏');
|
|
129
|
+
// 赞赏账户 defaults from 作者; fill it only when the platform left it empty.
|
|
89
130
|
const author = str('author');
|
|
90
|
-
if (author !== '')
|
|
91
|
-
|
|
131
|
+
if (author !== '') {
|
|
132
|
+
for (const selector of REWARD_ACCOUNT) {
|
|
133
|
+
const found = await trySelectors(page, [selector]).catch(() => ({ found: false, selector: null }));
|
|
134
|
+
if (!found.found) continue;
|
|
135
|
+
const empty = await page.evaluate(
|
|
136
|
+
`(() => { const el = document.querySelector(${JSON.stringify(selector)}); return !el || (el.value ?? '').trim() === ''; })()`,
|
|
137
|
+
);
|
|
138
|
+
if (empty === true) await fillSelector(page, selector, author);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await clickSelector(page, DIALOG_CONFIRM, 8_000);
|
|
92
143
|
});
|
|
93
|
-
|
|
94
|
-
`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`,
|
|
95
|
-
);
|
|
96
|
-
if (committed === false) {
|
|
97
|
-
warnings.push('原创声明: 未生效 - 作者须为赞赏账户(搜索选择),请手动设置');
|
|
98
|
-
await sweep();
|
|
99
|
-
}
|
|
144
|
+
if (!(await dialogsClosed())) warnings.push('赞赏: dialog did not close — check the draft');
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
const collection = str('collection');
|
|
103
148
|
if (collection !== '') {
|
|
104
149
|
await step('合集', async () => {
|
|
105
|
-
await
|
|
150
|
+
await openRow(ROW_COLLECTION, '合集');
|
|
106
151
|
await waitForVisible(page, TAG_INPUT, 8_000);
|
|
107
152
|
await fillSelector(page, TAG_INPUT, collection);
|
|
108
153
|
await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
|
|
109
154
|
await clickSelector(page, `${DIALOG} :text("${collection}")`, 8_000);
|
|
110
|
-
await clickSelector(page,
|
|
155
|
+
await clickSelector(page, DIALOG_CONFIRM, 8_000);
|
|
111
156
|
});
|
|
157
|
+
if (!(await dialogsClosed())) warnings.push('合集: dialog did not close — check the draft');
|
|
112
158
|
}
|
|
113
159
|
|
|
114
|
-
|
|
160
|
+
const originalUrl = str('originalUrl');
|
|
161
|
+
if (originalUrl !== '') {
|
|
162
|
+
await step('原文链接', async () => {
|
|
163
|
+
await openRow(ROW_URL, '原文链接');
|
|
164
|
+
const field = await trySelectors(page, URL_INPUT);
|
|
165
|
+
if (!field.found) throw new Error('no URL field in the 原文链接 dialog');
|
|
166
|
+
await fillSelector(page, field.selector ?? URL_INPUT.join(', '), originalUrl);
|
|
167
|
+
await clickSelector(page, DIALOG_CONFIRM, 8_000);
|
|
168
|
+
});
|
|
169
|
+
if (!(await dialogsClosed())) warnings.push('原文链接: dialog did not close — check the draft');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 创作来源 is the operator's chosen stance (`个人观点,仅供参考` for this column), not a fixed
|
|
173
|
+
// "AI generated" declaration — the label is the metadata value so the operator owns the wording.
|
|
174
|
+
const claimSource = str('claimSource');
|
|
175
|
+
if (claimSource !== '') {
|
|
115
176
|
await step('创作来源', async () => {
|
|
116
|
-
await
|
|
117
|
-
// Probes show no dialog on current builds — the checkbox alone declares AI generation.
|
|
177
|
+
await openRow(ROW_CLAIM, '创作来源');
|
|
118
178
|
const dialog = await waitForVisible(page, DIALOG, 3_000, { required: false });
|
|
119
179
|
if (dialog !== undefined) {
|
|
120
|
-
await clickSelector(page, `${DIALOG} :text("
|
|
121
|
-
await clickSelector(page,
|
|
180
|
+
await clickSelector(page, `${DIALOG} :text("${claimSource}")`, 8_000);
|
|
181
|
+
await clickSelector(page, DIALOG_CONFIRM, 8_000);
|
|
122
182
|
}
|
|
123
183
|
});
|
|
124
184
|
const checked = await page.evaluate(`!!document.querySelector('#js_claim_source_area input:checked')`);
|
|
125
|
-
if (checked === false) warnings.push('创作来源:
|
|
185
|
+
if (checked === false) warnings.push('创作来源: 未生效,请手动设置');
|
|
126
186
|
}
|
|
127
187
|
|
|
128
188
|
await sweep();
|
|
129
189
|
return warnings;
|
|
130
190
|
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Audio embed at the article top (operator request 2026-09-29): open the editor's audio menu (a
|
|
134
|
-
* hover dropdown — a JS click bypasses the hover gate), reuse an existing same-title 素材库
|
|
135
|
-
* material, and insert it at the caret (placed at the article start first).
|
|
136
|
-
*
|
|
137
|
-
* Uploading a missing material is deliberately manual: docs/04 records the 上传音频 form as
|
|
138
|
-
* iframe-based and unmapped, and the walk did not re-measure it. Fail-soft — every non-committed
|
|
139
|
-
* outcome returns a warning string, `''` means placed at the top.
|
|
140
|
-
*/
|
|
141
|
-
export async function insertAudio(page: WechatPage, title: string): Promise<string> {
|
|
142
|
-
const CARET = `(() => {
|
|
143
|
-
const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('标题'));
|
|
144
|
-
if (!pm || !pm.firstChild) return;
|
|
145
|
-
const r = document.createRange();
|
|
146
|
-
r.setStart(pm.firstChild, 0);
|
|
147
|
-
r.collapse(true);
|
|
148
|
-
const sel = window.getSelection();
|
|
149
|
-
sel?.removeAllRanges();
|
|
150
|
-
sel?.addRange(r);
|
|
151
|
-
})()`;
|
|
152
|
-
const DEDUPE = `(() => {
|
|
153
|
-
const want = ${JSON.stringify(title)}.replace(/\\s+/g, '');
|
|
154
|
-
const hit = [...document.querySelectorAll('label.frm_checkbox_label.audio_item')]
|
|
155
|
-
.find((l) => (l.textContent ?? '').replace(/\\s+/g, '').includes(want));
|
|
156
|
-
if (!hit) return 'missing';
|
|
157
|
-
const cb = (hit.closest('li') ?? hit.parentElement)?.querySelector('input[type=checkbox]');
|
|
158
|
-
if (!cb) return 'nobox';
|
|
159
|
-
cb.click();
|
|
160
|
-
return 'checked';
|
|
161
|
-
})()`;
|
|
162
|
-
const INSERT = `(() => {
|
|
163
|
-
const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '插入' && b.offsetWidth > 0);
|
|
164
|
-
if (!btn) return false;
|
|
165
|
-
btn.click();
|
|
166
|
-
return true;
|
|
167
|
-
})()`;
|
|
168
|
-
const CANCEL = `(() => {
|
|
169
|
-
const btn = [...document.querySelectorAll('button')].find((b) => (b.textContent ?? '').trim() === '取消' && b.offsetWidth > 0);
|
|
170
|
-
btn?.click();
|
|
171
|
-
})()`;
|
|
172
|
-
const VERIFY = `(() => {
|
|
173
|
-
const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('标题'));
|
|
174
|
-
if (!pm) return 'noeditor';
|
|
175
|
-
const node = pm.querySelector('mpvoice, [class*="audio"]');
|
|
176
|
-
if (!node) return 'absent';
|
|
177
|
-
const first = pm.firstElementChild;
|
|
178
|
-
return first && (first === node || first.contains(node)) ? 'top' : 'notop';
|
|
179
|
-
})()`;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
await page.evaluate(CARET);
|
|
183
|
-
await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
|
|
184
|
-
await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
|
|
185
|
-
// The 素材库 list loads async after the dialog opens — retry while rows stream in.
|
|
186
|
-
let dedupe = String(await page.evaluate(DEDUPE));
|
|
187
|
-
for (let i = 0; dedupe === 'missing' && i < AUDIO_ROW_RETRIES; i += 1) {
|
|
188
|
-
await page.waitForTimeout?.(AUDIO_ROW_SETTLE_MS);
|
|
189
|
-
dedupe = String(await page.evaluate(DEDUPE));
|
|
190
|
-
}
|
|
191
|
-
if (dedupe !== 'checked') {
|
|
192
|
-
await page.evaluate(CANCEL);
|
|
193
|
-
return dedupe === 'missing'
|
|
194
|
-
? '音频: 素材库无同名音频,请手动上传后插入'
|
|
195
|
-
: '音频: 同名素材行未找到勾选框,请手动插入';
|
|
196
|
-
}
|
|
197
|
-
if ((await page.evaluate(INSERT)) !== true) {
|
|
198
|
-
await page.evaluate(CANCEL);
|
|
199
|
-
return '音频: 插入按钮未找到,请手动插入';
|
|
200
|
-
}
|
|
201
|
-
await page.waitForTimeout?.(2_000);
|
|
202
|
-
const state = String(await page.evaluate(VERIFY));
|
|
203
|
-
if (state === 'absent' || state === 'noeditor') return '音频: 插入未生效,请手动检查';
|
|
204
|
-
if (state === 'notop') return '音频: 已插入但未在文首,请手动调整位置';
|
|
205
|
-
return '';
|
|
206
|
-
} catch {
|
|
207
|
-
return '音频: 自动插入失败,请手动插入';
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Polls a selector for visibility; optionally returns `undefined` instead of throwing. */
|
|
212
|
-
async function waitForVisible(
|
|
213
|
-
page: WechatPage,
|
|
214
|
-
selector: string,
|
|
215
|
-
timeoutMs: number,
|
|
216
|
-
opts: { required?: boolean } = {},
|
|
217
|
-
): Promise<string | undefined> {
|
|
218
|
-
const visible = await waitUntil(
|
|
219
|
-
async () => {
|
|
220
|
-
const found = await trySelectors(page, [selector]).catch(() => ({ found: false, selector: null }));
|
|
221
|
-
return found.found;
|
|
222
|
-
},
|
|
223
|
-
timeoutMs,
|
|
224
|
-
POLL_MS,
|
|
225
|
-
);
|
|
226
|
-
if (visible) return selector;
|
|
227
|
-
if (opts.required === false) return undefined;
|
|
228
|
-
throw new Error(`${selector}: not visible within ${timeoutMs}ms`);
|
|
229
|
-
}
|