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

显示当前所有直播

// ==UserScript==
// @name        Bilibili 动态页显示当前所有直播
// @description 显示当前所有直播
// @version     3.0
// @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((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.withCredentials = true;
        xhr.responseType = 'json';
        xhr.onload = () => {
            if (xhr.status == 200) {
                resolve(xhr);
            } else {
                reject(xhr);
            }
        };
        xhr.send();
    });
}

/**
 * 打开面板
 */
async function moreBtn() {
    // 基底div
    const base = document.createElement('div');
    base.className = 'sml-base'
    base.id = 'sml-base';
    base.innerHTML = `
<style class="sml-style" id="sml-style">
    .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;
        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;
    }
</style>
<div class="sml-mask sml-block" id="sml-mask"></div>
<div class="sml-mainbox sml-block" id="sml-mainbox">
    <div class="sml-title"><!--
     --><span>正在直播</span><!--
     --><span class="sml-titlesp2" id="sml-titlesp2">(0)</span><!--
     --><a class="sml-refresh sml-icon"></a><!--
     --><a class="sml-exit sml-icon"></a><!--
 --></div>
    <div class="sml-livepanel sml-block" id="sml-livepanel"></div>
</div>
`;
    const refresh = base.querySelector('.sml-refresh');
    refresh.onclick = loadContent;
    const exit = base.querySelector('.sml-exit');
    exit.onclick = exitBtn;
    document.body.appendChild(base);
    await loadContent();
}

/**
 * 加载内容
 */
async function loadContent() {
    const livepanel = document.querySelector('#sml-livepanel');
    livepanel.innerHTML = '';
    const titlesp2 = document.querySelector('#sml-titlesp2');
    let liveCount = 0;
    let hasMore = true;
    let page = 1;
    let maxPage = 1;
    while (hasMore && page <= maxPage) {
        try {
            const xhr = await getJSON(`https://api.live.bilibili.com/xlive/web-ucenter/user/following?page_size=29&page=${page}`);
            const jsondata = xhr.response.data;
            maxPage = jsondata.totalPage;
            for (const user of jsondata.list) {
                if (user.live_status == 0) {
                    hasMore = false;
                    break;
                }
                liveCount++;
                const roomid = user.roomid;
                const link = `https://live.bilibili.com/${roomid}`
                // 容器div
                const livecontainer = document.createElement('div');
                livecontainer.className = 'sml-livecontainer';
                // UP主头像a
                const a1 = document.createElement('a');
                a1.className = 'sml-block sml-a1';
                a1.style = `background-image:url('${user.face}@50w_50h.png');`;
                a1.href = link;
                a1.target = '_blank';
                // 直播信息a
                const a2 = document.createElement('a');
                a2.className = 'sml-block sml-a2';
                a2.href = link;
                a2.target = '_blank';
                // UP主名称div
                const upname = document.createElement('div');
                upname.className = 'sml-word sml-block sml-upname';
                upname.innerText = user.uname;
                // 分区名称div
                const areaname = document.createElement('a');
                areaname.className = 'sml-word sml-block sml-areaname';
                areaname.target = '_blank';
                // 直播名称div
                const livename = document.createElement('div');
                livename.className = 'sml-word sml-block sml-livename';
                livename.innerText = user.title;

                getJSON(`https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=${roomid}`).then(xhr => {
                    const 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}`;
                }, _ => { // 房间信息获取失败(非200)
                });
                a2.appendChild(upname);
                a2.appendChild(areaname);
                a2.appendChild(livename);
                livecontainer.appendChild(a1);
                livecontainer.appendChild(a2);
                livepanel.appendChild(livecontainer);
            }
        } catch (xhr) {
            const err = document.createElement('p');
            err.className = 'sml-center';
            err.innerText = `直播数据获取失败!(${xhr.status} ${xhr.statusText})`;
            livepanel.appendChild(err);
        }
        page++;
        titlesp2.innerText = `(${liveCount})`; // 人数
    }
    if (liveCount == 0) {
        const err = document.createElement('p');
        err.className = 'sml-center';
        err.innerText = '当前无直播';
        livepanel.appendChild(err);
    }
}

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

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

/**
 * 替换按钮功能
 */
function replaceBtnFunc() {
    const moreBtnEle = document.querySelector('.bili-dyn-live-users__more');
    if (moreBtnEle) {
        if (!document.querySelector('#sml-stop-event')) {
            const 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);