Google Images All Sizes

Adds 'All Sizes' and 'Full Size' buttons to google images.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Google Images All Sizes
// @version      1.11.0
// @description  Adds 'All Sizes' and 'Full Size' buttons to google images.
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @license      MIT
// @author       Nour Nasser
// @namespace    https://github.com/Nourz1234
// @match        *://www.google.com/search*udm=2*
// @match        *://www.google.com/search*tbm=isch*
// @match        *://www.google.com/imgres*
// @run-at       document-end
// @supportURL   https://github.com/Nourz1234/google-images-all-sizes/issues
// @connect      *
// @grant        GM.xmlHttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function () {
const SVGTags = {
    animate: null,
    circle: null,
    clipPath: null,
    defs: null,
    desc: null,
    ellipse: null,
    feBlend: null,
    feColorMatrix: null,
    feComponentTransfer: null,
    feComposite: null,
    feConvolveMatrix: null,
    feDiffuseLighting: null,
    feDisplacementMap: null,
    feDistantLight: null,
    feFlood: null,
    feFuncA: null,
    feFuncB: null,
    feFuncG: null,
    feFuncR: null,
    feGaussianBlur: null,
    feImage: null,
    feMerge: null,
    feMergeNode: null,
    feMorphology: null,
    feOffset: null,
    fePointLight: null,
    feSpecularLighting: null,
    feSpotLight: null,
    feTile: null,
    feTurbulence: null,
    filter: null,
    foreignObject: null,
    g: null,
    image: null,
    line: null,
    linearGradient: null,
    marker: null,
    mask: null,
    metadata: null,
    path: null,
    pattern: null,
    polygon: null,
    polyline: null,
    radialGradient: null,
    rect: null,
    stop: null,
    svg: null,
    switch: null,
    symbol: null,
    text: null,
    textPath: null,
    tspan: null,
    use: null,
    view: null,
};
function createElement(tag, props, ...children) {
    // console.info(tag, props, children);
    let elem;
    if (tag in SVGTags) {
        elem = document.createElementNS("http://www.w3.org/2000/svg", tag);
    }
    else {
        elem = document.createElement(tag);
    }
    setProps(elem, props || {});
    appendChildren(elem, children);
    return elem;
}
function setProps(elem, props) {
    Object.entries(props).forEach(([key, value]) => {
        if (key === "style" && value instanceof Object) {
            Object.assign(elem.style, value);
        }
        else if (key === "dataset" && value instanceof Object) {
            Object.assign(elem.dataset, value);
        }
        else if (hasKey(elem, key) && !isKeyReadonly(elem, key)) {
            elem[key] = value;
        }
        else {
            elem.setAttribute(key, String(value));
        }
    });
}
function appendChildren(elem, children) {
    children.flat().forEach(child => {
        if (child instanceof Node) {
            elem.appendChild(child);
        }
        else if (child !== null && child !== false) {
            elem.appendChild(document.createTextNode(String(child)));
        }
    });
}

function createDocumentFromHTML(html) {
    let doc = document.implementation.createHTMLDocument("");
    doc.open();
    doc.write(html);
    doc.close();
    return doc;
}
function isElementVisible(elem) {
    return elem.offsetParent !== null && elem.style.visibility != "hidden";
}
function isKeyReadonly(obj, key) {
    let currentObj = obj;
    while (currentObj !== null) {
        const desc = Object.getOwnPropertyDescriptor(currentObj, key);
        if (desc) {
            return desc.writable === false || desc.set === undefined;
        }
        currentObj = Object.getPrototypeOf(currentObj);
    }
    return true;
}
function hasKey(obj, key) {
    return key in obj;
}

