// ==UserScript== // @name Bilibili Evolved // @version 1.9.18 // @description 强大的哔哩哔哩增强脚本: 下载视频, 音乐, 封面, 弹幕 / 简化直播间, 评论区, 首页 / 自定义顶栏, 删除广告, 夜间模式 / 触屏设备支持 // @author Grant Howard, Coulomb-G // @copyright 2019, Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G) // @license MIT // @match *://*.bilibili.com/* // @run-at document-start // @updateURL https://github.com/the1812/Bilibili-Evolved/raw/master/bilibili-evolved.user.js // @downloadURL https://github.com/the1812/Bilibili-Evolved/raw/master/bilibili-evolved.user.js // @supportURL https://github.com/the1812/Bilibili-Evolved/issues // @homepage https://github.com/the1812/Bilibili-Evolved // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_setClipboard // @grant GM_info // @grant GM_xmlhttpRequest // @grant GM.getValue // @grant GM.setValue // @grant GM.setClipboard // @grant GM.info // @grant GM.xmlHttpRequest // @connect raw.githubusercontent.com // @connect * // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js // @require https://code.jquery.com/jquery-3.4.0.min.js // @require https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js // @require https://cdn.bootcss.com/jszip/3.1.5/jszip.min.js // @require https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js // @icon https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/images/logo-small.png // @icon64 https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/images/logo.png // ==/UserScript== Vue.config.productionTip = false Vue.config.devtools = false function logError (error) { let finalMessage = error if (typeof error === 'object' && 'stack' in error) { if (settings.toastInternalError) { finalMessage = `${error.message}\n${error.stack}` } else { finalMessage = error.message } } Toast.error(finalMessage, '错误') console.error(error) } function raiseEvent (element, eventName) { const event = document.createEvent('HTMLEvents') event.initEvent(eventName, true, true) element.dispatchEvent(event) } async function loadLazyPanel (selector) { await SpinQuery.unsafeJquery() const panel = await SpinQuery.any(() => unsafeWindow.$(selector)) if (!panel) { throw new Error(`Panel not found: ${selector}`) } panel.mouseover().mouseout() } async function loadDanmakuSettingsPanel () { const style = document.createElement('style') style.innerText = `.bilibili-player-video-danmaku-setting-wrap { display: none !important; }` document.body.insertAdjacentElement('beforeend', style) await loadLazyPanel('.bilibili-player-video-danmaku-setting') setTimeout(() => style.remove(), 300) } function contentLoaded (callback) { if (/complete|interactive|loaded/.test(document.readyState)) { callback() } else { document.addEventListener('DOMContentLoaded', () => callback()) } } function fullyLoaded (callback) { if (document.readyState === 'complete') { callback() } else { unsafeWindow.addEventListener('load', () => callback()) } } function fixed (number, precision = 1) { const str = number.toString() const index = str.indexOf('.') if (index !== -1) { if (str.length - index > precision + 1) { return str.substring(0, index + precision + 1) } else { return str } } else { return str + '.0' } } function isEmbeddedPlayer () { return location.host === 'player.bilibili.com' || document.URL.startsWith('https://www.bilibili.com/html/player.html') } function isIframe () { return document.body && unsafeWindow.parent.window !== unsafeWindow } const languageNameToCode = { '日本語': 'ja-JP', 'English': 'en-US', 'Deutsch': 'de-DE' } const languageCodeToName = { 'ja-JP': '日本語', 'en-US': 'English', 'de-DE': 'Deutsch' } function getI18nKey () { return settings.i18n ? languageNameToCode[settings.i18nLanguage] : 'zh-CN' } const dq = (selector, scopedSelector) => { if (!scopedSelector) { return document.querySelector(selector) } return selector.querySelector(scopedSelector) } const dqa = (selector, scopedSelector) => { if (!scopedSelector) { return [...document.querySelectorAll(selector)] } return [...selector.querySelectorAll(scopedSelector)] } const UserAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0` const EmptyImageUrl = 'data:image/svg+xml;utf-8,' const ascendingSort = (itemProp) => { return (a, b) => itemProp(a) - itemProp(b) } const descendingSort = (itemProp) => { return (a, b) => itemProp(b) - itemProp(a) } const formatFileSize = (bytes, fixed = 1) => { const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] let number = bytes let unitIndex = 0 while (number >= 1024) { number /= 1024 unitIndex++ } return `${Math.round(number * (10 ** fixed)) / (10 ** fixed)}${units[unitIndex]}` } const formatDuration = (time, fixed = 0) => { const second = (time % 60).toFixed(fixed) const minute = (Math.trunc(time / 60) % 60).toString() const hour = Math.trunc(time / 3600).toString() if (hour === '0') { return `${minute.padStart(2, '0')}:${second.padStart(2, '0')}` } return `${hour}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}` } const getDpiSourceSet = (src, baseSize, extension = 'jpg') => { const dpis = [1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4] if (extension.startsWith('.')) { extension = extension.substring(1) } return dpis.map(dpi => { if (typeof baseSize === 'object') { if ('width' in baseSize && 'height' in baseSize) { return `${src}@${Math.trunc(baseSize.width * dpi)}w_${Math.trunc(baseSize.height * dpi)}h.${extension} ${dpi}x` } else if ('width' in baseSize) { return `${src}@${Math.trunc(baseSize.width * dpi)}w.${extension} ${dpi}x` } else if ('height' in baseSize) { return `${src}@${Math.trunc(baseSize.height * dpi)}h.${extension} ${dpi}x` } } else { return `${src}@${Math.trunc(baseSize * dpi)}w_${Math.trunc(baseSize * dpi)}h.${extension} ${dpi}x` } }).join(",") } const isOffline = () => typeof offlineData !== 'undefined' const getUID = () => document.cookie.replace(/(?:(?:^|.*;\s*)DedeUserID\s*\=\s*([^;]*).*$)|^.*$/, '$1') const scriptVersion = (() => { const match = GM.info.script.name.match(/Bilibili Evolved \((.*)\)/) return match ? match[1] : 'Stable' })() const getCsrf = () => document.cookie.replace(/(?:(?:^|.*;\s*)bili_jct\s*\=\s*([^;]*).*$)|^.*$/, '$1') const formatCount = (count) => { if (typeof count === 'string') { count = parseInt(count) } if (count > 100000000) { return Math.round(count / 10000000) / 10 + '亿' } if (count > 10000) { return Math.round(count / 1000) / 10 + '万' } return count + '' } const escapeFilename = (filename, replacement = '') => { return filename.replace(/[\/\\:\*\?"<>\|]/g, replacement) } function html(strings, ...values) { return [...strings].reduce((previous, current, index) => { const value = values[index] return previous + current + (value === undefined ? '' : value) }, '') } const dashExtensions = ['.mp4', '.m4a'] const dashFragmentExtension = 'm4s' const customNavbarDefaultOrders = { blank1: 0, logo: 1, category: 2, rankingLink: 3, drawingLink: 4, musicLink: 5, gamesIframe: 6, livesIframe: 7, shopLink: 8, mangaLink: 9, blank2: 10, search: 11, userInfo: 12, messages: 13, activities: 14, bangumi: 15, watchlaterList: 16, favoritesList: 17, historyList: 18, upload: 19, blank3: 20, } const aria2RpcDefaultOption = { secretKey: '', dir: '', host: '127.0.0.1', port: '6800', method: 'get', skipByDefault: false, maxDownloadLimit: '', baseDir: '', } const settings = { useDarkStyle: false, compactLayout: false, // showBanner: true, hideBanner: false, expandDanmakuList: true, expandDescription: true, watchLaterRedirect: true, touchNavBar: false, touchVideoPlayer: false, customControlBackgroundOpacity: 0.64, customControlBackground: false, darkScheduleStart: '18:00', darkScheduleEnd: '6:00', darkSchedule: false, toast: true, fullTweetsTitle: true, fullPageTitle: false, removeVideoTopMask: false, removeLiveWatermark: true, harunaScale: true, removeAds: true, showBlockedAdsTip: false, hideTopSearch: false, touchVideoPlayerDoubleTapControl: false, customStyleColor: '#00A0D8', preserveRank: true, blurBackgroundOpacity: 0.382, useDefaultPlayerMode: false, applyPlayerModeOnPlay: true, defaultPlayerMode: '常规', useDefaultVideoQuality: false, defaultVideoQuality: '自动', useDefaultDanmakuSettings: false, enableDanmaku: true, rememberDanmakuSettings: false, danmakuSettings: { subtitlesPreserve: false, smartMask: false, }, defaultPlayerLayout: '新版', defaultBangumiLayout: '新版', skipChargeList: false, comboLike: false, autoLightOff: false, useCache: true, autoContinue: false, allowJumpContinue: false, autoPlay: false, deadVideoTitleProvider: '稍后再看', useBiliplusRedirect: false, biliplusRedirect: false, framePlayback: true, useCommentStyle: true, imageResolution: false, imageResolutionScale: 'auto', toastInternalError: false, i18n: false, i18nLanguage: '日本語', playerFocus: false, playerFocusOffset: -10, oldTweets: false, simplifyLiveroom: false, simplifyLiveroomSettings: { vip: true, fansMedal: true, title: true, userLevel: true, guard: true, systemMessage: true, welcomeMessage: true, giftMessage: true, guardPurchase: true, giftPanel: true, kanban: true, eventsBanner: false, popup: false, skin: false, }, customNavbar: true, customNavbarFill: false, customNavbarShadow: true, customNavbarCompact: false, customNavbarBlur: true, customNavbarBlurOpacity: 0.7, customNavbarOrder: { ...customNavbarDefaultOrders }, customNavbarHidden: [], customNavbarBoundsPadding: 5, playerShadow: false, narrowDanmaku: true, favoritesRedirect: true, outerWatchlater: true, hideOldEntry: true, videoScreenshot: false, hideBangumiReviews: false, filenameFormat: '[title][ - ep]', sideBarOffset: 0, noLiveAutoplay: false, hideHomeLive: false, noMiniVideoAutoplay: false, useDefaultVideoSpeed: false, defaultVideoSpeed: '1.0', hideCategory: false, foldComment: true, downloadVideoDefaultDanmaku: '无', aria2RpcOption: {...aria2RpcDefaultOption}, aria2RpcOptionSelectedProfile: '', aria2RpcOptionProfiles: [], searchHistory: [], seedsToCoins: true, autoSeedsToCoins: true, lastSeedsToCoinsDate: 0, autoDraw: false, keymap: false, doubleClickFullscreen: false, doubleClickFullscreenPreventSingleClick: false, simplifyHome: false, simplifyHomeStyle: '清爽', ajaxHook: false, scriptLoadingMode: '延后(自动)', scriptDownloadMode: 'bundle', guiSettingsDockSide: '左侧', fullActivityContent: true, feedsFilter: false, feedsFilterPatterns: [], feedsFilterTypes: [], feedsFilterSideCards: [], activityImageSaver: false, scriptBlockPatterns: [], customNavbarSeasonLogo: false, selectableColumnText: true, downloadVideoFormat: 'flv', downloadVideoDashCodec: 'AVC/H.264', watchlaterExpireWarnings: true, watchlaterExpireWarningDays: 14, superchatTranslate: false, miniPlayerTouchMove: false, hideBangumiSponsors: false, hideRecommendLive: false, hideRelatedVideos: false, defaultMedalID: 0, autoMatchMedal: false, cache: {}, } const fixedSettings = { guiSettings: true, viewCover: true, notifyNewVersion: true, clearCache: true, downloadVideo: true, enableDashDownload: true, downloadDanmaku: true, downloadAudio: true, medalHelper: true, about: true, playerLayout: false, forceWide: false, useNewStyle: false, overrideNavBar: false, touchVideoPlayerAnimation: false, allNavbarFill: false, showDeadVideoTitle: false, blurVideoControl: false, latestVersionLink: 'https://github.com/the1812/Bilibili-Evolved/raw/master/bilibili-evolved.user.js', currentVersion: GM.info.script.version, } const settingsChangeHandlers = {} function addSettingsListener (key, handler, initCall) { if (!settingsChangeHandlers[key]) { settingsChangeHandlers[key] = [handler] } else { settingsChangeHandlers[key].push(handler) } if (initCall) { const value = settings[key] handler(value, value) } } function removeSettingsListener (key, handler) { const handlers = settingsChangeHandlers[key] if (!handlers) { return } handlers.splice(handlers.indexOf(handler), 1) } async function loadSettings () { for (const key in fixedSettings) { settings[key] = fixedSettings[key] await GM.setValue(key, fixedSettings[key]) } if (Object.keys(languageCodeToName).includes(navigator.language)) { settings.i18n = true settings.i18nLanguage = languageCodeToName[navigator.language] } for (const key in settings) { let value = await GM.getValue(key) if (value === undefined) { value = settings[key] GM.setValue(key, settings[key]) } else if (settings[key] !== undefined && value.constructor === Object) { value = Object.assign(settings[key], value) } Object.defineProperty(settings, key, { get () { return value }, set (newValue) { value = newValue GM.setValue(key, newValue) const handlers = settingsChangeHandlers[key] if (handlers) { if (key === 'useDarkStyle') { setTimeout(() => handlers.forEach(h => h(newValue, value)), 200) } else { handlers.forEach(h => h(newValue, value)) } } const input = document.querySelector(`input[key=${key}]`) if (input !== null) { if (input.type === 'checkbox') { input.checked = newValue } else if (input.type === 'text' && !input.parentElement.classList.contains('gui-settings-dropdown')) { input.value = newValue } } } }) } } class Ajax { static send (xhr, body, text = true) { return new Promise((resolve, reject) => { xhr.addEventListener('load', () => { // if (xhr.status.toString().match(/^[45]/)) { // reject(xhr.status) // } else { resolve(text ? xhr.responseText : xhr.response) // } }) xhr.addEventListener('error', () => reject(xhr.status)) xhr.send(body) }) } static getBlob (url) { const xhr = new XMLHttpRequest() xhr.responseType = 'blob' xhr.open('GET', url) return this.send(xhr, undefined, false) } static getBlobWithCredentials (url) { const xhr = new XMLHttpRequest() xhr.responseType = 'blob' xhr.open('GET', url) xhr.withCredentials = true return this.send(xhr, undefined, false) } static async getJson (url) { return JSON.parse(await this.getText(url)) } static async getJsonWithCredentials (url) { return JSON.parse(await this.getTextWithCredentials(url)) } static getText (url) { const xhr = new XMLHttpRequest() xhr.open('GET', url) return this.send(xhr) } static getTextWithCredentials (url) { const xhr = new XMLHttpRequest() xhr.open('GET', url) xhr.withCredentials = true return this.send(xhr) } static postText (url, body) { const xhr = new XMLHttpRequest() xhr.open('POST', url) xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') return this.send(xhr, body) } static postTextWithCredentials (url, body) { const xhr = new XMLHttpRequest() xhr.open('POST', url) xhr.withCredentials = true xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') return this.send(xhr, body) } static postJson (url, json) { const xhr = new XMLHttpRequest() xhr.open('POST', url) xhr.setRequestHeader('Content-Type', 'application/json') return this.send(xhr, JSON.stringify(json), false) } static postJsonWithCredentials (url, json) { const xhr = new XMLHttpRequest() xhr.open('POST', url) xhr.withCredentials = true xhr.setRequestHeader('Content-Type', 'application/json') return this.send(xhr, JSON.stringify(json), false) } static getHandlers (name) { name = name.toLowerCase() let handlers = Ajax[name] if (handlers === undefined) { handlers = Ajax[name] = [] } return handlers } static addEventListener (type, handler) { const handlers = Ajax.getHandlers(type) handlers.push(handler) } static removeEventListener (type, handler) { const handlers = Ajax.getHandlers(type) handlers.splice(handlers.indexOf(handler), 1) } static monkey (details) { return new Promise((resolve, reject) => { const fullDetails = { ...details, onload: r => resolve(r.response), onerror: r => reject(r), } if (!('method' in fullDetails)) { fullDetails.method = 'GET' } GM.xmlHttpRequest(fullDetails) }) } } // https://github.com/the1812/Bilibili-Evolved/issues/84 let ajaxHooked = false function setupAjaxHook () { if (ajaxHooked) { return } ajaxHooked = true const original = { open: XMLHttpRequest.prototype.open, send: XMLHttpRequest.prototype.send } const fireHandlers = (name, thisArg, ...args) => Ajax.getHandlers(name).forEach(it => it.call(thisArg, ...args)) const hook = (name, thisArgs, ...args) => { fireHandlers('before' + name, thisArgs, ...args) const returnValue = original[name].call(thisArgs, ...args) fireHandlers('after' + name, thisArgs, ...args) return returnValue } const hookOnEvent = (name, thisArg) => { if (thisArg[name]) { const originalHandler = thisArg[name] thisArg[name] = (...args) => { fireHandlers('before' + name, thisArg, ...args) originalHandler.apply(thisArg, args) fireHandlers('after' + name, thisArg, ...args) } } else { thisArg[name] = (...args) => { fireHandlers('before' + name, thisArg, ...args) fireHandlers('after' + name, thisArg, ...args) } } } XMLHttpRequest.prototype.open = function (...args) { return hook('open', this, ...args) } XMLHttpRequest.prototype.send = function (...args) { hookOnEvent('onreadystatechange', this) hookOnEvent('onload', this) return hook('send', this, ...args) } } function loadResources () { Resource.root = 'https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/' Resource.all = {} Resource.displayNames = {} // Resource.reloadables = [ // 'useDarkStyle', // 'hideBanner', // 'customNavbar', // 'playerShadow', // 'narrowDanmaku', // 'compactLayout', // 'useCommentStyle', // 'removeVideoTopMask', // 'hideOldEntry', // 'hideBangumiReviews', // 'videoScreenshot', // 'blurVideoControl', // 'customControlBackground', // 'harunaScale', // 'removeLiveWatermark', // 'framePlayback', // 'hideCategory', // 'fullTweetsTitle', // 'fullActivityContent', // ] Resource.reloadables = [] for (const [key, data] of Object.entries(Resource.manifest)) { const resource = new Resource(data.path, { styles: data.styles, alwaysPreview: data.alwaysPreview }) resource.key = key resource.dropdown = data.dropdown if (data.reloadable) { Resource.reloadables.push(key) } if (data.displayNames) { resource.displayName = data.displayNames[key] Object.assign(Resource.displayNames, data.displayNames) } if (data.style) { const styleKey = key + 'Style' const style = Resource.all[styleKey] = new Resource(data.path.replace('.js', '.css'), { alwaysPreview: data.alwaysPreview }) style.key = styleKey switch (data.style) { case 'instant': { resource.styles.push(styleKey) break } case true: { resource.dependencies.push(style) break } case 'important': { resource.styles.push({ key: styleKey, important: true }) break } default: { if (typeof data.style === 'object') { resource.styles.push(Object.assign({ key: styleKey }, data.style)) } break } } } if (data.html === true) { const htmlKey = key + 'Html' const html = Resource.all[htmlKey] = new Resource(data.path.replace('.js', '.html'), { alwaysPreview: data.alwaysPreview }) html.key = htmlKey resource.dependencies.push(html) } Resource.all[key] = resource } for (const [key, data] of Object.entries(Resource.manifest)) { if (data.dependencies) { Resource.all[key].dependencies.push(...data.dependencies.map(name => Resource.all[name])) } } } // Placeholder class for Toast class Toast { show () { } dismiss () { } static show () { } static info () { } static success () { } static error () { } } class DoubleClickEvent { constructor (handler, singleClickHandler = null) { this.handler = handler this.singleClickHandler = singleClickHandler this.elements = [] this.clickedOnce = false this.doubleClickHandler = e => { if (!this.clickedOnce) { this.clickedOnce = true setTimeout(() => { if (this.clickedOnce) { this.clickedOnce = false this.singleClickHandler && this.singleClickHandler(e) } }, 200) } else { this.clickedOnce = false this.handler && this.handler(e) } } } bind (element) { if (this.elements.indexOf(element) === -1) { this.elements.push(element) element.addEventListener('click', this.doubleClickHandler) } } unbind (element) { const index = this.elements.indexOf(element) if (index === -1) { return } this.elements.splice(index, 1) element.removeEventListener('click', this.doubleClickHandler) } } let cidHooked = false const videoChangeCallbacks = [] class Observer { constructor (elements, callback) { this.elements = elements || [] this.callback = callback this.observer = null this.options = undefined } start () { this.elements.forEach(element => { this.observer = new MutationObserver(this.callback) this.observer.observe(element, this.options) }) return this } add (element) { this.elements.push(element) this.observer.observe(element, this.options) return this } stop () { this.observer && this.observer.disconnect() return this } // 向后兼容的接口, 实际并没有什么遍历 forEach (callback) { callback(this) } static observe (selector, callback, options) { callback([]) let elements = selector if (typeof selector === 'string') { elements = [...document.querySelectorAll(selector)] } else if (!Array.isArray(selector)) { elements = [selector] } const observer = new Observer(elements, callback) observer.options = options return observer.start() } static childList (selector, callback) { return Observer.observe(selector, callback, { childList: true, subtree: false, attributes: false }) } static childListSubtree (selector, callback) { return Observer.observe(selector, callback, { childList: true, subtree: true, attributes: false }) } static attributes (selector, callback) { return Observer.observe(selector, callback, { childList: false, subtree: false, attributes: true }) } static attributesSubtree (selector, callback) { return Observer.observe(selector, callback, { childList: false, subtree: true, attributes: true }) } static all (selector, callback) { return Observer.observe(selector, callback, { childList: true, subtree: true, attributes: true }) } static async videoChange (callback) { const cid = await SpinQuery.select(() => unsafeWindow.cid) if (cid === null) { return } if (!cidHooked) { let hookedCid = cid Object.defineProperty(unsafeWindow, 'cid', { get () { return hookedCid }, set (newId) { hookedCid = newId if (!Array.isArray(newId)) { videoChangeCallbacks.forEach(it => it()) } } }) cidHooked = true } // callback(); const videoContainer = await SpinQuery.select('#bofqi video') if (videoContainer) { Observer.childList(videoContainer, callback) } else { callback() } videoChangeCallbacks.push(callback) } } class SpinQuery { constructor (query, condition, action, failed) { this.maxRetry = 15 this.retry = 0 this.queryInterval = 1000 this.query = query this.condition = condition this.action = action this.failed = failed } start () { this.tryQuery(this.query, this.condition, this.action, this.failed) } tryQuery (query, condition, action, failed) { if (this.retry < this.maxRetry) { const result = query() if (condition(result)) { action(result) } else { if (document.hasFocus()) { this.retry++ } setTimeout(() => this.tryQuery(query, condition, action, failed), this.queryInterval) } } else { typeof failed === 'function' && failed() } } static condition (query, condition, action, failed) { if (action !== undefined) { new SpinQuery(query, condition, action, failed).start() } else { return new Promise((resolve) => { new SpinQuery(query, condition, it => resolve(it), () => resolve(null)).start() }) } } static select (query, action, failed) { if (typeof query === 'string') { const selector = query query = () => document.querySelector(selector) } return SpinQuery.condition(query, it => it !== null && it !== undefined, action, failed) } static any (query, action, failed) { if (typeof query === 'string') { const selector = query query = () => $(selector) } return SpinQuery.condition(query, it => it.length > 0, action, failed) } static count (query, count, action, failed) { if (typeof query === 'string') { const selector = query query = () => document.querySelectorAll(selector) } return SpinQuery.condition(query, it => it.length === count, action, failed) } static unsafeJquery (action, failed) { return SpinQuery.condition(() => unsafeWindow.$, jquery => jquery !== undefined, action, failed) } } class ColorProcessor { constructor (hex) { this.hex = hex } get rgb () { return this.hexToRgb(this.hex) } get rgba () { return this.hexToRgba(this.hex) } getHexRegex (alpha, shorthand) { const repeat = shorthand ? '' : '{2}' const part = `([a-f\\d]${repeat})` const count = alpha ? 4 : 3 const pattern = `#?${part.repeat(count)}` return new RegExp(pattern, 'ig') } hexToRgbOrRgba (hex, alpha) { const isShortHand = hex.length < 6 if (isShortHand) { const shorthandRegex = this.getHexRegex(alpha, true) hex = hex.replace(shorthandRegex, function (...args) { let result = '' let i = 1 while (args[i]) { result += args[i].repeat(2) i++ } return result }) } const regex = this.getHexRegex(alpha, false) const regexResult = regex.exec(hex) if (regexResult) { const color = { r: parseInt(regexResult[1], 16), g: parseInt(regexResult[2], 16), b: parseInt(regexResult[3], 16) } if (regexResult[4]) { color.a = parseInt(regexResult[4], 16) / 255 } return color } else if (alpha) { const rgb = this.hexToRgbOrRgba(hex, false) if (rgb) { rgb.a = 1 return rgb } } return null } hexToRgb (hex) { return this.hexToRgbOrRgba(hex, false) } hexToRgba (hex) { return this.hexToRgbOrRgba(hex, true) } rgbToString (color) { if (color.a) { return `rgba(${color.r},${color.g},${color.b},${color.a})` } return `rgb(${color.r},${color.g},${color.b})` } rgbToHsb (rgb) { const { r, g, b } = rgb const max = Math.max(r, g, b) const min = Math.min(r, g, b) const delta = max - min const s = Math.round((max === 0 ? 0 : delta / max) * 100) const v = Math.round(max / 255 * 100) let h if (delta === 0) { h = 0 } else if (r === max) { h = (g - b) / delta % 6 } else if (g === max) { h = (b - r) / delta + 2 } else if (b === max) { h = (r - g) / delta + 4 } h = Math.round(h * 60) if (h < 0) { h += 360 } return { h: h, s: s, b: v } } get hsb () { return this.rgbToHsb(this.rgb) } get grey () { const color = this.rgb return 1 - (0.299 * color.r + 0.587 * color.g + 0.114 * color.b) / 255 } get foreground () { const color = this.rgb if (color && this.grey < 0.35) { return '#000' } return '#fff' } makeImageFilter (originalRgb) { const { h, s } = this.rgbToHsb(originalRgb) const targetColor = this.hsb const hue = targetColor.h - h const saturate = ((targetColor.s - s) / 100 + 1) * 100 // const brightness = ((targetColor.b - b) / 100 + 1) * 100; const filter = `hue-rotate(${hue}deg) saturate(${saturate}%)` return filter } get blueImageFilter () { const blueColor = { r: 0, g: 160, b: 213 } return this.makeImageFilter(blueColor) } get pinkImageFilter () { const pinkColor = { r: 251, g: 113, b: 152 } return this.makeImageFilter(pinkColor) } get brightness () { return `${this.foreground === '#000' ? '100' : '0'}%` } get filterInvert () { return this.foreground === '#000' ? 'invert(0)' : 'invert(1)' } } const onlineData = {}; onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/v-dropdown.vue.min.js"] = (()=>{return(o,e)=>{const n=`
{{ value }}
`;e.applyStyleFromText(`.v-dropdown{--background-color:#eee;position:relative;background-color:var(--background-color);cursor:pointer;display:flex;align-items:center;justify-content:space-between;min-width:100px}body.dark .v-dropdown{--background-color:#333}.v-dropdown .dropdown-menu{transform-origin:top;transform:translateY(-4px) translateX(-50%);pointer-events:none;opacity:0;position:absolute;top:calc(100% + 2px);left:50%;background-color:var(--background-color);z-index:1;transition:.2s ease-out;box-shadow:rgba(0,0,0,.2) 0 4px 8px 0}.v-dropdown .dropdown-menu.opened{transform:translateY(0) translateX(-50%);pointer-events:initial;opacity:1}.v-dropdown .dropdown-menu li{padding:4px 16px;white-space:nowrap;min-width:64px;text-align:center;cursor:pointer;color:inherit;background-color:transparent}.v-dropdown .dropdown-menu li:hover{background-color:rgba(0,0,0,.16)}.v-dropdown .mdi-chevron-down{font-size:16pt;line-height:1;transform:rotate(0)}.v-dropdown .dropdown-menu.opened~.mdi-chevron-down{transform:rotate(180deg)}.v-dropdown .selected{user-select:none;padding:4px 8px}.round-corner .v-dropdown,.round-corner .v-dropdown .dropdown-menu,.round-corner .v-dropdown .dropdown-menu li{border-radius:var(--corner-radius)}`,"v-dropdown-style");return{export:Object.assign({template:n},{props:["items","value"],data(){return{dropdownOpen:false}},methods:{toggleDropdown(){this.dropdownOpen=!this.dropdownOpen;if(this.dropdownOpen){document.addEventListener("click",o=>{const e=o.target;if(e===this.$el||this.$el.contains(e)){return}this.dropdownOpen=false},{once:true,capture:true})}},select(o){this.$emit("update:value",o);this.$emit("change",o)}}})}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/default-video-speed.min.js"] = (()=>{return(e,a)=>{const i=a=>{const i=parseFloat(e.defaultVideoSpeed);a.playbackRate=i;SpinQuery.condition(()=>a,()=>a.playbackRate!==i,()=>a.playbackRate=i)};Observer.videoChange(()=>{const e=dq(".bilibili-player-video video");if(!e){return}i(e)})}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/v-checkbox.vue.min.js"] = (()=>{return(e,c)=>{const i=`
{{title}}
`;c.applyStyleFromText(`.v-checkbox{font-size:10pt;cursor:pointer;display:flex;align-items:center}.v-checkbox .mdi{font-size:15pt;line-height:1}.v-checkbox .content{flex-grow:1;text-align:left;padding:0 8px}.v-checkbox .mdi-checkbox-marked-circle{color:var(--theme-color);position:absolute;top:0;left:0;transform:scale(0);transition-timing-function:cubic-bezier(.6,-.28,.74,.05)}.v-checkbox .mdi-checkbox-blank-circle-outline{color:#8884;position:relative}.v-checkbox.checked .mdi-checkbox-blank-circle-outline{color:var(--theme-color)}.v-checkbox.checked .mdi-checkbox-marked-circle{transform:scale(1);transition-timing-function:cubic-bezier(.18,.89,.32,1.28)}`,"v-checkbox-style");return{export:Object.assign({template:i},{props:["checked","title"],methods:{toggleCheck(){this.$emit("update:checked",!this.checked)}}})}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/remove-promotions.min.css"] = `#home_popularize .adpos,#home_popularize .l-con,#reportFirst2 .extension,#slide_ad,.activity-m,.bili-header-m .nav-menu .nav-con .nav-item .text-red,.bilibili-player-promote-wrap,.gg-floor-module,.gg-window .operate-card,.home-app-download,.international-home .banner-card,.mascot,.mobile-link-l,.video-page-game-card,.video-page-special-card{display:none!important}#home_popularize{position:relative!important}.gg-window .online,.popularize-module .online{position:absolute!important;top:50%!important;right:.5%!important;transform:translateY(-100%)!important}.gg-window .online{right:0!important;padding:0 16px!important}#reportFirst2{position:relative;margin-bottom:4px}.blocked-ads{width:440px;height:220px;display:flex;color:#888;background-color:#8882;font-size:24pt;font-weight:700;align-items:center;justify-content:space-evenly}body.compact .blocked-ads{width:480px;height:240px}`; onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/watchlater.min.js"] = (()=>{return(e,t)=>{const i=e=>{const t=e.match(/(av[\d]+)\/p([\d]+)/);if(t){return`https://www.bilibili.com/video/${t[1]}/?p=${t[2]}`}else{return"javascript:;"}};const r=e=>{const t=e.map(e=>{const t=e.getAttribute("href");if(!t){return"javascript:;"}if(t.match(/.*watchlater.*|javascript:;/g)){return i(t)}if(t.indexOf("video/av")!==-1){return t}});e.forEach((e,i)=>{e.setAttribute("href",t[i]);e.setAttribute("target","_blank")})};const a=(...e)=>{for(const t of e){SpinQuery.select(()=>document.querySelectorAll(t),e=>r([...e]))}};SpinQuery.select(".watch-later-list").then(()=>{Observer.childListSubtree("#viewlater-app",()=>{SpinQuery.condition(()=>document.URL.match(/(av[\d]+)\/p([\d]+)/),e=>e&&document.URL.indexOf("watchlater")!==-1,()=>{const e=i(document.URL);if(e!==null){window.location.replace(e)}});SpinQuery.select("#viewlater-app .s-btn[href='#/']",e=>e.remove());a(".av-pic",".av-about>a")})});SpinQuery.select("li.nav-item[report-id*=watchlater]").then(()=>{Observer.childListSubtree("li.nav-item[report-id*=watchlater]",()=>{a(".av-item>a",".av-about>a","div.watch-later-m>ul>div>li>a");SpinQuery.select(".read-more.mr",e=>e.remove());SpinQuery.select(".read-more-grp>.read-more",e=>{e.style.width="auto";e.style.float="none"})})});SpinQuery.select(".van-popper-favorite").then(async e=>{if(!e){return}const t=Observer.childListSubtree(e,()=>{const i=e.querySelector(".play-all");if(i){const e="//www.bilibili.com/watchlater/#/list";Observer.attributes(i,()=>{if(i.getAttribute("href")==="//www.bilibili.com/watchlater/"){i.setAttribute("href",e);i.firstChild.classList.remove("bili-icon_dingdao_bofang");i.firstChild.classList.add("bili-icon_xinxi_yuedushu");i.lastChild.nodeValue="查看全部"}else if(i.getAttribute("href")!==e){i.firstChild.classList.add("bili-icon_dingdao_bofang");i.firstChild.classList.remove("bili-icon_xinxi_yuedushu");i.lastChild.nodeValue="播放全部"}});t.forEach(e=>e.stop())}})})}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/expand-description.min.js"] = (()=>{return(e,p)=>{p.applyStyle("expandDescriptionStyle")}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/video-card-info.min.js"] = (()=>{return(r,e)=>{}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/download-video.min.js"] = (()=>{return(t,e)=>{const{getFriendlyTitle:s}=e.import("title");const{VideoInfo:a,DanmakuInfo:i}=e.import("video-info");const{DownloadVideoPackage:n}=e.import("download-video-package");class o{async getApiGenerator(t=false){function e(e,s,a){if(t){if(a){return`https://api.bilibili.com/x/player/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json&fnver=0&fnval=16`}else{return`https://api.bilibili.com/x/player/playurl?avid=${e}&cid=${s}&otype=json&fnver=0&fnval=16`}}else{if(a){return`https://api.bilibili.com/x/player/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json`}else{return`https://api.bilibili.com/x/player/playurl?avid=${e}&cid=${s}&otype=json`}}}return e.bind(this)}async getDashUrl(t){return(await this.getApiGenerator(true))(c.aid,c.cid,t)}async getUrl(t){return(await this.getApiGenerator())(c.aid,c.cid,t)}}class r extends o{async getApiGenerator(t=false){function e(e,s,a){if(t){if(a){return`https://api.bilibili.com/pgc/player/web/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json&fourk=1&fnval=16`}else{return`https://api.bilibili.com/pgc/player/web/playurl?avid=${e}&cid=${s}&otype=json&fourk=1&fnval=16`}}else{if(a){return`https://api.bilibili.com/pgc/player/web/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json`}else{return`https://api.bilibili.com/pgc/player/web/playurl?avid=${e}&cid=${s}&qn=&otype=json`}}}return e.bind(this)}}class l extends o{constructor(t){super();this.ep=t}async getApiGenerator(t=false){function e(e,s,a){if(t){if(a){return`https://api.bilibili.com/pugv/player/web/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`}else{return`https://api.bilibili.com/pugv/player/web/playurl?avid=${e}&cid=${s}&otype=json&ep_id=${this.ep}&fnver=0&fnval=16`}}else{if(a){return`https://api.bilibili.com/pugv/player/web/playurl?avid=${e}&cid=${s}&qn=${a}&otype=json&ep_id=${this.ep}`}else{return`https://api.bilibili.com/pugv/player/web/playurl?avid=${e}&cid=${s}&otype=json&ep_id=${this.ep}`}}}return e.bind(this)}}const c={entity:new o,aid:"",cid:""};let d=[];let p=null;class h{constructor(t,e,s){this.quality=t;this.internalName=e;this.displayName=s}async downloadInfo(t=false){const e=new u(this);await e.fetchVideoInfo(t);return e}static parseFormats(t){const e=t.accept_quality;const s=t.accept_format.split(",");const a=t.accept_description;const i=e.map((t,e)=>{return new h(t,s[e],a[e])});return i}static async getAvailableDashFormats(){const t=await c.entity.getDashUrl();const e=await Ajax.getJsonWithCredentials(t);if(e.code!==0){throw new Error("获取清晰度信息失败.")}return h.parseFormats(e.data||e.result||e)}static async getAvailableFormats(){const t=await c.entity.getUrl();const e=await Ajax.getJsonWithCredentials(t);if(e.code!==0){throw new Error("获取清晰度信息失败.")}const s=e.data||e.result||e;return h.parseFormats(s)}}class u{constructor(t,e){this.fragmentSplitFactor=6*2;this.workingXhr=null;this.progressMap=new Map;this.format=t;this.fragments=e||[];this.videoSpeed=new f(this)}get danmakuOption(){return t.downloadVideoDefaultDanmaku}get isDash(){return this.fragments.some(t=>t.url.includes(".m4s"))}get totalSize(){return this.fragments.map(t=>t.size).reduce((t,e)=>t+e)}async fetchVideoInfo(t=false){if(!t){const t=await c.entity.getUrl(this.format.quality);const e=await Ajax.getTextWithCredentials(t);const s=JSON.parse(e.replace(/http:/g,"https:"));const a=s.data||s.result||s;if(a.quality!==this.format.quality){throw new Error("获取下载链接失败, 请确认当前账号有下载权限后重试.")}const i=a.durl;this.fragments=i.map(t=>{return{length:t.length,size:t.size,url:t.url,backupUrls:t.backup_url}})}else{const{dashToFragments:t,getDashInfo:s}=await e.importAsync("video-dash");const a=await s(await c.entity.getDashUrl(this.format.quality),this.format.quality);this.fragments=t(a)}return this.fragments}updateProgress(){const t=this.progressMap?[...this.progressMap.values()].reduce((t,e)=>t+e,0)/this.totalSize:0;if(t>1||t<0){console.error(`[下载视频] 进度异常: ${t}`,this.progressMap.values())}this.progress&&this.progress(t)}cancelDownload(){this.videoSpeed.stopMeasure();if(this.workingXhr!==null){this.workingXhr.forEach(t=>t.abort())}else{logError("Cancel Download Failed: forEach in this.workingXhr not found.")}}downloadFragment(t){const e=[];const s=this.isDash?4*1024*1024:16*1024*1024;let a;if(t.size<=s*6){a=t.size/this.fragmentSplitFactor}else{a=s}let i=0;const n=t=>[...this.progressMap.keys()].indexOf(t)+1;while(i{const a=new XMLHttpRequest;a.open("GET",t.url);a.responseType="arraybuffer";a.withCredentials=false;a.addEventListener("progress",t=>{console.log(`[下载视频] 视频片段${n(a)}下载进度: ${t.loaded}/${r} bytes loaded, ${o}`);this.progressMap.set(a,t.loaded);this.updateProgress()});a.addEventListener("load",()=>{if((""+a.status)[0]==="2"){console.log(`[下载视频] 视频片段${n(a)}下载完成`);e(a.response)}else{s(`视频片段${n(a)}请求失败, response = ${a.status}`)}});a.addEventListener("abort",()=>s("canceled"));a.addEventListener("error",()=>{console.error(`[下载视频] 视频片段${n(a)}下载失败: ${o}`);this.progressMap.set(a,0);this.updateProgress();a.open("GET",t.url);a.setRequestHeader("Range",o);a.send()});a.setRequestHeader("Range",o);this.progressMap.set(a,0);a.send();this.workingXhr.push(a)}));i=Math.round(i+a)+1}return Promise.all(e)}async copyUrl(){const t=this.fragments.map(t=>t.url).reduce((t,e)=>t+"\r\n"+e);GM.setClipboard(t,"text")}async showUrl(){const t=this.fragments.map(t=>`\n${t.url}\n`).reduce((t,e)=>t+"\r\n"+e);Toast.success(t+`复制全部`,"显示链接");const e=await SpinQuery.select("#copy-link");e.addEventListener("click",async()=>{await this.copyUrl()})}async exportData(t=false){const e=JSON.stringify([{fragments:this.fragments,title:s(),totalSize:this.fragments.map(t=>t.size).reduce((t,e)=>t+e),referer:document.URL.replace(window.location.search,"")}]);if(t){GM.setClipboard(e,"text")}else{const t=new Blob([e],{type:"text/json"});const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.json`,t);i.add(s()+"."+this.danmakuOption.toLowerCase(),a);await i.emit(`${s()}.zip`)}}async exportAria2(a=false){if(a){const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.${this.danmakuOption==="ASS"?"ass":"xml"}`,a);await i.emit();const o=t.aria2RpcOption;const r=this.fragments.map((t,e)=>{let a="";if(this.fragments.length>1&&!this.isDash){a=" - "+(e+1)}const i=[];if(o.secretKey!==""){i.push(`token:${o.secretKey}`)}i.push([t.url]);i.push({referer:document.URL.replace(window.location.search,""),"user-agent":UserAgent,out:`${s()}${a}${this.extension(t)}`,split:this.fragmentSplitFactor,dir:o.baseDir+o.dir||undefined,"max-download-limit":o.maxDownloadLimit||undefined});const n=encodeURIComponent(`${s()}${a}`);return{params:i,id:n}});const{sendRpc:l}=await e.importAsync("aria2-rpc");await l(r)}else{const t=`\n# Generated by Bilibili Evolved Video Export\n# https://github.com/the1812/Bilibili-Evolved/\n${this.fragments.map((t,e)=>{let a="";if(this.fragments.length>1&&!this.isDash){a=" - "+(e+1)}return`\n${t.url}\n referer=${document.URL.replace(window.location.search,"")}\n user-agent=${UserAgent}\n out=${s()}${a}${this.extension(t)}\n split=${this.fragmentSplitFactor}\n`.trim()}).join("\n")}\n`.trim();const e=new Blob([t],{type:"text/plain"});const a=await this.downloadDanmaku();const i=new n;i.add(`${s()}.txt`,e);i.add(s()+"."+this.danmakuOption.toLowerCase(),a);await i.emit(`${s()}.zip`)}}extension(t){const e=t||this.fragments[0];const s=[".flv",".mp4"].find(t=>e.url.includes(t));if(s){return s}else if(e.url.includes(".m4s")){return this.fragments.indexOf(e)===0?".mp4":".m4a"}else{console.warn("No extension detected.");return".flv"}}async downloadDanmaku(){if(this.danmakuOption!=="无"){const t=new i(c.cid);await t.fetchInfo();if(this.danmakuOption==="XML"){return t.rawXML}else{const{convertToAss:s}=await e.importAsync("download-danmaku");return s(t.rawXML)}}else{return null}}async download(){this.workingXhr=[];this.progressMap=new Map;this.updateProgress();const t=[];this.videoSpeed.startMeasure();for(const e of this.fragments){const s=await this.downloadFragment(e);t.push(s)}if(t.length<1){throw new Error("下载失败.")}const e=s();const a=new n;t.forEach((s,i)=>{let n;const o=this.fragments[i];if(t.length>1&&!this.isDash){n=`${e} - ${i+1}${this.extension(o)}`}else{n=`${e}${this.extension(o)}`}a.add(n,new Blob(Array.isArray(s)?s:[s]))});const i=await this.downloadDanmaku();a.add(`${s()}.${this.danmakuOption==="ASS"?"ass":"xml"}`,i);await a.emit(e+".zip");this.progress&&this.progress(0);this.videoSpeed.stopMeasure()}}class f{constructor(t){this.lastProgress=0;this.measureInterval=1e3;this.workingDownloader=t}startMeasure(){this.intervalTimer=setInterval(()=>{const t=this.workingDownloader.progressMap?[...this.workingDownloader.progressMap.values()].reduce((t,e)=>t+e,0):0;const e=t-this.lastProgress;if(this.speedUpdate!==undefined){this.speedUpdate(formatFileSize(e)+"/s")}this.lastProgress=t},this.measureInterval)}stopMeasure(){clearInterval(this.intervalTimer)}}async function w(){const t=await SpinQuery.select(()=>(unsafeWindow||window).aid);const e=await SpinQuery.select(()=>(unsafeWindow||window).cid);if(!(t&&e)){return false}c.aid=t;c.cid=e;if(document.URL.includes("bangumi")){c.entity=new r}else if(document.URL.includes("cheese")){const t=document.URL.match(/cheese\/play\/ep([\d]+)/);c.entity=new l(t[1])}else{c.entity=new o}try{d=await h.getAvailableFormats()}catch(t){return false}return true}async function m(){p=d[0];e.applyStyle("downloadVideoStyle");const t=dq("#download-video");t.addEventListener("click",()=>{const t=dq(".download-video");t.classList.toggle("opened");window.scroll(0,0);dq(".gui-settings-mask").click()});document.body.insertAdjacentHTML("beforeend",e.import("downloadVideoHtml"));g()}async function g(){let o;const r=new Vue({el:".download-video",components:{VDropdown:()=>e.importAsync("v-dropdown.vue"),VCheckbox:()=>e.importAsync("v-checkbox.vue"),RpcProfiles:()=>e.importAsync("aria2-rpc-profiles.vue")},data:{downloadSingle:true,coverUrl:EmptyImageUrl,aid:c.aid,cid:c.cid,dashModel:{value:t.downloadVideoFormat,items:["flv","dash"]},qualityModel:{value:p.displayName,items:d.map(t=>t.displayName)},danmakuModel:{value:t.downloadVideoDefaultDanmaku,items:["无","XML","ASS"]},codecModel:{value:t.downloadVideoDashCodec,items:["AVC/H.264","HEVC/H.265"]},progressPercent:0,size:"获取大小中",blobUrl:"",episodeList:[],downloading:false,speed:"",batch:false,rpcSettings:t.aria2RpcOption,showRpcSettings:false,busy:false,saveRpcSettingsText:"保存配置",enableDash:t.enableDashDownload,lastDirectDownloadLink:""},computed:{displaySize(){if(typeof this.size==="string"){return this.size}return formatFileSize(this.size)},sizeWarning(){if(typeof this.size==="string"){return false}return this.size>1073741824},selectedEpisodeCount(){return this.episodeList.filter(t=>t.checked).length},dash(){return this.dashModel.value==="dash"}},methods:{close(){this.$el.classList.remove("opened")},danmakuOptionChange(){t.downloadVideoDefaultDanmaku=this.danmakuModel.value},async codecChange(){t.downloadVideoDashCodec=this.codecModel.value;await this.formatChange()},async dashChange(){console.log("dash change");t.downloadVideoFormat=this.dashModel.value;const e=this.dashModel.value;let s=[];if(e==="flv"){s=await h.getAvailableFormats()}else{s=await h.getAvailableDashFormats()}d=s;[p]=e;this.qualityModel.items=s.map(t=>t.displayName);[this.qualityModel.value]=this.qualityModel.items;await this.formatChange()},async formatChange(){console.log("format change");const t=this.getFormat();try{this.size="获取大小中";const e=await t.downloadInfo(this.dash);this.size=e.totalSize}catch(t){this.size="获取大小失败";throw t}},getFormat(){const t=d.find(t=>t.displayName===this.qualityModel.value);if(!t){console.error(`No format found. model value = ${this.qualityModel.value}`);return null}return t},async exportData(t){if(this.busy===true){return}try{this.busy=true;if(!this.downloadSingle){await this.exportBatchData(t);return}const a=this.getFormat();const i=await a.downloadInfo(this.dash);switch(t){case"copyLink":await i.copyUrl();Toast.success("已复制链接到剪贴板.","下载视频",3e3);break;case"showLink":await i.showUrl();break;case"aria2":await i.exportAria2(false);break;case"aria2RPC":await i.exportAria2(true);break;case"copyVLD":await i.exportData(true);Toast.success("已复制VLD数据到剪贴板.","下载视频",3e3);break;case"exportVLD":await i.exportData(false);break;case"ffmpegFragments":if(i.fragments.length<2){Toast.info("当前视频没有分段.","分段列表",3e3)}else{const{getFragmentsList:t}=await e.importAsync("ffmpeg-support");const a=new n;a.add("ffmpeg-files.txt",t(i.fragments.length,s(),i.fragments.map(t=>i.extension(t))));await a.emit()}break;default:break}}catch(t){logError(t)}finally{this.busy=false}},async exportBatchData(t){const a=this.episodeList;if(a.every(t=>t.checked===false)){Toast.info("请至少选择1集或以上的数量!","批量导出",3e3);return}const o=t=>{const e=a.find(e=>e.cid===t.cid);if(e===undefined){return false}return e.checked};const r=this.getFormat();if(this.danmakuModel.value!=="无"){const t=Toast.info("下载弹幕中...","批量导出");const s=new n;try{if(this.danmakuModel.value==="XML"){for(const t of a.filter(o)){const e=new i(t.cid);await e.fetchInfo();s.add(t.title+".xml",e.rawXML)}}else{const{convertToAss:t}=await e.importAsync("download-danmaku");for(const e of a.filter(o)){const a=new i(e.cid);await a.fetchInfo();s.add(e.title+".ass",await t(a.rawXML))}}await s.emit(this.cid+".danmakus.zip")}catch(t){logError(`弹幕下载失败`);throw t}finally{t.dismiss()}}const l=Toast.info("获取链接中...","批量导出");const d=this.batchExtractor;d.config.itemFilter=o;d.config.api=await c.entity.getApiGenerator(this.dash);let p;try{switch(t){case"aria2":p=await d.collectAria2(r,l,false);await n.single(s(false)+".txt",new Blob([p],{type:"text/plain"}),{ffmpeg:this.ffmpegOption});return;case"aria2RPC":await d.collectAria2(r,l,true);Toast.success(`成功发送了批量请求.`,"aria2 RPC",3e3);return;case"copyVLD":GM.setClipboard(await d.collectData(r,l),{mimetype:"text/plain"});Toast.success("已复制批量vld数据到剪贴板.","批量导出",3e3);return;case"exportVLD":p=await d.collectData(r,l);await n.single(s(false)+".json",new Blob([p],{type:"text/json"}),{ffmpeg:this.ffmpegOption});return;case"ffmpegFragments":{const t=await d.getRawItems(r);const a=new u(r,t[0].fragments);const{getBatchFragmentsList:i}=await e.importAsync("ffmpeg-support");const o=i(t,this.dash||a.extension());if(!o){Toast.info("所有选择的分P都没有分段.","分段列表",3e3)}else{const t=new n;for(const[e,s]of o.entries()){t.add(e,s)}await t.emit(escapeFilename(`${s(false)}.zip`))}}break;case"ffmpegEpisodes":{const t=await d.getRawItems(r);const s=new u(r,t[0].fragments);const{getBatchEpisodesList:a}=await e.importAsync("ffmpeg-support");const i=a(t,this.dash||s.extension());const o=new n;o.add("ffmpeg-files.txt",i);await o.emit()}break;default:return}}catch(t){logError(t)}finally{l.dismiss()}},async checkBatch(){const t=["/www.bilibili.com/bangumi","/www.bilibili.com/video/av"];if(!t.some(t=>document.URL.includes(t))){this.batch=false;this.episodeList=[];return}const{BatchExtractor:s}=await e.importAsync("batch-download");if(await s.test()!==true){this.batch=false;this.episodeList=[];return}const a=this.batchExtractor=new s;this.batch=true;this.episodeList=(await a.getItemList()).map((t,e)=>{return{aid:t.aid,cid:t.cid,title:t.title,index:e,checked:true}})},cancelDownload(){if(o){o.cancelDownload()}},async startDownload(){const t=this.getFormat();try{this.downloading=true;const e=await t.downloadInfo(this.dash);e.videoSpeed.speedUpdate=(t=>this.speed=t);e.progress=(t=>{this.progressPercent=Math.trunc(t*100)});o=e;await e.download();this.lastDirectDownloadLink=n.lastPackageUrl}catch(t){if(t!=="canceled"){logError(t)}this.progressPercent=0}finally{this.downloading=false;this.speed=""}},selectAllEpisodes(){this.episodeList.forEach(t=>t.checked=true)},unselectAllEpisodes(){this.episodeList.forEach(t=>t.checked=false)},inverseAllEpisodes(){this.episodeList.forEach(t=>t.checked=!t.checked)},toggleRpcSettings(){this.showRpcSettings=!this.showRpcSettings},saveRpcSettings(){if(this.rpcSettings.host===""){this.rpcSettings.host="127.0.0.1"}if(this.rpcSettings.port===""){this.rpcSettings.port="6800"}t.aria2RpcOption=this.rpcSettings;const e=t.aria2RpcOptionProfiles.find(e=>e.name===t.aria2RpcOptionSelectedProfile);if(e){Object.assign(e,this.rpcSettings);t.aria2RpcOptionProfiles=t.aria2RpcOptionProfiles}this.saveRpcSettingsText="已保存";setTimeout(()=>this.saveRpcSettingsText="保存配置",2e3)},updateProfile(e){t.aria2RpcOption=this.rpcSettings=_.omit(e,"name")}},async mounted(){}});Observer.videoChange(async()=>{r.close();r.batch=false;r.downloadSingle=true;const t=dq("#download-video");const e=await w();t.style.display=e?"flex":"none";if(!e){return}r.aid=c.aid;r.cid=c.cid;try{const t=new a(c.aid);await t.fetchInfo();r.coverUrl=t.coverUrl.replace("http:","https:")}catch(t){r.coverUrl=EmptyImageUrl}r.dashChange();await r.checkBatch()})}return{widget:{content:`\n`,condition:w,success:m}}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/simplify-liveroom.min.js"] = (()=>{return(e,i)=>{const s={vip:"老爷图标",fansMedal:"粉丝勋章",title:"活动头衔",userLevel:"用户等级",guard:"舰长图标",systemMessage:"全区广播",welcomeMessage:"欢迎信息",giftMessage:"礼物弹幕",guardPurchase:"上舰提示",giftPanel:"付费礼物",kanban:"看板娘",eventsBanner:"活动横幅",popup:"抽奖提示",skin:"房间皮肤"};class t{constructor(i,s){this.skinDisabled=e.simplifyLiveroomSettings.skin;this.skinSelectors=i;this.skinClass=s;i.forEach(e=>{SpinQuery.select(e,i=>{Observer.attributes(e,e=>{e.forEach(e=>{if(e.attributeName==="class"){if(this.skinDisabled&&i.classList.contains(s)){i.classList.remove(s)}else if(!this.skinDisabled&&!i.classList.contains(s)){i.classList.add(s)}}})})})})}setSkin(e){this.skinDisabled=!e;this.skinSelectors.forEach(i=>{SpinQuery.select(i,i=>i.classList[e?"add":"remove"](this.skinClass))})}}const n=[new t(["#head-info-vm","#gift-control-vm","#rank-list-vm","#rank-list-ctnr-box",".gift-panel.base-panel",".gift-panel.extend-panel",".seeds-wrap>div:first-child",".gift-section>div:last-child",".z-gift-package>div>div",".right-action"],"live-skin-coloration-area"),new t([".rank-list-ctnr .tabs"],"isHundred"),new t([".rank-list-ctnr .tab-content > div"],"hundred")];const a=(e,i)=>{document.body.classList[e?"add":"remove"](`simplify-${i}`);if(i==="skin"){n.forEach(i=>i.setSkin(!e))}};const c=()=>document.URL.startsWith(`https://live.bilibili.com/`);if(c()){Object.keys(s).forEach(i=>{const s=e.simplifyLiveroomSettings[i];a(s,i)})}return{widget:{condition:c,content:`\n
\n\n简化直播间\n\n
\n`,success:()=>{const i=document.querySelector("#simplify-liveroom");const t=document.querySelector(".gui-settings-mask");i.addEventListener("click",e=>{const i=document.querySelector(".simplify-liveroom-settings");if(i.contains(e.target)||e.target===i){return}i.classList.toggle("opened")});i.addEventListener("mouseenter",()=>t.classList.add("transparent"));i.addEventListener("mouseleave",()=>t.classList.remove("transparent"));new Vue({el:".simplify-liveroom-settings",data:{items:Object.entries(s).map(([i,s])=>{const t=e.simplifyLiveroomSettings[i];a(t,i);return{key:i,name:s,checked:t}})},methods:{itemClick(i){i.checked=!i.checked;a(i.checked,i.key);e.simplifyLiveroomSettings=Object.assign(e.simplifyLiveroomSettings,{[i.key]:i.checked})}}})}}}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/hide-banner.min.css"] = `#banner_link,.custom-navbar .blur-layer,.z-top-container.has-banner>.header{display:none!important}.b-header-mask-wrp .b-header-mask-bg,div.blur-bg{opacity:0!important}.international-home .bili-banner{visibility:hidden!important;height:50px!important;min-height:unset!important}`; onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/hide-hash-tags.min.js"] = (()=>{return(t,e)=>{if(document.URL.replace(location.search,"")!=="https://t.bilibili.com/"){return}const n=`.left-panel .tag-panel,.right-panel .tag-panel{display: none !important}`;const a="hideHashTagsStyle";return e.toggleStyle(n,a)}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/trending-videos.min.js"] = (()=>{return(t,a)=>{const i=async(t,i)=>{const e=await Ajax.getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/ranking/index?day=${t}`);if(getUID()&&i===undefined){const{getWatchlaterList:t}=await a.importAsync("watchlater-api");i=await t()}if(e.code!==0){throw new Error(e.message)}return e.data.map(a=>({id:a.aid+"-"+t,aid:parseInt(a.aid),title:a.title,upID:a.mid,upName:a.author,coverUrl:a.pic.replace("http://","https://"),description:a.description,durationText:a.duration,playCount:formatCount(a.play),coins:formatCount(a.coins),favorites:formatCount(a.favorites),watchlater:i?i.includes(parseInt(a.aid)):null}))};return{export:{getTrendingVideos:i}}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/video-downloader-fragment.min.js"] = (()=>{return(r,e)=>{}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/text-validate.min.js"] = (()=>{return(e,t)=>{class a{constructor(e){this.key=e}get originalValue(){return e[this.key]}static getValidator(e){switch(e){case"customStyleColor":return new i(e);case"blurBackgroundOpacity":case"customControlBackgroundOpacity":case"customNavbarBlurOpacity":return new s(e);case"defaultPlayerMode":case"defaultVideoQuality":case"i18nLanguage":case"deadVideoTitleProvider":return new r(e);case"darkScheduleStart":case"darkScheduleEnd":return new n(e);case"playerFocusOffset":return new u(e);case"sideBarOffset":return new o(e,-40,40);default:return new a(e)}}isValidate(e){return e}validate(e){const t=this.isValidate(e);if(t===undefined){return this.originalValue}return t}}class i extends a{isValidate(e){if(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/.test(e)){if(e.length<7){return`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`}else{return e}}}}class s extends a{isValidate(e){if(/^([-+]?\d+)(\.\d+)?$/.test(e)){const t=parseFloat(e);if(t>=0&&t<=1){return e}}}}class r extends a{isValidate(e){const[t]=Object.values(Resource.manifest).filter(e=>e.dropdown&&e.dropdown.key===this.key).map(e=>e.dropdown);if(t.items.indexOf(e)!==-1){return e}}}class n extends a{isValidate(e){const t=e.match(/^([\d]{1,2}):([\d]{1,2})$/);if(t&&t.length>=3){const e={hour:parseInt(t[1]),minute:parseInt(t[2])};(function(){while(this.minute<0){this.minute+=60;this.hour-=1}while(this.minute>=60){this.minute-=60;this.hour+=1}while(this.hour<0){this.hour+=24}while(this.hour>=24){this.hour-=24}}).call(e);return`${e.hour}:${e.minute<10?"0"+e.minute:e.minute}`}}}class u extends a{isValidate(e){const t=parseInt(e);if(!isNaN(t)){return t}}}class o extends a{constructor(e,t,a){super(e);this.min=t;this.max=a}isValidate(e){const t=parseInt(e);if(!isNaN(t)&&t>=this.min&&t<=this.max){return t}}}return{export:{Validator:a,ColorValidator:i,DropDownValidator:r,OpacityValidator:s,TimeValidator:n,NumberValidator:u}}}})(); onlineData["https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/min/keymap.min.js"] = (()=>{return(e,i)=>{const t=["https://www.bilibili.com/bangumi/","https://www.bilibili.com/video/"];if(t.some(e=>document.URL.startsWith(e))){const e={w:".bilibili-player-video-web-fullscreen",t:".bilibili-player-video-btn-widescreen",r:".bilibili-player-video-btn-repeat",m:".bilibili-player-video-btn-volume .bilibili-player-iconfont-volume",l:".video-toolbar .like",c:".video-toolbar .coin,.tool-bar .coin-info",s:".video-toolbar .collect"};let t;const a=e=>{let a=dq(".keymap-playback-tip");if(!a){const e=dq(".bilibili-player-video-wrap");if(!e){return}e.insertAdjacentHTML("afterbegin",`\n
\n\n
x\n
\n`);i.applyStyleFromText(`\n .keymap-playback-tip-container {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n padding: 8px 16px;\n background-color: #000A;\n color: white;\n pointer-events: none;\n opacity: 0;\n z-index: 100;\n display: flex;\n align-items: center;\n font-size: 14pt;\n border-radius: 4px;\n transition: .2s ease-out;\n }\n .keymap-playback-tip-container.show {\n opacity: 1;\n }\n .keymap-playback-tip-container i {\n line-height: 1;\n margin-right: 8px;\n font-size: 18pt;\n }\n`,"keymapStyle");a=dq(".keymap-playback-tip")}a.innerHTML=e.toString();if(t){clearTimeout(t)}dq(".keymap-playback-tip-container").classList.add("show");t=setTimeout(()=>{dq(".keymap-playback-tip-container").classList.remove("show")},2e3)};document.body.addEventListener("keydown",i=>{if(document.activeElement&&["input","textarea"].includes(document.activeElement.nodeName.toLowerCase())){return}const t=i.key.toLowerCase();const n=!i.shiftKey&&!i.altKey&&!i.ctrlKey;if(t in e&&n){const a=dq(e[t]);if(!a){return}i.stopPropagation();i.preventDefault();a.click()}else if(t==="d"&&n){const e=dq(".bilibili-player-video-danmaku-switch input");if(!e){return}i.stopPropagation();i.preventDefault();e.checked=!e.checked;raiseEvent(e,"change")}else if(i.shiftKey){const e=dq(".bilibili-player-video video");if(e===null){return}const n=[.5,.75,1,1.25,1.5,2];let o=true;if(t===">"||t==="ArrowUp".toLowerCase()){e.playbackRate=n.find(i=>i>e.playbackRate)||n[n.length-1];a(e.playbackRate)}else if(t==="<"||t==="ArrowDown".toLowerCase()){e.playbackRate=[...n].reverse().find(i=>i