高亮个别用户的弹幕

高亮个别用户的弹幕, 有时候找一些特殊人物(其他直播主出现在直播房间)用

目前为 2022-02-26 提交的版本。查看 最新版本

// ==UserScript==
// @name         高亮个别用户的弹幕
// @namespace    http://tampermonkey.net/
// @version      0.7.14
// @description  高亮个别用户的弹幕, 有时候找一些特殊人物(其他直播主出现在直播房间)用
// @author       Eric Lam
// @include      /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @include      /https?:\/\/eric2788\.github\.io\/scriptsettings\/highlight-user(\/)?/
// @include      /https?:\/\/eric2788\.neeemooo\.com\/scriptsettings\/highlight-user(\/)?/
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/gh/google/brotli@5692e422da6af1e991f9182345d58df87866bc5e/js/decode.js
// @require      https://cdn.jsdelivr.net/gh/eric2788/bliveproxy@d66adfa34cbf41db3d313f49d0814e47cb3b6c4c/bliveproxy-unsafe.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js
// @grant        GM.xmlHttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// @run-at       document-start
// @connect      api.bilibili.com
// @website      https://eric2788.github.io/scriptsettings/highlight-user
// @homepage     https://eric2788.neeemooo.com/scriptsettings/highlight-user
// ==/UserScript==

