Greasy Fork

Greasy Fork is available in English.

高亮个别用户的弹幕

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

目前为 2021-02-06 提交的版本。查看 最新版本

// ==UserScript==
// @name         高亮个别用户的弹幕
// @namespace    http://tampermonkey.net/
// @version      0.6.7
// @description  高亮个别用户的弹幕, 有时候找一些特殊人物(其他直播主出现在直播房间)用
// @author       Eric Lam
// @include      /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+\??.*/
// @include      https://eric2788.github.io/scriptsettings/highlight-user
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/pako.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js
// @require      http://greasyfork.icu/scripts/417560-bliveproxy/code/bliveproxy.js
// @grant        GM.xmlHttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        unsafeWindow
// @website      https://eric2788.github.io/scriptsettings/highlight-user
// ==/UserScript==

(function() {
    'use strict';

    const defaultSettings = {
       highlightUsers: [
        396024008, // 日本兄贵
        604890122, // 日本兄贵
        623441609, // 凤玲天天 (DD)
        1618670884, // 日本兄贵
        406805563, // 乙女音
        2299184, // 古守
        198297, // 冰糖
        1576121 // paryi
       ],
       settings: {
         color: '#FFFF00',
         opacity: 1.0,
         playAudio: true,
         sound: '966q9uq4'
       }
    }

    const { highlightUsers, settings } = GM_getValue('settings', defaultSettings)

    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 = new Audio(`https://mobcup.net/d/${settings.sound}/mp3`)
        const highlights = new Set()
        toastr.options = {
            "closeButton": false,
            "debug": false,
            "newestOnTop": true,
            "progressBar": true,
            "positionClass": "toast-bottom-left",
            "preventDuplicates": false,
            "onclick": null,
            "showDuration": "300",
            "hideDuration": "1000",
            "timeOut": "5000",
            "extendedTimeOut": "1000",
            "showEasing": "swing",
            "hideEasing": "linear",
            "showMethod": "fadeIn",
            "hideMethod": "fadeOut"
        }

        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], config)
        }

        async function launch(){
            while(!unsafeWindow.bliveproxy){
                console.log('cannot not find bliveproxy, wait one second')
                await sleep(1000)
            }
            unsafeWindow.bliveproxy.addCommandHandler('DANMU_MSG', command => {
                const userId = command.info[2][0]
                console.debug(userId)
                if (!highlightUsers.includes(userId)) return
                console.debug('detected highlighted user: '+userId)
                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])
            })
            unsafeWindow.bliveproxy.addCommandHandler('INTERACT_WORD', ({data}) => {
                const {uid, uname} = data
                if (!highlightUsers.includes(uid)) return
                console.debug(`name: ${uname} has enter this live room`)
                toastr.info(`你所关注的用户 ${uname} 已进入此直播间。`, `噔噔咚!`)
                if (settings.playAudio) audio.play()
            })
        }

        launch().catch(console.error)
    }else if (location.origin == "https://eric2788.github.io"){
        const $ = mdui.$
        async function appendUser(userId){
            if ($(`#${userId}`).length > 0){
               mdui.alert('该用户已在列表内')
               return false
            }
            try {
                const { name, face } = await webRequest(`https://api.bilibili.com/x/space/acc/info?mid=${userId}&jsonp=jsonp`)
                GM_setValue(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: `无法索取用户资讯: ${err.message}`, 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}" alt="N"/></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())
            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)){
               mdui.snackbar('新增成功')
               e.target.value = ''
            }
        });

        $('#save-btn').on('click', e => {
            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.addEventListener('canplaythrough', () => audio.play())
        })

        async function initializeSettings(){
            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)
            $('#list-loading').hide()
        }

        initializeSettings().catch(console.error)

        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()
            }
            return { highlightUsers: [...users], settings }
        }

    }
})();

async function webRequest(url){
    const data = await GM.xmlHttpRequest({
            method: "GET",
            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))
}