调整B站视频页面的布局展示内容,拓展视频页中的功能
// ==UserScript== // @name 哔哩哔哩视频页功能拓展 // @namespace https://tampermonkey.net/ // @homepage https://space.bilibili.com/473239155/dynamic // @license Apache-2.0 // @version 0.4.0 // @description 调整B站视频页面的布局展示内容,拓展视频页中的功能 // @author byhgz // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/list/* // @icon https://static.hdslb.com/images/favicon.ico // @require https://update.greasyfork.org/scripts/517928/gz_ui_css-v1.js // @require https://greasyfork.org/scripts/462234-message/code/Message.js?version=1170653 // @require https://cdn.jsdelivr.net/npm/vue@2 // @require https://update.greasyfork.org/scripts/516282/Drawer_gz%E9%A1%B5%E9%9D%A2%E4%BE%A7%E8%BE%B9%E6%8A%BD%E5%B1%89%E7%BB%84%E4%BB%B6.js // @require https://update.greasyfork.org/scripts/517538/DynamicTabs_gz.js // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @noframes // ==/UserScript== "use strict"; const Tips = { info(text, config) { Qmsg.info(text, config); }, infoBottomRight(text) { this.info(text, {position: "bottomright"}); }, success(text, config) { Qmsg.success(text, config); }, successBottomRight(text) { this.success(text, {position: "bottomright"}); }, error(text, config) { Qmsg.error(text, config); }, errorBottomRight(text) { this.error(text, {position: "bottomright"}); } }; const gmUtil = { setData(key, content) { GM_setValue(key, content); }, getData(key, defaultValue) { return GM_getValue(key, defaultValue); }, delData(key) { if (!this.isData(key)) { return false; } GM_deleteValue(key); return true; }, isData(key) { return this.getData(key) !== undefined; }, addStyle(style) { GM_addStyle(style); }, addGMMenu(text, func, shortcutKey = null) { return GM_registerMenuCommand(text, func, shortcutKey); }, } const global_BiliBIli_video_page_settings_dataVue = new Vue({ data() { return { isDefPlayVideoPage: false, isCheckBackLaterPlayVideoPage: false, } }, created() { this.isDefPlayVideoPage = window.location.href.includes("www.bilibili.com/video/"); this.isCheckBackLaterPlayVideoPage = window.location.href.includes("www.bilibili.com/list/ml"); } }); const drawerGz = new Drawer_gz({ show: false, height: "300px", direction: "bottom", externalButtonText: "视频页设置", externalButtonWidth: "80px", externalButtonShow: gmUtil.getData("drawerGzDefShow", true), backgroundColor: "#ffffff", headerShow: false, drawerBorder: "1px solid #00f3ff", }); drawerGz.setBodyHtml(`<div id="video_page_settings_app"></div>`); const searchDrawerGz = new Drawer_gz({ show: false, direction: "left", width: "auto", externalButtonText: "搜索", externalButtonWidth: "80px", backgroundColor: "#ffffff", title:"搜索合集中视频", drawerBorder: "1px solid #00f3ff", zIndex:1499, externalButtonShow: false, }); searchDrawerGz.setBodyHtml(`<div id="video_page_settings_app_search"></div>`); const tabsConfig = [ {id: 'tab1', title: '合集操作', content: '<div id="collection"></div>'}, {id: 'tab2', title: '评论区操作', content: '<div id="comment-area-comment"></div>'}, {id: 'tab3', title: '关于和问题反馈', content: '<div id="problem-feedback"></div>'}, ]; const options = { classes: { tabButton: 'my-custom-tab-button', tabButtonActive: 'my-custom-tab-button-active', tabContent: 'my-custom-tab-content', tabContentActive: 'my-custom-tab-content-active' }, backgroundColor: '#eee', borderColor: '#ddd', textColor: '#333', fontWeight: 'bold', activeBackgroundColor: '#0056b3', activeTextColor: '#fff', contentBorderColor: '#bbb', contentBackgroundColor: '#ffffff', }; const dynamicTabsGz = new DynamicTabs_gz('#video_page_settings_app', tabsConfig, options); new Vue({ template: ` <div> <div v-if="isDefPlayVideoPage"> <button gz_type @click="listHighAdaptationBut">视频页合集列表自适应高度</button> <button gz_type @click="listHighlyRestoredBut">视频页合集列表高度复原</button> </div> <div v-if="isCheckBackLaterPlayVideoPage"> <button gz_type @click="listHighAdaptationCheckBackLaterBut">视频页合集列表自适应高度(稍后再看)</button> <button gz_type @click="listHighCheckBackLaterRestoredBut">视频页合集列表高度(稍后再看)</button> </div> <div> <button gz_type @click="openSearchDrawerGzBut">打开合集搜索栏</button> </div> </div>`, el: "#collection", data() { return { isDefPlayVideoPage: false, isCheckBackLaterPlayVideoPage: false, defVideoListMaxHeight: null, videoCheckBackLaterListBodyMaxHeight: null, videoCheckBackLaterListMaxHeight: null, } }, methods: { openSearchDrawerGzBut() { searchDrawerGz.show(true); drawerGz.show(false); }, listHighAdaptationBut() { getDefVideoPlayPageListEl().then(el => { if (this.defVideoListMaxHeight === null) { this.defVideoListMaxHeight = el.style.maxHeight; } el.style.maxHeight = "max-content"; Tips.successBottomRight("已自适应高度!") }) }, listHighlyRestoredBut() { if (this.defVideoListMaxHeight === null) { Tips.errorBottomRight("未自适应高度!无需调整."); return; } getDefVideoPlayPageListEl().then(el => { el.style.maxHeight = this.defVideoListMaxHeight; }) Tips.successBottomRight("已复原视频页合集列表高度"); }, listHighAdaptationCheckBackLaterBut() { getCheckBackLaterVideoListElements().then(({bodyList, listEl, defList}) => { if (this.videoCheckBackLaterListBodyMaxHeight === null) { this.videoCheckBackLaterListBodyMaxHeight = bodyList.style.maxHeight; this.videoCheckBackLaterListMaxHeight = listEl.style.maxHeight; } for (let el of defList) { el.style.maxHeight = "max-content"; } }) }, listHighCheckBackLaterRestoredBut() { if (this.videoCheckBackLaterListBodyMaxHeight === null) { Tips.errorBottomRight("未自适应高度!无需调整."); return; } getCheckBackLaterVideoListElements().then(({bodyList, listEl}) => { bodyList.style.maxHeight = this.videoCheckBackLaterListBodyMaxHeight; listEl.style.maxHeight = this.videoCheckBackLaterListMaxHeight; }); } }, created() { this.isDefPlayVideoPage = global_BiliBIli_video_page_settings_dataVue["isDefPlayVideoPage"]; this.isCheckBackLaterPlayVideoPage = global_BiliBIli_video_page_settings_dataVue["isCheckBackLaterPlayVideoPage"]; } }); const getDefVideoPageCollectionElList = async () => { return new Promise(resolve => { const i1 = setInterval(() => { const elList = document.querySelectorAll(".video-pod__body>.video-pod__list.section>div"); if (elList.length === 0) return; clearInterval(i1); const tempList = []; for (let el of elList) { tempList.push({ title: el.querySelector(".title").title.trim(), clickEl: el.querySelector(".single-p>.simple-base-item") }) } resolve(tempList) }, 500); }); } const getDefVideoPageAnthologyElList = async () => { return new Promise(resolve => { const i1 = setInterval(() => { const elList = document.querySelectorAll(".video-pod__body>.video-pod__list.multip.list>div"); if (elList.length === 0) return; clearInterval(i1); const tempList = []; for (let el of elList) { tempList.push({ title: el.querySelector(".title").title.trim(), clickEl: el }) } resolve(tempList); }, 500); }) } const isDefVideoPageAnthology = (maxIndex = 3) => { return new Promise((resolve, reject) => { let index = 1; const i1 = setInterval(() => { if (document.querySelector(".header-top .view-mode") === null) { if (index === maxIndex) { clearInterval(i1); reject(); } index++; return; } clearInterval(i1); resolve(); }, 1000); }) } const getCheckBackLaterVideoPageCollectionElList = async () => { return new Promise(resolve => { const i1 = setInterval(() => { const elList = document.querySelectorAll("#playlist-video-action-list>.action-list-inner>div"); if (elList.length === 0) return; clearInterval(i1); const tempList = []; for (let el of elList) { tempList.push({ title: el.querySelector(".title").title.trim(), clickEl: el.querySelector(".main") }) } resolve(tempList) }, 500); }); } new Vue({ el: "#video_page_settings_app_search", template: ` <div> <div> <input type="text" placeholder="搜索列表视频" v-model="inputValue" style="width: 80%"> <button gz_type="info" @click="refreshListBut">刷新</button> </div> <div> <div v-for="item in searchShowList"> <span>{{ item.title }}</span> <button gz_type @click="playVideo(item)">播放</button> </div> </div> </div>`, data() { return { inputValue: '', videoPageCollectionElList: [], searchShowList: [] } }, methods: { playVideo(item) { item.clickEl.click(); }, setSearchShowListAndVideoPageCollectionElList(dataList) { this.searchShowList.splice(0, this.searchShowList.length); this.videoPageCollectionElList.splice(0, this.videoPageCollectionElList.length); this.searchShowList.push(...dataList); this.videoPageCollectionElList.push(...dataList); }, loadCollectionList() { return new Promise(resolve => { if (global_BiliBIli_video_page_settings_dataVue["isCheckBackLaterPlayVideoPage"]) { getCheckBackLaterVideoPageCollectionElList().then(list => { this.setSearchShowListAndVideoPageCollectionElList(list); Qmsg.success("已获取到稍后再看视频页合集列表", {position: "bottomright"}); resolve(); }); } else { isDefVideoPageAnthology().then(async () => { const newVar = await getDefVideoPageAnthologyElList(); this.setSearchShowListAndVideoPageCollectionElList(newVar); Qmsg.success("已获取到视频选集列表", {position: "bottomright"}); resolve(); }).catch(() => { getDefVideoPageCollectionElList().then(list => { Qmsg.success("已获取到视频合集列表", {position: "bottomright"}); this.setSearchShowListAndVideoPageCollectionElList(list); resolve(); }) }); } }); }, refreshListBut() { const loading = Qmsg.loading("数据刷新中"); this.loadCollectionList().finally(loading.close()) } }, watch: { inputValue(newValue) { this.searchShowList.splice(0, this.searchShowList.length); for (let data of this.videoPageCollectionElList) { if (data.title.includes(newValue.trim())) { this.searchShowList.push(data); } } } }, created() { this.loadCollectionList(); } }); new Vue({ el: "#comment-area-comment", template: ` <div> <button gz_type @click="hideCommentAreaBut">隐藏评论区</button> <button gz_type @click="showCommentAreaBut">显示评论区</button> <div> <input type="checkbox" v-model="isAutoHideCommentArea">加载视频之后自动隐藏评论区 </div> </div>`, data() { return { isAutoHideCommentArea: false, } }, methods: { hideCommentAreaBut() { getElement("#commentapp").then(element => { element.setAttribute("data-is-hidden", "true"); element.style.display = "none"; Qmsg.success("已隐藏评论区", {position: "bottomright"}); }) }, showCommentAreaBut() { getElement("#commentapp").then(element => { element.setAttribute("data-is-hidden", "false"); element.style.display = ""; Qmsg.success("已显示评论区", {position: "bottomright"}); }) } }, watch: { isAutoHideCommentArea(val) { gmUtil.setData("isAutoHideCommentArea", val); } }, created() { this.isAutoHideCommentArea = gmUtil.getData("isAutoHideCommentArea", false) === true; if (this.isAutoHideCommentArea) { setTimeout(() => { this.hideCommentAreaBut(); }, 2000); } } }); new Vue({ el: "#problem-feedback", template: ` <div> <div v-for="item in feedbacks" :key="item.title"> {{ item.title }} <button gz_type><a :href="item.href" target="_blank">{{ item.href }}</a></button> </div> <hr> <div> <h1>作者其他脚本</h1> <div v-for="item in otherScriptSets" :key="item.title" :title="item.desc"> {{ item.title }} <button gz_type><a :href="item.url" target="_blank">{{ item.url }}</a></button> <span>{{ item.desc }}</span> </div> </div> </div> `, data() { return { feedbacks: [ { title: "gf反馈", href: "https://greasyfork.org/zh-CN/scripts/507821/feedback", }, { title: "q群反馈", href: "http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=tFU0xLt1uO5u5CXI2ktQRLh_XGAHBl7C&authKey=KAf4rICQYjfYUi66WelJAGhYtbJLILVWumOm%2BO9nM5fNaaVuF9Iiw3dJoPsVRUak&noverify=0&group_code=876295632", }, { title: "作者B站", href: "https://space.bilibili.com/473239155", } ], otherScriptSets: [ { title: "B站屏蔽增强器", url: "https://greasyfork.org/zh-CN/scripts/461382", desc: "支持动态屏蔽、评论区过滤屏蔽,视频屏蔽(标题、用户、uid等)、蔽根据用户名、uid、视频关键词、言论关键词和视频时长进行屏蔽和精简处理,支持获取b站相关数据并导出为json(用户收藏夹导出,历史记录导出、关注列表导出、粉丝列表导出)(详情看脚本主页描述)" }, { title: "去除b站首页右下角推广广告", url: "https://greasyfork.org/zh-CN/scripts/516566", desc: "移除b站首页右下角按钮广告和对应的横幅广告" }, { title: "b站首页视频列数调整", url: "https://greasyfork.org/zh-CN/scripts/512973", desc: "修改b站首页视频列表的列数,并移除大图" }, { title: "github链接新标签打开", url: "https://greasyfork.org/zh-CN/scripts/489538", desc: "github站内所有的链接都从新的标签页打开,而不从当前页面打开" } ] } } }); gmUtil.addGMMenu("默认隐藏抽屉开关按钮", () => { gmUtil.setData("drawerGzDefShow", false); alert("已设置,重新加载页面生效"); }); gmUtil.addGMMenu("默认显示抽屉开关按钮", () => { gmUtil.setData("drawerGzDefShow", true); alert("已设置,重新加载页面生效"); }); gmUtil.addGMMenu("显示或隐藏抽屉栏", () => { drawerGz.showDrawer(); }); const getElement = (selector, timeOut = 500) => { return new Promise(resolve => { const i1 = setInterval(() => { const el = document.querySelector(selector); if (el === null) return; clearInterval(i1); resolve(el); }, timeOut); }); } const getDefVideoPlayPageListEl = () => { return getElement(".video-pod__body"); } const getCheckBackLaterVideoListElements = async () => { const p1 = getElement("#playlist-video-action-list-body"); const p2 = getElement("#playlist-video-action-list"); const defList = await Promise.all([p1, p2]); [bodyListEl, listEl] = defList; return {bodyList: bodyListEl, listEl: listEl, defList: defList} }