// ==UserScript==
// @name xhamster-优化
// @version 1.0.7
// @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 Storage{static get(e,t=null){try{const n=GM_getValue(e);if(null==n)return t;try{return JSON.parse(n)}catch{return n}}catch(n){return void 0,t}}
static set(e,t){try{const n=JSON.stringify(t);return GM_setValue(e,n),!0}catch(n){return void 0,!1}}static delete(e){try{return GM_deleteValue(e),!0
}catch(t){return void 0,!1}}static listKeys(){try{return GM_listValues()}catch(e){return void 0,[]}}static migrateFromLocalStorage(e,t=!0){try{
const n=localStorage.getItem(e);if(null!==n){try{const t=JSON.parse(n);this.set(e,t)}catch{GM_setValue(e,n)}return t&&localStorage.removeItem(e),!0}
return!1}catch(n){return void 0,!1}}}const e={GLOBAL_HIDE_WATCHED:"xhamster_global_hide_watched",OLD_KEYS:["hideWatchedVideos"]};class StorageManager{
static cleanupOldSettings(){e.OLD_KEYS.forEach(e=>{Storage.delete(e)})}static getGlobalHideWatchedSetting(){
return Storage.get(e.GLOBAL_HIDE_WATCHED,!1)??!1}static setGlobalHideWatchedSetting(t){Storage.set(e.GLOBAL_HIDE_WATCHED,t)}}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 t=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
;if(document.querySelector("#xh-toggle-compact-btn"))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");if(t)return{container:e,insertAfter:t};const n=e.querySelector(".container-64b3c:last-child");return n?{
container:e,insertAfter:n}:{container:e,insertAfter:null}}return null})();if(e){const i=document.createElement("div");i.id="xh-toggle-compact-btn",
i.className="container-64b3c",
i.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 position: relative;\n z-index: 1000;\n "
;const o=document.createElement("a");o.className="root-48288 invert-48288 link-64b3c",o.href="#",
o.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 position: relative;\n z-index: 1001;\n "
;const a=document.createElement("div");a.className="h4-8643e invert-8643e linkText-64b3c",a.textContent="已观看",
a.style.cssText="\n white-space: nowrap;\n transition: all 0.3s ease;\n position: relative;\n z-index: 1002;\n ",
o.appendChild(a),i.appendChild(o);const r=()=>{WatchedVideoFilter.getCurrentHideWatchedSetting()?(a.textContent="已观看",
a.style.textDecoration="line-through",a.style.color="#999"):(a.textContent="已观看",a.style.textDecoration="none",a.style.color="")}
;i.addEventListener("click",e=>{e.preventDefault(),e.stopPropagation();const t=!WatchedVideoFilter.getCurrentHideWatchedSetting()
;WatchedVideoFilter.toggleWatchedVideos(!t),r()});try{if(e.insertAfter){const t=e.insertAfter.nextSibling
;t?e.container.insertBefore(i,t):e.container.appendChild(i)}else e.container.appendChild(i);void 0}catch(t){void 0;try{document.body.appendChild(i)
}catch(n){void 0}}r(),this.monitorButtonStability(i)}else void 0,setTimeout(()=>this.createToggleUI(),1e3)}static monitorButtonStability(e){let t=0
;const n=10,i=()=>{if(t++,!document.contains(e))return void 0,e.remove(),setTimeout(()=>this.createToggleUI(),500),void 0
;const o=e.getBoundingClientRect();0!==o.width&&0!==o.height||(void 0,e.style.display="flex",e.style.visibility="visible",e.style.opacity="1"),
t<n&&setTimeout(i,2e3)};setTimeout(i,1e3)}static initUnifiedObserver(){this.debouncedContentHandler=debounce(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),FavoritesManager.addRemoveButtons(),AdBlocker.removeTopMenuAds()},150),setTimeout(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),AdBlocker.removeTopMenuAds()},100),this.intervalId=window.setInterval(()=>{AdBlocker.removeVideoAds(),
WatchedVideoFilter.applyFilter(),AdBlocker.removeTopMenuAds()},3e3),this.mutationObserver=new MutationObserver(e=>{let t=!1;e.forEach(e=>{
if("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("thumb-image-container__watched")||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(".thumb-image-container__watched")||n.querySelector('a[href*="/fh/out?url="]')||n.querySelector(".badge-cb84e")||n.querySelector(".tabSet-792ed")||n.querySelector(".commentsWrap-99011"))
;(i||o)&&(t=!0)}}),"attributes"===e.type&&e.target.nodeType===Node.ELEMENT_NODE){const n=e.target
;(n.classList.contains("thumb-image-container__watched")||n.closest(".thumb-list__item.video-thumb"))&&(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),t={
childList:!0,subtree:!0,attributes:!0,attributeFilter:["class"]};e.length>0?e.forEach(e=>{
e&&e.nodeType===Node.ELEMENT_NODE&&this.mutationObserver.observe(e,t)
}):document.body&&document.body.nodeType===Node.ELEMENT_NODE?this.mutationObserver.observe(document.body,t):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)}};t.mutationObserver=null,t.intervalId=null,
t.debouncedContentHandler=null;let n=t;function init(){try{VideoEnhancer.init(),AdBlocker.init(),WatchedVideoFilter.init(),FavoritesManager.init(),
n.init()}catch(e){void 0}}function i(){setTimeout(()=>{n.triggerUpdate()},300)}function o(){n.cleanup()}init(),
"complete"===document.readyState?i():window.addEventListener("load",i),window.addEventListener("beforeunload",o),window.XhamsterOptimizer={
config:CONFIG,modules:{VideoEnhancer:VideoEnhancer,AdBlocker:AdBlocker,WatchedVideoFilter:WatchedVideoFilter,FavoritesManager:FavoritesManager,
AppController:n},utils:{triggerUpdate:()=>n.triggerUpdate(),cleanup:()=>n.cleanup()}}})();