feat: browser-side subscription fetch (bypass CORS with fallback)
This commit is contained in:
+41
-8
@@ -431,22 +431,55 @@
|
||||
if (!url) return toast('请输入订阅链接', 'error');
|
||||
|
||||
try {
|
||||
toast('正在获取订阅...', 'info');
|
||||
// First fetch and validate
|
||||
const res = await api('POST', '/api/subscription/import', { url });
|
||||
if (res.error) throw new Error(res.error);
|
||||
toast('订阅获取成功,正在应用...', 'info');
|
||||
toast('正在从浏览器获取订阅...', 'info');
|
||||
|
||||
// Then apply to mihomo
|
||||
const applyRes = await api('POST', '/api/subscription/apply', { url });
|
||||
// Fetch subscription from browser side (avoids server network issues)
|
||||
const resp = await fetch(url, {
|
||||
headers: { 'User-Agent': 'ClashForAndroid/2.5.12' },
|
||||
mode: 'cors',
|
||||
});
|
||||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||||
let content = await resp.text();
|
||||
|
||||
// Try base64 decode
|
||||
try {
|
||||
const decoded = atob(content.trim());
|
||||
if (decoded.includes('://') || decoded.includes('proxies')) content = decoded;
|
||||
} catch {}
|
||||
|
||||
if (!content.includes('proxies') && !content.includes('port:')) {
|
||||
throw new Error('无效的订阅内容');
|
||||
}
|
||||
|
||||
toast('订阅获取成功,正在推送到 mihomo...', 'info');
|
||||
|
||||
// Send content to mihomo via our server API
|
||||
const applyRes = await api('POST', '/api/subscription/push', { content, url });
|
||||
if (applyRes.error) throw new Error(applyRes.error);
|
||||
|
||||
toast('🎉 订阅导入并应用成功!', 'success');
|
||||
document.getElementById('sub-url').value = '';
|
||||
document.getElementById('sub-name').value = '';
|
||||
loadSubscriptions();
|
||||
loadDashboard();
|
||||
} catch(e) {
|
||||
toast('导入失败: ' + e.message, 'error');
|
||||
// CORS blocked - fallback to server-side fetch
|
||||
if (e.message.includes('Failed to fetch') || e.message.includes('NetworkError')) {
|
||||
toast('浏览器无法直接获取(CORS),改用服务器获取...', 'info');
|
||||
try {
|
||||
const res = await api('POST', '/api/subscription/import', { url });
|
||||
if (res.error) throw new Error(res.error);
|
||||
const applyRes = await api('POST', '/api/subscription/apply', { url });
|
||||
if (applyRes.error) throw new Error(applyRes.error);
|
||||
toast('🎉 订阅导入成功!', 'success');
|
||||
document.getElementById('sub-url').value = '';
|
||||
loadSubscriptions();
|
||||
} catch(e2) {
|
||||
toast('导入失败: ' + e2.message, 'error');
|
||||
}
|
||||
} else {
|
||||
toast('导入失败: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -355,6 +355,47 @@ async function handleApi(req, res, apiPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// POST /api/subscription/push - Push subscription content directly to mihomo
|
||||
if (apiPath === '/api/subscription/push' && method === 'POST') {
|
||||
const body = JSON.parse(await readBody(req));
|
||||
const { content, url } = body;
|
||||
if (!content) return sendError(res, 'Missing content', 400);
|
||||
|
||||
try {
|
||||
// Write config to mihomo directory
|
||||
const configDir = path.dirname(CONFIG.mihomoConfig);
|
||||
const subConfigPath = path.join(configDir, 'sub-config.yaml');
|
||||
fs.writeFileSync(subConfigPath, content, 'utf-8');
|
||||
|
||||
// Inject our settings
|
||||
let configContent = content;
|
||||
configContent = configContent.replace(/external-controller:\s*'[^']*'/, "external-controller: '0.0.0.0:9097'");
|
||||
configContent = configContent.replace(/secret:\s*"[^"]*"/, 'secret: "orangepi"');
|
||||
fs.writeFileSync(subConfigPath, configContent, 'utf-8');
|
||||
|
||||
// Reload mihomo with new config
|
||||
await mihomoApi('PUT', '/configs', { path: subConfigPath });
|
||||
|
||||
// Save subscription URL if provided
|
||||
if (url) {
|
||||
const subFile = path.join(configDir, 'subscriptions.json');
|
||||
let subscriptions = [];
|
||||
try { subscriptions = JSON.parse(fs.readFileSync(subFile, 'utf-8')); } catch {}
|
||||
const existing = subscriptions.find(s => s.url === url);
|
||||
if (existing) {
|
||||
existing.updatedAt = new Date().toISOString();
|
||||
} else {
|
||||
subscriptions.push({ url, addedAt: new Date().toISOString(), updatedAt: new Date().toISOString() });
|
||||
}
|
||||
fs.writeFileSync(subFile, JSON.stringify(subscriptions, null, 2));
|
||||
}
|
||||
|
||||
return sendJson(res, { ok: true, message: 'Subscription pushed and applied' });
|
||||
} catch (e) {
|
||||
return sendError(res, 'Failed to push subscription: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/subscriptions - List saved subscriptions
|
||||
if (apiPath === '/api/subscriptions' && method === 'GET') {
|
||||
const subFile = '/etc/mihomo/subscriptions.json';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Set-Location "f:\Orangepi\Orange Pi RV\OrangePi-Mihomo-UI"
|
||||
$env:MIHOMO_API = "http://192.168.10.1:9097"
|
||||
$env:MIHOMO_SECRET = "orangepi"
|
||||
$env:MIHOMO_CONFIG = ""
|
||||
$env:YACD_PATH = "f:\Orangepi\Orange Pi RV\OrangePi-Mihomo-UI\yacd-meta"
|
||||
$env:PORT = "8899"
|
||||
node server.js
|
||||
Reference in New Issue
Block a user