Pārlūkot izejas kodu

feat: discord service & configurable jsdelivr url

M1Screw 2 gadi atpakaļ
vecāks
revīzija
e3e878dc20

+ 1 - 0
app/routes.php

@@ -236,6 +236,7 @@ return static function (Slim\App $app): void {
         $group->get('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':support');
         $group->post('/setting/support', App\Controllers\Admin\Setting\SupportController::class . ':saveSupport');
         $group->post('/setting/test_email', App\Controllers\Admin\Setting\EmailController::class . ':testEmail');
+        $group->post('/setting/test_discord', App\Controllers\Admin\Setting\ImController::class . ':testDiscord');
         // 礼品卡
         $group->get('/giftcard', App\Controllers\Admin\GiftCardController::class . ':index');
         $group->post('/giftcard', App\Controllers\Admin\GiftCardController::class . ':add');

+ 7 - 8
composer.lock

@@ -6617,16 +6617,16 @@
     "packages-dev": [
         {
             "name": "cmgmyr/phploc",
-            "version": "8.0.2",
+            "version": "8.0.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/cmgmyr/phploc.git",
-                "reference": "35e308033e02264a59cb1b56cc2abb1a22483ca8"
+                "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/35e308033e02264a59cb1b56cc2abb1a22483ca8",
-                "reference": "35e308033e02264a59cb1b56cc2abb1a22483ca8",
+                "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/e61d4729df46c5920ab61973bfa3f70f81a70b5f",
+                "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f",
                 "shasum": ""
             },
             "require": {
@@ -6634,8 +6634,7 @@
                 "ext-json": "*",
                 "php": "^7.4 || ^8.0",
                 "phpunit/php-file-iterator": "^3.0|^4.0",
-                "sebastian/cli-parser": "^1.0|^2.0",
-                "sebastian/version": "^3.0|^4.0"
+                "sebastian/cli-parser": "^1.0|^2.0"
             },
             "require-dev": {
                 "friendsofphp/php-cs-fixer": "^3.2",
@@ -6671,7 +6670,7 @@
             "homepage": "https://github.com/cmgmyr/phploc",
             "support": {
                 "issues": "https://github.com/cmgmyr/phploc/issues",
-                "source": "https://github.com/cmgmyr/phploc/tree/8.0.2"
+                "source": "https://github.com/cmgmyr/phploc/tree/8.0.3"
             },
             "funding": [
                 {
@@ -6679,7 +6678,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-03-19T10:37:20+00:00"
+            "time": "2023-08-05T16:49:39+00:00"
         },
         {
             "name": "composer/pcre",

+ 2 - 0
config/.config.example.php

@@ -133,6 +133,8 @@ foreach ($_ENV['cdn_forwarded_ip'] as $cdn_forwarded_ip) {
     }
 }
 
+$_ENV['jsdelivr_url'] = 'fastly.jsdelivr.net'; // cdn.jsdelivr.net / fastly.jsdelivr.net / gcore.jsdelivr.net / testingcf.jsdelivr.net
+
 // https://sentry.io for production debugging
 $_ENV['sentry_dsn'] = '';
 

+ 10 - 0
config/settings.json

@@ -639,6 +639,16 @@
         "default": "",
         "mark": "aws ses发送者"
     },
+    {
+        "id": null,
+        "item": "discord_bot_token",
+        "value": "",
+        "class": "discord",
+        "is_public": 0,
+        "type": "string",
+        "default": "",
+        "mark": "Discord Bot Token"
+    },
     {
         "id": null,
         "item": "tawk_id",

+ 3 - 3
resources/views/tabler/admin/coupon.tpl

@@ -1,8 +1,8 @@
 {include file='admin/header.tpl'}
 
-<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
-<script src="//cdn.jsdelivr.net/npm/flatpickr"></script>
-<script src="//cdn.jsdelivr.net/npm/flatpickr/dist/l10n/zh.js"></script>
+<link rel="stylesheet" href="//{$config['jsdelivr_url']}/npm/flatpickr/dist/flatpickr.min.css">
+<script src="//{$config['jsdelivr_url']}/npm/flatpickr"></script>
+<script src="//{$config['jsdelivr_url']}/npm/flatpickr/dist/l10n/zh.js"></script>
 
 <div class="page-wrapper">
     <div class="container-xl">

+ 1 - 1
resources/views/tabler/admin/footer.tpl

@@ -134,7 +134,7 @@
         location.reload();
     });
 </script>
-<script src="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
+<script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 <script>console.table([['数据库查询', '执行时间'], ['{count($queryLog)} 次', '{$optTime} ms']])</script>
 
 </body>

+ 5 - 5
resources/views/tabler/admin/header.tpl

@@ -9,13 +9,13 @@
     <meta http-equiv="X-UA-Compatible" content="ie=edge" />
     <title>{$config['appName']}</title>
     <!-- CSS files -->
-    <link href="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
-    <link href="//cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
     <link href="//cdn.datatables.net/v/bs5/dt-1.13.5/datatables.min.css" rel="stylesheet" />
     <!-- JS files -->
-    <script src="//cdn.jsdelivr.net/npm/qrcode_js@latest/qrcode.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/clipboard@latest/dist/clipboard.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/qrcode_js@latest/qrcode.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/clipboard@latest/dist/clipboard.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
     <script src="//cdn.datatables.net/v/bs5/dt-1.13.5/datatables.min.js"></script>
     <style>
         .home-subtitle {

+ 1 - 1
resources/views/tabler/admin/index.tpl

@@ -313,6 +313,6 @@
         });
     </script>
 
-    <script src="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/libs/apexcharts/dist/apexcharts.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/libs/apexcharts/dist/apexcharts.min.js"></script>
 
 {include file='admin/footer.tpl'}

+ 2 - 2
resources/views/tabler/admin/node/create.tpl

@@ -1,7 +1,7 @@
 {include file='admin/header.tpl'}
 
-<script src="//cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.js"></script>
-<link href="//cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
+<script src="//{$config['jsdelivr_url']}/npm/jsoneditor@latest/dist/jsoneditor.min.js"></script>
+<link href="//{$config['jsdelivr_url']}/npm/jsoneditor@latest/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
 
 <div class="page-wrapper">
     <div class="container-xl">

+ 2 - 2
resources/views/tabler/admin/node/edit.tpl

@@ -1,7 +1,7 @@
 {include file='admin/header.tpl'}
 
-<script src="//cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.js"></script>
-<link href="//cdn.jsdelivr.net/npm/jsoneditor@latest/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
+<script src="//{$config['jsdelivr_url']}/npm/jsoneditor@latest/dist/jsoneditor.min.js"></script>
+<link href="//{$config['jsdelivr_url']}/npm/jsoneditor@latest/dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
 
 <div class="page-wrapper">
     <div class="container-xl">

+ 2 - 2
resources/views/tabler/admin/setting/email.tpl

@@ -321,8 +321,8 @@
             },
             success: function(data) {
                 if (data.ret === 1) {
-                    $('#success-message').text(data.msg);
-                    $('#success-dialog').modal('show');
+                    $('#success-noreload-message').text(data.msg);
+                    $('#success-noreload-dialog').modal('show');
                 } else {
                     $('#fail-message').text(data.msg);
                     $('#fail-dialog').modal('show');

+ 43 - 1
resources/views/tabler/admin/setting/im.tpl

@@ -34,7 +34,10 @@
                             <a href="#telegram_notification" class="nav-link active" data-bs-toggle="tab">Telegram 通知设定</a>
                         </li>
                         <li class="nav-item">
-                            <a href="#telegram_bot" class="nav-link" data-bs-toggle="tab">Telegram Bot 设定</a>
+                            <a href="#telegram_bot" class="nav-link" data-bs-toggle="tab">Telegram Bot</a>
+                        </li>
+                        <li class="nav-item">
+                            <a href="#discord_bot" class="nav-link" data-bs-toggle="tab">Discord Bot</a>
                         </li>
                     </ul>
                 </div>
@@ -318,6 +321,25 @@
                                 </div>
                             </div>
                         </div>
+                        <div class="tab-pane" id="discord_bot">
+                            <div class="card-body">
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">Bot Token</label>
+                                    <div class="col">
+                                        <input id="discord_bot_token" type="text" class="form-control" value="{$settings['discord_bot_token']}">
+                                    </div>
+                                </div>
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">Discord 用户 ID</label>
+                                    <input type="text" class="form-control" id="discord_user_id" value="">
+                                    <div class="row my-3">
+                                        <div class="col">
+                                            <button id="test-discord" class="btn btn-primary">发送测试信息</button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
                     </div>
                 </div>
             </div>
@@ -347,6 +369,26 @@
             }
         })
     });
