Browse Source

Merge pull request #2107 from sspanel-uim/dev

Dev 20230807
M1Screw 2 years ago
parent
commit
60068a8751
61 changed files with 1062 additions and 1803 deletions
  1. 1 1
      LICENSE
  2. 3 0
      app/routes.php
  3. 19 20
      composer.lock
  4. 2 8
      config/.config.example.php
  5. 380 430
      config/settings.json
  6. 3 3
      resources/views/tabler/admin/coupon.tpl
  7. 1 1
      resources/views/tabler/admin/footer.tpl
  8. 5 5
      resources/views/tabler/admin/header.tpl
  9. 1 1
      resources/views/tabler/admin/index.tpl
  10. 2 2
      resources/views/tabler/admin/node/create.tpl
  11. 2 2
      resources/views/tabler/admin/node/edit.tpl
  12. 2 2
      resources/views/tabler/admin/setting/email.tpl
  13. 139 62
      resources/views/tabler/admin/setting/im.tpl
  14. 0 9
      resources/views/tabler/admin/setting/reg.tpl
  15. 0 20
      resources/views/tabler/auth/register.tpl
  16. 1 1
      resources/views/tabler/footer.tpl
  17. 1 1
      resources/views/tabler/gateway/stripe.tpl
  18. 3 3
      resources/views/tabler/header.tpl
  19. 10 15
      resources/views/tabler/user/edit.tpl
  20. 1 1
      resources/views/tabler/user/footer.tpl
  21. 5 5
      resources/views/tabler/user/header.tpl
  22. 2 2
      src/Command/Tool.php
  23. 6 5
      src/Controllers/Admin/AnnController.php
  24. 2 2
      src/Controllers/Admin/DetectController.php
  25. 7 4
      src/Controllers/Admin/NodeController.php
  26. 2 1
      src/Controllers/Admin/Setting/EmailController.php
  27. 72 10
      src/Controllers/Admin/Setting/ImController.php
  28. 0 1
      src/Controllers/Admin/Setting/RegController.php
  29. 2 18
      src/Controllers/AuthController.php
  30. 2 1
      src/Controllers/HomeController.php
  31. 3 4
      src/Controllers/User/InfoController.php
  32. 13 13
      src/Models/User.php
  33. 3 8
      src/Services/Config.php
  34. 3 3
      src/Services/CronDetect.php
  35. 13 8
      src/Services/CronJob.php
  36. 36 0
      src/Services/IM.php
  37. 10 0
      src/Services/IM/Base.php
  38. 63 0
      src/Services/IM/Discord.php
  39. 50 0
      src/Services/IM/Slack.php
  40. 120 0
      src/Services/IM/Telegram.php
  41. 1 1
      src/Services/Mail.php
  42. 5 17
      src/Services/Mail/Mailgun.php
  43. 5 17
      src/Services/Mail/Postal.php
  44. 8 19
      src/Services/Mail/SendGrid.php
  45. 1 2
      src/Services/Mail/Ses.php
  46. 1 2
      src/Services/Mail/Smtp.php
  47. 1 1
      src/Services/View.php
  48. 0 184
      src/Utils/Telegram.php
  49. 2 93
      src/Utils/Telegram/Callback.php
  50. 1 1
      src/Utils/Telegram/Commands/CheckinCommand.php
  51. 7 2
      src/Utils/Telegram/Commands/HelpCommand.php
  52. 0 96
      src/Utils/Telegram/Commands/InfoCommand.php
  53. 1 1
      src/Utils/Telegram/Commands/MenuCommand.php
  54. 1 1
      src/Utils/Telegram/Commands/MyCommand.php
  55. 0 228
      src/Utils/Telegram/Commands/SetuserCommand.php
  56. 1 2
      src/Utils/Telegram/Commands/StartCommand.php
  57. 3 17
      src/Utils/Telegram/Message.php
  58. 2 3
      src/Utils/Telegram/Process.php
  59. 0 19
      src/Utils/Telegram/Reply.php
  60. 27 417
      src/Utils/Telegram/TelegramTools.php
  61. 5 8
      tests/App/Services/ConfigTest.php

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2019 Anankke
+Copyright (c) 2023 SSPanel-UIM Dev Team
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 3 - 0
app/routes.php

@@ -236,6 +236,9 @@ 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_telegram', App\Controllers\Admin\Setting\ImController::class . ':testTelegram');
+        $group->post('/setting/test_discord', App\Controllers\Admin\Setting\ImController::class . ':testDiscord');
+        $group->post('/setting/test_slack', App\Controllers\Admin\Setting\ImController::class . ':testSlack');
         // 礼品卡
         $group->get('/giftcard', App\Controllers\Admin\GiftCardController::class . ':index');
         $group->post('/giftcard', App\Controllers\Admin\GiftCardController::class . ':add');

+ 19 - 20
composer.lock

