chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,769 @@
package memory
import (
"context"
"strconv"
"strings"
"sync"
"time"
"telesrv/internal/domain"
)
// BotStore 是 store.BotStore 的内存实现。bot 的 users 行经注入的 UserStore 创建,
// 与 postgres 实现(单事务建 users+bots 两行)保持可见性一致。
// 内置 BotFather 的 bots 行预置(token 为空 = 不可登录),对齐迁移 0090 种子。
type BotStore struct {
mu sync.RWMutex
users *UserStore
byID map[int64]domain.BotProfile
states map[[2]int64]domain.BotChatState
permissions map[[2]int64]bool
appsByID map[int64]domain.BotApp
appShortNames map[botAppShortKey]int64
appSettings map[int64]domain.BotAppSettings
previewMedia map[int64]map[int64]domain.BotAppPreviewMedia
previewSeq int64
attachMenu map[int64]domain.BotAttachMenuBot
attachStates map[[2]int64]domain.BotAttachMenuState
requestButtons map[requestedButtonKey]domain.BotRequestedWebViewButton
emojiPerms map[[2]int64]bool
customMethod map[string]domain.BotWebViewCustomMethodQuery
}
type botAppShortKey struct {
botUserID int64
shortName string
}
type requestedButtonKey struct {
botUserID int64
userID int64
reqID string
}
// NewBotStore 创建内存 BotStore。
func NewBotStore(users *UserStore) *BotStore {
s := &BotStore{
users: users,
byID: make(map[int64]domain.BotProfile),
states: make(map[[2]int64]domain.BotChatState),
permissions: make(map[[2]int64]bool),
appsByID: make(map[int64]domain.BotApp),
appShortNames: make(map[botAppShortKey]int64),
appSettings: make(map[int64]domain.BotAppSettings),
previewMedia: make(map[int64]map[int64]domain.BotAppPreviewMedia),
attachMenu: make(map[int64]domain.BotAttachMenuBot),
attachStates: make(map[[2]int64]domain.BotAttachMenuState),
requestButtons: make(map[requestedButtonKey]domain.BotRequestedWebViewButton),
emojiPerms: make(map[[2]int64]bool),
customMethod: make(map[string]domain.BotWebViewCustomMethodQuery),
}
s.byID[domain.BotFatherUserID] = domain.BotProfile{
BotUserID: domain.BotFatherUserID,
OwnerUserID: domain.BotFatherUserID,
Description: "BotFather is the one bot to rule them all. Use it to create new bot accounts and manage your existing bots.",
Commands: []domain.BotCommand{
{Command: "newbot", Description: "create a new bot"},
{Command: "mybots", Description: "list your bots"},
{Command: "token", Description: "show a bot's token"},
{Command: "revoke", Description: "revoke a bot's token"},
{Command: "cancel", Description: "cancel the current operation"},
{Command: "help", Description: "show help"},
},
}
return s
}
func (s *BotStore) CreateBotAccount(ctx context.Context, user domain.User, profile domain.BotProfile) (domain.User, domain.BotProfile, error) {
user.Phone = ""
user.Username = strings.TrimSpace(strings.TrimPrefix(user.Username, "@"))
user.Bot = true
if user.BotInfoVersion < 1 {
user.BotInfoVersion = 1
}
// 持 BotStore.mu 跨「复核计数 → 建 users 行 → 写 profile」,与 postgres 的
// advisory lock + 事务内复核对齐,封死 count-then-insert TOCTOU。锁顺序
// BotStore.mu → UserStore.mu(UserStore 不反向引用 BotStore,无死锁)。
s.mu.Lock()
defer s.mu.Unlock()
owned := 0
for _, p := range s.byID {
if p.OwnerUserID == profile.OwnerUserID && p.BotUserID != p.OwnerUserID {
owned++
}
}
if owned >= domain.MaxBotsPerOwner {
return domain.User{}, domain.BotProfile{}, domain.ErrBotsTooMany
}
created, err := s.users.Create(ctx, user)
if err != nil {
return domain.User{}, domain.BotProfile{}, err
}
profile.BotUserID = created.ID
s.byID[created.ID] = cloneBotProfile(profile)
return created, profile, nil
}
func (s *BotStore) GetBot(_ context.Context, botUserID int64) (domain.BotProfile, bool, error) {
s.mu.RLock()
p, ok := s.byID[botUserID]
if ok {
p = s.enrichBotProfileLocked(p)
}
s.mu.RUnlock()
if !ok {
return domain.BotProfile{}, false, nil
}
return cloneBotProfile(p), true, nil
}
func (s *BotStore) GetBots(_ context.Context, botUserIDs []int64) (map[int64]domain.BotProfile, error) {
if len(botUserIDs) == 0 {
return nil, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
out := make(map[int64]domain.BotProfile, len(botUserIDs))
for _, id := range botUserIDs {
if id == 0 {
continue
}
if _, ok := out[id]; ok {
continue
}
if p, ok := s.byID[id]; ok {
out[id] = cloneBotProfile(s.enrichBotProfileLocked(p))
}
}
return out, nil
}
func (s *BotStore) ListBotsByOwner(_ context.Context, ownerUserID int64) ([]domain.BotProfile, error) {
if ownerUserID == 0 {
return nil, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]domain.BotProfile, 0)
for _, p := range s.byID {
if p.OwnerUserID == ownerUserID && p.BotUserID != p.OwnerUserID {
out = append(out, cloneBotProfile(s.enrichBotProfileLocked(p)))
}
}
sortBotProfiles(out)
return out, nil
}
func (s *BotStore) CountBotsByOwner(_ context.Context, ownerUserID int64) (int, error) {
if ownerUserID == 0 {
return 0, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
n := 0
for _, p := range s.byID {
if p.OwnerUserID == ownerUserID && p.BotUserID != p.OwnerUserID {
n++
}
}
return n, nil
}
func (s *BotStore) UpdateBotTokenSecret(_ context.Context, botUserID int64, secret string) error {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.byID[botUserID]
if !ok {
return domain.ErrBotNotFound
}
p.TokenSecret = secret
s.byID[botUserID] = p
return nil
}
// editProfile 在 BotStore.mu 内改 bots 行字段,随后 bump users 的 bot_info_version。
// 与 postgres 不同(非单事务),但 memory 仅测试替身、元数据更新低频,可接受。
func (s *BotStore) editProfile(botUserID int64, fn func(p *domain.BotProfile)) (int, error) {
s.mu.Lock()
p, ok := s.byID[botUserID]
if !ok {
s.mu.Unlock()
return 0, domain.ErrBotNotFound
}
fn(&p)
s.byID[botUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(botUserID)
if !ok {
return 0, domain.ErrBotNotFound
}
return ver, nil
}
func (s *BotStore) UpdateBotCommands(_ context.Context, botUserID int64, commands []domain.BotCommand) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) {
p.Commands = append([]domain.BotCommand(nil), commands...)
})
}
func (s *BotStore) UpdateBotInfo(_ context.Context, botUserID int64, upd domain.BotInfoUpdate) (int, error) {
if _, ok := s.GetBotProfile(botUserID); !ok {
return 0, domain.ErrBotNotFound
}
if upd.SetName || upd.SetAbout {
if !s.users.updateBotProfile(botUserID, upd.SetName, upd.Name, upd.SetAbout, upd.About) {
return 0, domain.ErrBotNotFound
}
}
return s.editProfile(botUserID, func(p *domain.BotProfile) {
if upd.SetDescription {
p.Description = upd.Description
}
})
}
func (s *BotStore) UpdateBotMenuButton(_ context.Context, botUserID int64, button domain.BotMenuButton) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) {
p.MenuButton = button
})
}
func (s *BotStore) SetBotInlinePlaceholder(_ context.Context, botUserID int64, placeholder string) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) {
p.InlinePlaceholder = placeholder
})
}
func (s *BotStore) SetBotInlineGeo(_ context.Context, botUserID int64, inlineGeo bool) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) { p.InlineGeo = inlineGeo })
}
func (s *BotStore) SetBotNochats(_ context.Context, botUserID int64, nochats bool) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) { p.Nochats = nochats })
}
func (s *BotStore) SetBotChatHistory(_ context.Context, botUserID int64, chatHistory bool) (int, error) {
return s.editProfile(botUserID, func(p *domain.BotProfile) { p.ChatHistory = chatHistory })
}
func (s *BotStore) CanBotSendMessage(_ context.Context, botUserID, userID int64) (bool, error) {
if botUserID == 0 || userID == 0 || botUserID == userID {
return false, nil
}
s.mu.RLock()
allowed := s.permissions[[2]int64{botUserID, userID}]
s.mu.RUnlock()
return allowed, nil
}
func (s *BotStore) AllowBotSendMessage(_ context.Context, botUserID, userID int64, _ bool) (bool, error) {
if botUserID == 0 || userID == 0 || botUserID == userID {
return false, domain.ErrBotNotFound
}
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.byID[botUserID]; !ok {
return false, domain.ErrBotNotFound
}
key := [2]int64{botUserID, userID}
if s.permissions[key] {
return false, nil
}
s.permissions[key] = true
return true, nil
}
func (s *BotStore) UpsertBotApp(_ context.Context, app domain.BotApp) (domain.BotApp, int, error) {
if app.BotUserID == 0 || app.ID == 0 || app.AccessHash == 0 || app.ShortName == "" || app.URL == "" {
return domain.BotApp{}, 0, domain.ErrBotAppInvalid
}
app.ShortName = strings.ToLower(strings.TrimSpace(app.ShortName))
s.mu.Lock()
if _, ok := s.byID[app.BotUserID]; !ok {
s.mu.Unlock()
return domain.BotApp{}, 0, domain.ErrBotNotFound
}
if existing, ok := s.appShortNames[botAppShortKey{botUserID: app.BotUserID, shortName: app.ShortName}]; ok && existing != app.ID {
s.mu.Unlock()
return domain.BotApp{}, 0, domain.ErrBotAppInvalid
}
if old, ok := s.appsByID[app.ID]; ok && old.BotUserID != app.BotUserID {
s.mu.Unlock()
return domain.BotApp{}, 0, domain.ErrBotAppInvalid
}
s.appsByID[app.ID] = app
s.appShortNames[botAppShortKey{botUserID: app.BotUserID, shortName: app.ShortName}] = app.ID
p := s.byID[app.BotUserID]
if app.Main {
for id, item := range s.appsByID {
if item.BotUserID == app.BotUserID && id != app.ID && item.Main {
item.Main = false
s.appsByID[id] = item
}
}
p.HasMainApp = true
}
if app.HasSettings {
p.HasMainApp = p.HasMainApp || app.Main
}
s.byID[app.BotUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(app.BotUserID)
if !ok {
return domain.BotApp{}, 0, domain.ErrBotNotFound
}
return cloneBotApp(app), ver, nil
}
func (s *BotStore) GetBotAppByID(_ context.Context, appID, accessHash int64) (domain.BotApp, bool, error) {
if appID == 0 || accessHash == 0 {
return domain.BotApp{}, false, nil
}
s.mu.RLock()
app, ok := s.appsByID[appID]
s.mu.RUnlock()
if !ok || app.AccessHash != accessHash {
return domain.BotApp{}, false, nil
}
return cloneBotApp(app), true, nil
}
func (s *BotStore) GetBotAppByShortName(_ context.Context, botUserID int64, shortName string) (domain.BotApp, bool, error) {
key := botAppShortKey{botUserID: botUserID, shortName: strings.ToLower(strings.TrimSpace(shortName))}
s.mu.RLock()
id, ok := s.appShortNames[key]
var app domain.BotApp
if ok {
app, ok = s.appsByID[id]
}
s.mu.RUnlock()
if !ok {
return domain.BotApp{}, false, nil
}
return cloneBotApp(app), true, nil
}
func (s *BotStore) GetMainBotApp(_ context.Context, botUserID int64) (domain.BotApp, bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
for _, app := range s.appsByID {
if app.BotUserID == botUserID && app.Main {
return cloneBotApp(app), true, nil
}
}
return domain.BotApp{}, false, nil
}
func (s *BotStore) ListBotApps(_ context.Context, botUserID int64) ([]domain.BotApp, error) {
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]domain.BotApp, 0)
for _, app := range s.appsByID {
if app.BotUserID == botUserID {
out = append(out, cloneBotApp(app))
}
}
sortBotApps(out)
return out, nil
}
func (s *BotStore) GetBotAppSettings(_ context.Context, botUserID int64) (domain.BotAppSettings, bool, error) {
s.mu.RLock()
settings, ok := s.appSettings[botUserID]
s.mu.RUnlock()
if !ok {
return domain.BotAppSettings{}, false, nil
}
return cloneBotAppSettings(settings), true, nil
}
func (s *BotStore) UpsertBotAppSettings(_ context.Context, botUserID int64, settings domain.BotAppSettings) (int, error) {
s.mu.Lock()
if _, ok := s.byID[botUserID]; !ok {
s.mu.Unlock()
return 0, domain.ErrBotNotFound
}
s.appSettings[botUserID] = cloneBotAppSettings(settings)
p := s.byID[botUserID]
c := cloneBotAppSettings(settings)
p.AppSettings = &c
s.byID[botUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(botUserID)
if !ok {
return 0, domain.ErrBotNotFound
}
return ver, nil
}
func (s *BotStore) ListBotAppPreviewMedia(_ context.Context, botUserID, appID int64) ([]domain.BotAppPreviewMedia, error) {
s.mu.RLock()
defer s.mu.RUnlock()
items := s.previewMedia[appID]
out := make([]domain.BotAppPreviewMedia, 0, len(items))
for _, media := range items {
if media.BotUserID == botUserID {
out = append(out, media)
}
}
sortBotPreviewMedia(out)
return out, nil
}
func (s *BotStore) UpsertBotAppPreviewMedia(_ context.Context, media domain.BotAppPreviewMedia) (domain.BotAppPreviewMedia, int, error) {
if media.BotUserID == 0 || media.AppID == 0 || (media.PhotoID == 0 && media.DocumentID == 0) {
return domain.BotAppPreviewMedia{}, 0, domain.ErrBotAppInvalid
}
s.mu.Lock()
if app, ok := s.appsByID[media.AppID]; !ok || app.BotUserID != media.BotUserID {
s.mu.Unlock()
return domain.BotAppPreviewMedia{}, 0, domain.ErrBotAppInvalid
}
if s.previewMedia[media.AppID] == nil {
s.previewMedia[media.AppID] = make(map[int64]domain.BotAppPreviewMedia)
}
if media.ID == 0 {
s.previewSeq++
media.ID = s.previewSeq
}
if media.Position <= 0 {
media.Position = len(s.previewMedia[media.AppID]) + 1
}
s.previewMedia[media.AppID][media.ID] = media
p := s.byID[media.BotUserID]
p.HasPreviewMedias = true
s.byID[media.BotUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(media.BotUserID)
if !ok {
return domain.BotAppPreviewMedia{}, 0, domain.ErrBotNotFound
}
return media, ver, nil
}
func (s *BotStore) DeleteBotAppPreviewMedia(_ context.Context, botUserID, appID, mediaID int64) (int, error) {
s.mu.Lock()
items := s.previewMedia[appID]
if mediaID == 0 || items == nil {
s.mu.Unlock()
return 0, domain.ErrBotAppInvalid
}
media, ok := items[mediaID]
if !ok || media.BotUserID != botUserID {
s.mu.Unlock()
return 0, domain.ErrBotAppInvalid
}
delete(items, mediaID)
has := false
for _, items := range s.previewMedia {
for _, media := range items {
if media.BotUserID == botUserID {
has = true
break
}
}
}
p := s.byID[botUserID]
p.HasPreviewMedias = has
s.byID[botUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(botUserID)
if !ok {
return 0, domain.ErrBotNotFound
}
return ver, nil
}
func (s *BotStore) ReorderBotAppPreviewMedia(_ context.Context, botUserID, appID int64, mediaIDs []int64) (int, error) {
s.mu.Lock()
items := s.previewMedia[appID]
if items == nil {
s.mu.Unlock()
return 0, domain.ErrBotAppInvalid
}
for pos, id := range mediaIDs {
media, ok := items[id]
if !ok || media.BotUserID != botUserID {
s.mu.Unlock()
return 0, domain.ErrBotAppInvalid
}
media.Position = pos + 1
items[id] = media
}
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(botUserID)
if !ok {
return 0, domain.ErrBotNotFound
}
return ver, nil
}
func (s *BotStore) UpsertAttachMenuBot(_ context.Context, bot domain.BotAttachMenuBot) (int, error) {
if bot.BotUserID == 0 || bot.ShortName == "" {
return 0, domain.ErrBotAttachMenuInvalid
}
bot.ShortName = strings.ToLower(strings.TrimSpace(bot.ShortName))
s.mu.Lock()
if _, ok := s.byID[bot.BotUserID]; !ok {
s.mu.Unlock()
return 0, domain.ErrBotNotFound
}
s.attachMenu[bot.BotUserID] = cloneAttachMenuBot(bot)
p := s.byID[bot.BotUserID]
p.HasAttachMenu = true
s.byID[bot.BotUserID] = p
s.mu.Unlock()
ver, ok := s.users.bumpBotInfoVersion(bot.BotUserID)
if !ok {
return 0, domain.ErrBotNotFound
}
return ver, nil
}
func (s *BotStore) GetAttachMenuBot(_ context.Context, botUserID int64) (domain.BotAttachMenuBot, bool, error) {
s.mu.RLock()
bot, ok := s.attachMenu[botUserID]
s.mu.RUnlock()
if !ok {
return domain.BotAttachMenuBot{}, false, nil
}
return cloneAttachMenuBot(bot), true, nil
}
func (s *BotStore) ListAttachMenuBots(_ context.Context) ([]domain.BotAttachMenuBot, error) {
s.mu.RLock()
defer s.mu.RUnlock()
out := make([]domain.BotAttachMenuBot, 0, len(s.attachMenu))
for _, bot := range s.attachMenu {
out = append(out, cloneAttachMenuBot(bot))
}
sortAttachMenuBots(out)
return out, nil
}
func (s *BotStore) GetAttachMenuState(_ context.Context, userID, botUserID int64) (domain.BotAttachMenuState, bool, error) {
s.mu.RLock()
state, ok := s.attachStates[[2]int64{userID, botUserID}]
s.mu.RUnlock()
if !ok {
return domain.BotAttachMenuState{}, false, nil
}
return state, true, nil
}
func (s *BotStore) SetAttachMenuState(_ context.Context, state domain.BotAttachMenuState) (domain.BotAttachMenuState, error) {
if state.UserID == 0 || state.BotUserID == 0 || state.UserID == state.BotUserID {
return domain.BotAttachMenuState{}, domain.ErrBotAttachMenuInvalid
}
s.mu.Lock()
if _, ok := s.attachMenu[state.BotUserID]; !ok {
s.mu.Unlock()
return domain.BotAttachMenuState{}, domain.ErrBotAttachMenuInvalid
}
s.attachStates[[2]int64{state.UserID, state.BotUserID}] = state
s.mu.Unlock()
return state, nil
}
func (s *BotStore) SaveRequestedWebViewButton(_ context.Context, button domain.BotRequestedWebViewButton) error {
if button.BotUserID == 0 || button.UserID == 0 || button.WebAppReqID == "" || button.ButtonID == 0 {
return domain.ErrBotRequestedButtonInvalid
}
s.mu.Lock()
s.requestButtons[requestedButtonKey{botUserID: button.BotUserID, userID: button.UserID, reqID: button.WebAppReqID}] = button
s.mu.Unlock()
return nil
}
func (s *BotStore) GetRequestedWebViewButton(_ context.Context, botUserID, userID int64, webAppReqID string) (domain.BotRequestedWebViewButton, bool, error) {
key := requestedButtonKey{botUserID: botUserID, userID: userID, reqID: webAppReqID}
s.mu.Lock()
button, ok := s.requestButtons[key]
if ok && !button.ExpiresAt.IsZero() && !button.ExpiresAt.After(time.Now()) {
delete(s.requestButtons, key)
ok = false
}
s.mu.Unlock()
if !ok {
return domain.BotRequestedWebViewButton{}, false, nil
}
return button, true, nil
}
func (s *BotStore) DeleteRequestedWebViewButton(_ context.Context, botUserID, userID int64, webAppReqID string) error {
s.mu.Lock()
delete(s.requestButtons, requestedButtonKey{botUserID: botUserID, userID: userID, reqID: webAppReqID})
s.mu.Unlock()
return nil
}
func (s *BotStore) SetBotEmojiStatusPermission(_ context.Context, botUserID, userID int64, allowed bool) error {
if botUserID == 0 || userID == 0 || botUserID == userID {
return domain.ErrBotNotFound
}
s.mu.Lock()
if _, ok := s.byID[botUserID]; !ok {
s.mu.Unlock()
return domain.ErrBotNotFound
}
if allowed {
s.emojiPerms[[2]int64{botUserID, userID}] = true
} else {
delete(s.emojiPerms, [2]int64{botUserID, userID})
}
s.mu.Unlock()
return nil
}
func (s *BotStore) BotEmojiStatusPermission(_ context.Context, botUserID, userID int64) (bool, error) {
s.mu.RLock()
allowed := s.emojiPerms[[2]int64{botUserID, userID}]
s.mu.RUnlock()
return allowed, nil
}
func (s *BotStore) PutWebViewCustomMethodQuery(_ context.Context, query domain.BotWebViewCustomMethodQuery) error {
if query.ID == "" || query.BotUserID == 0 || query.UserID == 0 || query.CustomMethod == "" {
return domain.ErrBotCustomMethodUnavailable
}
s.mu.Lock()
s.customMethod[query.ID] = query
s.mu.Unlock()
return nil
}
// GetBotProfile 是 GetBot 的同步只读快照(editProfile 前置存在性检查用)。
func (s *BotStore) GetBotProfile(botUserID int64) (domain.BotProfile, bool) {
s.mu.RLock()
p, ok := s.byID[botUserID]
s.mu.RUnlock()
return p, ok
}
func (s *BotStore) GetBotChatState(_ context.Context, botUserID, userID int64) (domain.BotChatState, bool, error) {
s.mu.RLock()
state, ok := s.states[[2]int64{botUserID, userID}]
s.mu.RUnlock()
if !ok {
return domain.BotChatState{}, false, nil
}
return cloneBotChatState(state), true, nil
}
func (s *BotStore) UpsertBotChatState(_ context.Context, state domain.BotChatState) error {
s.mu.Lock()
s.states[[2]int64{state.BotUserID, state.UserID}] = cloneBotChatState(state)
s.mu.Unlock()
return nil
}
func (s *BotStore) DeleteBotChatState(_ context.Context, botUserID, userID int64) error {
s.mu.Lock()
delete(s.states, [2]int64{botUserID, userID})
s.mu.Unlock()
return nil
}
func cloneBotProfile(p domain.BotProfile) domain.BotProfile {
out := p
out.Commands = append([]domain.BotCommand(nil), p.Commands...)
if p.AppSettings != nil {
settings := cloneBotAppSettings(*p.AppSettings)
out.AppSettings = &settings
}
return out
}
func (s *BotStore) enrichBotProfileLocked(p domain.BotProfile) domain.BotProfile {
if _, ok := s.appSettings[p.BotUserID]; ok {
settings := cloneBotAppSettings(s.appSettings[p.BotUserID])
p.AppSettings = &settings
}
for _, app := range s.appsByID {
if app.BotUserID == p.BotUserID && app.Main {
p.HasMainApp = true
break
}
}
if _, ok := s.attachMenu[p.BotUserID]; ok {
p.HasAttachMenu = true
}
for _, items := range s.previewMedia {
for _, media := range items {
if media.BotUserID == p.BotUserID {
p.HasPreviewMedias = true
return p
}
}
}
return p
}
func cloneBotApp(app domain.BotApp) domain.BotApp {
return app
}
func cloneBotAppSettings(settings domain.BotAppSettings) domain.BotAppSettings {
out := settings
out.PlaceholderPath = append([]byte(nil), settings.PlaceholderPath...)
return out
}
func cloneAttachMenuBot(bot domain.BotAttachMenuBot) domain.BotAttachMenuBot {
out := bot
out.PeerTypes = append([]string(nil), bot.PeerTypes...)
out.Icons = make([]domain.BotAttachMenuIcon, len(bot.Icons))
for i, icon := range bot.Icons {
out.Icons[i] = icon
out.Icons[i].Colors = append([]domain.BotAttachMenuIconColor(nil), icon.Colors...)
}
return out
}
func cloneBotChatState(state domain.BotChatState) domain.BotChatState {
out := state
if state.Draft != nil {
out.Draft = make(map[string]string, len(state.Draft))
for k, v := range state.Draft {
out.Draft[k] = v
}
}
return out
}
func sortBotProfiles(list []domain.BotProfile) {
for i := 1; i < len(list); i++ {
for j := i; j > 0 && list[j].BotUserID < list[j-1].BotUserID; j-- {
list[j], list[j-1] = list[j-1], list[j]
}
}
}
func sortBotApps(list []domain.BotApp) {
for i := 1; i < len(list); i++ {
for j := i; j > 0 && appSortKey(list[j]) < appSortKey(list[j-1]); j-- {
list[j], list[j-1] = list[j-1], list[j]
}
}
}
func appSortKey(app domain.BotApp) string {
return strconv.FormatInt(app.BotUserID, 10) + ":" + app.ShortName
}
func sortBotPreviewMedia(list []domain.BotAppPreviewMedia) {
for i := 1; i < len(list); i++ {
for j := i; j > 0 && (list[j].Position < list[j-1].Position || (list[j].Position == list[j-1].Position && list[j].ID < list[j-1].ID)); j-- {
list[j], list[j-1] = list[j-1], list[j]
}
}
}
func sortAttachMenuBots(list []domain.BotAttachMenuBot) {
for i := 1; i < len(list); i++ {
for j := i; j > 0 && list[j].BotUserID < list[j-1].BotUserID; j-- {
list[j], list[j-1] = list[j-1], list[j]
}
}
}