// ==UserScript== // @name 哔哩哔哩(bilibili.com)调整 // @namespace 哔哩哔哩(bilibili.com)调整 // @copyright QIAN // @license GPL-3.0 License // @version 0.1.50.5 // @description 一、首页新增推荐视频历史记录(仅记录前6个推荐位中的非广告内容),以防误点刷新错过想看的视频。二、动态页调整:默认显示"投稿视频"内容,可自行设置URL以免未来URL发生变化。三、播放页调整:1.自动定位到播放器(进入播放页,可自动定位到播放器,可设置偏移量及是否在点击主播放器时定位);2.可设置播放器默认模式;3.可设置是否自动选择最高画质;4.新增快速返回播放器漂浮按钮;5.新增点击评论区时间锚点可快速返回播放器;6.网页全屏模式解锁(网页全屏模式下可滚动查看评论,并在播放器控制栏新增快速跳转至评论区按钮);7.将视频简介内容优化后插入评论区或直接替换原简介区内容(替换原简介中固定格式的静态内容为跳转链接);8.视频播放过程中跳转指定时间节点至目标时间节点(可用来跳过片头片尾及中间广告等);9.新增点击视频合集、下方推荐视频、结尾推荐视频卡片快速返回播放器; // @author QIAN // @match *://www.bilibili.com // @match *://www.bilibili.com/video/* // @match *://www.bilibili.com/bangumi/play/* // @match *://www.bilibili.com/list/* // @match *://t.bilibili.com/* // @require https://cdn.jsdelivr.net/npm/md5@2.3.0/dist/md5.min.js // @require https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js // @require https://cdn.jsdelivr.net/npm/axios@1.6.5/dist/axios.min.js // @require https://asifadeaway.com/utils/ShadowDOMHelper.js?#sha256=13aa860c659bb4cf23ae25a4a4722c9df29e84d172fb0e70bc751b703d3dcda9 // @require https://scriptcat.org/lib/513/2.0.1/ElementGetter.js?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg= // @grant GM_info // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant window.onurlchange // @grant unsafeWindow // @supportURL https://github.com/QIUZAIYOU/Bilibili-Adjustment // @homepageURL https://github.com/QIUZAIYOU/Bilibili-Adjustment // @icon https://www.bilibili.com/favicon.ico?v=1 // @downloadURL https://update.greasyfork.icu/scripts/493405/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%EF%BC%88bilibilicom%EF%BC%89%E8%B0%83%E6%95%B4.user.js // @updateURL https://update.greasyfork.icu/scripts/493405/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%EF%BC%88bilibilicom%EF%BC%89%E8%B0%83%E6%95%B4.meta.js // ==/UserScript== // ?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg= // ?#sha256=e2c555a7dfeef9362ba134e6fef421a129ff562650de6331e6238fa3ba950bec (function () { 'use strict'; let vars = { theMainFunctionRunningCount: 0, thePrepFunctionRunningCount: 0, autoSelectScreenModeRunningCount: 0, autoCancelMuteRunningCount: 0, webfullUnlockRunningCount: 0, autoSelectVideoHighestQualityRunningCount: 0, insertGoToCommentButtonCount: 0, insertSetSkipTimeNodesButtonCount: 0, insertSetSkipTimeNodesSwitchButtonCount: 0, insertAutoEnableSubtitleSwitchButtonCount: 0, setIndexRecordRecommendVideoHistoryArrayCount: 0, functionExecutionsCount: 0, autoSubtitleRunningCount: 0, checkScreenModeSwitchSuccessDepths: 0, autoLocationToPlayerRetryDepths: 0, } let arrays = { screenModes: ['wide', 'web'], intervalIds: [], skipNodesRecords: [], indexRecommendVideoHistory: [], videoCategoriesActiveClass: ['adjustment_button', 'primary', 'plain'], } let objects = { videoCategories: { douga: { name: "动画", tids: [1, 24, 25, 47, 210, 86, 253, 27] }, anime: { name: "番剧", tids: [13, 51, 152, 32, 33] }, guochuang: { name: "国创", tids: [167, 153, 168, 169, 170, 195] }, music: { name: "音乐", tids: [3, 28, 31, 30, 59, 193, 29, 130, 243, 244] }, dance: { name: "舞蹈", tids: [129, 20, 154, 156, 198, 199, 200, 255] }, game: { name: "游戏", tids: [4, 17, 171, 172, 65, 173, 121, 136, 19] }, knowledge: { name: "知识", tids: [36, 201, 124, 228, 207, 208, 209, 229, 122] }, tech: { name: "科技", tids: [188, 95, 230, 231, 232, 233] }, sports: { name: "运动", tids: [234, 235, 249, 164, 236, 237, 238] }, car: { name: "汽车", tids: [223, 245, 246, 247, 248, 240, 227, 176, 258] }, life: { name: "生活", tids: [160, 138, 250, 251, 239, 161, 162, 21, 254] }, food: { name: "美食", tids: [211, 76, 212, 213, 214, 215] }, animal: { name: "动物圈", tids: [217, 218, 219, 220, 221, 222, 75] }, kichiku: { name: "鬼畜", tids: [119, 22, 26, 126, 216, 127] }, fashion: { name: "时尚", tids: [155, 157, 252, 158, 159] }, information: { name: "资讯", tids: [202, 203, 204, 205, 206] }, ent: { name: "娱乐", tids: [5, 71, 241, 242, 137] }, cinephile: { name: "影视", tids: [181, 182, 183, 85, 184] }, documentary: { name: "纪录片", tids: [177, 37, 178, 179, 180] }, movie: { name: "电影", tids: [23, 147, 145, 146, 83] }, tv: { name: "电视剧", tids: [11, 185, 187] } } } const selectors = { app: '#app', header: '#biliMainHeader', player: '#bilibili-player', playerWrap: '#playerWrap', playerWebscreen: '#bilibili-player.mode-webscreen', playerContainer: '#bilibili-player .bpx-player-container', playerController: '#bilibili-player .bpx-player-ctrl-btn', playerControllerBottomRight: '.bpx-player-control-bottom-right', playerTooltipArea: '.bpx-player-tooltip-area', playerTooltipTitle: '.bpx-player-tooltip-title', playerDanmuSetting: '.bpx-player-dm-setting', playerEndingRelateVideo: '.bpx-player-ending-related-item', volumeButton: '.bpx-player-ctrl-volume-icon', mutedButton: '.bpx-player-ctrl-muted-icon', video: '#bilibili-player video', videoWrap: '#bilibili-player .bpx-player-video-wrap', videoBwp: 'bwp-video', videoTitleArea: '#viewbox_report', videoFloatNav: '.fixed-sidenav-storage', videoComment: '#commentapp', videoCommentReplyList: '#comment .reply-list', videoRootReplyContainer: '#comment .root-reply-container', videoTime: 'a[data-type="seek"]', videoDescription: '#v_desc', videoDescriptionInfo: '#v_desc .basic-desc-info', videoDescriptionText: '#v_desc .desc-info-text', videoNextPlayAndRecommendLink: '.video-page-card-small .card-box', videoSectionsEpisodeLink: '.video-pod__list .video-pod__item', videoEpisodeListMultiMenuItem: '.bpx-player-ctrl-eplist-multi-menu-item', videoMultiPageLink: '#multi_page ul li', videoPreviousButton: '.bpx-player-ctrl-btn.bpx-player-ctrl-prev', videoNextButton: '.bpx-player-ctrl-btn.bpx-player-ctrl-next', bangumiApp: '#__next', bangumiComment: '#comment_module', bangumiFloatNav: '#__next div[class*="navTools_floatNavExp"] div[class*="navTools_navMenu"]', bangumiMainContainer: '.main-container', bangumiSectionsEpisodeLink: '#__next div[class*="numberList_wrapper"] div[class*="numberListItem_number_list_item"] ', qualitySwitchButtons: '.bpx-player-ctrl-quality-menu-item', screenModeWideEnterButton: '.bpx-player-ctrl-wide-enter', screenModeWideLeaveButton: '.bpx-player-ctrl-wide-leave', screenModeWebEnterButton: '.bpx-player-ctrl-web-enter', screenModeWebLeaveButton: '.bpx-player-ctrl-web-leave', screenModeFullControlButton: '.bpx-player-ctrl-full', danmukuBox: '#danmukuBox', danmuShowHideTip: 'div[aria-label="弹幕显示隐藏"]', membersContainer: '.members-info-container', membersUpAvatarFace: '.membersinfo-upcard:first-child picture img', upAvatarFace: '.up-info-container .up-avatar-wrap .bili-avatar .bili-avatar-face', upAvatarDecoration: '.up-info-container .up-avatar-wrap .bili-avatar .bili-avatar-pendent-dom .bili-avatar-img', upAvatarIcon: '.up-info-container .up-avatar-wrap .bili-avatar .bili-avatar-icon', setSkipTimeNodesPopover: '#setSkipTimeNodesPopover', setSkipTimeNodesPopoverToggleButton: '#setSkipTimeNodesPopoverToggleButton', setSkipTimeNodesPopoverHeaderExtra: '#setSkipTimeNodesPopover .header .extra', setSkipTimeNodesPopoverTips: '#setSkipTimeNodesPopover .tips', setSkipTimeNodesPopoverTipsDetail: '#setSkipTimeNodesPopover .tips .detail', setSkipTimeNodesPopoverTipsContents: '#setSkipTimeNodesPopover .tips .contents', setSkipTimeNodesPopoverRecords: '#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .records', setSkipTimeNodesPopoverClouds: '#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .clouds', setSkipTimeNodesPopoverResult: '#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .result', setSkipTimeNodesInput: '#setSkipTimeNodesInput', skipTimeNodesRecordsArray: '#skipTimeNodesRecordsArray', skipTimeNodesCloudsArray: '#skipTimeNodesCloudsArray', clearRecordsButton: '#clearRecordsButton', saveRecordsButton: '#saveRecordsButton', uploadSkipTimeNodesButton: '#uploadSkipTimeNodesButton', syncSkipTimeNodesButton: '#syncSkipTimeNodesButton', indexApp: '#i_cecream', indexRecommendVideoSix: '.recommended-container_floor-aside .feed-card:nth-child(-n+7)', indexRecommendVideoRollButtonWrapper: '.recommended-container_floor-aside .feed-roll-btn', indexRecommendVideoHistoryPopoverTitle: '#indexRecommendVideoHistoryPopoverTitle', indexRecommendVideoRollButton: '.recommended-container_floor-aside .feed-roll-btn button.roll-btn', indexRecommendVideoHistoryOpenButton: '#indexRecommendVideoHistoryOpenButton', indexRecommendVideoHistoryPopover: '#indexRecommendVideoHistoryPopover', indexRecommendVideoHistoryCategory: '#indexRecommendVideoHistoryCategory', indexRecommendVideoHistoryCategoryButtons: '#indexRecommendVideoHistoryCategory li', indexRecommendVideoHistoryCategoryButtonsExceptAll: '#indexRecommendVideoHistoryCategory li:not(.all)', indexRecommendVideoHistoryCategoryButtonAll: '#indexRecommendVideoHistoryCategory li.all', indexRecommendVideoHistoryList: '#indexRecommendVideoHistoryList', clearRecommendVideoHistoryButton: '#clearRecommendVideoHistoryButton', dynamicSettingPopover: '#dynamicSettingPopover', dynamicSettingSaveButton: '#dynamicSettingSaveButton', dynamicSettingPopoverTips: '#dynamicSettingPopoverTips', dynamicHeaderContainer: '#bili-header-container', videoSettingPopover: '#videoSettingPopover', videoSettingSaveButton: '#videoSettingSaveButton', notChargeHighLevelCover: '.not-charge-high-level-cover', switchSubtitleButton: '.bpx-player-ctrl-btn.bpx-player-ctrl-subtitle', AutoSkipSwitchInput: '#Auto-Skip-Switch', AutoEnableSubtitleSwitchInput: '#Auto-Enable-Subtitle', WebVideoLinkInput: '#Web-Video-Link', IsVip: '#Is-Vip', AutoLocate: '#Auto-Locate', AutoLocateVideo: '#Auto-Locate-Video', AutoLocateBangumi: '#Auto-Locate-Bangumi', TopOffset: '#Top-Offset', ClickPlayerAutoLocation: '#Click-Player-Auto-Location', AutoQuality: '#Auto-Quality', Quality4K: '#Quality-4K', Quality8K: '#Quality-8K', Checkbox4K: '.adjustment_checkbox.fourK', Checkbox8K: '.adjustment_checkbox.eightK', FourKAndEightK: '.fourK,.eightK', SelectScreenMode: 'input[name="Screen-Mode"]', WebfullUnlock: '#Webfull-Unlock', AutoReload: '#Auto-Reload', AutoSkip: '#Auto-Skip', InsertVideoDescriptionToComment: '#Insert-Video-Description-To-Comment', PauseVideo: '#PauseVideo', ContinuePlay: '#ContinuePlay', AutoSubtitle: '#AutoSubtitle', } const shadowRootSelectors = { videoComments: '#comment bili-comments', videoComment: '#feed > bili-comment-thread-renderer', biliRichText: '#content > bili-rich-text', videoTime: '#contents > a[data-type="seek"][data-video-time]', } const vals = { is_vip: () => { return utils.getValue('is_vip') }, player_type: () => { return utils.getValue('player_type') }, offset_top: () => { return Math.trunc(utils.getValue('offset_top')) }, auto_locate: () => { return utils.getValue('auto_locate') }, get_offset_method: () => { return utils.getValue('get_offset_method') }, auto_locate_video: () => { return utils.getValue('auto_locate_video') }, auto_locate_bangumi: () => { return utils.getValue('auto_locate_bangumi') }, click_player_auto_locate: () => { return utils.getValue('click_player_auto_locate') }, video_player_offset_top: () => { return Math.trunc(utils.getValue('video_player_offset_top')) }, bangumi_player_offset_top: () => { return Math.trunc(utils.getValue('bangumi_player_offset_top')) }, current_screen_mode: () => { return utils.getValue('current_screen_mode') }, selected_screen_mode: () => { return utils.getValue('selected_screen_mode') }, auto_select_video_highest_quality: () => { return utils.getValue('auto_select_video_highest_quality') }, contain_quality_4k: () => { return utils.getValue('contain_quality_4k') }, contain_quality_8k: () => { return utils.getValue('contain_quality_8k') }, webfull_unlock: () => { return utils.getValue('webfull_unlock') }, auto_reload: () => { return utils.getValue('auto_reload') }, auto_skip: () => { return utils.getValue('auto_skip') }, insert_video_description_to_comment: () => { return utils.getValue('insert_video_description_to_comment') }, web_video_link: () => { return utils.getValue('web_video_link') }, signIn_date: () => { return utils.getValue('signIn_date') }, dev_checkScreenModeSwitchSuccess_method: () => { return utils.getValue('dev_checkScreenModeSwitchSuccess_method') }, pause_video: () => { return utils.getValue('pause_video') }, continue_play: () => { return utils.getValue('continue_play') }, auto_subtitle: () => { return utils.getValue('auto_subtitle') }, } const styles = { BilibiliAdjustment: '::-webkit-scrollbar{width:6px!important;height:6px!important}::-webkit-scrollbar-track-piece{border-radius:0!important;background-color:#212121!important}::-webkit-scrollbar-thumb:vertical{height:5px!important;border-radius:6px!important;background-color:#00a1d6!important}::-webkit-scrollbar-thumb:horizontal{width:5px!important;border-radius:6px!important;background-color:#00a1d6!important}::-webkit-scrollbar-corner{border-radius:0!important;background-color:#141414!important}.adjustment_popover{position:fixed;top:50%;left:50%;box-sizing:border-box;margin:0;padding:15px;width:400px;max-height:70vh;border:0;border-radius:6px;font-size:1em;transform:translate(-50%,-50%);overscroll-behavior:contain;background:#212121;overflow-y:auto;color:#868686;border:1px solid #424242}.adjustment_popover::backdrop{backdrop-filter:blur(3px)}.adjustment_popoverTitle{margin-bottom:15px;text-align:center;font-weight:700;font-size:22px}.adjustment_popoverTitle .subTitle{font-size:14px}.recommend{padding:3px;border:1px solid #424242;border-radius:6px;box-sizing:border-box;text-align:center;margin-bottom:15px;font-size:14px}.recommend a{color:#00a1d6;text-decoration:none}.adjustment_buttonGroup{display:flex;margin-top:10px;align-items:center;justify-content:end;gap:10px}.adjustment_button{display:inline-block;box-sizing:border-box;margin:0;padding:10px 20px;outline:0;border:1px solid #424242;border-radius:4px;background:#fff;color:#606266;text-align:center;white-space:nowrap;font-weight:500;font-size:14px;line-height:1;cursor:pointer;transition:.1s;-webkit-appearance:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.adjustment_button.plain:disabled,.adjustment_button.plain:disabled:active,.adjustment_button.plain:disabled:focus,.adjustment_button.plain:disabled:hover,.adjustment_button:disabled,.adjustment_button:disabled:active,.adjustment_button:disabled:focus,.adjustment_button:disabled:hover{border-color:#ebeef5;background-color:#fff;background-image:none;color:#c0c4cc;cursor:not-allowed}.adjustment_button.primary{border-color:#00a1d6;background-color:#00a1d6;color:#fff}.adjustment_button.success{border-color:#67c23a;background-color:#67c23a;color:#fff}.adjustment_button.info{border-color:#909399;background-color:#909399;color:#fff}.adjustment_button.warning{border-color:#e6a23c;background-color:#e6a23c;color:#fff}.adjustment_button.danger{border-color:#f56c6c;background-color:#f56c6c;color:#fff}.adjustment_button.primary:focus,.adjustment_button.primary:hover{border-color:#66b1ff;background:#66b1ff;color:#fff}.adjustment_button.success:focus,.adjustment_button.success:hover{border-color:#85ce61;background:#85ce61;color:#fff}.adjustment_button.info:focus,.adjustment_button.info:hover{border-color:#a6a9ad;background:#a6a9ad;color:#fff}.adjustment_button.warning:focus,.adjustment_button.warning:hover{border-color:#ebb563;background:#ebb563;color:#fff}.adjustment_button.danger:focus,.adjustment_button.danger:hover{border-color:#f78989;background:#f78989;color:#fff}.adjustment_button.primary.plain{border-color:#b3d8ff;background:#ecf5ff;color:#409eff}.adjustment_button.success.plain{border-color:#c2e7b0;background:#f0f9eb;color:#67c23a}.adjustment_button.info.plain{border-color:#a6a9ad;background:#a6a9ad;color:#fff}.adjustment_button.warning.plain{border-color:#f5dab1;background:#fdf6ec;color:#e6a23c}.adjustment_button.danger.plain{border-color:#fbc4c4;background:#fef0f0;color:#f56c6c}.adjustment_button.primary.plain:focus,.adjustment_button.primary.plain:hover{border-color:#409eff;background:#409eff;color:#fff}.adjustment_button.success.plain:focus,.adjustment_button.success.plain:hover{border-color:#67c23a;background-color:#67c23a;color:#fff}.adjustment_button.info.plain:focus,.adjustment_button.info.plain:hover{border-color:#909399;background-color:#909399;color:#fff}.adjustment_button.warning.plain:focus,.adjustment_button.warning.plain:hover{border-color:#e6a23c;background-color:#e6a23c;color:#fff}.adjustment_button.danger.plain:focus,.adjustment_button.danger.plain:hover{border-color:#f56c6c;background-color:#f56c6c;color:#fff}.adjustment_button.primary:disabled,.adjustment_button.primary:disabled:active,.adjustment_button.primary:disabled:focus,.adjustment_button.primary:disabled:hover{border-color:#a0cfff;background-color:#a0cfff;color:#fff}.adjustment_button.success:disabled,.adjustment_button.success:disabled:active,.adjustment_button.success:disabled:focus,.adjustment_button.success:disabled:hover{border-color:#b3e19d;background-color:#b3e19d;color:#fff}.adjustment_button.info:disabled,.adjustment_button.info:disabled:active,.adjustment_button.info:disabled:focus,.adjustment_button.info:disabled:hover{border-color:#c8c9cc;background-color:#c8c9cc;color:#fff}.adjustment_button.warning:disabled,.adjustment_button.warning:disabled:active,.adjustment_button.warning:disabled:focus,.adjustment_button.warning:disabled:hover{border-color:#f3d19e;background-color:#f3d19e;color:#fff}.adjustment_button.danger:disabled,.adjustment_button.danger:disabled:active,.adjustment_button.danger:disabled:focus,.adjustment_button.danger:disabled:hover{border-color:#fab6b6;background-color:#fab6b6;color:#fff}.adjustment_button.primary.plain:disabled,.adjustment_button.primary.plain:disabled:active,.adjustment_button.primary.plain:disabled:focus,.adjustment_button.primary.plain:disabled:hover{border-color:#d9ecff;background-color:#ecf5ff;color:#8cc5ff}.adjustment_button.success.plain:disabled,.adjustment_button.success.plain:disabled:active,.adjustment_button.success.plain:disabled:focus,.adjustment_button.success.plain:disabled:hover{border-color:#e1f3d8;background-color:#f0f9eb;color:#a4da89}.adjustment_button.info.plain:disabled,.adjustment_button.info.plain:disabled:active,.adjustment_button.info.plain:disabled:focus,.adjustment_button.info.plain:disabled:hover{border-color:#e9e9eb;background-color:#f4f4f5;color:#bcbec2}.adjustment_button.warning.plain:disabled,.adjustment_button.warning.plain:disabled:active,.adjustment_button.warning.plain:disabled:focus,.adjustment_button.warning.plain:disabled:hover{border-color:#faecd8;background-color:#fdf6ec;color:#f0c78a}.adjustment_button.danger.plain:disabled,.adjustment_button.danger.plain:disabled:active,.adjustment_button.danger.plain:disabled:focus,.adjustment_button.danger.plain:disabled:hover{border-color:#fde2e2;background-color:#fef0f0;color:#f9a7a7}.adjustment_tips{display:inline-block;box-sizing:border-box;padding:3px 5px;height:fit-content;border:1px solid #d9ecff;border-radius:4px;background-color:#272727;color:#409eff;font-size:14px;line-height:1.5}.adjustment_tips.info{border-color:#424242;background-color:#272727;color:#868686}.adjustment_tips.success{border-color:#e1f3d8;background-color:#f0f9eb;color:#67c23a}.adjustment_tips.warning{border-color:#faecd8;background-color:#fdf6ec;color:#e6a23c}.adjustment_tips.danger{border-color:#fde2e2;background-color:#fef0f0;color:#f56c6c}.adjustment_form,.adjustment_form_item{display:flex;flex-direction:column}.adjustment_form{gap:15px}.adjustment_form_item{gap:15px;background:#2c2c2c;padding:15px;border-radius:6px}.adjustment_checkbox,.adjustment_form_item_content{display:flex;align-items:center;justify-content:space-between}.adjustment_checkbox_btn .knob,.adjustment_checkbox_btn .btn-bg{position:absolute;top:0;right:0;bottom:0;left:0}.adjustment_checkbox_btn,.adjustment_radio_btn{position:relative;top:50%;width:54px;height:26px;overflow:hidden}.adjustment_checkbox_btn.btn-pill,.adjustment_checkbox_btn.btn-pill>.btn-bg{border-radius:100px}.adjustment_checkbox_btn .checkbox,.adjustment_checkbox .radio{position:relative;width:100%;height:100%;padding:0;margin:0;opacity:0;cursor:pointer;z-index:3}.adjustment_checkbox_btn .knob{z-index:2}.adjustment_checkbox_btn .btn-bg{width:100%;background-color:#fcebeb;transition:.3s ease all;z-index:1}.adjustment_checkbox_btn .knob::before{content:"";position:absolute;top:3px;left:3px;width:9px;height:4px;color:#fff;font-size:10px;font-weight:bold;text-align:center;line-height:1;padding:8px 6px;background-color:#f44336;border-radius:50%;transition:.3s cubic-bezier(0.18,0.89,0.35,1.15) all}.adjustment_checkbox_btn .checkbox:checked+.knob::before{content:"";left:30px;background-color:#00a1d6}.adjustment_checkbox_btn .checkbox:checked ~ .btn-bg{background-color:#ebf7fc}.adjustment_checkbox_btn .knob,.adjustment_checkbox_btn .knob::before,.adjustment_checkbox_btn .btn-bg{transition:.3s ease all}.adjustment_radio_btn{gap:15px;width:66px;overflow:auto;height:26px;display:flex;align-items:center;justify-content:flex-start}.adjustment_radio_btn .radio{width:auto;height:auto}.adjustment_radio_btn .circle{position:absolute;top:0;right:0;bottom:0;left:0;z-index:2}.adjustment_radio_btn .radio+.circle::before{content:"";position:absolute;top:7px;left:5px;width:8px;height:8px;border-radius:50%;background:#424242;border:3px solid #272727;outline:2px solid #424242}.adjustment_radio_btn .radio:checked+.circle::before{background-color:#00a1d6;border-color:#ebf7fc;outline-color:#00a1d6}.adjustment_form_item label{font-size:18px}.adjustment_checkboxGroup{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:10px}.adjustment_checkboxGroup span{color:#666}.adjustment_checkbox{width:100%;font-size:16px;gap:3px}.adjustment_input{display:inline-flex;padding:1px 11px;outline:0;border:1px solid #424242;border-radius:6px;background:#272727;line-height:32px;cursor:text;align-items:center;justify-content:center;color:#868686;width:100%;box-sizing:border-box}', IndexAdjustment: '#indexRecommendVideoHistoryOpenButton{margin-top:10px}#indexRecommendVideoHistoryPopover{width:600px}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryPopoverTitle{display:flex;box-sizing:border-box;padding-bottom:15px;border-bottom:1px solid #424242;font-weight:700;font-size:22px;align-items:center;justify-content:space-between}#indexRecommendVideoHistoryPopover ul{display:flex;flex-direction:column;align-items:center;justify-content:space-between}ul#indexRecommendVideoHistoryCategory{display:grid;margin:10px 0 0;padding-bottom:10px;border-bottom:1px solid #424242!important;gap:5px;grid-template-columns:repeat(8,1fr);align-items:center;justify-content:center}ul#indexRecommendVideoHistoryCategory li{padding:3px 0!important;border:1px solid;border-radius:6px;justify-content:center}#indexRecommendVideoHistoryPopover ul li{display:flex;align-items:center;padding:7px 0;width:100%;border:1px solid #424242!important;line-height:24px}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryList li{border-width:0 0 1px 0!important}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryList li span{display:flex;width:32px;height:24px;margin-right:5px;border-radius:4px; overflow:hidden}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryList li span img{width:100%;height:24px;object-fit:inherit}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryList li a{color:#868686!important}#indexRecommendVideoHistoryPopover #indexRecommendVideoHistoryList li:hover a{color:#00a1d6!important}#clearRecommendVideoHistoryButton{position:sticky;display:flex;padding:10px;width:80px;border-radius:6px;background:#00a1d6;color:#fff;font-size:15px;line-height:16px;cursor:pointer;align-items:center;justify-content:center}', VideoPageAdjustment: '.back-to-top-wrap .locate{visibility:hidden}.back-to-top-wrap:has(.visible) .locate{visibility:visible}.bpx-player-container[data-screen=full] #goToComments{opacity:.6;cursor:not-allowed;pointer-events:none}#comment-description .user-name{display:flex;padding:0 5px;height:22px;border:1px solid;border-radius:4px;align-items:center;justify-content:center}.bpx-player-ctrl-skip{border:none!important;background:0 0!important}.bpx-player-container[data-screen=full] #setSkipTimeNodesPopoverToggleButton,.bpx-player-container[data-screen=web] #setSkipTimeNodesPopoverToggleButton{height:32px!important;line-height:32px!important}#setSkipTimeNodesPopover{top:50%!important;left:50%!important;box-sizing:border-box!important;padding:15px!important;max-width:456px!important;border:0!important;border-radius:6px!important;font-size:14px!important;transform:translate(-50%,-50%)!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper{display:flex!important;flex-direction:column!important;gap:7px!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper button{display:flex!important;width:100%;height:34px!important;border-style:solid!important;border-width:1px!important;border-radius:6px!important;text-align:center!important;line-height:34px!important;cursor:pointer;align-items:center!important;justify-content:center!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper button:disabled{cursor:not-allowed}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .header{display:flex!important;font-weight:700!important;align-items:center!important;justify-content:space-between!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .header .title{font-weight:700!important;font-size:16px!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .header .extra{font-size:12px!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .header .extra,#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .result{padding:2px 5px!important;border:1px solid #d9ecff!important;border-radius:6px!important;background-color:#ecf5ff!important;color:#409eff!important;font-weight:400!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .success{display:flex!important;padding:2px 5px!important;border-color:#e1f3d8!important;background-color:#f0f9eb!important;color:#67c23a!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .danger{display:flex!important;padding:2px 5px!important;border-color:#fde2e2!important;background-color:#fef0f0!important;color:#f56c6c!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .handles{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:7px!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips{position:relative!important;overflow:hidden;box-sizing:border-box!important;padding:7px!important;border-color:#e9e9eb!important;border-radius:6px!important;background-color:#f4f4f5!important;color:#909399!important;font-size:13px!important;transition:height .3s!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips.open{height:134px!important;line-height:20px!important;}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips.close{height:34px!important;line-height:22px!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips .detail{position:absolute!important;top:9px!important;right:7px!important;display:flex!important;cursor:pointer!important;transition:transform .3s!important}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips .detail.open{transform:rotate(0)}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .tips .detail.close{transform:rotate(180deg)}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .records{display:none;flex-direction:column!important;gap:7px}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .records .recordsButtonsGroup{display:flex!important;align-items:center!important;justify-content:space-between!important;gap:7px!important}#clearRecordsButton{border-color:#d3d4d6!important;background:#f4f4f5!important;color:#909399!important}#clearRecordsButton:disabled{border-color:#e9e9eb!important;background-color:#f4f4f5!important;color:#bcbec2!important}#saveRecordsButton{border-color:#c2e7b0!important;background:#f0f9eb!important;color:#67c23a!important}#saveRecordsButton:disabled{border-color:#e1f3d8!important;background-color:#f0f9eb!important;color:#a4da89!important}#setSkipTimeNodesInput{box-sizing:border-box!important;padding:5px!important;width:calc(100% - 39px)!important;height:34px!important;border:1px solid #cecece!important;border-radius:6px!important;line-height:34px!important}#uploadSkipTimeNodesButton,#syncSkipTimeNodesButton{width:52px!important;height:34px!important;border:none!important;background:#00a1d6!important;color:#fff!important}#uploadSkipTimeNodesButton:hover{background:#00b5e5!important}#skipTimeNodesRecordsArray{display:flex!important;padding:2px 5px!important;border-radius:6px!important}#bilibili-player .bpx-player-video-wrap{position:relative}#bilibili-player .bpx-player-video-wrap::before{position:absolute;display:block;background:var(--video-cover) top left no-repeat;background-size:cover;content:"";inset:0}#setSkipTimeNodesPopover .setSkipTimeNodesWrapper .records{display:none}', BodyHidden: 'body{overflow:hidden!important}', ResetPlayerLayout: 'body{padding-top:0;position:auto}#playerWrap{display:block}#bilibili-player{height:auto;position:relative}.bpx-player-mini-warp{display:none}', UnlockWebscreen: 'body.webscreen-fix{padding-top:BODYHEIGHT;position:unset}#bilibili-player.mode-webscreen{height:BODYHEIGHT;position:absolute}#playerWrap{display:none}#danmukuBox{margin-top:0}', FreezeHeaderAndVideoTitle: '#biliMainHeader{height:64px!important}#viewbox_report{height:108px!important;padding-top:22px!important}.members-info-container{height:86px!important;overflow:hidden!important;padding-top:11px!important}.membersinfo-wide .header{display:none!important}', DynamicSetting: '#dynamicSettingPopoverTitle{margin-bottom:15px;text-align:center;font-weight:700;font-size:21px}#dynamicSettingPopover #dynamicSettingPopoverTips{margin-top:5px}', VideoSetting: '#videoSettingPopover{width:550px;max-height:90vh}#Top-Offset{width:100px}.screen-mode .adjustment_checkboxGroup{flex-direction:row}.screen-mode .adjustment_checkboxGroup .adjustment_checkbox:last-child .adjustment_radio_btn{width:98px}', UnlockEpisodeSelector: '.bpx-player-control-bottom-right .bpx-player-ctrl-btn.bpx-player-ctrl-eplist{visibility:visible!important;width:36px!important}.bpx-player-ctrl-eplist-menu-wrap{min-height:auto!important;height:fit-content;overscroll-behavior:contain}' } const regexps = { // 如果使用全局检索符(g),则在多次使用 RegExp.prototype.test() 时会导致脚本执行失败, // 因为在全局检索符下(g), RegExp.prototype.test() 在匹配成功后会设置下一次匹配的起始索引 lastindex // 但是当前页面的 URL 为固定字符串,在上一次匹配成功后设置的 lastindex 后没有其他字符串,所以会匹配失败 // 例如:使用 /asifadeaway/g.test('https://www.asifadeaway.com/post/Watched.html') 检查是否含有字符串'asifadeaway',此时返回 true 并将 lastindex 设为 23 // 后续再执行一次同样的检查则会返回 false 并将 lastindex 设为 0,因为继上次检查匹配成功后再次检查会从索引位置 23 开始,而此位置往后并没有字符串'asifadeaway' // 以下的正则表达式都包含了整个 URL 字符串,所以匹配成功一次之后 lastindex 会被设置为 URL 字符串的长度,再次执行后必定返回 false // 所以会产生匹配成功之后再次匹配就会失败的奇怪现象,就是因为 lastindex 的值在上一次匹配成功后被设为了字符串的长度 // 因此不使用全局检索符(g) video: /.*:\/\/www\.bilibili\.com\/(video|bangumi\/play|list)\/.*/i, dynamic: /.*:\/\/t\.bilibili\.com\/.*/i, } const utils = { /** * 初始化所有数据 * - #region 初始化所有数据 */ initValue() { const value = [{ name: 'is_vip', value: true, }, { name: 'player_type', value: 'video', }, { name: 'offset_top', value: 5, }, { name: 'video_player_offset_top', value: 168, }, { name: 'bangumi_player_offset_top', value: 104, }, { name: 'auto_locate', value: true, }, { name: 'get_offset_method', value: 'function', }, { name: 'auto_locate_video', value: true, }, { name: 'auto_locate_bangumi', value: true, }, { name: 'click_player_auto_locate', value: true, }, { name: 'current_screen_mode', value: 'normal', }, { name: 'selected_screen_mode', value: 'wide', }, { name: 'auto_select_video_highest_quality', value: true, }, { name: 'contain_quality_4k', value: false, }, { name: 'contain_quality_8k', value: false, }, { name: 'webfull_unlock', value: false, }, { name: 'auto_reload', value: false, }, { name: 'auto_skip', value: false, }, { name: 'insert_video_description_to_comment', value: true }, { name: 'web_video_link', value: 'https://t.bilibili.com/?tab=video' }, { name: 'signIn_date', value: '' }, { name: 'dev_checkScreenModeSwitchSuccess_method', value: 'interval' }, { name: 'pause_video', value: false }, { name: 'continue_play', value: false }, { name: 'auto_subtitle', value: false }, ] value.forEach(v => { if (utils.getValue(v.name) === undefined) { utils.setValue(v.name, v.value) } }) }, // #endregion 初始化所有数据 /** * 获取自定义数据 * - #region 获取自定义数据 * @param {String} 数据名称 * @returns 数据数值 */ getValue(name) { return GM_getValue(name) }, // #endregion 获取自定义数据 /** * 写入自定义数据 * - #region 写入自定义数据 * @param {String} 数据名称 * @param {*} 数据数值 */ setValue(name, value) { GM_setValue(name, value) }, // #endregion 写入自定义数据 /** * 休眠 * - #region 休眠 * @param {Number} 时长 * @returns */ sleep(times) { return new Promise(resolve => setTimeout(resolve, times)) }, // #endregion 休眠 /** * 通过名称获取指定cookie的值 * - #region 通过名称获取指定cookie的值 * @param {String} name cookie中某一项的名称 * @returns */ getCookieByName(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); return null; }, // #endregion 通过名称获取指定cookie的值 /** * 判断数组长度是否为偶数 * - #region 判断数组长度是否为偶数 */ isArrayLengthEven(arr) { return arr.length % 2 === 0 }, // #endregion 判断数组长度是否为偶数 /** * 向文档插入自定义样式 * - #region 向文档插入自定义样式 * @param {String} id 样式表id * @param {String} css 样式内容 */ insertStyleToDocument(id, css) { const styleElement = GM_addStyle(css) styleElement.id = id }, // #endregion 向文档插入自定义样式 /** * 自定义日志打印 * - #region 自定义日志打印 * - info->信息;warn->警告 * - error->错误;debug->调试 */ logger: { info(content) { console.info('%c播放页调整', 'color:white;background:#006aff;padding:2px;border-radius:2px', content); }, warn(content) { console.warn('%c播放页调整', 'color:white;background:#ff6d00;padding:2px;border-radius:2px', content); }, error(content) { console.error('%c播放页调整', 'color:white;background:#f33;padding:2px;border-radius:2px', content); }, debug(content) { console.info('%c播放页调整(调试)', 'color:white;background:#cc00ff;padding:2px;border-radius:2px', content); }, }, // #endregion 自定义日志打印 /** * 检查当前文档是否被激活 * - #region 检查当前文档是否被激活 */ checkDocumentIsHidden() { // 定义可见性变化事件名称 const visibilityChangeEventNames = ['visibilitychange', 'mozvisibilitychange', 'webkitvisibilitychange', 'msvisibilitychange'] // 确定文档隐藏属性名称 const documentHiddenPropertyName = visibilityChangeEventNames.find(name => name in document) || 'onfocusin' in document || 'onpageshow' in window ? 'hidden' : null if (documentHiddenPropertyName !== null) { // 定义一个函数来检查文档是否隐藏 const isHidden = () => document[documentHiddenPropertyName] // 定义一个函数来处理可见性变化事件 const onChange = () => isHidden() // 为所有相关事件添加监听器 utils.addVisibilityEventListeners(visibilityChangeEventNames, onChange) // 返回当前的隐藏状态 return isHidden() } // 如果无法判断是否隐藏,则返回undefined return undefined }, // 辅助函数,用于添加事件监听器 addVisibilityEventListeners(eventList, handler) { eventList.forEach(eventName => document.addEventListener(eventName, handler)) window.addEventListener('focus', handler) window.addEventListener('blur', handler) window.addEventListener('pageshow', handler) window.addEventListener('pagehide', handler) }, // #endregion 检查当前文档是否被激活 /** * 刷新当前页面 * - #region 刷新当前页面 */ reloadCurrentTab(...args) { if (args && args[0] === true) { location.reload() } else if (vals.auto_reload()) location.reload() }, // #endregion 刷新当前页面 /** * 滚动文档至目标位置 * - #region 滚动文档至目标位置 * @param {Number} 滚动距离 */ documentScrollTo(offset) { document.documentElement.scrollTop = offset }, // #endregion 滚动文档至目标位置 /** * 获取指定 meta 标签的属性值 * - #region 获取指定meta标签的属性值 * @param {*} attribute 属性名称 * @returns 属性值 */ async getMetaContent(attribute) { const meta = await utils.getElementAndCheckExistence(`meta[${attribute}]`) if (meta) { return meta.getAttribute('content') } else { return null } }, // #endregion 获取指定meta标签的属性值 /** * 获取 Body 元素高度 * - #region 获取Body元素高度 * @returns Body 元素高度 */ getBodyHeight() { const bodyHeight = document.body.clientHeight || 0 const docHeight = document.documentElement.clientHeight || 0 return bodyHeight < docHeight ? bodyHeight : docHeight }, // #endregion 获取Body元素高度 /** * 确保页面销毁时清除所有定时器 * - #region 确保页面销毁时清除所有定时器 */ clearAllTimersWhenCloseTab() { window.addEventListener('beforeunload', () => { for (let id of arrays.intervalIds) { clearInterval(id) } arrays.intervalIds = [] }) }, // #endregion 确保页面销毁时清除所有定时器 /** * 获取目标元素至文档距离 * - #region 获取目标元素至文档距离 * @param {String} 目标元素 * @returns 顶部和左侧距离 */ getElementOffsetToDocument(element) { let rect, win if (!element.getClientRects().length) { return { top: 0, left: 0 } } rect = element.getBoundingClientRect() win = element.ownerDocument.defaultView return { top: rect.top + win.pageYOffset, left: rect.left + win.pageXOffset } }, // #endregion 获取目标元素至文档距离 /** * 创建并插入元素至目标元素 * - #region 创建并插入元素至目标元素 * @param {String} Html 字符串 * @param {Element} 目标元素 * @param {String} 插入方法(before/after/prepend/append) * @returns 被创建的元素 */ createElementAndInsert(HtmlString, target, method) { const element = elmGetter.create(HtmlString, target) target[method](element) return element }, // #endregion 创建并插入元素至目标元素 /** * 判断函数是否为异步函数 * - #region 判断函数是否为异步函数 * - 不使用 targetFunction() instanceof Promise 方法 * - 因为这会导致 targetFunction 函数在此处执行一遍,从而增加 vars 里相关的计数变量 * - 当之后真正执行时会因为相关计数变量值不等于 1 导致在 executeFunctionsSequentially 函数里获取不到返回值 */ isAsyncFunction(targetFunction) { return targetFunction.constructor.name === 'AsyncFunction' }, // #endregion 判断函数是否为异步函数 /** * 按顺序依次执行函数数组中的函数 * - #region 按顺序依次执行函数数组中的函数 * @param {Array} functionsArray 待执行的函数数组 * - 当函数为异步函数时,只有当前一个函数执行完毕时才会继续执行下一个函数 * - 当函数为同步函数时,则只会执行相应函数 */ executeFunctionsSequentially(functionsArray) { if (functionsArray.length > 0) { // console.log(functionsArray.length) const currentFunction = functionsArray.shift() if (utils.isAsyncFunction(currentFunction)) { currentFunction().then(result => { // console.log(currentFunction.name, result) if (result) { const { message, callback } = result if (message) utils.logger.info(message) if (callback && Array.isArray(callback)) utils.executeFunctionsSequentially(callback) } // else utils.logger.debug(currentFunction.name) utils.executeFunctionsSequentially(functionsArray) }).catch(error => { utils.logger.error(error) utils.reloadCurrentTab() }) } else { // console.log(currentFunction.name, result) const result = currentFunction() if (result) { const { message } = result if (message) utils.logger.info(message) } } } }, // #endregion 按顺序依次执行函数数组中的函数 /** * 检查元素数组中元素是否存在 * - #region 检查元素数组中元素是否存在 * @param {Array} elementsArray 元素数组 */ checkElementExistence(elementsArray) { if (Array.isArray(elementsArray)) { return elementsArray.map(element => Boolean(element)) } else { return [Boolean(elementsArray)] } }, // #endregion 检查元素数组中元素是否存在 /** * 获取元素并检查元素是否存在 * - #region 获取元素并检查元素是否存在 * @param {String | String[]} selectors 元素选择器 * @param {Number} delay 超时时间 * @param {Boolean} debug debug 开关 * @returns 获取的元素 */ async getElementAndCheckExistence(selectors, ...args) { let delay = 7000, debug = false if (args.length === 1) { const type = typeof args[0] if (type === 'number') delay = args[0] if (type === 'boolean') debug = args[0] } if (args.length === 2) { delay = args[0] debug = args[1] } const result = await elmGetter.get(selectors, delay) if (debug) utils.logger.debug(utils.checkElementExistence(result)) return result }, // #endregion 获取元素并检查元素是否存在 /** * 为元素添加监听器并执行相应函数 * - #region 为元素添加监听器并执行相应函数 */ async addEventListenerToElement() { if (window.location.href === 'https://www.bilibili.com/') { const [$indexRecommendVideoRollButton, $clearRecommendVideoHistoryButton] = await utils.getElementAndCheckExistence([selectors.indexRecommendVideoRollButton, selectors.clearRecommendVideoHistoryButton]) $indexRecommendVideoRollButton.addEventListener('click', () => { const functionsArray = [ modules.setIndexRecordRecommendVideoHistory, modules.getIndexRecordRecommendVideoHistory, modules.generatorVideoCategories ] setTimeout(() => { vars.setIndexRecordRecommendVideoHistoryArrayCount = 0 utils.executeFunctionsSequentially(functionsArray) }, 1000) }) $clearRecommendVideoHistoryButton.addEventListener('click', () => { modules.clearRecommendVideoHistory() }) } if (regexps.video.test(window.location.href)) { if (window.onurlchange === null) { window.addEventListener('urlchange', () => { modules.functionsNeedToExecuteWhenUrlHasChanged() // utils.logger.debug('URL改变了!') }) } else { modules.clickRelatedVideoAutoLocation() } window.addEventListener("popstate", () => { // utils.logger.debug('URL改变了!!') modules.functionsNeedToExecuteWhenUrlHasChanged() }) const [$playerContainer, $AutoSkipSwitchInput, $AutoEnableSubtitleSwitchInput] = await utils.getElementAndCheckExistence([selectors.playerContainer, selectors.AutoSkipSwitchInput, selectors.AutoEnableSubtitleSwitchInput]) $playerContainer.addEventListener('fullscreenchange', (event) => { let isFullscreen = document.fullscreenElement === event.target if (!isFullscreen) modules.locationToPlayer() }) document.addEventListener('keydown', (event) => { if (event.key === 'j') { $AutoSkipSwitchInput.click() } }) document.addEventListener('keydown', (event) => { if (event.key === 'l') { $AutoEnableSubtitleSwitchInput.click() } }) if (vals.auto_skip()) { const [$video, $setSkipTimeNodesPopoverToggleButton, $setSkipTimeNodesPopoverRecords, $skipTimeNodesRecordsArray, $saveRecordsButton] = await utils.getElementAndCheckExistence([selectors.video, selectors.setSkipTimeNodesPopoverToggleButton, selectors.setSkipTimeNodesPopoverRecords, selectors.skipTimeNodesRecordsArray, selectors.saveRecordsButton]) document.addEventListener('keydown', (event) => { if (event.key === 'k') { const currentTime = Math.ceil($video.currentTime) arrays.skipNodesRecords.push(currentTime) arrays.skipNodesRecords = Array.from(new Set(arrays.skipNodesRecords)) if (arrays.skipNodesRecords.length > 0) { $setSkipTimeNodesPopoverRecords.style.display = 'flex' $skipTimeNodesRecordsArray.innerText = `打点数据:${JSON.stringify(arrays.skipNodesRecords)}` if (utils.isArrayLengthEven(arrays.skipNodesRecords)) { $skipTimeNodesRecordsArray.classList.remove('danger') $skipTimeNodesRecordsArray.classList.add('success') $saveRecordsButton.removeAttribute('disabled') } else { $skipTimeNodesRecordsArray.classList.remove('success') $skipTimeNodesRecordsArray.classList.add('danger') $saveRecordsButton.setAttribute('disabled', true) } } } if (event.key === 'g') { $setSkipTimeNodesPopoverToggleButton.click() } }) } } } // #endregion 为元素添加监听器并执行相应函数 } const biliApis = { /** * 获取解密WBI鉴权后的参数 * - #region 获取解密WBI鉴权后的参数 * @param {Object} originalParams * @returns */ async getQueryWithWbi(originalParams) { const mixinKeyEncTab = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] // 对 imgKey 和 subKey 进行字符顺序打乱编码 const getMixinKey = (orig) => mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32) // 为请求参数进行 wbi 签名 const encWbi = (params, img_key, sub_key) => { const mixin_key = getMixinKey(img_key + sub_key), curr_time = Math.round(Date.now() / 1000), chr_filter = /[!'()*]/g Object.assign(params, { wts: curr_time }) // 添加 wts 字段 // 按照 key 重排参数 const query = Object.keys(params).sort().map(key => { // 过滤 value 中的 "!'()*" 字符 const value = params[key].toString().replace(chr_filter, '') return `${encodeURIComponent(key)}=${encodeURIComponent(value)}` }).join('&') const wbi_sign = MD5(query + mixin_key) // 计算 w_rid return query + '&w_rid=' + wbi_sign } // 获取最新的 img_key 和 sub_key const getWbiKeys = async () => { const url = 'https://api.bilibili.com/x/web-interface/nav' const res = await axios.get(url, { withCredentials: true }) const { data: { wbi_img: { img_url, sub_url } } } = res.data return { img_key: img_url.slice( img_url.lastIndexOf('/') + 1, img_url.lastIndexOf('.') ), sub_key: sub_url.slice( sub_url.lastIndexOf('/') + 1, sub_url.lastIndexOf('.') ) } } const main = async () => { const web_keys = await getWbiKeys() const params = originalParams, img_key = web_keys.img_key, sub_key = web_keys.sub_key const query = encWbi(params, img_key, sub_key) return query } return main() }, // #endregion 获取解密WBI鉴权后的参数 /** * 获取视频基本信息 * - #region 获取视频基本信息 * @param {String} videoId 视频ID(video BVID) * @returns videoInfo */ async getVideoInformation(videoId) { const url = `https://api.bilibili.com/x/web-interface/view?bvid=${videoId}` const { data, data: { code } } = await axios.get(url, { withCredentials: true }) if (code === 0) return data else if (code === -400) utils.logger.info("获取视频基本信息丨请求错误") else if (code === -403) utils.logger.info("获取视频基本信息丨权限不足") else if (code === -404) utils.logger.info("获取视频基本信息丨无视频") else if (code === 62002) utils.logger.info("获取视频基本信息丨稿件不可见") else if (code === 62004) utils.logger.info("获取视频基本信息丨稿件审核中") else if (code === 'ERR_BAD_REQUEST') utils.logger.info("获取视频基本信息丨请求失败") else utils.logger.warn("获取视频基本信息丨请求错误") }, // #endregion 获取视频基本信息 /** * 获取用户基本信息 * - #region 获取用户基本信息 * @param {String} userId 用户ID * @returns userInfo */ async getUserInformation(userId) { const url = `https://api.bilibili.com/x/web-interface/card?mid=${userId}` const { data, data: { code } } = await axios.get(url, { withCredentials: true }) if (code === 0) return data else if (code === -400) utils.logger.info("获取用户基本信息丨请求错误") else if (code === -403) utils.logger.info("获取用户基本信息丨权限不足") else if (code === -404) utils.logger.info("获取用户基本信息丨用户不存在") else if (code === 'ERR_BAD_REQUEST') utils.logger.info("获取用户基本信息丨请求失败") else utils.logger.warn("获取用户基本信息丨请求失败") }, // #endregion 获取用户基本信息 /** * 判断用户是否是大会员 * - #region 判断用户是否是大会员 * - 签到后执行,若已签到则不执行,避免触发多次请求 */ async isVip() { const userId = utils.getCookieByName('DedeUserID') const { data: { card: { vip: { status } } } } = await biliApis.getUserInformation(userId) if (status) utils.setValue('is_vip', true) else utils.setValue('is_vip', false) }, // #endregion 判断用户是否是大会员 /** * 自动签到 * - #region 自动签到 */ async autoSignIn() { const now = new Date() const signInDate = `${now.getFullYear()}-${(now.getMonth() + 1)}-${now.getDate()}` if (!vals.signIn_date() || vals.signIn_date() !== signInDate) { const url = `https://api.live.bilibili.com/sign/doSign` const { data: { code } } = await axios.get(url, { withCredentials: true }) if (code === 0) { utils.logger.info("自动签到丨签到成功") utils.setValue('signIn_date', signInDate) await biliApis.isVip() } else if (code === 1011040) { utils.logger.info("自动签到丨今日已签") utils.setValue('signIn_date', signInDate) } else { utils.logger.warn("自动签到丨签到异常") utils.setValue('signIn_date', '') } } else { utils.logger.info("自动签到丨今日已签") } }, // #endregion 自动签到 /** * 获取用户投稿视频列表 * - #region 获取用户投稿视频列表 */ async getUserVideoList(userId) { const wib = await biliApis.getQueryWithWbi({ mid: userId }) const url = `https://api.bilibili.com/x/space/wbi/arc/search?${wib}` const { data, data: { code } } = await axios.get(url, { withCredentials: true }) if (code === 0) return data else if (code === -400) { utils.logger.info("获取用户投稿视频列表丨权限不足") } else if (code === -412) { utils.logger.info("获取用户投稿视频列表丨请求被拦截") } else { utils.logger.warn("获取用户投稿视频列表丨请求失败") } }, // #endregion 获取用户投稿视频列表 } const modules = { //** ----------------------- 通用功能 ----------------------- **// // #region 通用功能 /** * 获取视频类型(video/bangumi) * - #region 获取视频类型 * 如果都没匹配上则弹窗报错 * @returns 当前视频类型 */ async getCurrentPlayerType(url = window.location.href) { let playerType const setCurrentPlayerType = () => { playerType = (url.startsWith('https://www.bilibili.com/video') || url.startsWith('https://www.bilibili.com/list/')) ? 'video' : url.startsWith('https://www.bilibili.com/bangumi') ? 'bangumi' : false if (!playerType) { utils.logger.debug('视频类型丨未匹配') alert('未匹配到当前视频类型,请反馈当前地址栏链接。') } utils.setValue('player_type', playerType) } window.addEventListener('focus', () => { setCurrentPlayerType() }) // utils.logger.debug(`${playerType} ${vals.player_type()}`) setCurrentPlayerType() if (vals.player_type() === playerType) return { message: `视频类型丨${playerType}` } else modules.getCurrentPlayerType() }, // #endregion 获取视频类型 /** * 判断用户是否登录 * - #region 判断用户是否登录 */ isLogin() { return Boolean(document.cookie.replace(/(?:(?:^|.*;\s*)bili_jct\s*=\s*([^;]*).*$)|^.*$/, '$1') || window.UserStatus.userInfo.isLogin || null) }, // #endregion 判断用户是否登录 /** * 获取视频ID/video BVID/bangumi EPID * - #region 获取视频ID */ getCurrentVideoID(url = window.location.href) { return url.startsWith('https://www.bilibili.com/video') ? url.split('/')[4] : url.startsWith('https://www.bilibili.com/bangumi') ? url.split('/')[5].split('?')[0] : 'error' }, // #endregion 获取视频ID /** * 获取Vue版本号 * - #region 获取Vue版本号 */ async getVueScopeId(selector) { const element = await utils.getElementAndCheckExistence(selector) // utils.logger.debug(element) return new Promise((resolve, reject) => { let attrsArray = Array.from(element.attributes) let vueScopeAttrs = attrsArray.filter(attr => attr.name.startsWith('data-v-')) vueScopeAttrs.filter(attr => { // 使用字符串分割来提取 'data-v-' 后面的部分 const vueScopeId = attr.name.split('data-v-')[1]; resolve(vueScopeId) // utils.logger.debug(vueScopeId) }) }) }, // #endregion 获取Vue版本号 // #endregion 通用功能 //** ----------------------- 视频播放页相关功能 ----------------------- **// // #region 视频播放页相关功能 /** * 检查视频元素是否存在 * - #region 检查视频元素是否存在 * - 若存在返回成功消息 * - 若不存在则抛出异常 */ async checkVideoExistence() { const [$videoWrap, $video] = await utils.getElementAndCheckExistence([selectors.videoWrap, selectors.video]) if ($video) return { message: '播放器|已找到', callback: [modules.setVideoCover.bind(null, $videoWrap, $video)] } else throw new Error('播放器|未找到') }, // #endregion 检查视频元素是否存在 /** * 检查视频是否可以播放 * - #region 检查视频是否可以播放 */ async checkVideoCanPlayThrough() { return new Promise((resolve, reject) => { let attempts = 100 let message, result const timer = setInterval(() => { const $video = document.querySelector(selectors.video) const videoReadyState = $video.readyState if (videoReadyState === 4) { message = '视频资源|可以播放' result = true resolve({ message, result }) clearInterval(timer) } else if (attempts <= 0) { message = '视频资源|加载失败' result = false utils.reloadCurrentTab(true) reject({ message, result }) clearInterval(timer) } attempts-- }, 100) arrays.intervalIds.push(timer) }) }, // #endregion 检查视频是否可以播放 /** * 监听屏幕模式变化(normal/wide/web/full) * - #region 监听屏幕模式变化 */ async observerPlayerDataScreenChanges() { const $playerContainer = await utils.getElementAndCheckExistence(selectors.playerContainer) const observer = new MutationObserver(() => { const playerDataScreen = $playerContainer.getAttribute('data-screen') utils.setValue('current_screen_mode', playerDataScreen) }) observer.observe($playerContainer, { attributes: true, attributeFilter: ['data-screen'], }) }, // #endregion 监听屏幕模式变化 /** * 获取当前屏幕模式 * - #region 获取当前屏幕模式 * @param {Number} 延时 * @returns */ async getCurrentScreenMode() { return new Promise((resolve) => { let attempts = 100 const timer = setInterval(() => { const $playerContainer = document.querySelector(selectors.playerContainer) const playerDataScreen = $playerContainer?.getAttribute('data-screen') if (playerDataScreen) { resolve(playerDataScreen) clearInterval(timer) } else if (attempts <= 0) { clearInterval(timer) throw new Error(`获取当前屏幕模式|失败:已达到最大重试次数`) } attempts-- }, 100) arrays.intervalIds.push(timer) }) }, // #endregion 获取当前屏幕模式 /** * 视频未开始播放时显示视频封面 * - #region 视频未开始播放时显示视频封面 * - 应用于舞蹈类视频 * - 视频播放时移除封面 */ async setVideoCover($videoWrap, $video) { if (vals.player_type() === 'bangumi') return const targetTids = Array.from(new Set().add([...objects.videoCategories.dance.tids, ...objects.videoCategories.fashion.tids])).flat() const { data: { pic, tid } } = await biliApis.getVideoInformation(modules.getCurrentVideoID(window.location.href)) if (targetTids.includes(tid) && pic) { $videoWrap.style.setProperty('--video-cover', `url(${pic.replace(/^http:/i, 'https:')})`) // 设置视频封面的CSS变量 $video.addEventListener('play', () => { $videoWrap.style.setProperty('--video-cover', '') }) // $video.addEventListener('pause', function () { // $videoWrap.style.setProperty('--video-cover', `url(${pic})`) // }) } }, // #endregion 视频未开始播放时显示视频封面 /** * 判断当前视频是否未充电 * - #region 判断当前视频是否未充电 */ checkVideoNoCharge() { return document.querySelector(selectors.notChargeHighLevelCover) }, // #endregion 判断当前视频是否未充电 // #region 自动选择播放器默认模式 /** * 执行自动切换屏幕模式 * - #region 执行自动切换屏幕模式 * - 功能未开启,不执行切换函数,直接返回成功 * - 功能开启,但当前屏幕已为宽屏或网页全屏,则直接返回成功 * - 功能开启,执行切换函数 */ async autoSelectScreenMode() { if (modules.checkVideoNoCharge()) return if (++vars.autoSelectScreenModeRunningCount !== 1) return if (vals.selected_screen_mode() === 'close') return { message: '屏幕模式|功能已关闭' } const currentScreenMode = await modules.getCurrentScreenMode() if (arrays.screenModes.includes(currentScreenMode)) return { message: `屏幕模式|当前已是 ${currentScreenMode.toUpperCase()} 模式` } if (arrays.screenModes.includes(vals.selected_screen_mode())) { const result = await modules.checkScreenModeSwitchSuccess(vals.selected_screen_mode()) if (result) return { message: `屏幕模式|${vals.selected_screen_mode().toUpperCase()}|切换成功` } else throw new Error(`屏幕模式|${vals.selected_screen_mode().toUpperCase()}|切换失败:已达到最大重试次数`) } }, // #endregion 执行自动切换屏幕模式 /** * 检查屏幕模式是否切换成功 * - #region 检查屏幕模式是否切换成功 * @param {*} expectScreenMode 期望的屏幕模式 * - 未成功自动重试 * - 定时器方式超过 10 次失败,1s 执行一次 * - 递归方式超过 10 次返回失败 */ async checkScreenModeSwitchSuccess(expectScreenMode) { const enterBtnMap = { wide: async () => await utils.getElementAndCheckExistence(selectors.screenModeWideEnterButton), web: async () => await utils.getElementAndCheckExistence(selectors.screenModeWebEnterButton), } // 定时器方式检查 if (vals.dev_checkScreenModeSwitchSuccess_method() === 'interval') { if (enterBtnMap[expectScreenMode]) { return new Promise((resolve) => { let attempts = 10 const timer = setInterval(async () => { const enterBtn = await enterBtnMap[expectScreenMode]() enterBtn.click() const currentScreenMode = await modules.getCurrentScreenMode() const equal = expectScreenMode === currentScreenMode const success = vals.player_type() === 'video' ? expectScreenMode === 'wide' ? equal && +getComputedStyle(document.querySelector(selectors.danmukuBox))['margin-top'].slice(0, -2) > 0 : equal : equal if (success) { clearInterval(timer) resolve(success) } else if (attempts <= 0) { clearInterval(timer) resolve(false) utils.logger.warn(`屏幕模式切换失败,继续尝试丨当前:${currentScreenMode},期望:${expectScreenMode}`) } attempts-- }, 1000) arrays.intervalIds.push(timer) }) } } if (vals.dev_checkScreenModeSwitchSuccess_method() === 'recursive') { // 递归方式检查 if (enterBtnMap[expectScreenMode]) { const enterBtn = await enterBtnMap[expectScreenMode]() enterBtn.click() const currentScreenMode = await modules.getCurrentScreenMode() const equal = expectScreenMode === currentScreenMode const success = vals.player_type() === 'video' ? expectScreenMode === 'wide' ? equal && +getComputedStyle(document.querySelector(selectors.danmukuBox))['margin-top'].slice(0, -2) > 0 : equal : equal // utils.logger.debug(`${vals.player_type()} ${expectScreenMode} ${currentScreenMode} ${equal} ${success}`) if (success) return success else { if (++vars.checkScreenModeSwitchSuccessDepths === 10) return false // utils.logger.warn(`屏幕模式切换失败,继续尝试丨当前:${currentScreenMode},期望:${expectScreenMode}`) await utils.sleep(300) return modules.checkScreenModeSwitchSuccess(expectScreenMode) } } } }, // #endregion 检查屏幕模式是否切换成功 // #endregion 自动选择播放器默认模式 // #region 自动定位至播放器 /** * 设置位置数据并滚动至播放器 * - #region 设置位置数据并滚动至播放器 * @returns */ async setLocationDataAndScrollToPlayer() { const getOffsetMethod = vals.get_offset_method() let playerOffsetTop if (getOffsetMethod === 'elements') { const $header = await utils.getElementAndCheckExistence(selectors.header) const $placeholderElement = await utils.getElementAndCheckExistence(selectors.videoTitleArea) || await utils.getElementAndCheckExistence(selectors.bangumiMainContainer) const headerHeight = $header.getBoundingClientRect().height const placeholderElementHeight = $placeholderElement.getBoundingClientRect().height playerOffsetTop = vals.player_type() === 'video' ? headerHeight + placeholderElementHeight : headerHeight + +getComputedStyle($placeholderElement)['margin-top'].slice(0, -2) } if (getOffsetMethod === 'function') { const $player = await utils.getElementAndCheckExistence(selectors.player) playerOffsetTop = utils.getElementOffsetToDocument($player).top } // utils.logger.debug(playerOffsetTop) vals.player_type() === 'video' ? utils.setValue('video_player_offset_top', playerOffsetTop) : utils.setValue('bangumi_player_offset_top', playerOffsetTop) await modules.getCurrentScreenMode() === 'wide' ? utils.documentScrollTo(playerOffsetTop - vals.offset_top()) : utils.documentScrollTo(0) return // utils.logger.debug('定位至播放器!') }, // #endregion 设置位置数据并滚动至播放器 /** * 自动定位至播放器并检查是否成功 * - #region 自动定位至播放器并检查是否成功 */ async autoLocationToPlayer() { const unlockbody = () => { document.getElementById('BodyHiddenStyle')?.remove() } const onAutoLocate = vals.auto_locate() && ((!vals.auto_locate_video() && !vals.auto_locate_bangumi()) || (vals.auto_locate_video() && vals.player_type() === 'video') || (vals.auto_locate_bangumi() && vals.player_type() === 'bangumi')) if (!onAutoLocate || vals.selected_screen_mode() === 'web') return { callback: [unlockbody] } await modules.setLocationDataAndScrollToPlayer() const playerOffsetTop = vals.player_type() === 'video' ? vals.video_player_offset_top() : vals.bangumi_player_offset_top() const result = await modules.checkAutoLocationSuccess(playerOffsetTop - vals.offset_top()) if (result) return { message: '自动定位|成功', callback: [unlockbody] } else { unlockbody() throw new Error(`自动定位|失败:已达到最大重试次数`) } }, // #endregion 自动定位至播放器并检查是否成功 /** * 递归检查屏自动定位是否成功 * - #region 递归检查屏自动定位是否成功 * @param {*} expectOffset 期望文档滚动偏移量 * - 未定位成功自动重试,递归超过 10 次则返回失败 * - 基础数据: * - videoOffsetTop:播放器相对文档顶部距离,大小不随页面滚动变化 * - videoClientTop:播放器相对浏览器视口顶部距离,大小随页面滚动变化 * - targetOffset:用户期望的播放器相对浏览器视口顶部距离,由用户自定义 * - 文档滚动距离:videoOffsetTop - targetOffset */ async checkAutoLocationSuccess(expectOffset) { const $video = await utils.getElementAndCheckExistence(selectors.video) utils.documentScrollTo(expectOffset) await utils.sleep(300) const videoClientTop = Math.trunc($video.getBoundingClientRect().top) const playerOffsetTop = vals.player_type() === 'video' ? vals.video_player_offset_top() : vals.bangumi_player_offset_top() // 成功条件:实际偏移量与用户设置偏移量相等/期望文档滚动偏移量与当前文档滚动偏移量相等/实际偏移量与用户设置偏移量误差小于5 const success = (videoClientTop === vals.offset_top()) || ((playerOffsetTop - vals.offset_top()) - Math.trunc(window.scrollY) === 0) || (Math.abs(videoClientTop - vals.offset_top()) < 5) if (success) return success else { if (++vars.autoLocationToPlayerRetryDepths === 10) return false // utils.logger.debug(`${videoOffsetTop} ${videoClientTop} ${vals.offset_top()} ${Math.abs((videoOffsetTop - vals.offset_top()) - Math.trunc(window.scrollY))}`) utils.logger.warn(` 自动定位失败,继续尝试 ----------------- 期望文档滚动偏移量:${playerOffsetTop - vals.offset_top()} 当前文档滚动偏移量:${Math.trunc(window.scrollY)} 文档滚动偏移量误差:${(playerOffsetTop - vals.offset_top()) - Math.trunc(window.scrollY)} 播放器顶部偏移量:${videoClientTop} 设置偏移量:${vals.offset_top()}`) utils.documentScrollTo(0) await utils.sleep(300) return modules.checkAutoLocationSuccess(expectOffset) } }, // #endregion 递归检查屏自动定位是否成功 /** * 文档滚动至播放器(使用已有数据) * - #region 文档滚动至播放器(使用已有数据) */ async locationToPlayer() { const videoCanPlay = await modules.checkVideoCanPlayThrough() if (videoCanPlay.result) { const playerOffsetTop = vals.player_type() === 'video' ? vals.video_player_offset_top() : vals.bangumi_player_offset_top() const scrollOffset = await modules.getCurrentScreenMode() !== 'web' ? playerOffsetTop - vals.offset_top() : 0 utils.documentScrollTo(scrollOffset) } }, // #endregion 文档滚动至播放器(使用已有数据) /** * 点击播放器自动定位 * - #region 点击播放器自动定位 */ async clickPlayerAutoLocation() { if (vals.click_player_auto_locate()) { const $video = await utils.getElementAndCheckExistence(selectors.video) $video.addEventListener('click', async () => { const currentScreenMode = await modules.getCurrentScreenMode() if (['full', 'mini'].includes(currentScreenMode)) return await modules.locationToPlayer() }) } }, // #endregion 点击播放器自动定位 /** * 点击时间锚点自动返回播放器 * - #region 点击时间锚点自动返回播放器 */ async clickVideoTimeAutoLocation() { const $video = await utils.getElementAndCheckExistence('video') const host = document.querySelector("#commentapp > bili-comments") const $descriptionClickTarget = 'video' ? await ShadowDOMHelper.queryUntil(host, "#feed > #bili-adjustment-contents") : '' if ($descriptionClickTarget) { await elmGetter.each(selectors.videoTime, $descriptionClickTarget, async (target) => { target.addEventListener('click', async (event) => { event.stopPropagation() await modules.locationToPlayer() // const targetTime = vals.player_type() === 'video' ? target.dataset.videoTime : target.dataset.time const targetTime = target.dataset.videoTime if (targetTime > $video.duration) alert('当前时间点大于视频总时长,将跳到视频结尾!') $video.currentTime = targetTime $video.play() }) }) } const stop = ShadowDOMHelper.watchQuery( host, '#feed', async item => { const links = ShadowDOMHelper.batchQuery(item, { seekLinks: '#comment >> bili-rich-text >> #contents > a[data-type="seek"]', replySeekLinks: 'bili-comment-replies-renderer >> bili-comment-reply-renderer >> bili-rich-text >> #contents > a[data-type="seek"]', }) links.forEach(link => { link.addEventListener('click', async (event) => { event.stopPropagation(); await modules.locationToPlayer() const targetTime = link.dataset.videoTime if (targetTime > $video.duration) alert('当前时间点大于视频总时长,将跳到视频结尾!') $video.currentTime = targetTime $video.play(); }) }) }, { nodeNameFilter: 'bili-comment-thread-renderer', debounce: 100, maxRetries: 5, observeExisting: true, scanInterval: 1000 } ) }, // #endregion 点击时间锚点自动返回播放器 // #endregion 自动定位至播放器 /** * 自动关闭静音 * - #region 自动关闭静音 */ async autoCancelMute() { if (++vars.autoCancelMuteRunningCount !== 1) return const [$mutedButton, $volumeButton] = await utils.getElementAndCheckExistence([selectors.mutedButton, selectors.volumeButton]) // const mutedButtonDisplay = getComputedStyle(mutedButton)['display'] // const volumeButtonDisplay = getComputedStyle(volumeButton)['display'] const mutedButtonDisplay = $mutedButton.style.display const volumeButtonDisplay = $volumeButton.style.display if (mutedButtonDisplay === 'block' || volumeButtonDisplay === 'none') { $mutedButton.click() // utils.logger.info('静音丨已关闭') return { message: '静音丨已关闭' } } }, // #endregion 自动关闭静音 /** * 自动开启字幕 * - #region 自动开启字幕 */ async autoEnableSubtitle() { if (!vals.auto_subtitle()) return const $switchSubtitleButton = await utils.getElementAndCheckExistence(selectors.switchSubtitleButton) const openStatus = $switchSubtitleButton.children[0].children[0].children[0].children[1].childElementCount === 1 if (!openStatus) { $switchSubtitleButton.children[0].children[0].click() return { message: '视频字幕丨已开启' } } }, // #endregion 自动开启字幕 /** * 插入自动开启字幕功能开关 * - #region 插入自动开启字幕功能开关 */ async insertAutoEnableSubtitleSwitchButton() { if (++vars.insertAutoEnableSubtitleSwitchButtonCount !== 1) return const autoEnableSubtitleSwitchButtonHtml = `