// ==UserScript==
// @name xhamster-优化
// @version 1.0.5
// @namespace https://sleazyfork.org/zh-CN/users/1461640-%E6%98%9F%E5%AE%BF%E8%80%81%E9%AD%94
// @author 星宿老魔
// @description 自动宽屏,自动最高画质,自动播放,隐藏已观看,收藏夹内视频快速移除
// @match https://zh.xhamster.com/*
// @match https://xhamster.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=xhamster.com
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_deleteValue
// @run-at document-start
// ==/UserScript==
(function(){"use strict";const CONFIG={domains:["zh.xhamster.com","xhamster.com"],pageTypes:{search:"/search/",category:"/categories/",tags:"/tags/",
video:"/videos/",excludedPrefixes:["/photos/","/users/","/my/","/messages","/live","/creators","/p/","/info"]},selectors:{playButton:".play-inner",
settingsButton:".settings",qualityOptions:".xp-settings-inner-list span[data-value]",menuContainer:".items-3b1bc",menuContainerAlt:".container-3b1bc",
moreButton:".dropdown-3b1bc",menuItem:".container-64b3c",menuLink:".root-48288.invert-48288.link-64b3c",
menuText:".h4-8643e.invert-8643e.linkText-64b3c",videoItems:".thumb-list__item.video-thumb",watchedIndicator:".thumb-image-container__watched",
hiddenByScript:".hidden-by-script",adContainers:".thumbContainer-53a78, .thumb-53a78",redirectLinks:'a[href*="/fh/out?url="]',
badges:".badge-cb84e, .badge",favoriteVideos:".video-thumb[data-video-id]",removeButton:".remove-btn",imageContainer:".thumb-image-container",
commentTabSet:".tabSet-792ed.tabSet-0c900",commentNoTabSet:".noTabs-0c900",commentBox:"#commentBox, .commentsWrap-99011",commentTab:".tab-98bb0",
commentCount:".comments-heading .value-42516, .triggerBlock-42516 .value-42516",commentItems:".commentItem-5228f",commentForm:".form-42516",
commentInput:".root-77ac5.desktop-77ac5",commentPagination:".pagerSection-99011, .pager-section",relatedTabTitle:".relatedTabTitleDesktop-0c900",
showMoreButton:'.container-90841.desktop-90841[data-role="show-more-container"]',
topMenuAds:['[data-nav-item-id="cams"]','[data-nav-item-id="dating"]','[data-nav-item-id="priority-vpn"]','[data-nav-item-id="ai-friend"]','[data-nav-item-id="flirtify"]','[data-item="premium"]']
},adKeywords:["已支付","付费","PAID","Premium","24/7不间断直播","约会","Free VPN","AI Girlfriend","性爱聊天","独家视频"],
adLinkPatterns:["/cam/out","/dating","/fh/out","/ff/out","priorityvpn.app","lovescape.com"],performance:{debounceDelay:300,retryLimit:20,
retryInterval:500,cleanupInterval:5e3}};class VideoEnhancer{static init(){let e=!1,t=!1;const n=()=>{if(e&&t){
const e=document.querySelector(".play-inner");e&&e.click()}},i=()=>{if(!e){const t=document.querySelector(".settings");t?.offsetParent&&(t.click(),
setTimeout(()=>{const t=document.querySelector(".quality.chooser-control.xp-settings-inner-list-inner");if(t)for(let i=0;i<t.childNodes.length;i++){
const o=t.childNodes[i],a=o.getAttribute?.("data-value");if(a&&"auto"!==a){o.click(),e=!0,n();break}}},100))}if(!t){
const e=document.querySelector(".large-mode");if(e?.offsetParent){const i=e.getAttribute("data-xp-tooltip");"Exit large mode"===i?(t=!0,
n()):"Large mode"===i&&(e.click(),t=!0,n())}}e&&t&&clearInterval(a)};i();let o=0;const a=setInterval(()=>{if(++o>50){clearInterval(a)
;const e=document.querySelector(".play-inner");e?.click()}else i()},100)}}class AdBlocker{static init(){void 0,this.addAdBlockStyles(),
this.removeTopMenuAds(),this.removeVideoAds()}static addAdBlockStyles(){
if("undefined"!=typeof GM_addStyle)GM_addStyle("\n .menu-ad-hidden { display: none !important; }\n ");else{void 0
;const e=document.createElement("style");e.textContent="\n .menu-ad-hidden { display: none !important; }\n ",document.head.appendChild(e)}
}static removeTopMenuAds(){try{let e=0
;['[data-nav-item-id="cams"]','[data-nav-item-id="dating"]','[data-nav-item-id="priority-vpn"]','[data-nav-item-id="ai-friend"]','[data-nav-item-id="flirtify"]','[data-item="premium"]'].forEach(t=>{
document.querySelectorAll(t).forEach(t=>{t&&t.parentNode&&(t.classList.add("menu-ad-hidden"),e++)})})
;const t=["24/7不间断直播","约会","Free VPN","AI Girlfriend","性爱聊天","独家视频"];return document.querySelectorAll(".container-64b3c").forEach(n=>{
const i=n.textContent?.trim();i&&t.some(e=>i.includes(e))&&(n.classList.contains("menu-ad-hidden")||(n.classList.add("menu-ad-hidden"),e++))}),e>0,e
}catch(e){return void 0,0}}static removeVideoAds(){let e=0;try{return document.querySelectorAll(".thumbContainer-53a78, .thumb-53a78").forEach(t=>{
t&&t.parentNode&&(t.remove(),e++)}),document.querySelectorAll('a[href*="/fh/out?url="]').forEach(t=>{
const n=t.closest(".thumb-list__item, .thumbContainer-53a78, .video-thumb");n&&n.parentNode&&(n.remove(),e++)}),
document.querySelectorAll(".thumb-list__item .title, .thumb-list__item .caption, .thumbContainer-53a78 .title, .video-thumb .title").forEach(t=>{
if(t.textContent&&(t.textContent.includes("已支付")||t.textContent.includes("付费")||t.textContent.includes("PAID")||t.textContent.includes("Premium"))){
const n=t.closest(".thumb-list__item, .thumbContainer-53a78, .video-thumb");n&&n.parentNode&&(n.remove(),e++)}}),e>0,e}catch(t){return void 0,0}}}
class StorageManager{static getGlobalHideWatchedSetting(){return GM_getValue("hideWatched_global",!1)}static setGlobalHideWatchedSetting(e){
GM_setValue("hideWatched_global",e)}static cleanupOldSettings(){
["hideWatched_home","hideWatched_search","hideWatched_category","hideWatched_tags","hideWatched_video"].forEach(e=>{
null!==GM_getValue(e,null)&&GM_deleteValue(e)})}}class PageDetector{static getCurrentPageType(){const e=window.location.pathname
;return e.startsWith(CONFIG.pageTypes.search)?"search":e.startsWith(CONFIG.pageTypes.category)?"category":e.startsWith(CONFIG.pageTypes.tags)?"tags":e.startsWith(CONFIG.pageTypes.video)?"video":CONFIG.pageTypes.excludedPrefixes.some(t=>e.startsWith(t))?null:"home"
}static shouldApplyHideWatchedFeature(){const e=window.location.pathname
;return!![CONFIG.pageTypes.search,CONFIG.pageTypes.category,CONFIG.pageTypes.tags,CONFIG.pageTypes.video].some(t=>e.startsWith(t))||!CONFIG.pageTypes.excludedPrefixes.some(t=>e.startsWith(t))
}static isInFavoritesPage(){const e=window.location.href;return e.includes("watch-later")||e.includes("/my/favorites/videos")}}
class WatchedVideoFilter{static init(){void 0,this.addFilterStyles(),this.applyFilter()}static addFilterStyles(){
if("undefined"!=typeof GM_addStyle)GM_addStyle("\n .hidden-by-script { display: none !important; }\n ");else{void 0
;const e=document.createElement("style");e.textContent="\n .hidden-by-script { display: none !important; }\n ",
document.head.appendChild(e)}}static applyFilter(){if(PageDetector.shouldApplyHideWatchedFeature()&&StorageManager.getGlobalHideWatchedSetting())try{
const e=this.safeQuerySelectorAll(".thumb-list__item.video-thumb");let t=0;e.forEach(e=>{
e&&this.safeQuerySelector(".thumb-image-container__watched",e)&&(e.classList.add("hidden-by-script"),t++)}),t>0}catch(e){void 0}}
static showWatchedVideos(){try{const e=this.safeQuerySelectorAll(".hidden-by-script");let t=0;e.forEach(e=>{
e&&e.classList&&(e.classList.remove("hidden-by-script"),t++)}),t>0}catch(e){void 0}}static toggleWatchedVideos(e){
StorageManager.setGlobalHideWatchedSetting(!e),e?this.showWatchedVideos():this.applyFilter()}static getCurrentHideWatchedSetting(){
return StorageManager.getGlobalHideWatchedSetting()}static safeQuerySelector(e,t=document){try{return t.querySelector(e)}catch(n){return void 0,null}}
static safeQuerySelectorAll(e,t=document){try{return t.querySelectorAll(e)}catch(n){return void 0,document.querySelectorAll("never-match")}}}
class FavoritesManager{static init(){void 0,this.initRemoveFeature()}static addRemoveButtons(){
if(window.location.href.includes("watch-later")||window.location.href.includes("/my/favorites/videos"))return document.querySelector(".xh-icon")?(document.querySelectorAll(".video-thumb[data-video-id]").forEach(e=>{
if(e.querySelector(".remove-btn"))return;const t=e.getAttribute("data-video-id");let n=null
;if(window.location.href.includes("watch-later"))n=window.location.pathname.match(/\/videos\/([^-]+)-watch-later/)?.[1]||null;else if(window.location.href.includes("/my/favorites/videos")){
const t=window.location.pathname.match(/\/videos\/([^\/]+)/);if(t)n=t[1];else{
const t=document.querySelector(".favorites-collection.active, .active[data-id]");if(t)n=t.getAttribute("data-id");else{
const t=e.closest(".favorites-collection, [data-collection-id]");t&&(n=t.getAttribute("data-collection-id")||t.getAttribute("data-id"))}}}
if(!t||!n)return;const i=document.createElement("div");i.className="remove-btn",
i.style.cssText="\n position: absolute;\n top: 5px;\n right: 5px;\n width: 24px;\n height: 24px;\n background: rgba(0, 0, 0, 0.7);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n z-index: 10;\n transition: background 0.2s;\n opacity: 1;\n visibility: visible;\n "
;const o=document.createElement("i");o.className="xh-icon bucket cobalt",o.style.cssText="color: white; font-size: 12px; display: inline-block;",
i.appendChild(o),i.addEventListener("mouseenter",()=>{i.style.background="rgba(255, 0, 0, 0.8)"}),i.addEventListener("mouseleave",()=>{
i.style.background="rgba(0, 0, 0, 0.7)"}),i.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation(),this.removeFromFavorites(t,n)})
;const a=e.querySelector(".thumb-image-container");a&&(a.style.position="relative",a.appendChild(i))}),
void 0):(setTimeout(()=>this.addRemoveButtons(),100),void 0)}static async removeFromFavorites(e,t){try{if((await fetch("/x-api",{method:"POST",
headers:{"Content-Type":"text/plain","X-Requested-With":"XMLHttpRequest"},body:JSON.stringify([{name:"favoriteVideosModelSync",requestData:{model:{
id:null,$id:this.generateUUID(),modelName:"favoriteVideosModel",itemState:"changed",collections:[],contentType:"videos",contentEntity:{id:e}}}}])
})).ok){const t=document.querySelector(`[data-video-id="${e}"]`);t&&t.remove()}else void 0}catch(n){void 0}}static generateUUID(){
return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"==e?t:3&t|8).toString(16)})}
static initRemoveFeature(){
(window.location.href.includes("watch-later")||window.location.href.includes("/my/favorites/videos"))&&("complete"===document.readyState?setTimeout(()=>this.addRemoveButtons(),500):window.addEventListener("load",()=>{
setTimeout(()=>this.addRemoveButtons(),500)}))}}function debounce(e,t){let n;return(...i)=>{clearTimeout(n),n=setTimeout(()=>e(...i),t)}}
const e=class{static init(){void 0,StorageManager.cleanupOldSettings(),this.addPreloadStyles(),this.createToggleUI(),this.initUnifiedObserver()}
static addPreloadStyles(){const e=document.createElement("style");e.id="xh-preload-styles",
e.textContent='\n /* 防止广告闪烁的预加载样式 */\n .menu-ad-hidden { display: none !important; }\n .hidden-by-script { display: none !important; }\n \n /* 预隐藏已知广告选择器 */\n [data-nav-item-id="cams"],\n [data-nav-item-id="dating"],\n [data-nav-item-id="priority-vpn"],\n [data-nav-item-id="ai-friend"],\n [data-nav-item-id="flirtify"],\n [data-item="premium"] {\n display: none !important;\n }\n \n /* 通过选择器隐藏广告(比:has()更兼容) */\n ',
document.head.insertBefore(e,document.head.firstChild)}static createToggleUI(){if(!PageDetector.shouldApplyHideWatchedFeature())return;const e=(()=>{
let e=document.querySelector(".items-3b1bc");if(e||(e=document.querySelector(".container-3b1bc")),
e||(e=document.querySelector("nav .container-3b1bc, header .container-3b1bc")),e){const t=e.querySelector(".dropdown-3b1bc");return t?{container:e,
insertAfter:t}:{container:e,insertBefore:null}}return null})();if(e){const n=document.createElement("div");n.id="xh-toggle-compact-btn",
n.className="container-64b3c",
n.style.cssText="\n display: flex;\n align-items: center;\n cursor: pointer;\n transition: all 0.3s ease;\n margin: 0;\n padding: 0;\n "
;const i=document.createElement("a");i.className="root-48288 invert-48288 link-64b3c",i.href="#",
i.style.cssText="\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 6px 12px;\n height: 100%;\n text-decoration: none;\n color: inherit;\n "
;const o=document.createElement("div");o.className="h4-8643e invert-8643e linkText-64b3c",o.textContent="隐藏已观看",
o.style.cssText="\n white-space: nowrap;\n transition: all 0.3s ease;\n ",i.appendChild(o),n.appendChild(i);const a=()=>{
WatchedVideoFilter.getCurrentHideWatchedSetting()?(o.textContent="隐藏已观看",o.style.color="#4CAF50"):(o.textContent="显示已观看",o.style.color="")}
;n.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const t=!WatchedVideoFilter.getCurrentHideWatchedSetting()
;WatchedVideoFilter.toggleWatchedVideos(!t),a()});try{if(!document.querySelector("#xh-toggle-compact-btn"))if(e.insertAfter){
const t=e.insertAfter.nextSibling;t?e.container.insertBefore(n,t):e.container.appendChild(n)}else e.container.appendChild(n)}catch(t){void 0}a()
}else setTimeout(()=>this.createToggleUI(),500)}static initUnifiedObserver(){this.debouncedContentHandler=debounce(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),FavoritesManager.addRemoveButtons(),AdBlocker.removeTopMenuAds()},300),setTimeout(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),AdBlocker.removeTopMenuAds()},100),this.intervalId=window.setInterval(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),AdBlocker.removeTopMenuAds()},5e3),this.mutationObserver=new MutationObserver(e=>{let t=!1;e.forEach(e=>{
"childList"===e.type&&e.addedNodes.length>0&&e.addedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){
const n=e,i=n.classList&&(n.classList.contains("thumb-list__item")||n.classList.contains("thumbContainer-53a78")||n.classList.contains("video-thumb")||n.classList.contains("thumb-53a78")||n.classList.contains("content")||n.classList.contains("main")||n.classList.contains("tabSet-792ed")||n.classList.contains("commentsWrap-99011")),o=n.querySelector&&(n.querySelector(".thumb-list__item")||n.querySelector(".video-thumb")||n.querySelector('a[href*="/fh/out?url="]')||n.querySelector(".badge-cb84e")||n.querySelector(".tabSet-792ed")||n.querySelector(".commentsWrap-99011"))
;(i||o)&&(t=!0)}})}),t&&this.debouncedContentHandler()})
;const e=[document.querySelector(".thumb-list"),document.querySelector(".videos-list"),document.querySelector(".content-container"),document.querySelector("main"),document.querySelector("#content")].filter(Boolean)
;e.length>0?e.forEach(e=>{e&&e.nodeType===Node.ELEMENT_NODE&&this.mutationObserver.observe(e,{childList:!0,subtree:!0})
}):document.body&&document.body.nodeType===Node.ELEMENT_NODE?this.mutationObserver.observe(document.body,{childList:!0,subtree:!0}):void 0}
static triggerUpdate(){this.debouncedContentHandler&&this.debouncedContentHandler()}static cleanup(){this.intervalId&&(clearInterval(this.intervalId),
this.intervalId=null),this.mutationObserver&&(this.mutationObserver.disconnect(),this.mutationObserver=null)}};e.mutationObserver=null,
e.intervalId=null,e.debouncedContentHandler=null;let t=e;function init(){try{VideoEnhancer.init(),AdBlocker.init(),WatchedVideoFilter.init(),
FavoritesManager.init(),t.init()}catch(e){void 0}}function n(){setTimeout(()=>{t.triggerUpdate()},300)}function i(){t.cleanup()}init(),
"complete"===document.readyState?n():window.addEventListener("load",n),window.addEventListener("beforeunload",i),window.XhamsterOptimizer={
config:CONFIG,modules:{VideoEnhancer:VideoEnhancer,AdBlocker:AdBlocker,WatchedVideoFilter:WatchedVideoFilter,FavoritesManager:FavoritesManager,
AppController:t},utils:{triggerUpdate:()=>t.triggerUpdate(),cleanup:()=>t.cleanup()}}})();