logInfo("Started!");
GM_addStyle(getFile("styles.css"));
const observer = new MutationObserver(addButtons);
observer.observe(document.body, { attributes: true, subtree: true });
function getPreviewImageUrl(source) {
    const imgs = source.closest("[data-tbnid]")
        ?.querySelectorAll("a > img");
    return Array.from(imgs || []).find(isElementVisible)?.src;
}
function getImageDetails(source) {
    const container = source.closest('div[data-sid]');
    if (!container) {
        throw new Error("Failed to find image container");
    }
    const query = container.dataset.query;
    const tbnId = container.dataset.sid;
    if (!tbnId) {
        throw new Error("Failed to fetch image tbn id");
    }
    const docId = document.querySelector(`[data-docid="${tbnId}"]`)?.dataset.refDocid;
    return { query, docId, tbnId };
}
async function getAllSizesForImage(source) {
    const { query, docId, tbnId } = getImageDetails(source);
    const url = new URL('/search', window.location.href);
    url.searchParams.append("q", query || "");
    url.searchParams.append("tbm", "isch");
    url.searchParams.append("docid", docId || "");
    url.searchParams.append("tbnid", tbnId);
    url.searchParams.append("tbs", "simg:m00");
    // logInfo(url.href);
    const response = await GM.xmlHttpRequest({
        method: "GET",
        url: url.href,
        headers: {
            "User-Agent": "Mozilla/5.0 (Android 7.0; Mobile; rv:60.0) Gecko/60.0 Firefox/60.0", // important!
        }
    });
    const doc = createDocumentFromHTML(response.responseText);
    return Array.from(doc.querySelectorAll("[data-docid]")).map(elem => {
        const img = elem.querySelector("img");
        return {
            docId: elem.dataset.docid,
            tbnId: elem.dataset.tbnid,
            width: elem.dataset.ow,
            height: elem.dataset.oh,
            src: elem.dataset.ou,
            previewSrc: img.dataset.src || img.src,
            alt: img.alt,
            site: elem.dataset.st,
            url: elem.dataset.ru,
            title: elem.dataset.pt,
        };
    });
}
function addButtons() {
    let btnBars = document.querySelectorAll('div[class="HJRshd"]');
    for (let btnBar of btnBars) {
        const btnOpenImageInFullSize = btnBar.querySelector('#gias_fullSize');
        const btnViewAllSizes = btnBar.querySelector('#gias_allSizes');
        if (btnOpenImageInFullSize !== null || btnViewAllSizes !== null)
            return;
        btnBar.insertAdjacentElement('afterbegin', renderMainButtons());
    }
}
function showImagesModal(images) {
    document.body.appendChild(renderImagesModal(images));
}
async function openImage(image, doNotOpenExternalWebsites) {
    logInfo("openImage", image);
    let response = await GM.xmlHttpRequest({
        method: 'HEAD',
        url: image.src,
    });
    const isImage = response.responseHeaders.toLowerCase().includes("content-type: image/");
    if (!isImage) {
        window.open(doNotOpenExternalWebsites ? image.previewSrc : image.src, "_blank");
        return;
    }
    response = await GM.xmlHttpRequest({
        method: 'GET',
        url: image.src,
        responseType: "blob",
    });
    window.open(URL.createObjectURL(response.response), "_blank");
}

