chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
294
internal/botapi/inline.go
Normal file
294
internal/botapi/inline.go
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
package botapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
||||
func inlineResultFromAPI(raw string) (domain.BotInlineResult, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return domain.BotInlineResult{}, errors.New("RESULT_ID_INVALID")
|
||||
}
|
||||
var payload apiInlineResult
|
||||
if err := json.Unmarshal([]byte(raw), &payload); err != nil {
|
||||
return domain.BotInlineResult{}, errors.New("RESULT_TYPE_INVALID")
|
||||
}
|
||||
if payload.ID == "" {
|
||||
return domain.BotInlineResult{}, errors.New("RESULT_ID_EMPTY")
|
||||
}
|
||||
if len(payload.ID) > domain.MaxBotInlineResultIDLen {
|
||||
return domain.BotInlineResult{}, errors.New("RESULT_ID_INVALID")
|
||||
}
|
||||
if payload.Type != "article" {
|
||||
return domain.BotInlineResult{}, errors.New("RESULT_TYPE_INVALID")
|
||||
}
|
||||
if payload.URL != "" && !validBotAPIHTTPSURL(payload.URL) {
|
||||
return domain.BotInlineResult{}, errors.New("BUTTON_URL_INVALID")
|
||||
}
|
||||
message, entities, noWebpage, err := inputTextMessageContentFromAPI(payload)
|
||||
if err != nil {
|
||||
return domain.BotInlineResult{}, err
|
||||
}
|
||||
markup, err := replyMarkupFromAPI(payload.ReplyMarkup)
|
||||
if err != nil {
|
||||
return domain.BotInlineResult{}, err
|
||||
}
|
||||
return domain.BotInlineResult{
|
||||
ID: payload.ID,
|
||||
Type: payload.Type,
|
||||
Title: payload.Title,
|
||||
Description: payload.Description,
|
||||
URL: payload.URL,
|
||||
Message: message,
|
||||
Entities: entities,
|
||||
ReplyMarkup: markup,
|
||||
NoWebpage: noWebpage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func inputTextMessageContentFromAPI(payload apiInlineResult) (string, []domain.MessageEntity, bool, error) {
|
||||
var content apiInputTextMessageContent
|
||||
if len(payload.InputMessageContent) > 0 && string(payload.InputMessageContent) != "null" {
|
||||
if err := json.Unmarshal(payload.InputMessageContent, &content); err != nil {
|
||||
return "", nil, false, errors.New("MESSAGE_EMPTY")
|
||||
}
|
||||
} else if payload.MessageText != "" {
|
||||
content.MessageText = payload.MessageText
|
||||
}
|
||||
if content.ParseMode != "" {
|
||||
return "", nil, false, errors.New("ENTITY_PARSE_UNSUPPORTED")
|
||||
}
|
||||
message := content.MessageText
|
||||
if message == "" {
|
||||
return "", nil, false, errors.New("MESSAGE_EMPTY")
|
||||
}
|
||||
if utf8.RuneCountInString(message) > domain.MaxMessageTextLength {
|
||||
return "", nil, false, errors.New("MESSAGE_TOO_LONG")
|
||||
}
|
||||
entities, err := messageEntitiesFromAPI(content.Entities)
|
||||
if err != nil {
|
||||
return "", nil, false, err
|
||||
}
|
||||
noWebpage := content.DisableWebPagePreview || content.LinkPreviewOptions.IsDisabled
|
||||
return message, entities, noWebpage, nil
|
||||
}
|
||||
|
||||
func messageEntitiesFromAPI(in []apiMessageEntity) ([]domain.MessageEntity, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(in) > domain.MaxMessageEntityCount {
|
||||
return nil, errors.New("ENTITIES_TOO_LONG")
|
||||
}
|
||||
out := make([]domain.MessageEntity, 0, len(in))
|
||||
for _, entity := range in {
|
||||
if entity.Offset < 0 || entity.Length <= 0 {
|
||||
return nil, errors.New("ENTITY_BOUNDS_INVALID")
|
||||
}
|
||||
mapped, ok := apiEntityType(entity.Type)
|
||||
if !ok {
|
||||
return nil, errors.New("ENTITY_TYPE_UNSUPPORTED")
|
||||
}
|
||||
item := domain.MessageEntity{
|
||||
Type: mapped,
|
||||
Offset: entity.Offset,
|
||||
Length: entity.Length,
|
||||
URL: entity.URL,
|
||||
Language: entity.Language,
|
||||
}
|
||||
if entity.User != nil {
|
||||
item.UserID = entity.User.ID
|
||||
}
|
||||
if entity.CustomEmojiID != "" {
|
||||
id, err := strconv.ParseInt(entity.CustomEmojiID, 10, 64)
|
||||
if err != nil || id <= 0 {
|
||||
return nil, errors.New("ENTITY_TYPE_UNSUPPORTED")
|
||||
}
|
||||
item.DocumentID = id
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func apiEntityType(in string) (domain.MessageEntityType, bool) {
|
||||
switch in {
|
||||
case "bold":
|
||||
return domain.MessageEntityBold, true
|
||||
case "italic":
|
||||
return domain.MessageEntityItalic, true
|
||||
case "underline":
|
||||
return domain.MessageEntityUnderline, true
|
||||
case "strikethrough":
|
||||
return domain.MessageEntityStrike, true
|
||||
case "code":
|
||||
return domain.MessageEntityCode, true
|
||||
case "pre":
|
||||
return domain.MessageEntityPre, true
|
||||
case "text_link":
|
||||
return domain.MessageEntityTextURL, true
|
||||
case "text_mention":
|
||||
return domain.MessageEntityMentionName, true
|
||||
case "spoiler":
|
||||
return domain.MessageEntitySpoiler, true
|
||||
case "blockquote":
|
||||
return domain.MessageEntityBlockquote, true
|
||||
case "custom_emoji":
|
||||
return domain.MessageEntityCustomEmoji, true
|
||||
case "mention":
|
||||
return domain.MessageEntityMention, true
|
||||
case "hashtag":
|
||||
return domain.MessageEntityHashtag, true
|
||||
case "cashtag":
|
||||
return domain.MessageEntityCashtag, true
|
||||
case "bot_command":
|
||||
return domain.MessageEntityBotCommand, true
|
||||
case "url":
|
||||
return domain.MessageEntityURL, true
|
||||
case "email":
|
||||
return domain.MessageEntityEmail, true
|
||||
case "phone_number":
|
||||
return domain.MessageEntityPhone, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func replyMarkupFromAPI(raw json.RawMessage) (*domain.MessageReplyMarkup, error) {
|
||||
if len(raw) == 0 || string(raw) == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
var payload apiInlineKeyboardMarkup
|
||||
if err := json.Unmarshal(raw, &payload); err != nil {
|
||||
return nil, errors.New("BUTTON_INVALID")
|
||||
}
|
||||
if len(payload.InlineKeyboard) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
out := &domain.MessageReplyMarkup{Inline: make([][]domain.MarkupButton, 0, len(payload.InlineKeyboard))}
|
||||
for _, row := range payload.InlineKeyboard {
|
||||
domainRow := make([]domain.MarkupButton, 0, len(row))
|
||||
for _, button := range row {
|
||||
item, err := markupButtonFromAPI(button)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainRow = append(domainRow, item)
|
||||
}
|
||||
out.Inline = append(out.Inline, domainRow)
|
||||
}
|
||||
if err := domain.ValidateReplyMarkup(out); err != nil {
|
||||
return nil, replyMarkupErrFromDomain(err)
|
||||
}
|
||||
if out.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func markupButtonFromAPI(button apiInlineKeyboardButton) (domain.MarkupButton, error) {
|
||||
if button.URL != "" {
|
||||
return domain.MarkupButton{Type: domain.MarkupButtonURL, Text: button.Text, URL: button.URL}, nil
|
||||
}
|
||||
if button.CallbackData != nil {
|
||||
if *button.CallbackData == "" || len([]byte(*button.CallbackData)) > domain.MaxCallbackDataLen {
|
||||
return domain.MarkupButton{}, errors.New("BUTTON_DATA_INVALID")
|
||||
}
|
||||
return domain.MarkupButton{Type: domain.MarkupButtonCallback, Text: button.Text, Data: []byte(*button.CallbackData)}, nil
|
||||
}
|
||||
return domain.MarkupButton{}, errors.New("BUTTON_INVALID")
|
||||
}
|
||||
|
||||
func replyMarkupErrFromDomain(err error) error {
|
||||
switch {
|
||||
case errors.Is(err, domain.ErrButtonURLInvalid):
|
||||
return errors.New("BUTTON_URL_INVALID")
|
||||
case errors.Is(err, domain.ErrButtonDataInvalid):
|
||||
return errors.New("BUTTON_DATA_INVALID")
|
||||
default:
|
||||
return errors.New("BUTTON_INVALID")
|
||||
}
|
||||
}
|
||||
|
||||
func preparedPeerTypesFromAPI(values map[string]string) []string {
|
||||
out := make([]string, 0, 5)
|
||||
if apiBool(values["allow_user_chats"]) {
|
||||
out = append(out, store.InlineQueryPeerTypePM)
|
||||
}
|
||||
if apiBool(values["allow_bot_chats"]) {
|
||||
out = append(out, store.InlineQueryPeerTypeBotPM)
|
||||
}
|
||||
if apiBool(values["allow_group_chats"]) {
|
||||
out = append(out, store.InlineQueryPeerTypeChat, store.InlineQueryPeerTypeMegagroup)
|
||||
}
|
||||
if apiBool(values["allow_channel_chats"]) {
|
||||
out = append(out, store.InlineQueryPeerTypeBroadcast)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func apiBool(raw string) bool {
|
||||
v, err := strconv.ParseBool(strings.TrimSpace(raw))
|
||||
return err == nil && v
|
||||
}
|
||||
|
||||
func validBotAPIHTTPSURL(raw string) bool {
|
||||
if raw == "" || len(raw) > domain.MaxBotInlineWebURLLen || strings.TrimSpace(raw) != raw {
|
||||
return false
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil || parsed.User != nil || parsed.Host == "" || strings.ToLower(parsed.Scheme) != "https" {
|
||||
return false
|
||||
}
|
||||
return !strings.ContainsAny(parsed.Host, " \t\r\n")
|
||||
}
|
||||
|
||||
type apiInlineResult struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
URL string `json:"url"`
|
||||
MessageText string `json:"message_text"`
|
||||
InputMessageContent json.RawMessage `json:"input_message_content"`
|
||||
ReplyMarkup json.RawMessage `json:"reply_markup"`
|
||||
}
|
||||
|
||||
type apiInputTextMessageContent struct {
|
||||
MessageText string `json:"message_text"`
|
||||
ParseMode string `json:"parse_mode"`
|
||||
Entities []apiMessageEntity `json:"entities"`
|
||||
DisableWebPagePreview bool `json:"disable_web_page_preview"`
|
||||
LinkPreviewOptions struct {
|
||||
IsDisabled bool `json:"is_disabled"`
|
||||
} `json:"link_preview_options"`
|
||||
}
|
||||
|
||||
type apiMessageEntity struct {
|
||||
Type string `json:"type"`
|
||||
Offset int `json:"offset"`
|
||||
Length int `json:"length"`
|
||||
URL string `json:"url"`
|
||||
User *struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"user"`
|
||||
Language string `json:"language"`
|
||||
CustomEmojiID string `json:"custom_emoji_id"`
|
||||
}
|
||||
|
||||
type apiInlineKeyboardMarkup struct {
|
||||
InlineKeyboard [][]apiInlineKeyboardButton `json:"inline_keyboard"`
|
||||
}
|
||||
|
||||
type apiInlineKeyboardButton struct {
|
||||
Text string `json:"text"`
|
||||
URL string `json:"url"`
|
||||
CallbackData *string `json:"callback_data"`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue