Merge pull request #5176 from pencilqaq/preview-features

feat(components): 添加下载up主专属表情包功能
This commit is contained in:
Grant Howard 2025-03-30 21:10:38 +08:00 committed by GitHub
commit 373fd29b02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,64 @@
<template>
<DefaultWidget
:disabled="downloading"
name="下载up主表情包"
icon="mdi-emoticon-outline"
@click="download()"
></DefaultWidget>
</template>
<script lang="ts">
import { DefaultWidget } from '@/ui'
import { logError } from '@/core/utils/log'
import { Emoticons } from './emoticons'
import { DownloadPackage } from '@/core/download'
import { Toast } from '@/core/toast'
const downloader = new Emoticons()
export default Vue.extend({
components: {
DefaultWidget,
},
data() {
return {
downloading: false,
midRegex: /mid=(\d+)/,
roomIdRegex: /^https:\/\/live\.bilibili\.com\/(\d+)(?:\?.*)?$/,
}
},
methods: {
async download() {
this.downloading = true
try {
const url = window.location.href
if (url.startsWith('https://live.bilibili.com/')) {
const rid = this.roomIdRegex.exec(url)[1]
const emoticonsArray = await downloader.getEmoticonsArray(rid)
if (emoticonsArray.length === 0) {
Toast.info('该up主没有专属表情包', '表情包下载')
return
}
const downloadPromises = await downloader.downloadEmoticons(emoticonsArray)
const results = await downloader.batchDownload(downloadPromises)
const dpackage = new DownloadPackage()
dpackage.noEscape = true
for (const result of results) {
if (result !== null) {
for (const emoticon of result.emoticons) {
dpackage.add(`${result.pkg_name}/${emoticon.emoji}.png`, emoticon.blob)
}
}
}
await dpackage.emit(`emoticons-${rid}.zip`)
}
} catch (error) {
Toast.error('下载失败详情查看控制台', '表情包下载')
logError(error)
} finally {
this.downloading = false
}
},
},
})
</script>

View File

@ -0,0 +1,106 @@
import { monkey } from '@/core/ajax'
import { useScopedConsole } from '@/core/utils/log'
const logger = useScopedConsole('表情包下载')
interface DownloadPromise {
pkg_name: string // 表情包名称
emoticons: { emoji: string; blob: Promise<Blob> }[]
}
export class Emoticons {
private async getEmoticonsByRoomId(roomId: string) {
const response = await this.apiRequest(
`https://api.live.bilibili.com/xlive/web-ucenter/v2/emoticon/GetEmoticons?platform=pc&room_id=${roomId}`,
)
return response
}
async downloadEmoticons(emoticonsArray) {
const downloadPromises: DownloadPromise[] = emoticonsArray.map(obj => {
const emoticons = obj.emoticons.map(item => {
try {
const blob = this.getImageBlob(item.url)
return {
emoji: item.emoji,
blob,
}
} catch (error) {
logger.error(`下载表情 ${item.emoji} 失败:`, error)
return null
}
})
return {
emoticons,
pkg_name: obj.pkg_name,
}
})
return downloadPromises
}
async getEmoticonsArray(roomId: string): Promise<Array<any>> {
try {
let response = await this.getEmoticonsByRoomId(roomId)
if (response.data.data.length === 0) {
logger.log('获取表情包失败尝试获取真实roomId')
const realRoomId = await this.getRealRoomId(roomId)
response = await this.getEmoticonsByRoomId(realRoomId)
}
return response.data.data.slice(2)
} catch (error) {
throw new Error('获取表情包失败')
}
}
async batchDownload(tasks: DownloadPromise[]) {
const results: any[] = []
for (const task of tasks) {
const promise = (async () => {
const blobs: Blob[] = []
const blobPromises = task.emoticons.map(e => e.blob)
for (const blobPromise of blobPromises) {
const blob = await blobPromise
blobs.push(blob)
}
return {
pkg_name: task.pkg_name,
emoticons: task.emoticons.map((e, i) => ({ emoji: e.emoji, blob: blobs[i] })),
}
})()
results.push(promise)
}
return Promise.all(results)
}
// 有的直播间id是超短id需要获取到原始的roomid才能通过api获取到表情包
async getRealRoomId(rid: string): Promise<string> {
try {
const response = await this.apiRequest(
`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${rid}`,
)
if (Object.keys(response.data).length === 0) {
throw new Error('获取直播间id失败')
}
return response.data.room_id
} catch (error) {
throw new Error('获取直播间id失败')
}
}
apiRequest = (url: string): Promise<any> => {
const details: MonkeyXhrBasicDetails = {
url,
method: 'GET',
responseType: 'json',
}
return monkey(details)
}
getImageBlob = async (url: string) => {
const details: MonkeyXhrBasicDetails = {
url,
method: 'GET',
responseType: 'blob',
}
return monkey<Blob>(details)
}
}

View File

@ -0,0 +1,3 @@
## 下载up主专属表情包
支持下载up主专属表情包处于任意直播间页面时下载按钮会在`功能`面板显示,以压缩包形式保存。

View File

@ -0,0 +1,16 @@
import { defineComponentMetadata } from '@/components/define'
export const component = defineComponentMetadata({
name: 'downloadEmoticons',
displayName: '下载表情',
tags: [componentsTags.utils, componentsTags.live],
entry: none,
author: {
name: 'Pencilqaq',
link: 'https://github.com/pencilqaq',
},
widget: {
component: () => import('./Widget.vue').then(m => m.default),
},
urlInclude: [/^https:\/\/live\.bilibili\.com\/(\d+)/],
})