owpengram-server/internal/domain/message_markup.go

150 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package domain
import (
"errors"
"net/url"
"strings"
"unicode/utf8"
)
const (
// MaxMarkupRows 限制 inline keyboard 行数。
MaxMarkupRows = 100
// MaxMarkupButtonsPerRow 限制单行按钮数。
MaxMarkupButtonsPerRow = 8
// MaxMarkupButtonsTotal 限制 inline keyboard 总按钮数(对齐官方约 100
MaxMarkupButtonsTotal = 100
// MaxCallbackDataLen 是 callback 按钮 data 的字节上限(对齐 Bot API 1-64
MaxCallbackDataLen = 64
// MaxMarkupButtonTextLen 是按钮文本长度上限rune 计数)。
MaxMarkupButtonTextLen = 256
// MaxBotCallbackAnswerLen 是 callback answer 弹窗/toast 文本上限。
MaxBotCallbackAnswerLen = 200
// MaxStartParamLen 是 messages.startBot 深链 payload 上限(对齐官方 64
MaxStartParamLen = 64
)
// markup / callback 业务错误。
var (
// ErrButtonDataInvalid 表示 callback data 越界(>64 字节)。
ErrButtonDataInvalid = errors.New("button data invalid")
// ErrButtonInvalid 表示键盘结构非法(行/按钮数超限、文本空/过长)。
ErrButtonInvalid = errors.New("button invalid")
// ErrButtonURLInvalid 表示 url 按钮的链接非法(非 https
ErrButtonURLInvalid = errors.New("button url invalid")
// ErrButtonTypeInvalid 表示按钮类型 P3 不支持webview/game/url_auth/request_* 等)。
ErrButtonTypeInvalid = errors.New("button type invalid")
// ErrStartParamInvalid 表示 startBot 的 start_param 越界。
ErrStartParamInvalid = errors.New("start param invalid")
)
// MarkupButtonType 标识 P3 支持的 inline 按钮类型。
type MarkupButtonType string
const (
// MarkupButtonCallback 是 keyboardButtonCallback点击触发 getBotCallbackAnswer
MarkupButtonCallback MarkupButtonType = "callback"
// MarkupButtonURL 是 keyboardButtonUrl点击打开链接
MarkupButtonURL MarkupButtonType = "url"
)
// MarkupButton 是一颗 inline keyboard 按钮P3 仅 callback/url
type MarkupButton struct {
Type MarkupButtonType `json:"type"`
Text string `json:"text"`
// Data 仅 callback 使用:原始字节(含 0x00/非 UTF-8/高位。json 自动 base64
// 编解码,保证经 JSONB 列字节级 round-tripupdateBotCallbackQuery.data 须原样)。
Data []byte `json:"data,omitempty"`
// URL 仅 url 使用。
URL string `json:"url,omitempty"`
// RequiresPassword 仅 callback 使用keyboardButtonCallback.requires_password
// 2FA SRP 校验 P3 stub
RequiresPassword bool `json:"requires_password,omitempty"`
}
// MessageReplyMarkup 是消息携带的 inline keyboard 快照P3 仅 ReplyInlineMarkup
type MessageReplyMarkup struct {
Inline [][]MarkupButton `json:"inline,omitempty"`
}
// IsZero 报告 markup 是否为空(无任何按钮)。空 markup 不写 wire flag、不入库。
func (m *MessageReplyMarkup) IsZero() bool {
if m == nil {
return true
}
for _, row := range m.Inline {
if len(row) > 0 {
return false
}
}
return true
}
// ValidateReplyMarkup 校验 inline keyboard 结构与各按钮校验须先于落库I9
// 空 markup 合法(视为清空/无键盘)。
func ValidateReplyMarkup(m *MessageReplyMarkup) error {
if m == nil {
return nil
}
if len(m.Inline) > MaxMarkupRows {
return ErrButtonInvalid
}
total := 0
for _, row := range m.Inline {
if len(row) > MaxMarkupButtonsPerRow {
return ErrButtonInvalid
}
total += len(row)
if total > MaxMarkupButtonsTotal {
return ErrButtonInvalid
}
for i := range row {
if err := validateMarkupButton(row[i]); err != nil {
return err
}
}
}
return nil
}
func validateMarkupButton(b MarkupButton) error {
text := strings.TrimSpace(b.Text)
if text == "" || utf8.RuneCountInString(b.Text) > MaxMarkupButtonTextLen {
return ErrButtonInvalid
}
switch b.Type {
case MarkupButtonCallback:
if len(b.Data) > MaxCallbackDataLen {
return ErrButtonDataInvalid
}
case MarkupButtonURL:
if err := validateButtonURL(b.URL); err != nil {
return err
}
default:
// webview/game/url_auth/request_* 等 P3 未实现类型:拒绝,绝不半实现下发。
return ErrButtonTypeInvalid
}
return nil
}
func validateButtonURL(raw string) error {
raw = strings.TrimSpace(raw)
if raw == "" || len(raw) > MaxBotMenuButtonURLLen {
return ErrButtonURLInvalid
}
u, err := url.Parse(raw)
if err != nil || u.Scheme != "https" || u.Host == "" {
return ErrButtonURLInvalid
}
return nil
}
// BotCallbackAnswer 是 bot 对一次 callback query 的应答setBotCallbackAnswer →
// 解挂等待中的 getBotCallbackAnswer
type BotCallbackAnswer struct {
Alert bool
Message string
URL string
CacheTime int
}