Merge tag 'v2.8.12-preview' into preview-fixes

This commit is contained in:
the1812 2024-07-19 21:25:32 +08:00
commit c740f16208
10 changed files with 110 additions and 77 deletions

View File

@ -62,6 +62,7 @@
"linebreak", "linebreak",
"Liveroom", "Liveroom",
"materialdesignicons", "materialdesignicons",
"matroska",
"medialist", "medialist",
"mimetype", "mimetype",
"minmax", "minmax",
@ -133,6 +134,6 @@
"*.tsx": "${capture}.ts", "*.tsx": "${capture}.ts",
"tsconfig.json": "tsconfig.*.json", "tsconfig.json": "tsconfig.*.json",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, .*", "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, .*",
"index.ts": "index.*.ts, index.md, index.*.md", "index.ts": "index.*.ts, index.md, index.*.md"
} }
} }

View File

@ -27,7 +27,7 @@
"type": "component", "type": "component",
"name": "feedsFilter", "name": "feedsFilter",
"displayName": "动态过滤器", "displayName": "动态过滤器",
"description": "按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看.\r\n", "description": "按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看.\r\n\r\n> 侧边卡片的 `正在直播` 指原版的板块, 如果你使用了 `直播信息扩充`, 则需要关闭该功能才能生效.\r\n",
"fullRelativePath": "../../registry/dist/components/feeds/filter.js", "fullRelativePath": "../../registry/dist/components/feeds/filter.js",
"fullAbsolutePath": "registry/dist/components/feeds/filter.js" "fullAbsolutePath": "registry/dist/components/feeds/filter.js"
}, },
@ -1116,7 +1116,7 @@
"type": "plugin", "type": "plugin",
"name": "downloadVideo.outputs.wasm", "name": "downloadVideo.outputs.wasm",
"displayName": "下载视频 - WASM 混流输出", "displayName": "下载视频 - WASM 混流输出",
"description": "by [@WakelessSloth56](https://github.com/WakelessSloth56)\n\n使用 WASM 在浏览器中下载并合并音视频", "description": "by [@WakelessSloth56](https://github.com/WakelessSloth56)\n\n使用 WASM 在浏览器中下载并合并音视频, 支持批量下载",
"fullRelativePath": "../../registry/dist/plugins/video/download/wasm-output.js", "fullRelativePath": "../../registry/dist/plugins/video/download/wasm-output.js",
"fullAbsolutePath": "registry/dist/plugins/video/download/wasm-output.js" "fullAbsolutePath": "registry/dist/plugins/video/download/wasm-output.js"
}, },

View File

@ -38,6 +38,8 @@
按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看. 按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看.
> 侧边卡片的 `正在直播` 指原版的板块, 如果你使用了 `直播信息扩充`, 则需要关闭该功能才能生效.
### [强制固定动态侧栏](../../registry/dist/components/feeds/fixed-sidebars.js) ### [强制固定动态侧栏](../../registry/dist/components/feeds/fixed-sidebars.js)
`fixedFeedsSidebars` `fixedFeedsSidebars`
@ -1493,7 +1495,7 @@ by [@diannaojiang](https://github.com/diannaojiang)
by [@WakelessSloth56](https://github.com/WakelessSloth56) by [@WakelessSloth56](https://github.com/WakelessSloth56)
使用 WASM 在浏览器中下载并合并音视频 使用 WASM 在浏览器中下载并合并音视频, 支持批量下载
### [快捷键扩展 - 视频倍速](../../registry/dist/plugins/video/player/speed.js) ### [快捷键扩展 - 视频倍速](../../registry/dist/plugins/video/player/speed.js)
`speed.keymap` `speed.keymap`

View File

@ -99,6 +99,9 @@ const sideCards: { [id: number]: SideCardType } = {
displayName: '发布动态', displayName: '发布动态',
}, },
} }
if (getComponentSettings('extendFeedsLive').enabled) {
delete sideCards[3]
}
let cardsManager: typeof import('@/components/feeds/api').feedsCardsManager let cardsManager: typeof import('@/components/feeds/api').feedsCardsManager
const sideBlock = 'feeds-filter-side-block-' const sideBlock = 'feeds-filter-side-block-'

View File

