Skip to content

Commit 9aed6ee

Browse files
committed
Update
1 parent caa9b5f commit 9aed6ee

File tree

1 file changed

+111
-45
lines changed

1 file changed

+111
-45
lines changed

improve-adult-experience.js

Lines changed: 111 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ==UserScript==
22
// @name Improve Adult Experience
33
// @description Skip intros, set best quality and duration filters by default, make unrelated video previews transparent
4-
// @version 0.6
4+
// @version 0.7
55
// @downloadURL https://userscripts.codonaft.com/improve-adult-experience.js
66
// @exclude-match https://spankbang.com/*/video/*
77
// @match https://spankbang.com/*
@@ -18,6 +18,9 @@
1818
(_ => {
1919
'use strict';
2020

21+
const MIN_DURATION_MIN = 20;
22+
const MIN_VIDEO_HEIGHT = 1080;
23+
2124
if (performance.getEntriesByType('navigation')[0]?.responseStatus !== 200) return;
2225

2326
const url = new URL(window.location.href);
@@ -26,7 +29,9 @@
2629
const p = url.pathname;
2730
let newUrl;
2831

32+
const currentTime = () => Math.round(Date.now() / 1000);
2933
const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
34+
const pickRandom = xs => xs[random(0, xs.length)];
3035

3136
const timeToSeconds = time => (time || '').trim().split(':').map(Number).reduceRight((total, value, index, parts) => total + value * 60 ** (parts.length - 1 - index), 0);
3237

@@ -40,16 +45,39 @@
4045
.forEach(i => target.dispatchEvent(new MouseEvent(i, { clientX, clientY, bubbles: true })))
4146
};
4247

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+
4354
const pornhub = _ => {
4455
// 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"]');
4572

4673
const processEmbedded = (document, similarVideos) => {
74+
const main = document.querySelector('div#main-container') || document.body;
4775
const css = `
4876
div.mgp_topBar { display: none !important; }
4977
div.mgp_thumbnailsGrid { display: none !important; }
5078
img.mgp_pornhub { display: none !important; }
5179
`;
52-
const styleApplied = [...document.body.querySelectorAll('style')]
80+
const styleApplied = [...main.querySelectorAll('style')]
5381
.filter(i => i.innerHTML === css)
5482
.length > 0;
5583
if (styleApplied) {
@@ -59,39 +87,47 @@
5987
console.log('applying style');
6088
const style = document.createElement('style');
6189
style.innerHTML = css;
62-
document.body.appendChild(style);
90+
main.appendChild(style);
6391

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');
6693
if (requiresRefresh) {
6794
console.log('refreshing after error');
68-
window.location.href = window.location.href;
95+
window.location.href = window.location.toString();
6996
}
7097

71-
const video = document.body.querySelector('video');
98+
const video = main.querySelector('video');
7299
if (!video) {
73100
console.log('embedding this video is probably not allowed');
74101
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+
76110
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);
80114
} else {
81115
console.log('giving up');
82116
}
83117
return;
84118
}
85119

86-
video.addEventListener('loadstart', _ => simulateClick(document, document.body.querySelector('div.mgp_playIcon')));
120+
video.addEventListener('loadstart', _ => simulateClick(document, main.querySelector('div.mgp_playIcon')));
87121
video.addEventListener('loadedmetadata', _ => {
88-
// TODO: save video size? video.videoHeight
122+
if (disliked(main)) {
123+
setUnwanted(url, Number.MAX_SAFE_INTEGER);
124+
}
89125
video.currentTime = random(video.duration / 4, video.duration / 3);
90126
});
91-
document.body.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => {
127+
main.querySelector('div.mgp_gridMenu')?.addEventListener('click', _ => setTimeout(_ => {
92128
if (video.paused) {
93129
console.log('paused on grid menu');
94-
const button = document.body.querySelector('div.mgp_playIcon');
130+
const button = main.querySelector('div.mgp_playIcon');
95131
simulateClick(document, button);
96132
setTimeout(_ => {
97133
if (video.paused) {
@@ -105,39 +141,69 @@
105141
video.load();
106142
};
107143

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) {
117147
const duration = timeToSeconds(i.textContent);
118148
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) {
121152
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);
127161
}
162+
return link.href;
128163
}
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);
131191

132192
if (p.startsWith('/embed/')) {
133193
// this branch gets selected for both iframed and redirected embedded player
134194
setTimeout(_ => {
135195
console.log('processing embedded');
136196
processEmbedded(document, similarVideos); // document is a part of iframe here
137-
}, 500);
197+
}, 1000);
138198
} 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);
140200
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+
141207
if (!params.has('t') || Number(params.get('t')) >= durationFromNormalPlayer) {
142208
window.stop();
143209
params.set('t', random(durationFromNormalPlayer / 4, durationFromNormalPlayer / 3));
@@ -146,7 +212,7 @@
146212
} else {
147213
console.log('fallback to embedded player');
148214
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');
150216
if (container) {
151217
const iframe = document.createElement('iframe');
152218
iframe.onload = _ => {
@@ -168,7 +234,7 @@
168234
}
169235
}
170236
} 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);
172238
params.set('hd', 1);
173239
newUrl = url.toString();
174240
}
@@ -179,13 +245,13 @@
179245

180246
if (p === '/' && params.has('k') && !params.has('quality')) {
181247
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`);
184250
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`)) {
186252
const ps = p.split('/');
187253
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]}`;
189255
newUrl = url.toString();
190256
}
191257
}
@@ -199,7 +265,7 @@
199265
url.pathname = '/trending_videos/'
200266
}
201267
params.set('q', 'fhd');
202-
params.set('d', '20');
268+
params.set('d', MIN_DURATION_MIN);
203269
newUrl = url.toString();
204270
}
205271
};
@@ -229,16 +295,16 @@
229295

230296
if (p.startsWith('/search/')) {
231297
if (params.get('length') !== 'full') {
232-
params.set('quality', '1080p');
298+
params.set('quality', `${MIN_VIDEO_HEIGHT}p`);
233299
params.set('length', 'full');
234300
newUrl = url.toString();
235301
}
236302
} else if (p.startsWith('/categories/') || p.startsWith('/channels/')) {
237303
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`;
239305
}
240306
} 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`;
242308
}
243309
};
244310

0 commit comments

Comments
 (0)