LegalPorno Enhancer

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

Stan na 21-01-2020. Zobacz najnowsza wersja.

  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 every scene.
  5. // @match http*://*.legalporno.com/*
  6. // @match http*://legalporno.com/*
  7. // @exclude http*://*legalporno.com/forum/*
  8. // @match https://*pornbox.com/*
  9. // @homepage https://github.com/HwtOxc8K/lp-filter
  10. // @version 1.0.0
  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_studio_label {
  145. position: absolute;
  146. top: 0;
  147. left: 0;
  148. z-index: 3;
  149. background-color: #a76523c9;
  150. border-radius: 3px;
  151. padding: 2px 7px;
  152. margin: 4px 6px;
  153. color: white;
  154. text-shadow: 1px 1px #00000080;
  155. font-size: 12px;
  156. }
  157.  
  158. .item__img:hover > div {
  159. display: none;
  160. }
  161.  
  162. .item__img-labels-bottom {
  163. top: 3px;
  164. right: 3px;
  165. left: unset;
  166. bottom: unset;
  167. display: flex
  168. }
  169.  
  170. .img-label {
  171. margin-top: unset;
  172. margin-left: 2px;
  173. }
  174.  
  175. .item__img-labels {
  176. display: flex;
  177. flex-direction: row-reverse;
  178. left: unset;
  179. right: 3px;
  180. top: 23px;
  181. }
  182.  
  183. /* Filter Css Begin */
  184. .filters_container {
  185. margin-left: -15px;
  186. margin-right: -15px;
  187. }
  188.  
  189. .filters_container--main {
  190. width: 100%;
  191. display: flex;
  192. justify-content: center;
  193. background: linear-gradient(
  194. 0deg,
  195. #babcbc 0%,
  196. #dfe1e1 2%,
  197. #dfe1e1 98%,
  198. #babcbc 100%
  199. );
  200. }
  201.  
  202. .filters_card {
  203. margin: 0 25px;
  204. padding-bottom: 15px;
  205. display: flex;
  206. flex-direction: column;
  207. align-items: center;
  208. position: relative;
  209. }
  210.  
  211. .filters_selections {
  212. display: grid;
  213. grid-template-columns: repeat(2, 1fr);
  214. }
  215.  
  216. .filters--divider {
  217. width: 1px;
  218. background-color: rgb(0, 0, 0, 0.25);
  219. height: 50px;
  220. margin-top: 15px;
  221. align-self: center;
  222. }
  223.  
  224. .filters--selection {
  225. padding: 1px 10px 0 0;
  226. min-width: 140px;
  227. display: flex;
  228. align-items: center;
  229. }
  230.  
  231. .filters--selection > label,
  232. .filters--selection > input[type="checkbox"] {
  233. margin: 0 2px 0 0;
  234. font-weight: bold;
  235. }
  236.  
  237. .fitlers--header {
  238. padding: 5px 0 5px 0;
  239. align-self: center;
  240. font-weight: bold;
  241. }
  242.  
  243. .enhancedFilterBtn {
  244. background: -webkit-linear-gradient(top,#a9a9a9 0%,#d97575 5%,#563434 100%) !important;
  245. border: 1px solid #722424 !important;
  246. }
  247.  
  248. .enhanced_toggle {
  249. background: -webkit-linear-gradient(
  250. top,
  251. #a9a9a9 0%,
  252. #d97575 5%,
  253. #563434 100%
  254. ) !important;
  255. // height: 100%;
  256. border: 1px solid #722424 !important;
  257. color: white;
  258. padding: 7px 11px;
  259. font-weight: 700;
  260. border: none !important;
  261. }
  262.  
  263. .enhanced_toggle_pb {
  264. height: 100%;
  265. padding: 7px 11px;
  266. border-radius: 300px;
  267. position: absolute;
  268. margin-left: 4px;
  269. }
  270.  
  271. .enhanced_toggle_lp {
  272. border-radius: 200px;
  273. margin-left: 4px;
  274. }
  275.  
  276. .enhanced_toggle:hover {
  277. cursor: pointer;
  278. }
  279.  
  280. .enhanced_toggle > i {
  281. font-size: 12px;
  282. }
  283. /* Filter Css End */
  284. `);
  285.  
  286. const callback = mutationsList => {
  287. for (let mutation of mutationsList) {
  288. for (const node of mutation.addedNodes) {
  289. if (node.nodeType === 1) {
  290. if (node.nodeName === 'DIV') {
  291. if (
  292. node.classList.contains('block-item') ||
  293. node.classList.contains('thumbnail')
  294. ) {
  295. enhanceScene(node);
  296. filterScene(node);
  297. }
  298. }
  299. }
  300. }
  301. }
  302. };
  303.  
  304. const getSceneInfo = scene => {
  305. const info = {};
  306. info.studio = scene.querySelector('.rating-studio-name, a[href^="#studio/"]');
  307. info.studio = info.studio ? info.studio.innerText : null;
  308. if (studios[info.studio]) {
  309. info.studio = studios[info.studio];
  310. }
  311. info.categories = [...scene.querySelectorAll('a[href*="niche/"]')]
  312. .filter(cat => cat.innerText && icon_categories[cat.innerText])
  313. .map(cat => icon_categories[cat.innerText])
  314. .sort();
  315. // info.categories = info.categories.filter(
  316. // cat => !(cat === 'Piss' && info.categories.includes('Piss Drink'))
  317. // );
  318. info.categories = Array.from(new Set(info.categories));
  319. return info;
  320. };
  321.  
  322. const enhanceScene = scene => {
  323. // Remove icons from thumnail. ("4k" and "new")
  324. const icons = scene.querySelectorAll('.icon');
  325. for (const icon of icons) {
  326. icon.classList.add('hide_element');
  327. }
  328.  
  329. const { studio, categories } = getSceneInfo(scene);
  330. if (studio) {
  331. const studioLabel = document.createElement('div');
  332. studioLabel.innerHTML = studio;
  333. studioLabel.classList.add('enhanced_studio_label');
  334. scene
  335. .querySelector('.thumbnail-image, .item__img')
  336. .appendChild(studioLabel);
  337. }
  338. if (categories.length) {
  339. const cat_div = document.createElement('div');
  340. cat_div.classList.add('enhanced_categories_container');
  341. for (const cat of categories) {
  342. const icon = document.createElement('div');
  343. icon.classList.add('enchanced_icon');
  344. icon.innerHTML = cat;
  345. cat_div.appendChild(icon);
  346. }
  347.  
  348. if (scene.querySelector('.thumbnail-image, .item__img')) {
  349. scene.querySelector('.thumbnail-image, .item__img').appendChild(cat_div);
  350. }
  351. }
  352. };
  353.  
  354. const is_scene_filtered = scene => {
  355. const { studio, categories } = getSceneInfo(scene);
  356.  
  357. for (const key of Object.keys(glo_filters.studios)) {
  358. if (studio && studio.includes(key) && !glo_filters.studios[key])
  359. return true;
  360. }
  361.  
  362. for (const key of Object.keys(glo_filters.categories)) {
  363. if (categories && categories.includes(key) && !glo_filters.categories[key])
  364. return true;
  365. }
  366.  
  367. return false;
  368. };
  369.  
  370. const filterScene = scene => {
  371. if (is_scene_filtered(scene)) {
  372. scene.classList.add('hiddenScene');
  373. }
  374. };
  375.  
  376. const config = { childList: true, subtree: true };
  377. const observer = new MutationObserver(callback);
  378. observer.observe(window.document, config);
  379.  
  380. const updateFilters = () => {
  381. GM_setValue('user_filters', JSON.stringify(glo_filters));
  382. const scenes = document.querySelectorAll('.thumbnail, .block-item');
  383. for (const scene of scenes) {
  384. if (scene.classList.contains('hiddenScene'))
  385. scene.classList.remove('hiddenScene');
  386. filterScene(scene);
  387. }
  388. };
  389.  
  390. const createFilterElement = () => {
  391. const { studios, categories } = glo_filters;
  392.  
  393. const studioHtml = Object.keys(studios).reduce(
  394. (acc, studio) =>
  395. (acc += `<div class="filters--selection">
  396. <input type="checkbox" name="studios" id="${studio}"
  397. ${studios[studio] ? 'checked="checked"' : ''}
  398. />
  399. <label for="${studio}">${studio}</label>
  400. </div>`),
  401. ''
  402. );
  403.  
  404. const categoriesHtml = Object.keys(categories).reduce(
  405. (acc, cat) =>
  406. (acc += `<div class="filters--selection">
  407. <input type="checkbox" name="categories" id="${cat}"
  408. ${categories[cat] ? 'checked="checked"' : ''}
  409. />
  410. <label for="${cat}">${cat}</label>
  411. </div>`),
  412. ''
  413. );
  414.  
  415. const form = document.createElement('form');
  416. form.classList.add('filters_container', 'hide_element');
  417. form.innerHTML = `
  418. <div class="filters_container--main">
  419. <div class="filters_card">
  420. <div class="fitlers--header">Studios:</div>
  421. <div class="filters_selections">
  422. ${studioHtml}
  423. </div>
  424. </div>
  425. <!-- -->
  426. <div class="filters--divider"></div>
  427. <!-- -->
  428. <div class="filters_card">
  429. <div class="fitlers--header">Categories:</div>
  430. <div class="filters_selections">
  431. ${categoriesHtml}
  432. </div>
  433. </div>
  434. </div>
  435. `;
  436. const lp_header = document.querySelector('.header');
  437. const pb_header = document.querySelector('#wrap-container');
  438. form.addEventListener('change', e => {
  439. glo_filters[e.target.name][e.target.id] = e.target.checked;
  440. updateFilters();
  441. });
  442.  
  443. if (lp_header) lp_header.insertBefore(form, lp_header.childNodes[2]);
  444. if (pb_header) pb_header.insertBefore(form, pb_header.childNodes[0]);
  445. createFilterToggle();
  446. };
  447.  
  448. const createFilterToggle = () => {
  449. const toggleFilterBtn = document.createElement('button');
  450. toggleFilterBtn.classList.add('enhanced_toggle');
  451. if (is_lp) {
  452. toggleFilterBtn.classList.add('enhanced_toggle_lp');
  453. } else {
  454. toggleFilterBtn.classList.add('enhanced_toggle_pb');
  455. }
  456. toggleFilterBtn.innerHTML = `<i class="fa fa-filter"></i>`;
  457.  
  458. toggleFilterBtn.addEventListener('click', () => {
  459. const filters_container = document.querySelector('.filters_container');
  460. if (filters_container) {
  461. filters_container.classList.toggle('hide_element');
  462. }
  463. });
  464.  
  465. const search_container = document.querySelector(
  466. '.nav-search-container, .header-block:nth-of-type(3)'
  467. );
  468. search_container.appendChild(toggleFilterBtn);
  469. };
  470.  
  471. document.addEventListener('DOMContentLoaded', () => {
  472. if (!is_lp && !document.querySelector('.nav-search-container')) return;
  473. createFilterElement();
  474. });