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;
|
|
|
|
|
|
|
|
|
|
class ClientController extends Controller
|
|
|
|
|
{
|
|
|
|
|
|
2024-07-18 18:29:35 -04:00
|
|
|
|
// 支持hy2 的客户端版本列表
|
|
|
|
|
const SupportedHy2ClientVersions = [
|
|
|
|
|
'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',
|
|
|
|
|
'v2rayN' => '6.31',
|
|
|
|
|
'surge' => '2398'
|
|
|
|
|
];
|
|
|
|
|
// allowed types
|
2024-07-18 19:04:02 -04:00
|
|
|
|
const AllowedTypes = ['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)
|
|
|
|
|
{
|
|
|
|
|
// filter types
|
|
|
|
|
$types = $request->input('types', 'all');
|
|
|
|
|
$typesArr = $types === 'all' ? self::AllowedTypes : array_values(array_intersect(explode('|', str_replace(['|', '|', ','], "|", $types)), self::AllowedTypes));
|
|
|
|
|
// filter keyword
|
|
|
|
|
$filterArr = mb_strlen($filter = $request->input('filter')) > 20 ? null : explode("|", str_replace(['|', '|', ','], "|", $filter));
|
|
|
|
|
$flag = strtolower($request->input('flag') ?? $request->header('User-Agent', ''));
|
|
|
|
|
$ip = $request->input('ip', $request->ip());
|
|
|
|
|
// get client version
|
|
|
|
|
$version = preg_match('/\/v?(\d+(\.\d+){0,2})/', $flag, $matches) ? $matches[1] : null;
|
2024-07-18 19:04:02 -04:00
|
|
|
|
$supportHy2 = $version ? collect(self::SupportedHy2ClientVersions)
|
|
|
|
|
->contains(fn($minVersion, $client) => stripos($flag, $client) !== false && $this->versionCompare($version, $minVersion)) : true;
|
2023-11-17 01:44:01 -05:00
|
|
|
|
$user = $request->user;
|
|
|
|
|
// account not expired and is not banned.
|
|
|
|
|
$userService = new UserService();
|
|
|
|
|
if ($userService->isAvailable($user)) {
|
2024-07-18 18:29:35 -04:00
|
|
|
|
// get ip location
|
2023-11-17 01:44:01 -05:00
|
|
|
|
$ip2region = new \Ip2Region();
|
2024-07-18 18:29:35 -04:00
|
|
|
|
$region = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ip2region->memorySearch($ip)['region'] ?? null) : null;
|
|
|
|
|
// get available servers
|
2024-04-09 12:51:03 -04:00
|
|
|
|
$servers = ServerService::getAvailableServers($user);
|
2024-07-18 18:29:35 -04:00
|
|
|
|
// filter servers
|
|
|
|
|
$serversFiltered = $this->serverFilter($servers, $typesArr, $filterArr, $region, $supportHy2);
|
2023-11-17 01:44:01 -05:00
|
|
|
|
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
|
|
|
|
|
$servers = $serversFiltered;
|
2024-07-18 18:29:35 -04:00
|
|
|
|
$this->addPrefixToServerName($servers);
|
2023-11-17 01:44:01 -05:00
|
|
|
|
if ($flag) {
|
|
|
|
|
foreach (array_reverse(glob(app_path('Protocols') . '/*.php')) as $file) {
|
|
|
|
|
$file = 'App\\Protocols\\' . basename($file, '.php');
|
|
|
|
|
$class = new $file($user, $servers);
|
|
|
|
|
$classFlags = explode(',', $class->flag);
|
2024-07-18 18:29:35 -04:00
|
|
|
|
foreach ($classFlags as $classFlag) {
|
|
|
|
|
if (stripos($flag, $classFlag) !== false) {
|
|
|
|
|
return $class->handle();
|
2023-11-17 01:44:01 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$class = new General($user, $servers);
|
|
|
|
|
return $class->handle();
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-18 18:29:35 -04:00
|
|
|
|
/**
|
|
|
|
|
* Summary of serverFilter
|
|
|
|
|
* @param mixed $typesArr
|
|
|
|
|
* @param mixed $filterArr
|
|
|
|
|
* @param mixed $region
|
|
|
|
|
* @param mixed $supportHy2
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
|
|
|
|
private function serverFilter($servers, $typesArr, $filterArr, $region, $supportHy2)
|
|
|
|
|
{
|
|
|
|
|
return collect($servers)->reject(function ($server) use ($typesArr, $filterArr, $region, $supportHy2) {
|
2024-07-18 19:04:02 -04:00
|
|
|
|
if ($server['type'] == "hysteria" && $server['version'] == 2) {
|
|
|
|
|
if(!in_array('hysteria2', $typesArr)){
|
|
|
|
|
return true;
|
|
|
|
|
}elseif(false == $supportHy2){
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-07-18 18:29:35 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($filterArr) {
|
|
|
|
|
foreach ($filterArr as $filter) {
|
|
|
|
|
if (stripos($server['name'], $filter) !== false || in_array($filter, $server['tags'] ?? [])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (strpos($region, '中国') !== false) {
|
|
|
|
|
$excludes = $server['excludes'] ?? [];
|
|
|
|
|
if (empty($excludes)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
foreach ($excludes as $v) {
|
|
|
|
|
$excludeList = explode("|", str_replace(["|", ",", " ", ","], "|", $v));
|
|
|
|
|
foreach ($excludeList as $needle) {
|
|
|
|
|
if (stripos($region, $needle) !== false) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})->values()->all();
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
* add prefix to server name
|
|
|
|
|
*/
|
|
|
|
|
private function addPrefixToServerName(&$servers)
|
|
|
|
|
{
|
|
|
|
|
// 线路名称增加协议类型
|
|
|
|
|
if (admin_setting('show_protocol_to_server_enable')) {
|
|
|
|
|
$typePrefixes = [
|
|
|
|
|
'hysteria' => [1 => '[Hy]', 2 => '[Hy2]'],
|
|
|
|
|
'vless' => '[vless]',
|
|
|
|
|
'shadowsocks' => '[ss]',
|
|
|
|
|
'vmess' => '[vmess]',
|
|
|
|
|
'trojan' => '[trojan]',
|
|
|
|
|
];
|
|
|
|
|
$servers = collect($servers)->map(function ($server) use ($typePrefixes) {
|
|
|
|
|
if (isset($typePrefixes[$server['type']])) {
|
|
|
|
|
$prefix = is_array($typePrefixes[$server['type']]) ? $typePrefixes[$server['type']][$server['version']] : $typePrefixes[$server['type']];
|
|
|
|
|
$server['name'] = $prefix . $server['name'];
|
|
|
|
|
}
|
|
|
|
|
return $server;
|
|
|
|
|
})->toArray();
|
|
|
|
|
}
|
|
|
|
|
}
|
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}",
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断版本号
|
|
|
|
|
*/
|
|
|
|
|
|
2024-07-18 18:29:35 -04:00
|
|
|
|
function versionCompare($version1, $version2)
|
|
|
|
|
{
|
2023-11-23 02:11:22 -05:00
|
|
|
|
if (!preg_match('/^\d+(\.\d+){0,2}/', $version1) || !preg_match('/^\d+(\.\d+){0,2}/', $version2)) {
|
2023-11-17 01:44:01 -05:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
$v1Parts = explode('.', $version1);
|
|
|
|
|
$v2Parts = explode('.', $version2);
|
|
|
|
|
|
|
|
|
|
$maxParts = max(count($v1Parts), count($v2Parts));
|
|
|
|
|
|
|
|
|
|
for ($i = 0; $i < $maxParts; $i++) {
|
2024-07-18 18:29:35 -04:00
|
|
|
|
$part1 = isset($v1Parts[$i]) ? (int) $v1Parts[$i] : 0;
|
|
|
|
|
$part2 = isset($v2Parts[$i]) ? (int) $v2Parts[$i] : 0;
|
2023-11-17 01:44:01 -05:00
|
|
|
|
|
|
|
|
|
if ($part1 < $part2) {
|
|
|
|
|
return false;
|
|
|
|
|
} elseif ($part1 > $part2) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 版本号相等
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|