package user import ( "log/slog" "math" "slices" "sync" "git.anhgelus.world/anhgelus/les-copaings-bot/config" "git.anhgelus.world/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/gokord" "github.com/nyttikord/gokord/bot" "github.com/nyttikord/gokord/user" ) type cXP struct { Cxp uint *Copaing } func (c *cXP) ToCopaing() *Copaing { return c.Copaing } 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 } pastLevel := exp.Level(old) s.Logger().Debug("adding xp", "member", m.DisplayName(), "old xp", old, "xp to add", xp, "old level", pastLevel) c.CopaingXPs = append(c.CopaingXPs, CopaingXP{CopaingID: c.ID, XP: uint(math.Floor(float64(xp) * c.GetBoost(m))), GuildID: c.GuildID}) if err = c.Save(); err != nil { s.Logger().Error( "saving user", "error", err.Error(), "xp", c.CopaingXPs, "discord_id", c.DiscordID, "guild_id", c.GuildID, ) return } newLevel := exp.Level(old + xp) if newLevel > pastLevel { fn(old+xp, newLevel) onNewLevel(s, m, newLevel) } } func (c *Copaing) GetXP(logger *slog.Logger) (uint, error) { cfg := config.GetGuildConfig(c.GuildID) return c.GetXPForDays(logger, cfg.DaysXPRemains) } func (c *Copaing) GetXPForDays(logger *slog.Logger, n uint) (uint, error) { xp := uint(0) rows, err := gokord.DB. Model(&CopaingXP{}). Where( "created_at >= ? and guild_id = ? and copaing_id = ?", exp.TimeStampNDaysBefore(n), c.GuildID, c.ID, ). Rows() if err != nil { return 0, err } defer rows.Close() for rows.Next() { var cxp CopaingXP err = gokord.DB.ScanRows(rows, &cxp) if err != nil { logger.Error("scanning rows", "error", err, "copaing", c.ID, "guild", c.GuildID) continue } xp += cxp.XP } return xp, nil } func (c *Copaing) GetBoost(m *user.Member) float64 { boost := 1.0 if m.PremiumSince != nil { boost = max(boost, 2.0) } cfg := config.GetGuildConfig(c.GuildID) for _, r := range cfg.BoostXpRoles { if slices.Contains(m.Roles, r.RoleID) { boost = max(boost, r.Boost) } } if m.User.PrimaryGuild.GuildID == c.GuildID { boost = max(boost, 1.5) } return boost } // GetBestXP returns n Copaing with the best XP within d days (d <= cfg.DaysXPRemain; d < 0 <=> d = cfg.DaysXPRemain) // // This function is slow func GetBestXP(logger *slog.Logger, guildId string, n uint, d int) ([]CopaingAccess, error) { if d < 0 { cfg := config.GetGuildConfig(guildId) d = int(cfg.DaysXPRemains) } rows, err := gokord.DB.Model(&Copaing{}).Where("guild_id = ?", guildId).Rows() if err != nil { return nil, err } defer rows.Close() var l []*cXP wg := sync.WaitGroup{} for rows.Next() { var c Copaing err = gokord.DB.ScanRows(rows, &c) if err != nil { logger.Error("scanning rows", "error", err, "copaing", c.ID, "guild", c.GuildID) continue } wg.Add(1) go func() { defer wg.Done() xp, err := c.GetXPForDays(logger, uint(d)) if err != nil { logger.Error("fetching xp", "error", err, "copaing", c.ID, "guild", c.GuildID) return } l = append(l, &cXP{Cxp: xp, Copaing: &c}) }() } wg.Wait() slices.SortFunc(l, func(a, b *cXP) int { // desc order if a.Cxp < b.Cxp { return 1 } if a.Cxp > b.Cxp { return -1 } return 0 }) m := min(len(l), int(n)) cs := make([]CopaingAccess, m) for i, c := range l[:m] { cs[i] = c } return cs, nil }