diff --git a/internal/storage/entry.go b/internal/storage/entry.go
index 1140e7a..e1bdd8d 100644
--- a/internal/storage/entry.go
+++ b/internal/storage/entry.go
@@ -9,13 +9,15 @@ import (
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{
- ID: shortid.MustGenerate(),
- Category: category,
- Title: title,
- Link: link,
- Timestamp: time.Now(),
+ ID: shortid.MustGenerate(),
+ Category: category,
+ Title: title,
+ Link: channelLink,
+ ChannelLink: channelLink,
+ DirectLink: directLink,
+ Timestamp: time.Now(),
}
err := s.db.Update(func(tx *bolt.Tx) error {
diff --git a/internal/storage/storage.go b/internal/storage/storage.go
index 0142a48..0e1b8ae 100644
--- a/internal/storage/storage.go
+++ b/internal/storage/storage.go
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"time"
bolt "go.etcd.io/bbolt"
@@ -27,11 +28,25 @@ type Category struct {
}
type Entry struct {
- ID string `json:"id"`
- Category string `json:"category"`
- Title string `json:"title"`
- Link string `json:"link"`
- Timestamp time.Time `json:"timestamp"`
+ ID string `json:"id"`
+ Category string `json:"category"`
+ Title string `json:"title"`
+ Link string `json:"link,omitempty"` // 兼容旧数据
+ ChannelLink string `json:"channel_link,omitempty"`
+ DirectLink string `json:"direct_link,omitempty"`
+ 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 {
diff --git a/internal/telegram/handlers_entry.go b/internal/telegram/handlers_entry.go
index 7fdbda7..518107c 100644
--- a/internal/telegram/handlers_entry.go
+++ b/internal/telegram/handlers_entry.go
@@ -128,7 +128,7 @@ func (b *Bot) handleDeleteEntryCallback(c tele.Context, entryID string, deleteCh
// 根据用户选择决定是否删除频道消息
msgDeleted := false
if deleteChannelMsg {
- if msgID := parseMessageIDFromLink(entry.Link); msgID != 0 {
+ if msgID := parseMessageIDFromLink(entry.MessageLink()); 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 {
diff --git a/internal/telegram/handlers_post.go b/internal/telegram/handlers_post.go
index 77adf73..a337417 100644
--- a/internal/telegram/handlers_post.go
+++ b/internal/telegram/handlers_post.go
@@ -2,6 +2,7 @@ package telegram
import (
"fmt"
+ "regexp"
"strings"
"sync"
"time"
@@ -26,6 +27,8 @@ var (
albumTimersMu sync.Mutex
)
+var directURLPattern = regexp.MustCompile(`https?://[^\s<>"']+`)
+
func (b *Bot) handlePost(c tele.Context) error {
msg := c.Message()
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)
}
- // 构建链接
- link := buildMessageLinkFromReply(c.Message(), replyMsg)
+ channelLink := 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 {
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()
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 {
@@ -116,7 +119,7 @@ func (b *Bot) handleTextInput(c tele.Context) error {
state := b.states.Get(c.Sender().ID)
// 私聊收到转发消息,直接启动投稿流程(无需先 /post)
- if isForwarded && b.cfg.IsAdmin(c.Sender().ID) {
+ if isForwarded && b.isAdmin(c.Sender().ID) {
if state == nil {
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 {
- if !b.cfg.IsAdmin(c.Sender().ID) {
+ if !b.isAdmin(c.Sender().ID) {
return nil
}
@@ -270,7 +273,7 @@ func (b *Bot) buildCategoryKeyboard(categories []storage.Category) *tele.ReplyMa
}
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: "无权限"})
}
@@ -437,10 +440,10 @@ func (b *Bot) handleConfirmCallback(c tele.Context, userID int64, action string)
return c.Respond(&tele.CallbackResponse{Text: "发送失败"})
}
- // 使用频道消息的链接
- link := b.buildChannelLink(channelMsg.ID)
+ channelLink := 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 {
c.Edit(fmt.Sprintf("❌ 保存失败: %v", err))
return c.Respond(&tele.CallbackResponse{Text: "保存失败"})
@@ -519,6 +522,56 @@ func extractTitle(msg *tele.Message) string {
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 {
if msg.OriginalChat == nil {
return ""
diff --git a/internal/toc/renderer.go b/internal/toc/renderer.go
index 62a374f..204f8e7 100644
--- a/internal/toc/renderer.go
+++ b/internal/toc/renderer.go
@@ -39,7 +39,11 @@ func (m *Manager) Render() (string, error) {
sb.WriteString(" 暂无内容\n")
} else {
for _, entry := range entries {
- sb.WriteString(fmt.Sprintf(" • %s\n", entry.Link, html.EscapeString(entry.Title)))
+ sb.WriteString(fmt.Sprintf(" • %s", html.EscapeString(entry.MessageLink()), html.EscapeString(entry.Title)))
+ if entry.HasDirectLink() {
+ sb.WriteString(fmt.Sprintf(" [直达]", html.EscapeString(entry.DirectLink)))
+ }
+ sb.WriteString("\n")
}
}
sb.WriteString("\n")