mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
281 lines
10 KiB
JavaScript
281 lines
10 KiB
JavaScript
export class ResourceManager {
|
|
constructor () {
|
|
this.data = Resource.all
|
|
this.skippedImport = []
|
|
this.attributes = {}
|
|
this.styleManager = new StyleManager(this)
|
|
const styleMethods = Object.getOwnPropertyNames(StyleManager.prototype).filter(it => it !== 'constructor')
|
|
for (const key of styleMethods) {
|
|
this[key] = function (...params) {
|
|
this.styleManager[key](...params)
|
|
}
|
|
}
|
|
this.setupColors()
|
|
}
|
|
setupColors () {
|
|
this.color = new ColorProcessor(settings.customStyleColor)
|
|
settings.foreground = this.color.foreground
|
|
settings.blueImageFilter = this.color.blueImageFilter
|
|
settings.pinkImageFilter = this.color.pinkImageFilter
|
|
settings.brightness = this.color.brightness
|
|
settings.filterInvert = this.color.filterInvert
|
|
|
|
const hexToRgba = input => this.color.rgbToString(this.color.hexToRgba(input))
|
|
let styles = []
|
|
styles.push('--theme-color:' + settings.customStyleColor)
|
|
for (let opacity = 10; opacity <= 90; opacity += 10) {
|
|
const color = this.color.hexToRgba(settings.customStyleColor)
|
|
color.a = opacity / 100
|
|
styles.push(`--theme-color-${opacity}:` + this.color.rgbToString(color))
|
|
}
|
|
styles.push('--foreground-color:' + settings.foreground)
|
|
styles.push('--foreground-color-b:' + hexToRgba(settings.foreground + 'b'))
|
|
styles.push('--foreground-color-d:' + hexToRgba(settings.foreground + 'd'))
|
|
styles.push('--blue-image-filter:' + settings.blueImageFilter)
|
|
styles.push('--pink-image-filter:' + settings.pinkImageFilter)
|
|
styles.push('--brightness:' + settings.brightness)
|
|
styles.push('--invert-filter:' + settings.filterInvert)
|
|
styles.push('--blur-background-opacity:' + settings.blurBackgroundOpacity)
|
|
// styles.push("--custom-control-background-opacity:" + settings.customControlBackgroundOpacity);
|
|
this.applyStyleFromText(`html{${styles.join(';')}}`, 'bilibili-evolved-variables')
|
|
}
|
|
resolveComponentName (componentName) {
|
|
const keyword = '/' + componentName.replace('./', '').replace('../', '') + '.min.js'
|
|
for (const [name, value] of Object.entries(Resource.all)) {
|
|
if (value.url.endsWith(keyword)) {
|
|
return name
|
|
}
|
|
}
|
|
return componentName
|
|
}
|
|
resolveComponent (componentName) {
|
|
const resource = Resource.all[this.resolveComponentName(componentName)]
|
|
if (!resource) {
|
|
this.skippedImport.push(componentName)
|
|
}
|
|
return resource
|
|
}
|
|
importAsync (componentName) {
|
|
return new Promise(resolve => {
|
|
const resource = this.resolveComponent(componentName)
|
|
if (!resource) {
|
|
resolve(unsafeWindow.bilibiliEvolved)
|
|
}
|
|
if (!Object.keys(this.attributes).includes(resource.key)) {
|
|
if (resource.type.name === 'html' || resource.type.name === 'style') {
|
|
resource.download().then(() => resolve(this.import(componentName)))
|
|
} else {
|
|
this.fetchByKey(resource.key).then(() => resolve(this.import(componentName)))
|
|
}
|
|
} else {
|
|
resolve(this.import(componentName))
|
|
}
|
|
})
|
|
}
|
|
import (componentName) {
|
|
const resource = this.resolveComponent(componentName)
|
|
if (!resource) {
|
|
return unsafeWindow.bilibiliEvolved
|
|
}
|
|
if (resource.type.name === 'html' || resource.type.name === 'style') {
|
|
if (!resource.downloaded) {
|
|
console.error(`Import failed: component "${componentName}" is not loaded.`)
|
|
return null
|
|
}
|
|
return resource.text
|
|
} else {
|
|
const attribute = this.attributes[this.resolveComponentName(componentName)]
|
|
if (attribute === undefined) {
|
|
console.error(`Import failed: component "${componentName}" is not loaded.`)
|
|
return null
|
|
}
|
|
return attribute.export
|
|
}
|
|
}
|
|
async fetchByKey (key) {
|
|
const resource = Resource.all[key]
|
|
if (!resource) {
|
|
return null
|
|
}
|
|
const text = await resource.download().catch(reason => {
|
|
console.error(`Download error, XHR status: ${reason}`)
|
|
let toastMessage = `无法下载组件<span>${Resource.all[key].displayName}</span>`
|
|
if (settings.toastInternalError) {
|
|
toastMessage += '\n' + reason
|
|
}
|
|
Toast.error(toastMessage, '错误')
|
|
})
|
|
await Promise.all(resource.dependencies
|
|
.filter(it => it.type.name === 'style')
|
|
.map(it => this.styleManager.fetchStyleByKey(it.key)))
|
|
await Promise.all(resource.dependencies
|
|
.filter(it => it.type.name === 'script')
|
|
.map(it => this.fetchByKey(it.key)))
|
|
this.applyComponent(key, text)
|
|
}
|
|
async fetch () {
|
|
const isCacheValid = this.validateCache()
|
|
let loadingToast = null
|
|
if (settings.toast === true) {
|
|
await this.fetchByKey('toast')
|
|
unsafeWindow.bilibiliEvolved.Toast = Toast = this.attributes.toast.export.Toast || this.attributes.toast.export
|
|
if (!isCacheValid && settings.useCache) {
|
|
loadingToast = Toast.info(/* html */`<div class="loading"></div>正在初始化脚本`, '初始化')
|
|
}
|
|
}
|
|
const promises = []
|
|
for (const key in settings) {
|
|
if (settings[key] === true && key !== 'toast') {
|
|
const promise = this.fetchByKey(key)
|
|
if (promise) {
|
|
promises.push(promise)
|
|
}
|
|
}
|
|
}
|
|
await Promise.all(promises)
|
|
saveSettings(settings)
|
|
if (loadingToast) {
|
|
loadingToast.dismiss()
|
|
}
|
|
this.applyReloadables() // reloadables run sync
|
|
// await this.applyDropdownOptions();
|
|
this.applyWidgets() // No need to wait the widgets
|
|
}
|
|
applyReloadables () {
|
|
const checkAttribute = (key, attributes) => {
|
|
if (attributes.reload && attributes.unload) {
|
|
addSettingsListener(key, newValue => {
|
|
if (newValue === true) {
|
|
attributes.reload()
|
|
} else {
|
|
attributes.unload()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
for (const key of Resource.reloadables) {
|
|
const attributes = this.attributes[key]
|
|
if (attributes === undefined) {
|
|
const fetchListener = async newValue => {
|
|
if (newValue === true) {
|
|
await this.styleManager.fetchStyleByKey(key)
|
|
await this.fetchByKey(key)
|
|
removeSettingsListener(key, fetchListener)
|
|
checkAttribute(key, this.attributes[key])
|
|
}
|
|
}
|
|
addSettingsListener(key, fetchListener)
|
|
} else {
|
|
checkAttribute(key, attributes)
|
|
}
|
|
}
|
|
}
|
|
applyComponent (key, text) {
|
|
const func = eval(text)
|
|
if (func) {
|
|
try {
|
|
const attribute = func(settings, this) || {}
|
|
this.attributes[key] = attribute
|
|
} catch (error) {
|
|
console.error(`Failed to apply feature "${key}": ${error}`)
|
|
let toastMessage = `加载组件<span>${Resource.all[key].displayName}</span>失败`
|
|
if (settings.toastInternalError) {
|
|
toastMessage += '\n' + error
|
|
}
|
|
Toast.error(toastMessage, '错误')
|
|
}
|
|
}
|
|
}
|
|
async applyWidget (info) {
|
|
let condition = true
|
|
if (typeof info.condition === 'function') {
|
|
condition = info.condition()
|
|
if (typeof condition === 'object' && 'then' in condition) {
|
|
condition = await condition.catch(() => { return false })
|
|
}
|
|
}
|
|
if (condition === true) {
|
|
if (info.content) {
|
|
document.querySelector('.widgets-container').insertAdjacentHTML('beforeend', info.content)
|
|
}
|
|
if (info.success) {
|
|
info.success()
|
|
}
|
|
}
|
|
}
|
|
async applyWidgets () {
|
|
await Promise.all(Object.values(this.attributes)
|
|
.filter(it => it.widget)
|
|
.map(it => this.applyWidget(it.widget))
|
|
)
|
|
}
|
|
async applyDropdownOptions () {
|
|
async function applyDropdownOption (info) {
|
|
if (Array.isArray(info)) {
|
|
await Promise.all(info.map(applyDropdownOption))
|
|
} else {
|
|
const dropdownInput = dq(`.gui-settings-dropdown input[key=${info.key}]`)
|
|
dropdownInput.value = settings[info.key]
|
|
dropdownInput.setAttribute('data-name', settings[info.key])
|
|
const dropdown = dropdownInput.parentElement
|
|
const list = dropdown.querySelector('ul')
|
|
const input = dropdown.querySelector('input')
|
|
info.items.forEach(itemHtml => {
|
|
list.insertAdjacentHTML('beforeend', `<li data-name="${itemHtml}">${itemHtml}</li>`)
|
|
})
|
|
list.querySelectorAll('li').forEach(li => li.addEventListener('click', () => {
|
|
input.value = li.innerText
|
|
input.setAttribute('data-name', li.getAttribute('data-name'))
|
|
settings[info.key] = li.getAttribute('data-name')
|
|
}))
|
|
}
|
|
}
|
|
const manifests = Object.values(Resource.manifest).filter(it => it.dropdown).map(it => it.dropdown)
|
|
Object.values(Resource.all)
|
|
// .concat(Object.values(this.attributes))
|
|
.filter(it => it.dropdown)
|
|
.map(it => it.dropdown)
|
|
.forEach(it => {
|
|
if (!manifests.some(m => m.key === it.key)) {
|
|
manifests.push(it)
|
|
}
|
|
})
|
|
await Promise.all(manifests.map(it => applyDropdownOption(it)))
|
|
}
|
|
toggleStyle (content, id) {
|
|
if (id === undefined) { // content is resource name
|
|
this.styleManager.applyStyle(content)
|
|
return {
|
|
reload: () => this.styleManager.applyStyle(content),
|
|
unload: () => this.styleManager.removeStyle(content)
|
|
}
|
|
} else { // content is style text
|
|
this.styleManager.applyStyleFromText(content, id)
|
|
return {
|
|
reload: () => this.styleManager.applyStyleFromText(content, id),
|
|
unload: () => document.getElementById(id).remove()
|
|
}
|
|
}
|
|
}
|
|
validateCache () {
|
|
if (typeof offlineData !== 'undefined') { // offline version always has cache
|
|
return true
|
|
}
|
|
if (Object.getOwnPropertyNames(settings.cache).length === 0) { // has no cache
|
|
return false
|
|
}
|
|
if (settings.cache.version === undefined) { // Has newly downloaded cache
|
|
settings.cache = Object.assign(settings.cache, { version: settings.currentVersion })
|
|
// settings.cache.version = settings.currentVersion;
|
|
saveSettings(settings)
|
|
return true
|
|
}
|
|
if (settings.cache.version !== settings.currentVersion) { // Has old version cache
|
|
settings.cache = {}
|
|
saveSettings(settings)
|
|
return false
|
|
}
|
|
return true // Has cache
|
|
}
|
|
}
|