Merge pull request #466 from v2board/dev

1.5.2
This commit is contained in:
tokumeikoi 2021-07-29 20:15:19 +09:00 committed by GitHub
commit fb449b490e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 1491 additions and 1039 deletions

1
.gitignore vendored
View File

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

View File

@ -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

View File

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

View File

@ -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));
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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) public function setTelegramWebhook(Request $request)
{ {
$telegramService = new TelegramService($request->input('telegram_bot_token')); $telegramService = new TelegramService($request->input('telegram_bot_token'));
@ -72,8 +83,10 @@ class ConfigController extends Controller
'subscribe' => [ 'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), 'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0), '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' => [ 'pay' => [
// alipay // alipay
@ -107,6 +120,7 @@ class ConfigController extends Controller
'epay_key' => config('v2board.epay_key'), 'epay_key' => config('v2board.epay_key'),
], ],
'frontend' => [ 'frontend' => [
'frontend_theme' => config('v2board.frontend_theme', 'v2board'),
'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'), 'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'), 'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'), 'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),

View File

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

View File

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

View File

@ -15,16 +15,8 @@ class ManageController extends Controller
public function getNodes(Request $request) public function getNodes(Request $request)
{ {
$serverService = new ServerService(); $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([ return response([
'data' => $servers 'data' => $serverService->getAllServers()
]); ]);
} }

View File

@ -7,10 +7,10 @@ use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail; use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate; use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob; use App\Jobs\SendEmailJob;
use App\Services\UserService;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -81,8 +81,12 @@ class UserController extends Controller
if (empty($request->input('id'))) { if (empty($request->input('id'))) {
abort(500, '参数错误'); abort(500, '参数错误');
} }
$user = User::find($request->input('id'));
if ($user->invite_user_id) {
$user['invite_user'] = User::find($user->invite_user_id);
}
return response([ return response([
'data' => User::find($request->input('id')) 'data' => $user
]); ]);
} }
@ -109,6 +113,14 @@ class UserController extends Controller
} }
$params['group_id'] = $plan->group_id; $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 { try {
$user->update($params); $user->update($params);
@ -265,30 +277,4 @@ class UserController extends Controller
'data' => true '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()
]);
}
} }

View File

@ -8,6 +8,7 @@ use App\Services\UserService;
use App\Utils\Clash; use App\Utils\Clash;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
class AppController extends Controller class AppController extends Controller
@ -25,21 +26,27 @@ class AppController extends Controller
$serverService = new ServerService(); $serverService = new ServerService();
$servers = $serverService->getAvailableServers($user); $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 = []; $proxy = [];
$proxies = []; $proxies = [];
foreach ($servers as $item) { foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') { 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']); array_push($proxies, $item['name']);
} }
if ($item['type'] === 'v2ray') { 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']); array_push($proxies, $item['name']);
} }
if ($item['type'] === 'trojan') { 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']); 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));
}
} }

View File

@ -2,18 +2,10 @@
namespace App\Http\Controllers\Client; namespace App\Http\Controllers\Client;
use App\Http\Controllers\Client\Protocols\V2rayN;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\ServerService; 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 Illuminate\Http\Request;
use App\Models\Server;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
use App\Services\UserService; use App\Services\UserService;
class ClientController extends Controller class ClientController extends Controller
@ -32,251 +24,18 @@ class ClientController extends Controller
$serverService = new ServerService(); $serverService = new ServerService();
$servers = $serverService->getAvailableServers($user); $servers = $serverService->getAvailableServers($user);
if ($flag) { if ($flag) {
if (strpos($flag, 'quantumult%20x') !== false) { foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
die($this->quantumultX($user, $servers)); $file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
} $class = new $file($user, $servers);
if (strpos($flag, 'quantumult') !== false) { if (strpos($flag, $class->flag) !== false) {
die($this->quantumult($user, $servers)); die($class->handle());
}
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'] . '"';
} }
} }
$uri .= "vmess://" . base64_encode($str) . "\r\n";
} }
// todo 1.5.3 remove
$class = new V2rayN($user, $servers);
die($class->handle());
die('该客户端暂不支持进行订阅');
} }
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);
}
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
} }
} }

