Pornhub Enhancer

Pornhub封面与切片下载

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Pornhub Enhancer
// @namespace    github/BlackVisor
// @version      v1.0.2
// @description  Pornhub封面与切片下载
// @license      GPL-3.0
// @icon         https://ei.phncdn.com/www-static/favicon.ico
// @match        https://*.pornhub.com/model/*/videos*
// @match        https://*.pornhub.com/pornstar/*/videos*
// @connect      *
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// ==/UserScript==

(function() {
  'use strict';
	var _GM_xmlhttpRequest = typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0;
	var crawlButtonId = "crawl-button";
	var uploadTimePattern = /\/videos\/(\d{6})\/(\d{2})\//;
	var stripInvalidChar = (source) => {
		let text = source;
		text = text.replace(/\xa0/g, " ");
		text = text.replace(/[<>:"/\\|?*]/g, "");
		text = text.replace(/[\x00-\x1F]/g, "");
		text = text.replace(/\s+/g, " ").trim();
		while (text.endsWith(".")) text = text.slice(0, -1).trim();
		return text;
	};
	var addCrawlButton = () => {
		console.log("[Pornhub] add crawl button event triggered");
		if (document.querySelector(crawlButtonId)) {
			console.warn("[Pornhub] crawl button existed");
			return;
		}
		const crawlButton = document.createElement("button");
		crawlButton.textContent = "下载封面与切片";
		crawlButton.id = crawlButtonId;
		crawlButton.style.position = "fixed";
		crawlButton.style.top = "30px";
		crawlButton.style.right = "30px";
		crawlButton.style.zIndex = "9999";
		crawlButton.style.padding = "8px 16px";
		crawlButton.style.backgroundColor = "#4CAF50";
		crawlButton.style.color = "white";
		crawlButton.style.border = "none";
		crawlButton.style.borderRadius = "4px";
		crawlButton.style.cursor = "pointer";
		crawlButton.style.boxShadow = "0 2px 5px rgba(0,0,0,0.2)";
		crawlButton.style.transition = "background-color 0.3s";
		crawlButton.addEventListener("mouseover", () => {
			crawlButton.style.backgroundColor = "#9ef5a0";
		});
		crawlButton.addEventListener("mouseout", () => {
			crawlButton.style.backgroundColor = "#4CAF50";
		});
		crawlButton.addEventListener("click", handleClickCrawlButton);
		document.body.appendChild(crawlButton);
	};
	var handleClickCrawlButton = () => {
		const videoCards = document.querySelectorAll("div.profileVids li.pcVideoListItem");
		const pendingDownloadList = [];
		for (const videoCard of videoCards) {
			const tagA = videoCard.querySelector("div.phimage a");
			let viewkey = "";
			let cover = "";
			let preview = "";
			let uploadTime = "";
			let title = "";
			if (tagA) {
				const href = tagA.getAttribute("href");
				if (href) {
					viewkey = href.split("=").at(-1) || "";
					title = stripInvalidChar(tagA.getAttribute("data-title") || "");
					const tagImg = tagA.querySelector("img");
					if (tagImg) {
						cover = tagImg.getAttribute("src") || "";
						preview = tagImg.getAttribute("data-mediabook") || "";
						if (cover) {
							const matched = cover.match(uploadTimePattern);
							if (matched) {
								uploadTime = `${matched[1]}${matched[2]}`;
								pendingDownloadList.push({
									viewkey,
									cover,
									preview,
									uploadTime,
									title
								});
							}
						}
					}
				}
			}
		}
		pendingDownloadList.forEach((video) => {
			if (!video.viewkey || !video.uploadTime || !video.title) console.error(`[Pornhub] found empty in viewkey=${video.viewkey}, uploadTime=${video.uploadTime}, title=${video.title}`);
			else {
				if (video.cover) download(video.cover, `${video.uploadTime}-----${video.viewkey}-----${video.title}-----cover.jpg`);
				else console.error(`[Pornhub] found empty cover for ${video.viewkey}`);
				if (video.preview) download(video.preview, `${video.uploadTime}-----${video.viewkey}-----${video.title}-----preview.webm`);
				else console.error(`[Pornhub] found empty preview for ${video.viewkey}`);
			}
		});
		console.log(`[Pornhub] download ${pendingDownloadList.length} / ${Array.from(videoCards).length} videos`);
	};
	var download = (src, name) => {
		if (!src || !name) {
			console.error(`[Pornhub] check empty for src=${src}, name=${name}`);
			return;
		}
		try {
			_GM_xmlhttpRequest({
				method: "GET",
				url: src,
				headers: {
					"Referer": window.location.href,
					"User-Agent": navigator.userAgent
				},
				responseType: "blob",
				onload: function(response) {
					if (response.status === 200) {
						const blob = response.response;
						const url = URL.createObjectURL(blob);
						const link = document.createElement("a");
						link.href = url;
						link.download = name;
						document.body.appendChild(link);
						link.click();
						setTimeout(() => {
							URL.revokeObjectURL(url);
							link.remove();
						}, 100);
					} else console.error(`[Pornhub] download ${src} failed with status: ${response.status}`);
				},
				onerror: function(err) {
					console.error(`[Pornhub] GM_xmlhttpRequest ${src} error:`, err);
				}
			});
		} catch (err) {
			console.error(`[Pornhub] download ${src} failed by gm: `, err);
		}
	};
	var init = () => {
		addCrawlButton();
	};
	init();
})();