// ==UserScript== // @name bilibili plus // @version 0.4.3 // @description 提供B站截图、获取封面、逐帧等功能,以及默认宽屏模式 // @namespace https://greasyfork.org/zh-CN/users/215623-christian-chen // @author 化猫之宿 // @encoding utf-8 // // @match *://www.bilibili.com/video/* // @match *://www.bilibili.com/bangumi/play/* // @match *://www.bilibili.com/blackboard/ // // @compatible chrome 54+ // @compatible firefox 49+ // // @grant GM_getValue // @grant GM_setValue // @downloadURL https://update.greasyfork.icu/scripts/373172/bilibili%20plus.user.js // @updateURL https://update.greasyfork.icu/scripts/373172/bilibili%20plus.meta.js // ==/UserScript== !function(modules) { var installedModules = {}; function __webpack_require__(moduleId) { if (installedModules[moduleId]) return installedModules[moduleId].exports; var module = installedModules[moduleId] = { i: moduleId, l: !1, exports: {} }; return modules[moduleId].call(module.exports, module, module.exports, __webpack_require__), module.l = !0, module.exports; } __webpack_require__.m = modules, __webpack_require__.c = installedModules, __webpack_require__.d = function(exports, name, getter) { __webpack_require__.o(exports, name) || Object.defineProperty(exports, name, { enumerable: !0, get: getter }); }, __webpack_require__.r = function(exports) { "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(exports, "__esModule", { value: !0 }); }, __webpack_require__.t = function(value, mode) { if (1 & mode && (value = __webpack_require__(value)), 8 & mode) return value; if (4 & mode && "object" == typeof value && value && value.__esModule) return value; var ns = Object.create(null); if (__webpack_require__.r(ns), Object.defineProperty(ns, "default", { enumerable: !0, value: value }), 2 & mode && "string" != typeof value) for (var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); return ns; }, __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function() { return module.default; } : function() { return module; }; return __webpack_require__.d(getter, "a", getter), getter; }, __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }, __webpack_require__.p = "", __webpack_require__(__webpack_require__.s = 0); }([ function(module, exports) { ({ inject: function(is_userscript) { const BILINAMES = { menuBar: "bpx-player-dm-root", bilibiliPlayer: "bilibili-player", playerArea: "bpx-player-container", playPause: [ "bpx-player-ctrl-play", "squirtle-play-wrap" ], wideBtn: [ "bpx-player-ctrl-wide", "squirtle-video-widescreen" ], wideExtend: [ "bpx-state-entered", "active" ], sideBar: "bui-collapse-wrap", sideBarClose: "bui-collapse-wrap-folded", sideBarArrow: "bui-collapse-arrow", videoInfo: "bpx-player-video-info", btnClass: [ "button-mode", "button-pic", "button-screen", "button-pre", "button-next" ] }; let user_settings, fps, side_bar, root, player_area, counter; fps = 29.97, counter = 0, user_settings = JSON.parse(document.documentElement.dataset.localsettings); const set = (setting, new_value) => { "user_settings" !== setting ? user_settings[setting] = new_value : user_settings = setting, document.documentElement.dataset.localsettings = JSON.stringify(user_settings); }, key = { fullscreenEnabled: 0, fullscreenchange: 1 }, webkit = [ "webkitFullscreenEnabled", "webkitfullscreenchange" ], eventKeys = ("fullscreenEnabled" in document && Object.keys(key) || webkit[0] in document && webkit || document, e => { switch (e.code.toLowerCase()) { case "period": root.querySelector(".button-next").click(); break; case "comma": root.querySelector(".button-pre").click(); } }); function modeWide(e, flag = !0) { let player, playerArea, btn_wide, wide_flag; if (player = document.querySelector("#" + BILINAMES.bilibiliPlayer), playerArea = document.querySelector("." + BILINAMES.playerArea), player && "wide" != playerArea.dataset.screen) { for (let i = 0; i < BILINAMES.wideBtn.length && (wide_flag = i, btn_wide = player.querySelector("." + BILINAMES.wideBtn[i]), !btn_wide); i++) ; btn_wide && !btn_wide.classList.contains("." + BILINAMES.wideExtend[wide_flag]) && btn_wide.click(); } e.querySelectorAll(".button-mode-svg").forEach(element => { element.classList.toggle("svg-display"); }), flag && (user_settings.MODE_WIDE ? user_settings.MODE_WIDE = !1 : user_settings.MODE_WIDE = !0, user_settings.MODE_WIDE && user_settings.MODE_DOWN && modeDown(e.parentNode.querySelector(".button-mode-dropdown")), set("MODE_WIDE", user_settings.MODE_WIDE)); } function modeDown(e, flag = !0) { let side_column, expand; side_column = document.querySelector("." + BILINAMES.sideBar), side_column && side_column.classList.contains(BILINAMES.sideBarClose) && (expand = side_column.querySelector("." + BILINAMES.sideBarArrow), expand.click()), e.querySelectorAll(".button-mode-svg").forEach(element => { element.classList.toggle("svg-display"); }), flag && (user_settings.MODE_DOWN ? user_settings.MODE_DOWN = !1 : user_settings.MODE_DOWN = !0, user_settings.MODE_DOWN && user_settings.MODE_WIDE && modeWide(e.parentNode.querySelector(".button-mode-wide")), set("MODE_DOWN", user_settings.MODE_DOWN)); } const pauseVideo = video => { let btnStart; for (let i = 0; i < BILINAMES.playPause.length && (btnStart = document.querySelector("." + BILINAMES.playPause), !btnStart); i++) ; btnStart && player_area && !player_area.classList.contains("bpx-state-paused") ? btnStart.click() : video.pause(); }, popPage = (obj, x, y) => { let popOut, width, height, pop_url; return width = x, height = y, "string" == typeof obj ? (pop_url = obj, popOut = window.open(pop_url, "popOut", "width=" + width + ",height=" + height)) : "object" == typeof obj && (popOut = window.open("", "_blank", "width=" + width + ",height=" + height), popOut.document.body.appendChild(obj)), popOut.focus(), popOut; }, getScreenshot = () => { let video, canvas, context, width, height, pop, style; video = document.querySelector("video"), canvas = document.createElement("canvas"), context = canvas.getContext("2d"), width = video.videoWidth, height = video.videoHeight, canvas.width = width, canvas.height = height, context.drawImage(video, 0, 0, width, height), pauseVideo(video), pop = popPage(canvas, width, height), style = document.createElement("style"), style.type = "text/css", style.innerHTML = "body {\n margin: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: #ccc;\n }\n canvas {\n max-width: 100%;\n max-height: 100%;\n }", pop.document.head.appendChild(style); }, getThumb = () => { const queryList = [ '[itemprop="image"]', '[itemprop="thumbnailUrl"]', '[property="og:image"]' ]; let thumbnail_url; for (let i = 0; i < queryList.length && (thumbnail_url = document.querySelector(queryList[i]), !thumbnail_url); i++) ; thumbnail_url = thumbnail_url && thumbnail_url.getAttribute("content"), thumbnail_url ? popPage(thumbnail_url, 881, 551) : alert("未找到缩略图!"); }, seekFrame = (direction, fps) => { let video; video = document.querySelector("video"), pauseVideo(video), "forward" === direction ? video.currentTime = video.currentTime + 1 / fps : "backward" === direction && (video.currentTime = video.currentTime - 1 / fps); }, main = () => { if (root = document.querySelector("." + BILINAMES.menuBar), player_area = document.querySelector("." + BILINAMES.playerArea), side_bar = document.querySelector("." + BILINAMES.sideBar), root && side_bar && player_area) (() => { const infoArea = document.querySelector("." + BILINAMES.videoInfo), divide = infoArea.querySelector(".bpx-player-video-info-divide"); infoArea.removeChild(divide), infoArea.style.flexDirection = "column", infoArea.style.height = "100%", infoArea.style.marginRight = "10px", infoArea.style.fontSize = "10px", infoArea.style.justifyContent = "center"; })(), (() => { let button_next, button_pre, button_pic, button_screen, button_mode; document.querySelector(".button-next") || (button_next = document.createElement("div"), button_next.className = "bpx-player-dm-switch bui-danmaku-switch button-pre", button_next.innerHTML = '\n \n \n 逐帧后退 <', button_pre = document.createElement("div"), button_pre.className = "bpx-player-dm-switch bui-danmaku-switch button-next", button_pre.innerHTML = '\n \n \n 逐帧前进 >', button_pic = document.createElement("div"), button_pic.className = "bpx-player-dm-switch bui-danmaku-switch button-pic", button_pic.innerHTML = '\n \n \n 缩略图', button_screen = document.createElement("div"), button_screen.className = "bpx-player-dm-switch bui-danmaku-switch button-screen", button_screen.innerHTML = '\n \n \n 截图', button_mode = document.createElement("div"), button_mode.className = "bpx-player-dm-switch bui-danmaku-switch button-mode", button_mode.innerHTML = '\n \n \n
', root.prepend(button_mode, button_pic, button_screen, button_next, button_pre)); })(), (root_el => { root_el.addEventListener("click", ("fullscreenEnabled" in document && Object.keys(key) || webkit[0] in document && webkit || document, e => { for (let el = e.target; el !== e.currentTarget; el = el.parentElement) el.classList.contains("button-pic") && getThumb(), el.classList.contains("button-screen") && getScreenshot(), el.classList.contains("button-next") && seekFrame("forward", 29.97), el.classList.contains("button-pre") && seekFrame("backward", 29.97), el.classList.contains("button-mode-wide") && modeWide(el), el.classList.contains("button-mode-dropdown") && modeDown(el); })), root_el.addEventListener("mouseover", (function(e) { for (let el = e.target; el !== e.currentTarget; el = el.parentElement) el.classList.contains(BILINAMES.btnClass[0]) && (el.querySelector(".show-mode").style.display = "block"), BILINAMES.btnClass.forEach((btn, ind) => { if (0 != ind && el.classList.contains(btn)) { const item = el.querySelector(".bpx-player-tooltip-item"); item.style.position = "absolute", item.style.width = "max-content", item.style.visibility = "visible", item.style.opacity = 1, item.style.top = "-35px", item.style.left = "-20px"; } }); })), root_el.addEventListener("mouseout", (function(e) { for (let el = e.target; el !== e.currentTarget; el = el.parentElement) el.classList.contains(BILINAMES.btnClass[0]) && (el.querySelector(".show-mode").style.display = "none"), BILINAMES.btnClass.forEach((btn, ind) => { 0 != ind && el.classList.contains(btn) && (el.querySelector(".bpx-player-tooltip-item").style = ""); }); })); const eventClick = () => { window.removeEventListener("keydown", eventKeys); }; player_area.addEventListener("click", e => { window.addEventListener("keydown", eventKeys), document.addEventListener("click", eventClick, { once: !0 }), e.stopPropagation(); }); })(root), (settings => { let button_mode; button_mode = root.querySelector(".button-mode"), setTimeout(() => { settings.MODE_WIDE && modeWide(button_mode.querySelector(".button-mode-wide"), !1), settings.MODE_DOWN && modeDown(button_mode.querySelector(".button-mode-dropdown"), !1); }, 500); })(user_settings), ((playerArea, setting) => { let currentVideo = null; const player = document.querySelector("#" + BILINAMES.bilibiliPlayer), video = playerArea.querySelector("video"); video && video.addEventListener("loadedmetadata", e => { let btn_wide, wide_flag; for (let i = 0; i < BILINAMES.wideBtn.length && (wide_flag = i, btn_wide = player.querySelector("." + BILINAMES.wideBtn[i]), !btn_wide); i++) ; currentVideo != e.target.src && (currentVideo = e.target.src, "wide" != playerArea.dataset.screen && setting.MODE_WIDE && btn_wide && !btn_wide.classList.contains("." + BILINAMES.wideExtend[wide_flag]) && btn_wide.click()); }); })(player_area, user_settings), new MutationObserver(() => { counter = 0, setTimeout(main, 100); }).observe(document.querySelector(".bpx-player-video-wrap"), { childList: !0 }); else { if (counter++, counter > 30) return; setTimeout(main, 300); } }; main(); }, setup: function() { let css_holder, holder; css_holder = document.createElement("style"), css_holder.type = "text/css", css_holder.innerHTML = ".show-mode {\n display: flex;\n flex-direction: column;\n align-items: center;\n position: absolute;\n z-index: 71;\n bottom: 46px;\n left: -40px;\n background-color: rgba(0, 0, 0, 0.8);\n font-size: 12px;\n padding: 5px 15px;\n color: hsla(0,0%,100%,.8);\n fill: hsla(0,0%,100%,.8);\n cursor: auto;\n }\n \n .show-mode .show-mode-bridge {\n width: 100%;\n height: 23px;\n position: absolute;\n top: 132px;\n }\n \n .button-mode-selects {\n width: max-content;\n height: 46px;\n cursor: pointer;\n text-align: left;\n }\n \n .button-mode-selects.opened {\n fill:#00a1d6; \n }\n \n .button-mode-selects:hover {\n color: hsla(0,0%,100%,1);\n fill: hsla(0,0%,100%,1);\n }\n .button-mode-selects.opened:hover {\n color: hsla(0,0%,100%,1);\n fill:#00a1d6; \n }\n \n .button-mode-svg {\n display: inline-block;\n font-size: 0;\n vertical-align: middle;\n height: 32px;\n width: 32px;\n }\n \n .bp-svgicon svg {\n width: 100%;\n height: 100%;\n }\n \n .button-mode-text {\n display: inline-block;\n height: 16px;\n line-height: 16px;\n padding-left: 10px;\n }\n \n .svg-display {\n display: none;\n }\n \n @media screen and (min-width: 1681px)\n {\n .bpx-player-dm-switch {\n margin-right: 12px;\n }\n }\n\n @media screen and (max-width: 860px) {\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-left {\n min-width: 108px;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-left .bpx-player-ctrl-time {\n position: absolute;\n top: 40px;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-center {\n min-width: 150px;\n padding: 0 2px;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-center .bpx-player-dm-root {\n width: 100%;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-center .bpx-player-dm-root div {\n margin-right: 2px;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-right {\n min-width: 150px;\n }\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-right .bpx-player-ctrl-eplist {\n width: max-content;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-right .bpx-player-ctrl-eplist .bpx-player-ctrl-eplist-result {\n width: max-content;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-right .bpx-player-ctrl-playbackrate {\n width: max-content;\n }\n .bpx-player-container[data-screen=full] .bpx-player-control-bottom .bpx-player-control-bottom-right .bpx-player-ctrl-playbackrate .bpx-player-ctrl-playbackrate-result {\n width: max-content;\n }\n ", holder = document.createElement("script"), holder.textContent = `(${this.inject}(${this.is_userscript}))`, document.head.appendChild(css_holder), document.documentElement.appendChild(holder); }, conveyMeassge: function() { let data_key, gate, sets, observe, default_settings; data_key = "localsettings", gate = document.documentElement, sets = JSON.parse(gate.dataset.localsettings || null), default_settings = { MODE_WIDE: !1, MODE_DANMU_CLOSE: !1, MODE_DOWN: !1 }, sets || (gate.dataset.localsettings = JSON.stringify(this.GM_getValue(this.data, default_settings))), observe = new MutationObserver(() => { this.GM_setValue(this.data, JSON.parse(gate.dataset.localsettings || null)); }), observe.observe(gate, { attributes: !0, attributeFilter: [ "data-localsettings" ] }); }, ini: function() { this.is_userscript = "object" == typeof GM_info, this.data = "userSettings", this.GM_setValue = GM_setValue, this.GM_getValue = GM_getValue, this.conveyMeassge(), this.setup(); } }).ini(); } ]);