LegalPorno Enhancer

Adds extra functionality to LegalPorno and Pornbox. Easily filter out unwanted categories. Also adds tags on top of every scene.

Version au 02/05/2020. Voir la dernière version.

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