您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
4chan grid based Image Gallery for threads that can load images, images with sounds, webms with sounds (Button on the Bottom Right)
当前为
// ==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!"); })();