(async function() {
    'use strict';

    const defaultSettings = {
       highlightUsers: [
        396024008, // 日本兄贵
        604890122, // 日本兄贵
        623441609, // 凤玲天天 (DD)
        1618670884, // 日本兄贵
        406805563, // 乙女音
        2299184, // 古守
        198297, // 冰糖
        1576121 // paryi
       ],
       settings: {
         color: '#FFFF00',
         opacity: 1.0,
         playAudio: false,
         sound: '966q9uq4',
         playAudioDanmu: false,
         sound_danmu: 'ma2g204k',
         join_notify_duration: 5000,
         join_notify_position: "bottom-left",
         volume: {
            danmu: 1.0,
            join: 1.0
         }
       }
    }

    const storage = GM_getValue('settings', defaultSettings)
    const { highlightUsers, settings: currentSettings } = storage
    const settings = { ...defaultSettings.settings, ...currentSettings }
    console.debug(highlightUsers)
    console.debug(settings)

    if (location.origin == 'https://live.bilibili.com'){
        console.log('using highlight filter')

        function hexToNum(color){
            const hex = color.substr(1)
            return parseInt(hex, 16)
        }

        $(document.head).append(`<link href="https://cdn.jsdelivr.net/gh/CodeSeven/[email protected]/build/toastr.min.css" rel="stylesheet" />`)

        const audio = {
            join: new Audio(`https://mobcup.net/d/${settings.sound}/mp3`),
            danmu: new Audio(`https://mobcup.net/d/${settings.sound_danmu}/mp3`)
        }
        audio.join.volume = settings.volume.join
        audio.danmu.volume = settings.volume.danmu
        const highlights = new Set()
        toastr.options = {
            "closeButton": false,
            "debug": false,
            "newestOnTop": true,
            "progressBar": true,
            "positionClass": `toast-${settings.join_notify_position}`,
            "preventDuplicates": false,
            "onclick": null,
            "showDuration": "300",
            "hideDuration": "1000",
            "timeOut": `${settings.join_notify_duration}`,
            "extendedTimeOut": "1000",
            "showEasing": "swing",
            "hideEasing": "linear",
            "showMethod": "fadeIn",
            "hideMethod": "fadeOut"
        }

        const elements = ['.bilibili-live-player-video-danmaku', '.danmaku-item-container']

        async function launch(){
            console.debug('launching highlight filter...')
            while(!unsafeWindow.bliveproxy){
                console.log('cannot not find bliveproxy, wait one second')
                await sleep(1000)
            }
            while(!elements.some(s => $(s).length > 0)){
                console.log('cannot not find element, wait one second')
                await sleep(1000)
            }

            function handleUserEnter(uid, uname){
               console.debug(`user enter: ${uid} (${uname})`)
                if (!highlightUsers.includes(uid)) return
                console.log(`name: ${uname} has enter this live room`)
                toastr.info(`你所关注的用户 ${uname} 已进入此直播间。`, `噔噔咚!`)
                if (settings.playAudio) audio.join.play()
            }

            console.debug('bliveproxy injected.')
            unsafeWindow.bliveproxy.addCommandHandler('DANMU_MSG', command => {
                const userId = command.info[2][0]
                console.debug(`user send danmu: ${userId}`)
                if (!highlightUsers.includes(userId)) return
                console.debug('detected highlighted user: '+userId)
                command.info[0][13] = "{}" // 把那些圖片彈幕打回原形
                if (settings.color) {
                    command.info[0][3] = hexToNum(settings.color)
                }
                command.info[1] += `(${command.info[2][1]})`
                console.debug(`converted danmaku: ${command.info[1]}`)
                highlights.add(command.info[1])
                if (settings.playAudioDanmu) audio.danmu.play()
            })
            unsafeWindow.bliveproxy.addCommandHandler('INTERACT_WORD', ({data}) => {
                const {uid, uname} = data
                handleUserEnter(uid, uname)
            })
            unsafeWindow.bliveproxy.addCommandHandler('ENTRY_EFFECT', ({data}) => {
                const {uid, copy_writing_v2, copy_writing} = data
                const title = copy_writing || copy_writing_v2
                const username = /^欢迎舰长 <%(?<name>.+)?%> 进入直播间$/g.exec(title)?.groups?.name ?? /^欢迎 <%(?<name>.+)?%> 进入直播间$/g.exec(title)?.groups?.name
                console.debug(uid, username, title)
                if (!username) {
                    console.warn(`未知舰长名字: ${uid} (parsing ${title})`)
                    return
                }
                handleUserEnter(uid, username)
            })
            if (settings.opacity){
                const config = { attributes: false, childList: true, subtree: true }
                function danmakuCheckCallback(mutationsList){
                    for(const mu of mutationsList){
                        for (const node of mu.addedNodes){
                            const danmaku = node?.innerText?.trim() ?? node?.data?.trim()
                            if (danmaku === undefined || danmaku === '') continue
                            if (!highlights.has(danmaku)) continue
                            console.debug('highlighting danmaku: '+danmaku)
                            const n = node.innerText !== undefined ? node : node.parentElement
                            const jimaku = $(n)
                            jimaku.css('opacity', `${settings.opacity}`)
                            highlights.delete(danmaku)
                        }
                    }
                }
                const danmakuObserver = new MutationObserver((mu, obs) => danmakuCheckCallback(mu))
                danmakuObserver.observe($('.bilibili-live-player-video-danmaku')[0] || $('.danmaku-item-container')[0], config)
            }
        }

        await launch()
    }else if (["https://eric2788.github.io", "https://eric2788.neeemooo.com"].includes(location.origin)){
        while(!unsafeWindow.mdui){
           console.debug('cannot find mdui, wait one second')
           await sleep(1000)
        }
        const $ = mdui.$
        async function appendUser(userId){
            if ($(`#${userId}`).length > 0){
               mdui.alert('该用户已在列表内')
               return false
            }
            try {
                const lastUpdate = GM_getValue('last.update', new Date())
                const haveData = GM_getValue(userId, null) != null
                const today = new Date()
                if (!haveData || Math.abs(today - lastUpdate) > (86400 * 1000 * 7)) {
                  console.log('cache outdated, updating user info...')
                  const { name, face } = await webRequest(`https://api.bilibili.com/x/space/acc/info?mid=${userId}&jsonp=jsonp`)
                  GM_setValue(userId, {name, face})
                  GM_setValue('last.update', new Date())
                  console.log('user info updated and saved to cache.')
                }else{
                  console.log('loading user info from cache.')
                }
                const {name, face} = GM_getValue(userId, {name: `无法索取用户资讯`, face: ''})
                $('#hightlight-users').append(`
                    <label class="mdui-list-item mdui-ripple">
                        <div class="mdui-checkbox">
                            <input type="checkbox" id="${userId}"/>
                            <i class="mdui-checkbox-icon"></i>
                        </div>
                        <div class="mdui-list-item-avatar"><img src="${face}"/></div>
                        <div class="mdui-list-item-content">${name} (${userId})</div>
                   </label>
                `)
                return true;
            }catch(err){
                console.warn(err)
                if (err.code == -412){
                  const {name, face} = GM_getValue(userId, {name: `无法索取用户资讯`, face: ''})
                  $('#hightlight-users').append(`
                    <label class="mdui-list-item mdui-ripple">
                        <div class="mdui-checkbox">
                            <input type="checkbox" id="${userId}"/>
                            <i class="mdui-checkbox-icon"></i>
                        </div>
                        <div class="mdui-list-item-avatar"><img src="${face}"/></div>
                        <div class="mdui-list-item-content">${name} (${userId})</div>
                   </label>
                  `)
                  return true;
                }else{
                  mdui.alert(`无法索取 ${userId} 的用户资讯: ${err.message}`)
                  return false;
                }
            }finally{
              $(`#${userId}`).on('change', e => {
                if (getTicked().length > 0) {
                    $('#delete-btn').show()
                } else {
                    $('#delete-btn').hide()
                }
              })
            }
       }


        function getTicked() {
            return $('#hightlight-users').find('.mdui-checkbox > input').filter((i, e) => $(e).prop('checked')).map((i, e) => $(e).attr('id'))
        }

        $('#delete-btn').on('click', e => {
            getTicked().each((i, id) => $(`#${id}`).parents('.mdui-list-item').remove())
            GM_setValue('settings', getSettings())
            mdui.snackbar('删除并保存成功')
            $('#delete-btn').hide()
        })

        $('#user-add').on('keypress', async (e) => {
            if (e.which != 13) return
            if (!$('#user-add')[0].checkValidity()) return
            if (await appendUser(e.target.value)){
               GM_setValue('settings', getSettings())
               mdui.snackbar('新增并保存成功')
               e.target.value = ''
            }
        });

        $('#save-btn').on('click', e => {
            if (!$('form')[0].checkValidity()){
               mdui.snackbar('保存失败,请检查格式或漏填')
               return
            }
            GM_setValue('settings', getSettings())
            mdui.snackbar('保存成功')
        })

        $('#try-listen').on('click', () => {
           const selected = $('input[name=sound]:checked').val()
           const audio = new Audio(`https://mobcup.net/d/${selected}/mp3`)
           audio.volume = parseVolume('#volume-join')
           audio.addEventListener('canplaythrough', () => audio.play())
        })

        $('#try-listen-danmu').on('click', () => {
           const selected = $('input[name=sound-danmu]:checked').val()
           const audio = new Audio(`https://mobcup.net/d/${selected}/mp3`)
           audio.volume = parseVolume('#volume-danmu')
           audio.addEventListener('canplaythrough', () => audio.play())
        })

        const joinNotifyPosSelect = new mdui.Select('#join-notify-position', {position: 'bottom'})


        $('#import-setting').on('click', async () => {
           try {
             const area = $('#setting-area').val()
             const {highlightUsers, settings: currentSettings } = JSON.parse(area)
             const settings = { ...defaultSettings.settings, ...currentSettings }
             $('.mdui-list-item').remove() // clear old data
             await initializeSettings({highlightUsers, settings})
             mdui.snackbar('设定档导入成功,请记得按下保存')
             $('#setting-area').val('')
           }catch(err){
             console.error(err)
             mdui.snackbar('设定档导入失败,请检查格式有没有错误')
           }
        })

        $('#export-setting').on('click', () => {
             const area = JSON.stringify(getSettings())
             $('#setting-area').val(area)
             const text = $('#setting-area')[0]
             text.select();
             text.setSelectionRange(0, 99999);
             document.execCommand("copy")
             mdui.snackbar('设定档已导出并复制成功')
             $('#setting-area').val('')
        })

        async function initializeSettings({highlightUsers, settings}){
            await Promise.all(highlightUsers.map((id) => appendUser(id)))
            $('#opacity')[0].valueAsNumber = settings.opacity
            $('#color').val(settings.color)
            $('#color-picker').val(settings.color)
            $('#color-picker-btn').css('color', settings.color)
            $('#play-audio').prop('checked', settings.playAudio)
            $('input[name=sound]').filter((i, e) => $(e).val() == settings.sound).prop('checked', true)
            $('#play-audio-danmu').prop('checked', settings.playAudioDanmu)
            $('input[name=sound-danmu]').filter((i, e) => $(e).val() == settings.sound_danmu).prop('checked', true)
            $('#join-notify-duration')[0].valueAsNumber = settings.join_notify_duration
            $('#join-notify-position').val(settings.join_notify_position)
            $('#volume-danmu').val(settings.volume.danmu * 100)
            $('#volume-join').val(settings.volume.join * 100)
            mdui.updateSliders()
            joinNotifyPosSelect.handleUpdate()
            $('#list-loading').hide()
        }

        await initializeSettings({highlightUsers, settings})

        function getSettings(){
            const users = new Set()
            $('#hightlight-users').find('.mdui-checkbox > input').map((i, e) => parseInt($(e).attr('id'))).filter((i,e) => !!e).each((i,e) => users.add(e))
            const settings = {
                opacity: $('#opacity')[0].valueAsNumber,
                color: $('#color')[0].checkValidity() ? $('#color').val() : '',
                playAudio: $('#play-audio').prop('checked'),
                sound: $('input[name=sound]:checked').val(),
                sound_danmu: $('input[name=sound-danmu]:checked').val(),
                playAudioDanmu: $('#play-audio-danmu').prop('checked'),
                join_notify_duration: $('#join-notify-duration')[0].valueAsNumber,
                join_notify_position: $('#join-notify-position').val(),
                volume: {
                   danmu: parseVolume('#volume-danmu'),
                   join: parseVolume('#volume-join')
                }
            }
            return { highlightUsers: [...users], settings }
        }

        function parseVolume(element){
            const val = $(element)[0].value
            if (val == 0) return 0.0
            return parseFloat((val / 100).toFixed(2)) || 1.0
        }

    }
})().catch(console.error);

async function webRequest(url){
    const data = await GM.xmlHttpRequest({
            method: "GET",
            headers: {
                'Content-type' : 'application/json',
                'Referer': 'https://www.bilibili.com',
                'Origin': 'https://www.bilibili.com'
            },
            url
          })
    const res = JSON.parse(data.response)
    if (res.code !== 0) throw res
    return res.data
}

async function sleep(ms){
   return new Promise((res,) => setTimeout(res,ms))
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址