surgio
Version:
Generating rules for Surge, Clash, Quantumult like a PRO
150 lines • 6.7 kB
JavaScript
;
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