mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'preview-fixes' into master-cdn
This commit is contained in:
commit
86616225f0
@ -74,10 +74,10 @@ const sideCards: { [id: number]: SideCardType } = {
|
||||
className: 'profile',
|
||||
displayName: '个人资料',
|
||||
},
|
||||
1: {
|
||||
className: 'following-tags',
|
||||
displayName: '话题',
|
||||
},
|
||||
// 1: {
|
||||
// className: 'following-tags',
|
||||
// displayName: '话题',
|
||||
// },
|
||||
2: {
|
||||
className: 'notice',
|
||||
displayName: '公告栏',
|
||||
@ -98,6 +98,10 @@ const sideCards: { [id: number]: SideCardType } = {
|
||||
className: 'compose',
|
||||
displayName: '发布动态',
|
||||
},
|
||||
7: {
|
||||
className: 'search-trendings',
|
||||
displayName: '热搜',
|
||||
},
|
||||
}
|
||||
if (getComponentSettings('extendFeedsLive').enabled) {
|
||||
delete sideCards[3]
|
||||
|
||||
@ -92,6 +92,11 @@
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
&.#{$side-block}-search-trendings {
|
||||
.bili-dyn-search-trendings {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
&.#{$side-block}-most-viewed {
|
||||
.bili-dyn-up-list,
|
||||
.card-list .most-viewed-panel {
|
||||
|
||||
@ -1,66 +1,204 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { childList } from '@/core/observer'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax'
|
||||
|
||||
let relationList: Element
|
||||
let oldObserver: MutationObserver
|
||||
const displayName = '关注时间显示'
|
||||
const console = useScopedConsole(displayName)
|
||||
|
||||
const observeFans = async (node: Element) => {
|
||||
// 监听关注列表元素变化
|
||||
const [observer] = childList(node, () => {
|
||||
// 读取Vue属性里的关注列表
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const subscribeTime = (
|
||||
relationList.parentElement.parentElement.parentElement.parentElement as any
|
||||
).__vue__.relationList.map(l => l.mtime)
|
||||
const SELECTORS = {
|
||||
profileFollowContainer: '.space-head-follow.b-follow',
|
||||
profileTextClass: 'subscribe-time-text',
|
||||
relationCardAnchor: '.relation-card-info__uname',
|
||||
relationCardContainer: '.relation-card-info',
|
||||
relationCardTimeClass: 'relation-card-info__time',
|
||||
relationCardSign: '.relation-card-info__sign',
|
||||
}
|
||||
|
||||
// 为所有子元素添加关注时间显示
|
||||
relationList.querySelectorAll('.list-item>.content').forEach((e, index) => {
|
||||
// 防止重复添加元素
|
||||
if (e.querySelector('.subscribe-time-fix') === null) {
|
||||
const time = subscribeTime[index]
|
||||
if (time !== undefined) {
|
||||
e.querySelector('p').insertAdjacentHTML(
|
||||
'afterend',
|
||||
`<div class="desc subscribe-time-fix">关注时间:${new Date(
|
||||
time * 1000,
|
||||
).toLocaleString()}</div>`,
|
||||
)
|
||||
}
|
||||
type FollowTimeInfo = {
|
||||
mtime: number
|
||||
label: string
|
||||
}
|
||||
|
||||
const midMap: Record<number, FollowTimeInfo> = {}
|
||||
|
||||
const insertFollowTimeToCard = (mid: number, dateStr: string, label: string) => {
|
||||
const anchor = document.querySelector<HTMLAnchorElement>(
|
||||
`${SELECTORS.relationCardAnchor}[href*="/${mid}"]`,
|
||||
)
|
||||
const card = anchor?.closest(SELECTORS.relationCardContainer)
|
||||
if (!card) {
|
||||
return
|
||||
}
|
||||
if (card.querySelector(`.${SELECTORS.relationCardTimeClass}`)) {
|
||||
return
|
||||
}
|
||||
|
||||
const div = document.createElement('div')
|
||||
div.className = SELECTORS.relationCardTimeClass
|
||||
div.textContent = `${label}:${dateStr}`
|
||||
|
||||
const sign = card.querySelector(SELECTORS.relationCardSign)
|
||||
if (sign?.parentNode) {
|
||||
sign.parentNode.insertBefore(div, sign.nextSibling)
|
||||
}
|
||||
}
|
||||
|
||||
let fetchWrapped = false
|
||||
let pendingUpdate = false
|
||||
|
||||
const updateCards = () => {
|
||||
if (pendingUpdate) {
|
||||
return
|
||||
}
|
||||
pendingUpdate = true
|
||||
requestAnimationFrame(() => {
|
||||
pendingUpdate = false
|
||||
const mids = Array.from(
|
||||
document.querySelectorAll<HTMLAnchorElement>(SELECTORS.relationCardAnchor),
|
||||
)
|
||||
.map(el => el.href.match(/\/(\d+)/)?.[1])
|
||||
.filter(Boolean)
|
||||
.map(Number)
|
||||
|
||||
mids.forEach(mid => {
|
||||
const data = midMap[mid]
|
||||
if (data) {
|
||||
const dateStr = new Date(data.mtime * 1000).toLocaleString()
|
||||
insertFollowTimeToCard(mid, dateStr, data.label)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 移除旧的MutationObserver
|
||||
oldObserver?.disconnect()
|
||||
oldObserver = observer
|
||||
|
||||
const { addImportantStyle } = await import('@/core/style')
|
||||
const { default: style } = await import('./subscribe-time.scss')
|
||||
addImportantStyle(style, 'subscribe-time-style')
|
||||
}
|
||||
const entry = async () => {
|
||||
const spaceContainer = await select('.s-space')
|
||||
childList(spaceContainer, async () => {
|
||||
if (!document.URL.match(/^https:\/\/space\.bilibili\.com\/\d+\/fans/)) {
|
||||
return
|
||||
|
||||
const waitForProfileContainer = () =>
|
||||
new Promise<Element>(resolve => {
|
||||
const check = () => document.querySelector(SELECTORS.profileFollowContainer)
|
||||
let container = check()
|
||||
if (container) {
|
||||
resolve(container)
|
||||
} else {
|
||||
const observer = new MutationObserver(() => {
|
||||
container = check()
|
||||
if (container) {
|
||||
observer.disconnect()
|
||||
resolve(container)
|
||||
}
|
||||
})
|
||||
observer.observe(document.body, { childList: true, subtree: true })
|
||||
}
|
||||
relationList = await select('.relation-list')
|
||||
observeFans(relationList)
|
||||
})
|
||||
|
||||
const insertSubscribeTime = (followTimeStr: string) => {
|
||||
const container = document.querySelector(SELECTORS.profileFollowContainer)
|
||||
if (!container) {
|
||||
return
|
||||
}
|
||||
if (container.querySelector(`.${SELECTORS.profileTextClass}`)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (getComputedStyle(container).position === 'static') {
|
||||
;(container as HTMLElement).style.position = 'relative'
|
||||
}
|
||||
|
||||
const infoEl = document.createElement('div')
|
||||
infoEl.className = SELECTORS.profileTextClass
|
||||
infoEl.textContent = `关注于 ${followTimeStr}`
|
||||
container.appendChild(infoEl)
|
||||
}
|
||||
|
||||
const entry = async () => {
|
||||
try {
|
||||
const { addImportantStyle } = await import('@/core/style')
|
||||
const { default: style } = await import('./subscribe-time.scss')
|
||||
addImportantStyle(style, 'subscribe-time-style')
|
||||
} catch (e) {
|
||||
console.error('样式加载失败:', e)
|
||||
}
|
||||
|
||||
new MutationObserver(updateCards).observe(document.body, { childList: true, subtree: true })
|
||||
|
||||
if (!fetchWrapped) {
|
||||
fetchWrapped = true
|
||||
const originalFetch = unsafeWindow.fetch
|
||||
unsafeWindow.fetch = new Proxy(originalFetch, {
|
||||
apply(target, thisArg, args) {
|
||||
const url = typeof args[0] === 'string' ? args[0] : args[0].url
|
||||
if (url.includes('/x/relation/fans') || url.includes('/x/relation/followings')) {
|
||||
return target.apply(thisArg, args).then(res => {
|
||||
res
|
||||
.clone()
|
||||
.json()
|
||||
.then(json => {
|
||||
const list = json?.data?.list
|
||||
if (!Array.isArray(list)) {
|
||||
console.warn('接口数据结构异常:', json)
|
||||
return
|
||||
}
|
||||
list.forEach(user => {
|
||||
if (typeof user.mid === 'number' && typeof user.mtime === 'number') {
|
||||
const label = url.includes('/x/relation/fans')
|
||||
? 'Ta 关注你的时间'
|
||||
: '你关注 Ta 的时间'
|
||||
midMap[user.mid] = { mtime: user.mtime, label }
|
||||
insertFollowTimeToCard(
|
||||
user.mid,
|
||||
new Date(user.mtime * 1000).toLocaleString(),
|
||||
label,
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(err => console.warn('JSON 解析失败:', err))
|
||||
return res
|
||||
})
|
||||
}
|
||||
return target.apply(thisArg, args)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const match = location.href.match(/space\.bilibili\.com\/(\d+)/)
|
||||
if (!match) {
|
||||
console.warn('无法提取 mid')
|
||||
return
|
||||
}
|
||||
const mid = Number(match[1])
|
||||
|
||||
const info = await bilibiliApi(
|
||||
getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/relation?mid=${mid}`),
|
||||
)
|
||||
if (!info?.relation) {
|
||||
console.warn('未获取到 relation 信息')
|
||||
return
|
||||
}
|
||||
if (!info.relation.mtime) {
|
||||
console.log('当前未关注或关注时间无效')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await waitForProfileContainer()
|
||||
} catch (e) {
|
||||
console.warn('等待关注容器超时,跳过插入关注时间')
|
||||
return
|
||||
}
|
||||
|
||||
insertSubscribeTime(new Date(info.relation.mtime * 1000).toLocaleString())
|
||||
}
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'subscribeTimeShow',
|
||||
author: {
|
||||
name: 'Light_Quanta',
|
||||
link: 'https://github.com/LightQuanta',
|
||||
},
|
||||
displayName: '关注时间显示',
|
||||
tags: [componentsTags.utils],
|
||||
urlInclude: [/^https:\/\/space\.bilibili\.com/],
|
||||
entry,
|
||||
description: {
|
||||
'zh-CN': '在粉丝/关注列表显示关注的具体时间',
|
||||
author: {
|
||||
name: 'CNOCM',
|
||||
link: 'https://github.com/CNOCM',
|
||||
},
|
||||
tags: [componentsTags.utils],
|
||||
urlInclude: [
|
||||
/^https:\/\/space\.bilibili\.com\/\d+\/(relation|fans)\/(fans|follow)/,
|
||||
/https:\/\/space\.bilibili\.com\/\d+/,
|
||||
],
|
||||
entry,
|
||||
description: { 'zh-CN': '在粉丝/关注列表及用户主页显示关注的具体时间。' },
|
||||
})
|
||||
|
||||
@ -1,14 +1,25 @@
|
||||
/* 上移原名称 */
|
||||
#page-follows .list-item .content .title {
|
||||
margin-top: -9px;
|
||||
.space-head-follow.b-follow {
|
||||
position: relative;
|
||||
|
||||
.subscribe-time-text {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
line-height: 16px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
/* 上移原简介/官方认证 */
|
||||
#page-follows .list-item .content p {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
/* 修复关注时间元素的高度带来的布局影响 */
|
||||
.subscribe-time-fix {
|
||||
margin-bottom: -10px;
|
||||
.relation-card-info__time {
|
||||
color: #888;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { videoUrls, watchlaterUrls } from '@/core/utils/urls'
|
||||
import { playerAgent } from '@/components/video/player-agent'
|
||||
import { getWatchlaterList, toggleWatchlater } from '@/components/video/watchlater'
|
||||
|
||||
let listener: (() => Promise<void>) | null = null
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'autoRemoveWatchlater',
|
||||
displayName: '自动移出稍后再看',
|
||||
@ -12,12 +13,16 @@ export const component = defineComponentMetadata({
|
||||
entry: () => {
|
||||
videoChange(async ({ aid }) => {
|
||||
const videoElement = await playerAgent.query.video.element()
|
||||
videoElement.addEventListener('ended', async () => {
|
||||
if (listener !== null) {
|
||||
videoElement.removeEventListener('ended', listener)
|
||||
}
|
||||
listener = async () => {
|
||||
const list = await getWatchlaterList()
|
||||
if (list.includes(parseInt(aid))) {
|
||||
await toggleWatchlater(aid)
|
||||
}
|
||||
})
|
||||
}
|
||||
videoElement.addEventListener('ended', listener)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
import Vue from 'vue'
|
||||
import { ComponentMetadata } from '@/components/types'
|
||||
import { getFormatStr, Video } from './video'
|
||||
import { getVue2Data } from '@/core/utils'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { VideoInfo } from '@/components/video/video-info'
|
||||
|
||||
interface RecommendList extends Vue {
|
||||
isOpen: boolean
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
related: {
|
||||
aid: string
|
||||
title: string
|
||||
pubdate: number
|
||||
owner: {
|
||||
name: string
|
||||
}
|
||||
}[]
|
||||
$children: (RecommendList & VideoPageCard)[]
|
||||
}
|
||||
|
||||
interface VideoPageCard extends Vue {
|
||||
name: string
|
||||
title: string
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
oldname: string
|
||||
item: {
|
||||
aid: string
|
||||
pubdate: number
|
||||
owner: {
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
name: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultVideo implements Video {
|
||||
console: any
|
||||
metadata: ComponentMetadata
|
||||
|
||||
constructor(console: any) {
|
||||
this.console = console
|
||||
}
|
||||
|
||||
readonly videoClasses = ['video-page-operator-card-small', 'video-page-card-small']
|
||||
|
||||
readonly showUploadTime = (relist: VideoPageCard[], forceUpdate = false, formatString = '') => {
|
||||
if (!formatString) {
|
||||
const { options } = getComponentSettings(this.metadata.name)
|
||||
formatString = options.formatString?.toString()
|
||||
}
|
||||
relist.forEach(async video => {
|
||||
// 使用临时变量保存视频名称,以避免计算属性导致的问题
|
||||
let videoName: string = video.name
|
||||
// 确认存放推荐视频列表的List中的元素是否被更新
|
||||
if (forceUpdate || !video.item.owner.mark) {
|
||||
video.item.owner.mark = true
|
||||
// 确认推荐视频卡片是否被更新
|
||||
if (forceUpdate || !video.mark) {
|
||||
video.mark = true
|
||||
if (!video.item.pubdate) {
|
||||
const info = new VideoInfo(video.item.aid)
|
||||
await info.fetchInfo()
|
||||
// 保存查询到的pubdate,以便后续使用
|
||||
video.item.pubdate = info.pubdate
|
||||
}
|
||||
const createTime: Date = new Date(video.item.pubdate * 1000)
|
||||
if (!video.oldname) {
|
||||
video.oldname = video.name
|
||||
}
|
||||
videoName = getFormatStr(createTime, formatString, video.oldname)
|
||||
video.name = videoName
|
||||
}
|
||||
// 保存生成后的name
|
||||
video.item.owner.name = videoName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
readonly getRecoList = () => {
|
||||
let reco_list = dq('#reco_list')
|
||||
// 2024.10.17 兼容最近的b站前端改动
|
||||
if (reco_list == null) {
|
||||
reco_list = dq('.recommend-list-v1')
|
||||
}
|
||||
let recoList: RecommendList = getVue2Data(reco_list)
|
||||
if (recoList.isOpen === undefined) {
|
||||
recoList = recoList.$children[0]
|
||||
if (recoList.isOpen === undefined) {
|
||||
this.console.log('结构获取失败')
|
||||
this.console.log(document.URL)
|
||||
this.console.log(recoList)
|
||||
}
|
||||
}
|
||||
return recoList
|
||||
}
|
||||
|
||||
settingChange(metadata: ComponentMetadata, setting: string): void {
|
||||
this.metadata = metadata
|
||||
const recoList: RecommendList = this.getRecoList()
|
||||
const relist: VideoPageCard[] = recoList.$children.filter(video =>
|
||||
this.videoClasses.includes(video.$el.className),
|
||||
)
|
||||
this.showUploadTime(relist, true, setting)
|
||||
}
|
||||
|
||||
urlChange(metadata: ComponentMetadata): void {
|
||||
this.metadata = metadata
|
||||
const recoList: RecommendList = this.getRecoList()
|
||||
this.console.debug('urlChange recoList.mark', recoList.mark)
|
||||
if (!recoList.mark) {
|
||||
recoList.mark = true
|
||||
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更,如果变更则更新
|
||||
recoList.$watch(
|
||||
'recListItems',
|
||||
() => {
|
||||
this.console.debug('recoListItems changed, now url is', document.URL)
|
||||
const relist = recoList.$children.filter(video =>
|
||||
this.videoClasses.includes(video.$el.className),
|
||||
)
|
||||
this.showUploadTime(relist)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,14 @@
|
||||
import Vue from 'vue'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { urlChange } from '@/core/observer'
|
||||
import { playerReady, getVue2Data } from '@/core/utils'
|
||||
import { videoUrls } from '@/core/utils/urls'
|
||||
import { VideoInfo } from '@/components/video/video-info'
|
||||
import { playerReady, matchUrlPattern } from '@/core/utils'
|
||||
import { videoUrls, mediaListUrls } from '@/core/utils/urls'
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { TestPattern } from '@/core/common-types'
|
||||
import desc from './desc.md'
|
||||
|
||||
interface RecommendList extends Vue {
|
||||
isOpen: boolean
|
||||
mark: boolean
|
||||
related: {
|
||||
aid: string
|
||||
title: string
|
||||
pubdate: number
|
||||
owner: {
|
||||
name: string
|
||||
}
|
||||
}[]
|
||||
$children: (RecommendList & VideoPageCard)[]
|
||||
}
|
||||
|
||||
interface VideoPageCard extends Vue {
|
||||
name: string
|
||||
title: string
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
oldname: string
|
||||
item: {
|
||||
aid: string
|
||||
pubdate: number
|
||||
owner: {
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
name: string
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Video } from './video'
|
||||
import { DefaultVideo } from './defaultVideo'
|
||||
import { MediaListVideo } from './mediaListVideo'
|
||||
|
||||
const displayName = '显示视频投稿时间'
|
||||
const console = useScopedConsole(displayName)
|
||||
@ -62,118 +33,38 @@ export const component = defineComponentMetadata({
|
||||
},
|
||||
],
|
||||
entry: async ({ metadata }) => {
|
||||
const getFormatStr = (time: Date, format: string, upName: string) => {
|
||||
const formatMap: any = {
|
||||
'M+': time.getMonth() + 1, // 月
|
||||
'd+': time.getDate(), // 日
|
||||
'h+': time.getHours(), // 时
|
||||
'm+': time.getMinutes(), // 分
|
||||
's+': time.getSeconds(), // 秒
|
||||
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
|
||||
}
|
||||
const constMap: any = {
|
||||
up: upName, // up名
|
||||
'\\\\r': '\r', // 回车符
|
||||
'\\\\n': '\n', // 换行符
|
||||
'\\\\t': '\t', // 制表符
|
||||
}
|
||||
// 处理年份
|
||||
let matchResult: RegExpMatchArray | null = format.match(/(y+)/)
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(
|
||||
matchResult[0],
|
||||
`${time.getFullYear()}`.substring(4 - matchResult[0].length),
|
||||
)
|
||||
}
|
||||
// 处理除年份外的时间
|
||||
for (const key in formatMap) {
|
||||
if (!key) {
|
||||
continue
|
||||
}
|
||||
matchResult = format.match(new RegExp(`(${key})`))
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(
|
||||
matchResult[0],
|
||||
matchResult[0].length === 1
|
||||
? formatMap[key]
|
||||
: `00${formatMap[key]}`.substring(`${formatMap[key]}`.length),
|
||||
)
|
||||
}
|
||||
}
|
||||
// 处理自定义替换文本
|
||||
for (const key in constMap) {
|
||||
if (!key) {
|
||||
continue
|
||||
}
|
||||
matchResult = format.match(new RegExp(`(${key})`))
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(matchResult[0], constMap[key])
|
||||
}
|
||||
}
|
||||
return format
|
||||
}
|
||||
const defaultVideo = new DefaultVideo(console)
|
||||
const VideoMap: { TestPattern: TestPattern; video: Video }[] = [
|
||||
{
|
||||
// 普通视频页面
|
||||
TestPattern: videoUrls.slice(0, 1),
|
||||
video: defaultVideo,
|
||||
},
|
||||
{
|
||||
// 合集类视频页面
|
||||
TestPattern: mediaListUrls,
|
||||
video: new MediaListVideo(console),
|
||||
},
|
||||
]
|
||||
|
||||
const showUploadTime = (relist: VideoPageCard[], forceUpdate = false, formatString = '') => {
|
||||
if (!formatString) {
|
||||
const { options } = getComponentSettings(metadata.name)
|
||||
formatString = options.formatString?.toString()
|
||||
}
|
||||
relist.forEach(async video => {
|
||||
// 使用临时变量保存视频名称,以避免计算属性导致的问题
|
||||
let videoName: string = video.name
|
||||
// 确认存放推荐视频列表的List中的元素是否被更新
|
||||
if (forceUpdate || !video.item.owner.mark) {
|
||||
video.item.owner.mark = true
|
||||
// 确认推荐视频卡片是否被更新
|
||||
if (forceUpdate || !video.mark) {
|
||||
video.mark = true
|
||||
if (!video.item.pubdate) {
|
||||
const info = new VideoInfo(video.item.aid)
|
||||
await info.fetchInfo()
|
||||
// 保存查询到的pubdate,以便后续使用
|
||||
video.item.pubdate = info.pubdate
|
||||
}
|
||||
const createTime: Date = new Date(video.item.pubdate * 1000)
|
||||
if (!video.oldname) {
|
||||
video.oldname = video.name
|
||||
}
|
||||
videoName = getFormatStr(createTime, formatString, video.oldname)
|
||||
video.name = videoName
|
||||
const getMatchVideo = () => {
|
||||
for (const item of VideoMap) {
|
||||
for (const pattern of item.TestPattern) {
|
||||
if (matchUrlPattern(pattern)) {
|
||||
return item.video
|
||||
}
|
||||
// 保存生成后的name
|
||||
video.item.owner.name = videoName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getRecoList = () => {
|
||||
let reco_list = dq('#reco_list')
|
||||
// 2024.10.17 兼容最近的b站前端改动
|
||||
if (reco_list == null) {
|
||||
reco_list = dq('.recommend-list-v1')
|
||||
}
|
||||
let recoList: RecommendList = getVue2Data(reco_list)
|
||||
if (recoList.isOpen === undefined) {
|
||||
recoList = recoList.$children[0]
|
||||
if (recoList.isOpen === undefined) {
|
||||
console.log('结构获取失败')
|
||||
console.log(document.URL)
|
||||
console.log(recoList)
|
||||
}
|
||||
}
|
||||
return recoList
|
||||
return defaultVideo // 默认使用 DefaultVideo
|
||||
}
|
||||
|
||||
const videoClasses = ['video-page-operator-card-small', 'video-page-card-small']
|
||||
|
||||
addComponentListener(
|
||||
`${metadata.name}.formatString`,
|
||||
(value: string) => {
|
||||
const recoList: RecommendList = getRecoList()
|
||||
const relist: VideoPageCard[] = recoList.$children.filter(video =>
|
||||
videoClasses.includes(video.$el.className),
|
||||
)
|
||||
showUploadTime(relist, true, value)
|
||||
(value: string, oldValue: string) => {
|
||||
const video = getMatchVideo()
|
||||
if (video) {
|
||||
video.settingChange(metadata, value, oldValue)
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
@ -181,22 +72,9 @@ export const component = defineComponentMetadata({
|
||||
urlChange(async () => {
|
||||
console.debug('urlChange now url is', document.URL)
|
||||
await playerReady()
|
||||
const recoList: RecommendList = getRecoList()
|
||||
console.debug('urlChange recoList.mark', recoList.mark)
|
||||
if (!recoList.mark) {
|
||||
recoList.mark = true
|
||||
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更,如果变更则更新
|
||||
recoList.$watch(
|
||||
'recListItems',
|
||||
() => {
|
||||
console.debug('recoListItems changed, now url is', document.URL)
|
||||
const relist = recoList.$children.filter(video =>
|
||||
videoClasses.includes(video.$el.className),
|
||||
)
|
||||
showUploadTime(relist)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
const video = getMatchVideo()
|
||||
if (video) {
|
||||
video.urlChange(metadata)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
import Vue from 'vue'
|
||||
import { ComponentMetadata } from '@/components/types'
|
||||
import { getFormatStr, Video } from './video'
|
||||
import { getVue2Data } from '@/core/utils'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { VideoInfo } from '@/components/video/video-info'
|
||||
|
||||
interface RecommendListUgc extends Vue {
|
||||
isFolded: boolean
|
||||
recLimit: number
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
$children: VideoCard[]
|
||||
}
|
||||
|
||||
interface VideoCard extends Vue {
|
||||
cardIndex: number
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
info: {
|
||||
aid: string
|
||||
title: string
|
||||
pubdate: number
|
||||
owner: {
|
||||
// 组件添加元素,非b站自有元素
|
||||
mark: boolean
|
||||
oldname: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
isModern: boolean
|
||||
isLazyloaded: boolean
|
||||
isMounted: boolean
|
||||
}
|
||||
|
||||
export class MediaListVideo implements Video {
|
||||
console: any
|
||||
metadata: ComponentMetadata
|
||||
|
||||
constructor(console: any) {
|
||||
this.console = console
|
||||
}
|
||||
|
||||
readonly videoClasses = ['recommend-video-card video-card']
|
||||
|
||||
readonly showUploadTime = (relist: VideoCard[], forceUpdate = false, formatString = '') => {
|
||||
if (!formatString) {
|
||||
const { options } = getComponentSettings(this.metadata.name)
|
||||
formatString = options.formatString?.toString()
|
||||
}
|
||||
relist.forEach(async video => {
|
||||
// 使用临时变量保存视频名称,以避免计算属性导致的问题
|
||||
let videoName: string = video.info.owner.name
|
||||
// 确认存放推荐视频列表的List中的元素是否被更新
|
||||
if (forceUpdate || !video.info.owner.mark) {
|
||||
video.info.owner.mark = true
|
||||
// 确认推荐视频卡片是否被更新
|
||||
if (forceUpdate || !video.mark) {
|
||||
video.mark = true
|
||||
if (!video.info.pubdate) {
|
||||
const info = new VideoInfo(video.info.aid)
|
||||
await info.fetchInfo()
|
||||
// 保存查询到的pubdate,以便后续使用
|
||||
video.info.pubdate = info.pubdate
|
||||
}
|
||||
const createTime: Date = new Date(video.info.pubdate * 1000)
|
||||
if (!video.info.owner.oldname) {
|
||||
video.info.owner.oldname = video.info.owner.name
|
||||
}
|
||||
videoName = getFormatStr(createTime, formatString, video.info.owner.oldname)
|
||||
}
|
||||
// 保存生成后的name
|
||||
video.info.owner.name = videoName
|
||||
}
|
||||
})
|
||||
}
|
||||
readonly getRecoList = () => {
|
||||
const reco_list = dq('.recommend-list-container')
|
||||
const recoList: RecommendListUgc = getVue2Data(reco_list)
|
||||
if (recoList.isFolded === undefined) {
|
||||
this.console.log('结构获取失败')
|
||||
this.console.log(document.URL)
|
||||
this.console.log(recoList)
|
||||
}
|
||||
return recoList
|
||||
}
|
||||
|
||||
settingChange(metadata: ComponentMetadata, setting: string): void {
|
||||
this.metadata = metadata
|
||||
const recoList: RecommendListUgc = this.getRecoList()
|
||||
const relist: VideoCard[] = recoList.$children.filter(video =>
|
||||
this.videoClasses.includes(video.$el.className),
|
||||
)
|
||||
this.showUploadTime(relist, true, setting)
|
||||
}
|
||||
|
||||
urlChange(metadata: ComponentMetadata): void {
|
||||
this.metadata = metadata
|
||||
const recoList: RecommendListUgc = this.getRecoList()
|
||||
this.console.debug('urlChange recoList.mark', recoList.mark)
|
||||
if (!recoList.mark) {
|
||||
recoList.mark = true
|
||||
// 使用vue组件自带的$watch方法监视推荐列表信息是否变更,如果变更则更新
|
||||
recoList.$watch(
|
||||
'visibleRelated',
|
||||
() => {
|
||||
this.console.debug('visibleRelated changed, now url is', document.URL)
|
||||
const relist = recoList.$children.filter(video =>
|
||||
this.videoClasses.includes(video.$el.className),
|
||||
)
|
||||
this.showUploadTime(relist)
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,3 +20,11 @@
|
||||
white-space: pre !important;
|
||||
}
|
||||
}
|
||||
// 稍后再看 css
|
||||
.recommend-video-card .card-box .info .upname {
|
||||
height: auto !important;
|
||||
.name {
|
||||
-webkit-line-clamp: unset !important;
|
||||
white-space: pre !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
import { ComponentMetadata } from '@/components/types'
|
||||
|
||||
/**
|
||||
* 视频播放器变化接口
|
||||
* 提供视频播放器设置变更和URL变更时的处理方法
|
||||
*/
|
||||
export interface Video {
|
||||
console: any
|
||||
metadata: ComponentMetadata
|
||||
|
||||
/**
|
||||
* 处理设置变更事件
|
||||
* @param metadata 组件元数据
|
||||
* @param setting 变更的设置值
|
||||
* @param oldSetting 变更前的设置值(可选)
|
||||
*/
|
||||
settingChange(metadata: ComponentMetadata, setting: string, oldSetting?: string): void
|
||||
|
||||
/**
|
||||
* 处理URL变更事件
|
||||
* @param metadata 组件元数据
|
||||
*/
|
||||
urlChange(metadata: ComponentMetadata): void
|
||||
}
|
||||
|
||||
export const getFormatStr = (time: Date, format: string, upName: string) => {
|
||||
const formatMap = {
|
||||
'M+': time.getMonth() + 1, // 月
|
||||
'd+': time.getDate(), // 日
|
||||
'h+': time.getHours(), // 时
|
||||
'm+': time.getMinutes(), // 分
|
||||
's+': time.getSeconds(), // 秒
|
||||
'q+': Math.floor((time.getMonth() + 3) / 3), // 季度
|
||||
}
|
||||
const constMap = {
|
||||
up: upName, // up名
|
||||
'\\\\r': '\r', // 回车符
|
||||
'\\\\n': '\n', // 换行符
|
||||
'\\\\t': '\t', // 制表符
|
||||
}
|
||||
// 处理年份
|
||||
let matchResult: RegExpMatchArray | null = format.match(/(y+)/)
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(
|
||||
matchResult[0],
|
||||
`${time.getFullYear()}`.substring(4 - matchResult[0].length),
|
||||
)
|
||||
}
|
||||
// 处理除年份外的时间
|
||||
for (const key in formatMap) {
|
||||
if (!key) {
|
||||
continue
|
||||
}
|
||||
matchResult = format.match(new RegExp(`(${key})`))
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(
|
||||
matchResult[0],
|
||||
matchResult[0].length === 1
|
||||
? formatMap[key]
|
||||
: `00${formatMap[key]}`.substring(`${formatMap[key]}`.length),
|
||||
)
|
||||
}
|
||||
}
|
||||
// 处理自定义替换文本
|
||||
for (const key in constMap) {
|
||||
if (!key) {
|
||||
continue
|
||||
}
|
||||
matchResult = format.match(new RegExp(`(${key})`))
|
||||
if (matchResult !== null) {
|
||||
format = format.replace(matchResult[0], constMap[key])
|
||||
}
|
||||
}
|
||||
return format
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user