瀏覽代碼

Add Shadowsocks series backend support

兔姬桑 4 年之前
父節點
當前提交
81b2c9df7d

+ 0 - 2
app/Components/Client/Clash.php

@@ -34,8 +34,6 @@ class Clash
             'cipher' => $server['method'],
             'obfs' => $server['obfs'],
             'protocol' => $server['protocol'],
-            'obfsparam' => $server['obfs_param'],
-            'protocolparam' => $server['protocol_param'],
             'obfs-param' => $server['obfs_param'],
             'protocol-param' => $server['protocol_param'],
             'udp' => $server['udp'],

+ 43 - 0
app/Http/Controllers/Api/WebApi/SSController.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Controllers\Api\WebApi;
+
+use App\Models\Node;
+use Illuminate\Http\JsonResponse;
+
+class SSController extends BaseController
+{
+    // 获取节点信息
+    public function getNodeInfo(Node $node): JsonResponse
+    {
+        $data = [
+            'id'           => $node->id,
+            'method'       => $node->method,
+            'speed_limit'  => $node->getRawOriginal('speed_limit'),
+            'client_limit' => $node->client_limit,
+            'redirect_url' => sysConfig('redirect_url'),
+        ];
+
+        if ($node->single) {
+            $data['port'] = $node->port;
+        }
+
+        return $this->returnData('获取节点信息成功', 'success', 200, $data);
+    }
+
+    // 获取节点可用的用户列表
+    public function getUserList(Node $node): JsonResponse
+    {
+        foreach ($node->users() as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'port'        => $user->port,
+                'passwd'      => $user->passwd,
+                'speed_limit' => $user->getRawOriginal('speed_limit'),
+                'enable'      => $user->enable,
+            ];
+        }
+
+        return $this->returnData('获取用户列表成功', 'success', 200, $data ?? [], ['updateTime' => time()]);
+    }
+}

+ 50 - 0
app/Http/Controllers/Api/WebApi/SSRController.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Http\Controllers\Api\WebApi;
+
+use App\Models\Node;
+use Illuminate\Http\JsonResponse;
+
+class SSRController extends BaseController
+{
+    // 获取节点信息
+    public function getNodeInfo(Node $node): JsonResponse
+    {
+        return $this->returnData('获取节点信息成功', 'success', 200, [
+            'id'           => $node->id,
+            'method'       => $node->method,
+            'protocol'     => $node->protocol,
+            'obfs'         => $node->obfs,
+            'obfs_param'   => $node->obfs_param ?? '',
+            'is_udp'       => $node->is_udp,
+            'speed_limit'  => $node->getRawOriginal('speed_limit'),
+            'client_limit' => $node->client_limit,
+            'single'       => $node->single,
+            'port'         => (string) $node->port,
+            'passwd'       => $node->passwd ?? '',
+            'push_port'    => $node->push_port,
+            'secret'       => $node->auth->secret,
+            'redirect_url' => sysConfig('redirect_url'),
+        ]);
+    }
+
+    // 获取节点可用的用户列表
+    public function getUserList(Node $node): JsonResponse
+    {
+        foreach ($node->users() as $user) {
+            $data[] = [
+                'uid'         => $user->id,
+                'port'        => $user->port,
+                'passwd'      => $user->passwd,
+                'method'      => $user->method,
+                'protocol'    => $user->protocol,
+                'obfs'        => $user->obfs,
+                'obfs_param'  => $node->obfs_param,
+                'speed_limit' => $user->getRawOriginal('speed_limit'),
+                'enable'      => $user->enable,
+            ];
+        }
+
+        return $this->returnData('获取用户列表成功', 'success', 200, $data ?? [], ['updateTime' => time()]);
+    }
+}

+ 1 - 1
app/Http/Controllers/Api/WebApi/V2RayController.php

@@ -29,7 +29,7 @@ class V2RayController extends BaseController
             'pem' => $cert ? $cert->pem : '',
             'v2_license' => (string) sysConfig('v2ray_license'),
             'v2_alter_id' => $node->v2_alter_id,
-            'v2_port' => $node->v2_port,
+            'v2_port' => $node->port,
             'v2_method' => $node->v2_method,
             'v2_net' => $node->v2_net,
             'v2_type' => $node->v2_type,

