diff --git a/public/index.html b/public/index.html index 490493e..3f41258 100644 --- a/public/index.html +++ b/public/index.html @@ -804,6 +804,43 @@ 刷新历史 + +
+
+ 最新订阅地址 (无Token/持久链接) +
+
+
+
+ Clash (YAML) + 加载中... +
+
+ + + 导入 Clash + +
+
+ +
+
+ Sing-box (JSON) + 加载中... +
+
+ + + 导入 Sing-box + +
+
+
+
@@ -898,6 +935,7 @@ window.addEventListener('DOMContentLoaded', async () => { lucide.createIcons(); await loadAppConfig(); + updateLatestUrls(); loadSubscriptions(); loadDnsTemplates(); }); @@ -1512,6 +1550,35 @@ } } + let latestClashUrl = ''; + let latestSingboxUrl = ''; + + function updateLatestUrls() { + const clientOrigin = getClientImportOrigin(); + latestClashUrl = `${clientOrigin}/sub/clash/latest`; + latestSingboxUrl = `${clientOrigin}/sub/singbox/latest`; + + const clashLinkEl = document.getElementById('latest-clash-link'); + const singboxLinkEl = document.getElementById('latest-singbox-link'); + if (clashLinkEl) clashLinkEl.textContent = latestClashUrl; + if (singboxLinkEl) singboxLinkEl.textContent = latestSingboxUrl; + + const clashImportEl = document.getElementById('latest-clash-import'); + const singboxImportEl = document.getElementById('latest-singbox-import'); + if (clashImportEl) { + clashImportEl.href = `clash://install-config?url=${encodeURIComponent(latestClashUrl)}&name=${encodeURIComponent('CatMata-Latest')}`; + } + if (singboxImportEl) { + singboxImportEl.href = `sing-box://import-remote-profile?url=${encodeURIComponent(latestSingboxUrl)}#${encodeURIComponent('CatMata-Latest')}`; + } + } + + function copyLatestLink(type) { + const link = type === 'clash' ? latestClashUrl : latestSingboxUrl; + copyLinkToClipboard(link); + } + + function renderHistoryList(list) { const tbody = document.getElementById('history-list'); tbody.innerHTML = ''; diff --git a/public/viewer.html b/public/viewer.html index ff2914e..5298dea 100644 --- a/public/viewer.html +++ b/public/viewer.html @@ -262,6 +262,32 @@
正在加载...
+
+

最新订阅地址 (无Token/持久链接)

+
+
+
+ Clash (YAML) + 加载中... +
+
+ + 导入 Clash +
+
+ +
+
+ Sing-box (JSON) + 加载中... +
+
+ + 导入 Sing-box +
+
+
+
@@ -339,6 +365,35 @@ } } + let latestClashUrl = ''; + let latestSingboxUrl = ''; + + function updateLatestUrls() { + const clientOrigin = getClientImportOrigin(); + latestClashUrl = `${clientOrigin}/sub/clash/latest`; + latestSingboxUrl = `${clientOrigin}/sub/singbox/latest`; + + const clashLinkEl = document.getElementById('latest-clash-link'); + const singboxLinkEl = document.getElementById('latest-singbox-link'); + if (clashLinkEl) clashLinkEl.textContent = latestClashUrl; + if (singboxLinkEl) singboxLinkEl.textContent = latestSingboxUrl; + + const clashImportEl = document.getElementById('latest-clash-import'); + const singboxImportEl = document.getElementById('latest-singbox-import'); + if (clashImportEl) { + clashImportEl.href = `clash://install-config?url=${encodeURIComponent(latestClashUrl)}&name=${encodeURIComponent('CatMata-Latest')}`; + } + if (singboxImportEl) { + singboxImportEl.href = `sing-box://import-remote-profile?url=${encodeURIComponent(latestSingboxUrl)}#${encodeURIComponent('CatMata-Latest')}`; + } + } + + function copyLatestLink(type) { + const link = type === 'clash' ? latestClashUrl : latestSingboxUrl; + copyLinkToClipboard(link); + } + + async function loadAppConfig() { try { const res = await fetch('/api/app-config', { credentials: 'same-origin' }); @@ -469,6 +524,7 @@ (async function init() { await loadAppConfig(); + updateLatestUrls(); if (await checkSession()) { showHistory(); await loadHistory(); diff --git a/sub2proxy.js b/sub2proxy.js index 7b274e6..3bbde5a 100644 --- a/sub2proxy.js +++ b/sub2proxy.js @@ -707,6 +707,64 @@ function startServer() { const parsedUrl = new URL(req.url, `http://${req.headers.host}`); const pathname = parsedUrl.pathname; + if (pathname === '/sub/clash/latest' && req.method === 'GET') { + const filePath = path.join(outDir, 'CatMata-sub.yaml'); + if (fs.existsSync(filePath)) { + res.writeHead(200, { + 'Content-Type': 'text/yaml; charset=utf-8', + 'Cache-Control': 'no-store', + 'Access-Control-Allow-Origin': '*' + }); + res.end(fs.readFileSync(filePath)); + } else { + const history = getHistoryList(); + if (history.length > 0 && history[0].yamlFile) { + const histPath = path.join(outDir, 'history', history[0].yamlFile); + if (fs.existsSync(histPath)) { + res.writeHead(200, { + 'Content-Type': 'text/yaml; charset=utf-8', + 'Cache-Control': 'no-store', + 'Access-Control-Allow-Origin': '*' + }); + res.end(fs.readFileSync(histPath)); + return; + } + } + res.writeHead(404); + res.end('Latest Clash profile not found'); + } + return; + } + + if (pathname === '/sub/singbox/latest' && req.method === 'GET') { + const filePath = path.join(outDir, 'CatMata-sub.json'); + if (fs.existsSync(filePath)) { + res.writeHead(200, { + 'Content-Type': 'application/json; charset=utf-8', + 'Cache-Control': 'no-store', + 'Access-Control-Allow-Origin': '*' + }); + res.end(fs.readFileSync(filePath)); + } else { + const history = getHistoryList(); + if (history.length > 0 && history[0].jsonFile) { + const histPath = path.join(outDir, 'history', history[0].jsonFile); + if (fs.existsSync(histPath)) { + res.writeHead(200, { + 'Content-Type': 'application/json; charset=utf-8', + 'Cache-Control': 'no-store', + 'Access-Control-Allow-Origin': '*' + }); + res.end(fs.readFileSync(histPath)); + return; + } + } + res.writeHead(404); + res.end('Latest Sing-box profile not found'); + } + return; + } + if (pathname === '/login' || pathname === '/login.html') { serveHtml(res, 'login.html'); return;