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 <noreply@anthropic.com>
This commit is contained in:
Astra 2026-09-14 11:28:16 +01:00
parent ecdb14aaca
commit 7fef8438c5
9 changed files with 166 additions and 174 deletions

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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
}

View file

@ -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{
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,
},
}
}

View file

@ -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)

View file

@ -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)
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
}

View file

@ -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{
button := tg.KeyboardButton{
Text: "Share",
Type: &tg.InputButtonTypeRequestPeer{
NameRequested: true, UsernameRequested: true, PhotoRequested: true,
Text: "Share", ButtonID: 99, PeerType: &tg.RequestPeerTypeUser{}, MaxQuantity: 3,
ButtonID: 99, PeerType: &tg.RequestPeerTypeUser{}, MaxQuantity: 3,
},
}
got, err := domainRequestedButtonFromTG(1001, nil, button)
if err != nil {

View file

@ -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}}},
}}},
},
}