@@ -123,16 +123,16 @@
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.277.8",
+            "version": "3.277.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "dc082f3a3e7bed6f453e839472baeb5b5dd433ec"
+                "reference": "f2437a755b70756425bf8f1bd588a7c583891900"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/dc082f3a3e7bed6f453e839472baeb5b5dd433ec",
-                "reference": "dc082f3a3e7bed6f453e839472baeb5b5dd433ec",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f2437a755b70756425bf8f1bd588a7c583891900",
+                "reference": "f2437a755b70756425bf8f1bd588a7c583891900",
                 "shasum": ""
             },
             "require": {
@@ -212,9 +212,9 @@
             "support": {
                 "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
                 "issues": "https://github.com/aws/aws-sdk-php/issues",
-                "source": "https://github.com/aws/aws-sdk-php/tree/3.277.8"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.277.9"
             },
-            "time": "2023-08-03T18:09:45+00:00"
+            "time": "2023-08-04T18:16:19+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
@@ -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",
@@ -8137,16 +8136,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "10.3.0",
+            "version": "10.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "c87ae282d17b256d09cfef0eb4f5db2d09cfd36a"
+                "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c87ae282d17b256d09cfef0eb4f5db2d09cfd36a",
-                "reference": "c87ae282d17b256d09cfef0eb4f5db2d09cfd36a",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d442ce7c4104d5683c12e67e4dcb5058159e9804",
+                "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804",
                 "shasum": ""
             },
             "require": {
@@ -8218,7 +8217,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.0"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.1"
             },
             "funding": [
                 {
@@ -8234,7 +8233,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-08-04T03:56:35+00:00"
+            "time": "2023-08-04T06:48:08+00:00"
         },
         {
             "name": "psr/cache",

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

@@ -79,14 +79,6 @@ $_ENV['auto_detect_ban_allow_users'] = [];          // 审计封禁的例外用
 $_ENV['auto_detect_ban_number']      = 30;             // 每次执行封禁所需的触发次数
 $_ENV['auto_detect_ban_time']        = 60;             // 每次封禁的时长 (分钟)
 
-//Bot 设置--------------------------------------------------------------------------------------------
-# Telegram bot
-$_ENV['enable_telegram']                    = false;        //是否开启 Telegram bot
-$_ENV['telegram_token']                     = '';           //Telegram bot,bot 的 token ,跟 father bot 申请
-$_ENV['telegram_chatid']                    = -111;         //Telegram bot,群组会话 ID,把机器人拉进群里之后跟他 /ping 一下即可得到
-$_ENV['telegram_bot']                       = '_bot';       //Telegram 机器人账号
-$_ENV['telegram_request_token']             = '';           //Webhook密钥,更新这个参数之后请 php xcat Tool setTelegram
-
 //节点检测-----------------------------------------------------------------------------------------------
 #GFW检测
 $_ENV['detect_gfw_port']     = 443;                                                  //所有节点服务器都打开的TCP端口
@@ -133,6 +125,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'] = '';
 

+ 380 - 430
config/settings.json

@@ -641,723 +641,673 @@
     },
     {
         "id": null,
-        "item": "tawk_id",
-        "value": "",
-        "class": "support",
+        "item": "enable_telegram",
+        "value": "0",
+        "class": "telegram",
         "is_public": 1,
-        "type": "string",
-        "default": "",
-        "mark": "tawk_id"
+        "type": "bool",
+        "default": "0",
+        "mark": "是否启用Telegram机器人"
     },
     {
         "id": null,
-        "item": "crisp_id",
+        "item": "telegram_token",
         "value": "",
-        "class": "support",
-        "is_public": 1,
+        "class": "telegram",
+        "is_public": 0,
         "type": "string",
         "default": "",
-        "mark": "crisp_id"
+        "mark": "Telegram Bot Token"
     },
     {
         "id": null,
-        "item": "livechat_id",
-        "value": "",
-        "class": "support",
-        "is_public": 1,
-        "type": "string",
-        "default": "",
-        "mark": "livechat_id"
+        "item": "telegram_chatid",
+        "value": "-1",
+        "class": "telegram",
+        "is_public": 0,
+        "type": "int",
+        "default": "-1",
+        "mark": "Telegram 群组会话 ID"
     },
     {
         "id": null,
-        "item": "mylivechat_id",
-        "value": "",
-        "class": "support",
+        "item": "telegram_bot",
+        "value": "_bot",
+        "class": "telegram",
         "is_public": 1,
         "type": "string",
-        "default": "",
-        "mark": "mylivechat_id"
+        "default": "_bot",
+        "mark": "Telegram 机器人账号"
     },
     {
         "id": null,
-        "item": "live_chat",
-        "value": "none",
-        "class": "support",
-        "is_public": 1,
+        "item": "telegram_request_token",
+        "value": "",
+        "class": "telegram",
+        "is_public": 0,
         "type": "string",
-        "default": "none",
-        "mark": "客服系统开关"
-    },
-    {
-        "id": null,
-        "item": "enable_ticket",
-        "value": "1",
-        "class": "support",
-        "is_public": 1,
-        "type": "bool",
-        "default": "1",
-        "mark": "启用工单系统"
+        "default": "",
+        "mark": "Telegram Webhook 密钥"
     },
     {
         "id": null,
-        "item": "mail_ticket",
+        "item": "telegram_add_node",
         "value": "1",
-        "class": "support",
+        "class": "telegram",
         "is_public": 0,
         "type": "bool",
         "default": "1",
-        "mark": "启用工单邮件提醒"
+        "mark": "是否启用Telegram机器人添加节点通知"
     },
     {
         "id": null,
-        "item": "ticket_limit",
-        "value": "3",
-        "class": "support",
+        "item": "telegram_add_node_text",
+        "value": "%node_name% 已被添加",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "3",
-        "mark": "用戶工单配額(每月)"
-    },
-    {
-        "id": null,
-        "item": "reg_mode",
-        "value": "open",
-        "class": "register",
-        "is_public": 1,
         "type": "string",
-        "default": "open",
-        "mark": "注册模式"
-    },
-    {
-        "id": null,
-        "item": "reg_email_verify",
-        "value": "0",
-        "class": "register",
-        "is_public": 1,
-        "type": "bool",
-        "default": "0",
-        "mark": "邮箱验证"
+        "default": "%node_name% 已被添加",
+        "mark": "Telegram机器人添加节点通知文本"
     },
     {
         "id": null,
-        "item": "enable_reg_im",
-        "value": "0",
-        "class": "register",
-        "is_public": 1,
+        "item": "telegram_update_node",
+        "value": "1",
+        "class": "telegram",
+        "is_public": 0,
         "type": "bool",
-        "default": "0",
-        "mark": "注册时是否要求用户输入IM联系方式"
+        "default": "1",
+        "mark": "是否启用Telegram机器人修改节点通知"
     },
     {
         "id": null,
-        "item": "reg_forbidden_ip",
-        "value": "127.0.0.0/8,::1/128",
-        "class": "register",
+        "item": "telegram_update_node_text",
+        "value": "%node_name% 已被修改",
+        "class": "telegram",
         "is_public": 0,
         "type": "string",
-        "default": "127.0.0.0/8,::1/128",
-        "mark": "注册时默认禁止访问IP列表"
+        "default": "%node_name% 已被修改",
+        "mark": "Telegram机器人修改节点通知文本"
     },
     {
         "id": null,
-        "item": "reg_forbidden_port",
-        "value": "",
-        "class": "register",
+        "item": "telegram_delete_node",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "string",
-        "default": "",
-        "mark": "注册时默认禁止访问端口列表"
+        "type": "bool",
+        "default": "1",
+        "mark": "是否启用Telegram机器人删除节点通知"
     },
     {
         "id": null,
-        "item": "random_group",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_delete_node_text",
+        "value": "%node_name% 被刪除了",
+        "class": "telegram",
         "is_public": 0,
         "type": "string",
-        "default": "0",
-        "mark": "注册时随机分配到的分组"
-    },
-    {
-        "id": null,
-        "item": "min_port",
-        "value": "10000",
-        "class": "register",
-        "is_public": 1,
-        "type": "int",
-        "default": "10000",
-        "mark": "用户端口池最小值"
-    },
-    {
-        "id": null,
-        "item": "max_port",
-        "value": "65535",
-        "class": "register",
-        "is_public": 1,
-        "type": "int",
-        "default": "65535",
-        "mark": "用户端口池最大值"
-    },
-    {
-        "id": null,
-        "item": "free_user_reset_day",
-        "value": "0",
-        "class": "register",
-        "is_public": 0,
-        "type": "int",
-        "default": "0",
-        "mark": "免费用戶的流量重置日"
+        "default": "%node_name% 被刪除了",
+        "mark": "Telegram机器人删除节点通知文本"
     },
     {
         "id": null,
-        "item": "free_user_reset_bandwidth",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_node_gfwed",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "0",
-        "mark": "需要重置的免费流量"
+        "type": "bool",
+        "default": "1",
+        "mark": "是否启用Telegram机器人节点被墙通知"
     },
     {
         "id": null,
-        "item": "sign_up_for_free_traffic",
-        "value": "20",
-        "class": "register",
+        "item": "telegram_node_gfwed_text",
+        "value": "喵喵喵~ %node_name% 节点被墙了喵~",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "20",
-        "mark": "注册时赠送的流量(GB)"
+        "type": "string",
+        "default": "喵喵喵~ %node_name% 节点被墙了喵~",
+        "mark": "Telegram机器人节点被墙通知"
     },
     {
         "id": null,
-        "item": "sign_up_for_free_time",
-        "value": "365",
-        "class": "register",
+        "item": "telegram_node_ungfwed",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "365",
-        "mark": "注册时设定的账户有效期(天)"
+        "type": "bool",
+        "default": "1",
+        "mark": "是否启用Telegram机器人节点被墙恢复通知"
     },
     {
         "id": null,
-        "item": "connection_ip_limit",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_node_ungfwed_text",
+        "value": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "0",
-        "mark": "注册时设定的连接 IP 限制"
+        "type": "string",
+        "default": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
+        "mark": "Telegram机器人节点被墙恢复文本"
     },
     {
         "id": null,
-        "item": "connection_rate_limit",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_node_online",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "0",
-        "mark": "注册时设定的使用速率限制"
+        "type": "bool",
+        "default": "1",
+        "mark": "是否启用Telegram机器人节点恢复上线通知"
     },
     {
         "id": null,
-        "item": "sign_up_for_class",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_node_online_text",
+        "value": "%node_name% 被修好了",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "0",
-        "mark": "注册时设定的等级"
+        "type": "string",
+        "default": "%node_name% 被修好了",
+        "mark": "Telegram机器人节点恢复上线通知文本"
     },
     {
         "id": null,
-        "item": "sign_up_for_class_time",
-        "value": "7",
-        "class": "register",
+        "item": "telegram_node_offline",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "7",
-        "mark": "注册时设定的等级过期时间(天)"
+        "type": "bool",
+        "default": "1",
+        "mark": "是否启用Telegram机器人节点离线通知"
     },
     {
         "id": null,
-        "item": "sign_up_for_method",
-        "value": "aes-128-gcm",
-        "class": "register",
+        "item": "telegram_node_offline_text",
+        "value": "%node_name% 出现了一些故障",
+        "class": "telegram",
         "is_public": 0,
         "type": "string",
-        "default": "aes-128-gcm",
-        "mark": "默认加密"
+        "default": "%node_name% 出现了一些故障",
+        "mark": "Telegram机器人节点离线通知文本"
     },
     {
         "id": null,
-        "item": "sign_up_for_daily_report",
-        "value": "0",
-        "class": "register",
+        "item": "telegram_daily_job",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
         "type": "bool",
-        "default": "0",
-        "mark": "注册后是否默认接收每日用量邮件推送"
+        "default": "1",
+        "mark": "是否启用Telegram机器人每日任务通知"
     },
     {
         "id": null,
-        "item": "sign_up_for_invitation_codes",
-        "value": "10",
-        "class": "register",
+        "item": "telegram_daily_job_text",
+        "value": "成功执行每日任务",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "10",
-        "mark": "初始邀请注册链接使用次数限制"
+        "type": "string",
+        "default": "成功执行每日任务",
+        "mark": "Telegram机器人每日任务通知文本"
     },
     {
         "id": null,
-        "item": "invitation_to_register_balance_reward",
+        "item": "telegram_diary",
         "value": "1",
-        "class": "invite",
-        "is_public": 1,
-        "type": "int",
+        "class": "telegram",
+        "is_public": 0,
+        "type": "bool",
         "default": "1",
-        "mark": "邀请注册余额奖励(元)"
+        "mark": "是否启用Telegram机器人系统运行状况通知"
     },
     {
         "id": null,
-        "item": "invitation_to_register_traffic_reward",
-        "value": "10",
-        "class": "invite",
-        "is_public": 1,
-        "type": "int",
-        "default": "10",
-        "mark": "邀请注册流量奖励(GB)"
+        "item": "telegram_diary_text",
+        "value": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
+        "class": "telegram",
+        "is_public": 0,
+        "type": "string",
+        "default": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
+        "mark": "Telegram机器人系统运行状况通知文本"
     },
     {
         "id": null,
-        "item": "invitation_mode",
-        "value": "after_paid",
-        "class": "invite",
+        "item": "telegram_unbind_kick_member",
+        "value": "0",
+        "class": "telegram",
         "is_public": 0,
-        "type": "string",
-        "default": "after_paid",
-        "mark": "邀请模式"
+        "type": "bool",
+        "default": "0",
+        "mark": "解绑Telegram账户后自动踢出群组"
     },
     {
         "id": null,
-        "item": "invite_rebate_mode",
-        "value": "limit_amount",
-        "class": "invite",
+        "item": "telegram_group_bound_user",
+        "value": "0",
+        "class": "telegram",
         "is_public": 0,
-        "type": "string",
-        "default": "limit_amount",
-        "mark": "返利模式"
+        "type": "bool",
+        "default": "0",
+        "mark": "是否仅允许已绑定Telegram账户的用户加入群组"
     },
     {
         "id": null,
-        "item": "rebate_frequency_limit",
-        "value": "3",
-        "class": "invite",
+        "item": "enable_welcome_message",
+        "value": "0",
+        "class": "telegram",
         "is_public": 0,
-        "type": "string",
-        "default": "3",
-        "mark": "返利总次数限制"
+        "type": "bool",
+        "default": "0",
+        "mark": "Telegram 机器人发送欢迎消息"
     },
     {
         "id": null,
-        "item": "rebate_amount_limit",
-        "value": "100",
-        "class": "invite",
+        "item": "telegram_group_quiet",
+        "value": "1",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "100",
-        "mark": "返利总金额限制"
+        "type": "bool",
+        "default": "1",
+        "mark": "Telegram 机器人在群组中不回应"
     },
     {
         "id": null,
-        "item": "rebate_ratio",
-        "value": "0.2",
-        "class": "invite",
-        "is_public": 1,
-        "type": "string",
-        "default": "0.2",
-        "mark": "返利比例"
+        "item": "allow_to_join_new_groups",
+        "value": "0",
+        "class": "telegram",
+        "is_public": 0,
+        "type": "bool",
+        "default": "0",
+        "mark": "允许 Bot 加入下方配置之外的群组"
     },
     {
         "id": null,
-        "item": "rebate_time_range_limit",
-        "value": "180",
-        "class": "invite",
+        "item": "group_id_allowed_to_join",
+        "value": "[]",
+        "class": "telegram",
         "is_public": 0,
-        "type": "int",
-        "default": "180",
-        "mark": "返利时间范围限制(天)"
+        "type": "array",
+        "default": "[]",
+        "mark": "允许加入的群组 ID"
     },
     {
         "id": null,
-        "item": "telegram_enable",
+        "item": "help_any_command",
         "value": "0",
         "class": "telegram",
         "is_public": 0,
         "type": "bool",
         "default": "0",
-        "mark": "是否启用Telegram机器人"
+        "mark": "允许任意未知的命令触发 /help 的回复"
     },
     {
         "id": null,
-        "item": "telegram_add_node",
-        "value": "1",
+        "item": "user_not_bind_reply",
+        "value": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
         "class": "telegram",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人添加节点通知"
+        "type": "string",
+        "default": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
+        "mark": "未绑定账户的回复"
     },
     {
         "id": null,
-        "item": "telegram_add_node_text",
-        "value": "%node_name% 已被添加",
-        "class": "telegram",
+        "item": "discord_bot_token",
+        "value": "",
+        "class": "discord",
         "is_public": 0,
         "type": "string",
-        "default": "%node_name% 已被添加",
-        "mark": "Telegram机器人添加节点通知文本"
+        "default": "",
+        "mark": "Discord Bot Token"
     },
     {
         "id": null,
-        "item": "telegram_update_node",
-        "value": "1",
-        "class": "telegram",
+        "item": "slack_token",
+        "value": "",
+        "class": "slack",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人修改节点通知"
+        "type": "string",
+        "default": "",
+        "mark": "Slack App Token"
     },
     {
         "id": null,
-        "item": "telegram_update_node_text",
-        "value": "%node_name% 已被修改",
-        "class": "telegram",
-        "is_public": 0,
+        "item": "tawk_id",
+        "value": "",
+        "class": "support",
+        "is_public": 1,
         "type": "string",
-        "default": "%node_name% 已被修改",
-        "mark": "Telegram机器人修改节点通知文本"
+        "default": "",
+        "mark": "tawk_id"
     },
     {
         "id": null,
-        "item": "telegram_delete_node",
-        "value": "1",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人删除节点通知"
+        "item": "crisp_id",
+        "value": "",
+        "class": "support",
+        "is_public": 1,
+        "type": "string",
+        "default": "",
+        "mark": "crisp_id"
     },
     {
         "id": null,
-        "item": "telegram_delete_node_text",
-        "value": "%node_name% 被刪除了",
-        "class": "telegram",
-        "is_public": 0,
+        "item": "livechat_id",
+        "value": "",
+        "class": "support",
+        "is_public": 1,
         "type": "string",
-        "default": "%node_name% 被刪除了",
-        "mark": "Telegram机器人删除节点通知文本"
+        "default": "",
+        "mark": "livechat_id"
     },
     {
         "id": null,
-        "item": "telegram_node_gfwed",
-        "value": "1",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人节点被墙通知"
+        "item": "mylivechat_id",
+        "value": "",
+        "class": "support",
+        "is_public": 1,
+        "type": "string",
+        "default": "",
+        "mark": "mylivechat_id"
     },
     {
         "id": null,
-        "item": "telegram_node_gfwed_text",
-        "value": "喵喵喵~ %node_name% 节点被墙了喵~",
-        "class": "telegram",
-        "is_public": 0,
+        "item": "live_chat",
+        "value": "none",
+        "class": "support",
+        "is_public": 1,
         "type": "string",
-        "default": "喵喵喵~ %node_name% 节点被墙了喵~",
-        "mark": "Telegram机器人节点被墙通知"
+        "default": "none",
+        "mark": "客服系统开关"
     },
     {
         "id": null,
-        "item": "telegram_node_ungfwed",
+        "item": "enable_ticket",
         "value": "1",
-        "class": "telegram",
-        "is_public": 0,
+        "class": "support",
+        "is_public": 1,
         "type": "bool",
         "default": "1",
-        "mark": "是否启用Telegram机器人节点被墙恢复通知"
-    },
-    {
-        "id": null,
-        "item": "telegram_node_ungfwed_text",
-        "value": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "string",
-        "default": "喵喵喵~ 被墙的 %node_name% 节点恢复了喵~",
-        "mark": "Telegram机器人节点被墙恢复文本"
+        "mark": "启用工单系统"
     },
     {
         "id": null,
-        "item": "telegram_node_online",
+        "item": "mail_ticket",
         "value": "1",
-        "class": "telegram",
+        "class": "support",
         "is_public": 0,
         "type": "bool",
         "default": "1",
-        "mark": "是否启用Telegram机器人节点恢复上线通知"
+        "mark": "启用工单邮件提醒"
     },
     {
         "id": null,
-        "item": "telegram_node_online_text",
-        "value": "%node_name% 被修好了",
-        "class": "telegram",
+        "item": "ticket_limit",
+        "value": "3",
+        "class": "support",
         "is_public": 0,
+        "type": "int",
+        "default": "3",
+        "mark": "用戶工单配額(每月)"
+    },
+    {
+        "id": null,
+        "item": "reg_mode",
+        "value": "open",
+        "class": "register",
+        "is_public": 1,
         "type": "string",
-        "default": "%node_name% 被修好了",
-        "mark": "Telegram机器人节点恢复上线通知文本"
+        "default": "open",
+        "mark": "注册模式"
     },
     {
         "id": null,
-        "item": "telegram_node_offline",
-        "value": "1",
-        "class": "telegram",
-        "is_public": 0,
+        "item": "reg_email_verify",
+        "value": "0",
+        "class": "register",
+        "is_public": 1,
         "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人节点离线通知"
+        "default": "0",
+        "mark": "邮箱验证"
     },
     {
         "id": null,
-        "item": "telegram_node_offline_text",
-        "value": "%node_name% 出现了一些故障",
-        "class": "telegram",
+        "item": "reg_forbidden_ip",
+        "value": "127.0.0.0/8,::1/128",
+        "class": "register",
         "is_public": 0,
         "type": "string",
-        "default": "%node_name% 出现了一些故障",
-        "mark": "Telegram机器人节点离线通知文本"
+        "default": "127.0.0.0/8,::1/128",
+        "mark": "注册时默认禁止访问IP列表"
     },
     {
         "id": null,
-        "item": "telegram_daily_job",
-        "value": "1",
-        "class": "telegram",
+        "item": "reg_forbidden_port",
+        "value": "",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人每日任务通知"
+        "type": "string",
+        "default": "",
+        "mark": "注册时默认禁止访问端口列表"
     },
     {
         "id": null,
-        "item": "telegram_daily_job_text",
-        "value": "成功执行每日任务",
-        "class": "telegram",
+        "item": "random_group",
+        "value": "0",
+        "class": "register",
         "is_public": 0,
         "type": "string",
-        "default": "成功执行每日任务",
-        "mark": "Telegram机器人每日任务通知文本"
+        "default": "0",
+        "mark": "注册时随机分配到的分组"
     },
     {
         "id": null,
-        "item": "telegram_diary",
-        "value": "1",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "是否启用Telegram机器人系统运行状况通知"
+        "item": "min_port",
+        "value": "10000",
+        "class": "register",
+        "is_public": 1,
+        "type": "int",
+        "default": "10000",
+        "mark": "用户端口池最小值"
     },
     {
         "id": null,
-        "item": "telegram_diary_text",
-        "value": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "string",
-        "default": "今日签到人数:%getTodayCheckinUser% \n 今日使用总流量:%lastday_total%",
-        "mark": "Telegram机器人系统运行状况通知文本"
+        "item": "max_port",
+        "value": "65535",
+        "class": "register",
+        "is_public": 1,
+        "type": "int",
+        "default": "65535",
+        "mark": "用户端口池最大值"
     },
     {
         "id": null,
-        "item": "telegram_unbind_kick_member",
+        "item": "free_user_reset_day",
         "value": "0",
-        "class": "telegram",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
+        "type": "int",
         "default": "0",
-        "mark": "解绑Telegram账户后自动踢出群组"
+        "mark": "免费用戶的流量重置日"
     },
     {
         "id": null,
-        "item": "telegram_group_bound_user",
+        "item": "free_user_reset_bandwidth",
         "value": "0",
-        "class": "telegram",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
+        "type": "int",
         "default": "0",
-        "mark": "是否仅允许已绑定Telegram账户的用户加入群组"
+        "mark": "需要重置的免费流量"
     },
     {
         "id": null,
-        "item": "telegram_show_group_link",
-        "value": "0",
-        "class": "telegram",
+        "item": "sign_up_for_free_traffic",
+        "value": "20",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
-        "default": "0",
-        "mark": "是否启用Telegram机器人显示用户群组链接"
+        "type": "int",
+        "default": "20",
+        "mark": "注册时赠送的流量(GB)"
     },
     {
         "id": null,
-        "item": "telegram_group_link",
-        "value": "https://t.me/joinchat/XXXXX",
-        "class": "telegram",
+        "item": "sign_up_for_free_time",
+        "value": "365",
+        "class": "register",
         "is_public": 0,
-        "type": "string",
-        "default": "https://t.me/joinchat/XXXXX",
-        "mark": "用户群组链接"
+        "type": "int",
+        "default": "365",
+        "mark": "注册时设定的账户有效期(天)"
     },
     {
         "id": null,
-        "item": "enable_welcome_message",
+        "item": "connection_ip_limit",
         "value": "0",
-        "class": "telegram",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
+        "type": "int",
         "default": "0",
-        "mark": "Telegram 机器人发送欢迎消息"
+        "mark": "注册时设定的连接 IP 限制"
     },
     {
         "id": null,
-        "item": "telegram_group_quiet",
-        "value": "1",
-        "class": "telegram",
+        "item": "connection_rate_limit",
+        "value": "0",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
-        "default": "1",
-        "mark": "Telegram 机器人在群组中不回应"
+        "type": "int",
+        "default": "0",
+        "mark": "注册时设定的使用速率限制"
     },
     {
         "id": null,
-        "item": "allow_to_join_new_groups",
+        "item": "sign_up_for_class",
         "value": "0",
-        "class": "telegram",
+        "class": "register",
         "is_public": 0,
-        "type": "bool",
+        "type": "int",
         "default": "0",
-        "mark": "允许 Bot 加入下方配置之外的群组"
+        "mark": "注册时设定的等级"
     },
     {
         "id": null,
-        "item": "group_id_allowed_to_join",
-        "value": "[]",
-        "class": "telegram",
+        "item": "sign_up_for_class_time",
+        "value": "7",
+        "class": "register",
         "is_public": 0,
-        "type": "array",
-        "default": "[]",
-        "mark": "允许加入的群组 ID"
+        "type": "int",
+        "default": "7",
+        "mark": "注册时设定的等级过期时间(天)"
     },
     {
         "id": null,
-        "item": "telegram_admins",
-        "value": "[]",
-        "class": "telegram",
+        "item": "sign_up_for_method",
+        "value": "aes-128-gcm",
+        "class": "register",
         "is_public": 0,
-        "type": "array",
-        "default": "[]",
-        "mark": "额外的 Telegram 管理员 ID"
+        "type": "string",
+        "default": "aes-128-gcm",
+        "mark": "默认加密"
     },
     {
         "id": null,
-        "item": "enable_not_admin_reply",
+        "item": "sign_up_for_daily_report",
         "value": "0",
-        "class": "telegram",
+        "class": "register",
         "is_public": 0,
         "type": "bool",
         "default": "0",
-        "mark": "非管理员操作管理员功能是否回复"
+        "mark": "注册后是否默认接收每日用量邮件推送"
     },
     {
         "id": null,
-        "item": "not_admin_reply_msg",
-        "value": "",
-        "class": "telegram",
+        "item": "sign_up_for_invitation_codes",
+        "value": "10",
+        "class": "register",
         "is_public": 0,
-        "type": "string",
-        "default": "",
-        "mark": "非管理员操作管理员功能的回复内容"
+        "type": "int",
+        "default": "10",
+        "mark": "初始邀请注册链接使用次数限制"
     },
     {
         "id": null,
-        "item": "no_user_found",
-        "value": "",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "string",
-        "default": "",
-        "mark": "管理员操作时,找不到用户的回复"
+        "item": "invitation_to_register_balance_reward",
+        "value": "1",
+        "class": "invite",
+        "is_public": 1,
+        "type": "int",
+        "default": "1",
+        "mark": "邀请注册余额奖励(元)"
     },
     {
         "id": null,
-        "item": "data_method_not_found",
-        "value": "",
-        "class": "telegram",
-        "is_public": 0,
-        "type": "string",
-        "default": "",
-        "mark": "管理员操作时,修改数据的字段没有找到的回复"
+        "item": "invitation_to_register_traffic_reward",
+        "value": "10",
+        "class": "invite",
+        "is_public": 1,
+        "type": "int",
+        "default": "10",
+        "mark": "邀请注册流量奖励(GB)"
     },
     {
         "id": null,
-        "item": "help_any_command",
-        "value": "0",
-        "class": "telegram",
+        "item": "invitation_mode",
+        "value": "after_paid",
+        "class": "invite",
         "is_public": 0,
-        "type": "bool",
-        "default": "0",
-        "mark": "允许任意未知的命令触发 /help 的回复"
+        "type": "string",
+        "default": "after_paid",
+        "mark": "邀请模式"
     },
     {
         "id": null,
-        "item": "enable_user_email_group_show",
-        "value": "0",
-        "class": "telegram",
+        "item": "invite_rebate_mode",
+        "value": "limit_amount",
+        "class": "invite",
         "is_public": 0,
-        "type": "bool",
-        "default": "0",
-        "mark": "开启在群组搜寻用户信息时显示用户完整邮箱,关闭则会对邮箱中间内容打码"
+        "type": "string",
+        "default": "limit_amount",
+        "mark": "返利模式"
     },
     {
         "id": null,
-        "item": "user_not_bind_reply",
-        "value": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
-        "class": "telegram",
+        "item": "rebate_frequency_limit",
+        "value": "3",
+        "class": "invite",
         "is_public": 0,
         "type": "string",
-        "default": "您未绑定本站账号,您可以进入网站的 **资料编辑**,在右下方绑定您的账号。",
-        "mark": "未绑定账户的回复"
+        "default": "3",
+        "mark": "返利总次数限制"
     },
     {
         "id": null,
-        "item": "telegram_general_pricing",
-        "value": "",
-        "class": "telegram",
+        "item": "rebate_amount_limit",
+        "value": "100",
+        "class": "invite",
         "is_public": 0,
+        "type": "int",
+        "default": "100",
+        "mark": "返利总金额限制"
+    },
+    {
+        "id": null,
+        "item": "rebate_ratio",
+        "value": "0.2",
+        "class": "invite",
+        "is_public": 1,
         "type": "string",
-        "default": "",
-        "mark": "面向游客的产品介绍"
+        "default": "0.2",
+        "mark": "返利比例"
     },
     {
         "id": null,
-        "item": "telegram_general_terms",
-        "value": "",
-        "class": "telegram",
+        "item": "rebate_time_range_limit",
+        "value": "180",
+        "class": "invite",
         "is_public": 0,
-        "type": "string",
-        "default": "",
-        "mark": "面向游客的服务条款"
+        "type": "int",
+        "default": "180",
+        "mark": "返利时间范围限制(天)"
     },
     {
         "id": null,

+ 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');

+ 139 - 62
resources/views/tabler/admin/setting/im.tpl

@@ -31,16 +31,22 @@
                     <div class="card-header">
                     <ul class="nav nav-tabs card-header-tabs" data-bs-toggle="tabs">
                         <li class="nav-item">
-                            <a href="#telegram_notification" class="nav-link active" data-bs-toggle="tab">Telegram 通知设定</a>
+                            <a href="#notification" class="nav-link active" data-bs-toggle="tab">通知设定</a>
                         </li>
                         <li class="nav-item">
-                            <a href="#telegram_bot" class="nav-link" data-bs-toggle="tab">Telegram Bot 设定</a>
+                            <a href="#telegram" class="nav-link" data-bs-toggle="tab">Telegram Bot</a>
+                        </li>
+                        <li class="nav-item">
+                            <a href="#discord" class="nav-link" data-bs-toggle="tab">Discord Bot</a>
+                        </li>
+                        <li class="nav-item">
+                            <a href="#slack" class="nav-link" data-bs-toggle="tab">Slack Bot</a>
                         </li>
                     </ul>
                 </div>
                 <div class="card-body">
                     <div class="tab-content">
-                        <div class="tab-pane active show" id="telegram_notification">
+                        <div class="tab-pane active show" id="notification">
                             <div class="card-body">
                                 <div class="form-group mb-3 row">
                                     <label class="form-label col-3 col-form-label">添加节点通知</label>
@@ -179,39 +185,57 @@
                                 </div>
                             </div>
                         </div>
-                        <div class="tab-pane" id="telegram_bot">
+                        <div class="tab-pane" id="telegram">
                             <div class="card-body">
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">解绑 Telegram 账户后自动踢出群组</label>
+                                    <label class="form-label col-3 col-form-label">是否启用 Telegram 机器人</label>
                                     <div class="col">
-                                        <select id="telegram_unbind_kick_member" class="col form-select" value="{$settings['telegram_unbind_kick_member']}">
-                                            <option value="0" {if ! $settings['telegram_unbind_kick_member']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['telegram_unbind_kick_member']}selected{/if}>开启</option>
+                                        <select id="enable_telegram" class="col form-select" value="{$settings['enable_telegram']}">
+                                            <option value="0" {if ! $settings['enable_telegram']}selected{/if}>关闭</option>
+                                            <option value="1" {if $settings['enable_telegram']}selected{/if}>开启</option>
                                         </select>
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">仅允许已绑定 Telegram 账户的用户加入群组</label>
+                                    <label class="form-label col-3 col-form-label">Bot Token</label>
                                     <div class="col">
-                                        <select id="telegram_group_bound_user" class="col form-select" value="{$settings['telegram_group_bound_user']}">
-                                            <option value="0" {if ! $settings['telegram_group_bound_user']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['telegram_group_bound_user']}selected{/if}>开启</option>
-                                        </select>
+                                        <input id="telegram_token" type="text" class="form-control" value="{$settings['telegram_token']}">
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">启用 Telegram 机器人显示用户群组链接</label>
+                                    <label class="form-label col-3 col-form-label">Telegram 群组会话 ID</label>
                                     <div class="col">
-                                        <select id="telegram_show_group_link" class="col form-select" value="{$settings['telegram_show_group_link']}">
-                                            <option value="0" {if ! $settings['telegram_show_group_link']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['telegram_show_group_link']}selected{/if}>开启</option>
+                                        <input id="telegram_chatid" type="text" class="form-control" value="{$settings['telegram_chatid']}">
+                                    </div>
+                                </div>
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">Telegram 机器人账号</label>
+                                    <div class="col">
+                                        <input id="telegram_bot" type="text" class="form-control" value="{$settings['telegram_bot']}">
+                                    </div>
+                                </div>
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">Telegram Webhook 密钥</label>
+                                    <div class="col">
+                                        <input id="telegram_request_token" type="text" class="form-control" value="{$settings['telegram_request_token']}">
+                                    </div>
+                                </div>
+                                <div class="form-group mb-3 row">
+                                    <label class="form-label col-3 col-form-label">解绑 Telegram 账户后自动踢出群组</label>
+                                    <div class="col">
+                                        <select id="telegram_unbind_kick_member" class="col form-select" value="{$settings['telegram_unbind_kick_member']}">
+                                            <option value="0" {if ! $settings['telegram_unbind_kick_member']}selected{/if}>关闭</option>
+                                            <option value="1" {if $settings['telegram_unbind_kick_member']}selected{/if}>开启</option>
                                         </select>
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">用户群组链接</label>
+                                    <label class="form-label col-3 col-form-label">仅允许已绑定 Telegram 账户的用户加入群组</label>
                                     <div class="col">
-                                        <input id="telegram_group_link" type="text" class="form-control" value="{$settings['telegram_group_link']}">
+                                        <select id="telegram_group_bound_user" class="col form-select" value="{$settings['telegram_group_bound_user']}">
+                                            <option value="0" {if ! $settings['telegram_group_bound_user']}selected{/if}>关闭</option>
+                                            <option value="1" {if $settings['telegram_group_bound_user']}selected{/if}>开启</option>
+                                        </select>
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
@@ -248,72 +272,65 @@
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">额外的 Telegram 管理员 ID</label>
-                                    <div class="col">
-                                        <input id="telegram_admins" type="text" class="form-control" value="{$settings['telegram_admins']}">
-                                    </div>
-                                </div>
-                                <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">非管理员操作管理员功能是否回复</label>
+                                    <label class="form-label col-3 col-form-label">允许任意未知的命令触发 /help 的回复</label>
                                     <div class="col">
-                                        <select id="enable_not_admin_reply" class="col form-select" value="{$settings['enable_not_admin_reply']}">
-                                            <option value="0" {if ! $settings['enable_not_admin_reply']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['enable_not_admin_reply']}selected{/if}>开启</option>
+                                        <select id="help_any_command" class="col form-select" value="{$settings['help_any_command']}">
+                                            <option value="0" {if ! $settings['help_any_command']}selected{/if}>关闭</option>
+                                            <option value="1" {if $settings['help_any_command']}selected{/if}>开启</option>
                                         </select>
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">非管理员操作管理员功能的回复内容</label>
-                                    <div class="col">
-                                        <input id="not_admin_reply_msg" type="text" class="form-control" value="{$settings['not_admin_reply_msg']}">
-                                    </div>
-                                </div>
-                                <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">管理员操作时,找不到用户的回复</label>
-                                    <div class="col">
-                                        <input id="no_user_found" type="text" class="form-control" value="{$settings['no_user_found']}">
-                                    </div>
-                                </div>
-                                <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">管理员操作时,修改数据的字段没有找到的回复</label>
+                                    <label class="form-label col-3 col-form-label">未绑定账户的回复</label>
                                     <div class="col">
-                                        <input id="data_method_not_found" type="text" class="form-control" value="{$settings['data_method_not_found']}">
+                                        <input id="user_not_bind_reply" type="text" class="form-control" value="{$settings['user_not_bind_reply']}">
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">允许任意未知的命令触发 /help 的回复</label>
-                                    <div class="col">
-                                        <select id="help_any_command" class="col form-select" value="{$settings['help_any_command']}">
-                                            <option value="0" {if ! $settings['help_any_command']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['help_any_command']}selected{/if}>开启</option>
-                                        </select>
+                                    <label class="form-label col-3 col-form-label">Telegram 用户 ID</label>
+                                    <input type="text" class="form-control" id="telegram_user_id" value="">
+                                    <div class="row my-3">
+                                        <div class="col">
+                                            <button id="test-telegram" class="btn btn-primary">发送测试信息</button>
+                                        </div>
                                     </div>
                                 </div>
+                            </div>
+                        </div>
+                        <div class="tab-pane" id="discord">
+                            <div class="card-body">
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">开启在群组搜寻用户信息时显示用户完整邮箱,关闭则会对邮箱中间内容打码</label>
+                                    <label class="form-label col-3 col-form-label">Bot Token</label>
                                     <div class="col">
-                                        <select id="enable_user_email_group_show" class="col form-select" value="{$settings['enable_user_email_group_show']}">
-                                            <option value="0" {if ! $settings['enable_user_email_group_show']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['enable_user_email_group_show']}selected{/if}>开启</option>
-                                        </select>
+                                        <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">未绑定账户的回复</label>
-                                    <div class="col">
-                                        <input id="user_not_bind_reply" type="text" class="form-control" value="{$settings['user_not_bind_reply']}">
+                                    <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 class="tab-pane" id="slack">
+                            <div class="card-body">
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">面向游客的产品介绍</label>
+                                    <label class="form-label col-3 col-form-label">App Token</label>
                                     <div class="col">
-                                        <input id="telegram_general_pricing" type="text" class="form-control" value="{$settings['telegram_general_pricing']}">
+                                        <input id="slack_token" type="text" class="form-control" value="{$settings['slack_token']}">
                                     </div>
                                 </div>
                                 <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">面向游客的服务条款</label>
-                                    <div class="col">
-                                        <input id="telegram_general_terms" type="text" class="form-control" value="{$settings['telegram_general_terms']}">
+                                    <label class="form-label col-3 col-form-label">Slack 用户 ID</label>
+                                    <input type="text" class="form-control" id="slack_user_id" value="">
+                                    <div class="row my-3">
+                                        <div class="col">
+                                            <button id="test-slack" class="btn btn-primary">发送测试信息</button>
+                                        </div>
                                     </div>
                                 </div>
                             </div>
@@ -347,6 +364,66 @@
             }
         })
     });
