Greasy Fork

Greasy Fork is available in English.

高亮个别用户的弹幕

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

当前为 2021-02-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         高亮个别用户的弹幕
// @namespace    http://tampermonkey.net/
// @version      0.6.5
// @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 highlightUsers = []
            $('#hightlight-users').find('.mdui-checkbox > input').map((i, e) => parseInt($(e).attr('id'))).filter((i,e) => !!e).each((i,e) => highlightUsers.push(e))
            const settings = {
                opacity: $('#opacity')[0].valueAsNumber,
                color: $('#color').val(),
                playAudio: $('#play-audio').prop('checked'),
                sound: $('input[name=sound]:checked').val()
            }
            return { highlightUsers, 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))
}