feat: 订阅支持输出哪吒探针兼容响应; 清理输出数据; 增加内部数据字段

This commit is contained in:
xream 2024-04-05 13:37:15 +08:00
parent 2bca669930
commit 33652af516
No known key found for this signature in database
GPG Key ID: 1D2C5225471789F9
8 changed files with 120 additions and 27 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "sub-store", "name": "sub-store",
"version": "2.14.277", "version": "2.14.278",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js", "main": "src/main.js",
"scripts": { "scripts": {

View File

@ -550,13 +550,25 @@ function ResolveDomainOperator({ provider, type: _type, filter, cache }) {
results[p.server], results[p.server],
); );
if (server && port) { if (server && port) {
p._domain = p.server;
p.server = server; p.server = server;
p.port = port; p.port = port;
p.resolved = true; p.resolved = true;
p._IPv4 = p.server;
if (!isIP(p._IP)) {
p._IP = p.server;
} }
} else { } else {
p.resolved = false;
}
} else {
p._domain = p.server;
p.server = results[p.server]; p.server = results[p.server];
p.resolved = true; p.resolved = true;
p[`_${type}`] = p.server;
if (!isIP(p._IP)) {
p._IP = p.server;
}
} }
} else { } else {
p.resolved = false; p.resolved = false;

View File

@ -153,7 +153,7 @@ export default function Clash_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }

View File

@ -168,7 +168,7 @@ export default function ClashMeta_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }

View File

@ -171,7 +171,7 @@ export default function ShadowRocket_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }

View File

@ -260,7 +260,7 @@ export default function Stash_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }

View File

@ -11,7 +11,7 @@ export default function URI_Producer() {
delete proxy.id; delete proxy.id;
delete proxy.resolved; delete proxy.resolved;
for (const key in proxy) { for (const key in proxy) {
if (proxy[key] == null) { if (proxy[key] == null || /^_/i.test(key)) {
delete proxy[key]; delete proxy[key];
} }
} }

View File

@ -1,7 +1,4 @@
import { import { getPlatformFromHeaders } from '@/utils/user-agent';
getUserAgentFromHeaders,
getPlatformFromUserAgent,
} from '@/utils/user-agent';
import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants'; import { COLLECTIONS_KEY, SUBS_KEY } from '@/constants';
import { findByName } from '@/utils/database'; import { findByName } from '@/utils/database';
import { getFlowHeaders } from '@/utils/flow'; import { getFlowHeaders } from '@/utils/flow';
@ -9,25 +6,39 @@ import $ from '@/core/app';
import { failed } from '@/restful/response'; import { failed } from '@/restful/response';
import { InternalServerError, ResourceNotFoundError } from '@/restful/errors'; import { InternalServerError, ResourceNotFoundError } from '@/restful/errors';
import { produceArtifact } from '@/restful/sync'; 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) { export default function register($app) {
$app.get('/download/collection/:name', downloadCollection); $app.get('/download/collection/:name', downloadCollection);
$app.get('/download/:name', downloadSubscription); $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) { async function downloadSubscription(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name); name = decodeURIComponent(name);
const userAgent = getUserAgentFromHeaders(req.headers);
const platform = const platform =
req.query.target || getPlatformFromUserAgent(userAgent) || 'JSON'; req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
$.info(
`正在下载订阅:${name}\ntarget: ${platform}\n来源 User-Agent: ${userAgent.UA}`,
);
$.info(`正在下载订阅:${name}`);
let { let {
url, url,
ua, ua,
@ -36,6 +47,7 @@ async function downloadSubscription(req, res) {
ignoreFailedRemoteSub, ignoreFailedRemoteSub,
produceType, produceType,
includeUnsupportedProxy, includeUnsupportedProxy,
resultFormat,
} = req.query; } = req.query;
if (url) { if (url) {
url = decodeURIComponent(url); url = decodeURIComponent(url);
@ -70,7 +82,7 @@ async function downloadSubscription(req, res) {
const sub = findByName(allSubs, name); const sub = findByName(allSubs, name);
if (sub) { if (sub) {
try { try {
const output = await produceArtifact({ let output = await produceArtifact({
type: 'subscription', type: 'subscription',
name, name,
platform, platform,
@ -141,6 +153,9 @@ async function downloadSubscription(req, res) {
} }
if (platform === 'JSON') { if (platform === 'JSON') {
if (resultFormat === 'nezha') {
output = nezhaTransform(output);
}
res.set('Content-Type', 'application/json;charset=utf-8').send( res.set('Content-Type', 'application/json;charset=utf-8').send(
output, output,
); );
@ -180,20 +195,20 @@ async function downloadCollection(req, res) {
let { name } = req.params; let { name } = req.params;
name = decodeURIComponent(name); name = decodeURIComponent(name);
const userAgent = getUserAgentFromHeaders(req.headers);
const platform = const platform =
req.query.target || getPlatformFromUserAgent(userAgent) || 'JSON'; req.query.target || getPlatformFromHeaders(req.headers) || 'JSON';
const allCols = $.read(COLLECTIONS_KEY); const allCols = $.read(COLLECTIONS_KEY);
const collection = findByName(allCols, name); const collection = findByName(allCols, name);
$.info( $.info(`正在下载组合订阅:${name}`);
`正在下载组合订阅:${name}\ntarget: ${platform}\n来源 User-Agent: ${userAgent.UA}`,
);
let { ignoreFailedRemoteSub, produceType, includeUnsupportedProxy } = let {
req.query; ignoreFailedRemoteSub,
produceType,
includeUnsupportedProxy,
resultFormat,
} = req.query;
if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') { if (ignoreFailedRemoteSub != null && ignoreFailedRemoteSub !== '') {
ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub); ignoreFailedRemoteSub = decodeURIComponent(ignoreFailedRemoteSub);
@ -211,7 +226,7 @@ async function downloadCollection(req, res) {
if (collection) { if (collection) {
try { try {
const output = await produceArtifact({ let output = await produceArtifact({
type: 'collection', type: 'collection',
name, name,
platform, platform,
@ -283,6 +298,9 @@ async function downloadCollection(req, res) {
} }
if (platform === 'JSON') { if (platform === 'JSON') {
if (resultFormat === 'nezha') {
output = nezhaTransform(output);
}
res.set('Content-Type', 'application/json;charset=utf-8').send( res.set('Content-Type', 'application/json;charset=utf-8').send(
output, 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);
}