|
1 | 1 | // ==UserScript== |
2 | 2 | // @name Improve Adult Experience |
3 | 3 | // @description Skip intros, set best quality and duration filters by default, make unrelated video previews transparent |
4 | | -// @version 0.6 |
| 4 | +// @version 0.7 |
5 | 5 | // @downloadURL https://userscripts.codonaft.com/improve-adult-experience.js |
6 | 6 | // @exclude-match https://spankbang.com/*/video/* |
7 | 7 | // @match https://spankbang.com/* |
|
18 | 18 | (_ => { |
19 | 19 | 'use strict'; |
20 | 20 |
|
| 21 | + const MIN_DURATION_MIN = 20; |
| 22 | + const MIN_VIDEO_HEIGHT = 1080; |
| 23 | + |
21 | 24 | if (performance.getEntriesByType('navigation')[0]?.responseStatus !== 200) return; |
22 | 25 |
|
23 | 26 | const url = new URL(window.location.href); |
|
26 | 29 | const p = url.pathname; |
27 | 30 | let newUrl; |
28 | 31 |
|
| 32 | + const currentTime = () => Math.round(Date.now() / 1000); |
29 | 33 | const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); |
| 34 | + const pickRandom = xs => xs[random(0, xs.length)]; |
30 | 35 |
|
31 | 36 | const timeToSeconds = time => (time || '').trim().split(':').map(Number).reduceRight((total, value, index, parts) => total + value * 60 ** (parts.length - 1 - index), 0); |
32 | 37 |
|
|
40 | 45 | .forEach(i => target.dispatchEvent(new MouseEvent(i, { clientX, clientY, bubbles: true }))) |
41 | 46 | }; |
42 | 47 |
|
| 48 | + const subscribeOnChanges = (node, f) => { |
| 49 | + f(node); |
| 50 | + new MutationObserver(mutations => mutations.forEach(m => m.addedNodes.forEach(f))) |
| 51 | + .observe(node, { childList: true, subtree: true }); |
| 52 | + }; |
| 53 | + |
43 | 54 | const pornhub = _ => { |
44 | 55 | // TODO: never redirect, just update the URLs |
| 56 | + // TODO: improve resistance to exceptions |
| 57 | + |
| 58 | + const UNWANTED = '__unwanted'; |
| 59 | + const loadUnwanted = () => JSON.parse(localStorage.getItem(UNWANTED)) || {}; |
| 60 | + const setUnwanted = (url, ttl) => { |
| 61 | + const id = videoId(url); |
| 62 | + if (!id) return; |
| 63 | + const unwanted = loadUnwanted(); |
| 64 | + if (!unwanted[id]) { |
| 65 | + localStorage.setItem(UNWANTED, JSON.stringify({ ...unwanted, [id]: ttl })) |
| 66 | + } |
| 67 | + }; |
| 68 | + const isUnwanted = url => currentTime() < loadUnwanted()[videoId(url)]; |
| 69 | + const videoId = url => url.searchParams.get('viewkey') || url.pathname.split('/').slice(-1)[0]; |
| 70 | + const watchedVideos = new Set; |
| 71 | + const disliked = main => !!main.querySelector('div.active[data-title="I Dislike This"]'); |
45 | 72 |
|
46 | 73 | const processEmbedded = (document, similarVideos) => { |
| 74 | + const main = document.querySelector('div#main-container') || document.body; |
47 | 75 | const css = ` |
48 | 76 | div.mgp_topBar { display: none !important; } |
49 | 77 | div.mgp_thumbnailsGrid { display: none !important; } |
50 | 78 | img.mgp_pornhub { display: none !important; } |
51 | 79 | `; |
52 | | - const styleApplied = [...document.body.querySelectorAll('style')] |
| 80 | + const styleApplied = [...main.querySelectorAll('style')] |
53 | 81 | .filter(i => i.innerHTML === css) |
54 | 82 | .length > 0; |
55 | 83 | if (styleApplied) { |
|
59 | 87 | console.log('applying style'); |
60 | 88 | const style = document.createElement('style'); |
61 | 89 | style.innerHTML = css; |
62 | | - document.body.appendChild(style); |
| 90 | + main.appendChild(style); |
63 | 91 |
|
64 | | - const requiresRefresh = document.body.querySelector('div.mgp_errorIcon') && |
65 | | - document.body.querySelector('p')?.textContent.includes('Please refresh the page'); |
| 92 | + const requiresRefresh = main.querySelector('div.mgp_errorIcon') && main.querySelector('p')?.textContent.includes('Please refresh the page'); |
66 | 93 | if (requiresRefresh) { |
67 | 94 | console.log('refreshing after error'); |
68 | | - window.location.href = window.location.href; |
| 95 | + window.location.href = window.location.toString(); |
69 | 96 | } |
70 | 97 |
|
71 | | - const video = document.body.querySelector('video'); |
| 98 | + const video = main.querySelector('video'); |
72 | 99 | if (!video) { |
73 | 100 | console.log('embedding this video is probably not allowed'); |
74 | 101 | window.stop(); |
75 | | - // window.parent.location = window.parent.location; // TODO: do this no more than once during 5 mins for a given video? |
| 102 | + |
| 103 | + if (!isUnwanted(url)) { |
| 104 | + console.log('making single refresh attempt'); |
| 105 | + setUnwanted(url, currentTime() + 5 * 60); |
| 106 | + window.location = url.toString(); |
| 107 | + return; |
| 108 | + } |
| 109 | + |
76 | 110 | if (similarVideos.length > 0) { |
77 | | - console.log('redirecting to random non-boring similar video'); |
78 | | - // TODO: use non-watched videos as priority? |
79 | | - window.location.href = similarVideos[random(0, similarVideos.length)]; // FIXME: back history |
| 111 | + console.log('redirecting to random non-unwanted similar video'); |
| 112 | + const newSimilarVideos = similarVideos.filter(i => !watchedVideos.has(i)); |
| 113 | + window.location.href = newSimilarVideos.length > 0 ? pickRandom(newSimilarVideos) : pickRandom(similarVideos); |
80 | 114 | } else { |
81 | 115 | console.log('giving up'); |
82 | 116 | } |
83 | 117 | return; |
84 | 118 | } |
85 | 119 |
|
86 | | - video.addEventListener('loadstart', _ => simulateClick(document, document.body.querySelector('div.mgp_playIcon'))); |
| 120 | + video.addEventListener('loadstart', _ => simulateClick(document, main.querySelector('div.mgp_playIcon'))); |
87 | 121 | video.addEventListener('loadedmetadata', _ => { |
88 | | - // TODO: save video size? video.videoHeight |
| 122 | + if (disliked(main)) { |
| 123 | + setUnwanted(url, Number.MAX_SAFE_INTEGER); |
| 124 | + } |
89 | 125 | video.currentTime = random(video.duration / 4, video.duration / 3); |
90 | 126 | }); |
91 | | - document.body.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => { |
| 127 | + main.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => { |
92 | 128 | if (video.paused) { |
93 | 129 | console.log('paused on grid menu'); |
94 | | - const button = document.body.querySelector('div.mgp_playIcon'); |
| 130 | + const button = main.querySelector('div.mgp_playIcon'); |
95 | 131 | simulateClick(document, button); |
96 | 132 | setTimeout(_ => { |
97 | 133 | if (video.paused) { |
|
105 | 141 | video.load(); |
106 | 142 | }; |
107 | 143 |
|
108 | | - const style = document.createElement('style'); |
109 | | - style.innerHTML = ` |
110 | | - div.boringcontent { opacity: 10%; } |
111 | | - div.boringcontent:hover { opacity: 40%; } |
112 | | - `; |
113 | | - document.body.appendChild(style); |
114 | | - |
115 | | - const similarVideos = [...document.body.querySelectorAll('var.duration')] |
116 | | - .flatMap(i => { |
| 144 | + const processPreview = i => { |
| 145 | + const link = i.closest('a'); |
| 146 | + if (link) { |
117 | 147 | const duration = timeToSeconds(i.textContent); |
118 | 148 | const t = random(duration / 4, duration / 3); |
119 | | - const link = i.closest('a'); |
120 | | - if (link) { |
| 149 | + |
| 150 | + const premiumRedirect = !link.href.startsWith('https://'); |
| 151 | + if (!premiumRedirect) { |
121 | 152 | link.href += `&t=${t}`; |
122 | | - const div = link.closest('div.phimage')?.parentNode; // TODO: find previews from current playlist as well |
123 | | - if (duration < 20 * 60) { // TODO: check quality, non-free-premiumness, my like or dislike; temporary (with exp backoff?) ban videos that fail to load? |
124 | | - div?.classList.add('boringcontent'); |
125 | | - } else { |
126 | | - return [link.href]; |
| 153 | + } |
| 154 | + |
| 155 | + const node = link.closest('div.phimage')?.parentNode || link.closest('li'); |
| 156 | + if (premiumRedirect || duration < MIN_DURATION_MIN * 60 || isUnwanted(new URL(link.href))) { |
| 157 | + node?.classList.add(UNWANTED); |
| 158 | + } else { |
| 159 | + if (link.querySelector('div.watchedVideoText')) { |
| 160 | + watchedVideos.add(link.href); |
127 | 161 | } |
| 162 | + return link.href; |
128 | 163 | } |
129 | | - return []; |
130 | | - }); |
| 164 | + } |
| 165 | + }; |
| 166 | + |
| 167 | + const processPlaylistItem = node => { |
| 168 | + if (node.nodeType !== 1) return; |
| 169 | + |
| 170 | + if (node.tagName === 'SPAN' && node.classList.contains('duration')) { |
| 171 | + processPreview(node); |
| 172 | + return; |
| 173 | + } |
| 174 | + |
| 175 | + node.childNodes.forEach(processPlaylistItem); |
| 176 | + }; |
| 177 | + |
| 178 | + const main = document.querySelector('div#main-container') || document.body; |
| 179 | + subscribeOnChanges(main, processPlaylistItem); |
| 180 | + |
| 181 | + const style = document.createElement('style'); |
| 182 | + style.innerHTML = ` |
| 183 | + div.${UNWANTED}, li.${UNWANTED} { opacity: 10%; } |
| 184 | + div.${UNWANTED}:hover, li.${UNWANTED}:hover { opacity: 40%; } |
| 185 | + `; |
| 186 | + main.appendChild(style); |
| 187 | + |
| 188 | + const similarVideos = [...main.querySelectorAll('var.duration')] |
| 189 | + .map(i => processPreview(i)) |
| 190 | + .filter(i => i); |
131 | 191 |
|
132 | 192 | if (p.startsWith('/embed/')) { |
133 | 193 | // this branch gets selected for both iframed and redirected embedded player |
134 | 194 | setTimeout(_ => { |
135 | 195 | console.log('processing embedded'); |
136 | 196 | processEmbedded(document, similarVideos); // document is a part of iframe here |
137 | | - }, 500); |
| 197 | + }, 1000); |
138 | 198 | } else if (p === '/view_video.php') { |
139 | | - const durationFromNormalPlayer = timeToSeconds(document.body.querySelector('span.mgp_total')?.textContent); |
| 199 | + const durationFromNormalPlayer = timeToSeconds(main.querySelector('span.mgp_total')?.textContent); |
140 | 200 | if (durationFromNormalPlayer) { |
| 201 | + const lowQuality = ![...main.querySelectorAll('ul.mgp_quality > li')].find(i => i.textContent.includes(MIN_VIDEO_HEIGHT)); |
| 202 | + console.log('low quality', lowQuality); |
| 203 | + if (lowQuality || disliked(main)) { |
| 204 | + setUnwanted(url, Number.MAX_SAFE_INTEGER); |
| 205 | + } |
| 206 | + |
141 | 207 | if (!params.has('t') || Number(params.get('t')) >= durationFromNormalPlayer) { |
142 | 208 | window.stop(); |
143 | 209 | params.set('t', random(durationFromNormalPlayer / 4, durationFromNormalPlayer / 3)); |
|
146 | 212 | } else { |
147 | 213 | console.log('fallback to embedded player'); |
148 | 214 | const embedUrl = `https://www.pornhub.com/embed/${params.get('viewkey')}`; |
149 | | - const container = document.body.querySelector('div.playerFlvContainer'); |
| 215 | + const container = main.querySelector('div.playerFlvContainer'); |
150 | 216 | if (container) { |
151 | 217 | const iframe = document.createElement('iframe'); |
152 | 218 | iframe.onload = _ => { |
|
168 | 234 | } |
169 | 235 | } |
170 | 236 | } else if (params.get('hd') !== '1' && !params.has('min_duration') && (p.startsWith('/categories/') || p === '/video' || p === '/video/search')) { |
171 | | - params.set('min_duration', 20); |
| 237 | + params.set('min_duration', MIN_DURATION_MIN); |
172 | 238 | params.set('hd', 1); |
173 | 239 | newUrl = url.toString(); |
174 | 240 | } |
|
179 | 245 |
|
180 | 246 | if (p === '/' && params.has('k') && !params.has('quality')) { |
181 | 247 | params.set('sort', 'rating'); |
182 | | - params.set('durf', '20min_more'); |
183 | | - params.set('quality', '1080P'); |
| 248 | + params.set('durf', `${MIN_DURATION_MIN}min_more`); |
| 249 | + params.set('quality', `${MIN_VIDEO_HEIGHT}P`); |
184 | 250 | newUrl = url.toString(); |
185 | | - } else if (p.startsWith('/c/') && !p.includes('q:1080P')) { |
| 251 | + } else if (p.startsWith('/c/') && !p.includes(`q:${MIN_VIDEO_HEIGHT}P`)) { |
186 | 252 | const ps = p.split('/'); |
187 | 253 | if (ps.length >= 3) { |
188 | | - url.pathname = `${ps[1]}/s:rating/d:20min_more/q:1080P/${ps[2]}`; |
| 254 | + url.pathname = `${ps[1]}/s:rating/d:${MIN_DURATION_MIN}min_more/q:${MIN_VIDEO_HEIGHT}P/${ps[2]}`; |
189 | 255 | newUrl = url.toString(); |
190 | 256 | } |
191 | 257 | } |
|
199 | 265 | url.pathname = '/trending_videos/' |
200 | 266 | } |
201 | 267 | params.set('q', 'fhd'); |
202 | | - params.set('d', '20'); |
| 268 | + params.set('d', MIN_DURATION_MIN); |
203 | 269 | newUrl = url.toString(); |
204 | 270 | } |
205 | 271 | }; |
|
229 | 295 |
|
230 | 296 | if (p.startsWith('/search/')) { |
231 | 297 | if (params.get('length') !== 'full') { |
232 | | - params.set('quality', '1080p'); |
| 298 | + params.set('quality', `${MIN_VIDEO_HEIGHT}p`); |
233 | 299 | params.set('length', 'full'); |
234 | 300 | newUrl = url.toString(); |
235 | 301 | } |
236 | 302 | } else if (p.startsWith('/categories/') || p.startsWith('/channels/')) { |
237 | 303 | if (!p.includes('/hd/')) { |
238 | | - newUrl = `${url}/hd/full-length/best?quality=1080p`; |
| 304 | + newUrl = `${url}/hd/full-length/best?quality=${MIN_VIDEO_HEIGHT}p`; |
239 | 305 | } |
240 | 306 | } else if (p === '/') { |
241 | | - newUrl = `${url}/hd/full-length/best/monthly?quality=1080p`; |
| 307 | + newUrl = `${url}/hd/full-length/best/monthly?quality=${MIN_VIDEO_HEIGHT}p`; |
242 | 308 | } |
243 | 309 | }; |
244 | 310 |
|
|
0 commit comments