// ==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/jquery@3.5.1/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/pako@1.0.10/dist/pako.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js // @require https://greasyfork.org/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 // @downloadURL none // ==/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(``) 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(` `) return true; }catch(err){ console.warn(err) if (err.code == -412){ const {name, face} = GM_getValue(userId, {name: `无法索取用户资讯: ${err.message}`, face: ''}) $('#hightlight-users').append(` `) 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)) }