// ==UserScript== // @name BB脚本 // @namespace https://data-baker.com/ // @version 1.0 // @description 切帧瞬间自动点击正常分组下的 face5关键点;去上限、零延迟、切帧预滚渲染。无调试浮窗。 // @author WorkBuddy // @match https://cvpc.data-baker.com/app/editor/cv/* // @icon https://cvpc.data-baker.com/favicon.ico // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; // ===================== 配置区 ===================== const CFG = { targetText: 'face5关键点', // 文件夹名称 ignoreGroupText: '未分组', // 要跳过的分组名 rowClass: 'bb-row-tmpl', // 行容器类名 groupClass: 'bb-row-tmpl--group', // 分组行标记类 trackClass: 'bb-row-tmpl--track', // 轨道行标记类 contentClass: 'bb-row-tmpl__content', // 手动点击的容器 arrowClass: 'folder-switch', // 展开箭头类名 clickTarget: 'content', // 'content' / 'arrow' / 'row' clickMode: 'pointer', // 'pointer' / 'click' clickDelay: 0, // 找到后立即点(零延迟) scrollIntoView: true, // 点击前是否滚动到可视区域 clickInterval: 50, // 两次点击最小间隔(ms),仅防极端狂点 revealCooldown: 200, // 滚动兜底冷却(ms) rescanAfterReveal: 20, // 滚动后主动再扫的延迟(ms) scanThrottle: 60 // scan 节流(ms) }; // ================================================= const s = document.createElement('script'); s.textContent = '(' + pageScript.toString() + ')(' + JSON.stringify(CFG) + ');'; document.documentElement.appendChild(s); s.remove(); function pageScript(cfg) { 'use strict'; console.log('[自动点击] 页面脚本已加载,目标:' + cfg.targetText); let running = false; let lastScan = 0; let pendingRescan = false; let currentFrameId = getFrameId(); let lastClickTime = 0; let pendingClick = false; let expandedState = false; let lastReveal = 0; let lastSkip = 0; let lastTotal = 0; let revealState = ''; function getFrameId() { try { const u = new URL(location.href); return u.searchParams.get('data_id') || u.searchParams.get('frame_id') || u.searchParams.get('frame') || ''; } catch (e) { return ''; } } function scoreRow(row) { let s = 0; if (row.classList.contains('is-selected')) s += 100; if (!row.classList.contains('is-muted')) s += 50; if (!row.classList.contains('is-missing')) s += 50; return s; } function getGroupName(row) { if (row.classList.contains(cfg.groupClass)) { const nameSpan = row.querySelector('.bb-folder-content .text-row-1 span'); return nameSpan ? nameSpan.textContent.trim() : ''; } const allRows = document.querySelectorAll('.' + cfg.rowClass); let foundSelf = false; for (let i = allRows.length - 1; i >= 0; i--) { if (allRows[i] === row) { foundSelf = true; continue; } if (foundSelf && allRows[i].classList.contains(cfg.groupClass)) { const nameSpan = allRows[i].querySelector('.bb-folder-content .text-row-1 span'); return nameSpan ? nameSpan.textContent.trim() : ''; } } return ''; } function findRow() { let best = null, bestScore = -1, skip = 0, total = 0; const spans = document.querySelectorAll( '.' + cfg.contentClass + ' span,' + '.bb-folder-content span,' + '.text-row-1 span' ); for (let i = 0; i < spans.length; i++) { if (spans[i].textContent.trim() === cfg.targetText) { const row = spans[i].closest('.' + cfg.rowClass) || spans[i].closest('.' + cfg.contentClass); if (!row) continue; if (!row.classList.contains(cfg.trackClass)) continue; total++; const groupName = getGroupName(row); if (groupName.indexOf(cfg.ignoreGroupText) !== -1) { skip++; continue; } if (row.classList.contains('is-muted') || row.classList.contains('is-missing')) { skip++; continue; } const sc = scoreRow(row); if (sc > bestScore) { bestScore = sc; best = row; } } } lastSkip = skip; lastTotal = total; return best; } // 滚动到第一个正常分组行,触发虚拟列表渲染其子项 function revealTarget(force) { const now = Date.now(); if (!force && now - lastReveal < cfg.revealCooldown) return; const groups = document.querySelectorAll('.' + cfg.rowClass + '.' + cfg.groupClass); for (let i = 0; i < groups.length; i++) { const nameSpan = groups[i].querySelector('.bb-folder-content .text-row-1 span'); const name = nameSpan ? nameSpan.textContent.trim() : ''; if (name && name.indexOf(cfg.ignoreGroupText) === -1) { groups[i].scrollIntoView({ block: 'center' }); lastReveal = now; revealState = (force ? '切帧预滚' : '已滚动') + '到「' + name + '」'; console.log('[自动点击] 滚动到正常分组触发渲染:' + name); if (!pendingRescan) { pendingRescan = true; setTimeout(function () { pendingRescan = false; scan(); }, cfg.rescanAfterReveal); } return; } } revealState = '未找到正常分组行'; } function isCollapsed(row) { const use = row.querySelector('.' + cfg.arrowClass + ' use') || row.querySelector('.folder-switch use'); if (!use) return false; const href = (use.getAttribute('xlink:href') || use.getAttribute('href') || '').toLowerCase(); return href.indexOf('skeleton') !== -1; } function simulatePointerClick(el) { const rect = el.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const base = { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y, detail: 1, button: 0, buttons: 1 }; const ptr = Object.assign({}, base, { pointerType: 'mouse', isPrimary: true }); el.dispatchEvent(new PointerEvent('pointerover', ptr)); el.dispatchEvent(new PointerEvent('pointerenter', ptr)); el.dispatchEvent(new PointerEvent('pointerdown', ptr)); el.dispatchEvent(new MouseEvent('mousedown', base)); el.dispatchEvent(new PointerEvent('pointerup', ptr)); el.dispatchEvent(new MouseEvent('mouseup', base)); el.dispatchEvent(new MouseEvent('click', base)); el.dispatchEvent(new PointerEvent('pointerleave', ptr)); el.dispatchEvent(new PointerEvent('pointerout', ptr)); } function performClick(row) { let el; if (cfg.clickTarget === 'content') { el = row.querySelector('.' + cfg.contentClass) || row; } else if (cfg.clickTarget === 'arrow') { el = row.querySelector('.' + cfg.arrowClass) || row; } else { el = row; } if (cfg.scrollIntoView && el.scrollIntoView) { el.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } if (cfg.clickMode === 'pointer') simulatePointerClick(el); else el.click(); return el; } // 已无每帧点击上限:靠 collapsed=false 自然停手,靠 clickInterval 防极端狂点 function scheduleClickIfNeeded(row, collapsed) { const now = Date.now(); if (!collapsed) { expandedState = true; return; } expandedState = false; if (pendingClick) return; if (now - lastClickTime < cfg.clickInterval) return; pendingClick = true; setTimeout(function () { pendingClick = false; const r = findRow(); if (!r) return; if (isCollapsed(r)) { performClick(r); lastClickTime = Date.now(); revealState = '已点击「' + cfg.targetText + '」'; console.log('[自动点击] 已点击展开:' + cfg.targetText); } }, cfg.clickDelay); } function scan() { const now = Date.now(); if (now - lastScan < cfg.scanThrottle) return; lastScan = now; if (running) return; running = true; const fid = getFrameId(); if (fid && fid !== currentFrameId) { currentFrameId = fid; lastClickTime = 0; // 允许新帧立即点击 revealState = '切帧重置'; console.log('[自动点击] 检测到切帧,重置:' + fid); revealTarget(true); // 切帧瞬间预滚,触发渲染 } const row = findRow(); if (!row) { revealTarget(false); // 没找到则兜底滚动 } else { revealState = '目标已就绪'; } const collapsed = row ? isCollapsed(row) : false; scheduleClickIfNeeded(row, collapsed); running = false; } const _push = history.pushState, _replace = history.replaceState; history.pushState = function () { _push.apply(this, arguments); scan(); }; history.replaceState = function () { _replace.apply(this, arguments); scan(); }; window.addEventListener('popstate', scan); const observer = new MutationObserver(function () { scan(); }); observer.observe(document.documentElement, { childList: true, subtree: true }); // 高频轮询兜底 let pollCount = 0; const poll = setInterval(function () { scan(); pollCount++; if (pollCount > 600) clearInterval(poll); // 约 36 秒后停止,靠 observer 继续 }, 120); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', scan); } else { scan(); } } })();