function renderMainButtons() {
    function onViewFullSize() {
        let imgUrl = getPreviewImageUrl(this);
        if (imgUrl !== null)
            window.open(imgUrl, '_blank');
    }
    async function onViewAllSizes() {
        this.classList.add("loading");
        this.disabled = true;
        try {
            const images = await getAllSizesForImage(this);
            // logInfo(images);
            showImagesModal(images);
            this.disabled = false;
        }
        catch (e) {
            handleError(e);
        }
        finally {
            this.classList.remove("loading");
        }
    }
    return (createElement("div", { style: { display: 'flex', gap: "1em" } },
        createElement("button", { id: "gias_fullSize", className: "btn", onclick: onViewFullSize }, "Full Size"),
        createElement("button", { id: "gias_allSizes", className: "btn", onclick: onViewAllSizes }, "All Sizes")));
}
function renderImagesModal(images) {
    const context = {
        doNotOpenExternalWebsites: true,
    };
    logInfo(images);
    const modal = (createElement("div", { className: "gias-modal", onclick: function (e) {
            if (e.target == this)
                this.remove();
        } },
        createElement("div", { className: "modal-content" },
            createElement("div", { className: "modal-head" },
                createElement("div", { className: "title" }, "All Sizes"),
                createElement("label", { title: "If the full size image link does not lead to an actual image then open the preview image instead of opening an external website.", htmlFor: "doNotOpenExternalWebsites" },
                    createElement("input", { id: "doNotOpenExternalWebsites", name: "doNotOpenExternalWebsites", type: "checkbox", checked: context.doNotOpenExternalWebsites, onchange: (e) => context.doNotOpenExternalWebsites = e.target.checked }),
                    "Don't open external websites"),
                createElement("button", { className: "close", type: "button", onclick: function () {
                        this.closest(".gias-modal")?.remove();
                    } },
                    createElement("svg", { width: "24", height: "24", viewBox: "0 0 24 24" },
                        createElement("path", { d: "M 4.9902344 3.9902344 A 1.0001 1.0001 0 0 0 4.2929688 5.7070312 L 10.585938 12 L 4.2929688 18.292969 A 1.0001 1.0001 0 1 0 5.7070312 19.707031 L 12 13.414062 L 18.292969 19.707031 A 1.0001 1.0001 0 1 0 19.707031 18.292969 L 13.414062 12 L 19.707031 5.7070312 A 1.0001 1.0001 0 0 0 18.980469 3.9902344 A 1.0001 1.0001 0 0 0 18.292969 4.2929688 L 12 10.585938 L 5.7070312 4.2929688 A 1.0001 1.0001 0 0 0 4.9902344 3.9902344 z" })))),
            createElement("div", { className: "modal-body" },
                createElement("div", { className: "img-panel" },
                    images.length && images.map(img => renderImage(img, context)),
                    !images.length &&
                        createElement("div", null, "No images found."))))));
    return modal;
}
function renderImage(image, context) {
    async function onOpenImage() {
        this.classList.add("loading");
        this.disabled = true;
        try {
            await openImage(image, context.doNotOpenExternalWebsites);
        }
        catch (error) {
            logError("Error opening image", error);
            window.open(image.src, "_blank");
        }
        finally {
            this.classList.remove("loading");
            this.disabled = false;
        }
    }
    function onOpenInGoogle() {
        const url = new URL("/imgres", window.location.href);
        // url.searchParams.set("q", "");
        // url.searchParams.set("imgurl", image.src);
        url.searchParams.set("imgrefurl", image.url);
        url.searchParams.set("docid", image.docId);
        url.searchParams.set("tbnid", image.tbnId);
        // url.searchParams.set("w", image.width);
        // url.searchParams.set("h", image.height);
        window.open(url.href, "_blank");
    }
    function openWebsite() {
        window.open(image.url, "_blank");
    }
    return (createElement("div", { className: "img-container" },
        createElement("div", { className: "img" },
            createElement("img", { loading: "lazy", src: image.previewSrc, alt: image.alt }),
            createElement("div", { className: "overlay" },
                createElement("div", { className: "actions-container" },
                    createElement("div", { className: "actions" },
                        createElement("button", { className: "btn", onclick: onOpenImage }, "Open"),
                        createElement("button", { className: "btn", onclick: onOpenInGoogle }, "Open in Google"),
                        createElement("button", { className: "btn", onclick: openWebsite }, "Open Website"))),
                createElement("div", { className: "img-size" },
                    image.width,
                    "x",
                    image.height))),
        createElement("div", { className: "img-info" },
            createElement("a", { className: "site", href: image.url, target: "_blank", title: image.site }, image.site),
            createElement("span", { className: "title", title: image.title }, image.title))));
}

function logInfo(...data) {
    console.info("[Google Images All Sizes]", ...data);
}
function logError(...data) {
    console.error("[Google Images All Sizes]", ...data);
}
function showMsg(message) {
    window.alert(`[Google Images All Sizes]:\n${message}`);
}
function handleError(...data) {
    logError(...data);
    showMsg("An unhandled error occurred.\nCheck console for details.");
}

