增加功能:删除选择;相册转发
This commit is contained in:
@@ -8,6 +8,12 @@ import (
|
||||
tele "gopkg.in/telebot.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
cbPrefixDelEntry = "del:"
|
||||
cbPrefixDelWithMsg = "delwithmsg:"
|
||||
cbPrefixDelOnlyToc = "delonlytoc:"
|
||||
)
|
||||
|
||||
func (b *Bot) handleEntryDel(c tele.Context) error {
|
||||
id := strings.TrimSpace(c.Message().Payload)
|
||||
if id == "" {
|
||||
@@ -19,26 +25,18 @@ func (b *Bot) handleEntryDel(c tele.Context) error {
|
||||
return c.Reply(fmt.Sprintf("❌ %v", err))
|
||||
}
|
||||
|
||||
// 尝试删除频道消息
|
||||
msgDeleted := false
|
||||
if msgID := parseMessageIDFromLink(entry.Link); msgID != 0 {
|
||||
channel := &tele.Chat{ID: b.cfg.Channel.ID}
|
||||
msg := &tele.Message{ID: msgID, Chat: channel}
|
||||
if err := b.bot.Delete(msg); err == nil {
|
||||
msgDeleted = true
|
||||
}
|
||||
}
|
||||
menu := &tele.ReplyMarkup{}
|
||||
delWithMsgBtn := menu.Data("🗑 删除条目+频道消息", cbPrefixDelWithMsg+id)
|
||||
delOnlyTocBtn := menu.Data("📝 仅删除目录条目", cbPrefixDelOnlyToc+id)
|
||||
cancelBtn := menu.Data("❌ 取消", cbCancel)
|
||||
menu.Inline(
|
||||
menu.Row(delWithMsgBtn),
|
||||
menu.Row(delOnlyTocBtn),
|
||||
menu.Row(cancelBtn),
|
||||
)
|
||||
|
||||
if err := b.storage.DeleteEntry(id); err != nil {
|
||||
return c.Reply(fmt.Sprintf("❌ 删除失败: %v", err))
|
||||
}
|
||||
|
||||
b.toc.TriggerUpdate()
|
||||
|
||||
if msgDeleted {
|
||||
return c.Reply(fmt.Sprintf("✅ 已删除: [%s] %s\n📨 频道消息已同步删除", entry.Category, entry.Title))
|
||||
}
|
||||
return c.Reply(fmt.Sprintf("✅ 已删除: [%s] %s", entry.Category, entry.Title))
|
||||
text := fmt.Sprintf("🗑 确认删除?\n\n分类: %s\n标题: %s\n\n请选择删除方式:", entry.Category, entry.Title)
|
||||
return c.Reply(text, menu)
|
||||
}
|
||||
|
||||
func (b *Bot) handleEntryEdit(c tele.Context) error {
|
||||
@@ -118,6 +116,46 @@ func (b *Bot) handleRefresh(c tele.Context) error {
|
||||
return c.Reply("🔄 目录刷新已触发")
|
||||
}
|
||||
|
||||
// handleDeleteEntryCallback 处理删除条目的回调
|
||||
func (b *Bot) handleDeleteEntryCallback(c tele.Context, entryID string, deleteChannelMsg bool) error {
|
||||
entry, err := b.storage.GetEntry(entryID)
|
||||
if err != nil {
|
||||
c.Edit(fmt.Sprintf("❌ %v", err))
|
||||
return c.Respond(&tele.CallbackResponse{Text: "条目不存在"})
|
||||
}
|
||||
|
||||
// 根据用户选择决定是否删除频道消息
|
||||
msgDeleted := false
|
||||
if deleteChannelMsg {
|
||||
if msgID := parseMessageIDFromLink(entry.Link); msgID != 0 {
|
||||
channel := &tele.Chat{ID: b.cfg.Channel.ID}
|
||||
msg := &tele.Message{ID: msgID, Chat: channel}
|
||||
if err := b.bot.Delete(msg); err == nil {
|
||||
msgDeleted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.storage.DeleteEntry(entryID); err != nil {
|
||||
c.Edit(fmt.Sprintf("❌ 删除失败: %v", err))
|
||||
return c.Respond(&tele.CallbackResponse{Text: "删除失败"})
|
||||
}
|
||||
|
||||
b.toc.TriggerUpdate()
|
||||
|
||||
var text string
|
||||
if deleteChannelMsg && msgDeleted {
|
||||
text = fmt.Sprintf("✅ 已删除: [%s] %s\n📨 频道消息已同步删除", entry.Category, entry.Title)
|
||||
} else if deleteChannelMsg && !msgDeleted {
|
||||
text = fmt.Sprintf("✅ 已删除: [%s] %s\n⚠️ 频道消息删除失败(可能已被删除)", entry.Category, entry.Title)
|
||||
} else {
|
||||
text = fmt.Sprintf("✅ 已删除: [%s] %s\n📝 频道消息已保留", entry.Category, entry.Title)
|
||||
}
|
||||
|
||||
c.Edit(text)
|
||||
return c.Respond(&tele.CallbackResponse{Text: "删除成功"})
|
||||
}
|
||||
|
||||
// parseMessageIDFromLink 从链接中解析消息ID
|
||||
// 支持格式: https://t.me/c/123456/789 或 https://t.me/username/789
|
||||
func parseMessageIDFromLink(link string) int {
|
||||
|
||||
@@ -3,6 +3,8 @@ package telegram
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tgchanbot/internal/storage"
|
||||
|
||||
@@ -14,6 +16,14 @@ const (
|
||||
cbPrefixConfirm = "confirm:"
|
||||
cbPrefixTitle = "title:"
|
||||
cbCancel = "cancel"
|
||||
|
||||
albumCollectDelay = 500 * time.Millisecond // 等待相册其他消息的时间
|
||||
)
|
||||
|
||||
// 相册收集定时器
|
||||
var (
|
||||
albumTimers = make(map[int64]*time.Timer)
|
||||
albumTimersMu sync.Mutex
|
||||
)
|
||||
|
||||
func (b *Bot) handlePost(c tele.Context) error {
|
||||
@@ -107,8 +117,10 @@ func (b *Bot) handleTextInput(c tele.Context) error {
|
||||
|
||||
// 私聊收到转发消息,直接启动投稿流程(无需先 /post)
|
||||
if isForwarded && b.cfg.IsAdmin(c.Sender().ID) {
|
||||
if state == nil || state.Step == StepAwaitForward {
|
||||
if state == nil {
|
||||
b.states.StartPost(c.Sender().ID)
|
||||
}
|
||||
if state == nil || state.Step == StepAwaitForward {
|
||||
return b.handleForwarded(c)
|
||||
}
|
||||
}
|
||||
@@ -141,8 +153,43 @@ func (b *Bot) handleForwarded(c tele.Context) error {
|
||||
return c.Reply("❌ 这不是一条转发消息,请转发一条消息给我")
|
||||
}
|
||||
|
||||
b.states.SetForwarded(c.Sender().ID, msg)
|
||||
userID := c.Sender().ID
|
||||
|
||||
// 相册消息处理
|
||||
if msg.AlbumID != "" {
|
||||
b.states.AddAlbumMessage(userID, msg)
|
||||
b.scheduleAlbumFinalize(c, userID)
|
||||
return nil // 等待收集完成
|
||||
}
|
||||
|
||||
// 单条消息
|
||||
b.states.SetForwarded(userID, msg)
|
||||
return b.showCategorySelection(c)
|
||||
}
|
||||
|
||||
// scheduleAlbumFinalize 延迟完成相册收集
|
||||
func (b *Bot) scheduleAlbumFinalize(c tele.Context, userID int64) {
|
||||
albumTimersMu.Lock()
|
||||
defer albumTimersMu.Unlock()
|
||||
|
||||
// 取消之前的定时器
|
||||
if timer, exists := albumTimers[userID]; exists {
|
||||
timer.Stop()
|
||||
}
|
||||
|
||||
// 设置新定时器
|
||||
albumTimers[userID] = time.AfterFunc(albumCollectDelay, func() {
|
||||
albumTimersMu.Lock()
|
||||
delete(albumTimers, userID)
|
||||
albumTimersMu.Unlock()
|
||||
|
||||
b.states.FinalizeAlbum(userID)
|
||||
b.showCategorySelectionAsync(c, userID)
|
||||
})
|
||||
}
|
||||
|
||||
// showCategorySelection 显示分类选择
|
||||
func (b *Bot) showCategorySelection(c tele.Context) error {
|
||||
categories, err := b.storage.ListCategories()
|
||||
if err != nil {
|
||||
b.states.Delete(c.Sender().ID)
|
||||
@@ -154,10 +201,48 @@ func (b *Bot) handleForwarded(c tele.Context) error {
|
||||
return c.Reply("❌ 暂无分类,请先使用 /cat_add 创建分类")
|
||||
}
|
||||
|
||||
state := b.states.Get(c.Sender().ID)
|
||||
msgCount := 1
|
||||
if state != nil && len(state.ForwardedMsgs) > 0 {
|
||||
msgCount = len(state.ForwardedMsgs)
|
||||
}
|
||||
|
||||
keyboard := b.buildCategoryKeyboard(categories)
|
||||
if msgCount > 1 {
|
||||
return c.Reply(fmt.Sprintf("📁 已收到 %d 条消息(相册),请选择分类:", msgCount), keyboard)
|
||||
}
|
||||
return c.Reply("📁 请选择分类:", keyboard)
|
||||
}
|
||||
|
||||
// showCategorySelectionAsync 异步显示分类选择(用于定时器回调)
|
||||
func (b *Bot) showCategorySelectionAsync(c tele.Context, userID int64) {
|
||||
categories, err := b.storage.ListCategories()
|
||||
if err != nil {
|
||||
b.states.Delete(userID)
|
||||
c.Bot().Send(c.Chat(), fmt.Sprintf("❌ 获取分类失败: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if len(categories) == 0 {
|
||||
b.states.Delete(userID)
|
||||
c.Bot().Send(c.Chat(), "❌ 暂无分类,请先使用 /cat_add 创建分类")
|
||||
return
|
||||
}
|
||||
|
||||
state := b.states.Get(userID)
|
||||
msgCount := 1
|
||||
if state != nil && len(state.ForwardedMsgs) > 0 {
|
||||
msgCount = len(state.ForwardedMsgs)
|
||||
}
|
||||
|
||||
keyboard := b.buildCategoryKeyboard(categories)
|
||||
if msgCount > 1 {
|
||||
c.Bot().Send(c.Chat(), fmt.Sprintf("📁 已收到 %d 条消息(相册),请选择分类:", msgCount), keyboard)
|
||||
} else {
|
||||
c.Bot().Send(c.Chat(), "📁 请选择分类:", keyboard)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) buildCategoryKeyboard(categories []storage.Category) *tele.ReplyMarkup {
|
||||
menu := &tele.ReplyMarkup{}
|
||||
var rows []tele.Row
|
||||
@@ -208,6 +293,14 @@ func (b *Bot) handleCallback(c tele.Context) error {
|
||||
case strings.HasPrefix(data, cbPrefixConfirm):
|
||||
action := strings.TrimPrefix(data, cbPrefixConfirm)
|
||||
return b.handleConfirmCallback(c, userID, action)
|
||||
|
||||
case strings.HasPrefix(data, cbPrefixDelWithMsg):
|
||||
entryID := strings.TrimPrefix(data, cbPrefixDelWithMsg)
|
||||
return b.handleDeleteEntryCallback(c, entryID, true)
|
||||
|
||||
case strings.HasPrefix(data, cbPrefixDelOnlyToc):
|
||||
entryID := strings.TrimPrefix(data, cbPrefixDelOnlyToc)
|
||||
return b.handleDeleteEntryCallback(c, entryID, false)
|
||||
}
|
||||
|
||||
return c.Respond()
|
||||
@@ -325,12 +418,20 @@ func (b *Bot) handleConfirmCallback(c tele.Context, userID int64, action string)
|
||||
|
||||
defer b.states.Delete(userID)
|
||||
|
||||
msg := state.ForwardedMsg
|
||||
title := state.Title
|
||||
|
||||
// 复制消息内容发送到频道(非转发,可编辑)
|
||||
channel := &tele.Chat{ID: b.cfg.Channel.ID}
|
||||
channelMsg, err := b.sendMessageCopy(channel, msg)
|
||||
|
||||
var channelMsg *tele.Message
|
||||
var err error
|
||||
|
||||
// 相册消息
|
||||
if len(state.ForwardedMsgs) > 1 {
|
||||
channelMsg, err = b.sendAlbumCopy(channel, state.ForwardedMsgs)
|
||||
} else {
|
||||
// 单条消息
|
||||
channelMsg, err = b.sendMessageCopy(channel, state.ForwardedMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Edit(fmt.Sprintf("❌ 发送到频道失败: %v", err))
|
||||
return c.Respond(&tele.CallbackResponse{Text: "发送失败"})
|
||||
@@ -404,6 +505,47 @@ func (b *Bot) sendMessageCopy(to *tele.Chat, msg *tele.Message) (*tele.Message,
|
||||
return nil, fmt.Errorf("不支持的消息类型")
|
||||
}
|
||||
|
||||
// sendAlbumCopy 复制相册发送到频道,返回第一条消息
|
||||
func (b *Bot) sendAlbumCopy(to *tele.Chat, msgs []*tele.Message) (*tele.Message, error) {
|
||||
if len(msgs) == 0 {
|
||||
return nil, fmt.Errorf("相册为空")
|
||||
}
|
||||
|
||||
var album tele.Album
|
||||
for i, msg := range msgs {
|
||||
var caption string
|
||||
if i == 0 {
|
||||
// 只有第一条消息带 caption
|
||||
caption = msg.Caption
|
||||
}
|
||||
|
||||
if msg.Photo != nil {
|
||||
album = append(album, &tele.Photo{File: msg.Photo.File, Caption: caption})
|
||||
} else if msg.Video != nil {
|
||||
album = append(album, &tele.Video{File: msg.Video.File, Caption: caption})
|
||||
} else if msg.Document != nil {
|
||||
album = append(album, &tele.Document{File: msg.Document.File, Caption: caption})
|
||||
} else if msg.Audio != nil {
|
||||
album = append(album, &tele.Audio{File: msg.Audio.File, Caption: caption})
|
||||
}
|
||||
}
|
||||
|
||||
if len(album) == 0 {
|
||||
return nil, fmt.Errorf("相册中没有支持的媒体类型")
|
||||
}
|
||||
|
||||
sentMsgs, err := b.bot.SendAlbum(to, album, tele.ModeHTML)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(sentMsgs) == 0 {
|
||||
return nil, fmt.Errorf("发送相册失败")
|
||||
}
|
||||
|
||||
return &sentMsgs[0], nil
|
||||
}
|
||||
|
||||
func buildChannelLink(channelID int64, msgID int) string {
|
||||
chatID := channelID
|
||||
if chatID < 0 {
|
||||
|
||||
@@ -17,12 +17,14 @@ const (
|
||||
)
|
||||
|
||||
type PostState struct {
|
||||
UserID int64
|
||||
ForwardedMsg *tele.Message
|
||||
SelectedCat string
|
||||
Title string
|
||||
Step PostStep
|
||||
CreatedAt time.Time
|
||||
UserID int64
|
||||
ForwardedMsg *tele.Message // 单条消息
|
||||
ForwardedMsgs []*tele.Message // 相册消息
|
||||
AlbumID string // 相册ID
|
||||
SelectedCat string
|
||||
Title string
|
||||
Step PostStep
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type StateManager struct {
|
||||
@@ -74,6 +76,51 @@ func (sm *StateManager) SetForwarded(userID int64, msg *tele.Message) *PostState
|
||||
}
|
||||
|
||||
state.ForwardedMsg = msg
|
||||
state.ForwardedMsgs = nil
|
||||
state.AlbumID = ""
|
||||
state.Step = StepAwaitCategory
|
||||
return state
|
||||
}
|
||||
|
||||
// AddAlbumMessage 添加相册消息,返回是否应该继续等待更多消息
|
||||
func (sm *StateManager) AddAlbumMessage(userID int64, msg *tele.Message) (shouldWait bool) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
state := sm.states[userID]
|
||||
if state == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 第一条相册消息
|
||||
if state.AlbumID == "" {
|
||||
state.AlbumID = msg.AlbumID
|
||||
state.ForwardedMsgs = []*tele.Message{msg}
|
||||
return true
|
||||
}
|
||||
|
||||
// 同一相册的后续消息
|
||||
if state.AlbumID == msg.AlbumID {
|
||||
state.ForwardedMsgs = append(state.ForwardedMsgs, msg)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// FinalizeAlbum 完成相册收集
|
||||
func (sm *StateManager) FinalizeAlbum(userID int64) *PostState {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
state := sm.states[userID]
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(state.ForwardedMsgs) > 0 {
|
||||
state.ForwardedMsg = state.ForwardedMsgs[0] // 用第一条提取标题
|
||||
}
|
||||
state.Step = StepAwaitCategory
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user