Allen2030 已修改 5 hours ago. 還原成這個修訂版本
1 file changed, 150 insertions
ech0-api.ts(檔案已創建)
| @@ -0,0 +1,150 @@ | |||
| 1 | + | // Ech0 API 工具函数 | |
| 2 | + | ||
| 3 | + | interface EssayData { | |
| 4 | + | id: number; | |
| 5 | + | content: string; | |
| 6 | + | time: string; | |
| 7 | + | tags: string[]; | |
| 8 | + | images?: string[]; | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | /** | |
| 12 | + | * 从Ech0 RSS获取动态数据 | |
| 13 | + | * @param apiUrl Ech0 API地址 | |
| 14 | + | * @returns 转换后的动态数据数组 | |
| 15 | + | */ | |
| 16 | + | export async function fetchEch0Posts(apiUrl: string): Promise<EssayData[]> { | |
| 17 | + | try { | |
| 18 | + | const response = await fetch(`${apiUrl}/rss`); | |
| 19 | + | ||
| 20 | + | if (!response.ok) { | |
| 21 | + | throw new Error(`Failed to fetch Ech0 posts: ${response.status}`); | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | const xmlText = await response.text(); | |
| 25 | + | return parseRssData(xmlText); | |
| 26 | + | } catch (error) { | |
| 27 | + | console.error('Error fetching Ech0 posts:', error); | |
| 28 | + | // 出错时返回空数组,避免页面崩溃 | |
| 29 | + | return []; | |
| 30 | + | } | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | /** | |
| 34 | + | * 解析RSS XML数据 | |
| 35 | + | * @param xmlText RSS XML文本 | |
| 36 | + | * @returns 转换后的动态数据数组 | |
| 37 | + | */ | |
| 38 | + | function parseRssData(xmlText: string): EssayData[] { | |
| 39 | + | // 使用正则表达式解析RSS数据,避免使用DOMParser(浏览器特有API) | |
| 40 | + | const entryRegex = /<entry>([\s\S]*?)<\/entry>/g; | |
| 41 | + | const entries: EssayData[] = []; | |
| 42 | + | let match; | |
| 43 | + | ||
| 44 | + | let index = 0; | |
| 45 | + | while ((match = entryRegex.exec(xmlText)) !== null) { | |
| 46 | + | const entryText = match[1]; | |
| 47 | + | index++; | |
| 48 | + | ||
| 49 | + | // 提取更新时间 | |
| 50 | + | const updatedRegex = /<updated>([\s\S]*?)<\/updated>/; | |
| 51 | + | const updatedMatch = entryText.match(updatedRegex); | |
| 52 | + | const updated = updatedMatch ? updatedMatch[1] : ''; | |
| 53 | + | ||
| 54 | + | // 提取摘要(使用更宽松的正则表达式,支持换行符) | |
| 55 | + | const summaryRegex = /<summary[^>]*>([\s\S]*?)<\/summary>/i; | |
| 56 | + | const summaryMatch = entryText.match(summaryRegex); | |
| 57 | + | const summary = summaryMatch ? summaryMatch[1] : ''; | |
| 58 | + | ||
| 59 | + | // 提取纯文本内容 | |
| 60 | + | const content = extractPlainText(summary); | |
| 61 | + | ||
| 62 | + | // 提取图片 | |
| 63 | + | const images = extractImages(summary); | |
| 64 | + | ||
| 65 | + | entries.push({ | |
| 66 | + | id: index, | |
| 67 | + | content, | |
| 68 | + | time: formatDate(updated), | |
| 69 | + | tags: ['生活'], // 默认标签 | |
| 70 | + | images: images.length > 0 ? images : undefined | |
| 71 | + | }); | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // 按ID倒序排列 | |
| 75 | + | return entries.sort((a, b) => b.id - a.id); | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | /** | |
| 79 | + | * 从HTML中提取纯文本 | |
| 80 | + | * @param html HTML文本 | |
| 81 | + | * @returns 纯文本 | |
| 82 | + | */ | |
| 83 | + | function extractPlainText(html: string): string { | |
| 84 | + | // 解码HTML实体 | |
| 85 | + | let decodedHtml = html | |
| 86 | + | .replace(/</g, '<') | |
| 87 | + | .replace(/>/g, '>') | |
| 88 | + | .replace(/&/g, '&') | |
| 89 | + | .replace(/"/g, '"') | |
| 90 | + | .replace(/"/g, '"') | |
| 91 | + | .replace(/'/g, "'") | |
| 92 | + | .replace(/
/g, '\n'); | |
| 93 | + | ||
| 94 | + | // 使用正则表达式移除HTML标签 | |
| 95 | + | let plainText = decodedHtml.replace(/<[^>]*>/g, '').trim(); | |
| 96 | + | ||
| 97 | + | // 如果纯文本为空,说明可能是纯图片的说说,返回一个占位符 | |
| 98 | + | return plainText || '[图片]'; | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | /** | |
| 102 | + | * 从HTML中提取图片URL | |
| 103 | + | * @param html HTML文本 | |
| 104 | + | * @returns 图片URL数组 | |
| 105 | + | */ | |
| 106 | + | function extractImages(html: string): string[] { | |
| 107 | + | console.log('原始HTML:', html); | |
| 108 | + | ||
| 109 | + | // 解码HTML实体 | |
| 110 | + | let decodedHtml = html | |
| 111 | + | .replace(/</g, '<') | |
| 112 | + | .replace(/>/g, '>') | |
| 113 | + | .replace(/&/g, '&') | |
| 114 | + | .replace(/"/g, '"') | |
| 115 | + | .replace(/"/g, '"') | |
| 116 | + | .replace(/'/g, "'"); | |
| 117 | + | ||
| 118 | + | console.log('解码后HTML:', decodedHtml); | |
| 119 | + | ||
| 120 | + | // 使用更宽松的正则表达式提取图片URL | |
| 121 | + | const imgRegex = /<img[^>]*src=["']([^"']+)["']/gi; | |
| 122 | + | const images: string[] = []; | |
| 123 | + | let match; | |
| 124 | + | ||
| 125 | + | while ((match = imgRegex.exec(decodedHtml)) !== null) { | |
| 126 | + | let url = match[1]; | |
| 127 | + | console.log('找到图片URL:', url); | |
| 128 | + | ||
| 129 | + | // 将HTTP URL转换为HTTPS | |
| 130 | + | if (url.startsWith('http://')) { | |
| 131 | + | url = url.replace('http://', 'https://'); | |
| 132 | + | console.log('转换为HTTPS:', url); | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | images.push(url); | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | console.log('最终图片数组:', images); | |
| 139 | + | return images; | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | /** | |
| 143 | + | * 格式化日期 | |
| 144 | + | * @param dateString ISO日期字符串 | |
| 145 | + | * @returns YYYY-MM-DD格式的日期字符串 | |
| 146 | + | */ | |
| 147 | + | function formatDate(dateString: string): string { | |
| 148 | + | const date = new Date(dateString); | |
| 149 | + | return date.toISOString().split('T')[0]; | |
| 150 | + | } | |
上一頁
下一頁