MissAV去广告、影院模式

missav 广告拦截与界面优化

目前为 2025-01-18 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @run-at document-start
  3. // @name MissAV去广告、影院模式
  4. // @description missav 广告拦截与界面优化
  5. // @icon https://static.missav.com/img/favicon.png
  6. // @namespace loadingi.local
  7. // @version 3.1.1
  8. // @author ch
  9. // @match https://missav.ai/*
  10. // @match https://missav.ws/*
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant unsafeWindow
  14. // @grant GM_xmlhttpRequest
  15. // @compatible chrome
  16. // @compatible firefox
  17. // @compatible edge
  18. // @license GPL-3.0-only
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // 统一管理所有选择器
  25. const SELECTORS = {
  26. // 样式相关选择器
  27. STYLES: {
  28. PROGRESS_BUTTONS: '.isolate.inline-flex.rounded-md.shadow-sm',
  29. PROGRESS_CONTROL: '.sm\\:hidden',
  30. LOOP_BUTTON: '.sm\\:ml-6 button',
  31. // INFO_TEXT: '.mb-1.text-secondary.break-all.line-clamp-2',
  32. // ASPECT_RATIO: '.aspect-w-16.aspect-h-9',
  33. PLAYER_CONTAINER: 'div.relative.-mx-4.sm\\:m-0.-mt-6',
  34. // VIDEO_WRAPPER: '.aspect-w-16.aspect-h-9.relative'
  35. },
  36. // 广告相关选择器
  37. ADS: {
  38. SCRIPTS: [
  39. "script[src*='app.1aad5686.js']",
  40. "script[src*='inpage.push.js']",
  41. "script[src*='hartattenuate.com']",
  42. "script[src*='ads']",
  43. "script[src*='pop']",
  44. "script[src*='banner']"
  45. ],
  46. ELEMENTS: [
  47. 'div.sm\\:container.mx-auto.mb-5.px-4',
  48. 'ul.mb-4.list-none.text-nord14.grid.grid-cols-2.gap-2',
  49. // class="relative ml-4" 无聊的东西 myavlive 之类的
  50. 'div.relative.ml-4',
  51. 'div.root--ujvuu', // 隐藏底部右下角浮窗广告
  52. 'div.under_player',
  53. 'div.space-y-5.mb-5',
  54. 'div[class^="rootContent--"]',
  55. 'div[class^="fixed right-2 bottom-2"]',
  56. 'div[class^="space-y-6 mb-6"]',
  57. 'div.space-y-2.mb-4.ml-4.list-disc.text-nord14',
  58. 'div[id*="ads"]',
  59. 'div[id*="banner"]',
  60. 'div[class*="ads"]',
  61. 'div[class*="banner"]',
  62. '.ad-container',
  63. '#ad-container'
  64. ],
  65. SCRIPT_PATTERNS: [
  66. 'htmlAds',
  67. 'popAds',
  68. 'bannerAds',
  69. 'adsConfig'
  70. ]
  71. },
  72. THEATER: {
  73. PLAYER_CONTAINER: 'body > div:nth-child(3) > div.sm\\:container.mx-auto.px-4.content-without-search.pb-12 > div > div.flex-1.order-first > div:nth-child(2) > div.relative.-mx-4.sm\\:m-0.-mt-6',
  74. PLAYER_WRAPPER: '.aspect-w-16.aspect-h-9',
  75. VIDEO_ELEMENT: 'video#player',
  76. PROGRESS: 'div.sm\\:hidden.flex.justify-between.-mx-4.px-4.pt-3.pb-1.bg-black',
  77. AB_LOOP: 'div.flex.items-center.flex-nowrap.leading-5',
  78. AB_LOOP_CONTROLS: '.theater-controls-abloop'
  79. }
  80. };
  81.  
  82. // 添加统一的按钮样式常量
  83. const BUTTON_STYLES = {
  84. BASE: {
  85. backgroundColor: '#222',
  86. borderRadius: '15px',
  87. borderColor: 'black',
  88. borderWidth: '1px',
  89. color: 'burlywood',
  90. cursor: 'pointer',
  91. transition: 'all 0.3s ease',
  92. outline: 'none',
  93. minWidth: '80px',
  94. padding: '2px 4px',
  95. marginBottom: '10px',
  96.  
  97. },
  98. HOVER: {
  99. backgroundColor: '#333'
  100. }
  101. };
  102.  
  103. // 创建统一的按钮工厂函数
  104. function createStyledButton(text, onClick) {
  105. const button = document.createElement('button');
  106. Object.assign(button.style, BUTTON_STYLES.BASE);
  107. button.innerText = text;
  108. button.addEventListener('mouseover', () => Object.assign(button.style, BUTTON_STYLES.HOVER));
  109. button.addEventListener('mouseout', () => Object.assign(button.style, { backgroundColor: BUTTON_STYLES.BASE.backgroundColor }));
  110. button.addEventListener('click', onClick);
  111. return button;
  112. }
  113.  
  114. // 统一的样式更新函数
  115. function updateStyles() {
  116. // 使用更高效的选择器
  117. const styleUpdates = [
  118. {
  119. selector: SELECTORS.STYLES.PROGRESS_BUTTONS,
  120. styles: {
  121. background: 'rgba(85, 35, 49, 0.37)'
  122. }
  123. },
  124. {
  125. selector: SELECTORS.STYLES.PROGRESS_CONTROL,
  126. styles: {
  127. display: 'flex',
  128. visibility: 'visible',
  129. opacity: '1'
  130. }
  131. },
  132. {
  133. selector: SELECTORS.STYLES.LOOP_BUTTON,
  134. styles: { borderWidth: '0px' }
  135. }
  136. ];
  137.  
  138. styleUpdates.forEach(({selector, styles}) => {
  139. document.querySelectorAll(selector).forEach(el => {
  140. Object.assign(el.style, styles);
  141. });
  142. });
  143.  
  144. // 设置背景
  145. document.body.style.backgroundColor = 'black';
  146. }
  147.  
  148. // 优化的广告拦截函数
  149. function blockAds() {
  150. // 合并所有选择器为一个字符串
  151. const allSelectors = [
  152. ...SELECTORS.ADS.SCRIPTS,
  153. ...SELECTORS.ADS.ELEMENTS
  154. ].join(',');
  155. // 一次性查询所有需要删除的元素
  156. document.querySelectorAll(allSelectors).forEach(el => el?.remove());
  157. // 优化 iframe 移除
  158. const iframes = document.getElementsByTagName('iframe');
  159. Array.from(iframes).forEach(iframe => iframe.remove());
  160. // 优化脚本检查
  161. const scriptPattern = new RegExp(SELECTORS.ADS.SCRIPT_PATTERNS.join('|'));
  162. document.querySelectorAll('script').forEach(script => {
  163. if (scriptPattern.test(script.innerText)) {
  164. script.remove();
  165. }
  166. });
  167. }
  168.  
  169. // 优化的播放器设置函数
  170. function setupPlayer() {
  171. // 移除所有带有 @click="pop()" 的元素的点击事件
  172. document.querySelectorAll('[\\@click="pop()"]').forEach(element => {
  173. element.removeAttribute('@click');
  174. });
  175.  
  176. // 移除窗口失焦暂停
  177. const aspectElements = document.getElementsByClassName('aspect-w-16 aspect-h-9');
  178. if(aspectElements[11]) {
  179. aspectElements[11].removeAttribute('@click');
  180. aspectElements[11].removeAttribute('@keyup.space.window');
  181. }
  182. }
  183.  
  184. // 更新影院模式样式
  185. function addTheaterModeStyles() {
  186. const style = document.createElement('style');
  187. style.textContent = `
  188. .theater-overlay {
  189. position: fixed;
  190. top: 0;
  191. left: 0;
  192. width: 100vw;
  193. height: 100vh;
  194. background: rgba(0, 0, 0, 0.95);
  195. z-index: 9998;
  196. display: none;
  197. }
  198. /* 修改播放器容器样式 */
  199. .theater-mode-container {
  200. position: fixed !important;
  201. top: 0 !important;
  202. left: 0 !important;
  203. width: 100vw !important;
  204. height: 100vh !important;
  205. transform: none !important;
  206. z-index: 9999 !important;
  207. margin: 0 !important;
  208. padding: 0 !important;
  209. display: flex !important;
  210. align-items: center !important;
  211. justify-content: center !important;
  212. background: transparent !important;
  213. pointer-events: auto !important;
  214. }
  215. /* 修改视频包装器样式 */
  216. .theater-mode-container .aspect-w-16.aspect-h-9 {
  217. position: relative !important;
  218. width: 100vw !important;
  219. max-width: none !important;
  220. height: 100vh !important;
  221. margin: 0 auto !important;
  222. pointer-events: auto !important;
  223. }
  224. /* 修改视频元素样式 */
  225. .theater-mode-container video {
  226. position: absolute !important;
  227. top: 0 !important;
  228. left: 0 !important;
  229. width: 100% !important;
  230. height: 100% !important;
  231. object-fit: contain !important;
  232. pointer-events: auto !important;
  233. }
  234. /* 确保所有父容器不限制尺寸和层级 */
  235. .theater-mode-container * {
  236. max-width: none !important;
  237. max-height: none !important;
  238. pointer-events: auto !important;
  239. }
  240. /* 修改播放器控制栏样式 */
  241. .theater-mode-container .plyr__controls {
  242. position: fixed !important;
  243. bottom: 0 !important;
  244. left: 0 !important;
  245. width: 100% !important;
  246. z-index: 10000 !important;
  247. // background: rgba(254, 98, 142, 0.27) !important;
  248. padding: 10px !important;
  249. opacity: 1 !important;
  250. visibility: visible !important;
  251. display: flex !important;
  252. }
  253. /* 降低导航栏层级*/
  254. .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
  255. z-index: 1 !important;
  256. }
  257. /* 影院模式下不显示导航栏*/
  258. .theater-mode-container .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
  259. display: none !important;
  260. }
  261. /* 确保时间显示可见 */
  262. .theater-mode-container .plyr__time {
  263. display: inline-block !important;
  264. color: white !important;
  265. opacity: 1 !important;
  266. visibility: visible !important;
  267. }
  268. /* 控制条样式 */
  269. .theater-controls-progress {
  270. position: fixed !important;
  271. bottom: 104px !important;
  272. z-index: 10000 !important;
  273. // background: rgba(254, 98, 142, 0.27) !important;
  274. background: transparent !important;
  275. padding: 10px !important;
  276. width: 100% !important;
  277. max-width: none !important;
  278. pointer-events: auto !important;
  279. }
  280. .theater-controls-abloop {
  281. position: fixed !important;
  282. bottom: 52px !important;
  283. left: 0px !important;
  284. width: 100% !important;
  285. z-index: 10000 !important;
  286. // background: rgba(254, 98, 142, 0.27) !important;
  287. padding: 10px !important;
  288. pointer-events: auto !important;
  289. }
  290.  
  291. /* 添加音量控制器宽度设置 */
  292. .theater-mode-container .plyr__controls__item.plyr__volume {
  293. width: 40px !important;
  294. }
  295.  
  296. /* 隐藏指定的控制按钮 */
  297. .theater-mode-container .plyr__controls__item[data-plyr="rewind"],
  298. .theater-mode-container .plyr__controls__item[data-plyr="fast-forward"],
  299. .theater-mode-container .plyr__control[data-plyr="settings"],
  300. .theater-mode-container .plyr__controls__item[data-plyr="pip"],
  301. .theater-mode-container .plyr__controls__item[data-plyr="fullscreen"] {
  302. display: none !important;
  303. }
  304. `;
  305. document.head.appendChild(style);
  306. }
  307.  
  308. // 更新 toggleTheaterMode 函数
  309. function toggleTheaterMode() {
  310. const playerContainer = document.querySelector(SELECTORS.THEATER.PLAYER_CONTAINER);
  311. const progress = document.querySelector(SELECTORS.THEATER.PROGRESS);
  312. const abLoop = document.querySelector(SELECTORS.THEATER.AB_LOOP);
  313. // 获取或创建遮罩层
  314. let overlay = document.querySelector('.theater-overlay');
  315. if (!overlay) {
  316. overlay = document.createElement('div');
  317. overlay.className = 'theater-overlay';
  318. document.body.appendChild(overlay);
  319. }
  320. const isTheaterMode = overlay.style.display === 'none' || overlay.style.display === '';
  321. // 获取按钮并更新文本
  322. const theaterButton = document.querySelector('.theater-mode-button');
  323. const abLoopButton = document.querySelector('.ab-loop-button');
  324. if (theaterButton) {
  325. theaterButton.innerText = isTheaterMode ? '关闭影院' : '影院模式';
  326. }
  327. // 控制 AB 循环按钮的显示/隐藏
  328. if (abLoopButton) {
  329. abLoopButton.style.display = isTheaterMode ? 'block' : 'none';
  330. }
  331. if (isTheaterMode) {
  332. // 进入影院模式
  333. overlay.style.display = 'block';
  334. if (playerContainer) {
  335. playerContainer.classList.add('theater-mode-container');
  336. // 确保所有父容器都不限制尺寸和层级
  337. let parent = playerContainer.parentElement;
  338. while (parent && parent !== document.body) {
  339. parent.style.setProperty('max-width', 'none', 'important');
  340. parent.style.setProperty('max-height', 'none', 'important');
  341. parent.style.setProperty('overflow', 'visible', 'important');
  342. parent.style.setProperty('z-index', 'auto', 'important');
  343. parent = parent.parentElement;
  344. }
  345. }
  346. if (progress) {
  347. progress.classList.add('theater-controls-progress');
  348. }
  349. if (abLoop) {
  350. abLoop.classList.add('theater-controls-abloop');
  351. // 默认隐藏 AB 循环控制栏
  352. abLoop.style.display = 'none';
  353. }
  354. document.addEventListener('keydown', handleEscKey);
  355. } else {
  356. // 退出影院模式
  357. overlay.style.display = 'none';
  358. if (playerContainer) {
  359. playerContainer.classList.remove('theater-mode-container');
  360. // 恢复父容器的原始样式
  361. let parent = playerContainer.parentElement;
  362. while (parent && parent !== document.body) {
  363. parent.style.removeProperty('max-width');
  364. parent.style.removeProperty('max-height');
  365. parent.style.removeProperty('overflow');
  366. parent.style.removeProperty('z-index');
  367. parent = parent.parentElement;
  368. }
  369. }
  370. if (progress) {
  371. progress.classList.remove('theater-controls-progress');
  372. }
  373. if (abLoop) {
  374. abLoop.classList.remove('theater-controls-abloop');
  375. }
  376. document.removeEventListener('keydown', handleEscKey);
  377. }
  378. }
  379.  
  380. // ESC键处理函数
  381. function handleEscKey(e) {
  382. if (e.key === 'Escape') {
  383. toggleTheaterMode();
  384. }
  385. }
  386.  
  387. // 更新浮动按钮创建函数
  388. function createFloatingButtons() {
  389. const buttonContainer = document.createElement('div');
  390. Object.assign(buttonContainer.style, {
  391. position: 'fixed',
  392. top: '14px',
  393. right: '98px',
  394. zIndex: '10001',
  395. display: 'flex',
  396. flexDirection: 'row', // 改为水平排列
  397. gap: '10px'
  398. });
  399.  
  400. // 创建影院模式按钮
  401. const theaterButton = createStyledButton('影院模式', toggleTheaterMode);
  402. theaterButton.className = 'theater-mode-button';
  403. buttonContainer.appendChild(theaterButton);
  404. // 创建 AB 循环按钮(初始文本为"显示AB循环")
  405. const abLoopButton = createStyledButton('A/B', toggleABLoopControls);
  406. abLoopButton.className = 'ab-loop-button';
  407. abLoopButton.style.display = 'none'; // 初始隐藏
  408. buttonContainer.appendChild(abLoopButton);
  409. document.body.appendChild(buttonContainer);
  410. }
  411.  
  412. // 添加防抖函数
  413. function debounce(func, wait) {
  414. let timeout;
  415. return function executedFunction(...args) {
  416. const later = () => {
  417. clearTimeout(timeout);
  418. func(...args);
  419. };
  420. clearTimeout(timeout);
  421. timeout = setTimeout(later, wait);
  422. };
  423. }
  424.  
  425. // 优化观察者
  426. function initObserver() {
  427. const debouncedUpdate = debounce(() => {
  428. blockAds();
  429. updateStyles();
  430. setupPlayer();
  431. }, 100);
  432.  
  433. const observer = new MutationObserver(debouncedUpdate);
  434. observer.observe(document.body, {
  435. childList: true,
  436. subtree: true
  437. });
  438. }
  439.  
  440. // 简化清理函数
  441. function cleanup() {
  442. document.removeEventListener('keydown', handleEscKey);
  443. }
  444.  
  445. // 添加 AB 循环控制函数
  446. function toggleABLoopControls() {
  447. const abLoopControls = document.querySelector(SELECTORS.THEATER.AB_LOOP_CONTROLS);
  448. const abLoopButton = document.querySelector('.ab-loop-button');
  449. if (abLoopControls) {
  450. const isVisible = abLoopControls.style.display !== 'none';
  451. abLoopControls.style.display = isVisible ? 'none' : 'flex';
  452. // 更新按钮文本,默认显示"显示AB循环"
  453. if (abLoopButton) {
  454. abLoopButton.innerText = isVisible ? 'A/B' : 'no A/B';
  455. }
  456. }
  457. }
  458.  
  459. // 主函数
  460. function init() {
  461. updateStyles();
  462. blockAds();
  463. setupPlayer();
  464. addTheaterModeStyles(); // 添加影院模式样式
  465. createFloatingButtons();
  466. initObserver();
  467. }
  468.  
  469. // 当 DOM 加载完成后执行初始化
  470. if (document.readyState === 'loading') {
  471. document.addEventListener('DOMContentLoaded', init);
  472. } else {
  473. init();
  474. }
  475. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址