From 6ac7f3244267769feca0a4f78ec4b38f0f5f04d1 Mon Sep 17 00:00:00 2001 From: hellcat Date: Thu, 17 Sep 2026 16:46:59 +0800 Subject: [PATCH] no message --- app/routes.php | 2 +- src/Controllers/SubController.php | 76 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/app/routes.php b/app/routes.php index 9f3ed5986..914634df3 100755 --- a/app/routes.php +++ b/app/routes.php @@ -99,7 +99,7 @@ return static function (Slim\App $app): void { $app->get('/oauth/{type}', App\Controllers\OAuthController::class . ':index'); // 通用订阅 $app->get('/sub/{token}/{subtype}', App\Controllers\SubController::class . ':index'); - $app->get('/subv2/{subtype}/{token}', App\Controllers\SubController::class . ':index'); + $app->get('/subv2/{subtype}', App\Controllers\SubController::class . ':indexv2'); // User $app->group('/user', static function (RouteCollectorProxy $group): void { $group->get('', App\Controllers\UserController::class . ':index'); diff --git a/src/Controllers/SubController.php b/src/Controllers/SubController.php index f13457e90..62ecfb54f 100755 --- a/src/Controllers/SubController.php +++ b/src/Controllers/SubController.php @@ -93,4 +93,80 @@ final class SubController extends BaseController ->write($sub_info); } + public function indexv2($request, $response, $args): ResponseInterface + { + $err_msg = '订阅链接无效'; + $subtype = $args['subtype']; + $subtype_list = ['json', 'clash', 'sip008', 'singbox', 'v2rayjson', 'sip002', 'ss', 'v2ray', 'trojan']; + + if ( + !$_ENV['Subscribe'] || + !in_array($subtype, $subtype_list) + ) { + return ResponseHelper::error($response, $err_msg); + } + + // 从 ?token=xxx 获取 Token + $queryParams = $request->getQueryParams(); + $token = $queryParams['token'] ?? ''; + + $token = $this->antiXss->xss_clean($token); + + if ( + $_ENV['enable_rate_limit'] && + ( + !(new RateLimit())->checkRateLimit('sub_ip', $request->getServerParam('REMOTE_ADDR')) || + !(new RateLimit())->checkRateLimit('sub_token', $token) + ) + ) { + return ResponseHelper::error($response, $err_msg); + } + + $link = (new Link())->where('token', $token)->first(); + + if ($link === null || !$link->isValid()) { + return ResponseHelper::error($response, $err_msg); + } + + $user = $link->user(); + $sub_info = Subscribe::getContent($user, $subtype); + + $content_type = match ($subtype) { + 'clash' => 'application/yaml', + 'json', 'sip008', 'singbox', 'v2rayjson' => 'application/json', + default => 'text/plain', + }; + + $sub_details = ' upload=' . $user->u + . '; download=' . $user->d + . '; total=' . $user->transfer_enable + . '; expire=' . strtotime($user->class_expire); + + // Clash specific + $sub_content_disposition = 'attachment; filename=' . $_ENV['app_name_en']; + $sub_profile_update_interval = 6; + $sub_profile_web_page_url = $_ENV['baseUrl']; + + if (Config::obtain('subscribe_log')) { + (new SubscribeLog())->add( + $user, + $subtype, + $this->antiXss->xss_clean($request->getHeaderLine('User-Agent')) + ); + } + + if ($subtype === 'clash') { + return $response->withHeader('Subscription-Userinfo', $sub_details) + ->withHeader('Content-Disposition', $sub_content_disposition) + ->withHeader('Profile-Update-Interval', $sub_profile_update_interval) + ->withHeader('Profile-Web-Page-Url', $sub_profile_web_page_url) + ->withHeader('Content-Type', $content_type) + ->write($sub_info); + } + + return $response->withHeader('Subscription-Userinfo', $sub_details) + ->withHeader('Content-Type', $content_type) + ->write($sub_info); + } + }