Redgifs Tweaks

tweaks for redgifs page

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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;
        }
    }
`);