From 807b765e2c17e1c1ce8c96973341eeb8c0b03992 Mon Sep 17 00:00:00 2001
From: jocayn <1579649885@qq.com>
Date: Sat, 13 Jun 2026 17:54:46 +0800
Subject: [PATCH] feat: add /sub/clash/latest and /sub/singbox/latest public
subscription APIs and display them on the web dashboard UI
---
public/index.html | 67 ++++++++++++++++++++++++++++++++++++++++++++++
public/viewer.html | 56 ++++++++++++++++++++++++++++++++++++++
sub2proxy.js | 58 +++++++++++++++++++++++++++++++++++++++
3 files changed, 181 insertions(+)
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)
+ 加载中...
+
+
+
+
+
+
+ Sing-box (JSON)
+ 加载中...
+
+
+
+
+
@@ -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)
+ 加载中...
+
+
+
+
+
+
+ Sing-box (JSON)
+ 加载中...
+
+
+
+
+
@@ -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;