+ 0 - 56
app/Http/Controllers/Api/WebApi/VNetController.php

@@ -1,56 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Api\WebApi;
-
-use App\Models\Node;
-use Illuminate\Http\JsonResponse;
-
-class VNetController extends BaseController
-{
-    // 获取节点信息
-    public function getNodeInfo(Node $node): JsonResponse
-    {
-        return $this->returnData('获取节点信息成功', 'success', 200, [
-            'id' => $node->id,
-            'method' => $node->method,
-            'protocol' => $node->protocol,
-            'obfs' => $node->obfs,
-            'obfs_param' => $node->obfs_param ?? '',
-            'is_udp' => $node->is_udp,
-            'speed_limit' => $node->getRawOriginal('speed_limit'),
-            'client_limit' => $node->client_limit,
-            'single' => $node->single,
-            'port' => (string) $node->port,
-            'passwd' => $node->passwd ?? '',
-            'push_port' => $node->push_port,
-            'secret' => $node->auth->secret,
-            'redirect_url' => sysConfig('redirect_url'),
-        ]);
-    }
-
-    // 获取节点可用的用户列表
-    public function getUserList(Node $node): JsonResponse
-    {
-        foreach ($node->users() as $user) {
-            $order = $user->orders()->activePlan()->first(); // 取出用户正在使用的套餐
-            if ($order) {
-                $speed_limit = $order->goods->getRawOriginal('speed_limit');
-            } else {
-                $speed_limit = $user->getRawOriginal('speed_limit');
-            }
-            $data[] = [
-                'uid' => $user->id,
-                'port' => $user->port,
-                'passwd' => $user->passwd,
-                'method' => $user->method,
-                'protocol' => $user->protocol,
-                'obfs' => $user->obfs,
-                'obfs_param' => $node->obfs_param,
-                'speed_limit' => $speed_limit,
-                'enable' => $user->enable,
-            ];
-        }
-
-        return $this->returnData('获取用户列表成功', 'success', 200, $data ?? [], ['updateTime' => time()]);
-    }
-}

+ 9 - 9
app/Http/Controllers/ClientController.php

