Greasy Fork

4chan Gallery

4chan grid based Image Gallery for threads that can browse images, images with sounds, webms with sounds

目前为 2024-06-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         4chan Gallery
// @namespace    http://tampermonkey.net/
// @version      2024-06-05 (2.0)
// @description  4chan grid based Image Gallery for threads that can browse images, images with sounds, webms with sounds
// @author       TheDarkEnjoyer
// @match        https://boards.4chan.org/*/thread/*
// @match        https://boards.4chan.org/*/archive
// @match        https://boards.4channel.org/*/thread/*
// @match        https://boards.4channel.org/*/archive
// @match        https://warosu.org/*/thread/*
// @match        https://warosu.org/*/
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    "use strict";
    // injectVideoJS();

    let threadURL = window.location.href;
    let lastScrollPosition = 0;
    let gallerySize = { width: 0, height: 0 };

    function setStyles(element, styles) {
        for (const property in styles) {
            element.style[property] = styles[property];
        }
    }

    function injectVideoJS() {
        const link = document.createElement("link");
        link.href = "https://vjs.zencdn.net/8.10.0/video-js.css";
        link.rel = "stylesheet";
        document.head.appendChild(link);

        // theme
        const theme = document.createElement("link");
        theme.href = "https://unpkg.com/@videojs/themes@1/dist/city/index.css";
        theme.rel = "stylesheet";
        document.head.appendChild(theme);

        const script = document.createElement("script");
        script.src = "https://vjs.zencdn.net/8.10.0/video.min.js";
        document.body.appendChild(script);
        ("VideoJS injected successfully!");
    }

    const loadButton = () => {
        const isArchivePage = window.location.pathname.includes("/archive");

        const button = document.createElement("button");
        button.textContent = "Open Image Gallery";
        button.id = "openImageGallery";
        setStyles(button, {
            position: "fixed",
            bottom: "20px",
            right: "20px",
            zIndex: "1000",
            backgroundColor: "#1c1c1c",
            color: "#d9d9d9",
            padding: "10px 20px",
            borderRadius: "5px",
            border: "none",
            cursor: "pointer",
            boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
        });

        const openImageGallery = () => {
            const gallery = document.createElement("div");
            gallery.id = "imageGallery";
            setStyles(gallery, {
                position: "fixed",
                top: "0",
                left: "0",
                width: "100%",
                height: "100%",
                backgroundColor: "rgba(0, 0, 0, 0.8)",
                display: "flex",
                justifyContent: "center",
                alignItems: "center",
                zIndex: "9999",
            });

            const gridContainer = document.createElement("div");
            setStyles(gridContainer, {
                display: "grid",
                gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
                gap: "10px",
                padding: "20px",
                backgroundColor: "#1c1c1c",
                color: "#d9d9d9",
                maxWidth: "80%",
                maxHeight: "80%",
                overflowY: "auto",
                resize: "both",
                overflow: "auto",
                border: "1px solid #d9d9d9",
            });

            // Restore the previous grid container size
            if (gallerySize.width > 0 && gallerySize.height > 0) {
                gridContainer.style.width = `${gallerySize.width}px`;
                gridContainer.style.height = `${gallerySize.height}px`;
            }

            let mode = "all"; // Default mode is "all"
            let autoPlayWebms = false; // Default auto play webms without sound is false

            // Toggle mode button
            const toggleModeButton = document.createElement("button");
            toggleModeButton.textContent = "Toggle Mode (All)";
            setStyles(toggleModeButton, {
                position: "absolute",
                top: "10px",
                left: "10px",
                backgroundColor: "#1c1c1c",
                color: "#d9d9d9",
                padding: "10px 20px",
                borderRadius: "5px",
                border: "none",
                cursor: "pointer",
                boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
            });
            toggleModeButton.addEventListener("click", () => {
                mode = mode === "all" ? "webm" : "all";
                toggleModeButton.textContent = `Toggle Mode (${mode === "all" ? "All" : "Webm & Images with Sound"
                    })`;
                gridContainer.innerHTML = ""; // Clear the grid
                loadPosts(mode); // Reload posts based on the new mode
            });
            gallery.appendChild(toggleModeButton);

            // Toggle auto play webms button
            const toggleAutoPlayButton = document.createElement("button");
            toggleAutoPlayButton.textContent = "Auto Play Webms without Sound";
            setStyles(toggleAutoPlayButton, {
                position: "absolute",
                top: "10px",
                left: "350px",
                backgroundColor: "#1c1c1c",
                color: "#d9d9d9",
                padding: "10px 20px",
                borderRadius: "5px",
                border: "none",
                cursor: "pointer",
                boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
            });
            toggleAutoPlayButton.addEventListener("click", () => {
                autoPlayWebms = !autoPlayWebms;
                toggleAutoPlayButton.textContent = autoPlayWebms
                    ? "Stop Auto Play Webms"
                    : "Auto Play Webms without Sound";
                gridContainer.innerHTML = ""; // Clear the grid
                loadPosts(mode); // Reload posts based on the new mode and auto play setting
            });
            gallery.appendChild(toggleAutoPlayButton);

            const loadPosts = (mode) => {
                const checkedThreads = isArchivePage
                    ? // Get all checked threads in the archive page or the current link if it's not an archive page
                    Array.from(
                        document.querySelectorAll(
                            ".flashListing input[type='checkbox']:checked"
                        )
                    ).map((checkbox) => {
                        let archiveSite = checkbox.parentNode.parentNode.querySelector('a').href;
                        return archiveSite;
                    })
                    : [threadURL];

                const loadPostsFromThread = (thread) => {
                    // get the website url without the protocol and next slash
                    const websiteUrl = thread
                        .replace(/(^\w+:|^)\/\//, "")
                        .split("/")[0];

                    // const board = thread.split("/thread/")[0].split("/").pop();
                    // const threadNo = `${parseInt(thread.split("thread/").pop())}`
                    fetch(thread)
                        .then((response) => response.text())
                        .then((html) => {
                            const parser = new DOMParser();
                            const doc = parser.parseFromString(html, "text/html");
                            let posts;

                            // use a case statement to deal with different websites
                            switch (websiteUrl) {
                                case "warosu.org":
                                    posts = doc.querySelectorAll(".comment");
                                    break;
                                case "boards.4chan.org":
                                case "boards.4channel.org":
                                default:
                                    posts = doc.querySelectorAll(".postContainer");
                                    break;
                            }

                            posts.forEach((post) => {
                                let mediaLinkFlag = false;
                                let thumbnailUrl;
                                let mediaLink;
                                let fileName;
                                let comment;

                                let isVideo;
                                let isImage;
                                let soundLink;

                                // case statement for different websites
                                switch (websiteUrl) {
                                    case "warosu.org":
                                        let thumbnailElement = post.querySelector("img");

                                        //  File: 3.61 MB, 852x480, awa[sound=files.catbox.moe%2Fshcsjl.ogg].webm
                                        fileName = post.querySelector(".fileinfo")?.innerText.split(", ")[2];
                                        thumbnailUrl = thumbnailElement?.src;
                                        mediaLink = thumbnailElement?.parentNode.href;
                                        comment = post.querySelector("blockquote");

                                        if (mediaLink) {
                                            isVideo = mediaLink.includes(".webm");
                                            isImage =
                                                mediaLink.includes(".jpg") ||
                                                mediaLink.includes(".png") ||
                                                mediaLink.includes(".gif");
                                            soundLink = fileName.match(/\[sound=(.+?)\]/);
                                            mediaLinkFlag = true;
                                        } else {
                                            return; // Skip posts without media links
                                        }
                                        break;
                                    case "boards.4chan.org":
                                    case "boards.4channel.org":
                                    default:
                                        if (post.querySelector(".fileText")) {
                                            mediaLink = post.querySelector(".fileText a");
                                            if (mediaLink.href.includes("4cdn") || mediaLink.href.includes("4chan.org")) {
                                                if (mediaLink.title) {
                                                    fileName = mediaLink.title;
                                                } else {
                                                    fileName = mediaLink.innerText;
                                                }
                                            } else {
                                                fileName = mediaLink.innerText;
                                            }
                                        } else {
                                            return; // Skip posts without media links
                                        }

                                        thumbnailUrl = post.querySelector(".fileThumb img")?.src;
                                        comment = post.querySelector(".postMessage");

                                        if (mediaLink) {
                                            mediaLink = mediaLink.href;

                                            isVideo = mediaLink.includes(".webm");
                                            isImage =
                                                mediaLink.includes(".jpg") ||
                                                mediaLink.includes(".png") ||
                                                mediaLink.includes(".gif");
                                            soundLink =
                                                fileName.match(/\[sound=(.+?)\]/);
                                            mediaLinkFlag = true;
                                        }
                                        break;
                                }

                                if (mediaLinkFlag) {
                                    // Check if the post should be loaded based on the mode
                                    if (
                                        mode === "all" ||
                                        (mode === "webm" && (isVideo || (isImage && soundLink)))
                                    ) {
                                        const cell = document.createElement("div");
                                        setStyles(cell, {
                                            border: "1px solid #d9d9d9",
                                            position: "relative",
                                        });

                                        const buttonDiv = document.createElement("div");
                                        setStyles(buttonDiv, {
                                            display: "flex",
                                            justifyContent: "space-between",
                                            alignItems: "center",
                                            padding: "5px",
                                        });

                                        if (isVideo) {
                                            const videoContainer = document.createElement("div");
                                            setStyles(videoContainer, {
                                                position: "relative",
                                                display: "flex",
                                                justifyContent: "center",
                                            });

                                            const videoThumbnail = document.createElement("img");
                                            videoThumbnail.src = thumbnailUrl;
                                            videoThumbnail.alt = "Video Thumbnail";
                                            setStyles(videoThumbnail, {
                                                width: "100%",
                                                maxHeight: "200px",
                                                objectFit: "contain",
                                                cursor: "pointer",
                                            });
                                            videoThumbnail.loading = "lazy";

                                            const video = document.createElement("video");
                                            video.src = mediaLink;
                                            video.muted = true;
                                            video.controls = true;
                                            video.title = comment.innerText;
                                            video.videothumbnailDisplayed = "true";
                                            video.setAttribute("fileName", fileName);
                                            setStyles(video, {
                                                maxWidth: "100%",
                                                maxHeight: "200px",
                                                objectFit: "contain",
                                                cursor: "pointer",
                                                display: "none",
                                            });

                                            // videoJS stuff (not working for some reason)
                                            // video.className = "video-js";
                                            // video.setAttribute("data-setup", "{}");
                                            // const source = document.createElement("source");
                                            // source.src = mediaLink;
                                            // source.type = "video/webm";
                                            // video.appendChild(source);

                                            videoThumbnail.addEventListener("click", () => {
                                                videoThumbnail.style.display = "none";
                                                video.style.display = "block";
                                                video.videothumbnailDisplayed = "false";
                                                video.load();
                                            });

                                            // hide the video thumbnail and show the video when hovered
                                            videoThumbnail.addEventListener("mouseenter", () => {
                                                videoThumbnail.style.display = "none";
                                                video.style.display = "block";
                                                video.videothumbnailDisplayed = "false";
                                                video.load();
                                            });

                                            // Play webms without sound automatically on hover or if autoPlayWebms is true
                                            if (!soundLink) {
                                                if (autoPlayWebms) {
                                                    video.addEventListener("canplaythrough", () => {
                                                        video.play();
                                                        video.loop = true; // Loop webms when autoPlayWebms is true
                                                    });
                                                } else {
                                                    video.addEventListener("mouseenter", () => {
                                                        video.play();
                                                    });
                                                    video.addEventListener("mouseleave", () => {
                                                        video.pause();
                                                    });
                                                }
                                            }

                                            video.addEventListener("click", () => {
                                                post.scrollIntoView({ behavior: "smooth" });
                                                gallerySize = {
                                                    width: gridContainer.offsetWidth,
                                                    height: gridContainer.offsetHeight,
                                                };
                                                document.body.removeChild(gallery);
                                            });

                                            videoContainer.appendChild(videoThumbnail);
                                            videoContainer.appendChild(video);

                                            if (soundLink) {
                                                video.preload = "none"; // Disable video preload for better performance

                                                const audio = document.createElement("audio");
                                                audio.src = decodeURIComponent(
                                                    soundLink[1].startsWith("http")
                                                        ? soundLink[1]
                                                        : `https://${soundLink[1]}`
                                                );
                                                videoContainer.appendChild(audio);

                                                const resetButton = document.createElement("button");
                                                resetButton.textContent = "Reset";
                                                setStyles(resetButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                resetButton.addEventListener("click", () => {
                                                    video.currentTime = 0;
                                                    audio.currentTime = 0;
                                                });
                                                buttonDiv.appendChild(resetButton);

                                                // html5 video play
                                                video.onplay = (event) => {
                                                    audio.play();
                                                }

                                                video.onpause = (event) => {
                                                    audio.pause();
                                                }

                                                let lastVideoTime = 0;
                                                // Sync audio with video on timeupdate event only if the difference is 2 seconds or more
                                                video.addEventListener("timeupdate", () => {
                                                    if (
                                                        Math.abs(video.currentTime - lastVideoTime) >= 2
                                                    ) {
                                                        audio.currentTime = video.currentTime;
                                                        lastVideoTime = video.currentTime;
                                                    }
                                                    lastVideoTime = video.currentTime;
                                                });
                                            }

                                            const cellButton = document.createElement("button");
                                            cellButton.textContent = "View Post";
                                            setStyles(cellButton, {
                                                backgroundColor: "#1c1c1c",
                                                color: "#d9d9d9",
                                                padding: "5px 10px",
                                                borderRadius: "3px",
                                                border: "none",
                                                cursor: "pointer",
                                                boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                            });
                                            cellButton.addEventListener("click", () => {
                                                post.scrollIntoView({ behavior: "smooth", block: "center"});
                                                gallerySize = {
                                                    width: gridContainer.offsetWidth,
                                                    height: gridContainer.offsetHeight,
                                                };
                                                document.body.removeChild(gallery);
                                            });

                                            buttonDiv.appendChild(cellButton);
                                            cell.appendChild(videoContainer);
                                        } else if (isImage) {
                                            const imageContainer = document.createElement("div");
                                            setStyles(imageContainer, {
                                                position: "relative",
                                                display: "flex",
                                                justifyContent: "center",
                                                alignItems: "center",
                                            });

                                            const image = document.createElement("img");
                                            image.src = mediaLink;
                                            image.setAttribute("fileName", fileName);
                                            setStyles(image, {
                                                maxWidth: "100%",
                                                maxHeight: "200px",
                                                objectFit: "contain",
                                                cursor: "pointer",
                                            });

                                            let createDarkenBackground = () => {
                                                const background = document.createElement("div");
                                                background.id = "darkenBackground";
                                                setStyles(background, {
                                                    position: "fixed",
                                                    top: "0",
                                                    left: "0",
                                                    width: "100%",
                                                    height: "100%",
                                                    backgroundColor: "rgba(0, 0, 0, 0.3)",
                                                    backdropFilter: "blur(5px)",
                                                    zIndex: "9999",
                                                });
                                                return background;
                                            }

                                            let zoomImage = () => {
                                                // have the image pop up centered in front of the screen so that it fills about 80% of the screen
                                                image.style = "";
                                                setStyles(image, {
                                                    position: "fixed",
                                                    top: "50%",
                                                    left: "50%",
                                                    transform: "translate(-50%, -50%)",
                                                    zIndex: "10000",
                                                    height: "80%",
                                                    width: "80%",
                                                    objectFit: "contain",
                                                    cursor: "pointer",
                                                });

                                                // darken and blur the background behind the image without affecting the image
                                                const background = createDarkenBackground();
                                                gallery.appendChild(background);

                                                // create a container for the buttons, number, and download buttons (even space between them)
                                                // position: fixed; bottom: 10px; display: flex; flex-direction: row; justify-content: space-around; z-index: 10000; width: 100%; margin:auto;
                                                const bottomContainer = document.createElement("div");
                                                setStyles(bottomContainer, {
                                                    position: "fixed",
                                                    bottom: "10px",
                                                    display: "flex",
                                                    flexDirection: "row",
                                                    justifyContent: "space-around",
                                                    zIndex: "10000",
                                                    width: "100%",
                                                    margin: "auto",
                                                });
                                                background.appendChild(bottomContainer);

                                                // buttons on the bottom left of the screen for reverse image search (SauceNAO, Google Lens, Yandex)
                                                const buttonContainer = document.createElement("div");
                                                setStyles(buttonContainer, {
                                                    display: "flex",
                                                    gap: "10px",
                                                });
                                                buttonContainer.setAttribute("mediaLink", mediaLink);

                                                const sauceNAOButton = document.createElement("button");
                                                sauceNAOButton.textContent = "SauceNAO";
                                                setStyles(sauceNAOButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                sauceNAOButton.addEventListener("click", () => {
                                                    window.open(
                                                        `https://saucenao.com/search.php?url=${encodeURIComponent(
                                                            buttonContainer.getAttribute("mediaLink")
                                                        )}`
                                                    );
                                                });
                                                buttonContainer.appendChild(sauceNAOButton);

                                                const googleLensButton = document.createElement("button");
                                                googleLensButton.textContent = "Google Lens";
                                                setStyles(googleLensButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                googleLensButton.addEventListener("click", () => {
                                                    window.open(
                                                        `https://lens.google.com/uploadbyurl?url=${encodeURIComponent(
                                                            buttonContainer.getAttribute("mediaLink")
                                                        )}`
                                                    );
                                                });
                                                buttonContainer.appendChild(googleLensButton);

                                                const yandexButton = document.createElement("button");
                                                yandexButton.textContent = "Yandex";
                                                setStyles(yandexButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                yandexButton.addEventListener("click", () => {
                                                    window.open(
                                                        `https://yandex.com/images/search?rpt=imageview&url=${encodeURIComponent(
                                                            buttonContainer.getAttribute("mediaLink")
                                                        )}`
                                                    );
                                                });
                                                buttonContainer.appendChild(yandexButton);

                                                bottomContainer.appendChild(buttonContainer);

                                                // download container for video/img and audio
                                                const downloadButtonContainer = document.createElement("div");
                                                setStyles(downloadButtonContainer, {
                                                    display: "flex",
                                                    gap: '10px',
                                                });
                                                bottomContainer.appendChild(downloadButtonContainer);

                                                const downloadButton = document.createElement("a");
                                                downloadButton.textContent = "Download Video/Image";
                                                downloadButton.href = mediaLink;
                                                downloadButton.download = fileName;
                                                downloadButton.target = "_blank";
                                                setStyles(downloadButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                downloadButtonContainer.appendChild(downloadButton);

                                                const audioDownloadButton = document.createElement("a");
                                                audioDownloadButton.textContent = "Download Audio";
                                                audioDownloadButton.target = "_blank";
                                                if (soundLink) {
                                                    audioDownloadButton.href = decodeURIComponent(
                                                        soundLink[1].startsWith("http")
                                                            ? soundLink[1]
                                                            : `https://${soundLink[1]}`
                                                    );
                                                    audioDownloadButton.download = soundLink[1].split("/").pop();
                                                } else {
                                                    audioDownloadButton.style.display = "none";
                                                }
                                                setStyles(audioDownloadButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                downloadButtonContainer.appendChild(audioDownloadButton);

                                                // number on the bottom right of the screen to show which image is currently being viewed
                                                const imageNumber = document.createElement("div");
                                                let currentImageNumber = Array.from(cell.parentNode.children).indexOf(cell) + 1;
                                                let imageTotal = cell.parentNode.children.length;
                                                imageNumber.textContent = `${currentImageNumber}/${imageTotal}`;
                                                setStyles(imageNumber, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                    zIndex: "10000",
                                                });
                                                bottomContainer.appendChild(imageNumber);

                                                // title of the image/video on the top left of the screen
                                                const imageTitle = document.createElement("div");
                                                imageTitle.textContent = fileName;
                                                setStyles(imageTitle, {
                                                    position: "fixed",
                                                    top: "10px",
                                                    left: "10px",
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                    zIndex: "10000",
                                                });
                                                background.appendChild(imageTitle);

                                                let currentCell = cell;
                                                // use left and right arrow keys to navigate between images/videos
                                                let keybindHandler = (event) => {
                                                    if (event.key === "ArrowLeft") {
                                                        // get the previous cell in the grid
                                                        const previousCell = currentCell.previousElementSibling;
                                                        if (previousCell) {
                                                            if (gallery.querySelector("#zoomedVideo")) {
                                                                if (gallery.querySelector("#zoomedVideo").querySelector("audio")) {
                                                                    gallery.querySelector("#zoomedVideo").querySelector("audio").pause();
                                                                }
                                                                gallery.removeChild(gallery.querySelector("#zoomedVideo"));
                                                            } else if (gallery.querySelector("#zoomedImage")) {
                                                                gallery.removeChild(gallery.querySelector("#zoomedImage"));
                                                            } else {
                                                                image.style = "";
                                                                setStyles(image, {
                                                                    maxWidth: "100%",
                                                                    maxHeight: "200px",
                                                                    objectFit: "contain",
                                                                });
                                                            }

                                                            // check if it has a video
                                                            const video = previousCell?.querySelector("video");
                                                            if (video) {
                                                                const video = previousCell.querySelector("video").cloneNode(true);
                                                                video.id = "zoomedVideo";
                                                                video.style = "";
                                                                setStyles(video, {
                                                                    position: "fixed",
                                                                    top: "50%",
                                                                    left: "50%",
                                                                    transform: "translate(-50%, -50%)",
                                                                    zIndex: "10000",
                                                                    height: "80%",
                                                                    width: "80%",
                                                                    objectFit: "contain",
                                                                    cursor: "pointer",
                                                                    preload: "auto",
                                                                });
                                                                gallery.appendChild(video);

                                                                // check if there is an audio element
                                                                let audio = previousCell.querySelector("audio");
                                                                if (audio) {
                                                                    audio = audio.cloneNode(true);

                                                                    // same event listeners as the video
                                                                    video.onplay = (event) => {
                                                                        audio.play();
                                                                    }

                                                                    video.onpause = (event) => {
                                                                        audio.pause();
                                                                    }

                                                                    let lastVideoTime = 0;
                                                                    video.addEventListener("timeupdate", () => {
                                                                        if (
                                                                            Math.abs(video.currentTime - lastVideoTime) >= 2
                                                                        ) {
                                                                            audio.currentTime = video.currentTime;
                                                                            lastVideoTime = video.currentTime;
                                                                        }
                                                                        lastVideoTime = video.currentTime;
                                                                    });
                                                                    video.appendChild(audio);
                                                                }
                                                            } else {
                                                                // if it doesn't have a video, it must have an image
                                                                const currentImage = previousCell.querySelector("img").cloneNode(true);
                                                                currentImage.id = "zoomedImage";
                                                                currentImage.style = "";
                                                                setStyles(currentImage, {
                                                                    position: "fixed",
                                                                    top: "50%",
                                                                    left: "50%",
                                                                    transform: "translate(-50%, -50%)",
                                                                    zIndex: "10000",
                                                                    height: "80%",
                                                                    width: "80%",
                                                                    objectFit: "contain",
                                                                    cursor: "pointer",
                                                                });
                                                                gallery.appendChild(currentImage);
                                                                currentImage.addEventListener("click", () => {
                                                                    gallery.removeChild(currentImage);
                                                                    gallery.removeChild(background);
                                                                    document.removeEventListener("keydown", keybindHandler);
                                                                });

                                                                let audio = previousCell.querySelector("audio");
                                                                if (audio) {
                                                                    audio = audio.cloneNode(true);
                                                                    currentImage.appendChild(audio);

                                                                    // event listeners when hovering over the image
                                                                    currentImage.addEventListener("mouseenter", () => {
                                                                        audio.play();
                                                                    });
                                                                    currentImage.addEventListener("mouseleave", () => {
                                                                        audio.pause();
                                                                    });
                                                                }
                                                            }

                                                            if (previousCell) {
                                                                currentCell = previousCell;
                                                                buttonContainer.setAttribute("mediaLink", previousCell.querySelector("img").src);

                                                                currentImageNumber -= 1;
                                                                imageNumber.textContent = `${currentImageNumber}/${imageTotal}`;

                                                                // filename of the video if it has one, otherwise the filename of the image
                                                                imageTitle.textContent = video ? video.getAttribute("fileName") : previousCell.querySelector("img").getAttribute("fileName");

                                                                // update the download button links
                                                                downloadButton.href = video ? video.src : previousCell.querySelector("img").src;
                                                                if (previousCell.querySelector("audio")) {
                                                                    audioDownloadButton.href = previousCell.querySelector("audio").src;
                                                                    audioDownloadButton.download = previousCell.querySelector("audio").src.split("/").pop();
                                                                    audioDownloadButton.style.display = "block";
                                                                } else {
                                                                    audioDownloadButton.style.display = "none";
                                                                }
                                                            }
                                                        }
                                                    } else if (event.key === "ArrowRight") {
                                                    // get the next cell in the grid
                                                    const nextCell = currentCell.nextElementSibling;
                                                    if (nextCell) {
                                                        if (gallery.querySelector("#zoomedVideo")) {
                                                            if (gallery.querySelector("#zoomedVideo").querySelector("audio")) {
                                                                gallery.querySelector("#zoomedVideo").querySelector("audio").pause();
                                                            }
                                                            gallery.removeChild(gallery.querySelector("#zoomedVideo"));
                                                            // ("removed video");
                                                        } else if (gallery.querySelector("#zoomedImage")) {
                                                            gallery.removeChild(gallery.querySelector("#zoomedImage"));
                                                            // ("removed image");
                                                        } else {
                                                            image.style = "";
                                                            setStyles(image, {
                                                                maxWidth: "100%",
                                                                maxHeight: "200px",
                                                                objectFit: "contain",
                                                            });
                                                        }

                                                        // check if it has a video
                                                        const video = nextCell?.querySelector("video");
                                                        if (video) {
                                                            const video = nextCell.querySelector("video").cloneNode(true);
                                                            video.id = "zoomedVideo";
                                                            video.style = "";
                                                            setStyles(video, {
                                                                position: "fixed",
                                                                top: "50%",
                                                                left: "50%",
                                                                transform: "translate(-50%, -50%)",
                                                                zIndex: "10000",
                                                                height: "80%",
                                                                width: "80%",
                                                                objectFit: "contain",
                                                                cursor: "pointer",
                                                                preload: "auto",
                                                            });

                                                            // check if there is an audio element
                                                            let audio = nextCell.querySelector("audio");
                                                            if (audio) {
                                                                audio = audio.cloneNode(true);

                                                                // same event listeners as the video
                                                                video.onplay = (event) => {
                                                                    audio.play();
                                                                }

                                                                video.onpause = (event) => {
                                                                    audio.pause();
                                                                }

                                                                let lastVideoTime = 0;
                                                                video.addEventListener("timeupdate", () => {
                                                                    if (
                                                                        Math.abs(video.currentTime - lastVideoTime) >= 2
                                                                    ) {
                                                                        audio.currentTime = video.currentTime;
                                                                        lastVideoTime = video.currentTime;
                                                                    }
                                                                    lastVideoTime = video.currentTime;
                                                                });
                                                                video.appendChild(audio);
                                                            }
                                                            gallery.appendChild(video);
                                                        } else {
                                                            const currentImage = nextCell.querySelector("img").cloneNode(true);
                                                            currentImage.id = "zoomedImage";
                                                            currentImage.style = "";
                                                            setStyles(currentImage, {
                                                                position: "fixed",
                                                                top: "50%",
                                                                left: "50%",
                                                                transform: "translate(-50%, -50%)",
                                                                zIndex: "10000",
                                                                height: "80%",
                                                                width: "80%",
                                                                objectFit: "contain",
                                                                cursor: "pointer",
                                                            });
                                                            gallery.appendChild(currentImage);
                                                            currentImage.addEventListener("click", () => {
                                                                gallery.removeChild(currentImage);
                                                                gallery.removeChild(background);
                                                                document.removeEventListener("keydown", keybindHandler);
                                                            });

                                                            let audio = nextCell.querySelector("audio");
                                                            if (audio) {
                                                                audio = nextCell.querySelector("audio").cloneNode(true);
                                                                currentImage.appendChild(audio);

                                                                currentImage.addEventListener("mouseenter", () => {
                                                                    audio.play();
                                                                });
                                                                currentImage.addEventListener("mouseleave", () => {
                                                                    audio.pause();
                                                                });
                                                            }
                                                        }
                                                        if (nextCell) {
                                                            currentCell = nextCell;
                                                            buttonContainer.setAttribute("mediaLink", nextCell.querySelector("img").src);

                                                            currentImageNumber += 1;
                                                            imageNumber.textContent = `${currentImageNumber}/${imageTotal}`;

                                                            // filename of the video if it has one, otherwise the filename of the image
                                                            imageTitle.textContent = video ? video.getAttribute("fileName") : nextCell.querySelector("img").getAttribute("fileName");

                                                            // update the download button links
                                                            downloadButton.href = video ? video.src : nextCell.querySelector("img").src;
                                                            if (nextCell.querySelector("audio")) {
                                                                audioDownloadButton.href = nextCell.querySelector("audio").src;
                                                                audioDownloadButton.download = nextCell.querySelector("audio").src.split("/").pop();
                                                                audioDownloadButton.style.display = "block";
                                                            } else {
                                                                audioDownloadButton.style.display = "none";
                                                            }
                                                        }
                                                    }
                                                }};
                                                document.addEventListener("keydown", keybindHandler);

                                                image.addEventListener("click", () => {
                                                    image.style = "";
                                                    setStyles(image, {
                                                        maxWidth: "99%",
                                                        maxHeight: "199px",
                                                        objectFit: "contain",
                                                    });

                                                    if (gallery.querySelector("#darkenBackground")) {
                                                        gallery.removeChild(background);
                                                    };
                                                    document.removeEventListener("keydown", keybindHandler);

                                                    image.addEventListener("click", zoomImage, { once: true });
                                                }, { once: true });

                                            }

                                            image.addEventListener("click", zoomImage, { once: true });
                                            image.title = comment.innerText;
                                            image.loading = "lazy";

                                            if (soundLink) {
                                                const audio = document.createElement("audio");
                                                audio.src = decodeURIComponent(
                                                    soundLink[1].startsWith("http")
                                                        ? soundLink[1]
                                                        : `https://${soundLink[1]}`
                                                );
                                                audio.loop = true;
                                                imageContainer.appendChild(audio);

                                                image.addEventListener("mouseenter", () => {
                                                    audio.play();
                                                });
                                                image.addEventListener("mouseleave", () => {
                                                    audio.pause();
                                                });

                                                const playPauseButton =
                                                    document.createElement("button");
                                                playPauseButton.textContent = "Play/Pause";
                                                setStyles(playPauseButton, {
                                                    backgroundColor: "#1c1c1c",
                                                    color: "#d9d9d9",
                                                    padding: "5px 10px",
                                                    borderRadius: "3px",
                                                    border: "none",
                                                    cursor: "pointer",
                                                    boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
                                                });
                                                playPauseButton.addEventListener("click", () => {
                                                    if (audio.paused) {
                                                        audio.play();
                                                    } else {
                                                        audio.pause();
                                                    }
                                                });
                                                buttonDiv.appendChild(playPauseButton);
                                            }
                                            imageContainer.appendChild(image);
                                            cell.appendChild(imageContainer);
                                        } else {
                                            return; // Skip non-video and non-image posts
                                        }

                                        cell.appendChild(buttonDiv);
                                        gridContainer.appendChild(cell);
                                    }
                                }
                            });
                        })
                        .catch((error) => console.error(error));
                };

                checkedThreads.forEach(loadPostsFromThread);
            };

            loadPosts(mode);

            gallery.appendChild(gridContainer);

            const closeButton = document.createElement("button");
            closeButton.textContent = "Close";
            closeButton.id = "closeGallery";
            setStyles(closeButton, {
                position: "absolute",
                top: "10px",
                right: "10px",
                zIndex: "10000",
                backgroundColor: "#1c1c1c",
                color: "#d9d9d9",
                padding: "10px 20px",
                borderRadius: "5px",
                border: "none",
                cursor: "pointer",
                boxShadow: "0 2px 4px rgba(0, 0, 0, 0.3)",
            });
            closeButton.addEventListener("click", () => {
                gallerySize = {
                    width: gridContainer.offsetWidth,
                    height: gridContainer.offsetHeight,
                };
                document.body.removeChild(gallery);
            });
            gallery.appendChild(closeButton);

            document.body.appendChild(gallery);

            // Store the current scroll position and grid container size when closing the gallery
            // (`Last scroll position: ${lastScrollPosition} px`);
            gridContainer.addEventListener("scroll", () => {
                lastScrollPosition = gridContainer.scrollTop;
                // (`Current scroll position: ${lastScrollPosition} px`);
            });

            // Restore the last scroll position and grid container size when opening the gallery after a timeout if the url is the same
            if (window.location.href === threadURL) {
                setTimeout(() => {
                    gridContainer.scrollTop = lastScrollPosition;
                    // (`Restored scroll position: ${lastScrollPosition} px`);
                    if (gallerySize.width > 0 && gallerySize.height > 0) {
                        gridContainer.style.width = `${gallerySize.width}px`;
                        gridContainer.style.height = `${gallerySize.height}px`;
                    }
                }, 200);
            } else {
                // Reset the last scroll position and grid container size if the url is different
                threadURL = window.location.href;
                lastScrollPosition = 0;
                gallerySize = { width: 0, height: 0 };
            }
        };

        button.addEventListener("click", openImageGallery);

        // Append the button to the body
        document.body.appendChild(button);

        if (isArchivePage) {
            // adds the category to thead
            const thead = document.querySelector(".flashListing thead tr");
            const checkboxCell = document.createElement("td");
            checkboxCell.className = "postblock";
            checkboxCell.textContent = "Selected";
            thead.insertBefore(checkboxCell, thead.firstChild);

            // Add checkboxes to each thread row
            const threadRows = document.querySelectorAll(".flashListing tbody tr");
            threadRows.forEach((row) => {
                const checkbox = document.createElement("input");
                checkbox.type = "checkbox";
                const checkboxCell = document.createElement("td");
                checkboxCell.appendChild(checkbox);
                row.insertBefore(checkboxCell, row.firstChild);
            });
        }
    };

    // Check if there are at least two posts before loading the button
    let posts;
    switch (window.location.hostname) {
        case "warosu.org":
            posts = document.querySelectorAll(".comment");
            break;
        case "boards.4chan.org":
        case "boards.4channel.org":
        default:
            posts = document.querySelectorAll(".postContainer");
            break;
    }

    // Use the "i" key to open and close the gallery/grid
    document.addEventListener("keydown", (event) => {
        if (event.key === "i") {
            if (document.querySelector("#imageGallery")) {
                gallerySize = {
                    width: document.querySelector("#imageGallery").querySelector("div").offsetWidth,
                    height: document.querySelector("#imageGallery").querySelector("div").offsetHeight,
                };
                document.body.removeChild(document.querySelector("#imageGallery"));
            }
            else {
                if (document.querySelector("#openImageGallery")) {
                    document.querySelector("#openImageGallery").click();
                }
            }
        }
    });

    loadButton();
    ("4chan Gallery loaded successfully!");
})();