package telegram import ( "fmt" "strings" "tgchanbot/internal/storage" tele "gopkg.in/telebot.v3" ) const ( cbPrefixCat = "cat:" cbPrefixConfirm = "confirm:" cbPrefixTitle = "title:" cbCancel = "cancel" ) func (b *Bot) handlePost(c tele.Context) error { msg := c.Message() payload := strings.TrimSpace(c.Message().Payload) // 快捷方式: 回复消息 + /post <分类> /tt <标题> if msg.ReplyTo != nil && payload != "" { return b.handleQuickPost(c, msg.ReplyTo, payload) } // 常规交互流程 b.states.StartPost(c.Sender().ID) return c.Reply("📨 请转发需要归档到目录的消息\n\n消息将被同步发送到频道并添加到目录\n\n💡 快捷方式: 回复消息并发送\n`/post <分类> /tt <标题>`", tele.ModeMarkdown) } func (b *Bot) handleQuickPost(c tele.Context, replyMsg *tele.Message, payload string) error { // 解析: <分类> /tt <标题> parts := strings.SplitN(payload, "/tt", 2) category := strings.TrimSpace(parts[0]) var title string if len(parts) > 1 { title = strings.TrimSpace(parts[1]) } if category == "" { return c.Reply("❌ 用法: /post <分类> /tt <标题>\n例如: /post iOS /tt 某个APP推荐") } // 验证分类存在 if !b.storage.CategoryExists(category) { categories, _ := b.storage.ListCategories() var names []string for _, cat := range categories { names = append(names, cat.Name) } return c.Reply(fmt.Sprintf("❌ 分类 [%s] 不存在\n\n可用分类: %s", category, strings.Join(names, ", "))) } // 标题: 优先使用指定标题,否则从回复消息提取 if title == "" { title = extractTitle(replyMsg) } // 构建链接 link := buildMessageLinkFromReply(c.Message(), replyMsg) // 创建条目 entry, err := b.storage.CreateEntry(category, title, link) if err != nil { return c.Reply(fmt.Sprintf("❌ 保存失败: %v", err)) } b.toc.TriggerUpdate() return c.Reply(fmt.Sprintf("✅ 已添加\n\nID: `%s`\n分类: %s\n标题: %s\n链接: %s", entry.ID, entry.Category, entry.Title, entry.Link), tele.ModeMarkdown) } func buildMessageLinkFromReply(currentMsg, replyMsg *tele.Message) string { chat := currentMsg.Chat msgID := replyMsg.ID if chat.Username != "" { return fmt.Sprintf("https://t.me/%s/%d", chat.Username, msgID) } chatID := chat.ID if chatID < 0 { chatID = -chatID - 1000000000000 } return fmt.Sprintf("https://t.me/c/%d/%d", chatID, msgID) } func (b *Bot) handleTextInput(c tele.Context) error { msg := c.Message() if msg == nil { return nil } // 只处理私聊消息,忽略群组对话 if c.Chat().Type != tele.ChatPrivate { return nil } // 只处理有活跃投稿状态的用户 state := b.states.Get(c.Sender().ID) if state == nil { return nil } // 转发消息处理 (StepAwaitForward) // OriginalChat: 转发自频道/群组; OriginalSender: 转发自个人用户 isForwarded := msg.OriginalChat != nil || msg.OriginalSender != nil if isForwarded && state.Step == StepAwaitForward { return b.handleForwarded(c) } // 自定义标题输入 (StepAwaitTitle) if state.Step == StepAwaitTitle { return b.handleTitleInput(c) } return nil } func (b *Bot) handleForwarded(c tele.Context) error { if !b.cfg.IsAdmin(c.Sender().ID) { return nil } state := b.states.Get(c.Sender().ID) if state == nil || state.Step != StepAwaitForward { return nil } msg := c.Message() // 检查是否为转发消息(来自频道/群组或个人用户) if msg.OriginalChat == nil && msg.OriginalSender == nil { return c.Reply("❌ 这不是一条转发消息,请转发一条消息给我") } b.states.SetForwarded(c.Sender().ID, msg) categories, err := b.storage.ListCategories() if err != nil { b.states.Delete(c.Sender().ID) return c.Reply(fmt.Sprintf("❌ 获取分类失败: %v", err)) } if len(categories) == 0 { b.states.Delete(c.Sender().ID) return c.Reply("❌ 暂无分类,请先使用 /cat_add 创建分类") } keyboard := b.buildCategoryKeyboard(categories) return c.Reply("📁 请选择分类:", keyboard) } func (b *Bot) buildCategoryKeyboard(categories []storage.Category) *tele.ReplyMarkup { menu := &tele.ReplyMarkup{} var rows []tele.Row var currentRow []tele.Btn for _, cat := range categories { btn := menu.Data(cat.Name, cbPrefixCat+cat.Name) currentRow = append(currentRow, btn) if len(currentRow) == 3 { rows = append(rows, menu.Row(currentRow...)) currentRow = nil } } if len(currentRow) > 0 { rows = append(rows, menu.Row(currentRow...)) } cancelBtn := menu.Data("❌ 取消", cbCancel) rows = append(rows, menu.Row(cancelBtn)) menu.Inline(rows...) return menu } func (b *Bot) handleCallback(c tele.Context) error { if !b.cfg.IsAdmin(c.Sender().ID) { return c.Respond(&tele.CallbackResponse{Text: "无权限"}) } // telebot v3 会在 data 前加 \f 前缀,需要去掉 data := strings.TrimPrefix(c.Callback().Data, "\f") userID := c.Sender().ID switch { case data == cbCancel: return b.handleCancelCallback(c, userID) case strings.HasPrefix(data, cbPrefixCat): category := strings.TrimPrefix(data, cbPrefixCat) return b.handleCategoryCallback(c, userID, category) case strings.HasPrefix(data, cbPrefixTitle): action := strings.TrimPrefix(data, cbPrefixTitle) return b.handleTitleCallback(c, userID, action) case strings.HasPrefix(data, cbPrefixConfirm): action := strings.TrimPrefix(data, cbPrefixConfirm) return b.handleConfirmCallback(c, userID, action) } return c.Respond() } func (b *Bot) handleCancelCallback(c tele.Context, userID int64) error { b.states.Delete(userID) c.Edit("❌ 已取消") return c.Respond(&tele.CallbackResponse{Text: "已取消"}) } func (b *Bot) handleTitleCallback(c tele.Context, userID int64, action string) error { state := b.states.Get(userID) if state == nil || state.Step != StepAwaitTitle { return c.Respond(&tele.CallbackResponse{Text: "会话已过期"}) } if action == "default" { // 使用默认标题 title := extractTitle(state.ForwardedMsg) b.states.SetTitle(userID, title) return b.showConfirmation(c, state, title) } // 自定义标题 - 提示用户输入 c.Edit("✏️ 请发送新标题:") return c.Respond() } func (b *Bot) handleTitleInput(c tele.Context) error { state := b.states.Get(c.Sender().ID) if state == nil || state.Step != StepAwaitTitle { return nil } title := strings.TrimSpace(c.Message().Text) if title == "" { return c.Reply("❌ 标题不能为空,请重新输入:") } if len(title) > 50 { title = title[:47] + "..." } b.states.SetTitle(c.Sender().ID, title) return b.showConfirmationMsg(c, state, title) } func (b *Bot) showConfirmation(c tele.Context, state *PostState, title string) error { channelName := "未知频道" if state.ForwardedMsg.OriginalChat != nil { channelName = state.ForwardedMsg.OriginalChat.Title } menu := &tele.ReplyMarkup{} confirmBtn := menu.Data("✅ 确认添加", cbPrefixConfirm+"yes") cancelBtn := menu.Data("❌ 取消", cbCancel) menu.Inline(menu.Row(confirmBtn, cancelBtn)) text := fmt.Sprintf("📋 确认添加?\n\n频道: %s\n分类: %s\n标题: %s", channelName, state.SelectedCat, title) c.Edit(text, menu) return c.Respond() } func (b *Bot) showConfirmationMsg(c tele.Context, state *PostState, title string) error { channelName := "未知频道" if state.ForwardedMsg.OriginalChat != nil { channelName = state.ForwardedMsg.OriginalChat.Title } menu := &tele.ReplyMarkup{} confirmBtn := menu.Data("✅ 确认添加", cbPrefixConfirm+"yes") cancelBtn := menu.Data("❌ 取消", cbCancel) menu.Inline(menu.Row(confirmBtn, cancelBtn)) text := fmt.Sprintf("📋 确认添加?\n\n频道: %s\n分类: %s\n标题: %s", channelName, state.SelectedCat, title) return c.Reply(text, menu) } func (b *Bot) handleCategoryCallback(c tele.Context, userID int64, category string) error { state := b.states.Get(userID) if state == nil || state.Step != StepAwaitCategory { return c.Respond(&tele.CallbackResponse{Text: "会话已过期"}) } b.states.SetCategory(userID, category) // 提取默认标题 defaultTitle := extractTitle(state.ForwardedMsg) menu := &tele.ReplyMarkup{} useDefaultBtn := menu.Data("✅ 使用此标题", cbPrefixTitle+"default") customBtn := menu.Data("✏️ 自定义标题", cbPrefixTitle+"custom") cancelBtn := menu.Data("❌ 取消", cbCancel) menu.Inline( menu.Row(useDefaultBtn), menu.Row(customBtn), menu.Row(cancelBtn), ) text := fmt.Sprintf("📝 确认标题\n\n分类: %s\n标题: %s\n\n使用此标题或自定义?", category, defaultTitle) c.Edit(text, menu) return c.Respond() } func (b *Bot) handleConfirmCallback(c tele.Context, userID int64, action string) error { if action != "yes" { return b.handleCancelCallback(c, userID) } state := b.states.Get(userID) if state == nil || state.Step != StepAwaitConfirm { return c.Respond(&tele.CallbackResponse{Text: "会话已过期"}) } 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) if err != nil { c.Edit(fmt.Sprintf("❌ 发送到频道失败: %v", err)) return c.Respond(&tele.CallbackResponse{Text: "发送失败"}) } // 使用频道消息的链接 link := buildChannelLink(b.cfg.Channel.ID, channelMsg.ID) entry, err := b.storage.CreateEntry(state.SelectedCat, title, link) if err != nil { c.Edit(fmt.Sprintf("❌ 保存失败: %v", err)) return c.Respond(&tele.CallbackResponse{Text: "保存失败"}) } b.toc.TriggerUpdate() text := fmt.Sprintf("✅ 已添加\n\nID: %s\n分类: %s\n标题: %s", entry.ID, entry.Category, entry.Title) c.Edit(text) return c.Respond(&tele.CallbackResponse{Text: "添加成功"}) } // sendMessageCopy 复制消息内容发送(非转发,可编辑) func (b *Bot) sendMessageCopy(to *tele.Chat, msg *tele.Message) (*tele.Message, error) { // 图片 if msg.Photo != nil { photo := &tele.Photo{File: msg.Photo.File, Caption: msg.Caption} return b.bot.Send(to, photo, tele.ModeHTML) } // 视频 if msg.Video != nil { video := &tele.Video{File: msg.Video.File, Caption: msg.Caption} return b.bot.Send(to, video, tele.ModeHTML) } // 文档 if msg.Document != nil { doc := &tele.Document{File: msg.Document.File, Caption: msg.Caption} return b.bot.Send(to, doc, tele.ModeHTML) } // 音频 if msg.Audio != nil { audio := &tele.Audio{File: msg.Audio.File, Caption: msg.Caption} return b.bot.Send(to, audio, tele.ModeHTML) } // 语音 if msg.Voice != nil { voice := &tele.Voice{File: msg.Voice.File} return b.bot.Send(to, voice) } // 贴纸 if msg.Sticker != nil { sticker := &tele.Sticker{File: msg.Sticker.File} return b.bot.Send(to, sticker) } // 动图 if msg.Animation != nil { anim := &tele.Animation{File: msg.Animation.File, Caption: msg.Caption} return b.bot.Send(to, anim, tele.ModeHTML) } // 纯文本 if msg.Text != "" { return b.bot.Send(to, msg.Text, tele.ModeHTML, tele.NoPreview) } return nil, fmt.Errorf("不支持的消息类型") } func buildChannelLink(channelID int64, msgID int) string { chatID := channelID if chatID < 0 { chatID = -chatID - 1000000000000 } return fmt.Sprintf("https://t.me/c/%d/%d", chatID, msgID) } func extractTitle(msg *tele.Message) string { text := msg.Text if text == "" { text = msg.Caption } lines := strings.Split(text, "\n") title := strings.TrimSpace(lines[0]) if len(title) > 50 { title = title[:47] + "..." } if title == "" { title = "无标题" } return title } func buildMessageLink(msg *tele.Message) string { if msg.OriginalChat == nil { return "" } chat := msg.OriginalChat msgID := msg.OriginalMessageID if msgID == 0 { msgID = msg.ID } if chat.Username != "" { return fmt.Sprintf("https://t.me/%s/%d", chat.Username, msgID) } chatID := chat.ID if chatID < 0 { chatID = -chatID - 1000000000000 } return fmt.Sprintf("https://t.me/c/%d/%d", chatID, msgID) }