PMT QoL Package

A bunch of tools to enhance your experience on the PMT website.

// ==UserScript==
// @name         PMT QoL Package
// @namespace    https://ejaz.is-a.dev/scripts
// @version      1.3.4
// @description  A bunch of tools to enhance your experience on the PMT website.
// @author       Ejaz Ali
// @match        https://www.physicsandmathstutor.com/*
// @match        https://pmt.physicsandmathstutor.com/*
// @match        https://www.physicsandmathstutor.com/pdf-pages/*
// @match        https://pmt.physicsandmathstutor.com/pdf-pages/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=physicsandmathstutor.com
// @license      GPL3.0
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    const tts = Date.now();

    const verInformation = {
        ver: "1.3.4",
        description: ["Blocking the store link in the header", "Fixed videos not loading when ad-blocking is enabled", "Fixed flashcards not loading when ad-blocking is enabled", "PMT have begun adding retention ads to try and bypass PMT QoL, these have also been blocked", "You now have statistics"]
    }

    const baseSettings = {
        ad_blocking: true, // Attempts to patch ads from the DOM level, not the network level.
        native_ad_blocking: true, // Blocks native advertisements

        pdf_bypass: true, // Bypasses
        pdf_newTab: false, // Launches PDFs in New Tabs
        pdf_cloak: false, // Cloaks the PDF viewer rather than attempts to bypass, better for high-latency connections that can't handle redirects that much.

        pinned_topics: true, // Allows topics and PDFs to be pinned

        // EXPERIMENTS
        pdf_link_inject: false // EXPERIMENT: Injects PDF bypass into links as well on the PMT website
    }

    const baseStats = {
        adsBlocked: 0,
        nativeAdsBlocked: 0,
        pdfBypassed: 0,
    }

    var settings = baseSettings

    const loclst = window.localStorage;
    const propSettings = loclst.getItem("PMTQoL_settings");

    if (propSettings == null || propSettings == undefined) {
        loclst.setItem("PMTQoL_settings", btoa(JSON.stringify(settings)))
    } else {
        settings = JSON.parse(atob(propSettings));
        Object.keys(baseSettings).forEach((item) => {
            if (!Object.keys(settings).includes(item)) {
                settings[item] = baseSettings[item];
                console.log("PMT QoL: New settings have been discovered, adding them to the registry.")
            }
        })
        loclst.setItem("PMTQoL_settings", btoa(JSON.stringify(settings)))
    }



    var statistics = baseStats;
    const propStatistics = loclst.getItem("PMTQoL_statistics");

    if (propStatistics == null || propStatistics == undefined) {
        loclst.setItem("PMTQoL_statistics", btoa(JSON.stringify(statistics)));
    } else {
        statistics = JSON.parse(atob(propStatistics))
    }

    // We want to bypass as fast as possible
    function pdfBypassSpeed() {
        console.log(`Bypassed  ${Date.now() - tts}ms after`)
        try {
            if (window.location.pathname.includes("pdf-pages")) {
                if (settings.pdf_bypass) {
                    const pdf = new URL(window.location).searchParams.get("pdf");
                    const pdfDecoded = decodeURIComponent(pdf);
                    incrementStat("pdf-bypass")

                    if (settings.pdf_newTab) {
                        window.open(pdfDecoded, "_BLANK");
                        history.back()
                    } else {
                        window.location.replace(pdfDecoded)
                    }
                }
            }
        } catch(err) {}
    }

    pdfBypassSpeed();

    function pageTypeWorkflow(URL) {
        if (window.location.pathname == "/") {
            indexPage();
        }
        if (!window.location.pathname.includes("pdf-pages")) {
            mainPages();
        } else {
            pdfPages();
        }

        if (window.location.pathname == "/ejaz/qol-settings") {
            settingsPage();
        }

        if (window.location.pathname == "/ejaz/qol-pinned") {
            pinsPage();
        }
    }

    function pinsPage() {
        document.title = "PMT QoL Pins";
        var losa = window.localStorage;
        var pins = losa.getItem("PMTQoL_pins");

        if (pins == undefined || pins == null) {
                pins = [];
                losa.setItem("PMTQoL_pins", btoa(JSON.stringify(pins)))
        } else {
            try {
            pins = JSON.parse(atob(pins))
            } catch(err) {
                alert("There is a problem with your pins.")
                pins = [];
            }
        }

        if (pins.length == 0) {
            document.getElementById("content-full").innerHTML = `You have no pins 😭<br>Click the pin button above a topic and it will show here.`
        } else {
            document.getElementById("content-full").innerHTML = `<h2>Pins</h2>`;

            pins.forEach((item) => {
                const pin = document.createElement("a");
                pin.innerText = item.title;
                pin.href = item.complete;
                const br = document.createElement("br")

                document.getElementById("content-full").appendChild(pin)
                document.getElementById("content-full").appendChild(br)
            })
        }
    }

    function settingsPage() {
        document.title = "PMT QoL Settings"
        document.getElementById("content-full").innerHTML = `
        <div>
            <h2>PMT QoL Settings</h2>
            <p>PMT QoL is a user-script extension that is currently running to enhance your experience on the PMT website.</p>
            <p>If you do not remember installing it, you should <a href="https://www.wikihow.com/Delete-a-Script-in-Tampermonkey">remove it</a>.</p>
            <div>
                <h3>Advertisements</h3>
                <input type="checkbox" id="ad_blocking" name="ad_blocking" ${settings.ad_blocking ? "checked" : ""}>
                <label for="ad_blocking">Hide Advertisements</label>
                <p style="margin-top: 0;">Adverts are hidden on the DOM level. This means requests to ads are still being made, but they aren't shown to the user.<br>
                While this works well, you should consider using an adblock extension like <a href="https://ublockorigin.com/">uBlock Origin</a>, which is a lightweight content blocker.</p>

                <input type="checkbox" id="native_ad_blocking" name="native_ad_blocking" ${settings.native_ad_blocking ? "checked" : ""}>
                <label for="native_ad_blocking">Native Advertisement Blocking</label>
                <p style="margin-top: 0;">Native advertisments are made by PMT internally to promote other PMT services. Bear in mind, this feature is very experimental and doesn't catch everything.</p>

                <h3>PDF Viewer</h3>
                <p style="margin-top: 0;">PMT uses a custom PDF viewer that is shown to the user instead of the system's default PDF viewer.<br>
                This means that PMT is injecting adverts and modifying the appearance of the PDF viewer as they please.</p>

                <input type="checkbox" id="pdf_bypass" name="pdf_bypass" ${settings.pdf_bypass ? "checked" : ""}>
                <label for="pdf_bypass">Bypass Custom PDF Viewer</label>
                <p style="margin-top: 0;">If this is enabled, all your other PDF settings will not work.<br>
                In this mode, PMT's custom PDF viewer is completely bypassed and you will be redirect to the system PDF viewer.<br>
                This is the experience that most other sites give you.</p>

                <input type="checkbox" id="pdf_newTab" name="pdf_newTab" ${settings.pdf_newTab ? "checked" : ""}>
                <label for="pdf_newTab">Launch PDFs in New Tabs</label>
                <p style="margin-top: 0;">This works after the PDF has been launched.<br>
                The current page will go back and a new tab will open with the PDF.<br>
                This works even when bypassing.</p>

                <input type="checkbox" id="pdf_cloak" name="pdf_cloak" ${settings.pdf_cloak ? "checked" : ""}>
                <label for="pdf_cloak">Cloak Custom PDF Viewer (experimental)</label>
                <p style="margin-top: 0;">This is for people who have internet connections with high latency and cannot use PDF Bypass.<br>
                Requires <b>Bypass Custom PDF Viewer</b> to be OFF.<br>
                In this mode, the custom PDF viewer will be injected to look like the system PDF viewer.<br>
                It may be very restrictive.</p>

                <h3>Enhancements</h3>
                <p style="margin-top: 0;">Features that just makes things easier.</p>

                <input type="checkbox" id="pinned_topics" name="pinned_topics" ${settings.pinned_topics ? "checked" : ""}>
                <label for="pinned_topics">Allow pinning topics to quickly access them later</label>
                <p style="margin-top: 0;">When this setting is ON, a new section in the header will display and show all of your Pinned Topics<br>
                To pin a topic, click the link that is shown next to the title of a topic.</p>

                <a href="#unfinished-features" id="unfinished-trigger">Unfinished features and development</a>

                <div id="unfinished-features">
                    <input type="checkbox" id="pdf_link_inject" name="pdf_link_inject" ${settings.pdf_link_inject ? "checked" : ""}>
                    <label for="pdf_link_inject">Bypass PDFs by injecting their links as well</label>
                    <p style="margin-top: 0;">Requires <b>Bypass Custom PDF Viewer</b> to be ON.<br>This is an experimental fix for an issue that is caused when opening a PDF page that isn't focused.</p>
                </div>

                <style>
                    #unfinished-features {
                        display: none;
                    }
                    #unfinished-features:target {
                        display: block;
                    }
                </style>
            </div>
        </div>
        `;
        Object.keys(settings).forEach((item) => {
            console.log(item)
            document.getElementById(item).addEventListener("change", (e) => {
                const value = document.getElementById(item).checked;
                settings[item] = value;

                const ls = window.localStorage;
                ls.setItem("PMTQoL_settings", btoa(JSON.stringify(settings)));
            })
        })
    }

    function pdfPages() {
        if (!settings.pdf_bypass) {
            if (settings.pdf_newTab) {
                const Nt = new URL(window.location).searchParams.get("QoLNt");
                if (Nt != "true") {
                    incrementStat("pdf-bypass")
                    window.open(window.location.href + "&QoLNt=true", "_blank")
                    history.back()
                }
            }
        } else {
            console.log("The page should not be in this state as it was expected to bypass ages ago. Retrying that.")
            let attemptNo = 1;
            setTimeout(() => {
                console.log(`Attempt number ${attemptNo}`)
                pdfBypassSpeed();
                attemptNo++
            }, 1000);
        }
        if (settings.ad_blocking) {
            adBlocking()
            setInterval(() => {
                adBlocking()
            }, 100)
        }

        if (settings.pdf_cloak) {
            console.log("Cloaking PDF");
            const pdfContent = document.getElementById("pdf-content");
            const style = document.createElement("style");
            style.innerHTML = `#pdf-content iframe { width: 100vw; height: 100vh; top: 50%; left: 50%; transform: translate(-50%, -50%); position: fixed; }`;
            document.body.appendChild(style);
            console.log("Pass 1");
            const ifr = pdfContent.firstElementChild;
            ifr.setAttribute("style", "width: 100vw; height: 100vh; top: 50%; left: 50%; transform: translate(-50%, -50%); position: fixed;")
            console.log("Pass 2");
        }
    }

    function mainPages() {
        const header = document.getElementsByClassName("ubermenu-nav")[0];
        //<li id="menu-item-42954" class="ubermenu-item ubermenu-item-type-post_type ubermenu-item-object-page ubermenu-item-42954 ubermenu-item-level-0 ubermenu-column ubermenu-column-auto">
        //	<a class="ubermenu-target ubermenu-item-layout-default ubermenu-item-layout-text_only" href="https://www.physicsandmathstutor.com/contact/" tabindex="0">
        //		<span class="ubermenu-target-title ubermenu-target-text">Contact</span>
        //	</a>
        //</li>
        if (settings.ad_blocking) {
            adBlocking()
            setInterval(() => {
                adBlocking()
            }, 100)
        }

        const settingButtonLink = document.createElement("li");
        settingButtonLink.className = "ubermenu-item ubermenu-item-type-post_type ubermenu-item-object-page ubermenu-item-level-0 ubermenu-column ubermenu-column-auto";

        const settingButtonA = document.createElement("a");
        settingButtonA.innerText = "QoL Settings"
        settingButtonA.className = "ubermenu-target ubermenu-item-layout-default ubermenu-item-layout-text_only"
        settingButtonA.href = "/ejaz/qol-settings"

        settingButtonLink.appendChild(settingButtonA)

        header.appendChild(settingButtonLink)

        if (settings.pdf_bypass) {
            if (settings.pdf_link_inject) {
                console.log("Injecting links...");

                const links = document.getElementsByTagName("a");

                for (const link of links) {
                    try {
                        const href = new URL(link.href);
                        if (href.pathname == "/pdf-pages/") {
                            const encodedPdf = href.searchParams.get("pdf");
                            const decodedPdf = decodeURIComponent(encodedPdf);
                            link.href = new URL(decodedPdf);
                        }
                    } catch(e) {
                        console.error(`ERROR: ${e}`)
                    }
                }
            }
        }

        if (settings.native_ad_blocking) {
            document.getElementById("menu-item-44285").remove();
            nativeAdBlocking();
            setInterval(() => {
                nativeAdBlocking()
            }, 100)
        }

        if (settings.pinned_topics) {
            const pinButtonLink = document.createElement("li");
            pinButtonLink.className = "ubermenu-item ubermenu-item-type-post_type ubermenu-item-object-page ubermenu-item-level-0 ubermenu-column ubermenu-column-auto";

            const pinButtonA = document.createElement("a");
            pinButtonA.innerText = "Pinned Topics"
            pinButtonA.className = "ubermenu-target ubermenu-item-layout-default ubermenu-item-layout-text_only"
            pinButtonA.href = "/ejaz/qol-pinned"

            pinButtonLink.appendChild(pinButtonA)

            header.appendChild(pinButtonLink)

            var pins = loclst.getItem("PMTQoL_pins");
            if (pins == undefined || pins == null) {
                pins = [];
                loclst.setItem("PMTQoL_pins", btoa(JSON.stringify(pins)))
            } else {
                try {
                pins = JSON.parse(atob(pins))
                } catch(err) {
                    alert("There is a problem with your pins.")
                    pins = [];
                }
            }

            var URLs = pins.map(x => x.url);

            try {
                const title = document.getElementsByClassName("entry-title")[0];


                title.innerHTML += ` (<a id='pinBtn' href='#pin'>${URLs.includes(window.location.pathname) ? "Remove" : "Pin"}</a>)`

                document.getElementById("pinBtn").addEventListener("click", (e) => {
                    var losa = window.localStorage;

                    pins = loclst.getItem("PMTQoL_pins");
                    if (pins == undefined || pins == null) {
                        pins = [];
                        losa.setItem("PMTQoL_pins", btoa(JSON.stringify(pins)))
                    } else {
                        try {
                        pins = JSON.parse(atob(pins))
                        } catch(err) {
                            alert("There is a problem with your pins.")
                            pins = [];
                        }
                    }

                    URLs = pins.map(x => x.url);
                    const elem = document.getElementById("pinBtn");
                    if (!URLs.includes(window.location.pathname)) {
                        pins.push({
                            url: window.location.pathname,
                            complete: window.location.href,
                            title: document.title,
                        });

                        loclst.setItem("PMTQoL_pins", btoa(JSON.stringify(pins)))
                        elem.innerText = "Remove"
                    } else {
                        pins.forEach((item, index) => {
                            if (item.url == window.location.pathname) {
                                pins.splice(index, 1);
                            }
                        })

                        loclst.setItem("PMTQoL_pins", btoa(JSON.stringify(pins)))
                        elem.innerText = "Pin"

                    }
                })

            } catch(err) {

            }
        }

        document.getElementsByClassName("copyright")[0].innerHTML += "& PMT QoL (Ejaz Ali)";
    }

    function indexPage() {
        // When the index page is ran.

        const whatsNewContainer = document.createElement("div")
        whatsNewContainer.classList.add("dropshadowboxes-container");

        const whatsNew = document.createElement("div");
        whatsNew.className = "dropshadowboxes-drop-shadow dropshadowboxes-rounded-corners dropshadowboxes-inside-and-outside-shadow dropshadowboxes-lifted-both dropshadowboxes-effect-default";
        whatsNew.setAttribute("style", "border: 2px solid #DDD; background-color: white;");

        const title = document.createElement("h3");
        title.innerText = "What's New?"

        const description = document.createElement("ul");
        for (const up of verInformation.description) {
            const li = document.createElement("li");
            li.style.textAlign = "left";
            li.innerText = up;
            description.appendChild(li)
        }
        //description.innerText += "\nTo learn more about the security content of PMT QoL and audit the code visit the Greasyfork page."

        const version = document.createElement("a");
        version.innerText = verInformation.ver;

        whatsNew.appendChild(title);
        whatsNew.appendChild(version);
        whatsNew.appendChild(description);

        whatsNewContainer.appendChild(whatsNew);

        document.getElementsByClassName("col-300")[0].appendChild(whatsNewContainer);



        const statisticsContainer = document.createElement("div")
        statisticsContainer.classList.add("dropshadowboxes-container");

        const statisticsElem = document.createElement("div");
        statisticsElem.className = "dropshadowboxes-drop-shadow dropshadowboxes-rounded-corners dropshadowboxes-inside-and-outside-shadow dropshadowboxes-lifted-both dropshadowboxes-effect-default";
        statisticsElem.setAttribute("style", "border: 2px solid #DDD; background-color: white;");

        const titleA = document.createElement("h3");
        titleA.innerText = "Statistics"

        const individStats = document.createElement("ul");


        if (settings.ad_blocking) {
            const li = document.createElement("li");
            li.style.textAlign = "left";
            li.innerText = "Ads blocked: ";

            setInterval(() => {
                const ls = window.localStorage;
                const internalStats = JSON.parse(atob(ls.getItem("PMTQoL_statistics")));

                li.innerText = `Ads blocked: ${internalStats.adsBlocked}`;
            }, 100)
            individStats.appendChild(li)
        }

        if (settings.native_ad_blocking) {
            const li = document.createElement("li");
            li.style.textAlign = "left";
            li.innerText = "Native Ads Blocked: ";
            setInterval(() => {
                const ls = window.localStorage;
                const internalStats = JSON.parse(atob(ls.getItem("PMTQoL_statistics")));

                li.innerText = `Native Ads Blocked: ${internalStats.nativeAdsBlocked}`;
            }, 100);

            individStats.appendChild(li)
        }

        if (settings.pdf_bypass) {
            const li = document.createElement("li");
            li.style.textAlign = "left";
            li.innerText = "PDFs Bypassed: ";
            setInterval(() => {
                const ls = window.localStorage;
                const internalStats = JSON.parse(atob(ls.getItem("PMTQoL_statistics")));

                li.innerText = `PDFs Bypassed: ${internalStats.pdfBypassed}`;
            }, 100);

            individStats.appendChild(li)
        }

        statisticsElem.appendChild(titleA);
        statisticsElem.appendChild(individStats);

        statisticsContainer.appendChild(statisticsElem);

        document.getElementsByClassName("col-300")[2].appendChild(statisticsContainer);

    }

    function nativeAdBlocking() {
        // Tutors
        try {
            const tutors = document.getElementsByClassName("tutor-profile-box");
            for (const item of tutors) {
                console.log("Killed native tutor ad")
                item.remove()
                incrementStat("native-ads")
            }
        } catch(error) {}

        // Contact Button
        try { document.getElementById("menu-item-42954").remove(); incrementStat("native-ads") } catch(e) {}

        // Teachers Area
        try { document.getElementById("menu-item-46223").remove(); incrementStat("native-ads") } catch(e) {}

        // Teachers Area
        try { document.getElementById("menu-item-48616").remove(); incrementStat("native-ads") } catch(e) {}
    }

    function incrementStat(stat) {
        const ls = window.localStorage;
        let stats = JSON.parse(atob(ls.getItem("PMTQoL_statistics")));

        if (stat == "ads") {
            stats.adsBlocked++;
        }

        if (stat == "native-ads") {
            stats.nativeAdsBlocked++
        }

        if (stat == "pdf-bypass") {
            stats.pdfBypassed++
        }

        ls.setItem("PMTQoL_statistics", btoa(JSON.stringify(stats)));
    }

    function adBlocking() {
        try { document.getElementById("gostory").remove(); console.log("Killed native ad"); incrementStat("ads") } catch(err) {}
        try { document.getElementById("PMT_Top").remove(); console.log("Killed top ad"); incrementStat("ads") } catch(err) {}
        try { document.getElementById("sidebar_ads").remove(); console.log("Killed sidebar ads"); incrementStat("ads") } catch(err) {}
        try { document.getElementById("sidebarSection").remove(); console.log("Killed sidebar ads"); incrementStat("ads") } catch(err) {}
        try { document.getElementsByClassName("pmt_eoc_parrent")[0].remove(); console.log("Killed sidebar ads"); incrementStat("ads") } catch(err) {}
        try { document.getElementsByClassName("ad-hpmpu")[0].remove(); console.log("Killing retention advert (this implementation needs to be fixed)."); incrementStat("ads")} catch(err) {}
        try {
            const stickies = document.getElementsByClassName("PMT_Desktop_Sticky");
            for (const item of stickies) {
                console.log("Killed sticky ad")
                item.remove()
                incrementStat("ads")
            }
        } catch(error) {}
        try {
            const frames = document.getElementsByTagName("iframe");
            for (const item of frames) {
                const kill = iFrameDeathAllowed(window.location, item.src);
                if (kill) {
                    console.log("Killing frame ad (risky move, will improve in a future release)")
                    item.remove()
                    incrementStat("ads")
                } else {

                }
            }
        } catch(error) {}
        try {
            const scripts = document.getElementsByTagName("script");
            for (const item of scripts) {
                if (window.location.pathname.includes("pdf-pages")) {
                    console.log("Killing script, there is no need for these on this type of page.")
                    item.remove()
                    incrementStat("ads")
                } else {

                }
            }
        } catch(error) {}
    }

    function iFrameDeathAllowed(location, src) {
        const readableSrc = new URL(src, window.location)
        const iframeHost = readableSrc.host;
        const allowedHosts = ["www.youtube.com", "docs.google.com"]

        if (location.pathname.includes("pdf-pages")) {
            return false;
        }

        for (const host of allowedHosts) {
            if (iframeHost == host) {
                return false;
            }
        }

        return true;
    }

    document.body.style.display = "none"
    try {
        pageTypeWorkflow(window.location);
        document.body.style.display = "block"
    } catch(err) {
        document.body.style.display = "block"
        const p = document.createElement("p");
        console.error(err);
        p.innerText = "Something went wrong with PMT QoL, please report this to me on Twitter (@ninnmus)"
        document.body.append(p)
    }

    window.addEventListener("load", (event) => {
        if (window.location.pathname.includes("pdf-pages")) {
            pdfPages();
        }
    });
})();