Greasy Fork is available in English.

LeetCode 付费题目移除(兼容新版本力扣)

LeetCode 付费题目移除, 匹配 1.题目列表 2. 官方题单 3. 标签 tag 界面 4. 题目界面的左下角"题目列表"

// ==UserScript==
// @name         LeetCode 付费题目移除(兼容新版本力扣)
// @version      1.1.0.1
// @description  LeetCode 付费题目移除, 匹配 1.题目列表 2. 官方题单 3. 标签 tag 界面 4. 题目界面的左下角"题目列表"
// @namespace    https://greasyfork.org/zh-CN/users/412790
// @require      https://code.jquery.com/jquery-3.4.1.min.js
// @match        *://leetcode-cn.com/problemset/*
// @match        *://leetcode-cn.com/tag/*
// @match        *://leetcode-cn.com/problems/*
// @match        *://leetcode-cn.com/problem-list/*
// @grant        GM_addStyle
// @license      GPL-3.0-only
// ==/UserScript==
/* globals $, MyToast */

GM_addStyle(`
*{
margin: 0;
}
.my-toast{
position: fixed;
background: rgba(0,0,0,.7);
border-radius: 4px;
top: 93%;
left: 50%;
transform: translate(-50%,-50%);
max-width: 60%;
text-align: center;
transition: all .3s;
}
.my-toast-text{
color: #fff;
padding: 4px 10px;
padding-bottom: 6px;
}
`);

let hideCount = 0, prevCount = 0, urlID = 0, hidden;

const scriptName = "LEETCODE-PAID-REMOVE";

const problemList = new Map();

const checkInterval = 604800e3; // 86400e3 * 7, 检查题目列表更新的时间间隔, 一天 86400e3 微秒, 即每隔一周检查一次;

const removeElement = (element) => {
    element && element.parentNode && element.parentNode.removeChild(element);
};

const MyToast = function () {
    // Empty Function
};
MyToast.prototype = {
    create: function (str, duration) {
        let Text = '<div class="my-toast-text" style="z-index:1024">' + str + '</div>',
            Html = '<div class="my-toast" style="z-index:1024">' + Text + '</div>';
        if (document.querySelector(".my-toast")) {
            removeElement(document.querySelector(".my-toast"));
            if (typeof (hidden) !== "undefined") {
                clearTimeout(hidden);
            }
        }
        document.body.insertAdjacentHTML('beforeend', Html);
        if (duration == null) {
            duration = 2e3;
        }
        this.show();
        hidden = setTimeout(() => {
            this.hide();
        }, duration);
    },
    show: function () {
        document.querySelector(".my-toast").style.display = "block";
        document.querySelector(".my-toast").style.marginTop = "-" + Math.round(document.querySelector(".my-toast").offsetHeight / 2) + "px";
    },
    hide: function () {
        if (document.querySelector(".my-toast")) {
            prevCount = 0;
            hideCount = 0;
            removeElement(document.querySelector(".my-toast"));
        }
    },
    toast: function (str, duration) {
        console.log(str);
        return this.create(str, duration);
    }
};

const removeProblemSet = () => {
    let rowGroup = $("[role=\"rowgroup\"]");
    if (rowGroup.length <= 0) {
        return;
    }
    for (let childElement of rowGroup.children()) {
        const childJquery = $(childElement);
        const problemName = childJquery.find("div").get(1).innerText;
        const frontendQuestionId = parseProblemId(problemName);
        if (problemList.get(frontendQuestionId)) {
            childJquery.hide();
            hideCount++;
        } else {
            childJquery.show();
        }
    }
    if (hideCount !== prevCount) {
        prevCount = hideCount;
        simpleToast.toast(`已隐藏付费题目 ${hideCount} 道`);
    }
};

const removeTag = () => {
    let imgCollections = document.querySelectorAll(".ant-table-tbody img");
    for (let img of imgCollections) {
        if (img.getAttribute("myhidden") === "hidden") {
            continue;
        }
        const node = getLevel0ParentNode(0, img);
        if (node != null) {
            img.setAttribute("myHidden", "hidden");
            node.style.display = "none";
            hideCount++;
        }
    }
    if (hideCount !== prevCount) {
        prevCount = hideCount;
        simpleToast.toast(`已隐藏付费题目 ${hideCount} 道`);
    }
};

