Bilibili话题栏

来点真正有用的话题栏,不会真有人看话题排行吧(

// ==UserScript==
// @name         Bilibili话题栏
// @namespace    https://space.bilibili.com/475210
// @version      0.4
// @description  来点真正有用的话题栏,不会真有人看话题排行吧(
// @author       Xinrea
// @match        https://t.bilibili.com/
// @match        https://t.bilibili.com/?*
// @grant        none
// @license      MIT
// ==/UserScript==


function getCookie(cname)
{
  var name = cname + "=";
  var ca = document.cookie.split(';');
  for(var i=0; i<ca.length; i++)
  {
    var c = ca[i].trim();
    if (c.indexOf(name)==0) return c.substring(name.length,c.length);
  }
  return "";
}

function htmlToElements(html) {
    var template = document.createElement('template');
    template.innerHTML = html;
    return template.content.childNodes;
}

async function runWhenReady(readySelector) {
    return new Promise(resolve => {
        var numAttempts = 0;
        var tryNow = function() {
            var elem = document.querySelector(readySelector);
            if (elem) {
                resolve(elem);
            } else {
                numAttempts++;
                if (numAttempts >= 34) {
                    console.warn('Giving up after 34 attempts. Could not find: ' + readySelector);
                } else {
                    setTimeout(tryNow, 250 * Math.pow(1.1, numAttempts));
                }
            }
        };
        tryNow();
    });
}

function createTagItem(tag_name, description, url) {
    const template = `<div class="relevant-topic"><div class="relevant-topic__title">${tag_name}</div> <div class="relevant-topic__exposed"><span>${description}</span></div></div>`;
    let newElement = htmlToElements(template)[0];
    newElement.addEventListener("click", ()=>{
        window.open(url, '_blank').focus();
    })
    return newElement;
}

(function() {
    'use strict';
    const mid = getCookie('DedeUserID');
    if (mid == "") {
        console.log("未登录");
        return;
    }
    async function getSubTags(mid) {
        let resp = await fetch("https://api.bilibili.com/x/space/tag/sub/list?vmid="+mid+"&jsonp=jsonp", {
            "referrerPolicy": "no-referrer-when-downgrade",
            "body": null,
            "method": "GET",
            "mode": "cors",
            "credentials": "include"
        });
        resp = await resp.json();
        if (resp.code === 0) {
            // item: name, description, url, timestamp
            let ret = [];
            if (resp.data.tags) {
                for (let i = 0; i < resp.data.tags.length; i++) {
                    let latest = await getTagLatest(resp.data.tags[i].tag_id);
                    ret.push({
                        name: resp.data.tags[i].tag_name,
                        description: formatTagDescription(latest),
                        url: `https://t.bilibili.com/topic/${resp.data.tags[i].tag_id}`,
                        timestamp: latest.desc.timestamp
                    });
                    await sleep(100);
                }
            }
            return ret;
        }
        return [];
    }
    async function getSubTopics() {
        let resp = await fetch("https://app.bilibili.com/x/topic/web/fav/list?page_size=100&page_num=1", {
            "referrerPolicy": "strict-origin-when-cross-origin",
            "body": null,
            "method": "GET",
            "mode": "cors",
            "credentials": "include"
        });
        resp = await resp.json();
        if (resp.code === 0) {
            let ret = [];
            let items = resp.data.topic_list.topic_items;
            if (items) {
                for (let i = 0; i < items.length; i++) {
                    let latest = await getTopicLatest(items[i].id);
                    ret.push({
                        name: items[i].name,
                        description: formatTopicDescription(latest),
                        url: items[i].jump_url,
                        timestamp: latest.dynamic_card_item.modules.module_author.pub_ts
                    });
                    await sleep(100);
                }
            }
            return ret;
        }
        return [];
    }
    async function getTagLatest(tag_id) {
        let resp = await fetch(`https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_new?topic_id=${tag_id}`, {
            "referrerPolicy": "strict-origin-when-cross-origin",
            "body": null,
            "method": "GET",
            "mode": "cors",
            "credentials": "include"
        });
        resp = await resp.json();
        if (resp.code === 0) {
            resp.data.cards.sort((a,b)=>{return b.desc.timestamp - a.desc.timestamp});
            return resp.data.cards[0];
        }
        return null;
    }
    async function getTopicLatest(topic_id) {
        let resp = await fetch(`https://app.bilibili.com/x/topic/web/details/cards?topic_id=${topic_id}&sort_by=3&offset=&page_size=1&source=Web`, {
            "referrerPolicy": "strict-origin-when-cross-origin",
            "body": null,
            "method": "GET",
            "mode": "cors",
            "credentials": "include"
        });
        resp = await resp.json();
        if (resp.code === 0) {
            return resp.data.topic_card_list.items[0];
        }
        return null;
    }
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    function formatTagDescription(latest) {
        let unit = "秒";
        let n = Math.floor(Date.now()/1000) - latest.desc.timestamp;
        if (n > 60) {
            n = Math.floor(n/60);
            unit = "分钟";
            if (n > 60) {
                n = Math.floor(n/60);
                unit = "小时";
                if (n > 24) {
                    n = Math.floor(n/24);
                    unit = "天";
                }
            }
        }
        return `${latest.desc.user_profile.info.uname} · ${n}${unit}前`;
    }
    function formatTopicDescription(latest) {
        let uname = "";
        let timestamp = null;
        switch(latest.topic_type) {
            case "DYNAMIC":
                uname = latest.dynamic_card_item.modules.module_author.name;
                timestamp =latest.dynamic_card_item.modules.module_author.pub_ts;
                break;
        }
        if (timestamp == null) return "";
        let unit = "秒";
        let n = Math.floor(Date.now()/1000) - timestamp;
        if (n > 60) {
            n = Math.floor(n/60);
            unit = "分钟";
            if (n > 60) {
                n = Math.floor(n/60);
                unit = "小时";
                if (n > 24) {
                    n = Math.floor(n/24);
                    unit = "天";
                }
            }
        }
        return `${uname} · ${n}${unit}前`;
    }
    async function main() {
        let e = await runWhenReady("#app > div.bili-dyn-home--member > aside.right > section.sticky > div");
        // Clean original items
        e.innerHTML = '<div class="topic-panel__nav"><span class="topic-panel__nav-title">关注的话题</span></div><div class="relevant-topic"><div class="relevant-topic__title">信息加载中</div></div>';
        let topiclist = await getSubTopics();
        let taglist = await getSubTags(mid);
        let list = topiclist.concat(taglist);
        list.sort((a,b)=>{return b.timestamp - a.timestamp});
        e.removeChild(e.lastChild);
        for (let i = 0; i < list.length; i++) {
            let item = createTagItem(list[i].name, list[i].description, list[i].url);
            e.appendChild(item);
        }
    }
    main();
})();