Redgifs Tweaks

tweaks for redgifs page

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name                Redgifs Tweaks
// @namespace           https://greasyfork.org/users/821661
// @match               https://www.redgifs.com/*
// @exclude-match       https://www.redgifs.com/ifr/*
// @grant               GM_addStyle
// @grant               GM_xmlhttpRequest
// @run-at              document-start
// @version             0.4.9a
// @author              hdyzen
// @description         tweaks for redgifs page
// @license             MIT
// @noframes
// ==/UserScript==

const gifsURLS = new Map();

function observerInit() {
    const mutationsHandler = (mutations) => {
        for (const mutation of mutations) {
            if (mutation.type === "attributes" && mutation.target.classList.contains("GifPreview")) {
                const likeButton = mutation.target.querySelector(".sideBarItem:has(.LikeButton)");

                if (!likeButton || likeButton.nextElementSibling.querySelector(".download-button")) return;

                const gifID = mutation.target.id.split("_")[1];
                likeButton.insertAdjacentHTML("afterend", getDownloadButton(gifID));

                const entriesDL = likeButton.nextElementSibling.querySelectorAll("[data-url]");
                addListenerToEntries(entriesDL);
            }
        }
    };

    const observer = new MutationObserver(mutationsHandler);

    observer.observe(document.documentElement, {
        subtree: true,
        attributes: true,
        attributeFilter: ["class"],
    });
}
observerInit();

function patchJSONParse() {
    const originalJParse = JSON.parse;

    JSON.parse = function (text, reviver) {
        const result = originalJParse.call(this, text, reviver);

        if (Array.isArray(result.gifs)) {
            result.gifs = result.gifs.filter((gif) => {
                if (gif.cta !== null) return false;

                gifsURLS.set(gif.id, gif.urls);
                return true;
            });
        }

        return result;
    };
}
patchJSONParse();

function addListenerToEntries(entries) {
    for (const entry of entries) {
        entry.addEventListener("click", downloadAsBlob);
    }
}

async function downloadAsBlob(ev) {
    const vUrl = ev.target.dataset.url;
    const abort = ev.target._abortReq?.abort;

    if (abort) abort();

    const dlPromise = new Promise((resolve, reject) => {
        const req = GM_xmlhttpRequest({
            method: "GET",
            url: vUrl,
            responseType: "blob",

            onload: (res) => resolve(res),
            onerror: (err) => reject(err),
            onabort: () => reject(new Error("aborted")),

            onprogress(evt) {
                const loaded = (evt.loaded / evt.total) * 100;
                const progress = (loaded / 100) * ev.target.offsetWidth;

                ev.target.style.boxShadow = `${progress}px 0 0 0 rgba(192, 28, 119, 0.5) inset`;
            },
        });

        ev.target._abortReq = req;
    });

    try {
        const res = await dlPromise;
        const blobUrl = URL.createObjectURL(res.response);
        const link = document.createElement("a");
        link.href = blobUrl;
        link.download = vUrl.split("/").at(-1);

        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(blobUrl);
    } catch (err) {
        console.log("Erro on download:", err);
    }
}

function getDownloadButton(gifID) {
    const gifURLS = gifsURLS.get(gifID);
    let buttonsHTML = "";
    const buttonsOrders = {
        hd: "0",
        sd: "1",
        silent: "2",
        poster: "3",
        thumbnail: "4",
    };

    for (const key in gifURLS) {
        if (key === "html") continue;

        buttonsHTML += `<button data-url="${gifURLS[key]}" style="order: ${buttonsOrders[key] || "5"}" class="item">${key}</button>`;
    }

    const sidebarItem = `
        <li class="sideBarItem">
            <div class="download-button">
                <label for="${gifID}" class="icon">
                    <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512" width="42">
                        <path d="M336 176h40a40 40 0 0140 40v208a40 40 0 01-40 40H136a40 40 0 01-40-40V216a40 40 0 0140-40h40"
                            fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32">
                        </path>
                        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"
                            d="M176 272l80 80 80-80M256 48v288"></path>
                    </svg>
                </label>
                <input type="checkbox" name="${gifID}" id="${gifID}" hidden>
                <div class="list">
                    ${buttonsHTML}
                </div >
            </div >
        </li>
    `;

    return sidebarItem;
}

GM_addStyle(`
    /* Annoyances */
    .sideBarItem:has(.liveAdButton), .InformationBar_ishalloween {
        display: none !important;
    }
    .topNav-wrap > div:not([class]), .OnlyFansCreatorsSidebar {
        visibility: hidden !important;
        opacity: 0 !important;
    }
    /* Download button/list */
    .download-button {
        position: relative;
    }
    .download-button svg {
        width: 42px !important;
        height: 42px !important;
    }
    .download-button .icon {
        background: none;
        border: none;
        color: #fff;
        cursor: pointer;
    }
    .download-button > input:checked +.list {
        visibility: visible;
        opacity: 1;
    }
    .download-button .list {
        visibility: hidden;
        opacity: 0;
        display: flex;
        justify-content: center;
        align-content: center;
        flex-direction: column;
        font-size: 1rem;
        background: rgb(10, 10, 10, .8);
        backdrop-filter: blur(20px);
        position: absolute;
        bottom: 0;
        right: 100%;
        width: max-content;
        border-radius: 0.75rem;
        border-start-end-radius: 0.25rem;
        overflow: hidden;
        box-shadow: 0 0 20px 0 rgba(0, 0, 0, .6);
        border: 1px solid rgba(255, 255, 255, .05);
        transition: .2s ease;

        & > .item {
            padding: .5rem 1rem;
            background: none;
            border: none;
            color: #fff;
            transition: .3s ease background-color;
        }
        & > .item:hover {
            background: rgb(255, 255, 255, .1);
            cursor: pointer;
        }
    }
`);