Spaces:
Running
Running
| import { formatElapsedTime } from '#shared/utils/helpers'; | |
| import toastFactory from '~/composables/toast'; | |
| import type { Metadata } from '~/store/v2/metadata'; | |
| import { Downloader } from '~/utils/download/Downloader'; | |
| import type { DownloaderStatus } from '~/utils/download/types'; | |
| export interface DownloadArticleOptions { | |
| // 文章内容下载成功回调 | |
| onContent: (url: string) => void; | |
| // 文章状态异常回调(不含「已删除」) | |
| onStatusChange: (url: string, status: string) => void; | |
| // 文章被删除回调 | |
| onDelete: (url: string) => void; | |
| // 文章阅读量抓取成功回调 | |
| onMetadata: (url: string, metadata: Metadata) => void; | |
| // 文章留言抓取成功回调 | |
| onComment: (url: string) => void; | |
| // 修复单篇文章下载的 fakeid 专用 | |
| onFakeID: (url: string, fakeid: string) => void; | |
| } | |
| export default (options: Partial<DownloadArticleOptions> = {}) => { | |
| const toast = toastFactory(); | |
| const loading = ref(false); | |
| const completed_count = ref(0); | |
| const total_count = ref(0); | |
| let downloader: Downloader | null = null; | |
| // 抓取文章内容(html) | |
| async function downloadArticleHTML(urls: string[]) { | |
| if (urls.length === 0) { | |
| toast.warning('提示', '请先选择文章'); | |
| return; | |
| } | |
| try { | |
| loading.value = true; | |
| cleanupDownloader(); | |
| downloader = new Downloader(urls); | |
| downloader.on('download:progress', (url: string, success: boolean, status: DownloaderStatus) => { | |
| console.debug( | |
| `进度: (进行中:${status.pending.length} / 已完成:${status.completed.length} / 已失败:${status.failed.length} / 已删除:${status.deleted.length})` | |
| ); | |
| completed_count.value = status.completed.length; | |
| if (success && typeof options.onContent === 'function') { | |
| options.onContent(url); | |
| } | |
| }); | |
| downloader.on('download:deleted', (url: string) => { | |
| if (typeof options.onDelete === 'function') { | |
| options.onDelete(url); | |
| } | |
| }); | |
| downloader.on('download:exception', (url: string, msg: string) => { | |
| if (typeof options.onStatusChange === 'function') { | |
| options.onStatusChange(url, msg); | |
| } | |
| }); | |
| downloader.on('download:begin', () => { | |
| console.debug('开始抓取【文章内容】...'); | |
| completed_count.value = 0; | |
| total_count.value = urls.length; | |
| }); | |
| downloader.on('download:finish', (seconds: number, status: DownloaderStatus) => { | |
| console.debug('耗时:', formatElapsedTime(seconds)); | |
| toast.success( | |
| '【文章内容】抓取完成', | |
| `本次抓取耗时 ${formatElapsedTime(seconds)}, 成功:${status.completed.length}, 失败:${status.failed.length}, 检测到已被删除:${status.deleted.length}` | |
| ); | |
| }); | |
| downloader.on('download:stop', () => { | |
| toast.info('HTML下载任务已停止'); | |
| }); | |
| await downloader.startDownload('html'); | |
| } catch (error) { | |
| console.error('【文章内容】抓取失败:', error); | |
| alert((error as Error).message); | |
| } finally { | |
| loading.value = false; | |
| cleanupDownloader(); | |
| } | |
| } | |
| // 抓取文章阅读量、点赞量等元数据 | |
| async function downloadArticleMetadata(urls: string[]) { | |
| if (urls.length === 0) { | |
| toast.warning('提示', '请先选择文章'); | |
| return; | |
| } | |
| try { | |
| loading.value = true; | |
| cleanupDownloader(); | |
| downloader = new Downloader(urls); | |
| downloader.on('download:progress', (url: string, success: boolean, status: DownloaderStatus) => { | |
| console.debug( | |
| `进度: (进行中:${status.pending.length} / 已完成:${status.completed.length} / 已失败:${status.failed.length} / 已删除:${status.deleted.length})` | |
| ); | |
| completed_count.value = status.completed.length; | |
| }); | |
| downloader.on('download:metadata', (url: string, metadata: Metadata) => { | |
| if (typeof options.onMetadata === 'function') { | |
| options.onMetadata(url, metadata); | |
| } | |
| }); | |
| downloader.on('download:deleted', (url: string) => { | |
| if (typeof options.onDelete === 'function') { | |
| options.onDelete(url); | |
| } | |
| }); | |
| downloader.on('download:exception', (url: string, msg: string) => { | |
| if (typeof options.onStatusChange === 'function') { | |
| options.onStatusChange(url, msg); | |
| } | |
| }); | |
| downloader.on('download:begin', () => { | |
| console.debug('开始抓取【阅读量】...'); | |
| completed_count.value = 0; | |
| total_count.value = urls.length; | |
| }); | |
| downloader.on('download:finish', (seconds: number, status: DownloaderStatus) => { | |
| console.debug('耗时:', formatElapsedTime(seconds)); | |
| toast.success( | |
| '【阅读量】抓取完成', | |
| `本次抓取耗时 ${formatElapsedTime(seconds)}, 成功:${status.completed.length}, 失败:${status.failed.length}, 检测到已被删除:${status.deleted.length}` | |
| ); | |
| }); | |
| await downloader.startDownload('metadata'); | |
| } catch (error) { | |
| console.error('【阅读量】抓取失败:', error); | |
| alert((error as Error).message); | |
| } finally { | |
| loading.value = false; | |
| cleanupDownloader(); | |
| } | |
| } | |
| // 抓取文章留言数据 | |
| async function downloadArticleComment(urls: string[]) { | |
| if (urls.length === 0) { | |
| toast.warning('提示', '请先选择文章'); | |
| return; | |
| } | |
| try { | |
| loading.value = true; | |
| cleanupDownloader(); | |
| downloader = new Downloader(urls); | |
| downloader.on('download:progress', (url: string, success: boolean, status: DownloaderStatus) => { | |
| console.debug( | |
| `进度: (进行中:${status.pending.length} / 已完成:${status.completed.length} / 已失败:${status.failed.length} / 已删除:${status.deleted.length})` | |
| ); | |
| completed_count.value = status.completed.length; | |
| if (success && typeof options.onComment === 'function') { | |
| options.onComment(url); | |
| } | |
| }); | |
| downloader.on('download:begin', () => { | |
| console.debug('开始抓取【留言内容】...'); | |
| completed_count.value = 0; | |
| total_count.value = urls.length; | |
| }); | |
| downloader.on('download:finish', (seconds: number, status: DownloaderStatus) => { | |
| console.debug('耗时:', formatElapsedTime(seconds)); | |
| toast.success( | |
| '【留言内容】抓取完成', | |
| `本次抓取耗时 ${formatElapsedTime(seconds)}, 成功:${status.completed.length}, 失败:${status.failed.length}` | |
| ); | |
| }); | |
| await downloader.startDownload('comments'); | |
| } catch (error) { | |
| console.error('【留言内容】抓取失败:', error); | |
| alert((error as Error).message); | |
| } finally { | |
| loading.value = false; | |
| cleanupDownloader(); | |
| } | |
| } | |
| // 修复单篇文章fakeid | |
| async function fixSingleFakeidTask(urls: string[]) { | |
| if (urls.length === 0) { | |
| toast.warning('提示', '请先选择文章'); | |
| return; | |
| } | |
| try { | |
| loading.value = true; | |
| cleanupDownloader(); | |
| downloader = new Downloader(urls); | |
| downloader.on('download:progress', (url: string, success: boolean, status: DownloaderStatus) => { | |
| console.debug( | |
| `进度: (进行中:${status.pending.length} / 已完成:${status.completed.length} / 已失败:${status.failed.length})` | |
| ); | |
| completed_count.value = status.completed.length; | |
| }); | |
| downloader.on('download:begin', () => { | |
| console.debug('开始修复 fakeid ...'); | |
| completed_count.value = 0; | |
| total_count.value = urls.length; | |
| }); | |
| downloader.on('fix:fakeid', (url: string, fakeid: string) => { | |
| console.debug(`${url} 修复成功 fakeid: ${fakeid}`); | |
| if (typeof options.onFakeID === 'function') { | |
| options.onFakeID(url, fakeid); | |
| } | |
| }); | |
| downloader.on('download:finish', (seconds: number, status: DownloaderStatus) => { | |
| console.debug('耗时:', formatElapsedTime(seconds)); | |
| toast.success( | |
| '【fakeid】修复完成', | |
| `本次耗时 ${formatElapsedTime(seconds)}, 成功:${status.completed.length}, 失败:${status.failed.length}` | |
| ); | |
| }); | |
| await downloader.startDownload('fakeid'); | |
| } catch (error) { | |
| console.error('【fakeid】修复失败:', error); | |
| alert((error as Error).message); | |
| } finally { | |
| loading.value = false; | |
| cleanupDownloader(); | |
| } | |
| } | |
| async function download(type: 'html' | 'metadata' | 'comment' | 'fakeid', urls: string[]) { | |
| if (type === 'html') { | |
| await downloadArticleHTML(urls); | |
| } else if (type === 'metadata') { | |
| await downloadArticleMetadata(urls); | |
| } else if (type === 'comment') { | |
| await downloadArticleComment(urls); | |
| } else if (type === 'fakeid') { | |
| await fixSingleFakeidTask(urls); | |
| } | |
| } | |
| function cleanupDownloader() { | |
| if (downloader) { | |
| downloader.removeAllListeners(); | |
| downloader = null; | |
| } | |
| } | |
| function stop() { | |
| if (downloader) { | |
| downloader.stop(); | |
| // 注意:不在此处清理监听器,等 download:stop 事件触发后由 finally 块清理 | |
| } | |
| } | |
| return { | |
| loading, | |
| completed_count, | |
| total_count, | |
| download, | |
| stop, | |
| }; | |
| }; | |