UNPKG

surgio

Version:

Generating rules for Surge, Clash, Quantumult like a PRO

185 lines 7.53 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseJSONConfig = exports.getV2rayNSubscription = void 0; const assert_1 = __importDefault(require("assert")); const logger_1 = require("@surgio/logger"); const zod_1 = require("zod"); const lodash_1 = __importDefault(require("lodash")); const types_1 = require("../types"); const utils_1 = require("../utils"); const relayable_url_1 = __importDefault(require("../utils/relayable-url")); const ss_1 = require("../utils/ss"); const Provider_1 = __importDefault(require("./Provider")); class V2rayNSubscribeProvider extends Provider_1.default { compatibleMode; skipCertVerify; udpRelay; tls13; #originalUrl; constructor(name, config) { super(name, config); const schema = zod_1.z.object({ url: zod_1.z.string().url(), udpRelay: zod_1.z.boolean().optional(), tls13: zod_1.z.boolean().optional(), compatibleMode: zod_1.z.boolean().optional(), skipCertVerify: zod_1.z.boolean().optional(), }); const result = schema.safeParse(config); // istanbul ignore next if (!result.success) { throw new utils_1.SurgioError('V2rayNSubscribeProvider 配置校验失败', { cause: result.error, providerName: name, }); } this.#originalUrl = result.data.url; this.compatibleMode = result.data.compatibleMode; this.skipCertVerify = result.data.skipCertVerify; this.tls13 = result.data.tls13; this.udpRelay = result.data.udpRelay; } // istanbul ignore next get url() { return (0, relayable_url_1.default)(this.#originalUrl, this.config.relayUrl); } getNodeList = async (params = {}) => { const requestHeaders = this.determineRequestHeaders(params.requestUserAgent, params.requestHeaders); const cacheKey = Provider_1.default.getResourceCacheKey(requestHeaders, this.url); const nodeList = await (0, exports.getV2rayNSubscription)({ url: this.url, skipCertVerify: this.skipCertVerify, tls13: this.tls13, udpRelay: this.udpRelay, isCompatibleMode: this.compatibleMode, requestHeaders, cacheKey, }); if (this.config.hooks?.afterNodeListResponse) { const newList = await this.config.hooks.afterNodeListResponse(nodeList, params); if (newList) { return newList; } } return nodeList; }; getNodeListV2 = async (params = {}) => { const nodeList = await this.getNodeList(params); return { nodeList }; }; } exports.default = V2rayNSubscribeProvider; /** * @see https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) */ const getV2rayNSubscription = async ({ url, isCompatibleMode, skipCertVerify, tls13, udpRelay, requestHeaders, cacheKey, }) => { (0, assert_1.default)(url, '未指定订阅地址 url'); if (isCompatibleMode) { logger_1.logger.warn('运行在兼容模式,请注意生成的节点是否正确。'); } async function requestConfigFromRemote() { const response = await Provider_1.default.requestCacheableResource(url, requestHeaders, cacheKey); const configString = response.body; const configList = (0, utils_1.fromBase64)(configString) .split('\n') .filter((item) => !!item) .filter((item) => { const pick = item.startsWith('vmess://') || item.startsWith('ss://'); if (!pick) { logger_1.logger.warn(`不支持读取 V2rayN 订阅中的节点 ${item},该节点会被省略。`); } return pick; }); return configList .map((item) => { if (item.startsWith('vmess://')) { return (0, exports.parseJSONConfig)((0, utils_1.fromBase64)(item.replace('vmess://', '')), isCompatibleMode, skipCertVerify, udpRelay, tls13); } if (item.startsWith('ss://')) { return { ...(0, ss_1.parseSSUri)(item), udpRelay: udpRelay === true, skipCertVerify: skipCertVerify === true, tls13: tls13 === true, }; } return undefined; }) .filter((item) => !!item); } return await requestConfigFromRemote(); }; exports.getV2rayNSubscription = getV2rayNSubscription; const parseJSONConfig = (json, isCompatibleMode, skipCertVerify, udpRelay, tls13) => { const config = JSON.parse(json); // istanbul ignore next if (!isCompatibleMode && (!config.v || Number(config.v) !== 2)) { throw new Error(`该节点 ${config.ps} 可能不是一个有效的 V2rayN 节点。请参考 https://url.royli.dev/Qtrci 进行排查,或者将解析模式改为兼容模式`); } // istanbul ignore next if (!['tcp', 'ws', 'h2', 'grpc'].includes(config.net)) { logger_1.logger.warn(`不支持读取 network 类型为 ${config.net} 的 Vmess 节点,节点 ${config.ps} 会被省略。`); return undefined; } // istanbul ignore next if (!['none', 'http'].includes(config.type)) { logger_1.logger.warn(`不支持读取 type 类型为 ${config.type} 的 Vmess 节点,节点 ${config.ps} 会被省略。`); return undefined; } const vmessNode = { nodeName: config.ps, type: types_1.NodeTypeEnum.Vmess, hostname: config.add, port: config.port, method: config.scy || 'auto', uuid: config.id, alterId: config.aid || '0', network: config.net, udpRelay: udpRelay === true, tls: config.tls === 'tls', }; if (vmessNode.tls) { if (skipCertVerify) { vmessNode.skipCertVerify = true; } if (tls13) { vmessNode.tls13 = true; } if (config.sni) { vmessNode.sni = config.sni; } } switch (config.net) { case 'tcp': if (config.type === 'http') { vmessNode.network = 'http'; lodash_1.default.set(vmessNode, 'httpOpts.path', [config.path || '/']); lodash_1.default.set(vmessNode, 'httpOpts.method', 'GET'); if (config.host) { lodash_1.default.set(vmessNode, 'httpOpts.headers.Host', config.host); } } break; case 'ws': lodash_1.default.set(vmessNode, 'wsOpts.path', config.path || '/'); if (config.host) { lodash_1.default.set(vmessNode, 'wsOpts.headers.Host', config.host); } break; case 'h2': lodash_1.default.set(vmessNode, 'h2Opts.path', config.path || '/'); if (config.host) { lodash_1.default.set(vmessNode, 'h2Opts.host', [config.host]); } break; case 'grpc': lodash_1.default.set(vmessNode, 'grpcOpts.serviceName', config.path); break; } return vmessNode; }; exports.parseJSONConfig = parseJSONConfig; //# sourceMappingURL=V2rayNSubscribeProvider.js.map