CamWhores PervertMonkey

Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Sort by Duration and Views. Mass friend request button. Download button

이 스크립트를 설치하려면 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         CamWhores PervertMonkey
// @namespace    pervertmonkey
// @version      3.0.24
// @author       violent-orangutan
// @description  Infinite scroll [optional]. Filter by Title, Duration and Private/Public. Sort by Duration and Views. Mass friend request button. Download button
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=camwhores.tv
// @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://*.camwhores.tv/*
// @match        https://*.camwhores.*/*
// @exclude      https://*.camwhores.tv/*mode=async*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/core/pervertmonkey.core.umd.js
// @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 LSKDB = class {
		constructor(prefix = "lsm-", lockKey = "lsmngr-lock") {
			this.prefix = prefix;
			this.lockKey = lockKey;
		}
		prefixedKey(key) {
			return `${this.prefix}${key}`;
		}
		getAllKeys() {
			const res = [];
			for (const key in localStorage) if (key.startsWith(this.prefix)) res.push(key.slice(this.prefix.length));
			return res;
		}
		getKeys(n = 12, remove = true) {
			const res = [];
			for (const key in localStorage) {
				if (res.length >= n) break;
				if (key.startsWith(this.prefix)) res.push(key.slice(this.prefix.length));
			}
			if (remove) res.forEach((k) => this.removeKey(k));
			return res;
		}
		hasKey(key) {
			return localStorage.getItem(this.prefixedKey(key)) !== null;
		}
		removeKey(key) {
			localStorage.removeItem(this.prefixedKey(key));
		}
		setKey(key) {
			localStorage.setItem(this.prefixedKey(key), "");
		}
		isLocked() {
			const lock = parseInt(localStorage.getItem(this.lockKey));
			return !(!lock || Date.now() - lock > 300 * 1e3);
		}
		lock(value) {
			if (value) localStorage.setItem(this.lockKey, JSON.stringify(Date.now()));
			else localStorage.removeItem(this.lockKey);
		}
	};
	var _GM_addStyle = (() => typeof GM_addStyle != "undefined" ? GM_addStyle : void 0)();
	var $ = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)().$;
	_GM_addStyle(`
.item.private .thumb, .item .thumb.private { opacity: 1 !important; }
.haveNoAccess { background: linear-gradient(to bottom, #b50000 0%, #2c2c2c 100%) red !important; }
.haveAccess { background: linear-gradient(to bottom, #4e9299 0%, #2c2c2c 100%) green !important; }
.friend-button { background: radial-gradient(#5ccbf4, #e1ccb1) !important; }
`);
	var IS_MEMBER_PAGE = /^(\/members\/\d+\/|\/my\/)$/.test(location.pathname);
	var IS_MESSAGES = /^\/my\/messages\//.test(location.pathname);
	var IS_COMMUNITY_LIST = /\/members\/$/.test(location.pathname);
	var IS_VIDEO_PAGE = /^(\/videos)?\/\d+\//.test(location.pathname);
	var IS_LOGGED_IN = document.cookie.includes("kt_member");
	var rules = new ___core.Rules({
		containerSelector: "[id*=\"playlist\"]:has(> .item .title),[id*=\"videos\"]:has(> .item .title),form:has(>.item .title)",
		paginationStrategyOptions: {
			paginationSelector: ".pagination:not([id *= member])",
			overwritePaginationLast: IS_MEMBER_PAGE ? () => 1 : (x) => x === 9 ? 9999 : x
		},
		thumbs: { selector: ".list-videos .item, .playlist .item, .list-playlists > div > .item, .item:has(.title)" },
		thumb: {
			strategy: "auto-select",
			selectors: {
				private: {
					type: "boolean",
					selector: "[class*=private]"
				},
				views: {
					selector: ".views",
					type: "float"
				}
			}
		},
		thumbImg: { selector: "data-original" },
		gropeStrategy: "all-in-all",
		schemeOptions: [
			"Title Filter",
			"Duration Filter",
			"Privacy Filter",
			"Sort By",
			"Badge",
			{
				title: "Advanced",
				content: [{
					autoRequestAccess: false,
					label: "auto send friend request on check access"
				}]
			}
		],
		animatePreview
	});
	function animatePreview(container) {
		const tick = new ___utils.Tick(500);
		function killjquery(n = 10) {
			if (n > 0) {
				n--;
				$("img[data-cnt]").off();
				setTimeout(() => killjquery(n), 250);
			}
		}
		killjquery();
		function rotateImg(src, count) {
			return src.replace(/(\d)(?=\.jpg$)/, (_, n) => `${(0, ___utils.circularShift)(parseInt(n), count)}`);
		}
		___utils.OnHover.create(container, ".list-videos .item, .playlist .item, .list-playlists > div > .item, .item:has(.title)", (e) => {
			const img = e.querySelector("img");
			const origin = img.src;
			const count = parseInt(img.getAttribute("data-cnt")) || 5;
			tick.start(() => {
				img.src = rotateImg(img.src, count);
			}, () => {
				img.src = origin;
			});
			return () => tick.stop();
		});
	}
	var createDownloadButton = () => (0, ___utils.downloader)({
		append: ".tabs-menu > ul",
		buttonHtml: "<li><a href=\"#tab_comments\" class=\"toggle-button\" style=\"text-decoration: none;\">download 📼</a></li>",
		doBefore: () => $(".fp-ui").click()
	});
	var DEFAULT_FRIEND_REQUEST_FORMDATA = (0, ___utils.objectToFormData)({
		message: "",
		action: "add_to_friends_complete",
		function: "get_block",
		block_id: "member_profile_view_view_profile",
		format: "json",
		mode: "async"
	});
	var lskdb = new LSKDB();
	async function friendRequest(id) {
		const url = Number.isInteger(id) ? `${location.origin}/members/${id}/` : id;
		await fetch(url, {
			body: DEFAULT_FRIEND_REQUEST_FORMDATA,
			method: "post"
		});
	}
	function getMemberLinks(e) {
		return Array.from(e?.querySelectorAll(".item > a") || [], (l) => l.href).filter((l) => /\/members\/\d+\/$/.test(l));
	}
	async function getMemberFriends(id) {
		const url = new URL(IS_COMMUNITY_LIST ? "/members/" : `/members/${id}/friends/`, location.origin);
		const paginationStrategy = (0, ___core.getPaginationStrategy)({
			doc: await (0, ___utils.fetchHtml)(url.href),
			url,
			overwritePaginationLast: (x) => x === 9 ? 999 : x
		});
		const gen = ___core.InfiniteScroller.generatorForPaginationStrategy(paginationStrategy);
		for (const url in gen) getMemberLinks(await (0, ___utils.fetchHtml)(url)).forEach((a) => {
			const id = a.match(/\d+/)?.[0];
			lskdb.setKey(id);
		});
		await processFriendship();
	}
	var processFriendshipStarted = false;
	async function processFriendship(batchSize = 1, interval = 5e3) {
		if (lskdb.isLocked()) return;
		const friendlist = lskdb.getKeys(batchSize);
		if (friendlist?.length < 1) return;
		if (!processFriendshipStarted) {
			processFriendshipStarted = true;
			console.log("processFriendshipStarted");
		}
		lskdb.lock(true);
		const urls = friendlist.map((id) => `${location.origin}/members/${id}/`);
		for (const url of urls) {
			await (0, ___utils.wait)(interval);
			await friendRequest(url);
		}
		lskdb.lock(false);
		await processFriendship();
	}
	function createPrivateVideoFriendButton() {
		if (!document.querySelector(".no-player")) return;
		const member = document.querySelector(".no-player a")?.href;
		const button = (0, ___utils.parseHtml)("<button class=\"friend-button\"><span>Friend Request</span></button>");
		document.querySelector(".no-player .message")?.append(button);
		button.addEventListener("click", () => friendRequest(member), { once: true });
	}
	function createFriendButton() {
		const button = (0, ___utils.parseHtml)("<a href=\"#friend_everyone\" class=\"button friend-button\"><span>Friend Everyone</span></a>");
		document.querySelector(".main-container-user > .headline, .headline")?.append(button);
		const memberid = location.pathname.match(/\d+/)?.[0];
		button.addEventListener("click", () => {
			button.style.background = "radial-gradient(#ff6114, #5babc4)";
			button.innerText = "processing requests";
			getMemberFriends(memberid).then(() => {
				button.style.background = "radial-gradient(blue, lightgreen)";
				button.innerText = "friend requests sent";
			});
		}, { once: true });
	}
	async function requestAccess() {
		checkPrivateVidsAccess();
		setTimeout(processFriendship, 5e3);
	}
	async function checkPrivateVidsAccess() {
		const checkAccess = async (item) => {
			const videoURL = item.firstElementChild.href;
			const doc = await (0, ___utils.fetchHtml)(videoURL);
			if (!doc.querySelector(".player")) return;
			const haveAccess = !doc.querySelector(".no-player");
			if (!haveAccess && rules.store.state.autoRequestAccess) {
				const uid = doc.querySelector(".message a")?.href.match(/\d+/)?.at(-1);
				lskdb.setKey(uid);
			}
			item.classList.add(haveAccess ? "haveAccess" : "haveNoAccess");
		};
		const thumbs = document.querySelectorAll(".item.private:not(.haveAccess,.haveNoAccess)");
		for (const thumb of thumbs) await checkAccess(thumb);
	}
	function getUserInfo(e) {
		return {
			uploadedCount: (0, ___utils.querySelectorLastNumber)("#list_videos_uploaded_videos strong", e),
			friendsCount: (0, ___utils.querySelectorLastNumber)("#list_members_friends .headline", e)
		};
	}
	async function acceptFriendRequest(id) {
		const url = new URL(`/my/messages/${id}/`, location.origin);
		const memberUrl = new URL(`/members/${id}/`, location.origin);
		await fetch(url, {
			headers: {
				Accept: "*/*",
				"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
			},
			body: `action=confirm_add_to_friends&message_from_user_id=${id}&function=get_block&block_id=list_messages_my_conversation_messages&confirm=Confirm&format=json&mode=async`,
			method: "POST"
		});
		await (0, ___utils.fetchHtml)(memberUrl).then((doc) => console.log("userInfo", memberUrl.href, getUserInfo(doc)));
	}
	async function clearMessages() {
		const pages = ___core.InfiniteScroller.generatorForPaginationStrategy((0, ___core.getPaginationStrategy)({ overwritePaginationLast: (x) => x === 9 ? 999 : x }));
		for await (const p of pages) {
			const doc = await (0, ___utils.fetchHtml)(p.url);
			const messages = Array.from(doc.querySelectorAll("#list_members_my_conversations_items .item > a") || []).map((a) => a.href);
			for (const m of messages) await checkMessageHistory(m);
		}
		async function deleteMessage(url, id) {
			const deleteURL = `${url}?mode=async&format=json&function=get_block&block_id=list_messages_my_conversation_messages&action=delete_conversation&conversation_user_id=${id}`;
			await fetch(deleteURL);
		}
		async function getConversation(url) {
			const doc = await (0, ___utils.fetchHtml)(url);
			const hasFriendRequest = !!doc.querySelector("input[value=confirm_add_to_friends]");
			const originalText = (0, ___utils.querySelectorText)(doc, ".original-text");
			return {
				id: url.match(/\d+/)?.[0],
				hasFriendRequest,
				originalText,
				messages: (0, ___utils.querySelectorText)(doc, ".list-messages")
			};
		}
		async function checkMessageHistory(url) {
			const { originalText, hasFriendRequest, id, messages } = await getConversation(url);
			if (!(originalText || hasFriendRequest)) await deleteMessage(url, id);
			else {
				console.log({
					originalText,
					url,
					messages
				});
				if (hasFriendRequest) await acceptFriendRequest(id);
			}
		}
	}
	var FRIEND_REQUEST_INTERVAL = 5e3;
	if (IS_LOGGED_IN) {
		setTimeout(processFriendship, FRIEND_REQUEST_INTERVAL);
		if (IS_MEMBER_PAGE || IS_COMMUNITY_LIST) createFriendButton();
	}
	if (IS_VIDEO_PAGE) {
		createDownloadButton();
		createPrivateVideoFriendButton();
	}
	if (IS_MESSAGES) {
		const button = (0, ___utils.parseHtml)("<button>clear messages</button>");
		document.querySelector(".headline")?.append(button);
		button.addEventListener("click", clearMessages);
	}
	rules.store.eventSubject.subscribe((event) => {
		if (event.includes("check access")) requestAccess();
	});
})(___core, ___utils);