+
+    $("#test-telegram").click(function() {
+        $.ajax({
+            url: '/admin/setting/test_telegram',
+            type: 'POST',
+            dataType: "json",
+            data: {
+                telegram_user_id: $('#telegram_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');
+                }
+            }
+        })
+    });
+
+    $("#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');
+                }
+            }
+        })
+    });
+
+    $("#test-slack").click(function() {
+        $.ajax({
+            url: '/admin/setting/test_slack',
+            type: 'POST',
+            dataType: "json",
+            data: {
+                slack_user_id: $('#slack_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'}

+ 0 - 9
resources/views/tabler/admin/setting/reg.tpl

@@ -73,15 +73,6 @@
                                         </select>
                                     </div>
                                 </div>
-                                <div class="form-group mb-3 row">
-                                    <label class="form-label col-3 col-form-label">是否要求用户输入IM联系方式</label>
-                                    <div class="col">
-                                        <select id="enable_reg_im" class="col form-select" value="{$settings['enable_reg_im']}">
-                                            <option value="0" {if ! $settings['enable_reg_im']}selected{/if}>关闭</option>
-                                            <option value="1" {if $settings['enable_reg_im']}selected{/if}>开启</option>
-                                        </select>
-                                    </div>
-                                </div>
                             </div>
                         </div>
                         <div class="tab-pane" id="default_value">

+ 0 - 20
resources/views/tabler/auth/register.tpl

@@ -28,22 +28,6 @@
                                 <input id="repasswd" type="password" class="form-control" placeholder="重复登录密码">
                             </div>
                         </div>
-                        {if $public_setting['enable_reg_im']}
-                            <div class="mb-3">
-                                <select id="im_type" class="col form-select">
-                                    <option value="0">请选择社交软件</option>
-                                    <option value="1">WeChat</option>
-                                    <option value="2">QQ</option>
-                                    <option value="4">Telegram</option>
-                                    <option value="5">Discord</option>
-                                </select>
-                            </div>
-                            <div class="mb-3">
-                                <div class="input-group input-group-flat">
-                                    <input id="im_value" type="text" class="form-control" placeholder="社交账号">
-                                </div>
-                            </div>
-                        {/if}
                         {if $public_setting['reg_mode'] !== 'close' }
                             <div class="mb-3">
                                 <div class="input-group input-group-flat">
@@ -128,10 +112,6 @@
                 url: '/auth/register',
                 dataType: "json",
                 data: {
-                    {if $public_setting['enable_reg_im']}
-                        im_value: $('#im_value').val(),
-                        im_type: $('#im_type').val(),
-                    {/if}
                     {if $public_setting['reg_email_verify']}
                         emailcode: $('#emailcode').val(),
                     {/if}

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

+ 10 - 15
resources/views/tabler/user/edit.tpl

@@ -90,25 +90,20 @@
                                         <div class="col-sm-12 col-md-6">
                                             <div class="card">
                                                 <div class="card-body">
-                                                    <h3 class="card-title">联系方式</h3>
+                                                    <h3 class="card-title">IM 账号</h3>
                                                     <div class="mb-3">
                                                         <select id="imtype" class="form-select">
                                                             <option value="1" {if $user->im_type === 1}selected{/if}>
-                                                                WeChat</option>
+                                                                Slack</option>
                                                             <option value="2" {if $user->im_type === 2}selected{/if}>
-                                                                QQ</option>
-                                                            <option value="3" {if $user->im_type === 3}selected{/if}>
-                                                                Facebook</option>
+                                                                Discord</option>
                                                             <option value="4" {if $user->im_type === 4}selected{/if}>
                                                                 Telegram</option>
-                                                            <option value="5" {if $user->im_type === 5}selected{/if}>
-                                                                Discord</option>
                                                         </select>
                                                     </div>
                                                     <div class="mb-3">
                                                         <input id="imvalue" type="text" class="form-control" 
-                                                            {if $user->im_type === 4} disabled="" {/if}
-                                                            value="{$user->im_value}" placeholder="社交账户">
+                                                            disabled="" value="{$user->im_value}" placeholder="社交账户">
                                                     </div>
                                                 </div>
                                                 <div class="card-footer">
@@ -135,7 +130,7 @@
                                                 </div>
                                             </div>
                                         </div>
-                                        {if $config['enable_telegram']}
+                                        {if $public_setting['enable_telegram']}
                                         <div class="col-sm-12 col-md-6">
                                             {if $user->telegram_id !== 0}
                                             <div class="card">
@@ -167,7 +162,7 @@
                                                             手机电脑平板等如已安装 Telegram 可点击
                                                         </div>
                                                         <div class="col-6 col-sm-2 col-md-2 col-sm mb-3">
-                                                            <a href="https://t.me/{$telegram_bot}?start={$bind_token}"
+                                                            <a href="https://t.me/{$public_setting['telegram_bot']}?start={$bind_token}"
                                                                 class="btn btn-primary w-100">
                                                                 一键绑定
                                                             </a>
@@ -176,7 +171,7 @@
                                                     <div class="row">
                                                         <div class="col-6 col-sm-2 col-md-2 col-xl mb-3">
                                                             向机器人 <a
-                                                                href="https://t.me/{$telegram_bot}">@{$telegram_bot}</a>
+                                                                href="https://t.me/{$public_setting['telegram_bot']}">@{$public_setting['telegram_bot']}</a>
                                                             发送验证码绑定
                                                         </div>
                                                         <div class="col-6 col-sm-2 col-md-2 col-sm mb-3">
@@ -198,18 +193,18 @@
                                         <div class="col-sm-12 col-md-6">
                                             <div class="card">
                                                 <div class="card-body">
-                                                    <h3 class="card-title">两步认证</h3>
+                                                    <h3 class="card-title">多因素认证</h3>
                                                     <div class="col-md-12">
                                                         <div class="col-sm-6 col-md-6">
                                                             <p>
                                                                 <i class="ti ti-brand-apple"></i>
                                                                 <a target="view_window"
-                                                                    href="https://apps.apple.com/us/app/google-authenticator/id388497605">苹果客户端
+                                                                    href="https://apps.apple.com/us/app/google-authenticator/id388497605">iOS 客户端
                                                                 </a>
                                                                 &nbsp;&nbsp;&nbsp;
                                                                 <i class="ti ti-brand-android"></i>
                                                                 <a target="view_window"
-                                                                    href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=zh&gl=US">安卓客户端
+                                                                    href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Android 客户端
                                                                 </a>
                                                             </p>
                                                         </div>

+ 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 - 2
src/Command/Tool.php

@@ -72,8 +72,8 @@ EOL;
      */
     public function setTelegram(): void
     {
-        $WebhookUrl = $_ENV['baseUrl'] . '/telegram_callback?token=' . $_ENV['telegram_request_token'];
-        $telegram = new Api($_ENV['telegram_token']);
+        $WebhookUrl = $_ENV['baseUrl'] . '/telegram_callback?token=' . Setting::obtain('telegram_request_token');
+        $telegram = new Api(Setting::obtain('telegram_token'));
         $telegram->removeWebhook();
 
         if ($telegram->setWebhook(['url' => $WebhookUrl])) {

+ 6 - 5
src/Controllers/Admin/AnnController.php

@@ -6,8 +6,9 @@ namespace App\Controllers\Admin;
 
 use App\Controllers\BaseController;
 use App\Models\Ann;
+use App\Models\Setting;
 use App\Models\User;
-use App\Utils\Telegram;
+use App\Services\IM\Telegram;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -113,9 +114,9 @@ final class AnnController extends BaseController
             }
         }
 
-        if ($_ENV['enable_telegram']) {
+        if (Setting::obtain('enable_telegram')) {
             try {
-                Telegram::sendHtml('新公告:' . PHP_EOL . $content);
+                (new Telegram())->sendHtml(0, '新公告:' . PHP_EOL . $content);
             } catch (TelegramSDKException $e) {
                 return $response->withJson([
                     'ret' => 0,
@@ -163,9 +164,9 @@ final class AnnController extends BaseController
             ]);
         }
 
-        if ($_ENV['enable_telegram']) {
+        if (Setting::obtain('enable_telegram')) {
             try {
-                Telegram::sendHtml('公告更新:' . PHP_EOL . $request->getParam('content'));
+                (new Telegram())->sendHtml(0, '公告更新:' . PHP_EOL . $request->getParam('content'));
             } catch (TelegramSDKException $e) {
                 return $response->withJson([
                     'ret' => 0,

+ 2 - 2
src/Controllers/Admin/DetectController.php

@@ -8,7 +8,7 @@ use App\Controllers\BaseController;
 use App\Models\DetectBanLog;
 use App\Models\DetectLog;
 use App\Models\DetectRule;
-use App\Utils\Telegram;
+use App\Services\IM\Telegram;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -122,7 +122,7 @@ final class DetectController extends BaseController
             ]);
         }
 
-        Telegram::sendMarkdown('有新的审计规则:' . $rule->name);
+        (new Telegram())->sendMarkdown(0, '有新的审计规则:' . $rule->name);
         return $response->withJson([
             'ret' => 1,
             'msg' => '添加成功',

+ 7 - 4
src/Controllers/Admin/NodeController.php

@@ -8,7 +8,7 @@ use App\Controllers\BaseController;
 use App\Models\Node;
 use App\Models\Setting;
 use App\Services\Cloudflare;
-use App\Utils\Telegram;
+use App\Services\IM\Telegram;
 use App\Utils\Tools;
 use Cloudflare\API\Endpoints\EndpointException;
 use Exception;
@@ -157,7 +157,8 @@ final class NodeController extends BaseController
 
         if (Setting::obtain('telegram_add_node')) {
             try {
-                Telegram::send(
+                (new Telegram())->send(
+                    0,
                     str_replace(
                         '%node_name%',
                         $request->getParam('name'),
@@ -245,7 +246,8 @@ final class NodeController extends BaseController
 
         if (Setting::obtain('telegram_update_node')) {
             try {
-                Telegram::send(
+                (new Telegram())->send(
+                    0,
                     str_replace(
                         '%node_name%',
                         $request->getParam('name'),
@@ -302,7 +304,8 @@ final class NodeController extends BaseController
 
         if (Setting::obtain('telegram_delete_node')) {
             try {
-                Telegram::send(
+                (new Telegram())->send(
+                    0,
                     str_replace(
                         '%node_name%',
                         $node->name,

+ 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' => '测试邮件发送成功',

+ 72 - 10
src/Controllers/Admin/Setting/ImController.php

@@ -6,12 +6,22 @@ namespace App\Controllers\Admin\Setting;
 
 use App\Controllers\BaseController;
 use App\Models\Setting;
+use App\Services\IM\Discord;
+use App\Services\IM\Slack;
+use App\Services\IM\Telegram;
 use Exception;
+use GuzzleHttp\Exception\GuzzleException;
+use Telegram\Bot\Exceptions\TelegramSDKException;
 use function json_encode;
 
 final class ImController extends BaseController
 {
     public static array $update_field = [
+        'enable_telegram',
+        'telegram_token',
+        'telegram_chatid',
+        'telegram_bot',
+        'telegram_request_token',
         'telegram_add_node',
         'telegram_add_node_text',
         'telegram_update_node',
@@ -32,22 +42,14 @@ final class ImController extends BaseController
         'telegram_diary_text',
         'telegram_unbind_kick_member',
         'telegram_group_bound_user',
-        'telegram_show_group_link',
-        'telegram_group_link',
         'enable_welcome_message',
         'telegram_group_quiet',
         'allow_to_join_new_groups',
         'group_id_allowed_to_join',
-        'telegram_admins',
-        'enable_not_admin_reply',
-        'not_admin_reply_msg',
-        'no_user_found',
-        'data_method_not_found',
         'help_any_command',
-        'enable_user_email_group_show',
         'user_not_bind_reply',
-        'telegram_general_pricing',
-        'telegram_general_terms',
+        'discord_bot_token',
+        'slack_token',
     ];
 
     /**
@@ -100,4 +102,64 @@ final class ImController extends BaseController
             'msg' => '保存成功',
         ]);
     }
+
+    public function testTelegram($request, $response, $args)
+    {
+        try {
+            (new Telegram())->send(
+                $request->getParam('telegram_user_id'),
+                '这是一条测试消息',
+            );
+        } catch (TelegramSDKException|Exception $e) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '测试信息发送失败 ' . $e->getMessage(),
+            ]);
+        }
+
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '测试信息发送成功',
+        ]);
+    }
+
+    public function testDiscord($request, $response, $args)
+    {
+        try {
+            (new Discord())->send(
+                $request->getParam('discord_user_id'),
+                '这是一条测试消息',
+            );
+        } catch (GuzzleException|Exception $e) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '测试信息发送失败 ' . $e->getMessage(),
+            ]);
+        }
+
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '测试信息发送成功',
+        ]);
+    }
+
+    public function testSlack($request, $response, $args)
+    {
+        try {
+            (new Slack())->send(
+                $request->getParam('slack_user_id'),
+                '这是一条测试消息',
+            );
+        } catch (GuzzleException|Exception $e) {
+            return $response->withJson([
+                'ret' => 0,
+                'msg' => '测试信息发送失败 ' . $e->getMessage(),
+            ]);
+        }
+
+        return $response->withJson([
+            'ret' => 1,
+            'msg' => '测试信息发送成功',
+        ]);
+    }
 }

+ 0 - 1
src/Controllers/Admin/Setting/RegController.php

@@ -15,7 +15,6 @@ final class RegController extends BaseController
         'reg_mode',
         'reg_email_verify',
         'sign_up_for_daily_report',
-        'enable_reg_im',
         'random_group',
         'min_port',
         'max_port',

+ 2 - 18
src/Controllers/AuthController.php

@@ -363,24 +363,8 @@ final class AuthController extends BaseController
             }
         }
 
-        // Check IM
-        if (Setting::obtain('enable_reg_im')) {
-            $imtype = $antiXss->xss_clean($request->getParam('im_type'));
-            $imvalue = $antiXss->xss_clean($request->getParam('im_value'));
-
-            if ($imtype === '' || $imvalue === '') {
-                return ResponseHelper::error($response, '请填上你的联络方式');
-            }
-
-            $user = User::where('im_value', $imvalue)->where('im_type', $imtype)->first();
-
-            if ($user !== null) {
-                return ResponseHelper::error($response, '此联络方式已注册');
-            }
-        } else {
-            $imtype = 1;
-            $imvalue = '';
-        }
+        $imtype = 1;
+        $imvalue = '';
 
         // check email format
         $check_res = Tools::isEmailLegal($email);

+ 2 - 1
src/Controllers/HomeController.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace App\Controllers;
 
+use App\Models\Setting;
 use App\Services\Auth;
 use App\Utils\Telegram\Process;
 use Exception;
@@ -81,7 +82,7 @@ final class HomeController extends BaseController
     {
         $token = $request->getQueryParam('token');
 
-        if ($_ENV['enable_telegram'] && $token === $_ENV['telegram_request_token']) {
+        if (Setting::obtain('enable_telegram') && $token === Setting::obtain('telegram_request_token')) {
             Process::index($request);
             $result = '1';
         } else {

+ 3 - 4
src/Controllers/User/InfoController.php

@@ -13,7 +13,7 @@ use App\Services\Config;
 use App\Services\MFA;
 use App\Utils\Hash;
 use App\Utils\ResponseHelper;
-use App\Utils\Telegram;
+use App\Utils\Telegram\TelegramTools;
 use App\Utils\Tools;
 use Exception;
 use Psr\Http\Message\ResponseInterface;
@@ -38,7 +38,7 @@ final class InfoController extends BaseController
     public function index(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
     {
         $themes = Tools::getDir(BASE_PATH . '/resources/views');
-        $bind_token = Telegram::addBindSession($this->user);
+        $bind_token = $this->user->telegram_id === 0 ? TelegramTools::addBindSession($this->user) : '';
         $methods = Config::getSupportParam('method');
         $gaurl = MFA::getGaUrl($this->user);
 
@@ -48,7 +48,6 @@ final class InfoController extends BaseController
             ->assign('bind_token', $bind_token)
             ->assign('methods', $methods)
             ->assign('gaurl', $gaurl)
-            ->assign('telegram_bot', $_ENV['telegram_bot'])
             ->registerClass('Config', Config::class)
             ->fetch('user/edit.tpl'));
     }
@@ -217,7 +216,7 @@ final class InfoController extends BaseController
 
         $user = $this->user;
 
-        if ($value === 2 && ! $_ENV['enable_telegram']) {
+        if ($value === 2 && ! Setting::obtain('enable_telegram')) {
             return ResponseHelper::error(
                 $response,
                 '修改失败,当前无法使用 Telegram 接收每日报告'

+ 13 - 13
src/Models/User.php

@@ -5,9 +5,9 @@ declare(strict_types=1);
 namespace App\Models;
 
 use App\Services\DB;
+use App\Services\IM\Telegram;
 use App\Services\Mail;
 use App\Utils\Hash;
-use App\Utils\Telegram;
 use App\Utils\Telegram\TelegramTools;
 use App\Utils\Tools;
 use Exception;
@@ -61,9 +61,8 @@ final class User extends Model
     public function imType(): string
     {
         return match ($this->im_type) {
-            1 => '微信',
-            2 => 'QQ',
-            5 => 'Discord',
+            1 => 'Slack',
+            2 => 'Discord',
             default => 'Telegram',
         };
     }
@@ -349,7 +348,7 @@ final class User extends Model
         $this->telegram_id = 0;
 
         if ($this->save()) {
-            if ($_ENV['enable_telegram']
+            if (Setting::obtain('enable_telegram')
                 &&
                 Setting::obtain('telegram_group_bound_user')
                 &&
@@ -360,7 +359,7 @@ final class User extends Model
                 TelegramTools::SendPost(
                     'kickChatMember',
                     [
-                        'chat_id' => $_ENV['telegram_chatid'],
+                        'chat_id' => Setting::obtain('telegram_chatid'),
                         'user_id' => $telegram_id,
                     ]
                 );
@@ -426,19 +425,19 @@ final class User extends Model
      */
     public function sendTelegram(string $text): bool
     {
-        $result = false;
         try {
             if ($this->telegram_id > 0) {
-                Telegram::send(
+                (new Telegram())->send(
+                    $this->telegram_id,
                     $text,
-                    $this->telegram_id
                 );
-                $result = true;
+                return true;
             }
         } catch (Exception $e) {
             echo $e->getMessage();
         }
-        return $result;
+
+        return false;
     }
 
     /**
@@ -452,9 +451,10 @@ final class User extends Model
         $enable_traffic = $this->enableTraffic();
         $used_traffic = $this->usedTraffic();
         $unused_traffic = $this->unusedTraffic();
+
         switch ($this->daily_mail_enable) {
             case 1:
-                echo 'Send daily mail to user: ' . $this->id;
+                echo 'Send daily mail to user: ' . $this->id . PHP_EOL;
                 $this->sendMail(
                     $_ENV['appName'] . '-每日流量报告以及公告',
                     'traffic_report.tpl',
@@ -471,7 +471,7 @@ final class User extends Model
                 );
                 break;
             case 2:
-                echo 'Send daily Telegram message to user: ' . $this->id;
+                echo 'Send daily Telegram message to user: ' . $this->id . PHP_EOL;
                 $text = date('Y-m-d') . ' 流量使用报告' . PHP_EOL . PHP_EOL;
                 $text .= '流量总计:' . $enable_traffic . PHP_EOL;
                 $text .= '已用流量:' . $used_traffic . PHP_EOL;

+ 3 - 8
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'],
@@ -27,13 +23,12 @@ final class Config
             'enable_kill' => $_ENV['enable_kill'],
             'enable_change_email' => $_ENV['enable_change_email'],
 
-            'enable_telegram' => $_ENV['enable_telegram'],
-            'telegram_bot' => $_ENV['telegram_bot'],
-
             'subscribeLog' => $_ENV['subscribeLog'],
             'subscribeLog_keep_days' => $_ENV['subscribeLog_keep_days'],
 
             'enable_r2_client_download' => $_ENV['enable_r2_client_download'],
+
+            'jsdelivr_url' => $_ENV['jsdelivr_url'],
         ];
     }
 

+ 3 - 3
src/Services/CronDetect.php

@@ -9,7 +9,7 @@ use App\Models\DetectLog;
 use App\Models\Node;
 use App\Models\Setting;
 use App\Models\User;
-use App\Utils\Telegram;
+use App\Services\IM\Telegram;
 use App\Utils\Tools;
 use Telegram\Bot\Exceptions\TelegramSDKException;
 use function file_get_contents;
@@ -66,7 +66,7 @@ final class CronDetect
                 }
 
                 if (Setting::obtain('telegram_node_gfwed')) {
-                    Telegram::send($notice_text);
+                    (new Telegram())->send(0, $notice_text);
                 }
 
                 $node->gfw_block = true;
@@ -95,7 +95,7 @@ final class CronDetect
             }
 
             if (Setting::obtain('telegram_node_ungfwed')) {
-                Telegram::send($notice_text);
+                (new Telegram())->send(0, $notice_text);
             }
 
             $node->gfw_block = false;

+ 13 - 8
src/Services/CronJob.php

@@ -16,7 +16,7 @@ use App\Models\Setting;
 use App\Models\SubscribeLog;
 use App\Models\User;
 use App\Models\UserHourlyUsage;
-use App\Utils\Telegram;
+use App\Services\IM\Telegram;
 use App\Utils\Tools;
 use DateTime;
 use Exception;
@@ -130,7 +130,7 @@ final class CronJob
                 }
 
                 if (Setting::obtain('telegram_node_offline')) {
-                    Telegram::send($notice_text);
+                    (new Telegram())->send(0, $notice_text);
                 }
 
                 $node->online = 0;
@@ -159,7 +159,7 @@ final class CronJob
                 }
 
                 if (Setting::obtain('telegram_node_online')) {
-                    Telegram::send($notice_text);
+                    (new Telegram())->send(0, $notice_text);
                 }
 
                 $node->online = 1;
@@ -214,18 +214,22 @@ final class CronJob
                 echo Tools::toDateTime(time()) . '邮件队列处理超时,已跳过' . PHP_EOL;
                 break;
             }
+
             DB::beginTransaction();
             $email_queues_raw = DB::select('SELECT * FROM email_queue LIMIT 1 FOR UPDATE SKIP LOCKED');
+
             if (count($email_queues_raw) === 0) {
                 DB::commit();
                 break;
             }
+
             $email_queues = array_map(static function ($value) {
                 return (array) $value;
             }, $email_queues_raw);
             $email_queue = $email_queues[0];
             echo '发送邮件至 ' . $email_queue['to_email'] . PHP_EOL;
             DB::delete('DELETE FROM email_queue WHERE id = ?', [$email_queue['id']]);
+
             if (Tools::isEmail($email_queue['to_email'])) {
                 try {
                     Mail::send(
@@ -240,6 +244,7 @@ final class CronJob
             } else {
                 echo $email_queue['to_email'] . ' 邮箱格式错误,已跳过' . PHP_EOL;
             }
+
             DB::commit();
         }
 
@@ -586,8 +591,7 @@ final class CronJob
 
     public static function sendDailyTrafficReport(): void
     {
-        $users = User::where('daily_mail_enable', 1)->get();
-
+        $users = User::whereIn('daily_mail_enable', [1, 2])->get();
         $ann_latest_raw = Ann::orderBy('date', 'desc')->first();
 
         if ($ann_latest_raw === null) {
@@ -600,7 +604,7 @@ final class CronJob
             $user->sendDailyNotification($ann_latest);
         }
 
-        echo Tools::toDateTime(time()) . ' 成功发送每日邮件' . PHP_EOL;
+        echo Tools::toDateTime(time()) . ' 成功发送每日流量报告' . PHP_EOL;
     }
 
     /**
@@ -608,7 +612,7 @@ final class CronJob
      */
     public static function sendTelegramDailyJob(): void
     {
-        Telegram::send(Setting::obtain('telegram_daily_job_text'));
+        (new Telegram())->send(0, Setting::obtain('telegram_daily_job_text'));
 
         echo Tools::toDateTime(time()) . ' 成功发送 Telegram 每日任务提示' . PHP_EOL;
     }
@@ -620,7 +624,8 @@ final class CronJob
     {
         $analytics = new Analytics();
 
-        Telegram::send(
+        (new Telegram())->send(
+            0,
             str_replace(
                 [
                     '%getTodayCheckinUser%',

+ 36 - 0
src/Services/IM.php

@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services;
+
+/*
+ * IM Service
+ */
+
+use App\Services\IM\Discord;
+use App\Services\IM\Slack;
+use App\Services\IM\Telegram;
+use GuzzleHttp\Exception\GuzzleException;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+
+final class IM
+{
+    public static function getClient($type): Discord|Slack|Telegram
+    {
+        return match ($type) {
+            '1' => new Discord(),
+            '2' => new Slack(),
+            default => new Telegram(),
+        };
+    }
+
+    /**
+     * @throws GuzzleException
+     * @throws TelegramSDKException
+     */
+    public static function send($to, $msg, $type): void
+    {
+        self::getClient($type)->send($to, $msg);
+    }
+}

+ 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());
+        }
+    }
+}

+ 50 - 0
src/Services/IM/Slack.php

@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services\IM;
+
+use App\Models\Setting;
+use Exception;
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+
+final class Slack extends Base
+{
+    private string $token;
+    private Client $client;
+
+    public function __construct()
+    {
+        $this->token = Setting::obtain('slack_token');
+        $this->client = new Client();
+    }
+
+    /**
+     * @throws GuzzleException
+     * @throws Exception
+     */
+    public function send($to, $msg): void
+    {
+        $url = "https://slack.com/api/chat.postMessage";
+
+        $headers = [
+            'Authorization' => 'Bearer '.$this->token,
+            'Content-Type' => 'application/json',
+        ];
+
+        $body = [
+            'channel' => $to,
+            'text' => $msg,
+        ];
+
+        $response = $this->client->post($url, [
+            'headers' => $headers,
+            'json' => $body,
+        ]);
+
+        if ($response->getStatusCode() !== 200) {
+            throw new Exception($response->getBody()->getContents());
+        }
+    }
+}

+ 120 - 0
src/Services/IM/Telegram.php

@@ -0,0 +1,120 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Services\IM;
+
+use App\Models\Setting;
+use Telegram\Bot\Api;
+use Telegram\Bot\Exceptions\TelegramSDKException;
+use function strip_tags;
+
+final class Telegram extends Base
+{
+    private Api $bot;
+
+    /**
+     * @throws TelegramSDKException
+     */
+    public function __construct()
+    {
+        $this->bot = new Api(Setting::obtain('telegram_token'));
+    }
+
+    /**
+     * 发送讯息,默认给群组发送
+     *
+     * @throws TelegramSDKException
+     */
+    public function send($to = 0, $msg = ''): void
+    {
+        if ($to === 0) {
+            $to = Setting::obtain('telegram_chatid');
+        }
+
+        $sendMessage = [
+            'chat_id' => $to,
+            'text' => $msg,
+            'parse_mode' => '',
+            'disable_web_page_preview' => false,
+            'reply_to_message_id' => null,
+            'reply_markup' => null,
+        ];
+
+        $this->bot->sendMessage($sendMessage);
+    }
+
+    /**
+     * 以 HTML 格式发送讯息,默认给群组发送
+     *
+     * @throws TelegramSDKException
+     */
+    public function sendHtml($to = 0, $msg = ''): void
+    {
+        if ($to === 0) {
+            $to = Setting::obtain('telegram_chatid');
+        }
+
+        $sendMessage = [
+            'chat_id' => $to,
+            'text' => strip_tags(
+                $msg,
+                ['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
+                    'code', 'pre',
+                ]
+            ),
+            'parse_mode' => 'HTML',
+            'disable_web_page_preview' => false,
+            'reply_to_message_id' => null,
+            'reply_markup' => null,
+        ];
+
+        $this->bot->sendMessage($sendMessage);
+    }
+
+    /**
+     * 以 Markdown 格式发送讯息,默认给群组发送
+     *
+     * @throws TelegramSDKException
+     */
+    public function sendMarkdown($to = 0, $msg = ''): void
+    {
+        if ($to === 0) {
+            $to = Setting::obtain('telegram_chatid');
+        }
+
+        $sendMessage = [
+            'chat_id' => $to,
+            'text' => $msg,
+            'parse_mode' => 'Markdown',
+            'disable_web_page_preview' => false,
+            'reply_to_message_id' => null,
+            'reply_markup' => null,
+        ];
+
+        $this->bot->sendMessage($sendMessage);
+    }
+
+    /**
+     * 以 MarkdownV2 格式发送讯息,默认给群组发送
+     *
+     * @throws TelegramSDKException
+     */
+    public function sendMarkdownV2($to = 0, $msg = ''): void
+    {
+        if ($to === 0) {
+            $to = Setting::obtain('telegram_chatid');
+        }
+
+        $sendMessage = [
+            'chat_id' => $to,
+            'text' => $msg,
+            'parse_mode' => 'MarkdownV2',
+            'disable_web_page_preview' => false,
+            'reply_to_message_id' => null,
+            'reply_markup' => null,
+        ];
+
+        $this->bot->sendMessage($sendMessage);
+    }
+}

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

+ 5 - 17
src/Services/Mail/Mailgun.php

@@ -6,35 +6,23 @@ namespace App\Services\Mail;
 
 use App\Models\Setting;
 use Exception;
-use Mailgun\Mailgun as MailgunService;
+use Mailgun\Mailgun as MG;
 use Psr\Http\Client\ClientExceptionInterface;
 use function basename;
 
 final class Mailgun extends Base
 {
-    private array $config;
-    private MailgunService $mg;
+    private MG $mg;
     private mixed $domain;
     private mixed $sender;
 
     public function __construct()
-    {
-        $this->config = $this->getConfig();
-        $this->mg = MailgunService::create($this->config['key']);
-        $this->domain = $this->config['domain'];
-        $this->sender = $this->config['sender_name'] . ' <' . $this->config['sender'] . '>';
-    }
-
-    public function getConfig(): array
     {
         $configs = Setting::getClass('mailgun');
 
-        return [
-            'key' => $configs['mailgun_key'],
-            'domain' => $configs['mailgun_domain'],
-            'sender' => $configs['mailgun_sender'],
-            'sender_name' => $configs['mailgun_sender_name'],
-        ];
+        $this->mg = MG::create($configs['mailgun_key']);
+        $this->domain = $configs['mailgun_domain'];
+        $this->sender = $configs['mailgun_sender_name'] . ' <' . $configs['mailgun_sender'] . '>';
     }
 
     /**

+ 5 - 17
src/Services/Mail/Postal.php

@@ -12,30 +12,18 @@ use function mime_content_type;
 
 final class Postal extends Base
 {
-    private array $config;
     private Client $client;
     private Message $message;
 
     public function __construct()
-    {
-        $this->config = $this->getConfig();
-        $this->client = new Client($this->config['host'], $this->config['key']);
-        $this->message = new Message();
-        $this->message->sender($this->config['sender']); # 发件邮箱
-        $this->message->from($this->config['name']. ' <' . $this->config['sender'] . '>'); # 发件人
-        $this->message->replyTo($this->config['sender']);
-    }
-
-    public function getConfig(): array
     {
         $configs = Setting::getClass('postal');
 
-        return [
-            'host' => $configs['postal_host'],
-            'key' => $configs['postal_key'],
-            'sender' => $configs['postal_sender'],
-            'name' => $configs['postal_name'],
-        ];
+        $this->client = new Client($configs['postal_host'], $configs['postal_key']);
+        $this->message = new Message();
+        $this->message->sender($configs['postal_sender']); # 发件邮箱
+        $this->message->from($configs['postal_name'] . ' <' . $configs['postal_sender'] . '>'); # 发件人
+        $this->message->replyTo($configs['postal_sender']);
     }
 
     public function send($to, $subject, $text, $files): void

+ 8 - 19
src/Services/Mail/SendGrid.php

@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace App\Services\Mail;
 
 use App\Models\Setting;
+use SendGrid as SG;
 use SendGrid\Mail\Mail;
 use SendGrid\Mail\TypeException;
 use function base64_encode;
@@ -13,30 +14,19 @@ use function file_get_contents;
 
 final class SendGrid extends Base
 {
-    private array $config;
-    private \SendGrid $sg;
-    private mixed $sender;
-    private mixed $name;
+    private SG $sg;
     private Mail $email;
 
+    /**
+     * @throws TypeException
+     */
     public function __construct()
-    {
-        $this->config = $this->getConfig();
-        $this->sg = new \SendGrid($this->config['key']);
-        $this->sender = $this->config['sender'];
-        $this->name = $this->config['name'];
-        $this->email = new Mail();
-    }
-
-    public function getConfig(): array
     {
         $configs = Setting::getClass('sendgrid');
 
-        return [
-            'key' => $configs['sendgrid_key'],
-            'sender' => $configs['sendgrid_sender'],
-            'name' => $configs['sendgrid_name'],
-        ];
+        $this->sg = new SG($configs['sendgrid_key']);
+        $this->email = new Mail();
+        $this->email->setFrom($configs['sendgrid_sender'], $configs['sendgrid_name']);
     }
 
     /**
@@ -44,7 +34,6 @@ final class SendGrid extends Base
      */
     public function send($to, $subject, $text, $files): void
     {
-        $this->email->setFrom($this->sender, $this->name);
         $this->email->setSubject($subject);
         $this->email->addTo($to);
         $this->email->addContent('text/html', $text);

+ 1 - 2
src/Services/Mail/Ses.php

@@ -30,14 +30,13 @@ final class Ses extends Base
     public function send($to, $subject, $text, $files): void
     {
         $ses = $this->ses;
-        $configs = Setting::getClass('aws_ses');
         $char_set = 'UTF-8';
 
         $ses->sendEmail([
             'Destination' => [
                 'ToAddresses' => [$to],
             ],
-            'Source' => $configs['aws_ses_sender'],
+            'Source' => Setting::obtain('aws_ses_sender'),
             'Message' => [
                 'Body' => [
                     'Html' => [

+ 1 - 2
src/Services/Mail/Smtp.php

@@ -13,7 +13,7 @@ final class Smtp extends Base
     private PHPMailer $mail;
 
     /**
-     * @throws \PHPMailer\PHPMailer\Exception
+     * @throws Exception
      */
     public function __construct()
     {
@@ -43,7 +43,6 @@ final class Smtp extends Base
     }
 
     /**
-     * @throws \PHPMailer\PHPMailer\Exception
      * @throws Exception
      */
     public function send($to, $subject, $text, $files): void

+ 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);
 

+ 0 - 184
src/Utils/Telegram.php

@@ -1,184 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Utils;
-
-use App\Services\Cache;
-use Exception;
-use RedisException;
-use Telegram\Bot\Api;
-use Telegram\Bot\Exceptions\TelegramSDKException;
-use voku\helper\AntiXSS;
-use function strip_tags;
-
-final class Telegram
-{
-    /**
-     * 发送讯息,默认给群组发送
-     *
-     * @throws TelegramSDKException
-     */
-    public static function send(string $messageText, int $chat_id = 0): void
-    {
-        if ($_ENV['enable_telegram']) {
-            $bot = null;
-
-            if ($chat_id === 0) {
-                $chat_id = $_ENV['telegram_chatid'];
-            }
-            // 发送给非群组时使用异步
-            $async = ($chat_id !== $_ENV['telegram_chatid']);
-            $bot = new Api($_ENV['telegram_token'], $async);
-            $sendMessage = [
-                'chat_id' => $chat_id,
-                'text' => $messageText,
-                'parse_mode' => '',
-                'disable_web_page_preview' => false,
-                'reply_to_message_id' => null,
-                'reply_markup' => null,
-            ];
-
-            $bot->sendMessage($sendMessage);
-        }
-    }
-
-    /**
-     * 以 HTML 格式发送讯息,默认给群组发送
-     *
-     * @throws TelegramSDKException
-     */
-    public static function sendHtml(string $messageText, int $chat_id = 0): void
-    {
-        if ($_ENV['enable_telegram']) {
-            $bot = null;
-
-            if ($chat_id === 0) {
-                $chat_id = $_ENV['telegram_chatid'];
-            }
-            // 发送给非群组时使用异步
-            $async = ($chat_id !== $_ENV['telegram_chatid']);
-            $bot = new Api($_ENV['telegram_token'], $async);
-            $sendMessage = [
-                'chat_id' => $chat_id,
-                'text' => strip_tags(
-                    $messageText,
-                    ['b', 'strong', 'i', 'em', 'u', 'ins', 's', 'strike','del', 'span','tg-spoiler', 'a', 'tg-emoji',
-                        'code', 'pre',
-                    ]
-                ),
-                'parse_mode' => 'HTML',
-                'disable_web_page_preview' => false,
-                'reply_to_message_id' => null,
-                'reply_markup' => null,
-            ];
-
-            try {
-                $bot->sendMessage($sendMessage);
-            } catch (Exception $e) {
-                echo $e->getMessage();
-            }
-        }
-    }
-
-    /**
-     * 以 Markdown 格式发送讯息,默认给群组发送
-     *
-     * @throws TelegramSDKException
-     */
-    public static function sendMarkdown(string $messageText, int $chat_id = 0): void
-    {
-        if ($_ENV['enable_telegram']) {
-            $bot = null;
-
-            if ($chat_id === 0) {
-                $chat_id = $_ENV['telegram_chatid'];
-            }
-            // 发送给非群组时使用异步
-            $async = ($chat_id !== $_ENV['telegram_chatid']);
-            $bot = new Api($_ENV['telegram_token'], $async);
-            $sendMessage = [
-                'chat_id' => $chat_id,
-                'text' => $messageText,
-                'parse_mode' => 'Markdown',
-                'disable_web_page_preview' => false,
-                'reply_to_message_id' => null,
-                'reply_markup' => null,
-            ];
-
-            try {
-                $bot->sendMessage($sendMessage);
-            } catch (Exception $e) {
-                echo $e->getMessage();
-            }
-        }
-    }
-
-    /**
-     * 以 MarkdownV2 格式发送讯息,默认给群组发送
-     *
-     * @throws TelegramSDKException
-     */
-    public static function sendMarkdownV2(string $messageText, int $chat_id = 0): void
-    {
-        if ($_ENV['enable_telegram']) {
-            $bot = null;
-
-            if ($chat_id === 0) {
-                $chat_id = $_ENV['telegram_chatid'];
-            }
-            // 发送给非群组时使用异步
-            $async = ($chat_id !== $_ENV['telegram_chatid']);
-            $bot = new Api($_ENV['telegram_token'], $async);
-            $sendMessage = [
-                'chat_id' => $chat_id,
-                'text' => $messageText,
-                'parse_mode' => 'MarkdownV2',
-                'disable_web_page_preview' => false,
-                'reply_to_message_id' => null,
-                'reply_markup' => null,
-            ];
-
-            try {
-                $bot->sendMessage($sendMessage);
-            } catch (Exception $e) {
-                echo $e->getMessage();
-            }
-        }
-    }
-
-    /**
-     * @throws RedisException
-     */
-    public static function verifyBindSession($token): int
-    {
-        $antiXss = new AntiXSS();
-        $redis = Cache::initRedis();
-        $uid = $redis->get('telegram_bind:' . $antiXss->xss_clean($token));
-
-        if (! $uid) {
-            return 0;
-        }
-
-        $redis->del('telegram_bind:' . $token);
-
-        return (int) $uid;
-    }
-
-    /**
-     * @throws RedisException
-     */
-    public static function addBindSession($user): string
-    {
-        $redis = Cache::initRedis();
-        $token = Tools::genRandomChar(16);
-
-        $redis->setex(
-            'telegram_bind:' . $token,
-            600,
-            $user->id
-        );
-
-        return $token;
-    }
-}

+ 2 - 93
src/Utils/Telegram/Callback.php

@@ -95,36 +95,7 @@ final class Callback
         if (str_starts_with($this->CallbackData, 'user.')) {
             // 用户相关
             $this->userCallback();
-            return;
         }
-        //游客回调数据处理
-        $this->guestCallback();
-    }
-
-    /**
-     * 游客的回复
-     *
-     * @return array
-     */
-    public static function getGuestIndexKeyboard(): array
-    {
-        $Keyboard = [
-            [
-                [
-                    'text' => '产品介绍',
-                    'callback_data' => 'general.pricing',
-                ],
-                [
-                    'text' => '服务条款',
-                    'callback_data' => 'general.terms',
-                ],
-            ],
-        ];
-        $text = '游客你好,以下是 BOT 菜单:' . PHP_EOL . PHP_EOL . '本站用户请前往用户中心进行 Telegram 绑定操作。';
-        return [
-            'text' => $text,
-            'keyboard' => $Keyboard,
-        ];
     }
 
     /**
@@ -174,60 +145,6 @@ final class Callback
         TelegramTools::sendPost('answerCallbackQuery', $sendMessage);
     }
 
-    /**
-     * 回调数据处理
-     *
-     * @throws TelegramSDKException
-     */
-    public function guestCallback(): void
-    {
-        $CallbackDataExplode = explode('|', $this->CallbackData);
-        switch ($CallbackDataExplode[0]) {
-            case 'general.pricing':
-                // 产品介绍
-                $sendMessage = [
-                    'text' => Setting::obtain('telegram_general_pricing'),
-                    'disable_web_page_preview' => false,
-                    'reply_to_message_id' => null,
-                    'reply_markup' => json_encode(
-                        [
-                            'inline_keyboard' => self::getGuestIndexKeyboard()['keyboard'],
-                        ]
-                    ),
-                ];
-                break;
-            case 'general.terms':
-                // 服务条款
-                $sendMessage = [
-                    'text' => Setting::obtain('telegram_general_terms'),
-                    'disable_web_page_preview' => false,
-                    'reply_to_message_id' => null,
-                    'reply_markup' => json_encode(
-                        [
-                            'inline_keyboard' => self::getGuestIndexKeyboard()['keyboard'],
-                        ]
-                    ),
-                ];
-                break;
-            default:
-                // 主菜单
-                $temp = self::getGuestIndexKeyboard();
-                $sendMessage = [
-                    'text' => $temp['text'],
-                    'disable_web_page_preview' => false,
-                    'reply_to_message_id' => null,
-                    'reply_markup' => json_encode(
-                        [
-                            'inline_keyboard' => $temp['keyboard'],
-                        ]
-                    ),
-                ];
-                break;
-        }
-
-        $this->replyWithMessage($sendMessage);
-    }
-
     public static function getUserIndexKeyboard($user): array
     {
         $checkin = (! $user->isAbleToCheckin() ? '已签到' : '签到');
@@ -262,14 +179,7 @@ final class Callback
         $text = Reply::getUserTitle($user);
         $text .= PHP_EOL . PHP_EOL;
         $text .= Reply::getUserInfo($user);
-        if (Setting::obtain('telegram_show_group_link')) {
-            $Keyboard[] = [
-                [
-                    'text' => '加入用户群',
-                    'url' => Setting::obtain('telegram_group_link'),
-                ],
-            ];
-        }
+
         return [
             'text' => $text,
             'keyboard' => $Keyboard,
@@ -292,7 +202,6 @@ final class Callback
                     'show_alert' => true,
                 ]);
             }
-            $this->guestCallback();
         }
         $CallbackDataExplode = explode('|', $this->CallbackData);
         $Operate = explode('.', $CallbackDataExplode[0]);
@@ -776,7 +685,7 @@ final class Callback
                 TelegramTools::sendPost(
                     'unbanChatMember',
                     [
-                        'chat_id' => $_ENV['telegram_chatid'],
+                        'chat_id' => Setting::obtain('telegram_chatid'),
                         'user_id' => $this->triggerUser['id'],
                     ]
                 );

+ 1 - 1
src/Utils/Telegram/Commands/CheckinCommand.php

@@ -40,7 +40,7 @@ final class CheckinCommand extends Command
                 // 群组中不回应
                 return null;
             }
-            if ($ChatID !== $_ENV['telegram_chatid']) {
+            if ($ChatID !== Setting::obtain('telegram_chatid')) {
                 // 非我方群组
                 return null;
             }

+ 7 - 2
src/Utils/Telegram/Commands/HelpCommand.php

@@ -27,20 +27,25 @@ final class HelpCommand extends Command
     {
         $Update = $this->getUpdate();
         $Message = $Update->getMessage();
+
         if ($Message->getChat()->getId() < 0 && Setting::obtain('telegram_group_quiet')) {
             return;
         }
-        if (! preg_match('/^\/help\s?(@' . $_ENV['telegram_bot'] . ')?.*/i', $Message->getText()) &&
-            Setting::obtain('help_any_command') === false) {
+
+        if (! preg_match('/^\/help\s?(@' . Setting::obtain('telegram_bot') . ')?.*/i', $Message->getText()) &&
+            ! Setting::obtain('help_any_command')) {
             return;
         }
+
         $this->replyWithChatAction(['action' => Actions::TYPING]);
         $commands = $this->telegram->getCommands();
         $text = '系统中可用的所有命令.';
         $text .= PHP_EOL . PHP_EOL;
+
         foreach ($commands as $name => $handler) {
             $text .= '/' . $name . PHP_EOL . '`    - ' . $handler->getDescription() . '`' . PHP_EOL;
         }
+
         $this->replyWithMessage(
             [
                 'text' => $text,

+ 0 - 96
src/Utils/Telegram/Commands/InfoCommand.php

@@ -1,96 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Utils\Telegram\Commands;
-
-use App\Models\Setting;
-use App\Models\User;
-use App\Utils\Telegram\Reply;
-use App\Utils\Telegram\TelegramTools;
-use Telegram\Bot\Actions;
-use Telegram\Bot\Commands\Command;
-use function in_array;
-use function json_decode;
-
-/**
- * Class InfoCommand.
- */
-final class InfoCommand extends Command
-{
-    /**
-     * @var string Command Name
-     */
-    protected string $name = 'info';
-
-    /**
-     * @var string Command Description
-     */
-    protected string $description = '[群组]     获取被回复消息的用户信息,管理员命令.';
-
-    public function handle(): void
-    {
-        $Update = $this->getUpdate();
-        $Message = $Update->getMessage();
-
-        // 消息会话 ID
-        $ChatID = $Message->getChat()->getId();
-
-        // 消息 ID
-        $MessageID = $Message->getMessageId();
-
-        if ($ChatID < 0) {
-            // 发送 '输入中' 会话状态
-            $this->replyWithChatAction(['action' => Actions::TYPING]);
-            // 触发用户
-            $SendUser = [
-                'id' => $Message->getFrom()->getId(),
-            ];
-            if (! in_array($SendUser['id'], json_decode(Setting::obtain('telegram_admins')))) {
-                $AdminUser = User::where('is_admin', 1)->where('telegram_id', $SendUser['id'])->first();
-                if ($AdminUser === null) {
-                    // 非管理员回复消息
-                    if (Setting::obtain('enable_not_admin_reply') && Setting::obtain('not_admin_reply_msg') !== '') {
-                        $this->replyWithMessage(
-                            [
-                                'text' => Setting::obtain('not_admin_reply_msg'),
-                                'parse_mode' => 'HTML',
-                                'reply_to_message_id' => $MessageID,
-                            ]
-                        );
-                    }
-                    return;
-                }
-            }
-            if ($Message->getReplyToMessage() !== null) {
-                // 回复源消息用户
-                $FindUser = [
-                    'id' => $Message->getReplyToMessage()->getFrom()->getId(),
-                ];
-                $User = TelegramTools::getUser($FindUser['id']);
-                if ($User === null) {
-                    $this->replyWithMessage(
-                        [
-                            'text' => Setting::obtain('no_user_found'),
-                            'reply_to_message_id' => $MessageID,
-                        ]
-                    );
-                } else {
-                    $this->replyWithMessage(
-                        [
-                            'text' => Reply::getUserInfoFromAdmin($User, $ChatID),
-                            'reply_to_message_id' => $MessageID,
-                        ]
-                    );
-                }
-            } else {
-                $this->replyWithMessage(
-                    [
-                        'text' => '请回复消息使用.',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-            }
-        }
-    }
-}

+ 1 - 1
src/Utils/Telegram/Commands/MenuCommand.php

@@ -49,7 +49,7 @@ final class MenuCommand extends Command
 
             $user = TelegramTools::getUser($SendUser['id']);
             if ($user === null) {
-                $reply = Callback::getGuestIndexKeyboard();
+                $reply = null;
             } else {
                 $reply = Callback::getUserIndexKeyboard($user);
             }

+ 1 - 1
src/Utils/Telegram/Commands/MyCommand.php

@@ -45,7 +45,7 @@ final class MyCommand extends Command
                 // 群组中不回应
                 return null;
             }
-            if ($ChatID !== $_ENV['telegram_chatid']) {
+            if ($ChatID !== Setting::obtain('telegram_chatid')) {
                 // 非我方群组
                 return null;
             }

+ 0 - 228
src/Utils/Telegram/Commands/SetuserCommand.php

@@ -1,228 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace App\Utils\Telegram\Commands;
-
-use App\Models\Setting;
-use App\Models\User;
-use App\Utils\Telegram\Reply;
-use App\Utils\Telegram\TelegramTools;
-use Telegram\Bot\Actions;
-use Telegram\Bot\Commands\Command;
-use function count;
-use function in_array;
-use function json_decode;
-
-/**
- * Class SetuserCommand.
- */
-final class SetuserCommand extends Command
-{
-    /**
-     * @var string Command Name
-     */
-    protected string $name = 'setuser';
-
-    /**
-     * @var string Command Description
-     */
-    protected string $description = '[群组/私聊] 修改用户数据,管理员命令.';
-
-    public function handle(): void
-    {
-        $Update = $this->getUpdate();
-        $Message = $Update->getMessage();
-
-        // 消息 ID
-        $MessageID = $Message->getMessageId();
-
-        // 消息会话 ID
-        $ChatID = $Message->getChat()->getId();
-
-        // 触发用户
-        $SendUser = [
-            'id' => $Message->getFrom()->getId(),
-            'name' => $Message->getFrom()->getFirstName() . ' ' . $Message->getFrom()->getLastName(),
-            'username' => $Message->getFrom()->getUsername(),
-        ];
-
-        if (! in_array($SendUser['id'], json_decode(Setting::obtain('telegram_admins'), true))) {
-            $AdminUser = User::where('is_admin', 1)->where('telegram_id', $SendUser['id'])->first();
-            if ($AdminUser === null
-                && Setting::obtain('enable_not_admin_reply')
-                && Setting::obtain('not_admin_reply_msg') !== ''
-            ) {
-                // 非管理员回复消息
-                $this->replyWithMessage(
-                    [
-                        'text' => Setting::obtain('not_admin_reply_msg'),
-                        'parse_mode' => 'HTML',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-            }
-        }
-
-        // 发送 '输入中' 会话状态
-        $this->replyWithChatAction(['action' => Actions::TYPING]);
-
-        $this->reply($SendUser, $Message, $MessageID, $ChatID);
-    }
-
-    public function reply($SendUser, $Message, $MessageID, $ChatID): void
-    {
-        $User = null;
-        $FindUser = null;
-        $argumentsExplode = explode(' ', trim($Message->getText()));
-        $arguments = implode(' ', $argumentsExplode);
-        if ($Message->getReplyToMessage() !== null) {
-            // 回复源消息用户
-            $FindUser = [
-                'id' => $Message->getReplyToMessage()->getFrom()->getId(),
-            ];
-            $User = TelegramTools::getUser($FindUser['id']);
-            if ($User === null) {
-                $this->replyWithMessage(
-                    [
-                        'text' => Setting::obtain('no_user_found'),
-                        'parse_mode' => 'HTML',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-                return;
-            }
-
-            if ($arguments === '/setuser') {
-                // 无参数时回复用户信息
-                $this->replyWithMessage(
-                    [
-                        'text' => Reply::getUserInfoFromAdmin($User, $ChatID),
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-                return;
-            }
-        }
-
-        if ($arguments === '/setuser') {
-            $strArray = [
-                '/setuser [用户识别] [操作字段] [操作参数]',
-                '',
-                '用户识别:[使用回复消息方式则无需填写]',
-                '- [' . implode(' | ', array_keys(TelegramTools::getUserSearchMethods())) . '],可省略,默认:email',
-                '- 例 1:/setuser admin@admin 操作字段 操作参数',
-                '- 例 2:/setuser port:10086  操作字段 操作参数',
-                '',
-                '操作字段:',
-                '[' . implode(' | ', array_keys(TelegramTools::getUserActionOption())) . ']',
-                '',
-                '操作参数:',
-                '- 请查看对应选项支持的写法.',
-            ];
-            $this->replyWithMessage(
-                [
-                    'text' => TelegramTools::strArrayToCode($strArray),
-                    'parse_mode' => 'HTML',
-                    'reply_to_message_id' => $MessageID,
-                ]
-            );
-            return;
-        }
-
-        // 命令格式:
-        // - /setuser [用户识别码] 选项 操作值
-
-        // ############## 命令解析 ##############
-        $UserCode = '';
-        if ($User === null) {
-            $Options = TelegramTools::strExplode($arguments, ' ', 3);
-            if (count($Options) < 3) {
-                $this->replyWithMessage(
-                    [
-                        'text' => '没有提供选项或操作值.',
-                        'parse_mode' => 'HTML',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-                return;
-            }
-            // 用户识别码
-            $UserCode = $Options[0];
-            // 选项
-            $Option = $Options[1];
-            // 操作值
-            $value = $Options[2];
-        } else {
-            $Options = TelegramTools::strExplode($arguments, ' ', 2);
-            if (count($Options) < 2) {
-                $this->replyWithMessage(
-                    [
-                        'text' => '没有提供选项或操作值.',
-                        'parse_mode' => 'HTML',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-                return;
-            }
-            // 选项
-            $Option = $Options[0];
-            // 操作值
-            $value = $Options[1];
-        }
-        // ############## 命令解析 ##############
-
-        // ############## 用户识别码处理 ##############
-        if ($User === null) {
-            // 默认搜寻字段
-            $useMethod = 'email';
-            if (str_contains($UserCode, ':')) {
-                // 如果指定了字段
-                $UserCodeExplode = explode(':', $UserCode);
-                $Search = $UserCodeExplode[0];
-                $UserCode = $UserCodeExplode[1];
-                $SearchMethods = TelegramTools::getUserSearchMethods();
-                $useTempMethod = TelegramTools::getOptionMethod($SearchMethods, $Search);
-                if ($useTempMethod !== '') {
-                    $useMethod = $useTempMethod;
-                }
-            }
-            $User = TelegramTools::getUser($UserCode, $useMethod);
-            if ($User === null) {
-                $this->replyWithMessage(
-                    [
-                        'text' => Setting::obtain('no_user_found'),
-                        'parse_mode' => 'HTML',
-                        'reply_to_message_id' => $MessageID,
-                    ]
-                );
-                return;
-            }
-        }
-        // ############## 用户识别码处理 ##############
-
-        // ############## 字段选项处理 ##############
-        $OptionMethods = TelegramTools::getUserActionOption();
-        $useOptionMethod = TelegramTools::getOptionMethod($OptionMethods, $Option);
-        if ($useOptionMethod === '') {
-            $this->replyWithMessage(
-                [
-                    'text' => Setting::obtain('data_method_not_found'),
-                    'parse_mode' => 'HTML',
-                    'reply_to_message_id' => $MessageID,
-                ]
-            );
-            return;
-        }
-        // ############## 字段选项处理 ##############
-
-        $reply = TelegramTools::operationUser($User, $useOptionMethod, $value, $ChatID);
-        $this->replyWithMessage(
-            [
-                'text' => $reply['msg'],
-                'parse_mode' => 'HTML',
-                'reply_to_message_id' => $MessageID,
-            ]
-        );
-    }
-}

+ 1 - 2
src/Utils/Telegram/Commands/StartCommand.php

@@ -6,7 +6,6 @@ namespace App\Utils\Telegram\Commands;
 
 use App\Models\Setting;
 use App\Models\User;
-use App\Utils\Telegram;
 use App\Utils\Telegram\TelegramTools;
 use RedisException;
 use Telegram\Bot\Actions;
@@ -89,7 +88,7 @@ final class StartCommand extends Command
      */
     public function bindingAccount($SendUser, $MessageText): void
     {
-        $Uid = Telegram::verifyBindSession($MessageText);
+        $Uid = TelegramTools::verifyBindSession($MessageText);
         if ($Uid === 0) {
             $text = '绑定失败了呢,经检查发现:【' . $MessageText . '】的有效期为 10 分钟,你可以在我们网站上的 **资料编辑** 页面刷新后重试.';
         } else {

+ 3 - 17
src/Utils/Telegram/Message.php

@@ -5,11 +5,9 @@ declare(strict_types=1);
 namespace App\Utils\Telegram;
 
 use App\Models\Setting;
-use App\Utils\Telegram;
 use RedisException;
 use Telegram\Bot\Api;
 use Telegram\Bot\Exceptions\TelegramSDKException;
-use function count;
 use function in_array;
 use function json_decode;
 use function strlen;
@@ -64,7 +62,7 @@ final class Message
             $MessageData = trim($this->Message->getText());
             if ($this->ChatID > 0 && strlen($MessageData) === 16) {
                 // 私聊
-                $Uid = Telegram::verifyBindSession($MessageData);
+                $Uid = TelegramTools::verifyBindSession($MessageData);
                 if ($Uid === 0) {
                     $text = '绑定失败了呢,经检查发现:【' .
                         $MessageData . '】的有效期为 10 分钟,你可以在我们网站上的 **资料编辑** 页面刷新后重试.';
@@ -139,7 +137,7 @@ final class Message
             'name' => $NewChatMember->getFirstName() . ' ' . $NewChatMember->getLastName(),
         ];
 
-        if ($NewChatMember->getUsername() === $_ENV['telegram_bot']) {
+        if ($NewChatMember->getUsername() === Setting::obtain('telegram_bot')) {
             // 机器人加入新群组
             if (! Setting::obtain('allow_to_join_new_groups')
                 &&
@@ -159,18 +157,6 @@ final class Message
                         'user_id' => $Member['id'],
                     ]
                 );
-
-                if (count(json_decode(Setting::obtain('telegram_admins'))) >= 1) {
-                    foreach (json_decode(Setting::obtain('telegram_admins')) as $id) {
-                        $this->bot->sendMessage(
-                            [
-                                'text' => '根据你的设定,Bot 退出了一个群组.' . PHP_EOL .
-                                    '群组名称:' . $this->Message->getChat()->getTitle(),
-                                'chat_id' => $id,
-                            ]
-                        );
-                    }
-                }
             } else {
                 $this->replyWithMessage(
                     [
@@ -184,7 +170,7 @@ final class Message
 
             if (Setting::obtain('telegram_group_bound_user')
                 &&
-                $this->ChatID === $_ENV['telegram_chatid']
+                $this->ChatID === Setting::obtain('telegram_chatid')
                 &&
                 $NewUser === null
                 &&

+ 2 - 3
src/Utils/Telegram/Process.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace App\Utils\Telegram;
 
+use App\Models\Setting;
 use MaxMind\Db\Reader\InvalidDatabaseException;
 use Psr\Http\Message\RequestInterface;
 use RedisException;
@@ -19,18 +20,16 @@ final class Process
      */
     public static function index(RequestInterface $request): void
     {
-        $bot = new Api($_ENV['telegram_token']);
+        $bot = new Api(Setting::obtain('telegram_token'));
 
         $bot->addCommands([
             new Commands\MyCommand(),
             new Commands\HelpCommand(),
-            new Commands\InfoCommand(),
             new Commands\MenuCommand(),
             new Commands\PingCommand(),
             new Commands\StartCommand(),
             new Commands\UnbindCommand(),
             new Commands\CheckinCommand(),
-            new Commands\SetuserCommand(),
         ]);
 
         $bot->commandsHandler(true, $request);

+ 0 - 19
src/Utils/Telegram/Reply.php

@@ -51,23 +51,4 @@ final class Reply
         }
         return $text;
     }
-
-    /**
-     * [admin]获取用户信息
-     */
-    public static function getUserInfoFromAdmin(User $user, int $ChatID): string
-    {
-        $strArray = [
-            '#' . $user->id . ' ' . $user->user_name . ' 的用户信息',
-            '',
-            '用户邮箱:' . TelegramTools::getUserEmail($user->email, $ChatID),
-            '账户余额:' . $user->money,
-            '账户状态:' . ((int) $user->is_banned === 1 ? '封禁' : '正常'),
-            '用户等级:' . $user->class,
-            '剩余流量:' . $user->unusedTraffic(),
-            '等级到期:' . $user->class_expire,
-            '账户到期:' . $user->expire_in,
-        ];
-        return implode(PHP_EOL, $strArray);
-    }
 }

+ 27 - 417
src/Utils/Telegram/TelegramTools.php

@@ -6,35 +6,20 @@ namespace App\Utils\Telegram;
 
 use App\Models\Setting;
 use App\Models\User;
-use App\Models\UserMoneyLog;
+use App\Services\Cache;
 use App\Utils\Tools;
-use function array_map;
-use function count;
+use RedisException;
+use voku\helper\AntiXSS;
 use function curl_close;
 use function curl_exec;
 use function curl_init;
 use function curl_setopt;
-use function date;
-use function explode;
-use function implode;
-use function in_array;
-use function is_numeric;
 use function json_encode;
-use function number_format;
-use function str_pad;
-use function stripos;
-use function strlen;
-use function strpos;
-use function strtotime;
-use function substr;
-use function time;
-use function trim;
 use const CURLOPT_HTTPHEADER;
 use const CURLOPT_POST;
 use const CURLOPT_POSTFIELDS;
 use const CURLOPT_TIMEOUT;
 use const CURLOPT_URL;
-use const PHP_EOL;
 
 final class TelegramTools
 {
@@ -58,7 +43,7 @@ final class TelegramTools
      */
     public static function sendPost($Method, $Params): void
     {
-        $URL = 'https://api.telegram.org/bot' . $_ENV['telegram_token'] . '/' . $Method;
+        $URL = 'https://api.telegram.org/bot' . Setting::obtain('telegram_token') . '/' . $Method;
         $POSTData = json_encode($Params);
         $C = curl_init();
         curl_setopt($C, CURLOPT_URL, $URL);
@@ -71,420 +56,45 @@ final class TelegramTools
     }
 
     /**
-     * 用户识别搜索字段
+     * @param $token
      *
-     * @return array
-     */
-    public static function getUserSearchMethods(): array
-    {
-        return [
-            'id' => [],
-            'email' => ['email'],
-            'port' => ['port'],
-        ];
-    }
-
-    /**
-     * 操作字段
-     *
-     * @return array
-     */
-    public static function getUserActionOption(): array
-    {
-        return [
-            'is_admin' => ['is_admin'],
-            'banned' => ['banned'],
-            'money' => ['remark_user_option_money'],
-            'port' => ['port'],
-            'transfer_enable' => ['transfer_enable'],
-            'passwd' => ['passwd'],
-            'method' => ['method'],
-            'invite_num' => ['invite_num'],
-            'node_group' => ['node_group'],
-            'class' => ['class'],
-            'class_expire' => ['class_expire'],
-            'expire_in' => ['expire_in'],
-            'node_speedlimit' => ['node_speedlimit'],
-            'node_iplimit' => ['node_iplimit'],
-        ];
-    }
-
-    /**
-     * 待定
-     *
-     * @param $User
-     * @param $useOptionMethod
-     * @param $value
-     * @param $ChatID
+     * @return int
      *
-     * @return array
+     * @throws RedisException
      */
-    public static function operationUser($User, $useOptionMethod, $value, $ChatID): array
+    public static function verifyBindSession($token): int
     {
-        $Email = self::getUserEmail($User->email, $ChatID);
-        $old = $User->$useOptionMethod;
-        $useOptionMethodName = self::getUserActionOption()[$useOptionMethod][0];
-        switch ($useOptionMethod) {
-            // ##############
-            case 'is_banned':
-            case 'is_admin':
-                $strArray = [
-                    '// 支持的写法',
-                    '// [启用|是] —— 字面意思',
-                    '// [禁用|否] —— 字面意思',
-                ];
-                if (str_contains($value, ' ')) {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                if (in_array($value, ['启用', '是'])) {
-                    $User->$useOptionMethod = 1;
-                    $new = '启用';
-                } elseif (in_array($value, ['禁用', '否'])) {
-                    $User->$useOptionMethod = 0;
-                    $new = '禁用';
-                } else {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                $old = ($old ? '启用' : '禁用');
-                break;
-                // ##############
-            case 'transfer_enable':
-                $strArray = [
-                    '// 支持的写法,不支持单位 b,不区分大小写',
-                    '// 支持的单位:kb | mb | gb | tb | pb',
-                    '//  2kb —— 指定为该值得流量',
-                    '// +2kb —— 增加流量',
-                    '// -2kb —— 减少流量',
-                    '// *2   —— 以当前流量做乘法,不支持填写单位',
-                    '// /2   —— 以当前流量做除法,不支持填写单位',
-                ];
-                if (str_contains($value, ' ')) {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                $new = self::trafficMethod($User->$useOptionMethod, $value);
-                if ($new === null) {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                $User->$useOptionMethod = $new;
-                $old = Tools::autoBytes($old);
-                $new = Tools::autoBytes($new);
-                break;
-                // ##############
-            case 'expire_in':
-            case 'class_expire':
-                $strArray = [
-                    '// 支持的写法,单位:天',
-                    '// 使用天数设置不能包含空格',
-                    '//  2 —— 以当前时间为基准的天数设置',
-                    '// +2 —— 增加天数',
-                    '// -2 —— 减少天数',
-                    '// 指定日期,在日期与时间中含有一个空格',
-                    '// 2020-02-30 —— 指定日期',
-                    '// 2020-02-30 08:00:00 —— 指定日期精确到秒',
-                ];
-                if (str_starts_with($value, '+')
-                    ||
-                    str_starts_with($value, '-')
-                ) {
-                    $operator = substr($value, 0, 1);
-                    $number = substr($value, 1);
-                    if (is_numeric($number)) {
-                        $number *= 86400;
-                        $old_time = strtotime($old);
-                        $new = ($operator === '+' ? $old_time + $number : $old_time - $number);
-                        $new = date('Y-m-d H:i:s', (int) $new);
-                    } else {
-                        if (strtotime($value) === false) {
-                            return [
-                                'ok' => false,
-                                'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                            ];
-                        }
-                        $new = strtotime($value);
-                        $new = date('Y-m-d H:i:s', $new);
-                    }
-                } else {
-                    $number = $value;
-                    if (is_numeric($value)) {
-                        $number *= 86400;
-                        $new = time() + $number;
-                        $new = date('Y-m-d H:i:s', (int) $new);
-                    } else {
-                        if (strtotime($value) === false) {
-                            return [
-                                'ok' => false,
-                                'msg' => '处理出错,不支持的写法.' . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                            ];
-                        }
-                        $new = strtotime($value);
-                        $new = date('Y-m-d H:i:s', $new);
-                    }
-                }
-                $User->$useOptionMethod = $new;
-                break;
-                // ##############
-            case 'method':
-            case 'passwd':
-            case 'money':
-                $strArray = [
-                    '// 参数值中不允许有空格,结果会含小数 2 位',
-                    '// +2  —— 增加余额',
-                    '// -2  —— 减少余额',
-                    '// *2  —— 以当前余额做乘法',
-                    '// /2  —— 以当前余额做除法',
-                ];
-                $value = explode(' ', $value)[0];
-                $new = self::computingMethod($User->$useOptionMethod, $value, true);
-                if ($new === null) {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.'  . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                $User->$useOptionMethod = $new;
-                break;
-                // ##############
-            case 'class':
-            case 'invite_num':
-            case 'node_group':
-            case 'node_iplimit':
-            case 'node_speedlimit':
-                $strArray = [
-                    '// 参数值中不允许有空格',
-                    '// +2  —— 增加值',
-                    '// -2  —— 减少值',
-                    '// *2  —— 以当前值做乘法',
-                    '// /2  —— 以当前值做除法',
-                ];
-                $value = explode(' ', $value)[0];
-                $new = self::computingMethod($User->$useOptionMethod, $value);
-                if ($new === null) {
-                    return [
-                        'ok' => false,
-                        'msg' => '处理出错,不支持的写法.'  . PHP_EOL . PHP_EOL . self::strArrayToCode($strArray),
-                    ];
-                }
-                $User->$useOptionMethod = $new;
-                break;
-                // ##############
-            default:
-                return [
-                    'ok' => false,
-                    'msg' => '尚不支持.',
-                ];
-        }
-        if ($User->save()) {
-            if ($useOptionMethod === 'money') {
-                $diff = $new - $old;
-                $remark = ($diff > 0 ? '管理员添加余额' : '管理员扣除余额');
-                (new UserMoneyLog())->add($User->id, (float) $old, (float) $new, (float) $diff, $remark);
-            }
-            $strArray = [
-                '目标用户:' . $Email,
-                '被修改项:' . $useOptionMethodName . '[' . $useOptionMethod . ']',
-                '修改前为:' . $old,
-                '修改后为:' . $new,
-            ];
-            return [
-                'ok' => true,
-                'msg' => self::strArrayToCode($strArray),
-            ];
-        }
-        return [
-            'ok' => false,
-            'msg' => '保存出错',
-        ];
-    }
+        $antiXss = new AntiXSS();
+        $redis = Cache::initRedis();
+        $uid = $redis->get('telegram_bind:' . $antiXss->xss_clean($token));
 
-    /**
-     * 获取用户邮箱
-     *
-     * @param string $email  邮箱
-     * @param int    $ChatID 会话 ID
-     */
-    public static function getUserEmail(string $email, int $ChatID): string
-    {
-        if (Setting::obtain('enable_user_email_group_show') || $ChatID > 0) {
-            return $email;
-        }
-        $a = strpos($email, '@');
-        if ($a === false) {
-            return $email;
+        if (! $uid) {
+            return 0;
         }
-        $string = substr($email, $a);
-        return $a === 1 ? '*' . $string : substr($email, 0, 1) . str_pad('', $a - 1, '*') . $string;
-    }
 
-    /**
-     * 分割字符串
-     *
-     * @param string $Str       源字符串
-     * @param string $Delimiter 分割定界符
-     * @param int    $Quantity  最大返回数量
-     *
-     * @return array
-     */
-    public static function strExplode(string $Str, string $Delimiter, int $Quantity = 10): array
-    {
-        $return = [];
-        $Str = trim($Str);
-        for ($x = 0; $x <= 10; $x++) {
-            if (str_contains($Str, $Delimiter) && count($return) < $Quantity - 1) {
-                $temp = substr($Str, 0, strpos($Str, $Delimiter));
-                $return[] = $temp;
-                $Str = trim(substr($Str, strlen($temp)));
-            } else {
-                $return[] = $Str;
-                break;
-            }
-        }
-        return $return;
-    }
+        $redis->del('telegram_bind:' . $token);
 
-    /**
-     * 查找字符串是否是某个方法的别名
-     *
-     * @param array  $MethodGroup 方法别名的数组
-     * @param string $Search      被搜索的字符串
-     */
-    public static function getOptionMethod(array $MethodGroup, string $Search): string
-    {
-        $useMethod = '';
-        foreach ($MethodGroup as $MethodName => $Remarks) {
-            if (strlen($MethodName) === strlen($Search) && stripos($MethodName, $Search) === 0) {
-                $useMethod = $MethodName;
-                break;
-            }
-            if (count($Remarks) >= 1) {
-                foreach ($Remarks as $Remark) {
-                    if (strlen($Remark) === strlen($Search) && stripos($Remark, $Search) === 0) {
-                        $useMethod = $MethodName;
-                        break 2;
-                    }
-                }
-            }
-        }
-        return $useMethod;
+        return (int) $uid;
     }
 
     /**
-     * 使用 $Value 给定的运算式与 $Source 计算结果
+     * @param $user
      *
-     * @param string $Source         源数值
-     * @param string $Value          运算式含增改数值
-     * @param bool   $FloatingNumber 是否格式化为浮点数
-     */
-    public static function computingMethod(string $Source, string $Value, bool $FloatingNumber = false): ?string
-    {
-        if ((str_starts_with($Value, '+')
-                ||
-                str_starts_with($Value, '-')
-                ||
-                str_starts_with($Value, '*')
-                ||
-                str_starts_with($Value, '/'))
-            &&
-            is_numeric(substr($Value, 1))
-        ) {
-            $Source = match (substr($Value, 0, 1)) {
-                '+' => (int) $Source + (int) substr($Value, 1),
-                '-' => (int) $Source - (int) substr($Value, 1),
-                '*' => (int) $Source * (int) substr($Value, 1),
-                '/' => (int) $Source / (int) substr($Value, 1),
-                default => null,
-            };
-        } else {
-            if (is_numeric($Value)) {
-                $Source = $Value;
-            } else {
-                $Source = null;
-            }
-        }
-        if ($Source !== null) {
-            $Source = ($FloatingNumber === false
-                ? number_format($Source, 0, '.', '')
-                : number_format($Source, 2, '.', ''));
-        }
-        return $Source;
-    }
-
-    /**
-     * 使用 $Value 给定的运算式及流量单位与 $Source 计算结果
+     * @return string
      *
-     * @param string $Source 源数值
-     * @param string $Value  运算式含增改数值
+     * @throws RedisException
      */
-    public static function trafficMethod(string $Source, string $Value): ?int
+    public static function addBindSession($user): string
     {
-        if (str_starts_with($Value, '+')
-            ||
-            str_starts_with($Value, '-')
-            ||
-            str_starts_with($Value, '*')
-            ||
-            str_starts_with($Value, '/')
-        ) {
-            $operator = substr($Value, 0, 1);
-            if (! in_array($operator, ['*', '/'])) {
-                $number = Tools::autoBytesR(substr($Value, 1));
-            } else {
-                $number = substr($Value, 1, strlen($Value) - 1);
-                if (! is_numeric($number)) {
-                    return null;
-                }
-            }
-            if ($number === null) {
-                return null;
-            }
-
-            $Source = match ($operator) {
-                '+' => (int) $Source + (int) $number,
-                '-' => (int) $Source - (int) $number,
-                '*' => (int) $Source * (int) $number,
-                '/' => (int) $Source / (int) $number,
-                default => null,
-            };
-        } else {
-            if (is_numeric($Value)) {
-                if ((int) $Value === 0) {
-                    $Source = 0;
-                } else {
-                    $Source = Tools::autoBytesR($Value . 'KB');
-                }
-            } else {
-                $Source = Tools::autoBytesR($Value);
-            }
-        }
-        return $Source;
-    }
+        $redis = Cache::initRedis();
+        $token = Tools::genRandomChar(16);
 
-    /**
-     * 字符串数组转 TG HTML 等宽字符串
-     *
-     * @param array $strArray 字符串数组
-     */
-    public static function strArrayToCode(array $strArray): string
-    {
-        return implode(
-            PHP_EOL,
-            array_map(
-                static function ($item) {
-                    return '<code>' . $item . '</code>';
-                },
-                $strArray
-            )
+        $redis->setex(
+            'telegram_bind:' . $token,
+            600,
+            $user->id
         );
+
+        return $token;
     }
 }

+ 5 - 8
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
     {
@@ -24,11 +24,10 @@ class ConfigTest extends TestCase
             'enable_analytics_code' => false,
             'enable_kill' => true,
             'enable_change_email' => false,
-            'enable_telegram' => true,
-            'telegram_bot' => 'my_bot',
             'subscribeLog' => true,
             'subscribeLog_keep_days' => 30,
             'enable_r2_client_download' => true,
+            'jsdelivr_url' => 'cdn.jsdelivr.net',
         ];
 
         $mockEnv = [
@@ -41,14 +40,13 @@ class ConfigTest extends TestCase
             'enable_analytics_code' => false,
             'enable_kill' => true,
             'enable_change_email' => false,
-            'enable_telegram' => true,
-            'telegram_bot' => 'my_bot',
             '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']);
@@ -59,11 +57,10 @@ class ConfigTest extends TestCase
         $this->assertSame($mockEnv['enable_analytics_code'], $config['enable_analytics_code']);
         $this->assertSame($mockEnv['enable_kill'], $config['enable_kill']);
         $this->assertSame($mockEnv['enable_change_email'], $config['enable_change_email']);
-        $this->assertSame($mockEnv['enable_telegram'], $config['enable_telegram']);
-        $this->assertSame($mockEnv['telegram_bot'], $config['telegram_bot']);
         $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']);
     }
 
     /**