UNPKG

surgio

Version:

Generating rules for Surge, Clash, Quantumult like a PRO

150 lines 6.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseSsdConfig = exports.getSsdSubscription = void 0; const assert_1 = __importDefault(require("assert")); const logger_1 = require("@surgio/logger"); const bytes_1 = __importDefault(require("bytes")); const zod_1 = require("zod"); const types_1 = require("../types"); const utils_1 = require("../utils"); const relayable_url_1 = __importDefault(require("../utils/relayable-url")); const Provider_1 = __importDefault(require("./Provider")); const logger = (0, logger_1.createLogger)({ service: 'surgio:SsdProvider', }); class SsdProvider extends Provider_1.default { #originalUrl; udpRelay; constructor(name, config) { super(name, config); const schema = zod_1.z.object({ url: zod_1.z.string().url(), udpRelay: zod_1.z.boolean().optional(), }); const result = schema.safeParse(config); // istanbul ignore next if (!result.success) { throw new utils_1.SurgioError('SsdProvider 配置校验失败', { cause: result.error, providerName: name, }); } this.#originalUrl = result.data.url; this.udpRelay = result.data.udpRelay; this.supportGetSubscriptionUserInfo = true; } // istanbul ignore next get url() { return (0, relayable_url_1.default)(this.#originalUrl, this.config.relayUrl); } getSubscriptionUserInfo = async (params = {}) => { const requestHeaders = this.determineRequestHeaders(params.requestUserAgent, params.requestHeaders); const cacheKey = Provider_1.default.getResourceCacheKey(requestHeaders, this.url); const { subscriptionUserInfo } = await (0, exports.getSsdSubscription)(this.url, requestHeaders, cacheKey, this.udpRelay); if (subscriptionUserInfo) { return subscriptionUserInfo; } return undefined; }; 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.getSsdSubscription)(this.url, requestHeaders, cacheKey, this.udpRelay); if (this.config.hooks?.afterNodeListResponse) { const newList = await this.config.hooks.afterNodeListResponse(nodeList, params); if (newList) { return newList; } } return nodeList; }; getNodeListV2 = async (params = {}) => { const requestHeaders = this.determineRequestHeaders(params.requestUserAgent, params.requestHeaders); const cacheKey = Provider_1.default.getResourceCacheKey(requestHeaders, this.url); const { nodeList, subscriptionUserInfo } = await (0, exports.getSsdSubscription)(this.url, requestHeaders, cacheKey, this.udpRelay); if (this.config.hooks?.afterNodeListResponse) { const newList = await this.config.hooks.afterNodeListResponse(nodeList, params); if (newList) { return { nodeList: newList, subscriptionUserInfo }; } } return { nodeList, subscriptionUserInfo }; }; } exports.default = SsdProvider; // 协议定义:https://github.com/TheCGDF/SSD-Windows/wiki/HTTP%E8%AE%A2%E9%98%85%E5%8D%8F%E5%AE%9A const getSsdSubscription = async (url, requestHeaders, cacheKey, udpRelay) => { (0, assert_1.default)(url, '未指定订阅地址 url'); const response = await Provider_1.default.requestCacheableResource(url, requestHeaders, cacheKey); // istanbul ignore next if (!response.body.startsWith('ssd://')) { throw new Error(`暂仅支持 ssd:// 开头的订阅地址,${url} 无法处理`); } const base64 = response.body.replace('ssd://', ''); const data = JSON.parse((0, utils_1.fromBase64)(base64)); const { servers, traffic_used, traffic_total, expiry } = data; const nodeList = servers.map((server) => (0, exports.parseSsdConfig)(data, server, udpRelay)); if (!response.subscriptionUserInfo && traffic_used && traffic_total && expiry) { response.subscriptionUserInfo = { upload: 0, download: bytes_1.default.parse(`${traffic_used}GB`), total: bytes_1.default.parse(`${traffic_total}GB`), expire: Math.floor(new Date(expiry).getTime() / 1000), }; } return { nodeList: nodeList.filter((item) => item !== undefined), subscriptionUserInfo: response.subscriptionUserInfo, }; }; exports.getSsdSubscription = getSsdSubscription; const parseSsdConfig = (globalConfig, server, udpRelay) => { const { airport, port, encryption, password } = globalConfig; const plugin = server.plugin ?? globalConfig.plugin; const pluginOptsString = server.plugin_options ?? globalConfig.plugin_options; const pluginOpts = pluginOptsString ? (0, utils_1.decodeStringList)(pluginOptsString.split(';')) : {}; // istanbul ignore next if (plugin && !['simple-obfs', 'v2ray-plugin'].includes(plugin)) { logger.warn(`不支持从 SSD 订阅中读取 ${plugin} 类型的 Shadowsocks 节点,节点 ${server.remarks} 会被省略`); return undefined; } // istanbul ignore next if (plugin === 'v2ray-plugin' && pluginOpts.mode.toLowerCase() === 'quic') { logger.warn(`不支持从 SSD 订阅中读取 QUIC 模式的 Shadowsocks 节点,节点 ${server.remarks} 会被省略`); return undefined; } return { type: types_1.NodeTypeEnum.Shadowsocks, nodeName: server.remarks ?? `${airport} ${server.server}:${server.port ?? port}`, hostname: server.server, port: server.port ?? port, method: server.encryption ?? encryption, password: server.password ?? password, udpRelay: udpRelay === true, // obfs-local ...(plugin && plugin === 'simple-obfs' ? { obfs: pluginOpts.obfs, obfsHost: pluginOpts['obfs-host'] || 'www.bing.com', } : null), // v2ray-plugin ...(plugin && plugin === 'v2ray-plugin' ? { obfs: pluginOpts.tls ? 'wss' : 'ws', obfsHost: pluginOpts.host, } : null), }; }; exports.parseSsdConfig = parseSsdConfig; //# sourceMappingURL=SsdProvider.js.map