feat: Singbox、clash订阅增加绕过服务器地址规则

1、优化订阅下发代码
2、singbox、clash、meta订阅下发增加绕过服务器地址规则,防止重复代理
This commit is contained in:
xboard
2024-07-19 06:29:35 +08:00
parent 3f264c17ba
commit 163d09c71b
5 changed files with 216 additions and 152 deletions

View File

@@ -8,136 +8,65 @@ use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ClientController extends Controller
{
// 支持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
const AllowedTypes = ['vmess', 'vless', 'trojan', 'hysteria', 'shadowsocks'];
public function subscribe(Request $request)
{
// 节点类型筛选
$allowedTypes = ['vmess', 'vless', 'trojan', 'hysteria', 'hysteria2', 'shadowsocks'];
$types = $request->input('types', "vmess|vless|trojan|hysteria|shadowsocks");
if ($types === "all") $types = implode('|', $allowedTypes);
$typesArr = $types ? collect(explode('|', str_replace(['|','',','], "|" , $types)))->reject(function($type) use ($allowedTypes){
return !in_array($type, $allowedTypes);
})->values()->all() : [];
// 节点关键词筛选字段获取
$filterArr = (mb_strlen($request->input('filter')) > 20) ? null : explode("|" ,str_replace(['|','',','], "|" , $request->input('filter')));
$flag = $request->input('flag') ?? $request->header('User-Agent', '');
$flag = strtolower($flag);
$ip = $request->input('ip') ?? $request->ip();
preg_match('/\/v?(\d+(\.\d+){0,2})/', $flag, $matches);
$version = $matches[1]??null;
$supportedClientVersions = [
'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'
];
$supportHy2 = true;
if ($version) {
$supportHy2 = collect($supportedClientVersions)
->contains(function ($minVersion, $client) use ($flag, $version) {
return stripos($flag, $client) !== false && $this->versionCompare($version, $minVersion);
});
}
// 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;
$supportHy2 = $version
&& collect(self::SupportedHy2ClientVersions)
->contains(fn($minVersion, $client) => stripos($flag, $client) !== false && $this->versionCompare($version, $minVersion));
$user = $request->user;
// account not expired and is not banned.
$userService = new UserService();
if ($userService->isAvailable($user)) {
// 获取IP地址信息
// get ip location
$ip2region = new \Ip2Region();
$geo = filter_var($ip,FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip2region->memorySearch($ip) : [];
$region = $geo['region'] ?? null;
// 获取服务器列表
$region = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? ($ip2region->memorySearch($ip)['region'] ?? null) : null;
// get available servers
$servers = ServerService::getAvailableServers($user);
// 判断不满足,不满足的直接过滤掉
$serversFiltered = collect($servers)->reject(function ($server) use ($typesArr, $filterArr, $region, $supportHy2){
// 过滤类型
if($typesArr){
// 默认过滤掉hysteria2 线路
if($server['type'] == "hysteria" && $server['version'] == 2 && !in_array('hysteria2', $typesArr)
&& !$supportHy2
){
return true;
}
if(!in_array($server['type'], $typesArr) && !($server['type'] == "hysteria" && $server['version'] == 2 && in_array('hysteria2', $typesArr))) return true;
}
// 过滤关键词
if($filterArr){
$rejectFlag = true;
foreach($filterArr as $filter){
if(stripos($server['name'],$filter) !== false
|| in_array($filter, $server['tags'] ?? [])
) $rejectFlag = false;
}
if($rejectFlag) return true;
}
// 过滤地区
if(strpos($region, '中国') !== false){
$excludes = $server['excludes'];
if(blank($excludes)) return false;
foreach($excludes as $v){
$excludeList = explode("|",str_replace(["",","," ",""],"|",$v));
$rejectFlag = false;
foreach($excludeList as $needle){
if(stripos($region, $needle) !== false){
return true;
}
}
};
}
})->values()->all();
// filter servers
$serversFiltered = $this->serverFilter($servers, $typesArr, $filterArr, $region, $supportHy2);
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
$servers = $serversFiltered;
// 线路名称增加协议类型
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']])) {
// 如果是 hysteria 类型,根据版本选择前缀
$prefix = is_array($typePrefixes[$server['type']]) ? $typePrefixes[$server['type']][$server['version']] : $typePrefixes[$server['type']];
// 设置服务器名称
$server['name'] = $prefix . $server['name'];
}
return $server;
})->toArray();
}
$this->addPrefixToServerName($servers);
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);
$isMatch = function() use ($classFlags, $flag){
foreach ($classFlags as $classFlag){
if(stripos($flag, $classFlag) !== false) return true;
foreach ($classFlags as $classFlag) {
if (stripos($flag, $classFlag) !== false) {
return $class->handle();
}
return false;
};
// 判断是否匹配
if ($isMatch()) {
return $class->handle();
}
}
}
@@ -145,16 +74,93 @@ class ClientController extends Controller
return $class->handle();
}
}
/**
* 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) {
if (
$typesArr && (
($server['type'] == "hysteria" && $server['version'] == 2 && !in_array('hysteria2', $typesArr) && !$supportHy2) ||
(!in_array($server['type'], $typesArr) && !($server['type'] == "hysteria" && $server['version'] == 2 && in_array('hysteria2', $typesArr)))
)
) {
return true;
}
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();
}
}
/**
* Summary of setSubscribeInfoToServers
* @param mixed $servers
* @param mixed $user
* @param mixed $rejectServerCount
* @return void
*/
private function setSubscribeInfoToServers(&$servers, $user, $rejectServerCount = 0)
{
if (!isset($servers[0])) return;
if($rejectServerCount > 0){
if (!isset($servers[0]))
return;
if ($rejectServerCount > 0) {
array_unshift($servers, array_merge($servers[0], [
'name' => "去除{$rejectServerCount}不合适线路",
'name' => "过滤掉{$rejectServerCount}条线路",
]));
}
if (!(int)admin_setting('show_info_to_server_enable', 0)) return;
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);
@@ -180,7 +186,8 @@ class ClientController extends Controller
* 判断版本号
*/
function versionCompare($version1, $version2) {
function versionCompare($version1, $version2)
{
if (!preg_match('/^\d+(\.\d+){0,2}/', $version1) || !preg_match('/^\d+(\.\d+){0,2}/', $version2)) {
return false;
}
@@ -190,8 +197,8 @@ class ClientController extends Controller
$maxParts = max(count($v1Parts), count($v2Parts));
for ($i = 0; $i < $maxParts; $i++) {
$part1 = isset($v1Parts[$i]) ? (int)$v1Parts[$i] : 0;
$part2 = isset($v2Parts[$i]) ? (int)$v2Parts[$i] : 0;
$part1 = isset($v1Parts[$i]) ? (int) $v1Parts[$i] : 0;
$part2 = isset($v2Parts[$i]) ? (int) $v2Parts[$i] : 0;
if ($part1 < $part2) {
return false;