@@ -16,31 +16,31 @@ class ClientController extends Controller
 {
     public function config(string $target, User $user, array $servers)
     {
-        if (strpos($target, 'quantumult%20x') !== false) {
+        if (str_contains($target, 'quantumult%20x')) {
             return $this->quantumultX($user, $servers);
         }
-        if (strpos($target, 'quantumult') !== false) {
+        if (str_contains($target, 'quantumult')) {
             return $this->quantumult($user, $servers);
         }
-        if (strpos($target, 'clash') !== false) {
+        if (str_contains($target, 'clash')) {
             return $this->clash($servers);
         }
-        if (strpos($target, 'surfboard') !== false) {
+        if (str_contains($target, 'surfboard')) {
             return $this->surfboard($user, $servers);
         }
-        if (strpos($target, 'surge') !== false) {
+        if (str_contains($target, 'surge')) {
             return $this->surge($user, $servers);
         }
-        if (strpos($target, 'shadowrocket') !== false) {
+        if (str_contains($target, 'shadowrocket')) {
             return $this->shadowrocket($user, $servers);
         }
-        if (strpos($target, 'v2rayn') !== false) {
+        if (str_contains($target, 'v2rayn')) {
             return $this->v2rayN($user, $servers);
         }
-        if (strpos($target, 'v2rayng') !== false) {
+        if (str_contains($target, 'v2rayng')) {
             return $this->v2rayN($user, $servers);
         }
-        if (strpos($target, 'v2rayu') !== false) {
+        if (str_contains($target, 'v2rayu')) {
             return $this->v2rayN($user, $servers);
         }
 //            if (strpos($target, 'shadowsocks') !== false) {

+ 1 - 8
app/Http/Controllers/TelegramController.php

@@ -14,13 +14,6 @@ class TelegramController extends Controller
 {
     protected $msg;
 
-    public function __construct(Request $request)
-    {
-        if (hash_equals(sysConfig('telegram_token'), $request->input('access_token'))) {
-            abort(500, 'authentication failed');
-        }
-    }
-
     public function webhook(Request $request)
     {
         $this->msg = $this->getMessage($request->input());
@@ -118,7 +111,7 @@ class TelegramController extends Controller
         }
         $telegramService = new TelegramService();
         if (! $oauth = UserOauth::query()->where([
-            'type' => 'telegram',
+            'type'       => 'telegram',
             'identifier' => $msg->chat_id,
         ])->first()) {
             $this->help();

+ 4 - 2
app/Http/Controllers/UserController.php

@@ -454,9 +454,11 @@ class UserController extends Controller
     {
         //$view['articleList'] = Article::type(1)->orderByDesc('sort')->latest()->limit(10)->paginate(5);
         $data = [];
-        if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
+        if (Node::whereType(0)->whereStatus(1)->exists()) {
             $data[] = 'ss';
-            //array_push
+        }
+        if (Node::whereIn('type', [1, 4])->whereStatus(1)->exists()) {
+            $data[] = 'ssr';
         }
         if (Node::whereType(2)->whereStatus(1)->exists()) {
             $data[] = 'v2';

+ 2 - 0
app/Http/Kernel.php

@@ -13,6 +13,7 @@ use App\Http\Middleware\isSecurity;
 use App\Http\Middleware\Permission;
 use App\Http\Middleware\RedirectIfAuthenticated;
 use App\Http\Middleware\SetLocale;
+use App\Http\Middleware\Telegram;
 use App\Http\Middleware\TrimStrings;
 use App\Http\Middleware\TrustProxies;
 use App\Http\Middleware\VerifyCsrfToken;
@@ -103,6 +104,7 @@ class Kernel extends HttpKernel
         'guest' => RedirectIfAuthenticated::class,
         'password.confirm' => RequirePassword::class,
         'signed' => ValidateSignature::class,
+        'telegram'=>Telegram::class,
         'throttle' => ThrottleRequests::class,
         'verified' => EnsureEmailIsVerified::class,
         'webApi' => WebApi::class,

+ 25 - 0
app/Http/Middleware/Telegram.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class Telegram
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param           $request
+     * @param  Closure  $next
+     *
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if (hash_equals(sysConfig('telegram_token'), $request->input('access_token'))) {
+            abort(500, 'authentication failed');
+        }
+
+        return $next($request);
+    }
+}

+ 4 - 5
app/Http/Requests/Admin/NodeRequest.php

@@ -26,8 +26,8 @@ class NodeRequest extends FormRequest
             'sort' => 'required|numeric|between:0,255',
             'is_udp' => 'required|boolean',
             'status' => 'required|boolean',
-            'type' => 'required|numeric|between:1,4',
-            'method' => 'required_if:type,1,4|exists:ss_config,name',
+            'type' => 'required|numeric|between:0,4',
+            'method' => 'required|exists:ss_config,name',
             'protocol' => 'required_if:type,1,4|exists:ss_config,name',
             'protocol_param' => 'nullable|string',
             'obfs' => 'required_if:type,1,4|exists:ss_config,name',
@@ -36,10 +36,9 @@ class NodeRequest extends FormRequest
             'is_subscribe' => 'required|boolean',
             'detection_type' => 'required|numeric|between:0,3',
             'single' => 'required|boolean',
-            'port' => 'required_if:single,1|numeric|between:1,65535|nullable',
-            'passwd' => 'required_if:single,1|string|nullable',
+            'port' => 'required_if:single,1,type,2,type,3|numeric|between:1,65535|nullable',
+            'passwd' => 'exclude_unless:type,1,type,4|required_if:single,1|string|nullable',
             'v2_alter_id' => 'required_if:type,2|numeric|between:0,65535',
-            'v2_port' => 'required_if:type,2|numeric|between:0,65535',
             'v2_method' => 'required_if:type,2',
             'v2_net' => 'required_if:type,2',
             'v2_type' => 'required_if:type,2',

+ 30 - 17
app/Models/Node.php

@@ -73,22 +73,6 @@ class Node extends Model
         return $this->hasOne(NodeAuth::class);
     }
 
-    public function ips(int $type = 4): array
-    {
-        // 使用DDNS的node先通过gethostbyname获取ip地址
-        if ($this->attributes['is_ddns']) { // When ddns is enable, only domain can be used to check the ip
-            $ip = gethostbyname($this->attributes['server']);
-            if (strcmp($ip, $this->attributes['server']) === 0) {
-                Log::warning('获取 【'.$this->attributes['server'].'】 IP失败'.$ip);
-                $ip = '';
-            }
-        } else {
-            $ip = $type === 4 ? $this->attributes['ip'] : $this->attributes['ipv6']; // check the multiple existing of ip
-        }
-
-        return array_map('trim', explode(',', $ip));
-    }
-
     public function level_table(): HasOne
     {
         return $this->hasOne(Level::class, 'level', 'level');
@@ -122,6 +106,22 @@ class Node extends Model
         return false;
     }
 
+    public function ips(int $type = 4): array
+    {
+        // 使用DDNS的node先通过gethostbyname获取ip地址
+        if ($this->attributes['is_ddns']) { // When ddns is enable, only domain can be used to check the ip
+            $ip = gethostbyname($this->attributes['server']);
+            if (strcmp($ip, $this->attributes['server']) === 0) {
+                Log::warning('获取 【'.$this->attributes['server'].'】 IP失败'.$ip);
+                $ip = '';
+            }
+        } else {
+            $ip = $type === 4 ? $this->attributes['ip'] : $this->attributes['ipv6']; // check the multiple existing of ip
+        }
+
+        return array_map('trim', explode(',', $ip));
+    }
+
     public function config(User $user)
     {
         $config = [
@@ -131,10 +131,23 @@ class Node extends Model
             'group' => sysConfig('website_name'),
         ];
         switch ($this->type) {
+            case 0:
+                $config = array_merge($config, [
+                    'type'   => 'shadowsocks',
+                    'method' => $this->method,
+                    'udp'    => $this->is_udp,
+                    'passwd' => $user->passwd,
+                ]);
+                if ($this->single) {
+                    $config['port'] = $this->is_relay ? $this->relay_port : $this->port;
+                } else {
+                    $config['port'] = $user->port;
+                }
+                break;
             case 2:
                 $config = array_merge($config, [
                     'type'        => 'v2ray',
-                    'port'        => $this->is_relay ? $this->relay_port : $this->v2_port,
+                    'port'        => $this->is_relay ? $this->relay_port : $this->port,
                     'uuid'        => $user->vmess_id,
                     'method'      => $this->v2_method,
                     'v2_alter_id' => $this->v2_alter_id,

+ 37 - 0
database/migrations/2021_07_25_124022_drop_v2_port.php

@@ -0,0 +1,37 @@
+<?php
+
+use App\Models\Node;
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class DropV2Port extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        foreach (Node::whereType(2)->get() as $node) {
+            $node->port = $node->v2_port;
+        }
+
+        Schema::table('node', function (Blueprint $table) {
+            $table->dropColumn('v2_port');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('node', function (Blueprint $table) {
+            $table->unsignedSmallInteger('v2_port')->default(0)->comment('V2Ray服务端口')->after('v2_alter_id');
+        });
+    }
+}

+ 100 - 80
resources/views/admin/node/info.blade.php

@@ -147,8 +147,14 @@
                                         <ul class="col-md-9 list-unstyled list-inline">
                                             <li class="list-inline-item">
                                                 <div class="radio-custom radio-primary">
-                                                    <input type="radio" id="shadowsocks" name="type" value="1" checked>
-                                                    <label for="shadowsocks">Shadowsocks(R)</label>
+                                                    <input type="radio" id="shadowsocks" name="type" value="0">
+                                                    <label for="shadowsocks">Shadowsocks</label>
+                                                </div>
+                                            </li>
+                                            <li class="list-inline-item">
+                                                <div class="radio-custom radio-primary">
+                                                    <input type="radio" id="shadowsocksR" name="type" value="1">
+                                                    <label for="shadowsocksR">ShadowsocksR</label>
                                                 </div>
                                             </li>
                                             <li class="list-inline-item">
@@ -173,7 +179,7 @@
                                     </div>
                                     <hr/>
                                     <!-- SS/SSR 设置部分 -->
-                                    <div class="ssr-setting">
+                                    <div class="ss-setting">
                                         <div class="form-group row">
                                             <label for="method" class="col-md-3 col-form-label">加密方式</label>
                                             <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control" name="method" id="method">
@@ -182,38 +188,40 @@
                                                 @endforeach
                                             </select>
                                         </div>
-                                        <div class="form-group row">
-                                            <label for="protocol" class="col-md-3 col-form-label">协议</label>
-                                            <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control" name="protocol" id="protocol">
-                                                @foreach (Helpers::protocolList() as $protocol)
-                                                    <option value="{{$protocol->name}}" @if(!isset($node) && $protocol->is_default) selected @endif>{{$protocol->name}}</option>
-                                                @endforeach
-                                            </select>
-                                        </div>
-                                        <div class="form-group row">
-                                            <label for="protocol_param" class="col-md-3 col-form-label"> 协议参数 </label>
-                                            <input type="text" class="form-control col-md-4" name="protocol_param" id="protocol_param">
-                                        </div>
-                                        <div class="form-group row">
-                                            <label for="obfs" class="col-md-3 col-form-label">混淆</label>
-                                            <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control" name="obfs" id="obfs">
-                                                @foreach (Helpers::obfsList() as $obfs)
-                                                    <option value="{{$obfs->name}}" @if(!isset($node) && $obfs->is_default) selected @endif>{{$obfs->name}}</option>
-                                                @endforeach
-                                            </select>
-                                        </div>
-                                        <div class="form-group row obfs_param">
-                                            <label for="obfs_param" class="col-md-3 col-form-label"> 混淆参数 </label>
-                                            <textarea class="form-control col-md-8" rows="5" name="obfs_param" id="obfs_param"
-                                                      placeholder="混淆不为 [plain] 时可填入参数进行流量伪装;&#13;&#10;混淆为 [http_simple] 时,建议端口为 80;&#13;&#10;混淆为 [tls] 时,建议端口为 443;"></textarea>
-                                        </div>
-                                        <div class="form-group row">
-                                            <label for="compatible" class="col-md-3 col-form-label">兼容SS</label>
-                                            <div class="col-md-9">
-                                                <input type="checkbox" id="compatible" name="compatible" data-plugin="switchery">
+                                        <div class="ssr-setting">
+                                            <div class="form-group row">
+                                                <label for="protocol" class="col-md-3 col-form-label">协议</label>
+                                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control" name="protocol" id="protocol">
+                                                    @foreach (Helpers::protocolList() as $protocol)
+                                                        <option value="{{$protocol->name}}" @if(!isset($node) && $protocol->is_default) selected @endif>{{$protocol->name}}</option>
+                                                    @endforeach
+                                                </select>
                                             </div>
-                                            <div class="text-help offset-md-3">
-                                                如果兼容请在服务端配置协议和混淆时加上<span class="red-700">_compatible</span>
+                                            <div class="form-group row">
+                                                <label for="protocol_param" class="col-md-3 col-form-label"> 协议参数 </label>
+                                                <input type="text" class="form-control col-md-4" name="protocol_param" id="protocol_param">
+                                            </div>
+                                            <div class="form-group row">
+                                                <label for="obfs" class="col-md-3 col-form-label">混淆</label>
+                                                <select data-plugin="selectpicker" data-style="btn-outline btn-primary" class="col-md-5 form-control" name="obfs" id="obfs">
+                                                    @foreach (Helpers::obfsList() as $obfs)
+                                                        <option value="{{$obfs->name}}" @if(!isset($node) && $obfs->is_default) selected @endif>{{$obfs->name}}</option>
+                                                    @endforeach
+                                                </select>
+                                            </div>
+                                            <div class="form-group row obfs_param">
+                                                <label for="obfs_param" class="col-md-3 col-form-label"> 混淆参数 </label>
+                                                <textarea class="form-control col-md-8" rows="5" name="obfs_param" id="obfs_param"
+                                                          placeholder="混淆不为 [plain] 时可填入参数进行流量伪装;&#13;&#10;混淆为 [http_simple] 时,建议端口为 80;&#13;&#10;混淆为 [tls] 时,建议端口为 443;"></textarea>
+                                            </div>
+                                            <div class="form-group row">
+                                                <label for="compatible" class="col-md-3 col-form-label">兼容SS</label>
+                                                <div class="col-md-9">
+                                                    <input type="checkbox" id="compatible" name="compatible" data-plugin="switchery">
+                                                </div>
+                                                <div class="text-help offset-md-3">
+                                                    如果兼容请在服务端配置协议和混淆时加上<span class="red-700">_compatible</span>
+                                                </div>
                                             </div>
                                         </div>
                                         <hr/>
@@ -226,28 +234,28 @@
                                                 如果启用请配置服务端的<span class="red-700"><a href="javascript:showTnc();">additional_ports</a></span>信息
                                             </div>
                                         </div>
-                                        <div class="single-setting hidden">
+                                        <div class="single-setting">
                                             <div class="form-group row">
                                                 <label for="single_port" class="col-md-3 col-form-label">[单] 端口</label>
                                                 <input type="number" class="form-control col-md-4" name="port" value="443" id="single_port"/>
                                                 <span class="text-help offset-md-3"> 推荐80或443,服务端需要配置 <br>
                                                     严格模式:用户的端口无法连接,只能通过以下指定的端口进行连接(<a href="javascript:showPortsOnlyConfig();">如何配置</a>)</span>
                                             </div>
-                                            <div class="form-group row">
+                                            <div class="form-group row ssr-setting">
                                                 <label for="passwd" class="col-md-3 col-form-label">[单] 密码</label>
                                                 <input type="text" class="form-control col-md-4" name="passwd" id="passwd" placeholder="password">
                                             </div>
                                         </div>
                                     </div>
                                     <!-- V2ray 设置部分 -->
-                                    <div class="v2ray-setting hidden">
+                                    <div class="v2ray-setting">
                                         <div class="form-group row">
                                             <label for="v2_alter_id" class="col-md-3 col-form-label">额外ID</label>
                                             <input type="text" class="form-control col-md-4" name="v2_alter_id" value="16" id="v2_alter_id" required/>
                                         </div>
                                         <div class="form-group row">
                                             <label for="v2_port" class="col-md-3 col-form-label">服务端口</label>
-                                            <input type="number" class="form-control col-md-4" name="v2_port" id="v2_port" value="10053" required/>
+                                            <input type="number" class="form-control col-md-4" name="port" id="v2_port" value="10053" required/>
                                         </div>
                                         <div class="form-group row">
                                             <label for="v2_method" class="col-md-3 col-form-label">加密方式</label>
@@ -319,7 +327,7 @@
                                         </div>
                                     </div>
                                     <!-- Trojan 设置部分 -->
-                                    <div class="trojan-setting hidden">
+                                    <div class="trojan-setting">
                                         <div class="form-group row">
                                             <label for="trojan_port" class="col-md-3 col-form-label">连接端口</label>
                                             <input type="number" class="form-control col-md-4" name="port" id="trojan_port" value="443"/>
@@ -369,7 +377,7 @@
                                             <input type="checkbox" id="is_relay" name="is_relay" data-plugin="switchery" onchange="switchSetting('is_relay')">
                                         </div>
                                     </div>
-                                    <div class="relay-setting hidden">
+                                    <div class="relay-setting">
                                         <div class="form-group row">
                                             <label for="relay_port" class="col-md-3 col-form-label"> 中转端口 </label>
                                             <input type="number" class="form-control col-md-4" name="relay_port" id="relay_port" value="443">
@@ -403,6 +411,9 @@
         const string = "{{strtolower(Str::random())}}";
         $(document).ready(function() {
             let v2_path = $('#v2_path');
+            switchSetting('single');
+            switchSetting('is_relay');
+            switchSetting('is_ddns');
             @isset($node)
 
             @if($node->is_ddns)
@@ -439,33 +450,34 @@
             $('#passwd').val('{{$node->passwd}}');
             $("input[name='type'][value='{{$node->type}}']").click();
 
-            @if($node->type == 1 || $node->type == 4)
-            // ShadowsocksR
-            $('#method').selectpicker('val', '{{$node->method}}');
-            $('#protocol').selectpicker('val', '{{$node->protocol}}');
-            $('#protocol_param').val('{{$node->protocol_param}}');
-            $('#obfs').selectpicker('val', '{{$node->obfs}}');
-            $('#obfs_param').val('{{$node->obfs_param}}');
-            @if($node->compatible)
-            $('#compatible').click();
-            @endif
-            @endif
-
-            @if($node->type === 2)
-            //V2Ray
-            $('#v2_alter_id').val('{{$node->v2_alter_id}}');
-            $('#v2_port').val('{{$node->v2_port}}');
-            $('#v2_method').selectpicker('val', '{{$node->v2_method}}');
-            $('#v2_net').selectpicker('val', '{{$node->v2_net}}');
-            $('#v2_type').selectpicker('val', '{{$node->v2_type}}');
-            $('#v2_host').val('{{$node->v2_host}}');
-            $('#v2_sni').val('{{$node->v2_sni}}');
-            v2_path.val('{{$node->v2_path}}');
-            @if($node->v2_tls)
-            $('#v2_tls').click();
-            @endif
-            $('#tls_provider').val('{!! $node->tls_provider !!}');
-            @endif
+            switch ({{$node->type}}) {
+                case 1:
+                case 4:
+                    @if ($node->compatible)
+                    $('#compatible').click();
+                    @endif
+                    $('#protocol').selectpicker('val', '{{$node->protocol}}');
+                    $('#protocol_param').val('{{$node->protocol_param}}');
+                    $('#obfs').selectpicker('val', '{{$node->obfs}}');
+                    $('#obfs_param').val('{{$node->obfs_param}}');
+                case 0:
+                    $('#method').selectpicker('val', '{{$node->method}}');
+                    break;
+                case 2:
+                    //V2Ray
+                    $('#v2_alter_id').val('{{$node->v2_alter_id}}');
+                    $('#v2_method').selectpicker('val', '{{$node->v2_method}}');
+                    $('#v2_net').selectpicker('val', '{{$node->v2_net}}');
+                    $('#v2_type').selectpicker('val', '{{$node->v2_type}}');
+                    $('#v2_host').val('{{$node->v2_host}}');
+                    $('#v2_sni').val('{{$node->v2_sni}}');
+                    v2_path.val('{{$node->v2_path}}');
+                    @if($node->v2_tls)
+                    $('#v2_tls').click();
+                    @endif
+                    $('#tls_provider').val('{!! $node->tls_provider !!}');
+                default:
+            }
 
             @if($node->is_relay)
             // 中转
@@ -474,6 +486,7 @@
             $('#relay_server').val('{{$node->relay_server}}');
             @endif
             @else
+            $('input[name=\'type\'][value=\'0\']').click();
             $('#status').click();
             $('#is_udp').click();
             $('#is_subscribe').click();
@@ -487,12 +500,15 @@
         // ajax同步提交
         function Submit() {
             const type = $('input[name=\'type\']:checked').val();
-            let port = null;
+            let port;
             switch (type) {
-                case '2': break;
+                case '2':
+                    port = $('#v2_port').val();
+                    break;
                 case '3':
                     port = $('#trojan_port').val();
                     break;
+                case '0':
                 case '1':
                 case '4':
                 default:
@@ -534,7 +550,6 @@
                     port: port,
                     passwd: $('#passwd').val(),
                     v2_alter_id: $('#v2_alter_id').val(),
-                    v2_port: $('#v2_port').val(),
                     v2_method: $('#v2_method').val(),
                     v2_net: $('#v2_net').val(),
                     v2_type: $('#v2_type').val(),
@@ -563,13 +578,9 @@
                     let str = '';
                     const errors = data.responseJSON;
                     if ($.isEmptyObject(errors) === false) {
-                        if ($.isEmptyObject(errors.message) === false && typeof errors.message === 'string') {
-                            str += errors.message;
-                        } else {
-                            $.each(errors.errors, function(index, value) {
-                                str += '<li>' + value + '</li>';
-                            });
-                        }
+                        $.each(errors.errors, function(index, value) {
+                            str += '<li>' + value + '</li>';
+                        });
                         swal.fire({title: '提示', html: str, icon: 'error', confirmButtonText: '{{trans('common.confirm')}}'});
                     }
                 },
@@ -585,8 +596,9 @@
                 case 'single':
                     if (check) {
                         $('.single-setting').show();
+                        $('#single_port').attr('required', true);
                     } else {
-                        $('#single_port').val('');
+                        $('#single_port').val('').removeAttr('required');
                         $('#passwd').val('');
                         $('.single-setting').hide();
                     }
@@ -623,24 +635,32 @@
         // 设置服务类型
         $('input:radio[name=\'type\']').on('change', function() {
             const type = parseInt($(this).val());
+            const $ss_setting = $('.ss-setting');
             const $ssr_setting = $('.ssr-setting');
             const $v2ray_setting = $('.v2ray-setting');
             const $trojan_setting = $('.trojan-setting');
             $ssr_setting.hide();
+            $ss_setting.hide();
             $v2ray_setting.hide();
             $trojan_setting.hide();
+            $('#v2_port').removeAttr('required');
+            $('#trojan_port').removeAttr('required');
             switch (type) {
-                case 1:
-                    $ssr_setting.show();
+                case 0:
+                    $ss_setting.show();
                     break;
                 case 2:
                     $v2ray_setting.show();
+                    $('#v2_port').val('').attr('required', true);
                     $('#v2_net').selectpicker('val', 'tcp');
                     break;
                 case 3:
                     $trojan_setting.show();
+                    $('#trojan_port').val('').attr('required', true);
                     break;
+                case 1:
                 case 4:
+                    $ss_setting.show();
                     $ssr_setting.show();
                     break;
                 default:

+ 4 - 1
resources/views/user/help.blade.php

@@ -56,7 +56,10 @@
                                                                     </button>
                                                                     <div class="dropdown-menu" aria-labelledby="sublink" role="menu">
                                                                         @if(in_array('ss',$sub))
-                                                                            <a class="dropdown-item" onclick="linkManager('1')" role="menuitem">只订阅SS/SSR</a>
+                                                                            <a class="dropdown-item" onclick="linkManager('0')" role="menuitem">只订阅SS</a>
+                                                                        @endif
+                                                                        @if(in_array('ssr',$sub))
+                                                                            <a class="dropdown-item" onclick="linkManager('1')" role="menuitem">只订阅SSR (包含ss)</a>
                                                                         @endif
                                                                         @if(in_array('v2',$sub))
                                                                             <a class="dropdown-item" onclick="linkManager('2')" role="menuitem">只订阅V2Ray</a>

+ 15 - 4
routes/api.php

@@ -2,12 +2,23 @@
 
 // 后端WEBAPI
 Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi'], function () {
+    // ss后端WEBAPI V1版
+    Route::group(['prefix' => 'ss/v1'], function () {
+        Route::get('node/{node}', 'SSController@getNodeInfo'); // 获取节点信息
+        Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息
+        Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数
+        Route::get('userList/{node}', 'SSController@getUserList'); // 获取节点可用的用户列表
+        Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志
+        Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则
+        Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录
+    });
+
     // VNet后端WEBAPI V1版
     Route::group(['prefix' => 'web/v1'], function () {
-        Route::get('node/{node}', 'VNetController@getNodeInfo'); // 获取节点信息
+        Route::get('node/{node}', 'SSRController@getNodeInfo'); // 获取节点信息
         Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息
         Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数
-        Route::get('userList/{node}', 'VNetController@getUserList'); // 获取节点可用的用户列表
+        Route::get('userList/{node}', 'SSRController@getUserList'); // 获取节点可用的用户列表
         Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志
         Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则
         Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录
@@ -15,10 +26,10 @@ Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi'], function (
 
     // VNet后端WEBAPI V2版
     Route::group(['prefix' => 'vnet/v2'], function () {
-        Route::get('node/{node}', 'VNetController@getNodeInfo'); // 获取节点信息
+        Route::get('node/{node}', 'SSRController@getNodeInfo'); // 获取节点信息
         Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息
         Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数
-        Route::get('userList/{node}', 'VNetController@getUserList'); // 获取节点可用的用户列表
+        Route::get('userList/{node}', 'SSRController@getUserList'); // 获取节点可用的用户列表
         Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志
         Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则
         Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录

+ 1 - 1
routes/web.php

@@ -9,7 +9,7 @@ if (env('APP_KEY') && config('settings')) {
 }
 
 Route::get('callback/checkout', 'Gateway\PayPal@getCheckout')->name('paypal.checkout'); // 支付回调相关
-Route::post('api/telegram/webhook', 'TelegramController@webhook'); // Telegram fallback
+Route::post('api/telegram/webhook', 'TelegramController@webhook')->middleware('telegram'); // Telegram fallback
 
 // 登录相关
 Route::middleware(['isForbidden', 'affiliate', 'isMaintenance'])->group(function () {