View File

@ -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;
}
}

View File

@ -1,10 +1,61 @@
<?php <?php
namespace App\Utils; namespace App\Http\Controllers\Client\Protocols;
use Symfony\Component\Yaml\Yaml;
class Clash 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) public static function buildShadowsocks($uuid, $server)
{ {
$array = []; $array = [];
@ -50,6 +101,14 @@ class Clash
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']]; $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; return $array;
} }

View File

@ -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;
}
}

View File

@ -1,10 +1,40 @@
<?php <?php
namespace App\Utils; namespace App\Http\Controllers\Client\Protocols;
class QuantumultX 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) public static function buildShadowsocks($password, $server)
{ {
$config = [ $config = [

View File

@ -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;
}
}

View File

@ -1,10 +1,46 @@
<?php <?php
namespace App\Utils; namespace App\Http\Controllers\Client\Protocols;
class Shadowrocket 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) public static function buildShadowsocks($password, $server)
{ {
$name = rawurlencode($server['name']); $name = rawurlencode($server['name']);
@ -44,6 +80,19 @@ class Shadowrocket
$config['obfsParam'] = $wsSettings['headers']['Host']; $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); $query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
$uri = "vmess://{$userinfo}?{$query}"; $uri = "vmess://{$userinfo}?{$query}";
$uri .= "\r\n"; $uri .= "\r\n";

View File

@ -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;
}
}

View File

