Sleazy Fork is available in English.

Analvids/LegalPorno Enhancer

Adds extra functionality to Analvids (Formerly known as LegalPorno) and Pornbox. Easily filter out unwanted categories. Also adds tags on top of every scene.

Versión del día 18/07/2021. Echa un vistazo a la versión más reciente.

  1. // ==UserScript==
  2. // @name Analvids/LegalPorno Enhancer
  3. // @namespace https://www.analvids.com/forum/viewtopic.php?f=96&t=24238
  4. // @description Adds extra functionality to Analvids (Formerly known as LegalPorno) and Pornbox. Easily filter out unwanted categories. Also adds tags on top of every scene.
  5. // @match http*://*.analvids.com/*
  6. // @match http*://analvids.com/*
  7. // @exclude http*://*analvids.com/forum/*
  8. // @exclude http*://account.analvids.com/*
  9. // @match https://*pornbox.com/*
  10. // @version 1.2.12
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_addStyle
  14. // @run-at document-start
  15. // ==/UserScript==
  16.  
  17. const is_lp = !!window.location.href.match(/analvids/);
  18.  
  19. let glo_filters = {
  20. studios: {
  21. 'Gonzo.com': true,
  22. 'Giorgio Grandi': true,
  23. 'Interracial Vision': true,
  24. 'American Anal': true,
  25. "Giorgio's Lab": true,
  26. Focus: true,
  27. Assablanca: true,
  28. 'Mr Anal': true,
  29. 'Porn World': true,
  30. 'Girls Gone Wild': true,
  31. 'Kinky Sex': true,
  32. Private: true,
  33. 'Mixed studios': true,
  34. 'Outside the Studio': true,
  35. },
  36. categories: {
  37. Piss: true,
  38. 'Piss Drink': true,
  39. Fisting: true,
  40. Prolapse: true,
  41. Shemale: true,
  42. },
  43. };
  44.  
  45. const studio_colors = {
  46. 'Gonzo.com': { bg: '#cd0000', text: '#fff' },
  47. 'Giorgio Grandi': { bg: '#693180', text: '#fff' },
  48. 'Interracial Vision': { bg: '#242424', text: '#e6e6e6' },
  49. 'American Anal': { bg: '#b45219', text: '#fffa78' },
  50. "Giorgio's Lab": { bg: '#206d04', text: '#fff' },
  51. 'Kinky Sex': { bg: '#000', text: '#fff' },
  52. 'NRX-Studio': {bg: '#e8c459', text: "#000000ab"},
  53. 'N&F studio': {bg: "#1091c4", text: "#fff"},
  54. 'Yummy Estudio': {bg: "#009B3A", text: "#FEDF00"}
  55. };
  56.  
  57. // Uncomment the following line to reset global filters
  58. // GM_setValue('user_filters', JSON.stringify(glo_filters));
  59.  
  60. validateUserFilters = (filters) => {
  61. if (!filters.studios || !filters.categories) return false;
  62. if (
  63. Object.keys(filters.studios).length !==
  64. Object.keys(glo_filters.studios).length
  65. )
  66. return false;
  67. if (
  68. Object.keys(filters.categories).length !==
  69. Object.keys(glo_filters.categories).length
  70. )
  71. return false;
  72. if (
  73. Object.keys(filters.categories)
  74. .sort()
  75. .some(
  76. (val, idx) => val !== Object.keys(glo_filters.categories).sort()[idx]
  77. )
  78. )
  79. return false;
  80. if (
  81. Object.keys(filters.studios)
  82. .sort()
  83. .some((val, idx) => val !== Object.keys(glo_filters.studios).sort()[idx])
  84. )
  85. return false;
  86. return true;
  87. };
  88.  
  89. try {
  90. const user_config = GM_getValue('user_filters');
  91. if (!user_config)
  92. throw new Error('No user filters found. Using default ones.');
  93. user_filters = JSON.parse(user_config);
  94. if (!validateUserFilters(user_filters))
  95. throw new Error('Invalid user settings.');
  96. glo_filters = user_filters;
  97. } catch (e) {}
  98.  
  99. const studios = {
  100. Interracial: 'Interracial Vision',
  101. 'Hard Porn World': 'Porn World',
  102. };
  103.  
  104. const icon_categories = {
  105. interracial: 'IR',
  106. 'double anal (DAP)': 'DAP',
  107. fisting: 'Fisting',
  108. prolapse: 'Prolapse',
  109. transsexual: 'Shemale',
  110. trans: 'Shemale',
  111. 'triple anal (TAP)': 'TAP',
  112. piss: 'Piss',
  113. squirting: 'Squirt',
  114. '3+ on 1': '3+on1',
  115. 'double vaginal (DPP)': 'DPP',
  116. 'first time': '1st',
  117. 'triple penetration': 'TP',
  118. '0% pussy': '0% Pussy',
  119. 'cum swallowing': 'Cum Swallow',
  120. 'piss drinking': 'Piss Drink',
  121. 'anal creampies': 'Anal Creampie',
  122. '1 on 1': '1on1',
  123. milf: 'Milf',
  124. 'facial cumshot': 'Facial',
  125. };
  126.  
  127. GM_addStyle(`
  128. .hiddenScene {
  129. display: none !important;
  130. }
  131.  
  132. .hide_element {
  133. display: none !important;
  134. }
  135.  
  136. .thumbnail-duration {
  137. top: 0 !important;
  138. right: 0 !important;
  139. left: unset !important;
  140. bottom: unset !important;
  141. margin: 5px !important;
  142. }
  143.  
  144. .enhanced_categories_container {
  145. width: 100%;
  146. display: flex;
  147. flex-wrap: wrap-reverse;
  148. position: absolute;
  149. bottom:0; left: 0;
  150. z-index: 3;
  151. padding: 3px 5px;
  152. }
  153.  
  154. .enchanced_icon {
  155. border-radius: 3px;
  156. background-color: #616161;
  157. color: #eee;
  158. opacity: 0.9;
  159. padding: 2px 7px;
  160. margin-right: 2px;
  161. margin-top: 2px;
  162. font-size: 13px;
  163. }
  164.  
  165. .enhanced--studio {
  166. position: absolute;
  167. top: 0;
  168. left: 0;
  169. z-index: 3;
  170. display: flex;
  171. margin: 4px 6px;
  172. }
  173.  
  174. .enhanced--studio {
  175. background-color: #a76523c9;
  176. border-radius: 3px;
  177. padding: 2px 7px;
  178. color: white;
  179. text-shadow: 1px 1px #00000080;
  180. font-size: 12px;
  181. margin-right: 4px;
  182. }
  183.  
  184. .enhanced_views_label {
  185. position: absolute;
  186. top: 28px;
  187. right: 5px;
  188. font-size: 11px;
  189. display: flex;
  190. }
  191.  
  192. .enhanced--views {
  193. background-color: rgba(102,102,102,0.6);
  194. padding: 2px 6px;
  195. border-radius: 3px;
  196. color: #fff;
  197. display: flex;
  198. height: 100%;
  199. align-items: center;
  200. justify-content: center;
  201. }
  202.  
  203. .enhanced--views > i {
  204. margin-right: 5px;
  205. }
  206.  
  207. .item__img:hover > div {
  208. display: none;
  209. }
  210.  
  211. .item__img-labels-bottom {
  212. top: 3px;
  213. right: 3px;
  214. left: unset;
  215. bottom: unset;
  216. display: flex
  217. }
  218.  
  219. .img-label {
  220. margin-top: unset;
  221. margin-left: 2px;
  222. }
  223.  
  224. .item__img-labels {
  225. display: flex;
  226. flex-direction: row-reverse;
  227. left: unset;
  228. right: 3px;
  229. top: 23px;
  230. }
  231.  
  232. /* Filter Css Begin */
  233. .filters_container_lp {
  234. margin-left: -15px;
  235. margin-right: -15px;
  236. }
  237.  
  238. .filters_container--main {
  239. width: 100%;
  240. display: flex;
  241. justify-content: center;
  242. background: linear-gradient(
  243. 0deg,
  244. #babcbc 0%,
  245. #dfe1e1 2%,
  246. #dfe1e1 98%,
  247. #babcbc 100%
  248. );
  249. }
  250.  
  251. .filters_card {
  252. margin: 0 25px;
  253. padding-bottom: 15px;
  254. display: flex;
  255. flex-direction: column;
  256. align-items: center;
  257. position: relative;
  258. }
  259.  
  260. .filters_selections {
  261. display: grid;
  262. grid-template-columns: repeat(2, 1fr);
  263. }
  264.  
  265. .filters--divider {
  266. width: 1px;
  267. background-color: rgb(0, 0, 0, 0.25);
  268. height: 50px;
  269. margin-top: 15px;
  270. align-self: center;
  271. }
  272.  
  273. .filters--selection {
  274. padding: 1px 10px 0 0;
  275. min-width: 140px;
  276. display: flex;
  277. align-items: center;
  278. }
  279.  
  280. .filters--selection > label,
  281. .filters--selection > input[type="checkbox"] {
  282. margin: 0 2px 0 0;
  283. font-weight: bold;
  284. }
  285.  
  286. .fitlers--header {
  287. padding: 5px 0 5px 0;
  288. align-self: center;
  289. font-weight: bold;
  290. }
  291.  
  292. .enhanced_toggle {
  293. background: -webkit-linear-gradient(
  294. top,
  295. #a9a9a9 0%,
  296. #d97575 5%,
  297. #563434 100%
  298. ) !important;
  299. // height: 100%;
  300. border: 1px solid #722424 !important;
  301. color: white;
  302. padding: 7px 11px;
  303. font-weight: 700;
  304. border: none !important;
  305. }
  306.  
  307. .enhanced_toggle_pb {
  308. height: 100%;
  309. padding: 7px 11px;
  310. border-radius: 300px;
  311. position: absolute;
  312. margin-left: 4px;
  313. }
  314.  
  315. .enhanced_toggle_lp {
  316. border-radius: 200px;
  317. margin-left: 4px;
  318. }
  319.  
  320. .enhanced_toggle:hover {
  321. cursor: pointer;
  322. }
  323.  
  324. .enhanced_toggle > i {
  325. font-size: 12px;
  326. }
  327. /* Filter Css End */
  328.  
  329. .enhanced--sort {
  330. display: grid;
  331. grid-template-columns: 1fr 1fr;
  332. justify-items: center;
  333. grid-gap: 2px;
  334. position: absolute;
  335.  
  336. top: -4px;
  337. right: 210px;
  338. }
  339.  
  340. .enhanced--sort--navbar {
  341. right: 25px;
  342. top: 6px;
  343. }
  344.  
  345. .enhanced--sort > span {
  346. grid-column-end: span 2;
  347. color: #fff;
  348. }
  349.  
  350. .enhanced--sort > button {
  351. width: 70px;
  352. height: 23px;
  353. justify-content: center;
  354. align-items: center;
  355. text-align: center;
  356. padding: 0;
  357. margin: 0;
  358. }
  359.  
  360. .page-section {
  361. position: relative !important;
  362. }
  363. `);
  364.  
  365. const getSceneInfo = (scene) => {
  366. const info = {
  367. studio: null,
  368. categories: null,
  369. date: null,
  370. views: null,
  371. };
  372.  
  373. info.studio = scene.querySelector('.rating-studio-name, a[href^="#studio/"]');
  374. info.studio = info.studio ? info.studio.innerText : null;
  375. if (studios[info.studio]) {
  376. info.studio = studios[info.studio];
  377. }
  378. info.categories = [...scene.querySelectorAll('a[href*="niche/"]')]
  379. .filter((cat) => cat.innerText && icon_categories[cat.innerText])
  380. .map((cat) => icon_categories[cat.innerText])
  381. .sort();
  382. // info.categories = info.categories.filter(
  383. // cat => !(cat === 'Piss' && info.categories.includes('Piss Drink'))
  384. // );
  385. info.categories = Array.from(new Set(info.categories));
  386.  
  387. const views = scene.querySelector('.rating-views');
  388. const date = scene.querySelector('.glyphicon-calendar');
  389.  
  390. if (views) {
  391. info.views = parseInt(views.innerText.replace('VIEWS:', '').trim(), 10);
  392. }
  393. if (date) {
  394. try {
  395. info.date = new Date(
  396. date.parentElement.innerText.replace('RELEASE:', '').trim()
  397. );
  398. } catch (e) {}
  399. }
  400.  
  401. return info;
  402. };
  403.  
  404. const formatViews = (n) => {
  405. if (n >= 1e3 && n < 1e6) return +(n / 1e3).toFixed(0) + 'k';
  406. if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(0) + 'm';
  407. if (n >= 1e9 && n < 1e12) return +(n / 1e9).toFixed(0) + 'b';
  408. // if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
  409. return n;
  410. };
  411.  
  412. const enhanceScene = (scene) => {
  413. // Remove icons from thumnail. ("4k" and "new")
  414. const icons = scene.querySelectorAll('.icon, .thumbnail-price');
  415. for (const icon of icons) {
  416. icon.classList.add('hide_element');
  417. }
  418.  
  419. const { studio, categories, views, date } = getSceneInfo(scene);
  420.  
  421. if (studio) {
  422. const studioLabel = document.createElement('div');
  423. studioLabel.classList.add('enhanced--studio');
  424. studioLabel.innerHTML = studio;
  425.  
  426. if (studio_colors[studio]) {
  427. studioLabel.style = `
  428. background-color: ${studio_colors[studio]['bg']}; color: ${studio_colors[studio]['text']};
  429. `;
  430. }
  431.  
  432. if (views) {
  433. const viewsLabel = document.createElement('div');
  434. viewsLabel.innerHTML = `
  435. <div class='enhanced--views'><i class='fa fa-eye'></i>${formatViews(
  436. views
  437. )}</div>`;
  438. viewsLabel.classList.add('enhanced_views_label');
  439. if (scene.querySelector('.thumbnail-image')) {
  440. scene.querySelector('.thumbnail-image').appendChild(viewsLabel);
  441. }
  442. }
  443.  
  444. // studioLabel.classList.add('enhanced_title_tags');
  445. scene
  446. .querySelector('.thumbnail-image, .item__img')
  447. .appendChild(studioLabel);
  448. }
  449. if (categories.length) {
  450. const cat_div = document.createElement('div');
  451. cat_div.classList.add('enhanced_categories_container');
  452. for (const cat of categories) {
  453. const icon = document.createElement('div');
  454. icon.classList.add('enchanced_icon');
  455. icon.innerHTML = cat;
  456. cat_div.appendChild(icon);
  457. }
  458.  
  459. if (scene.querySelector('.thumbnail-image, .item__img')) {
  460. scene.querySelector('.thumbnail-image, .item__img').appendChild(cat_div);
  461. }
  462. }
  463. };
  464.  
  465. const isSceneFiltered = (scene) => {
  466. const { studio, categories, views, date } = getSceneInfo(scene);
  467.  
  468. for (const key of Object.keys(glo_filters.studios)) {
  469. if (studio && studio.includes(key) && !glo_filters.studios[key])
  470. return true;
  471. }
  472.  
  473. for (const key of Object.keys(glo_filters.categories)) {
  474. if (categories && categories.includes(key) && !glo_filters.categories[key])
  475. return true;
  476. }
  477.  
  478. return false;
  479. };
  480.  
  481. const filterScene = (scene) => {
  482. if (isSceneFiltered(scene)) {
  483. scene.classList.add('hiddenScene');
  484. }
  485. };
  486.  
  487. const updateFilters = () => {
  488. GM_setValue('user_filters', JSON.stringify(glo_filters));
  489. const scenes = document.querySelectorAll('.thumbnail, .block-item');
  490. for (const scene of scenes) {
  491. if (scene.classList.contains('hiddenScene'))
  492. scene.classList.remove('hiddenScene');
  493. filterScene(scene);
  494. }
  495. };
  496.  
  497. const createFilterElement = () => {
  498. const { studios, categories } = glo_filters;
  499.  
  500. const studioHtml = Object.keys(studios).reduce(
  501. (acc, studio) =>
  502. (acc += `<div class="filters--selection">
  503. <input type="checkbox" name="studios" id="${studio}"
  504. ${studios[studio] ? 'checked="checked"' : ''}
  505. />
  506. <label for="${studio}">${studio}</label>
  507. </div>`),
  508. ''
  509. );
  510.  
  511. const categoriesHtml = Object.keys(categories).reduce(
  512. (acc, cat) =>
  513. (acc += `<div class="filters--selection">
  514. <input type="checkbox" name="categories" id="${cat}"
  515. ${categories[cat] ? 'checked="checked"' : ''}
  516. />
  517. <label for="${cat}">${cat}</label>
  518. </div>`),
  519. ''
  520. );
  521.  
  522. const form = document.createElement('form');
  523. form.classList.add('filters_container', 'hide_element');
  524. if (is_lp) form.classList.add('filters_container_lp');
  525. form.innerHTML = `<div class="filters_container--main">
  526. <div class="filters_card">
  527. <div class="fitlers--header">Studios:</div>
  528. <div class="filters_selections">
  529. ${studioHtml}
  530. </div>
  531. </div>
  532. <!-- -->
  533. <div class="filters--divider"></div>
  534. <!-- -->
  535. <div class="filters_card">
  536. <div class="fitlers--header">Categories:</div>
  537. <div class="filters_selections">
  538. ${categoriesHtml}
  539. </div>
  540. </div>
  541. </div>`;
  542.  
  543. form.addEventListener('change', (e) => {
  544. glo_filters[e.target.name][e.target.id] = e.target.checked;
  545. updateFilters();
  546. });
  547.  
  548. const lp_header = document.querySelector('.header');
  549. const pb_header = document.querySelector('#wrap-container');
  550.  
  551. if (lp_header) lp_header.insertBefore(form, lp_header.childNodes[2]);
  552. if (pb_header) pb_header.insertBefore(form, pb_header.childNodes[0]);
  553. createFilterToggle();
  554. };
  555.  
  556. const createFilterToggle = () => {
  557. const toggleFilterBtn = document.createElement('button');
  558. toggleFilterBtn.classList.add('enhanced_toggle');
  559. toggleFilterBtn.classList.add(`enhanced_toggle_${is_lp ? 'lp' : 'pb'}`);
  560.  
  561. toggleFilterBtn.innerHTML = `<i class="fa fa-filter"></i>`;
  562.  
  563. toggleFilterBtn.addEventListener('click', () => {
  564. const filters_container = document.querySelector('.filters_container');
  565. if (filters_container) {
  566. filters_container.classList.toggle('hide_element');
  567. }
  568. });
  569.  
  570. const search_container = document.querySelector(
  571. '.nav-search-container, .header-block:nth-of-type(3)'
  572. );
  573. is_lp ? (search_container.style = 'display: flex;') : null;
  574. search_container.appendChild(toggleFilterBtn);
  575. };
  576.  
  577. const callback = (mutationsList) => {
  578. for (let mutation of mutationsList) {
  579. for (const node of mutation.addedNodes) {
  580. if (node.nodeType === 1) {
  581. if (node.nodeName === 'DIV') {
  582. if (
  583. node.classList.contains('block-item') ||
  584. node.classList.contains('thumbnail')
  585. ) {
  586. enhanceScene(node);
  587. filterScene(node);
  588. }
  589. }
  590. }
  591. }
  592. }
  593. };
  594.  
  595. const config = { childList: true, subtree: true, attributes: true };
  596. const observer = new MutationObserver(callback);
  597. observer.observe(window.document, config);
  598.  
  599. const sortScenes = ({ date = false, views = false, asc = true } = {}) => {
  600. const node = document.querySelector('.thumbnails');
  601. [...node.children]
  602. .sort((a, b) => {
  603. const infoA = getSceneInfo(a);
  604. const infoB = getSceneInfo(b);
  605.  
  606. let order = -1;
  607.  
  608. if (date) {
  609. order = infoA.date > infoB.date ? -1 : 1;
  610. } else if (views) {
  611. order = infoA.views > infoB.views ? -1 : 1;
  612. } else {
  613. order = 0;
  614. }
  615.  
  616. if (a.classList.contains('button--load-more')) return 0;
  617. if (b.classList.contains('button--load-more')) return 0;
  618.  
  619. return !asc ? order : order * -1;
  620. })
  621. .map((scene) => {
  622. node.appendChild(scene);
  623. node.appendChild(document.createTextNode(' '));
  624. });
  625. };
  626.  
  627. const sortBtns = () => {
  628. const sortDiv = document.createElement('div');
  629. sortDiv.classList.add('enhanced--sort');
  630. sortDiv.innerHTML = `
  631. <span>Sort By:</span>
  632. <button asc="false">Date</button>
  633. <button asc="false">Views</button>
  634. `;
  635.  
  636. sortDiv.addEventListener('click', (e) => {
  637. if (e.target.tagName === 'BUTTON') {
  638. const asc = e.target.getAttribute('asc') === 'true';
  639. e.target.setAttribute('asc', !asc);
  640. const sortObj = { asc };
  641. sortObj[e.target.innerText.toLowerCase()] = true;
  642. sortScenes(sortObj);
  643. }
  644. });
  645.  
  646. const navbar = document.querySelectorAll('.navbar > .container-fluid');
  647. if (navbar.length) {
  648. navbar[navbar.length - 1].appendChild(sortDiv);
  649. sortDiv.classList.add('enhanced--sort--navbar');
  650. return;
  651. }
  652.  
  653. const cont = document.querySelector('.page-section');
  654. if (cont) {
  655. sortDiv.children[0].style = 'color: #000;';
  656. cont.appendChild(sortDiv);
  657. }
  658. };
  659.  
  660. document.addEventListener('DOMContentLoaded', () => {
  661. if (!is_lp && !document.querySelector('.nav-search-container')) return;
  662. createFilterElement();
  663. sortBtns();
  664.  
  665. const nodes = document.querySelectorAll('.block-item, .thumbnail');
  666. [...nodes].map((node) => {
  667. enhanceScene(node);
  668. filterScene(node);
  669. });
  670. });