哔哩哔哩视频页功能拓展

调整B站视频页面的布局展示内容,拓展视频页中的功能

// ==UserScript==
// @name        哔哩哔哩视频页功能拓展
// @namespace   https://tampermonkey.net/
// @homepage    https://space.bilibili.com/473239155/dynamic
// @license     Apache-2.0
// @version     0.3.1
// @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://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);
    },
}
gmUtil.addStyle(`
button[gz_type] {
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    background: #fff;
    border: 1px solid #dcdfe6;
    color: #606266;
    -webkit-appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin: 0;
    transition: .1s;
    font-weight: 500;
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    padding: 12px 20px;
    font-size: 14px;
    border-radius: 4px;
}
button[gz_type="primary"] {
    color: #fff;
    background-color: #409eff;
    border-color: #409eff;
}
button[gz_type="success"] {
    color: #fff;
    background-color: #67c23a;
    border-color: #67c23a;
}
button[gz_type="info"] {
    color: #fff;
    background-color: #909399;
    border-color: #909399;
}
button[gz_type="warning"] {
    color: #fff;
    background-color: #e6a23c;
    border-color: #e6a23c;
}
button[gz_type="danger"] {
    color: #fff;
    background-color: #f56c6c;
    border-color: #f56c6c;
}
button[border] {
    border-radius: 20px;
    padding: 12px 23px;
}
`);
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",
    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站内所有的链接都从新的标签页打开,而不从当前页面打开"
                }
            ]
        }
    }
});
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}
}