Merge pull request 'main' (#2) from zuimao_kaf/mygoTgChanBot:main into main

Reviewed-on: https://github.carrydelahaye.work/carrydela/mygoTgChanBot/pulls/2
This commit is contained in:
2026-03-18 10:33:16 +08:00
5 changed files with 97 additions and 23 deletions

View File

@@ -9,12 +9,14 @@ import (
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
func (s *Storage) CreateEntry(category, title, link string) (*Entry, error) { func (s *Storage) CreateEntry(category, title, channelLink, directLink string) (*Entry, error) {
entry := &Entry{ entry := &Entry{
ID: shortid.MustGenerate(), ID: shortid.MustGenerate(),
Category: category, Category: category,
Title: title, Title: title,
Link: link, Link: channelLink,
ChannelLink: channelLink,
DirectLink: directLink,
Timestamp: time.Now(), Timestamp: time.Now(),
} }

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
@@ -30,10 +31,24 @@ type Entry struct {
ID string `json:"id"` ID string `json:"id"`
Category string `json:"category"` Category string `json:"category"`
Title string `json:"title"` Title string `json:"title"`
Link string `json:"link"` Link string `json:"link,omitempty"` // 兼容旧数据
ChannelLink string `json:"channel_link,omitempty"`
DirectLink string `json:"direct_link,omitempty"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
} }
func (e Entry) MessageLink() string {
if e.ChannelLink != "" {
return e.ChannelLink
}
return e.Link
}
func (e Entry) HasDirectLink() bool {
direct := strings.TrimSpace(e.DirectLink)
return direct != "" && direct != e.MessageLink()
}
type Storage struct { type Storage struct {
db *bolt.DB db *bolt.DB
} }

View File

@@ -128,7 +128,7 @@ func (b *Bot) handleDeleteEntryCallback(c tele.Context, entryID string, deleteCh
// 根据用户选择决定是否删除频道消息 // 根据用户选择决定是否删除频道消息
msgDeleted := false msgDeleted := false
if deleteChannelMsg { if deleteChannelMsg {
if msgID := parseMessageIDFromLink(entry.Link); msgID != 0 { if msgID := parseMessageIDFromLink(entry.MessageLink()); msgID != 0 {
channel := &tele.Chat{ID: b.cfg.Channel.ID} channel := &tele.Chat{ID: b.cfg.Channel.ID}
msg := &tele.Message{ID: msgID, Chat: channel} msg := &tele.Message{ID: msgID, Chat: channel}
if err := b.bot.Delete(msg); err == nil { if err := b.bot.Delete(msg); err == nil {

View File

@@ -2,6 +2,7 @@ package telegram
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -26,6 +27,8 @@ var (
albumTimersMu sync.Mutex albumTimersMu sync.Mutex
) )
var directURLPattern = regexp.MustCompile(`https?://[^\s<>"']+`)
func (b *Bot) handlePost(c tele.Context) error { func (b *Bot) handlePost(c tele.Context) error {
msg := c.Message() msg := c.Message()
payload := strings.TrimSpace(c.Message().Payload) payload := strings.TrimSpace(c.Message().Payload)
@@ -69,11 +72,11 @@ func (b *Bot) handleQuickPost(c tele.Context, replyMsg *tele.Message, payload st
title = extractTitle(replyMsg) title = extractTitle(replyMsg)
} }
// 构建链接 channelLink := buildMessageLinkFromReply(c.Message(), replyMsg)
link := buildMessageLinkFromReply(c.Message(), replyMsg) directLink := extractDirectLink(replyMsg)
// 创建条目 // 创建条目
entry, err := b.storage.CreateEntry(category, title, link) entry, err := b.storage.CreateEntry(category, title, channelLink, directLink)
if err != nil { if err != nil {
return c.Reply(fmt.Sprintf("❌ 保存失败: %v", err)) return c.Reply(fmt.Sprintf("❌ 保存失败: %v", err))
} }
@@ -81,7 +84,7 @@ func (b *Bot) handleQuickPost(c tele.Context, replyMsg *tele.Message, payload st
b.toc.TriggerUpdate() b.toc.TriggerUpdate()
return c.Reply(fmt.Sprintf("✅ 已添加\n\nID: `%s`\n分类: %s\n标题: %s\n链接: %s", return c.Reply(fmt.Sprintf("✅ 已添加\n\nID: `%s`\n分类: %s\n标题: %s\n链接: %s",
entry.ID, entry.Category, entry.Title, entry.Link), tele.ModeMarkdown) entry.ID, entry.Category, entry.Title, entry.MessageLink()), tele.ModeMarkdown)
} }
func buildMessageLinkFromReply(currentMsg, replyMsg *tele.Message) string { func buildMessageLinkFromReply(currentMsg, replyMsg *tele.Message) string {
@@ -116,7 +119,7 @@ func (b *Bot) handleTextInput(c tele.Context) error {
state := b.states.Get(c.Sender().ID) state := b.states.Get(c.Sender().ID)
// 私聊收到转发消息,直接启动投稿流程(无需先 /post // 私聊收到转发消息,直接启动投稿流程(无需先 /post
if isForwarded && b.cfg.IsAdmin(c.Sender().ID) { if isForwarded && b.isAdmin(c.Sender().ID) {
if state == nil { if state == nil {
b.states.StartPost(c.Sender().ID) b.states.StartPost(c.Sender().ID)
} }
@@ -138,7 +141,7 @@ func (b *Bot) handleTextInput(c tele.Context) error {
} }
func (b *Bot) handleForwarded(c tele.Context) error { func (b *Bot) handleForwarded(c tele.Context) error {
if !b.cfg.IsAdmin(c.Sender().ID) { if !b.isAdmin(c.Sender().ID) {
return nil return nil
} }
@@ -270,7 +273,7 @@ func (b *Bot) buildCategoryKeyboard(categories []storage.Category) *tele.ReplyMa
} }
func (b *Bot) handleCallback(c tele.Context) error { func (b *Bot) handleCallback(c tele.Context) error {
if !b.cfg.IsAdmin(c.Sender().ID) { if !b.isAdmin(c.Sender().ID) {
return c.Respond(&tele.CallbackResponse{Text: "无权限"}) return c.Respond(&tele.CallbackResponse{Text: "无权限"})
} }
@@ -437,10 +440,10 @@ func (b *Bot) handleConfirmCallback(c tele.Context, userID int64, action string)
return c.Respond(&tele.CallbackResponse{Text: "发送失败"}) return c.Respond(&tele.CallbackResponse{Text: "发送失败"})
} }
// 使用频道消息的链接 channelLink := b.buildChannelLink(channelMsg.ID)
link := b.buildChannelLink(channelMsg.ID) directLink := extractDirectLinkFromState(state)
entry, err := b.storage.CreateEntry(state.SelectedCat, title, link) entry, err := b.storage.CreateEntry(state.SelectedCat, title, channelLink, directLink)
if err != nil { if err != nil {
c.Edit(fmt.Sprintf("❌ 保存失败: %v", err)) c.Edit(fmt.Sprintf("❌ 保存失败: %v", err))
return c.Respond(&tele.CallbackResponse{Text: "保存失败"}) return c.Respond(&tele.CallbackResponse{Text: "保存失败"})
@@ -519,6 +522,56 @@ func extractTitle(msg *tele.Message) string {
return title return title
} }
func extractDirectLinkFromMessages(msgs []*tele.Message) string {
for _, msg := range msgs {
if msg == nil {
continue
}
if link := extractDirectLink(msg); link != "" {
return link
}
}
return ""
}
func extractDirectLinkFromState(state *PostState) string {
if state == nil {
return ""
}
if link := extractDirectLink(state.ForwardedMsg); link != "" {
return link
}
return extractDirectLinkFromMessages(state.ForwardedMsgs)
}
func extractDirectLink(msg *tele.Message) string {
if msg == nil {
return ""
}
for _, entity := range msg.Entities {
if entity.Type == tele.EntityTextLink && entity.URL != "" {
return entity.URL
}
}
for _, entity := range msg.CaptionEntities {
if entity.Type == tele.EntityTextLink && entity.URL != "" {
return entity.URL
}
}
for _, text := range []string{msg.Text, msg.Caption} {
if text == "" {
continue
}
if match := directURLPattern.FindString(text); match != "" {
return match
}
}
return ""
}
func buildMessageLink(msg *tele.Message) string { func buildMessageLink(msg *tele.Message) string {
if msg.OriginalChat == nil { if msg.OriginalChat == nil {
return "" return ""

View File

@@ -39,7 +39,11 @@ func (m *Manager) Render() (string, error) {
sb.WriteString(" <i>暂无内容</i>\n") sb.WriteString(" <i>暂无内容</i>\n")
} else { } else {
for _, entry := range entries { for _, entry := range entries {
sb.WriteString(fmt.Sprintf(" • <a href=\"%s\">%s</a>\n", entry.Link, html.EscapeString(entry.Title))) sb.WriteString(fmt.Sprintf(" • <a href=\"%s\">%s</a>", html.EscapeString(entry.MessageLink()), html.EscapeString(entry.Title)))
if entry.HasDirectLink() {
sb.WriteString(fmt.Sprintf(" <a href=\"%s\">[直达]</a>", html.EscapeString(entry.DirectLink)))
}
sb.WriteString("\n")
} }
} }
sb.WriteString("\n") sb.WriteString("\n")