+
+    $("#test-discord").click(function() {
+        $.ajax({
+            url: '/admin/setting/test_discord',
+            type: 'POST',
+            dataType: "json",
+            data: {
+                discord_user_id: $('#discord_user_id').val(),
+            },
+            success: function(data) {
+                if (data.ret === 1) {
+                    $('#success-noreload-message').text(data.msg);
+                    $('#success-noreload-dialog').modal('show');
+                } else {
+                    $('#fail-message').text(data.msg);
+                    $('#fail-dialog').modal('show');
+                }
+            }
+        })
+    });
 </script>
 
 {include file='admin/footer.tpl'}

+ 1 - 1
resources/views/tabler/footer.tpl

@@ -46,7 +46,7 @@
     </div>
 </div>
 
-<script src="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
+<script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 
 {include file='live_chat.tpl'}
 

+ 1 - 1
resources/views/tabler/gateway/stripe.tpl

@@ -1,4 +1,4 @@
-<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler-payments.min.css">
+<link rel="stylesheet" href="https://{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler-payments.min.css">
 
 <div class="card-inner">
     <h4>

+ 3 - 3
resources/views/tabler/header.tpl

@@ -9,9 +9,9 @@
     <meta http-equiv="X-UA-Compatible" content="ie=edge" />
     <title>{$config['appName']}</title>
     <!-- CSS files -->
