From 3b58fbf33aa347942def7c9c614c938c5f0f536b Mon Sep 17 00:00:00 2001 From: Hakadao Date: Thu, 18 Apr 2024 15:06:53 +0800 Subject: [PATCH 1/3] fix: resolve duplicate request issue --- src/background/messageListeners/index.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/background/messageListeners/index.ts b/src/background/messageListeners/index.ts index 3be80fc5..584fea54 100644 --- a/src/background/messageListeners/index.ts +++ b/src/background/messageListeners/index.ts @@ -13,16 +13,17 @@ import API_USER from './user' import API_VIDEO from './video' import API_WATCHLATER from './watchLater' +// Merge all API objects into one +const FullAPI = Object.assign({}, API_AUTH, API_ANIME, API_HISTORY, API_FAVORITE, API_MOMENT, API_NOTIFICATION, API_RANKING, API_SEARCH, API_USER, API_VIDEO, API_WATCHLATER) +// Create a message listener for each API +const handleMessage = apiListenerFactory(FullAPI) + export function setupAllMsgLstnrs() { browser.runtime.onConnect.removeListener(handleConnect) browser.runtime.onConnect.addListener(handleConnect) - - function handleConnect() { - // Merge all API objects into one - const FullAPI = Object.assign({}, API_AUTH, API_ANIME, API_HISTORY, API_FAVORITE, API_MOMENT, API_NOTIFICATION, API_RANKING, API_SEARCH, API_USER, API_VIDEO, API_WATCHLATER) - // Create a message listener for each API - const handleMessage = apiListenerFactory(FullAPI) - browser.runtime.onMessage.removeListener(handleMessage) - browser.runtime.onMessage.addListener(handleMessage) - } +} + +function handleConnect() { + browser.runtime.onMessage.removeListener(handleMessage) + browser.runtime.onMessage.addListener(handleMessage) } From 613b45a77b7ba6889555645503a13f79c531eaf5 Mon Sep 17 00:00:00 2001 From: Hakadao Date: Thu, 18 Apr 2024 16:30:15 +0800 Subject: [PATCH 2/3] chore: adjust error message for missing contentScriptQuery in utils.ts --- src/background/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/utils.ts b/src/background/utils.ts index 8196f9d4..5a9679f4 100644 --- a/src/background/utils.ts +++ b/src/background/utils.ts @@ -61,7 +61,7 @@ function apiListenerFactory(API_MAP: APIMAP) { const contentScriptQuery = message.contentScriptQuery // 检测是否有contentScriptQuery if (!contentScriptQuery || !API_MAP[contentScriptQuery]) - return console.error('no contentScriptQuery') + return console.error(`Cannot find this contentScriptQuery: ${contentScriptQuery}`) if (API_MAP[contentScriptQuery] instanceof Function) return (API_MAP[contentScriptQuery] as APIFunction)(message, sender, sendResponse) From 9265e5380dd288efaa12c2a8c69847c12262aa8a Mon Sep 17 00:00:00 2001 From: Hakadao Date: Thu, 18 Apr 2024 16:33:14 +0800 Subject: [PATCH 3/3] refactor: use new top bar moment api --- src/background/messageListeners/moment.ts | 23 +- src/background/msg.define.ts | 5 +- src/components/TopBar/TopBar.vue | 4 +- .../TopBar/components/MomentsPop.vue | 372 ++++++++---------- src/components/TopBar/notify.ts | 43 +- src/components/TopBar/types.ts | 17 - src/models/moment/topBarLiveMoment.ts | 29 ++ src/models/moment/topBarMoment.ts | 108 +++++ 8 files changed, 337 insertions(+), 264 deletions(-) create mode 100644 src/models/moment/topBarLiveMoment.ts create mode 100644 src/models/moment/topBarMoment.ts diff --git a/src/background/messageListeners/moment.ts b/src/background/messageListeners/moment.ts index 05eb1672..1eeda9b7 100644 --- a/src/background/messageListeners/moment.ts +++ b/src/background/messageListeners/moment.ts @@ -10,30 +10,19 @@ const API_MOMENT: APIMAP = { params: {}, afterHandle: AHS.J_D, }, - getTopBarNewMoments: { - url: 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new', + getTopBarMoments: { + url: 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/nav', _fetch: { method: 'get', }, params: { - uid: '', - type_list: '268435455', + type: 'video', + update_baseline: '', + offset: '', }, afterHandle: AHS.J_D, }, - getTopbarHistoryMoments: { - url: 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history', - _fetch: { - method: 'get', - }, - params: { - uid: '', - type_list: '268435455', - offset_dynamic_id: '', - }, - afterHandle: AHS.J_D, - }, - getTopbarLiveMoments: { + getTopBarLiveMoments: { url: 'https://api.live.bilibili.com/xlive/web-ucenter/v1/xfetter/FeedList', _fetch: { method: 'get', diff --git a/src/background/msg.define.ts b/src/background/msg.define.ts index d697cbd8..8dfcf564 100644 --- a/src/background/msg.define.ts +++ b/src/background/msg.define.ts @@ -28,9 +28,8 @@ enum HISTORY { } enum MOMENT { GET_TOP_BAR_NEW_MOMENTS_COUNT = 'getTopBarNewMomentsCount', - GET_TOP_BAR_NEW_MOMENTS = 'getTopBarNewMoments', - GET_TOP_BAR_HISTORY_MOMENTS = 'getTopbarHistoryMoments', - GET_TOP_BAR_LIVE_MOMENTS = 'getTopbarLiveMoments', + GET_TOP_BAR_MOMENTS = 'getTopBarMoments', + GET_TOP_BAR_LIVE_MOMENTS = 'getTopBarLiveMoments', GET_MOMENTS = 'getMoments', } enum NOTIFICATION { diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index 406d8e46..288dc6cb 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -85,7 +85,7 @@ const notifications = useDelayedHover({ const moments = useDelayedHover({ enter: () => { showMomentsPop.value = true - momentsPopRef.value && momentsPopRef.value.initData() + momentsPopRef.value && momentsPopRef.value.checkIfHasNewMomentsThenUpdateMoments() }, leave: () => showMomentsPop.value = false, }) @@ -383,7 +383,7 @@ defineExpose({ import { useI18n } from 'vue-i18n' -import type { Ref } from 'vue' import { onMounted, reactive, ref, watch } from 'vue' -import { isNewArticle, isNewVideo, setLastestOffsetID } from '../notify' -import { MomentType } from '../types' -import type { MomentItem } from '../types' -import { getCSRF, getUserID, isHomePage, smoothScrollToTop } from '~/utils/main' -import { calcTimeSince } from '~/utils/dataFormatter' + +// import { isNewArticle, setLastOffsetID, setLastestOffsetID } from '../notify' + +import type { TopBarMomentResult } from '~/models/moment/topBarMoment' +import type { TopBarLiveMomentResult } from '~/models/moment/topBarLiveMoment' +import { getCSRF, isHomePage, smoothScrollToTop } from '~/utils/main' import API from '~/background/msg.define' +type MomentType = 'video' | 'live' | 'article' +interface MomentTab { type: MomentType, name: any } +interface MomentCard { + type: MomentType + title: string + author: string + authorFace: string + pubTime?: string + cover: string + link: string + rid?: number +} + const { t } = useI18n() -const moments = reactive([]) +const moments = reactive([]) const addedWatchLaterList = reactive([]) -const momentTabs = reactive([ +const momentTabs = reactive([ { - id: 0, + type: 'video', name: t('topbar.moments_dropdown.tabs.videos'), - isSelected: true, }, { - id: 1, + type: 'live', name: t('topbar.moments_dropdown.tabs.live'), - isSelected: false, }, { - id: 2, + type: 'article', name: t('topbar.moments_dropdown.tabs.articles'), - isSelected: false, }, ]) -const selectedTab = ref(0) +const selectedMomentTab = ref(momentTabs[0]) const isLoading = ref(false) -// when noMoreContent is true, the user can't scroll down to load more content -const noMoreContent = ref(false) +const noMoreContent = ref(false) // when noMoreContent is true, the user can't scroll down to load more content const livePage = ref(1) -const momentsWrap = ref() as Ref +const momentsWrap = ref() +const momentUpdateBaseline = ref('') +const momentOffset = ref('') +const newMomentsCount = ref(0) -watch(selectedTab, (newVal, oldVal) => { +watch(() => selectedMomentTab.value.type, (newVal, oldVal) => { if (newVal === oldVal) return @@ -55,205 +67,168 @@ onMounted(() => { >= momentsWrap.value.scrollHeight - 20 && moments.length > 0 && !isLoading.value - ) { - if (selectedTab.value === 0 && !noMoreContent.value) - getTopbarHistoryMoments([MomentType.Video, MomentType.Bangumi]) - else if (selectedTab.value === 1 && !noMoreContent.value) - getTopbarLiveMoments(livePage.value) - else if (selectedTab.value === 2 && !noMoreContent.value) - getTopbarHistoryMoments([MomentType.Article]) - } + ) + getData() }) } }) -function onClickTab(tabId: number) { +function onClickTab(tab: MomentTab) { // Prevent changing tab when loading, cuz it will cause a bug - if (isLoading.value || tabId === selectedTab.value) + if (isLoading.value || tab.type === selectedMomentTab.value.type) return - selectedTab.value = tabId - moments.length = 0 - momentTabs.forEach((tab) => { - tab.isSelected = tab.id === tabId - }) + selectedMomentTab.value = tab + initData() } async function initData() { - if (selectedTab.value === 0) { - await getTopBarNewMoments([MomentType.Video, MomentType.Bangumi]) - } - else if (selectedTab.value === 1) { - livePage.value = 1 - getTopbarLiveMoments(livePage.value) - } - else if (selectedTab.value === 2) { - await getTopBarNewMoments([MomentType.Article]) - } + moments.length = 0 + momentUpdateBaseline.value = '' + momentOffset.value = '' + newMomentsCount.value = 0 + livePage.value = 1 + + getData() } -async function getTopBarNewMoments(type_list: number[]) { - isLoading.value = true - try { - const res = await browser.runtime - .sendMessage({ - contentScriptQuery: API.MOMENT.GET_TOP_BAR_NEW_MOMENTS, - uid: getUserID(), - type_list, - }) - - if (res.code === 0) { - // If there are no new moments, do not change the data to record the scroll position - if (moments.length !== 0) { - if (res.data.new_num === 0) - return - } - - moments.length = 0 - - if (Array.isArray(res.data.cards) && res.data.cards.length > 0) { - res.data.cards.forEach((item: any) => { - pushItemIntoMoments(item) - }) - } - - if (moments.length !== 0 && res.data.cards.length < 20) { - isLoading.value = false - noMoreContent.value = true - return - } - - // set this lastest offset id, which will clear the new moment's marker point - // after you watch these moments. - if (selectedTab.value === 0) - setLastestOffsetID(MomentType.Video, moments[0].id) - else if (selectedTab.value === 2) - setLastestOffsetID(MomentType.Article, moments[0].id) - - noMoreContent.value = false - } - } - finally { - isLoading.value = false - } +function getData() { + if (selectedMomentTab.value.type !== 'live') + getTopBarMoments() + else + getTopBarLiveMoments() } -function getTopbarHistoryMoments(type_list: number[]) { - isLoading.value = true - browser.runtime - .sendMessage({ - contentScriptQuery: API.MOMENT.GET_TOP_BAR_HISTORY_MOMENTS, - uid: getUserID(), - type_list, - offset_dynamic_id: moments[moments.length - 1].dynamic_id_str, - }) - .then((res) => { +function checkIfHasNewMomentsThenUpdateMoments() { + if (selectedMomentTab.value.type === 'live') + return + + browser.runtime.sendMessage({ + contentScriptQuery: API.MOMENT.GET_TOP_BAR_MOMENTS, + type: selectedMomentTab.value.type, + update_baseline: momentUpdateBaseline.value || undefined, + }) + .then((res: TopBarMomentResult) => { if (res.code === 0) { - if (res.data.has_more === 0) { - isLoading.value = false + const { has_more, items, update_baseline, update_num } = res.data + + if (!has_more) { + noMoreContent.value = true + return + } + if (update_num === 0) + return + + for (let i = update_num - 1; i >= 0; i--) { + moments.unshift({ + type: selectedMomentTab.value.type, + title: items[i].title, + author: items[i].author.name, + authorFace: items[i].author.face, + pubTime: items[i].pub_time, + cover: items[i].cover, + link: items[i].jump_url, + rid: items[i].rid, + }) + } + + newMomentsCount.value = update_num + momentUpdateBaseline.value = update_baseline + // newMomentsCount.value = update_num + // setLastOffsetID('video', offset) + } + }) + .finally(() => isLoading.value = false) +} + +function getTopBarMoments() { + if (isLoading.value) + return + isLoading.value = true + browser.runtime.sendMessage({ + contentScriptQuery: API.MOMENT.GET_TOP_BAR_MOMENTS, + type: selectedMomentTab.value.type, + update_baseline: momentUpdateBaseline.value || undefined, + offset: momentOffset.value || undefined, + }) + .then((res: TopBarMomentResult) => { + if (res.code === 0) { + const { has_more, items, offset, update_baseline, update_num } = res.data + + if (!has_more) { noMoreContent.value = true return } - res.data.cards.forEach((item: any) => { - pushItemIntoMoments(item) - }) - noMoreContent.value = false + newMomentsCount.value = update_num + momentUpdateBaseline.value = update_baseline + momentOffset.value = offset + + // set this lastest offset id, which will clear the new moment's marker point + // after you watch these moments. + + // setLastOffsetID('video', offset) + + moments.push( + ...items.map(item => ({ + type: selectedMomentTab.value.type, + title: item.title, + author: item.author.name, + authorFace: item.author.face, + pubTime: item.pub_time, + cover: item.cover, + link: item.jump_url, + rid: item.rid, + }), + ), + ) } - isLoading.value = false }) + .finally(() => isLoading.value = false) } -function getTopbarLiveMoments(page: number) { +function isNewMoment(index: number) { + return index < newMomentsCount.value +} + +function getTopBarLiveMoments() { isLoading.value = true browser.runtime .sendMessage({ contentScriptQuery: API.MOMENT.GET_TOP_BAR_LIVE_MOMENTS, - page, + page: livePage.value, pagesize: 10, }) - .then((res) => { + .then((res: TopBarLiveMomentResult) => { if (res.code === 0) { - // if the length of this list is less then the pageSize, it means that it have no more contents - if (moments.length !== 0 && res.data.list.length < 10) { - isLoading.value = false - noMoreContent.value = true + const { list, pagesize } = res.data + // if the length of this list is less then the pageSize, it means that it have no more contents + if (moments.length !== 0 && list.length < Number(pagesize)) { + noMoreContent.value = true return } // if the length of this list is equal to the pageSize, this means that it may have the next page. - if (res.data.list.length === 10) + if (list.length === Number(pagesize)) livePage.value++ - res.data.list.forEach((item: any) => { - moments.push({ - id: item.roomid, - uid: item.uid, - name: item.uname, - face: item.face, - url: item.link, + + moments.push( + ...list.map(item => ({ + type: selectedMomentTab.value.type, title: item.title, + author: item.uname, + authorFace: item.face, cover: item.pic, - } as MomentItem) - }) + link: item.link, + }), + ), + ) + noMoreContent.value = false } - isLoading.value = false }) -} - -function pushItemIntoMoments(item: any) { - const card = JSON.parse(item.card) - - if (item.desc.type === MomentType.Video) { - // if this is a video moment - moments.push({ - type: item.desc.type, - id: item.desc.dynamic_id, - uid: item.desc.uid, - name: item.desc.user_profile.info.uname, - face: item.desc.user_profile.info.face, - aid: card.aid, - bvid: item.desc.bvid, - url: card.short_link_v2 || `https://www.bilibili.com/video/${item.desc.bvid}`, - ctime: card.ctime, - title: card.title, - cover: card.pic, - dynamic_id_str: item.desc.dynamic_id_str, - isNew: isNewVideo(item.desc.dynamic_id), - } as MomentItem) - } - else if (item.desc.type === MomentType.Bangumi) { - // bangumi moment - moments.push({ - type: item.desc.type, - id: item.desc.dynamic_id, - name: card.apiSeasonInfo.title, - face: card.apiSeasonInfo.cover, - episode_id: card.episode_id, - url: card.url, - title: card.new_desc, - cover: card.cover, - dynamic_id_str: item.desc.dynamic_id_str, - isNew: isNewVideo(item.desc.dynamic_id), - } as MomentItem) - } - else if (item.desc.type === MomentType.Article) { - // article moment - moments.push({ - type: item.desc.type, - id: item.desc.dynamic_id, - uid: item.desc.uid, - name: item.desc.user_profile.info.uname, - face: item.desc.user_profile.info.face, - url: `https://www.bilibili.com/read/cv${card.id}`, - ctime: card.publish_time, - title: card.title, - cover: card.image_urls[0], - dynamic_id_str: item.desc.dynamic_id_str, - isNew: isNewArticle(item.desc.dynamic_id), - } as MomentItem) - } + .finally(() => isLoading.value = false) } function toggleWatchLater(aid: number) { @@ -286,7 +261,7 @@ function toggleWatchLater(aid: number) { } defineExpose({ - initData, + checkIfHasNewMomentsThenUpdateMoments, }) @@ -314,13 +289,13 @@ defineExpose({
{{ tab.name }}
@@ -354,11 +329,12 @@ defineExpose({ /> +
- - {{ moment.name }} + {{ moment.author }}
{{ moment.title }}
- -
- {{ - moment.ctime - ? calcTimeSince(new Date(moment.ctime * 1000)) - : moment.ctime - }} + +
+ {{ moment.pubTime }}
- + diff --git a/src/components/TopBar/notify.ts b/src/components/TopBar/notify.ts index b45c093b..984e851f 100644 --- a/src/components/TopBar/notify.ts +++ b/src/components/TopBar/notify.ts @@ -1,30 +1,29 @@ -import { MomentType } from './types' -import { getCookie, getUserID, setCookie } from '~/utils/main' +// https://github.dev/the1812/Bilibili-Evolved/blob/8a4e422612a7bd0b42da9aa50c21c7bf3ea401b8/src/components/feeds/notify.ts#L1 + +// import { getCookie, getUserID, setCookie } from '~/utils/main' /** Update the time interval of topbar notifications and moments counts */ export const updateInterval = 1000 * 60 * 5 // Updated every 5 minutes -const getVideoOffsetID = (): number => Number.parseInt(`${getCookie(`bp_video_offset_${getUserID()}`)}`, 10) || 0 -const getArticleOffsetID = (): number => Number.parseInt(`${getCookie(`bp_article_offset_${getUserID()}`)}`, 10) || 0 +// const getLastID = (): string => `${getCookie(`bp_t_offset_${getUserID()}`)}` -function compareOffsetID(currentOffsetID: number, lastestOffsetID: number): boolean { - if (currentOffsetID === lastestOffsetID) - return false - else if (currentOffsetID > lastestOffsetID) - return true - else - return false -} +// function compareID(currentID: string, lastOffsetID: string): boolean { +// if (currentID === lastOffsetID) +// return false +// else if (Number(currentID) > Number(lastOffsetID)) +// return true +// else +// return false +// } -export function setLastestOffsetID(type: MomentType, offsetID: number) { - if (offsetID === null || offsetID === undefined) - return +// export function setLastId(id: string) { +// if (id === null || id === undefined) +// return - if (type === MomentType.Video || type === MomentType.Bangumi) - setCookie(`bp_video_offset_${getUserID()}`, offsetID.toString(), 30) - else if (type === MomentType.Article) - setCookie(`bp_article_offset_${getUserID()}`, offsetID.toString(), 30) -} +// if (compareID(id)) +// return -export const isNewVideo = (currentOffsetID: number): boolean => compareOffsetID(currentOffsetID, getVideoOffsetID()) -export const isNewArticle = (currentOffsetID: number): boolean => compareOffsetID(currentOffsetID, getArticleOffsetID()) +// setCookie(`bp_t_offset_${getUserID()}`, id, 30) +// } + +// export const isNewId = (id: string): boolean => compareID(id, getLastID()) diff --git a/src/components/TopBar/types.ts b/src/components/TopBar/types.ts index 27b6a6a8..90ff35d3 100644 --- a/src/components/TopBar/types.ts +++ b/src/components/TopBar/types.ts @@ -58,23 +58,6 @@ export enum MomentType { Documentary = 4101, } -export interface MomentItem { - type?: MomentType - id: number - uid: number - name: string - face: string - aid?: number - bvid?: string - episode_id?: number - url: string - ctime?: number - title: string - cover: string - dynamic_id_str?: string - isNew: boolean -} - // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/history&toview/history.md#%E8%8E%B7%E5%8F%96%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E5%88%97%E8%A1%A8_web%E7%AB%AF export enum HistoryType { Archive = 'archive', // archive:稿件 diff --git a/src/models/moment/topBarLiveMoment.ts b/src/models/moment/topBarLiveMoment.ts new file mode 100644 index 00000000..82c5af57 --- /dev/null +++ b/src/models/moment/topBarLiveMoment.ts @@ -0,0 +1,29 @@ +// https://app.quicktype.io/?l=ts + +export interface TopBarLiveMomentResult { + code: number + message: string + ttl: number + data: Data +} + +export interface Data { + results: number + page: string + pagesize: string + list: List[] +} + +export interface List { + cover: string + face: string + uname: string + title: string + roomid: number + pic: string + online: number + link: string + uid: number + parent_area_id: number + area_id: number +} diff --git a/src/models/moment/topBarMoment.ts b/src/models/moment/topBarMoment.ts new file mode 100644 index 00000000..1d0e7284 --- /dev/null +++ b/src/models/moment/topBarMoment.ts @@ -0,0 +1,108 @@ +// https://app.quicktype.io/?l=ts + +export interface TopBarMomentResult { + code: number + message: string + ttl: number + data: Data +} + +export interface Data { + has_more: boolean + items: Item[] + offset: string + update_baseline: string + update_num: number +} + +export interface Item { + author: Author + cover: string + id_str: string + jump_url: string + pub_time: string + rid: number + title: string + type: number + visible: boolean +} + +export interface Author { + face: string + jump_url: string + mid: number + name: string + official: Official + vip: Vip +} + +export interface Official { + desc: string + role: number + title: string + type: number +} + +export interface Vip { + avatar_icon: AvatarIcon + avatar_subscript: number + avatar_subscript_url: string + due_date: number + label: Label + nickname_color: Color + role: number + status: number + theme_type: number + tv_due_date: number + tv_vip_pay_type: number + tv_vip_status: number + type: number + vip_pay_type: number +} + +export interface AvatarIcon { + icon_resource: IconResource + icon_type?: number +} + +export interface IconResource { + type?: number + url?: string +} + +export interface Label { + 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: Text + text_color: TextColor + use_img_label: boolean +} + +export enum Color { + Empty = '', + Fb7299 = '#FB7299', +} + +export enum LabelTheme { + AnnualVip = 'annual_vip', + Empty = '', + Vip = 'vip', +} + +export enum Text { + Empty = '', + 大会员 = '大会员', + 年度大会员 = '年度大会员', +} + +export enum TextColor { + Empty = '', + Ffffff = '#FFFFFF', +}