diff options
| author | William Hergès <william@herges.fr> | 2026-01-17 16:31:25 +0100 |
|---|---|---|
| committer | William Hergès <william@herges.fr> | 2026-01-17 16:31:25 +0100 |
| commit | febb77607e81fbb182dd456733ea5adafda44ed4 (patch) | |
| tree | 3ff850a34d716df8315d3b9839768e6a4ff60a4c | |
| parent | 55befa3a53ab56bac31026b1b6099b2d31fd6d91 (diff) | |
perf(member): use stat for load
| -rw-r--r-- | commands/rank.go | 75 | ||||
| -rw-r--r-- | commands/reset.go | 48 | ||||
| -rw-r--r-- | commands/stats.go | 97 | ||||
| -rw-r--r-- | events.go | 22 | ||||
| -rw-r--r-- | justfile | 7 | ||||
| -rw-r--r-- | main.go | 12 | ||||
| -rw-r--r-- | user/member.go | 40 | ||||
| -rw-r--r-- | user/state.go | 69 | ||||
| -rw-r--r-- | user/xp.go | 18 |
9 files changed, 226 insertions, 162 deletions
diff --git a/commands/rank.go b/commands/rank.go index 4080864..c65c758 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "git.anhgelus.world/anhgelus/les-copaings-bot/exp" @@ -10,51 +11,45 @@ import ( "github.com/nyttikord/gokord/event" ) -func Rank(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { - c := user.GetCopaing(i.Member.User.ID, i.GuildID) // current user = member who used /rank - msg := "Votre niveau" - m := i.Member - var err error - if v, ok := optMap["copaing"]; ok { - u := v.UserValue(s.UserAPI()) - if u.Bot { - err = resp.SetMessage("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() - if err != nil { - s.Logger().Error("reply error user is a bot", "error", err) +func Rank(ctx context.Context) func(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { + return func(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { + c := user.GetCopaing(ctx, i.Member.User.ID, i.GuildID) // current user = member who used /rank + msg := "Votre niveau" + m := i.Member + var err error + if v, ok := optMap["copaing"]; ok { + u := v.UserValue(s.UserAPI()) + if u.Bot { + err = resp.SetMessage("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() + if err != nil { + s.Logger().Error("reply error user is a bot", "error", err) + } + return } - return - } - m, err = s.GuildAPI().Member(i.GuildID, u.ID) - if err != nil { - s.Logger().Error("fetching guild member", "error", err, "user", u.Username, "guild", i.GuildID) - err = resp.SetMessage("Erreur : impossible de récupérer le membre").IsEphemeral().Send() + m, err = s.GuildAPI().Member(i.GuildID, u.ID) if err != nil { - s.Logger().Error("reply error fetching guild member", "error", err) + s.Logger().Error("fetching guild member", "error", err, "user", u.Username, "guild", i.GuildID) + err = resp.SetMessage("Erreur : impossible de récupérer le membre").IsEphemeral().Send() + if err != nil { + s.Logger().Error("reply error fetching guild member", "error", err) + } + return } - return + c = user.GetCopaing(ctx, u.ID, i.GuildID) // current user = member targeted by member who wrote /rank + msg = fmt.Sprintf("Le niveau de %s", m.DisplayName()) } - c = user.GetCopaing(u.ID, i.GuildID) // current user = member targeted by member who wrote /rank - msg = fmt.Sprintf("Le niveau de %s", m.DisplayName()) - } - xp, err := c.GetXP(s.Logger()) - if err != nil { - s.Logger().Error("fetching xp", "error", err, "copaing", c.ID, "guild", i.GuildID) - err = resp.SetMessage("Erreur : impossible de récupérer l'XP").IsEphemeral().Send() + xp := c.XPs + lvl := exp.Level(xp) + nxtLvlXP := exp.LevelXP(lvl + 1) + err = resp.SetMessage(fmt.Sprintf( + "%s : **%d**\n> XP : %d\n> Prochain niveau dans %d XP", + msg, + lvl, + xp, + nxtLvlXP-xp, + )).Send() if err != nil { - s.Logger().Error("reply error fetching xp", "error", err) + s.Logger().Error("sending rank", "error", err) } - return - } - lvl := exp.Level(xp) - nxtLvlXP := exp.LevelXP(lvl + 1) - err = resp.SetMessage(fmt.Sprintf( - "%s : **%d**\n> XP : %d\n> Prochain niveau dans %d XP", - msg, - lvl, - xp, - nxtLvlXP-xp, - )).Send() - if err != nil { - s.Logger().Error("sending rank", "error", err) } } diff --git a/commands/reset.go b/commands/reset.go index 21bfeb7..56d3e44 100644 --- a/commands/reset.go +++ b/commands/reset.go @@ -1,6 +1,8 @@ package commands import ( + "context" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/cmd" @@ -16,31 +18,33 @@ func Reset(s bot.Session, i *event.InteractionCreate, _ cmd.OptionMap, resp *cmd } } -func ResetUser(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { - resp.IsEphemeral() - v, ok := optMap["user"] - if !ok { - if err := resp.SetMessage("Le user n'a pas été renseigné.").Send(); err != nil { - s.Logger().Error("sending error copaing not set", "error", err) +func ResetUser(ctx context.Context) func(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { + return func(s bot.Session, i *event.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + v, ok := optMap["user"] + if !ok { + if err := resp.SetMessage("Le user n'a pas été renseigné.").Send(); err != nil { + s.Logger().Error("sending error copaing not set", "error", err) + } + return } - return - } - m := v.UserValue(s.UserAPI()) - if m.Bot { - if err := resp.SetMessage("Les bots n'ont pas de niveau :upside_down:").Send(); err != nil { - s.Logger().Error("sending error bot does not have xp", "error", err) + m := v.UserValue(s.UserAPI()) + if m.Bot { + if err := resp.SetMessage("Les bots n'ont pas de niveau :upside_down:").Send(); err != nil { + s.Logger().Error("sending error bot does not have xp", "error", err) + } + return } - return - } - err := user.GetCopaing(m.ID, i.GuildID).Delete() - if err != nil { - s.Logger().Error("deleting copaing", "error", err, "user", m.Username, "guild", i.GuildID) - err = resp.SetMessage("Erreur : impossible de reset l'utilisateur").Send() + err := user.GetCopaing(ctx, m.ID, i.GuildID).Copaing(ctx).Delete(ctx) if err != nil { - s.Logger().Error("sending error while deleting", "error", err) + s.Logger().Error("deleting copaing", "error", err, "user", m.Username, "guild", i.GuildID) + err = resp.SetMessage("Erreur : impossible de reset l'utilisateur").Send() + if err != nil { + s.Logger().Error("sending error while deleting", "error", err) + } + } + if err = resp.SetMessage("Le user bien été reset.").Send(); err != nil { + s.Logger().Error("sending reset success", "error", err) } - } - if err = resp.SetMessage("Le user bien été reset.").Send(); err != nil { - s.Logger().Error("sending reset success", "error", err) } } diff --git a/commands/stats.go b/commands/stats.go index 4fc35ae..716616d 100644 --- a/commands/stats.go +++ b/commands/stats.go @@ -2,6 +2,7 @@ package commands import ( "bytes" + "context" "errors" "fmt" "image/color" @@ -46,56 +47,58 @@ var colors = []color.RGBA{ {193, 18, 31, 255}, } -func Stats(s bot.Session, i *event.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { - cfg := config.GetGuildConfig(i.GuildID) - days := 15 - if gokord.Debug { - days = 90 - } - if v, ok := opt["days"]; ok { - in := v.IntValue() - if in < 1 || uint(in) > cfg.DaysXPRemains { - msg := fmt.Sprintf("Nombre de jours invalide. Il doit être strictement positif et inférieur à %d", cfg.DaysXPRemains) - if err := resp.SetMessage(msg).IsEphemeral().Send(); err != nil { - s.Logger().Error("sending error invalid days", "error", err) - } - return +func Stats(ctx context.Context) func(s bot.Session, i *event.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { + return func(s bot.Session, i *event.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { + cfg := config.GetGuildConfig(i.GuildID) + days := 15 + if gokord.Debug { + days = 90 } - days = int(in) - } - err := resp.IsDeferred().Send() - if err != nil { - s.Logger().Error("sending deferred", "error", err) - return - } - go func() { - var w io.WriterTo - if v, ok := opt["user"]; ok { - w, err = statsMember(s, i, days, v.UserValue(s.UserAPI()).ID) - } else { - w, err = statsAll(s, i, days) - } - if err != nil { - s.Logger().Error("generating stats", "error", err, "guild", i.GuildID) - if err = resp.IsEphemeral().SetMessage("Il y a eu une erreur...").Send(); err != nil { - s.Logger().Error("sending error occurred", "error", err) + if v, ok := opt["days"]; ok { + in := v.IntValue() + if in < 1 || uint(in) > cfg.DaysXPRemains { + msg := fmt.Sprintf("Nombre de jours invalide. Il doit être strictement positif et inférieur à %d", cfg.DaysXPRemains) + if err := resp.SetMessage(msg).IsEphemeral().Send(); err != nil { + s.Logger().Error("sending error invalid days", "error", err) + } + return } - return - } - b := new(bytes.Buffer) - _, err = w.WriteTo(b) - if err != nil { - s.Logger().Error("writing png", "error", err) + days = int(in) } - err = resp.AddFile(&channel.File{ - Name: "plot.png", - ContentType: "image/png", - Reader: b, - }).Send() + err := resp.IsDeferred().Send() if err != nil { - s.Logger().Error("sending stats", "error", err) + s.Logger().Error("sending deferred", "error", err) + return } - }() + go func() { + var w io.WriterTo + if v, ok := opt["user"]; ok { + w, err = statsMember(ctx, s, i, days, v.UserValue(s.UserAPI()).ID) + } else { + w, err = statsAll(s, i, days) + } + if err != nil { + s.Logger().Error("generating stats", "error", err, "guild", i.GuildID) + if err = resp.IsEphemeral().SetMessage("Il y a eu une erreur...").Send(); err != nil { + s.Logger().Error("sending error occurred", "error", err) + } + return + } + b := new(bytes.Buffer) + _, err = w.WriteTo(b) + if err != nil { + s.Logger().Error("writing png", "error", err) + } + err = resp.AddFile(&channel.File{ + Name: "plot.png", + ContentType: "image/png", + Reader: b, + }).Send() + if err != nil { + s.Logger().Error("sending stats", "error", err) + } + }() + } } func statsAll(s bot.Session, i *event.InteractionCreate, days int) (io.WriterTo, error) { @@ -104,7 +107,7 @@ func statsAll(s bot.Session, i *event.InteractionCreate, days int) (io.WriterTo, }) } -func statsMember(s bot.Session, i *event.InteractionCreate, days int, discordID string) (io.WriterTo, error) { +func statsMember(ctx context.Context, s bot.Session, i *event.InteractionCreate, days int, discordID string) (io.WriterTo, error) { _, err := s.GuildAPI().Member(i.GuildID, discordID) if err != nil { return nil, err @@ -112,7 +115,7 @@ func statsMember(s bot.Session, i *event.InteractionCreate, days int, discordID return stats(s, i, days, func(before, after string) *gorm.DB { return gokord.DB.Raw( before+"WHERE guild_id = ? and created_at > ? and copaing_id = ?"+after, - i.GuildID, exp.TimeStampNDaysBefore(uint(days)), user.GetCopaing(discordID, i.GuildID).ID, + i.GuildID, exp.TimeStampNDaysBefore(uint(days)), user.GetCopaing(ctx, discordID, i.GuildID).ID, ) }) } @@ -24,7 +24,7 @@ var ( connectedSince = map[string]int64{} ) -func OnMessage(_ context.Context, s bot.Session, m *event.MessageCreate) { +func OnMessage(ctx context.Context, s bot.Session, m *event.MessageCreate) { if m.Author.Bot { return } @@ -32,13 +32,13 @@ func OnMessage(_ context.Context, s bot.Session, m *event.MessageCreate) { if cfg.IsDisabled(s, m.ChannelID) { return } - c := user.GetCopaing(m.Author.ID, m.GuildID) + cc := user.GetCopaing(ctx, m.Author.ID, m.GuildID) // add exp trimmed := exp.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author m.Member.GuildID = m.GuildID xp := min(exp.MessageXP(uint(len(trimmed)), exp.CalcDiversity(trimmed)), MaxXpPerMessage) - c.AddXP(s, m.Member, xp, func(_ uint, _ uint) { + cc.AddXP(ctx, s, m.Member, xp, func(_ uint, _ uint) { if err := s.ChannelAPI().MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil { s.Logger().Error( "add reaction for new level", @@ -50,7 +50,7 @@ func OnMessage(_ context.Context, s bot.Session, m *event.MessageCreate) { }) } -func OnVoiceUpdate(_ context.Context, s bot.Session, e *event.VoiceStateUpdate) { +func OnVoiceUpdate(ctx context.Context, s bot.Session, e *event.VoiceStateUpdate) { if e.Member.User.Bot { return } @@ -64,7 +64,7 @@ func OnVoiceUpdate(_ context.Context, s bot.Session, e *event.VoiceStateUpdate) if cfg.IsDisabled(s, e.BeforeUpdate.ChannelID) { return } - onDisconnect(s, e) + onDisconnect(ctx, s, e) } } @@ -77,9 +77,9 @@ func onConnection(s bot.Session, e *event.VoiceStateUpdate) { connectedSince[genMapKey(e.GuildID, e.UserID)] = time.Now().Unix() } -func onDisconnect(s bot.Session, e *event.VoiceStateUpdate) { +func onDisconnect(ctx context.Context, s bot.Session, e *event.VoiceStateUpdate) { now := time.Now().Unix() - c := user.GetCopaing(e.UserID, e.GuildID) + cc := user.GetCopaing(ctx, e.UserID, e.GuildID) // check the validity of user con, ok := connectedSince[genMapKey(e.GuildID, e.UserID)] if !ok || con == NotConnected { @@ -99,7 +99,7 @@ func onDisconnect(s bot.Session, e *event.VoiceStateUpdate) { } timeInVocal = min(timeInVocal, MaxTimeInVocal) e.Member.GuildID = e.GuildID - c.AddXP(s, e.Member, exp.VocalXP(uint(timeInVocal)), func(_ uint, newLevel uint) { + cc.AddXP(ctx, s, e.Member, exp.VocalXP(uint(timeInVocal)), func(_ uint, newLevel uint) { cfg := config.GetGuildConfig(e.GuildID) if len(cfg.FallbackChannel) == 0 { return @@ -113,12 +113,12 @@ func onDisconnect(s bot.Session, e *event.VoiceStateUpdate) { }) } -func OnLeave(_ context.Context, s bot.Session, e *event.GuildMemberRemove) { +func OnLeave(ctx context.Context, s bot.Session, e *event.GuildMemberRemove) { s.Logger().Debug("leave event", "user", e.User.Username) if e.User.Bot { return } - c := user.GetCopaing(e.User.ID, e.GuildID) + c := user.GetCopaing(ctx, e.User.ID, e.GuildID).Copaing(ctx) err := gokord.DB. Where("copaing_id = ? and guild_id = ?", c.ID, e.GuildID). Delete(&user.CopaingXP{}). @@ -126,7 +126,7 @@ func OnLeave(_ context.Context, s bot.Session, e *event.GuildMemberRemove) { if err != nil { s.Logger().Error("deleting user xp from DB", "user", e.User.Username, "guild", e.GuildID) } - if err = c.Delete(); err != nil { + if err = c.Delete(ctx); err != nil { s.Logger().Error("deleting user from DB", "user", e.User.Username, "guild", e.GuildID) } } @@ -1,7 +1,8 @@ -dev: +dev: stop podman network create db podman run -p 5432:5432 --rm --network db --name postgres --env-file .env -v ./data:/var/lib/postgresql/data -d postgres:alpine podman run -p 8080:8080 --rm --network db --name adminer -d adminer + sleep 5 go run . update: @@ -11,11 +12,11 @@ update: go run . stop: - podman stop postgres adminer + podman stop postgres adminer || (echo "no container"; exit 0) podman network rm db clean-network: - podman network rm db + podman network rm db || echo "no network" build: GOAMD64=v3 go build -ldflags "-s" . @@ -86,13 +86,15 @@ func main() { adm := gokord.AdminPermission + ctx := user.SetState(context.Background(), user.NewState()) + rankCmd := cmd.New("rank", "Affiche le niveau d'un copaing"). AddOption(cmd.NewOption( types.CommandOptionUser, "copaing", "Le niveau du Copaing que vous souhaitez obtenir", )). - SetHandler(commands.Rank) + SetHandler(commands.Rank(ctx)) configCmd := cmd.New("config", "Modifie la config"). SetPermission(&adm). @@ -111,7 +113,7 @@ func main() { "user", "Copaing a reset", ).IsRequired()). - SetHandler(commands.ResetUser). + SetHandler(commands.ResetUser(ctx)). SetPermission(&adm) creditsCmd := cmd.New("credits", "Crédits"). @@ -128,7 +130,7 @@ func main() { "user", "Utilisateur à inspecter", )). - SetHandler(commands.Stats) + SetHandler(commands.Stats(ctx)) rolereactCmd := cmd.New("rolereact", "Envoie un message permettant de récupérer des rôles grâce à des réactions"). SetPermission(&adm). @@ -182,7 +184,7 @@ func main() { user.PeriodicReducer(dg) - stopPeriodicReducer = gokord.NewTimer(d, func(stop chan<- interface{}) { + stopPeriodicReducer = gokord.NewTimer(d, func(stop chan<- any) { dg.Logger().Debug("periodic reducer") user.PeriodicReducer(dg) }) @@ -279,7 +281,7 @@ func main() { b.AddHandler(OnVoiceUpdate) b.AddHandler(OnLeave) - b.Start(context.Background()) + b.Start(ctx) if stopPeriodicReducer != nil { stopPeriodicReducer <- true diff --git a/user/member.go b/user/member.go index 9068a6f..690f7c5 100644 --- a/user/member.go +++ b/user/member.go @@ -1,6 +1,7 @@ package user import ( + "context" "time" "github.com/anhgelus/gokord" @@ -26,26 +27,47 @@ type CopaingAccess interface { GetXP() uint } -func GetCopaing(discordID string, guildID string) *Copaing { - c := Copaing{DiscordID: discordID, GuildID: guildID} - if err := c.Load(); err != nil { - panic(err) +func GetCopaing(ctx context.Context, discordID string, guildID string) *CopaingCached { + state := GetState(ctx) + cc, err := state.Copaing(guildID, discordID) + if err != nil { + c := Copaing{DiscordID: discordID, GuildID: guildID} + if err := c.Load(ctx); err != nil { + panic(err) + } + cc = FromCopaing(&c) } - return &c + return cc } -func (c *Copaing) Load() error { - return gokord.DB. +func (c *Copaing) Load(ctx context.Context) error { + err := gokord.DB. Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID). Preload("CopaingXPs"). FirstOrCreate(c). Error + if err != nil { + return err + } + state := GetState(ctx) + _, err = state.CopaingAdd(c, 0) + return err } -func (c *Copaing) Save() error { +func (c *Copaing) Save(ctx context.Context) error { + state := GetState(ctx) + _, err := state.CopaingAdd(c, 0) + if err != nil { + return err + } return gokord.DB.Save(c).Error } -func (c *Copaing) Delete() error { +func (c *Copaing) Delete(ctx context.Context) error { + state := GetState(ctx) + err := state.CopaingRemove(c) + if err != nil { + return err + } return gokord.DB.Where("guild_id = ? AND discord_id = ?", c.GuildID, c.DiscordID).Delete(c).Error } diff --git a/user/state.go b/user/state.go index bef2f53..07096db 100644 --- a/user/state.go +++ b/user/state.go @@ -1,6 +1,7 @@ package user import ( + "context" "sync" "github.com/nyttikord/gokord/state" @@ -14,6 +15,35 @@ type CopaingCached struct { XPToAdd uint } +// Copaing turns a CopaingCached into a Copaing. +// This operation is heavy. +func (cc *CopaingCached) Copaing(ctx context.Context) *Copaing { + c := Copaing{DiscordID: cc.DiscordID, GuildID: cc.GuildID} + if err := c.Load(ctx); err != nil { + panic(err) + } + return &c +} + +func (cc *CopaingCached) Save(ctx context.Context) error { + state := GetState(ctx) + + state.mu.Lock() + defer state.mu.Unlock() + + return state.storage.Write(KeyCopaingCachedRaw(cc.GuildID, cc.DiscordID), *cc) +} + +func FromCopaing(c *Copaing) *CopaingCached { + return &CopaingCached{ + ID: c.ID, + DiscordID: c.DiscordID, + GuildID: c.GuildID, + XPs: calcXP(c), + XPToAdd: 0, + } +} + const KeyCopaingCachedPrefix = "cc:" func KeyCopaingCached(c *Copaing) state.Key { @@ -29,6 +59,22 @@ type State struct { storage state.MapStorage[CopaingCached] } +func NewState() *State { + return &State{ + storage: state.MapStorage[CopaingCached]{}, + } +} + +const ContextKeyState = "state" + +func GetState(ctx context.Context) *State { + return ctx.Value(ContextKeyState).(*State) +} + +func SetState(ctx context.Context, state *State) context.Context { + return context.WithValue(ctx, ContextKeyState, state) +} + func (s *State) Copaing(guildID, copaingID string) (*CopaingCached, error) { s.mu.RLock() defer s.mu.RUnlock() @@ -42,26 +88,19 @@ func (s *State) Copaing(guildID, copaingID string) (*CopaingCached, error) { } // CopaingAdd does not call Copaing.Load! -func (s *State) CopaingAdd(c *Copaing, xpToAdd uint) error { - s.mu.Lock() - defer s.mu.Unlock() - - sum := calcXP(c) +func (s *State) CopaingAdd(c *Copaing, xpToAdd uint) (*CopaingCached, error) { var err error var cc *CopaingCached - if cc, err = s.Copaing(c.GuildID, c.DiscordID); err != nil { - cc.XPs = sum + if cc, err = s.Copaing(c.GuildID, c.DiscordID); err == nil { + cc.XPs = calcXP(c) cc.XPToAdd = xpToAdd } else { - cc = &CopaingCached{ - ID: c.ID, - DiscordID: c.DiscordID, - GuildID: c.GuildID, - XPs: sum, - XPToAdd: xpToAdd, - } + cc = FromCopaing(c) } - return s.storage.Write(KeyCopaingCached(c), *cc) + s.mu.Lock() + defer s.mu.Unlock() + + return cc, s.storage.Write(KeyCopaingCached(c), *cc) } func (s *State) CopaingRemove(c *Copaing) error { @@ -1,6 +1,7 @@ package user import ( + "context" "log/slog" "slices" "sync" @@ -25,17 +26,14 @@ func (c *cXP) GetXP() uint { return c.Cxp } -func (c *Copaing) AddXP(s bot.Session, m *user.Member, xp uint, fn func(uint, uint)) { - old, err := c.GetXP(s.Logger()) - if err != nil { - s.Logger().Error("getting xp", "error", err, "user", m.DisplayName(), "guild", c.GuildID) - return - } +func (cc *CopaingCached) AddXP(ctx context.Context, s bot.Session, m *user.Member, xp uint, fn func(uint, uint)) { + old := cc.XPs pastLevel := exp.Level(old) s.Logger().Debug("adding xp", "user", m.DisplayName(), "old", old, "to add", xp) - c.CopaingXPs = append(c.CopaingXPs, CopaingXP{CopaingID: c.ID, XP: xp, GuildID: c.GuildID}) - if err = c.Save(); err != nil { - s.Logger().Error("saving user", "error", err, "user", m.DisplayName(), "xp", xp, "guild", c.GuildID) + cc.XPs += xp + cc.XPToAdd += xp + if err := cc.Save(ctx); err != nil { + s.Logger().Error("saving user in state", "error", err, "user", m.DisplayName(), "xp", xp, "guild", cc.GuildID) return } newLevel := exp.Level(old + xp) @@ -91,7 +89,7 @@ func GetBestXP(logger *slog.Logger, guildId string, n uint, d int) ([]CopaingAcc } defer rows.Close() var l []*cXP - wg := sync.WaitGroup{} + var wg sync.WaitGroup for rows.Next() { var c Copaing err = gokord.DB.ScanRows(rows, &c) |
