feat: sync built-in sticker bot

This commit is contained in:
A 2026-07-01 21:55:55 +08:00
parent 7096625e13
commit 6867d201ed
60 changed files with 7063 additions and 144 deletions

View file

@ -12,7 +12,7 @@ import (
// BotStore 是 store.BotStore 的内存实现。bot 的 users 行经注入的 UserStore 创建,
// 与 postgres 实现(单事务建 users+bots 两行)保持可见性一致。
// 内置 BotFather 的 bots 行预置token 为空 = 不可登录),对齐迁移 0090 种子。
// 内置 service bots 的 bots 行预置token 为空 = 不可登录),对齐迁移种子。
type BotStore struct {
mu sync.RWMutex
users *UserStore
@ -59,7 +59,13 @@ func NewBotStore(users *UserStore) *BotStore {
emojiPerms: make(map[[2]int64]bool),
customMethod: make(map[string]domain.BotWebViewCustomMethodQuery),
}
s.byID[domain.BotFatherUserID] = domain.BotProfile{
s.byID[domain.BotFatherUserID] = botFatherSeedProfile()
s.byID[domain.StickersBotUserID] = stickersSeedProfile()
return s
}
func botFatherSeedProfile() domain.BotProfile {
return 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.",
@ -72,7 +78,25 @@ func NewBotStore(users *UserStore) *BotStore {
{Command: "help", Description: "show help"},
},
}
return s
}
func stickersSeedProfile() domain.BotProfile {
return domain.BotProfile{
BotUserID: domain.StickersBotUserID,
OwnerUserID: domain.StickersBotUserID,
Description: "Create custom sticker and emoji packs for telesrv.",
Commands: []domain.BotCommand{
{Command: "start", Description: "start the sticker pack assistant"},
{Command: "help", Description: "show help"},
{Command: "newpack", Description: "create a sticker pack"},
{Command: "newemoji", Description: "create a custom emoji pack"},
{Command: "addsticker", Description: "add to one of your packs"},
{Command: "delsticker", Description: "remove one item from a pack"},
{Command: "publish", Description: "publish the current pack"},
{Command: "cancel", Description: "cancel the current operation"},
{Command: "packs", Description: "list your created packs"},
},
}
}
func (s *BotStore) CreateBotAccount(ctx context.Context, user domain.User, profile domain.BotProfile) (domain.User, domain.BotProfile, error) {

View file

@ -2,6 +2,7 @@ package memory
import (
"context"
"sort"
"sync"
"telesrv/internal/domain"
)
@ -14,6 +15,7 @@ type PasswordStore struct {
accountSettings map[int64]domain.AccountSettings
notifySettings map[notifySettingsKey]domain.PeerNotifySettings
stickerCollections map[stickerCollectionKey][]domain.StickerCollectionItem
userStickerSets map[int64]map[int64]domain.UserStickerSet
savedMusic map[int64][]domain.Document
businessProfiles map[int64]domain.BusinessProfile
businessChatLinks map[string]domain.BusinessChatLink
@ -48,6 +50,7 @@ func NewPasswordStore() *PasswordStore {
accountSettings: make(map[int64]domain.AccountSettings),
notifySettings: make(map[notifySettingsKey]domain.PeerNotifySettings),
stickerCollections: make(map[stickerCollectionKey][]domain.StickerCollectionItem),
userStickerSets: make(map[int64]map[int64]domain.UserStickerSet),
savedMusic: make(map[int64][]domain.Document),
businessProfiles: make(map[int64]domain.BusinessProfile),
businessChatLinks: make(map[string]domain.BusinessChatLink),
@ -234,6 +237,113 @@ func (s *PasswordStore) ClearStickerCollection(_ context.Context, userID int64,
return nil
}
func (s *PasswordStore) InstallUserStickerSet(_ context.Context, userID int64, setID int64, kind domain.StickerSetKind, archived bool, installedDate int) error {
if userID == 0 || setID == 0 {
return domain.ErrStickerInvalid
}
s.mu.Lock()
defer s.mu.Unlock()
sets := s.userStickerSets[userID]
if sets == nil {
sets = make(map[int64]domain.UserStickerSet)
s.userStickerSets[userID] = sets
}
order := int64(installedDate) << 32
sets[setID] = domain.UserStickerSet{
OwnerUserID: userID,
StickerSetID: setID,
Kind: kind,
Archived: archived,
InstalledDate: installedDate,
OrderValue: order,
}
return nil
}
func (s *PasswordStore) UninstallUserStickerSet(_ context.Context, userID int64, setID int64) error {
s.mu.Lock()
if sets := s.userStickerSets[userID]; sets != nil {
delete(sets, setID)
}
s.mu.Unlock()
return nil
}
func (s *PasswordStore) SetUserStickerSetArchived(_ context.Context, userID int64, setID int64, archived bool, now int) error {
s.mu.Lock()
if sets := s.userStickerSets[userID]; sets != nil {
if item, ok := sets[setID]; ok {
item.Archived = archived
if !archived && now > 0 {
item.OrderValue = int64(now) << 32
}
sets[setID] = item
}
}
s.mu.Unlock()
return nil
}
func (s *PasswordStore) ReorderUserStickerSets(_ context.Context, userID int64, kind domain.StickerSetKind, order []int64, now int) error {
s.mu.Lock()
defer s.mu.Unlock()
sets := s.userStickerSets[userID]
if sets == nil {
return nil
}
orderValue := int64(now) << 32
for _, id := range order {
item, ok := sets[id]
if !ok || item.Kind != kind {
continue
}
item.OrderValue = orderValue
sets[id] = item
orderValue--
}
return nil
}
func (s *PasswordStore) ListUserStickerSets(_ context.Context, userID int64, kind domain.StickerSetKind, archived *bool, offsetID int64, limit int) ([]domain.UserStickerSet, int, error) {
if limit <= 0 || limit > domain.MaxInstalledStickerSets {
limit = domain.MaxInstalledStickerSets
}
s.mu.RLock()
raw := s.userStickerSets[userID]
items := make([]domain.UserStickerSet, 0, len(raw))
for _, item := range raw {
if item.Kind != kind {
continue
}
if archived != nil && item.Archived != *archived {
continue
}
items = append(items, item)
}
s.mu.RUnlock()
sort.SliceStable(items, func(i, j int) bool {
if items[i].OrderValue == items[j].OrderValue {
return items[i].StickerSetID > items[j].StickerSetID
}
return items[i].OrderValue > items[j].OrderValue
})
total := len(items)
if offsetID != 0 {
start := 0
for i, item := range items {
if item.StickerSetID == offsetID {
start = i + 1
break
}
}
items = items[start:]
}
if len(items) > limit {
items = items[:limit]
}
return append([]domain.UserStickerSet(nil), items...), total, nil
}
func (s *PasswordStore) ListNotifyExceptions(_ context.Context, ownerUserID int64) ([]domain.NotifyException, error) {
s.mu.RLock()
defer s.mu.RUnlock()

View file

@ -15,11 +15,11 @@ type UserStore struct {
nextID int64
}
// NewUserStore 创建内存 UserStore。内置系统账号777000 / BotFather预置进表,
// 与 postgres 的迁移种子0005 / 0090保持双 store 行为一致。
// NewUserStore 创建内存 UserStore。内置系统账号777000 / BotFather / Stickers
// 预置进表,与 postgres 的迁移种子保持双 store 行为一致。
func NewUserStore() *UserStore {
s := &UserStore{byID: make(map[int64]domain.User), nextID: domain.UserIDSequenceBase}
for _, id := range []int64{domain.OfficialSystemUserID, domain.BotFatherUserID} {
for _, id := range []int64{domain.OfficialSystemUserID, domain.BotFatherUserID, domain.StickersBotUserID} {
if u, ok := domain.SystemUserByID(id); ok {
s.byID[u.ID] = u
}