import { getUID } from '@/core/utils' import { getJsonWithCredentials } from '@/core/ajax' import { formatCount, formatDuration } from '@/core/utils/formatters' import { watchlaterList } from '@/core/watchlater' import { descendingStringSort } from '@/core/utils/sort' import { VideoCard } from '../video-card' import { FeedsCard, FeedsCardType } from './types' export * from './types' export * from './manager' /** * 搜索视频卡片中重复的 aid, 合并为联合投稿 * @param cards 视频卡片 */ export const groupVideoFeeds = (cards: VideoCard[]) => { const groups = lodash.groupBy(cards, c => c.aid) const cardToCooperationItem = (card: VideoCard) => ( { id: card.upID, name: card.upName, faceUrl: card.upFaceUrl, } ) const results = Object.values(groups).map(groupCards => { if (groupCards.length === 1) { return groupCards[0] } const [firstCard, ...restCards] = groupCards firstCard.cooperation = [ cardToCooperationItem(firstCard), ...restCards.map(cardToCooperationItem), ] console.log([...firstCard.cooperation]) return firstCard }).sort(descendingStringSort(it => it.id)) return results } /** 判断动态卡片是否包含预约功能. * > 在预约功能刚上线时, 无论使用什么参数请求动态流, 预约过的东西都会插入到其中. * > 比如传参番剧类型结果依然混入的预约视频, 导致数据混乱. 不知道之后有没有修复, 目前是使用此方法过滤掉预约类卡片. */ export const isPreOrderedVideo = (card: any) => _.get(card, 'extra.is_reserve_recall', 0) === 1 export const getVideoFeeds = async (type: 'video' | 'bangumi' = 'video'): Promise => { if (!getUID()) { return [] } const json = await getJsonWithCredentials( `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type === 'video' ? 8 : 512}`, ) if (json.code !== 0) { throw new Error(json.message) } const dataCards = json.data.cards as any[] const dataCardsWithoutPreOrder = dataCards.filter(it => !isPreOrderedVideo(JSON.parse(it.card))) if (type === 'video') { return groupVideoFeeds(dataCards.map( (c: any): VideoCard => { const card = JSON.parse(c.card) const topics = lodash.get(c, 'display.topic_info.topic_details', []).map( (it: any) => ({ id: it.topic_id, name: it.topic_name, }), ) return { id: c.desc.dynamic_id_str, aid: card.aid, bvid: c.desc.bvid || card.bvid, title: card.title, upID: c.desc.user_profile.info.uid, upName: c.desc.user_profile.info.uname, upFaceUrl: c.desc.user_profile.info.face, coverUrl: card.pic, description: card.desc, timestamp: c.timestamp, time: new Date(c.timestamp * 1000), topics, dynamic: card.dynamic, like: formatCount(c.desc.like), duration: card.duration, durationText: formatDuration(card.duration, 0), playCount: formatCount(card.stat.view), danmakuCount: formatCount(card.stat.danmaku), watchlater: watchlaterList.includes(card.aid), } }, )) } if (type === 'bangumi') { return dataCardsWithoutPreOrder.map( (c: any): VideoCard => { const card = JSON.parse(c.card) return { id: c.desc.dynamic_id_str, aid: card.aid, bvid: c.desc.bvid || card.bvid, epID: card.episode_id, title: card.new_desc, upName: card.apiSeasonInfo.title, upFaceUrl: card.apiSeasonInfo.cover, coverUrl: card.cover, description: '', timestamp: c.timestamp, time: new Date(c.timestamp * 1000), like: formatCount(c.desc.like), durationText: '', playCount: formatCount(card.play_count), danmakuCount: formatCount(card.bullet_count), watchlater: false, } }, ) } return [] } // let mockupCalled = false /** * 获取动态 * @param type 动态类型, 或传入类型ID列表返回最新动态 * @param afterID 返回指定ID之前的动态历史, 省略则返回最新的动态 */ export const getFeeds = async (type: FeedsCardType | string, afterID?: string | number) => { if (typeof type === 'string') { return getJsonWithCredentials(`https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=${getUID()}&type_list=${type}`) } 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) { api = `https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid=${getUID()}&offset_dynamic_id=${afterID}&type=${id}` } return getJsonWithCredentials(api) } /** * 向动态卡片的菜单中添加菜单项 * @param card 动态卡片 * @param config 菜单项配置 */ export const addMenuItem = (card: FeedsCard, config: { className: string text: string action: (e: MouseEvent) => void }) => { const morePanel = dq(card.element, '.more-panel') as HTMLElement const { className, text, action } = config if (!morePanel || dq(morePanel, `.${className}`)) { return } const menuItem = document.createElement('p') menuItem.classList.add('child-button', 'c-pointer', className) menuItem.textContent = text const vueScopeAttributes = [...new Set([...morePanel.children].map((element: HTMLElement) => element.getAttributeNames().filter(it => it.startsWith('data-v-'))).flat())] vueScopeAttributes.forEach(attr => menuItem.setAttribute(attr, '')) menuItem.addEventListener('click', e => { action(e) card.element.click() }) morePanel.appendChild(menuItem) }