// ==UserScript== // @name 米游社收藏自动排序为最新 // @namespace https://bbs.tampermonkey.net.cn/ // @version 1.2.0 // @description 无延迟自动选择排序为「最新」 // @author Mr.Zhong // @match https://www.miyoushe.com/*/collection/* // @match https://www.miyoushe.com/*/collection // @run-at document-end // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const TARGET_TEXT = '最新'; const DONE_FLAG = '__miyoushe_sort_latest_done__'; if (window[DONE_FLAG]) return; // 等两个渲染帧 —— 比 setTimeout 快,且能保证 Vue/React 完成 DOM 更新 const nextFrame = () => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)) ); const isVisible = el => { if (!el || !el.isConnected) return false; const s = getComputedStyle(el); if (s.display === 'none' || s.visibility === 'hidden' || s.opacity === '0') return false; return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length); }; const isActive = el => /active|selected|checked|current/i.test(el.className || ''); const findItem = () => { for (const el of document.querySelectorAll('.mhy-selectmenu__item')) { if ((el.textContent || '').trim() === TARGET_TEXT) return el; } return null; }; const getTrigger = item => { const sm = item.closest('.mhy-selectmenu'); if (!sm) return null; return sm.querySelector( '.mhy-selectmenu__selected, .mhy-selectmenu__input, .mhy-selectmenu__value, [class*="__selected"]' ) || sm; }; // 派发一整套鼠标事件,比 .click() 兼容性更好 const fire = el => { const opts = { bubbles: true, cancelable: true, view: window }; el.dispatchEvent(new MouseEvent('mousedown', opts)); el.dispatchEvent(new MouseEvent('mouseup', opts)); el.dispatchEvent(new MouseEvent('click', opts)); }; // 等待某个元素可见 —— MutationObserver 驱动,没有任何 sleep function waitUntilVisible(selector, matchFn, timeout = 1200) { return new Promise(resolve => { const check = () => { for (const el of document.querySelectorAll(selector)) { if (matchFn(el) && isVisible(el)) { resolve(el); return true; } } return false; }; if (check()) return; const mo = new MutationObserver(() => { if (check()) mo.disconnect(); }); mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); setTimeout(() => { mo.disconnect(); resolve(null); }, timeout); }); } async function pick() { const item = findItem(); if (!item) return false; if (isActive(item)) return true; // ① 先直接派发事件:绝大多数 UI 库不检查可见性,能直接选中、零闪烁 fire(item); await nextFrame(); if (isActive(item)) return true; // ② 没生效 → 展开下拉,等它可见的瞬间立刻点,没有 sleep const trigger = getTrigger(item); if (!trigger) return false; trigger.click(); const visible = await waitUntilVisible( '.mhy-selectmenu__item', el => (el.textContent || '').trim() === TARGET_TEXT ); if (!visible) return false; fire(visible); await nextFrame(); return true; } let tries = 0; const run = async () => { if (window[DONE_FLAG]) return; let ok = false; try { ok = await pick(); } catch (e) { /* ignore */ } if (ok) { window[DONE_FLAG] = true; console.log('[米游社排序] 已自动选择:' + TARGET_TEXT); mo.disconnect(); clearInterval(timer); return; } if (++tries > 15) { mo.disconnect(); clearInterval(timer); } }; const mo = new MutationObserver(() => { if (findItem()) run(); }); mo.observe(document.body, { childList: true, subtree: true }); const timer = setInterval(run, 600); run(); })();