Greasy Fork 支持简体中文。

OP.GG搜索增强

优化OP.GG页面的英雄搜索功能,支持使用中文诙谐名

// ==UserScript==
// @name         OP.GG搜索增强
// @namespace    http://tampermonkey.net/
// @version      1.0.3
// @description  优化OP.GG页面的英雄搜索功能,支持使用中文诙谐名
// @author       s-opgg
// @match        https://www.op.gg/modes/*
// @icon         https://opgg-static.akamaized.net/meta/images/lol/latest/champion/Jinx.png?image=e_upscale,c_crop,h_103,w_103,x_9,y_9/q_auto:good,f_webp,w_160,h_160
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @connect      www.op.gg
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const HERO_LIST_KEY = 'HeroList';
    const HERO_LIST_VERSION_KEY = "HeroListVersion";
    const LAST_UPDATE_TIME_KEY = "LastUpdateTime";
    const ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; // 1天的毫秒数
    let heroListData = [];
    let currentUrl = window.location.href;

    // 在页面加载时检查并更新英雄列表
    checkAndUpdateHeroList();

    // 添加手动更新菜单命令
    GM_registerMenuCommand('手动更新英雄搜索映射表', updateHeroList);

    // 监听 URL 变化,spa跳转时,重新执行run
    observeUrlChange();

    // 主逻辑
    run();

    // 主逻辑
    async function run() {
        console.log("[TamOPGG]run")

        // 初始化英雄列表数据
        heroListData = GM_getValue(HERO_LIST_KEY, []);
        await performDOMReplacement();
        // 隐藏广告、无关内容
        hiddenAd();

        try {
            const searchInput = await waitForElement("#cloneSearchInput")
            searchInput.addEventListener('input', debounce(handleInput, 500));
        } catch (e) {
            console.error("[TamOPGG]run", e)
        }
    };

    // 监听 URL 变化
    function observeUrlChange() {
        const observer = new MutationObserver(() => {
            const newUrl = window.location.href;
            if (newUrl !== currentUrl) {
                console.log("[TamOPGG] URL 发生变化,重新执行 run 函数");
                currentUrl = newUrl;
                run();
            }
        });

        // 监听 body 的变化(包括 URL 变化)
        observer.observe(document.body, { childList: true, subtree: true });
    }

    function handleInput() {
        // const championRankingMap = getChampionRankingMap();
        const inputValue = this.value.toLowerCase();
        // 根据输入的内容过滤匹配的英雄
        console.log("[TamOPGG]inputValue: ", inputValue)

        // 英雄选项卡
        const trList = document.querySelectorAll("#content-container aside table tbody tr")
        // 点击排行(强度 or 胜率)的时候,会改变index。
        // 直接每次都从img.src获取位置
        const imgList = document.querySelectorAll("#content-container aside table tbody tr img.bg-image")
        const matchAlias = [];
        heroListData.filter(({ keywords, alias }) => {
            const isMatchHero = keywords?.includes(inputValue);
            if (isMatchHero) {
                matchAlias.push(alias.toLowerCase())
            }
        });
        console.log("[TamOPGG]matchAlias: ", matchAlias)


        imgList.forEach((el, i) => {
            const curHeroDom = trList[i];
            const isMatchHero = matchAlias.includes(getAlisaByImgSrc(el.src));

            if (isMatchHero) {
                curHeroDom.style.display = "table-row"
            } else {
                curHeroDom.style.display = "none";
            }
        })
    }

    // 获取img.src上面的alias
    function getAlisaByImgSrc(url) {
        const regex = /\/([^\/]+)\.png/;
        const match = url.match(regex);
        return match[1].toLowerCase();
    }

    // 替换原来的input
    async function performDOMReplacement() {
        // 获取 search 输入框
        console.log("[TamOPGG]performDOMReplacement")

        try {
            const searchInput = await waitForElement('#filterChampionInput');
            // console.log('[TamOPGG]Element found:', searchInput);
            // 克隆输入框
            const clonedInput = searchInput.cloneNode(true);
            clonedInput.id = "cloneSearchInput";
            // 替换原输入框
            searchInput.parentNode.replaceChild(clonedInput, searchInput);
            // 在这里添加你要执行的操作
        } catch (error) {
            console.error(error);
        }
    }

    // 隐藏无关内容、广告
    function hiddenAd() {
        console.log("[TamOPGG]关闭广告");
        try {
            const adSelector = [".page-title", "#opgg-kit-house-image-banner", "#banner-container"];
            adSelector.forEach(selector => {
                waitForElements(selector).then(els => {
                    els.forEach(el => {
                        el.style.display = "none";
                    })
                })
            })
        } catch (e) {
            console.error("[TamOPGG]关闭广告", e)
        }
    }

    // 检查并更新英雄列表
    function checkAndUpdateHeroList() {
        const lastUpdateTime = GM_getValue(LAST_UPDATE_TIME_KEY, 0);
        const currentTime = Date.now();
        const localVersion = GM_getValue(HERO_LIST_VERSION_KEY, '');

        if (currentTime - lastUpdateTime > ONE_DAY_IN_MILLIS || !localVersion) {
            updateHeroList();
        } else {
            console.log('[TamOPGG] 英雄列表1天内已更新过,跳过(如需更新请手动)。');
        }
    }

    // 更新英雄列表
    function updateHeroList() {
        const url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js';

        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            onload: function (response) {
                if (response.status === 200) {
                    const {hero, version} = JSON.parse(response.responseText);
                    const lastVersion = GM_getValue(HERO_LIST_VERSION_KEY);
                    if(lastVersion === version) {
                        console.log('[TamOPGG] 英雄列表已是最新,版本号:', version);
                        return;
                    };

                    const heroList = hero.map(({ alias, keywords, name }) => ({
                        name,
                        alias,
                        keywords
                    }))
                    GM_setValue(HERO_LIST_KEY, heroList);
                    GM_setValue(HERO_LIST_VERSION_KEY, version);
                    GM_setValue(LAST_UPDATE_TIME_KEY, Date.now());
                    console.log(`[TamOPGG] 英雄列表更新成功,版本号: ${version}`);
                    console.log('[TamOPGG] 更新后的英雄列表:', heroList);
                } else {
                    console.error('[TamOPGG] 获取英雄列表失败:', response.statusText);
                }
            },
            onerror: function (error) {
                console.error('[TamOPGG] 请求出错:', error);
            },
        });
    }

    function waitForElement(selector, { timeout = 5000, single = true } = {}) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const observer = new MutationObserver((mutationsList) => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'childList') {
                        let result;
                        result = single ? document.querySelector(selector) : document.querySelectorAll(selector);

                        if ((single && result) || (!single && result.length > 0)) {
                            observer.disconnect();
                            resolve(result);
                            return;
                        }
                    }
                }
                if (Date.now() - startTime > timeout) {
                    observer.disconnect();
                    reject(new Error(`[enhancer]waitForElement: 元素"${selector}"没找到,within ${timeout}ms.`));
                }
            });

            const config = { childList: true, subtree: true };
            observer.observe(document.documentElement, config);
        });
    }

    function waitForElements(selector, { timeout = 5000 } = {}) {
        return waitForElement(selector, { timeout, single: false })
    }

    // 防抖:多次触发只执行最后一次
    function debounce(cb, delay) {
        let timer = null;

        return function (...args) {
            timer && clearTimeout(timer);
            timer = setTimeout(() => {
                cb.apply(this, args);
            }, delay)
        }
    }
})();