Sleazy Fork is available in English.

Better R34Hentai

Add custom filters, tag markers to animated posts, and fix spaces in URLs

  1. // ==UserScript==
  2. // @name Better R34Hentai
  3. // @description Add custom filters, tag markers to animated posts, and fix spaces in URLs
  4. // @version 1.0.1
  5. // @match *://rule34hentai.net/*
  6. // @match *://www.rule34hentai.net/*
  7. // @author Alighieri
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx
  9. // @grant none
  10. // @run-at document-end
  11. // @namespace https://greasyfork.org/users/1399147
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Fix bad URL
  18. var currentURL = window.location.href;
  19. var fixedURL = currentURL.replace(/%20|\s/g, '+');
  20. if (currentURL !== fixedURL) {
  21. window.location.href = fixedURL;
  22. }
  23.  
  24. const allAnchors = document.querySelectorAll('a')
  25. allAnchors.forEach((anchor) => {
  26. anchor.href = anchor.href.replace(/%20|\s/g, '+');
  27. })
  28. // ==================================================
  29.  
  30. // Custom filters
  31. let userSort = localStorage.getItem('userSort');
  32.  
  33. let userRating = localStorage.getItem('userRating');
  34.  
  35. let userPrefs = localStorage.getItem('userPrefs');
  36. userPrefs = JSON.parse(userPrefs);
  37.  
  38. const form = document.querySelector('#Navigationleft > div > form');
  39.  
  40. const customHtml = `<style>
  41. @import url('https://fonts.googleapis.com/css?family=Poppins:300,400,500,700');
  42.  
  43. :root {
  44. --light-color: #232B32;
  45. --dark-color: #152028;
  46. --border-color: #444;
  47.  
  48. --space-xxxs: calc(0.375 * 1rem);
  49. --body-line-height: 1.4;
  50.  
  51. --checkbox-radio-size: 18px;
  52. --checkbox-radio-gap: calc(0.375 * 1rem);
  53. --checkbox-radio-border-width: 2px;
  54. --checkbox-radio-line-height: 1.4;
  55.  
  56. --radio-marker-size: 8px;
  57.  
  58. --checkbox-marker-size: 12px;
  59. --checkbox-radius: 4px;
  60.  
  61. --color-bg: hsl(0, 0%, 100%);
  62. --color-contrast-low: hsl(240, 4%, 65%);
  63. --color-contrast-lower: hsl(240, 4%, 85%);
  64. --color-primary: #ecb307;
  65.  
  66. --radius: 0.375em;
  67. --radius-md: var(--radius, 0.375em);
  68. }
  69.  
  70. .radio,
  71. .checkbox {
  72. position: absolute;
  73. margin: 0 !important;
  74. padding: 0 !important;
  75. opacity: 0;
  76. height: 0;
  77. width: 0;
  78. pointer-events: none;
  79. }
  80.  
  81. .radio+label,
  82. .checkbox+label {
  83. display: inline-flex;
  84. align-items: flex-start;
  85. line-height: var(--checkbox-radio-line-height);
  86. user-select: none;
  87. cursor: pointer;
  88. }
  89.  
  90. .radio+label::before,
  91. .checkbox+label::before {
  92. content: '';
  93. display: inline-block;
  94. position: relative;
  95. top: calc((1em * var(--checkbox-radio-line-height) - var(--checkbox-radio-size)) / 2);
  96. flex-shrink: 0;
  97. width: var(--checkbox-radio-size);
  98. height: var(--checkbox-radio-size);
  99. background-color: var(--color-bg);
  100. border-width: var(--checkbox-radio-border-width);
  101. border-color: var(--color-contrast-low);
  102. border-style: solid;
  103. background-repeat: no-repeat;
  104. background-position: center;
  105. margin-right: var(--checkbox-radio-gap);
  106. transition: transform .2s, border .2s;
  107. }
  108.  
  109. .radio:not(:checked):not(:focus)+label:hover::before,
  110. .checkbox:not(:checked):not(:focus)+label:hover::before {
  111. border-color: lightness(var(--color-contrast-low), 0.7);
  112. }
  113.  
  114. .radio+label::before {
  115. border-radius: 50%;
  116. }
  117.  
  118. .checkbox+label::before {
  119. border-radius: var(--checkbox-radius);
  120. }
  121.  
  122. .radio:checked+label::before,
  123. .checkbox:checked+label::before {
  124. background-color: var(--color-primary);
  125. box-shadow: none;
  126. border-color: var(--color-primary);
  127. transition: transform .2s;
  128. }
  129.  
  130. .radio:active+label::before,
  131. .checkbox:active+label::before {
  132. transform: scale(0.8);
  133. transition: transform .2s;
  134. }
  135.  
  136. .radio:checked:active+label::before,
  137. .checkbox:checked:active+label::before {
  138. transform: none;
  139. transition: none;
  140. }
  141.  
  142. .radio:checked+label::before {
  143. 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");
  144. background-size: var(--radio-marker-size);
  145. }
  146.  
  147. .checkbox:checked+label::before {
  148. 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");
  149. background-size: var(--checkbox-marker-size);
  150. }
  151.  
  152. .radio:checked:active+label::before,
  153. .checkbox:checked:active+label::before,
  154. .radio:focus+label::before,
  155. .checkbox:focus+label::before {
  156. border-color: var(--color-primary);
  157. box-shadow: 0 0 0 3px alpha(var(--color-primary), 0.2);
  158. }
  159.  
  160. .radio--bg+label,
  161. .checkbox--bg+label {
  162. padding: var(--space-xxxxs) var(--space-xxxs);
  163. border-radius: var(--radius-md);
  164. transition: background .2s;
  165. }
  166.  
  167. .radio--bg+label:hover,
  168. .checkbox--bg+label:hover {
  169. background-color: var(--color-contrast-lower);
  170. }
  171.  
  172. .radio--bg:active+label,
  173. .checkbox--bg:active+label,
  174. .radio--bg:focus+label,
  175. .checkbox--bg:focus+label {
  176. background-color: alpha(var(--color-primary), 0.1);
  177. }
  178.  
  179. fieldset {
  180. margin: 14px 0;
  181. }
  182.  
  183. .f-row, .f-col {
  184. display: flex;
  185. }
  186.  
  187. .f-row {
  188. flex-direction: row;
  189. justify-content: space-between;
  190. }
  191.  
  192. .f-col {
  193. flex-direction: column;
  194. }
  195.  
  196. [tooltip]::before {
  197. content: '?';
  198. font-weight: bold;
  199. }
  200.  
  201. [tooltip] {
  202. position: relative;
  203. }
  204.  
  205. [tooltip]:hover:after {
  206. content: attr(tooltip);
  207. position: absolute;
  208. transform: translate(0%, -100%);
  209. top: -10px;
  210. font-size: 16px;
  211. white-space: nowrap;
  212. min-width: 120px;
  213. padding: 0 10px;
  214. display: flex;
  215. align-items: center;
  216. justify-content: center;
  217. height: 30px;
  218. border-radius: 3px;
  219. background-color: #000;
  220. color: #fff;
  221. text-align: center;
  222. text-decoration: none;
  223. font-weight: lighter;
  224. z-index: 999;
  225. }
  226.  
  227. body {
  228. color: #fff;
  229. background: var(--dark-color);
  230. }
  231.  
  232. a,
  233. ul li a {
  234. color: #ecb307;
  235. }
  236.  
  237. header {
  238. background: var(--light-color);
  239. }
  240.  
  241. header img.wp-image-69454 {
  242. display: none;
  243. }
  244.  
  245. section > h3 {
  246. background: var(--light-color);
  247. }
  248.  
  249. footer,
  250. section > .blockbody,
  251. .comment,
  252. .setupblock {
  253. background: var(--light-color);
  254. }
  255.  
  256. .thumb img,
  257. header,
  258. footer,
  259. section > h3,
  260. section > .blockbody,
  261. .comment,
  262. .setupblock {
  263. border: 1px solid var(--border-color);
  264. }
  265.  
  266. #Favorited_Byleft .blockbody {
  267. display: none;
  268. }
  269.  
  270. .shm-image-list {
  271. display: flex;
  272. flex-wrap: wrap;
  273. justify-content: left;
  274. align-items: baseline;
  275. }
  276.  
  277. /* -------------------------------------------------- */
  278.  
  279.  
  280. /* Video */
  281. /* #6fe73c, #ecb307, #d5c623 */
  282. a[data-mime^="video/"] > img {
  283. background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(152,7,236,1) 100%);
  284. }
  285.  
  286. /* Video with Sound */
  287. /* #e73cd9, #ec0707, #ec0776 */
  288. a[data-mime^="video/"][data-tags*="sound"] > img {
  289. background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(236,7,118,1) 100%);
  290. }
  291.  
  292. /* GIF */
  293. /* #3ce7e4, #233bd5, #2cd523 */
  294. a[data-mime="image/gif"] > img {
  295. background: linear-gradient(45deg, rgba(2,0,36,1) 0%, rgba(60,231,228,1) 100%);
  296. }
  297.  
  298. a.thumb > span {
  299. padding: 3px;
  300. color: white;
  301. margin: 5px;
  302. font-family: 'Poppins';
  303. font-weight: 400;
  304. letter-spacing: 0.5px;
  305. border-radius: 5px;
  306. }
  307.  
  308. span.video {
  309. background: linear-gradient(-45deg, #fa6c9f 0%, #404cff 80%, #cc40ff 100%);
  310. }
  311.  
  312. span.sound {
  313. background: linear-gradient(-45deg, #52A0FD 0%, #00e2fa 80%, #00e2fa 100%);
  314. }
  315.  
  316. span.gif {
  317. background: linear-gradient(-45deg, #52A0FD 0%, #00e2fa 80%, #00e2fa 100%);
  318. }
  319.  
  320. span.time {
  321. background: linear-gradient(-45deg, #FD5252 0%, #fa0075 80%, #fa0000 100%);
  322. }
  323.  
  324. div#fluid_video_wrapper_video-id {
  325. min-width: 100%;
  326. max-width: 100%;
  327. min-height: 80vh;
  328. max-height: 80vh !important;
  329. aspect-ratio: 16/9;
  330. }
  331.  
  332. video {
  333. max-height: 80vh !important;
  334. }
  335.  
  336. .thumb img {
  337. background: var(--dark-color);
  338. filter: brightness(.9) contrast(1.1);
  339. border-radius: 5px;
  340. box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
  341. width: 188px;
  342. height: 188px;
  343. object-fit: contain;
  344. }
  345.  
  346. .shm-main-image {
  347. width: 100%;
  348. max-width: 85vw;
  349. }
  350. </style>
  351.  
  352. <fieldset id="sort">
  353. <legend class="form-legend">Sort by</legend>
  354.  
  355. <ul class="f-row">
  356. <li>
  357. <input class="radio" type="radio" name="sort" value="most-recent" id="most_recent" ${!userSort || userSort === 'most-recent' ? 'checked' : ''}>
  358. <label for="most_recent">Most Recent</label>
  359. </li>
  360.  
  361. <li>
  362. <input class="radio" type="radio" name="sort" value="top-voted" id="top_voted" ${userSort === 'top-voted' ? 'checked' : ''}>
  363. <label for="top_voted">Top Voted</label>
  364. </li>
  365. </ul>
  366. </fieldset>
  367.  
  368. <fieldset id="rating">
  369. <legend class="form-legend" tooltip="'Rating Explicit' refers to bestiality AND 3D cartoon loli/shota. Login required">
  370. Rating
  371. </legend>
  372.  
  373. <ul class="f-col">
  374. <li>
  375. <input class="radio" type="radio" name="rating" id="exp-default" value="exp-default" ${!userRating || userRating === 'exp-default' ? 'checked' : ''}>
  376. <label for="exp-default">Default</label>
  377. </li>
  378.  
  379. <li>
  380. <input class="radio" type="radio" name="rating" id="exp-hide" value="exp-hide" ${userRating === 'exp-hide' ? 'checked' : ''}>
  381. <label for="exp-hide">Hide Explicit</label>
  382. </li>
  383.  
  384. <li>
  385. <input class="radio" type="radio" name="rating" id="exp-only" value="exp-only" ${userRating === 'exp-only' ? 'checked' : ''}>
  386. <label for="exp-only">Only Explicit</label>
  387. </li>
  388. </ul>
  389. </fieldset>
  390.  
  391. <fieldset id="preferences">
  392. <legend class="form-legend">Preferences</legend>
  393.  
  394. <ul class="f-col">
  395. <li>
  396. <input class="checkbox" type="checkbox" name="hide-furry" id="hide-furry" ${!userPrefs || userPrefs.hideFurry ? 'checked' : ''}>
  397. <label for="hide-furry">Hide Furry/Monsters</label>
  398. </li>
  399. <li>
  400. <input class="checkbox" type="checkbox" name="hide-ai" id="hide-ai" ${!userPrefs || userPrefs.hideAI ? 'checked' : ''}>
  401. <label for="hide-ai">Hide AI Generated</label>
  402. </li>
  403. </ul>
  404. </fieldset>`;
  405.  
  406. form.insertAdjacentHTML("beforeend", customHtml);
  407.  
  408. const tagsInput = form.querySelector('input[name="search"]');
  409. const furryList = ['-Alien', '-Cat(s)', '-Dog(s)', '-Fish', '-Fox', '-Horse(s)', '-Insect(s)', '-Jaguar', '-Leopard', '-Pony', '-Sonic_The_Hedgehog', '-Werewolf', '-Wolf_(Wolves)', '-Zootopia', '-Animal_Crossing', '-Cockroach(es)', '-Pig(s)', '-Grimm', '-regenerator', '-Nemesis', '-Zombie(s)'];
  410.  
  411. const tagAnchors = document.querySelectorAll('table:not(#header) a[href^="/post/list/"]');
  412. tagAnchors.forEach((anchor) => {
  413. anchor.addEventListener('click', (event) => {
  414. event.preventDefault();
  415. let splited = event.target.href.split('/');
  416.  
  417. const sort = form.sort.value;
  418. if (sort) {
  419. if (sort === 'most-recent') {
  420. splited[5] += '+order=id_desc';
  421. } else if (sort === 'top-voted') {
  422. splited[5] += '+order=score_desc';
  423. }
  424. }
  425.  
  426. const rating = form.rating.value;
  427. if (rating) {
  428. if (rating === 'exp-hide') {
  429. splited[5] += '+-rating:e';
  430. } else if (rating === 'exp-only') {
  431. splited[5] += '+rating:e';
  432. }
  433. }
  434.  
  435. const hideFurry = form['hide-furry'].checked;
  436. if (hideFurry) {
  437. for (let item of furryList) {
  438. splited[5] += `+${item}`;
  439. }
  440. }
  441.  
  442. const hideAI = form['hide-ai'].checked;
  443. if (hideAI) {
  444. splited[5] += '+-AI-generated';
  445. }
  446.  
  447. const finalURL = splited.join('/');
  448. window.location = finalURL;
  449. });
  450. });
  451.  
  452. form.onsubmit = () => {
  453. const sort = form.sort.value;
  454. if (sort) {
  455. tagsInput.value = tagsInput.value.replaceAll(/(\-)?(order=id_desc|order=score_desc)/gi, '');
  456. if (sort === 'most-recent') {
  457. tagsInput.value += '+order=id_desc';
  458. } else if (sort === 'top-voted') {
  459. tagsInput.value += '+order=score_desc';
  460. }
  461. }
  462.  
  463. const rating = form.rating.value;
  464. if (rating) {
  465. tagsInput.value = tagsInput.value.replaceAll(/(\-)?rating:e/gi, '');
  466. if (rating === 'exp-hide') {
  467. tagsInput.value += '+-rating:e';
  468. } else if (rating === 'exp-only') {
  469. tagsInput.value += '+rating:e';
  470. }
  471. }
  472.  
  473. const hideFurry = form['hide-furry'].checked;
  474. if (hideFurry) {
  475. for (let item of furryList) {
  476. if (!tagsInput.value.includes(item)) {
  477. tagsInput.value += `+${item}`;
  478. }
  479. }
  480. } else {
  481. for (let item of furryList) {
  482. if (tagsInput.value.includes(item)) {
  483. tagsInput.value = tagsInput.value.replace(item, '');
  484. }
  485. }
  486. }
  487.  
  488. const hideAI = form['hide-ai'].checked;
  489. if (hideAI) {
  490. tagsInput.value += '+-AI-generated';
  491. }
  492. }
  493.  
  494. form.sort[0].onchange = (event) => {
  495. localStorage.setItem('userSort', event.target.value);
  496. };
  497.  
  498. form.rating[0].onchange = (event) => {
  499. localStorage.setItem('userRating', event.target.value);
  500. };
  501.  
  502. form['hide-furry'].onchange = (event) => {
  503. localStorage.setItem('userPrefs', JSON.stringify({ hideFurry: event.target.checked }));
  504. };
  505.  
  506. form['hide-ai'].onchange = (event) => {
  507. localStorage.setItem('userPrefs', JSON.stringify({ hideAI: event.target.checked }));
  508. };
  509. // ==================================================
  510.  
  511. // Better mark for animated posts
  512. const imgs = document.querySelectorAll('.shm-image-list .thumb img')
  513. const imgTimeRegex = /(?<=,\s)((\d{1,}.*s)(?=\s\/\/))/gi;
  514.  
  515. imgs.forEach((img) => {
  516. if (img.title.includes('webm') || img.title.includes('mp4')) {
  517. img.parentElement.style.setProperty('--video', "'VIDEO'");
  518. let vspan = document.createElement('span');
  519. vspan.className = 'video';
  520. vspan.innerText = 'VIDEO';
  521. img.parentElement.appendChild(vspan);
  522. }
  523.  
  524. if (img.title.includes('Sound')) {
  525. img.parentElement.style.setProperty('--sound', "'SOUND'");
  526. let vspan = document.createElement('span');
  527. vspan.className = 'sound';
  528. vspan.innerText = 'SOUND';
  529. img.parentElement.appendChild(vspan);
  530. }
  531.  
  532. if (img.title.includes('gif')) {
  533. img.parentElement.style.setProperty('--gif', "'GIF'");
  534. let vspan = document.createElement('span');
  535. vspan.className = 'gif';
  536. vspan.innerText = 'GIF';
  537. img.parentElement.appendChild(vspan);
  538. }
  539.  
  540. if (img.title.match(imgTimeRegex)) {
  541. img.parentElement.style.setProperty('--time', "'" + img.title.match(imgTimeRegex)[0] + "'")
  542. let vspan = document.createElement('span');
  543. vspan.className = 'time';
  544. vspan.innerText = img.title.match(imgTimeRegex)[0];
  545. img.parentElement.appendChild(vspan);
  546. }
  547. });
  548. // ==================================================
  549. })();