feat(rss): 添加RSS订阅功能并优化日期处理

- 在DEPLOYMENT.md中添加RSS_FEED_URL环境变量配置说明
- 修改日期处理函数,统一使用GMT时区格式
- 实现RSS订阅功能,支持通过Feedly等阅读器订阅
- 优化GitHub API调用的Base64编解码处理
- 更新README展示RSS订阅链接和访问方式
This commit is contained in:
justlovemaki
2025-06-15 14:34:24 +08:00
parent 1841248fec
commit 3c6740528e
9 changed files with 81 additions and 46 deletions

View File

@@ -59,8 +59,13 @@
**在线阅读地址:**
* 🌐 **主站点(GitHub Pages )**[website-1](https://justlovemaki.github.io/CloudFlare-AI-Insight-Daily/today/book/)
* 📖 **备用站点(Cloudflare)**[website-2](https://ai-today.justlikemaki.vip/)
你可以通过以下任一方式访问每日生成的最新资讯:
| 访问方式 | 链接 | 状态 |
| ----------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| 📡 **RSS 订阅** | [https://justlovemaki.github.io/CloudFlare-AI-Insight-Daily/rss.xml](https://justlovemaki.github.io/CloudFlare-AI-Insight-Daily/rss.xml) | 推荐使用 [Feedly](https://feedly.com/), [Folo](https://app.follow.is/) 等阅读器 |
| 🌐 **主站点 (GitHub)** | [https://justlovemaki.github.io/CloudFlare-AI-Insight-Daily/today/book/](https://justlovemaki.github.io/CloudFlare-AI-Insight-Daily/today/book/) | ✅ 稳定 🚀 速度 |
| 📖 **备用站点 (Cloudflare)** | [https://ai-today.justlikemaki.vip/](https://ai-today.justlikemaki.vip/) | |
**内容成果展示:**
@@ -70,7 +75,7 @@
| ![小酒馆](docs/images/sm2.png "img") | ![情报站](docs/images/sm1.png "img") |
**项目截图:**
**后台项目截图:**
| 网站首页 | 日报内容 | 播客脚本 |
| -------------------------------------- | -------------------------------------- | -------------------------------------- |

View File

@@ -172,6 +172,7 @@ TWITTER_FETCH_PAGES = "2"
* 在您的 GitHub 仓库页面,进入 `Settings` -> `Secrets and variables` -> `Actions`。
* 在 `Variables` 标签页,点击 `New repository variable`。
* 创建一个名为 `IMAGE_PROXY_URL` 的变量,值为您的代理服务地址,例如 `https://your-proxy.com/`。
* 创建一个名为 `RSS_FEED_URL` 的变量,值为您的后端服务地址,例如 `https://your-backend.com/rss`。
4. **🚀 触发 Action 并验证**
* 手动触发一次 `build-daily-book` 工作流,或等待其定时自动执行。

View File

@@ -1,15 +1,14 @@
export function insertFoot() {
return `
---
---
**收听语音版**
| 🎙️ **小宇宙** | 📹 **抖音** |
| --- | --- |
| [来生小酒馆](https://www.xiaoyuzhoufm.com/podcast/683c62b7c1ca9cf575a5030e) | [来生情报站](https://www.douyin.com/user/MS4wLjABAAAAwpwqPQlu38sO38VyWgw9ZjDEnN4bMR5j8x111UxpseHR9DpB6-CveI5KRXOWuFwG)|
| ![小酒馆](https://raw.githubusercontent.com/justlovemaki/CloudFlare-AI-Insight-Daily/refs/heads/main/docs/images/sm2.png "img") | ![情报站](https://raw.githubusercontent.com/justlovemaki/CloudFlare-AI-Insight-Daily/refs/heads/main/docs/images/sm1.png "img") |
**收听语音版**
| 🎙️ **小宇宙** | 📹 **抖音** |
| --- | --- |
| [来生小酒馆](https://www.xiaoyuzhoufm.com/podcast/683c62b7c1ca9cf575a5030e) | [来生情报站](https://www.douyin.com/user/MS4wLjABAAAAwpwqPQlu38sO38VyWgw9ZjDEnN4bMR5j8x111UxpseHR9DpB6-CveI5KRXOWuFwG)|
| ![小酒馆](https://raw.githubusercontent.com/justlovemaki/CloudFlare-AI-Insight-Daily/refs/heads/main/docs/images/sm2.png "img") | ![情报站](https://raw.githubusercontent.com/justlovemaki/CloudFlare-AI-Insight-Daily/refs/heads/main/docs/images/sm1.png "img") |
`;
}

View File

@@ -75,7 +75,7 @@ export async function getGitHubFileSha(env, filePath) {
*/
export async function createOrUpdateGitHubFile(env, filePath, content, commitMessage, existingSha = null) {
const GITHUB_BRANCH = env.GITHUB_BRANCH || 'main';
const base64Content = btoa(String.fromCharCode(...new TextEncoder().encode(content)));
const base64Content = b64EncodeUnicode(content);
const payload = {
message: commitMessage,
@@ -102,20 +102,41 @@ export async function getDailyReportContent(env, filePath) {
throw new Error("GitHub API configuration is missing in environment variables.");
}
const rawUrl = `https://raw.githubusercontent.com/${GITHUB_REPO_OWNER}/${GITHUB_REPO_NAME}/${GITHUB_BRANCH}/${filePath}`;
console.log(rawUrl)
try {
const response = await fetch(rawUrl);
if (!response.ok) {
if (response.status === 404) {
console.log(`File not found: ${filePath} on branch ${GITHUB_BRANCH}`);
return null;
}
throw new Error(`Failed to fetch file from GitHub: ${response.status} ${response.statusText}`);
}
return await response.text();
const data = await callGitHubApi(env, `/contents/${filePath}?ref=${GITHUB_BRANCH}`);
return b64DecodeUnicode(data.content);
} catch (error) {
console.error(`Error fetching daily report content from ${rawUrl}:`, error);
throw error;
}
}
// Base64 encode (UTF-8 safe)
function b64EncodeUnicode(str) {
// Replacing '+' with '-' and '/' with '_' makes it URL-safe, but GitHub API expects standard Base64
// Using btoa directly after encodeURIComponent is standard
try {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode('0x' + p1);
}));
} catch (e) {
console.error("Base64 Encoding Error:", e);
showStatus("Error: Could not encode content for GitHub.", true);
return null; // Return null on error
}
}
// Base64 decode (UTF-8 safe)
function b64DecodeUnicode(str) {
try {
// Standard Base64 decoding
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
} catch(e) {
console.error("Base64 Decoding Error:", e);
showStatus("Error: Could not decode file content from GitHub.", true);
return null; // Return null on error
}
}

View File

@@ -1,5 +1,5 @@
// src/handlers/commitToGitHub.js
import { getISODate, formatMarkdownText, replaceImageProxy,formatDateToChineseWithTime } from '../helpers.js';
import { getISODate, formatMarkdownText, replaceImageProxy,formatDateToGMT0WithTime, sleep } from '../helpers.js';
import { getGitHubFileSha, createOrUpdateGitHubFile } from '../github.js';
import { storeInKV } from '../kv.js';
import { marked } from '../marked.esm.js';
@@ -19,7 +19,7 @@ export async function handleCommitToGitHub(request, env) {
link: '/daily/'+dateStr+'.html',
content_html: null,
// 可以添加其他相關欄位,例如作者、來源等
published_date: formatDateToChineseWithTime(new Date()) // 記錄保存時間
published_date: formatDateToGMT0WithTime(new Date()) // 記錄保存時間
}
const filesToCommit = [];

View File

@@ -229,7 +229,7 @@ export async function handleGenAIContent(request, env) {
if (fullPromptForCall2_User) promptsMarkdownContent += `### User Input (Output of Call 1)\n\`\`\`\n${fullPromptForCall2_User}\n\`\`\`\n\n`;
let dailySummaryMarkdownContent = `# ${env.DAILY_TITLE} ${formatDateToChinese(dateStr)}\n\n${removeMarkdownCodeBlock(outputOfCall2)}`;
if (env.INSERT_FOOT) dailySummaryMarkdownContent += insertFoot() +`\n\n`;
if (env.INSERT_FOOT=='true') dailySummaryMarkdownContent += insertFoot() +`\n\n`;
const successHtml = generateGenAiPageHtml(
env,

View File

@@ -23,7 +23,6 @@ export async function handleRss(request, env) {
const allData = [];
const today = getShanghaiTime(); // 加上東八時區的偏移量
console.log(today);
for (let i = 0; i < days; i++) {
const date = new Date(today);
@@ -58,7 +57,7 @@ export async function handleRss(request, env) {
const finalData = Object.values(filteredData);
finalData.forEach(item => {
const pubDate = item.published_date ? formatRssDate(new Date(item.published_date)) : formatRssDate(new Date());
const pubDate = formatRssDate(new Date(item.published_date));
const content = minifyHTML(item.content_html);
const title = item.title || '无标题';
const link = env.BOOK_LINK+item.link || '#';
@@ -84,7 +83,7 @@ export async function handleRss(request, env) {
<link>${env.BOOK_LINK}</link>
<description> 近 ${days} 天的AI日报</description>
<language>zh-cn</language>
<lastBuildDate>${formatRssDate(new Date())}</lastBuildDate>
<lastBuildDate>${formatRssDate()}</lastBuildDate>
<atom:link href="${url.origin}/rss" rel="self" type="application/rss+xml" />
${rssItems}
</channel>

View File

@@ -1,4 +1,4 @@
import { replaceImageProxy, formatDateToChineseWithTime } from '../helpers.js';
import { replaceImageProxy, formatDateToGMT0WithTime } from '../helpers.js';
import { getDailyReportContent } from '../github.js';
import { storeInKV } from '../kv.js';
import { marked } from '../marked.esm.js';
@@ -24,16 +24,17 @@ export async function handleWriteRssData(request, env) {
link: '/daily/'+dateStr+'.html',
content_html: null,
// 可以添加其他相關欄位,例如作者、來源等
published_date: formatDateToChineseWithTime(new Date()) // 記錄保存時間
published_date: formatDateToGMT0WithTime(new Date()) // 記錄保存時間
}
report.content_html = marked.parse(replaceImageProxy(env.IMG_PROXY, content));
storeInKV(env.DATA_KV, `${dateStr}-report`, report);
return new Response(JSON.stringify({ message: `Successfully fetched and stored daily report for ${dateStr}`}), {
return new Response(JSON.stringify(report), {
headers: { 'Content-Type': 'application/json' },
status: 200
});
} catch (error) {
console.error('Error handling daily report:', error);
console.error('Error handling daily report:', error.message);
return new Response(`Error handling daily report: ${error.message}`, { status: 500 });
}
}

View File

@@ -231,20 +231,29 @@ export function formatDateToChineseWithTime(isoDateString) {
* @returns {string} 格式化後的日期字串
*/
export function formatRssDate(date) {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const dayOfWeek = days[date.getUTCDay()];
const dayOfMonth = date.getUTCDate();
const month = months[date.getUTCMonth()];
const year = date.getUTCFullYear();
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
return `${dayOfWeek}, ${dayOfMonth} ${month} ${year} ${hours}:${minutes}:${seconds} GMT`;
if (!date) return new Date().toUTCString();
return date.toUTCString();
}
export function formatDateToGMT0WithTime(isoDateString) {
if (!isoDateString) return '';
const date = new Date(isoDateString);
const options = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false, // 使用24小时制
timeZone: 'GMT'
};
// 使用 'zh-CN' 语言环境以确保中文格式
return new Intl.DateTimeFormat('zh-CN', options).format(date);
}
/**
* Converts English double quotes (") to Chinese double quotes (“”).
* @param {string} text - The input string.