Ver Fonte

Merge pull request #466 from v2board/dev

1.5.2
tokumeikoi há 3 anos atrás
pai
commit
fb449b490e
100 ficheiros alterados com 1295 adições e 662 exclusões
  1. 1 0
      .gitignore
  2. 0 16
      .styleci.yml
  3. 3 1
      app/Console/Commands/CheckOrder.php
  4. 65 0
      app/Console/Commands/CheckServer.php
  5. 3 0
      app/Console/Commands/ResetTraffic.php
  6. 1 3
      app/Console/Commands/Test.php
  7. 1 2
      app/Console/Commands/V2boardStatistics.php
  8. 1 1
      app/Console/Kernel.php
  9. 16 2
      app/Http/Controllers/Admin/ConfigController.php
  10. 8 4
      app/Http/Controllers/Admin/KnowledgeController.php
  11. 2 2
      app/Http/Controllers/Admin/PlanController.php
  12. 1 9
      app/Http/Controllers/Admin/Server/ManageController.php
  13. 14 28
      app/Http/Controllers/Admin/UserController.php
  14. 11 62
      app/Http/Controllers/Client/AppController.php
  15. 10 251
      app/Http/Controllers/Client/ClientController.php
  16. 93 0
      app/Http/Controllers/Client/Protocols/AnXray.php
  17. 60 1
      app/Http/Controllers/Client/Protocols/Clash.php
  18. 96 0
      app/Http/Controllers/Client/Protocols/Passwall.php
  19. 31 1
      app/Http/Controllers/Client/Protocols/QuantumultX.php
  20. 96 0
      app/Http/Controllers/Client/Protocols/SSRPlus.php
  21. 51 2
      app/Http/Controllers/Client/Protocols/Shadowrocket.php
  22. 57 0
      app/Http/Controllers/Client/Protocols/Shadowsocks.php
  23. 52 1
      app/Http/Controllers/Client/Protocols/Surfboard.php
  24. 58 2
      app/Http/Controllers/Client/Protocols/Surge.php
  25. 96 0
      app/Http/Controllers/Client/Protocols/V2rayN.php
  26. 97 0
      app/Http/Controllers/Client/Protocols/V2rayNG.php
  27. 19 1
      app/Http/Controllers/Guest/CommController.php
  28. 1 1
      app/Http/Controllers/Guest/PaymentController.php
  29. 1 0
      app/Http/Controllers/Guest/TelegramController.php
  30. 22 21
      app/Http/Controllers/Passport/AuthController.php
  31. 4 3
      app/Http/Controllers/Passport/CommController.php
  32. 6 6
      app/Http/Controllers/User/CouponController.php
  33. 1 1
      app/Http/Controllers/User/InviteController.php
  34. 10 5
      app/Http/Controllers/User/KnowledgeController.php
  35. 20 20
      app/Http/Controllers/User/OrderController.php
  36. 1 1
      app/Http/Controllers/User/PlanController.php
  37. 7 0
      app/Http/Controllers/User/TelegramController.php
  38. 22 22
      app/Http/Controllers/User/TicketController.php
  39. 19 14
      app/Http/Controllers/User/UserController.php
  40. 4 10
      app/Http/Middleware/User.php
  41. 5 2
      app/Http/Requests/Admin/ConfigSave.php
  42. 1 1
      app/Http/Requests/Admin/ServerV2raySave.php
  43. 1 1
      app/Http/Requests/Admin/UserFetch.php
  44. 1 0
      app/Http/Requests/Admin/UserUpdate.php
  45. 5 5
      app/Http/Requests/Passport/AuthForget.php
  46. 4 4
      app/Http/Requests/Passport/AuthLogin.php
  47. 4 4
      app/Http/Requests/Passport/AuthRegister.php
  48. 2 2
      app/Http/Requests/Passport/CommSendEmailVerify.php
  49. 3 3
      app/Http/Requests/User/OrderSave.php
  50. 4 4
      app/Http/Requests/User/TicketSave.php
  51. 2 3
      app/Http/Requests/User/TicketWithdraw.php
  52. 3 3
      app/Http/Requests/User/UserChangePassword.php
  53. 3 3
      app/Http/Requests/User/UserTransfer.php
  54. 2 2
      app/Http/Requests/User/UserUpdate.php
  55. 1 0
      app/Http/Routes/AdminRoute.php
  56. 84 0
      app/Payments/WechatPayNative.php
  57. 1 1
      app/Providers/AppServiceProvider.php
  58. 63 28
      app/Services/OrderService.php
  59. 7 0
      app/Services/PaymentService.php
  60. 16 5
      app/Services/ServerService.php
  61. 4 1
      app/Services/UserService.php
  62. 0 68
      app/Utils/URLSchemes.php
  63. 1 6
      composer.json
  64. 1 1
      config/app.php
  65. 2 1
      database/install.sql
  66. 14 0
      database/update.sql
  67. 0 21
      package.json
  68. 0 0
      public/assets/admin/umi.css
  69. 0 0
      public/assets/admin/umi.js
  70. 0 0
      public/assets/user/components.async.js
  71. 0 0
      public/assets/user/umi.js
  72. 0 0
      public/theme/v2board/assets/components.async.js
  73. 0 0
      public/theme/v2board/assets/components.chunk.css
  74. 0 0
      public/theme/v2board/assets/env.example.js
  75. 0 0
      public/theme/v2board/assets/static/Simple-Line-Icons.0cb0b9c5.woff2
  76. 0 0
      public/theme/v2board/assets/static/Simple-Line-Icons.78f07e2c.woff
  77. 0 0
      public/theme/v2board/assets/static/Simple-Line-Icons.d2285965.ttf
  78. 0 0
      public/theme/v2board/assets/static/Simple-Line-Icons.ed67e5a3.svg
  79. 0 0
      public/theme/v2board/assets/static/Simple-Line-Icons.f33df365.eot
  80. 0 0
      public/theme/v2board/assets/static/fa-brands-400.14c590d1.eot
  81. 0 0
      public/theme/v2board/assets/static/fa-brands-400.3e1b2a65.woff2
  82. 0 0
      public/theme/v2board/assets/static/fa-brands-400.5e8aa9ea.ttf
  83. 0 0
      public/theme/v2board/assets/static/fa-brands-400.91fd86e5.svg
  84. 0 0
      public/theme/v2board/assets/static/fa-brands-400.df02c782.woff
  85. 0 0
      public/theme/v2board/assets/static/fa-regular-400.285a9d2a.ttf
  86. 0 0
      public/theme/v2board/assets/static/fa-regular-400.5623624d.woff
  87. 0 0
      public/theme/v2board/assets/static/fa-regular-400.6b5ed912.svg
  88. 0 0
      public/theme/v2board/assets/static/fa-regular-400.aa66d0e0.eot
  89. 0 0
      public/theme/v2board/assets/static/fa-regular-400.ac21cac3.woff2
  90. 0 0
      public/theme/v2board/assets/static/fa-solid-900.3ded831d.woff
  91. 0 0
      public/theme/v2board/assets/static/fa-solid-900.42e1fbd2.eot
  92. 0 0
      public/theme/v2board/assets/static/fa-solid-900.649208f1.svg
  93. 0 0
      public/theme/v2board/assets/static/fa-solid-900.896e20e2.ttf
  94. 0 0
      public/theme/v2board/assets/static/fa-solid-900.d6d8d5da.woff2
  95. 0 0
      public/theme/v2board/assets/theme/black.css
  96. 0 0
      public/theme/v2board/assets/theme/darkblue.css
  97. 0 0
      public/theme/v2board/assets/theme/default.css
  98. 0 0
      public/theme/v2board/assets/umi.css
  99. 0 0
      public/theme/v2board/assets/umi.js
  100. 0 0
      public/theme/v2board/assets/vendors.async.js

+ 1 - 0
.gitignore

@@ -18,3 +18,4 @@ composer.phar
 composer.lock
 yarn.lock
 docker-compose.yml
+.DS_Store

+ 0 - 16
.styleci.yml

@@ -1,16 +0,0 @@
-php:
-  preset: laravel
-  enabled:
-    - alpha_ordered_imports
-  disabled:
-    - length_ordered_imports
-    - unused_use
-  finder:
-    not-name:
-      - index.php
-      - server.php
-js:
-  finder:
-    not-name:
-      - webpack.mix.js
-css: true

+ 3 - 1
app/Console/Commands/CheckOrder.php

@@ -42,7 +42,9 @@ class CheckOrder extends Command
      */
     public function handle()
     {
-        $orders = Order::get();
+        ini_set('memory_limit', -1);
+        $orders = Order::whereIn('status', [0, 1])
+            ->get();
         foreach ($orders as $item) {
             $orderService = new OrderService($item);
             switch ($item->status) {

+ 65 - 0
app/Console/Commands/CheckServer.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Services\ServerService;
+use App\Services\TelegramService;
+use App\Utils\CacheKey;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Cache;
+
+class CheckServer extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'check:server';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '节点检查任务';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $this->checkOffline();
+    }
+
+    private function checkOffline()
+    {
+        $serverService = new ServerService();
+        $servers = $serverService->getAllServers();
+        foreach ($servers as $server) {
+            if ($server['parent_id']) continue;
+            if ($server['last_check_at'] && (time() - $server['last_check_at']) > 1800) {
+                $telegramService = new TelegramService();
+                $message = sprintf(
+                    "节点掉线通知\r\n----\r\n节点名称:%s\r\n节点地址:%s\r\n",
+                    $server['name'],
+                    $server['host']
+                );
+                $telegramService->sendMessageWithAdmin($message);
+                Cache::forget(CacheKey::get(sprintf("SERVER_%s_LAST_CHECK_AT", strtoupper($server['type'])), $server->id));
+            }
+        }
+    }
+}

+ 3 - 0
app/Console/Commands/ResetTraffic.php

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
 use App\Models\User;
+use Illuminate\Support\Facades\DB;
 
 class ResetTraffic extends Command
 {
@@ -41,6 +42,7 @@ class ResetTraffic extends Command
      */
     public function handle()
     {
+        DB::beginTransaction();
         $resetTrafficMethod = config('v2board.reset_traffic_method', 0);
         switch ((int)$resetTrafficMethod) {
             // 1 a month
@@ -52,6 +54,7 @@ class ResetTraffic extends Command
                 $this->resetByExpireDay();
                 break;
         }
+        DB::commit();
     }
 
     private function resetByMonthFirstDay():void

+ 1 - 3
app/Console/Commands/Test.php

@@ -2,7 +2,7 @@
 
 namespace App\Console\Commands;
 
-use App\Services\PaymentService;
+use App\Models\Order;
 use Illuminate\Console\Command;
 
 class Test extends Command
@@ -38,7 +38,5 @@ class Test extends Command
      */
     public function handle()
     {
-        $paymentService = new PaymentService('MGate');
-        var_dump($paymentService->form());
     }
 }

+ 1 - 2
app/Console/Commands/V2boardStatistics.php

@@ -55,8 +55,7 @@ class V2boardStatistics extends Command
             ->whereNotIn('status', [0, 2]);
         $orderCount = $builder->count();
         $orderAmount = $builder->sum('total_amount');
-        $builder = $builder->where('commission_balance', '!=', 0)
-            ->where('commission_status', 0);
+        $builder = $builder->where('commission_balance', '!=', 0);
         $commissionCount = $builder->count();
         $commissionAmount = $builder->sum('commission_balance');
         $data = [

+ 1 - 1
app/Console/Kernel.php

@@ -25,7 +25,7 @@ class Kernel extends ConsoleKernel
     protected function schedule(Schedule $schedule)
     {
         // v2board
-        $schedule->command('v2board:statistics')->daily();
+        $schedule->command('v2board:statistics')->dailyAt('0:10');
         // check
         $schedule->command('check:order')->everyMinute();
         $schedule->command('check:commission')->everyMinute();

+ 16 - 2
app/Http/Controllers/Admin/ConfigController.php

@@ -21,6 +21,17 @@ class ConfigController extends Controller
         ]);
     }
 
