feat: clash/ss suport

This commit is contained in:
2026-04-13 22:19:30 +08:00
parent 699b038a6c
commit 35e95ee777
11 changed files with 665 additions and 129 deletions

View File

@@ -8,6 +8,8 @@ import rulesRouter from './routes/rules.js';
import surgeRouter from './routes/surge.js';
import db from './db.js';
import { generateSurgeConfig } from './services/generator.js';
import { generateClashConfig } from './services/clashGenerator.js';
import { generateSSRConfig } from './services/ssrGenerator.js';
// Ensure surge_token exists
function ensureSurgeToken(): string {
@@ -24,15 +26,17 @@ const PORT = parseInt(process.env.PORT || '3456', 10);
app.use(express.json());
function verifySurgeToken(token: string): boolean {
const row = db.prepare("SELECT value FROM config WHERE key = 'surge_token'").get() as any;
return !!row?.value && token === row.value;
}
// Surge endpoint (no auth, token-protected path)
app.get('/surge/:token', (req, res) => {
const row = db.prepare("SELECT value FROM config WHERE key = 'surge_token'").get() as any;
if (!row?.value || req.params.token !== row.value) {
return res.status(404).send('Not Found');
}
if (!verifySurgeToken(req.params.token)) return res.status(404).send('Not Found');
const host = req.headers.host || 'localhost:3456';
const protocol = req.secure ? 'https' : 'http';
const hostUrl = `${protocol}://${host}/surge/${row.value}`;
const hostUrl = `${protocol}://${host}/surge/${req.params.token}`;
const config = generateSurgeConfig(hostUrl);
res.set({
'Content-Type': 'text/plain; charset=utf-8',
@@ -41,6 +45,28 @@ app.get('/surge/:token', (req, res) => {
res.send(config);
});
// Clash endpoint (no auth, token-protected path)
app.get('/clash/:token', (req, res) => {
if (!verifySurgeToken(req.params.token)) return res.status(404).send('Not Found');
const config = generateClashConfig();
res.set({
'Content-Type': 'text/plain; charset=utf-8',
'Content-Disposition': 'attachment; filename=clash.yaml',
});
res.send(config);
});
// SSR endpoint (no auth, token-protected path)
app.get('/ssr/:token', (req, res) => {
if (!verifySurgeToken(req.params.token)) return res.status(404).send('Not Found');
const config = generateSSRConfig();
res.set({
'Content-Type': 'text/plain; charset=utf-8',
'Content-Disposition': 'attachment; filename=proxy.txt',
});
res.send(config);
});
// Auth routes (no auth required)
app.post('/api/auth/login', (req, res) => {
const { password } = req.body;
@@ -104,7 +130,7 @@ app.get('/api/stats', (_req, res) => {
const webDist = path.join(__dirname, '..', '..', 'web', 'dist');
app.use(express.static(webDist));
app.get('*', (req, res, next) => {
if (req.path.startsWith('/api') || req.path.startsWith('/surge')) return next();
if (req.path.startsWith('/api') || req.path.startsWith('/surge') || req.path.startsWith('/clash') || req.path.startsWith('/ssr')) return next();
res.sendFile(path.join(webDist, 'index.html'));
});