Google Images All Sizes

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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]();
}

})();