-    <link href="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
-    <link href="//cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
     <!-- JS files -->
     <script src="/assets/js/fuck.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
 </head>

+ 1 - 1
resources/views/tabler/user/footer.tpl

@@ -117,7 +117,7 @@
         location.reload();
     });
 </script>
-<script src="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
+<script src="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/js/tabler.min.js"></script>
 <script>console.table([['数据库查询', '执行时间'], ['{count($queryLog)} 次', '{$optTime} ms']])</script>
 
 {include file='live_chat.tpl'}

+ 5 - 5
resources/views/tabler/user/header.tpl

@@ -9,13 +9,13 @@
     <meta name="referrer" content="never">
     <title>{$config['appName']}</title>
     <!-- CSS files -->
-    <link href="//cdn.jsdelivr.net/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
-    <link href="//cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/core@latest/dist/css/tabler.min.css" rel="stylesheet" />
+    <link href="//{$config['jsdelivr_url']}/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" rel="stylesheet" />
     <!-- JS files -->
     <script src="/assets/js/fuck.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/qrcode_js@latest/qrcode.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/clipboard@latest/dist/clipboard.min.js"></script>
-    <script src="//cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/qrcode_js@latest/qrcode.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/clipboard@latest/dist/clipboard.min.js"></script>
+    <script src="//{$config['jsdelivr_url']}/npm/jquery/dist/jquery.min.js"></script>
     <style>
         .home-subtitle {
             font-size: 14px;

+ 2 - 1
src/Controllers/Admin/Setting/EmailController.php

@@ -113,9 +113,10 @@ final class EmailController extends BaseController
         } catch (Throwable $e) {
             return $response->withJson([
                 'ret' => 0,
-                'msg' => '测试邮件发送失败',
+                'msg' => '测试邮件发送失败 ' . $e->getMessage(),
             ]);
         }
+
         return $response->withJson([
             'ret' => 1,
             'msg' => '测试邮件发送成功',

+ 25 - 0
src/Controllers/Admin/Setting/ImController.php

@@ -6,7 +6,9 @@ namespace App\Controllers\Admin\Setting;
 
 use App\Controllers\BaseController;
 use App\Models\Setting;
+use App\Services\IM\Discord;
 use Exception;
+use GuzzleHttp\Exception\GuzzleException;
 use function json_encode;
 
 final class ImController extends BaseController
@@ -48,6 +50,7 @@ final class ImController extends BaseController
         'user_not_bind_reply',
         'telegram_general_pricing',
         'telegram_general_terms',
+        'discord_bot_token',
     ];
 
     /**
@@ -100,4 +103,26 @@ final class ImController extends BaseController
             'msg' => '保存成功',
         ]);
     }
+
+    public function testDiscord($request, $response, $args)
+    {
+        $to = $request->getParam('discord_user_id');
+
+        try {
+            (new Discord())->send(
+                $to,
+                '这是一条测试消息',
+            );
+        } catch (GuzzleException|Exception $e) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '测试信息发送失败 ' . $e->getMessage(),
+            ]);
+        }
+
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '测试信息发送成功',
+        ]);
+    }
 }

+ 3 - 5
src/Services/Config.php

@@ -7,11 +7,7 @@ namespace App\Services;
 // Config is singleton instance store all config
 final class Config
 {
-    private function __construct()
-    {
-    }
-
-    public static function getPublicConfig(): array
+    public static function getViewConfig(): array
     {
         return [
             'appName' => $_ENV['appName'],
@@ -34,6 +30,8 @@ final class Config
             'subscribeLog_keep_days' => $_ENV['subscribeLog_keep_days'],
 
             'enable_r2_client_download' => $_ENV['enable_r2_client_download'],
+
+            'jsdelivr_url' => $_ENV['jsdelivr_url'],
         ];
     }
 

+ 10 - 0
src/Services/IM/Base.php

@@ -0,0 +1,10 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services\IM;
+
+abstract class Base
+{
+    abstract public function send($to, $msg): void;
+}

+ 63 - 0
src/Services/IM/Discord.php

@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services\IM;
+
+use App\Models\Setting;
+use Exception;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use const VERSION;
+
+final class Discord extends Base
+{
+    private string $token;
+    private Client $client;
+
+    public function __construct()
+    {
+        $this->token = Setting::obtain('discord_bot_token');
+        $this->client = new Client();
+    }
+
+    /**
+     * @throws GuzzleException
+     * @throws Exception
+     */
+    public function send($to, $msg): void
+    {
+        $dm_url = "https://discord.com/api/v10/users/@me/channels";
+
+        $headers = [
+            'Authorization' => "Bot {$this->token}",
+            'User-Agent' => 'DiscordBot (' . $_ENV['appName'] . ', ' . VERSION . ')',
+            'Content-Type' => 'application/json',
+        ];
+
+        $dm_body = [
+            'recipient_id' => $to,
+        ];
+
+        $dm_response = $this->client->post($dm_url, [
+            'headers' => $headers,
+            'json' => $dm_body,
+        ]);
+
+        $channel_id = json_decode($dm_response->getBody()->getContents())->id;
+        $channel_url = "https://discord.com/api/v10/channels/{$channel_id}/messages";
+
+        $msg_body = [
+            'content' => $msg,
+        ];
+
+        $msg_response = $this->client->post($channel_url, [
+            'headers' => $headers,
+            'json' => $msg_body,
+        ]);
+
+        if ($msg_response->getStatusCode() !== 200) {
+            throw new Exception($msg_response->getBody()->getContents());
+        }
+    }
+}

