From 33652af51660935eab1421c9b3a45a3739d1fd92 Mon Sep 17 00:00:00 2001 From: xream Date: Fri, 5 Apr 2024 13:37:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=A2=E9=98=85=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E5=93=AA=E5=90=92=E6=8E=A2=E9=92=88=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E5=93=8D=E5=BA=94;=20=E6=B8=85=E7=90=86=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E6=95=B0=E6=8D=AE;=20=E5=A2=9E=E5=8A=A0=E5=86=85?= =?UTF-8?q?=E9=83=A8=E6=95=B0=E6=8D=AE=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- .../src/core/proxy-utils/processors/index.js | 12 ++ .../src/core/proxy-utils/producers/clash.js | 2 +- .../core/proxy-utils/producers/clashmeta.js | 2 +- .../proxy-utils/producers/shadowrocket.js | 2 +- .../src/core/proxy-utils/producers/stash.js | 2 +- backend/src/core/proxy-utils/producers/uri.js | 2 +- backend/src/restful/download.js | 123 +++++++++++++++--- 8 files changed, 120 insertions(+), 27 deletions(-) diff --git a/backend/package.json b/backend/package.json index f6114bb..d170aae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.14.277", + "version": "2.14.278", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/processors/index.js b/backend/src/core/proxy-utils/processors/index.js index 0e4a564..e41c426 100644 --- a/backend/src/core/proxy-utils/processors/index.js +++ b/backend/src/core/proxy-utils/processors/index.js @@ -550,13 +550,25 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) { results[p.server], ); if (server && port) { + p._domain = p.server; p.server = server; p.port = port; p.resolved = true; + p._IPv4 = p.server; + if (!isIP(p._IP)) { + p._IP = p.server; + } + } else { + p.resolved = false; } } else { + p._domain = p.server; p.server = results[p.server]; p.resolved = true; + p[`_${type}`] = p.server; + if (!isIP(p._IP)) { + p._IP = p.server; + } } } else { p.resolved = false; diff --git a/backend/src/core/proxy-utils/producers/clash.js b/backend/src/core/proxy-utils/producers/clash.js index 45ff222..9ae53c6 100644 --- a/backend/src/core/proxy-utils/producers/clash.js +++ b/backend/src/core/proxy-utils/producers/clash.js @@ -153,7 +153,7 @@ export default function Clash_Producer() { delete proxy.id; delete proxy.resolved; for (const key in proxy) { - if (proxy[key] == null) { + if (proxy[key] == null || /^_/i.test(key)) { delete proxy[key]; } } diff --git a/backend/src/core/proxy-utils/producers/clashmeta.js b/backend/src/core/proxy-utils/producers/clashmeta.js index a0594d1..37a279d 100644 --- a/backend/src/core/proxy-utils/producers/clashmeta.js +++ b/backend/src/core/proxy-utils/producers/clashmeta.js @@ -168,7 +168,7 @@ export default function ClashMeta_Producer() { delete proxy.id; delete proxy.resolved; for (const key in proxy) { - if (proxy[key] == null) { + if (proxy[key] == null || /^_/i.test(key)) { delete proxy[key]; } } diff --git a/backend/src/core/proxy-utils/producers/shadowrocket.js b/backend/src/core/proxy-utils/producers/shadowrocket.js index ce48ff6..5663585 100644 --- a/backend/src/core/proxy-utils/producers/shadowrocket.js +++ b/backend/src/core/proxy-utils/producers/shadowrocket.js @@ -171,7 +171,7 @@ export default function ShadowRocket_Producer() { delete proxy.id; delete proxy.resolved; for (const key in proxy) { - if (proxy[key] == null) { + if (proxy[key] == null || /^_/i.test(key)) { delete proxy[key]; } } diff --git a/backend/src/core/proxy-utils/producers/stash.js b/backend/src/core/proxy-utils/producers/stash.js index 7435a8e..c02093c 100644 --- a/backend/src/core/proxy-utils/producers/stash.js +++ b/backend/src/core/proxy-utils/producers/stash.js @@ -260,7 +260,7 @@ export default function Stash_Producer() { delete proxy.id; delete proxy.resolved; for (const key in proxy) { - if (proxy[key] == null) { + if (proxy[key] == null || /^_/i.test(key)) { delete proxy[key]; } } diff --git a/backend/src/core/proxy-utils/producers/uri.js b/backend/src/core/proxy-utils/producers/uri.js index da93b6a..fa9cfae 100644 --- a/backend/src/core/proxy-utils/producers/uri.js +++ b/backend/src/core/proxy-utils/producers/uri.js @@ -11,7 +11,7 @@ export default function URI_Producer() { delete proxy.id; delete proxy.resolved; for (const key in proxy) { - if (proxy[key] == null) { + if (proxy[key] == null || /^_/i.test(key)) { delete proxy[key]; } } diff --git a/backend/src/restful/download.js b/backend/src/restful/download.js index 3e4f0cb..a90dc83 100644 --- a/backend/src/restful/download.js +++ b/backend/src/restful/download.js @@ -1,7 +1,4 @@ -import { - getUserAgentFromHeaders, - getPlatformFromUserAgent, -} from '@/utils/user-agent'; +import { getPlatformFromHeaders } from '@/utils/user-agent'; import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants'; import { findByName } from '@/utils/database'; import { getFlowHeaders } from '@/utils/flow'; @@ -9,25 +6,39 @@ import $ from '@/core/app'; import { failed } from '@/restful/response'; import { InternalServerError, ResourceNotFoundError } from '@/restful/errors'; import { produceArtifact } from '@/restful/sync'; +// eslint-disable-next-line no-unused-vars +import { isIPv4, isIPv6 } from '@/utils'; +import { getISO } from '@/utils/geo'; +import env from '@/utils/env'; export default function register($app) { $app.get('/download/collection/:name', downloadCollection); $app.get('/download/:name', downloadSubscription); + $app.get( + '/download/collection/:name/api/v1/server/details', + async (req, res) => { + req.query.platform = 'JSON'; + req.query.produceType = 'internal'; + req.query.resultFormat = 'nezha'; + await downloadCollection(req, res); + }, + ); + $app.get('/download/:name/api/v1/server/details', async (req, res) => { + req.query.platform = 'JSON'; + req.query.produceType = 'internal'; + req.query.resultFormat = 'nezha'; + await downloadSubscription(req, res); + }); } async function downloadSubscription(req, res) { let { name } = req.params; name = decodeURIComponent(name); - const userAgent = getUserAgentFromHeaders(req.headers); - const platform = - req.query.target || getPlatformFromUserAgent(userAgent) || 'JSON'; - - $.info( - `正在下载订阅:${name}\ntarget: ${platform}\n来源 User-Agent: ${userAgent.UA}`, - ); + req.query.target || getPlatformFromHeaders(req.headers) || 'JSON'; + $.info(`正在下载订阅:${name}`); let { url, ua, @@ -36,6 +47,7 @@ async function downloadSubscription(req, res) { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy, + resultFormat, } = req.query; if (url) { url = decodeURIComponent(url); @@ -70,7 +82,7 @@ async function downloadSubscription(req, res) { const sub = findByName(allSubs, name); if (sub) { try { - const output = await produceArtifact({ + let output = await produceArtifact({ type: 'subscription', name, platform, @@ -141,6 +153,9 @@ async function downloadSubscription(req, res) { } if (platform === 'JSON') { + if (resultFormat === 'nezha') { + output = nezhaTransform(output); + } res.set('Content-Type', 'application/json;charset=utf-8').send( output, ); @@ -180,20 +195,20 @@ async function downloadCollection(req, res) { let { name } = req.params; name = decodeURIComponent(name); - const userAgent = getUserAgentFromHeaders(req.headers); - const platform = - req.query.target || getPlatformFromUserAgent(userAgent) || 'JSON'; + req.query.target || getPlatformFromHeaders(req.headers) || 'JSON'; const allCols = $.read(COLLECTIONS_KEY); const collection = findByName(allCols, name); - $.info( - `正在下载组合订阅:${name}\ntarget: ${platform}\n来源 User-Agent: ${userAgent.UA}`, - ); + $.info(`正在下载组合订阅:${name}`); - let { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy } = - req.query; + let { + ignoreFailedRemoteSub, + produceType, + includeUnsupportedProxy, + resultFormat, + } = req.query; if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub); @@ -211,7 +226,7 @@ async function downloadCollection(req, res) { if (collection) { try { - const output = await produceArtifact({ + let output = await produceArtifact({ type: 'collection', name, platform, @@ -283,6 +298,9 @@ async function downloadCollection(req, res) { } if (platform === 'JSON') { + if (resultFormat === 'nezha') { + output = nezhaTransform(output); + } res.set('Content-Type', 'application/json;charset=utf-8').send( output, ); @@ -319,3 +337,66 @@ async function downloadCollection(req, res) { ); } } + +function nezhaTransform(output) { + const result = { + code: 0, + message: 'success', + result: [], + }; + output.map((proxy, index) => { + // 如果节点上有数据 就取节点上的数据 + let CountryCode = proxy._geo?.countryCode || proxy._geo?.country; + // 简单判断下 + if (!/^[a-z]{2}$/i.test(CountryCode)) { + CountryCode = getISO(proxy.name); + } + // 简单判断下 + if (/^[a-z]{2}$/i.test(CountryCode)) { + // 如果节点上有数据 就取节点上的数据 + let time = proxy._unavailable ? 0 : Date.now(); + result.result.push({ + id: index, + name: proxy.name, + tag: `${proxy._tag ?? ''}`, + last_active: time, + // 暂时不用处理 现在 VPings App 端的接口支持域名查询 + // 其他场景使用 自己在 Sub-Store 加一步域名解析 + valid_ip: proxy._IP || proxy.server, + ipv4: proxy._IPv4 || proxy.server, + ipv6: proxy._IPv6 || (isIPv6(proxy.server) ? proxy.server : ''), + host: { + Platform: 'Sub-Store', + PlatformVersion: env.version, + CPU: [], + MemTotal: 1024, + DiskTotal: 1024, + SwapTotal: 1024, + Arch: '', + Virtualization: '', + BootTime: time, + CountryCode, // 目前需要 + Version: '', + }, + status: { + CPU: 0, + MemUsed: 0, + SwapUsed: 0, + DiskUsed: 0, + NetInTransfer: 0, + NetOutTransfer: 0, + NetInSpeed: 0, + NetOutSpeed: 0, + Uptime: 0, + Load1: 0, + Load5: 0, + Load15: 0, + TcpConnCount: 0, + UdpConnCount: 0, + ProcessCount: 0, + }, + }); + } + }); + return JSON.stringify(result, null, 2); +}