feat: sync bot rich messages and inline menus

This commit is contained in:
A 2026-07-21 15:44:43 +08:00
parent 2965f5d47d
commit 1a2d03f529
24 changed files with 2073 additions and 38 deletions

View file

@ -0,0 +1,99 @@
package botapi
import (
"bytes"
"encoding/json"
"errors"
"strconv"
"strings"
"telesrv/internal/domain"
)
const maxBotAPIRichSourceBytes = 256 << 10
func richMessageInputFromAPI(raw string) (domain.BotAPIRichMessageInput, error) {
raw = strings.TrimSpace(raw)
if raw == "" || len(raw) > maxBotAPIRichSourceBytes {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
var fields map[string]json.RawMessage
if err := json.Unmarshal([]byte(raw), &fields); err != nil {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
var out domain.BotAPIRichMessageInput
sources := 0
if value, ok := fields["html"]; ok && !bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
if err := json.Unmarshal(value, &out.HTML); err != nil || out.HTML == "" {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
sources++
}
if value, ok := fields["markdown"]; ok && !bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
if err := json.Unmarshal(value, &out.Markdown); err != nil || out.Markdown == "" {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
sources++
}
if value, ok := fields["blocks"]; ok && !bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
if len(bytes.TrimSpace(value)) == 0 {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
out.BlocksJSON = append([]byte(nil), value...)
sources++
}
if sources != 1 {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
if len(out.BlocksJSON) != 0 {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_BLOCKS_UNSUPPORTED")
}
if value, ok := fields["media"]; ok && !bytes.Equal(bytes.TrimSpace(value), []byte("null")) && !bytes.Equal(bytes.TrimSpace(value), []byte("[]")) {
out.MediaJSON = append([]byte(nil), value...)
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_MEDIA_UNSUPPORTED")
}
if value, ok := fields["is_rtl"]; ok {
if err := json.Unmarshal(value, &out.RTL); err != nil {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
}
if value, ok := fields["skip_entity_detection"]; ok {
if err := json.Unmarshal(value, &out.SkipEntityDetection); err != nil {
return domain.BotAPIRichMessageInput{}, errors.New("RICH_MESSAGE_INVALID")
}
}
return out, nil
}
func richReplyMessageID(values map[string]string) (int, error) {
legacy := apiInt(values["reply_to_message_id"], 0)
raw := strings.TrimSpace(values["reply_parameters"])
if raw == "" {
if legacy < 0 {
return 0, errors.New("REPLY_MESSAGE_ID_INVALID")
}
return legacy, nil
}
if legacy != 0 {
return 0, errors.New("REPLY_PARAMETERS_INVALID")
}
var payload struct {
MessageID int `json:"message_id"`
}
if err := json.Unmarshal([]byte(raw), &payload); err != nil || payload.MessageID <= 0 {
return 0, errors.New("REPLY_PARAMETERS_INVALID")
}
return payload.MessageID, nil
}
func apiInt64(raw string) (int64, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return 0, nil
}
value, err := strconv.ParseInt(raw, 10, 64)
if err != nil || value < 0 {
return 0, errors.New("VALUE_INVALID")
}
return value, nil
}