feat: sync Telegram Login OIDC provider

This commit is contained in:
A 2026-07-21 15:46:24 +08:00
parent 30774f8c39
commit ebead9e98c
63 changed files with 11374 additions and 37 deletions

View file

@ -347,6 +347,9 @@ func markupButtonFromAPI(button apiInlineKeyboardButton) (domain.MarkupButton, e
if button.CopyTextSet {
constructors++
}
if button.LoginURLSet {
constructors++
}
if constructors != 1 {
return domain.MarkupButton{}, errors.New("BUTTON_INVALID")
}
@ -357,6 +360,13 @@ func markupButtonFromAPI(button apiInlineKeyboardButton) (domain.MarkupButton, e
if button.URLSet {
return domain.MarkupButton{Type: domain.MarkupButtonURL, Text: button.Text, URL: button.URL, Style: style, IconCustomEmojiID: icon}, nil
}
if button.LoginURLSet {
return domain.MarkupButton{
Type: domain.MarkupButtonLoginURL, Text: button.Text, URL: button.LoginURL,
ForwardText: button.LoginForwardText, LoginBotUsername: button.LoginBotUsername,
RequestWriteAccess: button.LoginRequestWriteAccess, Style: style, IconCustomEmojiID: icon,
}, nil
}
if button.CallbackDataSet {
if button.CallbackData == "" || len([]byte(button.CallbackData)) > domain.MaxCallbackDataLen {
return domain.MarkupButton{}, errors.New("BUTTON_DATA_INVALID")
@ -689,23 +699,28 @@ type apiForceReply struct {
}
type apiInlineKeyboardButton struct {
Text string
URL string
URLSet bool
CallbackData string
CallbackDataSet bool
Style string
IconCustomEmojiID string
IconCustomEmojiIDSet bool
Unsupported bool
WebAppURL string
WebAppSet bool
SwitchInlineQuery string
SwitchInlineSet bool
SwitchInlineSamePeer bool
SwitchInlinePeerTypes []string
CopyText string
CopyTextSet bool
Text string
URL string
URLSet bool
CallbackData string
CallbackDataSet bool
Style string
IconCustomEmojiID string
IconCustomEmojiIDSet bool
Unsupported bool
WebAppURL string
WebAppSet bool
SwitchInlineQuery string
SwitchInlineSet bool
SwitchInlineSamePeer bool
SwitchInlinePeerTypes []string
CopyText string
CopyTextSet bool
LoginURL string
LoginForwardText string
LoginBotUsername string
LoginRequestWriteAccess bool
LoginURLSet bool
}
func (b *apiInlineKeyboardButton) UnmarshalJSON(data []byte) error {
@ -739,6 +754,20 @@ func (b *apiInlineKeyboardButton) UnmarshalJSON(data []byte) error {
}
b.WebAppURL = app.URL
}
if raw, ok := fields["login_url"]; ok {
b.LoginURLSet = true
var login struct {
URL string `json:"url"`
ForwardText string `json:"forward_text"`
BotUsername string `json:"bot_username"`
RequestWriteAccess bool `json:"request_write_access"`
}
if json.Unmarshal(raw, &login) != nil {
return errors.New("invalid login url")
}
b.LoginURL, b.LoginForwardText = login.URL, login.ForwardText
b.LoginBotUsername, b.LoginRequestWriteAccess = login.BotUsername, login.RequestWriteAccess
}
switchActions := 0
if raw, ok := fields["switch_inline_query"]; ok {
switchActions++
@ -807,7 +836,7 @@ func (b *apiInlineKeyboardButton) UnmarshalJSON(data []byte) error {
}
for key := range fields {
switch key {
case "text", "url", "callback_data", "web_app", "switch_inline_query", "switch_inline_query_current_chat", "switch_inline_query_chosen_chat", "copy_text", "style", "icon_custom_emoji_id":
case "text", "url", "callback_data", "web_app", "login_url", "switch_inline_query", "switch_inline_query_current_chat", "switch_inline_query_chosen_chat", "copy_text", "style", "icon_custom_emoji_id":
default:
b.Unsupported = true
}

View file

@ -509,6 +509,18 @@ func apiReplyMarkup(markup *domain.MessageReplyMarkup) map[string]any {
switch button.Type {
case domain.MarkupButtonURL:
item["url"] = button.URL
case domain.MarkupButtonLoginURL:
login := map[string]any{"url": button.URL}
if button.ForwardText != "" {
login["forward_text"] = button.ForwardText
}
if button.LoginBotUsername != "" {
login["bot_username"] = button.LoginBotUsername
}
if button.RequestWriteAccess {
login["request_write_access"] = true
}
item["login_url"] = login
case domain.MarkupButtonCallback:
item["callback_data"] = string(button.Data)
case domain.MarkupButtonWebView:

View file

@ -886,6 +886,19 @@ func TestReplyMarkupFromAPIReplyKeyboardVariants(t *testing.T) {
if err != nil || webApp == nil || webApp.Inline[0][0].Type != domain.MarkupButtonWebView {
t.Fatalf("web_app inline button = %#v err=%v", webApp, err)
}
login, err := replyMarkupFromAPI(json.RawMessage(`{"inline_keyboard":[[{"text":"Log in","login_url":{"url":"https://example.com/login","forward_text":"Open","bot_username":"auth_bot","request_write_access":true}}]]}`))
if err != nil || login == nil {
t.Fatalf("login_url inline button = %#v err=%v", login, err)
}
loginButton := login.Inline[0][0]
if loginButton.Type != domain.MarkupButtonLoginURL || loginButton.URL != "https://example.com/login" || loginButton.ForwardText != "Open" ||
loginButton.LoginBotUsername != "auth_bot" || !loginButton.RequestWriteAccess {
t.Fatalf("login_url button = %#v", loginButton)
}
projectedLogin := apiReplyMarkup(login)["inline_keyboard"].([][]map[string]any)[0][0]["login_url"].(map[string]any)
if projectedLogin["url"] != "https://example.com/login" || projectedLogin["bot_username"] != "auth_bot" || projectedLogin["request_write_access"] != true {
t.Fatalf("projected login_url = %#v", projectedLogin)
}
}
func TestReplyMarkupFromAPIPreservesSemanticButtonStyles(t *testing.T) {