mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: 更新 api 机器人
This commit is contained in:
@@ -82,6 +82,7 @@ func InitDB() error {
|
||||
&entity.Task{},
|
||||
&entity.TaskItem{},
|
||||
&entity.File{},
|
||||
&entity.TelegramChannel{},
|
||||
)
|
||||
if err != nil {
|
||||
utils.Fatal("数据库迁移失败: %v", err)
|
||||
@@ -146,6 +147,7 @@ func autoMigrate() error {
|
||||
&entity.SearchStat{},
|
||||
&entity.HotDrama{},
|
||||
&entity.File{},
|
||||
&entity.TelegramChannel{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -250,6 +250,59 @@ func (h *TelegramHandler) HandleWebhook(c *gin.Context) {
|
||||
h.telegramBotService.HandleWebhookUpdate(c)
|
||||
}
|
||||
|
||||
// GetBotStatus 获取机器人状态
|
||||
func (h *TelegramHandler) GetBotStatus(c *gin.Context) {
|
||||
// 这里可以返回机器人运行状态、最后活动时间等信息
|
||||
// 暂时返回基本状态信息
|
||||
|
||||
botUsername := h.telegramBotService.GetBotUsername()
|
||||
|
||||
status := map[string]interface{}{
|
||||
"bot_username": botUsername,
|
||||
"service_running": botUsername != "",
|
||||
"webhook_mode": false, // 当前使用长轮询模式
|
||||
"polling_mode": true,
|
||||
}
|
||||
|
||||
SuccessResponse(c, status)
|
||||
}
|
||||
|
||||
// TestBotMessage 测试机器人消息发送
|
||||
func (h *TelegramHandler) TestBotMessage(c *gin.Context) {
|
||||
var req struct {
|
||||
ChatID int64 `json:"chat_id" binding:"required"`
|
||||
Text string `json:"text" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.telegramBotService.SendMessage(req.ChatID, req.Text)
|
||||
if err != nil {
|
||||
ErrorResponse(c, "发送消息失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
SuccessResponse(c, map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "测试消息已发送",
|
||||
})
|
||||
}
|
||||
|
||||
// ReloadBotConfig 重新加载机器人配置
|
||||
func (h *TelegramHandler) ReloadBotConfig(c *gin.Context) {
|
||||
// 这里可以实现重新加载配置的逻辑
|
||||
// 目前通过重启服务来实现配置重新加载
|
||||
|
||||
SuccessResponse(c, map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "请重启服务器以重新加载配置",
|
||||
"note": "当前版本需要重启服务器才能重新加载机器人配置",
|
||||
})
|
||||
}
|
||||
|
||||
// getCurrentUsername 获取当前用户名(临时实现)
|
||||
func getCurrentUsername(c *gin.Context) string {
|
||||
// 这里应该从中间件中获取用户信息
|
||||
|
||||
3
main.go
3
main.go
@@ -345,6 +345,9 @@ func main() {
|
||||
api.GET("/telegram/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetBotConfig)
|
||||
api.PUT("/telegram/bot-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.UpdateBotConfig)
|
||||
api.POST("/telegram/validate-api-key", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ValidateApiKey)
|
||||
api.GET("/telegram/bot-status", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetBotStatus)
|
||||
api.POST("/telegram/reload-config", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ReloadBotConfig)
|
||||
api.POST("/telegram/test-message", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.TestBotMessage)
|
||||
api.GET("/telegram/channels", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetChannels)
|
||||
api.POST("/telegram/channels", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.CreateChannel)
|
||||
api.PUT("/telegram/channels/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.UpdateChannel)
|
||||
|
||||
35
migrations/telegram_channels.sql
Normal file
35
migrations/telegram_channels.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
-- 创建 Telegram 频道/群组表
|
||||
CREATE TABLE telegram_channels (
|
||||
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
-- Telegram 频道/群组信息
|
||||
chat_id BIGINT NOT NULL COMMENT 'Telegram 聊天ID',
|
||||
chat_name VARCHAR(255) NOT NULL COMMENT '聊天名称',
|
||||
chat_type VARCHAR(50) NOT NULL COMMENT '类型:channel/group',
|
||||
|
||||
-- 推送配置
|
||||
push_enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用推送',
|
||||
push_frequency INT DEFAULT 24 COMMENT '推送频率(小时)',
|
||||
content_categories TEXT COMMENT '推送的内容分类,用逗号分隔',
|
||||
content_tags TEXT COMMENT '推送的标签,用逗号分隔',
|
||||
|
||||
-- 频道状态
|
||||
is_active BOOLEAN DEFAULT TRUE COMMENT '是否活跃',
|
||||
last_push_at TIMESTAMP NULL COMMENT '最后推送时间',
|
||||
|
||||
-- 注册信息
|
||||
registered_by VARCHAR(100) COMMENT '注册者用户名',
|
||||
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
|
||||
|
||||
-- 索引
|
||||
INDEX idx_chat_id (chat_id),
|
||||
INDEX idx_chat_type (chat_type),
|
||||
INDEX idx_is_active (is_active),
|
||||
INDEX idx_push_enabled (push_enabled),
|
||||
INDEX idx_registered_at (registered_at),
|
||||
INDEX idx_last_push_at (last_push_at),
|
||||
|
||||
UNIQUE KEY uk_chat_id (chat_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Telegram 频道/群组表';
|
||||
@@ -66,9 +66,12 @@ func (s *TelegramBotServiceImpl) loadConfig() error {
|
||||
return fmt.Errorf("加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("从数据库加载到 %d 个配置项", len(configs))
|
||||
|
||||
// 初始化默认值
|
||||
s.config.Enabled = false
|
||||
s.config.ApiKey = ""
|
||||
s.config.AutoReplyEnabled = true
|
||||
s.config.AutoReplyEnabled = false // 默认禁用自动回复
|
||||
s.config.AutoReplyTemplate = "您好!我可以帮您搜索网盘资源,请输入您要搜索的内容。"
|
||||
s.config.AutoDeleteEnabled = false
|
||||
s.config.AutoDeleteInterval = 60
|
||||
@@ -77,24 +80,33 @@ func (s *TelegramBotServiceImpl) loadConfig() error {
|
||||
switch config.Key {
|
||||
case entity.ConfigKeyTelegramBotEnabled:
|
||||
s.config.Enabled = config.Value == "true"
|
||||
utils.Info("加载配置 %s = %s (Enabled: %v)", config.Key, config.Value, s.config.Enabled)
|
||||
case entity.ConfigKeyTelegramBotApiKey:
|
||||
s.config.ApiKey = config.Value
|
||||
utils.Info("加载配置 %s = [HIDDEN]", config.Key)
|
||||
case entity.ConfigKeyTelegramAutoReplyEnabled:
|
||||
s.config.AutoReplyEnabled = config.Value == "true"
|
||||
utils.Info("加载配置 %s = %s (AutoReplyEnabled: %v)", config.Key, config.Value, s.config.AutoReplyEnabled)
|
||||
case entity.ConfigKeyTelegramAutoReplyTemplate:
|
||||
if config.Value != "" {
|
||||
s.config.AutoReplyTemplate = config.Value
|
||||
}
|
||||
utils.Info("加载配置 %s = %s", config.Key, config.Value)
|
||||
case entity.ConfigKeyTelegramAutoDeleteEnabled:
|
||||
s.config.AutoDeleteEnabled = config.Value == "true"
|
||||
utils.Info("加载配置 %s = %s (AutoDeleteEnabled: %v)", config.Key, config.Value, s.config.AutoDeleteEnabled)
|
||||
case entity.ConfigKeyTelegramAutoDeleteInterval:
|
||||
if config.Value != "" {
|
||||
fmt.Sscanf(config.Value, "%d", &s.config.AutoDeleteInterval)
|
||||
}
|
||||
utils.Info("加载配置 %s = %s (AutoDeleteInterval: %d)", config.Key, config.Value, s.config.AutoDeleteInterval)
|
||||
default:
|
||||
utils.Debug("未知配置: %s = %s", config.Key, config.Value)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Info("Telegram Bot 配置已加载: Enabled=%v", s.config.Enabled)
|
||||
utils.Info("Telegram Bot 配置加载完成: Enabled=%v, AutoReplyEnabled=%v, ApiKey长度=%d",
|
||||
s.config.Enabled, s.config.AutoReplyEnabled, len(s.config.ApiKey))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -193,16 +205,25 @@ func (s *TelegramBotServiceImpl) setupWebhook() error {
|
||||
|
||||
// messageLoop 消息处理循环(长轮询模式)
|
||||
func (s *TelegramBotServiceImpl) messageLoop() {
|
||||
utils.Info("开始监听 Telegram 消息更新...")
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
|
||||
updates := s.bot.GetUpdatesChan(u)
|
||||
|
||||
utils.Info("消息监听循环已启动,等待消息...")
|
||||
|
||||
for update := range updates {
|
||||
if update.Message != nil {
|
||||
utils.Info("接收到新消息更新")
|
||||
s.handleMessage(update.Message)
|
||||
} else {
|
||||
utils.Debug("接收到其他类型更新: %v", update)
|
||||
}
|
||||
}
|
||||
|
||||
utils.Info("消息监听循环已结束")
|
||||
}
|
||||
|
||||
// handleMessage 处理接收到的消息
|
||||
@@ -218,25 +239,31 @@ func (s *TelegramBotServiceImpl) handleMessage(message *tgbotapi.Message) {
|
||||
|
||||
// 处理 /register 命令
|
||||
if strings.ToLower(text) == "/register" {
|
||||
utils.Info("处理 /register 命令 from ChatID=%d", chatID)
|
||||
s.handleRegisterCommand(message)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理 /start 命令
|
||||
if strings.ToLower(text) == "/start" {
|
||||
utils.Info("处理 /start 命令 from ChatID=%d", chatID)
|
||||
s.handleStartCommand(message)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理普通文本消息(搜索请求)
|
||||
if len(text) > 0 && !strings.HasPrefix(text, "/") {
|
||||
utils.Info("处理搜索请求 from ChatID=%d: %s", chatID, text)
|
||||
s.handleSearchRequest(message)
|
||||
return
|
||||
}
|
||||
|
||||
// 默认自动回复
|
||||
if s.config.AutoReplyEnabled {
|
||||
utils.Info("发送自动回复 to ChatID=%d (AutoReplyEnabled=%v)", chatID, s.config.AutoReplyEnabled)
|
||||
s.sendReply(message, s.config.AutoReplyTemplate)
|
||||
} else {
|
||||
utils.Info("跳过自动回复 to ChatID=%d (AutoReplyEnabled=%v)", chatID, s.config.AutoReplyEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +339,8 @@ func (s *TelegramBotServiceImpl) handleSearchRequest(message *tgbotapi.Message)
|
||||
|
||||
// sendReply 发送回复消息
|
||||
func (s *TelegramBotServiceImpl) sendReply(message *tgbotapi.Message, text string) {
|
||||
utils.Info("尝试发送回复消息到 ChatID=%d: %s", message.Chat.ID, text)
|
||||
|
||||
msg := tgbotapi.NewMessage(message.Chat.ID, text)
|
||||
msg.ParseMode = "Markdown"
|
||||
msg.ReplyToMessageID = message.MessageID
|
||||
@@ -322,6 +351,8 @@ func (s *TelegramBotServiceImpl) sendReply(message *tgbotapi.Message, text strin
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("消息发送成功 to ChatID=%d, MessageID=%d", sentMsg.Chat.ID, sentMsg.MessageID)
|
||||
|
||||
// 如果启用了自动删除,启动删除定时器
|
||||
if s.config.AutoDeleteEnabled && s.config.AutoDeleteInterval > 0 {
|
||||
time.AfterFunc(time.Duration(s.config.AutoDeleteInterval)*time.Minute, func() {
|
||||
|
||||
230
web/components/QqBotTab.vue
Normal file
230
web/components/QqBotTab.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="tab-content-container">
|
||||
<div class="space-y-8">
|
||||
<!-- 步骤1:Astrobot 安装指南 -->
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">1</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装 Astrobot</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-github text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">开源地址</p>
|
||||
<a
|
||||
href="https://github.com/Astrian/astrobot"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/Astrian/astrobot
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-book text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">安装教程</p>
|
||||
<a
|
||||
href="https://github.com/Astrian/astrobot/wiki"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/Astrian/astrobot/wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤2:插件安装 -->
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-green-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">2</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装插件</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-puzzle-piece text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">插件地址</p>
|
||||
<a
|
||||
href="https://github.com/ctwj/astrbot_plugin_urldb"
|
||||
target="_blank"
|
||||
class="text-green-600 dark:text-green-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/ctwj/astrbot_plugin_urldb
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong>插件特性:</strong><br>
|
||||
• 支持@机器人搜索功能<br>
|
||||
• 可配置API域名和密钥<br>
|
||||
• 自动格式化搜索结果<br>
|
||||
• 支持超时时间配置<br><br>
|
||||
<strong>安装步骤:</strong><br>
|
||||
1. Astrbot 插件市场, 搜索 urldb 安装<br>
|
||||
2. 根据下面的配置信息配置插件
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤3:配置信息 -->
|
||||
<div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-purple-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">3</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">配置信息</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">网站域名</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input
|
||||
:value="siteDomain"
|
||||
readonly
|
||||
class="flex-1"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="copyToClipboard(siteDomain)"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">API Token</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input
|
||||
:value="apiToken"
|
||||
readonly
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
class="flex-1"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="copyToClipboard(apiToken)"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong>配置说明:</strong><br>
|
||||
将上述信息配置到 Astrobot 的插件配置文件中,插件将自动连接到本系统进行资源搜索。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useNotification } from 'naive-ui'
|
||||
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
|
||||
import { useSystemConfigApi } from '~/composables/useApi'
|
||||
|
||||
// 定义配置表单类型
|
||||
interface BotConfigForm {
|
||||
api_token: string
|
||||
}
|
||||
|
||||
// 使用配置改动检测
|
||||
const {
|
||||
setOriginalConfig,
|
||||
updateCurrentConfig,
|
||||
getChangedConfig,
|
||||
hasChanges,
|
||||
updateOriginalConfig,
|
||||
saveConfig: saveConfigWithDetection
|
||||
} = useConfigChangeDetection<BotConfigForm>({
|
||||
debug: true,
|
||||
fieldMapping: {
|
||||
api_token: 'api_token'
|
||||
}
|
||||
})
|
||||
|
||||
const notification = useNotification()
|
||||
|
||||
// 获取网站域名和API Token
|
||||
const siteDomain = computed(() => {
|
||||
if (process.client) {
|
||||
return window.location.origin
|
||||
}
|
||||
return 'https://yourdomain.com'
|
||||
})
|
||||
|
||||
const apiToken = ref('')
|
||||
|
||||
// 获取API Token
|
||||
const fetchApiToken = async () => {
|
||||
try {
|
||||
const systemConfigApi = useSystemConfigApi()
|
||||
const response = await systemConfigApi.getSystemConfig()
|
||||
|
||||
if (response) {
|
||||
const configData = {
|
||||
api_token: (response as any).api_token || ''
|
||||
}
|
||||
|
||||
apiToken.value = configData.api_token || '未配置API Token'
|
||||
setOriginalConfig(configData)
|
||||
} else {
|
||||
apiToken.value = '未配置API Token'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取API Token失败:', error)
|
||||
apiToken.value = '获取失败'
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
notification.success({
|
||||
content: '已复制到剪贴板',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error)
|
||||
notification.error({
|
||||
content: '复制失败',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(async () => {
|
||||
fetchApiToken()
|
||||
console.log('QQ 机器人标签已加载')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* QQ 机器人标签样式 */
|
||||
</style>
|
||||
639
web/components/TelegramBotTab.vue
Normal file
639
web/components/TelegramBotTab.vue
Normal file
@@ -0,0 +1,639 @@
|
||||
<template>
|
||||
<div class="tab-content-container h-full overflow-y-auto">
|
||||
<div class="space-y-8 h-full">
|
||||
<!-- 机器人基本配置 -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">1</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">机器人配置</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 机器人启用开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">启用 Telegram 机器人</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">开启后机器人将开始工作</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.bot_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- API Key 配置 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="space-y-3">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">Bot API Key</label>
|
||||
<div class="flex space-x-3">
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.bot_api_key"
|
||||
placeholder="请输入 Telegram Bot API Key"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
class="flex-1"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="validatingApiKey"
|
||||
@click="validateApiKey"
|
||||
>
|
||||
校验
|
||||
</n-button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
从 @BotFather 获取 API Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 校验结果 -->
|
||||
<div v-if="apiKeyValidationResult" class="p-3 rounded-md"
|
||||
:class="apiKeyValidationResult.valid ? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300' : 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300'">
|
||||
<div class="flex items-center">
|
||||
<i :class="apiKeyValidationResult.valid ? 'fas fa-check-circle' : 'fas fa-times-circle'"
|
||||
class="mr-2"></i>
|
||||
<span>{{ apiKeyValidationResult.valid ? '' : 'Fail' }}</span>
|
||||
<span v-if="apiKeyValidationResult.valid && apiKeyValidationResult.bot_info" class="mt-2 text-xs">
|
||||
机器人:@{{ apiKeyValidationResult.bot_info.username }} ({{ apiKeyValidationResult.bot_info.first_name }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="savingBotConfig"
|
||||
:disabled="!hasBotConfigChanges"
|
||||
@click="saveBotConfig"
|
||||
>
|
||||
保存配置
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动回复配置 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-8 h-8 bg-green-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">2</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">自动回复设置</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 自动回复开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">启用自动回复</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">收到消息时自动回复帮助信息</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.auto_reply_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 回复模板 -->
|
||||
<div v-if="telegramBotConfig.auto_reply_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">回复模板</label>
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.auto_reply_template"
|
||||
type="textarea"
|
||||
placeholder="请输入自动回复内容"
|
||||
:rows="3"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 自动删除开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">自动删除回复</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">定时删除机器人发送的回复消息</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.auto_delete_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 删除间隔 -->
|
||||
<div v-if="telegramBotConfig.auto_delete_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">删除间隔(分钟)</label>
|
||||
<n-input-number
|
||||
v-model:value="telegramBotConfig.auto_delete_interval"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 频道和群组管理 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center">
|
||||
<div class="w-8 h-8 bg-purple-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">频道和群组管理</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理推送对象的频道和群组</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-button
|
||||
@click="refreshChannels"
|
||||
:loading="refreshingChannels"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</template>
|
||||
刷新
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="testBotConnection"
|
||||
:loading="testingConnection"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-robot"></i>
|
||||
</template>
|
||||
测试连接
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="showRegisterChannelDialog = true"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
注册频道/群组
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 频道列表 -->
|
||||
<div v-if="telegramChannels.length > 0" class="space-y-4">
|
||||
<div v-for="channel in telegramChannels" :key="channel.id"
|
||||
class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<i :class="channel.chat_type === 'channel' ? 'fab fa-telegram-plane' : 'fas fa-users'"
|
||||
class="text-lg text-blue-600 dark:text-blue-400"></i>
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">{{ channel.chat_name }}</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ channel.chat_type === 'channel' ? '频道' : '群组' }} • ID: {{ channel.chat_id }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-tag :type="channel.is_active ? 'success' : 'warning'" size="small">
|
||||
{{ channel.is_active ? '活跃' : '非活跃' }}
|
||||
</n-tag>
|
||||
<n-button size="small" @click="editChannel(channel)">
|
||||
<template #icon>
|
||||
<i class="fas fa-edit"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button size="small" type="error" @click="unregisterChannel(channel)">
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推送配置 -->
|
||||
<div v-if="channel.push_enabled" class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">推送频率</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ channel.push_frequency }} 小时</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">内容分类</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ channel.content_categories || '全部' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">标签</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ channel.content_tags || '全部' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">推送已禁用</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="text-center py-8">
|
||||
<i class="fab fa-telegram-plane text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">暂无频道或群组</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">点击上方按钮注册推送对象</p>
|
||||
<n-button type="primary" @click="showRegisterChannelDialog = true">
|
||||
立即注册
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 注册频道对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showRegisterChannelDialog"
|
||||
preset="card"
|
||||
title="注册频道/群组"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||
将机器人添加到频道或群组,然后发送命令获取频道信息并注册为推送对象。
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-info-circle text-blue-600 dark:text-blue-400 mt-1"></i>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">注册步骤:</h4>
|
||||
<ol class="text-sm text-blue-700 dark:text-blue-300 space-y-1 list-decimal list-inside">
|
||||
<li>将 @{{ telegramBotConfig.bot_enabled ? '机器人用户名' : '机器人' }} 添加为频道管理员或群组成员</li>
|
||||
<li>在频道/群组中发送 <code class="bg-blue-200 dark:bg-blue-800 px-1 rounded">/register</code> 命令</li>
|
||||
<li>机器人将自动识别并注册该频道/群组</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!telegramBotConfig.bot_enabled || !telegramBotConfig.bot_api_key" class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 dark:text-yellow-400 mt-1"></i>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">配置未完成</h4>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">请先启用机器人并配置有效的 API Key。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center py-4">
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="showRegisterChannelDialog = false"
|
||||
>
|
||||
我知道了
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useNotification, useDialog } from 'naive-ui'
|
||||
import { useTelegramApi } from '~/composables/useApi'
|
||||
|
||||
// Telegram 相关数据和状态
|
||||
const telegramBotConfig = ref<any>({
|
||||
bot_enabled: false,
|
||||
bot_api_key: '',
|
||||
auto_reply_enabled: true,
|
||||
auto_reply_template: '您好!我可以帮您搜索网盘资源,请输入您要搜索的内容。',
|
||||
auto_delete_enabled: false,
|
||||
auto_delete_interval: 60,
|
||||
})
|
||||
|
||||
const telegramChannels = ref<any[]>([])
|
||||
const validatingApiKey = ref(false)
|
||||
const savingBotConfig = ref(false)
|
||||
const apiKeyValidationResult = ref<any>(null)
|
||||
const hasBotConfigChanges = ref(false)
|
||||
const showRegisterChannelDialog = ref(false)
|
||||
const refreshingChannels = ref(false)
|
||||
const testingConnection = ref(false)
|
||||
|
||||
// 使用统一的Telegram API
|
||||
const telegramApi = useTelegramApi()
|
||||
|
||||
// 获取 Telegram 配置
|
||||
const fetchTelegramConfig = async () => {
|
||||
try {
|
||||
const data = await telegramApi.getBotConfig() as any
|
||||
if (data) {
|
||||
telegramBotConfig.value = { ...data }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取 Telegram 配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取频道列表
|
||||
const fetchTelegramChannels = async () => {
|
||||
try {
|
||||
const data = await telegramApi.getChannels() as any[]
|
||||
if (data) {
|
||||
telegramChannels.value = data
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('获取频道列表失败:', error)
|
||||
// 如果是表不存在的错误,给出更友好的提示
|
||||
if (error?.message?.includes('telegram_channels') ||
|
||||
error?.message?.includes('does not exist') ||
|
||||
error?.message?.includes('relation') && error?.message?.includes('does not exist')) {
|
||||
notification.error({
|
||||
content: '频道列表表不存在,请重启服务器以创建表',
|
||||
duration: 5000
|
||||
})
|
||||
} else {
|
||||
notification.error({
|
||||
content: '获取频道列表失败,请稍后重试',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
// 清空列表以显示空状态
|
||||
telegramChannels.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 处理机器人配置变更
|
||||
const handleBotConfigChange = () => {
|
||||
hasBotConfigChanges.value = true
|
||||
}
|
||||
|
||||
// 校验 API Key
|
||||
const validateApiKey = async () => {
|
||||
if (!telegramBotConfig.value.bot_api_key) {
|
||||
notification.error({
|
||||
content: '请输入 API Key',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
validatingApiKey.value = true
|
||||
try {
|
||||
const data = await telegramApi.validateApiKey({
|
||||
api_key: telegramBotConfig.value.bot_api_key
|
||||
}) as any
|
||||
|
||||
console.log('API Key 校验结果:', data)
|
||||
if (data) {
|
||||
apiKeyValidationResult.value = data
|
||||
if (data.valid) {
|
||||
notification.success({
|
||||
content: 'API Key 校验成功',
|
||||
duration: 2000
|
||||
})
|
||||
} else {
|
||||
notification.error({
|
||||
content: data.error,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
apiKeyValidationResult.value = {
|
||||
valid: false,
|
||||
error: error?.message || '校验失败'
|
||||
}
|
||||
notification.error({
|
||||
content: 'API Key 校验失败',
|
||||
duration: 2000
|
||||
})
|
||||
} finally {
|
||||
validatingApiKey.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存机器人配置
|
||||
const saveBotConfig = async () => {
|
||||
savingBotConfig.value = true
|
||||
|
||||
// 先校验key 是否有效
|
||||
try {
|
||||
const data = await telegramApi.validateApiKey({
|
||||
api_key: telegramBotConfig.value.bot_api_key
|
||||
}) as any
|
||||
|
||||
console.log('API Key 校验结果:', data)
|
||||
if (data) {
|
||||
apiKeyValidationResult.value = data
|
||||
if (!data.valid) {
|
||||
notification.error({
|
||||
content: data.error,
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
apiKeyValidationResult.value = {
|
||||
valid: false,
|
||||
error: error?.message || '校验失败'
|
||||
}
|
||||
notification.error({
|
||||
content: 'API Key 校验失败',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const configRequest: any = {}
|
||||
if (hasBotConfigChanges.value) {
|
||||
const config = telegramBotConfig.value as any
|
||||
configRequest.bot_enabled = config.bot_enabled
|
||||
configRequest.bot_api_key = config.bot_api_key
|
||||
configRequest.auto_reply_enabled = config.auto_reply_enabled
|
||||
configRequest.auto_reply_template = config.auto_reply_template
|
||||
configRequest.auto_delete_enabled = config.auto_delete_enabled
|
||||
configRequest.auto_delete_interval = config.auto_delete_interval
|
||||
}
|
||||
|
||||
await telegramApi.updateBotConfig(configRequest)
|
||||
|
||||
notification.success({
|
||||
content: '配置保存成功',
|
||||
duration: 2000
|
||||
})
|
||||
hasBotConfigChanges.value = false
|
||||
// 重新获取配置以确保同步
|
||||
await fetchTelegramConfig()
|
||||
} catch (error: any) {
|
||||
notification.error({
|
||||
content: error?.message || '配置保存失败',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
savingBotConfig.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑频道
|
||||
const editChannel = (channel: any) => {
|
||||
// TODO: 实现编辑频道功能
|
||||
console.log('编辑频道:', channel)
|
||||
}
|
||||
|
||||
// 注销频道(带确认)
|
||||
const unregisterChannel = async (channel: any) => {
|
||||
try {
|
||||
// 使用 Naïve UI 的确认对话框
|
||||
dialog.create({
|
||||
title: '确认注销频道',
|
||||
content: `确定要注销频道 "${channel.chat_name}" 吗?\n\n此操作将停止向该频道推送内容,并会向频道发送注销通知。`,
|
||||
positiveText: '确定注销',
|
||||
negativeText: '取消',
|
||||
type: 'warning',
|
||||
onPositiveClick: async () => {
|
||||
await performUnregisterChannel(channel)
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
console.log('用户取消了注销操作')
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建确认对话框失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const performUnregisterChannel = async (channel: any) => {
|
||||
try {
|
||||
await telegramApi.deleteChannel(channel.id)
|
||||
|
||||
notification.success({
|
||||
content: `频道 "${channel.chat_name}" 已成功注销`,
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
// 重新获取频道列表,更新UI
|
||||
await fetchTelegramChannels()
|
||||
|
||||
// 尝试向频道发送通知(可选)
|
||||
await sendChannelNotification(channel)
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('注销频道失败:', error)
|
||||
|
||||
// 提供更详细的错误信息
|
||||
let errorMessage = '取消注册失败'
|
||||
if (error?.message?.includes('telegram_channels') ||
|
||||
error?.message?.includes('does not exist')) {
|
||||
errorMessage = '频道表不存在,请重启服务器创建表'
|
||||
} else if (error?.message) {
|
||||
errorMessage = `注销失败: ${error.message}`
|
||||
}
|
||||
|
||||
notification.error({
|
||||
content: errorMessage,
|
||||
duration: 4000
|
||||
})
|
||||
|
||||
// 如果删除失败,仍然尝试刷新列表以确保UI同步
|
||||
try {
|
||||
await fetchTelegramChannels()
|
||||
} catch (refreshError) {
|
||||
console.warn('刷新频道列表失败:', refreshError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 向频道发送注销通知
|
||||
const sendChannelNotification = async (channel: any) => {
|
||||
try {
|
||||
const message = `📢 **频道注销通知**\n\n频道 **${channel.chat_name}** 已从机器人推送系统中移除。\n\n❌ 停止推送:此频道将不会再收到自动推送内容\n\n💡 如需继续接收推送,请联系管理员重新注册此频道。`
|
||||
|
||||
await telegramApi.testBotMessage({
|
||||
chat_id: channel.chat_id,
|
||||
text: message
|
||||
})
|
||||
|
||||
notification.success({
|
||||
content: `已向频道 "${channel.chat_name}" 发送注销通知`,
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
console.log(`已向频道 ${channel.chat_name} 发送注销通知`)
|
||||
} catch (error: any) {
|
||||
console.warn(`向频道 ${channel.chat_name} 发送通知失败:`, error)
|
||||
notification.warning({
|
||||
content: `向频道 "${channel.chat_name}" 发送通知失败,但频道已从系统中移除`,
|
||||
duration: 4000
|
||||
})
|
||||
// 不抛出错误,因为主操作(删除频道)已经成功
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新频道列表
|
||||
const refreshChannels = async () => {
|
||||
refreshingChannels.value = true
|
||||
try {
|
||||
await fetchTelegramChannels()
|
||||
notification.success({
|
||||
content: '频道列表已刷新',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
content: '刷新频道列表失败',
|
||||
duration: 2000
|
||||
})
|
||||
} finally {
|
||||
refreshingChannels.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 测试机器人连接
|
||||
const testBotConnection = async () => {
|
||||
testingConnection.value = true
|
||||
try {
|
||||
const data = await telegramApi.getBotStatus() as any
|
||||
if (data && data.service_running) {
|
||||
notification.success({
|
||||
content: `机器人连接正常!用户名:@${data.bot_username}`,
|
||||
duration: 3000
|
||||
})
|
||||
} else {
|
||||
notification.warning({
|
||||
content: '机器人服务未运行或未配置',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
notification.error({
|
||||
content: '测试连接失败:' + (error?.message || '请检查配置'),
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
testingConnection.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const notification = useNotification()
|
||||
const dialog = useDialog()
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(async () => {
|
||||
await fetchTelegramConfig()
|
||||
await fetchTelegramChannels()
|
||||
console.log('Telegram 机器人标签已加载')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Telegram 机器人标签样式 */
|
||||
</style>
|
||||
@@ -263,6 +263,32 @@ function log(...args: any[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Telegram Bot管理API
|
||||
export const useTelegramApi = () => {
|
||||
const getBotConfig = () => useApiFetch('/telegram/bot-config').then(parseApiResponse)
|
||||
const updateBotConfig = (data: any) => useApiFetch('/telegram/bot-config', { method: 'PUT', body: data }).then(parseApiResponse)
|
||||
const validateApiKey = (data: any) => useApiFetch('/telegram/validate-api-key', { method: 'POST', body: data }).then(parseApiResponse)
|
||||
const getBotStatus = () => useApiFetch('/telegram/bot-status').then(parseApiResponse)
|
||||
const reloadBotConfig = () => useApiFetch('/telegram/reload-config', { method: 'POST' }).then(parseApiResponse)
|
||||
const testBotMessage = (data: any) => useApiFetch('/telegram/test-message', { method: 'POST', body: data }).then(parseApiResponse)
|
||||
const getChannels = () => useApiFetch('/telegram/channels').then(parseApiResponse)
|
||||
const createChannel = (data: any) => useApiFetch('/telegram/channels', { method: 'POST', body: data }).then(parseApiResponse)
|
||||
const updateChannel = (id: number, data: any) => useApiFetch(`/telegram/channels/${id}`, { method: 'PUT', body: data }).then(parseApiResponse)
|
||||
const deleteChannel = (id: number) => useApiFetch(`/telegram/channels/${id}`, { method: 'DELETE' }).then(parseApiResponse)
|
||||
return {
|
||||
getBotConfig,
|
||||
updateBotConfig,
|
||||
validateApiKey,
|
||||
getBotStatus,
|
||||
reloadBotConfig,
|
||||
testBotMessage,
|
||||
getChannels,
|
||||
createChannel,
|
||||
updateChannel,
|
||||
deleteChannel
|
||||
}
|
||||
}
|
||||
|
||||
// Meilisearch管理API
|
||||
export const useMeilisearchApi = () => {
|
||||
const getStatus = () => useApiFetch('/meilisearch/status').then(parseApiResponse)
|
||||
@@ -277,18 +303,18 @@ export const useMeilisearchApi = () => {
|
||||
const updateIndexSettings = () => useApiFetch('/meilisearch/update-settings', { method: 'POST' }).then(parseApiResponse)
|
||||
const getSyncProgress = () => useApiFetch('/meilisearch/sync-progress').then(parseApiResponse)
|
||||
const debugGetAllDocuments = () => useApiFetch('/meilisearch/debug/documents').then(parseApiResponse)
|
||||
return {
|
||||
getStatus,
|
||||
return {
|
||||
getStatus,
|
||||
getUnsyncedCount,
|
||||
getUnsyncedResources,
|
||||
getSyncedResources,
|
||||
getAllResources,
|
||||
testConnection,
|
||||
syncAllResources,
|
||||
stopSync,
|
||||
syncAllResources,
|
||||
stopSync,
|
||||
clearIndex,
|
||||
updateIndexSettings,
|
||||
getSyncProgress,
|
||||
debugGetAllDocuments
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,147 +19,8 @@
|
||||
class="mb-6"
|
||||
>
|
||||
<n-tab-pane name="qq" tab="QQ机器人">
|
||||
<div class="tab-content-container">
|
||||
<div class="space-y-8">
|
||||
<!-- 步骤1:Astrobot 安装指南 -->
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">1</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装 Astrobot</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-github text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">开源地址</p>
|
||||
<a
|
||||
href="https://github.com/Astrian/astrobot"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/Astrian/astrobot
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-book text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">安装教程</p>
|
||||
<a
|
||||
href="https://github.com/Astrian/astrobot/wiki"
|
||||
target="_blank"
|
||||
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/Astrian/astrobot/wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤2:插件安装 -->
|
||||
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-green-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">2</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装插件</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-puzzle-piece text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">插件地址</p>
|
||||
<a
|
||||
href="https://github.com/ctwj/astrbot_plugin_urldb"
|
||||
target="_blank"
|
||||
class="text-green-600 dark:text-green-400 hover:underline text-sm"
|
||||
>
|
||||
https://github.com/ctwj/astrbot_plugin_urldb
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong>插件特性:</strong><br>
|
||||
• 支持@机器人搜索功能<br>
|
||||
• 可配置API域名和密钥<br>
|
||||
• 自动格式化搜索结果<br>
|
||||
• 支持超时时间配置<br><br>
|
||||
<strong>安装步骤:</strong><br>
|
||||
1. Astrbot 插件市场, 搜索 urldb 安装<br>
|
||||
2. 根据下面的配置信息配置插件
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 步骤3:配置信息 -->
|
||||
<div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-6">
|
||||
<div class="flex items-center mb-4">
|
||||
<div class="w-8 h-8 bg-purple-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">3</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">配置信息</h3>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">网站域名</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input
|
||||
:value="siteDomain"
|
||||
readonly
|
||||
class="flex-1"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="copyToClipboard(siteDomain)"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">API Token</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-input
|
||||
:value="apiToken"
|
||||
readonly
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
class="flex-1"
|
||||
/>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="copyToClipboard(apiToken)"
|
||||
type="primary"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-copy"></i>
|
||||
</template>
|
||||
复制
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong>配置说明:</strong><br>
|
||||
将上述信息配置到 Astrobot 的插件配置文件中,插件将自动连接到本系统进行资源搜索。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
<QqBotTab />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="wechat" tab="微信公众号">
|
||||
<div class="tab-content-container">
|
||||
@@ -173,235 +34,7 @@
|
||||
|
||||
<n-tab-pane name="telegram" tab="Telegram机器人">
|
||||
<div class="tab-content-container">
|
||||
<div class="space-y-8">
|
||||
<!-- 机器人基本配置 -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">1</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">机器人配置</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 机器人启用开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">启用 Telegram 机器人</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">开启后机器人将开始工作</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.bot_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- API Key 配置 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="space-y-3">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">Bot API Key</label>
|
||||
<div class="flex space-x-3">
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.bot_api_key"
|
||||
placeholder="请输入 Telegram Bot API Key"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
class="flex-1"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="validatingApiKey"
|
||||
@click="validateApiKey"
|
||||
>
|
||||
校验
|
||||
</n-button>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
从 @BotFather 获取 API Key
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 校验结果 -->
|
||||
<div v-if="apiKeyValidationResult" class="p-3 rounded-md"
|
||||
:class="apiKeyValidationResult.valid ? 'bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300' : 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300'">
|
||||
<div class="flex items-center">
|
||||
<i :class="apiKeyValidationResult.valid ? 'fas fa-check-circle' : 'fas fa-times-circle'"
|
||||
class="mr-2"></i>
|
||||
<span>{{ apiKeyValidationResult.message }}</span>
|
||||
</div>
|
||||
<div v-if="apiKeyValidationResult.valid && apiKeyValidationResult.botInfo" class="mt-2 text-xs">
|
||||
机器人:@{{ apiKeyValidationResult.botInfo.username }} ({{ apiKeyValidationResult.botInfo.first_name }})
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="savingBotConfig"
|
||||
:disabled="!hasBotConfigChanges"
|
||||
@click="saveBotConfig"
|
||||
>
|
||||
保存配置
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自动回复配置 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-8 h-8 bg-green-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">2</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">自动回复设置</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- 自动回复开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">启用自动回复</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">收到消息时自动回复帮助信息</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.auto_reply_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 回复模板 -->
|
||||
<div v-if="telegramBotConfig.auto_reply_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">回复模板</label>
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.auto_reply_template"
|
||||
type="textarea"
|
||||
placeholder="请输入自动回复内容"
|
||||
:rows="3"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 自动删除开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">自动删除回复</label>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">定时删除机器人发送的回复消息</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.auto_delete_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 删除间隔 -->
|
||||
<div v-if="telegramBotConfig.auto_delete_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">删除间隔(分钟)</label>
|
||||
<n-input-number
|
||||
v-model:value="telegramBotConfig.auto_delete_interval"
|
||||
:min="1"
|
||||
:max="1440"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 频道和群组管理 -->
|
||||
<div v-if="telegramBotConfig.bot_enabled" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center">
|
||||
<div class="w-8 h-8 bg-purple-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||
<span class="text-sm font-bold">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">频道和群组管理</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">管理推送对象的频道和群组</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="showRegisterChannelDialog = true"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-plus"></i>
|
||||
</template>
|
||||
注册频道/群组
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 频道列表 -->
|
||||
<div v-if="telegramChannels.length > 0" class="space-y-4">
|
||||
<div v-for="channel in telegramChannels" :key="channel.id"
|
||||
class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center space-x-3">
|
||||
<i :class="channel.chat_type === 'channel' ? 'fab fa-telegram-plane' : 'fas fa-users'"
|
||||
class="text-lg text-blue-600 dark:text-blue-400"></i>
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">{{ channel.chat_name }}</h4>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ channel.chat_type === 'channel' ? '频道' : '群组' }} • ID: {{ channel.chat_id }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-tag :type="channel.is_active ? 'success' : 'warning'" size="small">
|
||||
{{ channel.is_active ? '活跃' : '非活跃' }}
|
||||
</n-tag>
|
||||
<n-button size="small" @click="editChannel(channel)">
|
||||
<template #icon>
|
||||
<i class="fas fa-edit"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
<n-button size="small" type="error" @click="unregisterChannel(channel)">
|
||||
<template #icon>
|
||||
<i class="fas fa-trash"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推送配置 -->
|
||||
<div v-if="channel.push_enabled" class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">推送频率</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ channel.push_frequency }} 小时</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">内容分类</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ channel.content_categories || '全部' }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">标签</label>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ channel.content_tags || '全部' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">推送已禁用</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="text-center py-8">
|
||||
<i class="fab fa-telegram-plane text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">暂无频道或群组</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-4">点击上方按钮注册推送对象</p>
|
||||
<n-button type="primary" @click="showRegisterChannelDialog = true">
|
||||
立即注册
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TelegramBotTab />
|
||||
</div>
|
||||
</n-tab-pane>
|
||||
|
||||
@@ -416,62 +49,16 @@
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
|
||||
<!-- 注册频道对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showRegisterChannelDialog"
|
||||
preset="card"
|
||||
title="注册频道/群组"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||
将机器人添加到频道或群组,然后发送命令获取频道信息并注册为推送对象。
|
||||
</div>
|
||||
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-info-circle text-blue-600 dark:text-blue-400 mt-1"></i>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-blue-800 dark:text-blue-200 mb-2">注册步骤:</h4>
|
||||
<ol class="text-sm text-blue-700 dark:text-blue-300 space-y-1 list-decimal list-inside">
|
||||
<li>将 @{{ telegramBotConfig.bot_enabled ? '机器人用户名' : '机器人' }} 添加为频道管理员或群组成员</li>
|
||||
<li>在频道/群组中发送 <code class="bg-blue-200 dark:bg-blue-800 px-1 rounded">/register</code> 命令</li>
|
||||
<li>机器人将自动识别并注册该频道/群组</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!telegramBotConfig.bot_enabled || !telegramBotConfig.bot_api_key" class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
|
||||
<div class="flex items-start space-x-3">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-600 dark:text-yellow-400 mt-1"></i>
|
||||
<div>
|
||||
<h4 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">配置未完成</h4>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">请先启用机器人并配置有效的 API Key。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center py-4">
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="showRegisterChannelDialog = false"
|
||||
>
|
||||
我知道了
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
</AdminPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import AdminPageLayout from '~/components/AdminPageLayout.vue'
|
||||
import { useConfigChangeDetection } from '~/composables/useConfigChangeDetection'
|
||||
import TelegramBotTab from '~/components/TelegramBotTab.vue'
|
||||
import QqBotTab from '~/components/QqBotTab.vue'
|
||||
|
||||
// 设置页面布局
|
||||
definePageMeta({
|
||||
@@ -479,260 +66,10 @@ definePageMeta({
|
||||
ssr: false
|
||||
})
|
||||
|
||||
// 定义配置表单类型
|
||||
interface BotConfigForm {
|
||||
api_token: string
|
||||
}
|
||||
|
||||
// 使用配置改动检测
|
||||
const {
|
||||
setOriginalConfig,
|
||||
updateCurrentConfig,
|
||||
getChangedConfig,
|
||||
hasChanges,
|
||||
updateOriginalConfig,
|
||||
saveConfig: saveConfigWithDetection
|
||||
} = useConfigChangeDetection<BotConfigForm>({
|
||||
debug: true,
|
||||
fieldMapping: {
|
||||
api_token: 'api_token'
|
||||
}
|
||||
})
|
||||
|
||||
// Telegram 相关数据和状态
|
||||
const telegramBotConfig = ref({
|
||||
bot_enabled: false,
|
||||
bot_api_key: '',
|
||||
auto_reply_enabled: true,
|
||||
auto_reply_template: '您好!我可以帮您搜索网盘资源,请输入您要搜索的内容。',
|
||||
auto_delete_enabled: false,
|
||||
auto_delete_interval: 60,
|
||||
})
|
||||
|
||||
const telegramChannels = ref([])
|
||||
const validatingApiKey = ref(false)
|
||||
const savingBotConfig = ref(false)
|
||||
const apiKeyValidationResult = ref(null)
|
||||
const hasBotConfigChanges = ref(false)
|
||||
const showRegisterChannelDialog = ref(false)
|
||||
|
||||
// 获取 Telegram 配置
|
||||
const fetchTelegramConfig = async () => {
|
||||
try {
|
||||
const response = await $fetch('/api/telegram/bot-config', {
|
||||
method: 'GET'
|
||||
})
|
||||
if (response && response.data) {
|
||||
telegramBotConfig.value = { ...response.data }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取 Telegram 配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取频道列表
|
||||
const fetchTelegramChannels = async () => {
|
||||
try {
|
||||
const response = await $fetch('/api/telegram/channels', {
|
||||
method: 'GET'
|
||||
})
|
||||
if (response && response.data) {
|
||||
telegramChannels.value = response.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取频道列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理机器人配置变更
|
||||
const handleBotConfigChange = () => {
|
||||
hasBotConfigChanges.value = true
|
||||
}
|
||||
|
||||
// 校验 API Key
|
||||
const validateApiKey = async () => {
|
||||
if (!telegramBotConfig.value.bot_api_key) {
|
||||
notification.error({
|
||||
content: '请输入 API Key',
|
||||
duration: 2000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
validatingApiKey.value = true
|
||||
try {
|
||||
const response = await $fetch('/api/telegram/validate-api-key', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
api_key: telegramBotConfig.value.bot_api_key
|
||||
}
|
||||
})
|
||||
|
||||
if (response && response.data) {
|
||||
apiKeyValidationResult.value = response.data
|
||||
if (response.data.valid) {
|
||||
notification.success({
|
||||
content: 'API Key 校验成功',
|
||||
duration: 2000
|
||||
})
|
||||
} else {
|
||||
notification.error({
|
||||
content: response.data.error,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
apiKeyValidationResult.value = {
|
||||
valid: false,
|
||||
error: error.data?.message || '校验失败'
|
||||
}
|
||||
notification.error({
|
||||
content: 'API Key 校验失败',
|
||||
duration: 2000
|
||||
})
|
||||
} finally {
|
||||
validatingApiKey.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存机器人配置
|
||||
const saveBotConfig = async () => {
|
||||
savingBotConfig.value = true
|
||||
try {
|
||||
const configRequest = {}
|
||||
if (hasBotConfigChanges.value) {
|
||||
configRequest.bot_enabled = telegramBotConfig.value.bot_enabled
|
||||
configRequest.bot_api_key = telegramBotConfig.value.bot_api_key
|
||||
configRequest.auto_reply_enabled = telegramBotConfig.value.auto_reply_enabled
|
||||
configRequest.auto_reply_template = telegramBotConfig.value.auto_reply_template
|
||||
configRequest.auto_delete_enabled = telegramBotConfig.value.auto_delete_enabled
|
||||
configRequest.auto_delete_interval = telegramBotConfig.value.auto_delete_interval
|
||||
}
|
||||
|
||||
const response = await $fetch('/api/telegram/bot-config', {
|
||||
method: 'PUT',
|
||||
body: configRequest
|
||||
})
|
||||
|
||||
if (response && response.success) {
|
||||
notification.success({
|
||||
content: '配置保存成功',
|
||||
duration: 2000
|
||||
})
|
||||
hasBotConfigChanges.value = false
|
||||
// 重新获取配置以确保同步
|
||||
await fetchTelegramConfig()
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
content: error.data?.message || '配置保存失败',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
savingBotConfig.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑频道
|
||||
const editChannel = (channel) => {
|
||||
// TODO: 实现编辑频道功能
|
||||
console.log('编辑频道:', channel)
|
||||
}
|
||||
|
||||
// 注销频道
|
||||
const unregisterChannel = async (channel) => {
|
||||
try {
|
||||
await notification.warning({
|
||||
content: `确定取消注册 "${channel.chat_name}" 吗?`,
|
||||
duration: 10000,
|
||||
action: () => performUnregisterChannel(channel)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('注销频道操作失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const performUnregisterChannel = async (channel) => {
|
||||
try {
|
||||
const response = await $fetch(`/api/telegram/channels/${channel.id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
if (response && response.success) {
|
||||
notification.success({
|
||||
content: '频道已取消注册',
|
||||
duration: 2000
|
||||
})
|
||||
// 重新获取频道列表
|
||||
await fetchTelegramChannels()
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
content: error.data?.message || '取消注册失败',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const notification = useNotification()
|
||||
const activeTab = ref('qq')
|
||||
|
||||
// 获取网站域名和API Token
|
||||
const siteDomain = computed(() => {
|
||||
if (process.client) {
|
||||
return window.location.origin
|
||||
}
|
||||
return 'https://yourdomain.com'
|
||||
})
|
||||
|
||||
const apiToken = ref('')
|
||||
|
||||
// 获取API Token
|
||||
const fetchApiToken = async () => {
|
||||
try {
|
||||
const { useSystemConfigApi } = await import('~/composables/useApi')
|
||||
const systemConfigApi = useSystemConfigApi()
|
||||
const response = await systemConfigApi.getSystemConfig()
|
||||
|
||||
if (response) {
|
||||
const configData = {
|
||||
api_token: (response as any).api_token || ''
|
||||
}
|
||||
|
||||
apiToken.value = configData.api_token || '未配置API Token'
|
||||
setOriginalConfig(configData)
|
||||
} else {
|
||||
apiToken.value = '未配置API Token'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取API Token失败:', error)
|
||||
apiToken.value = '获取失败'
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
const copyToClipboard = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
notification.success({
|
||||
content: '已复制到剪贴板',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error)
|
||||
notification.error({
|
||||
content: '复制失败',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(async () => {
|
||||
fetchApiToken()
|
||||
await fetchTelegramConfig()
|
||||
await fetchTelegramChannels()
|
||||
console.log('机器人管理页面已加载')
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user