// ==UserScript== // @name 虎牙直播功能增强 // @namespace https://gitee.com/Kaiter-Plus/TampermonkeyScript/tree/master/虎牙直播功能增强 // @author Kaiter-Plus // @description 给虎牙直播添加额外功能(同步时间、画面镜像、自动选择最高画、自动选领取百宝箱奖励、自动网页全屏) // @version 1.41 // @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 // @note 2020/12/10 添加 “同步时间” 功能 // @note 2020/12/28 添加 “画面镜像” 功能 // @note 2021/01/29 代码重构 // @note 2021/03/01 添加 “自动选择最高画质” 功能,并同时提供配置开关,默认关闭 // @note 2021/03/02 添加 “自动选领取百宝箱奖励” 功能,并同时提供配置开关,默认关闭 // @note 2021/03/03 修改 更改配置时为不用重载界面 // @note 2021/03/04 修复了一个小 bug // @note 2021/03/08 修复了最后两个宝箱不会领取的 bug // @note 2021/03/10 紧急修复了宝箱不会领取的 bug // @note 2021/03/12 添加了脚本的配置选项 // @note 2021/03/13 优化自动领取百宝箱逻辑 // @note 2021/03/22 添加 “自动网页全屏” 功能,并同时提供配置开关,默认关闭 // @note 2021/05/25 虎牙网站更新,修复脚本失效 // @note 2021/06/12 虎牙网站更新,再一次修复脚本失效,同时移除虎牙自带的节日广告 // @note 2021/12/20 修复退出网页全屏需自动刷新才能正常使用的bug // @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 = null // 配置选项 const config = [ // 自动选择最高画质 GM_getValue('isSelectedHightestImageQuality'), // 自动领取百宝箱奖励 GM_getValue('isGetChest'), // 获取是否自动网页全屏 GM_getValue('isFullScreen') ] // 初始化 function init() { timer.initTimer = setInterval(() => { if (!controlContainer || 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 li')) } 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() })()