Greasy Fork is available in English.

Bilibili 动态页显示当前所有直播

显示当前所有直播

// ==UserScript==
// @name        Bilibili 动态页显示当前所有直播
// @description 显示当前所有直播
// @version     2.3.1
// @author      Myitian
// @license     MIT
// @namespace   myitian.bili.tPage-showMoreLives
// @match       t.bilibili.com/*
// @icon        https://www.bilibili.com/favicon.ico
// @grant       none
// ==/UserScript==

/**
 * 获取 JSON
 * @param {string | URL} url
 * @returns {Promise<XMLHttpRequest>}
 */
function getJSON(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.withCredentials = true;
        xhr.responseType = 'json';
        xhr.onload = function () {
            if (xhr.status == 200) {
                resolve(xhr);
            } else {
                reject(xhr);
            }
        };
        xhr.send();
    });
};

/**
 * 打开面板
 * @param {Event} event
 */
function moreBtn(event) {
    // 基底div
    var base = document.createElement('div');
    base.className = 'sml-base'
    base.id = 'sml-base';

    // 样式style
    var smlstyle = document.createElement('style');
    var textNode = document.createTextNode(`
    .sml-mask {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #000;
        filter: alpha(opacity=65);
        -ms-filter: "alpha(opacity=65)";
        opacity: .65;
        z-index: 10000;
    }

    .sml-mainbox {
        position: fixed;
        top: 50%;
        left: 50%;
        --width: 1000px;
        --height: 666px;
        width: var(--width);
        height: var(--height);
        margin-left: calc(var(--width) / -2);
        margin-top: calc(var(--height) / -2);
        border-radius: 4px;
        background-color: #fff;
        z-index: 10001;
    }

    .sml-livepanel {
        height: calc(100% - 58px);
        width: 100%;
        overflow: auto;
    }

    @media screen and (max-width:1030px) {
        .sml-mainbox {
            --width: 756px;
        }
    }

    @media screen and (max-width:786px) {
        .sml-mainbox {
            --width: 512px;
        }
    }

    @media screen and (max-width:542px) {
        .sml-mainbox {
            --width: 268px;
        }
    }

    @media screen and (max-height:690px) {
        .sml-mainbox {
            --height: 590px;
        }
    }

    @media screen and (max-height:614px) {
        .sml-mainbox {
            --height: 514px;
        }
    }

    @media screen and (max-height:538px) {
        .sml-mainbox {
            --height: 438px;
        }
    }

    @media screen and (max-height:462px) {
        .sml-mainbox {
            --height: 362px;
        }
    }

    @media screen and (max-height:386px) {
        .sml-mainbox {
            --height: 286px;
        }
    }

    @media screen and (max-height:310px) {
        .sml-mainbox {
            --height: 210px;
        }
    }

    @media screen and (max-height:234px) {
        .sml-mainbox {
            --height: 134px;
        }
    }

    .sml-title {
        position: relative;
        padding: 0 20px;
        height: 50px;
        line-height: 50px;
        font-size: 16px;
        color: #222;
        border-bottom: 1px solid #e5e9ef;
    }

    .sml-icon {
        position: absolute;
        line-height: 50px;
        width: 13px;
        height: 50px;
    }

    .sml-refresh {
        right: 60px;
        background: url("data:image/png; base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALBAMAAABbgmoVAAAAJFBMVEUAAACZoqqZoqqZoqqZoqqZoqqZoqqZoqqZoqqZoqqZoqqZoqpJ643QAAAAC3RSTlMAv4DPn2AwQN+PUFHSbdYAAABLSURBVAjXY9hQvHtzAQPDpi0JKZsYGHYHMDBEL2DYoRTAwGLAICg4gYFNgAEKxBgYJgoKMJgwMARpbGBYDVK5gYEJpG8DAwPIlA0ABXgUExBfrckAAAAASUVORK5CYII=") no-repeat 50%;
        cursor: pointer;
    }

    .sml-exit {
        right: 20px;
        background: url("data:image/png; base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANBAMAAACAxflPAAAAIVBMVEUAAACao6qZpa2bpKqapaqZo6qboqqapKuZoqqao6qZoqqZADmtAAAACnRSTlMA6kFUMM2Eat+9b+FOdgAAAENJREFUCNdjgAFOBSDhysDAJMTAwCIMZCsqMDgaMIAEQFyQQKEBmGZfBtHhmAjUAlLMJATmGgC1gLgQM1hBijXgxgMACcIFDbl0pdMAAAAASUVORK5CYII=") no-repeat 50%;
        cursor: pointer;
    }

    .sml-titlesp2 {
        color: #99a2aa;
        letter-spacing: 0;
        font-size: 14px;
    }

    .sml-livecontainer {
        display: inline-flex;
        flex-grow: 0;
        flex-shrink: 0;
        position: relative;
        margin-bottom: 10px;
        margin-top: 10px;
        margin-left: 16px;
        height: 56px;
        -webkit-box-align: center;
        -ms-flex-align: center;
        align-items: center;
        -webkit-box-pack: start;
        -ms-flex-pack: start;
        justify-content: flex-start;
        width: 224px;
    }

    .sml-a1 {
        width: 38px;
        height: 38px;
        border-radius: 22px;
        position: relative;
        background-color: #fff;
        margin: 1px;
        margin-right: 11px;
        -ms-flex-negative: 0;
        flex-shrink: 0;
        background-size: cover;
        background-color: #ddd;
        -webkit-box-shadow: 0 0 0 1px #f25d8e;
        box-shadow: 0 0 0 1px #f25d8e;
        border: 1px solid #fff;
    }

    .sml-a2 {
        text-overflow: ellipsis;
        overflow: hidden;
        word-break: keep-all;
        max-width: 176px;
        padding-right: 16px;
        letter-spacing: 0;
    }

    .sml-upname {
        font-size: 14px;
        color: #222;
        line-height: 20px;
        max-height: 20px;
        display: -webkit-box;
        -webkit-line-clamp: 1;
    }

    .sml-areaname {
        font-size: 12px;
        color: #999;
        line-height: 20px;
        max-height: 20px;
        display: -webkit-box;
        -webkit-line-clamp: 1;
    }

    .sml-areaname:hover {
        color: #23ade5
    }

    .sml-livename {
        font-size: 12px;
        color: #6d757a;
        line-height: 16px;
        word-break: break-all;
        word-break: break-word;
        text-overflow: ellipsis;
        max-height: 32px;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .sml-word {
        word-break: break-all;
        word-break: break-word;
        text-overflow: ellipsis;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .sml-center {
        text-align: center;
    }

    .sml-block {
        display: block;
    }`);

    smlstyle.appendChild(textNode);
    smlstyle.className = 'sml-style';
    smlstyle.id = 'sml-style';

    // 背景div
    var mask = document.createElement('div');
    mask.className = 'sml-mask sml-block';
    mask.id = 'sml-mask';
    mask.onclick = exitBtn;

    // 主体div
    var mainbox = document.createElement('div');
    mainbox.className = 'sml-mainbox sml-block';
    mainbox.id = 'sml-mainbox';

    // 标题栏div
    var title = document.createElement('div');
    title.className = 'sml-title';

    // 标题span
    var titlesp1 = document.createElement('span');
    titlesp1.innerText = '正在直播';

    // 刷新a
    var refresh = document.createElement('a');
    refresh.className = 'sml-refresh sml-icon';
    refresh.onclick = loadContent;

    // 退出a
    var exit = document.createElement('a');
    exit.className = 'sml-exit sml-icon';
    exit.onclick = exitBtn;

    // 人数span
    var titlesp2 = document.createElement('span');
    titlesp2.className = 'sml-titlesp2'
    titlesp2.id = 'sml-titlesp2';
    titlesp2.innerText = '(0)';

    // 正在直播面板div
    var livepanel = document.createElement('div');
    livepanel.className = 'sml-livepanel sml-block';
    livepanel.id = 'sml-livepanel';

    title.appendChild(titlesp1);
    title.appendChild(titlesp2);
    title.appendChild(refresh);
    title.appendChild(exit);
    mainbox.appendChild(title);
    mainbox.appendChild(livepanel);
    base.appendChild(smlstyle);
    base.appendChild(mask);
    base.appendChild(mainbox);
    document.body.appendChild(base);

    loadContent();
}

