Sleazy Fork is available in English.

SpankBang.com Improved

Infinite scroll (optional). Filter by duration, include/exclude phrases

Versão de: 19/08/2024. Veja: a última versão.

  1. // ==UserScript==
  2. // @name SpankBang.com Improved
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.85
  5. // @license MIT
  6. // @description Infinite scroll (optional). Filter by duration, include/exclude phrases
  7. // @author smartacephale
  8. // @supportURL https://github.com/smartacephale/sleazy-fork
  9. // @match https://*.spankbang.com/*
  10. // @grant GM_addStyle
  11. // @require https://unpkg.com/billy-herrington-utils@1.1.1/dist/billy-herrington-utils.umd.js
  12. // @require https://unpkg.com/jabroni-outfit@1.4.8/dist/jabroni-outfit.umd.js
  13. // @require https://update.greasyfork.org/scripts/494204/data-manager.user.js?version=1430668
  14. // @require https://update.greasyfork.org/scripts/494205/pagination-manager.user.js
  15. // @run-at document-idle
  16. // @icon https://www.google.com/s2/favicons?sz=64&domain=spankbang.com
  17. // ==/UserScript==
  18. /* globals $ DataManager PaginationManager */
  19.  
  20. const { Tick, findNextSibling, parseDom, fetchWith, fetchHtml, fetchText, SyncPull, wait, computeAsyncOneAtTime, timeToSeconds,
  21. parseIntegerOr, stringToWords, parseCSSUrl, circularShift, range, listenEvents, Observer, LazyImgLoader,
  22. watchElementChildrenCount, watchDomChangesWithThrottle, copyAttributes, replaceElementTag, isMob,
  23. objectToFormData, parseDataParams, sanitizeStr, chunks, getAllUniqueParents
  24. } = window.bhutils;
  25. const { JabroniOutfitStore, defaultStateWithDuration, JabroniOutfitUI, DefaultScheme } = window.jabronioutfit;
  26.  
  27. const LOGO = `
  28. ⡕⡧⡳⡽⣿⣇⠀⢀⠀⠄⠐⡐⠌⡂⡂⠠⠀⠠⠀⠠⠀⠠⡐⡆⡇⣇⢎⢆⠆⠌⢯⡷⡥⡂⡐⠨⣻⣳⢽⢝⣵⡫⣗⢯⣺⢵⢹⡪⡳⣝⢮⡳⣿⣿⣿⣿⣿⣿⣿⣿
  29. ⢎⢞⡜⣞⣿⡯⡆⠀⠄⠐⠀⢐⠡⢊⢐⢀⠈⠀⠄⠁⡀⠡⠸⡸⡪⣪⢺⢸⢱⠡⢑⡝⣟⣧⡂⠅⠪⣻⣎⢯⡺⣪⣗⢯⣺⢪⡣⡯⣝⢼⡪⣞⣽⣿⣿⣿⣿⣿⣿⣿
  30. ⡱⡣⣣⢳⣻⣯⢿⡐⠀⠂⠁⢀⠪⢐⠐⠄⡀⠁⠄⠁⠀⠄⡑⠆⡎⢎⢎⢇⢕⠬⠀⢇⢣⢻⣞⠌⠌⡪⣷⡱⣝⢮⣺⢕⡗⣕⢧⢳⢕⢗⣝⢞⣾⣿⣿⣿⣿⣿⣿⣻
  31. ⡸⡪⣪⡚⣞⣯⡗⣝⡀⠄⠁⡀⠌⡂⠅⡁⠄⠂⠐⠈⠀⢂⢐⢔⢜⢜⢜⢜⢔⢬⢊⠠⠨⠨⣻⣽⢐⠨⡺⣞⢼⢕⣗⡽⣪⢺⢜⢵⡹⣕⢵⡫⣾⣿⣿⣿⣿⣿⢯⣟
  32. ⢎⢇⢇⢧⡫⣿⡞⡜⡄⠀⠄⠀⠐⠄⡁⠀⠄⠐⠀⠂⢁⢐⢔⢕⢕⠱⢱⠱⡱⡱⡱⡅⡂⢁⠪⡿⣎⡢⡘⢽⡪⡧⣳⡝⣜⢎⢗⢇⢯⡪⡧⣻⡺⣿⣿⣿⣿⢯⡻⡮
  33. ⢕⢝⢜⡜⡮⣻⣗⢕⠅⡀⠂⠈⢈⠐⠀⠁⢀⠐⠀⡁⠄⢊⢢⢣⢣⢣⢣⢣⢣⢫⢪⢪⠀⠄⠨⡚⣿⣳⡜⡘⣗⢽⡺⡪⡺⡸⡵⡹⡕⡧⣫⡺⡺⣿⣿⡿⡯⡯⣫⢯
  34. ⢸⢱⢱⢱⢕⢯⣿⢜⠌⢀⠀⠂⠀⠂⠁⠈⠀⠀⠄⠀⠄⢑⠌⡆⢇⢅⠕⡌⡬⡪⡪⡪⠀⠄⢁⠸⡸⣯⡻⣪⢺⡵⡫⡪⣣⢫⡪⡣⡏⣞⢜⢮⣫⣻⡿⣫⢯⢞⣗⢽
  35. ⢪⢪⢪⢪⢎⢞⢾⡇⢕⠄⡆⡪⡘⡔⢔⢀⠐⠀⢀⠁⠠⠀⠑⠜⡌⡖⡕⡕⡕⢕⠑⠀⠠⠐⠀⡀⠕⡳⣻⣜⣕⣯⢣⠣⡣⡣⡣⡫⡪⡪⣎⢧⣧⡳⣝⢮⡳⡽⣜⢵
  36. ⡸⡸⡸⡸⡸⡱⣫⢯⣲⢱⢱⢘⢜⢜⢕⢕⢅⣂⠀⡀⠐⠀⡁⢐⠨⢐⠡⡊⢌⠂⠄⠂⠀⠂⡠⡠⡢⡫⡳⡱⡝⡮⡪⣇⢧⢳⢕⣝⢮⡫⡮⡳⣕⢵⣓⢗⡝⡮⡺⡜
  37. ⠱⡑⡕⡕⡝⡜⣎⢿⣪⢗⡝⡜⡜⡜⡜⢜⢕⢎⢎⢆⢎⢔⢄⠢⡌⢆⢕⠨⡢⢱⢰⢸⢸⣜⢮⢎⢎⢎⢇⢇⢯⢪⢇⡗⣝⢮⢳⢕⡗⣝⢮⡫⡮⣣⡳⡕⣝⢼⢸⢸
  38. ⢜⢸⢨⢪⢪⢪⢪⣻⣺⢯⢎⣇⢧⢧⢧⢧⢯⢾⡵⣯⢾⣼⣜⣜⢜⢜⢜⢜⢜⢜⡜⡮⣗⡯⣟⣎⢎⢆⢇⢕⢕⢇⢗⡝⣜⢮⣳⣟⣾⣷⣷⣿⣮⣎⢎⢎⢎⢎⢎⠎
  39. ⠣⡑⢌⠎⡜⢜⢜⢜⢜⡕⣧⡳⣫⢏⡯⡯⡯⡯⣯⢯⣟⢷⣻⣟⣿⢷⣷⣳⡽⡮⡾⣝⡎⡏⡎⡇⡕⡜⡔⡕⡕⣝⢜⢮⡳⡽⣺⢽⣻⢿⢿⣟⣿⣺⣝⢮⡢⡧⣧⡠
  40. ⠐⠨⢢⢑⢅⢣⢱⢜⢞⢮⡳⣝⢮⣻⣪⢯⢯⡻⣺⢝⡾⣝⢷⢽⣺⢯⢿⢽⣻⣮⡳⣕⢇⢇⢇⢇⠪⡪⡪⡪⡪⣎⢯⢳⣹⡪⡯⣫⢯⣻⢽⣳⣳⣳⢳⣝⢮⡫⡷⣷
  41. ⠀⠨⡂⡅⡖⡵⡹⣕⢏⡧⢯⢮⡳⣣⢷⢽⢕⣯⡳⣏⡯⣞⡽⣳⢽⢽⢽⣝⣗⣗⣟⢮⢳⡱⡱⡱⡑⡕⡜⢜⢜⡜⣎⢧⡣⡯⡺⣝⡵⡯⡯⣞⣞⢮⣳⡳⣝⣞⡽⣺
  42. ⠀⠰⡸⡸⣸⢪⡫⣎⢗⢽⢕⣗⢽⣕⢯⣳⡫⣞⢮⣳⢽⣪⢯⢞⡽⣺⢵⣳⣳⡳⣳⢝⢵⡹⡪⣎⢎⢆⢇⢇⢇⢗⡕⡧⡳⣝⡝⡮⣞⡽⡽⡵⣳⣫⢞⡮⣗⣗⢽⣳
  43. ⠀⠨⢢⢣⢳⡱⣝⢼⢭⢳⢳⢕⣗⢗⡽⣪⢞⡽⣕⢷⢝⡮⣏⡯⣞⣗⢽⡺⡼⣺⢵⡫⡧⣳⢹⢜⡜⡜⡜⢜⢪⢣⡫⣎⢯⡪⣞⣝⢞⢮⢏⡯⣳⣳⣫⢾⢕⣗⢯⣞
  44. ⠀⠀⠱⡱⡱⡕⡧⡳⡕⣏⢗⣝⢮⢳⢝⢮⡳⣝⢮⡳⣝⢮⡳⣝⢮⡺⡵⣫⢯⡳⣳⢝⣞⡜⣎⢇⢎⠎⡪⢊⠎⡎⡎⡮⡺⣜⢮⢮⣫⣫⡳⡽⣕⣗⢵⣫⢯⡺⡵⣳
  45. ⠀⠀⠈⢎⢎⠮⡺⡸⡕⡧⡳⣕⢝⢮⡫⣣⢯⣪⢳⢝⢮⢳⢝⢮⡳⣝⣝⢮⡳⣝⢮⡳⡵⣝⢼⢸⢐⠅⠂⡐⢅⢇⢣⢣⢳⡱⣝⠮⡮⣪⢞⣝⢮⡺⣕⢗⣗⢽⡹⣪
  46. ⠀⠈⠀⠌⡒⡝⡜⣕⢕⢧⢫⡪⡳⡱⣝⢜⢮⡪⣳⢹⡪⣳⢹⢕⢽⡸⣜⢵⢝⢮⢳⢝⢞⢮⡪⡣⡣⡑⢅⢢⠱⡘⡜⡜⣜⢜⠮⡝⡮⡪⡧⡳⡵⣹⡪⡳⡕⡗⣝⢮
  47. ⠀⠐⠀⡁⠌⢎⢎⢎⢮⢪⢎⢮⢪⢳⢱⢝⢜⢎⢮⢣⡫⡪⡎⣗⢕⢧⢳⡱⡝⣎⢗⣝⢕⡗⣝⢼⢸⢨⢢⢃⢇⢣⢣⢣⢣⢳⢹⢪⢎⢗⢝⡜⣎⢮⢪⡳⡹⣪⢺⢸
  48. ⠀⠠⠐⢀⠐⠈⡎⡪⡪⡪⡪⡪⡣⡫⡪⡪⡣⡫⡪⡣⡣⡫⣪⢪⢺⢸⢪⢪⢺⢸⢪⡪⡺⡸⣪⢪⢪⢪⠸⡨⡊⡎⡎⡎⡇⡏⡎⡞⡜⡕⣕⢕⢕⡕⡇⡧⡫⡪⡪⡪
  49. ⢀⠀⠐⠀⠄⠁⠨⡊⡎⡎⡎⡎⡎⡎⡎⡎⡇⡏⡎⡎⡎⡎⡎⡎⡎⡮⡪⡣⡳⡱⡱⡕⡝⣜⢜⢜⢜⢔⢕⢱⠸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡸⡨⡣⡣⡣⡣⡣⡣⡃⡇
  50. ⠀⠀⠈⡀⠂⠐⠀⢑⠜⡌⢎⢪⠪⡪⡪⡪⢪⠪⡪⢪⠪⡪⡪⢪⠪⡊⡎⡜⡌⡎⢎⢎⢎⢎⠎⡎⡪⠢⡑⢌⢪⢘⢔⠱⡡⢣⢃⢇⠕⡕⢅⢇⢣⢱⢑⢕⠸⡐⡱⡘`;
  51.  
  52.  
  53. class SPANKBANG_RULES {
  54. constructor() {
  55. this.PAGINATION = document.querySelector('.paginate-bar, .pagination');
  56. this.PAGINATION_LAST = parseInt(
  57. document.querySelector('.paginate-bar .status span')?.innerText.match(/\d+/)?.[0] ||
  58. document.querySelector('.pagination .next')?.previousElementSibling?.innerText);
  59. this.CONTAINER = document.querySelectorAll('.results .video-list')[0];
  60. this.HAS_VIDEOS = !!this.GET_THUMBS(document.body).length > 0;
  61. }
  62.  
  63. GET_THUMBS(html) {
  64. return Array.from(html.querySelectorAll('.video-item:not(.clear-fix)') || [])
  65. .filter(e => !e.parentElement.hasAttribute('data-disabled-layout-change'));
  66. }
  67.  
  68. THUMB_URL(thumb) {
  69. return thumb.querySelector('.thumb').href;
  70. }
  71.  
  72. THUMB_IMG_DATA(thumb) {
  73. const img = thumb.querySelector('img');
  74. const imgSrc = img.getAttribute('data-src');
  75. img.removeAttribute('data-src');
  76. return { img, imgSrc };
  77. }
  78.  
  79. THUMB_DATA(thumb) {
  80. const title = sanitizeStr(thumb.querySelector('.name')?.innerText);
  81. const duration = (parseInt(thumb.querySelector('span.l')?.innerText) || 1) * 60;
  82. return { title, duration };
  83. }
  84.  
  85. URL_DATA() {
  86. const url = new URL(window.location.href);
  87. const offset = parseInt(url.pathname.match(/\/(\d+)\/$/)?.pop()) || 1;
  88. if (!/\/\d+\/$/.test(url.pathname)) url.pathname = `${url.pathname}/${offset}/`;
  89.  
  90. const iteratable_url = n => {
  91. url.pathname = url.pathname.replace(/\/\d+\/$/, `/${n}/`);
  92. return url.href;
  93. };
  94.  
  95. return { offset, iteratable_url };
  96. }
  97. }
  98.  
  99. const RULES = new SPANKBANG_RULES();
  100.  
  101. //====================================================================================================
  102.  
  103. function createPreviewElement(src, mount) {
  104. const elem = parseDom(`
  105. <div class="video-js vjs-controls-disabled vjs-touch-enabled vjs-workinghover vjs-v7 vjs-user-active vjs-playing vjs-has-started mp4t_video-dimensions"
  106. id="mp4t_video" tabindex="-1" lang="en" translate="no" role="region" aria-label="Video Player" style="display: none;">
  107. <video id="mp4t_video_html5_api" class="vjs-tech" tabindex="-1" loop="loop" autoplay="autoplay" muted="muted" playsinline="playsinline"></video>
  108. <div class="vjs-poster vjs-hidden" tabindex="-1" aria-disabled="false"></div>
  109. <div class="vjs-text-track-display" translate="yes" aria-live="off" aria-atomic="true">
  110. <div style="position: absolute; inset: 0px; margin: 1.5%;"></div>
  111. </div>
  112. <div class="vjs-loading-spinner" dir="ltr">
  113. <span class="vjs-control-text">Video Player is loading.</span>
  114. </div><button class="vjs-big-play-button" type="button" title="Play Video" aria-disabled="false">
  115. <span class="vjs-icon-placeholder" aria-hidden="true"></span>
  116. <span class="vjs-control-text" aria-live="polite">
  117. </div>`);
  118.  
  119. mount.append(elem);
  120. const video = elem.querySelector('video');
  121. video.src = src;
  122. video.addEventListener('loadeddata', () => {
  123. elem.style.display = 'block';
  124. }, false);
  125.  
  126. return {
  127. elem,
  128. removeElem: () => {
  129. video.removeAttribute('src');
  130. video.load();
  131. elem.remove();
  132. }
  133. };
  134. }
  135.  
  136. function animate() {
  137. function handleThumbHover(e) {
  138. if (!(e.target.classList.contains('cover') && e.target.getAttribute('data-preview'))) return;
  139. const videoSrc = e.target.getAttribute('data-preview');
  140. const { removeElem } = createPreviewElement(videoSrc, e.target.parentElement.parentElement);
  141. e.target.parentElement.parentElement.addEventListener('mouseleave', removeElem, { once: true });
  142. }
  143.  
  144. document.body.addEventListener('mouseover', handleThumbHover);
  145. }
  146.  
  147. //====================================================================================================
  148.  
  149. console.log(LOGO);
  150.  
  151. const SCROLL_RESET_DELAY = 350;
  152.  
  153. const store = new JabroniOutfitStore(defaultStateWithDuration);
  154. const { state, stateLocale } = store;
  155. const { applyFilters, handleLoadedHTML } = new DataManager(RULES, state);
  156. store.subscribe(applyFilters);
  157.  
  158. if (RULES.HAS_VIDEOS) {
  159. animate();
  160. new JabroniOutfitUI(store);
  161. document.querySelectorAll('.video-list').forEach(c => {
  162. handleLoadedHTML(c, c);
  163. });
  164. }
  165.  
  166. if (RULES.PAGINATION) {
  167. const paginationManager = new PaginationManager(state, stateLocale, RULES, handleLoadedHTML, SCROLL_RESET_DELAY);
  168. }
  169.