+    public function getThemeTemplate()
+    {
+        $path = public_path('theme/');
+        $files = array_map(function ($item) use ($path) {
+            return str_replace($path, '', $item);
+        }, glob($path . '*'));
+        return response([
+            'data' => $files
+        ]);
+    }
+
     public function setTelegramWebhook(Request $request)
     {
         $telegramService = new TelegramService($request->input('telegram_bot_token'));
@@ -72,8 +83,10 @@ class ConfigController extends Controller
                 'subscribe' => [
                     'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
                     'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
-                    'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 0),
-                    'surplus_enable' => (int)config('v2board.surplus_enable', 1)
+                    'surplus_enable' => (int)config('v2board.surplus_enable', 1),
+                    'new_order_event_id' => (int)config('v2board.new_order_event_id', 0),
+                    'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0),
+                    'change_order_event_id' => (int)config('v2board.change_order_event_id', 0),
                 ],
                 'pay' => [
                     // alipay
@@ -107,6 +120,7 @@ class ConfigController extends Controller
                     'epay_key' => config('v2board.epay_key'),
                 ],
                 'frontend' => [
+                    'frontend_theme' => config('v2board.frontend_theme', 'v2board'),
                     'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
                     'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
                     'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),

+ 8 - 4
app/Http/Controllers/Admin/KnowledgeController.php

@@ -77,11 +77,15 @@ class KnowledgeController extends Controller
     public function sort(KnowledgeSort $request)
     {
         DB::beginTransaction();
-        foreach ($request->input('knowledge_ids') as $k => $v) {
-            if (!Knowledge::find($v)->update(['sort' => $k + 1])) {
-                DB::rollBack();
-                abort(500, '保存失败');
+        try {
+            foreach ($request->input('knowledge_ids') as $k => $v) {
+                $knowledge = Knowledge::find($v);
+                $knowledge->timestamps = false;
+                $knowledge->update(['sort' => $k + 1]);
             }
+        } catch (\Exception $e) {
+            DB::rollBack();
+            abort(500, '保存失败');
         }
         DB::commit();
         return response([

+ 2 - 2
app/Http/Controllers/Admin/PlanController.php

@@ -52,8 +52,8 @@ class PlanController extends Controller
             // update user group id and transfer
             try {
                 User::where('plan_id', $plan->id)->update([
-                    'group_id' => $plan->group_id,
-                    'transfer_enable' => $plan->transfer_enable * 1073741824
+                    'group_id' => $params['group_id'],
+                    'transfer_enable' => $params['transfer_enable'] * 1073741824
                 ]);
                 $plan->update($params);
             } catch (\Exception $e) {

+ 1 - 9
app/Http/Controllers/Admin/Server/ManageController.php

@@ -15,16 +15,8 @@ class ManageController extends Controller
     public function getNodes(Request $request)
     {
         $serverService = new ServerService();
-        $servers = array_merge(
-            $serverService->getShadowsocksServers(),
-            $serverService->getV2rayServers(),
-            $serverService->getTrojanServers()
-        );
-        $serverService->mergeData($servers);
-        $tmp = array_column($servers, 'sort');
-        array_multisort($tmp, SORT_ASC, $servers);
         return response([
-            'data' => $servers
+            'data' => $serverService->getAllServers()
         ]);
     }
 

+ 14 - 28
app/Http/Controllers/Admin/UserController.php

@@ -7,10 +7,10 @@ use App\Http\Requests\Admin\UserGenerate;
 use App\Http\Requests\Admin\UserSendMail;
 use App\Http\Requests\Admin\UserUpdate;
 use App\Jobs\SendEmailJob;
+use App\Services\UserService;
 use App\Utils\Helper;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
-use App\Models\Order;
 use App\Models\User;
 use App\Models\Plan;
 use Illuminate\Support\Facades\DB;
@@ -81,8 +81,12 @@ class UserController extends Controller
         if (empty($request->input('id'))) {
             abort(500, '参数错误');
         }
+        $user = User::find($request->input('id'));
+        if ($user->invite_user_id) {
+            $user['invite_user'] = User::find($user->invite_user_id);
+        }
         return response([
-            'data' => User::find($request->input('id'))
+            'data' => $user
         ]);
     }
 
@@ -109,6 +113,14 @@ class UserController extends Controller
             }
             $params['group_id'] = $plan->group_id;
         }
+        if ($request->input('invite_user_email')) {
+            $inviteUser = User::where('email', $request->input('invite_user_email'))->first();
+            if ($inviteUser) {
+                $params['invite_user_id'] = $inviteUser->id;
+            }
+        } else {
+            $params['invite_user_id'] = null;
+        }
 
         try {
             $user->update($params);
@@ -265,30 +277,4 @@ class UserController extends Controller
             'data' => true
         ]);
     }
-
-    public function setInviteUser(Request $request)
-    {
-        $request->validate([
-            'user_id' => 'required|integer',
-            'invite_user' => 'required',
-        ], [
-            'user_id.required' => '用户ID不能为空',
-            'user_id.integer' => '用户ID参数有误',
-            'invite_user.required' => '邀请人不能为空'
-        ]);
-
-        $user = User::find($request->input('user_id'));
-        if (!$user) abort(500, '用户不存在');
-        if (strpos($request->input('invite_user'), '@') !== -1) {
-            $inviteUser = User::where('email', $request->input('invite_user'))->first();
-        } else {
-            $inviteUser = User::find($request->input('invite_user'));
-        }
-        if (!$inviteUser) abort(500, '邀请人不存在');
-        $user->invite_user_id = $inviteUser->id;
-
-        return response([
-            'data' => $user->save()
-        ]);
-    }
 }

+ 11 - 62
app/Http/Controllers/Client/AppController.php

@@ -8,6 +8,7 @@ use App\Services\UserService;
 use App\Utils\Clash;
 use Illuminate\Http\Request;
 use App\Models\Server;
+use Illuminate\Support\Facades\File;
 use Symfony\Component\Yaml\Yaml;
 
 class AppController extends Controller
@@ -25,21 +26,27 @@ class AppController extends Controller
             $serverService = new ServerService();
             $servers = $serverService->getAvailableServers($user);
         }
-        $config = Yaml::parseFile(base_path() . '/resources/rules/app.clash.yaml');
+        $defaultConfig = base_path() . '/resources/rules/app.clash.yaml';
+        $customConfig = base_path() . '/resources/rules/custom.app.clash.yaml';
+        if (File::exists($customConfig)) {
+            $config = Yaml::parseFile($customConfig);
+        } else {
+            $config = Yaml::parseFile($defaultConfig);
+        }
         $proxy = [];
         $proxies = [];
 
         foreach ($servers as $item) {
             if ($item['type'] === 'shadowsocks') {
-                array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
+                array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
                 array_push($proxies, $item['name']);
             }
             if ($item['type'] === 'v2ray') {
-                array_push($proxy, Clash::buildVmess($user['uuid'], $item));
+                array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item));
                 array_push($proxies, $item['name']);
             }
             if ($item['type'] === 'trojan') {
-                array_push($proxy, Clash::buildTrojan($user['uuid'], $item));
+                array_push($proxy, Protocols\Clash::buildTrojan($user['uuid'], $item));
                 array_push($proxies, $item['name']);
             }
         }
@@ -84,62 +91,4 @@ class AppController extends Controller
             ]
         ]);
     }
-
-    public function config(Request $request)
-    {
-        if (empty($request->input('server_id'))) {
-            abort(500, '参数错误');
-        }
-        $user = $request->user;
-        if ($user->expired_at < time() && $user->expired_at !== NULL) {
-            abort(500, '订阅计划已过期');
-        }
-        $server = Server::where('show', 1)
-            ->where('id', $request->input('server_id'))
-            ->first();
-        if (!$server) {
-            abort(500, '服务器不存在');
-        }
-        $json = json_decode(self::CLIENT_CONFIG);
-        //socks
-        $json->inbound->port = (int)self::SOCKS_PORT;
-        //http
-        $json->inboundDetour[0]->port = (int)self::HTTP_PORT;
-        //other
-        $json->outbound->settings->vnext[0]->address = (string)$server->host;
-        $json->outbound->settings->vnext[0]->port = (int)$server->port;
-        $json->outbound->settings->vnext[0]->users[0]->id = (string)$user->uuid;
-        $json->outbound->settings->vnext[0]->users[0]->alterId = (int)$server->alter_id;
-        $json->outbound->settings->vnext[0]->remark = (string)$server->name;
-        $json->outbound->streamSettings->network = $server->network;
-        if ($server->networkSettings) {
-            switch ($server->network) {
-                case 'tcp':
-                    $json->outbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
-                    break;
-                case 'kcp':
-                    $json->outbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
-                    break;
-                case 'ws':
-                    $json->outbound->streamSettings->wsSettings = json_decode($server->networkSettings);
-                    break;
-                case 'http':
-                    $json->outbound->streamSettings->httpSettings = json_decode($server->networkSettings);
-                    break;
-                case 'domainsocket':
-                    $json->outbound->streamSettings->dsSettings = json_decode($server->networkSettings);
-                    break;
-                case 'quic':
-                    $json->outbound->streamSettings->quicSettings = json_decode($server->networkSettings);
-                    break;
-            }
-        }
-        if ($request->input('is_global')) {
-            $json->routing->settings->rules[0]->outboundTag = 'proxy';
-        }
-        if ($server->tls) {
-            $json->outbound->streamSettings->security = "tls";
-        }
-        die(json_encode($json, JSON_UNESCAPED_UNICODE));
-    }
 }

+ 10 - 251
app/Http/Controllers/Client/ClientController.php

@@ -2,18 +2,10 @@
 
 namespace App\Http\Controllers\Client;
 
+use App\Http\Controllers\Client\Protocols\V2rayN;
 use App\Http\Controllers\Controller;
 use App\Services\ServerService;
-use App\Utils\Clash;
-use App\Utils\QuantumultX;
-use App\Utils\Shadowrocket;
-use App\Utils\Surge;
-use App\Utils\Surfboard;
-use App\Utils\URLSchemes;
 use Illuminate\Http\Request;
-use App\Models\Server;
-use App\Utils\Helper;
-use Symfony\Component\Yaml\Yaml;
 use App\Services\UserService;
 
 class ClientController extends Controller
@@ -32,251 +24,18 @@ class ClientController extends Controller
             $serverService = new ServerService();
             $servers = $serverService->getAvailableServers($user);
             if ($flag) {
-                if (strpos($flag, 'quantumult%20x') !== false) {
-                    die($this->quantumultX($user, $servers));
-                }
-                if (strpos($flag, 'quantumult') !== false) {
-                    die($this->quantumult($user, $servers));
-                }
-                if (strpos($flag, 'clash') !== false) {
-                    die($this->clash($user, $servers));
-                }
-                if (strpos($flag, 'surfboard') !== false) {
-                    die($this->surfboard($user, $servers));
-                }
-                if (strpos($flag, 'surge') !== false) {
-                    die($this->surge($user, $servers));
-                }
-                if (strpos($flag, 'shadowrocket') !== false) {
-                    die($this->shadowrocket($user, $servers));
-                }
-                if (strpos($flag, 'shadowsocks') !== false) {
-                    die($this->shaodowsocksSIP008($user, $servers));
-                }
-            }
-            die($this->origin($user, $servers));
-        }
-    }
-    // TODO: Ready to stop support
-    private function quantumult($user, $servers = [])
-    {
-        $uri = '';
-        header('subscription-userinfo: upload=' . $user['u'] . '; download=' . $user['d'] . ';total=' . $user['transfer_enable']);
-        foreach ($servers as $item) {
-            if ($item['type'] === 'v2ray') {
-                $str = '';
-                $str .= $item['name'] . '= vmess, ' . $item['host'] . ', ' . $item['port'] . ', chacha20-ietf-poly1305, "' . $user['uuid'] . '", over-tls=' . ($item['tls'] ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
-                if ($item['network'] === 'ws') {
-                    $str .= ', obfs=ws';
-                    if ($item['networkSettings']) {
-                        $wsSettings = json_decode($item['networkSettings'], true);
-                        if (isset($wsSettings['path'])) $str .= ', obfs-path="' . $wsSettings['path'] . '"';
-                        if (isset($wsSettings['headers']['Host'])) $str .= ', obfs-header="Host:' . $wsSettings['headers']['Host'] . '"';
+                foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
+                    $file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
+                    $class = new $file($user, $servers);
+                    if (strpos($flag, $class->flag) !== false) {
+                        die($class->handle());
                     }
                 }
-                $uri .= "vmess://" . base64_encode($str) . "\r\n";
-            }
-        }
-        return base64_encode($uri);
-    }
-
-    private function shadowrocket($user, $servers = [])
-    {
-        $uri = '';
-        //display remaining traffic and expire date
-        $upload = round($user['u'] / (1024*1024*1024), 2);
-        $download = round($user['d'] / (1024*1024*1024), 2);
-        $totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
-        $expiredDate = date('Y-m-d', $user['expired_at']);
-        $uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                $uri .= Shadowrocket::buildShadowsocks($user['uuid'], $item);
-            }
-            if ($item['type'] === 'v2ray') {
-                $uri .= Shadowrocket::buildVmess($user['uuid'], $item);
-            }
-            if ($item['type'] === 'trojan') {
-                $uri .= Shadowrocket::buildTrojan($user['uuid'], $item);
-            }
-        }
-        return base64_encode($uri);
-    }
-
-    private function quantumultX($user, $servers = [])
-    {
-        $uri = '';
-        header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                $uri .= QuantumultX::buildShadowsocks($user['uuid'], $item);
-            }
-            if ($item['type'] === 'v2ray') {
-                $uri .= QuantumultX::buildVmess($user['uuid'], $item);
             }
