diff --git a/plugin/telegram/handle.go b/plugin/telegram/handle.go index d5581a78..ca77e7c7 100644 --- a/plugin/telegram/handle.go +++ b/plugin/telegram/handle.go @@ -13,7 +13,6 @@ func (b *Bot) handleSingleMessages(ctx context.Context, messages []Message) erro if err != nil { return err } - if attachment != nil { attachments = append(attachments, *attachment) } @@ -33,7 +32,7 @@ func (b *Bot) handleGroupMessages(ctx context.Context, groupMessages []Message) messages := make(map[string]Message, len(groupMessages)) attachments := make(map[string][]Attachment, len(groupMessages)) - // Group all captions, blobs and messages + // Group all captions, blobs and messages. for _, message := range groupMessages { groupID := *message.MediaGroupID @@ -53,9 +52,9 @@ func (b *Bot) handleGroupMessages(ctx context.Context, groupMessages []Message) } } - // Handle each group message + // Handle each group message. for groupID, message := range messages { - // replace Caption with all Caption in the group + // replace Caption with all Caption in the group. caption := captions[groupID] message.Caption = &caption err := b.handler.MessageHandle(ctx, b, message, attachments[groupID]) diff --git a/plugin/telegram/message_entity.go b/plugin/telegram/message_entity.go index ebc66d16..8809e28c 100644 --- a/plugin/telegram/message_entity.go +++ b/plugin/telegram/message_entity.go @@ -16,6 +16,7 @@ const ( Strikethrough = "strikethrough" // “strikethrough” (strikethrough text) Code = "code" // “code” (monowidth string) Pre = "pre" // “pre” (monowidth block) + Spoiler = "spoiler" // “spoiler” (hidden text) TextLink = "text_link" // “text_link” (for clickable text URLs) TextMention = "text_mention" // “text_mention” (for users without usernames) ) diff --git a/server/integration/telegram.go b/server/integration/telegram.go index 96ba9e71..20f85ac8 100644 --- a/server/integration/telegram.go +++ b/server/integration/telegram.go @@ -6,17 +6,22 @@ import ( "encoding/json" "fmt" "path/filepath" + "slices" "strconv" "time" "unicode/utf16" "github.com/lithammer/shortuuid/v4" "github.com/pkg/errors" + "github.com/yourselfhosted/gomark/ast" + "github.com/yourselfhosted/gomark/parser" + "github.com/yourselfhosted/gomark/parser/tokenizer" "github.com/usememos/memos/plugin/telegram" "github.com/usememos/memos/plugin/webhook" storepb "github.com/usememos/memos/proto/gen/store" apiv1 "github.com/usememos/memos/server/route/api/v1" + apiv2 "github.com/usememos/memos/server/route/api/v2" "github.com/usememos/memos/store" ) @@ -48,6 +53,7 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, return errors.Wrap(err, "Failed to SendReplyMessage") } + messageSenderID := strconv.FormatInt(message.From.ID, 10) var creatorID int32 userSettingList, err := t.store.ListUserSettings(ctx, &store.FindUserSetting{ Key: storepb.UserSettingKey_USER_SETTING_TELEGRAM_USER_ID, @@ -56,11 +62,12 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, return errors.Wrap(err, "Failed to find userSettingList") } for _, userSetting := range userSettingList { - if userSetting.GetTelegramUserId() == strconv.FormatInt(message.From.ID, 10) { + if userSetting.GetTelegramUserId() == messageSenderID { creatorID = userSetting.UserId } } + // If creatorID is not found, ask the user to set the telegram userid in UserSetting of memos. if creatorID == 0 { _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Please set your telegram userid %d in UserSetting of memos", message.From.ID), nil) return err @@ -71,26 +78,46 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, CreatorID: creatorID, Visibility: store.Private, } - if message.Text != nil { create.Content = convertToMarkdown(*message.Text, message.Entities) } - if message.Caption != nil { create.Content = convertToMarkdown(*message.Caption, message.CaptionEntities) } - if message.ForwardFromChat != nil { create.Content += fmt.Sprintf("\n\n[Message link](%s)", message.GetMessageLink()) } - memoMessage, err := t.store.CreateMemo(ctx, create) if err != nil { _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Failed to CreateMemo: %s", err), nil) return err } - // create resources + // Dynamically upsert tags from memo content. + nodes, err := parser.Parse(tokenizer.Tokenize(create.Content)) + if err != nil { + return errors.Wrap(err, "Failed to parse content") + } + tags := []string{} + apiv2.TraverseASTNodes(nodes, func(node ast.Node) { + if tagNode, ok := node.(*ast.Tag); ok { + tag := tagNode.Content + if !slices.Contains(tags, tag) { + tags = append(tags, tag) + } + } + }) + for _, tag := range tags { + _, err := t.store.UpsertTag(ctx, &store.Tag{ + Name: tag, + CreatorID: creatorID, + }) + if err != nil { + return errors.Wrap(err, "Failed to upsert tag") + } + } + + // Create memo related resources. for _, attachment := range attachments { // Fill the common field of create create := store.Resource{ @@ -117,9 +144,7 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, keyboard := generateKeyboardForMemoID(memoMessage.ID) _, err = bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Saved as %s Memo %d", memoMessage.Visibility, memoMessage.ID), keyboard) - _ = t.dispatchMemoRelatedWebhook(ctx, *memoMessage, "memos.memo.created") - return err } @@ -233,6 +258,9 @@ func convertToMarkdown(text string, messageEntities []telegram.MessageEntity) st case telegram.TextLink: before = "[" after = fmt.Sprintf(`](%s)`, e.URL) + case telegram.Spoiler: + before = "||" + after = "||" } if before != "" { diff --git a/server/route/api/v2/tag_service.go b/server/route/api/v2/tag_service.go index 79f892bf..3e888a86 100644 --- a/server/route/api/v2/tag_service.go +++ b/server/route/api/v2/tag_service.go @@ -111,7 +111,7 @@ func (s *APIV2Service) RenameTag(ctx context.Context, request *apiv2pb.RenameTag if err != nil { return nil, status.Errorf(codes.Internal, "failed to parse memo: %v", err) } - traverseASTNodes(nodes, func(node ast.Node) { + TraverseASTNodes(nodes, func(node ast.Node) { if tag, ok := node.(*ast.Tag); ok && tag.Content == request.OldName { tag.Content = request.NewName } @@ -215,7 +215,7 @@ func (s *APIV2Service) GetTagSuggestions(ctx context.Context, request *apiv2pb.G } // Dynamically upsert tags from memo content. - traverseASTNodes(nodes, func(node ast.Node) { + TraverseASTNodes(nodes, func(node ast.Node) { if tagNode, ok := node.(*ast.Tag); ok { tag := tagNode.Content if !slices.Contains(tagNameList, tag) { @@ -248,24 +248,24 @@ func (s *APIV2Service) convertTagFromStore(ctx context.Context, tag *store.Tag) }, nil } -func traverseASTNodes(nodes []ast.Node, fn func(ast.Node)) { +func TraverseASTNodes(nodes []ast.Node, fn func(ast.Node)) { for _, node := range nodes { fn(node) switch n := node.(type) { case *ast.Paragraph: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.Heading: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.Blockquote: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.OrderedList: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.UnorderedList: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.TaskList: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) case *ast.Bold: - traverseASTNodes(n.Children, fn) + TraverseASTNodes(n.Children, fn) } } }