function getFile(name) {
const files = {
'styles.css': () =>
`
/* variables */
:root {
    --clr-text-light: #e4e4e4;
    --clr-text-dark: #242424;

    /* Support for light mode? What is this? Did I really need to add this? */
    --clr-background: white;
    --clr-background2: lightgray;
    --clr-background-disabled: #424242;
    --clr-border: #cfcfcf;
    --clr-text: var(--clr-text-dark);
    --clr-text-disabled: #6e6e6e;
    --clr-gray-text: #9e9e9e;
    --clr-accent: #0060df;
    @media (prefers-color-scheme: dark) {
        --clr-background: #1f1f1f;
        --clr-background2: #292929;
        /* --clr-background-disabled: #424242; */
        --clr-border: #3f3f3f;
        --clr-text: var(--clr-text-light);
        /* --clr-text-disabled: #6e6e6e; */
        /* --clr-gray-text: #9e9e9e; */
        /* --clr-accent: #0060df; */
    }

    --img-height: 200px;
    --img-min-width: 100px;
    --img-max-width: 356px;
    --gap: 0.5em;

    /* use system accent color if available */
    @supports (color: AccentColor) {
        --clr-accent: AccentColor;
    }
}

/* The Modal (background) */
.gias-modal {
    all: revert;

    position: fixed;
    display: flex;
    flex-flow: row;
    justify-content: center;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100vh;
    z-index: 10000;
    background: rgba(0, 0, 0, 0.6);

    font-family: Arial;
    font-size: medium;
    accent-color: var(--clr-accent);
    color: var(--clr-text);

    * {
        transition: all 0.3s ease-out;
    }

    a {
        color: var(--clr-gray-text);
        font-size: small;
    }

    /* Modal Content */
    .modal-content {
        background: var(--clr-background);
        border: 1px solid var(--clr-border);
        border-radius: 1em;
        margin: 3em;
        padding: var(--gap);
        display: flex;
        flex-flow: column;
    }

    /* Modal Header */
    .modal-head {
        display: flex;
        flex-flow: row;
        justify-content: space-between;
        align-items: center;
        gap: 3em;
        border: 0;
        border-bottom: 1px solid var(--clr-border);

        .title {
            font-size: large;
            margin-left: 0.3em;
        }
    }

    /* Modal Body */
    .modal-body {
        display: flex;
        justify-content: center;
        overflow: auto;
        padding-top: 1em;
    }

    .img-panel {
        display: flex;
        flex-flow: row;
        flex-wrap: wrap;
        justify-content: center;
        gap: var(--gap);
    }

    .img-container {
        display: flex;
        flex-flow: column;
        gap: var(--gap);
    }

    .img {
        min-width: var(--img-min-width);
        max-width: var(--img-max-width);
        height: var(--img-height);
        border-radius: 1em;
        overflow: hidden;
        display: flex;
        flex-flow: column;
        justify-content: center;
        background: var(--clr-background2);
        position: relative;
        filter: drop-shadow(0px 2px 5px black);

        img {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
        }
    }

    .overlay {
        position: absolute;
        width: 100%;
        height: 100%;

        display: flex;
        flex-flow: column;
    }

    .actions-container {
        display: flex;
        flex-flow: column;
        justify-content: center;
        align-items: center;

        flex-grow: 1;

        opacity: 0;
        background: rgba(0, 0, 0, 0.6);
    }

    .actions-container:hover {
        opacity: 1;
    }

    .actions {
        display: flex;
        flex-flow: column;
        width: max-content;

        gap: var(--gap);
    }

    .img-size {
        text-align: center;
        color: var(--clr-text-light);
        background: rgba(0, 0, 0, 0.6);
    }

    .img-info {
        display: flex;
        flex-flow: column;
        /* start at 0 */
        width: 0px;
        /* grow to fill available space (i don't know how it works, but it does ¯\\_(ツ)_/¯) */
        min-width: 100%;

        white-space: nowrap;

        .site,
        .title {
            overflow: hidden;
            text-overflow: ellipsis;
        }

        .site {
            text-decoration: none;
        }

        .site:hover {
            text-decoration: underline;
        }
    }

    .close {
        align-self: flex-start;
        fill: var(--clr-gray-text);
        border: none;
        background: none;
        cursor: pointer;
    }

    .close:hover {
        filter: brightness(125%);
    }

    .gap {
        gap: var(--gap);
    }

    .d-flex {
        display: flex;
        align-items: center;
    }
}


.btn {
    text-decoration: none;
    color: var(--clr-text-light);
    background: var(--clr-accent);
    padding: 0.5em;
    box-sizing: border-box;
    border: none;
    border-radius: 1em;
    flex-grow: 1;
    cursor: pointer;
    /* overflow: hidden; */
    position: relative;
}

.btn:hover {
    filter: brightness(125%);
}

.btn:disabled {
    background: var(--clr-background-disabled);
    color: var(--clr-text-disabled);
    pointer-events: none;
}

.loading::before {
    content: '';
    position: absolute;
    pointer-events: none;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    aspect-ratio: 1;
    height: 25px;
    width: auto;

    background: var(--clr-background);
    border-radius: 50%;
}

.loading::after {
    content: '';
    position: absolute;
    pointer-events: none;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    aspect-ratio: 1;
    height: 25px;
    width: auto;

    box-sizing: border-box;
    border: 4px solid transparent;
    border-top: 4px solid #007bff;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% {
        transform: translate(-50%, -50%) rotate(0deg);
    }

    100% {
        transform: translate(-50%, -50%) rotate(360deg);
    }
}
`,

};
return files[name]();
}

})();