+ 1 - 1
src/Services/Mail.php

@@ -44,7 +44,7 @@ final class Mail
         $smarty->setcompiledir(BASE_PATH . '/storage/framework/smarty/compile/');
         $smarty->setcachedir(BASE_PATH . '/storage/framework/smarty/cache/');
         // add config
-        $smarty->assign('config', Config::getPublicConfig());
+        $smarty->assign('config', Config::getViewConfig());
         foreach ($ary as $key => $value) {
             $smarty->assign($key, $value);
         }

+ 1 - 1
src/Services/View.php

@@ -28,7 +28,7 @@ final class View
         $smarty->setcompiledir(BASE_PATH . '/storage/framework/smarty/compile/'); //设置生成文件存放目录
         $smarty->setcachedir(BASE_PATH . '/storage/framework/smarty/cache/'); //设置缓存文件存放目录
         // add config
-        $smarty->assign('config', Config::getPublicConfig());
+        $smarty->assign('config', Config::getViewConfig());
         $smarty->assign('public_setting', Setting::getPublicConfig());
         $smarty->assign('user', $user);
 

+ 5 - 2
tests/App/Services/ConfigTest.php

@@ -10,7 +10,7 @@ use App\Services\Config;
 class ConfigTest extends TestCase
 {
     /**
-     * @covers App\Services\Config::getPublicConfig
+     * @covers App\Services\Config::getViewConfig
      */
     public function testGetPublicConfig(): void
     {
@@ -29,6 +29,7 @@ class ConfigTest extends TestCase
             'subscribeLog' => true,
             'subscribeLog_keep_days' => 30,
             'enable_r2_client_download' => true,
+            'jsdelivr_url' => 'cdn.jsdelivr.net',
         ];
 
         $mockEnv = [
@@ -46,9 +47,10 @@ class ConfigTest extends TestCase
             'subscribeLog' => true,
             'subscribeLog_keep_days' => 30,
             'enable_r2_client_download' => true,
+            'jsdelivr_url' => 'cdn.jsdelivr.net',
         ];
 
-        $config = Config::getPublicConfig();
+        $config = Config::getViewConfig();
 
         $this->assertSame($mockEnv['appName'], $config['appName']);
         $this->assertSame($mockEnv['baseUrl'], $config['baseUrl']);
@@ -64,6 +66,7 @@ class ConfigTest extends TestCase
         $this->assertSame($mockEnv['subscribeLog'], $config['subscribeLog']);
         $this->assertSame($mockEnv['subscribeLog_keep_days'], $config['subscribeLog_keep_days']);
         $this->assertSame($mockEnv['enable_r2_client_download'], $config['enable_r2_client_download']);
+        $this->assertSame($mockEnv['jsdelivr_url'], $config['jsdelivr_url']);
     }
 
     /**