/**
 * 加载内容
 */
function loadContent() {
    var livepanel = document.querySelector('#sml-livepanel');
    livepanel.innerHTML = '';
    var titlesp2 = document.querySelector('#sml-titlesp2');
    getJSON('https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/w_live_users?size=-1').then(xhr => {
        // 无意中发现的,这个接口填负数可以返回用户关注的所有正在直播的人
        var jsondata = xhr.response.data;

        titlesp2.innerText = '(' + jsondata.count + ')'; // 人数

        if (jsondata.count > 0) {

            for (var i = 0; i < jsondata.count; i++) {
                // 容器div
                let livecontainer = document.createElement('div');
                livecontainer.className = 'sml-livecontainer';

                // UP主头像a
                let a1 = document.createElement('a');
                a1.className = 'sml-block sml-a1';
                a1.style = 'background-image:url("' + jsondata.items[i].face + '@50w_50h.png");';
                a1.href = jsondata.items[i].link;
                a1.target = '_blank';

                // 直播信息a
                let a2 = document.createElement('a');
                a2.className = 'sml-block sml-a2';
                a2.href = jsondata.items[i].link;
                a2.target = '_blank';

                // UP主名称div
                let upname = document.createElement('div');
                upname.className = 'sml-word sml-block sml-upname';
                upname.innerText = jsondata.items[i].uname;

                // 分区名称div
                let areaname = document.createElement('a');
                areaname.className = 'sml-word sml-block sml-areaname';
                var roomid = jsondata.items[i].link.split('/')[3].split('?')[0];
                getJSON('https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=' + roomid).then(xhr => {
                    var jsondata = xhr.response.data.room_info;
                    // 获取房间信息
                    areaname.innerText = jsondata.area_name;
                    areaname.href = 'https://live.bilibili.com/p/eden/area-tags?parentAreaId=' + jsondata.parent_area_id + '&areaId=' + jsondata.area_id;
                    areaname.target = '_blank';
                }, xhr => { // 房间信息获取失败(非200)
                });

                // 直播名称div
                let livename = document.createElement('div');
                livename.className = 'sml-word sml-block sml-livename';
                livename.innerText = jsondata.items[i].title;

                a2.appendChild(upname);
                a2.appendChild(areaname);
                a2.appendChild(livename);

                livecontainer.appendChild(a1);
                livecontainer.appendChild(a2);

                livepanel.appendChild(livecontainer);
            }
        } else {
            var err = document.createElement('p');
            err.className = 'sml-center';
            err.innerText = "当前无直播";
            livepanel.appendChild(err);
        }
    }, xhr => { // 直播数据获取失败(非200)
        var err = document.createElement('p');
        err.className = 'sml-center';
        err.innerText = "直播数据获取失败!(" + xhr.status + " " + xhr.statusText + ")";
        livepanel.appendChild(err);
    });
}