@ -1 +1,3 @@
按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看. 按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片. 详细设置请前往[动态首页](https://t.bilibili.com/)查看.
> 侧边卡片的 `正在直播` 指原版的板块, 如果你使用了 `直播信息扩充`, 则需要关闭该功能才能生效.

View File

@ -6,27 +6,38 @@ const darkMetaColor = '#111'
const add = async () => { const add = async () => {
document.body.classList.add('dark') document.body.classList.add('dark')
localStorage.setItem('pbp_theme_v4', 'b') localStorage.setItem('pbp_theme_v4', 'b')
const meta = dq('meta[name="theme-color"]') as HTMLMetaElement
if (!meta) { const themeColorMeta = dq('meta[name="theme-color"]') as HTMLMetaElement
if (!themeColorMeta) {
document.head.insertAdjacentHTML( document.head.insertAdjacentHTML(
'beforeend', 'beforeend',
`<meta name="theme-color" content="${darkMetaColor}">`, `<meta name="theme-color" content="${darkMetaColor}">`,
) )
} else { } else {
meta.dataset.light = meta.content themeColorMeta.dataset.light = themeColorMeta.content
meta.content = darkMetaColor themeColorMeta.content = darkMetaColor
}
const colorSchemeMeta = dq('meta[name="color-scheme"]') as HTMLMetaElement
if (!colorSchemeMeta) {
document.head.insertAdjacentHTML('beforeend', `<meta name="color-scheme" content="dark">`)
} else {
colorSchemeMeta.content = 'dark'
} }
} }
const remove = async () => { const remove = async () => {
document.body.classList.remove('dark') document.body.classList.remove('dark')
const meta = dq('meta[name="theme-color"]') as HTMLMetaElement
if (!meta) { const themeColorMeta = dq('meta[name="theme-color"]') as HTMLMetaElement
return if (themeColorMeta?.dataset.light) {
} themeColorMeta.content = themeColorMeta.dataset.light
if (meta.dataset.light) {
meta.content = meta.dataset.light
} else { } else {
meta.remove() themeColorMeta?.remove()
}
const colorSchemeMeta = dq('meta[name="color-scheme"]') as HTMLMetaElement
if (colorSchemeMeta) {
colorSchemeMeta.content = 'light'
} }
} }

View File

@ -12,6 +12,8 @@ const getIpLocation = (item: CommentReplyItem) => {
let version = 2 let version = 2
let bbComment: any let bbComment: any
const marginLeft = 15
// 带IP属地显示的评论创建 // 带IP属地显示的评论创建
const createListCon = function listCon(item, i, pos) { const createListCon = function listCon(item, i, pos) {
const blCon = this._parentBlacklistDom(item, i, pos) const blCon = this._parentBlacklistDom(item, i, pos)
@ -28,7 +30,9 @@ const createListCon = function listCon(item, i, pos) {
'<span class="time-location">', '<span class="time-location">',
'<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'), '<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'),
item?.reply_control?.location item?.reply_control?.location
? `<span class="reply-location">${item?.reply_control?.location || ''}</span>` ? `<span class="reply-location" style="margin-left:${marginLeft}px;">${
item?.reply_control?.location || ''
}</span>`
: '', : '',
'</span>', '</span>',
item.lottery_id item.lottery_id
@ -89,7 +93,9 @@ const createSubReplyItem = function subReply(item, i) {
'<span class="time-location">', '<span class="time-location">',
'<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'), '<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'),
item?.reply_control?.location item?.reply_control?.location
? `<span class="reply-location">${item?.reply_control?.location || ''}</span>` ? `<span class="reply-location" style="margin-left:${marginLeft}px;">${
item?.reply_control?.location || ''
}</span>`
: '', : '',
'</span>', '</span>',
`<span class="like ${item.action === 1 ? 'liked' : ''}"><i></i><span>${ `<span class="like ${item.action === 1 ? 'liked' : ''}"><i></i><span>${
@ -134,7 +140,7 @@ const blackroomCreateListCon = function listCon(e, n, t) {
this._createPlatformDom(e.content.plat), this._createPlatformDom(e.content.plat),
`<span class="time">${this._formateTime(e.ctime)}</span>`, `<span class="time">${this._formateTime(e.ctime)}</span>`,
e?.reply_control?.location e?.reply_control?.location
? `<span style="margin-left:-15px;">${e?.reply_control?.location || ''}</span>` ? `<span style="margin-left:${marginLeft - 15}px;">${e?.reply_control?.location || ''}</span>`
: '', : '',
e.lottery_id e.lottery_id
? '' ? ''
@ -196,7 +202,7 @@ const blackroomCreateSubReplyItem = function subReply(e, n) {
'<div class="info">', '<div class="info">',
`<span class="time">${this._formateTime(e.ctime)}</span>`, `<span class="time">${this._formateTime(e.ctime)}</span>`,
e?.reply_control?.location e?.reply_control?.location
? `<span style="margin-left:-15px;">${e?.reply_control?.location || ''}</span>` ? `<span style="margin-left:${marginLeft - 15}px;">${e?.reply_control?.location || ''}</span>`
: '', : '',
`<span class="like ${1 === e.action ? 'liked' : ''}"><i></i><span>${ `<span class="like ${1 === e.action ? 'liked' : ''}"><i></i><span>${
e.like || '' e.like || ''
@ -275,7 +281,7 @@ const processItems = (items: CommentReplyItem[]) => {
if (replyTime.childElementCount === 0) { if (replyTime.childElementCount === 0) {
// 避免在评论更新的情况下重复添加 // 避免在评论更新的情况下重复添加
const replyLocation = document.createElement('span') const replyLocation = document.createElement('span')
replyLocation.style.marginLeft = '5px' replyLocation.style.marginLeft = `${marginLeft}px`
replyLocation.innerText = location replyLocation.innerText = location
replyTime.appendChild(replyLocation) replyTime.appendChild(replyLocation)
} }

View File

@ -6,7 +6,7 @@ export const storeNames = {
} as const } as const
async function database() { async function database() {
return new Promise((reslove: (db: IDBDatabase) => void, reject) => { return new Promise((resolve: (db: IDBDatabase) => void, reject) => {
const req = unsafeWindow.indexedDB.open(DB_NAME, DB_VERSION) const req = unsafeWindow.indexedDB.open(DB_NAME, DB_VERSION)
req.onerror = reject req.onerror = reject
req.onupgradeneeded = () => { req.onupgradeneeded = () => {
@ -18,34 +18,34 @@ async function database() {
db.createObjectStore(v) db.createObjectStore(v)
}) })
} }
req.onsuccess = () => reslove(req.result) req.onsuccess = () => resolve(req.result)
}) })
} }
async function objectStore(name: string, mode?: IDBTransactionMode) { async function objectStore(name: string, mode?: IDBTransactionMode) {
return database().then( return database().then(
db => db =>
new Promise((reslove: (db: IDBObjectStore) => void, reject) => { new Promise((resolve: (db: IDBObjectStore) => void, reject) => {
const tr = db.transaction(name, mode) const tr = db.transaction(name, mode)
reslove(tr.objectStore(name)) resolve(tr.objectStore(name))
tr.onerror = reject tr.onerror = reject
}), }),
) )
} }
async function get(store: IDBObjectStore, key: IDBValidKey | IDBKeyRange) { async function get(store: IDBObjectStore, key: IDBValidKey | IDBKeyRange) {
return new Promise((reslove: (db: any) => void, reject) => { return new Promise((resolve: (db: any) => void, reject) => {
const res = store.get(key) const res = store.get(key)
res.onerror = reject res.onerror = reject
res.onsuccess = () => reslove(res.result) res.onsuccess = () => resolve(res.result)
}) })
} }
async function put(store: IDBObjectStore, value: any, key?: IDBValidKey) { async function put(store: IDBObjectStore, value: any, key?: IDBValidKey) {
return new Promise((reslove: (db: IDBValidKey) => void, reject) => { return new Promise((resolve: (db: IDBValidKey) => void, reject) => {
const res = store.put(value, key) const res = store.put(value, key)
res.onerror = reject res.onerror = reject
res.onsuccess = () => reslove(res.result) res.onsuccess = () => resolve(res.result)
}) })
} }

View File

@ -1,12 +1,17 @@
import { DownloadPackage } from '@/core/download' import { DownloadPackage } from '@/core/download'
import { meta } from '@/core/meta' import { meta } from '@/core/meta'
import { getComponentSettings } from '@/core/settings'
import { Toast } from '@/core/toast' import { Toast } from '@/core/toast'
import { title as pluginTitle } from '.'
import type { Options } from '../../../../components/video/download'
import { DownloadVideoAction } from '../../../../components/video/download/types'
import { FFmpeg } from './ffmpeg' import { FFmpeg } from './ffmpeg'
import { getCacheOrGet, httpGet, toastProgress, toBlobUrl } from './utils' import { getCacheOrGet, httpGet, toastProgress, toBlobUrl } from './utils'
const ffmpeg = new FFmpeg() const ffmpeg = new FFmpeg()
async function load(toast: Toast) { async function loadFFmpeg() {
const toast = Toast.info('正在加载 FFmpeg', `${pluginTitle} - 初始化`)
await ffmpeg.load({ await ffmpeg.load({
workerLoadURL: toBlobUrl( workerLoadURL: toBlobUrl(
await getCacheOrGet( await getCacheOrGet(
@ -33,24 +38,26 @@ async function load(toast: Toast) {
'application/wasm', 'application/wasm',
), ),
}) })
toast.message = '完成!'
toast.close()
} }
export async function run(
async function single(
name: string, name: string,
toast: Toast,
videoUrl: string, videoUrl: string,
audioUrl: string, audioUrl: string,
isFlac: boolean, isFlac: boolean,
pageIndex = 1,
totalPages = 1,
) { ) {
if (!ffmpeg.loaded) { const toast = Toast.info('', `${pluginTitle} - ${pageIndex} / ${totalPages}`)
await load(toast)
}
ffmpeg.writeFile('video', await httpGet(videoUrl, toastProgress(toast, '正在下载视频流'))) ffmpeg.writeFile('video', await httpGet(videoUrl, toastProgress(toast, '正在下载视频流')))
ffmpeg.writeFile('audio', await httpGet(audioUrl, toastProgress(toast, '正在下载音频流'))) ffmpeg.writeFile('audio', await httpGet(audioUrl, toastProgress(toast, '正在下载音频流')))
toast.message = '混流中……'
toast.message = '混流中……'
const outputExt = isFlac ? 'mkv' : 'mp4' const outputExt = isFlac ? 'mkv' : 'mp4'
name = isFlac ? name.replace(/.[^/.]+$/, `.${outputExt}`) : name name = name.replace(/.[^/.]+$/, `.${outputExt}`)
await ffmpeg.exec([ await ffmpeg.exec([
'-i', '-i',
'video', 'video',
@ -59,7 +66,7 @@ export async function run(
'-c:v', '-c:v',
'copy', 'copy',
'-c:a', '-c:a',
isFlac ? 'flac' : 'copy', 'copy',
'-f', '-f',
isFlac ? 'matroska' : 'mp4', isFlac ? 'matroska' : 'mp4',
`output.${outputExt}`, `output.${outputExt}`,
@ -71,7 +78,40 @@ export async function run(
}) })
toast.message = '完成!' toast.message = '完成!'
toast.duration = 1500 toast.duration = 1000
await DownloadPackage.single(name, outputBlob) await DownloadPackage.single(name, outputBlob)
} }
export async function run(action: DownloadVideoAction) {
if (!ffmpeg.loaded) {
await loadFFmpeg()
}
const { dashAudioExtension, dashFlacAudioExtension, dashVideoExtension } =
getComponentSettings<Options>('downloadVideo').options
const pages = action.infos
for (let i = 0; i < pages.length; i++) {
const page = pages[i]
const [video, audio] = page.titledFragments
if (
!(
page.fragments.length === 2 &&
video.extension === dashVideoExtension &&
(audio.extension === dashAudioExtension || audio.extension === dashFlacAudioExtension)
)
) {
throw new Error('仅支持 DASH 格式视频和音频')
}
await single(
video.title,
video.url,
audio.url,
audio.extension === dashFlacAudioExtension,
i + 1,
pages.length,
)
}
}

View File

@ -1,12 +1,10 @@
import { Toast, ToastType } from '@/core/toast' import { Toast } from '@/core/toast'
import { PluginMetadata } from '@/plugins/plugin' import { PluginMetadata } from '@/plugins/plugin'
import { DownloadVideoOutput } from '../../../../components/video/download/types' import { DownloadVideoOutput } from '../../../../components/video/download/types'
import { run } from './handler' import { run } from './handler'
import { getComponentSettings } from '@/core/settings'
import type { Options } from '../../../../components/video/download'
const title = 'WASM 混流输出' export const title = 'WASM 混流输出'
const desc = '使用 WASM 在浏览器中下载并合并音视频' const desc = '使用 WASM 在浏览器中下载并合并音视频, 支持批量下载'
export const plugin: PluginMetadata = { export const plugin: PluginMetadata = {
name: 'downloadVideo.outputs.wasm', name: 'downloadVideo.outputs.wasm',
@ -23,40 +21,10 @@ export const plugin: PluginMetadata = {
displayName: 'WASM', displayName: 'WASM',
description: `${desc},运行过程中请勿关闭页面,初次使用或清除缓存后需要加载约 30 MB 的 WASM 文件`, description: `${desc},运行过程中请勿关闭页面,初次使用或清除缓存后需要加载约 30 MB 的 WASM 文件`,
runAction: async action => { runAction: async action => {
const fragments = action.infos.flatMap(it => it.titledFragments) try {
const { dashAudioExtension, dashFlacAudioExtension, dashVideoExtension } = await run(action)
getComponentSettings<Options>('downloadVideo').options } catch (error) {
Toast.error(String(error), title)
if (
fragments.length > 2 ||
(fragments.length === 2 &&
!(
fragments[0].extension === dashVideoExtension &&
(fragments[1].extension === dashAudioExtension ||
fragments[1].extension === dashFlacAudioExtension)
))
) {
Toast.error('仅支持单个视频下载', title).duration = 1500
} else {
const toast = Toast.info('正在加载', title)
try {
if (fragments.length === 2) {
await run(
fragments[0].title,
toast,
fragments[0].url,
fragments[1].url,
fragments[1].extension === dashFlacAudioExtension,
) // [dash]
} else {
toast.type = ToastType.Error
toast.message = '仅支持 dash 格式视频下载'
toast.duration = 1500
}
} catch (error) {
toast.close()
Toast.error(String(error), title)
}
} }
}, },
}) })