FILE / ScuroNeko/Laniakea
tgrich/buttons.go
Исходный файл и его история в репозитории.
Golang lint / lint (push) Failing after 1m37s
(fix): finalize v2 contracts (tests): cover v2 migration (doc): prepare release guidance
135 lines
4.3 KiB
Go
135 lines
4.3 KiB
Go
package tgrich
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"git.scuroneko.dev/scuroneko/laniakea/v2/tgapi"
|
|
)
|
|
|
|
func (v *richValidator) button(b tgapi.RichMessageButton, depth int) error {
|
|
actions := 0
|
|
for _, set := range []bool{b.URL != "", b.CallbackData != "", b.WebApp != nil, b.LoginURL != nil, b.SwitchInlineQuery != nil, b.SwitchInlineQueryCurrentChat != nil, b.SwitchInlineQueryChosenChat != nil, b.CopyText != nil, b.Disabled != nil} {
|
|
if set {
|
|
actions++
|
|
}
|
|
}
|
|
if actions != 1 {
|
|
return fmt.Errorf("%w: exactly one action is required", ErrRichInvalidButton)
|
|
}
|
|
switch b.Style {
|
|
case "", "danger", "success", "primary":
|
|
case "link":
|
|
if b.CallbackData == "" {
|
|
return fmt.Errorf("%w: link style requires callback data", ErrRichInvalidButton)
|
|
}
|
|
default:
|
|
return fmt.Errorf("%w: unsupported style", ErrRichInvalidButton)
|
|
}
|
|
if len(b.CallbackData) > 64 {
|
|
return fmt.Errorf("%w: callback data exceeds 64 bytes", ErrRichInvalidButton)
|
|
}
|
|
if b.CopyText != nil && (utf8.RuneCountInString(b.CopyText.Text) < 1 || utf8.RuneCountInString(b.CopyText.Text) > 256) {
|
|
return fmt.Errorf("%w: copy text must contain 1-256 characters", ErrRichInvalidButton)
|
|
}
|
|
if b.LoginURL != nil && b.LoginURL.BotUsername != "" {
|
|
return fmt.Errorf("%w: login bot username is unsupported", ErrRichInvalidButton)
|
|
}
|
|
return v.buttonText(b.Text, depth)
|
|
}
|
|
|
|
func (v *richValidator) buttonText(text tgapi.RichText, depth int) error {
|
|
if depth >= maxRichDepth {
|
|
return ErrRichNestingTooDeep
|
|
}
|
|
switch el := text.(type) {
|
|
case tgapi.RichTextPlain:
|
|
return v.addChars(string(el))
|
|
case tgapi.RichTextCustomEmoji:
|
|
return v.addChars(el.AlternativeText)
|
|
case tgapi.RichTextDateTime:
|
|
return v.buttonText(el.Text, depth+1)
|
|
case tgapi.RichTextArray:
|
|
for _, item := range el {
|
|
if err := v.buttonText(item, depth+1); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("%w: label allows only plain text, custom emoji, and date-time entities", ErrRichInvalidButton)
|
|
}
|
|
}
|
|
|
|
func renderButton(b tgapi.RichMessageButton, step int) (string, error) {
|
|
if step >= maxRichDepth {
|
|
return "", ErrRichNestingTooDeep
|
|
}
|
|
text, err := renderText(b.Text, step+1)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
attrs := map[string]string{}
|
|
if b.Style != "" {
|
|
attrs["style"] = string(b.Style)
|
|
}
|
|
// Empty query values are significant; render them explicitly below.
|
|
var query *string
|
|
switch {
|
|
case b.URL != "":
|
|
attrs["type"], attrs["url"] = "url", b.URL
|
|
case b.CallbackData != "":
|
|
attrs["type"], attrs["data"] = "callback_data", b.CallbackData
|
|
case b.WebApp != nil:
|
|
attrs["type"], attrs["url"] = "web_app", b.WebApp.URL
|
|
case b.LoginURL != nil:
|
|
attrs["type"], attrs["url"] = "login_url", b.LoginURL.URL
|
|
if b.LoginURL.ForwardText != "" {
|
|
attrs["forward-text"] = b.LoginURL.ForwardText
|
|
}
|
|
if b.LoginURL.RequestWriteAccess {
|
|
attrs["request-write-access"] = ""
|
|
}
|
|
case b.SwitchInlineQuery != nil:
|
|
attrs["type"], query = "switch_inline_query", b.SwitchInlineQuery
|
|
case b.SwitchInlineQueryCurrentChat != nil:
|
|
attrs["type"], query = "switch_inline_query_current_chat", b.SwitchInlineQueryCurrentChat
|
|
case b.SwitchInlineQueryChosenChat != nil:
|
|
c := b.SwitchInlineQueryChosenChat
|
|
attrs["type"], query = "switch_inline_query_chosen_chat", &c.Query
|
|
for key, enabled := range map[string]bool{"allow-user-chats": c.AllowUserChats, "allow-bot-chats": c.AllowBotChats, "allow-group-chats": c.AllowGroupChats, "allow-channel-chats": c.AllowChannelChats} {
|
|
if enabled {
|
|
attrs[key] = ""
|
|
}
|
|
}
|
|
case b.CopyText != nil:
|
|
attrs["type"], attrs["text"] = "copy_text", b.CopyText.Text
|
|
case b.Disabled != nil:
|
|
attrs["type"] = "disabled"
|
|
default:
|
|
return "", ErrRichInvalidButton
|
|
}
|
|
formatted := formatAttrs(attrs)
|
|
if query != nil {
|
|
formatted = append(formatted, `query="`+escapeHTML(*query)+`"`)
|
|
}
|
|
return openTag("tg-button", formatted) + text + "</tg-button>", nil
|
|
}
|
|
|
|
func renderButtons(row tgapi.InputRichBlockButtons, step int) (string, error) {
|
|
var content strings.Builder
|
|
for _, button := range row.Buttons {
|
|
html, err := renderButton(button, step+1)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
content.WriteString(html)
|
|
}
|
|
attrs := map[string]string{}
|
|
if row.Align != "" {
|
|
attrs["align"] = string(row.Align)
|
|
}
|
|
return openTag("tg-button-row", formatAttrs(attrs)) + content.String() + "</tg-button-row>", nil
|
|
}
|