mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: tg
This commit is contained in:
@@ -17,6 +17,8 @@ func TelegramChannelToResponse(channel entity.TelegramChannel) dto.TelegramChann
|
||||
ChatType: channel.ChatType,
|
||||
PushEnabled: channel.PushEnabled,
|
||||
PushFrequency: channel.PushFrequency,
|
||||
PushStartTime: channel.PushStartTime,
|
||||
PushEndTime: channel.PushEndTime,
|
||||
ContentCategories: channel.ContentCategories,
|
||||
ContentTags: channel.ContentTags,
|
||||
IsActive: channel.IsActive,
|
||||
@@ -43,6 +45,8 @@ func RequestToTelegramChannel(req dto.TelegramChannelRequest, registeredBy strin
|
||||
ChatType: req.ChatType,
|
||||
PushEnabled: req.PushEnabled,
|
||||
PushFrequency: req.PushFrequency,
|
||||
PushStartTime: req.PushStartTime,
|
||||
PushEndTime: req.PushEndTime,
|
||||
ContentCategories: req.ContentCategories,
|
||||
ContentTags: req.ContentTags,
|
||||
IsActive: req.IsActive,
|
||||
|
||||
@@ -9,6 +9,8 @@ type TelegramChannelRequest struct {
|
||||
ChatType string `json:"chat_type" binding:"required"` // channel 或 group
|
||||
PushEnabled bool `json:"push_enabled"`
|
||||
PushFrequency int `json:"push_frequency"`
|
||||
PushStartTime string `json:"push_start_time"`
|
||||
PushEndTime string `json:"push_end_time"`
|
||||
ContentCategories string `json:"content_categories"`
|
||||
ContentTags string `json:"content_tags"`
|
||||
IsActive bool `json:"is_active"`
|
||||
@@ -22,6 +24,8 @@ type TelegramChannelResponse struct {
|
||||
ChatType string `json:"chat_type"`
|
||||
PushEnabled bool `json:"push_enabled"`
|
||||
PushFrequency int `json:"push_frequency"`
|
||||
PushStartTime string `json:"push_start_time"`
|
||||
PushEndTime string `json:"push_end_time"`
|
||||
ContentCategories string `json:"content_categories"`
|
||||
ContentTags string `json:"content_tags"`
|
||||
IsActive bool `json:"is_active"`
|
||||
|
||||
@@ -18,6 +18,8 @@ type TelegramChannel struct {
|
||||
// 推送配置
|
||||
PushEnabled bool `json:"push_enabled" gorm:"default:true;comment:是否启用推送"`
|
||||
PushFrequency int `json:"push_frequency" gorm:"default:24;comment:推送频率(小时)"`
|
||||
PushStartTime string `json:"push_start_time" gorm:"size:10;comment:推送开始时间,格式HH:mm"`
|
||||
PushEndTime string `json:"push_end_time" gorm:"size:10;comment:推送结束时间,格式HH:mm"`
|
||||
ContentCategories string `json:"content_categories" gorm:"type:text;comment:推送的内容分类,用逗号分隔"`
|
||||
ContentTags string `json:"content_tags" gorm:"type:text;comment:推送的标签,用逗号分隔"`
|
||||
|
||||
|
||||
@@ -88,7 +88,29 @@ func (r *TelegramChannelRepositoryImpl) UpdateLastPushAt(id uint, lastPushAt tim
|
||||
func (r *TelegramChannelRepositoryImpl) FindDueForPush() ([]entity.TelegramChannel, error) {
|
||||
var channels []entity.TelegramChannel
|
||||
// 查找活跃、启用推送的频道,且距离上次推送已超过推送频率小时的记录
|
||||
err := r.db.Where("is_active = ? AND push_enabled = ? AND (last_push_at IS NULL OR last_push_at < DATE_SUB(NOW(), INTERVAL push_frequency HOUR))",
|
||||
true, true).Find(&channels).Error
|
||||
return channels, err
|
||||
|
||||
// 先获取所有活跃且启用推送的频道
|
||||
err := r.db.Where("is_active = ? AND push_enabled = ?", true, true).Find(&channels).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 在内存中过滤出需要推送的频道(更可靠的跨数据库方案)
|
||||
var dueChannels []entity.TelegramChannel
|
||||
now := time.Now()
|
||||
|
||||
for _, channel := range channels {
|
||||
// 如果从未推送过,或者距离上次推送已超过推送频率小时
|
||||
if channel.LastPushAt == nil {
|
||||
dueChannels = append(dueChannels, channel)
|
||||
} else {
|
||||
// 计算下次推送时间:上次推送时间 + 推送频率小时
|
||||
nextPushTime := channel.LastPushAt.Add(time.Duration(channel.PushFrequency) * time.Hour)
|
||||
if now.After(nextPushTime) {
|
||||
dueChannels = append(dueChannels, channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dueChannels, nil
|
||||
}
|
||||
|
||||
@@ -74,12 +74,11 @@ func (h *TelegramHandler) UpdateBotConfig(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 这里应该启动或停止 Telegram bot 服务
|
||||
// if req.BotEnabled != nil && *req.BotEnabled {
|
||||
// go h.telegramBotService.Start()
|
||||
// } else {
|
||||
// go h.telegramBotService.Stop()
|
||||
// }
|
||||
// 重新加载机器人服务配置
|
||||
if err := h.telegramBotService.ReloadConfig(); err != nil {
|
||||
ErrorResponse(c, "重新加载机器人配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
SuccessResponse(c, map[string]interface{}{
|
||||
@@ -182,6 +181,8 @@ func (h *TelegramHandler) UpdateChannel(c *gin.Context) {
|
||||
channel.ChatType = req.ChatType
|
||||
channel.PushEnabled = req.PushEnabled
|
||||
channel.PushFrequency = req.PushFrequency
|
||||
channel.PushStartTime = req.PushStartTime
|
||||
channel.PushEndTime = req.PushEndTime
|
||||
channel.ContentCategories = req.ContentCategories
|
||||
channel.ContentTags = req.ContentTags
|
||||
channel.IsActive = req.IsActive
|
||||
@@ -255,16 +256,48 @@ func (h *TelegramHandler) HandleWebhook(c *gin.Context) {
|
||||
|
||||
// GetBotStatus 获取机器人状态
|
||||
func (h *TelegramHandler) GetBotStatus(c *gin.Context) {
|
||||
// 这里可以返回机器人运行状态、最后活动时间等信息
|
||||
// 暂时返回基本状态信息
|
||||
// 获取机器人运行时状态
|
||||
runtimeStatus := h.telegramBotService.GetRuntimeStatus()
|
||||
|
||||
botUsername := h.telegramBotService.GetBotUsername()
|
||||
// 获取配置状态
|
||||
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||||
if err != nil {
|
||||
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析配置状态
|
||||
configStatus := map[string]interface{}{
|
||||
"enabled": false,
|
||||
"auto_reply_enabled": false,
|
||||
"api_key_configured": false,
|
||||
}
|
||||
|
||||
for _, config := range configs {
|
||||
switch config.Key {
|
||||
case "telegram_bot_enabled":
|
||||
configStatus["enabled"] = config.Value == "true"
|
||||
case "telegram_auto_reply_enabled":
|
||||
configStatus["auto_reply_enabled"] = config.Value == "true"
|
||||
case "telegram_bot_api_key":
|
||||
configStatus["api_key_configured"] = config.Value != ""
|
||||
}
|
||||
}
|
||||
|
||||
// 合并状态信息
|
||||
status := map[string]interface{}{
|
||||
"bot_username": botUsername,
|
||||
"service_running": botUsername != "",
|
||||
"webhook_mode": false, // 当前使用长轮询模式
|
||||
"polling_mode": true,
|
||||
"config": configStatus,
|
||||
"runtime": runtimeStatus,
|
||||
"overall_status": runtimeStatus["is_running"].(bool),
|
||||
"status_text": func() string {
|
||||
if runtimeStatus["is_running"].(bool) {
|
||||
return "运行中"
|
||||
} else if configStatus["enabled"].(bool) {
|
||||
return "已启用但未运行"
|
||||
} else {
|
||||
return "已停止"
|
||||
}
|
||||
}(),
|
||||
}
|
||||
|
||||
SuccessResponse(c, status)
|
||||
@@ -306,6 +339,35 @@ func (h *TelegramHandler) ReloadBotConfig(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// DebugBotConnection 调试机器人连接
|
||||
func (h *TelegramHandler) DebugBotConnection(c *gin.Context) {
|
||||
// 获取机器人状态信息用于调试
|
||||
botUsername := h.telegramBotService.GetBotUsername()
|
||||
|
||||
debugInfo := map[string]interface{}{
|
||||
"bot_username": botUsername,
|
||||
"is_running": botUsername != "",
|
||||
"timestamp": "2024-01-01T12:00:00Z", // 当前时间
|
||||
"debugging_enabled": true,
|
||||
"expected_logs": []string{
|
||||
"[TELEGRAM:SERVICE] Telegram Bot (@username) 已启动",
|
||||
"[TELEGRAM:MESSAGE] 开始监听 Telegram 消息更新...",
|
||||
"[TELEGRAM:MESSAGE] 消息监听循环已启动,等待消息...",
|
||||
"[TELEGRAM:MESSAGE] 收到消息: ChatID=xxx, Text='/register'",
|
||||
"[TELEGRAM:MESSAGE] 处理 /register 命令 from ChatID=xxx",
|
||||
},
|
||||
"troubleshooting_steps": []string{
|
||||
"1. 检查服务器日志中是否有 TELEGRAM 相关日志",
|
||||
"2. 确认机器人已添加到群组并设为管理员",
|
||||
"3. 验证 API Key 是否正确",
|
||||
"4. 检查自动回复是否已启用",
|
||||
"5. 重启服务器重新加载配置",
|
||||
},
|
||||
}
|
||||
|
||||
SuccessResponse(c, debugInfo)
|
||||
}
|
||||
|
||||
// GetTelegramLogs 获取Telegram相关的日志
|
||||
func (h *TelegramHandler) GetTelegramLogs(c *gin.Context) {
|
||||
// 解析查询参数
|
||||
|
||||
3
main.go
3
main.go
@@ -348,14 +348,15 @@ func main() {
|
||||
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/debug-connection", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.DebugBotConnection)
|
||||
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)
|
||||
api.DELETE("/telegram/channels/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.DeleteChannel)
|
||||
api.POST("/telegram/webhook", telegramHandler.HandleWebhook)
|
||||
api.GET("/telegram/logs", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetTelegramLogs)
|
||||
api.GET("/telegram/logs/stats", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.GetTelegramLogStats)
|
||||
api.POST("/telegram/logs/clear", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ClearTelegramLogs)
|
||||
api.POST("/telegram/webhook", telegramHandler.HandleWebhook)
|
||||
}
|
||||
|
||||
// 静态文件服务
|
||||
|
||||
@@ -89,7 +89,7 @@ func (m *MeilisearchService) HealthCheck() error {
|
||||
// 使用官方SDK的健康检查
|
||||
_, err := m.client.Health()
|
||||
if err != nil {
|
||||
utils.Error("Meilisearch健康检查失败: %v", err)
|
||||
// utils.Error("Meilisearch健康检查失败: %v", err)
|
||||
return fmt.Errorf("Meilisearch健康检查失败: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@ package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
"golang.org/x/net/proxy"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"github.com/robfig/cron/v3"
|
||||
@@ -16,6 +19,9 @@ import (
|
||||
type TelegramBotService interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
IsRunning() bool
|
||||
ReloadConfig() error
|
||||
GetRuntimeStatus() map[string]interface{}
|
||||
ValidateApiKey(apiKey string) (bool, map[string]interface{}, error)
|
||||
GetBotUsername() string
|
||||
SendMessage(chatID int64, text string) error
|
||||
@@ -42,6 +48,12 @@ type TelegramBotConfig struct {
|
||||
AutoReplyTemplate string
|
||||
AutoDeleteEnabled bool
|
||||
AutoDeleteInterval int // 分钟
|
||||
ProxyEnabled bool
|
||||
ProxyType string // http, https, socks5
|
||||
ProxyHost string
|
||||
ProxyPort int
|
||||
ProxyUsername string
|
||||
ProxyPassword string
|
||||
}
|
||||
|
||||
func NewTelegramBotService(
|
||||
@@ -75,6 +87,13 @@ func (s *TelegramBotServiceImpl) loadConfig() error {
|
||||
s.config.AutoReplyTemplate = "您好!我可以帮您搜索网盘资源,请输入您要搜索的内容。"
|
||||
s.config.AutoDeleteEnabled = false
|
||||
s.config.AutoDeleteInterval = 60
|
||||
// 初始化代理默认值
|
||||
s.config.ProxyEnabled = false
|
||||
s.config.ProxyType = "http"
|
||||
s.config.ProxyHost = ""
|
||||
s.config.ProxyPort = 8080
|
||||
s.config.ProxyUsername = ""
|
||||
s.config.ProxyPassword = ""
|
||||
|
||||
for _, config := range configs {
|
||||
switch config.Key {
|
||||
@@ -100,6 +119,26 @@ func (s *TelegramBotServiceImpl) loadConfig() error {
|
||||
fmt.Sscanf(config.Value, "%d", &s.config.AutoDeleteInterval)
|
||||
}
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (AutoDeleteInterval: %d)", config.Key, config.Value, s.config.AutoDeleteInterval)
|
||||
case "telegram_proxy_enabled":
|
||||
s.config.ProxyEnabled = config.Value == "true"
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyEnabled: %v)", config.Key, config.Value, s.config.ProxyEnabled)
|
||||
case "telegram_proxy_type":
|
||||
s.config.ProxyType = config.Value
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyType: %s)", config.Key, config.Value, s.config.ProxyType)
|
||||
case "telegram_proxy_host":
|
||||
s.config.ProxyHost = config.Value
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
||||
case "telegram_proxy_port":
|
||||
if config.Value != "" {
|
||||
fmt.Sscanf(config.Value, "%d", &s.config.ProxyPort)
|
||||
}
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s (ProxyPort: %d)", config.Key, config.Value, s.config.ProxyPort)
|
||||
case "telegram_proxy_username":
|
||||
s.config.ProxyUsername = config.Value
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
||||
case "telegram_proxy_password":
|
||||
s.config.ProxyPassword = config.Value
|
||||
utils.Info("[TELEGRAM:CONFIG] 加载配置 %s = %s", config.Key, "[HIDDEN]")
|
||||
default:
|
||||
utils.Debug("未知配置: %s = %s", config.Key, config.Value)
|
||||
}
|
||||
@@ -128,11 +167,73 @@ func (s *TelegramBotServiceImpl) Start() error {
|
||||
}
|
||||
|
||||
// 创建 Bot 实例
|
||||
bot, err := tgbotapi.NewBotAPI(s.config.ApiKey)
|
||||
var bot *tgbotapi.BotAPI
|
||||
|
||||
if s.config.ProxyEnabled && s.config.ProxyHost != "" {
|
||||
// 配置代理
|
||||
utils.Info("[TELEGRAM:PROXY] 配置代理: %s://%s:%d", s.config.ProxyType, s.config.ProxyHost, s.config.ProxyPort)
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
if s.config.ProxyType == "socks5" {
|
||||
// SOCKS5 代理配置
|
||||
var auth *proxy.Auth
|
||||
if s.config.ProxyUsername != "" {
|
||||
auth = &proxy.Auth{
|
||||
User: s.config.ProxyUsername,
|
||||
Password: s.config.ProxyPassword,
|
||||
}
|
||||
}
|
||||
|
||||
dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", s.config.ProxyHost, s.config.ProxyPort), auth, proxy.Direct)
|
||||
if proxyErr != nil {
|
||||
return fmt.Errorf("创建 SOCKS5 代理失败: %v", proxyErr)
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: dialer.Dial,
|
||||
},
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
} else {
|
||||
// HTTP/HTTPS 代理配置
|
||||
proxyURL := &url.URL{
|
||||
Scheme: s.config.ProxyType,
|
||||
Host: fmt.Sprintf("%s:%d", s.config.ProxyHost, s.config.ProxyPort),
|
||||
User: nil,
|
||||
}
|
||||
|
||||
if s.config.ProxyUsername != "" {
|
||||
proxyURL.User = url.UserPassword(s.config.ProxyUsername, s.config.ProxyPassword)
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
},
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
botInstance, botErr := tgbotapi.NewBotAPIWithClient(s.config.ApiKey, tgbotapi.APIEndpoint, httpClient)
|
||||
if botErr != nil {
|
||||
return fmt.Errorf("创建 Telegram Bot (代理模式) 失败: %v", botErr)
|
||||
}
|
||||
bot = botInstance
|
||||
|
||||
utils.Info("[TELEGRAM:PROXY] Telegram Bot 已配置代理连接")
|
||||
} else {
|
||||
// 直接连接(无代理)
|
||||
var err error
|
||||
bot, err = tgbotapi.NewBotAPI(s.config.ApiKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 Telegram Bot 失败: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("[TELEGRAM:PROXY] Telegram Bot 使用直连模式")
|
||||
}
|
||||
|
||||
s.bot = bot
|
||||
s.isRunning = true
|
||||
|
||||
@@ -168,13 +269,104 @@ func (s *TelegramBotServiceImpl) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning 检查机器人服务是否正在运行
|
||||
func (s *TelegramBotServiceImpl) IsRunning() bool {
|
||||
return s.isRunning && s.bot != nil
|
||||
}
|
||||
|
||||
// ReloadConfig 重新加载机器人配置
|
||||
func (s *TelegramBotServiceImpl) ReloadConfig() error {
|
||||
utils.Info("[TELEGRAM:SERVICE] 开始重新加载配置...")
|
||||
|
||||
// 重新加载配置
|
||||
if err := s.loadConfig(); err != nil {
|
||||
utils.Error("[TELEGRAM:SERVICE] 重新加载配置失败: %v", err)
|
||||
return fmt.Errorf("重新加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
utils.Info("[TELEGRAM:SERVICE] 配置重新加载完成: Enabled=%v, AutoReplyEnabled=%v",
|
||||
s.config.Enabled, s.config.AutoReplyEnabled)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRuntimeStatus 获取机器人运行时状态
|
||||
func (s *TelegramBotServiceImpl) GetRuntimeStatus() map[string]interface{} {
|
||||
status := map[string]interface{}{
|
||||
"is_running": s.IsRunning(),
|
||||
"bot_initialized": s.bot != nil,
|
||||
"config_loaded": s.config != nil,
|
||||
"cron_running": s.cronScheduler != nil,
|
||||
"username": "",
|
||||
"uptime": 0,
|
||||
}
|
||||
|
||||
if s.bot != nil {
|
||||
status["username"] = s.GetBotUsername()
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// ValidateApiKey 验证 API Key
|
||||
func (s *TelegramBotServiceImpl) ValidateApiKey(apiKey string) (bool, map[string]interface{}, error) {
|
||||
if apiKey == "" {
|
||||
return false, nil, fmt.Errorf("API Key 不能为空")
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(apiKey)
|
||||
var bot *tgbotapi.BotAPI
|
||||
var err error
|
||||
|
||||
// 如果启用了代理,使用代理验证
|
||||
if s.config.ProxyEnabled && s.config.ProxyHost != "" {
|
||||
var httpClient *http.Client
|
||||
|
||||
if s.config.ProxyType == "socks5" {
|
||||
var auth *proxy.Auth
|
||||
if s.config.ProxyUsername != "" {
|
||||
auth = &proxy.Auth{
|
||||
User: s.config.ProxyUsername,
|
||||
Password: s.config.ProxyPassword,
|
||||
}
|
||||
}
|
||||
|
||||
dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%d", s.config.ProxyHost, s.config.ProxyPort), auth, proxy.Direct)
|
||||
if proxyErr != nil {
|
||||
// 如果代理失败,回退到直连
|
||||
utils.Warn("[TELEGRAM:PROXY] SOCKS5 代理验证失败,回退到直连: %v", proxyErr)
|
||||
bot, err = tgbotapi.NewBotAPI(apiKey)
|
||||
} else {
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: dialer.Dial,
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
bot, err = tgbotapi.NewBotAPIWithClient(apiKey, tgbotapi.APIEndpoint, httpClient)
|
||||
}
|
||||
} else {
|
||||
proxyURL := &url.URL{
|
||||
Scheme: s.config.ProxyType,
|
||||
Host: fmt.Sprintf("%s:%d", s.config.ProxyHost, s.config.ProxyPort),
|
||||
User: nil,
|
||||
}
|
||||
|
||||
if s.config.ProxyUsername != "" {
|
||||
proxyURL.User = url.UserPassword(s.config.ProxyUsername, s.config.ProxyPassword)
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyURL(proxyURL),
|
||||
},
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
bot, err = tgbotapi.NewBotAPIWithClient(apiKey, tgbotapi.APIEndpoint, httpClient)
|
||||
}
|
||||
} else {
|
||||
// 直连验证
|
||||
bot, err = tgbotapi.NewBotAPI(apiKey)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("无效的 API Key: %v", err)
|
||||
}
|
||||
@@ -323,17 +515,50 @@ func (s *TelegramBotServiceImpl) handleSearchRequest(message *tgbotapi.Message)
|
||||
return
|
||||
}
|
||||
|
||||
// 这里使用简单的资源搜索,实际项目中需要完善搜索逻辑
|
||||
// resources, err := s.resourceRepo.Search(query, nil, 0, 10)
|
||||
// 暂时模拟一个搜索结果
|
||||
results := []string{
|
||||
fmt.Sprintf("🔍 搜索关键词: %s", query),
|
||||
"暂无相关资源,请尝试其他关键词。",
|
||||
"",
|
||||
fmt.Sprintf("💡 提示:如需精确搜索,请使用更具体的关键词。"),
|
||||
utils.Info("[TELEGRAM:SEARCH] 处理搜索请求: %s", query)
|
||||
|
||||
// 使用资源仓库进行搜索
|
||||
resources, total, err := s.resourceRepo.Search(query, nil, 1, 5) // 限制为5个结果
|
||||
if err != nil {
|
||||
utils.Error("[TELEGRAM:SEARCH] 搜索失败: %v", err)
|
||||
s.sendReply(message, "搜索服务暂时不可用,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
response := fmt.Sprintf("🔍 *搜索结果*\n\n关键词: `%s`\n\n❌ 未找到相关资源\n\n💡 建议:\n• 尝试使用更通用的关键词\n• 检查拼写是否正确\n• 减少关键词数量", query)
|
||||
s.sendReply(message, response)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建搜索结果消息
|
||||
resultText := fmt.Sprintf("🔍 *搜索结果*\n\n关键词: `%s`\n总共找到: %d 个资源\n\n", query, total)
|
||||
|
||||
// 显示前5个结果
|
||||
for i, resource := range resources {
|
||||
if i >= 5 {
|
||||
break
|
||||
}
|
||||
|
||||
title := resource.Title
|
||||
if len(title) > 50 {
|
||||
title = title[:47] + "..."
|
||||
}
|
||||
|
||||
description := resource.Description
|
||||
if len(description) > 100 {
|
||||
description = description[:97] + "..."
|
||||
}
|
||||
|
||||
resultText += fmt.Sprintf("%d. *%s*\n%s\n\n", i+1, title, description)
|
||||
}
|
||||
|
||||
// 如果有更多结果,添加提示
|
||||
if total > 5 {
|
||||
resultText += fmt.Sprintf("... 还有 %d 个结果\n\n", total-5)
|
||||
resultText += "💡 如需查看更多结果,请访问网站搜索"
|
||||
}
|
||||
|
||||
resultText := strings.Join(results, "\n")
|
||||
s.sendReply(message, resultText)
|
||||
}
|
||||
|
||||
|
||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -51,6 +51,7 @@ declare module 'vue' {
|
||||
NTag: typeof import('naive-ui')['NTag']
|
||||
NText: typeof import('naive-ui')['NText']
|
||||
NThing: typeof import('naive-ui')['NThing']
|
||||
NTimePicker: typeof import('naive-ui')['NTimePicker']
|
||||
NUpload: typeof import('naive-ui')['NUpload']
|
||||
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
|
||||
NVirtualList: typeof import('naive-ui')['NVirtualList']
|
||||
|
||||
@@ -13,14 +13,36 @@
|
||||
<div class="space-y-4">
|
||||
<!-- 机器人启用开关 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<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>
|
||||
<div class="flex items-center space-x-3">
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.bot_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
<!-- 运行状态指示器 -->
|
||||
<div v-if="botStatus" class="flex items-center space-x-2">
|
||||
<n-tag
|
||||
:type="botStatus.overall_status ? 'success' : (telegramBotConfig.bot_enabled ? 'warning' : 'default')"
|
||||
size="small"
|
||||
class="min-w-16 text-center"
|
||||
>
|
||||
{{ botStatus.status_text }}
|
||||
</n-tag>
|
||||
<n-button
|
||||
size="small"
|
||||
@click="refreshBotStatus"
|
||||
:loading="statusRefreshing"
|
||||
circle
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</template>
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Key 配置 -->
|
||||
@@ -83,12 +105,13 @@
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.auto_reply_enabled"
|
||||
:disabled="telegramBotConfig.bot_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 回复模板 -->
|
||||
<div v-if="telegramBotConfig.auto_reply_enabled">
|
||||
<div v-if="telegramBotConfig.auto_reply_enabled || telegramBotConfig.bot_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"
|
||||
@@ -124,6 +147,107 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 代理配置 -->
|
||||
<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-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="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">通过代理服务器连接 Telegram API</p>
|
||||
</div>
|
||||
<n-switch
|
||||
v-model:value="telegramBotConfig.proxy_enabled"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 代理设置 -->
|
||||
<div v-if="telegramBotConfig.proxy_enabled" class="space-y-4">
|
||||
<!-- 代理类型 -->
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">代理类型</label>
|
||||
<n-select
|
||||
v-model:value="telegramBotConfig.proxy_type"
|
||||
:options="[
|
||||
{ label: 'HTTP', value: 'http' },
|
||||
{ label: 'HTTPS', value: 'https' },
|
||||
{ label: 'SOCKS5', value: 'socks5' }
|
||||
]"
|
||||
placeholder="选择代理类型"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 代理主机和端口 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">代理主机</label>
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.proxy_host"
|
||||
placeholder="例如: 127.0.0.1 或 proxy.example.com"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">代理端口</label>
|
||||
<n-input-number
|
||||
v-model:value="telegramBotConfig.proxy_port"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="例如: 8080"
|
||||
@update:value="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 代理认证 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">用户名 (可选)</label>
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.proxy_username"
|
||||
placeholder="代理用户名"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">密码 (可选)</label>
|
||||
<n-input
|
||||
v-model:value="telegramBotConfig.proxy_password"
|
||||
type="password"
|
||||
placeholder="代理密码"
|
||||
@input="handleBotConfigChange"
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
<ul class="text-sm text-blue-700 dark:text-blue-300 space-y-1">
|
||||
<li>• HTTP/HTTPS 代理支持基本的认证</li>
|
||||
<li>• SOCKS5 代理支持用户名/密码认证</li>
|
||||
<li>• 配置完成后需要重启机器人服务</li>
|
||||
<li>• 确保代理服务器稳定可靠</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</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 justify-between mb-6">
|
||||
@@ -147,15 +271,6 @@
|
||||
</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"
|
||||
@@ -238,7 +353,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end p-2">
|
||||
<div class="flex justify-end p-2 gap-2">
|
||||
<n-button
|
||||
@click="testBotConnection"
|
||||
:loading="testingConnection"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-robot"></i>
|
||||
</template>
|
||||
测试连接
|
||||
</n-button>
|
||||
<n-button
|
||||
@click="debugBotConnection"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-bug"></i>
|
||||
</template>
|
||||
调试
|
||||
</n-button>
|
||||
<n-button @click="showLogDrawer = true">
|
||||
<template #icon>
|
||||
<i class="fas fa-list-alt"></i>
|
||||
@@ -261,9 +393,9 @@
|
||||
v-model:show="showRegisterChannelDialog"
|
||||
preset="card"
|
||||
title="注册频道/群组"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
:style="{ width: '800px' }"
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||
@@ -305,6 +437,148 @@
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 编辑频道对话框 -->
|
||||
<n-modal
|
||||
v-model:show="showEditChannelDialog"
|
||||
preset="card"
|
||||
:title="`编辑频道 - ${editingChannel?.chat_name || ''}`"
|
||||
size="large"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
>
|
||||
<div v-if="editingChannel" class="space-y-6">
|
||||
<!-- 频道基本信息 -->
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4">
|
||||
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">频道信息</h4>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-600 dark:text-gray-400">频道名称:</span>
|
||||
<span class="ml-2 text-gray-900 dark:text-white">{{ editingChannel.chat_name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600 dark:text-gray-400">频道ID:</span>
|
||||
<span class="ml-2 text-gray-900 dark:text-white">{{ editingChannel.chat_id }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600 dark:text-gray-400">类型:</span>
|
||||
<span class="ml-2 text-gray-900 dark:text-white">{{ editingChannel.chat_type === 'channel' ? '频道' : '群组' }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600 dark:text-gray-400">状态:</span>
|
||||
<n-tag :type="editingChannel.is_active ? 'success' : 'warning'" size="small" class="ml-2">
|
||||
{{ editingChannel.is_active ? '活跃' : '非活跃' }}
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推送设置 -->
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-base font-medium text-gray-900 dark:text-white">推送设置</h4>
|
||||
|
||||
<!-- 启用推送 -->
|
||||
<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="editingChannel.push_enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 推送频率 -->
|
||||
<div v-if="editingChannel.push_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">推送频率</label>
|
||||
<n-select
|
||||
v-model:value="editingChannel.push_frequency"
|
||||
:options="[
|
||||
{ label: '每5分钟', value: 0.0833 },
|
||||
{ label: '每10分钟', value: 0.1667 },
|
||||
{ label: '每15分钟', value: 0.25 },
|
||||
{ label: '每30分钟', value: 0.5 },
|
||||
{ label: '每小时', value: 1 },
|
||||
{ label: '每2小时', value: 2 },
|
||||
{ label: '每3小时', value: 3 },
|
||||
{ label: '每6小时', value: 6 },
|
||||
{ label: '每12小时', value: 12 },
|
||||
{ label: '每天', value: 24 },
|
||||
{ label: '每2天', value: 48 },
|
||||
{ label: '每周', value: 168 }
|
||||
]"
|
||||
placeholder="选择推送频率"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 推送时间段 -->
|
||||
<div v-if="editingChannel.push_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">推送时间段</label>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400">开始时间</label>
|
||||
<n-time-picker
|
||||
v-model:value="editingChannel.push_start_time"
|
||||
format="HH:mm"
|
||||
placeholder="选择开始时间"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs text-gray-600 dark:text-gray-400">结束时间</label>
|
||||
<n-time-picker
|
||||
v-model:value="editingChannel.push_end_time"
|
||||
format="HH:mm"
|
||||
placeholder="选择结束时间"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
留空表示全天推送,不设置时间限制
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 内容分类 -->
|
||||
<div v-if="editingChannel.push_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">内容分类</label>
|
||||
<n-input
|
||||
v-model:value="editingChannel.content_categories"
|
||||
placeholder="输入内容分类,多个用逗号分隔 (如: 电影,电视剧,动漫)"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">留空表示推送所有分类的内容</p>
|
||||
</div>
|
||||
|
||||
<!-- 标签过滤 -->
|
||||
<div v-if="editingChannel.push_enabled">
|
||||
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">标签过滤</label>
|
||||
<n-input
|
||||
v-model:value="editingChannel.content_tags"
|
||||
placeholder="输入标签关键词,多个用逗号分隔 (如: 高清,1080p,蓝光)"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
/>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">留空表示推送所有标签的内容</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex justify-end space-x-3 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<n-button @click="showEditChannelDialog = false">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="savingChannel"
|
||||
@click="saveChannelSettings"
|
||||
>
|
||||
保存设置
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- Telegram 日志抽屉 -->
|
||||
<n-drawer
|
||||
v-model:show="showLogDrawer"
|
||||
@@ -313,9 +587,9 @@
|
||||
placement="right"
|
||||
>
|
||||
<n-drawer-content>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-4 h-full overflow-y-auto flex flex-col">
|
||||
<!-- 日志控制栏 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-0 flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">时间范围:</span>
|
||||
<n-select
|
||||
@@ -342,7 +616,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||
<div class="h-1 flex-1 space-y-2 overflow-y-auto">
|
||||
<div v-if="telegramLogs.length === 0 && !loadingLogs" class="text-center py-8">
|
||||
<i class="fas fa-list-alt text-4xl text-gray-400 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">暂无日志</h3>
|
||||
@@ -376,7 +650,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 日志统计 -->
|
||||
<div class="flex justify-between items-center text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="flex-0 flex justify-between items-center text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>显示 {{ telegramLogs.length }} 条日志</span>
|
||||
<span v-if="telegramLogs.length > 0">
|
||||
加载于 {{ formatTimestamp(new Date().toISOString()) }}
|
||||
@@ -400,6 +674,12 @@ const telegramBotConfig = ref<any>({
|
||||
auto_reply_template: '您好!我可以帮您搜索网盘资源,请输入您要搜索的内容。',
|
||||
auto_delete_enabled: false,
|
||||
auto_delete_interval: 60,
|
||||
proxy_enabled: false,
|
||||
proxy_type: 'http',
|
||||
proxy_host: '',
|
||||
proxy_port: 8080,
|
||||
proxy_username: '',
|
||||
proxy_password: '',
|
||||
})
|
||||
|
||||
const telegramChannels = ref<any[]>([])
|
||||
@@ -408,12 +688,19 @@ const savingBotConfig = ref(false)
|
||||
const apiKeyValidationResult = ref<any>(null)
|
||||
const hasBotConfigChanges = ref(false)
|
||||
const showRegisterChannelDialog = ref(false)
|
||||
const showEditChannelDialog = ref(false)
|
||||
const showLogDrawer = ref(false)
|
||||
const refreshingChannels = ref(false)
|
||||
const testingConnection = ref(false)
|
||||
const telegramLogs = ref<any[]>([])
|
||||
const loadingLogs = ref(false)
|
||||
const logHours = ref(24)
|
||||
const editingChannel = ref<any>(null)
|
||||
const savingChannel = ref(false)
|
||||
|
||||
// 机器人状态相关变量
|
||||
const botStatus = ref<any>(null)
|
||||
const statusRefreshing = ref(false)
|
||||
|
||||
// 使用统一的Telegram API
|
||||
const telegramApi = useTelegramApi()
|
||||
@@ -423,6 +710,10 @@ const fetchTelegramConfig = async () => {
|
||||
try {
|
||||
const data = await telegramApi.getBotConfig() as any
|
||||
if (data) {
|
||||
// 确保当机器人启用时,自动回复始终为true
|
||||
if (data.bot_enabled) {
|
||||
data.auto_reply_enabled = true
|
||||
}
|
||||
telegramBotConfig.value = { ...data }
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -434,9 +725,12 @@ const fetchTelegramConfig = async () => {
|
||||
const fetchTelegramChannels = async () => {
|
||||
try {
|
||||
const data = await telegramApi.getChannels() as any[]
|
||||
if (data) {
|
||||
telegramChannels.value = data
|
||||
if (data !== undefined && data !== null) {
|
||||
telegramChannels.value = Array.isArray(data) ? data : []
|
||||
} else {
|
||||
telegramChannels.value = []
|
||||
}
|
||||
console.log('频道列表已更新:', telegramChannels.value.length, '个频道')
|
||||
} catch (error: any) {
|
||||
console.error('获取频道列表失败:', error)
|
||||
// 如果是表不存在的错误,给出更友好的提示
|
||||
@@ -460,6 +754,10 @@ const fetchTelegramChannels = async () => {
|
||||
|
||||
// 处理机器人配置变更
|
||||
const handleBotConfigChange = () => {
|
||||
// 当机器人启用时,自动回复必须为true
|
||||
if (telegramBotConfig.value.bot_enabled) {
|
||||
telegramBotConfig.value.auto_reply_enabled = true
|
||||
}
|
||||
hasBotConfigChanges.value = true
|
||||
}
|
||||
|
||||
@@ -550,17 +848,24 @@ const saveBotConfig = async () => {
|
||||
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
|
||||
// 当机器人启用时,自动回复必须为true
|
||||
configRequest.auto_reply_enabled = config.bot_enabled ? true : 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
|
||||
configRequest.proxy_enabled = config.proxy_enabled
|
||||
configRequest.proxy_type = config.proxy_type
|
||||
configRequest.proxy_host = config.proxy_host
|
||||
configRequest.proxy_port = config.proxy_port
|
||||
configRequest.proxy_username = config.proxy_username
|
||||
configRequest.proxy_password = config.proxy_password
|
||||
}
|
||||
|
||||
await telegramApi.updateBotConfig(configRequest)
|
||||
|
||||
notification.success({
|
||||
content: '配置保存成功',
|
||||
duration: 2000
|
||||
content: '配置保存成功,机器人服务已重新加载配置',
|
||||
duration: 3000
|
||||
})
|
||||
hasBotConfigChanges.value = false
|
||||
// 重新获取配置以确保同步
|
||||
@@ -577,8 +882,8 @@ const saveBotConfig = async () => {
|
||||
|
||||
// 编辑频道
|
||||
const editChannel = (channel: any) => {
|
||||
// TODO: 实现编辑频道功能
|
||||
console.log('编辑频道:', channel)
|
||||
editingChannel.value = { ...channel }
|
||||
showEditChannelDialog.value = true
|
||||
}
|
||||
|
||||
// 注销频道(带确认)
|
||||
@@ -612,6 +917,9 @@ const performUnregisterChannel = async (channel: any) => {
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
// 添加短暂延迟确保数据库事务完成
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 重新获取频道列表,更新UI
|
||||
await fetchTelegramChannels()
|
||||
|
||||
@@ -694,14 +1002,20 @@ const testBotConnection = async () => {
|
||||
testingConnection.value = true
|
||||
try {
|
||||
const data = await telegramApi.getBotStatus() as any
|
||||
if (data && data.service_running) {
|
||||
if (data && data.overall_status) {
|
||||
notification.success({
|
||||
content: `机器人连接正常!用户名:@${data.bot_username}`,
|
||||
content: `机器人连接正常!用户名:@${data.runtime?.username || '未知'}`,
|
||||
duration: 3000
|
||||
})
|
||||
} else {
|
||||
let warningMessage = '机器人服务未运行或未配置'
|
||||
if (data?.config?.enabled) {
|
||||
warningMessage = '机器人已启用但未运行,请检查 API Key 配置'
|
||||
} else if (!data?.config?.api_key_configured) {
|
||||
warningMessage = 'API Key 未配置,请先配置有效的 API Key'
|
||||
}
|
||||
notification.warning({
|
||||
content: '机器人服务未运行或未配置',
|
||||
content: warningMessage,
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
@@ -814,10 +1128,115 @@ const getCategoryLabel = (category: string): string => {
|
||||
const notification = useNotification()
|
||||
const dialog = useDialog()
|
||||
|
||||
// 保存频道设置
|
||||
const saveChannelSettings = async () => {
|
||||
if (!editingChannel.value) return
|
||||
|
||||
savingChannel.value = true
|
||||
try {
|
||||
const updateData = {
|
||||
chat_name: editingChannel.value.chat_name,
|
||||
chat_type: editingChannel.value.chat_type,
|
||||
push_enabled: editingChannel.value.push_enabled,
|
||||
push_frequency: editingChannel.value.push_frequency,
|
||||
content_categories: editingChannel.value.content_categories,
|
||||
content_tags: editingChannel.value.content_tags,
|
||||
is_active: editingChannel.value.is_active,
|
||||
push_start_time: editingChannel.value.push_start_time,
|
||||
push_end_time: editingChannel.value.push_end_time
|
||||
}
|
||||
|
||||
await telegramApi.updateChannel(editingChannel.value.id, updateData)
|
||||
|
||||
notification.success({
|
||||
content: `频道 "${editingChannel.value.chat_name}" 设置已更新`,
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
// 关闭对话框
|
||||
showEditChannelDialog.value = false
|
||||
|
||||
// 刷新频道列表
|
||||
await fetchTelegramChannels()
|
||||
|
||||
} catch (error: any) {
|
||||
notification.error({
|
||||
content: `保存频道设置失败: ${error?.message || '请稍后重试'}`,
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
savingChannel.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新机器人状态
|
||||
const refreshBotStatus = async () => {
|
||||
statusRefreshing.value = true
|
||||
try {
|
||||
const data = await telegramApi.getBotStatus() as any
|
||||
botStatus.value = data
|
||||
notification.success({
|
||||
content: '机器人状态已刷新',
|
||||
duration: 2000
|
||||
})
|
||||
} catch (error: any) {
|
||||
notification.error({
|
||||
content: '刷新状态失败:' + (error?.message || '请稍后重试'),
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
statusRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 调试机器人连接
|
||||
const debugBotConnection = async () => {
|
||||
try {
|
||||
const data = await telegramApi.getBotStatus() as any
|
||||
|
||||
let message = `🔍 **Telegram 机器人调试信息**\n\n`
|
||||
message += `🤖 机器人状态: ${data.runtime?.is_running ? '✅ 运行中' : '❌ 未运行'}\n`
|
||||
message += `👤 用户名: @${data.runtime?.username || '未知'}\n`
|
||||
message += `⚡ 工作模式: 长轮询\n\n`
|
||||
|
||||
message += `📋 **故障排查步骤:**\n`
|
||||
message += `1. 检查服务器控制台是否有 [TELEGRAM] 日志\n`
|
||||
message += `2. 确认机器人已添加到群组并设为管理员\n`
|
||||
message += `3. 验证 API Key 配置是否正确\n`
|
||||
message += `4. 确认自动回复功能已启用\n`
|
||||
message += `5. 重启服务器重新加载配置\n\n`
|
||||
|
||||
message += `🔧 **预期日志输出:**\n`
|
||||
message += `• [TELEGRAM:SERVICE] Telegram Bot (@用户名) 已启动\n`
|
||||
message += `• [TELEGRAM:MESSAGE] 收到消息: ChatID=xxx, Text='/register'\n`
|
||||
message += `• [TELEGRAM:MESSAGE] 处理 /register 命令 from ChatID=xxx\n`
|
||||
message += `• [TELEGRAM:MESSAGE:SUCCESS] 消息发送成功\n\n`
|
||||
|
||||
message += `💡 **如果没有日志输出:**\n`
|
||||
message += `• 服务器可能未正确启动机器人服务\n`
|
||||
message += `• API Key 可能有误\n`
|
||||
message += `• 数据库配置可能有问题`
|
||||
|
||||
notification.info({
|
||||
title: '🤖 机器人连接调试',
|
||||
content: message,
|
||||
duration: 15000,
|
||||
keepAliveOnHover: true
|
||||
})
|
||||
} catch (error: any) {
|
||||
notification.error({
|
||||
title: '🔧 调试失败',
|
||||
content: `无法获取机器人状态: ${error?.message || '网络错误或服务未运行'}`,
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载时获取配置
|
||||
onMounted(async () => {
|
||||
await fetchTelegramConfig()
|
||||
await fetchTelegramChannels()
|
||||
await refreshBotStatus() // 初始化机器人状态
|
||||
console.log('Telegram 机器人标签已加载')
|
||||
})
|
||||
|
||||
|
||||
@@ -269,6 +269,7 @@ export const useTelegramApi = () => {
|
||||
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 debugBotConnection = () => useApiFetch('/telegram/debug-connection').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)
|
||||
@@ -283,6 +284,7 @@ export const useTelegramApi = () => {
|
||||
updateBotConfig,
|
||||
validateApiKey,
|
||||
getBotStatus,
|
||||
debugBotConnection,
|
||||
reloadBotConfig,
|
||||
testBotMessage,
|
||||
getChannels,
|
||||
|
||||
Reference in New Issue
Block a user