diff --git a/src/panes/SearchPane/SearchPane.vue b/src/panes/SearchPane/SearchPane.vue index 54f1683..8f07dd7 100644 --- a/src/panes/SearchPane/SearchPane.vue +++ b/src/panes/SearchPane/SearchPane.vue @@ -8,23 +8,26 @@ import { GetBangumiInfoParams, GetCheeseInfoParams, GetNormalInfoParams, + GetUserVideoInfoParams, SearchParams, SearchResult, } from '../../bindings.ts' import NormalSeasonPanel from './components/NormalSeasonPanel.vue' import NormalSinglePanel from './components/NormalSinglePanel.vue' -import { extractBvid, extractAid, extractEpId, extractSeasonId } from '../../utils.tsx' +import { extractBvid, extractAid, extractEpId, extractSeasonId, extractUid } from '../../utils.tsx' import { useStore } from '../../store.ts' import BangumiPanel from './components/BangumiPanel.vue' import CheesePanel from './components/CheesePanel.vue' +import UserVideoPanel from './components/UserVideoPanel.vue' -export type SearchType = 'Auto' | 'Normal' | 'Bangumi' | 'Cheese' +export type SearchType = 'Auto' | 'Normal' | 'Bangumi' | 'Cheese' | 'UserVideo' const searchTypeOptions: SelectProps['options'] = [ { label: '自动', value: 'Auto' }, { label: '视频', value: 'Normal' }, { label: '番剧', value: 'Bangumi' }, { label: '课程', value: 'Cheese' }, + { label: 'UP投稿', value: 'UserVideo' }, ] const store = useStore() @@ -43,8 +46,10 @@ const searchLabel = computed(() => { return '链接 / ep... / ss...' } else if (searchTypeSelected.value === 'Cheese') { return '链接 / ep... / ss...' + } else if (searchTypeSelected.value === 'UserVideo') { + return '个人空间链接 / uid...' } - return '链接 / av... / BV... / ep... / ss...' + return '链接 / av... / BV... / ep... / ss... / uid...' }) async function search(input: string, searchType: SearchType) { @@ -67,6 +72,8 @@ async function search(input: string, searchType: SearchType) { await searchBangumi(input, isUrl) } else if (searchType === 'Cheese') { await searchCheese(input, isUrl) + } else if (searchType === 'UserVideo') { + await searchUserVideo(input, isUrl) } else { message.error('未知的搜索类型') } @@ -81,6 +88,7 @@ async function searchAuto(input: string, isUrl: boolean) { const aid = extractAid(input) const epId = extractEpId(input) const seasonId = extractSeasonId(input) + const uid = extractUid(input) if (bvid !== undefined) { params = { Normal: { Bvid: bvid } } @@ -90,6 +98,8 @@ async function searchAuto(input: string, isUrl: boolean) { params = { Bangumi: { EpId: epId } } } else if (seasonId !== undefined) { params = { Bangumi: { SeasonId: seasonId } } + } else if (uid !== undefined) { + params = { UserVideo: { mid: uid, pn: 1 } } } } else if (input.toLowerCase().startsWith('bv')) { params = { Normal: { Bvid: input } } @@ -108,10 +118,15 @@ async function searchAuto(input: string, isUrl: boolean) { if (!isNaN(seasonId)) { params = { Bangumi: { SeasonId: seasonId } } } + } else if (input.toLowerCase().startsWith('uid')) { + const uid = parseInt(input.substring(3), 10) + if (!isNaN(uid)) { + params = { UserVideo: { mid: uid, pn: 1 } } + } } if (params === undefined) { - message.error('解析输入失败,请输入正确的链接或ID(如 av... / BV... / ep... / ss...)') + message.error('解析输入失败,请输入正确的链接或ID(如 av... / BV... / ep... / ss... / uid...)') return } @@ -228,6 +243,39 @@ async function searchCheese(input: string, isUrl: boolean) { searchResult.value = result.data } +async function searchUserVideo(input: string, isUrl: boolean) { + let params: GetUserVideoInfoParams | undefined + + if (isUrl) { + const uid = extractUid(input) + if (uid !== undefined) { + params = { mid: uid, pn: 1 } + } + } else if (input.toLowerCase().startsWith('uid')) { + const uid = parseInt(input.substring(3), 10) + if (!isNaN(uid)) { + params = { mid: uid, pn: 1 } + } + } else { + const uid = parseInt(input, 10) + if (!isNaN(uid)) { + params = { mid: uid, pn: 1 } + } + } + + if (params === undefined) { + message.error('解析输入失败,请输入正确的个人空间链接或ID(如 uid...)') + return + } + + const result = await commands.search({ UserVideo: params }) + if (result.status === 'error') { + console.error(result.error) + return + } + searchResult.value = result.data +} + defineExpose({ search }) @@ -267,6 +315,7 @@ defineExpose({ search }) + diff --git a/src/panes/SearchPane/components/EpisodeCard.vue b/src/panes/SearchPane/components/EpisodeCard.vue index 0cd9b83..90e873c 100644 --- a/src/panes/SearchPane/components/EpisodeCard.vue +++ b/src/panes/SearchPane/components/EpisodeCard.vue @@ -6,8 +6,10 @@ import { EpInBangumi, EpInCheese, EpInNormal, + EpInUserVideo, NormalInfo, NormalSearchResult, + UserVideoSearchResult, } from '../../../bindings.ts' import SimpleCheckbox from '../../../components/SimpleCheckbox.vue' import { PhDownloadSimple, PhGoogleChromeLogo, PhQueue, PhMagnifyingGlass } from '@phosphor-icons/vue' @@ -37,9 +39,9 @@ export type EpisodeInfo = { } const props = defineProps<{ - searchResult: NormalSearchResult | BangumiSearchResult | CheeseSearchResult - episode: NormalInfo | EpInNormal | EpInBangumi | EpInCheese - episodeType: 'NormalSingle' | 'NormalSeason' | 'Bangumi' | 'Cheese' + searchResult: NormalSearchResult | BangumiSearchResult | CheeseSearchResult | UserVideoSearchResult + episode: NormalInfo | EpInNormal | EpInBangumi | EpInCheese | EpInUserVideo + episodeType: 'NormalSingle' | 'NormalSeason' | 'Bangumi' | 'Cheese' | 'UserVideo' downloadEpisode?: (episodeInfo: EpisodeInfo) => Promise checkboxChecked?: (episodeInfo: EpisodeInfo) => boolean handleCheckboxClick?: (episodeInfo: EpisodeInfo) => void @@ -110,6 +112,19 @@ const episodeInfo = computed(() => { upUid: searchResult.info.up_info.mid, pubTime: episode.release_date, } + } else if (props.episodeType === 'UserVideo') { + const episode = props.episode as EpInUserVideo + return { + episodeType: 'Normal', + aid: episode.aid, + bvid: episode.bvid, + href: `https://www.bilibili.com/video/${episode.bvid}/`, + cover: episode.pic, + title: episode.title, + upName: episode.author, + upUid: episode.mid, + pubTime: episode.created, + } } throw new Error(`错误的 episodeType: ${props.episodeType}`) }) diff --git a/src/panes/SearchPane/components/UserVideoPanel.vue b/src/panes/SearchPane/components/UserVideoPanel.vue new file mode 100644 index 0000000..9cf6631 --- /dev/null +++ b/src/panes/SearchPane/components/UserVideoPanel.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/src/utils.tsx b/src/utils.tsx index c40065a..e521025 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -61,6 +61,18 @@ export function extractSeasonId(url: string): number | undefined { } } +export function extractUid(url: string): number | undefined { + const parsedUrl = new URL(url) + if (parsedUrl.hostname === 'space.bilibili.com') { + const segments = parsedUrl.pathname.split('/') + const uidSegment = segments[1] + const uid = parseInt(uidSegment, 10) + if (!isNaN(uid)) { + return uid + } + } +} + export function useEpisodeDropdown(onCheck: () => void, onUncheck: () => void, onSelectAll: () => void) { const dropdownX = ref(0) const dropdownY = ref(0)