owpengram-server/internal/domain/bot.go

313 lines
11 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"
"strconv"
"strings"
"time"
)
const (
// MaxBotsPerOwner 是每个用户可创建的 bot 上限(对齐官方 appConfig bots_create_limit_default
MaxBotsPerOwner = 20
// BotTokenSecretLength 是 bot token 随机段长度(官方格式 <bot_id>:<35 位>)。
BotTokenSecretLength = 35
// BotUsernameSuffix 是 bot username 的强制后缀(大小写不敏感)。
BotUsernameSuffix = "bot"
// MaxBotNameLength 是 bot 显示名first_name上限。
MaxBotNameLength = 64
// MaxBotInlinePlaceholderLen 是 inline mode placeholder 上限bots.inline_placeholder
MaxBotInlinePlaceholderLen = 128
// MaxBotAppShortNameLen 是 bot app short_name 上限。
MaxBotAppShortNameLen = 64
// MaxBotAppTitleLen 是 bot app title 上限。
MaxBotAppTitleLen = 128
// MaxBotAppDescriptionLen 是 bot app description 上限。
MaxBotAppDescriptionLen = 512
// MaxBotAppURLLen 是 bot app / attach menu webview URL 上限。
MaxBotAppURLLen = 512
// MaxBotAttachMenuPeerTypes 限制 attach menu peer_types 数量,避免无界响应。
MaxBotAttachMenuPeerTypes = 8
// MaxBotAttachMenuIcons 限制 attach menu icon 变体数量。
MaxBotAttachMenuIcons = 8
// MaxBotPreviewMedia 是单 bot main mini app preview media 上限。
MaxBotPreviewMedia = 20
// MaxBotRequestedPeerQuantity 限制 request peer 一次选择数量。
MaxBotRequestedPeerQuantity = 10
// MaxBotCustomMethodLen 限制 WebView custom method 名称长度。
MaxBotCustomMethodLen = 128
// MaxBotCustomMethodPayloadLen 限制 WebView custom method JSON 载荷长度。
MaxBotCustomMethodPayloadLen = 4096
)
// bot 业务错误。
var (
ErrBotTokenInvalid = errors.New("bot token invalid")
ErrBotsTooMany = errors.New("bot create limit exceeded")
ErrBotNameInvalid = errors.New("bot name invalid")
ErrBotUsernameInvalid = errors.New("bot username invalid")
ErrBotNotFound = errors.New("bot not found")
// ErrBotCommandInvalid 表示命令名/描述非法bots.setBotCommands
ErrBotCommandInvalid = errors.New("bot command invalid")
// ErrBotInfoInvalid 表示 setBotInfo 的 name/about/description 越界。
ErrBotInfoInvalid = errors.New("bot info invalid")
// ErrBotMenuButtonInvalid 表示 menu button 文本/URL 非法。
ErrBotMenuButtonInvalid = errors.New("bot menu button invalid")
// ErrBotInlinePlaceholderInvalid 表示 inline placeholder 为空以外的文本越界。
ErrBotInlinePlaceholderInvalid = errors.New("bot inline placeholder invalid")
// ErrBotAppInvalid 表示 bot app 元数据、URL 或 access hash 非法。
ErrBotAppInvalid = errors.New("bot app invalid")
// ErrBotAppShortNameInvalid 表示 bot app short_name 非法。
ErrBotAppShortNameInvalid = errors.New("bot app short name invalid")
// ErrBotAttachMenuInvalid 表示 attach menu catalog 或用户状态非法。
ErrBotAttachMenuInvalid = errors.New("bot attach menu invalid")
// ErrBotRequestedButtonInvalid 表示 request-peer button 上下文非法或已过期。
ErrBotRequestedButtonInvalid = errors.New("bot requested button invalid")
// ErrBotDownloadParamsInvalid 表示 mini app download 参数未通过安全策略。
ErrBotDownloadParamsInvalid = errors.New("bot download params invalid")
// ErrBotCustomMethodUnavailable 表示没有可完成 WebView custom method 的 bot 侧回答。
ErrBotCustomMethodUnavailable = errors.New("bot custom method unavailable")
// ErrBotSessionsNotRevoked 表示 token 已轮换但撤销已登录 session 失败
//token 不可回滚,调用方须告知用户重试以确保旧 session 终止)。
ErrBotSessionsNotRevoked = errors.New("bot sessions not revoked")
)
const (
// MaxBotCommands 是单个 bot 的命令上限default scope
MaxBotCommands = 100
// MaxBotCommandLen 是命令名长度上限(不含前导 /)。
MaxBotCommandLen = 32
// MaxBotCommandDescriptionLen 是命令描述长度上限。
MaxBotCommandDescriptionLen = 256
// MaxBotAboutLen 是 bot aboutusers.about长度上限。
MaxBotAboutLen = 120
// MaxBotDescriptionLen 是 bot description"What can this bot do?")长度上限。
MaxBotDescriptionLen = 512
// MaxBotMenuButtonTextLen / MaxBotMenuButtonURLLen 是菜单按钮文本/URL 上限。
MaxBotMenuButtonTextLen = 64
MaxBotMenuButtonURLLen = 512
)
// BotCommand 是 bot 的一条命令botInfo.commands 元素)。
type BotCommand struct {
Command string `json:"command"`
Description string `json:"description"`
}
// BotMenuButtonType 标识菜单按钮类型。
type BotMenuButtonType int
const (
// BotMenuButtonDefault 是默认(不显示特殊按钮,客户端按 commands 是否非空决定 '/' 圆钮)。
BotMenuButtonDefault BotMenuButtonType = 0
// BotMenuButtonCommands 显式让客户端展示命令菜单按钮。
BotMenuButtonCommands BotMenuButtonType = 1
// BotMenuButtonWebView 是带文本+URL 的 web view 按钮。
BotMenuButtonWebView BotMenuButtonType = 2
)
// BotMenuButton 是 bot 的菜单按钮per-bot 全局per-user 维度 P2 不做)。
type BotMenuButton struct {
Type BotMenuButtonType
Text string
URL string
}
// BotInfoUpdate 是 bots.setBotInfo 的部分更新name→users.first_name、
// about→users.about、description→bots.description。各 SetXxx 为 false 时不动该字段。
type BotInfoUpdate struct {
SetName bool
Name string
SetAbout bool
About string
SetDescription bool
Description string
}
// BotApp 是 mini app catalog 的一项。一个 bot 可拥有多个 appID/access_hash
// 在创建后稳定short_name 在同一 bot 下唯一。
type BotApp struct {
ID int64
BotUserID int64
ShortName string
Title string
Description string
URL string
PhotoID int64
DocumentID int64
AccessHash int64
Hash int64
Inactive bool
RequestWriteAccess bool
HasSettings bool
Main bool
}
// BotAppSettings 是 BotInfo.app_settings 的协议中立表示。
type BotAppSettings struct {
PlaceholderPath []byte
BackgroundColor int
BackgroundDarkColor int
HeaderColor int
HeaderDarkColor int
HasBackgroundColor bool
HasBackgroundDark bool
HasHeaderColor bool
HasHeaderDarkColor bool
}
// BotAppPreviewMedia 是 main mini app preview media 的持久快照。
type BotAppPreviewMedia struct {
ID int64
BotUserID int64
AppID int64
Position int
PhotoID int64
DocumentID int64
}
// BotAttachMenuIconColor 描述 attach menu icon 的主题色覆盖。
type BotAttachMenuIconColor struct {
Name string
Color int
}
// BotAttachMenuIcon 描述 attach menu 的平台 icon 文档引用。
type BotAttachMenuIcon struct {
Name string
DocumentID int64
Colors []BotAttachMenuIconColor
}
// BotAttachMenuBot 是全局 attach/side menu catalog 的一项。
type BotAttachMenuBot struct {
BotUserID int64
AppID int64
ShortName string
Inactive bool
HasSettings bool
RequestWriteAccess bool
ShowInAttachMenu bool
ShowInSideMenu bool
SideMenuDisclaimerNeeded bool
PeerTypes []string
Icons []BotAttachMenuIcon
}
// BotAttachMenuState 是用户对某个 attach menu bot 的启用与写权限状态。
type BotAttachMenuState struct {
UserID int64
BotUserID int64
Enabled bool
WriteAllowed bool
}
// BotRequestedWebViewButton 是 bots.requestWebViewButton 创建的 request-peer 上下文。
type BotRequestedWebViewButton struct {
WebAppReqID string
BotUserID int64
UserID int64
ButtonID int
Text string
PeerType string
MaxQuantity int
CreatedAt time.Time
ExpiresAt time.Time
}
// BotWebViewCustomMethodQuery 是 custom method 的 pending 记录。没有 bot 侧回答
// registry 时只记录查询并显式返回 blocked 错误,避免假成功。
type BotWebViewCustomMethodQuery struct {
ID string
BotUserID int64
UserID int64
CustomMethod string
ParamsJSON string
CreatedAt time.Time
ExpiresAt time.Time
}
// ValidBotCommandName 校验命令名1-32 位小写字母/数字/下划线(不含前导 /)。
func ValidBotCommandName(cmd string) bool {
if len(cmd) == 0 || len(cmd) > MaxBotCommandLen {
return false
}
for _, r := range cmd {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9', r == '_':
default:
return false
}
}
return true
}
// BotProfile 是 bots 表一行bot 账号的元数据与 token。
// 显示名/username/about 复用 users 行,不在此重复。
type BotProfile struct {
BotUserID int64
OwnerUserID int64
TokenSecret string
Description string
Commands []BotCommand
ChatHistory bool // privacy mode 关闭 = truebot_chat_history flag
Nochats bool // true = 不允许加群bot_nochats flag
InlineGeo bool
InlinePlaceholder string
MenuButton BotMenuButton
HasMainApp bool
HasAttachMenu bool
HasPreviewMedias bool
AppSettings *BotAppSettings
}
// BotChatState 是内置 bot当前仅 BotFather与某用户的对话状态机持久态。
type BotChatState struct {
BotUserID int64
UserID int64
// Command 是进行中的主命令newbot/token/revoke
Command string `json:"command"`
// Step 是主命令内的当前步骤name/username/choose
Step string `json:"step"`
// Draft 暂存跨步骤的中间输入(如 newbot 已收的 name
Draft map[string]string `json:"draft,omitempty"`
}
// FormatBotToken 拼装完整 bot token<bot_user_id>:<secret>)。
func FormatBotToken(botUserID int64, secret string) string {
return strconv.FormatInt(botUserID, 10) + ":" + secret
}
// ParseBotToken 拆解完整 bot token。格式非法时 ok=false不区分具体原因
// 调用方统一回 ACCESS_TOKEN_INVALID避免泄漏存在性
func ParseBotToken(token string) (botUserID int64, secret string, ok bool) {
idPart, secret, found := strings.Cut(token, ":")
if !found || idPart == "" || secret == "" {
return 0, "", false
}
id, err := strconv.ParseInt(idPart, 10, 64)
if err != nil || id <= 0 {
return 0, "", false
}
return id, secret, true
}
// ValidBotUsername 校验 bot username5-32 位、字母开头、[A-Za-z0-9_]、
// 以 bot 结尾大小写不敏感。BotFather 等种子账号不经此校验。
func ValidBotUsername(username string) bool {
if len(username) < 5 || len(username) > 32 {
return false
}
for i, r := range username {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z':
case r >= '0' && r <= '9', r == '_':
if i == 0 {
return false
}
default:
return false
}
}
return strings.HasSuffix(strings.ToLower(username), BotUsernameSuffix)
}