@sovovs/bycli 2.1.31 → 2.1.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/cli-manifest.json CHANGED
@@ -28485,6 +28485,42 @@
28485
28485
  "sourceFile": "weixin/drafts.js",
28486
28486
  "navigateBefore": false
28487
28487
  },
28488
+ {
28489
+ "site": "weixin",
28490
+ "name": "home-overview",
28491
+ "aliases": [
28492
+ "overview",
28493
+ "dashboard",
28494
+ "fans"
28495
+ ],
28496
+ "description": "读取公众号首页概览:原创内容、总用户数(粉丝数)、昨日阅读/分享/新增关注人数及阅读增长百分比;账号级昨日汇总,单篇文章数据请用 published",
28497
+ "access": "read",
28498
+ "domain": "mp.weixin.qq.com",
28499
+ "strategy": "cookie",
28500
+ "browser": true,
28501
+ "args": [
28502
+ {
28503
+ "name": "settle",
28504
+ "type": "int",
28505
+ "default": 3,
28506
+ "required": false,
28507
+ "help": "打开首页后等待渲染的秒数"
28508
+ }
28509
+ ],
28510
+ "columns": [
28511
+ "original_count",
28512
+ "total_users",
28513
+ "yesterday_reads",
28514
+ "yesterday_reads_change_pct",
28515
+ "yesterday_shares",
28516
+ "yesterday_new_followers",
28517
+ "stat_range"
28518
+ ],
28519
+ "type": "js",
28520
+ "modulePath": "weixin/home-overview.js",
28521
+ "sourceFile": "weixin/home-overview.js",
28522
+ "navigateBefore": false
28523
+ },
28488
28524
  {
28489
28525
  "site": "weixin",
28490
28526
  "name": "published",
@@ -0,0 +1,114 @@
1
+ import { CommandExecutionError } from '@sovovs/bycli/errors';
2
+
3
+ const DOMAIN = 'mp.weixin.qq.com';
4
+
5
+ /**
6
+ * @typedef {{
7
+ * originalCount: number | null,
8
+ * totalUsers: number | null,
9
+ * yesterdayReads: number | null,
10
+ * yesterdayReadsChangePct: number | null,
11
+ * yesterdayShares: number | null,
12
+ * yesterdayNewFollowers: number | null,
13
+ * statRange: string | null,
14
+ * }} HomeOverview
15
+ */
16
+
17
+ /** @param {string} token */
18
+ export function buildHomeUrl(token) {
19
+ const parameters = new URLSearchParams({
20
+ t: 'home/index',
21
+ lang: 'zh_CN',
22
+ token: String(token),
23
+ });
24
+ return `https://${DOMAIN}/cgi-bin/home?${parameters.toString()}`;
25
+ }
26
+
27
+ /**
28
+ * Reads the home dashboard counters from `window.wx.cgiData` with a DOM fallback.
29
+ * Kept as a string so it can run through `page.evaluate` unchanged.
30
+ */
31
+ export const HOME_OVERVIEW_SCRIPT = `(() => {
32
+ const digits = value => {
33
+ if (value === null || value === undefined) return null;
34
+ const text = String(value).replace(/[\\s,]/g, '');
35
+ const match = /^-?\\d+(?:\\.\\d+)?$/.exec(text);
36
+ return match ? Number(text) : null;
37
+ };
38
+ const textOf = selector => {
39
+ const node = document.querySelector(selector);
40
+ return node ? node.textContent : null;
41
+ };
42
+ const overviewByTitle = keyword => {
43
+ const items = Array.from(document.querySelectorAll('.weui-desktop-data-overview'));
44
+ const matched = items.find(item => {
45
+ const title = item.querySelector('.weui-desktop-data-overview__title');
46
+ return title ? title.textContent.indexOf(keyword) !== -1 : false;
47
+ });
48
+ if (!matched) return { value: null, changePct: null };
49
+ const desc = matched.querySelector('.weui-desktop-data-overview__desc');
50
+ const tips = matched.querySelector('.tips_num');
51
+ return {
52
+ value: digits(desc ? desc.textContent : null),
53
+ changePct: digits(tips ? tips.textContent.replace('%', '') : null),
54
+ };
55
+ };
56
+
57
+ const cgiData = (window.wx && window.wx.cgiData) || {};
58
+ const summary = cgiData.yesterdaySummary || {};
59
+ const reads = overviewByTitle('昨日阅读');
60
+ const shares = overviewByTitle('昨日分享');
61
+ const followers = overviewByTitle('昨日新增关注');
62
+ const statRange = textOf('.weui-desktop-data_description');
63
+
64
+ return {
65
+ originalCount: digits(textOf('.weui-desktop-user_sum.original_cnt')),
66
+ totalUsers: digits(cgiData.total_friend_cnt) ?? digits(textOf('.weui-desktop-user_num .weui-desktop-user_sum')),
67
+ yesterdayReads: digits(summary.pv) ?? reads.value,
68
+ yesterdayReadsChangePct: digits(summary.pvAnalysis && summary.pvAnalysis.changeRate) ?? reads.changePct,
69
+ yesterdayShares: digits(summary.share) ?? shares.value,
70
+ yesterdayNewFollowers: digits(summary.subscribe) ?? followers.value,
71
+ statRange: statRange ? statRange.trim().replace(/\\s+/g, ' ') : null,
72
+ };
73
+ })()`;
74
+
75
+ /**
76
+ * @param {unknown} payload
77
+ * @returns {HomeOverview}
78
+ */
79
+ export function normalizeHomeOverview(payload) {
80
+ if (!payload || typeof payload !== 'object') {
81
+ throw new CommandExecutionError(
82
+ 'WeChat home overview returned an unreadable payload',
83
+ 'Reload the Official Account home page and run the command again.',
84
+ );
85
+ }
86
+ const raw = /** @type {Record<string, unknown>} */ (payload);
87
+ const number = key => (Number.isFinite(raw[key]) ? Number(raw[key]) : null);
88
+ const overview = {
89
+ originalCount: number('originalCount'),
90
+ totalUsers: number('totalUsers'),
91
+ yesterdayReads: number('yesterdayReads'),
92
+ yesterdayReadsChangePct: number('yesterdayReadsChangePct'),
93
+ yesterdayShares: number('yesterdayShares'),
94
+ yesterdayNewFollowers: number('yesterdayNewFollowers'),
95
+ statRange: typeof raw.statRange === 'string' && raw.statRange.trim()
96
+ ? raw.statRange.trim().replace(/\s+/g, ' ')
97
+ : null,
98
+ };
99
+
100
+ const metrics = [
101
+ overview.originalCount,
102
+ overview.totalUsers,
103
+ overview.yesterdayReads,
104
+ overview.yesterdayShares,
105
+ overview.yesterdayNewFollowers,
106
+ ];
107
+ if (metrics.every(value => value === null)) {
108
+ throw new CommandExecutionError(
109
+ 'WeChat home overview exposed no counters',
110
+ 'The home dashboard layout may have changed; check that the Official Account home page renders its data panel.',
111
+ );
112
+ }
113
+ return overview;
114
+ }
@@ -0,0 +1,51 @@
1
+ import { cli, Strategy } from '@sovovs/bycli/registry';
2
+ import { resolveBrowserCredentials } from './_wechat/auth-session.js';
3
+ import {
4
+ HOME_OVERVIEW_SCRIPT,
5
+ buildHomeUrl,
6
+ normalizeHomeOverview,
7
+ } from './_wechat/home-overview.js';
8
+
9
+ const DOMAIN = 'mp.weixin.qq.com';
10
+
11
+ const COLUMNS = [
12
+ 'original_count',
13
+ 'total_users',
14
+ 'yesterday_reads',
15
+ 'yesterday_reads_change_pct',
16
+ 'yesterday_shares',
17
+ 'yesterday_new_followers',
18
+ 'stat_range',
19
+ ];
20
+
21
+ export const homeOverviewCommand = cli({
22
+ site: 'weixin',
23
+ name: 'home-overview',
24
+ access: 'read',
25
+ domain: DOMAIN,
26
+ aliases: ['overview', 'dashboard', 'fans'],
27
+ description: '读取公众号首页概览:原创内容、总用户数(粉丝数)、昨日阅读/分享/新增关注人数及阅读增长百分比;账号级昨日汇总,单篇文章数据请用 published',
28
+ strategy: Strategy.COOKIE,
29
+ browser: true,
30
+ navigateBefore: false,
31
+ args: [
32
+ { name: 'settle', type: 'int', default: 3, help: '打开首页后等待渲染的秒数' },
33
+ ],
34
+ columns: COLUMNS,
35
+ func: async (page, args) => {
36
+ const { token } = await resolveBrowserCredentials(page);
37
+ await page.goto(buildHomeUrl(token));
38
+ const settle = Number(args.settle ?? 3);
39
+ await page.wait(Number.isFinite(settle) && settle > 0 ? settle : 3);
40
+ const overview = normalizeHomeOverview(await page.evaluate(HOME_OVERVIEW_SCRIPT));
41
+ return [{
42
+ original_count: overview.originalCount,
43
+ total_users: overview.totalUsers,
44
+ yesterday_reads: overview.yesterdayReads,
45
+ yesterday_reads_change_pct: overview.yesterdayReadsChangePct,
46
+ yesterday_shares: overview.yesterdayShares,
47
+ yesterday_new_followers: overview.yesterdayNewFollowers,
48
+ stat_range: overview.statRange,
49
+ }];
50
+ },
51
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovovs/bycli",
3
- "version": "2.1.31",
3
+ "version": "2.1.32",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },