Greasy Fork is available in English.
使用 PointerEvent 模拟真实点击,解决 V4 无反应问题,智能检测视频切换。
当前为
// ==UserScript==
// @name 抖音强制最高画质 (V5-强力指针版)
// @namespace http://tampermonkey.net/
// @version 5.0
// @description 使用 PointerEvent 模拟真实点击,解决 V4 无反应问题,智能检测视频切换。
// @author You
// @match https://www.douyin.com/*
// @match https://live.douyin.com/*
// @grant none
// @run-at document-idle
// @license All Rights Reserved
// ==/UserScript==
(function() {
'use strict';
// === 配置 ===
const CHECK_INTERVAL = 1500; // 检测频率
const QUALITIES = ["超清 4K", "超清 2K", "高清 1080P"]; // 优先级
// === 状态 ===
let lastVideoSrc = "";
let isChecked = false;
// === 工具:模拟全套指针事件 (解决“没反应”的核心) ===
function triggerPointerEvent(element, eventType) {
if (!element) return;
const event = new PointerEvent(eventType, {
bubbles: true,
cancelable: true,
view: window,
pointerId: 1,
width: 1,
height: 1,
isPrimary: true,
pointerType: 'mouse'
});
element.dispatchEvent(event);
}
// === 工具:通过文字找元素 (解决类名失效) ===
function findElementByText(tag, text) {
const elements = document.getElementsByTagName(tag);
for (let i = elements.length - 1; i >= 0; i--) {
const el = elements[i];
// 排除隐藏元素
if (el.offsetParent === null) continue;
// 严格匹配文字,避免匹配到大容器
if (el.innerText && el.innerText.trim() === text) {
return el;
}
}
return null;
}
// === 工具:查找包含特定文字的父级按钮 ===
function findResolutionButton() {
// 常见的几种显示状态
const keywords = ["智能", "标清", "高清", "超清", "4K", "1080P", "720P"];
// 遍历 span 和 div
const tags = ['span', 'div'];
for (let tag of tags) {
const els = document.getElementsByTagName(tag);
for (let i = els.length - 1; i >= 0; i--) {
const el = els[i];
if (!el.innerText) continue;
const txt = el.innerText.trim();
// 如果文字完全匹配某个关键词,或者包含 "高清 1080P" 这种组合
if (keywords.some(k => txt.includes(k))) {
// 必须是在控制栏里的(通常高度比较小,宽度适中)
// 这里的过滤逻辑:通常清晰度按钮是个较小的容器
if (el.clientHeight > 10 && el.clientHeight < 50 && el.clientWidth < 150) {
return el;
}
}
}
}
return null;
}
// === 主逻辑 ===
function mainLoop() {
const video = document.querySelector('video');
if (!video) return;
// 1. 视频切换检测
if (video.src !== lastVideoSrc) {
console.log("[画质] 发现新视频,重置状态...");
lastVideoSrc = video.src;
isChecked = false;
}
if (isChecked) return;
// 2. 寻找清晰度入口按钮
const triggerBtn = findResolutionButton();
if (!triggerBtn) return;
const currentText = triggerBtn.innerText;
// 如果已经是 4K,直接标记完成
if (currentText.includes("4K")) {
isChecked = true;
return;
}
// 3. 模拟鼠标悬停 (使用 PointerEvent)
// 必须连续触发 over 和 enter
triggerPointerEvent(triggerBtn, 'pointerover');
triggerPointerEvent(triggerBtn, 'pointerenter');
// 4. 等待菜单弹出
setTimeout(() => {
// 查找所有选项
// 这里我们找所有的 DOM 节点,筛选出包含目标画质的
let foundTarget = false;
// 遍历我们想要的画质优先级
for (let q of QUALITIES) {
// 如果当前按钮已经是这个画质,就不点了
if (currentText.includes(q)) {
foundTarget = true;
break;
}
// 在页面中寻找这个选项(菜单弹出后,选项应该在 DOM 里了)
// 同样使用模糊搜索,找 span 或 div
const allDivs = document.querySelectorAll('div, span, p');
for (let node of allDivs) {
if (node.innerText === q && node !== triggerBtn) {
console.log(`[画质] 点击切换: ${q}`);
// 模拟点击
triggerPointerEvent(node, 'pointerdown');
triggerPointerEvent(node, 'mousedown');
triggerPointerEvent(node, 'pointerup');
triggerPointerEvent(node, 'mouseup');
node.click(); // 保险起见加个 click
foundTarget = true;
// === 关键:解决菜单不消失 ===
// 1. 移出按钮
triggerPointerEvent(triggerBtn, 'pointerout');
triggerPointerEvent(triggerBtn, 'pointerleave');
// 2. 往视频中间点一下(模拟取消焦点,不会暂停视频,通常能收起菜单)
// 只要不触发 click,只触发 move/out
const player = document.querySelector('.xgplayer-container') || document.body;
triggerPointerEvent(player, 'pointermove');
break;
}
}
if (foundTarget) break;
}
// 无论找没找到,只要执行过一次检测流程,且当前不是“智能”,就标记为已检查
// 如果是“智能”,可能菜单没加载出来,下次循环再试一次
if (foundTarget || !currentText.includes("智能")) {
isChecked = true;
// 再次强制清理菜单
triggerPointerEvent(triggerBtn, 'pointerleave');
}
}, 300);
}
// 启动
setInterval(mainLoop, CHECK_INTERVAL);
})();