B站直播SC悬浮窗

将B站直播的SC部分展开显示到一个悬浮窗

目前为 2023-03-17 提交的版本。查看 最新版本

// ==UserScript==
// @name         B站直播SC悬浮窗
// @namespace    https://gitee.com/zhangsongqiang/userscript/
// @version      0.2
// @description  将B站直播的SC部分展开显示到一个悬浮窗
// @author       Zhangsq37
// @match        https://live.bilibili.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect      *
// @run-at    context-menu
// @noframes
// @license MIT
// ==/UserScript==

//div.chat-item danmaku-item superChat-card-detail

(function () {

    const dragToMove = (dragNode, moveNode) => {//(被拖动的Part,被移动的整体)
        dragNode.onmousedown = function (event) {
            event = event || window.event
            //获取鼠标按下时的坐标
            let x = event.clientX
            let y = event.clientY
            //获取鼠标按下时距离div边框的距离
            let ol = x - moveNode.offsetLeft
            let ot = y - moveNode.offsetTop
            //使用document的原因:防止用户拖拽速度过快导致元素的onmousemove事件失效,所以把onmousemove事件绑定在document上就能避免失效
            document.onmousemove = function (event) {
                event = event || window.event
                moveNode.style.left = event.clientX - ol + 'px'
                moveNode.style.top = event.clientY - ot + 'px'
            }
            //取消onmousemove事件和onmouseup事件
            document.onmouseup = function () {
                document.onmousemove = 'null'
                document.onmouseup = 'null'
            }
        }
    };

    const genrate_new_superchatItem = (sender_uname, sender_uid, send_content, superChatPrice, sender_fans_medal_label, sender_fans_medal_level) => {

        var sc_background_color = get_sc_background_color(superChatPrice);
        var sc_show_time = get_sc_show_time(superChatPrice);

        var div_superchatItem = document.createElement("div");
        div_superchatItem.className = "superchatItem";
        div_superchatItem.style.backgroundColor = sc_background_color;

        var div_sc_title = document.createElement("div");
        div_sc_title.className = "sc_title";

        var div_sc_sender = document.createElement("div");
        div_sc_sender.className = "sc_sender";

        var div_sender_fans_medal_label = document.createElement("div");
        div_sender_fans_medal_label.className = "sender_fans_medal_label";

        var div_sender_fans_medal_level = document.createElement("div");

        div_sender_fans_medal_level.className = "sender_fans_medal_level";


        if (sender_fans_medal_label) {
            var fans_medal_color = get_fans_medal_color(sender_fans_medal_level);
            div_sender_fans_medal_label.style.border = "1px solid " + fans_medal_color;
            div_sender_fans_medal_label.style.backgroundColor = fans_medal_color;
            div_sender_fans_medal_label.innerText = sender_fans_medal_label;

            div_sender_fans_medal_level.style.border = "1px solid " + fans_medal_color;
            div_sender_fans_medal_level.style.color = fans_medal_color;
            div_sender_fans_medal_level.innerText = sender_fans_medal_level;
        }
        else {
            div_sender_fans_medal_label.style.visibility = "hidden";
            div_sender_fans_medal_level.style.visibility = "hidden";

        }

        var div_sender_uname = document.createElement("div");
        div_sender_uname.className = "sender_uname";


        var a_uname = document.createElement("a");
        a_uname.href = "https://space.bilibili.com/" + sender_uid;
        a_uname.target = "_blank";
        a_uname.innerText = sender_uname;
        a_uname.title = sender_uname;

        div_sender_uname.appendChild(a_uname);
        div_sc_sender.appendChild(div_sender_fans_medal_label);
        div_sc_sender.appendChild(div_sender_fans_medal_level);
        div_sc_sender.appendChild(div_sender_uname);

        var div_sc_info = document.createElement("div");
        div_sc_info.className = "sc_info";

        var div_sc_sendtime = document.createElement("div");
        div_sc_sendtime.className = "sc_sendtime";
        div_sc_sendtime.setAttribute("send-time", (new Date()).getTime().toString());
        div_sc_sendtime.innerText = (new Date()).toLocaleTimeString();

        var div_sc_price = document.createElement("div");
        div_sc_price.className = "sc_price";
        div_sc_price.innerText = "¥" + superChatPrice;

        div_sc_info.appendChild(div_sc_sendtime);
        div_sc_info.appendChild(div_sc_price);

        div_sc_title.appendChild(div_sc_sender);
        div_sc_title.appendChild(div_sc_info);

        var div_sc_content = document.createElement("div");
        div_sc_content.className = "sc_content";
        div_sc_content.innerText = send_content;

        var div_superchat_progress = document.createElement("div");
        div_superchat_progress.className = "superchat_progress";

        var div_superchat_progress_value = document.createElement("div");
        div_superchat_progress_value.className = "superchat_progress_value";
        div_superchat_progress_value.setAttribute("show-time", sc_show_time.toString());
        div_superchat_progress_value.setAttribute("leave-time", sc_show_time.toString());

        div_superchat_progress.appendChild(div_superchat_progress_value);

        div_superchatItem.appendChild(div_sc_title);
        div_superchatItem.appendChild(div_sc_content);
        div_superchatItem.appendChild(div_superchat_progress);
        return div_superchatItem;
    }

    var sc_callback = (obj) => {
        var progress = obj.querySelector("div.superchat_progress>div.superchat_progress_value");
        const total_time = parseInt(progress.getAttribute("show-time"));
        console.log("show_time:", total_time);

        var timer_ = setInterval((timer) => {
            var leave_time = parseInt(progress.getAttribute("leave-time"));
            leave_time = leave_time - 1;
            if (leave_time == 0) {
                progress.style.width = "0%";
                obj.style.backgroundColor = "lightgray";
                clearInterval(timer_);
            }
            else {
                progress.setAttribute("leave-time", leave_time.toString());
                progress.style.width = (leave_time * 100 / total_time).toString() + "%";
            }
        }, 1000);

        console.log("set");
    }

    const get_fans_medal_color = (fans_medal_level) => {
        if ((fans_medal_level > 0) && (fans_medal_level <= 4)) {
            return "#5c968e";
        } else if ((fans_medal_level > 4) && (fans_medal_level <= 8)) {
            return "#5d7b9e";
        } else if ((fans_medal_level > 8) && (fans_medal_level <= 12)) {
            return "#8c7ca4";
        } else if ((fans_medal_level > 12) && (fans_medal_level <= 16)) {
            return "#bc6484";
        } else if ((fans_medal_level > 16) && (fans_medal_level <= 20)) {
            return "#c49c24";
        } else if ((fans_medal_level > 20) && (fans_medal_level <= 24)) {
            return "#2c6a61";
        } else if ((fans_medal_level > 24) && (fans_medal_level <= 28)) {
            return "#142464";
        } else if ((fans_medal_level > 28) && (fans_medal_level <= 32)) {
            return "#442474";
        } else if ((fans_medal_level > 32) && (fans_medal_level <= 36)) {
            return "#942347";
        } else if ((fans_medal_level > 36) && (fans_medal_level <= 40)) {
            return "#fc7424";
        };
    };

    const get_sc_background_color = (price) => {
        if ((price >= 30) && (price < 50)) {
            return "#2c64b4";
        } else if ((price >= 50) && (price < 100)) {
            return "#447c9c";
        } else if ((price >= 100) && (price < 500)) {
            return "#c89c24";
        } else if ((price >= 500) && (price < 1000)) {
            return "#e49444";
        } else if ((price >= 1000) && (price < 2000)) {
            return "#e44c4c";
        } else if (price >= 2000) {
            return "#ac1c34";
        } else {
            return "#ffffff";
        }
    };

    const get_sc_show_time = (price) => {
        if ((price >= 30) && (price < 50)) {
            return 60;
        } else if ((price >= 50) && (price < 100)) {
            return 120;
        } else if ((price >= 100) && (price < 500)) {
            return 300;
        } else if ((price >= 500) && (price < 1000)) {
            return 1800;
        } else if ((price >= 1000) && (price < 2000)) {
            return 3600;
        } else if (price >= 2000) {
            return 7200;
        } else {
            return 1;
        }
    };



    var ChatPanelContainer = document.querySelector("div#chat-history-list>div#chat-items");

    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
    const options = { childList: true };

    const styles_superchat_panel = `div#superChat_Display_Panel_CtrlBar {
        width: 100%;
        height: 20px;
        display: flex;
        flex-wrap: nowrap;
        justify-content: space-between;
    }

    div#superChat_Display_Panel {
        margin-bottom: 10px;
        overflow-y: scroll;
        min-height: 260px;
        height: calc(100% - 40px);
    }

    div#superChat_Display_Container {
        min-width: 400px;
        min-height: 300px;
        padding-bottom: 10px;
        resize: both;
        overflow-x: hidden;
        overflow-y: auto;

        position: absolute;

        top:150px;
        right: 0px;

    }`;

    const styles_superchat_items = `div.superchatItem {
        width: 85%;
        height: auto;
        border: 2px solid gray;
        border-radius: 10px;
        padding: 0%;
        margin: 5%;
        margin-right: 10%;

        transition: background-color 2s;
    }

    div.superchatItem>div.sc_title {
        width: 100%;
        padding-top: 4px;
        padding-bottom: 4px;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        background-color: #ffffffe0;

        border-top-left-radius: 10px;
        border-top-right-radius: 10px;
    }

    div.superchatItem>div.sc_title>div.sc_sender {
        min-width: 60%;
        max-width: 80%;
        height: 16px;
        display: flex;
        padding-left: 5px;

        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    div.superchatItem>div.sc_title>div.sc_sender>div.sender_fans_medal_label {
        border-top-left-radius: 6px;
        border-bottom-left-radius: 6px;
        padding-left: 4px;
        padding-right: 4px;
        padding-bottom: 4px;
        /*text-align: center;
        text-justify: auto;*/
        color: white;
    }

    div.superchatItem>div.sc_title>div.sc_sender>div.sender_fans_medal_level {
        border-top-right-radius: 6px;
        border-bottom-right-radius: 6px;
        padding-left: 4px;
        padding-right: 4px;
        /*text-align: center*/
        ;
    }

    div.superchatItem>div.sc_title>div.sc_sender>div.sender_uname>a {
        text-decoration: none;
        padding-left: 10px;
        color: #a70404bd;

    }

    div.superchatItem>div.sc_title>div.sc_info {
        width: 100%;
        display: flex;
        flex-wrap: nowrap;
        justify-content: flex-end;
    }

    div.superchatItem>div.sc_title>div.sc_info>div.sc_sendtime {
        color: #000000ae;
        font-weight: bold;
        padding-left: 20px;
        padding-right: 20px;
        display: inline-block;
    }

    div.superchatItem>div.sc_title>div.sc_info>div.sc_price {
        color: #00000050;
        font-weight: bold;
        padding-right: 5px;
        display: inline-block;
    }


    div.superchatItem>div.sc_content {
        padding: 5px;
        border-bottom-left-radius: 10px;
        border-bottom-right-radius: 10px;
        word-break: break-all;
        /* 只对英文起作用,以字母作为换行依据 */
        word-wrap: break-word;
        /* 只对英文起作用,以单词作为换行依据 */
        white-space: pre-wrap;
        /* 只对中文起作用,强制换行 */
    }

    div.superchatItem>div.superchat_progress {
        width: 100%;
        height: 3px;
        border-radius: 2px;
    }

    div.superchatItem>div.superchat_progress>div.superchat_progress_value {
        width: 100%;
        height: 3px;
        border-radius: 1px;
        background-color: #00000060;
        transition: width 1s linear;
    }`;

    // childList:子节点的变动(指新增,删除或者更改)。
    // attributes:属性的变动。
    // characterData:节点内容或节点文本的变动。
    // subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
    // attributeOldValue:布尔值,表示观察attributes变动时,是否需要记录变动前的属性值。
    // characterDataOldValue:布尔值,表示观察characterData变动时,是否需要记录变动前的值。
    // attributeFilter:数组,表示需要观察的特定属性(比如[‘class’,‘src’])

    window.onload = window.onresize = () => {
        var devicewidth = document.documentElement.clientWidth;
        var scale = devicewidth / 1920;
        document.body.style.zoom = scale;
    };

    if (ChatPanelContainer == null) {
        console.warn("没有发现弹幕节点");
    }
    else {

        if (document.querySelector("div#superChat_Display_Panel")) {
            console.warn("本脚本仅已经执行!!!");
        }
        else {
            console.warn("Observe start");

            GM_addStyle(styles_superchat_panel);
            GM_addStyle(styles_superchat_items);

            var superChat_Display_Panel = document.createElement("div");
            superChat_Display_Panel.id = "superChat_Display_Panel";
            superChat_Display_Panel.style.width = "100%";
            superChat_Display_Panel.style.border = "1px solid gray";

            var checkbox_superchat_auto_scroll = document.createElement("input");
            checkbox_superchat_auto_scroll.id = "checkbox_superchat_auto_scroll";
            checkbox_superchat_auto_scroll.type = "checkbox";

            var label_for_superchat_auto_scroll_heckbox = document.createElement("label");
            label_for_superchat_auto_scroll_heckbox.setAttribute("for", "checkbox_superchat_auto_scroll");
            label_for_superchat_auto_scroll_heckbox.innerText = "SC自动滚动";

            checkbox_superchat_auto_scroll.onchange = () => {
                if (checkbox_superchat_auto_scroll.checked) {
                    superChat_Display_Panel.scrollTo(0, 999999999);
                }
            };

            var div_checkbox_superchat_auto_scroll = document.createElement("div");
            div_checkbox_superchat_auto_scroll.className = "div_checkbox_superchat_auto_scroll";
            div_checkbox_superchat_auto_scroll.style.paddingLeft = "10px";
            div_checkbox_superchat_auto_scroll.appendChild(checkbox_superchat_auto_scroll);
            div_checkbox_superchat_auto_scroll.appendChild(label_for_superchat_auto_scroll_heckbox);

            var superChat_Display_Panel_CtrlBar = document.createElement("div");
            superChat_Display_Panel_CtrlBar.id = "superChat_Display_Panel_CtrlBar";
            superChat_Display_Panel_CtrlBar.appendChild(div_checkbox_superchat_auto_scroll);

            var superChat_Display_Container = document.createElement("div");
            superChat_Display_Container.id = "superChat_Display_Container";
            superChat_Display_Container.style.width = "400px";
            superChat_Display_Container.style.height = "220px";
            superChat_Display_Container.style.border = "1px solid black";
            superChat_Display_Container.style.borderRadius = "10px";
            superChat_Display_Container.style.zIndex = "999999999";
            superChat_Display_Container.appendChild(superChat_Display_Panel_CtrlBar);
            superChat_Display_Container.appendChild(superChat_Display_Panel);

            document.body.appendChild(superChat_Display_Container);
            dragToMove(superChat_Display_Panel_CtrlBar, superChat_Display_Container);

            const Parse_and_Add_SuperchatNode = (superchatNode) => {
                var sender_uname = superchatNode.querySelector("div.card-item-middle-top>div.card-item-middle-top-right>div.name").innerText;
                var sender_uid = superchatNode.getAttribute("data-uid");
                var send_content = superchatNode.querySelector("div.card-item-middle-bottom>div.input-contain>span.text").innerText;
                var superChatPrice = parseInt(superchatNode.querySelector("div.card-item-top-right").innerText.split("0电池")[0]);
                var sender_fans_medal_label = superchatNode.querySelector("span.fans-medal-content");
                var sender_fans_medal_level = superchatNode.querySelector("div.fans-medal-level");
                if (sender_fans_medal_label) {
                    sender_fans_medal_label = sender_fans_medal_label.innerText;
                    sender_fans_medal_level = sender_fans_medal_level.innerText;
                }
                var new_superchatItem = genrate_new_superchatItem(sender_uname, sender_uid, send_content, superChatPrice, sender_fans_medal_label, sender_fans_medal_level);
                sc_callback(new_superchatItem);
                superChat_Display_Panel.appendChild(new_superchatItem);
                if (checkbox_superchat_auto_scroll.checked) {
                    superChat_Display_Panel.scrollTo(0, 999999999);
                }
            };

            const danmuku_callback = (mutationRecoards, observer) => {
                mutationRecoards.forEach(mutationRecoard => {
                    if (mutationRecoard.type == "childList") {
                        mutationRecoard.addedNodes.forEach(
                            addnode => {
                                if (addnode.classList.contains("superChat-card-detail")) {//是SC
                                    Parse_and_Add_SuperchatNode(addnode);
                                    console.info(addnode);
                                }
                            }
                        );
                        mutationRecoard.removedNodes.forEach(
                            rmnode => {
                                console.error(rmnode);
                            }
                        );
                    }
                    else {
                        console.error(mutationRecoard);
                    }
                });
            }

            const chat_mutation = new MutationObserver(danmuku_callback);
            chat_mutation.observe(ChatPanelContainer, options);
        }
    }
})();

QingJ © 2025

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