A script to improve the use of rule34, now with API key support!
< Valutazione su Better Rule34
Hi. I asked ChatGPT that question, and with 2 hours of spare time, I came up with this patched version that includes only these two buttons. I tested this script and it worked for me (using brave browser with tampermonkey). edit: The download all button not working(((
// ==UserScript==
// @name Better Rule34 fixed
// @namespace http://tampermonkey.net/
// @version 0.90
// @description barebone with only random and download all fixes
// @author user342247947999
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM.xmlHttpRequest
// @require https://unpkg.com/[email protected]
// ==/UserScript==
;(function(){
"use strict";
// ——— Simple GM+xmlHttpRequest to get text or blob ———
function gmFetchText(url){
return new Promise((res, rej) => {
GM.xmlHttpRequest({
method: "GET",
url,
responseType: "text",
anonymous: false,
headers: {
Referer: "https://rule34.xxx/",
Origin: "https://rule34.xxx"
},
onload: r => (r.status>=200&&r.status<300) ? res(r.responseText) : rej(r.status),
onerror: e => rej(e)
});
});
}
// ——— Read your current tags field (“foo bar” → “foo+bar”) ———
function getTags(){
const form = document.querySelector('form[action*="s=list"]')
|| document.querySelector(".tag-search")
|| document;
const inp = form.querySelector("input[name=tags]");
return inp ? inp.value.trim().replace(/\s+/g,"+") : "";
}
// ——— Random logic by scraping pages and thumbnails ———
async function doRandom(){
try {
const tags = getTags();
const base =
"https://rule34.xxx/index.php?page=post&s=list"
+ (tags? `&tags=${tags}` : "");
// 1) load first page HTML
const html1 = await gmFetchText(base);
const doc1 = new DOMParser().parseFromString(html1, "text/html");
// 2) find all pagination links (<a href="…pid=xxxx">) and collect pids
const pidSet = new Set();
doc1.querySelectorAll('a[href*="pid="]').forEach(a=>{
try {
const u = new URL(a.href, base);
pidSet.add(u.searchParams.get("pid")||"0");
}catch(e){}
});
// always include 0
pidSet.add("0");
const pids = Array.from(pidSet);
if (!pids.length) throw "no pages found";
// 3) pick a random pid
const pickPid = pids[Math.floor(Math.random()*pids.length)];
// 4) fetch that page
const pageUrl = pickPid==="0"? base : base + "&pid=" + pickPid;
const html2 = await gmFetchText(pageUrl);
const doc2 = new DOMParser().parseFromString(html2, "text/html");
// 5) collect all thumbnail <a> under .image-list
const thumbs = Array.from(doc2.querySelectorAll(".image-list a"));
if (!thumbs.length) throw "no thumbnails";
// 6) pick one at random and navigate
const pick = thumbs[Math.floor(Math.random()*thumbs.length)];
location.href = pick.href;
}
catch(err){
alert("Random failed: " + err);
}
}
// ——— DAPI JSON fetch (returns parsed array) ———
function apiJSON(url){
return new Promise((res, rej) => {
GM.xmlHttpRequest({
method: "GET",
url: url + "&json=1",
responseType: "json",
anonymous: false,
headers: {
Referer: "https://rule34.xxx/",
Origin: "https://rule34.xxx"
},
onload(r){
if(r.status>=200&&r.status<300){
// some GM versions put parsed JSON in .response, others raw in .responseText
const data = r.response || JSON.parse(r.responseText||"[]");
return res(data);
}
rej("HTTP " + r.status);
},
onerror:e=>rej(e)
});
});
}
// ——— Download All logic ———
async function doDownloadAll(){
try {
const tags = getTags();
const idxUrl =
"https://rule34.xxx/index.php?page=dapi&s=post&q=index"
+ "&limit=1000&pid=0"
+ (tags? `&tags=${tags}` : "");
const posts = await apiJSON(idxUrl);
if (!Array.isArray(posts) || !posts.length) throw "no posts";
const zipFiles = {};
for (let p of posts){
// use sample for video to reduce size
const ext = p.file_url.split(".").pop().toLowerCase();
const isVid = ["mp4","webm","avi","mov"].includes(ext);
const dl = isVid? p.sample_url : p.file_url;
const blob = await fetch(dl).then(r=>r.blob());
const name = dl.split("/").pop();
zipFiles[name] = new Uint8Array(await blob.arrayBuffer());
}
const zipped = fflate.zipSync(zipFiles, { level: 0 });
const blob = new Blob([zipped], { type:"application/zip" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = (tags||"all") + ".zip";
document.body.appendChild(a);
a.click();
a.remove();
}
catch(err){
alert("Download All failed: " + err);
}
}
// ——— Inject our two buttons into the search form ———
function inject(){
const form = document.querySelector('form[action*="s=list"]')
|| document.querySelector(".tag-search");
if (!form) return;
const btnR = document.createElement("button");
btnR.textContent = "Random";
btnR.style.marginLeft = "8px";
btnR.onclick = e => { e.preventDefault(); doRandom(); };
const btnD = document.createElement("button");
btnD.textContent = "Download All";
btnD.style.marginLeft = "4px";
btnD.onclick = e => { e.preventDefault(); doDownloadAll(); };
form.appendChild(btnR);
form.appendChild(btnD);
}
window.addEventListener("load", ()=> setTimeout(inject, 300));
})();
and tested with Thorium x Violentmonkey
Also, pressing "Random" also redirects to the post page with tag "all", no matter which version of the site I use. Also, no matter what version of the site I use, pressing the "Download All" button below the search bar also does nothing, also no matter which version of the site I use (as in, it doesn't work neither in desktop version, nor in mobile version.)