@ -1,10 +1,61 @@
<?php <?php
namespace App\Utils; namespace App\Http\Controllers\Client\Protocols;
class Surfboard 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) public static function buildShadowsocks($password, $server)
{ {
$config = [ $config = [

View File

@ -1,10 +1,66 @@
<?php <?php
namespace App\Utils; namespace App\Http\Controllers\Client\Protocols;
class Surge 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) public static function buildShadowsocks($password, $server)
{ {
$config = [ $config = [

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -11,8 +11,26 @@ class CommController extends Controller
{ {
return response([ return response([
'data' => [ '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;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -11,25 +11,25 @@ class CouponController extends Controller
public function check(Request $request) public function check(Request $request)
{ {
if (empty($request->input('code'))) { 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(); $coupon = Coupon::where('code', $request->input('code'))->first();
if (!$coupon) { if (!$coupon) {
abort(500, __('user.coupon.check.coupon_invalid')); abort(500, __('Invalid coupon'));
} }
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) { 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) { 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) { if (time() > $coupon->ended_at) {
abort(500, __('user.coupon.check.coupon_expired')); abort(500, __('This coupon has expired'));
} }
if ($coupon->limit_plan_ids) { if ($coupon->limit_plan_ids) {
$limitPlanIds = json_decode($coupon->limit_plan_ids); $limitPlanIds = json_decode($coupon->limit_plan_ids);
if (!in_array($request->input('plan_id'), $limitPlanIds)) { 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([ return response([

View File

@ -14,7 +14,7 @@ class InviteController extends Controller
public function save(Request $request) public function save(Request $request)
{ {
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) { 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 = new InviteCode();
$inviteCode->user_id = $request->session()->get('id'); $inviteCode->user_id = $request->session()->get('id');

View File

@ -17,18 +17,23 @@ class KnowledgeController extends Controller
->where('show', 1) ->where('show', 1)
->first() ->first()
->toArray(); ->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')); $user = User::find($request->session()->get('id'));
$userService = new UserService(); $userService = new UserService();
if ($userService->isAvailable($user)) { if ($userService->isAvailable($user)) {
$appleId = config('v2board.apple_id'); $appleId = config('v2board.apple_id');
$appleIdPassword = config('v2board.apple_id_password'); $appleIdPassword = config('v2board.apple_id_password');
} else { } else {
$appleId = __('user.knowledge.fetch.apple_id_must_be_plan'); $appleId = __('No active subscription. Unable to use our provided Apple ID');
$appleIdPassword = __('user.knowledge.fetch.apple_id_must_be_plan'); $appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
$this->formatAccessData($knowledge['body']); $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('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
$knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']); $knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
$knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $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;} 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-->'); $accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
if ($accessData) { 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);
} }
} }
} }

View File

@ -52,12 +52,12 @@ class OrderController extends Controller
->where('trade_no', $request->input('trade_no')) ->where('trade_no', $request->input('trade_no'))
->first(); ->first();
if (!$order) { 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['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id'); $order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) { if (!$order['plan']) {
abort(500, __('user.order.details.plan_not_exist')); abort(500, __('Subscription plan does not exist'));
} }
return response([ return response([
'data' => $order 'data' => $order
@ -68,38 +68,38 @@ class OrderController extends Controller
{ {
$userService = new UserService(); $userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) { 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')); $plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
if (!$plan) { 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 ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
if ($request->input('cycle') !== 'reset_price') { 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') { 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) { 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 ($request->input('cycle') === 'reset_price') {
if ($user->expired_at <= time() || !$user->plan_id) { 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)) { 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(); DB::beginTransaction();
@ -115,7 +115,7 @@ class OrderController extends Controller
$couponService = new CouponService($request->input('coupon_code')); $couponService = new CouponService($request->input('coupon_code'));
if (!$couponService->use($order)) { if (!$couponService->use($order)) {
DB::rollBack(); DB::rollBack();
abort(500, __('user.order.save.coupon_use_failed')); abort(500, __('Coupon failed'));
} }
$order->coupon_id = $couponService->getId(); $order->coupon_id = $couponService->getId();
} }
@ -130,14 +130,14 @@ class OrderController extends Controller
if ($remainingBalance > 0) { if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, - $order->total_amount)) { if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
DB::rollBack(); DB::rollBack();
abort(500, __('user.order.save.insufficient_balance')); abort(500, __('Insufficient balance'));
} }
$order->balance_amount = $order->total_amount; $order->balance_amount = $order->total_amount;
$order->total_amount = 0; $order->total_amount = 0;
} else { } else {
if (!$userService->addBalance($order->user_id, - $user->balance)) { if (!$userService->addBalance($order->user_id, - $user->balance)) {
DB::rollBack(); DB::rollBack();
abort(500, __('user.order.save.insufficient_balance')); abort(500, __('Insufficient balance'));
} }
$order->balance_amount = $user->balance; $order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance; $order->total_amount = $order->total_amount - $user->balance;
@ -146,7 +146,7 @@ class OrderController extends Controller
if (!$order->save()) { if (!$order->save()) {
DB::rollback(); DB::rollback();
abort(500, __('user.order.save.order_create_failed')); abort(500, __('Failed to create order'));
} }
DB::commit(); DB::commit();
@ -165,7 +165,7 @@ class OrderController extends Controller
->where('status', 0) ->where('status', 0)
->first(); ->first();
if (!$order) { if (!$order) {
abort(500, __('user.order.checkout.order_not_exist_or_paid')); abort(500, __('Order does not exist or has been paid'));
} }
// free process // free process
if ($order->total_amount <= 0) { if ($order->total_amount <= 0) {
@ -178,7 +178,7 @@ class OrderController extends Controller
]); ]);
} }
$payment = Payment::find($method); $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); $paymentService = new PaymentService($payment->payment, $payment->id);
$result = $paymentService->pay([ $result = $paymentService->pay([
'trade_no' => $tradeNo, 'trade_no' => $tradeNo,
@ -200,7 +200,7 @@ class OrderController extends Controller
->where('user_id', $request->session()->get('id')) ->where('user_id', $request->session()->get('id'))
->first(); ->first();
if (!$order) { if (!$order) {
abort(500, __('user.order.check.order_not_exist')); abort(500, __('Order does not exist'));
} }
return response([ return response([
'data' => $order->status 'data' => $order->status
@ -224,20 +224,20 @@ class OrderController extends Controller
public function cancel(Request $request) public function cancel(Request $request)
{ {
if (empty($request->input('trade_no'))) { 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')) $order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id')) ->where('user_id', $request->session()->get('id'))
->first(); ->first();
if (!$order) { if (!$order) {
abort(500, __('user.order.cancel.order_not_exist')); abort(500, __('Order does not exist'));
} }
if ($order->status !== 0) { 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); $orderService = new OrderService($order);
if (!$orderService->cancel()) { if (!$orderService->cancel()) {
abort(500, __('user.order.cancel.cancel_failed')); abort(500, __('Cancel failed'));
} }
return response([ return response([
'data' => true 'data' => true

View File

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

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\User; namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\TelegramService; use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller 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();
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ class UserFetch extends FormRequest
public function rules() public function rules()
{ {
return [ 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.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => 'required' 'filter.*.value' => 'required'
]; ];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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']
];
}
}

View File

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

View File

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

View File

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

View File

@ -8,8 +8,6 @@ use App\Models\User;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use App\Utils\Helper;
use App\Utils\URLSchemes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class ServerService class ServerService
@ -29,7 +27,6 @@ class ServerService
$v2ray[$i]['type'] = 'v2ray'; $v2ray[$i]['type'] = 'v2ray';
$groupId = json_decode($v2ray[$i]['group_id']); $groupId = json_decode($v2ray[$i]['group_id']);
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
$v2ray[$i]['link'] = URLSchemes::buildVmess($v2ray[$i], $user);
if ($v2ray[$i]['parent_id']) { if ($v2ray[$i]['parent_id']) {
$v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id'])); $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id']));
} else { } else {
@ -54,7 +51,6 @@ class ServerService
for ($i = 0; $i < count($trojan); $i++) { for ($i = 0; $i < count($trojan); $i++) {
$trojan[$i]['type'] = 'trojan'; $trojan[$i]['type'] = 'trojan';
$groupId = json_decode($trojan[$i]['group_id']); $groupId = json_decode($trojan[$i]['group_id']);
$trojan[$i]['link'] = URLSchemes::buildTrojan($trojan[$i], $user);
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($trojan[$i]['parent_id']) { if ($trojan[$i]['parent_id']) {
$trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $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++) { for ($i = 0; $i < count($shadowsocks); $i++) {
$shadowsocks[$i]['type'] = 'shadowsocks'; $shadowsocks[$i]['type'] = 'shadowsocks';
$groupId = json_decode($shadowsocks[$i]['group_id']); $groupId = json_decode($shadowsocks[$i]['group_id']);
$shadowsocks[$i]['link'] = URLSchemes::buildShadowsocks($shadowsocks[$i], $user);
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($shadowsocks[$i]['parent_id']) { if ($shadowsocks[$i]['parent_id']) {
$shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $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': case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings);
break; 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;
}
} }

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

@ -321,6 +321,7 @@ CREATE TABLE `v2_user` (
`password_algo` char(10) DEFAULT NULL, `password_algo` char(10) DEFAULT NULL,
`balance` int(11) NOT NULL DEFAULT '0', `balance` int(11) NOT NULL DEFAULT '0',
`discount` int(11) DEFAULT NULL, `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_rate` int(11) DEFAULT NULL,
`commission_balance` int(11) NOT NULL DEFAULT '0', `commission_balance` int(11) NOT NULL DEFAULT '0',
`t` 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; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2021-05-06 16:14:04 -- 2021-07-13 13:50:52

View File

@ -411,3 +411,17 @@ ALTER TABLE `v2_order`
ALTER TABLE `v2_payment` ALTER TABLE `v2_payment`
ADD `uuid` char(32) NOT NULL AFTER `id`; 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`;

View File

@ -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"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View File

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 680 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 832 KiB

After

Width:  |  Height:  |  Size: 832 KiB

File diff suppressed because one or more lines are too long

1
public/theme/v2board/assets/umi.js vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More