servers; $user = $this->user; $uri = ''; foreach ($servers as $item) { $uri .= match ($item['type']) { Server::TYPE_SHADOWSOCKS => self::buildShadowsocks($item['password'], $item), Server::TYPE_VMESS => self::buildVmess($item['password'], $item), Server::TYPE_VLESS => self::buildVless($item['password'], $item), Server::TYPE_TROJAN => self::buildTrojan($item['password'], $item), Server::TYPE_SOCKS => self::buildSocks5($item['password'], $item), Server::TYPE_HTTP => self::buildHttp($item['password'], $item), default => '' }; } return response(base64_encode($uri)) ->header('content-type', 'text/plain') ->header('subscription-userinfo', "upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); } public static function buildShadowsocks($password, $server) { $protocol_settings = $server['protocol_settings']; $password = data_get($server, 'password', $password); $addr = Helper::wrapIPv6($server['host']); $config = [ "shadowsocks={$addr}:{$server['port']}", "method=" . data_get($protocol_settings, 'cipher'), "password={$password}", ]; if (data_get($protocol_settings, 'plugin') && data_get($protocol_settings, 'plugin_opts')) { $plugin = data_get($protocol_settings, 'plugin'); $pluginOpts = data_get($protocol_settings, 'plugin_opts', ''); $parsedOpts = collect(explode(';', $pluginOpts)) ->filter() ->mapWithKeys(function ($pair) { if (!str_contains($pair, '=')) { return []; } [$key, $value] = explode('=', $pair, 2); return [trim($key) => trim($value)]; }) ->all(); if ($plugin === 'obfs') { if (isset($parsedOpts['obfs'])) { $config[] = "obfs={$parsedOpts['obfs']}"; } if (isset($parsedOpts['obfs-host'])) { $config[] = "obfs-host={$parsedOpts['obfs-host']}"; } if (isset($parsedOpts['path'])) { $config[] = "obfs-uri={$parsedOpts['path']}"; } } } self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } public static function buildVmess($uuid, $server) { $protocol_settings = $server['protocol_settings']; $addr = Helper::wrapIPv6($server['host']); $config = [ "vmess={$addr}:{$server['port']}", "method=" . data_get($protocol_settings, 'cipher', 'auto'), "password={$uuid}", ]; self::applyTransportSettings($config, $protocol_settings); self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } public static function buildVless($uuid, $server) { $protocol_settings = $server['protocol_settings']; $addr = Helper::wrapIPv6($server['host']); $config = [ "vless={$addr}:{$server['port']}", 'method=none', "password={$uuid}", ]; self::applyTransportSettings($config, $protocol_settings); if ($flow = data_get($protocol_settings, 'flow')) { $config[] = "vless-flow={$flow}"; } self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } private static function applyTransportSettings(&$config, $settings, bool $nativeTls = false, ?array $tlsData = null) { $tlsMode = (int) data_get($settings, 'tls', 0); $network = data_get($settings, 'network', 'tcp'); $host = null; $isWs = $network === 'ws'; switch ($network) { case 'ws': $config[] = $tlsMode ? 'obfs=wss' : 'obfs=ws'; if ($path = data_get($settings, 'network_settings.path')) { $config[] = "obfs-uri={$path}"; } $host = data_get($settings, 'network_settings.headers.Host'); break; case 'tcp': $headerType = data_get($settings, 'network_settings.header.type', 'tcp'); if ($headerType === 'http') { $config[] = 'obfs=http'; $paths = data_get($settings, 'network_settings.header.request.path', ['/']); $config[] = 'obfs-uri=' . (is_array($paths) ? ($paths[0] ?? '/') : $paths); $hostVal = data_get($settings, 'network_settings.header.request.headers.Host'); $host = is_array($hostVal) ? ($hostVal[0] ?? null) : $hostVal; } elseif ($tlsMode) { $config[] = $nativeTls ? 'over-tls=true' : 'obfs=over-tls'; } break; } switch ($tlsMode) { case 2: // Reality $host = $host ?? data_get($settings, 'reality_settings.server_name'); if ($pubKey = data_get($settings, 'reality_settings.public_key')) { $config[] = "reality-base64-pubkey={$pubKey}"; } if ($shortId = data_get($settings, 'reality_settings.short_id')) { $config[] = "reality-hex-shortid={$shortId}"; } break; case 1: // TLS $resolved = $tlsData ?? (array) data_get($settings, 'tls_settings', []); $allowInsecure = (bool) ($resolved['allow_insecure'] ?? false); $config[] = 'tls-verification=' . ($allowInsecure ? 'false' : 'true'); $host = $host ?? ($resolved['server_name'] ?? null); break; } if ($host) { $config[] = ($nativeTls && !$isWs) ? "tls-host={$host}" : "obfs-host={$host}"; } } private static function applyCommonSettings(&$config, $server) { $config[] = 'fast-open=true'; if ($server['type'] !== Server::TYPE_HTTP) { $config[] = 'udp-relay=true'; } $config[] = "tag={$server['name']}"; } public static function buildTrojan($password, $server) { $protocol_settings = $server['protocol_settings']; $addr = Helper::wrapIPv6($server['host']); $config = [ "trojan={$addr}:{$server['port']}", "password={$password}", ]; $tlsData = [ 'allow_insecure' => data_get($protocol_settings, 'allow_insecure', false), 'server_name' => data_get($protocol_settings, 'server_name'), ]; self::applyTransportSettings($config, $protocol_settings, true, $tlsData); self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } public static function buildSocks5($password, $server) { $protocol_settings = $server['protocol_settings']; $addr = Helper::wrapIPv6($server['host']); $config = [ "socks5={$addr}:{$server['port']}", "username={$password}", "password={$password}", ]; self::applyTransportSettings($config, $protocol_settings, true); self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } public static function buildHttp($password, $server) { $protocol_settings = $server['protocol_settings']; $addr = Helper::wrapIPv6($server['host']); $config = [ "http={$addr}:{$server['port']}", "username={$password}", "password={$password}", ]; self::applyTransportSettings($config, $protocol_settings, true); self::applyCommonSettings($config, $server); return implode(',', array_filter($config)) . "\r\n"; } }