@sovovs/bycli 2.1.53 → 2.1.55
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/cli-manifest.json +47 -47
- package/clis/weixin/_wechat/user-info.js +76 -3
- package/clis/weixin/articles.js +1 -1
- package/clis/weixin/{accounts.js → get-public-account-info.js} +3 -3
- package/clis/weixin/published-articles.js +35 -6
- package/clis/weixin/save-articles.js +1 -1
- package/package.json +1 -1
package/cli-manifest.json
CHANGED
|
@@ -28753,51 +28753,6 @@
|
|
|
28753
28753
|
"sourceFile": "weibo/user-posts.js",
|
|
28754
28754
|
"navigateBefore": "https://weibo.com"
|
|
28755
28755
|
},
|
|
28756
|
-
{
|
|
28757
|
-
"site": "weixin",
|
|
28758
|
-
"name": "accounts",
|
|
28759
|
-
"description": "Search WeChat official accounts and return their fakeids",
|
|
28760
|
-
"access": "read",
|
|
28761
|
-
"domain": "mp.weixin.qq.com",
|
|
28762
|
-
"strategy": "intercept",
|
|
28763
|
-
"browser": "conditional",
|
|
28764
|
-
"args": [
|
|
28765
|
-
{
|
|
28766
|
-
"name": "query",
|
|
28767
|
-
"type": "str",
|
|
28768
|
-
"required": true,
|
|
28769
|
-
"positional": true,
|
|
28770
|
-
"help": "Official-account name to search for"
|
|
28771
|
-
},
|
|
28772
|
-
{
|
|
28773
|
-
"name": "limit",
|
|
28774
|
-
"type": "int",
|
|
28775
|
-
"default": 10,
|
|
28776
|
-
"required": false,
|
|
28777
|
-
"help": "Maximum number of matching accounts to return"
|
|
28778
|
-
},
|
|
28779
|
-
{
|
|
28780
|
-
"name": "auth-source",
|
|
28781
|
-
"type": "str",
|
|
28782
|
-
"default": "browser",
|
|
28783
|
-
"required": false,
|
|
28784
|
-
"help": "Credential source: browser session or environment variables",
|
|
28785
|
-
"choices": [
|
|
28786
|
-
"browser",
|
|
28787
|
-
"env"
|
|
28788
|
-
]
|
|
28789
|
-
}
|
|
28790
|
-
],
|
|
28791
|
-
"columns": [
|
|
28792
|
-
"nickname",
|
|
28793
|
-
"fakeid",
|
|
28794
|
-
"alias"
|
|
28795
|
-
],
|
|
28796
|
-
"type": "js",
|
|
28797
|
-
"modulePath": "weixin/accounts.js",
|
|
28798
|
-
"sourceFile": "weixin/accounts.js",
|
|
28799
|
-
"navigateBefore": true
|
|
28800
|
-
},
|
|
28801
28756
|
{
|
|
28802
28757
|
"site": "weixin",
|
|
28803
28758
|
"name": "article-fetch",
|
|
@@ -28909,7 +28864,7 @@
|
|
|
28909
28864
|
"type": "str",
|
|
28910
28865
|
"required": true,
|
|
28911
28866
|
"positional": true,
|
|
28912
|
-
"help": "Official-account fakeid returned by weixin
|
|
28867
|
+
"help": "Official-account fakeid returned by weixin get-public-account-info"
|
|
28913
28868
|
},
|
|
28914
28869
|
{
|
|
28915
28870
|
"name": "name",
|
|
@@ -29514,6 +29469,51 @@
|
|
|
29514
29469
|
"modulePath": "weixin/freepublish-list.js",
|
|
29515
29470
|
"sourceFile": "weixin/freepublish-list.js"
|
|
29516
29471
|
},
|
|
29472
|
+
{
|
|
29473
|
+
"site": "weixin",
|
|
29474
|
+
"name": "get-public-account-info",
|
|
29475
|
+
"description": "Search WeChat official accounts and return their fakeids",
|
|
29476
|
+
"access": "read",
|
|
29477
|
+
"domain": "mp.weixin.qq.com",
|
|
29478
|
+
"strategy": "intercept",
|
|
29479
|
+
"browser": "conditional",
|
|
29480
|
+
"args": [
|
|
29481
|
+
{
|
|
29482
|
+
"name": "query",
|
|
29483
|
+
"type": "str",
|
|
29484
|
+
"required": true,
|
|
29485
|
+
"positional": true,
|
|
29486
|
+
"help": "Official-account name to search for"
|
|
29487
|
+
},
|
|
29488
|
+
{
|
|
29489
|
+
"name": "limit",
|
|
29490
|
+
"type": "int",
|
|
29491
|
+
"default": 10,
|
|
29492
|
+
"required": false,
|
|
29493
|
+
"help": "Maximum number of matching accounts to return"
|
|
29494
|
+
},
|
|
29495
|
+
{
|
|
29496
|
+
"name": "auth-source",
|
|
29497
|
+
"type": "str",
|
|
29498
|
+
"default": "browser",
|
|
29499
|
+
"required": false,
|
|
29500
|
+
"help": "Credential source: browser session or environment variables",
|
|
29501
|
+
"choices": [
|
|
29502
|
+
"browser",
|
|
29503
|
+
"env"
|
|
29504
|
+
]
|
|
29505
|
+
}
|
|
29506
|
+
],
|
|
29507
|
+
"columns": [
|
|
29508
|
+
"nickname",
|
|
29509
|
+
"fakeid",
|
|
29510
|
+
"alias"
|
|
29511
|
+
],
|
|
29512
|
+
"type": "js",
|
|
29513
|
+
"modulePath": "weixin/get-public-account-info.js",
|
|
29514
|
+
"sourceFile": "weixin/get-public-account-info.js",
|
|
29515
|
+
"navigateBefore": true
|
|
29516
|
+
},
|
|
29517
29517
|
{
|
|
29518
29518
|
"site": "weixin",
|
|
29519
29519
|
"name": "home-overview",
|
|
@@ -29765,7 +29765,7 @@
|
|
|
29765
29765
|
"type": "str",
|
|
29766
29766
|
"required": true,
|
|
29767
29767
|
"positional": true,
|
|
29768
|
-
"help": "Official-account fakeid returned by weixin
|
|
29768
|
+
"help": "Official-account fakeid returned by weixin get-public-account-info"
|
|
29769
29769
|
},
|
|
29770
29770
|
{
|
|
29771
29771
|
"name": "name",
|
|
@@ -204,6 +204,12 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
204
204
|
}
|
|
205
205
|
return cleanValueText(node);
|
|
206
206
|
};
|
|
207
|
+
const textWithout = (node, selector) => {
|
|
208
|
+
if (!node) return null;
|
|
209
|
+
const copy = node.cloneNode(true);
|
|
210
|
+
copy.querySelectorAll(selector).forEach(item => item.remove());
|
|
211
|
+
return compact(copy.textContent);
|
|
212
|
+
};
|
|
207
213
|
const switchValue = row => {
|
|
208
214
|
const node = firstVisible(row, [
|
|
209
215
|
'input[type="checkbox"]',
|
|
@@ -263,6 +269,45 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
263
269
|
const label = compact(heading && heading.textContent)
|
|
264
270
|
|| compact(sectionNode.getAttribute('aria-label'))
|
|
265
271
|
|| '其他信息';
|
|
272
|
+
const authorizationTable = Array.from(sectionNode.querySelectorAll('table'))
|
|
273
|
+
.filter(visible)
|
|
274
|
+
.find(table => {
|
|
275
|
+
const headers = Array.from(table.querySelectorAll('thead th, tr > th'))
|
|
276
|
+
.filter(visible)
|
|
277
|
+
.map(node => compact(node.textContent));
|
|
278
|
+
return headers.includes('第三方平台名称')
|
|
279
|
+
&& headers.includes('已授权权限')
|
|
280
|
+
&& headers.includes('授权时间');
|
|
281
|
+
}) || null;
|
|
282
|
+
const records = authorizationTable === null ? null : Array.from(
|
|
283
|
+
authorizationTable.querySelectorAll('tbody > tr, tr'),
|
|
284
|
+
).filter(visible).flatMap(row => {
|
|
285
|
+
const cells = Array.from(row.querySelectorAll(':scope > td')).filter(visible);
|
|
286
|
+
if (cells.length < 3 || row.querySelector('.empty_tips')) return [];
|
|
287
|
+
const nameNode = firstVisible(cells[0], [
|
|
288
|
+
'.plugin_info h4',
|
|
289
|
+
'.plugin_info .name',
|
|
290
|
+
'h4',
|
|
291
|
+
]);
|
|
292
|
+
const name = compact(nameNode && nameNode.textContent);
|
|
293
|
+
if (!name) return [];
|
|
294
|
+
const descriptionNode = firstVisible(cells[0], [
|
|
295
|
+
'.plugin_info .desc',
|
|
296
|
+
'.plugin_info p',
|
|
297
|
+
]);
|
|
298
|
+
const permissions = Array.from(cells[1].querySelectorAll('.privilege'))
|
|
299
|
+
.filter(visible)
|
|
300
|
+
.flatMap(node => {
|
|
301
|
+
const permission = textWithout(node, '.dot');
|
|
302
|
+
return permission ? [permission] : [];
|
|
303
|
+
});
|
|
304
|
+
return [{
|
|
305
|
+
name,
|
|
306
|
+
description: compact(descriptionNode && descriptionNode.textContent),
|
|
307
|
+
permissions,
|
|
308
|
+
authorized_at: cleanValueText(cells[2]),
|
|
309
|
+
}];
|
|
310
|
+
});
|
|
266
311
|
let rows = Array.from(sectionNode.querySelectorAll(rowSelector)).filter(visible);
|
|
267
312
|
rows = rows.filter(node => !rows.some(other => other !== node && other.contains(node)));
|
|
268
313
|
const fields = [];
|
|
@@ -339,7 +384,10 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
339
384
|
}
|
|
340
385
|
}
|
|
341
386
|
|
|
342
|
-
if (fields.length > 0
|
|
387
|
+
if (fields.length > 0 || (records && records.length > 0)) {
|
|
388
|
+
return [{ label, fields, ...(records === null ? {} : { records }) }];
|
|
389
|
+
}
|
|
390
|
+
if (records !== null) return [{ label, fields, records, empty: true }];
|
|
343
391
|
const empty = Boolean(sectionNode.querySelector('table thead th, table tr > th'));
|
|
344
392
|
return empty ? [{ label, fields, empty: true }] : [];
|
|
345
393
|
});
|
|
@@ -361,6 +409,25 @@ function normalizeField(field, tabLabel, sectionIndex, fieldIndex) {
|
|
|
361
409
|
};
|
|
362
410
|
}
|
|
363
411
|
|
|
412
|
+
function normalizeAuthorizationRecord(record, tabLabel, sectionIndex, recordIndex) {
|
|
413
|
+
const prefix = `WeChat ${tabLabel} returned an invalid authorization record at section ${sectionIndex} index ${recordIndex}`;
|
|
414
|
+
execution(object(record), prefix);
|
|
415
|
+
const name = text(record.name);
|
|
416
|
+
const authorizedAt = text(record.authorized_at);
|
|
417
|
+
execution(name && authorizedAt && Array.isArray(record.permissions), prefix);
|
|
418
|
+
const permissions = record.permissions.map((permission, permissionIndex) => {
|
|
419
|
+
const normalized = text(permission);
|
|
420
|
+
execution(normalized, `${prefix} permission ${permissionIndex}`);
|
|
421
|
+
return normalized;
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
name,
|
|
425
|
+
description: text(record.description),
|
|
426
|
+
permissions,
|
|
427
|
+
authorized_at: authorizedAt,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
364
431
|
export function normalizeUserInfoTab(tabId, payload) {
|
|
365
432
|
const definition = TAB_DEFINITIONS.find(tab => tab.id === tabId);
|
|
366
433
|
execution(definition, `WeChat account settings returned an unknown tab: ${String(tabId)}`);
|
|
@@ -381,8 +448,14 @@ export function normalizeUserInfoTab(tabId, payload) {
|
|
|
381
448
|
const fields = rawFields.map((field, fieldIndex) => (
|
|
382
449
|
normalizeField(field, definition.label, sectionIndex, fieldIndex)
|
|
383
450
|
));
|
|
384
|
-
|
|
385
|
-
|
|
451
|
+
const hasRecords = Object.prototype.hasOwnProperty.call(section, 'records');
|
|
452
|
+
const rawRecords = hasRecords ? section.records : [];
|
|
453
|
+
execution(Array.isArray(rawRecords), prefix);
|
|
454
|
+
const records = rawRecords.map((record, recordIndex) => (
|
|
455
|
+
normalizeAuthorizationRecord(record, definition.label, sectionIndex, recordIndex)
|
|
456
|
+
));
|
|
457
|
+
return fields.length > 0 || records.length > 0 || section.empty === true
|
|
458
|
+
? [{ label, fields, ...(hasRecords ? { records } : {}) }]
|
|
386
459
|
: [];
|
|
387
460
|
});
|
|
388
461
|
|
package/clis/weixin/articles.js
CHANGED
|
@@ -19,7 +19,7 @@ export const articlesCommand = cli({
|
|
|
19
19
|
description: 'List published articles from a WeChat official account',
|
|
20
20
|
strategy: Strategy.COOKIE, browser: browserRequired,
|
|
21
21
|
args: [
|
|
22
|
-
{ name: 'fakeid', positional: true, required: true, help: 'Official-account fakeid returned by weixin
|
|
22
|
+
{ name: 'fakeid', positional: true, required: true, help: 'Official-account fakeid returned by weixin get-public-account-info' },
|
|
23
23
|
{ name: 'name', help: 'Official-account name; exact case-insensitive match required for browser Sogou fallback' }, { name: 'limit', type: 'int', help: 'Maximum number of articles to return' }, { name: 'max-pages', type: 'int', help: 'Maximum number of history pages to scan' },
|
|
24
24
|
{ name: 'auth-source', default: 'browser', choices: ['browser', 'env'], help: 'Credential source: browser session or environment variables' },
|
|
25
25
|
],
|
|
@@ -8,8 +8,8 @@ import { readAuthSource } from './_wechat/args.js';
|
|
|
8
8
|
const DOMAIN = 'mp.weixin.qq.com';
|
|
9
9
|
const browserRequired = args => readAuthSource(args) === 'browser';
|
|
10
10
|
|
|
11
|
-
export const
|
|
12
|
-
site: 'weixin', name: '
|
|
11
|
+
export const getPublicAccountInfoCommand = cli({
|
|
12
|
+
site: 'weixin', name: 'get-public-account-info', access: 'read', domain: DOMAIN,
|
|
13
13
|
description: 'Search WeChat official accounts and return their fakeids',
|
|
14
14
|
strategy: Strategy.INTERCEPT, browser: browserRequired,
|
|
15
15
|
args: [
|
|
@@ -32,7 +32,7 @@ export const accountsCommand = cli({
|
|
|
32
32
|
credentials = { ...credentials, fingerprint: await captureSearchBizFingerprint(page, query) };
|
|
33
33
|
}
|
|
34
34
|
const rows = await executeSearchBiz({ page, source: authSource, credentials, query, limit });
|
|
35
|
-
if (rows.length === 0) throw new EmptyResultError('weixin
|
|
35
|
+
if (rows.length === 0) throw new EmptyResultError('weixin get-public-account-info', `No official accounts matched "${query}".`);
|
|
36
36
|
return rows.map(row => ({ nickname: row.nickname, fakeid: row.fakeid, alias: row.alias || null }));
|
|
37
37
|
},
|
|
38
38
|
});
|
|
@@ -17,6 +17,12 @@ function sourceValue(value) {
|
|
|
17
17
|
return source;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function limitValue(value) {
|
|
21
|
+
const limit = value ?? 20;
|
|
22
|
+
if (!Number.isSafeInteger(limit) || limit < 1) throw new ArgumentError('limit must be a positive safe integer');
|
|
23
|
+
return limit;
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
export function projectOfficialRows(rows, fallbackReason = null) {
|
|
21
27
|
return rows.map(row => ({
|
|
22
28
|
article_id: row.article_id, title: row.title, author: row.author, digest: row.digest,
|
|
@@ -39,6 +45,29 @@ async function browserRows(page, args, fallbackReason) {
|
|
|
39
45
|
}));
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
async function officialRows(args) {
|
|
49
|
+
const limit = limitValue(args.limit);
|
|
50
|
+
const rows = [];
|
|
51
|
+
let offset = 0;
|
|
52
|
+
while (rows.length < limit) {
|
|
53
|
+
const remaining = limit - rows.length;
|
|
54
|
+
const count = remaining > 20 ? 20 : remaining;
|
|
55
|
+
let pageRows;
|
|
56
|
+
try {
|
|
57
|
+
pageRows = await freepublishListCommand.func({
|
|
58
|
+
...args, offset, count, content: args.content ?? 'none',
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof EmptyResultError && rows.length > 0) break;
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
rows.push(...pageRows);
|
|
65
|
+
offset += count;
|
|
66
|
+
if (pageRows.length < count) break;
|
|
67
|
+
}
|
|
68
|
+
return rows.slice(0, limit);
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
export const publishedArticlesCommand = cli({
|
|
43
72
|
site: 'weixin', name: 'published-articles', access: 'read', domain: 'mp.weixin.qq.com',
|
|
44
73
|
description: 'List published Weixin articles with optional official-API-to-browser fallback',
|
|
@@ -56,17 +85,17 @@ export const publishedArticlesCommand = cli({
|
|
|
56
85
|
columns: FACADE_COLUMNS,
|
|
57
86
|
func: async (page, args) => {
|
|
58
87
|
const source = sourceValue(args.source);
|
|
59
|
-
|
|
88
|
+
const limit = limitValue(args.limit);
|
|
89
|
+
const normalizedArgs = { ...args, limit };
|
|
90
|
+
if (source === 'browser') return browserRows(page, normalizedArgs, null);
|
|
60
91
|
const credentials = readOfficialApiCredentials(args);
|
|
61
|
-
if (source === 'auto' && !credentials.configured) return browserRows(page,
|
|
92
|
+
if (source === 'auto' && !credentials.configured) return browserRows(page, normalizedArgs, 'api-not-configured');
|
|
62
93
|
try {
|
|
63
|
-
const rows = await
|
|
64
|
-
...args, offset: 0, count: Math.min(args.limit ?? 20, 20), content: args.content ?? 'none',
|
|
65
|
-
});
|
|
94
|
+
const rows = await officialRows(normalizedArgs);
|
|
66
95
|
return projectOfficialRows(rows);
|
|
67
96
|
} catch (error) {
|
|
68
97
|
if (source === 'auto' && isFreepublishFallbackEligible(error)) {
|
|
69
|
-
return browserRows(page,
|
|
98
|
+
return browserRows(page, normalizedArgs, 'api-not-authorized');
|
|
70
99
|
}
|
|
71
100
|
if (error instanceof EmptyResultError) throw error;
|
|
72
101
|
throw error;
|
|
@@ -164,7 +164,7 @@ export const saveArticlesCommand = cli({
|
|
|
164
164
|
description: 'Download WeChat official-account articles as Markdown files',
|
|
165
165
|
strategy: Strategy.COOKIE, browser: browserRequired,
|
|
166
166
|
args: [
|
|
167
|
-
{ name: 'fakeid', positional: true, required: true, help: 'Official-account fakeid returned by weixin
|
|
167
|
+
{ name: 'fakeid', positional: true, required: true, help: 'Official-account fakeid returned by weixin get-public-account-info' }, { name: 'name', help: 'Official-account name; exact case-insensitive match required for browser Sogou fallback' },
|
|
168
168
|
{ name: 'output', default: './weixin-articles', help: 'Directory for saved Markdown files' }, { name: 'limit', type: 'int', help: 'Maximum number of articles to save' },
|
|
169
169
|
{ name: 'max-pages', type: 'int', help: 'Maximum number of history pages to scan' }, { name: 'auth-source', default: 'browser', choices: ['browser', 'env'], help: 'Credential source: browser session or environment variables' },
|
|
170
170
|
],
|