feat: SubscribedSeries page

This commit is contained in:
Hakadao
2023-12-03 17:36:13 +08:00
parent 4ca1486973
commit df9d53bbe1
6 changed files with 497 additions and 31 deletions

View File

@@ -53,7 +53,7 @@ function handleMessage(message: any) {
.catch(error => console.error(error))
}
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/17b7cb85cef19d7f2e94f8d896e68413f6217e26/docs/dynamic/all.md#%E8%8E%B7%E5%8F%96%E5%8A%A8%E6%80%81%E5%88%97%E8%A1%A8
// https://socialsisteryi.github.io/bilibili-API-collect/docs/dynamic/all.html#%E8%8E%B7%E5%8F%96%E5%8A%A8%E6%80%81%E5%88%97%E8%A1%A8
else if (message.contentScriptQuery === 'getMoments') {
const url = `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=-480&type=${message.type}&offset=${message.offset}&update_baseline=${message.updateBaseline}`
return fetch(url)

View File

@@ -2,7 +2,7 @@
import { getCSRF, removeHttpFromUrl } from '~/utils/main'
import { calcCurrentTime, calcTimeSince, numFormatter } from '~/utils/dataFormatter'
const props = defineProps<{
interface Props {
id: number
duration?: number
durationStr?: string
@@ -20,14 +20,27 @@ const props = defineProps<{
capsuleText?: string
bvid?: string
aid?: number
epid?: number
isFollowed?: boolean
horizontal?: boolean
tag?: string
rank?: number
}>()
topRightContent?: boolean
}
const props = withDefaults(defineProps<Props>(),
{
topRightContent: true,
},
)
const videoUrl = computed(() => {
return `https://www.bilibili.com/video/${props.bvid ?? `av${props.aid}`}`
if (props.bvid || props.aid)
return `https://www.bilibili.com/video/${props.bvid ?? `av${props.aid}`}`
else if (props.epid)
return `https://www.bilibili.com/bangumi/play/ep${props.epid}`
else
return ''
})
const isDislike = ref<boolean>(false)
@@ -197,6 +210,7 @@ function handelMouseLeave() {
</div>
<button
v-if="topRightContent"
pos="absolute top-0 right-0" z="2"
p="x-2 y-1" m="1"
rounded="$bew-radius"
@@ -230,7 +244,7 @@ function handelMouseLeave() {
<div
:style="{
width: horizontal ? '100%' : 'unset',
marginTop: horizontal ? '0' : '1rem'
marginTop: horizontal ? '0' : '1rem',
}"
flex="~"
>

View File

@@ -4,7 +4,7 @@ import ForYou from './components/ForYou.vue'
import Following from './components/Following.vue'
import Trending from './components/Trending.vue'
import Ranking from './components/Ranking.vue'
import Pgc from './components/Pgc.vue'
import SubscribedSeries from './components/SubscribedSeries.vue'
import type { HomeTab } from './types'
import { HomeSubPage } from './types'
import emitter from '~/utils/mitt'
@@ -16,7 +16,7 @@ const handleBackToTop = inject('handleBackToTop') as (targetScrollTop: number) =
const recommendContentKey = ref<string>(`recommendContent${Number(new Date())}`)
const activatedPage = ref<HomeSubPage>(HomeSubPage.ForYou)
const pages = { ForYou, Following, Pgc, Trending, Ranking }
const pages = { ForYou, Following, SubscribedSeries, Trending, Ranking }
const showSearchPageMode = ref<boolean>(false)
const shouldMoveTabsUp = ref<boolean>(false)
@@ -31,8 +31,8 @@ const tabs = computed((): HomeTab[] => {
value: HomeSubPage.Following,
},
{
label: 'Following Anime, Shows & Movies',
value: HomeSubPage.Pgc,
label: 'Subscribed Series',
value: HomeSubPage.SubscribedSeries,
},
{
label: t('home.trending'),

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { Ref } from 'vue'
import type { MomentModel } from '../types'
import type { DataItem as MomentItem, MomentResultModel } from '~/models/moment/momentResult'
import emitter from '~/utils/mitt'
const videoList = reactive<MomentModel[]>([])
const momentList = reactive<MomentItem[]>([])
const isLoading = ref<boolean>(false)
const needToLoginFirst = ref<boolean>(false)
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
@@ -34,7 +34,7 @@ async function getFollowedUsersVideos() {
isLoading.value = true
try {
const response = await browser.runtime.sendMessage({
const response: MomentResultModel = await browser.runtime.sendMessage({
contentScriptQuery: 'getMoments',
type: 'pgc',
offset: offset.value,
@@ -51,19 +51,19 @@ async function getFollowedUsersVideos() {
offset.value = response.data.offset
updateBaseline.value = response.data.update_baseline
const resData = [] as MomentModel[]
const resData = [] as MomentItem[]
response.data.items.forEach((item: MomentModel) => {
response.data.items.forEach((item: MomentItem) => {
resData.push(item)
})
// when videoList has length property, it means it is the first time to load
if (!videoList.length) {
Object.assign(videoList, resData)
if (!momentList.length) {
Object.assign(momentList, resData)
}
else {
// else we concat the new data to the old data
Object.assign(videoList, videoList.concat(resData))
Object.assign(momentList, momentList.concat(resData))
}
}
else if (response.code === -101) {
@@ -94,19 +94,19 @@ function jumpToLoginPage() {
grid="~ 2xl:cols-5 xl:cols-4 lg:cols-3 md:cols-2 gap-5"
>
<VideoCard
v-for="video in videoList"
:id="Number(video.modules.module_dynamic.major.archive.aid)"
:key="video.modules.module_dynamic.major.archive.aid"
:duration-str="video.modules.module_dynamic.major.archive.duration_text"
:title="video.modules.module_dynamic.major.archive.title"
:cover="video.modules.module_dynamic.major.archive.cover"
:author="video.modules.module_author.name"
:author-face="video.modules.module_author.face"
:mid="video.modules.module_author.mid"
:view-str="video.modules.module_dynamic.major.archive.stat.play"
:danmaku-str="video.modules.module_dynamic.major.archive.stat.danmaku"
:capsule-text="video.modules.module_author.pub_time"
:bvid="video.modules.module_dynamic.major.archive.bvid"
v-for="moment in momentList"
:id="moment.modules.module_author.mid"
:key="moment.modules.module_author.mid"
:top-right-content="false"
:title="`${moment.modules.module_dynamic.major.pgc?.title}`"
:cover="`${moment.modules.module_dynamic.major.pgc?.cover}`"
:author="moment.modules.module_author.name"
:author-face="moment.modules.module_author.face"
:mid="moment.modules.module_author.mid"
:view-str="moment.modules.module_dynamic.major.pgc?.stat.play"
:danmaku-str="moment.modules.module_dynamic.major.pgc?.stat.danmaku"
:capsule-text="moment.modules.module_author.pub_time"
:epid="moment.modules.module_dynamic.major.pgc?.epid"
/>
<!-- skeleton -->

View File

@@ -6,7 +6,7 @@ export interface HomeTab {
export enum HomeSubPage {
ForYou = 'ForYou',
Following = 'Following',
Pgc = 'Pgc',
SubscribedSeries = 'SubscribedSeries',
Trending = 'Trending',
Ranking = 'Ranking',
}

View File

@@ -0,0 +1,452 @@
// https://app.quicktype.io/?l=ts
export interface MomentResultModel {
code: number
message: string
ttl: number
data: Data
}
export interface Data {
has_more: boolean
items: DataItem[]
offset: string
update_baseline: string
update_num: number
}
export interface DataItem {
basic: Basic
id_str: string
modules: Modules
type: ItemType
visible: boolean
}
export interface Basic {
comment_id_str: string
comment_type: number
like_icon: LikeIcon
rid_str: string
}
export interface LikeIcon {
action_url: string
end_url: string
id: number
start_url: string
}
export interface Modules {
module_author: ModuleAuthor
module_dynamic: ModuleDynamic
module_more: ModuleMore
module_stat: ModuleStat
module_interaction?: ModuleInteraction
}
export interface ModuleAuthor {
avatar?: Avatar
face: string
face_nft: boolean
following: boolean
jump_url: string
label: ModuleAuthorLabel
mid: number
name: string
official_verify?: OfficialVerify
pendant?: Pendant
pub_action: PubAction
pub_location_text?: string
pub_time: string
pub_ts: number
type: ModuleAuthorType
vip?: Vip
decorate?: Decorate
}
export interface Avatar {
container_size: ContainerSize
fallback_layers: FallbackLayers
mid: string
layers?: AvatarLayer[]
}
export interface ContainerSize {
height: number
width: number
}
export interface FallbackLayers {
is_critical_group: boolean
layers: FallbackLayersLayer[]
}
export interface FallbackLayersLayer {
general_spec: GeneralSpec
layer_config: LayerConfig
resource: PurpleResource
visible: boolean
}
export interface GeneralSpec {
pos_spec: PosSpec
render_spec: RenderSpec
size_spec: ContainerSize
}
export interface PosSpec {
axis_x: number
axis_y: number
coordinate_pos: number
}
export interface RenderSpec {
opacity: number
}
export interface LayerConfig {
is_critical?: boolean
tags: Tags
}
export interface Tags {
AVATAR_LAYER?: Layer
GENERAL_CFG?: GeneralCFG
ICON_LAYER?: Layer
PENDENT_LAYER?: Layer
}
export interface Layer {
}
export interface GeneralCFG {
config_type: number
general_config: GeneralConfig
}
export interface GeneralConfig {
web_css_style: WebCSSStyle
}
export interface WebCSSStyle {
borderRadius: BorderRadius
'background-color'?: BackgroundColor
border?: Border
boxSizing?: BoxSizing
}
export enum BackgroundColor {
RGB255255255 = 'rgb(255,255,255)',
}
export enum Border {
The2PxSolidRGBA2552552551 = '2px solid rgba(255,255,255,1)',
}
export enum BorderRadius {
The50 = '50%',
}
export enum BoxSizing {
BorderBox = 'border-box',
}
export interface PurpleResource {
res_image: ResImage
res_type: number
}
export interface ResImage {
image_src: ImageSrc
}
export interface ImageSrc {
placeholder?: number
remote?: Remote
src_type: number
local?: number
}
export interface Remote {
bfs_style: BFSStyle
url: string
}
export enum BFSStyle {
WidgetLayerAvatar = 'widget-layer-avatar',
}
export interface AvatarLayer {
is_critical_group?: boolean
layers: LayerLayer[]
}
export interface LayerLayer {
general_spec: GeneralSpec
layer_config: LayerConfig
resource: FluffyResource
visible: boolean
}
export interface FluffyResource {
res_image?: ResImage
res_type: number
res_animation?: ResAnimation
}
export interface ResAnimation {
webp_src: WebpSrc
}
export interface WebpSrc {
remote: Remote
src_type: number
}
export interface Decorate {
card_url: string
fan: Fan
id: number
jump_url: string
name: string
type: number
}
export interface Fan {
color: string
is_fan: boolean
num_str: string
number: number
}
export enum ModuleAuthorLabel {
Empty = '',
= '番剧',
}
export interface OfficialVerify {
desc: string
type: number
}
export interface Pendant {
expire: number
image: string
image_enhance: string
image_enhance_frame: string
n_pid: number
name: Name
pid: number
}
export enum Name {
Empty = '',
EveOneCat2 = 'EveOneCat2',
}
export enum PubAction {
稿 = '投稿了视频',
= '更新了',
}
export enum ModuleAuthorType {
AuthorTypeNormal = 'AUTHOR_TYPE_NORMAL',
AuthorTypePgc = 'AUTHOR_TYPE_PGC',
}
export interface Vip {
avatar_subscript: number
avatar_subscript_url: string
due_date: number
label: LabelClass
nickname_color: Color
status: number
theme_type: number
type: number
}
export interface LabelClass {
bg_color: Color
bg_style: number
border_color: string
img_label_uri_hans: string
img_label_uri_hans_static: string
img_label_uri_hant: string
img_label_uri_hant_static: string
label_theme: LabelTheme
path: string
text: LabelText
text_color: TextColorEnum
use_img_label: boolean
}
export enum Color {
Empty = '',
Fb7299 = '#FB7299',
}
export enum LabelTheme {
AnnualVip = 'annual_vip',
Empty = '',
TenAnnualVip = 'ten_annual_vip',
}
export enum LabelText {
Empty = '',
= '十年大会员',
= '年度大会员',
}
export enum TextColorEnum {
Empty = '',
Ffffff = '#FFFFFF',
}
export interface ModuleDynamic {
additional: null
desc: ModuleDynamicDesc | null
major: Major
topic: Topic | null
}
export interface ModuleDynamicDesc {
rich_text_nodes: PurpleRichTextNode[]
text: string
}
export interface PurpleRichTextNode {
orig_text: string
text: string
type: string
}
export interface Major {
archive?: Archive
type: MajorType
pgc?: Pgc
}
export interface Archive {
aid: string
badge: Badge
bvid: string
cover: string
desc: string
disable_preview: number
duration_text: string
jump_url: string
stat: Stat
title: string
type: number
}
export interface Badge {
bg_color: Color
color: TextColorEnum
icon_url?: null
text: BadgeText
}
export enum BadgeText {
稿 = '投稿视频',
= '番剧',
}
export interface Stat {
danmaku: string
play: string
}
export interface Pgc {
badge: Badge
cover: string
epid: number
jump_url: string
season_id: number
stat: Stat
sub_type: number
title: string
type: number
}
export enum MajorType {
MajorTypeArchive = 'MAJOR_TYPE_ARCHIVE',
MajorTypePgc = 'MAJOR_TYPE_PGC',
}
export interface Topic {
id: number
jump_url: string
name: string
}
export interface ModuleInteraction {
items: ModuleInteractionItem[]
}
export interface ModuleInteractionItem {
desc: ItemDesc
type: number
}
export interface ItemDesc {
rich_text_nodes: FluffyRichTextNode[]
text: string
}
export interface FluffyRichTextNode {
orig_text: string
rid?: string
text: string
type: string
emoji?: Emoji
}
export interface Emoji {
icon_url: string
size: number
text: string
type: number
}
export interface ModuleMore {
three_point_items: ThreePointItem[]
}
export interface ThreePointItem {
label: ThreePointItemLabel
type: ThreePointItemType
}
export enum ThreePointItemLabel {
= '举报',
= '取消关注',
}
export enum ThreePointItemType {
ThreePointFollowing = 'THREE_POINT_FOLLOWING',
ThreePointReport = 'THREE_POINT_REPORT',
}
export interface ModuleStat {
comment: Comment
forward: Comment
like: Like
}
export interface Comment {
count: number
forbidden: boolean
}
export interface Like {
count: number
forbidden: boolean
status: boolean
}
export enum ItemType {
DynamicTypeAV = 'DYNAMIC_TYPE_AV',
DynamicTypePgcUnion = 'DYNAMIC_TYPE_PGC_UNION',
}