@sovovs/bycli 2.1.51 → 2.1.53
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/clis/weixin/_wechat/user-info.js +173 -81
- package/package.json +1 -1
|
@@ -53,26 +53,50 @@ export function buildSelectTabScript(label) {
|
|
|
53
53
|
const compact = value => String(value || '').trim().replace(/\\s+/g, ' ');
|
|
54
54
|
const visible = node => {
|
|
55
55
|
if (!node || node.hidden || node.closest('[hidden], [aria-hidden="true"]')) return false;
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
for (let current = node; current && current.nodeType === 1; current = current.parentElement) {
|
|
57
|
+
const style = window.getComputedStyle(current);
|
|
58
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
58
61
|
};
|
|
59
|
-
const
|
|
62
|
+
const selectors = [
|
|
63
|
+
'a[href]',
|
|
64
|
+
'button',
|
|
60
65
|
'[role="tab"]',
|
|
61
66
|
'.weui-desktop-tab__nav',
|
|
62
67
|
'.tab_nav',
|
|
63
|
-
'a',
|
|
64
|
-
'button',
|
|
65
68
|
'li',
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
];
|
|
70
|
+
let node = null;
|
|
71
|
+
for (const selector of selectors) {
|
|
72
|
+
node = Array.from(document.querySelectorAll(selector)).find(candidate => visible(candidate)
|
|
73
|
+
&& compact(candidate.textContent) === ${JSON.stringify(label)});
|
|
74
|
+
if (node) break;
|
|
75
|
+
}
|
|
76
|
+
if (!node) return { selected: false, disabled: false, href: null };
|
|
70
77
|
const disabled = node.disabled === true
|
|
71
78
|
|| node.getAttribute('aria-disabled') === 'true'
|
|
72
79
|
|| /(^|\\s)(disabled|is-disabled)(\\s|$)/.test(node.className || '');
|
|
73
|
-
if (disabled) return { selected: false, disabled: true };
|
|
80
|
+
if (disabled) return { selected: false, disabled: true, href: null };
|
|
81
|
+
const rawHref = node.tagName === 'A' ? node.getAttribute('href') : null;
|
|
82
|
+
if (rawHref) {
|
|
83
|
+
const href = node.href;
|
|
84
|
+
let destination = null;
|
|
85
|
+
try { destination = new URL(href); } catch { /* handled as a click-only placeholder */ }
|
|
86
|
+
const trustedNavigation = destination
|
|
87
|
+
&& destination.origin === window.location.origin
|
|
88
|
+
&& destination.pathname === '/cgi-bin/settingpage'
|
|
89
|
+
&& Boolean(destination.searchParams.get('token'));
|
|
90
|
+
if (trustedNavigation) return { selected: true, disabled: false, href };
|
|
91
|
+
const clickOnly = /^javascript:/i.test(rawHref)
|
|
92
|
+
|| rawHref.charAt(0) === '#'
|
|
93
|
+
|| (destination
|
|
94
|
+
&& destination.origin === window.location.origin
|
|
95
|
+
&& destination.pathname === '/cgi-bin/settingpage');
|
|
96
|
+
if (!clickOnly) return { selected: true, disabled: false, href };
|
|
97
|
+
}
|
|
74
98
|
node.click();
|
|
75
|
-
return { selected: true, disabled: false };
|
|
99
|
+
return { selected: true, disabled: false, href: null };
|
|
76
100
|
})()`;
|
|
77
101
|
}
|
|
78
102
|
|
|
@@ -84,8 +108,11 @@ export const SETTINGS_SESSION_SCRIPT = `(() => {
|
|
|
84
108
|
];
|
|
85
109
|
const visible = node => {
|
|
86
110
|
if (!node || node.hidden || node.closest('[hidden], [aria-hidden="true"]')) return false;
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
for (let current = node; current && current.nodeType === 1; current = current.parentElement) {
|
|
112
|
+
const style = window.getComputedStyle(current);
|
|
113
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
89
116
|
};
|
|
90
117
|
return {
|
|
91
118
|
href: window.location.href,
|
|
@@ -121,8 +148,11 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
121
148
|
};
|
|
122
149
|
const visible = node => {
|
|
123
150
|
if (!node || node.hidden || node.closest('[hidden], [aria-hidden="true"]')) return false;
|
|
124
|
-
|
|
125
|
-
|
|
151
|
+
for (let current = node; current && current.nodeType === 1; current = current.parentElement) {
|
|
152
|
+
const style = window.getComputedStyle(current);
|
|
153
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
126
156
|
};
|
|
127
157
|
const firstVisible = (root, selectors) => {
|
|
128
158
|
for (const selector of selectors) {
|
|
@@ -131,20 +161,73 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
131
161
|
}
|
|
132
162
|
return null;
|
|
133
163
|
};
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
const ignoredValueSelector = [
|
|
165
|
+
'a',
|
|
166
|
+
'button',
|
|
167
|
+
'[role="button"]',
|
|
168
|
+
'[role="tooltip"]',
|
|
169
|
+
'svg',
|
|
170
|
+
'.setting_opr',
|
|
171
|
+
'.weui-desktop-setting__status',
|
|
172
|
+
'.setting_status',
|
|
173
|
+
'.status',
|
|
174
|
+
'.frm_tips',
|
|
175
|
+
'.weui-desktop-form__tips',
|
|
176
|
+
'.weui-desktop-setting__desc',
|
|
177
|
+
'.setting_desc',
|
|
178
|
+
'.tips',
|
|
179
|
+
'.desc',
|
|
180
|
+
'.description',
|
|
181
|
+
'.help',
|
|
182
|
+
'.icon-question',
|
|
183
|
+
'[class*="question"]',
|
|
184
|
+
'[class*="help"]',
|
|
185
|
+
'[class*="popover"]',
|
|
186
|
+
'[class*="-ask"]',
|
|
187
|
+
].join(',');
|
|
188
|
+
const cleanValueText = node => {
|
|
189
|
+
if (!node) return null;
|
|
190
|
+
const copy = node.cloneNode(true);
|
|
191
|
+
copy.querySelectorAll(ignoredValueSelector).forEach(item => item.remove());
|
|
192
|
+
return compact(copy.textContent);
|
|
193
|
+
};
|
|
194
|
+
const primaryValueText = node => {
|
|
195
|
+
if (node && node.matches('.weui-desktop-setting__item__info')) {
|
|
196
|
+
const directText = compact(Array.from(node.childNodes)
|
|
197
|
+
.filter(child => child.nodeType === Node.TEXT_NODE)
|
|
198
|
+
.map(child => child.textContent)
|
|
199
|
+
.join(' '));
|
|
200
|
+
if (directText) return cleanValueText(node);
|
|
201
|
+
const primaryChild = Array.from(node.children)
|
|
202
|
+
.find(child => visible(child) && !child.matches(ignoredValueSelector));
|
|
203
|
+
if (primaryChild) return cleanValueText(primaryChild);
|
|
204
|
+
}
|
|
205
|
+
return cleanValueText(node);
|
|
206
|
+
};
|
|
207
|
+
const switchValue = row => {
|
|
208
|
+
const node = firstVisible(row, [
|
|
209
|
+
'input[type="checkbox"]',
|
|
210
|
+
'[role="switch"]',
|
|
211
|
+
'.weui-desktop-switch',
|
|
212
|
+
'.weui-switch',
|
|
213
|
+
'[class*="switch"]',
|
|
214
|
+
]);
|
|
215
|
+
if (!node) return null;
|
|
216
|
+
const input = node.matches('input[type="checkbox"]')
|
|
217
|
+
? node
|
|
218
|
+
: node.querySelector('input[type="checkbox"]');
|
|
219
|
+
if (input) return Boolean(input.checked);
|
|
220
|
+
const ariaNode = node.hasAttribute('aria-checked')
|
|
221
|
+
? node
|
|
222
|
+
: node.querySelector('[aria-checked]');
|
|
223
|
+
const ariaChecked = ariaNode && ariaNode.getAttribute('aria-checked');
|
|
224
|
+
if (ariaChecked === 'true') return true;
|
|
225
|
+
if (ariaChecked === 'false') return false;
|
|
226
|
+
const className = [node.className, node.parentElement && node.parentElement.className].join(' ');
|
|
227
|
+
if (/(^|[\\s_-])(checked|on|active)([\\s_-]|$)/i.test(className)) return true;
|
|
228
|
+
if (/(^|[\\s_-])(unchecked|off)([\\s_-]|$)/i.test(className)) return false;
|
|
229
|
+
return null;
|
|
230
|
+
};
|
|
148
231
|
const rowSelectors = [
|
|
149
232
|
'.setting_item',
|
|
150
233
|
'.weui-desktop-setting__item',
|
|
@@ -183,72 +266,82 @@ export const USER_INFO_EXTRACT_SCRIPT = `(() => {
|
|
|
183
266
|
let rows = Array.from(sectionNode.querySelectorAll(rowSelector)).filter(visible);
|
|
184
267
|
rows = rows.filter(node => !rows.some(other => other !== node && other.contains(node)));
|
|
185
268
|
const fields = [];
|
|
186
|
-
const actions = [];
|
|
187
269
|
const seenFields = new Set();
|
|
188
|
-
const
|
|
270
|
+
const fieldRows = new Set();
|
|
271
|
+
const genericParts = node => Array.from(node.children).flatMap(child => {
|
|
272
|
+
if (!visible(child)
|
|
273
|
+
|| /^(H1|H2|H3|H4|H5|H6|A|BUTTON)$/.test(child.tagName)
|
|
274
|
+
|| child.getAttribute('role') === 'button') return [];
|
|
275
|
+
const value = cleanValueText(child);
|
|
276
|
+
return value ? [value] : [];
|
|
277
|
+
});
|
|
189
278
|
|
|
190
279
|
for (const row of rows) {
|
|
280
|
+
const headerCells = row.tagName === 'TR' ? row.querySelectorAll(':scope > th') : [];
|
|
281
|
+
const dataCells = row.tagName === 'TR' ? row.querySelectorAll(':scope > td') : [];
|
|
282
|
+
if (headerCells.length > 0 && dataCells.length === 0) continue;
|
|
191
283
|
const labelNode = firstVisible(row, [
|
|
192
284
|
'.frm_label',
|
|
193
285
|
'.weui-desktop-setting__label',
|
|
286
|
+
'.weui-desktop-setting__item__label',
|
|
194
287
|
'.weui-desktop-form__label',
|
|
195
288
|
'dt',
|
|
196
289
|
'th',
|
|
197
290
|
]);
|
|
198
|
-
const fieldLabel =
|
|
199
|
-
const rowActions = actionNodes(row);
|
|
200
|
-
for (const node of rowActions) {
|
|
201
|
-
const item = action(node);
|
|
202
|
-
const key = JSON.stringify(item);
|
|
203
|
-
if (!seenActions.has(key)) {
|
|
204
|
-
seenActions.add(key);
|
|
205
|
-
actions.push(item);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
291
|
+
const fieldLabel = cleanValueText(labelNode);
|
|
208
292
|
if (!fieldLabel) continue;
|
|
209
293
|
const valueNode = firstVisible(row, [
|
|
210
294
|
'.weui-desktop-setting__value',
|
|
295
|
+
'.weui-desktop-setting__item__info',
|
|
211
296
|
'.setting_value',
|
|
212
297
|
'.frm_value',
|
|
213
298
|
'dd',
|
|
214
299
|
'td',
|
|
215
300
|
'.frm_controls',
|
|
216
301
|
]);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
copy.querySelectorAll('a, button, [role="button"], .setting_opr, .weui-desktop-setting__status, .setting_status, .status')
|
|
226
|
-
.forEach(node => node.remove());
|
|
227
|
-
fieldValue = compact(copy.textContent);
|
|
302
|
+
let fieldValue = switchValue(row);
|
|
303
|
+
if (fieldValue === null) fieldValue = primaryValueText(valueNode);
|
|
304
|
+
if (fieldValue === null) {
|
|
305
|
+
const parts = genericParts(row);
|
|
306
|
+
const labelIndex = parts.indexOf(fieldLabel);
|
|
307
|
+
if (labelIndex !== -1) {
|
|
308
|
+
fieldValue = parts[labelIndex + 1] || null;
|
|
309
|
+
}
|
|
228
310
|
}
|
|
229
311
|
const item = {
|
|
230
312
|
label: fieldLabel,
|
|
231
313
|
value: fieldValue,
|
|
232
|
-
status: compact(statusNode && statusNode.textContent),
|
|
233
314
|
};
|
|
234
315
|
const key = JSON.stringify(item);
|
|
235
316
|
if (!seenFields.has(key)) {
|
|
236
317
|
seenFields.add(key);
|
|
237
318
|
fields.push(item);
|
|
238
319
|
}
|
|
320
|
+
fieldRows.add(row);
|
|
239
321
|
}
|
|
240
322
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
323
|
+
let genericRows = Array.from(sectionNode.querySelectorAll('div, li'))
|
|
324
|
+
.filter(node => visible(node)
|
|
325
|
+
&& !fieldRows.has(node)
|
|
326
|
+
&& !rows.some(row => row !== node && row.contains(node))
|
|
327
|
+
&& genericParts(node).length >= 2);
|
|
328
|
+
genericRows = genericRows.filter(node => !genericRows.some(other => other !== node && node.contains(other)));
|
|
329
|
+
for (const row of genericRows) {
|
|
330
|
+
const parts = genericParts(row);
|
|
331
|
+
const item = {
|
|
332
|
+
label: parts[0],
|
|
333
|
+
value: parts[1] || null,
|
|
334
|
+
};
|
|
245
335
|
const key = JSON.stringify(item);
|
|
246
|
-
if (!
|
|
247
|
-
|
|
248
|
-
|
|
336
|
+
if (!seenFields.has(key)) {
|
|
337
|
+
seenFields.add(key);
|
|
338
|
+
fields.push(item);
|
|
249
339
|
}
|
|
250
340
|
}
|
|
251
|
-
|
|
341
|
+
|
|
342
|
+
if (fields.length > 0) return [{ label, fields }];
|
|
343
|
+
const empty = Boolean(sectionNode.querySelector('table thead th, table tr > th'));
|
|
344
|
+
return empty ? [{ label, fields, empty: true }] : [];
|
|
252
345
|
});
|
|
253
346
|
|
|
254
347
|
if (sections.length === 0 && /(无权限|暂无权限|暂不支持|不可用)/.test(document.body.textContent || '')) {
|
|
@@ -264,20 +357,7 @@ function normalizeField(field, tabLabel, sectionIndex, fieldIndex) {
|
|
|
264
357
|
execution(label, prefix);
|
|
265
358
|
return {
|
|
266
359
|
label,
|
|
267
|
-
value: text(field.value),
|
|
268
|
-
status: text(field.status),
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function normalizeAction(action, tabLabel, sectionIndex, actionIndex) {
|
|
273
|
-
const prefix = `WeChat ${tabLabel} returned an invalid action at section ${sectionIndex} index ${actionIndex}`;
|
|
274
|
-
execution(object(action), prefix);
|
|
275
|
-
const label = text(action.label);
|
|
276
|
-
execution(label, prefix);
|
|
277
|
-
return {
|
|
278
|
-
label,
|
|
279
|
-
enabled: action.enabled !== false,
|
|
280
|
-
path: sanitizeActionPath(action.href ?? action.path),
|
|
360
|
+
value: typeof field.value === 'boolean' ? field.value : text(field.value),
|
|
281
361
|
};
|
|
282
362
|
}
|
|
283
363
|
|
|
@@ -297,15 +377,13 @@ export function normalizeUserInfoTab(tabId, payload) {
|
|
|
297
377
|
const label = text(section.label);
|
|
298
378
|
execution(label, prefix);
|
|
299
379
|
const rawFields = section.fields ?? [];
|
|
300
|
-
|
|
301
|
-
execution(Array.isArray(rawFields) && Array.isArray(rawActions), prefix);
|
|
380
|
+
execution(Array.isArray(rawFields), prefix);
|
|
302
381
|
const fields = rawFields.map((field, fieldIndex) => (
|
|
303
382
|
normalizeField(field, definition.label, sectionIndex, fieldIndex)
|
|
304
383
|
));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return fields.length > 0 || actions.length > 0 ? [{ label, fields, actions }] : [];
|
|
384
|
+
return fields.length > 0 || section.empty === true
|
|
385
|
+
? [{ label, fields }]
|
|
386
|
+
: [];
|
|
309
387
|
});
|
|
310
388
|
|
|
311
389
|
execution(sections.length > 0, `WeChat ${definition.label} exposed no recognizable settings`);
|
|
@@ -332,6 +410,20 @@ export async function collectUserInfoTabs(page, { settle = 2 } = {}) {
|
|
|
332
410
|
});
|
|
333
411
|
continue;
|
|
334
412
|
}
|
|
413
|
+
if (selection.href !== null && selection.href !== undefined) {
|
|
414
|
+
let navigationUrl;
|
|
415
|
+
try {
|
|
416
|
+
navigationUrl = new URL(selection.href);
|
|
417
|
+
} catch {
|
|
418
|
+
throw new CommandExecutionError('WeChat account settings exposed an invalid tab destination');
|
|
419
|
+
}
|
|
420
|
+
execution(
|
|
421
|
+
navigationUrl.origin === ORIGIN
|
|
422
|
+
&& Boolean(navigationUrl.searchParams.get('token')?.trim()),
|
|
423
|
+
'WeChat account settings exposed an untrusted tab destination',
|
|
424
|
+
);
|
|
425
|
+
await page.goto(navigationUrl.toString());
|
|
426
|
+
}
|
|
335
427
|
await page.wait(settle);
|
|
336
428
|
const payload = await page.evaluate(USER_INFO_EXTRACT_SCRIPT);
|
|
337
429
|
result.push({ id: definition.id, data: normalizeUserInfoTab(definition.id, payload) });
|