// ==UserScript== // @name bilibili订阅+ // @namespace https://github.com/YanxinTang/Tampermonkey // @version 0.7.9 // @description bilibili导航添加订阅按钮以及订阅列表 // @author tyx1703 // @license MIT // @noframes // @require https://cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js // @match *.bilibili.com/* // @exclude *://live.bilibili.com/* // @exclude *://manga.bilibili.com/* // @exclude *://bw.bilibili.com/* // @exclude *://show.bilibili.com/* // @downloadURL https://update.greasyfork.icu/scripts/30283/bilibili%E8%AE%A2%E9%98%85%2B.user.js // @updateURL https://update.greasyfork.icu/scripts/30283/bilibili%E8%AE%A2%E9%98%85%2B.meta.js // ==/UserScript== (async function () { const DedeUserID = getCookie("DedeUserID"); const loginStatus = DedeUserID !== ""; if (!loginStatus) { log("少侠请先登录~ 哔哩哔哩 (゜-゜)つロ 干杯~"); return; } const PER_PAGE = 15; try { const lastPopoverButton = await getLastPopoverButton(); const subscribeMenuEl = document.createElement("li"); subscribeMenuEl.setAttribute("id", "subscribe"); lastPopoverButton.after(subscribeMenuEl); const getBangumis = (page) => { return fetch( `//api.bilibili.com/x/space/bangumi/follow/list?type=1&follow_status=0&pn=${page}&ps=${PER_PAGE}&vmid=${DedeUserID}`, { method: "GET", credentials: "include", } ) .then((response) => response.json()) .then((response) => response.data) .then(({ list, ...rest }) => { return { list: list.map((item) => ({ ...item, id: item.media_id })), ...rest, }; }); }; const getCinemas = (page) => { return fetch( `//api.bilibili.com/x/space/bangumi/follow/list?type=2&follow_status=0&pn=${page}&ps=${PER_PAGE}&vmid=${DedeUserID}`, { method: "GET", credentials: "include", } ) .then((response) => response.json()) .then((response) => response.data) .then(({ list, ...rest }) => { return { list: list.map((item) => ({ ...item, id: item.media_id })), ...rest, }; }); }; const getFloowings = (page) => { return fetch( `//api.bilibili.com/x/relation/followings?&pn=${page}&ps=${PER_PAGE}&vmid=${DedeUserID}&order=desc`, { method: "GET", credentials: "include", } ) .then((response) => response.json()) .then((response) => { return { list: response.data.list.map((item) => ({ ...item, id: item.mid, })), total: response.data.total, pn: page, }; }); }; const VideoItem = { props: ["item"], computed: { coverURL() { return this.item.cover.replace("http:", ""); }, }, template: `
{{item.new_ep.index_show}}
{{item.title}}
{{item.time}}
{{item?.new_ep?.long_title ?? '' }}
`, }; const UserItem = { props: ["item"], computed: { spaceURL() { return `https://space.bilibili.com/${this.item.mid}`; }, avatarURL() { return this.item.face.replace("http:", ""); }, }, template: `
{{item.uname}}
{{item.sign }}
`, }; new Vue({ el: subscribeMenuEl, components: { VideoItem, UserItem }, data() { return { isPanelVisible: false, loading: false, inLeaveAnimation: false, activeTab: "bangumis", tabs: [ { key: "bangumis", name: "追番" }, { key: "cinemas", name: "追剧" }, { key: "floowings", name: "关注" }, ], dataset: { bangumis: { list: [], total: 0, page: 0, component: "VideoItem", }, cinemas: { list: [], total: 0, page: 0, component: "VideoItem", }, floowings: { list: [], total: 0, page: 0, component: "UserItem", }, }, }; }, created() { this.load(); }, computed: { list() { return this.dataset[this.activeTab].list; }, total() { return this.dataset[this.activeTab].total; }, page() { return this.dataset[this.activeTab].page; }, tabComponent() { return this.dataset[this.activeTab].component; }, }, methods: { async load() { const tab = this.activeTab; let request; if (tab === "bangumis") { request = getBangumis; } if (tab === "cinemas") { request = getCinemas; } if (tab === "floowings") { request = getFloowings; } try { this.loading = true; const { list, total, pn } = await request(this.page + 1); this.dataset[tab].list = [...this.dataset[tab].list, ...list]; this.dataset[tab].total = total; this.dataset[tab].page = pn; } catch (error) { throw error; } finally { this.loading = false; } }, changeTabHandler(tab) { this.activeTab = tab.key; if (this.list.length <= 0) { this.load(); } }, onMouseoverHandler() { if (!this.inLeaveAnimation) { this.isPanelVisible = true; } }, onMouseleaveHandler() { this.isPanelVisible = false; }, onContentBeforeLeaveHandler() { this.inLeaveAnimation = true; }, onContentAfterLeaveHandler() { this.inLeaveAnimation = false; }, onScrollHandler() { const panelContent = this.$refs.panelContent; if ( !this.loading && this.list.length < this.total && panelContent.scrollHeight - panelContent.scrollTop - 50 <= panelContent.clientHeight ) { this.load(); } }, }, template: `
  • 订阅
    {{tab.name}}
  • `, }); } catch (error) { log(error); } function getLastPopoverButton(count = 1) { if (count >= 30) { return Promise.reject("获取顶部按列表超时"); } return new Promise((resolve) => { const popoverButtons = document.body.querySelectorAll( ".bili-header .bili-header__bar .right-entry>.v-popover-wrap" ); if (popoverButtons.length) { resolve(popoverButtons[popoverButtons.length - 1]); return; } setTimeout(() => { resolve(getLastPopoverButton(count++)); }, 100); }); } /** * Get cookie by name * @param {string} name */ function getCookie(name) { const value = "; " + document.cookie; let parts = value.split("; " + name + "="); if (parts.length == 2) { return parts.pop().split(";").shift(); } return ""; } /** * print something in console with custom style * @param {*} stuff */ function log(stuff) { console.log( "%cbilibili订阅+:", "background: #f25d8e; border-radius: 3px; color: #fff; padding: 0 8px", stuff ); } })();