2025-05-11 23:06:57 +08:00

110 lines
3.7 KiB
Plaintext
Raw Permalink 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.

/**
* 导入Deno标准库中的HTTP服务器模块
*/
import { serve } from "https://deno.land/std/http/server.ts";
/**
* API映射表定义了路由前缀到实际API端点的映射关系
* 用于将请求转发到相应的第三方服务
*/
const apiMapping = {
'/discord': 'https://discord.com/api', // Discord API
'/telegram': 'https://api.telegram.org', // Telegram API
'/openai': 'https://api.openai.com', // OpenAI API
'/claude': 'https://api.anthropic.com', // Anthropic Claude API
'/gemini': 'https://generativelanguage.googleapis.com', // Google Gemini API
'/meta': 'https://www.meta.ai/api', // Meta AI API
'/groq': 'https://api.groq.com/openai', // Groq API (OpenAI兼容)
'/xai': 'https://api.x.ai', // X.AI API
'/cohere': 'https://api.cohere.ai', // Cohere API
'/huggingface': 'https://api-inference.huggingface.co', // Hugging Face API
'/together': 'https://api.together.xyz', // Together AI API
'/novita': 'https://api.novita.ai', // Novita AI API
'/portkey': 'https://api.portkey.ai', // Portkey API
'/fireworks': 'https://api.fireworks.ai', // Fireworks AI API
'/openrouter': 'https://openrouter.ai/api' // OpenRouter API
};
/**
* 启动HTTP服务器处理所有传入的请求
*/
serve(async (request) => {
// 解析请求URL
const url = new URL(request.url);
const pathname = url.pathname;
// 处理根路径和index.html请求
if (pathname === '/' || pathname === '/index.html') {
return new Response('Service is running!', {
status: 200,
headers: { 'Content-Type': 'text/html' }
});
}
// 处理robots.txt请求禁止搜索引擎爬取
if (pathname === '/robots.txt') {
return new Response('User-agent: *\nDisallow: /', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
});
}
// 从请求路径中提取API前缀和剩余路径
const [prefix, rest] = extractPrefixAndRest(pathname, Object.keys(apiMapping));
if (!prefix) {
return new Response('Not Found', { status: 404 });
}
// 构建目标URL
const targetUrl = `${apiMapping[prefix]}${rest}`;
try {
// 创建新的请求头,只保留允许的头部信息
const headers = new Headers();
const allowedHeaders = ['accept', 'content-type', 'authorization'];
for (const [key, value] of request.headers.entries()) {
if (allowedHeaders.includes(key.toLowerCase())) {
headers.set(key, value);
}
}
// 转发请求到目标API
const response = await fetch(targetUrl, {
method: request.method,
headers: headers,
body: request.body
});
// 设置安全相关的响应头
const responseHeaders = new Headers(response.headers);
responseHeaders.set('X-Content-Type-Options', 'nosniff');
responseHeaders.set('X-Frame-Options', 'DENY');
responseHeaders.set('Referrer-Policy', 'no-referrer');
// 返回API响应
return new Response(response.body, {
status: response.status,
headers: responseHeaders
});
} catch (error) {
// 处理请求错误
console.error('Failed to fetch:', error);
return new Response('Internal Server Error', { status: 500 });
}
});
/**
* 从路径中提取API前缀和剩余部分
* @param {string} pathname - 请求路径
* @param {string[]} prefixes - 可用的API前缀列表
* @returns {[string|null, string|null]} - 返回匹配的前缀和剩余路径,如果没有匹配则返回[null, null]
*/
function extractPrefixAndRest(pathname, prefixes) {
for (const prefix of prefixes) {
if (pathname.startsWith(prefix)) {
return [prefix, pathname.slice(prefix.length)];
}
}
return [null, null];
}