[ 1 => '[Hy]', 2 => '[Hy2]' ], 'vless' => '[vless]', 'shadowsocks' => '[ss]', 'vmess' => '[vmess]', 'trojan' => '[trojan]', 'tuic' => '[tuic]', 'socks' => '[socks]', 'anytls' => '[anytls]' ]; public function subscribe(Request $request) { HookManager::call('client.subscribe.before'); $request->validate([ 'types' => ['nullable', 'string'], 'filter' => ['nullable', 'string'], 'flag' => ['nullable', 'string'], ]); $user = $request->user(); $userService = new UserService(); if (!$userService->isAvailable($user)) { HookManager::call('client.subscribe.unavailable'); return response('', 403, ['Content-Type' => 'text/plain']); } return $this->doSubscribe($request, $user); } public function doSubscribe(Request $request, $user, $servers = null) { if ($servers === null) { $servers = ServerService::getAvailableServers($user); $servers = HookManager::filter('client.subscribe.servers', $servers, $user, $request); } $clientInfo = $this->getClientInfo($request); $requestedTypes = $this->parseRequestedTypes($request->input('types')); $filterKeywords = $this->parseFilterKeywords($request->input('filter')); $protocolClassName = app('protocols.manager')->matchProtocolClassName($clientInfo['flag']) ?? General::class; $serversFiltered = $this->filterServers( servers: $servers, allowedTypes: $requestedTypes, filterKeywords: $filterKeywords ); $this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered)); $serversFiltered = $this->addPrefixToServerName($serversFiltered); // Instantiate the protocol class with filtered servers and client info $protocolInstance = app()->make($protocolClassName, [ 'user' => $user, 'servers' => $serversFiltered, 'clientName' => $clientInfo['name'] ?? null, 'clientVersion' => $clientInfo['version'] ?? null, 'userAgent' => $clientInfo['flag'] ?? null ]); return $protocolInstance->handle(); } /** * Parses the input string for requested server types. */ private function parseRequestedTypes(?string $typeInputString): array { if (blank($typeInputString) || $typeInputString === 'all') { return Server::VALID_TYPES; } $requested = collect(preg_split('/[|,|]+/', $typeInputString)) ->map(fn($type) => trim($type)) ->filter() // Remove empty strings that might result from multiple delimiters ->all(); return array_values(array_intersect($requested, Server::VALID_TYPES)); } /** * Parses the input string for filter keywords. */ private function parseFilterKeywords(?string $filterInputString): ?array { if (blank($filterInputString) || mb_strlen($filterInputString) > 20) { return null; } return collect(preg_split('/[|,|]+/', $filterInputString)) ->map(fn($keyword) => trim($keyword)) ->filter() // Remove empty strings ->all(); } /** * Filters servers based on allowed types and keywords. */ private function filterServers(array $servers, array $allowedTypes, ?array $filterKeywords): array { return collect($servers)->filter(function ($server) use ($allowedTypes, $filterKeywords) { // Condition 1: Server type must be in the list of allowed types if ($allowedTypes && !in_array($server['type'], $allowedTypes)) { return false; // Filter out (don't keep) } // Condition 2: If filterKeywords are provided, at least one keyword must match if (!empty($filterKeywords)) { // Check if $filterKeywords is not empty $keywordMatch = collect($filterKeywords)->contains(function ($keyword) use ($server) { return stripos($server['name'], $keyword) !== false || in_array($keyword, $server['tags'] ?? []); }); if (!$keywordMatch) { return false; // Filter out if no keywords match } } // Keep the server if its type is allowed AND (no filter keywords OR at least one keyword matched) return true; })->values()->all(); } private function getClientInfo(Request $request): array { $flag = strtolower($request->input('flag') ?? $request->header('User-Agent', '')); $clientName = null; $clientVersion = null; if (preg_match('/([a-zA-Z0-9\-_]+)[\/\s]+(v?[0-9]+(?:\.[0-9]+){0,2})/', $flag, $matches)) { $potentialName = strtolower($matches[1]); $clientVersion = preg_replace('/^v/', '', $matches[2]); if (in_array($potentialName, app('protocols.flags'))) { $clientName = $potentialName; } } if (!$clientName) { $flags = collect(app('protocols.flags'))->sortByDesc(fn($f) => strlen($f))->values()->all(); foreach ($flags as $name) { if (stripos($flag, $name) !== false) { $clientName = $name; if (!$clientVersion) { $pattern = '/' . preg_quote($name, '/') . '[\/\s]+(v?[0-9]+(?:\.[0-9]+){0,2})/i'; if (preg_match($pattern, $flag, $vMatches)) { $clientVersion = preg_replace('/^v/', '', $vMatches[1]); } } break; } } } if (!$clientVersion) { if (preg_match('/\/v?(\d+(?:\.\d+){0,2})/', $flag, $matches)) { $clientVersion = $matches[1]; } } return [ 'flag' => $flag, 'name' => $clientName, 'version' => $clientVersion ]; } private function setSubscribeInfoToServers(&$servers, $user, $rejectServerCount = 0) { if (!isset($servers[0])) return; if ($rejectServerCount > 0) { array_unshift($servers, array_merge($servers[0], [ 'name' => "过滤掉{$rejectServerCount}条线路", ])); } if (!(int) admin_setting('show_info_to_server_enable', 0)) return; $useTraffic = $user['u'] + $user['d']; $totalTraffic = $user['transfer_enable']; $remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic); $expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : __('长期有效'); $userService = new UserService(); $resetDay = $userService->getResetDay($user); array_unshift($servers, array_merge($servers[0], [ 'name' => "套餐到期:{$expiredDate}", ])); if ($resetDay) { array_unshift($servers, array_merge($servers[0], [ 'name' => "距离下次重置剩余:{$resetDay} 天", ])); } array_unshift($servers, array_merge($servers[0], [ 'name' => "剩余流量:{$remainingTraffic}", ])); } private function addPrefixToServerName(array $servers): array { if (!admin_setting('show_protocol_to_server_enable', false)) { return $servers; } return collect($servers) ->map(function (array $server): array { $server['name'] = $this->getPrefixedServerName($server); return $server; }) ->all(); } private function getPrefixedServerName(array $server): string { $type = $server['type'] ?? ''; if (!isset(self::PROTOCOL_PREFIXES[$type])) { return $server['name'] ?? ''; } $prefix = is_array(self::PROTOCOL_PREFIXES[$type]) ? self::PROTOCOL_PREFIXES[$type][$server['protocol_settings']['version'] ?? 1] ?? '' : self::PROTOCOL_PREFIXES[$type]; return $prefix . ($server['name'] ?? ''); } }