// ==UserScript==
// @name Better R34Hentai
// @description Add custom filters, tag markers to animated posts, and fix spaces in URLs
// @version 1.0.0
// @match *://rule34hentai.net/*
// @match *://www.rule34hentai.net/*
// @author Alighieri
// @icon https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx
// @grant none
// @run-at document-end
// @namespace https://greasyfork.org/users/1399147
// ==/UserScript==
(function() {
'use strict';
// Fix bad URL
var currentURL = window.location.href;
var fixedURL = currentURL.replace(/%20|\s/g, '+');
if (currentURL !== fixedURL) {
window.location.href = fixedURL;
}
const allAnchors = document.querySelectorAll('a')
allAnchors.forEach((anchor) => {
anchor.href = anchor.href.replace(/%20|\s/g, '+');
})
// ==================================================
// Custom filters
let userSort = localStorage.getItem('userSort');
let userRating = localStorage.getItem('userRating');
let userPrefs = localStorage.getItem('userPrefs');
userPrefs = JSON.parse(userPrefs);
const form = document.querySelector('#Navigationleft > div > form');
const customHtml = `<style>
@import url('https://fonts.googleapis.com/css?family=Poppins:300,400,500,700');
:root {
--light-color: #232B32;
--dark-color: #152028;
--border-color: #444;
--space-xxxs: calc(0.375 * 1rem);
--body-line-height: 1.4;
--checkbox-radio-size: 18px;
--checkbox-radio-gap: calc(0.375 * 1rem);
--checkbox-radio-border-width: 2px;
--checkbox-radio-line-height: 1.4;
--radio-marker-size: 8px;
--checkbox-marker-size: 12px;
--checkbox-radius: 4px;
--color-bg: hsl(0, 0%, 100%);
--color-contrast-low: hsl(240, 4%, 65%);
--color-contrast-lower: hsl(240, 4%, 85%);
--color-primary: #ecb307;
--radius: 0.375em;
--radius-md: var(--radius, 0.375em);
}
.radio,
.checkbox {
position: absolute;
margin: 0 !important;
padding: 0 !important;
opacity: 0;
height: 0;
width: 0;
pointer-events: none;
}
.radio+label,
.checkbox+label {
display: inline-flex;
align-items: flex-start;
line-height: var(--checkbox-radio-line-height);
user-select: none;
cursor: pointer;
}
.radio+label::before,
.checkbox+label::before {
content: '';
display: inline-block;
position: relative;
top: calc((1em * var(--checkbox-radio-line-height) - var(--checkbox-radio-size)) / 2);
flex-shrink: 0;
width: var(--checkbox-radio-size);
height: var(--checkbox-radio-size);
background-color: var(--color-bg);
border-width: var(--checkbox-radio-border-width);
border-color: var(--color-contrast-low);
border-style: solid;
background-repeat: no-repeat;
background-position: center;
margin-right: var(--checkbox-radio-gap);
transition: transform .2s, border .2s;
}
.radio:not(:checked):not(:focus)+label:hover::before,
.checkbox:not(:checked):not(:focus)+label:hover::before {
border-color: lightness(var(--color-contrast-low), 0.7);
}
.radio+label::before {
border-radius: 50%;
}
.checkbox+label::before {
border-radius: var(--checkbox-radius);
}
.radio:checked+label::before,
.checkbox:checked+label::before {
background-color: var(--color-primary);
box-shadow: none;
border-color: var(--color-primary);
transition: transform .2s;
}
.radio:active+label::before,
.checkbox:active+label::before {
transform: scale(0.8);
transition: transform .2s;
}
.radio:checked:active+label::before,
.checkbox:checked:active+label::before {
transform: none;
transition: none;
}
.radio:checked+label::before {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cg class='nc-icon-wrapper' fill='%23ffffff'%3E%3Ccircle cx='8' cy='8' r='8' fill='%23ffffff'%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
background-size: var(--radio-marker-size);
}
.checkbox:checked+label::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpolyline points='1 6.5 4 9.5 11 2.5' fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'/%3E%3C/svg%3E");
background-size: var(--checkbox-marker-size);
}
.radio:checked:active+label::before,
.checkbox:checked:active+label::before,
.radio:focus+label::before,
.checkbox:focus+label::before {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px alpha(var(--color-primary), 0.2);
}
.radio--bg+label,
.checkbox--bg+label {
padding: var(--space-xxxxs) var(--space-xxxs);
border-radius: var(--radius-md);
transition: background .2s;
}
.radio--bg+label:hover,
.checkbox--bg+label:hover {
background-color: var(--color-contrast-lower);
}
.radio--bg:active+label,
.checkbox--bg:active+label,
.radio--bg:focus+label,
.checkbox--bg:focus+label {
background-color: alpha(var(--color-primary), 0.1);
}
fieldset {
margin: 14px 0;
}
.f-row, .f-col {
display: flex;
}
.f-row {
flex-direction: row;
justify-content: space-between;
}
.f-col {
flex-direction: column;
}
[tooltip]::before {
content: '?';
font-weight: bold;
}
[tooltip] {
position: relative;
}
[tooltip]:hover:after {
content: attr(tooltip);
position: absolute;
transform: translate(0%, -100%);
top: -10px;
font-size: 16px;
white-space: nowrap;
min-width: 120px;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
height: 30px;
border-radius: 3px;
background-color: #000;
color: #fff;
text-align: center;
text-decoration: none;
font-weight: lighter;
z-index: 999;
}
body {
color: #fff;
background: var(--dark-color);
}
a,
ul li a {
color: #ecb307;
}
header {
background: var(--light-color);
}
header img.wp-image-69454 {
display: none;
}
section > h3 {
background: var(--light-color);
}
footer,
section > .blockbody,
.comment,
.setupblock {
background: var(--light-color);
}
.thumb img,
header,
footer,
section > h3,
section > .blockbody,
.comment,
.setupblock {
border: 1px solid var(--border-color);
}
#Favorited_Byleft .blockbody {
display: none;
}
.shm-image-list {
display: flex;
flex-wrap: wrap;
justify-content: left;
align-items: baseline;
}
/* -------------------------------------------------- */
/* Video */
/* #6fe73c, #ecb307, #d5c623 */
a[data-mime^="video/"] > img {
background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(152,7,236,1) 100%);
}
/* Video with Sound */
/* #e73cd9, #ec0707, #ec0776 */
a[data-mime^="video/"][data-tags*="sound"] > img {
background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(236,7,118,1) 100%);
}
/* GIF */
/* #3ce7e4, #233bd5, #2cd523 */
a[data-mime="image/gif"] > img {
background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(60,231,228,1) 100%);
}
a.thumb > span {
padding: 3px;
color: white;
margin: 5px;
font-family: 'Poppins';
font-weight: 400;
letter-spacing: 0.5px;
border-radius: 5px;
}
span.video {
background: linear-gradient(-45deg, #fa6c9f 0%, #404cff 80%, #cc40ff 100%);
}
span.sound {
background: linear-gradient(-45deg, #52A0FD 0%, #00e2fa 80%, #00e2fa 100%);
}
span.gif {
background: linear-gradient(-45deg, #52A0FD 0%, #00e2fa 80%, #00e2fa 100%);
}
span.time {
background: linear-gradient(-45deg, #FD5252 0%, #fa0075 80%, #fa0000 100%);
}
div#fluid_video_wrapper_video-id {
min-width: 100%;
max-width: 100%;
min-height: 80vh;
max-height: 80vh !important;
aspect-ratio: 16/9;
}
video {
max-height: 80vh !important;
}
.thumb img {
background: var(--dark-color);
filter: brightness(.9) contrast(1.1);
border-radius: 5px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
width: 188px;
height: 188px;
object-fit: contain;
}
.shm-main-image {
width: 100%;
max-width: 85vw;
}
</style>
<fieldset id="sort">
<legend class="form-legend">Sort by</legend>
<ul class="f-row">
<li>
<input class="radio" type="radio" name="sort" value="most-recent" id="most_recent" ${!userSort || userSort === 'most-recent' ? 'checked' : ''}>
<label for="most_recent">Most Recent</label>
</li>
<li>
<input class="radio" type="radio" name="sort" value="top-voted" id="top_voted" ${userSort === 'top-voted' ? 'checked' : ''}>
<label for="top_voted">Top Voted</label>
</li>
</ul>
</fieldset>
<fieldset id="rating">
<legend class="form-legend" tooltip="'Rating Explicit' refers to bestiality AND 3D cartoon loli/shota. Login required">
Rating
</legend>
<ul class="f-col">
<li>
<input class="radio" type="radio" name="rating" id="exp-default" value="exp-default" ${!userRating || userRating === 'exp-default' ? 'checked' : ''}>
<label for="exp-default">Default</label>
</li>
<li>
<input class="radio" type="radio" name="rating" id="exp-hide" value="exp-hide" ${userRating === 'exp-hide' ? 'checked' : ''}>
<label for="exp-hide">Hide Explicit</label>
</li>
<li>
<input class="radio" type="radio" name="rating" id="exp-only" value="exp-only" ${userRating === 'exp-only' ? 'checked' : ''}>
<label for="exp-only">Only Explicit</label>
</li>
</ul>
</fieldset>
<fieldset id="preferences">
<legend class="form-legend">Preferences</legend>
<ul class="f-col">
<li>
<input class="checkbox" type="checkbox" name="hide-furry" id="hide-furry" ${!userPrefs || userPrefs.hideFurry ? 'checked' : ''}>
<label for="hide-furry">Hide Furry</label>
</li>
</ul>
</fieldset>`;
form.insertAdjacentHTML("beforeend", customHtml);
const tagsInput = form.querySelector('input[name="search"]');
const furryList = ['-Alien', '-Cat(s)', '-Dog(s)', '-Fish', '-Fox', '-Horse(s)', '-Insect(s)', '-Jaguar', '-Leopard', '-My_Little_Pony_Friendship_Is_Magic', '-Pony', '-Sonic_(Series)', '-Werewolf', '-Wolf_(Wolves)'];
const tagAnchors = document.querySelectorAll('table:not(#header) a[href^="/post/list/"]');
tagAnchors.forEach((anchor) => {
anchor.addEventListener('click', (event) => {
event.preventDefault();
let splited = event.target.href.split('/');
const sort = form.sort.value;
if (sort) {
if (sort === 'most-recent') {
splited[5] += '+order=id_desc';
} else if (sort === 'top-voted') {
splited[5] += '+order=score_desc';
}
}
const rating = form.rating.value;
if (rating) {
if (rating === 'exp-hide') {
splited[5] += '+-rating:e';
} else if (rating === 'exp-only') {
splited[5] += '+rating:e';
}
}
const hideFurry = form['hide-furry'].checked;
if (hideFurry) {
for (let item of furryList) {
splited[5] += `+${item}`;
}
}
const finalURL = splited.join('/');
window.location = finalURL;
});
});
form.onsubmit = () => {
const sort = form.sort.value;
if (sort) {
tagsInput.value = tagsInput.value.replaceAll(/(\-)?(order=id_desc|order=score_desc)/gi, '');
if (sort === 'most-recent') {
tagsInput.value += '+order=id_desc';
} else if (sort === 'top-voted') {
tagsInput.value += '+order=score_desc';
}
}
const rating = form.rating.value;
if (rating) {
tagsInput.value = tagsInput.value.replaceAll(/(\-)?rating:e/gi, '');
if (rating === 'exp-hide') {
tagsInput.value += '+-rating:e';
} else if (rating === 'exp-only') {
tagsInput.value += '+rating:e';
}
}
const hideFurry = form['hide-furry'].checked;
if (hideFurry) {
for (let item of furryList) {
if (!tagsInput.value.includes(item)) {
tagsInput.value += `+${item}`;
}
}
} else {
for (let item of furryList) {
if (tagsInput.value.includes(item)) {
tagsInput.value = tagsInput.value.replace(item, '');
}
}
}
}
form.sort[0].onchange = (event) => {
localStorage.setItem('userSort', event.target.value);
};
form.rating[0].onchange = (event) => {
localStorage.setItem('userRating', event.target.value);
};
form['hide-furry'].onchange = (event) => {
localStorage.setItem('userPrefs', JSON.stringify({ hideFurry: event.target.checked }));
};
// ==================================================
// Better mark for animated posts
const imgs = document.querySelectorAll('.shm-image-list .thumb img')
const imgTimeRegex = /(?<=,\s)((\d{1,}.*s)(?=\s\/\/))/gi;
imgs.forEach((img) => {
if (img.title.includes('webm') || img.title.includes('mp4')) {
img.parentElement.style.setProperty('--video', "'VIDEO'");
let vspan = document.createElement('span');
vspan.className = 'video';
vspan.innerText = 'VIDEO';
img.parentElement.appendChild(vspan);
}
if (img.title.includes('Sound')) {
img.parentElement.style.setProperty('--sound', "'SOUND'");
let vspan = document.createElement('span');
vspan.className = 'sound';
vspan.innerText = 'SOUND';
img.parentElement.appendChild(vspan);
}
if (img.title.includes('gif')) {
img.parentElement.style.setProperty('--gif', "'GIF'");
let vspan = document.createElement('span');
vspan.className = 'gif';
vspan.innerText = 'GIF';
img.parentElement.appendChild(vspan);
}
if (img.title.match(imgTimeRegex)) {
img.parentElement.style.setProperty('--time', "'" + img.title.match(imgTimeRegex)[0] + "'")
let vspan = document.createElement('span');
vspan.className = 'time';
vspan.innerText = img.title.match(imgTimeRegex)[0];
img.parentElement.appendChild(vspan);
}
});
// ==================================================
})();