162 lines
4.9 KiB
Go
162 lines
4.9 KiB
Go
|
|
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
|
||
|
|
}
|