From 270369ae0ab523a00cb84bdcedbbf7126b272b0b Mon Sep 17 00:00:00 2001 From: dela Date: Thu, 5 Feb 2026 21:29:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=BD=AC=E5=8F=91=E8=B7=B3?= =?UTF-8?q?=E6=94=B6=E8=97=8F=EF=BC=9B=E4=BF=AE=E5=A4=8D=E8=8B=A5=E5=B9=B2?= =?UTF-8?q?=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + internal/storage/category.go | 56 +++++++++++++++++++++++++++++ internal/telegram/bot.go | 2 ++ internal/telegram/handlers_cat.go | 18 ++++++++++ internal/telegram/handlers_entry.go | 9 ++--- internal/telegram/handlers_post.go | 21 +++++++---- 6 files changed, 96 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e67c380..82921a9 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ toc: |------|------| | `/cat_add <名称> [排序]` | 创建分类 | | `/cat_del <名称>` | 删除分类 | +| `/cat_rename <旧名称> <新名称>` | 重命名分类(自动更新所有条目) | | `/cat_list` | 列出所有分类 | ### 管理员管理 (仅超级管理员) diff --git a/internal/storage/category.go b/internal/storage/category.go index debd5ea..0b80e66 100644 --- a/internal/storage/category.go +++ b/internal/storage/category.go @@ -105,6 +105,62 @@ func (s *Storage) UpdateCategoryOrder(name string, order int) error { }) } +func (s *Storage) RenameCategory(oldName, newName string) error { + return s.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketCategories) + + // 检查旧分类是否存在 + data := b.Get([]byte(oldName)) + if data == nil { + return fmt.Errorf("category %q not found", oldName) + } + + // 检查新名称是否已存在 + if b.Get([]byte(newName)) != nil { + return fmt.Errorf("category %q already exists", newName) + } + + // 解码旧分类 + var cat Category + if err := decodeJSON(data, &cat); err != nil { + return err + } + + // 更新名称 + cat.Name = newName + newData, err := encodeJSON(cat) + if err != nil { + return err + } + + // 删除旧键,写入新键 + if err := b.Delete([]byte(oldName)); err != nil { + return err + } + if err := b.Put([]byte(newName), newData); err != nil { + return err + } + + // 同步更新所有条目的分类名 + entries := tx.Bucket(bucketEntries) + return entries.ForEach(func(k, v []byte) error { + var entry Entry + if err := decodeJSON(v, &entry); err != nil { + return err + } + if entry.Category == oldName { + entry.Category = newName + newEntryData, err := encodeJSON(entry) + if err != nil { + return err + } + return entries.Put(k, newEntryData) + } + return nil + }) + }) +} + func (s *Storage) CategoryExists(name string) bool { exists := false s.db.View(func(tx *bolt.Tx) error { diff --git a/internal/telegram/bot.go b/internal/telegram/bot.go index 126020d..786305b 100644 --- a/internal/telegram/bot.go +++ b/internal/telegram/bot.go @@ -51,6 +51,7 @@ func (b *Bot) setupRoutes() { // Category management adminOnly.Handle("/cat_add", b.handleCatAdd) adminOnly.Handle("/cat_del", b.handleCatDel) + adminOnly.Handle("/cat_rename", b.handleCatRename) adminOnly.Handle("/cat_list", b.handleCatList) // Post flow @@ -97,6 +98,7 @@ func (b *Bot) setCommands() { {Text: "cat_list", Description: "分类列表"}, {Text: "cat_add", Description: "添加分类 <名称> [排序]"}, {Text: "cat_del", Description: "删除分类 <名称>"}, + {Text: "cat_rename", Description: "重命名分类 <旧名称> <新名称>"}, {Text: "del", Description: "删除条目 "}, {Text: "edit", Description: "编辑标题 <新标题>"}, {Text: "move", Description: "移动条目 <新分类>"}, diff --git a/internal/telegram/handlers_cat.go b/internal/telegram/handlers_cat.go index 42ff833..698f102 100644 --- a/internal/telegram/handlers_cat.go +++ b/internal/telegram/handlers_cat.go @@ -42,6 +42,24 @@ func (b *Bot) handleCatDel(c tele.Context) error { return c.Reply(fmt.Sprintf("✅ 分类 [%s] 已删除", name)) } +func (b *Bot) handleCatRename(c tele.Context) error { + args := strings.Fields(c.Message().Payload) + if len(args) < 2 { + return c.Reply("用法: /cat_rename <旧名称> <新名称>\n例如: /cat_rename iOS Apple") + } + + oldName := args[0] + newName := args[1] + + if err := b.storage.RenameCategory(oldName, newName); err != nil { + return c.Reply(fmt.Sprintf("❌ 重命名失败: %v", err)) + } + + b.toc.TriggerUpdate() + + return c.Reply(fmt.Sprintf("✅ 分类 [%s] 已重命名为 [%s]", oldName, newName)) +} + func (b *Bot) handleCatList(c tele.Context) error { categories, err := b.storage.ListCategories() if err != nil { diff --git a/internal/telegram/handlers_entry.go b/internal/telegram/handlers_entry.go index 37c963e..7fdbda7 100644 --- a/internal/telegram/handlers_entry.go +++ b/internal/telegram/handlers_entry.go @@ -2,6 +2,7 @@ package telegram import ( "fmt" + "html" "strconv" "strings" @@ -94,7 +95,7 @@ func (b *Bot) handleEntryList(c tele.Context) error { var sb strings.Builder if category != "" { - sb.WriteString(fmt.Sprintf("📋 分类 [%s] 的条目:\n\n", category)) + sb.WriteString(fmt.Sprintf("📋 分类 [%s] 的条目:\n\n", html.EscapeString(category))) } else { sb.WriteString("📋 所有条目:\n\n") } @@ -103,12 +104,12 @@ func (b *Bot) handleEntryList(c tele.Context) error { for _, e := range entries { if category == "" && e.Category != currentCat { currentCat = e.Category - sb.WriteString(fmt.Sprintf("\n【%s】\n", currentCat)) + sb.WriteString(fmt.Sprintf("\n【%s】\n", html.EscapeString(currentCat))) } - sb.WriteString(fmt.Sprintf("• [%s] %s\n", e.ID, e.Title)) + sb.WriteString(fmt.Sprintf("• %s %s\n", e.ID, html.EscapeString(e.Title))) } - return c.Reply(sb.String()) + return c.Reply(sb.String(), tele.ModeHTML) } func (b *Bot) handleRefresh(c tele.Context) error { diff --git a/internal/telegram/handlers_post.go b/internal/telegram/handlers_post.go index 2f78945..77adf73 100644 --- a/internal/telegram/handlers_post.go +++ b/internal/telegram/handlers_post.go @@ -110,8 +110,8 @@ func (b *Bot) handleTextInput(c tele.Context) error { return nil } - // 检查是否为转发消息 - isForwarded := msg.OriginalChat != nil || msg.OriginalSender != nil + // 检查是否为转发消息(OriginalUnixtime 在隐私设置隐藏来源时仍存在) + isForwarded := msg.OriginalChat != nil || msg.OriginalSender != nil || msg.OriginalUnixtime > 0 state := b.states.Get(c.Sender().ID) @@ -148,8 +148,8 @@ func (b *Bot) handleForwarded(c tele.Context) error { } msg := c.Message() - // 检查是否为转发消息(来自频道/群组或个人用户) - if msg.OriginalChat == nil && msg.OriginalSender == nil { + // 检查是否为转发消息(OriginalUnixtime 在隐私设置隐藏来源时仍存在) + if msg.OriginalChat == nil && msg.OriginalSender == nil && msg.OriginalUnixtime == 0 { return c.Reply("❌ 这不是一条转发消息,请转发一条消息给我") } @@ -438,7 +438,7 @@ func (b *Bot) handleConfirmCallback(c tele.Context, userID int64, action string) } // 使用频道消息的链接 - link := buildChannelLink(b.cfg.Channel.ID, channelMsg.ID) + link := b.buildChannelLink(channelMsg.ID) entry, err := b.storage.CreateEntry(state.SelectedCat, title, link) if err != nil { @@ -484,8 +484,15 @@ func (b *Bot) sendAlbumCopy(to *tele.Chat, msgs []*tele.Message) (*tele.Message, return &sentMsgs[0], nil } -func buildChannelLink(channelID int64, msgID int) string { - chatID := channelID +func (b *Bot) buildChannelLink(msgID int) string { + // 尝试获取频道信息,检查是否为公开频道 + chat, err := b.bot.ChatByID(b.cfg.Channel.ID) + if err == nil && chat.Username != "" { + return fmt.Sprintf("https://t.me/%s/%d", chat.Username, msgID) + } + + // 私有频道 fallback + chatID := b.cfg.Channel.ID if chatID < 0 { chatID = -chatID - 1000000000000 }