-            if ($item['type'] === 'trojan') {
-                $uri .= QuantumultX::buildTrojan($user['uuid'], $item);
-            }
-        }
-        return base64_encode($uri);
-    }
-
-    private function origin($user, $servers = [])
-    {
-        $uri = '';
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                $uri .= URLSchemes::buildShadowsocks($item, $user);
-            }
-            if ($item['type'] === 'v2ray') {
-                $uri .= URLSchemes::buildVmess($item, $user);
-            }
-            if ($item['type'] === 'trojan') {
-                $uri .= URLSchemes::buildTrojan($item, $user);
-            }
-        }
-        return base64_encode($uri);
-    }
-
-    private function shaodowsocksSIP008($user, $servers = [])
-    {
-        $configs = [];
-        $subs = [];
-        $subs['servers'] = [];
-        $subs['bytes_used'] = '';
-        $subs['bytes_remaining'] = '';
-
-        $bytesUsed = $user['u'] + $user['d'];
-        $bytesRemaining = $user['transfer_enable'] - $bytesUsed;
-
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                array_push($configs, URLSchemes::buildShadowsocksSIP008($item, $user));
-            }
-        }
-
-        $subs['version'] = 1;
-        $subs['bytes_used'] = $bytesUsed;
-        $subs['bytes_remaining'] = $bytesRemaining;
-        $subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
-
-        return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
-    }
-
-    private function surge($user, $servers = [])
-    {
-        $proxies = '';
-        $proxyGroup = '';
-
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                // [Proxy]
-                $proxies .= Surge::buildShadowsocks($user['uuid'], $item);
-                // [Proxy Group]
-                $proxyGroup .= $item['name'] . ', ';
-            }
-            if ($item['type'] === 'v2ray') {
-                // [Proxy]
-                $proxies .= Surge::buildVmess($user['uuid'], $item);
-                // [Proxy Group]
-                $proxyGroup .= $item['name'] . ', ';
-            }
-            if ($item['type'] === 'trojan') {
-                // [Proxy]
-                $proxies .= Surge::buildTrojan($user['uuid'], $item);
-                // [Proxy Group]
-                $proxyGroup .= $item['name'] . ', ';
-            }
-        }
-
-        $defaultConfig = base_path() . '/resources/rules/default.surge.conf';
-        $customConfig = base_path() . '/resources/rules/custom.surge.conf';
-        if (\File::exists($customConfig)) {
-            $config = file_get_contents("$customConfig");
-        } else {
-            $config = file_get_contents("$defaultConfig");
-        }
-
-        // Subscription link
-        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
-
-        $config = str_replace('$subs_link', $subsURL, $config);
-        $config = str_replace('$proxies', $proxies, $config);
-        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
-        return $config;
-    }
-
-    private function surfboard($user, $servers = [])
-    {
-        $proxies = '';
-        $proxyGroup = '';
-
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                // [Proxy]
-                $proxies .= Surfboard::buildShadowsocks($user['uuid'], $item);
-                // [Proxy Group]
-                $proxyGroup .= $item['name'] . ', ';
-            }
-            if ($item['type'] === 'v2ray') {
-                // [Proxy]
-                $proxies .= Surfboard::buildVmess($user['uuid'], $item);
-                // [Proxy Group]
-                $proxyGroup .= $item['name'] . ', ';
-            }
-        }
-
-        $defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
-        $customConfig = base_path() . '/resources/rules/custom.surfboard.conf';
-        if (\File::exists($customConfig)) {
-            $config = file_get_contents("$customConfig");
-        } else {
-            $config = file_get_contents("$defaultConfig");
-        }
-
-        // Subscription link
-        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
-
-        $config = str_replace('$subs_link', $subsURL, $config);
-        $config = str_replace('$proxies', $proxies, $config);
-        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
-        return $config;
-    }
-
-    private function clash($user, $servers = [])
-    {
-        $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
-        $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
-        if (\File::exists($customConfig)) {
-            $config = Yaml::parseFile($customConfig);
-        } else {
-            $config = Yaml::parseFile($defaultConfig);
-        }
-        $proxy = [];
-        $proxies = [];
-
-        foreach ($servers as $item) {
-            if ($item['type'] === 'shadowsocks') {
-                array_push($proxy, Clash::buildShadowsocks($user['uuid'], $item));
-                array_push($proxies, $item['name']);
-            }
-            if ($item['type'] === 'v2ray') {
-                array_push($proxy, Clash::buildVmess($user['uuid'], $item));
-                array_push($proxies, $item['name']);
-            }
-            if ($item['type'] === 'trojan') {
-                array_push($proxy, Clash::buildTrojan($user['uuid'], $item));
-                array_push($proxies, $item['name']);
-            }
-        }
-
-        $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
-        foreach ($config['proxy-groups'] as $k => $v) {
-            if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
-            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
+            // todo 1.5.3 remove
+            $class = new V2rayN($user, $servers);
+            die($class->handle());
+            die('该客户端暂不支持进行订阅');
         }
-        $yaml = Yaml::dump($config);
-        $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
-        return $yaml;
     }
 }

+ 93 - 0
app/Http/Controllers/Client/Protocols/AnXray.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+class AnXray
+{
+    public $flag = 'anxray';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+    public static function buildShadowsocks($uuid, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server['cipher']}:{$uuid}")
+        );
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
+    }
+
+    public static function buildShadowsocksSIP008($uuid, $server)
+    {
+        $config = [
+            "id" => $server['id'],
+            "remarks" => $server['name'],
+            "server" => $server['host'],
+            "server_port" => $server['port'],
+            "password" => $uuid,
+            "method" => $server['cipher']
+        ];
+        return $config;
+    }
+
+    public static function buildVmess($uuid, $server)
+    {
+        $config = [
+            "encryption" => "none",
+            "type" => urlencode($server['network']),
+            "security" => $server['tls'] ? "tls" : "",
+            "sni" => $server['tls'] ? urlencode(json_decode($server['tlsSettings'], true)['serverName']) : ""
+        ];
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']);
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']);
+        }
+        if ((string)$server['network'] === 'grpc') {
+            $grpcSettings = json_decode($server['networkSettings'], true);
+            if (isset($grpcSettings['serviceName'])) $config['serviceName'] = urlencode($grpcSettings['serviceName']);
+        }
+        return "vmess://" . $uuid . "@" . $server['host'] . ":" . $server['port'] . "?" . http_build_query($config) . "#" . urlencode($server['name']) . "\r\n";
+    }
+
+    public static function buildTrojan($uuid, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $query = http_build_query([
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
+        ]);
+        $uri = "trojan://{$uuid}@{$server['host']}:{$server['port']}?{$query}#{$name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+}

+ 60 - 1
app/Utils/Clash.php → app/Http/Controllers/Client/Protocols/Clash.php

@@ -1,10 +1,61 @@
 <?php
 
-namespace App\Utils;
+namespace App\Http\Controllers\Client\Protocols;
 
+use Symfony\Component\Yaml\Yaml;
 
 class Clash
 {
+    public $flag = 'clash';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
+        $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
+        $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
+        if (\File::exists($customConfig)) {
+            $config = Yaml::parseFile($customConfig);
+        } else {
+            $config = Yaml::parseFile($defaultConfig);
+        }
+        $proxy = [];
+        $proxies = [];
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'v2ray') {
+                array_push($proxy, self::buildVmess($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+            if ($item['type'] === 'trojan') {
+                array_push($proxy, self::buildTrojan($user['uuid'], $item));
+                array_push($proxies, $item['name']);
+            }
+        }
+
+        $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
+        foreach ($config['proxy-groups'] as $k => $v) {
+            if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
+            $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
+        }
+        $yaml = Yaml::dump($config);
+        $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
+        return $yaml;
+    }
+
     public static function buildShadowsocks($uuid, $server)
     {
         $array = [];
@@ -50,6 +101,14 @@ class Clash
                     $array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
             }
         }
+        if ($server['network'] === 'grpc') {
+            $array['network'] = 'grpc';
+            if ($server['networkSettings']) {
+                $grpcObject = json_decode($server['networkSettings'], true);
+                $array['grpc-opts'] = [];
+                $array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName'];
+            }
+        }
 
         return $array;
     }

+ 96 - 0
app/Http/Controllers/Client/Protocols/Passwall.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+
+class Passwall
+{
+    public $flag = 'passwall';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+    public static function buildShadowsocks($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server['cipher']}:{$password}")
+        );
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
+    }
+
+    public static function buildVmess($uuid, $server)
+    {
+        $config = [
+            "v" => "2",
+            "ps" => $server['name'],
+            "add" => $server['host'],
+            "port" => (string)$server['port'],
+            "id" => $uuid,
+            "aid" => (string)$server['alter_id'],
+            "net" => $server['network'],
+            "type" => "none",
+            "host" => "",
+            "path" => "",
+            "tls" => $server['tls'] ? "tls" : "",
+        ];
+        if ($server['tls']) {
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $config['sni'] = $tlsSettings['serverName'];
+            }
+        }
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
+        }
+        if ((string)$server['network'] === 'grpc') {
+            $grpcSettings = json_decode($server['networkSettings'], true);
+            if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
+        }
+        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $query = http_build_query([
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
+        ]);
+        $uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+
+}

+ 31 - 1
app/Utils/QuantumultX.php → app/Http/Controllers/Client/Protocols/QuantumultX.php

@@ -1,10 +1,40 @@
 <?php
 
-namespace App\Utils;
+namespace App\Http\Controllers\Client\Protocols;
 
 
 class QuantumultX
 {
+    public $flag = 'quantumult%20x';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+        header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
     public static function buildShadowsocks($password, $server)
     {
         $config = [

+ 96 - 0
app/Http/Controllers/Client/Protocols/SSRPlus.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+
+class SSRPlus
+{
+    public $flag = 'ssrplus';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+    public static function buildShadowsocks($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server['cipher']}:{$password}")
+        );
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
+    }
+
+    public static function buildVmess($uuid, $server)
+    {
+        $config = [
+            "v" => "2",
+            "ps" => $server['name'],
+            "add" => $server['host'],
+            "port" => (string)$server['port'],
+            "id" => $uuid,
+            "aid" => (string)$server['alter_id'],
+            "net" => $server['network'],
+            "type" => "none",
+            "host" => "",
+            "path" => "",
+            "tls" => $server['tls'] ? "tls" : "",
+        ];
+        if ($server['tls']) {
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $config['sni'] = $tlsSettings['serverName'];
+            }
+        }
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
+        }
+        if ((string)$server['network'] === 'grpc') {
+            $grpcSettings = json_decode($server['networkSettings'], true);
+            if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
+        }
+        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $query = http_build_query([
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
+        ]);
+        $uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+
+}

+ 51 - 2
app/Utils/Shadowrocket.php → app/Http/Controllers/Client/Protocols/Shadowrocket.php

@@ -1,10 +1,46 @@
 <?php
 
-namespace App\Utils;
-
+namespace App\Http\Controllers\Client\Protocols;
 
 class Shadowrocket
 {
+    public $flag = 'shadowrocket';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+
+        $uri = '';
+        //display remaining traffic and expire date
+        $upload = round($user['u'] / (1024*1024*1024), 2);
+        $download = round($user['d'] / (1024*1024*1024), 2);
+        $totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
+        $expiredDate = date('Y-m-d', $user['expired_at']);
+        $uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+
     public static function buildShadowsocks($password, $server)
     {
         $name = rawurlencode($server['name']);
@@ -44,6 +80,19 @@ class Shadowrocket
                     $config['obfsParam'] = $wsSettings['headers']['Host'];
             }
         }
+        if ($server['network'] === 'grpc') {
+            $config['obfs'] = "grpc";
+            if ($server['networkSettings']) {
+                $grpcSettings = json_decode($server['networkSettings'], true);
+                if (isset($grpcSettings['serviceName']) && !empty($grpcSettings['serviceName']))
+                    $config['path'] = $grpcSettings['serviceName'];
+            }
+            if (isset($tlsSettings)) {
+                $config['host'] = $tlsSettings['serverName'];
+            } else {
+                $config['host'] = $server['host'];
+            }
+        }
         $query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
         $uri = "vmess://{$userinfo}?{$query}";
         $uri .= "\r\n";

