// ==UserScript==
// @run-at document-start
// @name AV_AD_Block
// @description missav 广告拦截与界面优化
// @icon https://static.missav.com/img/favicon.png
// @namespace loadingi.local
// @version 3.1
// @author ch
// @match https://missav.ai/*
// @match https://missav.ws/*
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @compatible chrome
// @compatible firefox
// @compatible edge
// @license GPL-3.0-only
// ==/UserScript==
(function() {
'use strict';
// 统一管理所有选择器
const SELECTORS = {
// 样式相关选择器
STYLES: {
PROGRESS_BUTTONS: '.isolate.inline-flex.rounded-md.shadow-sm',
PROGRESS_CONTROL: '.sm\\:hidden',
LOOP_BUTTON: '.sm\\:ml-6 button',
// INFO_TEXT: '.mb-1.text-secondary.break-all.line-clamp-2',
// ASPECT_RATIO: '.aspect-w-16.aspect-h-9',
PLAYER_CONTAINER: 'div.relative.-mx-4.sm\\:m-0.-mt-6',
// VIDEO_WRAPPER: '.aspect-w-16.aspect-h-9.relative'
},
// 广告相关选择器
ADS: {
SCRIPTS: [
"script[src*='app.1aad5686.js']",
"script[src*='inpage.push.js']",
"script[src*='hartattenuate.com']",
"script[src*='ads']",
"script[src*='pop']",
"script[src*='banner']"
],
ELEMENTS: [
'div.sm\\:container.mx-auto.mb-5.px-4',
'ul.mb-4.list-none.text-nord14.grid.grid-cols-2.gap-2',
// class="relative ml-4" 无聊的东西 myavlive 之类的
'div.relative.ml-4',
'div.root--ujvuu', // 隐藏底部右下角浮窗广告
'div.under_player',
'div.space-y-5.mb-5',
'div[class^="rootContent--"]',
'div[class^="fixed right-2 bottom-2"]',
'div[class^="space-y-6 mb-6"]',
'div.space-y-2.mb-4.ml-4.list-disc.text-nord14',
'div[id*="ads"]',
'div[id*="banner"]',
'div[class*="ads"]',
'div[class*="banner"]',
'.ad-container',
'#ad-container'
],
SCRIPT_PATTERNS: [
'htmlAds',
'popAds',
'bannerAds',
'adsConfig'
]
},
THEATER: {
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',
PLAYER_WRAPPER: '.aspect-w-16.aspect-h-9',
VIDEO_ELEMENT: 'video#player',
PROGRESS: 'div.sm\\:hidden.flex.justify-between.-mx-4.px-4.pt-3.pb-1.bg-black',
AB_LOOP: 'div.flex.items-center.flex-nowrap.leading-5',
AB_LOOP_CONTROLS: '.theater-controls-abloop'
}
};
// 添加统一的按钮样式常量
const BUTTON_STYLES = {
BASE: {
backgroundColor: '#222',
borderRadius: '15px',
borderColor: 'black',
borderWidth: '1px',
color: 'burlywood',
cursor: 'pointer',
transition: 'all 0.3s ease',
outline: 'none',
minWidth: '80px',
padding: '2px 4px',
marginBottom: '10px',
},
HOVER: {
backgroundColor: '#333'
}
};
// 创建统一的按钮工厂函数
function createStyledButton(text, onClick) {
const button = document.createElement('button');
Object.assign(button.style, BUTTON_STYLES.BASE);
button.innerText = text;
button.addEventListener('mouseover', () => Object.assign(button.style, BUTTON_STYLES.HOVER));
button.addEventListener('mouseout', () => Object.assign(button.style, { backgroundColor: BUTTON_STYLES.BASE.backgroundColor }));
button.addEventListener('click', onClick);
return button;
}
// 统一的样式更新函数
function updateStyles() {
// 使用更高效的选择器
const styleUpdates = [
{
selector: SELECTORS.STYLES.PROGRESS_BUTTONS,
styles: {
background: 'rgba(85, 35, 49, 0.37)'
}
},
{
selector: SELECTORS.STYLES.PROGRESS_CONTROL,
styles: {
display: 'flex',
visibility: 'visible',
opacity: '1'
}
},
{
selector: SELECTORS.STYLES.LOOP_BUTTON,
styles: { borderWidth: '0px' }
}
];
styleUpdates.forEach(({selector, styles}) => {
document.querySelectorAll(selector).forEach(el => {
Object.assign(el.style, styles);
});
});
// 设置背景
document.body.style.backgroundColor = 'black';
}
// 优化的广告拦截函数
function blockAds() {
// 合并所有选择器为一个字符串
const allSelectors = [
...SELECTORS.ADS.SCRIPTS,
...SELECTORS.ADS.ELEMENTS
].join(',');
// 一次性查询所有需要删除的元素
document.querySelectorAll(allSelectors).forEach(el => el?.remove());
// 优化 iframe 移除
const iframes = document.getElementsByTagName('iframe');
Array.from(iframes).forEach(iframe => iframe.remove());
// 优化脚本检查
const scriptPattern = new RegExp(SELECTORS.ADS.SCRIPT_PATTERNS.join('|'));
document.querySelectorAll('script').forEach(script => {
if (scriptPattern.test(script.innerText)) {
script.remove();
}
});
}
// 优化的播放器设置函数
function setupPlayer() {
// 移除所有带有 @click="pop()" 的元素的点击事件
document.querySelectorAll('[\\@click="pop()"]').forEach(element => {
element.removeAttribute('@click');
});
// 移除窗口失焦暂停
const aspectElements = document.getElementsByClassName('aspect-w-16 aspect-h-9');
if(aspectElements[11]) {
aspectElements[11].removeAttribute('@click');
aspectElements[11].removeAttribute('@keyup.space.window');
}
}
// 更新影院模式样式
function addTheaterModeStyles() {
const style = document.createElement('style');
style.textContent = `
.theater-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.95);
z-index: 9998;
display: none;
}
/* 修改播放器容器样式 */
.theater-mode-container {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
transform: none !important;
z-index: 9999 !important;
margin: 0 !important;
padding: 0 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
background: transparent !important;
pointer-events: auto !important;
}
/* 修改视频包装器样式 */
.theater-mode-container .aspect-w-16.aspect-h-9 {
position: relative !important;
width: 100vw !important;
max-width: none !important;
height: 100vh !important;
margin: 0 auto !important;
pointer-events: auto !important;
}
/* 修改视频元素样式 */
.theater-mode-container video {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
object-fit: contain !important;
pointer-events: auto !important;
}
/* 确保所有父容器不限制尺寸和层级 */
.theater-mode-container * {
max-width: none !important;
max-height: none !important;
pointer-events: auto !important;
}
/* 修改播放器控制栏样式 */
.theater-mode-container .plyr__controls {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
width: 100% !important;
z-index: 10000 !important;
// background: rgba(254, 98, 142, 0.27) !important;
padding: 10px !important;
opacity: 1 !important;
visibility: visible !important;
display: flex !important;
}
/* 降低导航栏层级*/
.fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
z-index: 1 !important;
}
/* 影院模式下不显示导航栏*/
.theater-mode-container .fixed.z-max.w-full.bg-gradient-to-b.from-darkest {
display: none !important;
}
/* 确保时间显示可见 */
.theater-mode-container .plyr__time {
display: inline-block !important;
color: white !important;
opacity: 1 !important;
visibility: visible !important;
}
/* 控制条样式 */
.theater-controls-progress {
position: fixed !important;
bottom: 104px !important;
z-index: 10000 !important;
// background: rgba(254, 98, 142, 0.27) !important;
background: transparent !important;
padding: 10px !important;
width: 100% !important;
max-width: none !important;
pointer-events: auto !important;
}
.theater-controls-abloop {
position: fixed !important;
bottom: 52px !important;
left: 0px !important;
width: 100% !important;
z-index: 10000 !important;
// background: rgba(254, 98, 142, 0.27) !important;
padding: 10px !important;
pointer-events: auto !important;
}
/* 添加音量控制器宽度设置 */
.theater-mode-container .plyr__controls__item.plyr__volume {
width: 40px !important;
}
/* 隐藏指定的控制按钮 */
.theater-mode-container .plyr__controls__item[data-plyr="rewind"],
.theater-mode-container .plyr__controls__item[data-plyr="fast-forward"],
.theater-mode-container .plyr__control[data-plyr="settings"],
.theater-mode-container .plyr__controls__item[data-plyr="pip"],
.theater-mode-container .plyr__controls__item[data-plyr="fullscreen"] {
display: none !important;
}
`;
document.head.appendChild(style);
}
// 更新 toggleTheaterMode 函数
function toggleTheaterMode() {
const playerContainer = document.querySelector(SELECTORS.THEATER.PLAYER_CONTAINER);
const progress = document.querySelector(SELECTORS.THEATER.PROGRESS);
const abLoop = document.querySelector(SELECTORS.THEATER.AB_LOOP);
// 获取或创建遮罩层
let overlay = document.querySelector('.theater-overlay');
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'theater-overlay';
document.body.appendChild(overlay);
}
const isTheaterMode = overlay.style.display === 'none' || overlay.style.display === '';
// 获取按钮并更新文本
const theaterButton = document.querySelector('.theater-mode-button');
const abLoopButton = document.querySelector('.ab-loop-button');
if (theaterButton) {
theaterButton.innerText = isTheaterMode ? '关闭影院' : '影院模式';
}
// 控制 AB 循环按钮的显示/隐藏
if (abLoopButton) {
abLoopButton.style.display = isTheaterMode ? 'block' : 'none';
}
if (isTheaterMode) {
// 进入影院模式
overlay.style.display = 'block';
if (playerContainer) {
playerContainer.classList.add('theater-mode-container');
// 确保所有父容器都不限制尺寸和层级
let parent = playerContainer.parentElement;
while (parent && parent !== document.body) {
parent.style.setProperty('max-width', 'none', 'important');
parent.style.setProperty('max-height', 'none', 'important');
parent.style.setProperty('overflow', 'visible', 'important');
parent.style.setProperty('z-index', 'auto', 'important');
parent = parent.parentElement;
}
}
if (progress) {
progress.classList.add('theater-controls-progress');
}
if (abLoop) {
abLoop.classList.add('theater-controls-abloop');
// 默认隐藏 AB 循环控制栏
abLoop.style.display = 'none';
}
document.addEventListener('keydown', handleEscKey);
} else {
// 退出影院模式
overlay.style.display = 'none';
if (playerContainer) {
playerContainer.classList.remove('theater-mode-container');
// 恢复父容器的原始样式
let parent = playerContainer.parentElement;
while (parent && parent !== document.body) {
parent.style.removeProperty('max-width');
parent.style.removeProperty('max-height');
parent.style.removeProperty('overflow');
parent.style.removeProperty('z-index');
parent = parent.parentElement;
}
}
if (progress) {
progress.classList.remove('theater-controls-progress');
}
if (abLoop) {
abLoop.classList.remove('theater-controls-abloop');
}
document.removeEventListener('keydown', handleEscKey);
}
}
// ESC键处理函数
function handleEscKey(e) {
if (e.key === 'Escape') {
toggleTheaterMode();
}
}
// 更新浮动按钮创建函数
function createFloatingButtons() {
const buttonContainer = document.createElement('div');
Object.assign(buttonContainer.style, {
position: 'fixed',
top: '14px',
right: '98px',
zIndex: '10001',
display: 'flex',
flexDirection: 'row', // 改为水平排列
gap: '10px'
});
// 创建影院模式按钮
const theaterButton = createStyledButton('影院模式', toggleTheaterMode);
theaterButton.className = 'theater-mode-button';
buttonContainer.appendChild(theaterButton);
// 创建 AB 循环按钮(初始文本为"显示AB循环")
const abLoopButton = createStyledButton('A/B', toggleABLoopControls);
abLoopButton.className = 'ab-loop-button';
abLoopButton.style.display = 'none'; // 初始隐藏
buttonContainer.appendChild(abLoopButton);
document.body.appendChild(buttonContainer);
}
// 添加防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 优化观察者
function initObserver() {
const debouncedUpdate = debounce(() => {
blockAds();
updateStyles();
setupPlayer();
}, 100);
const observer = new MutationObserver(debouncedUpdate);
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// 简化清理函数
function cleanup() {
document.removeEventListener('keydown', handleEscKey);
}
// 添加 AB 循环控制函数
function toggleABLoopControls() {
const abLoopControls = document.querySelector(SELECTORS.THEATER.AB_LOOP_CONTROLS);
const abLoopButton = document.querySelector('.ab-loop-button');
if (abLoopControls) {
const isVisible = abLoopControls.style.display !== 'none';
abLoopControls.style.display = isVisible ? 'none' : 'flex';
// 更新按钮文本,默认显示"显示AB循环"
if (abLoopButton) {
abLoopButton.innerText = isVisible ? 'A/B' : 'no A/B';
}
}
}
// 主函数
function init() {
updateStyles();
blockAds();
setupPlayer();
addTheaterModeStyles(); // 添加影院模式样式
createFloatingButtons();
initObserver();
}
// 当 DOM 加载完成后执行初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();