LegalPorno Enhancer

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

Version vom 29.01.2020. Aktuellste Version

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