Xhamster PervertMonkey

Infinite scroll [optional], Filter by Title, Duration and Watched/Unwatched. Sort by Duration and Views

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Xhamster PervertMonkey
// @namespace    pervertmonkey
// @version      5.0.23
// @author       violent-orangutan
// @description  Infinite scroll [optional], Filter by Title, Duration and Watched/Unwatched. Sort by Duration and Views
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xhamster.com
// @homepage     https://github.com/smartacephale/sleazy-fork#readme
// @homepageURL  https://sleazyfork.org/en/users/1253342-smartacephale
// @source       https://github.com/smartacephale/sleazy-fork
// @supportURL   https://github.com/smartacephale/sleazy-fork/issues
// @match        https://*.xhamster.com/*
// @match        https://*.xhamster.*/*
// @exclude      https://*.xhamster.com/embed*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/core/pervertmonkey.core.umd.js
// @grant        GM_addElement
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-idle
// ==/UserScript==

var ___core = window.pervertmonkey.core || pervertmonkey.core;
var ___utils = ___core;


(function(___core, ___utils) {
	"use strict";
	var _GM_addElement = (() => typeof GM_addElement != "undefined" ? GM_addElement : void 0)();
	var _unsafeWindow = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
	var IS_VIDEO_PAGE = /^\/videos|moments\//.test(location.pathname);
	var IS_PLAYLIST = /^\/my\/favorites\/videos\/\w+/.test(location.pathname);
	function createThumb(data) {
		return (0, ___utils.instantiateTemplate)(".video-thumb", {
			href: data.pageURL,
			"data-previewvideo": data.trailerURL,
			"data-previewvideo-fallback": data.trailerFallbackUrl,
			"data-sprite": data.spriteURL,
			title: data.title,
			"data-video-id": data.id,
			srcset: data.thumbURL,
			src: data.imageURL
		}, {
			".video-thumb-views": data.views,
			"[title]": data.title,
			"[data-role=\"video-duration\"] div": data.duration
		});
	}
	var getPaginationData = !IS_PLAYLIST ? void 0 : async (url) => {
		return (0, ___utils.parseHtml)(`<div>${(await (0, ___utils.fetchJson)(url)).list.map((e) => createThumb(e)).join("\n")}</div>`);
	};
	function createPlaylistPaginationStrategy() {
		const collectionId = location.pathname.split("/my/favorites/videos/")[1].split("-")[0];
		const data = _unsafeWindow.initials;
		const paginationLast = data.favoritesVideoPaging.maxPages;
		const paginationOffset = data.favoritesVideoPaging.active;
		return {
			paginationSelector: "nav[class *= \"pagination\"]",
			getPaginationLast: () => paginationLast,
			getPaginationOffset: () => paginationOffset,
			getPaginationUrlGenerator: () => (offset) => {
				return `https://xhamster.com/api/front/favorite/get-playlist?id=${collectionId}&perPage=60&page=${offset}`;
			}
		};
	}
	var rules = new ___core.Rules({
		paginationStrategyOptions: IS_PLAYLIST ? createPlaylistPaginationStrategy() : { paginationSelector: ".prev-next-list, .test-pager" },
		getPaginationData,
		containerSelectorLast: ".thumb-list",
		thumbs: {
			selector: ".video-thumb",
			transform: (thumb) => {
				if (thumb.querySelector("[class*=placeholder]")) console.log("has placeholder", thumb);
				return thumb;
			}
		},
		thumb: { selectors: {
			title: ".video-thumb-info__name,.video-thumb-info>a",
			duration: ".thumb-image-container__duration",
			watched: {
				selector: "[data-role=\"video-watched",
				type: "boolean"
			},
			views: {
				selector: ".video-thumb-views",
				type: "float"
			}
		} },
		thumbImg: { strategy: "auto" },
		gropeStrategy: "all-in-all",
		customDataFilterFns: [{ filterWatched: (e, state) => !!(state.filterWatched && e.watched) }, { filterUnwatched: (e, state) => !!(state.filterUnwatched && !e.watched) }],
		schemeOptions: [
			"Title Filter",
			{
				title: "Filter Watched",
				content: [{
					filterWatched: false,
					label: "watched"
				}, {
					filterUnwatched: false,
					label: "unwatched"
				}]
			},
			"Sort By",
			"Duration Filter",
			"Badge",
			"Advanced"
		],
		animatePreview
	});
	function animatePreview() {
		function createPreviewVideoElement(src, mount) {
			const video = _GM_addElement("video", {
				playsInline: true,
				autoplay: true,
				loop: true,
				class: "thumb-image-container__video",
				src
			});
			video.addEventListener("loadeddata", () => {
				mount.before(video);
			}, false);
			return () => (0, ___utils.exterminateVideo)(video);
		}
		___utils.OnHover.create(document.body, ".video-thumb", (e) => {
			const container = e.querySelector(".thumb-image-container__image");
			const videoSrc = e.querySelector("[data-previewvideo]")?.getAttribute("data-previewvideo");
			return createPreviewVideoElement(videoSrc, container);
		});
	}
	function expandMoreVideoPage() {
		(0, ___utils.watchElementChildrenCount)(rules.container, () => setTimeout(rules.gropeInit, 1800));
		(0, ___utils.waitForElementToAppear)(document.body, "button[data-role=\"show-more-next\"]", (e) => {
			new ___utils.Observer((target) => {
				target.click();
			}).observe(e);
		});
	}
	if (IS_VIDEO_PAGE) expandMoreVideoPage();
})(___core, ___utils);