筛选ABDC

小工具

// ==UserScript==
// @name         筛选ABDC
// @namespace    http://tampermonkey.net/
// @version      2025-08-31-004
// @description  小工具
// @author       周利斌
// @match        https://so1.imageoss.com/*
// @match        https://so3.cljtscd.com/scholar*
// @match        https://scispace.com/*
// @match        https://scholar.google.com/*
// @match        https://*.webofscience.com/*
// @match        https://*.scopus.com/*
// @match        *://*/*
// @include      *webofscience*
// @include      *www-scopus-com*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=scholar.google.com
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==


(function () {
    "use strict";

    // 更新网址          https://greasyfork.org/zh-CN/scripts/533214-%E7%AD%9B%E9%80%89abdc
    // 更新网址          https://scriptcat.org/zh-CN/script-show-page/3808

    const btnStyle = "padding:5px;min-width: auto;"
    function appendTo(parentEle, tagName = "a", attrs = {}, functions = {}, id = "") {
        attrs = typeof attrs !== "string" ? attrs : { textContent: attrs };
        functions = typeof functions !== "function" ? functions : { click: functions };
        if (id && document.getElementById(id)) return document.getElementById(id)
        const ele = document.createElement(tagName);
        if (id) ele.id = id;
        for (const key in attrs) { ele[key] = attrs[key]; ele.setAttribute(key, attrs[key]); }
        for (const key in functions) ele.addEventListener(key, functions[key]);
        if (parentEle) parentEle.appendChild(ele);
        return ele;
    }
    function waitUtilAsync(callback, interval = 1000, timeout = 10000) {
        return new Promise((resolve, ) => {
            const start = Date.now();
            const intervalId = setInterval(() => {
                const d = callback();
                if (d) {
                    clearInterval(intervalId);
                    resolve(d);
                } else if (Date.now() - start > timeout) {
                    clearInterval(intervalId);
                    resolve(false);
                }
            }, interval);
        });
    }
    function runOnce(func, time = 100) {
        let locked = false;
        return function () {
            if (locked) return;
            locked = true;
            setTimeout(() => {
                func();
                locked = false;
            }, time);
        };
    }
    function getRootDiv() {
        const existingDiv = document.getElementById('z_root_div');
        if (existingDiv) {
            return existingDiv;
        }
        const rootDiv = document.createElement("div")
        rootDiv.id = "z_root_div"
        rootDiv.style = `
                    position: fixed;
                    z-index:999999;
                    background-color: rgba(198, 198, 198, 0.7);
                    cursor: move;
                    border:1px solid rgba(191, 70, 173, 0.7);
                    max-width: 50vw;
                    left: 50%;
                    top: 50%;
                    user-select:none !important;
                `;
        rootDiv.classList.add("notranslate");
        rootDiv.setAttribute("translate", 'no');
        document.body.appendChild(rootDiv);
        if (window.GM_getValue&&typeof GM_getValue !== "undefined") {
            let savedLeftPercent = GM_getValue('rootDivLeftPercent', 50); // 默认50%
            let savedTopPercent = GM_getValue('rootDivTopPercent', 50);
            if(savedTopPercent>100||savedTopPercent<0) savedTopPercent = 10            
            if(savedLeftPercent>100||savedLeftPercent<0) savedLeftPercent = 10
            
            rootDiv.style.left = savedLeftPercent + '%';
            rootDiv.style.top = savedTopPercent + '%';
        }
        let isDragging = false;
        let offsetX, offsetY;
        rootDiv.addEventListener('mousedown', (e) => {
            isDragging = true;
            // 暂时去掉transform以便计算精确位置
            offsetX = e.clientX - rootDiv.getBoundingClientRect().left;
            offsetY = e.clientY - rootDiv.getBoundingClientRect().top;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const newLeftPx = e.clientX - offsetX;
                const newTopPx = e.clientY - offsetY;
                rootDiv.style.left = newLeftPx + 'px';
                rootDiv.style.top = newTopPx + 'px';
            }
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                if ((rootDiv.style.left + "").indexOf("%") == -1) {
                    const leftPx = parseFloat(rootDiv.style.left);
                    const topPx = parseFloat(rootDiv.style.top);

                    let leftPercent = leftPx / document.documentElement.clientWidth * 100;
                    let topPercent = topPx / document.documentElement.clientHeight * 100;
                    leftPercent = Math.min(Math.max(leftPercent, 0), 100);
                    topPercent = Math.min(Math.max(topPercent, 0), 100);

                    rootDiv.style.left = leftPercent + '%';
                    rootDiv.style.top = topPercent + '%';
                    if (typeof GM_setValue !== "undefined") {
                        GM_setValue('rootDivLeftPercent', leftPercent);
                        GM_setValue('rootDivTopPercent', topPercent);
                    }
                }
            }
        });
        setTimeout(() => {
            if (rootDiv.childElementCount == 0) rootDiv.remove()
        }, 2000)
        return rootDiv;
    }
    // Your code here...
    waitUtilAsync(() => document.querySelectorAll(".gs_a") > 0, 100).then(() => {
        document.querySelectorAll(".gs_a").forEach(f => {
            f.classList.add("notranslate");
            f.setAttribute("translate", 'no');
        })
    })

    async function filterRank(selector = ".gs_r.gs_or.gs_scl", checkSelector = "input[type=checkbox]") {
        
        if(window.filterRankRegisted){
            return;
        }
        if (!(await waitUtilAsync(() => document.querySelector(selector)))) { 
            console.log("未找到", selector); setTimeout(()=>filterRank(selector,checkSelector),1000); return; }
        console.log("找到", selector); 
        if(window.filterRankRegisted){
            return;
        }else{            
            window.filterRankRegisted =1
        }
        if (!window.rankInfoObserver) {
            console.log("注册 rankInfoObserver")
            window.rankInfoObserver =
                new MutationObserver(mutations => {
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1 && (node.classList.contains('srankSpan')||node.classList.contains('srankDiv'))) {
                                console.log(`.srankdiv 的数量: ${document.querySelectorAll('.srankSpan,.srankDiv').length} 开启查询`);
                                queryFilterRunOnce()
                            }
                        });
                    });
                });
            window.rankInfoObserver.observe(document.body, { childList: true, subtree: true });
            console.log("注册 rankInfoObserver 成功",window.rankInfoObserver)
        }
        const divStat = appendTo(getRootDiv(), "span", "统计信息")
        String.prototype.asIF = function () { return parseFloat(this.match(/([\d.]+)$/g)?.[0]) || 0 }
        const RankFilter = {}
        function checkFunc(rks) {
            let hit=1
            if (RankFilter.ALL && RankFilter.ALL[1] == 0) {
                hit+=1 //
                for (const k of Object.keys(RankFilter)) {
                    if (k != "ALL") {
                        const [checkFun, q1] = RankFilter[k]
                        if (q1 == 0) continue //无效条件
                        const check = checkFun(rks)
                        if (q1 == 1 && !check) return 0 // 如果未能满足条件 排除
                        if (q1 == 2 && check) return 0 // 排除所有满足条件的
                        hit+=1
                    }
                }
            }
            return hit 
            //返回值 
            //0 有条件不符合要求被排除
            //1 选中了All,等于不进行查询直接返回所有结果
            //2 没选中All,但是也没返回0,也就是当前条件所有条件都是无效条件。
            //大于2 至少有一个条件满足了
        } 
        const queryFilterRunOnce = runOnce(queryFilter, 500)
        function queryFilter() {
            console.log(Object.entries(RankFilter).flat().flat())
            const qList = document.querySelectorAll(selector)
            let check = 0, checked = 0;
            for (const row of qList) {
                if (!row.querySelector(".srankInfo")) continue; //对没有等级的不处理
                const rks = [...row.querySelectorAll(".srankInfo")].map(a => a.textContent || "")
                const ck = checkSelector && row.querySelector(checkSelector)                
                const ck_result = checkFunc(rks)
                // console.log(ck,ckr)
                if (ck) { // wos 不隐藏未选中的
                    if (ck_result>1) {
                        row.children[0].style.backgroundColor =
                            getComputedStyle(row.querySelector(".srankInfo")).backgroundColor;
                        if (ck.checked) {
                            checked+=1;
                        } else {
                            ck.click();
                            check+=1;
                            console.log("点击了",check)
                        }
                    }
                console.log(check,checked)
                }
                else {//scholar 未选中隐藏
                    row.style.transition = "border-left .5s, max-height .5s, overflow-y .5s, padding .5s";
                    if (ck_result) {
                        if (row.tagName == "TR") {
                            row.style.display = ""
                        } else {
                            row.style.borderLeft = ""
                            row.style.maxHeight = "";
                            row.style.overflowY = ""
                            row.style.padding = ""
                        }
                        check++
                    }
                    else {
                        if (row.tagName == "TR") {
                            row.style.display = "none"
                        } else {
                            row.style.borderLeft = "5px solid #300"
                            row.style.maxHeight = "1px"
                            row.style.overflowY = "hidden"
                            row.style.padding = "0px"
                        }
                        checked++;
                    }
                }
            }
            //统计信息
            stat_info(qList, check, checked);
        }
        function createRankFilterBtn(btnTxt = "", checkFunc = () => false) {
            const btn = appendTo(getRootDiv(), "button", { type: "button", style: btnStyle, textContent: btnTxt }, () => {
                let q1 = RankFilter[btnTxt][1]
                q1 = (q1 + 1) % (btnTxt == "ALL" ? 2 : 3);
                GM_setValue("RankFilter" + btnTxt, q1)
                update_btn(btn, q1)
                queryFilterRunOnce()
            })
            update_btn(btn, window.GM_getValue&&typeof GM_getValue !== "undefined"&&GM_getValue("RankFilter" + btnTxt) * 1 || 0)
            function update_btn(btn, q1) {
                btn.style.background = [
                    "rgba(255, 255, 255, 0.7)",
                    "rgba(246, 163, 234, 0.7)",
                    "rgba(152, 198, 248, 0.7)"][q1]
                const pp = ["", "++", "--"][q1]
                btn.textContent = `${pp}${btnTxt}${pp}`;
                RankFilter[btnTxt] = [checkFunc, q1];
            }
        }
        createRankFilterBtn("ALL", () => true);
        createRankFilterBtn("ABDC A*/A", ms => ms.some(m => m.includes("ABDC A")));
        createRankFilterBtn("ABDC B", ms => ms.some(m => m.includes("ABDC B")));
        createRankFilterBtn("ABDC", ms => ms.some(m => m.includes("ABDC")));
        createRankFilterBtn("FMS", ms => ms.some(m => m.includes("FMS")));
        createRankFilterBtn("SSCI", ms => ms.some(m => m.includes("SSCI")));
        createRankFilterBtn("SCIE", ms => ms.some(m => m.includes("SCIE")));
        createRankFilterBtn("ESCI", ms => ms.some(m => m.includes("ESCI")));
        createRankFilterBtn("EI", ms => ms.some(m => m.includes("EI")));
        createRankFilterBtn("IF>1", ms => ms.some(m => m.startsWith("IF") && m.asIF() > 1));
        createRankFilterBtn("IF>2", ms => ms.some(m => m.startsWith("IF") && m.asIF() > 2));
        createRankFilterBtn("IF>8", ms => ms.some(m => m.startsWith("IF") && m.asIF() > 8));
        createRankFilterBtn("CiteScore", ms => ms.some(m => m.includes("CiteScore")));
        createRankFilterBtn("CiteScore>12", ms => ms.some(m => m.includes("CiteScore") && m.asIF() > 12));
        createRankFilterBtn("JCR Q1", ms => ms.some(m => m.includes("JCR Q1")));
        createRankFilterBtn("1区", ms => ms.some(m => m.includes("1区")));
        createRankFilterBtn("1、2区", ms => ms.some(m => m.includes("1区") || m.includes("2区")));
        createRankFilterBtn("4区", ms => ms.some(m => m.includes("4区")));
        createRankFilterBtn("区", ms => ms.some(m => m.includes("区")));
        createRankFilterBtn("医学", ms => ms.some(m => m.includes("医学")));
        createRankFilterBtn("材料", ms => ms.some(m => m.includes("材料")));
        createRankFilterBtn("物理", ms => ms.some(m => m.includes("物理")));
        createRankFilterBtn("化学", ms => ms.some(m => m.includes("化学")));
        createRankFilterBtn("生物", ms => ms.some(m => m.includes("生物")));
        if (document.querySelector("#gs_bdy")) {
            //设置谷歌查询结果的最小高度,让滚动条一直存在
            document.querySelector("#gs_bdy").style.minHeight = "600px";
        }
        function stat_info(qList, check = 0, checked = 0) {
            console.log("stat_info",check,checked)
            const checkCount = document.querySelector("#snRecListTop .mat-checkbox")?.textContent || "";
            const page = document.querySelector("#snNextPageTop")?.value || "";
            const pageAll = document.querySelector("body > app-wos > main > div > div > div.holder > div > div > div.held > app-input-route > app-base-summary-component > div > div.results.ng-star-inserted > app-page-controls.app-page-controls.ng-star-inserted > div > form > div")?.textContent || "";
            const rankLength =
                [...qList].map(a => a.querySelector(".srankInfo:not(.rcited,.ryear)")).filter(f => f).length;
            const birdLength = document.querySelectorAll(".scicrx-btn .scicrx-svgicon").length;
            const citeLength = document.querySelectorAll(".rcited").length;
            divStat.textContent = `【选中${check}/已选${checked}/当前页${qList.length}/总选中${checkCount} / 页码:${page} ${pageAll} / 引用${citeLength}等级${rankLength}小鸟${birdLength}】`;
        }
        queryFilter()
    }
    filterRank(".gs_r.gs_or.gs_scl", "") // scholar
    filterRank(".summary-record") // wos 


    async function initSortElements() {
        if (!(await waitUtilAsync(() => document.querySelector("#gs_res_ccl_mid")))) { console.log("未找到initSortElements"); return; }
        /**
         * 从节点中提取引用次数和 rimf 值,并计算综合得分
         * @param {HTMLElement} node - 要处理的节点
         * @returns {number} 综合得分
         */
        function getCiteCount(node) {
            const citeText = node.querySelector('a[href*="cites"]')?.textContent || "0";
            const citeInt = parseInt(citeText.match(/(\d+)/)?.[1], 10) || 0;
            const rimf = parseInt(node.querySelector('.rimf')?.getAttribute("val"), 10) || 0;
            return citeInt * 1000 + rimf;
        }
        let sortCount = 0
        /**
         * 对元素进行排序
         */
        function sortElements() {
            const gsResCclMid = document.getElementById('gs_res_ccl_mid');
            if (!gsResCclMid) {
                console.error('未找到 gs_res_ccl_mid 元素');
                return;
            }

            const gsOrElements = [...gsResCclMid.querySelectorAll('.gs_or')]
                .map(node => ({ node, citeCount: getCiteCount(node) }));

            const gsOrElementsSorted = [...gsOrElements].sort((a, b) => b.citeCount - a.citeCount);

            // 检查是否需要重新排序
            const needResort = gsOrElements.some((element, index) => element.node !== gsOrElementsSorted[index].node);
            if (strSort == "开启排序") {
                if (needResort) {
                    console.log("重排", ++sortCount)
                    gsResCclMid.innerHTML = '';
                    gsResCclMid.append(...gsOrElementsSorted.map(item => item.node)); // 重新添加排序后的节点
                    setTimeout(sortElements, 1000);
                } else {
                    setTimeout(sortElements, 5000);
                }
            }
        }

        let strSort = window.GM_getValue&&typeof GM_getValue !== "undefined"&&GM_getValue("btnSortElements", "禁用排序")
        const btnSortElements = appendTo(getRootDiv(), "button", { type: "button", style: btnStyle, textContent: strSort }, () => {
            strSort = strSort == "开启排序" ? "禁用排序" : "开启排序"
            GM_setValue("btnSortElements", strSort)
            btnSortElements.textContent = strSort;
            updateBtnSortElements()
        })
        function updateBtnSortElements() {
            if (strSort == "开启排序") {
                setTimeout(sortElements, 1000);
                btnSortElements.style.background = "#7ae3bc87"
            } else {
                btnSortElements.style.background = ""
            }
        }
        updateBtnSortElements()


    }
    initSortElements()

    if (location.href.indexOf("scispace") > -1) {
        filterRank("main table tr", "") // scispace
        waitUtilAsync(() => document.querySelector(".border-primary [data-icon=files]"))
            .then(() => {
                // scrollTo(0,650)
                let queryCount = 10;

                function sciSpaceAutoQuery() {
                    const queryBtn = document.querySelector(".border-primary [data-icon=files],.border-primary [data-icon=spinner-third]")?.parentElement
                    if (queryBtn) {
                        //queryBtn.scrollIntoView({ behavior: "smooth", block: "center" });
                        if (queryCount > 0) {
                            setTimeout(sciSpaceAutoQuery, 2000)
                            if (!queryBtn.getAttribute("disabled")) {
                                queryCount--;
                                queryBtn.click()
                            } 
                        }
                    } else {
                        queryToolBtn10.remove()
                        // queryToolBtn5.remove()
                    }
                } 
                const queryToolBtn10 = appendTo(getRootDiv(),
                    "button", { style: btnStyle, textContent: "Scispace加载更多" },
                    () => {
                        queryCount = 15;
                        sciSpaceAutoQuery()
                    }
                )                
                scrollTo(0,document.querySelector("table").getBoundingClientRect().top+(window.pageYOffset||document.documentElement.scrollTop)-60)
                document.querySelector(".border-primary [data-icon=files]").parentElement.click()
            })
    }
})();