- // ==UserScript==
- // @name PornHub Improved
- // @namespace http://tampermonkey.net/
- // @license MIT
- // @version 1.5.3
- // @description Infinite scroll (optional). Lazy loading. Filter by duration, include/exclude phrases
- // @author smartacephale
- // @match https://*.pornhub.com/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=pornhub.com
- // @grant GM_addStyle
- // @run-at document-end
- // @require https://unpkg.com/vue@3.4.21/dist/vue.global.prod.js
- // @require https://update.greasyfork.org/scripts/494206/utils.user.js
- // @require data:, let tempVue = unsafeWindow.Vue; unsafeWindow.Vue = Vue; const { ref, watch, reactive, createApp } = Vue;
- // @require https://update.greasyfork.org/scripts/494207/persistent-state.user.js
- // @require https://update.greasyfork.org/scripts/494204/data-manager.user.js
- // @require https://update.greasyfork.org/scripts/494205/pagination-manager.user.js
- // @require https://update.greasyfork.org/scripts/494203/vue-ui.user.js
- // @run-at document-idle
- // ==/UserScript==
- /* globals jQuery, $, Vue, createApp, watch, reactive, findNextSibling, watchElementChildrenCount,
- timeToSeconds, parseDOM, parseIntegerOr, fetchHtml, stringToWords, Observer
- LazyImgLoader, PersistentState, DataManager, PaginationManager, VueUI */
-
- const LOGO = `
- ⣞⣞⣞⣞⣞⣞⣞⣞⢞⣖⢔⠌⢆⠇⡇⢇⡓⡆⢠⠰⠠⡄⡄⡠⡀⡄⣄⠢⡂⡆⢆⢆⢒⢔⢕⢜⢔⢕⢕⢗⣗⢗⢗⢕⠕⠕⢔⢲⣲⢮⣺⣺⡺⡸⡪⡚⢎⢎⢺⢪
- ⣺⣺⣺⣺⣺⣺⡺⣮⣳⣳⡳⣕⢥⠱⡘⢔⠱⠑⠡⣫⠈⡞⢼⠔⡑⡕⢕⢝⢜⢜⢜⢔⢅⢣⠪⡸⢸⠸⡸⡱⡑⢍⠢⡑⢌⢪⢨⢪⣺⡻⣗⣷⡿⣽⣺⡦⡵⣔⢷⢽
- ⣺⣺⣺⣺⣺⣺⢽⣺⣺⣺⣺⣺⣪⢣⡑⡠⠐⠈⠨⡪⣂⠉⡪⡪⡆⢕⠅⡕⢕⢕⢕⢕⢕⢕⠱⡨⠢⡑⢌⢂⢊⢆⢕⠸⡨⢢⠱⡑⡜⡎⡞⡮⡯⡯⡯⡻⡽⡽⡽⣽
- ⣺⣺⣞⣞⣞⡾⣽⣺⣺⣺⣺⣺⢮⡳⡽⣔⢔⢈⠐⠈⡎⡀⠅⢣⢫⡢⡑⠌⢜⠸⡸⡸⡱⠡⡱⢐⢅⠪⡐⡅⢕⠌⢆⠣⡊⢆⠣⡑⢌⠪⡊⡎⡎⡗⡝⡜⡜⢜⢹⢸
- ⣞⣞⣾⣺⣳⢯⣗⣿⣺⣳⣗⡯⣗⡯⣟⣮⡳⣧⡳⣢⢢⠐⢰⣀⠂⡑⢕⠨⢂⠕⡑⢌⠢⠡⠠⡁⢂⢑⠨⠠⠡⡈⡂⡢⡂⡆⣆⡪⡨⡊⡂⠎⡢⡑⢕⢱⢘⢌⢎⢎
- ⣳⣳⣳⣻⣺⢽⣺⣗⡿⣞⣷⣻⣗⣯⣗⣗⣟⡮⡯⣮⡳⡳⡔⡨⢆⠂⠱⡌⡐⠌⡂⠅⢅⠣⡑⢌⠢⡢⡑⡱⡡⡣⡣⡣⡳⣝⢮⣟⣿⣿⣿⣦⡈⢎⢎⢪⣪⢮⣳⣳
- ⣺⣺⣳⣻⣞⣿⣺⢷⣟⣯⣿⣽⢿⣾⣳⣟⣮⢯⣟⢮⢯⢯⢮⢪⠪⡊⡈⢆⠢⢑⠨⠨⠢⡑⢌⠢⣃⢪⠨⡢⠱⡘⢜⢜⢜⢜⢗⣝⡯⡿⡿⣟⢦⣕⣞⣾⣺⣻⣞⣾
- ⣺⣞⣷⣳⣟⣾⡽⣯⣟⣯⣿⣾⣿⣻⣽⣾⣽⣻⣺⢽⢽⢯⡯⣧⡣⡓⡔⠠⡑⠄⢕⠡⡑⢌⢢⢑⠔⡐⠅⠪⠨⢊⢢⢑⢕⢕⢕⢕⠱⡑⢕⢕⢗⣿⣺⣯⢿⣳⣟⢾
- ⣞⣾⣺⣞⡷⣗⣿⣽⡾⣿⣟⣿⣟⣿⣿⣷⣿⣽⢾⡽⣽⢽⣽⣳⢽⡜⡌⢦⢘⢌⠢⡑⡌⢆⠕⡐⡁⠂⢅⠪⡘⢌⢪⠨⡢⡃⢎⠢⡑⢅⠕⢌⢎⣞⣞⢾⣝⣞⡮⡯
- ⢷⣳⣗⡯⣟⡷⣿⢾⣻⣟⣿⣻⣿⣿⢿⣿⣾⣟⣿⢯⣯⣟⡾⣽⢯⣯⢯⣇⢇⢆⢣⠱⡘⡐⡁⡂⡐⠅⡅⢕⠸⡨⠢⡑⡰⢡⠡⡊⢌⠢⡑⡑⢜⢜⢮⠳⢓⢣⢫⢮
- ⢽⣺⢾⣽⣳⣟⣟⣿⣯⣿⣿⣿⣿⣿⣿⣿⣻⣿⣻⡿⣞⣷⣻⣽⣻⣞⣿⡾⡵⡱⡡⢑⠈⠄⠂⠄⠂⡑⠨⡂⡣⡊⡪⢂⢎⠢⡃⡪⠢⣑⢱⡨⡮⣺⣖⠈⡜⢜⢼⢽
- ⢽⣺⢽⣞⣾⣳⡯⣷⢿⡷⣿⣿⣻⣿⣟⣿⣽⣿⣻⣟⣯⡿⣞⣷⣻⣞⣷⣻⡳⡑⡅⠅⠠⠁⠌⡠⠁⠄⢅⢐⢐⠌⢔⠡⡢⢱⠨⡊⡎⡎⡮⡪⡯⡳⠡⡪⡪⡪⣯⣿
- ⢽⣺⢽⣺⣳⢯⡿⣽⣟⣿⣻⣿⡿⣿⡿⣿⣿⣻⣯⣿⢷⣻⡽⣞⣷⣳⣳⡳⡱⡐⠄⡁⡂⡁⢂⠂⢅⠕⢅⢊⠢⡑⢡⢑⢌⣂⡣⡑⡅⣇⢇⢧⢕⢜⢜⢜⢮⢯⣿⣺
- ⡻⡮⣟⣾⣺⢽⢯⣗⣿⣺⣽⡷⣿⣿⢿⣿⣯⣿⣯⣿⢿⣽⣻⡽⣞⣞⡞⡎⡎⠄⢂⠐⡀⡂⢅⠪⡐⡅⠇⠅⢂⠪⡐⠅⢕⠰⡑⡝⡜⡜⡎⡎⡎⡎⡎⢎⢎⢗⣗⢷
- ⢯⣻⣺⣺⣺⢽⢯⢷⣻⣞⣷⢿⣻⣾⢿⣯⣿⡷⣿⣻⣯⡷⣯⣟⣷⡳⣝⢜⢜⠀⡂⠌⡐⢌⢢⢱⠨⡪⠨⠨⠀⠅⠊⢌⠢⡑⡑⢌⠎⡪⢊⢎⢊⠆⡊⡢⢂⢣⢣⣳
- ⣳⣳⣳⡳⡽⡽⡯⣟⣾⣺⢽⡯⣷⣟⣿⣳⣿⢽⣯⢿⡷⣟⣷⣻⢾⣝⢮⡪⡪⡀⡂⠌⡌⡪⠢⡃⡇⡕⢕⠅⡅⢅⢑⠄⠅⡊⢌⠢⡑⢌⢢⢱⡰⣕⠒⡀⡊⡆⣗⢷
- ⣺⡺⡮⡯⡯⡯⡯⣗⡷⣯⢯⡯⣗⣯⢷⣻⣞⣯⣟⣯⣟⣯⣿⢽⣻⣺⢵⢝⢮⢢⢂⢑⢌⠪⡪⢸⢨⢪⢪⢪⢪⢪⢢⢪⢪⢢⢱⢸⡸⣜⢮⢳⢱⡁⡜⣌⢆⢗⢕⢽
- ⢞⢮⢯⢯⢯⢯⢯⣗⡯⣯⢯⣟⣷⣻⡯⣟⡷⣟⣷⢯⡿⣾⡽⣯⡿⣾⢽⢽⡱⡱⡱⢐⠔⡑⠜⡌⡎⡎⡮⡪⡣⣏⢮⡣⡳⣕⢕⢧⢯⡪⠣⡣⢃⢇⢣⢣⢳⣕⡯⣯
- ⢝⣝⢽⢝⢽⣝⣗⣗⢯⢯⣟⢾⣺⣳⢯⡯⡿⣽⣞⣯⢿⣳⡿⣽⢯⢿⣽⣳⢝⡎⢌⠢⡑⢌⢪⠸⡸⡸⡸⡜⡮⣪⡳⣝⣕⣗⢽⢽⡕⢌⢪⢰⡱⡴⡵⡽⡽⣮⣻⡺
- ⡳⡵⡹⡽⣕⣗⢷⢽⣝⣗⡯⣟⣞⡾⡽⡽⡯⣗⣗⡿⣽⣗⣿⢽⡯⣿⣞⡾⣝⡎⡢⢑⢌⢢⠱⡑⡕⡕⡕⡝⡜⡮⣺⣪⡺⡮⡯⣗⣿⢵⣳⣳⢯⢯⢯⢯⣻⣺⡪⡊
- ⡯⡪⡯⣺⡺⡪⣯⣳⣳⡳⣯⣳⡳⣯⣻⢽⢯⣗⡷⡯⣗⡷⡯⡿⡽⣗⣯⢯⣗⣇⢊⠔⠔⡅⡣⡱⡸⡸⡸⡸⡸⡪⡺⣜⢮⢯⢯⡯⣿⢽⣺⣺⢽⢽⣝⣗⢗⠕⡁⡂
- ⡯⡺⣪⢺⣪⣻⣺⡺⣮⣻⣺⡺⣽⣺⡺⡽⡽⡮⡯⡯⣗⡯⣿⢽⢯⣗⡯⣟⣞⣎⠐⢌⠪⡂⠑⠌⡆⢇⢇⢇⢇⢏⢮⡪⡫⣯⡳⡯⡯⣟⣞⢮⢯⣳⡳⡕⠅⠅⡀⢂
- ⡮⡪⡪⡳⡵⣕⢗⣝⣞⣞⢮⣻⡺⡮⡯⡯⡯⣯⢯⣟⡾⣽⢽⡽⣽⡺⣽⣳⡳⡕⡈⡢⢑⠌⡄⢔⠸⡘⡜⡜⡜⡜⡜⣎⢯⢮⢯⢯⢯⡳⡽⣝⣗⢕⠇⠁⠀⠂⡂⠂`;
-
- //====================================================================================================
-
- class PORNHUB_RULES {
- constructor() {
- const { pathname } = window.location;
-
- this.IS_MODEL_PAGE = pathname.startsWith('/model/');
- this.IS_VIDEO_PAGE = pathname.startsWith('/view_video.php');
- this.IS_PLAYLIST_PAGE = pathname.startsWith('/playlist/');
-
- this.PAGINATION = document.querySelector('.paginationGated');
- this.PAGINATION_LAST = parseInt(document.querySelector('.page_next')?.previousElementSibling.innerText);
-
- this.CONTAINER = document.querySelector('ul.videos.row-5-thumbs, ul.videos.nf-videos, ul#singleFeedSection, ul#videoSearchResult, ul#singleFeedSection');
-
- this.INTERSECTION_OBSERVABLE = this.CONTAINER && findNextSibling(this.CONTAINER);
- }
-
- THUMB_URL(thumb) {
- return thumb.querySelector('.linkVideoThumb').href;
- }
-
- GET_THUMBS(html) {
- const parent = html.querySelector(this.IS_MODEL_PAGE ? '.videos.row-5-thumbs' : 'ul.videos.nf-videos') || html;
- return parent.querySelectorAll('li.videoBox.videoblock, li.videoblock');
- }
-
- THUMB_IMG_DATA(thumb) {
- const img = thumb.querySelector('.js-videoThumb.thumb.js-videoPreview');
- const imgSrc = img?.getAttribute('data-mediumthumb') || img?.getAttribute('data-path').replace('{index}', '1');
- if (!img?.complete || img.naturalWidth === 0) { return ({});}
- return { img, imgSrc };
- }
-
- THUMB_DATA(thumb) {
- const title = thumb.querySelector('span.title').innerText.toLowerCase();
- const duration = timeToSeconds(thumb.querySelector('.duration').innerText);
- return {
- title,
- duration,
- };
- }
-
- URL_DATA() {
- const { origin, pathname, href } = window.location;
- const url = new URL(window.location.href);
- const offset = parseInt(url.searchParams.get('page')) || 1;
- const iteratable_url = n => {
- url.searchParams.set('page', n);
- return url.href;
- }
- return {
- offset,
- iteratable_url
- }
- }
- }
-
- const RULES = new PORNHUB_RULES();
-
- //====================================================================================================
-
- const { state } = new PersistentState({
- filterDurationFrom: 0,
- filterDurationTo: 600,
- filterDuration: false,
- filterExcludeWords: "",
- filterExclude: false,
- filterIncludeWords: "",
- filterInclude: false,
- infiniteScrollEnabled: true,
- uiEnabled: true,
- });
-
- const stateLocale = reactive({
- pagIndexLast: 1,
- pagIndexCur: 1,
- });
-
- watch([() => state.filterDurationFrom, () => state.filterDurationTo], (a, b) => {
- state.filterDurationFrom = parseIntegerOr(a[0], b[0]);
- state.filterDurationTo = parseIntegerOr(a[1], b[1]);
- if (state.filterDuration) filter_({ filterDuration: true });
- });
-
- watch(() => state.filterDuration, () => filter_({ filterDuration: true }));
-
- watch(() => state.filterExclude, () => filter_({ filterExclude: true }));
-
- watch(() => state.filterExcludeWords, () => {
- if (state.filterExclude) filter_({ filterExclude: true });
- }, { deep: true });
-
- watch(() => state.filterInclude, () => filter_({ filterInclude: true }));
-
- watch(() => state.filterIncludeWords, () => {
- if (state.filterInclude) filter_({ filterInclude: true });
- }, { deep: true });
-
-
- //====================================================================================================
-
- console.log(LOGO);
-
- const SCROLL_RESET_DELAY = 350;
-
- const { filter_, handleLoadedHTML } = new DataManager(RULES, state);
-
- if (window.location.pathname.startsWith('/view_video.php')) {
- const containers = Array.from(document.querySelectorAll('li.pcVideoListItem.js-pop.videoBox'))
- .reduce((acc, v) => acc.includes(v.parentElement) ? acc : [...acc, v.parentElement], []).slice(2,);
- containers.forEach(c => handleLoadedHTML(c, c));
- }
-
- if (RULES.CONTAINER) {
- handleLoadedHTML(RULES.CONTAINER);
- }
-
- if (window.location.pathname.startsWith('/playlist/')) {
- const container = document.querySelector('ul#videoPlaylist');
- handleLoadedHTML(container);
- watchElementChildrenCount(container, () => {
- handleLoadedHTML(container);
- });
- }
-
- if (RULES.PAGINATION) {
- const paginationManager = new PaginationManager(state, stateLocale, RULES, handleLoadedHTML, SCROLL_RESET_DELAY);
- }
-
- const ui = new VueUI(state, stateLocale);