@gobing-ai/knowledge-kit 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +10 -10
- 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 +12 -12
- package/plugins/generations/dailynews-gen/dist/index.js +10 -10
- package/plugins/generations/dailynews-gen/src/script-builder.ts +4 -2
- package/plugins/generations/episode-plan-gen/dist/index.js +23 -13
- package/plugins/generations/episode-plan-gen/src/index.ts +23 -2
- package/plugins/generations/gemini-tts/dist/index.js +9 -9
- package/plugins/generations/image-gen/dist/index.js +9 -9
- package/plugins/generations/news-report-gen/dist/index.js +12 -12
- package/plugins/generations/voice-gen/dist/index.js +9 -9
- 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/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +173 -15
- package/plugins/kk/skills/content-judge/SKILL.md +16 -4
- package/plugins/kk/skills/content-judge/references/rubrics.md +8 -2
- package/plugins/kk/skills/content-judge/references/workflow-integration.md +2 -1
- package/plugins/kk/skills/content-safety/SKILL.md +24 -13
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +65 -8
- package/plugins/publishings/emdash-pub/dist/index.js +9 -9
- package/plugins/publishings/infoq-pub/dist/index.js +8602 -211
- package/plugins/publishings/infoq-pub/package.json +3 -1
- package/plugins/publishings/infoq-pub/plugin.json +1 -1
- package/plugins/publishings/infoq-pub/src/index.ts +246 -29
- package/plugins/publishings/infoq-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/juejin-pub/dist/index.js +8665 -211
- package/plugins/publishings/juejin-pub/package.json +3 -1
- package/plugins/publishings/juejin-pub/plugin.json +1 -1
- package/plugins/publishings/juejin-pub/src/index.ts +305 -29
- package/plugins/publishings/juejin-pub/src/profile-check.ts +19 -0
- 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 +8610 -211
- package/plugins/publishings/substack-pub/package.json +3 -1
- package/plugins/publishings/substack-pub/plugin.json +1 -1
- package/plugins/publishings/substack-pub/src/index.ts +271 -29
- package/plugins/publishings/substack-pub/src/profile-check.ts +74 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +9 -9
- package/plugins/publishings/wechatmp-pub/dist/index.js +1227 -348
- package/plugins/publishings/wechatmp-pub/src/enrich.ts +229 -0
- package/plugins/publishings/wechatmp-pub/src/flow.ts +896 -0
- package/plugins/publishings/wechatmp-pub/src/index.ts +186 -516
- package/plugins/publishings/wechatmp-pub/src/modes.ts +403 -0
- package/plugins/publishings/wechatmp-pub/src/page.ts +260 -0
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +7 -5
- package/plugins/publishings/x-pub/dist/index.js +8626 -221
- package/plugins/publishings/x-pub/package.json +3 -1
- package/plugins/publishings/x-pub/plugin.json +1 -1
- package/plugins/publishings/x-pub/src/index.ts +258 -20
- package/plugins/publishings/x-pub/src/profile-check.ts +19 -0
- package/plugins/publishings/xhs-pub/dist/index.js +843 -346
- package/plugins/publishings/xhs-pub/plugin.json +1 -1
- package/plugins/publishings/xhs-pub/src/flow.ts +572 -0
- package/plugins/publishings/xhs-pub/src/index.ts +101 -593
- package/plugins/publishings/xhs-pub/src/modes.ts +300 -0
- package/plugins/publishings/xhs-pub/src/page.ts +267 -0
- package/plugins/publishings/xhs-pub/src/profile-check.ts +2 -3
- package/plugins/publishings/zenn-pub/dist/index.js +9 -9
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { trySelectors } from '@gobing-ai/publish-harness';
|
|
2
|
+
import { waitUntil } from '@gobing-ai/publish-harness/browser';
|
|
3
|
+
import { clickSelector, fillSelector, POLL_MS, type WechatPage } from './page';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The 文章 operation's `metadata.wechatmp` enrichment rail (2026-09-29, ADR-026).
|
|
7
|
+
*
|
|
8
|
+
* The daily MP stage wires operator preferences into `metadata.wechatmp` (原创声明 + 作者, 合集,
|
|
9
|
+
* AI 创作来源, audio embed) and this module drives the v2 editor's settings panel for them. Every
|
|
10
|
+
* step is commit-verified (a DOM re-read), so a step that reports success but does not stick becomes
|
|
11
|
+
* a loud `warnings` entry — the draft still saves, and the operator sees exactly which setting needs
|
|
12
|
+
* a manual pass in the draft box.
|
|
13
|
+
*
|
|
14
|
+
* What is deliberately NOT here: the cover. The panel has NO file input (discovery §5.2) — the
|
|
15
|
+
* pre-ADR-029 cover step uploaded the file into the editor's *image* input, i.e. into the body. The
|
|
16
|
+
* cover is now the `从正文选择` step over the body images (`./flow`), and `metadata.wechatmp.coverPath`
|
|
17
|
+
* is attached as the first body image there.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DIALOG = '.weui-desktop-dialog:visible';
|
|
21
|
+
const CONFIRM = `${DIALOG} button:has-text("确认"), ${DIALOG} button:has-text("确定")`;
|
|
22
|
+
const ORIGINAL_SWITCH = '.setting-group__switch.js_edit_ori';
|
|
23
|
+
const TAG_ROW = '.allow_click_opr.js_article_tags_label';
|
|
24
|
+
const TAG_INPUT = 'input[placeholder*="合集"]';
|
|
25
|
+
const CLAIM_SOURCE = '#js_claim_source_area .frm_checkbox_label';
|
|
26
|
+
const AUDIO_MENU = 'li.js_insertaudio';
|
|
27
|
+
const AUDIO_DIALOG = '.audio_dialog';
|
|
28
|
+
/** The 素材库 rows stream in after the dialog opens, so the dedupe probe retries while they load. */
|
|
29
|
+
const AUDIO_DIALOG_TIMEOUT_MS = 15_000;
|
|
30
|
+
const AUDIO_ROW_RETRIES = 2;
|
|
31
|
+
const AUDIO_ROW_SETTLE_MS = 1_200;
|
|
32
|
+
/** The 合集 picker's options load after the dialog opens (the pre-ADR-029 settle, kept). */
|
|
33
|
+
const COLLECTION_SETTLE_MS = 1_200;
|
|
34
|
+
/** The settings panel re-renders after a dialog confirm and can briefly detach rows. */
|
|
35
|
+
const STEP_RETRY_SETTLE_MS = 1_000;
|
|
36
|
+
const SWEEP_SETTLE_MS = 400;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set the operator's draft metadata (原创/作者/合集/创作来源) in the v2 editor.
|
|
40
|
+
*
|
|
41
|
+
* 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
|
+
* saves. Dialog mapping evidence (probes 2026-09-29, drafts 100000805/808): 原创 + 赞赏 share ONE
|
|
44
|
+
* dialog (声明类型 radios, 作者 input, 确定) and the 赞赏 switch stays hidden for this account, so
|
|
45
|
+
* 赞赏 is a manual step; the 合集 dialog is a 请选择合集 input + 确认; 创作来源 is an inline checkbox
|
|
46
|
+
* (no dialog). The 原创 dialog only closes when WeChat's own validation passes — the author must be
|
|
47
|
+
* the reward (赏) account picked from its search, which does not resolve headless for this account,
|
|
48
|
+
* so a non-closing dialog is swept and surfaced as a warning.
|
|
49
|
+
*/
|
|
50
|
+
export async function enrichArticle(page: WechatPage, meta: Record<string, unknown>): Promise<string[]> {
|
|
51
|
+
const warnings: string[] = [];
|
|
52
|
+
const str = (key: string) => (typeof meta[key] === 'string' ? (meta[key] as string) : '');
|
|
53
|
+
const on = (key: string) => meta[key] === true;
|
|
54
|
+
// Sweep any leaked dialog so the next step (especially 保存为草稿) is never overlay-blocked.
|
|
55
|
+
const sweep = async () => {
|
|
56
|
+
const cancel = await trySelectors(page, ['button:has-text("取消")']);
|
|
57
|
+
if (cancel.found)
|
|
58
|
+
await page
|
|
59
|
+
.locator(cancel.selector ?? '')
|
|
60
|
+
.first()
|
|
61
|
+
.click({ timeout: 2_000 })
|
|
62
|
+
.catch(() => undefined);
|
|
63
|
+
await page.press?.('Escape');
|
|
64
|
+
await page.waitForTimeout?.(SWEEP_SETTLE_MS);
|
|
65
|
+
};
|
|
66
|
+
// One retry per step: the settings panel re-renders after dialog confirms and can briefly
|
|
67
|
+
// detach rows (observed on the tags row right after the original-declaration confirm).
|
|
68
|
+
const step = async (name: string, fn: () => Promise<void>) => {
|
|
69
|
+
try {
|
|
70
|
+
await fn();
|
|
71
|
+
return;
|
|
72
|
+
} catch {
|
|
73
|
+
// retry once below
|
|
74
|
+
}
|
|
75
|
+
await sweep();
|
|
76
|
+
await page.waitForTimeout?.(STEP_RETRY_SETTLE_MS);
|
|
77
|
+
try {
|
|
78
|
+
await fn();
|
|
79
|
+
} catch (error) {
|
|
80
|
+
warnings.push(`${name}: ${(error instanceof Error ? error.message : String(error)).slice(0, 120)}`);
|
|
81
|
+
await sweep();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (on('original')) {
|
|
86
|
+
await step('原创声明', async () => {
|
|
87
|
+
await clickSelector(page, ORIGINAL_SWITCH, 10_000);
|
|
88
|
+
await clickSelector(page, `${DIALOG} :text("文字原创")`, 8_000);
|
|
89
|
+
const author = str('author');
|
|
90
|
+
if (author !== '') await fillSelector(page, `${DIALOG} input[placeholder*="作者"]`, author);
|
|
91
|
+
await clickSelector(page, CONFIRM, 8_000);
|
|
92
|
+
});
|
|
93
|
+
const committed = await page.evaluate(
|
|
94
|
+
`[...document.querySelectorAll('.weui-desktop-dialog')].every((d) => !d.offsetWidth)`,
|
|
95
|
+
);
|
|
96
|
+
if (committed === false) {
|
|
97
|
+
warnings.push('原创声明: 未生效 - 作者须为赞赏账户(搜索选择),请手动设置');
|
|
98
|
+
await sweep();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const collection = str('collection');
|
|
103
|
+
if (collection !== '') {
|
|
104
|
+
await step('合集', async () => {
|
|
105
|
+
await clickSelector(page, TAG_ROW, 8_000);
|
|
106
|
+
await waitForVisible(page, TAG_INPUT, 8_000);
|
|
107
|
+
await fillSelector(page, TAG_INPUT, collection);
|
|
108
|
+
await page.waitForTimeout?.(COLLECTION_SETTLE_MS);
|
|
109
|
+
await clickSelector(page, `${DIALOG} :text("${collection}")`, 8_000);
|
|
110
|
+
await clickSelector(page, CONFIRM, 8_000);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (on('aiGenerated')) {
|
|
115
|
+
await step('创作来源', async () => {
|
|
116
|
+
await clickSelector(page, CLAIM_SOURCE, 8_000);
|
|
117
|
+
// Probes show no dialog on current builds — the checkbox alone declares AI generation.
|
|
118
|
+
const dialog = await waitForVisible(page, DIALOG, 3_000, { required: false });
|
|
119
|
+
if (dialog !== undefined) {
|
|
120
|
+
await clickSelector(page, `${DIALOG} :text("内容由AI生成")`, 8_000);
|
|
121
|
+
await clickSelector(page, CONFIRM, 8_000);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
const checked = await page.evaluate(`!!document.querySelector('#js_claim_source_area input:checked')`);
|
|
125
|
+
if (checked === false) warnings.push('创作来源: 勾选未生效,请手动设置');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await sweep();
|
|
129
|
+
return warnings;
|
|
130
|
+
}
|
|
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
|
+
}
|