This commit is contained in:
dela
2026-02-04 22:33:45 +08:00
commit d82badc6e3
16 changed files with 2261 additions and 0 deletions

114
internal/toc/manager.go Normal file
View File

@@ -0,0 +1,114 @@
package toc
import (
"log"
"sync"
"time"
"tgchanbot/internal/storage"
tele "gopkg.in/telebot.v3"
)
type Manager struct {
storage *storage.Storage
bot *tele.Bot
chanID int64
debounce time.Duration
mu sync.Mutex
pending bool
timer *time.Timer
stopCh chan struct{}
}
func NewManager(store *storage.Storage, bot *tele.Bot, chanID int64, debounce time.Duration) *Manager {
return &Manager{
storage: store,
bot: bot,
chanID: chanID,
debounce: debounce,
stopCh: make(chan struct{}),
}
}
func (m *Manager) TriggerUpdate() {
m.mu.Lock()
defer m.mu.Unlock()
m.pending = true
if m.timer != nil {
m.timer.Stop()
}
m.timer = time.AfterFunc(m.debounce, func() {
m.doUpdate()
})
}
func (m *Manager) doUpdate() {
m.mu.Lock()
m.pending = false
m.mu.Unlock()
content, err := m.Render()
if err != nil {
log.Printf("TOC render error: %v", err)
return
}
if err := m.updateMessage(content); err != nil {
log.Printf("TOC update error: %v", err)
}
}
func (m *Manager) updateMessage(content string) error {
chat := &tele.Chat{ID: m.chanID}
msgID, err := m.storage.GetTocMsgID()
if err != nil {
return err
}
if msgID == 0 {
msg, err := m.bot.Send(chat, content, tele.ModeMarkdown, tele.NoPreview)
if err != nil {
return err
}
return m.storage.SetTocMsgID(msg.ID)
}
existingMsg := &tele.Message{
ID: msgID,
Chat: chat,
}
_, err = m.bot.Edit(existingMsg, content, tele.ModeMarkdown, tele.NoPreview)
if err != nil {
if err == tele.ErrMessageNotModified {
return nil
}
if err.Error() == "telegram: message to edit not found (400)" {
msg, err := m.bot.Send(chat, content, tele.ModeMarkdown, tele.NoPreview)
if err != nil {
return err
}
return m.storage.SetTocMsgID(msg.ID)
}
return err
}
return nil
}
func (m *Manager) Stop() {
m.mu.Lock()
defer m.mu.Unlock()
if m.timer != nil {
m.timer.Stop()
}
close(m.stopCh)
}

64
internal/toc/renderer.go Normal file
View File

@@ -0,0 +1,64 @@
package toc
import (
"fmt"
"strings"
)
func (m *Manager) Render() (string, error) {
categories, err := m.storage.ListCategories()
if err != nil {
return "", err
}
entriesByCategory, err := m.storage.GetEntriesByCategory()
if err != nil {
return "", err
}
var sb strings.Builder
sb.WriteString("📚 **频道目录**\n")
sb.WriteString("━━━━━━━━━━━━━━━\n\n")
if len(categories) == 0 {
sb.WriteString("_暂无分类_")
return sb.String(), nil
}
for _, cat := range categories {
entries := entriesByCategory[cat.Name]
sb.WriteString(fmt.Sprintf("📁 **%s**", cat.Name))
if len(entries) > 0 {
sb.WriteString(fmt.Sprintf(" (%d)", len(entries)))
}
sb.WriteString("\n")
if len(entries) == 0 {
sb.WriteString(" _暂无内容_\n")
} else {
for _, entry := range entries {
sb.WriteString(fmt.Sprintf(" • [%s](%s)\n", escapeMarkdown(entry.Title), entry.Link))
}
}
sb.WriteString("\n")
}
sb.WriteString("━━━━━━━━━━━━━━━\n")
sb.WriteString("_自动生成_")
return sb.String(), nil
}
func escapeMarkdown(s string) string {
replacer := strings.NewReplacer(
"[", "\\[",
"]", "\\]",
"(", "\\(",
")", "\\)",
"*", "\\*",
"_", "\\_",
"`", "\\`",
)
return replacer.Replace(s)
}