UNPKG

@herbertgao/surgio

Version:

Generating rules for Surge, Clash, Quantumult like a PRO

370 lines 16.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Artifact = void 0; const events_1 = require("events"); const path_1 = __importDefault(require("path")); const logger_1 = require("@surgio/logger"); const bluebird_1 = __importDefault(require("bluebird")); const fs_extra_1 = __importDefault(require("fs-extra")); const lodash_1 = __importDefault(require("lodash")); const provider_1 = require("../provider"); const types_1 = require("../types"); const utils_1 = require("../utils"); const dns_1 = require("../utils/dns"); const filters_1 = require("../filters"); const flag_1 = require("../utils/flag"); const validators_1 = require("../validators"); const template_1 = require("./template"); const json_template_1 = require("./json-template"); class Artifact extends events_1.EventEmitter { surgioConfig; options; initProgress = 0; artifact; providerNameList; nodeConfigListMap = new Map(); providerMap = new Map(); nodeList = []; customFilters = {}; netflixFilter = filters_1.internalFilters.netflixFilter; youtubePremiumFilter = filters_1.internalFilters.youtubePremiumFilter; constructor(surgioConfig, artifactConfig, options = {}) { super(); this.surgioConfig = surgioConfig; this.options = options; this.artifact = validators_1.ArtifactValidator.parse(artifactConfig); const mainProviderName = this.artifact.provider; const combineProviders = this.artifact.combineProviders || []; this.providerNameList = [mainProviderName].concat(combineProviders); } get isReady() { return this.initProgress === this.providerNameList.length; } getRenderContext(extendRenderContext = {}) { const config = this.surgioConfig; const gatewayConfig = config.gateway; const gatewayToken = gatewayConfig?.viewerToken || gatewayConfig?.accessToken; const { name: artifactName, downloadUrl } = this.artifact; const { nodeList, netflixFilter, youtubePremiumFilter, customFilters } = this; const remoteSnippets = lodash_1.default.keyBy(this.options.remoteSnippetList || [], (item) => item.name); const mergedCustomParams = this.getMergedCustomParams(extendRenderContext); return { proxyTestUrl: config.proxyTestUrl, proxyTestInterval: config.proxyTestInterval, internetTestUrl: config.internetTestUrl, internetTestInterval: config.internetTestInterval, downloadUrl: downloadUrl ? downloadUrl : (0, utils_1.getDownloadUrl)(config.urlBase, artifactName, true, gatewayToken), snippet: (filePath) => { return (0, template_1.loadLocalSnippet)(config.templateDir, filePath); }, remoteSnippets, nodeList, provider: this.artifact.provider, providerName: this.artifact.provider, artifactName, getDownloadUrl: (name) => (0, utils_1.getDownloadUrl)(config.urlBase, name, true, gatewayToken), getUrl: (p) => (0, utils_1.getUrl)(config.publicUrl, p, gatewayToken), getNodeNames: utils_1.getNodeNames, getClashNodes: utils_1.getClashNodes, getClashNodeNames: utils_1.getClashNodeNames, getSingboxNodes: utils_1.getSingboxNodes, getSingboxNodeNames: utils_1.getSingboxNodeNames, getSurgeNodes: utils_1.getSurgeNodes, getSurgeNodeNames: utils_1.getSurgeNodeNames, getSurgeWireguardNodes: utils_1.getSurgeWireguardNodes, getSurfboardNodes: utils_1.getSurfboardNodes, getSurfboardNodeNames: utils_1.getSurfboardNodeNames, getShadowsocksNodes: utils_1.getShadowsocksNodes, getShadowsocksNodesJSON: utils_1.getShadowsocksNodesJSON, getShadowsocksrNodes: utils_1.getShadowsocksrNodes, getV2rayNNodes: utils_1.getV2rayNNodes, getQuantumultXNodes: utils_1.getQuantumultXNodes, getQuantumultXNodeNames: utils_1.getQuantumultXNodeNames, getLoonNodes: utils_1.getLoonNodes, getLoonNodeNames: utils_1.getLoonNodeNames, toUrlSafeBase64: utils_1.toUrlSafeBase64, toBase64: utils_1.toBase64, encodeURIComponent, ...filters_1.internalFilters, netflixFilter, youtubePremiumFilter, customFilters, customParams: mergedCustomParams, }; } async init(params = {}) { if (this.isReady) { throw new Error('Artifact 已经初始化完成'); } this.emit('initArtifact:start', { artifact: this.artifact }); await bluebird_1.default.map(this.providerNameList, async (providerName) => { await this.providerMapper(providerName, params.getNodeListParams); }, { concurrency: (0, utils_1.getNetworkConcurrency)(), }); this.providerNameList.forEach((providerName) => { const nodeConfigList = this.nodeConfigListMap.get(providerName); if (nodeConfigList) { nodeConfigList.forEach((nodeConfig) => { if (nodeConfig) { this.nodeList.push(nodeConfig); } }); } }); this.emit('initArtifact:end', { artifact: this.artifact }); return this; } getMergedCustomParams(extendableCustomParams = {}) { const globalCustomParams = this.surgioConfig.customParams; const { customParams: artifactCustomParams } = this.artifact; const merged = lodash_1.default.merge({}, globalCustomParams, artifactCustomParams, extendableCustomParams); return Object.freeze(merged); } render(templateEngine, extendRenderContext) { if (!this.isReady) { throw new Error('Artifact 还未初始化'); } const targetTemplateEngine = templateEngine || this.options.templateEngine; if (!targetTemplateEngine) { throw new Error('没有可用的 Nunjucks 环境'); } if (this.artifact.templateType === 'json' && !this.artifact.extendTemplate) { throw new Error('JSON 模板需要提供 extendTemplate 函数'); } const renderContext = this.getRenderContext(extendRenderContext); const { templateString, template, templateType } = this.artifact; const result = templateString ? targetTemplateEngine.renderString(templateString, { templateEngine: targetTemplateEngine, ...renderContext, }) : templateType === 'default' ? targetTemplateEngine.render(`${template}.tpl`, { templateEngine: targetTemplateEngine, ...renderContext, }) : (0, json_template_1.render)(this.surgioConfig.templateDir, `${template}.json`, this.artifact.extendTemplate, renderContext); this.emit('renderArtifact', { artifact: this.artifact, result }); return result; } async providerMapper(providerName, getNodeListParams = {}) { const config = this.surgioConfig; const mainProviderName = this.artifact.provider; const filePath = path_1.default.resolve(config.providerDir, `${providerName}.js`); this.emit('initProvider:start', { artifact: this.artifact, providerName, }); if (!fs_extra_1.default.existsSync(filePath)) { throw new Error(`文件 ${filePath} 不存在`); } let provider; let nodeConfigList; try { // eslint-disable-next-line prefer-const provider = await (0, provider_1.getProvider)(providerName, require(filePath)); this.providerMap.set(providerName, provider); } catch (err) /* istanbul ignore next */ { if ((0, utils_1.isSurgioError)(err)) { err.providerName = providerName; err.providerPath = filePath; throw err; } else { throw new utils_1.SurgioError((0, utils_1.isError)(err) ? err.message : '处理 Provider 失败', { cause: err, providerName, providerPath: filePath, }); } } try { try { nodeConfigList = await provider.getNodeList(this.getMergedCustomParams(getNodeListParams)); } catch (err) { if (provider.config.hooks?.onError && (0, utils_1.isError)(err)) { const result = await provider.config.hooks.onError(err); if (Array.isArray(result)) { const adHocProvider = new provider_1.CustomProvider('ad-hoc', { type: types_1.SupportProviderEnum.Custom, nodeList: result, }); nodeConfigList = await adHocProvider.getNodeList(); } else { nodeConfigList = []; } } else { throw err; } } } catch (err) /* istanbul ignore next */ { if ((0, utils_1.isSurgioError)(err)) { err.providerName = providerName; err.providerPath = filePath; throw err; } else { throw new utils_1.SurgioError((0, utils_1.isError)(err) ? err.message : '处理 Provider 失败', { cause: err, providerName, providerPath: filePath, }); } } // Filter 仅使用第一个 Provider 中的定义 if (providerName === mainProviderName) { if (provider.config.netflixFilter !== undefined) { this.netflixFilter = provider.config.netflixFilter; } if (provider.config.youtubePremiumFilter !== undefined) { this.youtubePremiumFilter = provider.config.youtubePremiumFilter; } this.customFilters = { ...this.customFilters, ...config.customFilters, ...provider.config.customFilters, }; } if ((0, filters_1.validateFilter)(provider.config.nodeFilter) && typeof provider.config.nodeFilter === 'object' && provider.config.nodeFilter.supportSort) { nodeConfigList = provider.config.nodeFilter.filter(nodeConfigList); } nodeConfigList = (await bluebird_1.default.map(nodeConfigList, async (nodeConfig) => { let isValid = false; if (nodeConfig.enable === false) { return undefined; } if (!provider.config.nodeFilter) { isValid = true; } else if ((0, filters_1.validateFilter)(provider.config.nodeFilter)) { isValid = typeof provider.config.nodeFilter === 'function' ? provider.config.nodeFilter(nodeConfig) : true; } if (isValid) { if (config.binPath && nodeConfig.type === types_1.NodeTypeEnum.Shadowsocksr && config.binPath[nodeConfig.type]) { nodeConfig.binPath = config.binPath[nodeConfig.type]; nodeConfig.localPort = provider.nextPort; } nodeConfig.provider = provider; nodeConfig.surgeConfig = Object.freeze({ ...config.surgeConfig, ...nodeConfig.surgeConfig, }); nodeConfig.clashConfig = Object.freeze({ ...config.clashConfig, ...nodeConfig.clashConfig, }); nodeConfig.quantumultXConfig = Object.freeze({ ...config.quantumultXConfig, ...nodeConfig.quantumultXConfig, }); nodeConfig.surfboardConfig = Object.freeze({ ...config.surfboardConfig, ...nodeConfig.surfboardConfig, }); if (provider.config.renameNode) { const newName = provider.config.renameNode(nodeConfig.nodeName); if (newName) { nodeConfig.nodeName = newName; } } if (provider.config.addFlag) { // 给节点名加国旗 nodeConfig.nodeName = (0, flag_1.prependFlag)(nodeConfig.nodeName, provider.config.removeExistingFlag); } else if (provider.config.removeExistingFlag) { // 去掉名称中的国旗 nodeConfig.nodeName = (0, flag_1.removeFlag)(nodeConfig.nodeName); } // TCP Fast Open if (typeof nodeConfig.tfo === 'undefined' && provider.config.tfo) { nodeConfig.tfo = provider.config.tfo; } // MPTCP if (typeof nodeConfig.mptcp === 'undefined' && provider.config.mptcp) { nodeConfig.mptcp = provider.config.mptcp; } // ECN if (typeof nodeConfig.ecn === 'undefined' && provider.config.ecn) { nodeConfig.ecn = provider.config.ecn; } // Block QUIC if (typeof nodeConfig.blockQuic === 'undefined' && provider.config.blockQuic) { nodeConfig.blockQuic = provider.config.blockQuic; } // Underlying Proxy if (!nodeConfig.underlyingProxy && provider.config.underlyingProxy) { nodeConfig.underlyingProxy = provider.config.underlyingProxy; } // Check whether the hostname resolves in case of blocking clash's node heurestic if (config?.checkHostname && 'hostname' in nodeConfig && !(0, utils_1.isIp)(nodeConfig.hostname)) { try { const domains = await (0, dns_1.resolveDomain)(nodeConfig.hostname); /* istanbul ignore next */ if (domains.length < 1) { logger_1.logger.warn(`DNS 解析结果中 ${nodeConfig.hostname} 未有对应 IP 地址,将忽略该节点`); return undefined; } /* istanbul ignore next */ else { nodeConfig.hostnameIp = domains; } } catch (err) /* istanbul ignore next */ { logger_1.logger.warn(`${nodeConfig.hostname} 无法解析,将忽略该节点`); return undefined; } } if (config?.resolveHostname && 'hostname' in nodeConfig && !(0, utils_1.isIp)(nodeConfig.hostname)) { /* istanbul ignore next */ if (nodeConfig.hostnameIp) { nodeConfig.hostname = nodeConfig.hostnameIp[0]; } /* istanbul ignore next */ else { try { nodeConfig.hostnameIp = await (0, dns_1.resolveDomain)(nodeConfig.hostname); nodeConfig.hostname = nodeConfig.hostnameIp[0]; } catch (err) { logger_1.logger.warn(`${nodeConfig.hostname} 无法解析,将忽略该域名的解析结果`); } } } return nodeConfig; } return undefined; })).filter((item) => item !== undefined); this.nodeConfigListMap.set(providerName, nodeConfigList); this.initProgress++; this.emit('initProvider:end', { artifact: this.artifact, providerName, provider, }); } } exports.Artifact = Artifact; //# sourceMappingURL=artifact.js.map