From 7fef8438c520ef85412f1ea6838c1bc6f47ee9f5 Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 14 Sep 2026 11:28:16 +0100 Subject: [PATCH] deps: bump github.com/iamxvbaba/td to v1.3.2 Matches upstream owpengram/owpengram-server's version. v1.3.2 collapses the old per-kind KeyboardButtonClass sum type (KeyboardButton, KeyboardButtonURL, KeyboardButtonCallback, KeyboardButtonRequestPeer, ...) into two unified structs mirroring Telegram's actual current MTProto layer: KeyboardButton (reply keyboards) and KeyboardInlineButton (inline keyboards), each carrying a Text/Style pair plus a Type field (ButtonTypeClass / InlineButtonTypeClass) that now holds what used to be the concrete Go type. Migrated the two call sites (internal/rpc/convert_markup.go, internal/rpc/bots_longtail.go) and their tests to the new shape. Behavior is unchanged -- every button kind (callback, url, url_auth, web_view, switch_inline, copy, request_phone, request_geo_location, request_poll, request_peer, simple_web_view) still round-trips the same domain fields, just read from/written to Type instead of the button's own concrete type. Co-Authored-By: Claude Sonnet 5 --- go.mod | 2 +- go.sum | 4 +- internal/mtprotoedge/bot_callback_e2e_test.go | 14 +- internal/rpc/bots_inline_rpc_test.go | 21 +- internal/rpc/bots_longtail.go | 37 ++-- internal/rpc/bots_longtail_rpc_test.go | 4 +- internal/rpc/convert_markup.go | 179 ++++++++---------- internal/rpc/convert_markup_test.go | 71 +++---- .../rpc/messages_bot_no_state_rpc_test.go | 8 +- 9 files changed, 166 insertions(+), 174 deletions(-) diff --git a/go.mod b/go.mod index ebfec292..f645086f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.19.1 github.com/gotd/ige v0.3.0 github.com/gotd/log/logzap v0.1.1 - github.com/iamxvbaba/td v1.2.1 + github.com/iamxvbaba/td v1.3.2 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/jackc/pgx/v5 v5.9.2 github.com/lestrrat-go/jwx/v3 v3.1.1 diff --git a/go.sum b/go.sum index 54be9510..8f268240 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/iamxvbaba/td v1.2.1 h1:5+Ji1F/tdrN8zUxeeEbTPHBQGSnTDE+UAH+8pQi7O1Y= -github.com/iamxvbaba/td v1.2.1/go.mod h1:INkZJi18XbXtVOrldDnPtmQCJvIXhgDZdbo9MV/NY7M= +github.com/iamxvbaba/td v1.3.2 h1:/EwvDU0oiArAof16WDDGfUXg/w02m2MqzDskCnXakZA= +github.com/iamxvbaba/td v1.3.2/go.mod h1:INkZJi18XbXtVOrldDnPtmQCJvIXhgDZdbo9MV/NY7M= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= diff --git a/internal/mtprotoedge/bot_callback_e2e_test.go b/internal/mtprotoedge/bot_callback_e2e_test.go index dcd34342..5b7e00b3 100644 --- a/internal/mtprotoedge/bot_callback_e2e_test.go +++ b/internal/mtprotoedge/bot_callback_e2e_test.go @@ -230,9 +230,9 @@ func TestBotInlineKeyboardCallbackFlow(t *testing.T) { return fmt.Errorf("bot getUsers(owner) = %d, want 1", len(got)) } ownerSeen := got[0].(*tg.User) - markup := &tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButtonClass{ - &tg.KeyboardButtonCallback{Text: "Press", Data: callbackData}, - &tg.KeyboardButtonURL{Text: "Site", URL: "https://example.com/x"}, + markup := &tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{Buttons: []tg.KeyboardInlineButton{ + {Text: "Press", Type: &tg.InlineButtonTypeCallback{Data: callbackData}}, + {Text: "Site", Type: &tg.InlineButtonTypeURL{URL: "https://example.com/x"}}, }}}} req := &tg.MessagesSendMessageRequest{ Peer: &tg.InputPeerUser{UserID: ownerSeen.ID, AccessHash: ownerSeen.AccessHash}, @@ -312,15 +312,15 @@ func TestBotInlineKeyboardCallbackFlow(t *testing.T) { if !ok || len(inline.Rows) != 1 || len(inline.Rows[0].Buttons) != 2 { t.Fatalf("unexpected markup shape: %#v", rm) } - cbBtn, ok := inline.Rows[0].Buttons[0].(*tg.KeyboardButtonCallback) + cbBtn, ok := inline.Rows[0].Buttons[0].Type.(*tg.InlineButtonTypeCallback) if !ok { - t.Fatalf("first button not callback: %#v", inline.Rows[0].Buttons[0]) + t.Fatalf("first button not callback: %#v", inline.Rows[0].Buttons[0].Type) } if string(cbBtn.Data) != string(callbackData) { t.Fatalf("callback data round-trip mismatch: got %v want %v", cbBtn.Data, callbackData) } - if _, ok := inline.Rows[0].Buttons[1].(*tg.KeyboardButtonURL); !ok { - t.Fatalf("second button not url: %#v", inline.Rows[0].Buttons[1]) + if _, ok := inline.Rows[0].Buttons[1].Type.(*tg.InlineButtonTypeURL); !ok { + t.Fatalf("second button not url: %#v", inline.Rows[0].Buttons[1].Type) } msgID = msg.ID } diff --git a/internal/rpc/bots_inline_rpc_test.go b/internal/rpc/bots_inline_rpc_test.go index 42ad7798..7e135c7d 100644 --- a/internal/rpc/bots_inline_rpc_test.go +++ b/internal/rpc/bots_inline_rpc_test.go @@ -844,8 +844,8 @@ func TestInlineBotArticleTextChannelRoundTrip(t *testing.T) { } editReq := &tg.MessagesEditInlineBotMessageRequest{ID: msgID} editReq.SetMessage("inline group edited") - editReq.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonCallback{Text: "Done", Data: []byte("v2")}}, + editReq.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{ + Buttons: []tg.KeyboardInlineButton{{Text: "Done", Type: &tg.InlineButtonTypeCallback{Data: []byte("v2")}}}, }}}) if ok, err := f.router.onMessagesEditInlineBotMessage(botCtx, editReq); err != nil || !ok { t.Fatalf("channel inline edit = %v,%v, want true,nil", ok, err) @@ -2065,12 +2065,13 @@ func assertTGInlineReplyMarkup(t *testing.T, msg *tg.Message, wantText string, w if len(markup.Rows) != 1 || len(markup.Rows[0].Buttons) != 1 { t.Fatalf("reply_markup rows = %+v, want one callback button", markup.Rows) } - button, ok := markup.Rows[0].Buttons[0].(*tg.KeyboardButtonCallback) + button := markup.Rows[0].Buttons[0] + buttonType, ok := button.Type.(*tg.InlineButtonTypeCallback) if !ok { - t.Fatalf("reply_markup button = %T, want callback", markup.Rows[0].Buttons[0]) + t.Fatalf("reply_markup button = %T, want callback", button.Type) } - if button.Text != wantText || !bytes.Equal(button.Data, wantData) { - t.Fatalf("reply_markup button = %q/%v, want %q/%v", button.Text, button.Data, wantText, wantData) + if button.Text != wantText || !bytes.Equal(buttonType.Data, wantData) { + t.Fatalf("reply_markup button = %q/%v, want %q/%v", button.Text, buttonType.Data, wantText, wantData) } } @@ -2119,8 +2120,8 @@ func inlineArticleResult(id, message string) tg.InputBotInlineResultClass { func inlineArticleResultWithCallback(id, message, button string, data []byte) tg.InputBotInlineResultClass { result := inlineArticleResult(id, message).(*tg.InputBotInlineResult) msg := result.SendMessage.(*tg.InputBotInlineMessageText) - msg.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonCallback{Text: button, Data: data}}, + msg.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{ + Buttons: []tg.KeyboardInlineButton{{Text: button, Type: &tg.InlineButtonTypeCallback{Data: data}}}, }}}) return result } @@ -2308,8 +2309,8 @@ func inlineContactResult(id, phone, first, last, vcard string) *tg.InputBotInlin func inlineContactResultWithCallback(id, phone, first, last string, data []byte) tg.InputBotInlineResultClass { result := inlineContactResult(id, phone, first, last, "BEGIN:VCARD\nFN:"+first+" "+last+"\nEND:VCARD") msg := result.SendMessage.(*tg.InputBotInlineMessageMediaContact) - msg.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonCallback{Text: "Contact", Data: data}}, + msg.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{ + Buttons: []tg.KeyboardInlineButton{{Text: "Contact", Type: &tg.InlineButtonTypeCallback{Data: data}}}, }}}) return result } diff --git a/internal/rpc/bots_longtail.go b/internal/rpc/bots_longtail.go index ae18d8ba..982fc539 100644 --- a/internal/rpc/bots_longtail.go +++ b/internal/rpc/bots_longtail.go @@ -594,7 +594,7 @@ func (r *Router) onBotsRequestWebViewButton(ctx context.Context, req *tg.BotsReq if err != nil { return nil, internalErr() } - if req.UserID == nil || req.Button == nil { + if req.UserID == nil || req.Button.Type == nil { return nil, buttonDataInvalidErr() } if r.deps.Bots == nil { @@ -622,21 +622,21 @@ func (r *Router) onBotsRequestWebViewButton(ctx context.Context, req *tg.BotsReq return &tg.BotsRequestedButton{WebappReqID: saved.WebAppReqID}, nil } -func (r *Router) onBotsGetRequestedWebViewButton(ctx context.Context, req *tg.BotsGetRequestedWebViewButtonRequest) (tg.KeyboardButtonClass, error) { +func (r *Router) onBotsGetRequestedWebViewButton(ctx context.Context, req *tg.BotsGetRequestedWebViewButtonRequest) (tg.KeyboardButton, error) { userID, _, err := r.currentUserID(ctx) if err != nil { - return nil, internalErr() + return tg.KeyboardButton{}, internalErr() } bot, err := r.resolveBotUserForViewer(ctx, userID, req.Bot) if err != nil { - return nil, err + return tg.KeyboardButton{}, err } button, found, err := r.deps.Bots.GetRequestedWebViewButton(ctx, bot.ID, userID, req.WebappReqID) if err != nil { - return nil, internalErr() + return tg.KeyboardButton{}, internalErr() } if !found { - return nil, buttonDataInvalidErr() + return tg.KeyboardButton{}, buttonDataInvalidErr() } return tgKeyboardButtonRequestPeer(button), nil } @@ -761,21 +761,20 @@ func (r *Router) tgBotPreviewMedia(ctx context.Context, item domain.BotAppPrevie return out } -func domainRequestedButtonFromTG(botUserID int64, _ tg.InputUserClass, button tg.KeyboardButtonClass) (domain.BotRequestedWebViewButton, error) { +func domainRequestedButtonFromTG(botUserID int64, _ tg.InputUserClass, button tg.KeyboardButton) (domain.BotRequestedWebViewButton, error) { var out domain.BotRequestedWebViewButton out.BotUserID = botUserID - switch b := button.(type) { - case *tg.InputKeyboardButtonRequestPeer: + out.Text = strings.TrimSpace(button.Text) + switch b := button.Type.(type) { + case *tg.InputButtonTypeRequestPeer: out.ButtonID = b.ButtonID - out.Text = strings.TrimSpace(b.Text) out.PeerType, out.PeerFilter = domainRequestPeerFilter(b.PeerType) out.MaxQuantity = b.MaxQuantity out.NameRequested = b.NameRequested out.UsernameRequested = b.UsernameRequested out.PhotoRequested = b.PhotoRequested - case *tg.KeyboardButtonRequestPeer: + case *tg.ButtonTypeRequestPeer: out.ButtonID = b.ButtonID - out.Text = strings.TrimSpace(b.Text) out.PeerType, out.PeerFilter = domainRequestPeerFilter(b.PeerType) out.MaxQuantity = b.MaxQuantity default: @@ -800,12 +799,14 @@ func requestPeerTypeName(peerType tg.RequestPeerTypeClass) string { } } -func tgKeyboardButtonRequestPeer(button domain.BotRequestedWebViewButton) tg.KeyboardButtonClass { - return &tg.KeyboardButtonRequestPeer{ - Text: button.Text, - ButtonID: button.ButtonID, - PeerType: tgRequestPeerTypeWithFilter(button.PeerType, button.PeerFilter), - MaxQuantity: button.MaxQuantity, +func tgKeyboardButtonRequestPeer(button domain.BotRequestedWebViewButton) tg.KeyboardButton { + return tg.KeyboardButton{ + Text: button.Text, + Type: &tg.ButtonTypeRequestPeer{ + ButtonID: button.ButtonID, + PeerType: tgRequestPeerTypeWithFilter(button.PeerType, button.PeerFilter), + MaxQuantity: button.MaxQuantity, + }, } } diff --git a/internal/rpc/bots_longtail_rpc_test.go b/internal/rpc/bots_longtail_rpc_test.go index ac647f30..55e07644 100644 --- a/internal/rpc/bots_longtail_rpc_test.go +++ b/internal/rpc/bots_longtail_rpc_test.go @@ -252,9 +252,9 @@ func TestBotsLongtailCommercialAndSettingsStubs(t *testing.T) { } if _, err := f.router.onBotsRequestWebViewButton(botCtx, &tg.BotsRequestWebViewButtonRequest{ UserID: inputUser(f.owner), - Button: &tg.KeyboardButtonSimpleWebView{ + Button: tg.KeyboardButton{ Text: "Open", - URL: "https://example.com/app", + Type: &tg.ButtonTypeSimpleWebView{URL: "https://example.com/app"}, }, }); !tgerr.Is(err, "BUTTON_DATA_INVALID") { t.Fatalf("request webview button err = %v, want BUTTON_DATA_INVALID", err) diff --git a/internal/rpc/convert_markup.go b/internal/rpc/convert_markup.go index 9005e162..5d1e8f90 100644 --- a/internal/rpc/convert_markup.go +++ b/internal/rpc/convert_markup.go @@ -229,34 +229,38 @@ func domainOutgoingReplyMarkupForSender(markup tg.ReplyMarkupClass, senderIsBot } } -func domainReplyKeyboardButton(button tg.KeyboardButtonClass) (domain.MarkupButton, error) { - style, icon, err := domainMarkupButtonStyle(button) +func domainReplyKeyboardButton(button tg.KeyboardButton) (domain.MarkupButton, error) { + style, icon, err := domainMarkupButtonStyle(button.GetStyle()) if err != nil { return domain.MarkupButton{}, err } - base := domain.MarkupButton{Style: style, IconCustomEmojiID: icon} - switch b := button.(type) { - case *tg.KeyboardButton: - base.Type, base.Text = domain.MarkupButtonText, b.Text - case *tg.KeyboardButtonRequestPhone: - base.Type, base.Text = domain.MarkupButtonRequestPhone, b.Text - case *tg.KeyboardButtonRequestGeoLocation: - base.Type, base.Text = domain.MarkupButtonRequestLocation, b.Text - case *tg.KeyboardButtonRequestPoll: - base.Type, base.Text = domain.MarkupButtonRequestPoll, b.Text - if quiz, ok := b.GetQuiz(); ok { + base := domain.MarkupButton{Style: style, IconCustomEmojiID: icon, Text: button.Text} + switch t := button.Type.(type) { + case nil, *tg.ButtonTypeDefault: + base.Type = domain.MarkupButtonText + case *tg.ButtonTypeRequestPhone: + base.Type = domain.MarkupButtonRequestPhone + case *tg.ButtonTypeRequestGeoLocation: + base.Type = domain.MarkupButtonRequestLocation + case *tg.ButtonTypeRequestPoll: + base.Type = domain.MarkupButtonRequestPoll + if quiz, ok := t.GetQuiz(); ok { if quiz { base.PollType = "quiz" } else { base.PollType = "regular" } } - case *tg.KeyboardButtonRequestPeer: - base.Type, base.Text = domain.MarkupButtonRequestPeer, b.Text - base.ButtonID, base.MaxQuantity = b.ButtonID, b.MaxQuantity - base.RequestPeerType, base.RequestPeerFilter = domainRequestPeerFilter(b.PeerType) - case *tg.KeyboardButtonSimpleWebView: - base.Type, base.Text, base.URL = domain.MarkupButtonSimpleWebView, b.Text, b.URL + case *tg.ButtonTypeRequestPeer: + base.Type = domain.MarkupButtonRequestPeer + base.ButtonID, base.MaxQuantity = t.ButtonID, t.MaxQuantity + base.RequestPeerType, base.RequestPeerFilter = domainRequestPeerFilter(t.PeerType) + case *tg.InputButtonTypeRequestPeer: + base.Type = domain.MarkupButtonRequestPeer + base.ButtonID, base.MaxQuantity = t.ButtonID, t.MaxQuantity + base.RequestPeerType, base.RequestPeerFilter = domainRequestPeerFilter(t.PeerType) + case *tg.ButtonTypeSimpleWebView: + base.Type, base.URL = domain.MarkupButtonSimpleWebView, t.URL default: return domain.MarkupButton{}, domain.ErrButtonTypeInvalid } @@ -281,29 +285,29 @@ func domainInlineMarkup(inline *tg.ReplyInlineMarkup) (*domain.MessageReplyMarku return out, nil } -func domainMarkupButton(btn tg.KeyboardButtonClass, buttonID int) (domain.MarkupButton, error) { - style, icon, err := domainMarkupButtonStyle(btn) +func domainMarkupButton(btn tg.KeyboardInlineButton, buttonID int) (domain.MarkupButton, error) { + style, icon, err := domainMarkupButtonStyle(btn.GetStyle()) if err != nil { return domain.MarkupButton{}, err } - switch b := btn.(type) { - case *tg.KeyboardButtonCallback: + switch t := btn.Type.(type) { + case *tg.InlineButtonTypeCallback: return domain.MarkupButton{ Type: domain.MarkupButtonCallback, - Text: b.Text, + Text: btn.Text, Style: style, IconCustomEmojiID: icon, - Data: append([]byte(nil), b.Data...), - RequiresPassword: b.RequiresPassword, + Data: append([]byte(nil), t.Data...), + RequiresPassword: t.RequiresPassword, }, nil - case *tg.KeyboardButtonURL: + case *tg.InlineButtonTypeURL: return domain.MarkupButton{ - Type: domain.MarkupButtonURL, Text: b.Text, URL: b.URL, + Type: domain.MarkupButtonURL, Text: btn.Text, URL: t.URL, Style: style, IconCustomEmojiID: icon, }, nil - case *tg.InputKeyboardButtonURLAuth: + case *tg.InputInlineButtonTypeURLAuth: botUserID := int64(0) - switch bot := b.Bot.(type) { + switch bot := t.Bot.(type) { case nil, *tg.InputUserEmpty, *tg.InputUserSelf: case *tg.InputUser: botUserID = bot.UserID @@ -311,34 +315,33 @@ func domainMarkupButton(btn tg.KeyboardButtonClass, buttonID int) (domain.Markup return domain.MarkupButton{}, domain.ErrButtonInvalid } return domain.MarkupButton{ - Type: domain.MarkupButtonLoginURL, Text: b.Text, URL: b.URL, - ForwardText: b.FwdText, ButtonID: buttonID, LoginBotUserID: botUserID, - RequestWriteAccess: b.RequestWriteAccess, Style: style, IconCustomEmojiID: icon, + Type: domain.MarkupButtonLoginURL, Text: btn.Text, URL: t.URL, + ForwardText: t.FwdText, ButtonID: buttonID, LoginBotUserID: botUserID, + RequestWriteAccess: t.RequestWriteAccess, Style: style, IconCustomEmojiID: icon, }, nil - case *tg.KeyboardButtonURLAuth: + case *tg.InlineButtonTypeURLAuth: return domain.MarkupButton{ - Type: domain.MarkupButtonLoginURL, Text: b.Text, URL: b.URL, - ForwardText: b.FwdText, ButtonID: b.ButtonID, + Type: domain.MarkupButtonLoginURL, Text: btn.Text, URL: t.URL, + ForwardText: t.FwdText, ButtonID: t.ButtonID, Style: style, IconCustomEmojiID: icon, }, nil - case *tg.KeyboardButtonWebView: - return domain.MarkupButton{Type: domain.MarkupButtonWebView, Text: b.Text, URL: b.URL, Style: style, IconCustomEmojiID: icon}, nil - case *tg.KeyboardButtonSwitchInline: - peerTypes, err := preparedInlinePeerTypesFromTG(b.PeerTypes) + case *tg.InlineButtonTypeWebView: + return domain.MarkupButton{Type: domain.MarkupButtonWebView, Text: btn.Text, URL: t.URL, Style: style, IconCustomEmojiID: icon}, nil + case *tg.InlineButtonTypeSwitchInline: + peerTypes, err := preparedInlinePeerTypesFromTG(t.PeerTypes) if err != nil { return domain.MarkupButton{}, domain.ErrButtonInvalid } - return domain.MarkupButton{Type: domain.MarkupButtonSwitchInline, Text: b.Text, Query: b.Query, SamePeer: b.SamePeer, PeerTypes: peerTypes, Style: style, IconCustomEmojiID: icon}, nil - case *tg.KeyboardButtonCopy: - return domain.MarkupButton{Type: domain.MarkupButtonCopy, Text: b.Text, CopyText: b.CopyText, Style: style, IconCustomEmojiID: icon}, nil + return domain.MarkupButton{Type: domain.MarkupButtonSwitchInline, Text: btn.Text, Query: t.Query, SamePeer: t.SamePeer, PeerTypes: peerTypes, Style: style, IconCustomEmojiID: icon}, nil + case *tg.InlineButtonTypeCopy: + return domain.MarkupButton{Type: domain.MarkupButtonCopy, Text: btn.Text, CopyText: t.CopyText, Style: style, IconCustomEmojiID: icon}, nil default: // webview/game/url_auth/request_*/switch_inline/buy 等 P3 未实现按钮类型。 return domain.MarkupButton{}, domain.ErrButtonTypeInvalid } } -func domainMarkupButtonStyle(btn tg.KeyboardButtonClass) (domain.MarkupButtonStyle, int64, error) { - style, ok := btn.GetStyle() +func domainMarkupButtonStyle(style tg.KeyboardButtonStyle, ok bool) (domain.MarkupButtonStyle, int64, error) { if !ok { return "", 0, nil } @@ -388,7 +391,7 @@ func tgReplyMarkup(m *domain.MessageReplyMarkup) tg.ReplyMarkupClass { case domain.MessageReplyMarkupKeyboard: rows := make([]tg.KeyboardButtonRow, 0, len(m.Keyboard)) for _, row := range m.Keyboard { - buttons := make([]tg.KeyboardButtonClass, 0, len(row)) + buttons := make([]tg.KeyboardButton, 0, len(row)) for _, btn := range row { buttons = append(buttons, tgReplyKeyboardButton(btn)) } @@ -415,93 +418,75 @@ func tgReplyMarkup(m *domain.MessageReplyMarkup) tg.ReplyMarkupClass { default: return nil } - rows := make([]tg.KeyboardButtonRow, 0, len(m.Inline)) + rows := make([]tg.KeyboardInlineButtonRow, 0, len(m.Inline)) for _, row := range m.Inline { - buttons := make([]tg.KeyboardButtonClass, 0, len(row)) + buttons := make([]tg.KeyboardInlineButton, 0, len(row)) for _, btn := range row { buttons = append(buttons, tgMarkupButton(btn)) } - rows = append(rows, tg.KeyboardButtonRow{Buttons: buttons}) + rows = append(rows, tg.KeyboardInlineButtonRow{Buttons: buttons}) } return &tg.ReplyInlineMarkup{Rows: rows} } -func tgMarkupButton(btn domain.MarkupButton) tg.KeyboardButtonClass { +func tgMarkupButton(btn domain.MarkupButton) tg.KeyboardInlineButton { + out := tg.KeyboardInlineButton{Text: btn.Text} + if style, ok := tgMarkupButtonStyle(btn); ok { + out.SetStyle(style) + } switch btn.Type { case domain.MarkupButtonURL: - out := &tg.KeyboardButtonURL{Text: btn.Text, URL: btn.URL} - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = &tg.InlineButtonTypeURL{URL: btn.URL} case domain.MarkupButtonLoginURL: - out := &tg.KeyboardButtonURLAuth{Text: btn.Text, URL: btn.URL, ButtonID: btn.ButtonID} + t := &tg.InlineButtonTypeURLAuth{URL: btn.URL, ButtonID: btn.ButtonID} if btn.ForwardText != "" { - out.SetFwdText(btn.ForwardText) + t.SetFwdText(btn.ForwardText) } - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = t case domain.MarkupButtonWebView: - out := &tg.KeyboardButtonWebView{Text: btn.Text, URL: btn.URL} - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = &tg.InlineButtonTypeWebView{URL: btn.URL} case domain.MarkupButtonSwitchInline: - out := &tg.KeyboardButtonSwitchInline{Text: btn.Text, Query: btn.Query, SamePeer: btn.SamePeer} + t := &tg.InlineButtonTypeSwitchInline{Query: btn.Query, SamePeer: btn.SamePeer} if len(btn.PeerTypes) > 0 { - out.SetPeerTypes(tgPreparedInlinePeerTypes(btn.PeerTypes)) + t.SetPeerTypes(tgPreparedInlinePeerTypes(btn.PeerTypes)) } - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = t case domain.MarkupButtonCopy: - out := &tg.KeyboardButtonCopy{Text: btn.Text, CopyText: btn.CopyText} - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = &tg.InlineButtonTypeCopy{CopyText: btn.CopyText} default: // callback - out := &tg.KeyboardButtonCallback{Text: btn.Text, Data: btn.Data} + t := &tg.InlineButtonTypeCallback{Data: btn.Data} if btn.RequiresPassword { - out.SetRequiresPassword(true) + t.SetRequiresPassword(true) } - if style, ok := tgMarkupButtonStyle(btn); ok { - out.SetStyle(style) - } - return out + out.Type = t } + return out } -func tgReplyKeyboardButton(btn domain.MarkupButton) tg.KeyboardButtonClass { - var out tg.KeyboardButtonClass +func tgReplyKeyboardButton(btn domain.MarkupButton) tg.KeyboardButton { + out := tg.KeyboardButton{Text: btn.Text} switch btn.Type { case domain.MarkupButtonRequestPhone: - out = &tg.KeyboardButtonRequestPhone{Text: btn.Text} + out.Type = &tg.ButtonTypeRequestPhone{} case domain.MarkupButtonRequestLocation: - out = &tg.KeyboardButtonRequestGeoLocation{Text: btn.Text} + out.Type = &tg.ButtonTypeRequestGeoLocation{} case domain.MarkupButtonRequestPoll: - button := &tg.KeyboardButtonRequestPoll{Text: btn.Text} + t := &tg.ButtonTypeRequestPoll{} if btn.PollType == "quiz" { - button.SetQuiz(true) + t.SetQuiz(true) } else if btn.PollType == "regular" { - button.SetQuiz(false) + t.SetQuiz(false) } - out = button + out.Type = t case domain.MarkupButtonRequestPeer: - out = &tg.KeyboardButtonRequestPeer{Text: btn.Text, ButtonID: btn.ButtonID, PeerType: tgRequestPeerTypeWithFilter(btn.RequestPeerType, btn.RequestPeerFilter), MaxQuantity: btn.MaxQuantity} + out.Type = &tg.ButtonTypeRequestPeer{ButtonID: btn.ButtonID, PeerType: tgRequestPeerTypeWithFilter(btn.RequestPeerType, btn.RequestPeerFilter), MaxQuantity: btn.MaxQuantity} case domain.MarkupButtonSimpleWebView: - out = &tg.KeyboardButtonSimpleWebView{Text: btn.Text, URL: btn.URL} + out.Type = &tg.ButtonTypeSimpleWebView{URL: btn.URL} default: - out = &tg.KeyboardButton{Text: btn.Text} + out.Type = &tg.ButtonTypeDefault{} } if style, ok := tgMarkupButtonStyle(btn); ok { - if setter, ok := out.(interface{ SetStyle(tg.KeyboardButtonStyle) }); ok { - setter.SetStyle(style) - } + out.SetStyle(style) } return out } diff --git a/internal/rpc/convert_markup_test.go b/internal/rpc/convert_markup_test.go index 834ca2df..d2636264 100644 --- a/internal/rpc/convert_markup_test.go +++ b/internal/rpc/convert_markup_test.go @@ -9,22 +9,20 @@ import ( ) func TestReplyKeyboardTLDomainRoundTrip(t *testing.T) { + statusButton := tg.KeyboardButton{Text: "Status"} + style := tg.KeyboardButtonStyle{} + style.SetBgPrimary(true) + style.SetIcon(123456) + statusButton.SetStyle(style) in := &tg.ReplyKeyboardMarkup{ Resize: true, SingleUse: true, Selective: true, Persistent: true, Placeholder: "Choose", - Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButtonClass{ - &tg.KeyboardButton{Text: "Help"}, - func() *tg.KeyboardButton { - button := &tg.KeyboardButton{Text: "Status"} - style := tg.KeyboardButtonStyle{} - style.SetBgPrimary(true) - style.SetIcon(123456) - button.SetStyle(style) - return button - }(), + Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButton{ + {Text: "Help"}, + statusButton, }}}, } got, err := domainOutgoingReplyMarkupForSender(in, true) @@ -43,8 +41,9 @@ func TestReplyKeyboardTLDomainRoundTrip(t *testing.T) { if !ok || len(wire.Rows) != 1 || len(wire.Rows[0].Buttons) != 2 { t.Fatalf("wire markup = %#v", wire) } - if button, ok := wire.Rows[0].Buttons[1].(*tg.KeyboardButton); !ok || button.Text != "Status" { - t.Fatalf("second button = %#v", wire.Rows[0].Buttons[1]) + button := wire.Rows[0].Buttons[1] + if button.Text != "Status" { + t.Fatalf("second button = %#v", button) } else if style, ok := button.GetStyle(); !ok || !style.GetBgPrimary() || style.Icon != 123456 { t.Fatalf("second button style = %#v ok=%v", style, ok) } @@ -54,30 +53,32 @@ func TestReplyKeyboardTLDomainRoundTrip(t *testing.T) { } func TestInlineButtonStyleTLDomainRoundTrip(t *testing.T) { - button := &tg.KeyboardButtonCallback{Text: "Delete", Data: []byte("delete")} + button := tg.KeyboardInlineButton{Text: "Delete", Type: &tg.InlineButtonTypeCallback{Data: []byte("delete")}} style := tg.KeyboardButtonStyle{} style.SetBgDanger(true) button.SetStyle(style) - got, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButtonClass{button}}}}, true) + got, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{Buttons: []tg.KeyboardInlineButton{button}}}}, true) if err != nil { t.Fatalf("domainReplyMarkupForSender: %v", err) } if got.Inline[0][0].Style != domain.MarkupButtonStyleDanger { t.Fatalf("domain style = %#v", got.Inline[0][0]) } - wire := tgReplyMarkup(got).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0].(*tg.KeyboardButtonCallback) + wire := tgReplyMarkup(got).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0] if roundTrip, ok := wire.GetStyle(); !ok || !roundTrip.GetBgDanger() { t.Fatalf("wire style = %#v ok=%v", roundTrip, ok) } + if _, ok := wire.Type.(*tg.InlineButtonTypeCallback); !ok { + t.Fatalf("wire type = %#v", wire.Type) + } } func TestLoginURLButtonTLDomainProjection(t *testing.T) { - button := &tg.InputKeyboardButtonURLAuth{ - Text: "Log in", URL: "https://example.com/login", Bot: &tg.InputUser{UserID: 9001, AccessHash: 77}, - } - button.SetRequestWriteAccess(true) - button.SetFwdText("Open login") - markup, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButtonClass{button}}}}, true) + buttonType := &tg.InputInlineButtonTypeURLAuth{URL: "https://example.com/login", Bot: &tg.InputUser{UserID: 9001, AccessHash: 77}} + buttonType.SetRequestWriteAccess(true) + buttonType.SetFwdText("Open login") + button := tg.KeyboardInlineButton{Text: "Log in", Type: buttonType} + markup, err := domainReplyMarkupForSender(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{Buttons: []tg.KeyboardInlineButton{button}}}}, true) if err != nil { t.Fatal(err) } @@ -85,8 +86,9 @@ func TestLoginURLButtonTLDomainProjection(t *testing.T) { if got.Type != domain.MarkupButtonLoginURL || got.LoginBotUserID != 9001 || !got.RequestWriteAccess || got.ForwardText != "Open login" || got.ButtonID != 0 { t.Fatalf("domain login_url = %#v", got) } - wire, ok := tgReplyMarkup(markup).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0].(*tg.KeyboardButtonURLAuth) - if !ok || wire.Text != "Log in" || wire.URL != "https://example.com/login" || wire.ButtonID != 0 || wire.FwdText != "Open login" { + wire := tgReplyMarkup(markup).(*tg.ReplyInlineMarkup).Rows[0].Buttons[0] + wireType, ok := wire.Type.(*tg.InlineButtonTypeURLAuth) + if !ok || wire.Text != "Log in" || wireType.URL != "https://example.com/login" || wireType.ButtonID != 0 || wireType.FwdText != "Open login" { t.Fatalf("wire login_url = %#v", wire) } } @@ -112,7 +114,7 @@ func TestReplyKeyboardHideAndForceReplyTLDomainRoundTrip(t *testing.T) { func TestReplyKeyboardRequestPhoneTLDomainRoundTrip(t *testing.T) { markup, err := domainOutgoingReplyMarkupForSender(&tg.ReplyKeyboardMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonRequestPhone{Text: "Share phone"}}, + Buttons: []tg.KeyboardButton{{Text: "Share phone", Type: &tg.ButtonTypeRequestPhone{}}}, }}}, true) if err != nil || markup == nil || len(markup.Keyboard) != 1 || len(markup.Keyboard[0]) != 1 || markup.Keyboard[0][0].Type != domain.MarkupButtonRequestPhone { @@ -122,7 +124,7 @@ func TestReplyKeyboardRequestPhoneTLDomainRoundTrip(t *testing.T) { if !ok || len(wire.Rows) != 1 || len(wire.Rows[0].Buttons) != 1 { t.Fatalf("request_phone wire = %#v", wire) } - if _, ok := wire.Rows[0].Buttons[0].(*tg.KeyboardButtonRequestPhone); !ok { + if _, ok := wire.Rows[0].Buttons[0].Type.(*tg.ButtonTypeRequestPhone); !ok { t.Fatalf("request_phone button = %#v", wire.Rows[0].Buttons[0]) } if _, err := domainReplyMarkupForSender(&tg.ReplyKeyboardHide{}, true); err == nil { @@ -138,9 +140,9 @@ func TestReplyKeyboardRequestPeerFiltersTLDomainRoundTrip(t *testing.T) { chatType.SetHasUsername(false) chatType.SetForum(true) chatType.SetUserAdminRights(tg.ChatAdminRights{DeleteMessages: true, ManageTopics: true}) - in := &tg.ReplyKeyboardMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButtonClass{ - &tg.KeyboardButtonRequestPeer{Text: "Premium person", ButtonID: 1, PeerType: userType, MaxQuantity: 2}, - &tg.KeyboardButtonRequestPeer{Text: "Forum", ButtonID: 2, PeerType: chatType, MaxQuantity: 1}, + in := &tg.ReplyKeyboardMarkup{Rows: []tg.KeyboardButtonRow{{Buttons: []tg.KeyboardButton{ + {Text: "Premium person", Type: &tg.ButtonTypeRequestPeer{ButtonID: 1, PeerType: userType, MaxQuantity: 2}}, + {Text: "Forum", Type: &tg.ButtonTypeRequestPeer{ButtonID: 2, PeerType: chatType, MaxQuantity: 1}}, }}}} markup, err := domainOutgoingReplyMarkupForSender(in, true) if err != nil { @@ -157,14 +159,14 @@ func TestReplyKeyboardRequestPeerFiltersTLDomainRoundTrip(t *testing.T) { t.Fatalf("chat filter = %#v", chatFilter) } wire := tgReplyMarkup(markup).(*tg.ReplyKeyboardMarkup) - wireUser := wire.Rows[0].Buttons[0].(*tg.KeyboardButtonRequestPeer).PeerType.(*tg.RequestPeerTypeUser) + wireUser := wire.Rows[0].Buttons[0].Type.(*tg.ButtonTypeRequestPeer).PeerType.(*tg.RequestPeerTypeUser) if bot, ok := wireUser.GetBot(); !ok || bot { t.Fatalf("wire user bot=%v ok=%v", bot, ok) } if premium, ok := wireUser.GetPremium(); !ok || !premium { t.Fatalf("wire user premium=%v ok=%v", premium, ok) } - wireChat := wire.Rows[0].Buttons[1].(*tg.KeyboardButtonRequestPeer).PeerType.(*tg.RequestPeerTypeChat) + wireChat := wire.Rows[0].Buttons[1].Type.(*tg.ButtonTypeRequestPeer).PeerType.(*tg.RequestPeerTypeChat) if !wireChat.Creator || !wireChat.BotParticipant { t.Fatalf("wire chat = %#v", wireChat) } @@ -177,9 +179,12 @@ func TestReplyKeyboardRequestPeerFiltersTLDomainRoundTrip(t *testing.T) { } func TestInputRequestPeerButtonPreservesRequestedMetadata(t *testing.T) { - button := &tg.InputKeyboardButtonRequestPeer{ - NameRequested: true, UsernameRequested: true, PhotoRequested: true, - Text: "Share", ButtonID: 99, PeerType: &tg.RequestPeerTypeUser{}, MaxQuantity: 3, + button := tg.KeyboardButton{ + Text: "Share", + Type: &tg.InputButtonTypeRequestPeer{ + NameRequested: true, UsernameRequested: true, PhotoRequested: true, + ButtonID: 99, PeerType: &tg.RequestPeerTypeUser{}, MaxQuantity: 3, + }, } got, err := domainRequestedButtonFromTG(1001, nil, button) if err != nil { diff --git a/internal/rpc/messages_bot_no_state_rpc_test.go b/internal/rpc/messages_bot_no_state_rpc_test.go index 03a16c2d..31d4a2d9 100644 --- a/internal/rpc/messages_bot_no_state_rpc_test.go +++ b/internal/rpc/messages_bot_no_state_rpc_test.go @@ -99,8 +99,8 @@ func TestMessagesEditInlineBotMessageEditsPrivateInlineMessage(t *testing.T) { editReq := &tg.MessagesEditInlineBotMessageRequest{ID: msgID} editReq.SetMessage("after edit") - editReq.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonCallback{Text: "done", Data: []byte("v2")}}, + editReq.SetReplyMarkup(&tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{ + Buttons: []tg.KeyboardInlineButton{{Text: "done", Type: &tg.InlineButtonTypeCallback{Data: []byte("v2")}}}, }}}) if ok, err := f.router.onMessagesEditInlineBotMessage(botCtx, editReq); err != nil || !ok { t.Fatalf("edit inline bot message = %v,%v, want true,nil", ok, err) @@ -291,8 +291,8 @@ func inlineArticleResultWithCallbackMarkup(id, message, button string, data []by Title: id, SendMessage: &tg.InputBotInlineMessageText{ Message: message, - ReplyMarkup: &tg.ReplyInlineMarkup{Rows: []tg.KeyboardButtonRow{{ - Buttons: []tg.KeyboardButtonClass{&tg.KeyboardButtonCallback{Text: button, Data: data}}, + ReplyMarkup: &tg.ReplyInlineMarkup{Rows: []tg.KeyboardInlineButtonRow{{ + Buttons: []tg.KeyboardInlineButton{{Text: button, Type: &tg.InlineButtonTypeCallback{Data: data}}}, }}}, }, }