2023-11-17 01:44:01 -05:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\V1\Client;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Protocols\General;
|
|
|
|
|
use App\Services\ServerService;
|
|
|
|
|
use App\Services\UserService;
|
|
|
|
|
use App\Utils\Helper;
|
|
|
|
|
use Illuminate\Http\Request;
|
2025-01-06 12:20:11 -05:00
|
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
|
use Illuminate\Validation\Rule;
|
2023-11-17 01:44:01 -05:00
|
|
|
|
|
|
|
|
|
class ClientController extends Controller
|
|
|
|
|
{
|
2025-01-06 12:20:11 -05:00
|
|
|
|
/**
|
|
|
|
|
* Protocol prefix mapping for server names
|
|
|
|
|
*/
|
|
|
|
|
private const PROTOCOL_PREFIXES = [
|
|
|
|
|
'hysteria' => [
|
|
|
|
|
1 => '[Hy]',
|
|
|
|
|
2 => '[Hy2]'
|
|
|
|
|
],
|
|
|
|
|
'vless' => '[vless]',
|
|
|
|
|
'shadowsocks' => '[ss]',
|
|
|
|
|
'vmess' => '[vmess]',
|
|
|
|
|
'trojan' => '[trojan]',
|
|
|
|
|
];
|
2023-11-17 01:44:01 -05:00
|
|
|
|
|
2024-07-18 18:29:35 -04:00
|
|
|
|
// 支持hy2 的客户端版本列表
|
2025-01-06 12:20:11 -05:00
|
|
|
|
private const CLIENT_VERSIONS = [
|
2024-07-18 18:29:35 -04:00
|
|
|
|
'NekoBox' => '1.2.7',
|
|
|
|
|
'sing-box' => '1.5.0',
|
|
|
|
|
'stash' => '2.5.0',
|
|
|
|
|
'Shadowrocket' => '1993',
|
|
|
|
|
'ClashMetaForAndroid' => '2.9.0',
|
|
|
|
|
'Nekoray' => '3.24',
|
|
|
|
|
'verge' => '1.3.8',
|
|
|
|
|
'ClashX Meta' => '1.3.5',
|
|
|
|
|
'Hiddify' => '0.1.0',
|
|
|
|
|
'loon' => '637',
|
2024-11-09 09:16:05 -05:00
|
|
|
|
'v2rayng' => '1.9.5',
|
2024-07-18 18:29:35 -04:00
|
|
|
|
'v2rayN' => '6.31',
|
2025-01-12 10:04:03 -05:00
|
|
|
|
'surge' => '2398',
|
|
|
|
|
'flclash' => '0.8.0'
|
2024-07-18 18:29:35 -04:00
|
|
|
|
];
|
2025-01-06 12:20:11 -05:00
|
|
|
|
|
|
|
|
|
private const ALLOWED_TYPES = ['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks', 'hysteria2'];
|
2023-11-17 01:44:01 -05:00
|
|
|
|
|
2024-07-18 18:29:35 -04:00
|
|
|
|
public function subscribe(Request $request)
|
|
|
|
|
{
|
2025-01-06 12:20:11 -05:00
|
|
|
|
$request->validate([
|
|
|
|
|
'types' => ['nullable', 'string'],
|
|
|
|
|
'filter' => ['nullable', 'string'],
|
|
|
|
|
'flag' => ['nullable', 'string'],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$user = $request->user();
|
2023-11-17 01:44:01 -05:00
|
|
|
|
$userService = new UserService();
|
2025-01-06 12:20:11 -05:00
|
|
|
|
|
|
|
|
|
if (!$userService->isAvailable($user)) {
|
|
|
|
|
return response()->json(['message' => 'Account unavailable'], 403);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$types = $this->getFilteredTypes($request->input('types', 'all'));
|
|
|
|
|
$filterArr = $this->getFilterArray($request->input('filter'));
|
|
|
|
|
$clientInfo = $this->getClientInfo($request);
|
|
|
|
|
|
|
|
|
|
// Get available servers and apply filters
|
|
|
|
|
$servers = ServerService::getAvailableServers($user);
|
|
|
|
|
$serversFiltered = $this->filterServers(
|
|
|
|
|
servers: $servers,
|
|
|
|
|
types: $types,
|
|
|
|
|
filters: $filterArr,
|
|
|
|
|
supportHy2: $clientInfo['supportHy2']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
|
|
|
|
|
$serversFiltered = $this->addPrefixToServerName($serversFiltered);
|
|
|
|
|
|
|
|
|
|
// Handle protocol response
|
|
|
|
|
if ($clientInfo['flag']) {
|
|
|
|
|
foreach (array_reverse(glob(app_path('Protocols') . '/*.php')) as $file) {
|
|
|
|
|
$className = 'App\\Protocols\\' . basename($file, '.php');
|
|
|
|
|
$protocol = new $className($user, $serversFiltered);
|
|
|
|
|
if (
|
|
|
|
|
collect($protocol->getFlags())
|
|
|
|
|
->contains(fn($f) => stripos($clientInfo['flag'], $f) !== false)
|
|
|
|
|
) {
|
|
|
|
|
return $protocol->handle();
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-06 12:20:11 -05:00
|
|
|
|
|
|
|
|
|
return (new General($user, $serversFiltered))->handle();
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
2025-01-06 12:20:11 -05:00
|
|
|
|
|
|
|
|
|
private function getFilteredTypes(string $types): array
|
2024-07-18 18:29:35 -04:00
|
|
|
|
{
|
2025-01-06 12:20:11 -05:00
|
|
|
|
return $types === 'all'
|
|
|
|
|
? self::ALLOWED_TYPES
|
|
|
|
|
: array_values(array_intersect(
|
|
|
|
|
explode('|', str_replace(['|', '|', ','], '|', $types)),
|
|
|
|
|
self::ALLOWED_TYPES
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-07-18 18:29:35 -04:00
|
|
|
|
|
2025-01-06 12:20:11 -05:00
|
|
|
|
private function getFilterArray(?string $filter): ?array
|
|
|
|
|
{
|
|
|
|
|
return mb_strlen($filter ?? '') > 20 ? null :
|
|
|
|
|
explode('|', str_replace(['|', '|', ','], '|', $filter));
|
|
|
|
|
}
|
2024-07-18 18:29:35 -04:00
|
|
|
|
|
2025-01-06 12:20:11 -05:00
|
|
|
|
private function getClientInfo(Request $request): array
|
|
|
|
|
{
|
|
|
|
|
$flag = strtolower($request->input('flag') ?? $request->header('User-Agent', ''));
|
|
|
|
|
preg_match('/\/v?(\d+(\.\d+){0,2})/', $flag, $matches);
|
|
|
|
|
$version = $matches[1] ?? null;
|
|
|
|
|
|
|
|
|
|
$supportHy2 = $version ? $this->checkHy2Support($flag, $version) : true;
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'flag' => $flag,
|
|
|
|
|
'version' => $version,
|
|
|
|
|
'supportHy2' => $supportHy2
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function checkHy2Support(string $flag, string $version): bool
|
|
|
|
|
{
|
2025-01-12 10:04:03 -05:00
|
|
|
|
$result = false;
|
2025-01-06 12:20:11 -05:00
|
|
|
|
foreach (self::CLIENT_VERSIONS as $client => $minVersion) {
|
|
|
|
|
if (stripos($flag, $client) !== false) {
|
2025-01-12 10:04:03 -05:00
|
|
|
|
$result = $result || version_compare($version, $minVersion, '>=');
|
2024-07-18 18:29:35 -04:00
|
|
|
|
}
|
2025-01-06 12:20:11 -05:00
|
|
|
|
}
|
2025-01-12 10:04:03 -05:00
|
|
|
|
return $result || !count(self::CLIENT_VERSIONS);
|
2024-07-18 18:29:35 -04:00
|
|
|
|
}
|
2025-01-06 12:20:11 -05:00
|
|
|
|
|
|
|
|
|
private function filterServers(array $servers, array $types, ?array $filters, bool $supportHy2): array
|
2024-07-18 18:29:35 -04:00
|
|
|
|
{
|
2025-01-06 12:20:11 -05:00
|
|
|
|
return collect($servers)->reject(function ($server) use ($types, $filters, $supportHy2) {
|
|
|
|
|
// Check Hysteria2 compatibility
|
|
|
|
|
if ($server['type'] === 'hysteria' && optional($server['protocol_settings'])['version'] === 2) {
|
|
|
|
|
if (!in_array('hysteria2', $types) || !$supportHy2) {
|
|
|
|
|
return true;
|
2024-07-18 18:29:35 -04:00
|
|
|
|
}
|
2025-01-06 12:20:11 -05:00
|
|
|
|
}
|
|
|
|
|
// Apply custom filters
|
|
|
|
|
if ($filters) {
|
|
|
|
|
return !collect($filters)->contains(function ($filter) use ($server) {
|
|
|
|
|
return stripos($server['name'], $filter) !== false
|
|
|
|
|
|| in_array($filter, $server['tags'] ?? []);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
})->values()->all();
|
2024-07-18 18:29:35 -04:00
|
|
|
|
}
|
2023-11-17 01:44:01 -05:00
|
|
|
|
|
2024-07-18 18:29:35 -04:00
|
|
|
|
/**
|
|
|
|
|
* Summary of setSubscribeInfoToServers
|
|
|
|
|
* @param mixed $servers
|
|
|
|
|
* @param mixed $user
|
|
|
|
|
* @param mixed $rejectServerCount
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2023-11-17 01:44:01 -05:00
|
|
|
|
private function setSubscribeInfoToServers(&$servers, $user, $rejectServerCount = 0)
|
|
|
|
|
{
|
2024-07-18 18:29:35 -04:00
|
|
|
|
if (!isset($servers[0]))
|
|
|
|
|
return;
|
|
|
|
|
if ($rejectServerCount > 0) {
|
2023-11-17 01:44:01 -05:00
|
|
|
|
array_unshift($servers, array_merge($servers[0], [
|
2024-07-18 18:29:35 -04:00
|
|
|
|
'name' => "过滤掉{$rejectServerCount}条线路",
|
2023-11-17 01:44:01 -05:00
|
|
|
|
]));
|
|
|
|
|
}
|
2024-07-18 18:29:35 -04:00
|
|
|
|
if (!(int) admin_setting('show_info_to_server_enable', 0))
|
|
|
|
|
return;
|
2023-11-17 01:44:01 -05:00
|
|
|
|
$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}",
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-01-06 12:20:11 -05:00
|
|
|
|
* Add protocol prefix to server names if enabled in admin settings
|
|
|
|
|
*
|
|
|
|
|
* @param array<int, array<string, mixed>> $servers
|
|
|
|
|
* @return array<int, array<string, mixed>>
|
2023-11-17 01:44:01 -05:00
|
|
|
|
*/
|
2025-01-06 12:20:11 -05:00
|
|
|
|
private function addPrefixToServerName(array $servers): array
|
2024-07-18 18:29:35 -04:00
|
|
|
|
{
|
2025-01-06 12:20:11 -05:00
|
|
|
|
if (!admin_setting('show_protocol_to_server_enable', false)) {
|
|
|
|
|
return $servers;
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-06 12:20:11 -05:00
|
|
|
|
return collect($servers)
|
|
|
|
|
->map(function (array $server): array {
|
|
|
|
|
$server['name'] = $this->getPrefixedServerName($server);
|
|
|
|
|
return $server;
|
|
|
|
|
})
|
|
|
|
|
->all();
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Get server name with protocol prefix
|
|
|
|
|
*
|
|
|
|
|
* @param array<string, mixed> $server
|
|
|
|
|
*/
|
|
|
|
|
private function getPrefixedServerName(array $server): string
|
|
|
|
|
{
|
|
|
|
|
$type = $server['type'] ?? '';
|
|
|
|
|
if (!isset(self::PROTOCOL_PREFIXES[$type])) {
|
|
|
|
|
return $server['name'] ?? '';
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-06 12:20:11 -05:00
|
|
|
|
$prefix = is_array(self::PROTOCOL_PREFIXES[$type])
|
|
|
|
|
? self::PROTOCOL_PREFIXES[$type][$server['protocol_settings']['version'] ?? 1] ?? ''
|
|
|
|
|
: self::PROTOCOL_PREFIXES[$type];
|
|
|
|
|
|
|
|
|
|
return $prefix . ($server['name'] ?? '');
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|