6.7.0 - Alpha12 - Fine tuning

This commit is contained in:
SuperMonster003
2025-12-08 23:30:11 +08:00
parent a99247b80c
commit 40443ae62a
22 changed files with 201 additions and 60 deletions

View File

@@ -76,6 +76,49 @@ interface FindTargetRowsOptionsBase {
interface FindTargetRowsOptions extends FindTargetRowsOptionsBase {
/** @default [] */
tableDataStructure?: Array<{ [dataItemName: TableDataStructureItemName]: TableDataStructureItem } | TableDataStructureItemName>;
/**
* Configuration for "expand" actions needed before searching the table.
* Example: { toggleSelector: 'a.exw-control' } will click Devsite's collapsible controls.
* zh-CN:
* 在查找表格前需要进行的 "展开" 动作配置.
* 示例: { toggleSelector: 'a.exw-control' } 将点击 Devsite 的可折叠控件.
*/
expandBeforeFinding?: ExpandBeforeFindingOptions | ExpandBeforeFindingOptions[];
}
interface ExpandBeforeFindingOptions {
/**
* Optional container selector to limit the scope of operation.
* zh-CN: 限定作用范围容器选择器, 可选.
*/
containerSelector?: string;
/**
* Selector for the "expand/collapse" button or toggle that needs to be clicked (required).
* zh-CN: 需要点击的 "展开/折叠" 按钮或开关的选择器 (必填).
*/
toggleSelector: string;
/**
* Attribute name to determine if expanded, defaults to 'aria-expanded'.
* zh-CN: 判定已展开的属性名, 默认 'aria-expanded'.
*
* @default 'aria-expanded'
*/
expandedAttr?: string;
/**
* Attribute value to determine if expanded, defaults to 'true'.
* zh-CN: 判定已展开的属性值, 默认 'true'.
*
* @default 'true'
*/
expandedValue?: string;
/**
* Maximum number of targets to click, no limit by default.
* zh-CN: 最多点击的目标数量, 默认无限制.
*
* @default Infinity
*/
maxClicks?: number;
}
interface FindTargetRowsOptionsForPageEvaluate extends FindTargetRowsOptionsBase {

View File

@@ -18,6 +18,9 @@ export async function fetchStudioAgpTable() {
// @ts-ignore
return findTargetRows({
url: URL,
expandBeforeFinding: {
toggleSelector: 'a.exw-control',
},
tableFilter: {
th: /Android Studio version/i,
},

View File

@@ -248,7 +248,7 @@ async function getPRCommitStatsByAuthor(pr) {
* zh-CN: 要处理的输入项列表.
* @param {number} limit
* Maximum concurrency (>=1).<br>
* zh=CN: 最大并发数 (>=1).
* zh-CN: 最大并发数 (>=1).
* @param {(item: Item, idx: number) => Promise<MapperResult>} mapper
* Async function to process one item.<br>
* zh-CN: 处理单个项的异步函数.

View File

@@ -55,6 +55,66 @@ function toEncodedRegexTagOptions(options) {
return structuredClone(traverse(options));
}
/**
* Try to click the "Expand" control before searching for the table.
* zh-CN: 在查找表格前尝试点击 "展开" 控件.
*
* @param {import('puppeteer').Page} page
* @param {FindTargetRowsOptions & PuppeteerOptions} options
* @returns {Promise<number>} Actual number of clicks. (zh-CN: 实际点击次数.)
*/
async function expandContentIfNeeded(page, options) {
const configs = options.expandBeforeFinding;
if (!configs) return 0;
/** @type {ExpandBeforeFindingOptions[]} */
const list = Array.isArray(configs) ? configs : [ configs ];
let totalClicked = 0;
for (const cfg of list) {
if (!cfg || !cfg.toggleSelector) continue;
const {
containerSelector,
toggleSelector,
expandedAttr = 'aria-expanded',
expandedValue = 'true',
maxClicks = Infinity,
} = cfg;
const containerHandle = containerSelector ? await page.$(containerSelector) : null;
const toggles = containerHandle
? await containerHandle.$$(toggleSelector)
: await page.$$(toggleSelector);
let clicked = 0;
for (const toggle of toggles) {
if (clicked >= maxClicks) break;
const isExpanded = await toggle.evaluate((el, attr, val) => {
const cur = el.getAttribute(attr);
return cur === val;
}, expandedAttr, expandedValue);
if (!isExpanded) {
try {
await toggle.click({ delay: 10 });
clicked++;
totalClicked++;
await sleep(200);
} catch {
/* Ignored. */
}
}
}
if (containerHandle) {
try {
await containerHandle.dispose();
} catch {
/* Ignored. */
}
}
}
return totalClicked;
}
/**
* @param {Page} page
* @returns {Promise<void>}
@@ -87,7 +147,7 @@ async function findTargetRowsWithPage(page, options = {}) {
return await page.evaluate((options, consts) => {
const regex = new RegExp(consts.regexId + String.raw`(?:(\w+):)?(.+)`);
const targets = Array.from(document.querySelectorAll(options.tableSelector ?? 'table'));
const target = targets.find(t => {
const filteredTargets = targets.filter(t => {
for (const [ selector, filter ] of Object.entries(options.tableFilter ?? {})) {
const elements = Array.from(t.querySelectorAll(selector));
if (Array.isArray(filter)) {
@@ -130,11 +190,11 @@ async function findTargetRowsWithPage(page, options = {}) {
}
return true;
});
if (!target) {
if (!filteredTargets.length) {
throw new Error('No target table found');
}
const tableRows = Array.from(target.querySelectorAll(options.tableRowSelector ?? 'tbody tr'));
const tableRows = filteredTargets.map(target => Array.from(target.querySelectorAll(options.tableRowSelector ?? 'tbody tr'))).flat(1);
const tableDataList = [];
tableRows.forEach((tr) => {
const tableData = {};
@@ -209,8 +269,11 @@ export async function findTargetRows(options) {
let rows = null;
const deadline = Date.now() + (options.findTargetRowsTimeout ?? 30000);
while (Date.now() < deadline) {
await expandContentIfNeeded(page, options);
rows = await findTargetRowsWithPage(page, options);
if (rows && rows.length) break;
await autoScroll(page);
await sleep(300);
}