Greasy Fork

4chan Gallery

4chan grid based Image Gallery for threads that can load images, images with sounds, webms with sounds (Button on the Bottom Right)

目前为 2024-03-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         4chan Gallery
// @namespace    http://tampermonkey.net/
// @version      2024-03-26 (1.3)
// @description  4chan grid based Image Gallery for threads that can load images, images with sounds, webms with sounds (Button on the Bottom Right)
// @author       TheDarkEnjoyer
// @match        http://boards.4chan.org/*/thread/*
// @match        https://boards.4chan.org/*/thread/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

(function () {
    "use strict";

    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 to load the button
    const loadButton = () => {
        const posts = document.querySelectorAll(".postContainer");

        // Check if there are at least two posts
        if (posts.length >= 2) {
            const button = document.createElement("button");
            button.textContent = "Open Image Gallery";
            setStyles(button, {
                position: "fixed",
                bottom: "20px",
                right: "20px",
                zIndex: "1000",
                backgroundColor: "var(--main-color)",
                color: "var(--text-color)",
            });

            // Append the button to the body after 2 seconds
            setTimeout(() => document.body.appendChild(button), 2000);

            const openImageGallery = () => {
                const gallery = document.createElement("div");
                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: "var(--main-color)",
                    color: "var(--text-color)",
                    maxWidth: "80%",
                    maxHeight: "80%",
                    overflowY: "auto",
                    resize: "both",
                    overflow: "auto",
                    border: "1px solid var(--text-color)",
                });

                // 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: "var(--main-color)",
                    color: "var(--text-color)",
                });
                toggleModeButton.addEventListener("click", () => {
                    mode = mode === "all" ? "webm" : "all";
                    toggleModeButton.textContent = `Toggle Mode (${mode === "all" ? "All" : "Webm"})`;
                    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: "200px",
                    backgroundColor: "var(--main-color)",
                    color: "var(--text-color)",
                });
                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) => {
                    // Loop through each post and add its image/video to the grid
                    posts.forEach((post) => {
                        const mediaLink = post.querySelector(".fileText a");
                        const comment = post.querySelector(".postMessage");

                        if (mediaLink) {
                            const isVideo = mediaLink.href.includes(".webm");
                            const fileName = mediaLink.href.split("/").pop();
                            const soundLink = mediaLink.title.match(/\[sound=(.+?)\]/);

                            // Check if the post should be loaded based on the mode
                            if (mode === "all" || (mode === "webm" && isVideo)) {
                                const cell = document.createElement("div");
                                setStyles(cell, {
                                    border: "1px solid var(--text-color)",
                                    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",
                                    });

                                    const video = document.createElement("video");
                                    video.src = mediaLink.href;
                                    setStyles(video, {
                                        maxWidth: "100%",
                                        maxHeight: "200px",
                                        objectFit: "contain",
                                        cursor: "pointer",
                                    });
                                    video.muted = true;
                                    video.addEventListener("click", () => {
                                        post.scrollIntoView({ behavior: "smooth" });
                                        gallerySize = {
                                            width: gridContainer.offsetWidth,
                                            height: gridContainer.offsetHeight,
                                        };
                                        document.body.removeChild(gallery);
                                    });
                                    video.controls = true;
                                    video.title = comment.textContent;

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

                                    videoContainer.appendChild(video);

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

                                        const playPauseButton = document.createElement("button");
                                        playPauseButton.textContent = "Play/Pause";
                                        playPauseButton.addEventListener("click", () => {
                                            if (video.paused && audio.paused) {
                                                video.play();
                                                audio.play();
                                            } else {
                                                video.pause();
                                                audio.pause();
                                            }
                                        });
                                        buttonDiv.appendChild(playPauseButton);

                                        const resetButton = document.createElement("button");
                                        resetButton.textContent = "Reset";
                                        resetButton.addEventListener("click", () => {
                                            video.currentTime = 0;
                                            audio.currentTime = 0;
                                        });
                                        buttonDiv.appendChild(resetButton);

                                        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: "var(--main-color)",
                                        color: "var(--text-color)",
                                    });
                                    cellButton.addEventListener("click", () => {
                                        post.scrollIntoView({ behavior: "smooth" });
                                        gallerySize = {
                                            width: gridContainer.offsetWidth,
                                            height: gridContainer.offsetHeight,
                                        };
                                        document.body.removeChild(gallery);
                                    });

                                    buttonDiv.appendChild(cellButton);
                                    cell.appendChild(videoContainer);
                                    cell.appendChild(buttonDiv);
                                } else {
                                    const image = document.createElement("img");
                                    image.src = mediaLink.href;
                                    setStyles(image, {
                                        maxWidth: "100%",
                                        maxHeight: "200px",
                                        objectFit: "contain",
                                        cursor: "pointer",
                                    });
                                    image.addEventListener("click", () => {
                                        post.scrollIntoView({ behavior: "smooth" });
                                        gallerySize = {
                                            width: gridContainer.offsetWidth,
                                            height: gridContainer.offsetHeight,
                                        };
                                        document.body.removeChild(gallery);
                                    });
                                    image.title = comment.textContent;

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

                                        const playPauseButton = document.createElement("button");
                                        playPauseButton.textContent = "Play/Pause";
                                        setStyles(playPauseButton, {
                                            position: "absolute",
                                            bottom: "10px",
                                            left: "10px",
                                        });
                                        playPauseButton.addEventListener("click", () => {
                                            if (audio.paused) {
                                                audio.play();
                                            } else {
                                                audio.pause();
                                            }
                                        });
                                        buttonDiv.appendChild(playPauseButton);
                                    }

                                    cell.appendChild(image);
                                    cell.appendChild(buttonDiv);
                                }
                                gridContainer.appendChild(cell);
                            }
                        }
                    });

                    // Store the current scroll position and grid container size when closing the gallery
                    console.log(`Last scroll position: ${lastScrollPosition} px`);
                    gridContainer.addEventListener("scroll", () => {
                        lastScrollPosition = gridContainer.scrollTop;
                        console.log(`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;
                            console.log(`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 };
                    }
                };

                loadPosts(mode); // Load posts based on the initial mode

                gallery.appendChild(gridContainer);

                const closeButton = document.createElement("button");
                closeButton.textContent = "Close";
                setStyles(closeButton, {
                    position: "absolute",
                    top: "10px",
                    right: "10px",
                    zIndex: "10000",
                    backgroundColor: "var(--main-color)",
                    color: "var(--text-color)",
                });
                closeButton.addEventListener("click", () => {
                    gallerySize = {
                        width: gridContainer.offsetWidth,
                        height: gridContainer.offsetHeight,
                    };
                    document.body.removeChild(gallery);
                });
                gallery.appendChild(closeButton);

                document.body.appendChild(gallery);
            };

            button.addEventListener("click", openImageGallery);
        } else {
            // If there are less than two posts, try again after 5 seconds
            setTimeout(loadButton, 5000);
        }
    };

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