Merge branch 'preview-features' of https://github.com/the1812/Bilibili-Evolved into preview

This commit is contained in:
the1812 2023-09-07 00:02:34 +08:00
commit 8486b3dc4f
14 changed files with 170 additions and 119 deletions

View File

@ -48,10 +48,6 @@
"name": "Build features", "name": "Build features",
"run": "cd registry\npnpm install\ncd ../\npnpm run build-features\n" "run": "cd registry\npnpm install\ncd ../\npnpm run build-features\n"
}, },
{
"name": "Build GitHub config",
"run": "pnpm run build-github-config"
},
{ {
"name": "Git commit", "name": "Git commit",
"id": "commit", "id": "commit",

View File

@ -36,8 +36,6 @@ jobs:
pnpm install pnpm install
cd ../ cd ../
pnpm run build-features pnpm run build-features
- name: Build GitHub config
run: pnpm run build-github-config
- name: Git commit - name: Git commit
id: commit id: commit
run: | run: |

View File

@ -10,7 +10,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { TabControl, VIcon } from '@/ui' import { TabControl, VIcon } from '@/ui'
import { feedsCardTypes } from '@/components/feeds/api' import { FeedsCardType, feedsCardTypes } from '@/components/feeds/api'
import { getNotifyCount } from '@/components/feeds/notify' import { getNotifyCount } from '@/components/feeds/notify'
import { popperMixin } from '../mixins' import { popperMixin } from '../mixins'
import { tabs } from './tabs/tabs' import { tabs } from './tabs/tabs'
@ -45,7 +45,11 @@ export default Vue.extend({
if (tab.name === 'live') { if (tab.name === 'live') {
return return
} }
const count = await getNotifyCount(feedsCardTypes[tab.name].id.toString()) const feedsCardType = feedsCardTypes[tab.name] as FeedsCardType
if (!feedsCardType.apiType) {
return
}
const count = await getNotifyCount(feedsCardType.apiType)
tab.count = count tab.count = count
console.log(tab) console.log(tab)
}) })

View File

