// ==UserScript==
// @name Better Rule34
// @name:fr Meilleure règle 34
// @namespace http://tampermonkey.net/
// @version 0.90
// @description A script to improve the use of rule34!
// @description:fr Un script pour améliorer l'utilisation de rule34!
// @author You
// @match https://rule34.xxx/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM.xmlHttpRequest
// @license MIT
// @require https://unpkg.com/fflate@0.8.2
// ==/UserScript==
const defaultConfig = {
imageResizeNotice: false,
theme: "dark",
undeletePosts: true,
clickAnywhereToStart: false,
htmlVideoPlayer: false,
dynamicResizing: false,
scrollPostsIntoView: false,
downloadFullSizedImages: false,
fitImageToScreen: false,
hideAlerts: false,
ImageHover: true
};
function initializeSettings(defaultConfig) {
Object.keys(defaultConfig).forEach(key => {
if (GM_getValue(key) === undefined) {
GM_setValue(key, defaultConfig[key]);
}
});
}
initializeSettings(defaultConfig);
const dark = {
"primary": "#121212",
"secondary": "#000011",
"contrast" : "#4a4a4a",
"complementary" : "#666666",
"tableBackground" : "transparent",
"linkColor" : "#00f"
};
const themes = {
"dark": dark
}
if(GM_getValue("dynamicResizing", "false") == "true"){
const css2 = `
div.sidebar {
max-width: 30%;
}
div.sidebar li {
font-size: 120%;
}
div.content {
width: 100%;
}
.thumb {
height: 20%;
width: auto;
}
`
GM_addStyle(css2);
}
const params = new URLSearchParams(window.location.search);
const settingsData = `{
"settings": [
{
"name": "imageResizeNotice",
"description": "Remove the image resize notice",
"type": "dropdown",
"options": ["resize", "no-resize"]
},
{
"name": "theme",
"description": "Theme selection",
"type": "dropdown",
"options": ["dark", "light", "auto"]
},
{
"name": "undeletePosts",
"description": "Display deleted posts",
"type": "checkbox"
},
{
"name": "clickAnywhereToStart",
"description": "Click anywhere on the page to start the video",
"type": "checkbox"
},
{
"name": "htmlVideoPlayer",
"description": "Use HTML video player instead of the Fluid Player",
"type": "checkbox"
},
{
"name": "dynamicResizing",
"description": "Dynamically resize the page for odd aspect ratios or large screens",
"type": "checkbox"
},
{
"name": "scrollPostsIntoView",
"description": "Scroll posts into view",
"type": "checkbox"
},
{
"name": "downloadFullSizedImages",
"description": "Download the full resolution image when saving the image",
"type": "checkbox"
},
{
"name": "fitImageToScreen",
"description": "Fit image to screen (buggy)",
"type": "checkbox"
},
{
"name": "hideAlerts",
"description": "Hide script warnings",
"type": "checkbox"
},
{
"name": "ImageHover",
"description": "Displays images on the search page when hovered",
"type": "checkbox"
}
]
}`;
// Parse the settings JSON
const settingsObj = JSON.parse(settingsData);
// Function to create and display the settings overlay
function openSettings() {
// Create the overlay div
const overlay = document.createElement('div');
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.display = 'flex';
overlay.style.justifyContent = 'center';
overlay.style.alignItems = 'center';
overlay.style.zIndex = '1000'; // Ensures it overlays everything else
// Create the centered div
const centeredDiv = document.createElement('div');
centeredDiv.style.width = '25vw';
centeredDiv.style.backgroundColor = 'white';
centeredDiv.style.padding = '20px';
centeredDiv.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
centeredDiv.style.textAlign = 'left';
centeredDiv.style.display = 'flex';
centeredDiv.style.flexDirection = 'column';
centeredDiv.style.justifyContent = 'center';
centeredDiv.style.alignItems = 'stretch';
// Create form elements for each setting
settingsObj.settings.forEach(setting => {
const settingDiv = document.createElement('div');
settingDiv.style.marginBottom = '10px';
const label = document.createElement('label');
label.innerText = setting.description;
label.style.display = 'block';
label.style.marginBottom = '5px';
settingDiv.appendChild(label);
if (setting.type === "checkbox") {
// Create a checkbox for boolean values
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = GM_getValue(setting.name, "false") === "true";
checkbox.addEventListener('change', () => {
GM_setValue(setting.name, checkbox.checked.toString());
});
settingDiv.appendChild(checkbox);
} else if (setting.type === "dropdown") {
// Create a dropdown for other values
const dropdown = document.createElement('select');
const currentValue = GM_getValue(setting.name, setting.options[0]);
setting.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option;
optionElement.innerText = option;
if (option === currentValue) {
optionElement.selected = true;
}
dropdown.appendChild(optionElement);
});
dropdown.addEventListener('change', () => {
GM_setValue(setting.name, dropdown.value);
});
settingDiv.appendChild(dropdown);
}
centeredDiv.appendChild(settingDiv);
});
// Append the centered div to the overlay
overlay.appendChild(centeredDiv);
// Append the overlay to the body
document.body.appendChild(overlay);
// Optionally, add a click event to close the overlay when clicking outside the centered div
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
});
}
function setTheme(){
let currentTheme = themes[GM_getValue("theme", "false")];
if(currentTheme){
const css = `
table a:link {
color: ${currentTheme.linkColor};
}
table a:visited {
color: ${currentTheme.linkColor};
}
body {
background-color: ${currentTheme.primary};
}
.flat-list{
background-color: ${currentTheme.secondary};
}
div#header ul#subnavbar {
background-color: ${currentTheme.secondary};
}
div#header ul#navbar li.current-page {
background-image: url(https://imgs.search.brave.com/77L3MmxBu09NuN5WiX4HlbmWjjUe7eAsmBbakS7-DTo/rs:fit:120:120:1/g:ce/aHR0cHM6Ly91cGxv/YWQud2lraW1lZGlh/Lm9yZy93aWtpcGVk/aWEvY29tbW9ucy90/aHVtYi8wLzAyL1Ry/YW5zcGFyZW50X3Nx/dWFyZS5zdmcvMTIw/cHgtVHJhbnNwYXJl/bnRfc3F1YXJlLnN2/Zy5wbmc);;
}
.current-page {
background-color: ${currentTheme.secondary};
background-color: brightness(110%);
}
.manual-page-chooser>input[type=text]{
background-color: ${currentTheme.secondary};
}
.manual-page-chooser>input[type=submit]{
background-color: ${currentTheme.secondary};
color: ${currentTheme.contrast};
}
div.tag-search input[type=text]{
background-color: ${currentTheme.secondary};
color: ${currentTheme.contrast};
}
div.tag-search input[type=submit]{
background-color: ${currentTheme.secondary};
color: ${currentTheme.contrast};
}
.col2 {
color: ${currentTheme.contrast};
}
h6 {
color: ${currentTheme.contrast};
}
h5 {
color: ${currentTheme.contrast};
}
.tag-count {
color: ${currentTheme.contrast};
}
b {
color: ${currentTheme.contrast};
}
li {
color: ${currentTheme.contrast};
}
ul {
color: ${currentTheme.contrast};
}
button {
background-color: ${currentTheme.secondary};
color: ${currentTheme.contrast};
box-sizing: border-box;
border: 1px solid;
margin-top: 3px;
border-color: ${currentTheme.contrast};
}
table.highlightable td {
color: ${currentTheme.contrast};
}
h2 {
color: ${currentTheme.contrast};
}
table.form p {
color: ${currentTheme.contrast};
}
table {
color: ${currentTheme.contrast};
}
label {
color: ${currentTheme.contrast};
}
table {
background-color: ${currentTheme.tableBackground};
}
div {
color: gray;
}
`;
GM_addStyle(css);
const e=document.getElementById("user-index");e&&[...e.getElementsByTagName("p")].map(e=>(e.style.color=currentTheme.contrast));
if(GM_getValue("resizePosts", "false") == "true" && window.location.href.startsWith("https://rule34.xxx/index.php?page=post&s=view")){
GM_addStyle(".content{max-height: 45%; max-width: 45%; overflow: auto;}");
document.getElementById("image").style.maxHeight = "50%";
document.getElementById("image").style.maxWidth = "fit-content";
document.getElementById("image").style.overflow = "auto";
}
}
}
let randNum
function getFromRule34(tags, index, limit, useBlacklist = false) {
if (tags == "all") {
tags = "";
}
let pid = index;
if (useBlacklist) {
tags += (" -" + decodeURIComponent(getCookie("tag_blacklist")).replaceAll("%20", " -").replaceAll("%2F", "/"));
}
console.log(tags);
const url = `https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&tags=${encodeURIComponent(tags)}&limit=${limit}&pid=${pid}&json=1`;
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: url,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.responseText);
console.log(data);
resolve(data);
} else {
reject(new Error(`HTTP error! Status: ${response.status}`));
}
},
onerror: function(error) {
reject(new Error(`Network error! ${error}`));
}
});
});
}
function getFromRule34WithId(id) {
const url = `https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&id=${id}&json=1`;
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: url,
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.responseText);
resolve(data[0]);
} else {
reject(new Error(`HTTP error! Status: ${response.status}`));
}
},
onerror: function(error) {
reject(new Error(`Network error! ${error}`));
}
});
});
}
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
function getTagsFromUrl(currentUrl) {
if(currentUrl.startsWith("https://rule34.xxx/index.php?page=post&s=list&tags=")) {
return currentUrl.replace("https://rule34.xxx/index.php?page=post&s=list&tags=", "");
}
}
function creatLinks() {
try {
if (window.location.href.startsWith("https://rule34.xxx/index.php?page=post&s=list&tags=")) {
var anchors = document.getElementsByClassName("image-list")[0].getElementsByTagName("a");
if (anchors.length > 0) {
for (var i = 0; i < anchors.length; i++) {
const urlParams = new URLSearchParams(window.location.search);
let pageNum = parseInt(urlParams.get("pid"));
if(!pageNum){pageNum=0}
anchors[i].href = (anchors[i].href + "&srchTags=" + getTagsFromUrl(window.location.href) + "&index=" + (i + pageNum).toString()).replace(/[\?&]pid=\d*/g, '');;
}
} else {
throw new Error("No elements found with class name 'image-list' or no anchor elements found within that class.");
}
} else {
throw new Error("The current URL does not start with 'https://rule34.xxx/index.php?page=post&s=list&tags='.");
}
} catch (error) {
console.error("An error occurred in creatLinks: " + error);
}
}
var preloadedData; // Variable to store the preloaded JSON data
// Function to preload data for the next post
function preloadNextPost(srchTags, nextIndex, limit) {
getFromRule34(srchTags, nextIndex, limit, true)
.then(jsonInfo => {
console.log(jsonInfo)
preloadedData = jsonInfo;
console.log(preloadedData)
})
.catch(error => {
console.error("An error occurred during API request:", error);
});
}
// Function to navigate to the next post using preloaded data
function navigateToNextPost(srchTags, nextIndex) {
if (!preloadedData || !preloadedData.length) {
console.error("No preloaded data available. Cannot proceed.");
return;
}
const nextPostId = preloadedData[0].id;
const newUrl = `https://rule34.xxx/index.php?page=post&s=view&id=${nextPostId}&srchTags=${encodeURIComponent(srchTags)}&index=${nextIndex}`;
window.location.href = newUrl;
}
// Event listener for the "DOMContentLoaded" event
setTimeout(function(){
const urlParams = new URLSearchParams(window.location.search);
const srchTags = urlParams.get("srchTags");
const currentIndex = parseInt(urlParams.get("index"));
if (!srchTags || isNaN(currentIndex)) {
console.error("Invalid URL parameters. Cannot proceed.");
return;
}
const nextIndex = currentIndex + 1;
console.log(nextIndex)
const limit = 1000;
// Preload data for the next post
preloadNextPost(srchTags, nextIndex, 1);
// Event listener for when the user tries to navigate to the next post
document.getElementById("nextButton").addEventListener("click", () => {
navigateToNextPost(srchTags, nextIndex);
});
},1000);
function backPost() {
const urlParams = new URLSearchParams(window.location.search);
const srchTags = urlParams.get("srchTags");
const currentIndex = parseInt(urlParams.get("index"), 10);
if (!srchTags || isNaN(currentIndex)) {
console.error("Invalid URL parameters. Cannot proceed.");
return;
}
const nextIndex = currentIndex - 1;
const limit = 1;
getFromRule34(srchTags, nextIndex, limit)
.then(jsonInfo => {
if (!jsonInfo || !jsonInfo.length) {
console.error("No data received from API. Cannot proceed.");
return;
}
const nextPostId = jsonInfo[0].id;
const newUrl = `https://rule34.xxx/index.php?page=post&s=view&id=${nextPostId}&srchTags=${encodeURIComponent(srchTags)}&index=${nextIndex}`;
window.location.href = newUrl;
})
.catch(error => {
console.error("An error occurred during API request:", error);
});
}
async function randomVideo() {
const urlParams = new URLSearchParams(window.location.search);
let srchTags = urlParams.get("tags");
if (!srchTags) {
// If tags parameter is not found in the URL, get the value from the input element
const tagsInput = document.querySelector("input[name='tags']");
srchTags = tagsInput.value.replace(/ /g, "+");
}
const posts = await getFromRule34(srchTags, 0, 1000);
if (posts.length === 0) {
console.error("No posts found for the given tags. Cannot proceed.");
return;
}
const randNum = Math.floor(Math.random() * posts.length);
const postId = posts[randNum].id;
const newUrl = `https://rule34.xxx/index.php?page=post&s=view&id=${postId}&tags=${encodeURIComponent(srchTags)}&index=${randNum}`;
window.location.href = newUrl;
}
async function downloadAllPostFiles() {
const urlParams = new URLSearchParams(window.location.search);
let srchTags = urlParams.get("tags");
if (!srchTags) {
const tagsInput = document.querySelector("input[name='tags']");
srchTags = tagsInput.value.replace(/ /g, "+");
}
const posts = await getFromRule34(srchTags, 0, 1000);
if (posts.length === 0) {
console.error("No posts found for the given tags. Cannot proceed.");
return;
}
const zipFiles = {};
const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv', '.wmv', '.mpeg']; // Video file extensions
let postsDownloaded = 0;
let totalPosts = posts.length;
for (const post of posts) {
const fileUrl = post.file_url;
const sampleUrl = post.sample_url;
const fileExtension = fileUrl.slice(fileUrl.lastIndexOf('.')).toLowerCase();
// Use sample_url for videos, otherwise use file_url
const downloadUrl = videoExtensions.includes(fileExtension) ? sampleUrl : fileUrl;
const fileName = downloadUrl.split("/").pop();
try {
const fileData = await fetchFileAsBlob(downloadUrl, fileName);
const uint8Array = new Uint8Array(await fileData.arrayBuffer());
zipFiles[fileName] = uint8Array;
console.log(`Added file ${fileName} to zip`);
postsDownloaded++;
document.title = `${postsDownloaded}/${totalPosts}`;
} catch (error) {
console.error(`Error fetching file ${downloadUrl}:`, error);
}
}
try {
const zipBlob = fflate.zipSync(zipFiles, {
level: 0,
mtime: new Date(),
});
console.log("Zip finished");
const a = document.createElement("a");
const zipBlobUrl = URL.createObjectURL(new Blob([zipBlob], { type: "application/zip" }));
a.href = zipBlobUrl;
a.download = `${srchTags.replace(/\+/g, "_")}.zip`;
a.click();
} catch (error) {
console.error("Error generating ZIP file:", error);
}
}
function fetchFileAsBlob(url, fileName) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: "GET",
url: url,
responseType: "blob",
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const contentType = response.responseHeaders && response.responseHeaders['Content-Type'];
resolve(response.response);
} else {
reject(new Error(`Failed to fetch ${url}: ${response.statusText}`));
}
},
onerror: function(error) {
reject(new Error(`Error fetching ${url}: ${error}`));
}
});
});
}
function makeButtons(){
let btn = document.createElement("button");
btn.innerHTML = "Random";
btn.onclick = randomVideo;
let btn4 = document.createElement("button");
btn4.innerHTML = "↓";
btn4.onclick = downloadAllPostFiles;
if(document.getElementsByClassName("tag-search")[0]){document.getElementsByClassName("tag-search")[0].appendChild(btn); document.getElementsByClassName("tag-search")[0].appendChild(btn4)};
if(document.getElementsByClassName("image-sublinks")[0]){
let btn3 = document.createElement("button");
btn3.innerHTML = "back";
btn3.onclick = backPost;
document.getElementsByClassName("image-sublinks")[0].appendChild(btn3);
let btn2 = document.createElement("button");
btn2.innerHTML = "next";
btn2.id = "nextButton"
document.getElementsByClassName("image-sublinks")[0].appendChild(btn2);
}
}
function allowInputResize(){
const awesompleteElement = document.getElementsByClassName("awesomplete")[0].childNodes[0];
// Add input event listener
awesompleteElement.addEventListener('input', resizeInput.bind(awesompleteElement));
// Add click event listener
awesompleteElement.addEventListener('click', resizeInput.bind(awesompleteElement));
// Add blur (focus loss) event listener
awesompleteElement.addEventListener('blur', restoreNormalSize.bind(awesompleteElement));
awesompleteElement.style.position = "relative"
awesompleteElement.style.zIndex = 99
// The resizeInput function
function resizeInput() {
this.style.minWidth = "100%"
this.style.width = this.value.length + "ch";
}
// Function to restore normal size
function restoreNormalSize() {
this.style.width = "100%"; // Clear the inline width style
}
}
const imageSublinks = document.getElementsByClassName("image-sublinks")[0];
if (imageSublinks) {
document.addEventListener("keydown", function(event) {
// Check if the active element is an input element
const activeElement = document.activeElement;
const isInputElement = activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA";
// If it's an input element, don't execute the functions
if (isInputElement) {
return;
}
// If it's not an input element, execute the functions based on the key press
if (event.keyCode === 39) {
const urlParams = new URLSearchParams(window.location.search);
const srchTags = urlParams.get("srchTags");
const currentIndex = parseInt(urlParams.get("index"));
const nextIndex = currentIndex + 1;
navigateToNextPost(srchTags, nextIndex);
} else if (event.keyCode === 37) {
backPost();
}
});
}
async function addDeletedPosts(id) {
let statusNotices = document.getElementsByClassName("status-notice");
if (statusNotices.length > 0) {
let foundDeletedPost = false;
// Iterate through each status notice
for (let i = 0; i < statusNotices.length; i++) {
let statusNotice = statusNotices[i];
if (statusNotice.firstChild.data.startsWith("This post was")) {
foundDeletedPost = true;
try {
const mediaJson = await getFromRule34WithId(id);
const mediaUrl = mediaJson.file_url;
const mediaType = mediaUrl.split('.').pop().toLowerCase();
const videoExtensions = [
"mp4", "webm", "ogg", "mov", "avi", "wmv", "flv", "mkv",
"3gp", "m4v", "mpg", "mpeg", "swf", "vob", "m2ts"
];
const imageExtensions = [
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "tif", "svg",
"webp", "heic", "heif", "ico", "raw", "psd", "ai", "eps"
];
if (videoExtensions.includes(mediaType)) {
let video = document.createElement("video");
video.src = mediaUrl;
video.controls = true;
video.style.maxHeight = "70%";
video.style.maxWidth = "70%";
video.style.overflow = "auto";
document.getElementById("fit-to-screen").appendChild(video);
} else if (imageExtensions.includes(mediaType)) {
let image = document.createElement("img");
image.src = mediaUrl;
image.style.maxHeight = "70%";
image.style.maxWidth = "70%";
image.style.overflow = "auto";
document.getElementById("fit-to-screen").appendChild(image);
}
statusNotice.remove();
} catch (e) {
console.error(e);
}
// Exit loop after handling first deleted post
break;
}
}
if (!foundDeletedPost) {
console.log("This post is not deleted.");
}
} else {
console.log("The status-notices element is not present on this page.");
}
}
async function downloadFile(fileUrl, filename) {
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: fileUrl,
responseType: 'blob',
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
const blob = response.response;
// Create a temporary link element to trigger the download
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename; // Use the provided filename
link.click();
// Clean up the temporary URL object
URL.revokeObjectURL(link.href);
resolve();
} else {
reject(new Error(`HTTP error! Status: ${response.status}`));
}
},
onerror: function(error) {
reject(new Error(`Network error! ${error}`));
}
});
});
}
function getLinksInDiv(element) {
var elements = element.parentNode.querySelectorAll("a");
return(elements[elements.length - 1])
}
function resizePostPopup(){
try {
if(GM_getValue("imageResizeNotice", "false") == "resize"){$('resized_notice').hide()}
if(GM_getValue("imageResizeNotice", "false") == "orignal"){Post.highres(); $('resized_notice').hide();}
} catch (e) {
console.error(e);
}
}
function startVideo(){
if (document.getElementById("gelcomVideoPlayer_fluid_initial_play")) {
document.getElementById("gelcomVideoPlayer").autoplay = true;
}
}
function addTagButtons(){
const classList = ["tag-type-copyright", "tag-type-general", "tag-type-character", "tag-type-artist","tag-type-metadata"]
for (const curClass of classList) {
const elements = document.getElementsByClassName(curClass);
for (const element of elements) {
const button = document.createElement("button");
button.innerHTML = "+";
button.onclick = function() {console.log(" " + getLinksInDiv(this).innerText); document.getElementsByName("tags")[0].value += " " + (getLinksInDiv(this).innerText.trim()).replaceAll(" ","_")}
element.insertBefore(button, element.firstChild);
}
}
}
function stretchyDiv(isImage){
let div;
if(isImage == 0){div = document.getElementById("fluid_video_wrapper_gelcomVideoPlayer")} else {div = document.getElementById("image")}
if(isImage == 1){
let newDiv = document.createElement("div");
newDiv.style.position = "relative";
div.parentNode.insertBefore(newDiv, div);
newDiv.appendChild(div);
div = newDiv;
document.getElementById("image").maxHeight = 9999
}
const resizer = document.createElement("div");
resizer.style.width = "10px";
resizer.style.height = "10px";
resizer.style.backgroundColor = "white";
resizer.style.position = "absolute";
resizer.style.bottom = "0";
resizer.style.right = "0";
resizer.style.cursor = "se-resize";
resizer.style.zIndex = "10";
let isResizing = false;
let currentX;
let currentY;
let initialWidth;
let initialHeight;
resizer.addEventListener("mousedown", function(e) {
document.body.style.userSelect = 'none';
isResizing = true;
currentX = e.clientX;
currentY = e.clientY;
initialWidth = parseFloat(getComputedStyle(div, null).getPropertyValue("width").replace("px", ""));
initialHeight = parseFloat(getComputedStyle(div, null).getPropertyValue("height").replace("px", ""));
});
document.addEventListener("mouseup", function() {
document.body.style.userSelect = '';
isResizing = false;
});
document.addEventListener("mousemove", function(e) {
if (isResizing) {
let inner = div.getElementsByTagName("img")[0];
let newWidth = initialWidth + (e.clientX - currentX);
let newHeight = initialHeight + (e.clientY - currentY);
if (!e.shiftKey) {
// Resize both width and height at the same rate
let ratio = initialWidth / initialHeight;
newHeight = newWidth / ratio;
}
if(isImage == 1){inner.style.width = newWidth + "px"; inner.style.height = newHeight + "px";}
div.style.width = newWidth + "px";
div.style.height = newHeight + "px";
div.style.maxHeight = "1000vh"
if(document.getElementById("gelcomVideoPlayer")){
document.getElementById("gelcomVideoPlayer").style.maxHeight = "1000vh"
document.getElementById("gelcomVideoPlayer").style.height = "100%";
}
if(document.getElementById("image")){
document.getElementById("image").style.width = newWidth + "px";
document.getElementById("image").style.height = newHeight + "px";
document.getElementById("image").style.maxHeight = "1000vh"
}
}
});
let strechySquare = div.appendChild(resizer);
}
function addInputBox(){
let inputBox = document.createElement("input");
let tagsElement = document.querySelector("[name='tags']");
inputBox.type = "text"
tagsElement.after(inputBox);
}
setTimeout(function(){
if (document.getElementById("fluid_video_wrapper_gelcomVideoPlayer")) {
stretchyDiv(0);
} else if (document.getElementById("image")) {
stretchyDiv(1);
}
}, 300);
function setTags(tags){
document.getElementsByName("tags")[0].value = tags
}
async function htmlVideoPlayer(id) {
if (document.getElementById("gelcomVideoContainer")) {
try {
const videoUrl = await getFromRule34WithId(id);
// Create video element
let video = document.createElement("video");
video.src = videoUrl.file_url;
video.controls = true;
video.style.maxHeight = "70%";
video.style.maxWidth = "70%";
video.style.overflow = "auto";
// Append video after gelcomVideoContainer
let gelcomVideoContainer = document.getElementById("gelcomVideoContainer");
gelcomVideoContainer.parentNode.insertBefore(video, gelcomVideoContainer.nextSibling);
// Remove gelcomVideoContainer
gelcomVideoContainer.remove();
// Remove status-notices if present
let statusNotices = document.getElementById("status-notices");
if (statusNotices) {
statusNotices.remove();
}
} catch (e) {
console.error(e);
}
}
}
function addCloseButtonToStatusNotice() {
const statusNoticeElements = document.querySelectorAll('.status-notice');
statusNoticeElements.forEach(element => {
const closeButton = document.createElement('button');
closeButton.textContent = 'x';
closeButton.addEventListener('click', () => {
element.parentNode.removeChild(element);
});
closeButton.style.background = 'none';
closeButton.style.border = 'none';
closeButton.style.cursor = 'pointer';
element.appendChild(closeButton);
});
}
async function overlayFullSizeImage(){
const urlParams = new URLSearchParams(window.location.search);
let id = urlParams.get("id");
const postJson = await getFromRule34WithId(id);
// Get the element with id "image"
const originalImage = document.getElementById("image");
// Create a new transparent image element
const newImage = document.createElement("img");
newImage.src = postJson.file_url;
newImage.style.opacity = "0"; // Set opacity to 0 for transparency
// Set the size of the new image to match the size of the original image
newImage.style.width = originalImage.width + "px";
newImage.style.height = originalImage.height + "px";
// Position the new image on top of the original image
newImage.style.position = "absolute";
newImage.style.top = originalImage.offsetTop + "px";
newImage.style.left = originalImage.offsetLeft + "px";
newImage.style.zIndex = "1"; // Set a higher z-index to overlay on top
// Insert the new transparent image before the original image
originalImage.parentNode.insertBefore(newImage, originalImage);
}
function convertSearchToLink(){
document.getElementsByName("commit")[0].innerHTML = `<a href="https://rule34.xxx/index.php?page=post&s=list&tags=all">${document.getElementsByName("commit")[0].innerHTML}</a>`
}
function fitPostToScreen(){
if(GM_getValue("downloadFullSizedImages", "false") == "true" && GM_getValue("hideAlerts", "false") == "false"){window.alert(`downloadFullSizedImage and fitImageToScreen often cause bugs when used together. To disable this alert turn hide alerts on in settings.`)}
let postElement
if(document.getElementById("fluid_video_wrapper_gelcomVideoPlayer")){postElement = document.getElementById("fluid_video_wrapper_gelcomVideoPlayer")} else {postElement = document.getElementById("image")}
postElement.style.maxHeight = "85vh"
postElement.style.width = "auto"
if(document.getElementById("gelcomVideoPlayer")){
document.getElementById("gelcomVideoPlayer").style.maxHeight = "85vh"
document.getElementById("gelcomVideoPlayer").style.width = "auto"
}
}
function addDownloadButtonToPosts() {
// CSS for the spinner
const style = document.createElement('style');
style.innerHTML = `
.spinner {
border: 2px solid #f3f3f3; /* Light grey */
border-top: 2px solid #3498db; /* Blue */
border-radius: 50%;
width: 12px;
height: 12px;
animation: spin 1s linear infinite;
display: none; /* Initially hidden */
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading .spinner {
display: inline-block; /* Show spinner */
}
.loading .button-text {
display: none; /* Hide button text */
}
`;
document.head.appendChild(style);
// Get all elements with the class "thumb"
const thumbElements = document.querySelectorAll('.thumb');
thumbElements.forEach(thumb => {
// Create a button element
const button = document.createElement('button');
button.style.position = 'absolute';
button.style.top = '0';
button.style.right = '0';
// Create span for button text
const buttonText = document.createElement('span');
buttonText.classList.add('button-text');
buttonText.innerHTML = '↓'; // Down arrow ASCII char
button.appendChild(buttonText);
// Create spinner element
const spinner = document.createElement('div');
spinner.classList.add('spinner');
button.appendChild(spinner);
// Add an event listener to the button
button.addEventListener('click', async () => {
button.classList.add('loading'); // Add loading class
try {
const aElement = thumb.querySelector('a');
if (aElement) {
const id = aElement.id;
const modifiedId = id.substring(1); // Remove the first character
const data = await getFromRule34WithId(modifiedId);
if (data) {
const fileUrl = data.file_url;
const sampleUrl = data.sample_url;
const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov', '.webm', '.flv', '.wmv', '.mpeg'];
const fileExtension = fileUrl.split('.').pop().toLowerCase();
const downloadUrl = videoExtensions.includes(fileExtension) ? sampleUrl : fileUrl;
const filename = downloadUrl.split('/').pop();
await downloadFile(downloadUrl, filename);
}
} else {
alert('No <a> element found within this thumb element.');
}
} catch (error) {
console.error('Error downloading file:', error);
} finally {
button.classList.remove('loading'); // Remove loading class
}
});
// Append the button to the thumb element
thumb.style.position = 'relative'; // Ensure the thumb element is positioned relatively
thumb.appendChild(button);
});
}
async function displayMediaData() {
const urlParams = new URLSearchParams(window.location.search);
let id = urlParams.get("id");
const postJson = await getFromRule34WithId(id);
let mediaURL = postJson.file_url;
const mediaType = mediaURL.split('.').pop().toLowerCase();
// Create a container for the tooltip
const tooltipContent = document.createElement('div');
if (mediaType === 'jpg' || mediaType === 'jpeg' || mediaType === 'png' || mediaType === 'gif') {
// Handle images
const img = new Image();
img.src = mediaURL;
img.onload = () => {
const width = img.width;
const height = img.height;
// Fetch the image using GM.xmlHttpRequest to get its size
GM.xmlHttpRequest({
method: 'GET',
url: mediaURL,
responseType: 'blob',
onload: (response) => {
const imageSizeBytes = response.response.size;
const imageSizeKB = imageSizeBytes / 1024;
const imageSizeMB = imageSizeKB / 1024;
let sizeInfo = '';
if (imageSizeMB >= 1) {
sizeInfo = `${imageSizeMB.toFixed(2)} MB`;
} else {
sizeInfo = `${imageSizeKB.toFixed(2)} KB`;
}
// Create the tooltip content
tooltipContent.innerHTML = `
<div>Media URL: ${mediaURL}</div>
<div>Media Type: ${mediaType}</div>
<div>Width: ${width}px</div>
<div>Height: ${height}px</div>
<div>Size: ${sizeInfo}</div>
`;
appendTooltipToPage(tooltipContent);
},
onerror: (error) => {
console.error('Failed to fetch image size:', error);
}
});
};
img.onerror = (error) => {
console.error('Failed to load image:', error);
};
} else if (mediaType === 'mp4' || mediaType === 'webm' || mediaType === 'ogg') {
// Handle videos
const video = document.createElement('video');
video.src = mediaURL;
mediaURL = postJson.sample_url;
video.onloadedmetadata = () => {
const width = video.videoWidth;
const height = video.videoHeight;
const duration = video.duration;
// Fetch the video using GM.xmlHttpRequest to get its size
GM.xmlHttpRequest({
method: 'GET',
url: mediaURL,
responseType: 'blob',
onload: (response) => {
const videoSizeBytes = response.response.size;
const videoSizeKB = videoSizeBytes / 1024;
const videoSizeMB = videoSizeKB / 1024;
let sizeInfo = '';
if (videoSizeMB >= 1) {
sizeInfo = `${videoSizeMB.toFixed(2)} MB`;
} else {
sizeInfo = `${videoSizeKB.toFixed(2)} KB`;
}
// Calculate frame rate using approximate method
video.currentTime = 1;
video.onseeked = () => {
// Create the tooltip content
tooltipContent.innerHTML = `
<div>Media URL: ${mediaURL}</div>
<div>Media Type: ${mediaType}</div>
<div>Width: ${width}px</div>
<div>Height: ${height}px</div>
<div>Size: ${sizeInfo}</div>
`;
appendTooltipToPage(tooltipContent);
};
},
onerror: (error) => {
console.error('Failed to fetch video size:', error);
}
});
};
video.onerror = (error) => {
console.error('Failed to load video:', error);
};
} else {
console.error('Unsupported media type:', mediaType);
}
}
function appendTooltipToPage(tooltipContent) {
// Create the information icon element
const infoIcon = document.createElement('span');
infoIcon.innerHTML = 'ℹ️';
infoIcon.style.cursor = 'pointer';
infoIcon.style.marginLeft = '10px';
infoIcon.title = 'Media Information';
// Create the tooltip element
const tooltip = document.createElement('div');
tooltip.appendChild(tooltipContent);
tooltip.style.position = 'absolute';
tooltip.style.backgroundColor = '#fff';
tooltip.style.border = '1px solid #ccc';
tooltip.style.padding = '10px';
tooltip.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
tooltip.style.display = 'none';
tooltip.style.zIndex = '1000';
// Append the tooltip to the info icon
infoIcon.appendChild(tooltip);
// Show the tooltip on hover
infoIcon.onmouseover = () => {
tooltip.style.display = 'block';
};
infoIcon.onmouseout = () => {
tooltip.style.display = 'none';
};
// Append the info icon to the element with class "image-sublinks"
const imageSublinks = document.querySelector('.image-sublinks');
if (imageSublinks) {
imageSublinks.appendChild(infoIcon);
}
}
let activeImageContainer = null; // Track the currently active image container
async function addHoverEffect() {
// Select all images with the class 'thumb'
const thumbImages = document.querySelectorAll('.thumb');
// Loop through each image and add an event listener for hover
thumbImages.forEach(img => {
img.addEventListener('mouseenter', async function(event) {
// If there's an existing active image container, remove it first
if (activeImageContainer) {
activeImageContainer.remove();
}
// Get the ID of the hovered image and remove non-numeric characters
let id = img.id.replace(/\D/g, '');
// Fetch the data for the image using its ID
const postData = await getFromRule34WithId(id);
if (!postData || !postData.file_url) {
console.error(`No file_url found for ID: ${id}`);
return;
}
// Create the image container div
const imageContainer = document.createElement('div');
imageContainer.style.position = 'absolute';
imageContainer.style.zIndex = '1000';
imageContainer.style.border = '2px solid black';
imageContainer.style.padding = '10px';
imageContainer.style.backgroundColor = 'white';
// Create the image element
const image = document.createElement('img');
image.style.maxWidth = '300px'; // Limit the size of the displayed image
image.style.maxHeight = '300px'; // Limit the size of the displayed image
image.src = postData.file_url;
// Append the image to the container
imageContainer.appendChild(image);
// Append the image container to the body
document.body.appendChild(imageContainer);
// Set the current active container to this one
activeImageContainer = imageContainer;
// Move the image container to the cursor position only if the mouse is still on the image
function moveImageAtCursor(e) {
// Check if the mouse is still on the image element
if (img.matches(':hover')) {
imageContainer.style.left = `${e.pageX + 10}px`; // Slight offset from cursor
imageContainer.style.top = `${e.pageY + 10}px`; // Slight offset from cursor
} else {
// If the mouse is no longer over the image, remove the image container
imageContainer.remove();
document.removeEventListener('mousemove', moveImageAtCursor);
activeImageContainer = null; // Reset active container
}
}
// Track mousemove to move the image with the cursor
document.addEventListener('mousemove', moveImageAtCursor);
// Remove the image when mouse leaves the image
img.addEventListener('mouseleave', function() {
imageContainer.remove();
document.removeEventListener('mousemove', moveImageAtCursor);
activeImageContainer = null; // Reset active container
});
});
});
}
if(window.location.href.startsWith("https://rule34.xxx/index.php?page=post&s=view")){
setTags(params.get("srchTags"))
} else if(window.location.href.startsWith("https://rule34.xxx/index.php?page=post&s=list")){
setTags(params.get("tags"))
}
let isFirstClick = true;
document.addEventListener("click", function() {
if (isFirstClick) {
startVideo()
isFirstClick = false;
}
});
if(GM_getValue("scrollPostsIntoView", "false")){
// Function to scroll an element into view
function scrollIntoView(element) {
if (element) {
setTimeout(function(){element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' })},250);
}
}
// Try to find the element with id "image"
var imageElement = document.querySelector('#image');
// If not found, try to find the element with id "gelcomVideoPlayer"
if (!imageElement) {
var videoPlayerElement = document.querySelector('#gelcomVideoPlayer');
scrollIntoView(videoPlayerElement);
} else {
scrollIntoView(imageElement);
}
}
GM_registerMenuCommand('Settings', openSettings);
makeButtons();
creatLinks()
addCloseButtonToStatusNotice()
setTheme()
setTimeout(resizePostPopup, 100)
setTimeout(addTagButtons, 100)
setTimeout(allowInputResize, 100)
try {
// Extract the ID from the URL
const urlParams = window.location.href.split("&");
const idParam = urlParams.find(part => part.startsWith("id="));
if (idParam) {
const id = idParam.replace("id=", "");
// Handle undeleted posts
if (GM_getValue("undeletePosts", "false") === "true") {
setTimeout(() => addDeletedPosts(id), 500);
}
// Handle HTML video player
if (GM_getValue("htmlVideoPlayer", "false") === "true") {
setTimeout(() => htmlVideoPlayer(id), 500);
}
} else {
console.log("No 'id' parameter found in the URL.");
}
} catch (e) {
console.log("Error in script execution:", e);
}
if (GM_getValue("downloadFullSizedImages", "false") === "true") {
setTimeout(overlayFullSizeImage, 500);
}
if (GM_getValue("fitImageToScreen", "false") === "true") {
setTimeout(fitPostToScreen, 500);
}
if (GM_getValue("ImageHover", "false") === "true") {
setTimeout(addHoverEffect, 500);
}
setTimeout(displayMediaData, 500);
setTimeout(addDownloadButtonToPosts, 500);
let noteBoxes = document.querySelectorAll(".note-box");
noteBoxes.forEach(function(noteBox) {
noteBox.style.zIndex = "999";
});