- // ==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!");
- })();