+ 57 - 0
app/Http/Controllers/Client/Protocols/Shadowsocks.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+class Shadowsocks
+{
+    public $flag = 'shadowsocks';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+
+        $configs = [];
+        $subs = [];
+        $subs['servers'] = [];
+        $subs['bytes_used'] = '';
+        $subs['bytes_remaining'] = '';
+
+        $bytesUsed = $user['u'] + $user['d'];
+        $bytesRemaining = $user['transfer_enable'] - $bytesUsed;
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                array_push($configs, self::SIP008($item, $user));
+            }
+        }
+
+        $subs['version'] = 1;
+        $subs['bytes_used'] = $bytesUsed;
+        $subs['bytes_remaining'] = $bytesRemaining;
+        $subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
+
+        return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
+    }
+
+    public static function SIP008($server, $user)
+    {
+        $config = [
+            "id" => $server['id'],
+            "remarks" => $server['name'],
+            "server" => $server['host'],
+            "server_port" => $server['port'],
+            "password" => $user['uuid'],
+            "method" => $server['cipher']
+        ];
+        return $config;
+    }
+}

+ 52 - 1
app/Utils/Surfboard.php → app/Http/Controllers/Client/Protocols/Surfboard.php

@@ -1,10 +1,61 @@
 <?php
 
-namespace App\Utils;
+namespace App\Http\Controllers\Client\Protocols;
 
 
 class Surfboard
 {
+    public $flag = 'surfboard';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+
+        $proxies = '';
+        $proxyGroup = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                // [Proxy]
+                $proxies .= Surfboard::buildShadowsocks($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'v2ray') {
+                // [Proxy]
+                $proxies .= Surfboard::buildVmess($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+        }
+
+        $defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
+        $customConfig = base_path() . '/resources/rules/custom.surfboard.conf';
+        if (\File::exists($customConfig)) {
+            $config = file_get_contents("$customConfig");
+        } else {
+            $config = file_get_contents("$defaultConfig");
+        }
+
+        // Subscription link
+        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
+
+        $config = str_replace('$subs_link', $subsURL, $config);
+        $config = str_replace('$proxies', $proxies, $config);
+        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
+        return $config;
+    }
+
+
     public static function buildShadowsocks($password, $server)
     {
         $config = [

+ 58 - 2
app/Utils/Surge.php → app/Http/Controllers/Client/Protocols/Surge.php

@@ -1,10 +1,66 @@
 <?php
 
-namespace App\Utils;
-
+namespace App\Http\Controllers\Client\Protocols;
 
 class Surge
 {
+    public $flag = 'surge';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+
+        $proxies = '';
+        $proxyGroup = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'shadowsocks') {
+                // [Proxy]
+                $proxies .= self::buildShadowsocks($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'v2ray') {
+                // [Proxy]
+                $proxies .= self::buildVmess($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+            if ($item['type'] === 'trojan') {
+                // [Proxy]
+                $proxies .= self::buildTrojan($user['uuid'], $item);
+                // [Proxy Group]
+                $proxyGroup .= $item['name'] . ', ';
+            }
+        }
+
+        $defaultConfig = base_path() . '/resources/rules/default.surge.conf';
+        $customConfig = base_path() . '/resources/rules/custom.surge.conf';
+        if (\File::exists($customConfig)) {
+            $config = file_get_contents("$customConfig");
+        } else {
+            $config = file_get_contents("$defaultConfig");
+        }
+
+        // Subscription link
+        $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
+
+        $config = str_replace('$subs_link', $subsURL, $config);
+        $config = str_replace('$proxies', $proxies, $config);
+        $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
+        return $config;
+    }
+
+
     public static function buildShadowsocks($password, $server)
     {
         $config = [

+ 96 - 0
app/Http/Controllers/Client/Protocols/V2rayN.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+
+class V2rayN
+{
+    public $flag = 'v2rayn';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+    public static function buildShadowsocks($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server['cipher']}:{$password}")
+        );
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
+    }
+
+    public static function buildVmess($uuid, $server)
+    {
+        $config = [
+            "v" => "2",
+            "ps" => $server['name'],
+            "add" => $server['host'],
+            "port" => (string)$server['port'],
+            "id" => $uuid,
+            "aid" => (string)$server['alter_id'],
+            "net" => $server['network'],
+            "type" => "none",
+            "host" => "",
+            "path" => "",
+            "tls" => $server['tls'] ? "tls" : "",
+        ];
+        if ($server['tls']) {
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $config['sni'] = $tlsSettings['serverName'];
+            }
+        }
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
+        }
+        if ((string)$server['network'] === 'grpc') {
+            $grpcSettings = json_decode($server['networkSettings'], true);
+            if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
+        }
+        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $query = http_build_query([
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
+        ]);
+        $uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+
+}

+ 97 - 0
app/Http/Controllers/Client/Protocols/V2rayNG.php

@@ -0,0 +1,97 @@
+<?php
+
+namespace App\Http\Controllers\Client\Protocols;
+
+
+class V2rayNG
+{
+    public $flag = 'v2rayng';
+    private $servers;
+    private $user;
+
+    public function __construct($user, $servers)
+    {
+        $this->user = $user;
+        $this->servers = $servers;
+    }
+
+    public function handle()
+    {
+        $servers = $this->servers;
+        $user = $this->user;
+        $uri = '';
+
+        foreach ($servers as $item) {
+            if ($item['type'] === 'v2ray') {
+                $uri .= self::buildVmess($user['uuid'], $item);
+            }
+            if ($item['type'] === 'shadowsocks') {
+                $uri .= self::buildShadowsocks($user['uuid'], $item);
+            }
+            if ($item['type'] === 'trojan') {
+                $uri .= self::buildTrojan($user['uuid'], $item);
+            }
+        }
+        return base64_encode($uri);
+    }
+
+    public static function buildShadowsocks($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $str = str_replace(
+            ['+', '/', '='],
+            ['-', '_', ''],
+            base64_encode("{$server['cipher']}:{$password}")
+        );
+        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
+    }
+
+    public static function buildVmess($uuid, $server)
+    {
+        $config = [
+            "v" => "2",
+            "ps" => $server['name'],
+            "add" => $server['host'],
+            "port" => (string)$server['port'],
+            "id" => $uuid,
+            "aid" => (string)$server['alter_id'],
+            "net" => $server['network'],
+            "type" => "none",
+            "host" => "",
+            "path" => "",
+            "tls" => $server['tls'] ? "tls" : "",
+        ];
+        if ($server['tls']) {
+            if ($server['tlsSettings']) {
+                $tlsSettings = json_decode($server['tlsSettings'], true);
+                if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
+                    $config['sni'] = $tlsSettings['serverName'];
+            }
+        }
+        if ((string)$server['network'] === 'ws') {
+            $wsSettings = json_decode($server['networkSettings'], true);
+            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
+            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
+        }
+        if ((string)$server['network'] === 'grpc') {
+            $grpcSettings = json_decode($server['networkSettings'], true);
+            if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
+        }
+        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
+    }
+
+    public static function buildTrojan($password, $server)
+    {
+        $name = rawurlencode($server['name']);
+        $query = http_build_query([
+            'allowInsecure' => $server['allow_insecure'],
+            'peer' => $server['server_name'],
+            'sni' => $server['server_name']
+        ]);
+        $uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
+        $uri .= "\r\n";
+        return $uri;
+    }
+
+
+}

+ 19 - 1
app/Http/Controllers/Guest/CommController.php

@@ -11,8 +11,26 @@ class CommController extends Controller
     {
         return response([
             'data' => [
-                'tos_url' => config('v2board.tos_url')
+                'tos_url' => config('v2board.tos_url'),
+                'is_email_verify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
+                'is_invite_force' => (int)config('v2board.invite_force', 0) ? 1 : 0,
+                'email_whitelist_suffix' => (int)config('v2board.email_whitelist_enable', 0)
+                    ? $this->getEmailSuffix()
+                    : 0,
+                'is_recaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
+                'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
+                'app_description' => config('v2board.app_description'),
+                'app_url' => config('v2board.app_url')
             ]
         ]);
     }
+
+    private function getEmailSuffix()
+    {
+        $suffix = config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
+        if (!is_array($suffix)) {
+            return preg_split('/,/', $suffix);
+        }
+        return $suffix;
+    }
 }

+ 1 - 1
app/Http/Controllers/Guest/PaymentController.php

@@ -20,7 +20,7 @@ class PaymentController extends Controller
             if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
                 abort(500, 'handle error');
             }
-            die('success');
+            die(isset($paymentService->customResult) ? $paymentService->customResult : 'success');
         } catch (\Exception $e) {
             abort(500, 'fail');
         }

+ 1 - 0
app/Http/Controllers/Guest/TelegramController.php

@@ -193,6 +193,7 @@ class TelegramController extends Controller
         }
         $telegramService = new TelegramService();
         $telegramService->sendMessage($msg->chat_id, "#`{$ticketId}` 的工单已回复成功", 'markdown');
+        $telegramService->sendMessageWithAdmin("#`{$ticketId}` 的工单已由 {$user->email} 进行回复", true);
     }
 
 

+ 22 - 21
app/Http/Controllers/Passport/AuthController.php

@@ -24,7 +24,7 @@ class AuthController extends Controller
             $recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
             $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
             if (!$recaptchaResp->isSuccess()) {
-                abort(500, '验证码有误');
+                abort(500, __('Invalid code is incorrect'));
             }
         }
         if ((int)config('v2board.email_whitelist_enable', 0)) {
@@ -32,36 +32,36 @@ class AuthController extends Controller
                 $request->input('email'),
                 config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
             ) {
-                abort(500, '邮箱后缀不处于白名单中');
+                abort(500, __('Email suffix is not in the Whitelist'));
             }
         }
         if ((int)config('v2board.email_gmail_limit_enable', 0)) {
             $prefix = explode('@', $request->input('email'))[0];
             if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) {
-                abort(500, '不支持Gmail别名邮箱');
+                abort(500, __('Gmail alias is not supported'));
             }
         }
         if ((int)config('v2board.stop_register', 0)) {
-            abort(500, '本站已关闭注册');
+            abort(500, __('Registration has closed'));
         }
         if ((int)config('v2board.invite_force', 0)) {
             if (empty($request->input('invite_code'))) {
-                abort(500, '必须使用邀请码才可以注册');
+                abort(500, __('You must use the invitation code to register'));
             }
         }
         if ((int)config('v2board.email_verify', 0)) {
             if (empty($request->input('email_code'))) {
-                abort(500, '邮箱验证码不能为空');
+                abort(500, __('Email verification code cannot be empty'));
             }
             if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
-                abort(500, '邮箱验证码有误');
+                abort(500, __('Incorrect email verification code'));
             }
         }
         $email = $request->input('email');
         $password = $request->input('password');
         $exist = User::where('email', $email)->first();
         if ($exist) {
-            abort(500, '邮箱已存在系统中');
+            abort(500, __('Email already exists'));
         }
         $user = new User();
         $user->email = $email;
