package store import ( "context" "database/sql" "strings" "time" ) // GuildEmoji — кастомный эмодзи сервера (AGENT.md 7.12). type GuildEmoji struct { ID uint64 GuildID uint64 Name string FileID uint64 Animated bool CreatorID *uint64 CreatedAt time.Time } // CreateGuildEmojiParams — параметры нового эмодзи. type CreateGuildEmojiParams struct { ID uint64 GuildID uint64 Name string FileID uint64 Animated bool CreatorID uint64 } const guildEmojiColumns = `id, guild_id, name, file_id, animated, creator_id, created_at` // CreateGuildEmoji регистрирует эмодзи сервера. func (s *Store) CreateGuildEmoji(ctx context.Context, params CreateGuildEmojiParams) (*GuildEmoji, error) { if params.ID == 0 { params.ID = s.NextID() } _, err := s.writer.ExecContext(ctx, ` INSERT INTO guild_emojis (id, guild_id, name, file_id, animated, creator_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, int64(params.ID), int64(params.GuildID), params.Name, int64(params.FileID), boolToInt(params.Animated), int64(params.CreatorID), s.Now()) if err != nil { return nil, mapError(err) } return s.GetGuildEmoji(ctx, params.ID) } func (s *Store) GetGuildEmoji(ctx context.Context, id uint64) (*GuildEmoji, error) { row := s.reader.QueryRowContext(ctx, `SELECT `+guildEmojiColumns+` FROM guild_emojis WHERE id = ?`, int64(id)) return scanGuildEmoji(row) } // ListGuildEmojis возвращает эмодзи сервера в порядке добавления. func (s *Store) ListGuildEmojis(ctx context.Context, guildID uint64, limit int) ([]GuildEmoji, error) { if limit <= 0 || limit > 500 { limit = 200 } rows, err := s.reader.QueryContext(ctx, ` SELECT `+guildEmojiColumns+` FROM guild_emojis WHERE guild_id = ? ORDER BY name LIMIT ?`, int64(guildID), limit) if err != nil { return nil, err } defer rows.Close() emojis := make([]GuildEmoji, 0, 16) for rows.Next() { emoji, err := scanGuildEmoji(rows) if err != nil { return nil, err } emojis = append(emojis, *emoji) } return emojis, rows.Err() } // ListEmojisForGuilds возвращает эмодзи нескольких серверов (для READY). func (s *Store) ListEmojisForGuilds(ctx context.Context, guildIDs []uint64) ([]GuildEmoji, error) { if len(guildIDs) == 0 { return []GuildEmoji{}, nil } placeholders := strings.TrimSuffix(strings.Repeat("?,", len(guildIDs)), ",") args := make([]any, 0, len(guildIDs)) for _, id := range guildIDs { args = append(args, int64(id)) } rows, err := s.reader.QueryContext(ctx, `SELECT `+guildEmojiColumns+` FROM guild_emojis WHERE guild_id IN (`+placeholders+`) ORDER BY name`, args...) if err != nil { return nil, err } defer rows.Close() emojis := make([]GuildEmoji, 0, 16) for rows.Next() { emoji, err := scanGuildEmoji(rows) if err != nil { return nil, err } emojis = append(emojis, *emoji) } return emojis, rows.Err() } // CountGuildEmojis считает эмодзи сервера (лимит по умолчанию — 100). func (s *Store) CountGuildEmojis(ctx context.Context, guildID uint64) (int, error) { var count int err := s.reader.QueryRowContext(ctx, `SELECT COUNT(*) FROM guild_emojis WHERE guild_id = ?`, int64(guildID)).Scan(&count) return count, err } // RenameGuildEmoji меняет имя эмодзи. func (s *Store) RenameGuildEmoji(ctx context.Context, id uint64, name string) (*GuildEmoji, error) { result, err := s.writer.ExecContext(ctx, `UPDATE guild_emojis SET name = ? WHERE id = ?`, name, int64(id)) if err != nil { return nil, mapError(err) } if affected, err := result.RowsAffected(); err == nil && affected == 0 { return nil, ErrNotFound } return s.GetGuildEmoji(ctx, id) } // DeleteGuildEmoji удаляет эмодзи сервера. func (s *Store) DeleteGuildEmoji(ctx context.Context, id uint64) error { result, err := s.writer.ExecContext(ctx, `DELETE FROM guild_emojis WHERE id = ?`, int64(id)) if err != nil { return err } if affected, err := result.RowsAffected(); err == nil && affected == 0 { return ErrNotFound } return nil } // EmojiNamesByID отдаёт имена эмодзи по идентификаторам (для проверки реакций). func (s *Store) EmojiByFileID(ctx context.Context, fileID uint64) (*GuildEmoji, error) { row := s.reader.QueryRowContext(ctx, `SELECT `+guildEmojiColumns+` FROM guild_emojis WHERE file_id = ?`, int64(fileID)) return scanGuildEmoji(row) } func scanGuildEmoji(scanner interface{ Scan(...any) error }) (*GuildEmoji, error) { var ( emoji GuildEmoji animated int creatorID sql.NullInt64 createdAt string ) err := scanner.Scan(&emoji.ID, &emoji.GuildID, &emoji.Name, &emoji.FileID, &animated, &creatorID, &createdAt) if err != nil { return nil, mapError(err) } emoji.Animated = animated == 1 emoji.CreatorID = optionalID(creatorID) emoji.CreatedAt = parseTimestamp(createdAt) return &emoji, nil }