diff --git a/handlers/telegram_handler.go b/handlers/telegram_handler.go
index dc2c1bf..bf3b067 100644
--- a/handlers/telegram_handler.go
+++ b/handlers/telegram_handler.go
@@ -511,6 +511,27 @@ func (h *TelegramHandler) GetTelegramLogStats(c *gin.Context) {
})
}
+// ManualPushToChannel 手动推送到频道
+func (h *TelegramHandler) ManualPushToChannel(c *gin.Context) {
+ idStr := c.Param("id")
+ channelID, err := strconv.ParseUint(idStr, 10, 32)
+ if err != nil {
+ ErrorResponse(c, "无效的频道ID", http.StatusBadRequest)
+ return
+ }
+
+ err = h.telegramBotService.ManualPushToChannel(uint(channelID))
+ if err != nil {
+ ErrorResponse(c, "手动推送失败: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ SuccessResponse(c, map[string]interface{}{
+ "success": true,
+ "message": "手动推送请求已提交",
+ })
+}
+
// ClearTelegramLogs 清理旧的Telegram日志
func (h *TelegramHandler) ClearTelegramLogs(c *gin.Context) {
daysStr := c.DefaultQuery("days", "30")
diff --git a/main.go b/main.go
index 0a0cb45..cc9ceca 100644
--- a/main.go
+++ b/main.go
@@ -435,6 +435,7 @@ func main() {
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)
+ api.POST("/telegram/manual-push/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), telegramHandler.ManualPushToChannel)
// 微信公众号相关路由
wechatHandler := handlers.NewWechatHandler(
diff --git a/services/telegram_bot_service.go b/services/telegram_bot_service.go
index b847b47..28ebf23 100644
--- a/services/telegram_bot_service.go
+++ b/services/telegram_bot_service.go
@@ -39,6 +39,7 @@ type TelegramBotService interface {
IsChannelRegistered(chatID int64) bool
HandleWebhookUpdate(c interface{})
CleanupDuplicateChannels() error
+ ManualPushToChannel(channelID uint) error
}
type TelegramBotServiceImpl struct {
@@ -1072,6 +1073,10 @@ func (s *TelegramBotServiceImpl) findResourcesForChannel(channel entity.Telegram
func (s *TelegramBotServiceImpl) findLatestResources(channel entity.TelegramChannel, excludeResourceIDs []uint) []interface{} {
params := s.buildFilterParams(channel)
+ // 添加按创建时间倒序的排序参数,确保获取最新资源
+ params["order_by"] = "created_at"
+ params["order_dir"] = "DESC"
+
// 在数据库查询中排除已推送的资源
if len(excludeResourceIDs) > 0 {
params["exclude_ids"] = excludeResourceIDs
@@ -1330,15 +1335,23 @@ func (s *TelegramBotServiceImpl) SendMessage(chatID int64, text string, img stri
} else {
// 如果 img 以 http 开头,则为图片URL,否则为文件remote_id
if strings.HasPrefix(img, "http") {
- // 发送图片URL
- photoMsg := tgbotapi.NewPhoto(chatID, tgbotapi.FileURL(img))
- photoMsg.Caption = text
- photoMsg.ParseMode = "HTML"
- _, err := s.bot.Send(photoMsg)
- if err != nil {
- utils.Error("[TELEGRAM:MESSAGE:ERROR] 发送图片消息失败: %v", err)
+ // 发送图片URL前先验证URL是否可访问并返回有效的图片格式
+ if s.isValidImageURL(img) {
+ photoMsg := tgbotapi.NewPhoto(chatID, tgbotapi.FileURL(img))
+ photoMsg.Caption = text
+ photoMsg.ParseMode = "HTML"
+ _, err := s.bot.Send(photoMsg)
+ if err != nil {
+ utils.Error("[TELEGRAM:MESSAGE:ERROR] 发送图片消息失败: %v", err)
+ // 如果URL方式失败,尝试将URL作为普通文本发送
+ return s.sendTextMessage(chatID, text)
+ }
+ return err
+ } else {
+ utils.Warn("[TELEGRAM:MESSAGE:WARNING] 图片URL无效,仅发送文本消息: %s", img)
+ // URL无效时只发送文本消息
+ return s.sendTextMessage(chatID, text)
}
- return err
} else {
// imgUrl := s.GetImgUrl(img)
//todo 判断 imgUrl 是否可用
@@ -1349,12 +1362,85 @@ func (s *TelegramBotServiceImpl) SendMessage(chatID int64, text string, img stri
_, err := s.bot.Send(photoMsg)
if err != nil {
utils.Error("[TELEGRAM:MESSAGE:ERROR] 发送图片消息失败: %v", err)
+ // 如果文件ID方式失败,尝试将URL作为普通文本发送
+ return s.sendTextMessage(chatID, text)
}
return err
}
}
}
+// isValidImageURL 验证图片URL是否有效
+func (s *TelegramBotServiceImpl) isValidImageURL(imageURL string) bool {
+ client := &http.Client{
+ Timeout: 10 * time.Second,
+ }
+
+ // 如果配置了代理,设置代理
+ if s.config.ProxyEnabled && s.config.ProxyHost != "" {
+ var proxyClient *http.Client
+ if s.config.ProxyType == "socks5" {
+ auth := &proxy.Auth{}
+ if s.config.ProxyUsername != "" {
+ auth.User = s.config.ProxyUsername
+ auth.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:IMAGE] 代理配置错误: %v", proxyErr)
+ return false
+ }
+ proxyClient = &http.Client{
+ Transport: &http.Transport{
+ Dial: dialer.Dial,
+ },
+ Timeout: 10 * time.Second,
+ }
+ } else {
+ proxyURL := &url.URL{
+ Scheme: s.config.ProxyType,
+ Host: fmt.Sprintf("%s:%d", s.config.ProxyHost, s.config.ProxyPort),
+ }
+ if s.config.ProxyUsername != "" {
+ proxyURL.User = url.UserPassword(s.config.ProxyUsername, s.config.ProxyPassword)
+ }
+ proxyClient = &http.Client{
+ Transport: &http.Transport{
+ Proxy: http.ProxyURL(proxyURL),
+ },
+ Timeout: 10 * time.Second,
+ }
+ }
+ client = proxyClient
+ }
+
+ resp, err := client.Head(imageURL)
+ if err != nil {
+ utils.Warn("[TELEGRAM:IMAGE] 检查图片URL失败: %v, URL: %s", err, imageURL)
+ return false
+ }
+ defer resp.Body.Close()
+
+ // 检查Content-Type是否为图片格式
+ contentType := resp.Header.Get("Content-Type")
+ isImage := strings.HasPrefix(contentType, "image/")
+ if !isImage {
+ utils.Warn("[TELEGRAM:IMAGE] URL不是图片格式: %s, Content-Type: %s", imageURL, contentType)
+ }
+ return isImage
+}
+
+// sendTextMessage 仅发送文本消息的辅助方法
+func (s *TelegramBotServiceImpl) sendTextMessage(chatID int64, text string) error {
+ msg := tgbotapi.NewMessage(chatID, text)
+ msg.ParseMode = "HTML"
+ _, err := s.bot.Send(msg)
+ if err != nil {
+ utils.Error("[TELEGRAM:MESSAGE:ERROR] 发送文本消息失败: %v", err)
+ }
+ return err
+}
+
// DeleteMessage 删除消息
func (s *TelegramBotServiceImpl) DeleteMessage(chatID int64, messageID int) error {
if s.bot == nil {
@@ -2017,3 +2103,25 @@ func (s *TelegramBotServiceImpl) isChannelInPushTimeRange(channel entity.Telegra
return currentTime >= startTime || currentTime <= endTime
}
}
+
+// ManualPushToChannel 手动推送内容到指定频道
+func (s *TelegramBotServiceImpl) ManualPushToChannel(channelID uint) error {
+ // 获取指定频道信息
+ channel, err := s.channelRepo.FindByID(channelID)
+ if err != nil {
+ return fmt.Errorf("找不到指定的频道: %v", err)
+ }
+
+ utils.Info("[TELEGRAM:MANUAL_PUSH] 开始手动推送到频道: %s (ID: %d)", channel.ChatName, channel.ChatID)
+
+ // 检查频道是否启用推送
+ if !channel.PushEnabled {
+ return fmt.Errorf("频道 %s 未启用推送功能", channel.ChatName)
+ }
+
+ // 推送内容到频道,使用频道配置的策略
+ s.pushToChannel(*channel)
+
+ utils.Info("[TELEGRAM:MANUAL_PUSH] 手动推送请求已提交: %s (ID: %d)", channel.ChatName, channel.ChatID)
+ return nil
+}
diff --git a/web/components/TelegramBotTab.vue b/web/components/TelegramBotTab.vue
index 121e914..6e27274 100644
--- a/web/components/TelegramBotTab.vue
+++ b/web/components/TelegramBotTab.vue
@@ -279,6 +279,16 @@
{{ channel.is_active ? '活跃' : '非活跃' }}
+
+
+
+
+
@@ -715,6 +725,8 @@ const loadingLogs = ref(false)
const logHours = ref(24)
const editingChannel = ref(null)
const savingChannel = ref(false)
+const testingPush = ref(false)
+const manualPushingChannel = ref(null)
// 机器人状态相关变量
const botStatus = ref(null)
@@ -1469,6 +1481,55 @@ const refreshBotStatus = async () => {
}
}
+// 手动推送内容到频道
+const manualPushToChannel = async (channel: any) => {
+ if (!channel || !channel.id) {
+ notification.warning({
+ content: '频道信息不完整',
+ duration: 2000
+ })
+ return
+ }
+
+ if (!telegramBotConfig.value.bot_enabled) {
+ notification.warning({
+ content: '请先启用机器人并配置API Key',
+ duration: 3000
+ })
+ return
+ }
+
+ manualPushingChannel.value = channel.id
+ try {
+ await telegramApi.manualPushToChannel(channel.id)
+
+ notification.success({
+ content: `手动推送请求已提交至频道 "${channel.chat_name}"`,
+ duration: 3000
+ })
+
+ // 更新频道推送时间
+ const updatedChannels = telegramChannels.value.map(c => {
+ if (c.id === channel.id) {
+ c.last_push_at = new Date().toISOString()
+ }
+ return c
+ })
+ telegramChannels.value = updatedChannels
+ } catch (error: any) {
+ console.error('手动推送失败:', error)
+ notification.error({
+ content: `手动推送失败: ${error?.message || '请稍后重试'}`,
+ duration: 3000
+ })
+ } finally {
+ // 只有当当前频道ID与推送中的频道ID匹配时才清除状态
+ if (manualPushingChannel.value === channel.id) {
+ manualPushingChannel.value = null
+ }
+ }
+}
+
// 调试机器人连接
const debugBotConnection = async () => {
try {
diff --git a/web/composables/useApi.ts b/web/composables/useApi.ts
index 06dcf18..ae07bbe 100644
--- a/web/composables/useApi.ts
+++ b/web/composables/useApi.ts
@@ -305,6 +305,7 @@ export const useTelegramApi = () => {
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 manualPushToChannel = (channelId: number) => useApiFetch(`/telegram/manual-push/${channelId}`, { method: 'POST' }).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)
@@ -320,6 +321,7 @@ export const useTelegramApi = () => {
debugBotConnection,
reloadBotConfig,
testBotMessage,
+ manualPushToChannel,
getChannels,
createChannel,
updateChannel,
diff --git a/web/stores/systemConfig.ts b/web/stores/systemConfig.ts
index f2a79b4..baca289 100644
--- a/web/stores/systemConfig.ts
+++ b/web/stores/systemConfig.ts
@@ -172,7 +172,7 @@ export const useSystemConfigStore = defineStore('systemConfig', {
this.error = null
try {
- console.log(`[SystemConfig] 开始获取配置 (force: ${force}, useAdminApi: ${useAdminApi})`)
+ // console.log(`[SystemConfig] 开始获取配置 (force: ${force}, useAdminApi: ${useAdminApi})`)
// 根据上下文选择API:管理员页面使用管理员API,其他页面使用公开API
const apiUrl = useAdminApi ? '/system/config' : '/public/system-config'
@@ -189,14 +189,14 @@ export const useSystemConfigStore = defineStore('systemConfig', {
// 保存到缓存(仅在客户端)
this.saveToCache(data)
- console.log('[SystemConfig] 配置获取并缓存成功')
- console.log('[SystemConfig] 自动处理状态:', data.auto_process_ready_resources)
- console.log('[SystemConfig] 自动转存状态:', data.auto_transfer_enabled)
+ // console.log('[SystemConfig] 配置获取并缓存成功')
+ // console.log('[SystemConfig] 自动处理状态:', data.auto_process_ready_resources)
+ // console.log('[SystemConfig] 自动转存状态:', data.auto_transfer_enabled)
} catch (error) {
this.isLoading = false
this.error = error instanceof Error ? error.message : '获取配置失败'
- console.error('[SystemConfig] 获取系统配置失败:', error)
+ // console.error('[SystemConfig] 获取系统配置失败:', error)
// 如果网络请求失败,尝试使用过期的缓存作为降级方案
if (!force) {