From 3b85063f7300ca4955739f26d75222b713677be7 Mon Sep 17 00:00:00 2001 From: xream Date: Mon, 21 Apr 2025 18:25:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8D=95=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BA=86=20SUB=5FSTORE=5FMMDB=5FCRON=20=E5=AE=9A=E6=97=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20MMDB.=20ASN:=20SUB=5FSTORE=5FMMDB=5FASN=5F?= =?UTF-8?q?PATH,=20SUB=5FSTORE=5FMMDB=5FASN=5FURL.=20COUNTRY:=20SUB=5FSTOR?= =?UTF-8?q?E=5FMMDB=5FCOUNTRY=5FPATH,=20SUB=5FSTORE=5FMMDB=5FCOUNTRY=5FURL?= =?UTF-8?q?;=20=E8=84=9A=E6=9C=AC=E4=B8=AD=E6=96=B0=E5=A2=9E=20ProxyUtils.?= =?UTF-8?q?downloadFile=20=E6=96=B9=E4=BE=BF=E4=B8=8B=E8=BD=BD=E4=BA=8C?= =?UTF-8?q?=E8=BF=9B=E5=88=B6=E6=96=87=E4=BB=B6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/package.json | 2 +- backend/src/core/proxy-utils/index.js | 3 +- backend/src/restful/index.js | 81 +++++++++++++++++++++++---- backend/src/utils/download.js | 22 ++++++++ scripts/demo.js | 1 + 5 files changed, 96 insertions(+), 13 deletions(-) diff --git a/backend/package.json b/backend/package.json index f7c84a6..b3c03e8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.19.29", + "version": "2.19.30", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.", "main": "src/main.js", "scripts": { diff --git a/backend/src/core/proxy-utils/index.js b/backend/src/core/proxy-utils/index.js index cdaf5f5..91218ec 100644 --- a/backend/src/core/proxy-utils/index.js +++ b/backend/src/core/proxy-utils/index.js @@ -1,7 +1,7 @@ import { Buffer } from 'buffer'; import rs from '@/utils/rs'; import YAML from '@/utils/yaml'; -import download from '@/utils/download'; +import download, { downloadFile } from '@/utils/download'; import { isIPv4, isIPv6, @@ -330,6 +330,7 @@ export const ProxyUtils = { MMDB, Gist, download, + downloadFile, isValidUUID, doh, }; diff --git a/backend/src/restful/index.js b/backend/src/restful/index.js index a03ca35..7f5222d 100644 --- a/backend/src/restful/index.js +++ b/backend/src/restful/index.js @@ -1,7 +1,7 @@ import express from '@/vendor/express'; import $ from '@/core/app'; import migrate from '@/utils/migration'; -import download from '@/utils/download'; +import download, { downloadFile } from '@/utils/download'; import { syncArtifacts, produceArtifact } from '@/restful/sync'; import { gistBackupAction } from '@/restful/miscs'; import { TOKENS_KEY } from '@/constants'; @@ -35,7 +35,7 @@ export default function serve() { const fe_be_path = eval('process.env.SUB_STORE_FRONTEND_BACKEND_PATH'); const fe_path = eval('process.env.SUB_STORE_FRONTEND_PATH'); if (be_prefix || be_merge) { - if(!fe_be_path.startsWith('/')){ + if (!fe_be_path.startsWith('/')) { throw new Error( 'SUB_STORE_FRONTEND_BACKEND_PATH should start with /', ); @@ -48,15 +48,20 @@ export default function serve() { $app.use((req, res, next) => { if (req.path.startsWith(fe_be_path)) { req.url = req.url.replace(fe_be_path, '') || '/'; - if(be_merge && req.url.startsWith('/api/')){ + if (be_merge && req.url.startsWith('/api/')) { req.query['share'] = 'true'; } next(); return; } - const pathname = decodeURIComponent(req._parsedUrl.pathname) || '/'; - if(be_merge && req.path.startsWith('/share/') && req.query.token){ - if (req.method.toLowerCase() !== 'get'){ + const pathname = + decodeURIComponent(req._parsedUrl.pathname) || '/'; + if ( + be_merge && + req.path.startsWith('/share/') && + req.query.token + ) { + if (req.method.toLowerCase() !== 'get') { res.status(405).send('Method not allowed'); return; } @@ -67,14 +72,14 @@ export default function serve() { `/share/${t.type}/${t.name}` === pathname && (t.exp == null || t.exp > Date.now()), ); - if (token){ + if (token) { next(); return; } } - if (be_merge && fe_path && req.path.indexOf('/',1) == -1) { - if (req.path.indexOf('.') == -1){ - req.url = "/index.html" + if (be_merge && fe_path && req.path.indexOf('/', 1) == -1) { + if (req.path.indexOf('.') == -1) { + req.url = '/index.html'; } const express_ = eval(`require("express")`); const mime_ = eval(`require("mime-types")`); @@ -85,7 +90,7 @@ export default function serve() { if (type) { res.set('Content-Type', type); } - } + }, }); staticFileMiddleware(req, res, next); return; @@ -230,6 +235,60 @@ export default function serve() { // 'Asia/Shanghai' // timeZone ); } + const mmdb_cron = eval('process.env.SUB_STORE_MMDB_CRON'); + const countryFile = eval('process.env.SUB_STORE_MMDB_COUNTRY_PATH'); + const countryUrl = eval('process.env.SUB_STORE_MMDB_COUNTRY_URL'); + const asnFile = eval('process.env.SUB_STORE_MMDB_ASN_PATH'); + const asnUrl = eval('process.env.SUB_STORE_MMDB_ASN_URL'); + if (mmdb_cron && ((countryFile && countryUrl) || (asnFile && asnUrl))) { + $.info(`[MMDB CRON] ${mmdb_cron} enabled`); + const { CronJob } = eval(`require("cron")`); + new CronJob( + mmdb_cron, + async function () { + try { + $.info(`[MMDB CRON] ${mmdb_cron} started`); + if (countryFile && countryUrl) { + try { + $.info( + `[MMDB CRON] downloading ${countryUrl} to ${countryFile}`, + ); + await downloadFile(countryUrl, countryFile); + } catch (e) { + $.error( + `[MMDB CRON] ${countryUrl} download failed: ${ + e.message ?? e + }`, + ); + } + } + if (asnFile && asnUrl) { + try { + $.info( + `[MMDB CRON] downloading ${asnUrl} to ${asnFile}`, + ); + await downloadFile(asnUrl, asnFile); + } catch (e) { + $.error( + `[MMDB CRON] ${asnUrl} download failed: ${ + e.message ?? e + }`, + ); + } + } + + $.info(`[MMDB CRON] ${mmdb_cron} finished`); + } catch (e) { + $.error( + `[MMDB CRON] ${mmdb_cron} error: ${e.message ?? e}`, + ); + } + }, // onTick + null, // onComplete + true, // start + // 'Asia/Shanghai' // timeZone + ); + } const path = eval(`require("path")`); const fs = eval(`require("fs")`); const data_url = eval('process.env.SUB_STORE_DATA_URL'); diff --git a/backend/src/utils/download.js b/backend/src/utils/download.js index dadae3f..d886edf 100644 --- a/backend/src/utils/download.js +++ b/backend/src/utils/download.js @@ -273,3 +273,25 @@ export default async function download( } return result; } + +export async function downloadFile(url, file) { + const undici = eval("require('undici')"); + const fs = eval("require('fs')"); + const { pipeline } = eval("require('stream/promises')"); + const { Agent, interceptors, request } = undici; + $.info(`Downloading file...\nURL: ${url}\nFile: ${file}`); + const { body, statusCode } = await request(url, { + dispatcher: new Agent().compose( + interceptors.redirect({ + maxRedirections: 3, + throwOnRedirect: true, + }), + ), + }); + if (statusCode !== 200) + throw new Error(`Failed to download file from ${url}`); + const fileStream = fs.createWriteStream(file); + await pipeline(body, fileStream); + $.info(`File downloaded from ${url} to ${file}`); + return file; +} diff --git a/scripts/demo.js b/scripts/demo.js index bad5656..6d1af3d 100644 --- a/scripts/demo.js +++ b/scripts/demo.js @@ -116,6 +116,7 @@ function operator(proxies = [], targetPlatform, context) { // getISO, // 获取 ISO 3166-1 alpha-2 代码 // Gist, // Gist 类 // download, // 内部的下载方法, 见 backend/src/utils/download.js + // downloadFile, // 下载二进制文件, 见 backend/src/utils/download.js // MMDB, // Node.js 环境 可用于模拟 Surge/Loon 的 $utils.ipasn, $utils.ipaso, $utils.geoip. 具体见 https://t.me/zhetengsha/1269 // isValidUUID, // 辅助判断是否为有效的 UUID // }