@ -23,14 +23,15 @@ export default Vue.extend({
}, },
mixins: [ mixins: [
nextPageMixin(feedsCardTypes.bangumi, (card: any) => { nextPageMixin(feedsCardTypes.bangumi, (card: any) => {
const cardJson = JSON.parse(card.card) const pgc = lodash.get(card, 'modules.module_dynamic.major.pgc')
const author = lodash.get(card, 'modules.module_author')
return { return {
id: card.desc.dynamic_id_str, id: card.id_str,
title: cardJson.apiSeasonInfo.title, title: author.name,
coverUrl: cardJson.apiSeasonInfo.cover, coverUrl: author.face,
epCoverUrl: cardJson.cover, epCoverUrl: pgc.cover,
epTitle: cardJson.new_desc, epTitle: pgc.title.replace(new RegExp(`^${author.name}`), ''),
url: cardJson.url, url: pgc.jump_url,
get new() { get new() {
return isNewID(this.id) return isNewID(this.id)
}, },

View File

@ -23,17 +23,18 @@ export default Vue.extend({
}, },
mixins: [ mixins: [
nextPageMixin(feedsCardTypes.column, (card: any) => { nextPageMixin(feedsCardTypes.column, (card: any) => {
const cardJson = JSON.parse(card.card) const article = lodash.get(card, 'modules.module_dynamic.major.article')
const author = lodash.get(card, 'modules.module_author')
return { return {
id: card.desc.dynamic_id_str, id: card.id_str,
cvID: cardJson.id, cvID: article.id.toString(),
title: cardJson.title, title: article.title,
upName: cardJson.author.name, upName: author.name,
upFaceUrl: cardJson.author.face, upFaceUrl: author.face,
upID: cardJson.author.mid, upID: author.mid,
description: cardJson.summary, description: article.desc,
covers: cardJson.image_urls, covers: article.covers,
originalCovers: cardJson.origin_image_urls, originalCovers: article.covers,
get new() { get new() {
return isNewID(this.id) return isNewID(this.id)
}, },

View File

@ -31,7 +31,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { VideoCard } from '@/components/feeds/video-card' import { VideoCard } from '@/components/feeds/video-card'
import { formatDuration, formatCount } from '@/core/utils/formatters' import { formatDuration, formatCount, parseDuration } from '@/core/utils/formatters'
import { isNewID } from '@/components/feeds/notify' import { isNewID } from '@/components/feeds/notify'
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api' import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
import VideoCardComponent from '@/components/feeds/VideoCard.vue' import VideoCardComponent from '@/components/feeds/VideoCard.vue'
@ -80,24 +80,25 @@ export default Vue.extend({
}, },
mixins: [ mixins: [
nextPageMixin(feedsCardTypes.video, (card: any) => { nextPageMixin(feedsCardTypes.video, (card: any) => {
const cardJson = JSON.parse(card.card) const archive = lodash.get(card, 'modules.module_dynamic.major.archive')
const author = lodash.get(card, 'modules.module_author')
return { return {
id: card.desc.dynamic_id_str, id: card.id_str,
aid: cardJson.aid, aid: archive.aid,
bvid: card.desc.bvid, bvid: archive.bvid,
videoUrl: `https://www.bilibili.com/${card.desc.bvid}`, videoUrl: `https://www.bilibili.com/${archive.bvid}`,
coverUrl: cardJson.pic, coverUrl: archive.cover,
title: cardJson.title, title: archive.title,
duration: cardJson.duration, duration: parseDuration(archive.duration_text),
durationText: formatDuration(cardJson.duration), durationText: formatDuration(parseDuration(archive.duration_text)),
description: cardJson.desc, description: archive.desc,
pubTime: formatPubTime(cardJson.pubdate * 1000), pubTime: formatPubTime(author.pub_ts * 1000),
pubTimeText: formatPubTimeText(cardJson.pubdate * 1000), pubTimeText: formatPubTimeText(author.pub_ts * 1000),
upFaceUrl: card.desc.user_profile.info.face, upFaceUrl: author.face,
upName: card.desc.user_profile.info.uname, upName: author.name,
upID: card.desc.user_profile.info.uid, upID: author.mid,
watchlater: true, watchlater: true,
playCount: formatCount(cardJson.stat.view), playCount: formatCount(archive.stat.play),
get new() { get new() {
return isNewID(this.id) return isNewID(this.id)
}, },

View File

@ -4,7 +4,7 @@ import {
applyContentFilter, applyContentFilter,
isPreOrderedVideo, isPreOrderedVideo,
} from '@/components/feeds/api' } from '@/components/feeds/api'
import { descendingStringSort } from '@/core/utils/sort' import { descendingBigIntSort } from '@/core/utils/sort'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { setLatestID } from '@/components/feeds/notify' import { setLatestID } from '@/components/feeds/notify'
import { VLoading, VEmpty, ScrollTrigger } from '@/ui' import { VLoading, VEmpty, ScrollTrigger } from '@/ui'
@ -33,7 +33,7 @@ export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
}, },
computed: { computed: {
sortedCards() { sortedCards() {
return ([...this.cards] as MappedItem[]).sort(descendingStringSort(it => it.id)) return ([...this.cards] as MappedItem[]).sort(descendingBigIntSort(it => it.id))
}, },
}, },
async created() { async created() {
@ -56,12 +56,12 @@ export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
this.hasMorePage = false this.hasMorePage = false
throw new Error(json.message) throw new Error(json.message)
} }
const jsonCards = lodash.get(json, 'data.cards', []).map(jsonMapper) as MappedItem[] const jsonCards = lodash.get(json, 'data.items', []).map(jsonMapper) as MappedItem[]
let concatCards = applyContentFilter( let concatCards = applyContentFilter(
cards cards
.concat(jsonCards) .concat(jsonCards)
.sort(descendingStringSort(it => it.id)) .sort(descendingBigIntSort(it => it.id))
.filter(card => !isPreOrderedVideo(card)), .filter(card => !isPreOrderedVideo(card)),
) )

View File

@ -38,7 +38,7 @@
:title="topic.name" :title="topic.name"
class="topic" class="topic"
target="_blank" target="_blank"
:href="'https://t.bilibili.com/topic/name/' + topic.name + '/feed'" :href="topic.url || 'https://t.bilibili.com/topic/name/' + topic.name + '/feed'"
> >
<VIcon icon="mdi-tag-outline" :size="14" /> <VIcon icon="mdi-tag-outline" :size="14" />
<div class="topic-name"> <div class="topic-name">

View File

@ -1,6 +1,6 @@
import { getUID, pascalCase } from '@/core/utils' import { getUID, pascalCase } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { formatCount, formatDuration } from '@/core/utils/formatters' import { formatCount, formatDuration, parseDuration } from '@/core/utils/formatters'
import { watchlaterList } from '@/components/video/watchlater' import { watchlaterList } from '@/components/video/watchlater'
import { getData, registerData } from '@/plugins/data' import { getData, registerData } from '@/plugins/data'
import { descendingStringSort } from '@/core/utils/sort' import { descendingStringSort } from '@/core/utils/sort'
@ -66,15 +66,19 @@ export const withContentFilter =
* @param afterID ID之前的动态历史, * @param afterID ID之前的动态历史,
*/ */
export const getFeedsUrl = (type: FeedsCardType | string, afterID?: string | number) => { export const getFeedsUrl = (type: FeedsCardType | string, afterID?: string | number) => {
const params = new URLSearchParams()
if (typeof type === 'string') { if (typeof type === 'string') {
return `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type}` params.set('type', type)
} else if (type.apiType) {
params.set('type', type.apiType)
} else {
console.warn(`unknown apiType for ${type.name}`)
params.set('type', 'all')
} }
const id = type.id.toString()
let api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${id}`
if (afterID) { if (afterID) {
api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid=${getUID()}&offset_dynamic_id=${afterID}&type=${id}` params.set('offset', afterID.toString())
} }
return api return `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?${params.toString()}`
} }
/** /**
* *
@ -100,59 +104,68 @@ export const getVideoFeeds = withContentFilter(
if (json.code !== 0) { if (json.code !== 0) {
throw new Error(json.message) throw new Error(json.message)
} }
const dataCards = json.data.cards as any[] const dataCards = json.data.items as any[]
const dataCardsWithoutPreOrder = dataCards.filter(it => !isPreOrderedVideo(JSON.parse(it.card)))
if (type === 'video') { if (type === 'video') {
return groupVideoFeeds( return groupVideoFeeds(
dataCards.map((c: any): VideoCard => { dataCards.map((card: any): VideoCard => {
const card = JSON.parse(c.card) const archive = lodash.get(card, 'modules.module_dynamic.major.archive')
const topics = lodash.get(c, 'display.topic_info.topic_details', []).map((it: any) => ({ const author = lodash.get(card, 'modules.module_author')
id: it.topic_id, const dynamic = lodash.get(card, 'modules.module_dynamic.desc.text', '')
name: it.topic_name, const stat = lodash.get(card, 'modules.module_stat')
})) const topics = (
lodash.get(card, 'modules.module_dynamic.desc.rich_text_nodes', []) as any[]
)
.filter(node => node.type === 'RICH_TEXT_NODE_TYPE_TOPIC')
.map(node => {
return { return {
id: c.desc.dynamic_id_str, id: node.text,
aid: card.aid, name: node.text,
bvid: c.desc.bvid || card.bvid, url: node.jump_url,
title: card.title, }
upID: c.desc.user_profile.info.uid, })
upName: c.desc.user_profile.info.uname, return {
upFaceUrl: c.desc.user_profile.info.face, id: card.id_str,
coverUrl: card.pic, aid: archive.aid,
description: card.desc, bvid: archive.bvid,
timestamp: c.timestamp, title: archive.title,
time: new Date(c.timestamp * 1000), upFaceUrl: author.face,
upName: author.name,
upID: author.mid,
coverUrl: archive.cover,
description: archive.desc,
timestamp: author.pub_ts * 1000,
time: new Date(author.pub_ts * 1000),
topics, topics,
dynamic: card.dynamic, dynamic,
like: formatCount(c.desc.like), like: formatCount(stat.like.count),
duration: card.duration, duration: parseDuration(archive.duration_text),
durationText: formatDuration(card.duration, 0), durationText: formatDuration(parseDuration(archive.duration_text)),
playCount: formatCount(card.stat.view), playCount: formatCount(archive.stat.play),
danmakuCount: formatCount(card.stat.danmaku), danmakuCount: formatCount(archive.stat.danmaku),
watchlater: watchlaterList.includes(card.aid), watchlater: watchlaterList.includes(archive.aid),
} }
}), }),
) )
} }
if (type === 'bangumi') { if (type === 'bangumi') {
return dataCardsWithoutPreOrder.map((c: any): VideoCard => { return dataCards.map((card: any): VideoCard => {
const card = JSON.parse(c.card) const pgc = lodash.get(card, 'modules.module_dynamic.major.pgc')
const author = lodash.get(card, 'modules.module_author')
const stat = lodash.get(card, 'modules.module_stat')
return { return {
id: c.desc.dynamic_id_str, id: card.id_str,
aid: card.aid, epID: pgc.epid,
bvid: c.desc.bvid || card.bvid, title: pgc.title.replace(new RegExp(`^${author.name}`), ''),
epID: card.episode_id, upName: author.name,
title: card.new_desc, upFaceUrl: author.face,
upName: card.apiSeasonInfo.title, coverUrl: pgc.cover,
upFaceUrl: card.apiSeasonInfo.cover,
coverUrl: card.cover,
description: '', description: '',
timestamp: c.timestamp, timestamp: author.pub_ts * 1000,
time: new Date(c.timestamp * 1000), time: new Date(author.pub_ts * 1000),
like: formatCount(c.desc.like), like: formatCount(stat.like.count),
durationText: '', durationText: '',
playCount: formatCount(card.play_count), playCount: formatCount(pgc.stat.play),
danmakuCount: formatCount(card.bullet_count), danmakuCount: formatCount(pgc.stat.danmaku),
watchlater: false, watchlater: false,
} }
}) })

View File

@ -4,6 +4,8 @@
export interface FeedsCardType { export interface FeedsCardType {
id: number id: number
name: string name: string
/** 用于新版动态 API 的 type (/x/polymer/web-dynamic/), 各种动态类型对应的 type 还不完善 */
apiType?: string
} }
/** /**
* *
@ -16,68 +18,71 @@ export const feedsCardTypes = {
repost: { repost: {
id: 1, id: 1,
name: '转发', name: '转发',
} as FeedsCardType, },
textWithImages: { textWithImages: {
id: 2, id: 2,
name: '图文', name: '图文',
} as FeedsCardType, },
text: { text: {
id: 4, id: 4,
name: '文字', name: '文字',
} as FeedsCardType, },
video: { video: {
id: 8, id: 8,
name: '视频', name: '视频',
} as FeedsCardType, apiType: 'video',
},
miniVideo: { miniVideo: {
id: 16, id: 16,
name: '小视频', name: '小视频',
} as FeedsCardType, },
column: { column: {
id: 64, id: 64,
name: '专栏', name: '专栏',
} as FeedsCardType, apiType: 'article',
},
audio: { audio: {
id: 256, id: 256,
name: '音频', name: '音频',
} as FeedsCardType, },
bangumi: { bangumi: {
id: 512, id: 512,
name: '番剧', name: '番剧',
} as FeedsCardType, apiType: 'pgc',
},
share: { share: {
id: 2048, id: 2048,
name: '分享', name: '分享',
} as FeedsCardType, },
manga: { manga: {
id: 2049, id: 2049,
name: '漫画', name: '漫画',
} as FeedsCardType, },
film: { film: {
id: 4098, id: 4098,
name: '电影', name: '电影',
} as FeedsCardType, },
tv: { tv: {
id: 4099, id: 4099,
name: 'TV剧', name: 'TV剧',
} as FeedsCardType, },
chinese: { chinese: {
id: 4100, id: 4100,
name: '国创', name: '国创',
} as FeedsCardType, },
documentary: { documentary: {
id: 4101, id: 4101,
name: '纪录片', name: '纪录片',
} as FeedsCardType, },
mediaList: { mediaList: {
id: 4300, id: 4300,
name: '收藏夹', name: '收藏夹',
} as FeedsCardType, },
liveRecord: { liveRecord: {
id: 2047, // FIXME: 暂时随便写个 id 了, 这个东西目前找不到 type id: 2047, // FIXME: 暂时随便写个 id 了, 这个东西目前找不到 type
name: '开播记录', name: '开播记录',
} as FeedsCardType, },
} } satisfies Record<string, FeedsCardType>
/** 是否是转发类型的卡片, 额外能够读取被转发动态的信息 */ /** 是否是转发类型的卡片, 额外能够读取被转发动态的信息 */
export const isRepostType = (card: FeedsCard): card is RepostFeedsCard => export const isRepostType = (card: FeedsCard): card is RepostFeedsCard =>
card.type === feedsCardTypes.repost card.type === feedsCardTypes.repost

View File

@ -1,6 +1,5 @@
import { getCookieValue, getUID } from '@/core/utils' import { getCookieValue, getUID } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { navbarFeedsTypeList } from './api'
export const updateInterval = 5 * 60 * 1000 // 每5分钟更新1次动态提醒数字 export const updateInterval = 5 * 60 * 1000 // 每5分钟更新1次动态提醒数字
export const getLatestID = () => getCookieValue(`bp_t_offset_${getUID()}`) export const getLatestID = () => getCookieValue(`bp_t_offset_${getUID()}`)
@ -33,10 +32,8 @@ export const updateLatestID = (cards: { id: string }[]) => {
const [id] = [...cards.map(c => c.id)].sort(compareID).reverse() const [id] = [...cards.map(c => c.id)].sort(compareID).reverse()
setLatestID(id) setLatestID(id)
} }
export const getNotifyCount = async (typeList?: string): Promise<number> => { export const getNotifyCount = async (type = 'all'): Promise<number> => {
const api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_num?rsp_type=1&uid=${getUID()}&update_num_dy_id=${getLatestID()}&type_list=${ const api = `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all/update?type=${type}&update_baseline=${getLatestID()}`
typeList || navbarFeedsTypeList
}`
const json = await getJsonWithCredentials(api) const json = await getJsonWithCredentials(api)
if (json.code !== 0) { if (json.code !== 0) {
return 0 return 0

View File

@ -23,6 +23,7 @@ export interface VideoCard {
topics?: { topics?: {
id: number id: number
name: string name: string
url?: string
}[] }[]
type?: string type?: string
points?: number points?: number

View File

@ -19,9 +19,27 @@ export const formatFileSize = (bytes: number, fixed = 1) => {
*/ */
export const formatPercent = (percent: number, fixed = 1) => export const formatPercent = (percent: number, fixed = 1) =>
`${Math.round(percent * 100 * 10 ** fixed) / 10 ** fixed}%` `${Math.round(percent * 100 * 10 ** fixed) / 10 ** fixed}%`
/** `H:mm:ss.F`, 1`mm:ss.F`
* @param time /**
* @param fixed (), *
* @param durationText
*/
export const parseDuration = (durationText: string) => {
const parts = durationText.split(':')
let result = 0
parts.forEach((part, index) => {
const isLastPart = index === parts.length - 1
const partNumber = isLastPart ? parseFloat(part) : parseInt(part)
if (Number.isNaN(partNumber)) {
return
}
result += partNumber * 60 ** (parts.length - 1 - index)
})
return result
}
/** `H:mm:ss.F`, 1 `mm:ss.F`
* @param time ()
* @param fixed (),
*/ */
export const formatDuration = (time: number, fixed = 0) => { export const formatDuration = (time: number, fixed = 0) => {
const second = (time % 60).toFixed(fixed) const second = (time % 60).toFixed(fixed)

View File

@ -9,6 +9,17 @@ export const ascendingStringSort =
<T>(itemProp: (obj: T) => string = selfSorter) => <T>(itemProp: (obj: T) => string = selfSorter) =>
(a: T, b: T) => (a: T, b: T) =>
itemProp(a).localeCompare(itemProp(b)) itemProp(a).localeCompare(itemProp(b))
/** 字符串大数字升序排序 */
export const ascendingBigIntSort =
<T>(itemProp: (obj: T) => string = selfSorter) =>
(a: T, b: T) => {
const numberA = BigInt(itemProp(a))
const numberB = BigInt(itemProp(b))
if (numberA === numberB) {
return 0
}
return numberA > numberB ? 1 : -1
}
/** 降序排序 */ /** 降序排序 */
export const descendingSort = export const descendingSort =
<T>(itemProp: (obj: T) => number = selfSorter) => <T>(itemProp: (obj: T) => number = selfSorter) =>
@ -19,3 +30,8 @@ export const descendingStringSort =
<T>(itemProp: (obj: T) => string = selfSorter) => <T>(itemProp: (obj: T) => string = selfSorter) =>
(a: T, b: T) => (a: T, b: T) =>
itemProp(b).localeCompare(itemProp(a)) itemProp(b).localeCompare(itemProp(a))
/** 字符串大数字降序排序 */
export const descendingBigIntSort =
<T>(itemProp: (obj: T) => string = selfSorter) =>
(a: T, b: T) =>
-ascendingBigIntSort(itemProp)(a, b)