Merge branch 'preview-fixes' into preview-features

This commit is contained in:
the1812 2024-04-16 09:02:32 +08:00
commit 00d8e8a0a2
17 changed files with 2695 additions and 3646 deletions

View File

@ -82,7 +82,7 @@
},
"pnpm": {
"overrides": {
"caniuse-lite": "1.0.30001547",
"caniuse-lite": "1.0.30001609",
"consolidate": "1.0.1"
}
},

6135
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,7 @@
}
.at-panel,
.emoji-panel,
.reply-box .box-expand,
.reply-operation .operation-list {
z-index: 111 !important;
}

View File

@ -286,9 +286,9 @@ const decode = lodash.curry(async (type: string, blob: Blob) => {
export const decodeDanmakuSegment = decode('DmSegMobileReply')
export const decodeDanmakuView = decode('DmWebViewReply')
// 这里为了兼容 pakku, 只能用 fetch https://github.com/xmcp/pakku.js/issues/153
// 同时安装了 pakku 时unsafeWindow.fetch 将调用 hook 过的 fetch 来获取修改后的弹幕
const fetchBlob = async (url: string) => {
const response = await fetch(url, { mode: 'cors', credentials: 'include' })
const response = await unsafeWindow.fetch(url, { mode: 'cors', credentials: 'include' })
return response.blob()
}
export const getDanmakuView = async (aid: string | number, cid: string | number) => {

View File

@ -1,4 +1,5 @@
import { defineComponentMetadata } from '@/components/define'
import { getVueData } from '@/components/feeds/api'
import { playerAgent } from '@/components/video/player-agent'
import { childListSubtree, videoChange } from '@/core/observer'
import { select } from '@/core/spin-query'
@ -44,9 +45,50 @@ export const component = defineComponentMetadata({
),
),
]
await playerReady()
// 初始化状态
const rightPanel = await Promise.any([
select('.right-container-inner'),
select('.playlist-container--right'),
])
// 如果未找到元素,提前退出不执行后续动作
if (!rightPanel) {
console.warn('未找到 rightPanelContainer 或 playListContainer')
return
}
const checkPlayListPlayMode = async () => {
// 检查是不是播放列表中的最后一个视频或者最后一个分 P 视频的最后一个视频
const videoSequentialNumber = dq('.list-count')
const sequentialNumbers = videoSequentialNumber.innerHTML.split('/')
// 检查最后一个元素是单个视频还是多 P 视频
const lastVedioElement = dq(
'.action-list .action-list-inner .action-list-item-wrap:last-child .action-list-item .actionlist-item-inner',
)
let isLastVideo = false
if (lastVedioElement.classList.contains('singlep-list-item-inner')) {
isLastVideo = lastVedioElement.classList.contains('siglep-active')
} else {
isLastVideo =
lastVedioElement.children[1].lastElementChild.classList.contains(
'multip-list-item-active',
)
}
const shouldContinue = !(sequentialNumbers[0] >= sequentialNumbers[1] && isLastVideo)
const app = document.getElementById('app')
const vueInstance = getVueData(app)
// 不用判断当前状态是什么,直接将将是否需要继续播放赋值 vue 实例中的 continuousPlay
vueInstance.setContinuousPlay(shouldContinue)
}
const isChecked = (container: HTMLElement) =>
Boolean(container.querySelector('.switch-button.on') || container.matches(':checked'))
await playerReady()
const checkPlayMode = async () => {
const element = (await select(
[...autoPlayControls.disable, ...autoPlayControls.enable].join(','),
@ -62,16 +104,19 @@ export const component = defineComponentMetadata({
element.click()
}
}
videoChange(async () => {
checkPlayMode()
const video = (await playerAgent.query.video.element()) as HTMLVideoElement
video?.addEventListener('play', checkPlayMode, { once: true })
})
const rightPanelContainer = await select('.right-container-inner')
if (!rightPanelContainer) {
console.warn('未找到 rightPanelContainer')
return
const checkRightPanelPlayMode = async () => {
rightPanel.classList.contains('right-container-inner')
? checkPlayMode()
: checkPlayListPlayMode()
}
childListSubtree(rightPanelContainer, () => checkPlayMode())
videoChange(async () => {
checkRightPanelPlayMode()
const video = (await playerAgent.query.video.element()) as HTMLVideoElement
video?.addEventListener('play', checkRightPanelPlayMode, { once: true })
})
childListSubtree(rightPanel, () => checkRightPanelPlayMode())
},
})

View File

@ -1 +1 @@
删除视频播放器中出现的各种弹窗, 类别可在选项中分别选择.
删除视频播放器中出现的各种弹窗, 类别可在选项中分别选择. 如果之前点了收起弹窗, 则收起后的小弹窗则会直接删除. (不受类别选择影响)

View File

@ -8,6 +8,13 @@ export const component = defineComponentMetadata({
entry: ({ settings, metadata }) => {
const { options } = settings
const { kebabCase } = lodash
addComponentListener(
metadata.name,
(enabled: boolean) => {
document.body.classList.toggle(kebabCase(metadata.name), enabled)
},
true,
)
Object.keys(options).forEach(optionName => {
addComponentListener(
`${metadata.name}.${optionName}`,

View File

@ -1,5 +1,9 @@
body {
&.remove-player-popup {
// https://www.bilibili.com/video/BV1WA4m1L7xa/
.bili-cmd-shrink {
display: none !important;
}
// https://www.bilibili.com/video/BV1V54y1b7yW/
&-combo-likes {
.bili-guide,

View File

@ -1,8 +1,7 @@
import { defineComponentMetadata } from '@/components/define'
import { childList, urlChange } from '@/core/observer'
import { playerReady } from '@/core/utils'
import { playerReady, getVue2Data } from '@/core/utils'
import { videoUrls } from '@/core/utils/urls'
import { getVueData } from '@/components/feeds/api'
import { VideoInfo } from '@/components/video/video-info'
import { useScopedConsole } from '@/core/utils/log'
import { addComponentListener, getComponentSettings } from '@/core/settings'
@ -147,7 +146,7 @@ export const component = defineComponentMetadata({
const getRecoList = () => {
const reco_list = dq('#reco_list')
let recoList: RecommendList = getVueData(reco_list)
let recoList: RecommendList = getVue2Data(reco_list)
if (recoList.isOpen === undefined) {
recoList = recoList.$children[0]
if (recoList.isOpen === undefined) {

View File

@ -4,10 +4,11 @@ export const plugin: PluginMetadata = {
name: 'feedsFilter.pluginBlocks.chargeFeeds',
displayName: '动态过滤器 - 移除充电专属动态',
async setup() {
const { forEachFeedsCard, getVueData } = await import('@/components/feeds/api')
const { getVue2Data } = await import('@/core/utils')
const { forEachFeedsCard } = await import('@/components/feeds/api')
forEachFeedsCard({
added: card => {
const vueData = getVueData(card.element)
const vueData = getVue2Data(card.element)
const blockedType: number | null = lodash.get(
vueData,
'data.modules.module_dynamic.major.blocked.blocked_type',

View File

@ -34,21 +34,40 @@ async function load(toast: Toast) {
),
})
}
export async function run(name: string, videoUrl: string, audioUrl: string, toast: Toast) {
export async function run(
name: string,
toast: Toast,
videoUrl: string,
audioUrl: string,
isFlac: boolean,
) {
if (!ffmpeg.loaded) {
await load(toast)
}
ffmpeg.writeFile('video', await httpGet(videoUrl, toastProgress(toast, '正在下载视频流')))
ffmpeg.writeFile('audio', await httpGet(audioUrl, toastProgress(toast, '正在下载音频流')))
toast.message = '混流中……'
await ffmpeg.exec(['-i', 'video', '-i', 'audio', '-c:v', 'copy', '-c:a', 'copy', 'output.mp4'])
const outputExt = isFlac ? 'mkv' : 'mp4'
name = isFlac ? name.replace(/.[^/.]+$/, `.${outputExt}`) : name
await ffmpeg.exec([
'-i',
'video',
'-i',
'audio',
'-c:v',
'copy',
'-c:a',
isFlac ? 'flac' : 'copy',
'-f',
isFlac ? 'matroska' : 'mp4',
`output.${outputExt}`,
])
const output = await ffmpeg.readFile('output.mp4')
const output = await ffmpeg.readFile(`output.${outputExt}`)
const outputBlob = new Blob([output], {
type: 'video/mp4',
type: isFlac ? 'video/x-matroska' : 'video/mp4',
})
toast.message = '完成!'

View File

@ -1,7 +1,9 @@
import { Toast } from '@/core/toast'
import { Toast, ToastType } from '@/core/toast'
import { PluginMetadata } from '@/plugins/plugin'
import { DownloadVideoOutput } from '../../../../components/video/download/types'
import { run } from './handler'
import { getComponentSettings } from '@/core/settings'
import type { Options } from '../../../../components/video/download'
const title = 'WASM 混流输出'
const desc = '使用 WASM 在浏览器中下载并合并音视频'
@ -22,12 +24,35 @@ export const plugin: PluginMetadata = {
description: `${desc},运行过程中请勿关闭页面,初次使用或清除缓存后需要加载约 30 MB 的 WASM 文件`,
runAction: async action => {
const fragments = action.infos.flatMap(it => it.titledFragments)
if (fragments.length !== 2) {
Toast.error('仅支持 DASH 格式', title)
const { dashAudioExtension, dashFlacAudioExtension, dashVideoExtension } =
getComponentSettings<Options>('downloadVideo').options
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 {
await run(fragments[0].title, fragments[0].url, fragments[1].url, toast)
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)

View File

@ -5,9 +5,8 @@ import { ListAdaptorKey, FeedsCardsListAdaptor } from './adaptor'
export const feedsCardCallbacks: Required<FeedsCardCallback>[] = []
export const getVueData = (el: any) =>
// eslint-disable-next-line no-underscore-dangle
el.__vue__ ?? el.parentElement.__vue__ ?? el.children[0].__vue__
// TODO: 仅做兼容性导出, v2.8.11 之后可移除
export { getVue2Data as getVueData } from '@/core/utils'
export const createNodeValidator =
(className: string) =>

View File

@ -1,6 +1,7 @@
import { childList } from '@/core/observer'
import { getVue2Data } from '@/core/utils'
import { sq } from '@/core/spin-query'
import { FeedsCardsManager, FeedsCardsManagerEventType, getVueData } from './base'
import { FeedsCardsManager, FeedsCardsManagerEventType } from './base'
import { feedsCardTypes, isRepostType, FeedsCard, FeedsCardType } from '../types'
const getFeedsCardType = (element: HTMLElement) => {
@ -85,7 +86,7 @@ const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
}
const el = await sq(
() => element,
(it: any) => Boolean(getVueData(it) || !element.parentNode),
(it: any) => Boolean(getVue2Data(it) || !element.parentNode),
{ queryInterval: 100 },
)
if (element.parentNode === null) {
@ -93,10 +94,10 @@ const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
return ''
}
if (el === null) {
console.warn(el, element, getVueData(el), element.parentNode)
console.warn(el, element, getVue2Data(el), element.parentNode)
return ''
}
const vueData = getVueData(el)
const vueData = getVue2Data(el)
if (type === feedsCardTypes.repost) {
const currentText = vueData.card.item.content
const repostData = getRepostData(vueData)
@ -135,7 +136,7 @@ const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
element.setAttribute('data-type', card.type.id.toString())
if (isRepostType(card)) {
const currentUsername = card.username
const vueData = getVueData(card.element)
const vueData = getVue2Data(card.element)
const repostUsername = lodash.get(vueData, 'card.origin_user.info.uname', '')
if (currentUsername === repostUsername) {
element.setAttribute('data-self-repost', 'true')

View File

@ -1,12 +1,7 @@
import { childList } from '@/core/observer'
import { descendingStringSort } from '@/core/utils/sort'
import { pascalCase } from '@/core/utils'
import {
createNodeValidator,
FeedsCardsManager,
FeedsCardsManagerEventType,
getVueData,
} from './base'
import { pascalCase, getVue2Data } from '@/core/utils'
import { createNodeValidator, FeedsCardsManager, FeedsCardsManagerEventType } from './base'
import { FeedsCard, FeedsCardType, feedsCardTypes, isRepostType } from '../types'
import { selectAll } from '@/core/spin-query'
@ -78,7 +73,7 @@ const getText = (dynamicModule: any, cardType: FeedsCardType) => {
return combineText(mainText, typeText)
}
const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {
const vueData = getVueData(element)
const vueData = getVue2Data(element)
const parseModules = (rawModules: any) => {
if (Array.isArray(rawModules)) {
return Object.fromEntries(
@ -157,7 +152,7 @@ export class FeedsCardsManagerV2 extends FeedsCardsManager {
if (!isNodeValid(node)) {
return
}
const vueData = getVueData(node)
const vueData = getVue2Data(node)
if (!vueData) {
return
}

View File

@ -1,4 +1,4 @@
import { dqa } from '@/core/utils'
import { dqa, getVue2Data } from '@/core/utils'
import { allMutations, childList, childListSubtree } from '@/core/observer'
import { contentLoaded } from '@/core/life-cycle'
@ -98,15 +98,14 @@ const itemRemovedCallbacks: CommentItemCallback[] = []
const commentAreaCallbacks: CommentAreaCallback[] = []
/** (v2) 获取 Vue 数据 (评论 / 回复) */
const getVueData = (element: HTMLElement) => {
// eslint-disable-next-line no-underscore-dangle
const props = (element as any).__vueParentComponent?.props
const getReplyFromVueData = (element: HTMLElement) => {
const props = getVue2Data(element)?.props
return props?.reply ?? props?.subReply
}
/** (v2) 获取回复对应的元素 */
const getReplyItemElement = (parent: HTMLElement, replyId: string) => {
const [replyElement] = dqa(parent, '.sub-reply-item').filter(
(it: HTMLElement) => getVueData(it).rpid_str === replyId,
(it: HTMLElement) => getReplyFromVueData(it).rpid_str === replyId,
)
return replyElement as HTMLElement
}
@ -116,13 +115,13 @@ const getCommentId = (element: HTMLElement) => {
if (attributeId) {
return attributeId
}
const vueData = getVueData(element)
const vueData = getReplyFromVueData(element)
return vueData?.rpid_str ?? ''
}
/** (v2) 解析评论对象 */
const parseCommentItemV2 = (element: HTMLElement) => {
const vueData = getVueData(element)
const vueData = getReplyFromVueData(element)
if (!vueData) {
throw new Error('Invalid comment item')
}

View File

@ -701,3 +701,8 @@ export const simulateClick = (target: EventTarget, eventParams?: PointerEventIni
target.dispatchEvent(mouseUpEvent)
target.dispatchEvent(clickEventEvent)
}
/** 尝试获取元素对应的 Vue Data (仅适用于 Vue 2 组件) */
export const getVue2Data = (el: any) =>
// eslint-disable-next-line no-underscore-dangle
el.__vue__ ?? el.parentElement.__vue__ ?? el.children[0].__vue__ ?? el.__vueParentComponent