@@ -74,7 +74,7 @@ class AuthController extends Controller
                 ->first();
             if (!$inviteCode) {
                 if ((int)config('v2board.invite_force', 0)) {
-                    abort(500, '邀请码无效');
+                    abort(500, __('Invalid invitation code'));
                 }
             } else {
                 $user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
@@ -97,7 +97,7 @@ class AuthController extends Controller
         }
 
         if (!$user->save()) {
-            abort(500, '注册失败');
+            abort(500, __('Register failed'));
         }
         if ((int)config('v2board.email_verify', 0)) {
             Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
@@ -116,18 +116,18 @@ class AuthController extends Controller
 
         $user = User::where('email', $email)->first();
         if (!$user) {
-            abort(500, '用户名或密码错误');
+            abort(500, __('Incorrect email or password'));
         }
         if (!Helper::multiPasswordVerify(
             $user->password_algo,
             $password,
             $user->password)
         ) {
-            abort(500, '用户名或密码错误');
+            abort(500, __('Incorrect email or password'));
         }
 
         if ($user->banned) {
-            abort(500, '该账户已被停止使用');
+            abort(500, __('Your account has been suspended'));
         }
 
         $data = [
@@ -165,14 +165,14 @@ class AuthController extends Controller
             $key =  CacheKey::get('TEMP_TOKEN', $request->input('verify'));
             $userId = Cache::get($key);
             if (!$userId) {
-                abort(500, '令牌有误');
+                abort(500, __('Token error'));
             }
             $user = User::find($userId);
             if (!$user) {
-                abort(500, '用户不存在');
+                abort(500, __('The user does not '));
             }
             if ($user->banned) {
-                abort(500, '该账户已被停止使用');
+                abort(500, __('Your account has been suspended'));
             }
             $request->session()->put('email', $user->email);
             $request->session()->put('id', $user->id);
@@ -190,7 +190,7 @@ class AuthController extends Controller
     {
         $user = User::where('token', $request->input('token'))->first();
         if (!$user) {
-            abort(500, '令牌有误');
+            abort(500, __('Token error'));
         }
 
         $code = Helper::guid();
@@ -204,11 +204,12 @@ class AuthController extends Controller
     public function getQuickLoginUrl(Request $request)
     {
         $authData = explode(':', base64_decode($request->input('auth_data')));
+        if (!isset($authData[0])) abort(403, __('Token error'));
         $user = User::where('email', $authData[0])
             ->where('password', $authData[1])
             ->first();
         if (!$user) {
-            abort(500, '令牌有误');
+            abort(500, __('Token error'));
         }
 
         $code = Helper::guid();
@@ -241,16 +242,16 @@ class AuthController extends Controller
     public function forget(AuthForget $request)
     {
         if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
-            abort(500, '邮箱验证码有误');
+            abort(500, __('Incorrect email verification code'));
         }
         $user = User::where('email', $request->input('email'))->first();
         if (!$user) {
-            abort(500, '该邮箱不存在系统中');
+            abort(500, __('This email is not registered in the system'));
         }
         $user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
         $user->password_algo = NULL;
         if (!$user->save()) {
-            abort(500, '重置失败');
+            abort(500, __('Reset failed'));
         }
         Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
         return response([

+ 4 - 3
app/Http/Controllers/Passport/CommController.php

@@ -17,6 +17,7 @@ use ReCaptcha\ReCaptcha;
 
 class CommController extends Controller
 {
+    // TODO: remove on 1.5.5
     public function config()
     {
         return response([
@@ -47,15 +48,15 @@ class CommController extends Controller
             $recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
             $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
             if (!$recaptchaResp->isSuccess()) {
-                abort(500, '验证码有误');
+                abort(500, __('Invalid code is incorrect'));
             }
         }
         $email = $request->input('email');
         if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
-            abort(500, '验证码已发送,请过一会再请求');
+            abort(500, __('Email verification code has been sent, please request again later'));
         }
         $code = rand(100000, 999999);
-        $subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
+        $subject = config('v2board.app_name', 'V2Board') . __('Email verification code');
 
         SendEmailJob::dispatch([
             'email' => $email,

+ 6 - 6
app/Http/Controllers/User/CouponController.php

@@ -11,25 +11,25 @@ class CouponController extends Controller
     public function check(Request $request)
     {
         if (empty($request->input('code'))) {
-            abort(500, __('user.coupon.check.coupon_not_empty'));
+            abort(500, __('Coupon cannot be empty'));
         }
         $coupon = Coupon::where('code', $request->input('code'))->first();
         if (!$coupon) {
-            abort(500, __('user.coupon.check.coupon_invalid'));
+            abort(500, __('Invalid coupon'));
         }
         if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
-            abort(500, __('user.coupon.check.coupon_not_available_by_number'));
+            abort(500, __('This coupon is no longer available'));
         }
         if (time() < $coupon->started_at) {
-            abort(500, __('user.coupon.check.coupon_not_available_by_time'));
+            abort(500, __('This coupon has not yet started'));
         }
         if (time() > $coupon->ended_at) {
-            abort(500, __('user.coupon.check.coupon_expired'));
+            abort(500, __('This coupon has expired'));
         }
         if ($coupon->limit_plan_ids) {
             $limitPlanIds = json_decode($coupon->limit_plan_ids);
             if (!in_array($request->input('plan_id'), $limitPlanIds)) {
-                abort(500, __('user.coupon.check.coupon_limit_plan'));
+                abort(500, __('The coupon code cannot be used for this subscription'));
             }
         }
         return response([

+ 1 - 1
app/Http/Controllers/User/InviteController.php

@@ -14,7 +14,7 @@ class InviteController extends Controller
     public function save(Request $request)
     {
         if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
-            abort(500, __('user.invite.save.invite_create_limit'));
+            abort(500, __('The maximum number of creations has been reached'));
         }
         $inviteCode = new InviteCode();
         $inviteCode->user_id = $request->session()->get('id');

+ 10 - 5
app/Http/Controllers/User/KnowledgeController.php

@@ -17,18 +17,23 @@ class KnowledgeController extends Controller
                 ->where('show', 1)
                 ->first()
                 ->toArray();
-            if (!$knowledge) abort(500, __('user.knowledge.fetch.knowledge_not_exist'));
+            if (!$knowledge) abort(500, __('Article does not exist'));
             $user = User::find($request->session()->get('id'));
             $userService = new UserService();
             if ($userService->isAvailable($user)) {
                 $appleId = config('v2board.apple_id');
                 $appleIdPassword = config('v2board.apple_id_password');
             } else {
-                $appleId = __('user.knowledge.fetch.apple_id_must_be_plan');
-                $appleIdPassword = __('user.knowledge.fetch.apple_id_must_be_plan');
+                $appleId = __('No active subscription. Unable to use our provided Apple ID');
+                $appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
                 $this->formatAccessData($knowledge['body']);
             }
-            $subscribeUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
+            $subscribeUrl = config('v2board.app_url', env('APP_URL'));
+            $subscribeUrls = explode(',', config('v2board.subscribe_url'));
+            if ($subscribeUrls) {
+                $subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
+            }
+            $subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
             $knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
             $knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
             $knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $knowledge['body']);
@@ -63,7 +68,7 @@ class KnowledgeController extends Controller
         function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $substr;}
         $accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
         if ($accessData) {
-            $body = str_replace($accessData, '<div class="v2board-no-access">'. __('user.knowledge.formatAccessData.no_access') .'</div>', $body);
+            $body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body);
         }
     }
 }

+ 20 - 20
app/Http/Controllers/User/OrderController.php

@@ -52,12 +52,12 @@ class OrderController extends Controller
             ->where('trade_no', $request->input('trade_no'))
             ->first();
         if (!$order) {
-            abort(500, __('user.order.details.order_not_exist'));
+            abort(500, __('Order does not exist or has been paid'));
         }
         $order['plan'] = Plan::find($order->plan_id);
         $order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
         if (!$order['plan']) {
-            abort(500, __('user.order.details.plan_not_exist'));
+            abort(500, __('Subscription plan does not exist'));
         }
         return response([
             'data' => $order
@@ -68,38 +68,38 @@ class OrderController extends Controller
     {
         $userService = new UserService();
         if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) {
-            abort(500, __('user.order.save.exist_open_order'));
+            abort(500, __('You have an unpaid or pending order, please try again later or cancel it'));
         }
 
         $plan = Plan::find($request->input('plan_id'));
         $user = User::find($request->session()->get('id'));
 
         if (!$plan) {
-            abort(500, __('user.order.save.plan_not_exist'));
+            abort(500, __('Subscription plan does not exist'));
         }
 
         if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
             if ($request->input('cycle') !== 'reset_price') {
-                abort(500, __('user.order.save.plan_stop_sell'));
+                abort(500, __('This subscription has been sold out, please choose another subscription'));
             }
         }
 
         if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') {
-            abort(500, __('user.order.save.plan_stop_renew'));
+            abort(500, __('This subscription cannot be renewed, please change to another subscription'));
         }
 
         if ($plan[$request->input('cycle')] === NULL) {
-            abort(500, __('user.order.save.plan_stop'));
+            abort(500, __('This payment cycle cannot be purchased, please choose another cycle'));
         }
 
         if ($request->input('cycle') === 'reset_price') {
             if ($user->expired_at <= time() || !$user->plan_id) {
-                abort(500, __('user.order.save.plan_exist_not_buy_package'));
+                abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
             }
         }
 
         if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
-            abort(500, __('user.order.save.plan_expired'));
+            abort(500, __('This subscription has expired, please change to another subscription'));
         }
 
         DB::beginTransaction();
@@ -115,7 +115,7 @@ class OrderController extends Controller
             $couponService = new CouponService($request->input('coupon_code'));
             if (!$couponService->use($order)) {
                 DB::rollBack();
-                abort(500, __('user.order.save.coupon_use_failed'));
+                abort(500, __('Coupon failed'));
             }
             $order->coupon_id = $couponService->getId();
         }
@@ -130,14 +130,14 @@ class OrderController extends Controller
             if ($remainingBalance > 0) {
                 if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
                     DB::rollBack();
-                    abort(500, __('user.order.save.insufficient_balance'));
+                    abort(500, __('Insufficient balance'));
                 }
                 $order->balance_amount = $order->total_amount;
                 $order->total_amount = 0;
             } else {
                 if (!$userService->addBalance($order->user_id, - $user->balance)) {
                     DB::rollBack();
-                    abort(500, __('user.order.save.insufficient_balance'));
+                    abort(500, __('Insufficient balance'));
                 }
                 $order->balance_amount = $user->balance;
                 $order->total_amount = $order->total_amount - $user->balance;
@@ -146,7 +146,7 @@ class OrderController extends Controller
 
         if (!$order->save()) {
             DB::rollback();
-            abort(500, __('user.order.save.order_create_failed'));
+            abort(500, __('Failed to create order'));
         }
 
         DB::commit();
@@ -165,7 +165,7 @@ class OrderController extends Controller
             ->where('status', 0)
             ->first();
         if (!$order) {
-            abort(500, __('user.order.checkout.order_not_exist_or_paid'));
+            abort(500, __('Order does not exist or has been paid'));
         }
         // free process
         if ($order->total_amount <= 0) {
@@ -178,7 +178,7 @@ class OrderController extends Controller
             ]);
         }
         $payment = Payment::find($method);
-        if (!$payment || $payment->enable !== 1) abort(500, __('user.order.checkout.pay_method_not_use'));
+        if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
         $paymentService = new PaymentService($payment->payment, $payment->id);
         $result = $paymentService->pay([
             'trade_no' => $tradeNo,
@@ -200,7 +200,7 @@ class OrderController extends Controller
             ->where('user_id', $request->session()->get('id'))
             ->first();
         if (!$order) {
-            abort(500, __('user.order.check.order_not_exist'));
+            abort(500, __('Order does not exist'));
         }
         return response([
             'data' => $order->status
@@ -224,20 +224,20 @@ class OrderController extends Controller
     public function cancel(Request $request)
     {
         if (empty($request->input('trade_no'))) {
-            abort(500, __('user.order.cancel.params_wrong'));
+            abort(500, __('Invalid parameter'));
         }
         $order = Order::where('trade_no', $request->input('trade_no'))
             ->where('user_id', $request->session()->get('id'))
             ->first();
         if (!$order) {
-            abort(500, __('user.order.cancel.order_not_exist'));
+            abort(500, __('Order does not exist'));
         }
         if ($order->status !== 0) {
-            abort(500, __('user.order.cancel.only_cancel_pending_order'));
+            abort(500, __('You can only cancel pending orders'));
         }
         $orderService = new OrderService($order);
         if (!$orderService->cancel()) {
-            abort(500, __('user.order.cancel.cancel_failed'));
+            abort(500, __('Cancel failed'));
         }
         return response([
             'data' => true

+ 1 - 1
app/Http/Controllers/User/PlanController.php

@@ -14,7 +14,7 @@ class PlanController extends Controller
             $plan = Plan::where('id', $request->input('id'))
                 ->first();
             if (!$plan) {
-                abort(500, __('user.plan.fetch.plan_not_exist'));
+                abort(500, __('Subscription plan does not exist'));
             }
             return response([
                 'data' => $plan

+ 7 - 0
app/Http/Controllers/User/TelegramController.php

@@ -3,7 +3,9 @@
 namespace App\Http\Controllers\User;
 
 use App\Http\Controllers\Controller;
+use App\Models\User;
 use App\Services\TelegramService;
+use Illuminate\Http\Request;
 
 class TelegramController extends Controller
 {
@@ -17,4 +19,9 @@ class TelegramController extends Controller
             ]
         ]);
     }
+
+    public function unbind(Request $request)
+    {
+        $user = User::where('user_id', $request->session()->get('id'))->first();
+    }
 }

+ 22 - 22
app/Http/Controllers/User/TicketController.php

@@ -23,7 +23,7 @@ class TicketController extends Controller
                 ->where('user_id', $request->session()->get('id'))
                 ->first();
             if (!$ticket) {
-                abort(500, __('user.ticket.fetch.ticket_not_exist'));
+                abort(500, __('Ticket does not exist'));
             }
             $ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
             for ($i = 0; $i < count($ticket['message']); $i++) {
@@ -56,7 +56,7 @@ class TicketController extends Controller
     {
         DB::beginTransaction();
         if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
-            abort(500, __('user.ticket.save.exist_other_open_ticket'));
+            abort(500, __('There are other unresolved tickets'));
         }
         $ticket = Ticket::create(array_merge($request->only([
             'subject',
@@ -67,7 +67,7 @@ class TicketController extends Controller
         ]));
         if (!$ticket) {
             DB::rollback();
-            abort(500, __('user.ticket.save.ticket_create_failed'));
+            abort(500, __('Failed to open ticket'));
         }
         $ticketMessage = TicketMessage::create([
             'user_id' => $request->session()->get('id'),
@@ -76,7 +76,7 @@ class TicketController extends Controller
         ]);
         if (!$ticketMessage) {
             DB::rollback();
-            abort(500, __('user.ticket.save.ticket_create_failed'));
+            abort(500, __('Failed to open ticket'));
         }
         DB::commit();
         $this->sendNotify($ticket, $ticketMessage);
@@ -88,22 +88,22 @@ class TicketController extends Controller
     public function reply(Request $request)
     {
         if (empty($request->input('id'))) {
-            abort(500, __('user.ticket.reply.params_wrong'));
+            abort(500, __('Invalid parameter'));
         }
         if (empty($request->input('message'))) {
-            abort(500, __('user.ticket.reply.message_not_empty'));
+            abort(500, __('Message cannot be empty'));
         }
         $ticket = Ticket::where('id', $request->input('id'))
             ->where('user_id', $request->session()->get('id'))
             ->first();
         if (!$ticket) {
-            abort(500, __('user.ticket.reply.ticket_not_exist'));
+            abort(500, __('Ticket does not exist'));
         }
         if ($ticket->status) {
-            abort(500, __('user.ticket.reply.ticket_close_not_reply'));
+            abort(500, __('The ticket is closed and cannot be replied'));
         }
         if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) {
-            abort(500, __('user.ticket.reply.wait_reply'));
+            abort(500, __('Please wait for the technical enginneer to reply'));
         }
         DB::beginTransaction();
         $ticketMessage = TicketMessage::create([
@@ -114,7 +114,7 @@ class TicketController extends Controller
         $ticket->last_reply_user_id = $request->session()->get('id');
         if (!$ticketMessage || !$ticket->save()) {
             DB::rollback();
-            abort(500, __('user.ticket.reply.ticket_reply_failed'));
+            abort(500, __('Ticket reply failed'));
         }
         DB::commit();
         $this->sendNotify($ticket, $ticketMessage);
@@ -127,17 +127,17 @@ class TicketController extends Controller
     public function close(Request $request)
     {
         if (empty($request->input('id'))) {
-            abort(500, __('user.ticket.close.params_wrong'));
+            abort(500, __('Invalid parameter'));
         }
         $ticket = Ticket::where('id', $request->input('id'))
             ->where('user_id', $request->session()->get('id'))
             ->first();
         if (!$ticket) {
-            abort(500, __('user.ticket.close.ticket_not_exist'));
+            abort(500, __('Ticket does not exist'));
         }
         $ticket->status = 1;
         if (!$ticket->save()) {
-            abort(500, __('user.ticket.close.close_failed'));
+            abort(500, __('Close failed'));
         }
         return response([
             'data' => true
@@ -163,15 +163,15 @@ class TicketController extends Controller
                 Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT
             )
         )) {
-            abort(500, __('user.ticket.withdraw.not_support_withdraw_method'));
+            abort(500, __('Unsupported withdrawal method'));
         }
         $user = User::find($request->session()->get('id'));
         $limit = config('v2board.commission_withdraw_limit', 100);
         if ($limit > ($user->commission_balance / 100)) {
-            abort(500, __('user.ticket.withdraw.system_require_withdraw_limit', ['limit' => $limit]));
+            abort(500, __('The current required minimum withdrawal commission is', ['limit' => $limit]));
         }
         DB::beginTransaction();
-        $subject = __('user.ticket.withdraw.ticket_subject');
+        $subject = __('[Commission Withdrawal Request] This ticket is opened by the system');
         $ticket = Ticket::create([
             'subject' => $subject,
             'level' => 2,
@@ -180,12 +180,12 @@ class TicketController extends Controller
         ]);
         if (!$ticket) {
             DB::rollback();
-            abort(500, __('user.ticket.withdraw.ticket_create_failed'));
+            abort(500, __('Failed to open ticket'));
         }
-        $message = __('user.ticket.withdraw.ticket_message', [
-            'method' => $request->input('withdraw_method'),
-            'account' => $request->input('withdraw_account')
-        ]);
+        $message = sprintf("%s\r\n%s",
+            __('Withdrawal method') . ":" . $request->input('withdraw_method'),
+            __('Withdrawal account') . ":" . $request->input('withdraw_account')
+        );
         $ticketMessage = TicketMessage::create([
             'user_id' => $request->session()->get('id'),
             'ticket_id' => $ticket->id,
@@ -193,7 +193,7 @@ class TicketController extends Controller
         ]);
         if (!$ticketMessage) {
             DB::rollback();
-            abort(500, __('user.ticket.withdraw.ticket_create_failed'));
+            abort(500, __('Failed to open ticket'));
         }
         DB::commit();
         $this->sendNotify($ticket, $ticketMessage);

+ 19 - 14
app/Http/Controllers/User/UserController.php

@@ -29,19 +29,19 @@ class UserController extends Controller
     {
         $user = User::find($request->session()->get('id'));
         if (!$user) {
-            abort(500, __('user.user.changePassword.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         if (!Helper::multiPasswordVerify(
             $user->password_algo,
             $request->input('old_password'),
             $user->password)
         ) {
-            abort(500, __('user.user.changePassword.old_password_wrong'));
+            abort(500, __('The old password is wrong'));
         }
         $user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
         $user->password_algo = NULL;
         if (!$user->save()) {
-            abort(500, __('user.user.changePassword.save_failed'));
+            abort(500, __('Save failed'));
         }
         $request->session()->flush();
         return response([
@@ -70,7 +70,7 @@ class UserController extends Controller
             ])
             ->first();
         if (!$user) {
-            abort(500, __('user.user.info.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         $user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
         return response([
@@ -110,15 +110,20 @@ class UserController extends Controller
             ])
             ->first();
         if (!$user) {
-            abort(500, __('user.user.getSubscribe.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         if ($user->plan_id) {
             $user['plan'] = Plan::find($user->plan_id);
             if (!$user['plan']) {
-                abort(500, __('user.user.getSubscribe.plan_not_exist'));
+                abort(500, __('Subscription plan does not exist'));
             }
         }
-        $user['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
+        $subscribeUrl = config('v2board.app_url', env('APP_URL'));
+        $subscribeUrls = explode(',', config('v2board.subscribe_url'));
+        if ($subscribeUrls) {
+            $subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
+        }
+        $user['subscribe_url'] = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
         $user['reset_day'] = $this->getResetDay($user);
         return response([
             'data' => $user
@@ -129,12 +134,12 @@ class UserController extends Controller
     {
         $user = User::find($request->session()->get('id'));
         if (!$user) {
-            abort(500, __('user.user.resetSecurity.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         $user->uuid = Helper::guid(true);
         $user->token = Helper::guid();
         if (!$user->save()) {
-            abort(500, __('user.user.resetSecurity.reset_failed'));
+            abort(500, __('Reset failed'));
         }
         return response([
             'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
@@ -150,12 +155,12 @@ class UserController extends Controller
 
         $user = User::find($request->session()->get('id'));
         if (!$user) {
-            abort(500, __('user.user.update.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         try {
             $user->update($updateData);
         } catch (\Exception $e) {
-            abort(500, __('user.user.update.save_failed'));
+            abort(500, __('Save failed'));
         }
 
         return response([
@@ -167,15 +172,15 @@ class UserController extends Controller
     {
         $user = User::find($request->session()->get('id'));
         if (!$user) {
-            abort(500, __('user.user.transfer.user_not_exist'));
+            abort(500, __('The user does not exist'));
         }
         if ($request->input('transfer_amount') > $user->commission_balance) {
-            abort(500, __('user.user.transfer.insufficient_commission_balance'));
+            abort(500, __('Insufficient commission balance'));
         }
         $user->commission_balance = $user->commission_balance - $request->input('transfer_amount');
         $user->balance = $user->balance + $request->input('transfer_amount');
         if (!$user->save()) {
-            abort(500, __('user.user.transfer.transfer_failed'));
+            abort(500, __('Transfer failed'));
         }
         return response([
             'data' => true

+ 4 - 10
app/Http/Middleware/User.php

@@ -17,20 +17,14 @@ class User
     {
         if ($request->input('auth_data')) {
             $authData = explode(':', base64_decode($request->input('auth_data')));
+            if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
             $user = \App\Models\User::where('password', $authData[1])
                 ->where('email', $authData[0])
                 ->first();
-            if ($user) {
-                $request->session()->put('email', $user->email);
-                $request->session()->put('id', $user->id);
-            }
+            if (!$user) abort(403, '鉴权失败,请重新登入');
+            $request->session()->put('email', $user->email);
+            $request->session()->put('id', $user->id);
         }
-//        if ($request->input('lang')) {
-//            $request->session()->put('lang', $request->input('lang'));
-//        }
-//        if ($request->session()->get('lang')) {
-//            App::setLocale($request->session()->get('lang'));
-//        }
         if (!$request->session()->get('id')) {
             abort(403, '未登录或登陆已过期');
         }

+ 5 - 2
app/Http/Requests/Admin/ConfigSave.php

@@ -31,7 +31,7 @@ class ConfigSave extends FormRequest
             'app_name' => '',
             'app_description' => '',
             'app_url' => 'nullable|url',
-            'subscribe_url' => 'nullable|url',
+            'subscribe_url' => 'nullable',
             'try_out_enable' => 'in:0,1',
             'try_out_plan_id' => 'integer',
             'try_out_hour' => 'numeric',
@@ -45,8 +45,10 @@ class ConfigSave extends FormRequest
             // subscribe
             'plan_change_enable' => 'in:0,1',
             'reset_traffic_method' => 'in:0,1',
-            'renew_reset_traffic_enable' => 'in:0,1',
             'surplus_enable' => 'in:0,1',
+            'new_order_event_id' => 'in:0,1',
+            'renew_order_event_id' => 'in:0,1',
+            'change_order_event_id' => 'in:0,1',
             // server
             'server_token' => 'nullable|min:16',
             'server_license' => 'nullable',
@@ -83,6 +85,7 @@ class ConfigSave extends FormRequest
             'epay_pid' => '',
             'epay_key' => '',
             // frontend
+            'frontend_theme' => '',
             'frontend_theme_sidebar' => 'in:dark,light',
             'frontend_theme_header' => 'in:dark,light',
             'frontend_theme_color' => 'in:default,darkblue,black',

+ 1 - 1
app/Http/Requests/Admin/ServerV2raySave.php

@@ -25,7 +25,7 @@ class ServerV2raySave extends FormRequest
             'tags' => 'nullable|array',
             'rate' => 'required|numeric',
             'alter_id' => 'required|integer',
-            'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
+            'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
             'networkSettings' => '',
             'ruleSettings' => '',
             'tlsSettings' => '',

+ 1 - 1
app/Http/Requests/Admin/UserFetch.php

@@ -14,7 +14,7 @@ class UserFetch extends FormRequest
     public function rules()
     {
         return [
-            'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned',
+            'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks',
             'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
             'filter.*.value' => 'required'
         ];

+ 1 - 0
app/Http/Requests/Admin/UserUpdate.php

@@ -27,6 +27,7 @@ class UserUpdate extends FormRequest
             'u' => 'integer',
             'd' => 'integer',
             'balance' => 'integer',
+            'commission_type' => 'integer',
             'commission_balance' => 'integer',
             'remarks' => 'nullable'
         ];

+ 5 - 5
app/Http/Requests/Passport/AuthForget.php

@@ -23,11 +23,11 @@ class AuthForget extends FormRequest
     public function messages()
     {
         return [
-            'email.required' => '邮箱不能为空',
-            'email.email' => '邮箱格式不正确',
-            'password.required' => '密码不能为空',
-            'password.min' => '密码必须大于8位数',
-            'email_code.required' => '邮箱验证码不能为空'
+            'email.required' => __('Email can not be empty'),
+            'email.email' => __('Email format is incorrect'),
+            'password.required' => __('Password can not be empty'),
+            'password.min' => __('Password must be greater than 8 digits'),
+            'email_code.required' => __('Email verification code cannot be empty')
         ];
     }
 }

+ 4 - 4
app/Http/Requests/Passport/AuthLogin.php

@@ -22,10 +22,10 @@ class AuthLogin extends FormRequest
     public function messages()
     {
         return [
-            'email.required' => '邮箱不能为空',
-            'email.email' => '邮箱格式不正确',
-            'password.required' => '密码不能为空',
-            'password.min' => '密码必须大于8位数'
+            'email.required' => __('Email can not be empty'),
+            'email.email' => __('Email format is incorrect'),
+            'password.required' => __('Password can not be empty'),
+            'password.min' => __('Password must be greater than 8 digits')
         ];
     }
 }

+ 4 - 4
app/Http/Requests/Passport/AuthRegister.php

@@ -22,10 +22,10 @@ class AuthRegister extends FormRequest
     public function messages()
     {
         return [
-            'email.required' => '邮箱不能为空',
-            'email.email' => '邮箱格式不正确',
-            'password.required' => '密码不能为空',
-            'password.min' => '密码必须大于8位数'
+            'email.required' => __('Email can not be empty'),
+            'email.email' => __('Email format is incorrect'),
+            'password.required' => __('Password can not be empty'),
+            'password.min' => __('Password must be greater than 8 digits')
         ];
     }
 }

+ 2 - 2
app/Http/Requests/Passport/CommSendEmailVerify.php

@@ -21,8 +21,8 @@ class CommSendEmailVerify extends FormRequest
     public function messages()
     {
         return [
-            'email.required' => '邮箱不能为空',
-            'email.email' => '邮箱格式不正确'
+            'email.required' => __('Email can not be empty'),
+            'email.email' => __('Email format is incorrect')
         ];
     }
 }

+ 3 - 3
app/Http/Requests/User/OrderSave.php

@@ -22,9 +22,9 @@ class OrderSave extends FormRequest
     public function messages()
     {
         return [
-            'plan_id.required' => '套餐ID不能为空',
-            'cycle.required' => '套餐周期不能为空',
-            'cycle.in' => '套餐周期有误'
+            'plan_id.required' => __('Plan ID cannot be empty'),
+            'cycle.required' => __('Plan cycle cannot be empty'),
+            'cycle.in' => __('Wrong plan cycle')
         ];
     }
 }

+ 4 - 4
app/Http/Requests/User/TicketSave.php

@@ -23,10 +23,10 @@ class TicketSave extends FormRequest
     public function messages()
     {
         return [
-            'subject.required' => '工单主题不能为空',
-            'level.required' => '工单级别不能为空',
-            'level.in' => '工单级别格式不正确',
-            'message.required' => '消息不能为空'
+            'subject.required' => __('Ticket subject cannot be empty'),
+            'level.required' => __('Ticket level cannot be empty'),
+            'level.in' => __('Incorrect ticket level format'),
+            'message.required' => __('Message cannot be empty')
         ];
     }
 }

+ 2 - 3
app/Http/Requests/User/TicketWithdraw.php

@@ -22,9 +22,8 @@ class TicketWithdraw  extends FormRequest
     public function messages()
     {
         return [
-            'withdraw_method.required' => '提现方式不能为空',
-            'withdraw_method.in' => '提现方式不支持',
-            'withdraw_account.required' => '提现账号不能为空'
+            'withdraw_method.required' => __('The withdrawal method cannot be empty'),
+            'withdraw_account.required' => __('The withdrawal account cannot be empty')
         ];
     }
 }

+ 3 - 3
app/Http/Requests/User/UserChangePassword.php

@@ -22,9 +22,9 @@ class UserChangePassword extends FormRequest
     public function messages()
     {
         return [
-            'old_password.required' => '旧密码不能为空',
-            'new_password.required' => '新密码不能为空',
-            'new_password.min' => '密码必须大于8位数'
+            'old_password.required' => __('Old password cannot be empty'),
+            'new_password.required' => __('New password cannot be empty'),
+            'new_password.min' => __('Password must be greater than 8 digits')
         ];
     }
 }

+ 3 - 3
app/Http/Requests/User/UserTransfer.php

@@ -21,9 +21,9 @@ class UserTransfer extends FormRequest
     public function messages()
     {
         return [
-            'transfer_amount.required' => '划转金额不能为空',
-            'transfer_amount.integer' => __('user.user.transfer.params_wrong'),
-            'transfer_amount.min' => __('user.user.transfer.params_wrong')
+            'transfer_amount.required' => __('The transfer amount cannot be empty'),
+            'transfer_amount.integer' => __('The transfer amount parameter is wrong'),
+            'transfer_amount.min' => __('The transfer amount parameter is wrong')
         ];
     }
 }

+ 2 - 2
app/Http/Requests/User/UserUpdate.php

@@ -22,8 +22,8 @@ class UserUpdate extends FormRequest
     public function messages()
     {
         return [
-            'show.in' => '过期提醒格式不正确',
-            'renew.in' => '流量提醒格式不正确'
+            'show.in' => __('Incorrect format of expiration reminder'),
+            'renew.in' => __('Incorrect traffic alert format')
         ];
     }
 }

+ 1 - 0
app/Http/Routes/AdminRoute.php

@@ -15,6 +15,7 @@ class AdminRoute
             $router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
             $router->post('/config/save', 'Admin\\ConfigController@save');
             $router->get ('/config/getEmailTemplate', 'Admin\\ConfigController@getEmailTemplate');
+            $router->get ('/config/getThemeTemplate', 'Admin\\ConfigController@getThemeTemplate');
             $router->post('/config/setTelegramWebhook', 'Admin\\ConfigController@setTelegramWebhook');
             // Plan
             $router->get ('/plan/fetch', 'Admin\\PlanController@fetch');

+ 84 - 0
app/Payments/WechatPayNative.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace App\Payments;
+
+use Omnipay\Omnipay;
+use Omnipay\WechatPay\Helper;
+
+class WechatPayNative {
+    public function __construct($config)
+    {
+        $this->config = $config;
+        $this->customResult = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
+    }
+
+    public function form()
+    {
+        return [
+            'app_id' => [
+                'label' => 'APPID',
+                'description' => '绑定微信支付商户的APPID',
+                'type' => 'input',
+            ],
+            'mch_id' => [
+                'label' => '商户号',
+                'description' => '微信支付商户号',
+                'type' => 'input',
+            ],
+            'api_key' => [
+                'label' => 'APIKEY(v1)',
+                'description' => '',
+                'type' => 'input',
+            ]
+        ];
+    }
+
+    public function pay($order)
+    {
+        $gateway = Omnipay::create('WechatPay_Native');
+        $gateway->setAppId($this->config['app_id']);
+        $gateway->setMchId($this->config['mch_id']);
+        $gateway->setApiKey($this->config['api_key']);
+        $gateway->setNotifyUrl($order['notify_url']);
+
+        $params = [
+            'body'              => $order['trade_no'],
+            'out_trade_no'      => $order['trade_no'],
+            'total_fee'         => $order['total_amount'],
+            'spbill_create_ip'  => '0.0.0.0',
+            'fee_type'          => 'CNY'
+        ];
+
+        $request  = $gateway->purchase($params);
+        $response = $request->send();
+        $response = $response->getData();
+        if ($response['return_code'] !== 'SUCCESS') {
+            abort(500, $response['return_msg']);
+        }
+        return [
+            'type' => 0,
+            'data' => $response['code_url']
+        ];
+    }
+
+    public function notify($params)
+    {
+        $data = Helper::xml2array(file_get_contents('php://input'));
+        $gateway = Omnipay::create('WechatPay');
+        $gateway->setAppId($this->config['app_id']);
+        $gateway->setMchId($this->config['mch_id']);
+        $gateway->setApiKey($this->config['api_key']);
+        $response = $gateway->completePurchase([
+            'request_params' => file_get_contents('php://input')
+        ])->send();
+
+        if (!$response->isPaid()) {
+            die('FAIL');
+        }
+
+        return [
+            'trade_no' => $data['out_trade_no'],
+            'callback_no' => $data['transaction_id']
+        ];
+    }
+}

+ 1 - 1
app/Providers/AppServiceProvider.php

@@ -23,6 +23,6 @@ class AppServiceProvider extends ServiceProvider
      */
     public function boot()
     {
-        //
+        $this->app['view']->addNamespace('theme', public_path() . '/theme');
     }
 }

+ 63 - 28
app/Services/OrderService.php

@@ -18,6 +18,7 @@ class OrderService
         'three_year_price' => 36
     ];
     public $order;
+    public $user;
 
     public function __construct(Order $order)
     {
@@ -27,11 +28,11 @@ class OrderService
     public function open()
     {
         $order = $this->order;
-        $user = User::find($order->user_id);
+        $this->user = User::find($order->user_id);
         $plan = Plan::find($order->plan_id);
 
         if ($order->refund_amount) {
-            $user->balance = $user->balance + $order->refund_amount;
+            $this->user->balance = $this->user->balance + $order->refund_amount;
         }
         DB::beginTransaction();
         if ($order->surplus_order_ids) {
@@ -46,18 +47,28 @@ class OrderService
         }
         switch ((string)$order->cycle) {
             case 'onetime_price':
-                $this->buyByOneTime($user, $plan);
+                $this->buyByOneTime($plan);
                 break;
             case 'reset_price':
-                $this->buyByResetTraffic($user);
+                $this->buyByResetTraffic();
                 break;
             default:
-                $this->buyByCycle($order, $user, $plan);
+                $this->buyByCycle($order, $plan);
         }
 
-        if ((int)config('v2board.renew_reset_traffic_enable', 0)) $this->buyByResetTraffic($user);
+        switch ((int)$order->type) {
+            case 1:
+                $this->openEvent(config('v2board.new_order_event_id', 0));
+                break;
+            case 2:
+                $this->openEvent(config('v2board.renew_order_event_id', 0));
+                break;
+            case 3:
+                $this->openEvent(config('v2board.change_order_event_id', 0));
+                break;
+        }
 
-        if (!$user->save()) {
+        if (!$this->user->save()) {
             DB::rollBack();
             abort(500, '开通失败');
         }
@@ -121,13 +132,26 @@ class OrderService
         $order->total_amount = $order->total_amount - $order->discount_amount;
     }
 
-    public function setInvite(User $user)
+    public function setInvite(User $user):void
     {
         $order = $this->order;
         if ($user->invite_user_id && $order->total_amount > 0) {
             $order->invite_user_id = $user->invite_user_id;
-            $commissionFirstTime = (int)config('v2board.commission_first_time_enable', 1);
-            if (!$commissionFirstTime || ($commissionFirstTime && !$this->haveValidOrder($user))) {
+            $isCommission = false;
+            switch ((int)$user->commission_type) {
+                case 0:
+                    $commissionFirstTime = (int)config('v2board.commission_first_time_enable', 1);
+                    $isCommission = (!$commissionFirstTime || ($commissionFirstTime && !$this->haveValidOrder($user)));
+                    break;
+                case 1:
+                    $isCommission = true;
+                    break;
+                case 2:
+                    $isCommission = !$this->haveValidOrder($user);
+                    break;
+            }
+
+            if ($isCommission) {
                 $inviter = User::find($user->invite_user_id);
                 if ($inviter && $inviter->commission_rate) {
                     $order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
@@ -191,7 +215,7 @@ class OrderService
             if ($item->cycle === 'onetime_price') continue;
             if ($this->orderIsUsed($item)) continue;
             $orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->cycle];
-            $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount']);
+            $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']);
         }
         if (!$orderSurplusMonth || !$orderSurplusAmount) return;
         $monthUnitPrice = $orderSurplusAmount / $orderSurplusMonth;
@@ -220,35 +244,35 @@ class OrderService
     }
 
 
-    private function buyByResetTraffic(User $user)
+    private function buyByResetTraffic()
     {
-        $user->u = 0;
-        $user->d = 0;
+        $this->user->u = 0;
+        $this->user->d = 0;
     }
 
-    private function buyByCycle(Order $order, User $user, Plan $plan)
+    private function buyByCycle(Order $order, Plan $plan)
     {
         // change plan process
         if ((int)$order->type === 3) {
-            $user->expired_at = time();
+            $this->user->expired_at = time();
         }
-        $user->transfer_enable = $plan->transfer_enable * 1073741824;
+        $this->user->transfer_enable = $plan->transfer_enable * 1073741824;
         // 从一次性转换到循环
-        if ($user->expired_at === NULL) $this->buyByResetTraffic($user);
+        if ($this->user->expired_at === NULL) $this->buyByResetTraffic();
         // 新购
-        if ($order->type === 1) $this->buyByResetTraffic($user);
-        $user->plan_id = $plan->id;
-        $user->group_id = $plan->group_id;
-        $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
+        if ($order->type === 1) $this->buyByResetTraffic();
+        $this->user->plan_id = $plan->id;
+        $this->user->group_id = $plan->group_id;
+        $this->user->expired_at = $this->getTime($order->cycle, $this->user->expired_at);
     }
 
-    private function buyByOneTime(User $user, Plan $plan)
+    private function buyByOneTime(Plan $plan)
     {
-        $this->buyByResetTraffic($user);
-        $user->transfer_enable = $plan->transfer_enable * 1073741824;
-        $user->plan_id = $plan->id;
-        $user->group_id = $plan->group_id;
-        $user->expired_at = NULL;
+        $this->buyByResetTraffic();
+        $this->user->transfer_enable = $plan->transfer_enable * 1073741824;
+        $this->user->plan_id = $plan->id;
+        $this->user->group_id = $plan->group_id;
+        $this->user->expired_at = NULL;
     }
 
     private function getTime($str, $timestamp)
@@ -271,4 +295,15 @@ class OrderService
                 return strtotime('+36 month', $timestamp);
         }
     }
+
+    private function openEvent($eventId)
+    {
+        switch ((int) $eventId) {
+            case 0:
+                break;
+            case 1:
+                $this->buyByResetTraffic();
+                break;
+        }
+    }
 }

+ 7 - 0
app/Services/PaymentService.php

@@ -7,6 +7,12 @@ use App\Models\Payment;
 
 class PaymentService
 {
+    public $method;
+    public $customResult;
+    protected $class;
+    protected $config;
+    protected $payment;
+
     public function __construct($method, $id = NULL, $uuid = NULL)
     {
         $this->method = $method;
@@ -22,6 +28,7 @@ class PaymentService
             $this->config['uuid'] = $payment['uuid'];
         };
         $this->payment = new $this->class($this->config);
+        if (isset($this->payment->customResult)) $this->customResult = $this->payment->customResult;
     }
 
     public function notify($params)

+ 16 - 5
app/Services/ServerService.php

@@ -8,8 +8,6 @@ use App\Models\User;
 use App\Models\Server;
 use App\Models\ServerTrojan;
 use App\Utils\CacheKey;
-use App\Utils\Helper;
-use App\Utils\URLSchemes;
 use Illuminate\Support\Facades\Cache;
 
 class ServerService
@@ -29,7 +27,6 @@ class ServerService
             $v2ray[$i]['type'] = 'v2ray';
             $groupId = json_decode($v2ray[$i]['group_id']);
             if (in_array($user->group_id, $groupId)) {
-                $v2ray[$i]['link'] = URLSchemes::buildVmess($v2ray[$i], $user);
                 if ($v2ray[$i]['parent_id']) {
                     $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id']));
                 } else {
@@ -54,7 +51,6 @@ class ServerService
         for ($i = 0; $i < count($trojan); $i++) {
             $trojan[$i]['type'] = 'trojan';
             $groupId = json_decode($trojan[$i]['group_id']);
-            $trojan[$i]['link'] = URLSchemes::buildTrojan($trojan[$i], $user);
             if (in_array($user->group_id, $groupId)) {
                 if ($trojan[$i]['parent_id']) {
                     $trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['parent_id']));
@@ -78,7 +74,6 @@ class ServerService
         for ($i = 0; $i < count($shadowsocks); $i++) {
             $shadowsocks[$i]['type'] = 'shadowsocks';
             $groupId = json_decode($shadowsocks[$i]['group_id']);
-            $shadowsocks[$i]['link'] = URLSchemes::buildShadowsocks($shadowsocks[$i], $user);
             if (in_array($user->group_id, $groupId)) {
                 if ($shadowsocks[$i]['parent_id']) {
                     $shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id']));
@@ -196,6 +191,9 @@ class ServerService
                 case 'quic':
                     $json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings);
                     break;
+                case 'grpc':
+                    $json->inbound->streamSettings->grpcSettings = json_decode($server->networkSettings);
+                    break;
             }
         }
     }
@@ -358,4 +356,17 @@ class ServerService
             }
         }
     }
+
+    public function getAllServers()
+    {
+        $servers = array_merge(
+            $this->getShadowsocksServers(),
+            $this->getV2rayServers(),
+            $this->getTrojanServers()
+        );
+        $this->mergeData($servers);
+        $tmp = array_column($servers, 'sort');
+        array_multisort($tmp, SORT_ASC, $servers);
+        return $servers;
+    }
 }

+ 4 - 1
app/Services/UserService.php

@@ -2,9 +2,12 @@
 
 namespace App\Services;
 
+use App\Models\InviteCode;
 use App\Models\Order;
 use App\Models\Server;
+use App\Models\Ticket;
 use App\Models\User;
+use Illuminate\Support\Facades\DB;
 
 class UserService
 {
@@ -52,7 +55,7 @@ class UserService
 
     public function addBalance(int $userId, int $balance):bool
     {
-        $user = User::find($userId);
+        $user = User::lockForUpdate()->find($userId);
         if (!$user) {
             return false;
         }

+ 0 - 68
app/Utils/URLSchemes.php

@@ -1,68 +0,0 @@
-<?php
-namespace App\Utils;
-
-use App\Models\Server;
-use App\Models\User;
-
-class URLSchemes
-{
-    public static function buildShadowsocks($server, User $user)
-    {
-        $name = rawurlencode($server['name']);
-        $str = str_replace(
-            ['+', '/', '='],
-            ['-', '_', ''],
-            base64_encode("{$server['cipher']}:{$user['uuid']}")
-        );
-        return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
-    }
-
-    public static function buildShadowsocksSIP008($server, User $user)
-    {
-        $config = [
-            "id" => $server['id'],
-            "remarks" => $server['name'],
-            "server" => $server['host'],
-            "server_port" => $server['port'],
-            "password" => $user['uuid'],
-            "method" => $server['cipher']
-        ];
-        return $config;
-    }
-
-    public static function buildVmess($server, User $user)
-    {
-        $config = [
-            "v" => "2",
-            "ps" => $server['name'],
-            "add" => $server['host'],
-            "port" => (string)$server['port'],
-            "id" => $user['uuid'],
-            "aid" => (string)$server['alter_id'],
-            "net" => $server['network'],
-            "type" => "none",
-            "host" => "",
-            "path" => "",
-            "tls" => $server['tls'] ? "tls" : ""
-        ];
-        if ((string)$server['network'] === 'ws') {
-            $wsSettings = json_decode($server['networkSettings'], true);
-            if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
-            if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
-        }
-        return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
-    }
-
-    public static function buildTrojan($server, User $user)
-    {
-        $name = rawurlencode($server['name']);
-        $query = http_build_query([
-            'allowInsecure' => $server['allow_insecure'],
-            'peer' => $server['server_name'],
-            'sni' => $server['server_name']
-        ]);
-        $uri = "trojan://{$user['uuid']}@{$server['host']}:{$server['port']}?{$query}#{$name}";
-        $uri .= "\r\n";
-        return $uri;
-    }
-}

+ 1 - 6
composer.json

@@ -15,6 +15,7 @@
         "laravel/framework": "^6.0",
         "laravel/tinker": "^1.0",
         "lokielse/omnipay-alipay": "3.0.6",
+        "lokielse/omnipay-wechatpay": "^3.0",
         "php-curl-class/php-curl-class": "^8.6",
         "stripe/stripe-php": "^7.36.1",
         "symfony/yaml": "^4.3"
@@ -64,11 +65,5 @@
         "post-create-project-cmd": [
             "@php artisan key:generate --ansi"
         ]
-    },
-    "repositories": {
-        "packagist": {
-            "type": "composer",
-            "url": "https://mirrors.aliyun.com/composer/"
-        }
     }
 }

+ 1 - 1
config/app.php

@@ -236,5 +236,5 @@ return [
     | The only modification by laravel config
     |
     */
-    'version' => '1.5.1.1621339182281'
+    'version' => '1.5.2.1626426358299'
 ];

+ 2 - 1
database/install.sql

@@ -321,6 +321,7 @@ CREATE TABLE `v2_user` (
                            `password_algo` char(10) DEFAULT NULL,
                            `balance` int(11) NOT NULL DEFAULT '0',
                            `discount` int(11) DEFAULT NULL,
+                           `commission_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime',
                            `commission_rate` int(11) DEFAULT NULL,
                            `commission_balance` int(11) NOT NULL DEFAULT '0',
                            `t` int(11) NOT NULL DEFAULT '0',
@@ -347,4 +348,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2021-05-06 16:14:04
+-- 2021-07-13 13:50:52

+ 14 - 0
database/update.sql

@@ -411,3 +411,17 @@ ALTER TABLE `v2_order`
 
 ALTER TABLE `v2_payment`
     ADD `uuid` char(32) NOT NULL AFTER `id`;
+
+ALTER TABLE `v2_user`
+    ADD UNIQUE `email_deleted_at` (`email`, `deleted_at`),
+DROP INDEX `email`;
+
+ALTER TABLE `v2_user`
+DROP `deleted_at`;
+
+ALTER TABLE `v2_user`
+    ADD UNIQUE `email` (`email`),
+DROP INDEX `email_deleted_at`;
+
+ALTER TABLE `v2_user`
+    ADD `commission_type` tinyint NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime' AFTER `discount`;

+ 0 - 21
package.json

@@ -1,21 +0,0 @@
-{
-    "private": true,
-    "scripts": {
-        "dev": "npm run development",
-        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
-        "watch": "npm run development -- --watch",
-        "watch-poll": "npm run watch -- --watch-poll",
-        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
-        "prod": "npm run production",
-        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
-    },
-    "devDependencies": {
-        "axios": "^0.19",
-        "cross-env": "^5.1",
-        "laravel-mix": "^4.0.7",
-        "lodash": "^4.17.13",
-        "resolve-url-loader": "^2.3.1",
-        "sass": "^1.15.2",
-        "sass-loader": "^7.1.0"
-    }
-}

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/assets/admin/umi.css


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/assets/admin/umi.js


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/assets/user/components.async.js


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/assets/user/umi.js


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/theme/v2board/assets/components.async.js


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/theme/v2board/assets/components.chunk.css


+ 0 - 0
public/assets/user/env.example.js → public/theme/v2board/assets/env.example.js


+ 0 - 0
public/assets/user/static/Simple-Line-Icons.0cb0b9c5.woff2 → public/theme/v2board/assets/static/Simple-Line-Icons.0cb0b9c5.woff2


+ 0 - 0
public/assets/user/static/Simple-Line-Icons.78f07e2c.woff → public/theme/v2board/assets/static/Simple-Line-Icons.78f07e2c.woff


+ 0 - 0
public/assets/user/static/Simple-Line-Icons.d2285965.ttf → public/theme/v2board/assets/static/Simple-Line-Icons.d2285965.ttf


+ 0 - 0
public/assets/user/static/Simple-Line-Icons.ed67e5a3.svg → public/theme/v2board/assets/static/Simple-Line-Icons.ed67e5a3.svg


+ 0 - 0
public/assets/user/static/Simple-Line-Icons.f33df365.eot → public/theme/v2board/assets/static/Simple-Line-Icons.f33df365.eot


+ 0 - 0
public/assets/user/static/fa-brands-400.14c590d1.eot → public/theme/v2board/assets/static/fa-brands-400.14c590d1.eot


+ 0 - 0
public/assets/user/static/fa-brands-400.3e1b2a65.woff2 → public/theme/v2board/assets/static/fa-brands-400.3e1b2a65.woff2


+ 0 - 0
public/assets/user/static/fa-brands-400.5e8aa9ea.ttf → public/theme/v2board/assets/static/fa-brands-400.5e8aa9ea.ttf


+ 0 - 0
public/assets/user/static/fa-brands-400.91fd86e5.svg → public/theme/v2board/assets/static/fa-brands-400.91fd86e5.svg


+ 0 - 0
public/assets/user/static/fa-brands-400.df02c782.woff → public/theme/v2board/assets/static/fa-brands-400.df02c782.woff


+ 0 - 0
public/assets/user/static/fa-regular-400.285a9d2a.ttf → public/theme/v2board/assets/static/fa-regular-400.285a9d2a.ttf


+ 0 - 0
public/assets/user/static/fa-regular-400.5623624d.woff → public/theme/v2board/assets/static/fa-regular-400.5623624d.woff


+ 0 - 0
public/assets/user/static/fa-regular-400.6b5ed912.svg → public/theme/v2board/assets/static/fa-regular-400.6b5ed912.svg


+ 0 - 0
public/assets/user/static/fa-regular-400.aa66d0e0.eot → public/theme/v2board/assets/static/fa-regular-400.aa66d0e0.eot


+ 0 - 0
public/assets/user/static/fa-regular-400.ac21cac3.woff2 → public/theme/v2board/assets/static/fa-regular-400.ac21cac3.woff2


+ 0 - 0
public/assets/user/static/fa-solid-900.3ded831d.woff → public/theme/v2board/assets/static/fa-solid-900.3ded831d.woff


+ 0 - 0
public/assets/user/static/fa-solid-900.42e1fbd2.eot → public/theme/v2board/assets/static/fa-solid-900.42e1fbd2.eot


+ 0 - 0
public/assets/user/static/fa-solid-900.649208f1.svg → public/theme/v2board/assets/static/fa-solid-900.649208f1.svg


+ 0 - 0
public/assets/user/static/fa-solid-900.896e20e2.ttf → public/theme/v2board/assets/static/fa-solid-900.896e20e2.ttf


+ 0 - 0
public/assets/user/static/fa-solid-900.d6d8d5da.woff2 → public/theme/v2board/assets/static/fa-solid-900.d6d8d5da.woff2


+ 0 - 0
public/assets/user/theme/black.css → public/theme/v2board/assets/theme/black.css


+ 0 - 0
public/assets/user/theme/darkblue.css → public/theme/v2board/assets/theme/darkblue.css


+ 0 - 0
public/assets/user/theme/default.css → public/theme/v2board/assets/theme/default.css


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/theme/v2board/assets/umi.css


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
public/theme/v2board/assets/umi.js


+ 0 - 0
public/assets/user/vendors.async.js → public/theme/v2board/assets/vendors.async.js


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff