@gobing-ai/knowledge-kit 0.0.25 → 0.0.26
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/package.json +1 -1
- package/plugins/generations/gemini-tts/dist/index.js +3 -3
- package/plugins/generations/news-report-gen/src/index.ts +5 -5
- package/plugins/generations/voice-gen/dist/index.js +3 -3
- package/plugins/generations/voice-gen/src/mp3.ts +4 -3
- package/plugins/generations/voice-gen/src/voicescript.ts +5 -2
- package/plugins/kk/commands/workflow-run.md +4 -4
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/kk-workflow-stages.ts +146 -99
- package/plugins/kk/scripts/validate-voicescript.ts +5 -2
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +73 -39
- package/plugins/publishings/wechatmp-pub/dist/index.js +8449 -307
- package/plugins/publishings/wechatmp-pub/package.json +1 -0
- package/plugins/publishings/wechatmp-pub/plugin.json +1 -1
- package/plugins/publishings/wechatmp-pub/src/index.ts +212 -167
- package/plugins/publishings/wechatmp-pub/src/profile-check.ts +34 -0
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
"kind": "publish",
|
|
4
4
|
"entry": "./dist/index.js",
|
|
5
5
|
"version": "1.0.0",
|
|
6
|
-
"description": "Publish plugin for WeChat
|
|
6
|
+
"description": "Publish plugin for the WeChat MP console via ts-browser-automation: draft-box first (ADR-022), headed publish behind a session preflight"
|
|
7
7
|
}
|
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
import { dirname } from 'node:path';
|
|
2
1
|
import { parseArgs } from 'node:util';
|
|
3
2
|
import { type Content, ContentSchema, type Result, ResultSchema } from '@gobing-ai/kk-core';
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
FakeFileCliTransport,
|
|
5
|
+
type PublishTransport,
|
|
6
|
+
type PublishTransportPayload,
|
|
7
|
+
resolveBrowserProfileDir,
|
|
8
|
+
} from '@gobing-ai/publish-harness';
|
|
9
|
+
import { BrowserProfileMissingError, withProfilePage } from '@gobing-ai/ts-browser-automation';
|
|
5
10
|
import { createNodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
6
11
|
import { echoError } from '@gobing-ai/ts-utils';
|
|
12
|
+
import type { Page } from 'playwright';
|
|
7
13
|
|
|
8
14
|
const TARGET = 'wechatmp';
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
/** Per-request timeout (ms) for WeChat HTTP calls — Bun's `fetch` has no default deadline (R5). */
|
|
12
|
-
const REQUEST_TIMEOUT_MS = 120_000;
|
|
13
|
-
const DRAFT_ADD_URL = 'https://api.weixin.qq.com/cgi-bin/draft/add';
|
|
14
|
-
const APP_ID_ENV = 'WECHAT_APP_ID';
|
|
15
|
-
const APP_SECRET_ENV = 'WECHAT_APP_SECRET';
|
|
16
|
-
|
|
17
|
-
export interface WechatDraftArticle {
|
|
18
|
-
article_type: 'news';
|
|
19
|
-
title: string;
|
|
20
|
-
author?: string;
|
|
21
|
-
digest?: string;
|
|
22
|
-
content: string;
|
|
23
|
-
content_source_url?: string;
|
|
24
|
-
thumb_media_id: string;
|
|
25
|
-
need_open_comment?: number;
|
|
26
|
-
only_fans_can_comment?: number;
|
|
27
|
-
}
|
|
15
|
+
const CONSOLE_URL = 'https://mp.weixin.qq.com/';
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
/** Proven console selectors (ported from the wt-publish-to-wechatmp Playwright flow). */
|
|
18
|
+
const SEL = {
|
|
19
|
+
// Console home: an authenticated session lands on /cgi-bin/home; logged-out shows the QR scan page.
|
|
20
|
+
loggedInFragment: '/cgi-bin/home',
|
|
21
|
+
menuItem: '.new-creation__menu .new-creation__menu-item',
|
|
22
|
+
titleInput: '#title',
|
|
23
|
+
authorInput: '#author',
|
|
24
|
+
editor: '.ProseMirror',
|
|
25
|
+
summaryInput: '#js_description',
|
|
26
|
+
saveDraftBtn: '.js_save_draft, button:has-text("保存为草稿"), button:has-text("保存草稿")',
|
|
27
|
+
publishBtn: '.js_publish, button:has-text("发表"), button:has-text("发布")',
|
|
28
|
+
confirmBtn: '.weui-desktop-dialog .weui-desktop-btn_primary, button:has-text("确定")',
|
|
29
|
+
toast: '.weui-desktop-toast',
|
|
30
|
+
} as const;
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const CONSOLE_READY_TIMEOUT_MS = 30_000;
|
|
33
|
+
const TOAST_TIMEOUT_MS = 15_000;
|
|
34
|
+
|
|
35
|
+
function fail(message: string): never {
|
|
36
|
+
throw new Error(message);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function escapeHtml(text: string): string {
|
|
@@ -121,182 +123,225 @@ export function markdownToWechatHtml(markdown: string): string {
|
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
function resolveTitle(content: Content): string {
|
|
124
|
-
if (content.title && content.title.trim()
|
|
125
|
-
|
|
126
|
-
return match?.[1] ? match[1].trim() : 'Untitled';
|
|
126
|
+
if (typeof content.title === 'string' && content.title.trim() !== '') return content.title.trim();
|
|
127
|
+
return fail('wechatmp-pub: content.title is required');
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
function resolveDigest(content: Content): string {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
.
|
|
136
|
-
|
|
137
|
-
.find((p) => p.length > 0);
|
|
138
|
-
return (para ?? '').slice(0, 120);
|
|
130
|
+
function resolveDigest(content: Content): string | undefined {
|
|
131
|
+
const body = typeof content.body === 'string' ? content.body : '';
|
|
132
|
+
if (body.trim() === '') return undefined;
|
|
133
|
+
const first = body
|
|
134
|
+
.split('\n')
|
|
135
|
+
.map((line) => line.trim())
|
|
136
|
+
.find((line) => line !== '');
|
|
137
|
+
return first ? first.slice(0, 54) : undefined;
|
|
139
138
|
}
|
|
140
139
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Browser flow (ts-browser-automation; pattern mirrors xhs-pub)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
export interface WechatPage {
|
|
145
|
+
goto(url: string): Promise<void>;
|
|
146
|
+
url(): string;
|
|
147
|
+
fill(selector: string, value: string): Promise<void>;
|
|
148
|
+
click(selector: string): Promise<void>;
|
|
149
|
+
setInnerHtml(selector: string, html: string): Promise<void>;
|
|
150
|
+
waitFor(selector: string, timeoutMs: number): Promise<void>;
|
|
151
|
+
waitForAny(selectors: readonly string[], timeoutMs: number): Promise<string>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isWechatLoggedIn(page: Page): boolean {
|
|
155
|
+
return page.url().includes(SEL.loggedInFragment);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type SessionCheck = (profileDir: string) => Promise<void>;
|
|
159
|
+
|
|
160
|
+
/** Headless session preflight: throws (incl. BrowserProfileMissingError) when the profile cannot drive the console. */
|
|
161
|
+
export async function checkWechatSession(profileDir: string): Promise<void> {
|
|
162
|
+
await withProfilePage(
|
|
163
|
+
{
|
|
164
|
+
profileDir,
|
|
165
|
+
headless: true,
|
|
166
|
+
url: CONSOLE_URL,
|
|
167
|
+
isAuthenticated: isWechatLoggedIn,
|
|
168
|
+
timeoutMs: CONSOLE_READY_TIMEOUT_MS,
|
|
169
|
+
},
|
|
170
|
+
async () => {},
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface WechatmpTransportOptions {
|
|
175
|
+
openPage?: (profileDir: string) => Promise<WechatPage>;
|
|
176
|
+
checkSession?: SessionCheck;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function wrapPlaywrightPage(page: Page): WechatPage {
|
|
154
180
|
return {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
181
|
+
goto: (url) => page.goto(url, { waitUntil: 'domcontentloaded' }).then(() => undefined),
|
|
182
|
+
url: () => page.url(),
|
|
183
|
+
fill: (selector, value) => page.locator(selector).first().fill(value),
|
|
184
|
+
click: (selector) => page.locator(selector).first().click(),
|
|
185
|
+
setInnerHtml: (selector, html) =>
|
|
186
|
+
page
|
|
187
|
+
.locator(selector)
|
|
188
|
+
.first()
|
|
189
|
+
.evaluate((node, value) => {
|
|
190
|
+
node.innerHTML = value;
|
|
191
|
+
node.dispatchEvent(new Event('input', { bubbles: true }));
|
|
192
|
+
}, html),
|
|
193
|
+
waitFor: (selector, timeoutMs) =>
|
|
194
|
+
page
|
|
195
|
+
.locator(selector)
|
|
196
|
+
.first()
|
|
197
|
+
.waitFor({ state: 'visible', timeout: timeoutMs })
|
|
198
|
+
.then(() => undefined),
|
|
199
|
+
waitForAny: async (selectors, timeoutMs) => {
|
|
200
|
+
const deadline = Date.now() + timeoutMs;
|
|
201
|
+
while (Date.now() < deadline) {
|
|
202
|
+
for (const selector of selectors) {
|
|
203
|
+
try {
|
|
204
|
+
await page.locator(selector).first().waitFor({ state: 'visible', timeout: 250 });
|
|
205
|
+
return selector;
|
|
206
|
+
} catch {
|
|
207
|
+
// try the next candidate
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return '';
|
|
212
|
+
},
|
|
166
213
|
};
|
|
167
214
|
}
|
|
168
215
|
|
|
169
|
-
function
|
|
170
|
-
|
|
171
|
-
|
|
216
|
+
async function defaultOpenPage(profileDir: string): Promise<WechatPage> {
|
|
217
|
+
return withProfilePage({ profileDir, headless: false, url: CONSOLE_URL }, async (handle) =>
|
|
218
|
+
wrapPlaywrightPage(handle.page),
|
|
219
|
+
);
|
|
172
220
|
}
|
|
173
221
|
|
|
174
|
-
export class
|
|
175
|
-
|
|
222
|
+
export class WechatmpBrowserTransport implements PublishTransport {
|
|
223
|
+
readonly kind = 'browser';
|
|
224
|
+
private readonly openPage: (profileDir: string) => Promise<WechatPage>;
|
|
225
|
+
private readonly checkSession: SessionCheck;
|
|
176
226
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
'WeChat live publish via freepublish/submit is certification-gated; default operation is draft. Confirm with the operator before a live/console path.',
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
if (operation !== 'draft') {
|
|
185
|
-
return fail(`invalid options.operation "${operation}": expected draft (default) or live`);
|
|
186
|
-
}
|
|
227
|
+
constructor(options: WechatmpTransportOptions = {}) {
|
|
228
|
+
this.openPage = options.openPage ?? defaultOpenPage;
|
|
229
|
+
this.checkSession = options.checkSession ?? ((profileDir) => checkWechatSession(profileDir));
|
|
230
|
+
}
|
|
187
231
|
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
async publish(payload: PublishTransportPayload): Promise<Result> {
|
|
233
|
+
const { content, options } = payload;
|
|
234
|
+
const want =
|
|
235
|
+
options?.operation === 'live'
|
|
236
|
+
? 'public'
|
|
237
|
+
: ((content.metadata?.publishStatus as string | undefined) ?? 'draft');
|
|
238
|
+
if (want !== 'draft' && want !== 'public') {
|
|
239
|
+
return this.failed(`publishStatus "${want}" is not draft|public`);
|
|
240
|
+
}
|
|
241
|
+
const title = resolveTitle(content);
|
|
242
|
+
if (typeof content.body !== 'string' || content.body.trim() === '') {
|
|
243
|
+
return this.failed('wechatmp-pub: content.body is required');
|
|
197
244
|
}
|
|
245
|
+
const profileDir = resolveBrowserProfileDir(TARGET, process.env.WECHATMP_PROFILE_DIR);
|
|
198
246
|
|
|
199
|
-
let articles: WechatDraftArticles;
|
|
200
247
|
try {
|
|
201
|
-
|
|
248
|
+
await this.checkSession(profileDir);
|
|
202
249
|
} catch (error: unknown) {
|
|
203
|
-
|
|
250
|
+
const why =
|
|
251
|
+
error instanceof BrowserProfileMissingError
|
|
252
|
+
? 'no saved console profile — log in once via `bun src/profile-check.ts login`'
|
|
253
|
+
: `console session preflight failed (${error instanceof Error ? error.message : String(error)})`;
|
|
254
|
+
// Non-blocking contract (mirrors xhs-pub): a sessionless run refuses fast and stays draftless —
|
|
255
|
+
// it must never open a headed login wall inside an unattended run.
|
|
256
|
+
return this.failed(`wechatmp-pub: ${why}`);
|
|
204
257
|
}
|
|
205
258
|
|
|
259
|
+
const page = await this.openPage(profileDir);
|
|
206
260
|
try {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
261
|
+
await page.waitFor(SEL.menuItem, CONSOLE_READY_TIMEOUT_MS);
|
|
262
|
+
await page.click(`${SEL.menuItem}:has-text("图文"), ${SEL.menuItem}`);
|
|
263
|
+
await page.waitFor(SEL.titleInput, CONSOLE_READY_TIMEOUT_MS);
|
|
264
|
+
await page.fill(SEL.titleInput, title);
|
|
265
|
+
await this.tryFill(page, SEL.authorInput, String(content.metadata?.author ?? ''));
|
|
266
|
+
await page.waitFor(SEL.editor, CONSOLE_READY_TIMEOUT_MS);
|
|
267
|
+
await page.setInnerHtml(SEL.editor, markdownToWechatHtml(content.body));
|
|
268
|
+
const digest = resolveDigest(content);
|
|
269
|
+
if (digest) await this.tryFill(page, SEL.summaryInput, digest);
|
|
270
|
+
|
|
271
|
+
if (want === 'draft') {
|
|
272
|
+
await page.click(SEL.saveDraftBtn);
|
|
273
|
+
} else {
|
|
274
|
+
await page.click(SEL.publishBtn);
|
|
275
|
+
await this.tryClick(page, SEL.confirmBtn);
|
|
218
276
|
}
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
headers: { 'Content-Type': 'application/json' },
|
|
224
|
-
body: JSON.stringify(articles),
|
|
225
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
const draftJson = (await draftResponse.json()) as {
|
|
229
|
-
media_id?: unknown;
|
|
230
|
-
errmsg?: unknown;
|
|
231
|
-
errcode?: unknown;
|
|
232
|
-
};
|
|
233
|
-
if (typeof draftJson.media_id !== 'string') {
|
|
234
|
-
return fail(
|
|
235
|
-
`WeChat draft/add failed: ${typeof draftJson.errmsg === 'string' ? draftJson.errmsg : 'missing media_id'}`,
|
|
277
|
+
const seen = await page.waitForAny([SEL.toast], TOAST_TIMEOUT_MS);
|
|
278
|
+
if (seen === '') {
|
|
279
|
+
return this.failed(
|
|
280
|
+
`save was clicked but no confirmation toast appeared within ${TOAST_TIMEOUT_MS}ms — verify the console manually`,
|
|
236
281
|
);
|
|
237
282
|
}
|
|
238
|
-
return
|
|
283
|
+
return {
|
|
239
284
|
ok: true,
|
|
240
285
|
target: TARGET,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
286
|
+
metadata: {
|
|
287
|
+
publishStatus: want,
|
|
288
|
+
note:
|
|
289
|
+
want === 'draft'
|
|
290
|
+
? 'saved to the WeChat MP draft box (草稿箱)'
|
|
291
|
+
: 'published via the console 发表 flow',
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
} catch (error: unknown) {
|
|
295
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
296
|
+
return this.failed(`console flow failed: ${why}`);
|
|
247
297
|
}
|
|
248
298
|
}
|
|
249
|
-
}
|
|
250
299
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
300
|
+
private async tryFill(page: WechatPage, selector: string, value: string): Promise<void> {
|
|
301
|
+
if (value === '') return;
|
|
302
|
+
try {
|
|
303
|
+
await page.fill(selector, value);
|
|
304
|
+
} catch {
|
|
305
|
+
// optional field — the console hides it for some account types
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private async tryClick(page: WechatPage, selector: string): Promise<void> {
|
|
310
|
+
try {
|
|
311
|
+
await page.click(selector);
|
|
312
|
+
} catch {
|
|
313
|
+
// confirm dialogs are not shown for every account type
|
|
314
|
+
}
|
|
254
315
|
}
|
|
255
|
-
return new WechatDraftApiTransport();
|
|
256
|
-
}
|
|
257
316
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (outDir && outDir !== '.') {
|
|
261
|
-
await createNodeFileSystem().ensureDir(outDir);
|
|
317
|
+
private failed(error: string): Result {
|
|
318
|
+
return { ok: false, target: TARGET, error, metadata: { publishStatus: 'draft' } };
|
|
262
319
|
}
|
|
263
|
-
await createNodeFileSystem().writeFile(outputPath, JSON.stringify(result, null, 2));
|
|
264
320
|
}
|
|
265
321
|
|
|
266
|
-
export
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
transport: PublishTransport = getPublishTransport(),
|
|
270
|
-
): Promise<void> {
|
|
271
|
-
await createNodeFileSystem().deleteFile(outputPath);
|
|
272
|
-
let content: Content;
|
|
273
|
-
let options: Record<string, unknown> | undefined;
|
|
274
|
-
try {
|
|
275
|
-
const raw = JSON.parse(await createNodeFileSystem().readFile(inputPath)) as Record<string, unknown>;
|
|
276
|
-
content = ContentSchema.parse(raw);
|
|
277
|
-
if (raw.options !== undefined && typeof raw.options === 'object' && raw.options !== null) {
|
|
278
|
-
options = raw.options as Record<string, unknown>;
|
|
279
|
-
}
|
|
280
|
-
} catch (error: unknown) {
|
|
281
|
-
const message = `Invalid Content input: ${error instanceof Error ? error.message : String(error)}`;
|
|
282
|
-
await writePublishResult(outputPath, fail(message));
|
|
283
|
-
throw new Error(message);
|
|
284
|
-
}
|
|
285
|
-
const result = ResultSchema.parse(await transport.publish({ content, options }));
|
|
286
|
-
await writePublishResult(outputPath, result);
|
|
287
|
-
if (!result.ok) throw new Error(result.error ?? 'Publish failed');
|
|
322
|
+
export function getPublishTransport(): PublishTransport {
|
|
323
|
+
if (process.env.KNOWLEDGE_KIT_PUBLISH_TRANSPORT === 'fake') return new FakeFileCliTransport();
|
|
324
|
+
return new WechatmpBrowserTransport();
|
|
288
325
|
}
|
|
289
326
|
|
|
290
|
-
export async function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
export async function processPublishIO(inPath: string, outPath: string, transport?: PublishTransport): Promise<void> {
|
|
328
|
+
const fs = createNodeFileSystem();
|
|
329
|
+
const raw = JSON.parse(await fs.readFile(inPath));
|
|
330
|
+
const content = ContentSchema.parse(raw);
|
|
331
|
+
const result = await (transport ?? getPublishTransport()).publish({ content, options: { target: TARGET } });
|
|
332
|
+
await fs.writeFile(outPath, JSON.stringify(ResultSchema.parse(result), null, 2));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
|
|
336
|
+
const { values } = parseArgs({
|
|
337
|
+
argv,
|
|
338
|
+
options: {
|
|
339
|
+
in: { type: 'string' },
|
|
340
|
+
out: { type: 'string' },
|
|
341
|
+
},
|
|
342
|
+
});
|
|
298
343
|
if (!values.in || !values.out) {
|
|
299
|
-
echoError('Missing required arguments: --in
|
|
344
|
+
echoError('Missing required arguments: --in <content.json> --out <result.json>');
|
|
300
345
|
return 1;
|
|
301
346
|
}
|
|
302
347
|
try {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolveBrowserProfileDir } from '@gobing-ai/publish-harness';
|
|
2
|
+
import { loginWithProfile } from '@gobing-ai/ts-browser-automation';
|
|
3
|
+
import { echo } from '@gobing-ai/ts-utils';
|
|
4
|
+
import { checkWechatSession } from './index';
|
|
5
|
+
|
|
6
|
+
const CONSOLE_URL = 'https://mp.weixin.qq.com/';
|
|
7
|
+
const LOGIN_TIMEOUT_MS = 180_000;
|
|
8
|
+
|
|
9
|
+
const profileDir = resolveBrowserProfileDir('wechatmp', process.env.WECHATMP_PROFILE_DIR);
|
|
10
|
+
const verb = process.argv[2] ?? 'check';
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
if (verb === 'login') {
|
|
14
|
+
echo(
|
|
15
|
+
`wechatmp login: opening the console in a headed browser — scan the QR once (timeout ${LOGIN_TIMEOUT_MS / 1000}s)`,
|
|
16
|
+
);
|
|
17
|
+
await loginWithProfile({
|
|
18
|
+
profileDir,
|
|
19
|
+
url: CONSOLE_URL,
|
|
20
|
+
isAuthenticated: (page) => page.url().includes('/cgi-bin/home'),
|
|
21
|
+
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
22
|
+
});
|
|
23
|
+
echo(`wechatmp login: session saved in ${profileDir}`);
|
|
24
|
+
} else if (verb === 'check') {
|
|
25
|
+
await checkWechatSession(profileDir);
|
|
26
|
+
echo(`wechatmp session: OK (${profileDir})`);
|
|
27
|
+
} else {
|
|
28
|
+
echo('usage: bun src/profile-check.ts login|check');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
} catch (error: unknown) {
|
|
32
|
+
echo(`wechatmp ${verb}: failed — ${error instanceof Error ? error.message : String(error)}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|