mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
517 lines
15 KiB
Go
517 lines
15 KiB
Go
package handlers
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/ctwj/urldb/db/converter"
|
||
"github.com/ctwj/urldb/db/dto"
|
||
"github.com/ctwj/urldb/db/entity"
|
||
"github.com/ctwj/urldb/db/repo"
|
||
"github.com/ctwj/urldb/services"
|
||
"github.com/ctwj/urldb/utils"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// TelegramHandler Telegram 处理器
|
||
type TelegramHandler struct {
|
||
telegramChannelRepo repo.TelegramChannelRepository
|
||
systemConfigRepo repo.SystemConfigRepository
|
||
telegramBotService services.TelegramBotService
|
||
}
|
||
|
||
// NewTelegramHandler 创建 Telegram 处理器
|
||
func NewTelegramHandler(
|
||
telegramChannelRepo repo.TelegramChannelRepository,
|
||
systemConfigRepo repo.SystemConfigRepository,
|
||
telegramBotService services.TelegramBotService,
|
||
) *TelegramHandler {
|
||
return &TelegramHandler{
|
||
telegramChannelRepo: telegramChannelRepo,
|
||
systemConfigRepo: systemConfigRepo,
|
||
telegramBotService: telegramBotService,
|
||
}
|
||
}
|
||
|
||
// GetBotConfig 获取机器人配置
|
||
func (h *TelegramHandler) GetBotConfig(c *gin.Context) {
|
||
configs, err := h.systemConfigRepo.GetOrCreateDefault()
|
||
if err != nil {
|
||
ErrorResponse(c, "获取配置失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
botConfig := converter.SystemConfigToTelegramBotConfig(configs)
|
||
SuccessResponse(c, botConfig)
|
||
}
|
||
|
||
// UpdateBotConfig 更新机器人配置
|
||
func (h *TelegramHandler) UpdateBotConfig(c *gin.Context) {
|
||
var req dto.TelegramBotConfigRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 转换为系统配置实体
|
||
configs := converter.TelegramBotConfigRequestToSystemConfigs(req)
|
||
|
||
// 保存配置
|
||
if len(configs) > 0 {
|
||
err := h.systemConfigRepo.UpsertConfigs(configs)
|
||
if err != nil {
|
||
ErrorResponse(c, "保存配置失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
|
||
// 重新加载配置缓存
|
||
if err := h.systemConfigRepo.SafeRefreshConfigCache(); err != nil {
|
||
ErrorResponse(c, "刷新配置缓存失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// 重新加载机器人服务配置
|
||
if err := h.telegramBotService.ReloadConfig(); err != nil {
|
||
ErrorResponse(c, "重新加载机器人配置失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// 配置更新完成后,尝试启动机器人(如果未运行且配置有效)
|
||
if startErr := h.telegramBotService.Start(); startErr != nil {
|
||
utils.Warn("[TELEGRAM:HANDLER] 配置更新后尝试启动机器人失败: %v", startErr)
|
||
// 启动失败不影响配置保存,只记录警告
|
||
}
|
||
|
||
// 返回成功
|
||
SuccessResponse(c, map[string]interface{}{
|
||
"success": true,
|
||
"message": "配置更新成功,机器人已尝试启动",
|
||
})
|
||
}
|
||
|
||
// ValidateApiKey 校验 API Key
|
||
func (h *TelegramHandler) ValidateApiKey(c *gin.Context) {
|
||
var req dto.ValidateTelegramApiKeyRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 如果请求中包含代理配置,临时更新服务配置进行校验
|
||
if req.ProxyEnabled {
|
||
// 这里只是为了校验,我们不应该修改全局配置
|
||
// 传递代理配置给服务进行校验
|
||
valid, botInfo, err := h.telegramBotService.ValidateApiKeyWithProxy(
|
||
req.ApiKey,
|
||
req.ProxyEnabled,
|
||
req.ProxyType,
|
||
req.ProxyHost,
|
||
req.ProxyPort,
|
||
req.ProxyUsername,
|
||
req.ProxyPassword,
|
||
)
|
||
if err != nil {
|
||
ErrorResponse(c, "校验失败: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
response := dto.ValidateTelegramApiKeyResponse{
|
||
Valid: valid,
|
||
BotInfo: botInfo,
|
||
}
|
||
|
||
if !valid {
|
||
response.Error = "无效的 API Key"
|
||
}
|
||
|
||
SuccessResponse(c, response)
|
||
} else {
|
||
// 使用默认配置校验
|
||
valid, botInfo, err := h.telegramBotService.ValidateApiKey(req.ApiKey)
|
||
if err != nil {
|
||
ErrorResponse(c, "校验失败: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
response := dto.ValidateTelegramApiKeyResponse{
|
||
Valid: valid,
|
||
BotInfo: botInfo,
|
||
}
|
||
|
||
if !valid {
|
||
response.Error = "无效的 API Key"
|
||
}
|
||
|
||
SuccessResponse(c, response)
|
||
}
|
||
}
|
||
|
||
// GetChannels 获取频道列表
|
||
func (h *TelegramHandler) GetChannels(c *gin.Context) {
|
||
channels, err := h.telegramChannelRepo.FindAll()
|
||
if err != nil {
|
||
ErrorResponse(c, "获取频道列表失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
channelResponses := converter.TelegramChannelsToResponse(channels)
|
||
SuccessResponse(c, channelResponses)
|
||
}
|
||
|
||
// CreateChannel 创建频道
|
||
func (h *TelegramHandler) CreateChannel(c *gin.Context) {
|
||
var req dto.TelegramChannelRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 检查频道是否已存在
|
||
existing, err := h.telegramChannelRepo.FindByChatID(req.ChatID)
|
||
if err == nil && existing != nil {
|
||
ErrorResponse(c, "该频道/群组已注册", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 获取当前用户信息作为注册者
|
||
username := getCurrentUsername(c) // 需要实现获取用户信息的方法
|
||
|
||
channel := converter.RequestToTelegramChannel(req, username)
|
||
|
||
if err := h.telegramChannelRepo.Create(&channel); err != nil {
|
||
ErrorResponse(c, "创建频道失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
response := converter.TelegramChannelToResponse(channel)
|
||
SuccessResponse(c, response)
|
||
}
|
||
|
||
// UpdateChannel 更新频道
|
||
func (h *TelegramHandler) UpdateChannel(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||
if err != nil {
|
||
ErrorResponse(c, "无效的ID", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
var req dto.TelegramChannelUpdateRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
ErrorResponse(c, "请求参数错误", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
utils.Info("[TELEGRAM:HANDLER] 接收到频道更新请求: ID=%s, ChatName=%s, PushStartTime=%s, PushEndTime=%s, ResourceStrategy=%s, TimeLimit=%s",
|
||
idStr, req.ChatName, req.PushStartTime, req.PushEndTime, req.ResourceStrategy, req.TimeLimit)
|
||
|
||
// 查找现有频道
|
||
channel, err := h.telegramChannelRepo.FindByID(uint(id))
|
||
if err != nil {
|
||
ErrorResponse(c, "频道不存在", http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
// 保存前的日志
|
||
utils.Info("[TELEGRAM:HANDLER] 更新前频道状态: PushStartTime=%s, PushEndTime=%s, ResourceStrategy=%s, TimeLimit=%s",
|
||
channel.PushStartTime, channel.PushEndTime, channel.ResourceStrategy, channel.TimeLimit)
|
||
|
||
// 如果前端传递了ChatID,验证它是否与现有频道匹配
|
||
if req.ChatID != 0 && req.ChatID != channel.ChatID {
|
||
ErrorResponse(c, "ChatID不匹配,无法更新此频道", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 更新频道信息
|
||
channel.ChatName = req.ChatName
|
||
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
|
||
channel.ResourceStrategy = req.ResourceStrategy
|
||
channel.TimeLimit = req.TimeLimit
|
||
|
||
if err := h.telegramChannelRepo.Update(channel); err != nil {
|
||
ErrorResponse(c, "更新频道失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// 保存后的日志
|
||
utils.Info("[TELEGRAM:HANDLER] 更新后频道状态: PushStartTime=%s, PushEndTime=%s, ResourceStrategy=%s, TimeLimit=%s",
|
||
channel.PushStartTime, channel.PushEndTime, channel.ResourceStrategy, channel.TimeLimit)
|
||
|
||
response := converter.TelegramChannelToResponse(*channel)
|
||
SuccessResponse(c, response)
|
||
}
|
||
|
||
// DeleteChannel 删除频道
|
||
func (h *TelegramHandler) DeleteChannel(c *gin.Context) {
|
||
idStr := c.Param("id")
|
||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||
if err != nil {
|
||
ErrorResponse(c, "无效的ID", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 检查频道是否存在
|
||
channel, err := h.telegramChannelRepo.FindByID(uint(id))
|
||
if err != nil {
|
||
ErrorResponse(c, "频道不存在", http.StatusNotFound)
|
||
return
|
||
}
|
||
|
||
// 删除频道
|
||
if err := h.telegramChannelRepo.Delete(uint(id)); err != nil {
|
||
ErrorResponse(c, "删除频道失败", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
SuccessResponse(c, map[string]interface{}{
|
||
"success": true,
|
||
"message": "频道 " + channel.ChatName + " 已成功移除",
|
||
})
|
||
}
|
||
|
||
// RegisterChannelByCommand 通过命令注册频道(供内部调用)
|
||
func (h *TelegramHandler) RegisterChannelByCommand(chatID int64, chatName, chatType string) error {
|
||
// 检查是否已注册
|
||
existing, err := h.telegramChannelRepo.FindByChatID(chatID)
|
||
if err == nil && existing != nil {
|
||
// 已存在,返回成功
|
||
return nil
|
||
}
|
||
|
||
// 创建新的频道记录
|
||
channel := entity.TelegramChannel{
|
||
ChatID: chatID,
|
||
ChatName: chatName,
|
||
ChatType: chatType,
|
||
PushEnabled: true,
|
||
PushFrequency: 15, // 默认15分钟
|
||
PushStartTime: "08:30", // 默认开始时间8:30
|
||
PushEndTime: "11:30", // 默认结束时间11:30
|
||
IsActive: true,
|
||
RegisteredBy: "bot_command",
|
||
RegisteredAt: time.Now(),
|
||
ResourceStrategy: "random", // 默认纯随机
|
||
TimeLimit: "none", // 默认无限制
|
||
}
|
||
|
||
return h.telegramChannelRepo.Create(&channel)
|
||
}
|
||
|
||
// HandleWebhook 处理 Telegram Webhook
|
||
func (h *TelegramHandler) HandleWebhook(c *gin.Context) {
|
||
// 将消息交给 bot 服务处理
|
||
// 这里可以根据需要添加身份验证
|
||
h.telegramBotService.HandleWebhookUpdate(c)
|
||
}
|
||
|
||
// GetBotStatus 获取机器人状态
|
||
func (h *TelegramHandler) GetBotStatus(c *gin.Context) {
|
||
// 获取机器人运行时状态
|
||
runtimeStatus := h.telegramBotService.GetRuntimeStatus()
|
||
|
||
// 获取配置状态
|
||
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{}{
|
||
"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)
|
||
}
|
||
|
||
// 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": "当前版本需要重启服务器才能重新加载机器人配置",
|
||
})
|
||
}
|
||
|
||
// 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) {
|
||
// 解析查询参数
|
||
hoursStr := c.DefaultQuery("hours", "24")
|
||
limitStr := c.DefaultQuery("limit", "100")
|
||
|
||
hours, err := strconv.Atoi(hoursStr)
|
||
if err != nil || hours <= 0 || hours > 720 { // 最多30天
|
||
hours = 24
|
||
}
|
||
|
||
limit, err := strconv.Atoi(limitStr)
|
||
if err != nil || limit <= 0 || limit > 1000 {
|
||
limit = 100
|
||
}
|
||
|
||
// 计算时间范围
|
||
endTime := time.Now()
|
||
startTime := endTime.Add(-time.Duration(hours) * time.Hour)
|
||
|
||
// 获取日志
|
||
logs, err := utils.GetTelegramLogs(&startTime, &endTime, limit)
|
||
if err != nil {
|
||
ErrorResponse(c, "获取日志失败: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
SuccessResponse(c, map[string]interface{}{
|
||
"logs": logs,
|
||
"count": len(logs),
|
||
"hours": hours,
|
||
"limit": limit,
|
||
"start": startTime.Format("2006-01-02 15:04:05"),
|
||
"end": endTime.Format("2006-01-02 15:04:05"),
|
||
})
|
||
}
|
||
|
||
// GetTelegramLogStats 获取Telegram日志统计信息
|
||
func (h *TelegramHandler) GetTelegramLogStats(c *gin.Context) {
|
||
hoursStr := c.DefaultQuery("hours", "24")
|
||
|
||
hours, err := strconv.Atoi(hoursStr)
|
||
if err != nil || hours <= 0 || hours > 720 {
|
||
hours = 24
|
||
}
|
||
|
||
stats, err := utils.GetTelegramLogStats(hours)
|
||
if err != nil {
|
||
ErrorResponse(c, "获取统计信息失败: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
SuccessResponse(c, map[string]interface{}{
|
||
"stats": stats,
|
||
"hours": hours,
|
||
})
|
||
}
|
||
|
||
// ClearTelegramLogs 清理旧的Telegram日志
|
||
func (h *TelegramHandler) ClearTelegramLogs(c *gin.Context) {
|
||
daysStr := c.DefaultQuery("days", "30")
|
||
|
||
days, err := strconv.Atoi(daysStr)
|
||
if err != nil || days <= 0 || days > 365 {
|
||
days = 30
|
||
}
|
||
|
||
err = utils.ClearOldTelegramLogs(days)
|
||
if err != nil {
|
||
ErrorResponse(c, "清理日志失败: "+err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
SuccessResponse(c, map[string]interface{}{
|
||
"message": fmt.Sprintf("已清理 %d 天前的日志文件", days),
|
||
"days": days,
|
||
})
|
||
}
|
||
|
||
// getCurrentUsername 获取当前用户名(临时实现)
|
||
func getCurrentUsername(c *gin.Context) string {
|
||
// 这里应该从中间件中获取用户信息
|
||
// 暂时返回默认值
|
||
return "admin"
|
||
}
|