feat: set memo visibility in telegram (#1824)

* Add telegram.Bot in MessageHandler

* Change single message handler like group messages

* Move message notify wrapper from plugin to server

* Add keyboard buttons on Telegram reply message

* Add support to telegram CallbackQuery update

* Set visibility in callbackQuery

* Change original reply message after callbackQuery

---------

Co-authored-by: Athurg Feng <athurg@gooth.org>
pull/1832/head
Athurg Gooth 2 years ago committed by GitHub
parent 8f7001cd9f
commit 4d59689126
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,21 @@
package telegram
import (
"context"
"net/url"
)
// AnswerCallbackQuery make an answerCallbackQuery api request.
func (b *Bot) AnswerCallbackQuery(ctx context.Context, callbackQueryID, text string) error {
formData := url.Values{
"callback_query_id": {callbackQueryID},
"text": {text},
}
err := b.postForm(ctx, "/answerCallbackQuery", formData, nil)
if err != nil {
return err
}
return nil
}

@ -2,18 +2,32 @@ package telegram
import ( import (
"context" "context"
"encoding/json"
"fmt"
"net/url" "net/url"
"strconv" "strconv"
) )
// EditMessage make an editMessageText api request. // EditMessage make an editMessageText api request.
func (b *Bot) EditMessage(ctx context.Context, chatID, messageID int, text string) (*Message, error) { func (b *Bot) EditMessage(ctx context.Context, chatID, messageID int, text string, inlineKeyboards [][]InlineKeyboardButton) (*Message, error) {
formData := url.Values{ formData := url.Values{
"message_id": {strconv.Itoa(messageID)}, "message_id": {strconv.Itoa(messageID)},
"chat_id": {strconv.Itoa(chatID)}, "chat_id": {strconv.Itoa(chatID)},
"text": {text}, "text": {text},
} }
if len(inlineKeyboards) > 0 {
var markup struct {
InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
}
markup.InlineKeyboard = inlineKeyboards
data, err := json.Marshal(markup)
if err != nil {
return nil, fmt.Errorf("fail to encode inlineKeyboard: %s", err)
}
formData.Set("reply_markup", string(data))
}
var result Message var result Message
err := b.postForm(ctx, "/editMessageText", formData, &result) err := b.postForm(ctx, "/editMessageText", formData, &result)
if err != nil { if err != nil {

@ -13,7 +13,8 @@ import (
type Handler interface { type Handler interface {
BotToken(ctx context.Context) string BotToken(ctx context.Context) string
MessageHandle(ctx context.Context, message Message, blobs map[string][]byte) error MessageHandle(ctx context.Context, bot *Bot, message Message, blobs map[string][]byte) error
CallbackQueryHandle(ctx context.Context, bot *Bot, callbackQuery CallbackQuery) error
} }
type Bot struct { type Bot struct {
@ -44,13 +45,24 @@ func (b *Bot) Start(ctx context.Context) {
continue continue
} }
singleMessages := make([]Message, 0, len(updates))
groupMessages := make([]Message, 0, len(updates)) groupMessages := make([]Message, 0, len(updates))
for _, update := range updates { for _, update := range updates {
offset = update.UpdateID + 1 offset = update.UpdateID + 1
if update.Message == nil {
// handle CallbackQuery update
if update.CallbackQuery != nil {
err := b.handler.CallbackQueryHandle(ctx, b, *update.CallbackQuery)
if err != nil {
log.Error("fail to handle CallbackQuery", zap.Error(err))
}
continue continue
} }
// handle Message update
if update.Message != nil {
message := *update.Message message := *update.Message
// skip message other than text or photo // skip message other than text or photo
@ -68,13 +80,16 @@ func (b *Bot) Start(ctx context.Context) {
continue continue
} }
err = b.handleSingleMessage(ctx, message) singleMessages = append(singleMessages, message)
if err != nil {
log.Error(fmt.Sprintf("fail to handleSingleMessage for messageID=%d", message.MessageID), zap.Error(err))
continue continue
} }
} }
err = b.handleSingleMessages(ctx, singleMessages)
if err != nil {
log.Error("fail to handle singleMessage", zap.Error(err))
}
err = b.handleGroupMessages(ctx, groupMessages) err = b.handleGroupMessages(ctx, groupMessages)
if err != nil { if err != nil {
log.Error("fail to handle plain text message", zap.Error(err)) log.Error("fail to handle plain text message", zap.Error(err))

@ -0,0 +1,11 @@
package telegram
type CallbackQuery struct {
ID string `json:"id"`
From User `json:"from"`
Message *Message `json:"message"`
InlineMessageID string `json:"inline_message_id"`
ChatInstance string `json:"chat_instance"`
Data string `json:"data"`
GameShortName string `json:"game_short_name"`
}

@ -3,50 +3,26 @@ package telegram
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/usememos/memos/common/log"
"go.uber.org/zap"
)
// notice message send to telegram.
const (
workingMessage = "Working on send your memo..."
successMessage = "Success"
) )
// handleSingleMessage handle a message not belongs to group. // handleSingleMessages handle single messages not belongs to group.
func (b *Bot) handleSingleMessage(ctx context.Context, message Message) error { func (b *Bot) handleSingleMessages(ctx context.Context, messages []Message) error {
reply, err := b.SendReplyMessage(ctx, message.Chat.ID, message.MessageID, workingMessage) for _, message := range messages {
if err != nil {
return fmt.Errorf("fail to SendReplyMessage: %s", err)
}
var blobs map[string][]byte var blobs map[string][]byte
// download blob if need // download blob if provided
if len(message.Photo) > 0 { if len(message.Photo) > 0 {
filepath, blob, err := b.downloadFileID(ctx, message.GetMaxPhotoFileID()) filepath, blob, err := b.downloadFileID(ctx, message.GetMaxPhotoFileID())
if err != nil { if err != nil {
log.Error("fail to downloadFileID", zap.Error(err)) return err
_, err = b.EditMessage(ctx, message.Chat.ID, reply.MessageID, err.Error())
if err != nil {
return fmt.Errorf("fail to EditMessage: %s", err)
}
return fmt.Errorf("fail to downloadFileID: %s", err)
} }
blobs = map[string][]byte{filepath: blob} blobs = map[string][]byte{filepath: blob}
} }
err = b.handler.MessageHandle(ctx, message, blobs) err := b.handler.MessageHandle(ctx, b, message, blobs)
if err != nil { if err != nil {
if _, err := b.EditMessage(ctx, message.Chat.ID, reply.MessageID, err.Error()); err != nil { return err
return fmt.Errorf("fail to EditMessage: %s", err)
} }
return fmt.Errorf("fail to MessageHandle: %s", err)
}
if _, err := b.EditMessage(ctx, message.Chat.ID, reply.MessageID, successMessage); err != nil {
return fmt.Errorf("fail to EditMessage: %s", err)
} }
return nil return nil
@ -80,23 +56,12 @@ func (b *Bot) handleGroupMessages(ctx context.Context, groupMessages []Message)
// Handle each group message // Handle each group message
for groupID, message := range messages { for groupID, message := range messages {
reply, err := b.SendReplyMessage(ctx, message.Chat.ID, message.MessageID, workingMessage)
if err != nil {
return fmt.Errorf("fail to SendReplyMessage: %s", err)
}
// replace Caption with all Caption in the group // replace Caption with all Caption in the group
caption := captions[groupID] caption := captions[groupID]
message.Caption = &caption message.Caption = &caption
if err := b.handler.MessageHandle(ctx, message, blobs[groupID]); err != nil { err := b.handler.MessageHandle(ctx, b, message, blobs[groupID])
if _, err = b.EditMessage(ctx, message.Chat.ID, reply.MessageID, err.Error()); err != nil { if err != nil {
return fmt.Errorf("fail to EditMessage: %s", err) return err
}
return fmt.Errorf("fail to MessageHandle: %s", err)
}
if _, err := b.EditMessage(ctx, message.Chat.ID, reply.MessageID, successMessage); err != nil {
return fmt.Errorf("fail to EditMessage: %s", err)
} }
} }

@ -0,0 +1,6 @@
package telegram
type InlineKeyboardButton struct {
Text string `json:"text"`
CallbackData string `json:"callback_data"`
}

@ -3,4 +3,5 @@ package telegram
type Update struct { type Update struct {
UpdateID int `json:"update_id"` UpdateID int `json:"update_id"`
Message *Message `json:"message"` Message *Message `json:"message"`
CallbackQuery *CallbackQuery `json:"callback_query"`
} }

@ -25,13 +25,24 @@ func (t *telegramHandler) BotToken(ctx context.Context) string {
return t.store.GetSystemSettingValueOrDefault(&ctx, api.SystemSettingTelegramBotTokenName, "") return t.store.GetSystemSettingValueOrDefault(&ctx, api.SystemSettingTelegramBotTokenName, "")
} }
func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Message, blobs map[string][]byte) error { const (
workingMessage = "Working on send your memo..."
successMessage = "Success"
)
func (t *telegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, message telegram.Message, blobs map[string][]byte) error {
reply, err := bot.SendReplyMessage(ctx, message.Chat.ID, message.MessageID, workingMessage)
if err != nil {
return fmt.Errorf("fail to SendReplyMessage: %s", err)
}
var creatorID int var creatorID int
userSettingList, err := t.store.FindUserSettingList(ctx, &api.UserSettingFind{ userSettingList, err := t.store.FindUserSettingList(ctx, &api.UserSettingFind{
Key: api.UserSettingTelegramUserIDKey, Key: api.UserSettingTelegramUserIDKey,
}) })
if err != nil { if err != nil {
return fmt.Errorf("Fail to find memo user: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Fail to find memo user: %s", err), nil)
return err
} }
for _, userSetting := range userSettingList { for _, userSetting := range userSettingList {
var value string var value string
@ -45,7 +56,8 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Me
} }
if creatorID == 0 { if creatorID == 0 {
return fmt.Errorf("Please set your telegram userid %d in UserSetting of Memos", message.From.ID) _, 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
} }
// create memo // create memo
@ -63,11 +75,13 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Me
memoMessage, err := t.store.CreateMemo(ctx, convertCreateMemoRequestToMemoMessage(&memoCreate)) memoMessage, err := t.store.CreateMemo(ctx, convertCreateMemoRequestToMemoMessage(&memoCreate))
if err != nil { if err != nil {
return fmt.Errorf("failed to CreateMemo: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateMemo: %s", err), nil)
return err
} }
if err := createMemoCreateActivity(ctx, t.store, memoMessage); err != nil { if err := createMemoCreateActivity(ctx, t.store, memoMessage); err != nil {
return fmt.Errorf("failed to createMemoCreateActivity: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to createMemoCreateActivity: %s", err), nil)
return err
} }
// create resources // create resources
@ -90,10 +104,12 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Me
} }
resource, err := t.store.CreateResource(ctx, &resourceCreate) resource, err := t.store.CreateResource(ctx, &resourceCreate)
if err != nil { if err != nil {
return fmt.Errorf("failed to CreateResource: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to CreateResource: %s", err), nil)
return err
} }
if err := createResourceCreateActivity(ctx, t.store, resource); err != nil { if err := createResourceCreateActivity(ctx, t.store, resource); err != nil {
return fmt.Errorf("failed to createResourceCreateActivity: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to createResourceCreateActivity: %s", err), nil)
return err
} }
_, err = t.store.UpsertMemoResource(ctx, &api.MemoResourceUpsert{ _, err = t.store.UpsertMemoResource(ctx, &api.MemoResourceUpsert{
@ -101,8 +117,57 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Me
ResourceID: resource.ID, ResourceID: resource.ID,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to UpsertMemoResource: %s", err) _, err := bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("failed to UpsertMemoResource: %s", err), nil)
return err
}
}
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)
return err
}
func (t *telegramHandler) CallbackQueryHandle(ctx context.Context, bot *telegram.Bot, callbackQuery telegram.CallbackQuery) error {
var memoID int
var visibility store.Visibility
n, err := fmt.Sscanf(callbackQuery.Data, "%s %d", &visibility, &memoID)
if err != nil || n != 2 {
return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("fail to parse callbackQuery.Data %s", callbackQuery.Data))
}
update := store.UpdateMemoMessage{
ID: memoID,
Visibility: &visibility,
}
err = t.store.UpdateMemo(ctx, &update)
if err != nil {
return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("fail to call UpdateMemo %s", err))
}
keyboard := generateKeyboardForMemoID(memoID)
_, err = bot.EditMessage(ctx, callbackQuery.Message.Chat.ID, callbackQuery.Message.MessageID, fmt.Sprintf("Saved as %s Memo %d", visibility, memoID), keyboard)
if err != nil {
return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("fail to EditMessage %s", err))
}
return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("Success change Memo %d to %s", memoID, visibility))
}
func generateKeyboardForMemoID(id int) [][]telegram.InlineKeyboardButton {
allVisibility := []store.Visibility{
store.Public,
store.Protected,
store.Private,
} }
buttons := make([]telegram.InlineKeyboardButton, 0, len(allVisibility))
for _, v := range allVisibility {
button := telegram.InlineKeyboardButton{
Text: v.String(),
CallbackData: fmt.Sprintf("%s %d", v, id),
}
buttons = append(buttons, button)
} }
return nil
return [][]telegram.InlineKeyboardButton{buttons}
} }

Loading…
Cancel
Save