const getLevel0ParentNode = (depth, node) => {
    if (!node || depth === 10) {
        return null;
    }
    if (node.className.match("ant-table-row ant-table-row-level-0")) {
        return node;
    } else if (node.parentNode) {
        return getLevel0ParentNode(depth + 1, node.parentNode);
    } else {
        return null;
    }
};

const addCss = () => {
    GM_addStyle(`div[data-paid-only="true"]{display:none!important}`);
};

const parseProblemId = (problemName) => {
    let problemId = "";
    for (let char of problemName) {
        if (char === ".") {
            return problemId;
        } else {
            problemId += char;
        }
    }
    return null;
}

const config = {
    childList: true,
    subtree: true,
    attributes: true,
    characterData: true,
    attributeOldValue: false,
    characterDataOldValue: false,
    attributeFilter: []
};

let Observer = new MutationObserver(() => {
        if (!document.querySelector("[role=\"rowgroup\"]") && !document.querySelector(".ant-table-content")) {
            Observer.disconnect();
            addObserverIfDesiredNodeAvailable();
        }
        work();
    }),
    simpleToast = new MyToast();


const addObserverIfDesiredNodeAvailable = () => {
    if (document.querySelector("[role=\"rowgroup\"]")) {
        Observer.observe(document.querySelector("[role=\"rowgroup\"]"), config);
        work();
    } else if (document.querySelector(".ant-table-content")) {
        Observer.observe(document.querySelector(".ant-table-content"), config);
        work();
    } else {
        setTimeout(() => addObserverIfDesiredNodeAvailable());
    }
};

const prepare = () => {
    const url = window.location.href;
    if (/(leetcode-cn.com\/problemset\/)/.test(url)) {
        urlID = 0;
        coreInit();
    } else if (/(leetcode-cn.com\/tag\/)/.test(url) || /(leetcode-cn.com\/problem-list\/)/.test(url)) {
        urlID = 1;
        coreInit();
    } else {
        urlID = 2;
        addCss();
    }
};

const checkNewWeek = (ts) => {
    if (ts == null) {
        return true;
    }
    ts = parseInt(ts);
    const t = new Date(ts);
    const tzOffset = new Date().getTimezoneOffset() + 480;
    t.setMinutes(t.getMinutes() + tzOffset);
    t.setHours(0, 0, 0, 0);
    const d = new Date();
    d.setMinutes(t.getMinutes() + tzOffset);
    return (d - t > checkInterval);
}

const getProblemMsg = "开始请求全部题目信息", refreshProblemCacheMsg = "本地题目缓存过期, 开始请求全部题目信息";

const coreInit = () => {
    if (!localStorage.getItem(scriptName + "-PROBLEM-LIST")) {
        getProblems(getProblemMsg);
    } else if (checkNewWeek(localStorage.getItem(scriptName + "-PROBLEM-UPDATE-TIME"))) {
        getProblems(refreshProblemCacheMsg);
    } else {
        buildMap();
    }
}

const getProblems = (msg) => {
    simpleToast.toast(msg);
    const startTime = new Date();
    $.get({
        url: 'https://leetcode-cn.com/api/problems/all/'
    }).then((response) => {
        writeLocalStorage(response, startTime);
        buildMap();
    });
};

const writeLocalStorage = (response, startTime) => {
    localStorage.setItem(scriptName + "-PROBLEM-UPDATE-TIME", Date.now().toString());
    localStorage.setItem(scriptName + "-PROBLEM-LIST", response);
    const endTime = new Date();
    simpleToast.toast(`获取并缓存题目数据成功, 耗时 ${new Date(endTime - startTime).getSeconds()} 秒, 将在一周之后再次更新缓存`);
}

const buildMap = () => {
    for (const item of JSON.parse(localStorage.getItem(scriptName + "-PROBLEM-LIST"))["stat_status_pairs"]) {
        problemList.set(item["stat"]["frontend_question_id"], item["paid_only"]);
    }
    addObserverIfDesiredNodeAvailable();
}

const work = () => {
    if (urlID === 0) {
        try {
            removeProblemSet();
        } catch (e) {
        }
    } else if (urlID === 1) {
        try {
            removeTag();
        } catch (e) {
        }
    }
};

prepare();

window.onpopstate = () => {
    Observer.disconnect();
    addObserverIfDesiredNodeAvailable();
};