Skip to content

Commit

Permalink
添加稍后阅读
Browse files Browse the repository at this point in the history
  • Loading branch information
easychen committed Dec 23, 2024
1 parent cb26eb6 commit b594f4b
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 1 deletion.
1 change: 1 addition & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ sdd
.env
cache
.DS_Store
readlater
198 changes: 197 additions & 1 deletion server/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import fs from 'fs/promises'
import path from 'path'
import crypto from 'crypto'
import { cors } from 'hono/cors'
import { put, list } from '@vercel/blob';
import { put, list, del } from '@vercel/blob';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import pkg from '../package.json' assert { type: 'json' };
import { Feed } from 'feed';


const __filename = fileURLToPath(import.meta.url);
Expand Down Expand Up @@ -42,6 +43,11 @@ function validateSDD(sdd) {
return requiredFields.every(field => field in sdd)
}

// 验证稍后阅读数据格式
function validateReadLater(data) {
return data.url && typeof data.url === 'string' && data.url.startsWith('http');
}

app.get('/rss/:name', async (c) => {
const name = c.req.param('name')
try {
Expand Down Expand Up @@ -196,6 +202,15 @@ app.get('/list', async (c) => {
}))
}

// 添加 read later 的 feed
items.unshift({
key: 'read-later',
title: '稍后阅读',
url: `${origin}/read-later?key=${ADD_KEY}`,
rss_url: `${origin}/read-later?key=${ADD_KEY}`,
favicon: `${origin}/favicon.ico`
});

return c.json({
success: true,
total: items.length,
Expand All @@ -207,6 +222,187 @@ app.get('/list', async (c) => {
}
})

app.post('/read-later', async (c) => {
if (!validateAuth(c)) {
return c.json({
error: 'Invalid or missing ADD_KEY. Please provide key via X-Add-Key header or ?key=xxx query parameter'
}, 403);
}

try {
const body = await c.req.json();
if (!validateReadLater(body)) {
return c.json({ error: 'Invalid format. URL is required and must be a valid HTTP URL' }, 400);
}

const item = {
id: generateUniqueKey(body.url),
url: body.url,
text: body.text || '',
timestamp: Date.now(),
title: body.title || body.url
};

if (isVercel) {
// 获取现有列表
const { blobs } = await list({
token: process.env.BLOB_READ_WRITE_TOKEN,
});

// 检查是否存在重复URL
const existingBlobs = blobs.filter(blob => blob.pathname.startsWith('readlater/'));
for (const blob of existingBlobs) {
const response = await fetch(blob.url);
const content = await response.json();
if (content.url === item.url) {
// 删除旧记录
await del(blob.pathname, { token: process.env.BLOB_READ_WRITE_TOKEN });
}
}

// 保存新记录
await put(`readlater/${item.id}.json`, JSON.stringify(item), {
access: 'public',
token: process.env.BLOB_READ_WRITE_TOKEN,
contentType: 'application/json',
addRandomSuffix: false
});
} else {
const readLaterDir = path.join(__dirname, 'readlater');
await fs.mkdir(readLaterDir, { recursive: true });

// 读取目录中的所有文件
const files = await fs.readdir(readLaterDir);
for (const file of files) {
const content = await fs.readFile(path.join(readLaterDir, file), 'utf-8');
const existingItem = JSON.parse(content);
if (existingItem.url === item.url) {
// 删除旧记录
await fs.unlink(path.join(readLaterDir, file));
}
}

// 保存新记录
await fs.writeFile(
path.join(readLaterDir, `${item.id}.json`),
JSON.stringify(item, null, 2)
);
}

return c.json({
success: true,
id: item.id
});
} catch (error) {
console.error('Error adding read-later item:', error);
return c.json({ error: error.message }, 500);
}
});

app.get('/read-later', async (c) => {
if (!validateAuth(c)) {
return c.json({
error: 'Invalid or missing ADD_KEY. Please provide key via X-Add-Key header or ?key=xxx query parameter'
}, 403);
}

try {
let items = [];
const protocol = c.req.header('x-forwarded-proto') || 'http';
const host = c.req.header('host');
const origin = `${protocol}://${host}`;

if (isVercel) {
const { blobs } = await list({
token: process.env.BLOB_READ_WRITE_TOKEN,
});

const readLaterBlobs = blobs.filter(blob => blob.pathname.startsWith('readlater/'));
items = await Promise.all(readLaterBlobs.map(async (blob) => {
const response = await fetch(blob.url);
return await response.json();
}));
} else {
const readLaterDir = path.join(__dirname, 'readlater');
await fs.mkdir(readLaterDir, { recursive: true });
const files = await fs.readdir(readLaterDir);

items = await Promise.all(files.map(async (file) => {
const content = await fs.readFile(path.join(readLaterDir, file), 'utf-8');
return JSON.parse(content);
}));
}

// 按时间戳排序
items.sort((a, b) => b.timestamp - a.timestamp);

// 限制最多50条记录
items = items.slice(0, 50);

// 如果在 Vercel 环境中,删除多余的条目
if (isVercel && items.length === 50) {
const { blobs } = await list({
token: process.env.BLOB_READ_WRITE_TOKEN,
});

const readLaterBlobs = blobs
.filter(blob => blob.pathname.startsWith('readlater/'))
.sort((a, b) => b.uploadedAt - a.uploadedAt)
.slice(50); // 获取第50条之后的所有记录

// 删除多余的记录
for (const blob of readLaterBlobs) {
await del(blob.pathname, { token: process.env.BLOB_READ_WRITE_TOKEN });
}
} else if (!isVercel && items.length === 50) {
// 在本地环境中删除多余的文件
const readLaterDir = path.join(__dirname, 'readlater');
const files = await fs.readdir(readLaterDir);
const sortedFiles = await Promise.all(files.map(async (file) => {
const stats = await fs.stat(path.join(readLaterDir, file));
return { file, mtime: stats.mtime };
}));

// 按修改时间排序并获取需要删除的文件
const filesToDelete = sortedFiles
.sort((a, b) => b.mtime - a.mtime)
.slice(50)
.map(item => item.file);

// 删除多余的文件
for (const file of filesToDelete) {
await fs.unlink(path.join(readLaterDir, file));
}
}

// 创建 RSS Feed
const feed = new Feed({
title: "Read Later List",
description: "Your personal read-later list",
id: `${origin}/read-later/feed`,
link: origin,
language: "zh-CN",
generator: "AI RSS Server"
});

items.forEach(item => {
feed.addItem({
title: item.title,
id: item.id,
link: item.url,
description: item.text,
date: new Date(item.timestamp)
});
});

c.header('Content-Type', 'application/xml');
return c.body(feed.rss2());
} catch (error) {
console.error('Error generating read-later feed:', error);
return c.json({ error: error.message }, 500);
}
});

const port = 3000
console.log(`Server is running on port ${port}`)

Expand Down

0 comments on commit b594f4b

Please sign in to comment.