bilibili便捷工具

为b站新增鼠标滑过快速获取未读消息、直播页面仿视频快捷键等等便捷操作

当前为 2023-08-28 提交的版本,查看 最新版本

// ==UserScript==
// @license     AGPL License
// @name        bilibili便捷工具
// @namespace   Violentmonkey Scripts
// @match       https://www.bilibili.com/*
// @match       https://message.bilibili.com/*
// @match       https://t.bilibili.com/*
// @match       https://search.bilibili.com/*
// @match       https://live.bilibili.com/*
// @match       https://space.bilibili.com/*
// @grant       GM_xmlhttpRequest
// @run-at      document-start
// @noframes
// @version     1.4
// @author      n1nja88888
// @description 为b站新增鼠标滑过快速获取未读消息、直播页面仿视频快捷键等等便捷操作
// ==/UserScript==
'use strict'
console.log('n1nja88888 creates this world!')
// 覆盖添加监听器函数
!function() {
    Element.prototype._addEventListener = Element.prototype.addEventListener
    Element.prototype._removeEventListener = Element.prototype.removeEventListener

    Element.prototype.addEventListener = function(type, listener, useCapture = false) {
        this._addEventListener(type, listener, useCapture)

        if (!this.eventListenerList) this.eventListenerList = {}
        if (!this.eventListenerList[type]) this.eventListenerList[type] = []

        this.eventListenerList[type].push({ type, listener, useCapture })
    }
    Element.prototype.removeEventListener = function(type, listener, useCapture = false) {
        this._removeEventListener(type, listener, useCapture)

        if (!this.eventListenerList) this.eventListenerList = {}
        if (!this.eventListenerList[type]) this.eventListenerList[type] = []
        for (let i = 0; i < this.eventListenerList[type].length; i++) {
            if (this.eventListenerList[type][i].listener === listener && this.eventListenerList[type][i].useCapture === useCapture) {
                this.eventListenerList[type].splice(i, 1)
                break
            }
        }

        if (this.eventListenerList[type].length == 0) delete this.eventListenerList[type]
    }
    Element.prototype.getEventListeners = function(type) {
        if (!this.eventListenerList) this.eventListenerList = {}

        if (!type) return this.eventListenerList
        return this.eventListenerList[type]
    }
    Element.prototype.clearEventListeners = function(a) {
        if (!this.eventListenerList)
            this.eventListenerList = {}
        if (!a) {
            for (let x in (this.getEventListeners())) this.clearEventListeners(x)
            return
        }
        const el = this.getEventListeners(a);
        if (!el)
            return
        for (let i = el.length - 1; i >= 0; --i) {
            let ev = el[i];
            this.removeEventListener(a, ev.listener, ev.useCapture)
        }
    }
}()
// 判断网页活跃元素
function isActive(eleSet) {
    //  不能为null等
    if (!eleSet) {
        return false
    }
    //  判断是否是集合
    if (!!eleSet[0]) {
        for (let i = 0; i < eleSet.length; i++) {
            if (document.activeElement === eleSet[i]) {
                return true
            }
        }
        return false
    } else {
        return document.activeElement === eleSet
    }
}
// 判断是否是组合键
function isCombKey(e) {
    return e.ctrlKey || e.shiftkey || e.altKey || e.metaKey
}
// 轮询获取网页元素
async function getEleAsync(selector) {
    return new Promise(res => {
        const interval = setInterval(() => {
            const ele = document.querySelector(selector)
            if (ele) {
                clearInterval(interval)
                res(ele)
            }
        }, 0.5e3)
    })
}
// 鼠标查看未读消息
async function unreadMsg() {
    // 查询未读消息
    function query(msg) {
        // 发送异步查看回复请求
        GM_xmlhttpRequest({
            url: 'https:// api.vc.bilibili.com/session_svr/v1/session_svr/single_unread?build=0&mobi_app=web&unread_type=0',
            responseType: 'json',
            onload(dataChat) {
                const chat = dataChat.response.data
                let chatCount = 0
                for (let prop in chat) {
                    chatCount += chat[prop]
                }
                GM_xmlhttpRequest({
                    url: 'https:// api.bilibili.com/x/msgfeed/unread?build=0&mobi_app=web',
                    responseType: 'json',
                    onload(data) {
                        // 未读消息对象
                        const unread = data.response.data
                        // 计算未读消息总数
                        let count = 0 - unread.up
                        for (let prop in unread) {
                            count += unread[prop]
                        }
                        count += chatCount
                        let div = null
                        // 未读消息样式
                        div = document.getElementsByClassName('red-num--message')[0]
                        // 不为零直接添加,为零的话则判断之前是否已经存在未读消息样式,有的话则移除改元素,即 清零消息
                        if (!!count) {
                            if (!div) {
                                div = document.createElement('div')
                                div.className = 'red-num--message'
                                div.textContent = count
                                msg.append(div)
                            } else {
                                div.textContent = count
                            }
                        } else {
                            if (div) {
                                msg.removeChild(div)
                            }
                        }
                        // 按消息栏顺序储存各类未读消息
                        let counts = [unread.reply, unread.at, unread.like, unread.sys_msg, chatCount]
                        for (let i = 0; i < 5; i++) {
                            // 获取对应一栏
                            let ele = document.getElementsByClassName('message-inner-list__item')[i]
                            // 未读消息样式,这个需要在对应栏去寻找
                            let span = ele.getElementsByClassName('message-inner-list__item--num')[0]
                            // 不为零直接添加,为零的话则判断之前是否已经存在未读消息样式,有的话则移除改元素,即 清零消息
                            if (counts[i]) {
                                if (!span) {
                                    span = document.createElement('span')
                                    span.className = 'message-inner-list__item--num'
                                    span.textContent = counts[i]
                                    ele.append(span)
                                } else {
                                    span.textContent = counts[i]
                                }
                            } else {
                                if (span) {
                                    ele.removeChild(span)
                                }
                            }
                        }
                    }
                })
            }
        })
    }
    const msg = await getEleAsync('.right-entry--message')
    msg.addEventListener('mouseenter', () => query(msg))
}
// 快捷键
async function shortcuts() {
    // t键网页全屏
    // 网页全屏按钮
    const btn = await getEleAsync('.bpx-player-ctrl-web')
    // 获得网页全屏的函数
    const webFullScreen = btn.getEventListeners().click[0].listener
    // 添加按键按下事件监听器
    document.addEventListener('keydown', (e) => {
        if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea'))) {
            if ('t' === e.key || 'T' === e.key) {
                webFullScreen()
            }
        }
    })
}
// 直播加强 f全屏 t网页全屏
async function liveAid() {
    function getFuncs(player) {
        player.dispatchEvent(new Event('mousemove'))
        const funcs = [
            document.querySelectorAll('.right-area .tip-wrap .icon')[0].getEventListeners().click[0].listener, // 全屏函数
            document.querySelectorAll('.right-area .tip-wrap .icon')[1].getEventListeners().click[0].listener, // 网页全屏
            document.querySelectorAll('.right-area .tip-wrap .icon')[2].getEventListeners().click[0].listener, // 弹幕
            document.querySelectorAll('.left-area .icon')[0].getEventListeners().click[0].listener // 播放
        ]
        player.dispatchEvent(new Event('mouseleave'))
        return funcs
    }
    // 获得播放器
    let player = document.querySelector('#live-player')
    let funcs = getFuncs(player)
    // 移除聊天框原本的keydown,另外这里需要同步执行
    let chatBox = await getEleAsync('#chat-control-panel-vm textarea')
    chatBox.removeEventListener('keydown', chatBox.getEventListeners().keydown[0].listener)
    // 添加按键按下事件监听器
    document.addEventListener('keydown', (e) => {
        //  只监听单按键事件
        if (isCombKey(e)) {
            return
        }
        switch (e.key) {
            // 快速定位到聊天框
            case 'Enter':
                if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea')) && !document.fullscreen) {
                    e.preventDefault()
                    chatBox.focus()
                }
                else if (isActive(chatBox)) {
                    e.preventDefault()
                    document.querySelector('#chat-control-panel-vm button').click()
                    chatBox.blur()
                }
                break
            case 'f':
            case 'F':
                if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea'))) {
                    funcs[0]()
                }
                break
            case 't':
            case 'T':
                if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea'))) {
                    funcs[1]()
                }
                break
            case 'd':
            case 'D':
                if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea'))) {
                    funcs[2]()
                }
                break
            case ' ':
                if (!isActive(document.getElementsByTagName('input')) && !isActive(document.getElementsByTagName('textarea'))) {
                    e.preventDefault()
                    funcs[3]()
                    const video = getEleAsync('video')
                    if (!document.querySelector('video').paused) {
                        // 有时候暂停会导致再启动时 会导致之前的函数全部失效 所以得重新赋值
                        funcs = getFuncs(player)
                        // 选择最高分辨率
                        const interval = setInterval(() => {
                            player.dispatchEvent(new Event('mousemove'))
                            const quality = document.querySelector('.quality-wrap')
                            quality.dispatchEvent(new Event('mouseenter'))
                            const list = quality.querySelectorAll('.list-it')
                            if (!!list[0]) {
                                if (!list[0].classList.value.match('selected'))
                                    list[0].click()
                                player.dispatchEvent(new Event('mouseleave'))
                                clearInterval(interval)
                            }
                        }, 0.5e3)
                    }
                }
                break
        }
    })
}
function main() {
    unreadMsg()
    // 只在视频播放页执行
    if (!!document.querySelector('video')) {
        shortcuts()
    }
    // 只在直播页面执行
    if (!!location.href.match('live')) {
        liveAid()
    }

}
const _onload = window.onload
window.onload = function() {
    if (!!_onload)
        _onload()
    main()
}