/**
 * 退出面板
 */
function exitBtn() {
    document.body.removeChild(document.querySelector('#sml-base'));
}

/**
 * 重新放置元素,清除事件处理器
 * @param {HTMLElement} oldEelement
 * @returns {HTMLElement}
 */
function replaceElement(oldEelement) {
    var newElement = document.createElement(oldEelement.tagName);
    for (let t of oldEelement.attributes) {
        newElement.setAttribute(t.name, t.value)
    }
    for (let t of oldEelement.childNodes) {
        newElement.appendChild(t);
    }
    var parent = oldEelement.parentElement;
    var next = oldEelement.nextSibling;
    parent.removeChild(oldEelement);
    parent.insertBefore(newElement, next);
    return newElement;
}

/**
 * 替换按钮功能
 * @param {Event} event
 */
function replaceBtnFunc(event) {
    var moreBtnEle = document.querySelector('.bili-dyn-live-users__more');
    if (moreBtnEle) {
        if (!document.querySelector('#sml-stop-event')) {
            var moreBtnEleNew = replaceElement(moreBtnEle);
            moreBtnEleNew.id = 'sml-stop-event';
            moreBtnEleNew.addEventListener('click', moreBtn);
        }
        window.removeEventListener('keydown', replaceBtnFunc);
        window.removeEventListener('mousemove', replaceBtnFunc);
        window.removeEventListener('mousedown', replaceBtnFunc);
    }
}

window.addEventListener('keydown', replaceBtnFunc);
window.addEventListener('mousemove', replaceBtnFunc);
window.addEventListener('mousedown', replaceBtnFunc); // 参数3表示在事件捕获时就执行