// ==UserScript==
// @name 虎牙直播功能增强
// @namespace https://gitee.com/Kaiter-Plus/TampermonkeyScript/tree/master/虎牙直播功能增强
// @author Kaiter-Plus
// @description 给虎牙直播添加额外功能(同步时间、画面镜像、自动选择最高画、自动选领取百宝箱奖励、自动网页全屏)
// @version 1.42
// @license BSD-3-Clause
// @match *://*.huya.com/*
// @icon https://www.huya.com/favicon.ico
// @noframes
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-end
// @downloadURL none
// ==/UserScript==
;(function () {
'use strict'
// 判断镜像状态
let isReverse = false
// 定时器
const timer = {
initTimer: null,
hightestTimer: null,
chestTimer: null
}
// 控制栏容器
let headerContainer = null
// 直播界面容器
let controlContainer = null
// 最高画质
let hightestImageQuality = null
// 所有宝箱
let chests = []
// 配置选项
const config = [
// 自动选择最高画质
GM_getValue('isSelectedHightestImageQuality'),
// 自动领取百宝箱奖励
GM_getValue('isGetChest'),
// 获取是否自动网页全屏
GM_getValue('isFullScreen')
]
// 初始化
function init() {
timer.initTimer = setInterval(() => {
if (controlContainer === null || chests.length === 0) {
headerContainer = document.querySelector('.duya-header-right div')
controlContainer = document.getElementById('player-ctrl-wrap')
// 使用数组保存
chests = Array.from(document.querySelectorAll('#player-box .player-box-list .box-item'))
} else {
clearInterval(timer.initTimer)
hightestImageQuality = document.querySelector('.player-videotype-list').children[0]
initStyle()
initTools()
removeAdvertisement()
}
}, 1000)
}
// 移除虎牙自带广告
function removeAdvertisement() {
let advertisement = document.querySelector('.duya-header-ad')
const timer = setInterval(() => {
if (advertisement) {
advertisement.parentNode.removeChild(advertisement)
clearInterval(timer)
} else {
advertisement = document.querySelector('.duya-header-ad')
}
})
}
// 初始化图标样式
function initStyle() {
GM_addStyle(`
#J_global_user_tips {
display: none;
}
.video-tools-icon {
position: absolute;
top: 11px;
}
.video-tools-icon .icon {
fill: currentColor;
cursor: pointer;
color: #b2b4b4;
}
.video-tools-icon:hover .icon {
fill: currentColor;
color: #ff9600;
}
.hy-header-style-normal .config-position {
top: 45px;
left: -69px;
}
.hy-header-style-normal .hy-nav-title svg {
position: relative;
top: -6px;
left: 24px;
fill: currentColor;
color: #555;
}
.hy-header-style-normal .hy-nav-title .title {
position: relative;
top: 7px;
left: -19px;
color: #555;
font-size: 12px;
}
.hy-header-style-normal .hy-nav-title:hover svg,
.hy-header-style-normal .hy-nav-title:hover .title {
fill: currentColor;
color: #ff9600;
}
.reset-style {
height: 94px!important;
width: 220px!important;
}
.config-arrow {
position: absolute;
top: 16px;
right: 6px;
width: 9px;
height: 5px;
overflow: hidden;
transition: transform .5s;
background: url(https://a.msstatic.com/huya/main3/assets/img/header/sprite/arrow_93ea4.png)
}
.hy-header-style-normal .hy-nav-title:hover .config-arrow {
transform: rotate(180deg);
background: url(https://a.msstatic.com/huya/main3/assets/img/header/sprite/arrow_on_e7b62.png)
}
.config-item {
padding: 5px 15px;
display: flex;
justify-content: space-around;
}
@media handheld, only screen and (max-width: 1440px) {
.duya-header-right>div>div:nth-child(1)>div:nth-child(4) {
display: none;
}
.duya-header-right>div>div:nth-child(1)>div:nth-child(5) {
display: none;
}
}
@media handheld, only screen and (max-width: 1721px) {
.duya-header-right>div>div:nth-child(1)>div:nth-child(1) {
display: none;
}
}
/* 自定义开关 */
.switchButton {
position: relative;
width: 46px;
z-index: 9999;
user-select: none;
}
.switchButton-checkbox {
display: none;
}
.switchButton-label {
display: block;
cursor: pointer;
border: 1px solid #999999;
border-radius: 20px;
overflow: hidden;
}
.switchButton-inner {
display: block;
width: 200%;
height: 18px;
margin-left: -100%;
overflow: hidden;
transition: margin 0.3s ease-in 0s;
}
.switchButton-switch {
position: absolute;
display: block;
width: 14px;
height: 14px;
margin: 2px;
background: #bbbbbb;
top: 0;
bottom: 0;
right: 25px;
border: 1px solid #584c3d;
border-radius: 14px;
transition: all 0.3s ease-in 0s;
}
.switchButton-checkbox:checked+.switchButton-label .switchButton-inner {
margin-left: 0;
}
.switchButton-checkbox:checked+.switchButton-label .switchButton-switch {
right: 0px;
background: #ff9600;
}
`)
}
// 初始化工具
function initTools() {
insertIcon()
if (config[0]) selectedHightestImageQuality()
if (config[1]) getChest()
if (config[2]) fullScreen()
}
// 创建功能图标
function createTagIcon(option) {
const tag = document.createElement(option.tagName)
tag.id = option.id
tag.className = option.className
tag.title = option.title
tag.innerHTML = option.innerHTML
tag.style = option.style
tag.addEventListener('click', option.eventListener)
tag.tabIndex = option.tabIndex ? option.tabIndex : ''
return tag
}
// 创建配置选项
function createConfigItem(configItems) {
let str = ''
for (const index in configItems) {
str = str.concat(`
${configItems[index]}
`)
}
return str
}
// 插入图标
function insertIcon() {
// 同步时间图标
const sync = createTagIcon({
tagName: 'div',
id: 'ex-videoSync',
className: 'video-tools-icon',
title: '同步时间',
innerHTML: `
`,
style: 'left: 96px;',
eventListener: setVideoSync
})
// 镜像图标
const rev = createTagIcon({
tagName: 'div',
id: 'ex-videoReverse',
className: 'video-tools-icon',
title: '镜像画面',
innerHTML: `
`,
style: 'left: 134px;',
eventListener: setVideoRev
})
// 插入配置选项
const settings = createTagIcon({
tagName: 'div',
id: 'ex-settings',
className: 'hy-nav-item',
title: '',
innerHTML: `
`,
style: '',
tabIndex: 1,
eventListener: configSettings
})
headerContainer.appendChild(settings)
// 创建用于添加自定义功能图标的 fragment,避免多次回流(重排)
const iconFragment = document.createDocumentFragment()
iconFragment.appendChild(sync)
iconFragment.appendChild(rev)
controlContainer.insertBefore(iconFragment, controlContainer.childNodes[3])
}
// 同步时间功能
function setVideoSync() {
let videoNode = document.getElementById('hy-video')
const buffered = videoNode.buffered
if (buffered.length == 0) {
// 暂停中
return
}
videoNode.currentTime = buffered.end(0)
}
// 镜像画面功能
function setVideoRev() {
let videoNode = document.getElementById('hy-video')
videoNode.style.transformOrigin = 'center'
if (isReverse) {
videoNode.style.transform = 'rotateY(0deg)'
isReverse = false
} else {
videoNode.style.transform = 'rotateY(180deg)'
isReverse = true
}
}
// 选择最高画质功能
function selectedHightestImageQuality() {
if (hightestImageQuality.className === 'on') return
hightestImageQuality.click()
timer.hightestTimer = setInterval(() => {
if (document.querySelector('.player-play-btn')) {
document.querySelector('.player-play-btn')[0].click()
document.querySelector('#player-tip-pause').remove()
clearInterval(timer.hightestTimer)
}
}, 500)
}
// 自动领取宝箱
function getChest() {
timer.chestTimer = setInterval(() => {
// 当最后一个还不是 undefined 时,说明还没有领取完,继续
if (chests[chests.length - 1]) {
// 遍历领取
for (const item of chests) {
// 如果是 undefined 则直接跳过
if (!item) continue
// 是否已经领取
const num = item.querySelector('.player-box-stat4').style.visibility
// 如果已经领取,把值设置为 undefined
if (num === 'visible') {
chests.splice(chests.indexOf(item), 1, (0)[0])
continue
}
// 如果可以领取则领取,领取后把值设置为 undefined
const get = item.querySelector('.player-box-stat3').style.visibility
if (get === 'visible') {
item.querySelector('.player-box-stat3').click()
chests.splice(chests.indexOf(item), 1, (0)[0])
document.querySelector('.player-chest-btn #player-box').style.display = 'none'
break
}
}
} else {
// 领取完,清空数组,结束定时器
chests = null
clearInterval(timer.chestTimer)
}
}, 10000)
}
// 网页全屏功能
function fullScreen() {
// 添加全屏动态样式
addDynamicStyle()
// 获取 剧场模式 按钮并点击
controlContainer.querySelector('#player-fullpage-btn').click()
// 获取 右侧聊天框 并点击
const danmuChat = document.querySelector('#player-fullpage-right-btn')
if (Array.from(danmuChat.classList).indexOf('player-fullpage-right-open') === -1) {
danmuChat.click()
}
// 隐藏 礼物栏
document.querySelector('#player-gift-wrap').style.display = 'none'
// 播放器高度设为 100%
document.querySelector('#player-wrap').style.height = '100%'
// 显示弹幕输入框
controlContainer.querySelector('#player-full-input').style.display = 'block'
// 隐藏 控制栏
controlContainer.classList.add('hidden-controls')
// 退出剧场模式
quitFullScreen()
// 添加鼠标事件
addEvent()
}
// 网页全屏样式
let dynamicStyle = null
// 添加动态样式
function addDynamicStyle() {
// 添加网页全屏样式
dynamicStyle = GM_addStyle(`
.hidden-controls {
bottom: -44px!important;
}
.show-controls {
bottom: 0px!important;
}
`)
}
// 网页全屏后监听鼠标覆盖 / 移开事件
function addEvent() {
const mouseWrap = document.querySelector('#player-mouse-event-wrap')
const danMuInput = controlContainer.querySelector('#player-full-input-txt')
mouseWrap.addEventListener('mousemove', throttle(showControls, 300))
mouseWrap.addEventListener('mousemove', debounce(hiddenControls, 1200)) // 1.2s 后隐藏控制栏
controlContainer.addEventListener('mouseover', showControls, true)
// 回车显示并聚焦输入框
document.addEventListener('keydown', e => {
if (e.key.toLowerCase() === 'enter') {
showControls()
danMuInput.focus()
}
})
// 回车发送弹幕
danMuInput.addEventListener('keydown', e => {
e.stopPropagation()
if (e.key.toLowerCase() === 'enter') {
controlContainer.querySelector('#player-full-input-btn').click()
document.querySelector('#ex-settings').focus()
hiddenControls()
}
})
}
// 退出网页全屏
function quitFullScreen() {
const narrowPageBtn = controlContainer.querySelector('.player-narrowpage')
narrowPageBtn.onclick = () => {
headerContainer.querySelector('#ON_OFF2').click()
if (!GM_getValue('isFullScreen')) {
// 移除网页全屏样式
dynamicStyle.parentNode.removeChild(dynamicStyle)
narrowPageBtn.onclick = null
}
}
}
// 节流函数
function throttle(fn, delay) {
let valid = true
return function () {
if (!valid) {
return false
}
valid = false
setTimeout(() => {
fn()
valid = true
}, delay)
}
}
// 防抖函数
function debounce(fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
// 显示 控制栏
function showControls() {
controlContainer.classList.add('show-controls')
controlContainer.classList.remove('hidden-controls')
}
// 隐藏 控制栏
function hiddenControls() {
controlContainer.classList.remove('show-controls')
controlContainer.classList.add('hidden-controls')
}
// 配置选项监听事件
function configSettings(e) {
const target = e.target
if (target.tagName.toLowerCase() === 'input') {
// 选择最高画质
if (target.id === 'ON_OFF0') {
switchFunction('isSelectedHightestImageQuality', target, selectedHightestImageQuality, timer.hightestTimer)
}
// 领取百宝箱奖励
if (target.id === 'ON_OFF1') {
switchFunction('isGetChest', target, getChest, timer.chestTimer)
}
// 网页全屏
if (target.id === 'ON_OFF2') {
switchFunction('isFullScreen', target, fullScreen, null)
}
e.stopPropagation()
}
}
// 配置选项功能
function switchFunction(key, target, fn, timer) {
GM_setValue(key, target.checked)
if (target.checked) {
fn()
} else {
clearInterval(timer)
}
}
// 初始化
init()
})()