diff --git a/bot.go b/bot.go index a6c64ba..b2e6c31 100644 --- a/bot.go +++ b/bot.go @@ -9,6 +9,7 @@ import ( "os" "sort" "strings" + "time" "github.com/redis/go-redis/v9" "github.com/vinovest/sqlx" @@ -66,13 +67,15 @@ func LoadSettingsFromEnv() *BotSettings { } type MsgContext struct { - Bot *Bot - Msg *Message - Update *Update - FromID int - Prefix string - Text string - Args []string + Bot *Bot + Msg *Message + Update *Update + From *User + CallbackMsgId int + FromID int + Prefix string + Text string + Args []string } type DatabaseContext struct { @@ -221,6 +224,7 @@ func (b *Bot) Run() { for { queue := b.updateQueue if queue.IsEmpty() { + time.Sleep(time.Millisecond * 25) continue } @@ -265,6 +269,7 @@ func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { } ctx.FromID = update.Message.From.ID + ctx.From = update.Message.From ctx.Msg = update.Message text = strings.TrimSpace(text) prefix, hasPrefix := b.checkPrefixes(text) @@ -298,6 +303,12 @@ func (b *Bot) handleCallback(update *Update, ctx *MsgContext) { return } + ctx.FromID = update.CallbackQuery.From.ID + ctx.From = update.CallbackQuery.From + ctx.Msg = update.CallbackQuery.Message + ctx.CallbackMsgId = update.CallbackQuery.Message.MessageID + ctx.Args = data.Args + for _, plugin := range b.plugins { _, ok := plugin.Payloads[data.Command] if !ok { @@ -325,6 +336,40 @@ type AnswerMessage struct { ctx *MsgContext } +func (ctx *MsgContext) edit(messageId int, text string, keyboard *InlineKeyboard) *AnswerMessage { + params := &EditMessageTextP{ + MessageID: messageId, + ChatID: ctx.Msg.Chat.ID, + Text: text, + ParseMode: ParseMD, + } + if keyboard != nil { + params.ReplyMarkup = keyboard.Get() + } + msg, err := ctx.Bot.EditMessageText(params) + if err != nil { + ctx.Bot.logger.Error(err) + return nil + } + return &AnswerMessage{ + MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: false, + } +} +func (m *AnswerMessage) Edit(text string) *AnswerMessage { + return m.ctx.edit(m.MessageID, text, nil) +} +func (ctx *MsgContext) EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage { + if ctx.CallbackMsgId == 0 { + ctx.Bot.logger.Error("Can't edit non-callback update message") + return nil + } + + return ctx.edit(ctx.CallbackMsgId, text, keyboard) +} +func (ctx *MsgContext) EditCallbackf(format string, keyboard *InlineKeyboard, args ...any) *AnswerMessage { + return ctx.EditCallback(fmt.Sprintf(format, args...), keyboard) +} + func (ctx *MsgContext) answer(text string, keyboard *InlineKeyboard) *AnswerMessage { params := &SendMessageP{ ChatID: ctx.Msg.Chat.ID, @@ -369,31 +414,6 @@ func (ctx *MsgContext) AnswerPhoto(photoId string, text string) *AnswerMessage { } } -func (m *AnswerMessage) Edit(text string) *AnswerMessage { - var msg *Message - var err error - if m.IsMedia { - msg, err = m.ctx.Bot.EditMessageCaption(&EditMessageCaptionP{ - MessageID: m.MessageID, - ChatID: m.ctx.Msg.Chat.ID, - Caption: text, - ParseMode: ParseMD, - }) - } else { - msg, err = m.ctx.Bot.EditMessageText(&EditMessageTextP{ - MessageID: m.MessageID, - ChatID: m.ctx.Msg.Chat.ID, - Text: text, - ParseMode: ParseMD, - }) - } - if err != nil { - m.ctx.Bot.logger.Error(err) - } - m.MessageID = msg.MessageID - return m -} - func (m *AnswerMessage) Delete() { _, err := m.ctx.Bot.DeleteMessage(&DeleteMessageP{ MessageID: m.MessageID, ChatID: m.ctx.Msg.Chat.ID, @@ -420,17 +440,17 @@ func (b *Bot) Logger() *Logger { } type ApiResponse struct { - Ok bool `json:"ok"` - Result map[string]interface{} `json:"result,omitempty"` - Description string `json:"description,omitempty"` - ErrorCode int `json:"error_code,omitempty"` + Ok bool `json:"ok"` + Result map[string]any `json:"result,omitempty"` + Description string `json:"description,omitempty"` + ErrorCode int `json:"error_code,omitempty"` } type ApiResponseA struct { - Ok bool `json:"ok"` - Result []interface{} `json:"result,omitempty"` - Description string `json:"description,omitempty"` - ErrorCode int `json:"error_code,omitempty"` + Ok bool `json:"ok"` + Result []any `json:"result,omitempty"` + Description string `json:"description,omitempty"` + ErrorCode int `json:"error_code,omitempty"` } // request is a low-level call to api. @@ -458,9 +478,10 @@ func (b *Bot) request(methodName string, params any) (map[string]interface{}, er if err != nil { return nil, err } + b.requestLogger.Debug(fmt.Sprintf("RES %s %s", methodName, string(data))) response := new(ApiResponse) - var result map[string]interface{} + var result map[string]any err = json.Unmarshal(data, &response) if err != nil { diff --git a/keyboard.go b/keyboard.go index 14cba31..bdb9e35 100644 --- a/keyboard.go +++ b/keyboard.go @@ -1,49 +1,70 @@ package laniakea -import "encoding/json" +import ( + "encoding/json" + "fmt" +) type InlineKeyboard struct { CurrentLine []InlineKeyboardButton Lines [][]InlineKeyboardButton + maxRow int } -func NewInlineKeyboard() *InlineKeyboard { +func NewInlineKeyboard(maxRow int) *InlineKeyboard { return &InlineKeyboard{ CurrentLine: make([]InlineKeyboardButton, 0), Lines: make([][]InlineKeyboardButton, 0), + maxRow: maxRow, } } func (in *InlineKeyboard) append(button InlineKeyboardButton) *InlineKeyboard { + if len(in.CurrentLine) == in.maxRow { + in.AddLine() + } in.CurrentLine = append(in.CurrentLine, button) return in } func (in *InlineKeyboard) AddUrlButton(text, url string) *InlineKeyboard { return in.append(InlineKeyboardButton{Text: text, URL: url}) } -func (in *InlineKeyboard) AddCallbackButton(text string, data CallbackData) *InlineKeyboard { - return in.append(InlineKeyboardButton{Text: text, CallbackData: data.ToJson()}) +func (in *InlineKeyboard) AddCallbackButton(text string, cmd string, args ...any) *InlineKeyboard { + return in.append(InlineKeyboardButton{Text: text, CallbackData: NewCallbackData(cmd, args...).ToJson()}) } func (in *InlineKeyboard) AddLine() *InlineKeyboard { + if len(in.CurrentLine) == 0 { + return in + } in.Lines = append(in.Lines, in.CurrentLine) in.CurrentLine = make([]InlineKeyboardButton, 0) return in } -func (in *InlineKeyboard) Get() *InlineKeyboardMarkup { +func (in *InlineKeyboard) Get() InlineKeyboardMarkup { if len(in.CurrentLine) > 0 { in.Lines = append(in.Lines, in.CurrentLine) } - return &InlineKeyboardMarkup{ + return InlineKeyboardMarkup{ InlineKeyboard: in.Lines, } } type CallbackData struct { - Command string `json:"cmd"` - Args []any `json:"args"` + Command string `json:"cmd"` + Args []string `json:"args"` } +func NewCallbackData(command string, args ...any) *CallbackData { + stringArgs := make([]string, len(args)) + for i, arg := range args { + stringArgs[i] = fmt.Sprint(arg) + } + return &CallbackData{ + Command: command, + Args: stringArgs, + } +} func (d *CallbackData) ToJson() string { data, err := json.Marshal(d) if err != nil { diff --git a/methods.go b/methods.go index 6fd6040..1695e64 100644 --- a/methods.go +++ b/methods.go @@ -1,11 +1,14 @@ package laniakea -import "fmt" +import ( + "encoding/json" + "fmt" +) -var NoParams = make(map[string]interface{}) +var NoParams = make(map[string]any) func (b *Bot) Updates() ([]*Update, error) { - params := make(map[string]interface{}) + params := make(map[string]any) params["offset"] = b.updateOffset params["timeout"] = 30 params["allowed_updates"] = b.updateTypes @@ -15,12 +18,20 @@ func (b *Bot) Updates() ([]*Update, error) { return nil, err } res := make([]*Update, 0) - for _, u := range data["data"].([]interface{}) { + for _, u := range data["data"].([]any) { updateObj := new(Update) - err = MapToStruct(u.(map[string]interface{}), updateObj) + data, err := json.Marshal(u) if err != nil { return res, err } + err = json.Unmarshal(data, updateObj) + if err != nil { + return res, err + } + //err = MapToStruct(u.(map[string]any), updateObj) + //if err != nil { + // return res, err + //} b.updateOffset = updateObj.UpdateID + 1 err = b.updateQueue.Enqueue(updateObj) if err != nil { @@ -50,20 +61,19 @@ func (b *Bot) GetMe() (*User, error) { } type SendMessageP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id"` - MessageThreadID int `json:"message_thread_id,omitempty"` - ParseMode ParseMode `json:"parse_mode,omitempty"` - Text string `json:"text"` - Entities []*MessageEntity `json:"entities,omitempty"` - LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"` - DisableNotifications bool `json:"disable_notifications,omitempty"` - ProtectContent bool `json:"protect_content,omitempty"` - AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` - MessageEffectID string `json:"message_effect_id,omitempty"` - ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` - ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` - // ReplyKeyboardMarkup *ReplyKeyboardMarkup `json:"reply_markup,omitempty"` + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id"` + MessageThreadID int `json:"message_thread_id,omitempty"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + Text string `json:"text"` + Entities []*MessageEntity `json:"entities,omitempty"` + LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"` + DisableNotifications bool `json:"disable_notifications,omitempty"` + ProtectContent bool `json:"protect_content,omitempty"` + AllowPaidBroadcast bool `json:"allow_paid_broadcast,omitempty"` + MessageEffectID string `json:"message_effect_id,omitempty"` + ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"` + ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` } func (b *Bot) SendMessage(params *SendMessageP) (*Message, error) { @@ -103,12 +113,13 @@ func (b *Bot) SendPhoto(params *SendPhotoP) (*Message, error) { } type EditMessageTextP struct { - BusinessConnectionID string `json:"business_connection_id,omitempty"` - ChatID int `json:"chat_id,omitempty"` - MessageID int `json:"message_id,omitempty"` - InlineMessageID string `json:"inline_message_id,omitempty"` - Text string `json:"text"` - ParseMode ParseMode `json:"parse_mode,omitempty"` + BusinessConnectionID string `json:"business_connection_id,omitempty"` + ChatID int `json:"chat_id,omitempty"` + MessageID int `json:"message_id,omitempty"` + InlineMessageID string `json:"inline_message_id,omitempty"` + Text string `json:"text"` + ParseMode ParseMode `json:"parse_mode,omitempty"` + ReplyMarkup InlineKeyboardMarkup `json:"reply_markup,omitempty"` } func (b *Bot) EditMessageText(params *EditMessageTextP) (*Message, error) { diff --git a/types.go b/types.go index 549eeef..f2fa3b7 100644 --- a/types.go +++ b/types.go @@ -12,9 +12,9 @@ type Update struct { DeletedBusinessMessage *Message `json:"deleted_business_messages,omitempty"` MessageReaction *MessageReactionUpdated `json:"message_reaction,omitempty"` MessageReactionCount *MessageReactionCountUpdated `json:"message_reaction_count,omitempty"` + CallbackQuery *CallbackQuery `json:"callback_query,omitempty"` InlineQuery int ChosenInlineResult int - CallbackQuery *CallbackQuery `json:"callback_query,omitempty"` } type User struct { @@ -44,7 +44,7 @@ type Chat struct { } type MessageReplyMarkup struct { - InlineKeyboard *InlineKeyboardMarkup `json:"inline_keyboard"` + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` } type Message struct { @@ -68,8 +68,6 @@ type InaccessableMessage struct { } type MaybeInaccessibleMessage struct { - Message - InaccessableMessage } type MessageEntity struct { @@ -109,7 +107,7 @@ type LinkPreviewOptions struct { } type InlineKeyboardMarkup struct { - InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` + InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard,omitempty"` } type InlineKeyboardButton struct { @@ -123,9 +121,9 @@ type ReplyKeyboardMarkup struct { } type CallbackQuery struct { - ID string `json:"id"` - From *User `json:"user"` - Message *MaybeInaccessibleMessage `json:"message"` + ID string `json:"id"` + From *User `json:"from"` + Message *Message `json:"message"` Data string `json:"data"` }