Files
blog/blog-admin/src/rss-api.ts
T
zqlit a74b3c7127
Deploy to Production / pre-check (push) Successful in 58s
Deploy to Production / build (push) Successful in 4m3s
Deploy to Production / deploy-edgeone (push) Successful in 3m48s
Deploy to Production / finalize (push) Successful in 26s
Deploy to Production / notify-failure (push) Skipped
归档 artalk-cf 评论后端 + rss-robot 到 blog-admin(含技术选型/模块分布 README)
2026-10-04 08:45:40 +08:00

110 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// rss-robot-cf —— 主入口
// 路由表 + Cron scheduled。迁自 EdgeOne 的 api/*.js 各端点 + check-feeds.js 抓取逻辑。
import type { Env } from './types';
import { runCron } from './lib/rss/cron';
/** 每小时抓取的源数(66 源 ÷ 22 ≈ 3 小时跑完一轮) */
const RSS_BATCH = 22;
import * as FeedsRoute from './routes/rss/feeds';
import * as LinksRoute from './routes/rss/links';
import { results, articles } from './routes/rss/results';
import { auth, health } from './routes/rss/misc';
import { favicon, update, proxy, cronRun, notifyMail, proxyHealth, deployStatusGet, deployStatusPost, deployNotify } from './routes/rss/tools';
import { linkApply, linkApplyList, linkApplyReview } from './routes/rss/links';
import * as WechatRoute from './routes/rss/wechat';
import { randomImage, artalkCommenter } from './routes/rss/media';
import * as GreetingRoute from './routes/rss/greeting';
import { respond } from './lib/rss/util';
const CORS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
export const RssApi = {
// 每小时轮转抓取:每次抓 RSS_BATCH 个源,游标存在 KV(rss_cursor),
// 三四个小时覆盖全部 66 个源。免费版单次调用限 50 subrequest,
// 所以批量不能大;连续失败的源会进入退避期被跳过(见 cron.ts)。
async scheduled(_controller: ScheduledController, env: Env, _ctx: ExecutionContext): Promise<void> {
await runCron(env, { limit: RSS_BATCH, rotate: true });
},
async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
// CORS 预检(所有端点)
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS });
}
// 微信验证文件(MP_verify_xxx.txt)
if (path.startsWith('/MP_verify_') && path.endsWith('.txt')) {
const code = path.replace('/MP_verify_', '').replace('.txt', '');
return new Response(code, { status: 200, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
}
// 健康检查
if (path === '/api/health') return health(request, env);
// 管理页(/feed 订阅源管理)与首页登录门
if (path === '/feed') {
const resp = await env.ASSETS!.fetch(new URL('/feed.html', request.url));
if (resp.ok) return new Response(resp.body, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
return respond({ error: 'feed.html not found' }, 404);
}
// 路由分发
try {
switch (path) {
case '/api/feeds':
return FeedsRoute.onRequest(request, env);
case '/api/links':
return LinksRoute.onRequest(request, env);
case '/api/results':
return results(request, env);
case '/api/articles':
return articles(request, env);
case '/api/auth':
return auth(request, env);
case '/api/favicon':
return favicon(request, env);
case '/api/update':
return update(request, env);
case '/api/cron/run':
return cronRun(request, env);
case '/api/deploy-status':
return request.method === 'POST' ? deployStatusPost(request, env) : deployStatusGet(request, env);
case '/api/deploy-notify':
return deployNotify(request, env);
case '/api/link-apply':
return linkApply(request, env);
case '/api/link-apply/list':
return linkApplyList(request, env);
case '/api/link-apply/review':
return linkApplyReview(request, env);
case '/api/proxy':
return proxy(request, env);
case '/api/notify-mail':
return notifyMail(request, env);
case '/api/proxy-health':
return proxyHealth(request, env);
case '/api/wechat-material':
return WechatRoute.onRequest(request, env);
case '/api/random-image':
return randomImage(request, env);
case '/api/artalk/commenter':
return artalkCommenter(request, env);
case '/api/ai/greeting':
return GreetingRoute.onRequest(request, env);
default:
return respond({ error: 'not found', path }, 404);
}
} catch (err) {
return respond({ error: (err as Error).message }, 500);
}
},
};