// ==UserScript==
// @name 虎牙实验室
// @namespace https://gitee.com/Kaiter-Plus/TampermonkeyScript/tree/master/huya-lab
// @author Kaiter-Plus
// @description 给虎牙直播添加额外功能(同步时间、画面镜像、自动选择最高画、自动选领取百宝箱奖励、自动网页全屏)
// @version 2.0.2
// @license BSD-3-Clause
// @match *://*.huya.com/*
// @icon https://www.huya.com/favicon.ico
// @require https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.13/vue.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.14/index.js
// @resource elementStyle https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.15.14/theme-chalk/index.css
// @noframes
// @grant GM_getResourceText
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-end
// @downloadURL https://update.greasyfork.icu/scripts/420867/%E8%99%8E%E7%89%99%E5%AE%9E%E9%AA%8C%E5%AE%A4.user.js
// @updateURL https://update.greasyfork.icu/scripts/420867/%E8%99%8E%E7%89%99%E5%AE%9E%E9%AA%8C%E5%AE%A4.meta.js
// ==/UserScript==
;(function () {
'use strict'
// 创建一个容器
const container = document.createElement('div')
container.id = 'huya-lab'
container.innerHTML = ``
document.body.appendChild(container)
// 添加 element-ui 样式
const elementStyle = GM_getResourceText('elementStyle')
GM_addStyle(elementStyle)
// 添加样式
initStyle()
// 记录获取播放器控制栏获取失败的次数
let failCount = 0
// 播放器控制栏
let playerControl = null
// 等待播放器的控制栏加载完成
const timer = setInterval(() => {
// 如果加载失败超过 100 次,提示插件初始化失败
if (failCount >= 100) {
clearInterval(timer)
Vue.prototype.$message.error('虎牙实验室插件加载失败,请刷新重试~')
} else {
if (playerControl === null) {
failCount += 1
playerControl = document.getElementById('player-ctrl-wrap')
} else {
clearInterval(timer)
// 开始初始化
init()
}
}
}, 1500)
function init() {
// 创建元素方法
Vue.prototype.createPlayerButton = option => {
const btn = document.createElement(option.tagName)
btn.id = option.id ? option.id : ''
btn.className = option.className ? option.className : ''
btn.title = option.title ? option.title : ''
btn.innerHTML = option.innerHTML ? option.innerHTML : ''
btn.style = option.style ? option.style : ''
btn.tabIndex = option.tabIndex ? option.tabIndex : ''
if (option.clickEventListener) btn.addEventListener('click', option.clickEventListener)
return btn
}
// 时间同步
const syncTimeIcon = {
data() {
return {
syncCurrentTime: false
}
},
created() {
// 初始化状态
this.syncCurrentTime = GM_getValue('syncCurrentTime') ? true : false
this.toggleIcon(this.syncCurrentTime)
},
methods: {
// 切换同步时间按钮显示和隐藏
toggleIcon(value) {
if (!playerControl) {
this.$message.warning('虎牙播放器控制条还没有加载完成,请稍后操作~')
this.syncCurrentTime = !this.syncCurrentTime
} else {
if (value) {
const icon = this.createIcon()
playerControl.appendChild(icon)
} else {
const syncBtn = document.getElementById('hy-sync')
if (syncBtn) syncBtn.parentNode.removeChild(syncBtn)
}
// 保存状态
GM_setValue('syncCurrentTime', value)
}
},
// 创建同步时间按钮
createIcon() {
const icon = this.createPlayerButton({
tagName: 'div',
id: 'hy-sync',
className: 'player-ctrl__btn',
title: '同步时间',
innerHTML: `
`,
clickEventListener: this.clickHandler
})
return icon
},
// 同步时间功能
clickHandler() {
const videoNode = document.getElementById('hy-video')
if (videoNode.tagName !== 'VIDEO') this.$message.warning('当前直播间不支持使用时间同步功能~')
if (videoNode) {
const buffered = videoNode.buffered
if (buffered.length !== 0) videoNode.currentTime = buffered.end(0)
} else {
this.$message.warning('虎牙直播视频容器还没有加载出来,请稍后重试~')
}
}
},
template: `
播放器同步时间功能
`
}
// 镜像
const reversePlayerIcon = {
data() {
return {
reversePlayer: false,
isReverse: false
}
},
created() {
// 初始化状态
this.reversePlayer = GM_getValue('reversePlayer') ? true : false
this.toggleIcon(this.reversePlayer)
},
methods: {
// 切换镜像按钮显示和隐藏
toggleIcon(value) {
if (!playerControl) {
this.$message.warning('虎牙播放器控制条还没有加载完成,请稍后操作~')
this.reversePlayer = !this.reversePlayer
} else {
if (value) {
const icon = this.createIcon()
playerControl.appendChild(icon)
} else {
const reverseBtn = document.getElementById('hy-reverse')
if (reverseBtn) reverseBtn.parentNode.removeChild(reverseBtn)
}
// 保存状态
GM_setValue('reversePlayer', value)
}
},
// 创建镜像按钮
createIcon() {
const icon = this.createPlayerButton({
tagName: 'div',
id: 'hy-reverse',
className: 'player-ctrl__btn',
title: '镜像画面',
innerHTML: `
`,
clickEventListener: this.clickHandler
})
return icon
},
// 画面镜像功能
clickHandler() {
const videoNode = document.getElementById('hy-video')
if (videoNode) {
videoNode.style.transformOrigin = 'center'
if (this.isReverse) {
videoNode.style.transform = 'rotateY(0deg)'
this.isReverse = false
} else {
videoNode.style.transform = 'rotateY(180deg)'
this.isReverse = true
}
} else {
this.$message.warning('虎牙直播视频容器还没有加载出来,请稍后重试~')
}
}
},
template: `
播放器镜像播放功能
`
}
// 自动选择最高画质
const selectHightestQuality = {
data() {
return {
autoSelect: false
}
},
created() {
// 初始化状态
this.autoSelect = GM_getValue('selectHightestQuality') ? true : false
this.toggleState(this.autoSelect)
},
methods: {
// 功能初始化
toggleState(value) {
if (value) {
const hightest = document.querySelector('.player-videotype-list').children[0]
console.log(hightest)
if (hightest.className === 'on') return
hightest.click()
// 防止切换画质时视频停止播放
const timer = setInterval(() => {
if (document.querySelector('.player-play-btn')) {
document.querySelector('.player-play-btn')[0].click()
document.querySelector('#player-tip-pause').remove()
clearInterval(timer)
}
}, 500)
}
// 保存状态
GM_setValue('selectHightestQuality', value)
}
},
template: `
自动选择最高画质功能
`
}
// 自动选领取百宝箱
const autoGetChests = {
data() {
return {
enabled: false,
timer: null,
loaded: false, // 领取页面是否加载完毕
chestList: null // 所有宝箱
}
},
created() {
// 初始化状态
this.enabled = GM_getValue('autoGetChests') ? true : false
this.toggleState(this.enabled)
},
methods: {
// 功能初始化
toggleState(value) {
if (value) {
this.loadChestView()
// 获取白炽宝箱(普通用户宝箱)
if (!document.querySelector('#box-list-top .item:nth-child(1)')) {
this.$message.warning('白炽宝箱还没有加载完成,请稍后操作~')
this.toggleState(this.enabled)
} else {
this.getChests()
}
} else {
clearInterval(this.timer)
}
// 保存状态
GM_setValue('autoGetChests', value)
},
// 自动获取百宝箱
getChests() {
this.timer = setInterval(() => {
if (!this.chestList) {
this.chestList = Array.from(document.querySelectorAll('#box-item-list>.box-item'))
} else {
// 如果已经领取完了,清除定时器
const lastChestText = this.chestList[this.chestList.length - 1].querySelector('p').innerText
if (lastChestText === '已领取') {
// this.$message.warning('宝箱已经领取完了~')
this.chestList = null
clearInterval(this.timer)
}
}
// 如果当前是可领取状态,领取
const btn = document.querySelector('#box-list-top .item:nth-child(1) .btn')
if (btn.style.display === 'block') {
// this.$message.warning('开始领取宝箱了~')
btn.click()
// 隐藏领取成功弹框
document.querySelector('#player-box-panel .box-mask').style.display = 'none'
}
}, 10000)
},
loadChestView() {
if (!this.loaded) {
// 第一次加载需要点击一下才会出现页面
const chestView = document.querySelector('#box-list-top')
if (!chestView) {
const chestBtn = document.querySelector('#player-gift-wrap .player-chest-btn')
chestBtn.click()
// 点击之后会显示宝箱的视图,隐藏
document.querySelector('#player-box-panel .close').click()
} else {
this.loaded = true
}
}
}
},
template: `
自动选领取百宝箱功能
`
}
// 网页全屏
const pageFullScreen = {
data() {
return {
enabled: false,
dynamicStyle: null // 动态样式
}
},
created() {
// 初始化状态
this.enabled = GM_getValue('pageFullScreen') ? true : false
this.toggleState(this.enabled)
},
methods: {
// 功能初始化
toggleState(value) {
// 保存状态
GM_setValue('pageFullScreen', value)
// 如果是 true 则开启网页全屏
if (value) {
// 添加全屏动态样式
this.addDynamicStyle()
if (!playerControl) {
this.$message.warning('虎牙播放器控制条还没有加载完成,请稍后操作~')
this.enabled = !this.enabled
} else {
// 获取 剧场模式 按钮并点击
playerControl.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'
// 隐藏 全屏按钮
playerControl.querySelector('#player-fullscreen-btn').style.display = 'none'
// 播放器高度设为 100%
document.querySelector('#player-wrap').style.height = '100%'
// 显示弹幕输入框
playerControl.querySelector('#player-full-input').style.display = 'block'
// 隐藏 控制栏
playerControl.classList.add('hidden-controls')
// 退出剧场模式事件
this.quitFullScreen()
// 添加鼠标事件
this.addEvent()
}
}
},
// 添加样式
addDynamicStyle() {
// 添加网页全屏样式
this.dynamicStyle = GM_addStyle(`
.hidden-controls {
bottom: -44px!important;
}
.show-controls {
bottom: 0px!important;
}
`)
},
// 添加事件
addEvent() {
const mouseWrap = document.querySelector('#player-mouse-event-wrap')
const danMuInput = playerControl.querySelector('#player-full-input-txt')
mouseWrap.addEventListener('mousemove', this.throttle(this.showControls, 300))
mouseWrap.addEventListener('mousemove', this.debounce(this.hiddenControls, 1200)) // 1.2s 后隐藏控制栏
playerControl.addEventListener('mouseover', this.showControls, true)
// 回车显示并聚焦输入框
document.addEventListener('keydown', e => {
if (e.key.toLowerCase() === 'enter') {
this.showControls()
danMuInput.focus()
}
})
// 回车发送弹幕
danMuInput.addEventListener('keydown', e => {
e.stopPropagation()
if (e.key.toLowerCase() === 'enter') {
playerControl.querySelector('#player-full-input-btn').click()
document.querySelector('#ex-settings').focus()
this.hiddenControls()
}
})
},
// 退出网页全屏
quitFullScreen() {
const narrowPageBtn = playerControl.querySelector('.player-narrowpage')
narrowPageBtn.onclick = () => {
this.enabled = !this.enabled
GM_setValue('pageFullScreen', this.enabled)
if (!this.enabled) {
// 移除网页全屏样式
this.dynamicStyle.parentNode.removeChild(this.dynamicStyle)
narrowPageBtn.onclick = null
// 显示 全屏按钮
playerControl.querySelector('#player-fullscreen-btn').style.display = 'block'
}
}
},
// 显示 控制栏
showControls() {
playerControl.classList.add('show-controls')
playerControl.classList.remove('hidden-controls')
},
// 隐藏 控制栏
hiddenControls() {
playerControl.classList.remove('show-controls')
playerControl.classList.add('hidden-controls')
},
// 节流函数
throttle(fn, delay) {
let valid = true
return function () {
if (!valid) {
return false
}
valid = false
setTimeout(() => {
fn()
valid = true
}, delay)
}
},
// 防抖函数
debounce(fn, delay) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
},
template: `
自动网页全屏功能
`
}
// 实验室
const lab = {
components: {
syncTimeIcon,
reversePlayerIcon,
selectHightestQuality,
autoGetChests,
pageFullScreen
},
data() {
return {
lab: {
pageFullScreen: false
}
}
},
created() {
this.removeAdvertisement()
},
methods: {
// 移除虎牙自带的广告
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')
}
})
}
},
template: `
`
}
// 创建 vue 实例
const app = new Vue({
el: container,
components: {
'hy-lab-icon': lab
}
})
}
// 添加自定义样式
function initStyle() {
GM_addStyle(`
@font-face {
font-family: element-icons;
src:url(https://unpkg.com/element-ui/lib/theme-chalk/fonts/element-icons.woff) format("woff");
}
#huya-lab {
position: fixed;
right: 0;
bottom: 20%;
padding: 12px 4px;
border: 1px solid #e3e5e7;
border-radius: 12px 0 0 12px;
background: #fff;
z-index: 10;
transform: translate3d(0,0,0);
transition: height cubic-bezier(.22,.58,.12,.98) .4s;
box-shadow: 0 0 20px 0 rgba(0, 85, 255, 0.1);
}
.hy-lab-icon {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 4px 0;
padding: 5px 4px;
color: #ff9600;
cursor: pointer;
}
.hy-lab-icon:hover .icon {
animation: jumping cubic-bezier(.22,.58,.12,.98) 1.5s infinite;
}
@keyframes jumping {
0% {
transform: translate(0,0) scale(1);
}
10% {
transform: translate(0,4px) scale(.8);
}
30% {
transform: translate(0,-13px) scale(.95);
}
45% {
transform: translate(0,4px) scale(.8);
}
50% {
transform: translate(0,0) scale(1);
}
53% {
transform: translate(0,0) scale(.9);
}
55% {
transform: translate(0,0) scale(1);
}
57% {
transform: translate(0,0) scale(.9);
}
61% {
transform: translate(0,0) scale(1);
}
100% {
transform: translate(0,0) scale(1);
}
}
.lab-item {
display: flex;
justify-content: space-between;
align-items: center;
color: #ff9600;
height: 40px;
}
.lab-item__title {
font-size: 16px;
line-height: 24px;
}
/* 播放器控制栏图标样式 */
.player-ctrl__btn {
display: block;
position: relative;
float: left;
width: 24px;
height: 24px;
margin: 10px 0 10px 15px;
cursor: pointer;
}
.player-ctrl__btn .icon {
fill: currentColor;
cursor: pointer;
color: #b2b4b4;
}
.player-ctrl__btn:hover .icon {
fill: currentColor;
color: #ff9600;
}
`)
}
})()