|
1 | 1 | // ==UserScript== |
2 | 2 | // @name Improve Adult Experience |
3 | | -// @description Skip intros, set best quality and duration filters by default, make unwanted video previews transparent |
4 | | -// @version 0.8 |
| 3 | +// @description Skip intros, set best quality and duration filters by default, make unwanted video previews transparent, do fallbacks in case of load failures |
| 4 | +// @version 0.9 |
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/* |
|
39 | 39 |
|
40 | 40 | const simulateClick = (document, node) => { |
41 | 41 | console.log('simulateClick'); |
42 | | - if (!node) return; |
43 | | - const rect = node.getBoundingClientRect(); |
44 | | - const clientX = rect.x + rect.width / 2; |
45 | | - const clientY = rect.y + rect.height / 2; |
46 | | - const target = document.elementFromPoint(clientX, clientY); |
47 | | - ['mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'] |
48 | | - .forEach(i => target.dispatchEvent(new MouseEvent(i, { clientX, clientY, bubbles: true }))) |
| 42 | + try { |
| 43 | + if (!node) return; |
| 44 | + const rect = node.getBoundingClientRect(); |
| 45 | + const clientX = rect.x + rect.width / 2; |
| 46 | + const clientY = rect.y + rect.height / 2; |
| 47 | + const target = document.elementFromPoint(clientX, clientY); |
| 48 | + ['mouseover', 'mousemove', 'mousedown', 'mouseup', 'click'] |
| 49 | + .forEach(i => target.dispatchEvent(new MouseEvent(i, { clientX, clientY, bubbles: true }))) |
| 50 | + } catch (e) { |
| 51 | + console.error(e); |
| 52 | + } |
49 | 53 | }; |
50 | 54 |
|
51 | 55 | const subscribeOnChanges = (node, f) => { |
52 | | - f(node); |
53 | | - new MutationObserver(mutations => mutations.forEach(m => m.addedNodes.forEach(f))) |
54 | | - .observe(node, { childList: true, subtree: true }); |
| 56 | + try { |
| 57 | + f(node); |
| 58 | + new MutationObserver(mutations => mutations.forEach(m => m.addedNodes.forEach(f))) |
| 59 | + .observe(node, { childList: true, subtree: true }); |
| 60 | + } catch (e) { |
| 61 | + console.error(e); |
| 62 | + } |
55 | 63 | }; |
56 | 64 |
|
57 | 65 | const pornhub = _ => { |
|
68 | 76 | const isUnwanted = url => currentTime() < loadUnwanted()[videoId(url)]; |
69 | 77 | const videoId = url => url.searchParams.get('viewkey') || url.pathname.split('/').slice(-1)[0]; |
70 | 78 | const watchedVideos = new Set; |
71 | | - const disliked = main => !!main.querySelector('div.active[data-title="I Dislike This"]'); |
72 | 79 |
|
73 | | - const processEmbedded = (document, similarVideos) => { |
74 | | - const main = document.querySelector('div#main-container') || document.body; |
| 80 | + const disliked = body => !!body.querySelector('div.active[data-title="I Dislike This"]'); |
| 81 | + const premiumRedirect = node => node.href.startsWith('javascript:'); |
75 | 82 |
|
| 83 | + const searchFilterParams = Object.entries({ |
| 84 | + 'min_duration': MIN_DURATION_MINS, |
| 85 | + 'hd': 1, |
| 86 | + 'o': 'tr', |
| 87 | + 't': 'm', |
| 88 | + }); |
| 89 | + |
| 90 | + const processEmbedded = (document, similarVideos) => { |
| 91 | + const body = document.body; |
76 | 92 | try { |
77 | 93 | const css = ` |
78 | 94 | div.mgp_topBar { display: none !important; } |
79 | 95 | div.mgp_thumbnailsGrid { display: none !important; } |
80 | 96 | img.mgp_pornhub { display: none !important; } |
81 | 97 | `; |
82 | | - const styleApplied = !![...main.querySelectorAll('style')].find(i => i.innerHTML === css); |
| 98 | + const styleApplied = !![...body.querySelectorAll('style')].find(i => i.innerHTML === css); |
83 | 99 | if (styleApplied) { |
84 | 100 | console.log('embedded video is already initialized'); |
85 | 101 | return; |
86 | 102 | } |
87 | 103 | console.log('applying style'); |
88 | 104 | const style = document.createElement('style'); |
89 | 105 | style.innerHTML = css; |
90 | | - main.appendChild(style); |
| 106 | + body.appendChild(style); |
91 | 107 | } catch (e) { |
92 | 108 | console.error(e); |
93 | 109 | } |
94 | 110 |
|
95 | 111 | try { |
96 | | - const requiresRefresh = main.querySelector('div.mgp_errorIcon') && main.querySelector('p')?.textContent.includes('Please refresh the page'); |
| 112 | + const requiresRefresh = body.querySelector('div.mgp_errorIcon') && body.querySelector('p')?.textContent.includes('Please refresh the page'); |
97 | 113 | if (requiresRefresh) { |
98 | 114 | console.log('refreshing after error'); |
99 | 115 | window.location.href = window.location.toString(); |
|
102 | 118 | console.error(e); |
103 | 119 | } |
104 | 120 |
|
105 | | - const video = main.querySelector('video'); |
| 121 | + const video = body.querySelector('video'); |
106 | 122 | try { |
107 | 123 | if (!video) { |
108 | 124 | console.log('embedding this video is probably not allowed'); |
|
128 | 144 | console.error(e); |
129 | 145 | } |
130 | 146 |
|
131 | | - video.addEventListener('loadstart', _ => simulateClick(document, main.querySelector('div.mgp_playIcon'))); |
| 147 | + video.addEventListener('loadstart', _ => simulateClick(document, body.querySelector('div.mgp_playIcon'))); |
132 | 148 | video.addEventListener('loadedmetadata', _ => { |
133 | | - if (disliked(main)) { |
| 149 | + if (disliked(body)) { |
134 | 150 | setUnwanted(url, Number.MAX_SAFE_INTEGER); |
135 | 151 | } |
136 | 152 | video.currentTime = random(video.duration / 4, video.duration / 3); |
137 | 153 | }); |
138 | | - main.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => { |
| 154 | + body.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => { |
139 | 155 | if (video.paused) { |
140 | 156 | console.log('paused on grid menu'); |
141 | | - const button = main.querySelector('div.mgp_playIcon'); |
| 157 | + const button = body.querySelector('div.mgp_playIcon'); |
142 | 158 | simulateClick(document, button); |
143 | 159 | setTimeout(_ => { |
144 | 160 | if (video.paused) { |
|
152 | 168 | video.load(); |
153 | 169 | }; |
154 | 170 |
|
155 | | - const premiumRedirect = node => node.href.startsWith('javascript:'); |
| 171 | + const body = document.body; |
156 | 172 |
|
157 | 173 | const processPreview = node => { |
158 | 174 | try { |
|
182 | 198 |
|
183 | 199 | const processPlaylistItem = node => { |
184 | 200 | if (node.nodeType !== 1) return; |
185 | | - |
186 | 201 | if (node.tagName === 'SPAN' && node.classList.contains('duration')) { |
187 | 202 | processPreview(node); |
188 | 203 | return; |
189 | 204 | } |
190 | | - |
191 | 205 | node.childNodes.forEach(processPlaylistItem); |
192 | 206 | }; |
193 | 207 |
|
194 | | - const main = document.querySelector('div#main-container') || document.body; |
195 | | - subscribeOnChanges(main, processPlaylistItem); |
196 | | - |
197 | | - try { |
198 | | - const style = document.createElement('style'); |
199 | | - style.innerHTML = ` |
200 | | - div.${UNWANTED}, li.${UNWANTED} { opacity: 10%; } |
201 | | - div.${UNWANTED}:hover, li.${UNWANTED}:hover { opacity: 40%; } |
202 | | - `; |
203 | | - main.appendChild(style); |
204 | | - } catch (e) { |
205 | | - console.error(e); |
206 | | - } |
207 | | - |
208 | | - const filterParams = Object.entries({ |
209 | | - 'min_duration': MIN_DURATION_MINS, |
210 | | - 'hd': 1, |
211 | | - 'o': 'tr', |
212 | | - 't': 'm', |
213 | | - }); |
214 | | - |
215 | 208 | const processLink = node => { |
216 | 209 | if (node.nodeType !== 1) return; |
217 | 210 | if (node.tagName === 'A') { |
218 | 211 | try { |
219 | | - if (premiumRedirect(node)) return; |
| 212 | + if (premiumRedirect(node) || node.closest('ul.filterListItem')) return; |
220 | 213 | const url = new URL(node.href.startsWith('https:') ? node.href : `${window.location.origin}${node.href}`); |
221 | 214 | const params = url.searchParams; |
222 | 215 | const p = url.pathname; |
223 | | - if (p === '/video' || p === '/video/search' || p.startsWith('/categories/')) { |
224 | | - filterParams.forEach(([key, value]) => params.set(key, value)); |
225 | | - node.href = url.toString(); |
| 216 | + const parts = p.split('/'); |
| 217 | + if (['/video', '/video/search'].includes(p) || p.startsWith('/categories/')) { |
| 218 | + searchFilterParams.forEach(([key, value]) => params.set(key, value)); |
| 219 | + } else if (p.startsWith('/pornstar/')) { |
| 220 | + if (parts.length === 3) { |
| 221 | + url.pathname = [...parts, 'videos', 'upload'].join('/'); |
| 222 | + } else if (!p.endsWith('/videos/upload')) { |
| 223 | + return; |
| 224 | + } |
| 225 | + params.set('o', 'lg'); |
| 226 | + } else if (['/model/', '/channels/'].find(i => p.startsWith(i))) { |
| 227 | + if (parts.length === 3) { |
| 228 | + url.pathname = [...parts, 'videos'].join('/'); |
| 229 | + } else if (!p.endsWith('/videos')) { |
| 230 | + return; |
| 231 | + } |
| 232 | + params.set('o', p.startsWith('/model/') ? 'lg' : 'ra'); |
226 | 233 | } |
| 234 | + setTimeout(_ => node.href = url.toString(), 500); |
227 | 235 | } catch (e) { |
228 | 236 | console.error(node.href, e); |
229 | 237 | } |
|
232 | 240 | node.childNodes.forEach(processLink); |
233 | 241 | }; |
234 | 242 |
|
235 | | - subscribeOnChanges(main, processLink); |
236 | | - |
237 | | - // TODO |
238 | | - /*const searchForm = main.querySelector('form#search_form') || main.querySelector('form'); |
239 | | - if (searchForm) { |
240 | | - filterParams.forEach(([key, value]) => { |
241 | | - const input = document.createElement('input'); |
242 | | - input.type = 'hidden'; |
243 | | - input.name = key; |
244 | | - input.value = value; |
245 | | - searchForm.appendChild(input); |
246 | | - }); |
247 | | - }*/ |
248 | | - |
249 | | - const similarVideos = [...main.querySelectorAll('var.duration')] |
| 243 | + const similarVideos = [...body.querySelectorAll('var.duration')] |
250 | 244 | .map(i => processPreview(i)) |
251 | 245 | .filter(i => i); |
| 246 | + subscribeOnChanges(body, processPlaylistItem); |
| 247 | + subscribeOnChanges(body, processLink); |
| 248 | + |
| 249 | + try { |
| 250 | + const style = document.createElement('style'); |
| 251 | + style.innerHTML = ` |
| 252 | + div.${UNWANTED}, li.${UNWANTED} { opacity: 10%; } |
| 253 | + div.${UNWANTED}:hover, li.${UNWANTED}:hover { opacity: 40%; } |
| 254 | + #searchSuggestions a:focus { background-color: #111111; } |
| 255 | + `; |
| 256 | + body.appendChild(style); |
| 257 | + } catch (e) { |
| 258 | + console.error(e); |
| 259 | + } |
| 260 | + |
| 261 | + const searchForm = body.querySelector('form#search_form') || body.querySelector('form'); |
| 262 | + searchForm?.addEventListener('submit', event => { |
| 263 | + event.preventDefault(); |
| 264 | + event.stopImmediatePropagation(); |
| 265 | + |
| 266 | + const node = document.activeElement; |
| 267 | + const searchInput = searchForm.querySelector('input#searchInput, input[name="search"]') || (node.tagName === 'INPUT' && searchForm.contains(node) ? node : undefined); |
| 268 | + const query = (searchInput?.value || '').trim(); |
| 269 | + if (query.length === 0) return; |
| 270 | + |
| 271 | + const url = new URL(searchForm.action); // TODO |
| 272 | + searchFilterParams.forEach(([key, value]) => url.searchParams.set(key, value)); |
| 273 | + url.searchParams.set('search', query); |
| 274 | + window.location.href = url.toString(); |
| 275 | + }, true); |
252 | 276 |
|
253 | 277 | if (p.startsWith('/embed/')) { |
254 | 278 | // this branch gets selected for both iframed and redirected embedded player |
|
257 | 281 | processEmbedded(document, similarVideos); // document is a part of iframe here |
258 | 282 | }, 1000); |
259 | 283 | } else if (p === '/view_video.php') { |
260 | | - const durationFromNormalPlayer = timeToSeconds(main.querySelector('span.mgp_total')?.textContent); |
| 284 | + const durationFromNormalPlayer = timeToSeconds(body.querySelector('span.mgp_total')?.textContent); |
261 | 285 | if (durationFromNormalPlayer) { |
262 | | - const lowQuality = ![...main.querySelectorAll('ul.mgp_quality > li')].find(i => i.textContent.includes(MIN_VIDEO_HEIGHT)); |
| 286 | + const lowQuality = ![...body.querySelectorAll('ul.mgp_quality > li')].find(i => i.textContent.includes(MIN_VIDEO_HEIGHT)); |
263 | 287 | console.log('low quality', lowQuality); |
264 | | - if (lowQuality || disliked(main)) { |
| 288 | + if (lowQuality || disliked(body)) { |
265 | 289 | setUnwanted(url, Number.MAX_SAFE_INTEGER); |
266 | 290 | } |
267 | 291 |
|
|
273 | 297 | } else { |
274 | 298 | console.log('fallback to embedded player'); |
275 | 299 | const embedUrl = `https://www.pornhub.com/embed/${params.get('viewkey')}`; |
276 | | - const container = main.querySelector('div.playerFlvContainer'); |
| 300 | + const container = body.querySelector('div.playerFlvContainer'); |
277 | 301 | if (container) { |
278 | 302 | const iframe = document.createElement('iframe'); |
279 | 303 | iframe.onload = _ => { |
|
0 commit comments