From c408afc8797b0da5e1d73d190a8f5884870b510c Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 12:50:20 +0200 Subject: style(files): reorganize everything --- user/level.go | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ user/member.go | 107 +++++++++++++++++++++++++++++ user/xp.go | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 user/level.go create mode 100644 user/member.go create mode 100644 user/xp.go (limited to 'user') diff --git a/user/level.go b/user/level.go new file mode 100644 index 0000000..0abd642 --- /dev/null +++ b/user/level.go @@ -0,0 +1,207 @@ +package user + +import ( + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/utils" + "github.com/anhgelus/les-copaings-bot/config" + "github.com/anhgelus/les-copaings-bot/exp" + "github.com/bwmarrin/discordgo" + "slices" + "sync" + "time" +) + +func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { + cfg := config.GetGuildConfig(m.GuildID) + xpForLevel := exp.LevelXP(level) + for _, role := range cfg.XpRoles { + if role.XP <= xpForLevel && !slices.Contains(m.Roles, role.RoleID) { + utils.SendDebug( + "Add role", + "role_id", role.RoleID, + "user_id", m.User.ID, + "guild_id", m.GuildID, + ) + err := dg.GuildMemberRoleAdd(m.GuildID, m.User.ID, role.RoleID) + if err != nil { + utils.SendAlert("exp/level.go - Adding role", err.Error(), "role_id", role.RoleID) + } + } else if role.XP > xpForLevel && slices.Contains(m.Roles, role.RoleID) { + utils.SendDebug( + "Remove role", + "role_id", role.RoleID, + "user_id", m.User.ID, + "guild_id", m.GuildID, + ) + err := dg.GuildMemberRoleRemove(m.GuildID, m.User.ID, role.RoleID) + if err != nil { + utils.SendAlert("exp/level.go - Removing role", err.Error(), "role_id", role.RoleID) + } + } + } +} + +func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { + m, err := dg.GuildMember(c.GuildID, c.DiscordID) + if err != nil { + utils.SendAlert( + "exp/level.go - Getting member for new level", err.Error(), + "discord_id", c.DiscordID, + "guild_id", c.GuildID, + ) + return + } + onNewLevel(dg, m, level) +} + +func LastEventUpdate(dg *discordgo.Session, c *Copaing) { + h := c.HourSinceLastEvent() + l := exp.Lose(h, c.XP) + xp := c.XPAlreadyRemoved() + oldXP := c.XP + if l-xp < 0 { + utils.SendWarn("lose - exp already removed is negative", "lose", l, "exp", xp) + c.XP = 0 + } else { + calc := int(c.XP) - int(l) + int(c.XPAlreadyRemoved()) + if calc < 0 { + c.XP = 0 + } else { + c.XP = uint(calc) + } + } + if oldXP != c.XP { + lvl := exp.Level(c.XP) + if exp.Level(oldXP) != lvl { + utils.SendDebug( + "Level changed", + "old", exp.Level(oldXP), + "new", lvl, + "discord_id", c.DiscordID, + "guild_id", c.GuildID, + ) + c.OnNewLevel(dg, lvl) + } + if err := c.Save(); err != nil { + utils.SendAlert( + "exp/level.go - Saving user", err.Error(), + "exp", c.XP, + "discord_id", c.DiscordID, + "guild_id", c.GuildID, + ) + } + } + c.SetLastEvent() +} + +func UpdateXP(dg *discordgo.Session, c *Copaing) { + oldXP := c.XP + if oldXP == 0 { + return + } + h := c.HourSinceLastEvent() + l := exp.Lose(h, c.XP) + xp := c.XPAlreadyRemoved() + if l-xp < 0 { + utils.SendWarn("lose - xp_removed is negative", "lose", l, "exp removed", xp) + c.AddXPAlreadyRemoved(0) + } else { + calc := int(c.XP) - int(l) + int(xp) + if calc < 0 { + c.AddXPAlreadyRemoved(c.XP) + c.XP = 0 + } else { + c.XP = uint(calc) + c.AddXPAlreadyRemoved(l - xp) + } + } + if oldXP != c.XP { + lvl := exp.Level(c.XP) + if exp.Level(oldXP) != lvl { + utils.SendDebug( + "Level updated", + "old", exp.Level(oldXP), + "new", lvl, + "discord_id", c.DiscordID, + "guild_id", c.GuildID, + ) + c.OnNewLevel(dg, lvl) + } + utils.SendDebug("Save XP", "old", oldXP, "new", c.XP, "user", c.DiscordID) + if err := c.Save(); err != nil { + utils.SendAlert( + "exp/level.go - Saving user", err.Error(), + "exp", c.XP, + "discord_id", c.DiscordID, + "guild_id", c.GuildID, + ) + } + } +} + +func PeriodicReducer(dg *discordgo.Session) { + var wg sync.WaitGroup + for _, g := range dg.State.Guilds { + var cs []*Copaing + err := gokord.DB.Where("guild_id = ?", g.ID).Find(&cs).Error + if err != nil { + utils.SendAlert("exp/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) + continue + } + for i, c := range cs { + if i%50 == 49 { + time.Sleep(15 * time.Second) // sleep prevents from spamming the Discord API and the database + } + var u *discordgo.User + u, err = dg.User(c.DiscordID) + if err != nil { + utils.SendAlert( + "exp/level.go - Fetching user", err.Error(), + "discord_id", c.DiscordID, + "guild_id", g.ID, + ) + utils.SendWarn("Removing user from database", "discord_id", c.DiscordID) + if err = gokord.DB.Delete(c).Error; err != nil { + utils.SendAlert( + "exp/level.go - Removing user from database", err.Error(), + "discord_id", c.DiscordID, + "guild_id", g.ID, + ) + } + continue + } + if u.Bot { + continue + } + if _, err = dg.GuildMember(g.ID, c.DiscordID); err != nil { + utils.SendAlert( + "exp/level.go - Fetching member", err.Error(), + "discord_id", c.DiscordID, + "guild_id", g.ID, + ) + utils.SendWarn( + "Removing user from guild in database", + "discord_id", c.DiscordID, + "guild_id", g.ID, + ) + if err = gokord.DB.Where("guild_id = ?", g.ID).Delete(c).Error; err != nil { + utils.SendAlert( + "exp/level.go - Removing user from guild in database", err.Error(), + "discord_id", c.DiscordID, + "guild_id", g.ID, + ) + } + continue + } + wg.Add(1) + go func() { + UpdateXP(dg, c) + wg.Done() + }() + } + wg.Wait() // finish the entire guild before starting another + utils.SendDebug("Periodic reduce, guild finished", "guild", g.Name) + time.Sleep(15 * time.Second) // sleep prevents from spamming the Discord API and the database + } + utils.SendDebug("Periodic reduce finished", "len(guilds)", len(dg.State.Guilds)) +} diff --git a/user/member.go b/user/member.go new file mode 100644 index 0000000..8a33ed3 --- /dev/null +++ b/user/member.go @@ -0,0 +1,107 @@ +package user + +import ( + "fmt" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/utils" + "gorm.io/gorm" +) + +type Copaing struct { + gorm.Model + DiscordID string `gorm:"not null"` + //XP []CopaingXP + XP uint `gorm:"default:0"` + GuildID string `gorm:"not null"` +} + +type leftCopaing struct { + ID uint + StopDelete chan<- interface{} +} + +//type CopaingXP struct { +// gorm.Model +// XP uint `gorm:"default:0"` +// CopaingID uint +//} + +var ( + leftCopaingsMap = map[string]*leftCopaing{} +) + +const ( + LastEvent = "last_event" + AlreadyRemoved = "already_removed" +) + +func GetCopaing(discordID string, guildID string) *Copaing { + c := Copaing{DiscordID: discordID, GuildID: guildID} + if err := c.Load(); err != nil { + utils.SendAlert( + "exp/member.go - Loading user", + err.Error(), + "discord_id", + discordID, + "guild_id", + guildID, + ) + return nil + } + return &c +} + +func (c *Copaing) Load() error { + // check if user left in the past 48 hours + k := c.GuildID + ":" + c.DiscordID + l, ok := leftCopaingsMap[k] + if !ok || l == nil { + // if not, common first or create + return gokord.DB.Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID).FirstOrCreate(c).Error + } + // else, getting last data + tmp := Copaing{ + Model: gorm.Model{ + ID: c.ID, + }, + DiscordID: c.DiscordID, + GuildID: c.GuildID, + } + if err := gokord.DB.Unscoped().Find(&tmp).Error; err != nil { + // if error, avoid getting old data and use new one + utils.SendAlert( + "exp/member.go - Getting user in soft delete", err.Error(), + "discord_id", c.DiscordID, + "guild_id", c.DiscordID, + "last_id", l.ID, + ) + return gokord.DB.Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID).FirstOrCreate(c).Error + } + // resetting internal data + tmp.Model = gorm.Model{} + l.StopDelete <- true + leftCopaingsMap[k] = nil + // creating new data + err := gokord.DB.Create(&tmp).Error + if err != nil { + return err + } + // delete old data + if err = gokord.DB.Unscoped().Delete(&tmp).Error; err != nil { + utils.SendAlert( + "exp/member.go - Deleting user in soft delete", err.Error(), + "discord_id", c.DiscordID, + "guild_id", c.DiscordID, + "last_id", l.ID, + ) + } + return nil +} + +func (c *Copaing) Save() error { + return gokord.DB.Save(c).Error +} + +func (c *Copaing) GenKey(key string) string { + return fmt.Sprintf("%s:%s:%s", c.GuildID, c.DiscordID, key) +} diff --git a/user/xp.go b/user/xp.go new file mode 100644 index 0000000..6cd9b75 --- /dev/null +++ b/user/xp.go @@ -0,0 +1,185 @@ +package user + +import ( + "context" + "errors" + "fmt" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/utils" + "github.com/anhgelus/les-copaings-bot/config" + "github.com/anhgelus/les-copaings-bot/exp" + "github.com/bwmarrin/discordgo" + "github.com/redis/go-redis/v9" + "gorm.io/gorm" + "math" + "strconv" + "time" +) + +func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { + pastLevel := exp.Level(c.XP) + old := c.XP + c.XP += xp + if err := c.Save(); err != nil { + utils.SendAlert( + "exp/level.go - Saving user", + err.Error(), + "exp", + c.XP, + "discord_id", + c.DiscordID, + "guild_id", + c.GuildID, + ) + c.XP = old + return + } + newLevel := exp.Level(c.XP) + if newLevel > pastLevel { + fn(c.XP, newLevel) + onNewLevel(s, m, newLevel) + } +} + +func (c *Copaing) SetLastEvent() { + client, err := config.GetRedisClient() + if err != nil { + utils.SendAlert("exp/member.go - Getting redis client (set)", err.Error()) + return + } + t := time.Now().Unix() + err = client.Set(context.Background(), c.GenKey(LastEvent), strconv.FormatInt(t, 10), 0).Err() + if err != nil { + utils.SendAlert("exp/member.go - Setting last event", err.Error(), "time", t, "base_key", c.GenKey("")) + return + } + err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), "0", 0).Err() + if err != nil { + utils.SendAlert( + "exp/member.go - Setting already removed to 0", + err.Error(), + "time", + t, + "base_key", + c.GenKey(""), + ) + return + } +} + +func (c *Copaing) HourSinceLastEvent() uint { + client, err := config.GetRedisClient() + if err != nil { + utils.SendAlert("exp/member.go - Getting redis client (get)", err.Error()) + return 0 + } + res := client.Get(context.Background(), c.GenKey(LastEvent)) + if errors.Is(res.Err(), redis.Nil) { + return 0 + } else if res.Err() != nil { + utils.SendAlert("exp/member.go - Getting last event", res.Err().Error(), "base_key", c.GenKey("")) + return 0 + } + t := time.Now().Unix() + last, err := strconv.Atoi(res.Val()) + if err != nil { + utils.SendAlert( + "exp/member.go - Converting time fetched into int (last event)", + err.Error(), + "base_key", + c.GenKey(""), + "val", + res.Val(), + ) + return 0 + } + if gokord.Debug { + return uint(math.Floor(float64(t-int64(last)) / 60)) // not hours of unix, is minutes of unix + } + return utils.HoursOfUnix(t - int64(last)) +} + +func (c *Copaing) AddXPAlreadyRemoved(xp uint) uint { + client, err := config.GetRedisClient() + if err != nil { + utils.SendAlert("exp/member.go - Getting redis client (set)", err.Error()) + return 0 + } + exp := xp + c.XPAlreadyRemoved() + err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), exp, 0).Err() + if err != nil { + utils.SendAlert( + "exp/member.go - Setting already removed", + err.Error(), + "exp already removed", + exp, + "base_key", + c.GenKey(""), + ) + return 0 + } + return exp +} + +func (c *Copaing) XPAlreadyRemoved() uint { + client, err := config.GetRedisClient() + if err != nil { + utils.SendAlert("exp/member.go - Getting redis client (exp)", err.Error()) + return 0 + } + res := client.Get(context.Background(), fmt.Sprintf("%s:%s", c.GenKey(""), AlreadyRemoved)) + if errors.Is(res.Err(), redis.Nil) { + return 0 + } else if res.Err() != nil { + utils.SendAlert("exp/member.go - Getting already removed", res.Err().Error(), "base_key", c.GenKey("")) + return 0 + } + xp, err := strconv.Atoi(res.Val()) + if err != nil { + utils.SendAlert( + "exp/member.go - Converting time fetched into int (already removed)", + err.Error(), + "base_key", + c.GenKey(""), + "val", + res.Val(), + ) + return 0 + } + if xp < 0 { + utils.SendAlert( + "exp/member.go - Assertion exp >= 0", + "exp is negative", + "base_key", + c.GenKey(""), + "exp", + xp, + ) + return 0 + } + return uint(xp) +} + +func (c *Copaing) Reset() { + gokord.DB.Where("guild_id = ? AND discord_id = ?", c.GuildID, c.DiscordID).Delete(c) +} + +func (c *Copaing) AfterDelete(db *gorm.DB) error { + id := c.ID + dID := c.DiscordID + gID := c.GuildID + k := c.GuildID + ":" + c.DiscordID + ch := utils.NewTimer(48*time.Hour, func(stop chan<- interface{}) { + if err := db.Unscoped().Where("id = ?", id).Delete(c).Error; err != nil { + utils.SendAlert( + "exp/member.go - Removing user from database", err.Error(), + "discord_id", dID, + "guild_id", gID, + ) + } + stop <- true + leftCopaingsMap[k] = nil + }) + leftCopaingsMap[k] = &leftCopaing{id, ch} + return nil +} -- cgit v1.2.3 From d38c57b83009bf0a8b22bede7ee786d6f54fc66a Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:00:09 +0200 Subject: fix(log): wrong pos in many alerts --- user/level.go | 26 +++++++++++++------------- user/member.go | 6 +++--- user/xp.go | 32 ++++++++++++++++---------------- 3 files changed, 32 insertions(+), 32 deletions(-) (limited to 'user') diff --git a/user/level.go b/user/level.go index 0abd642..122b707 100644 --- a/user/level.go +++ b/user/level.go @@ -24,7 +24,7 @@ func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { ) err := dg.GuildMemberRoleAdd(m.GuildID, m.User.ID, role.RoleID) if err != nil { - utils.SendAlert("exp/level.go - Adding role", err.Error(), "role_id", role.RoleID) + utils.SendAlert("user/level.go - Adding role", err.Error(), "role_id", role.RoleID) } } else if role.XP > xpForLevel && slices.Contains(m.Roles, role.RoleID) { utils.SendDebug( @@ -35,7 +35,7 @@ func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { ) err := dg.GuildMemberRoleRemove(m.GuildID, m.User.ID, role.RoleID) if err != nil { - utils.SendAlert("exp/level.go - Removing role", err.Error(), "role_id", role.RoleID) + utils.SendAlert("user/level.go - Removing role", err.Error(), "role_id", role.RoleID) } } } @@ -45,7 +45,7 @@ func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { m, err := dg.GuildMember(c.GuildID, c.DiscordID) if err != nil { utils.SendAlert( - "exp/level.go - Getting member for new level", err.Error(), + "user/level.go - Getting member for new level", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID, ) @@ -60,7 +60,7 @@ func LastEventUpdate(dg *discordgo.Session, c *Copaing) { xp := c.XPAlreadyRemoved() oldXP := c.XP if l-xp < 0 { - utils.SendWarn("lose - exp already removed is negative", "lose", l, "exp", xp) + utils.SendWarn("lose - xp already removed is negative", "lose", l, "xp", xp) c.XP = 0 } else { calc := int(c.XP) - int(l) + int(c.XPAlreadyRemoved()) @@ -84,7 +84,7 @@ func LastEventUpdate(dg *discordgo.Session, c *Copaing) { } if err := c.Save(); err != nil { utils.SendAlert( - "exp/level.go - Saving user", err.Error(), + "user/level.go - Saving user", err.Error(), "exp", c.XP, "discord_id", c.DiscordID, "guild_id", c.GuildID, @@ -103,7 +103,7 @@ func UpdateXP(dg *discordgo.Session, c *Copaing) { l := exp.Lose(h, c.XP) xp := c.XPAlreadyRemoved() if l-xp < 0 { - utils.SendWarn("lose - xp_removed is negative", "lose", l, "exp removed", xp) + utils.SendWarn("lose - xp_removed is negative", "lose", l, "xp removed", xp) c.AddXPAlreadyRemoved(0) } else { calc := int(c.XP) - int(l) + int(xp) @@ -130,8 +130,8 @@ func UpdateXP(dg *discordgo.Session, c *Copaing) { utils.SendDebug("Save XP", "old", oldXP, "new", c.XP, "user", c.DiscordID) if err := c.Save(); err != nil { utils.SendAlert( - "exp/level.go - Saving user", err.Error(), - "exp", c.XP, + "user/level.go - Saving user", err.Error(), + "xp", c.XP, "discord_id", c.DiscordID, "guild_id", c.GuildID, ) @@ -145,7 +145,7 @@ func PeriodicReducer(dg *discordgo.Session) { var cs []*Copaing err := gokord.DB.Where("guild_id = ?", g.ID).Find(&cs).Error if err != nil { - utils.SendAlert("exp/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) + utils.SendAlert("user/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) continue } for i, c := range cs { @@ -156,14 +156,14 @@ func PeriodicReducer(dg *discordgo.Session) { u, err = dg.User(c.DiscordID) if err != nil { utils.SendAlert( - "exp/level.go - Fetching user", err.Error(), + "user/level.go - Fetching user", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) utils.SendWarn("Removing user from database", "discord_id", c.DiscordID) if err = gokord.DB.Delete(c).Error; err != nil { utils.SendAlert( - "exp/level.go - Removing user from database", err.Error(), + "user/level.go - Removing user from database", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) @@ -175,7 +175,7 @@ func PeriodicReducer(dg *discordgo.Session) { } if _, err = dg.GuildMember(g.ID, c.DiscordID); err != nil { utils.SendAlert( - "exp/level.go - Fetching member", err.Error(), + "user/level.go - Fetching member", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) @@ -186,7 +186,7 @@ func PeriodicReducer(dg *discordgo.Session) { ) if err = gokord.DB.Where("guild_id = ?", g.ID).Delete(c).Error; err != nil { utils.SendAlert( - "exp/level.go - Removing user from guild in database", err.Error(), + "user/level.go - Removing user from guild in database", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) diff --git a/user/member.go b/user/member.go index 8a33ed3..91327ad 100644 --- a/user/member.go +++ b/user/member.go @@ -39,7 +39,7 @@ func GetCopaing(discordID string, guildID string) *Copaing { c := Copaing{DiscordID: discordID, GuildID: guildID} if err := c.Load(); err != nil { utils.SendAlert( - "exp/member.go - Loading user", + "user/member.go - Loading user", err.Error(), "discord_id", discordID, @@ -70,7 +70,7 @@ func (c *Copaing) Load() error { if err := gokord.DB.Unscoped().Find(&tmp).Error; err != nil { // if error, avoid getting old data and use new one utils.SendAlert( - "exp/member.go - Getting user in soft delete", err.Error(), + "user/member.go - Getting user in soft delete", err.Error(), "discord_id", c.DiscordID, "guild_id", c.DiscordID, "last_id", l.ID, @@ -89,7 +89,7 @@ func (c *Copaing) Load() error { // delete old data if err = gokord.DB.Unscoped().Delete(&tmp).Error; err != nil { utils.SendAlert( - "exp/member.go - Deleting user in soft delete", err.Error(), + "user/member.go - Deleting user in soft delete", err.Error(), "discord_id", c.DiscordID, "guild_id", c.DiscordID, "last_id", l.ID, diff --git a/user/xp.go b/user/xp.go index 6cd9b75..4b93f26 100644 --- a/user/xp.go +++ b/user/xp.go @@ -22,7 +22,7 @@ func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn f c.XP += xp if err := c.Save(); err != nil { utils.SendAlert( - "exp/level.go - Saving user", + "user/xp.go - Saving user", err.Error(), "exp", c.XP, @@ -44,19 +44,19 @@ func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn f func (c *Copaing) SetLastEvent() { client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("exp/member.go - Getting redis client (set)", err.Error()) + utils.SendAlert("user/xp.go - Getting redis client (set)", err.Error()) return } t := time.Now().Unix() err = client.Set(context.Background(), c.GenKey(LastEvent), strconv.FormatInt(t, 10), 0).Err() if err != nil { - utils.SendAlert("exp/member.go - Setting last event", err.Error(), "time", t, "base_key", c.GenKey("")) + utils.SendAlert("user/xp.go - Setting last event", err.Error(), "time", t, "base_key", c.GenKey("")) return } err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), "0", 0).Err() if err != nil { utils.SendAlert( - "exp/member.go - Setting already removed to 0", + "user/xp.go - Setting already removed to 0", err.Error(), "time", t, @@ -70,21 +70,21 @@ func (c *Copaing) SetLastEvent() { func (c *Copaing) HourSinceLastEvent() uint { client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("exp/member.go - Getting redis client (get)", err.Error()) + utils.SendAlert("user/xp.go - Getting redis client (get)", err.Error()) return 0 } res := client.Get(context.Background(), c.GenKey(LastEvent)) if errors.Is(res.Err(), redis.Nil) { return 0 } else if res.Err() != nil { - utils.SendAlert("exp/member.go - Getting last event", res.Err().Error(), "base_key", c.GenKey("")) + utils.SendAlert("user/xp.go - Getting last event", res.Err().Error(), "base_key", c.GenKey("")) return 0 } t := time.Now().Unix() last, err := strconv.Atoi(res.Val()) if err != nil { utils.SendAlert( - "exp/member.go - Converting time fetched into int (last event)", + "user/xp.go - Converting time fetched into int (last event)", err.Error(), "base_key", c.GenKey(""), @@ -102,14 +102,14 @@ func (c *Copaing) HourSinceLastEvent() uint { func (c *Copaing) AddXPAlreadyRemoved(xp uint) uint { client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("exp/member.go - Getting redis client (set)", err.Error()) + utils.SendAlert("user/xp.go - Getting redis client (set)", err.Error()) return 0 } exp := xp + c.XPAlreadyRemoved() err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), exp, 0).Err() if err != nil { utils.SendAlert( - "exp/member.go - Setting already removed", + "user/xp.go - Setting already removed", err.Error(), "exp already removed", exp, @@ -124,20 +124,20 @@ func (c *Copaing) AddXPAlreadyRemoved(xp uint) uint { func (c *Copaing) XPAlreadyRemoved() uint { client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("exp/member.go - Getting redis client (exp)", err.Error()) + utils.SendAlert("user/xp.go - Getting redis client (exp)", err.Error()) return 0 } res := client.Get(context.Background(), fmt.Sprintf("%s:%s", c.GenKey(""), AlreadyRemoved)) if errors.Is(res.Err(), redis.Nil) { return 0 } else if res.Err() != nil { - utils.SendAlert("exp/member.go - Getting already removed", res.Err().Error(), "base_key", c.GenKey("")) + utils.SendAlert("user/xp.go - Getting already removed", res.Err().Error(), "base_key", c.GenKey("")) return 0 } xp, err := strconv.Atoi(res.Val()) if err != nil { utils.SendAlert( - "exp/member.go - Converting time fetched into int (already removed)", + "user/xp.go - Converting time fetched into int (already removed)", err.Error(), "base_key", c.GenKey(""), @@ -148,11 +148,11 @@ func (c *Copaing) XPAlreadyRemoved() uint { } if xp < 0 { utils.SendAlert( - "exp/member.go - Assertion exp >= 0", - "exp is negative", + "user/xp.go - Assertion exp >= 0", + "xp is negative", "base_key", c.GenKey(""), - "exp", + "xp", xp, ) return 0 @@ -172,7 +172,7 @@ func (c *Copaing) AfterDelete(db *gorm.DB) error { ch := utils.NewTimer(48*time.Hour, func(stop chan<- interface{}) { if err := db.Unscoped().Where("id = ?", id).Delete(c).Error; err != nil { utils.SendAlert( - "exp/member.go - Removing user from database", err.Error(), + "user/xp.go - Removing user from database", err.Error(), "discord_id", dID, "guild_id", gID, ) -- cgit v1.2.3 From 6b6c775ab3af659e26f0c79dbabd86d88d2451d5 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:15:59 +0200 Subject: refactor(gorm): remove ...At fields from model --- user/member.go | 80 +++++++++++++--------------------------------------------- user/xp.go | 33 +++--------------------- 2 files changed, 21 insertions(+), 92 deletions(-) (limited to 'user') diff --git a/user/member.go b/user/member.go index 91327ad..dc93979 100644 --- a/user/member.go +++ b/user/member.go @@ -4,32 +4,21 @@ import ( "fmt" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "gorm.io/gorm" ) type Copaing struct { - gorm.Model - DiscordID string `gorm:"not null"` - //XP []CopaingXP - XP uint `gorm:"default:0"` - GuildID string `gorm:"not null"` + ID uint `gorm:"primarykey"` + DiscordID string `gorm:"not null"` + XP []CopaingXP `gorm:"constraint:OnDelete:SET NULL;"` + GuildID string `gorm:"not null"` } -type leftCopaing struct { - ID uint - StopDelete chan<- interface{} +type CopaingXP struct { + ID uint `gorm:"primarykey"` + XP uint `gorm:"default:0"` + CopaingID uint `gorm:"not null;constraint:OnDelete:CASCADE;"` } -//type CopaingXP struct { -// gorm.Model -// XP uint `gorm:"default:0"` -// CopaingID uint -//} - -var ( - leftCopaingsMap = map[string]*leftCopaing{} -) - const ( LastEvent = "last_event" AlreadyRemoved = "already_removed" @@ -52,50 +41,11 @@ func GetCopaing(discordID string, guildID string) *Copaing { } func (c *Copaing) Load() error { - // check if user left in the past 48 hours - k := c.GuildID + ":" + c.DiscordID - l, ok := leftCopaingsMap[k] - if !ok || l == nil { - // if not, common first or create - return gokord.DB.Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID).FirstOrCreate(c).Error - } - // else, getting last data - tmp := Copaing{ - Model: gorm.Model{ - ID: c.ID, - }, - DiscordID: c.DiscordID, - GuildID: c.GuildID, - } - if err := gokord.DB.Unscoped().Find(&tmp).Error; err != nil { - // if error, avoid getting old data and use new one - utils.SendAlert( - "user/member.go - Getting user in soft delete", err.Error(), - "discord_id", c.DiscordID, - "guild_id", c.DiscordID, - "last_id", l.ID, - ) - return gokord.DB.Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID).FirstOrCreate(c).Error - } - // resetting internal data - tmp.Model = gorm.Model{} - l.StopDelete <- true - leftCopaingsMap[k] = nil - // creating new data - err := gokord.DB.Create(&tmp).Error - if err != nil { - return err - } - // delete old data - if err = gokord.DB.Unscoped().Delete(&tmp).Error; err != nil { - utils.SendAlert( - "user/member.go - Deleting user in soft delete", err.Error(), - "discord_id", c.DiscordID, - "guild_id", c.DiscordID, - "last_id", l.ID, - ) - } - return nil + return gokord.DB. + Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID). + Preload("XP"). + FirstOrCreate(c). + Error } func (c *Copaing) Save() error { @@ -105,3 +55,7 @@ func (c *Copaing) Save() error { func (c *Copaing) GenKey(key string) string { return fmt.Sprintf("%s:%s:%s", c.GuildID, c.DiscordID, key) } + +func (c *Copaing) Delete() error { + return gokord.DB.Where("guild_id = ? AND discord_id = ?", c.GuildID, c.DiscordID).Delete(c).Error +} diff --git a/user/xp.go b/user/xp.go index 4b93f26..90be09d 100644 --- a/user/xp.go +++ b/user/xp.go @@ -10,7 +10,6 @@ import ( "github.com/anhgelus/les-copaings-bot/exp" "github.com/bwmarrin/discordgo" "github.com/redis/go-redis/v9" - "gorm.io/gorm" "math" "strconv" "time" @@ -105,20 +104,20 @@ func (c *Copaing) AddXPAlreadyRemoved(xp uint) uint { utils.SendAlert("user/xp.go - Getting redis client (set)", err.Error()) return 0 } - exp := xp + c.XPAlreadyRemoved() - err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), exp, 0).Err() + nxp := xp + c.XPAlreadyRemoved() + err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), nxp, 0).Err() if err != nil { utils.SendAlert( "user/xp.go - Setting already removed", err.Error(), "exp already removed", - exp, + nxp, "base_key", c.GenKey(""), ) return 0 } - return exp + return nxp } func (c *Copaing) XPAlreadyRemoved() uint { @@ -159,27 +158,3 @@ func (c *Copaing) XPAlreadyRemoved() uint { } return uint(xp) } - -func (c *Copaing) Reset() { - gokord.DB.Where("guild_id = ? AND discord_id = ?", c.GuildID, c.DiscordID).Delete(c) -} - -func (c *Copaing) AfterDelete(db *gorm.DB) error { - id := c.ID - dID := c.DiscordID - gID := c.GuildID - k := c.GuildID + ":" + c.DiscordID - ch := utils.NewTimer(48*time.Hour, func(stop chan<- interface{}) { - if err := db.Unscoped().Where("id = ?", id).Delete(c).Error; err != nil { - utils.SendAlert( - "user/xp.go - Removing user from database", err.Error(), - "discord_id", dID, - "guild_id", gID, - ) - } - stop <- true - leftCopaingsMap[k] = nil - }) - leftCopaingsMap[k] = &leftCopaing{id, ch} - return nil -} -- cgit v1.2.3 From e0a8f6634424f10a22b0a0740e0bbc17534eaa0e Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:26:32 +0200 Subject: refactor(xp): remove reducer --- user/level.go | 87 +-------------------------------------- user/xp.go | 128 ---------------------------------------------------------- 2 files changed, 1 insertion(+), 214 deletions(-) (limited to 'user') diff --git a/user/level.go b/user/level.go index 122b707..1143c59 100644 --- a/user/level.go +++ b/user/level.go @@ -54,91 +54,6 @@ func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { onNewLevel(dg, m, level) } -func LastEventUpdate(dg *discordgo.Session, c *Copaing) { - h := c.HourSinceLastEvent() - l := exp.Lose(h, c.XP) - xp := c.XPAlreadyRemoved() - oldXP := c.XP - if l-xp < 0 { - utils.SendWarn("lose - xp already removed is negative", "lose", l, "xp", xp) - c.XP = 0 - } else { - calc := int(c.XP) - int(l) + int(c.XPAlreadyRemoved()) - if calc < 0 { - c.XP = 0 - } else { - c.XP = uint(calc) - } - } - if oldXP != c.XP { - lvl := exp.Level(c.XP) - if exp.Level(oldXP) != lvl { - utils.SendDebug( - "Level changed", - "old", exp.Level(oldXP), - "new", lvl, - "discord_id", c.DiscordID, - "guild_id", c.GuildID, - ) - c.OnNewLevel(dg, lvl) - } - if err := c.Save(); err != nil { - utils.SendAlert( - "user/level.go - Saving user", err.Error(), - "exp", c.XP, - "discord_id", c.DiscordID, - "guild_id", c.GuildID, - ) - } - } - c.SetLastEvent() -} - -func UpdateXP(dg *discordgo.Session, c *Copaing) { - oldXP := c.XP - if oldXP == 0 { - return - } - h := c.HourSinceLastEvent() - l := exp.Lose(h, c.XP) - xp := c.XPAlreadyRemoved() - if l-xp < 0 { - utils.SendWarn("lose - xp_removed is negative", "lose", l, "xp removed", xp) - c.AddXPAlreadyRemoved(0) - } else { - calc := int(c.XP) - int(l) + int(xp) - if calc < 0 { - c.AddXPAlreadyRemoved(c.XP) - c.XP = 0 - } else { - c.XP = uint(calc) - c.AddXPAlreadyRemoved(l - xp) - } - } - if oldXP != c.XP { - lvl := exp.Level(c.XP) - if exp.Level(oldXP) != lvl { - utils.SendDebug( - "Level updated", - "old", exp.Level(oldXP), - "new", lvl, - "discord_id", c.DiscordID, - "guild_id", c.GuildID, - ) - c.OnNewLevel(dg, lvl) - } - utils.SendDebug("Save XP", "old", oldXP, "new", c.XP, "user", c.DiscordID) - if err := c.Save(); err != nil { - utils.SendAlert( - "user/level.go - Saving user", err.Error(), - "xp", c.XP, - "discord_id", c.DiscordID, - "guild_id", c.GuildID, - ) - } - } -} - func PeriodicReducer(dg *discordgo.Session) { var wg sync.WaitGroup for _, g := range dg.State.Guilds { @@ -195,7 +110,7 @@ func PeriodicReducer(dg *discordgo.Session) { } wg.Add(1) go func() { - UpdateXP(dg, c) + //do things wg.Done() }() } diff --git a/user/xp.go b/user/xp.go index 90be09d..23cefcd 100644 --- a/user/xp.go +++ b/user/xp.go @@ -1,18 +1,9 @@ package user import ( - "context" - "errors" - "fmt" - "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/exp" "github.com/bwmarrin/discordgo" - "github.com/redis/go-redis/v9" - "math" - "strconv" - "time" ) func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { @@ -39,122 +30,3 @@ func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn f onNewLevel(s, m, newLevel) } } - -func (c *Copaing) SetLastEvent() { - client, err := config.GetRedisClient() - if err != nil { - utils.SendAlert("user/xp.go - Getting redis client (set)", err.Error()) - return - } - t := time.Now().Unix() - err = client.Set(context.Background(), c.GenKey(LastEvent), strconv.FormatInt(t, 10), 0).Err() - if err != nil { - utils.SendAlert("user/xp.go - Setting last event", err.Error(), "time", t, "base_key", c.GenKey("")) - return - } - err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), "0", 0).Err() - if err != nil { - utils.SendAlert( - "user/xp.go - Setting already removed to 0", - err.Error(), - "time", - t, - "base_key", - c.GenKey(""), - ) - return - } -} - -func (c *Copaing) HourSinceLastEvent() uint { - client, err := config.GetRedisClient() - if err != nil { - utils.SendAlert("user/xp.go - Getting redis client (get)", err.Error()) - return 0 - } - res := client.Get(context.Background(), c.GenKey(LastEvent)) - if errors.Is(res.Err(), redis.Nil) { - return 0 - } else if res.Err() != nil { - utils.SendAlert("user/xp.go - Getting last event", res.Err().Error(), "base_key", c.GenKey("")) - return 0 - } - t := time.Now().Unix() - last, err := strconv.Atoi(res.Val()) - if err != nil { - utils.SendAlert( - "user/xp.go - Converting time fetched into int (last event)", - err.Error(), - "base_key", - c.GenKey(""), - "val", - res.Val(), - ) - return 0 - } - if gokord.Debug { - return uint(math.Floor(float64(t-int64(last)) / 60)) // not hours of unix, is minutes of unix - } - return utils.HoursOfUnix(t - int64(last)) -} - -func (c *Copaing) AddXPAlreadyRemoved(xp uint) uint { - client, err := config.GetRedisClient() - if err != nil { - utils.SendAlert("user/xp.go - Getting redis client (set)", err.Error()) - return 0 - } - nxp := xp + c.XPAlreadyRemoved() - err = client.Set(context.Background(), c.GenKey(AlreadyRemoved), nxp, 0).Err() - if err != nil { - utils.SendAlert( - "user/xp.go - Setting already removed", - err.Error(), - "exp already removed", - nxp, - "base_key", - c.GenKey(""), - ) - return 0 - } - return nxp -} - -func (c *Copaing) XPAlreadyRemoved() uint { - client, err := config.GetRedisClient() - if err != nil { - utils.SendAlert("user/xp.go - Getting redis client (exp)", err.Error()) - return 0 - } - res := client.Get(context.Background(), fmt.Sprintf("%s:%s", c.GenKey(""), AlreadyRemoved)) - if errors.Is(res.Err(), redis.Nil) { - return 0 - } else if res.Err() != nil { - utils.SendAlert("user/xp.go - Getting already removed", res.Err().Error(), "base_key", c.GenKey("")) - return 0 - } - xp, err := strconv.Atoi(res.Val()) - if err != nil { - utils.SendAlert( - "user/xp.go - Converting time fetched into int (already removed)", - err.Error(), - "base_key", - c.GenKey(""), - "val", - res.Val(), - ) - return 0 - } - if xp < 0 { - utils.SendAlert( - "user/xp.go - Assertion exp >= 0", - "xp is negative", - "base_key", - c.GenKey(""), - "xp", - xp, - ) - return 0 - } - return uint(xp) -} -- cgit v1.2.3 From 799df74fcda5266fd295b49fc759c605c815cad9 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 14:11:11 +0200 Subject: feat(xp): new add --- user/member.go | 34 +++++++++++++++++++++++++++++++--- user/xp.go | 13 ++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) (limited to 'user') diff --git a/user/member.go b/user/member.go index dc93979..b61aa76 100644 --- a/user/member.go +++ b/user/member.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" + "github.com/anhgelus/les-copaings-bot/config" + "time" ) type Copaing struct { @@ -14,9 +16,11 @@ type Copaing struct { } type CopaingXP struct { - ID uint `gorm:"primarykey"` - XP uint `gorm:"default:0"` - CopaingID uint `gorm:"not null;constraint:OnDelete:CASCADE;"` + ID uint `gorm:"primarykey"` + XP uint `gorm:"default:0"` + CopaingID uint `gorm:"not null;constraint:OnDelete:CASCADE;"` + GuildID string `gorm:"not null;"` + CreatedAt time.Time } const ( @@ -48,6 +52,30 @@ func (c *Copaing) Load() error { Error } +func (c *Copaing) GetXP() (uint, error) { + cfg := config.GetGuildConfig(c.GuildID) + xp := uint(0) + y, m, d := time.Unix(time.Now().Unix()-int64(cfg.DaysXPRemains*24*60*60), 0).Date() + rows, err := gokord.DB. + Model(&CopaingXP{}). + Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and discord_id = ?", y, m, d), c.GuildID, c.DiscordID). + Rows() + defer rows.Close() + if err != nil { + return 0, err + } + for rows.Next() { + var cXP CopaingXP + err = gokord.DB.ScanRows(rows, &cXP) + if err != nil { + utils.SendAlert("user/member.go - Scaning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID) + continue + } + xp += cXP.XP + } + return xp, nil +} + func (c *Copaing) Save() error { return gokord.DB.Save(c).Error } diff --git a/user/xp.go b/user/xp.go index 23cefcd..f8bcd06 100644 --- a/user/xp.go +++ b/user/xp.go @@ -7,10 +7,10 @@ import ( ) func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { - pastLevel := exp.Level(c.XP) - old := c.XP - c.XP += xp - if err := c.Save(); err != nil { + old, err := c.GetXP() + pastLevel := exp.Level(old) + c.XP = append(c.XP, CopaingXP{CopaingID: c.ID, XP: xp, GuildID: c.GuildID}) + if err = c.Save(); err != nil { utils.SendAlert( "user/xp.go - Saving user", err.Error(), @@ -21,12 +21,11 @@ func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn f "guild_id", c.GuildID, ) - c.XP = old return } - newLevel := exp.Level(c.XP) + newLevel := exp.Level(old + xp) if newLevel > pastLevel { - fn(c.XP, newLevel) + fn(old+xp, newLevel) onNewLevel(s, m, newLevel) } } -- cgit v1.2.3 From 01bafe9bf1de5be4e770b9500480807d4973d8d6 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 16:15:47 +0200 Subject: feat(top): implements new kind of tops --- user/member.go | 30 +++--------------- user/xp.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 25 deletions(-) (limited to 'user') diff --git a/user/member.go b/user/member.go index b61aa76..cf7c4a4 100644 --- a/user/member.go +++ b/user/member.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/config" "time" ) @@ -23,6 +22,11 @@ type CopaingXP struct { CreatedAt time.Time } +type CopaingAccess interface { + ToCopaing() *Copaing + GetXP() uint +} + const ( LastEvent = "last_event" AlreadyRemoved = "already_removed" @@ -52,30 +56,6 @@ func (c *Copaing) Load() error { Error } -func (c *Copaing) GetXP() (uint, error) { - cfg := config.GetGuildConfig(c.GuildID) - xp := uint(0) - y, m, d := time.Unix(time.Now().Unix()-int64(cfg.DaysXPRemains*24*60*60), 0).Date() - rows, err := gokord.DB. - Model(&CopaingXP{}). - Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and discord_id = ?", y, m, d), c.GuildID, c.DiscordID). - Rows() - defer rows.Close() - if err != nil { - return 0, err - } - for rows.Next() { - var cXP CopaingXP - err = gokord.DB.ScanRows(rows, &cXP) - if err != nil { - utils.SendAlert("user/member.go - Scaning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID) - continue - } - xp += cXP.XP - } - return xp, nil -} - func (c *Copaing) Save() error { return gokord.DB.Save(c).Error } diff --git a/user/xp.go b/user/xp.go index f8bcd06..c2e4d08 100644 --- a/user/xp.go +++ b/user/xp.go @@ -1,11 +1,30 @@ package user import ( + "fmt" + "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" + "github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/exp" "github.com/bwmarrin/discordgo" + "slices" + "sync" + "time" ) +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 *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { old, err := c.GetXP() pastLevel := exp.Level(old) @@ -29,3 +48,83 @@ func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn f onNewLevel(s, m, newLevel) } } + +func (c *Copaing) GetXP() (uint, error) { + cfg := config.GetGuildConfig(c.GuildID) + return c.GetXPForDays(cfg.DaysXPRemains) +} + +func (c *Copaing) GetXPForDays(n uint) (uint, error) { + xp := uint(0) + y, m, d := time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() + rows, err := gokord.DB. + Model(&CopaingXP{}). + Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and discord_id = ?", y, m, d), c.GuildID, c.DiscordID). + Rows() + defer rows.Close() + if err != nil { + return 0, err + } + for rows.Next() { + var cXP CopaingXP + err = gokord.DB.ScanRows(rows, &cXP) + if err != nil { + utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID) + continue + } + xp += cXP.XP + } + return xp, nil +} + +// 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(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() + defer rows.Close() + if err != nil { + return nil, err + } + var l []*cXP + wg := sync.WaitGroup{} + for rows.Next() { + var c Copaing + err = gokord.DB.ScanRows(rows, &c) + if err != nil { + utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", guildId) + continue + } + wg.Add(1) + go func() { + defer wg.Done() + xp, err := c.GetXPForDays(uint(d)) + if err != nil { + utils.SendAlert("user/xp.go - Fetching xp", err.Error(), "discord_id", c.DiscordID, "guild_id", 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 +} -- cgit v1.2.3 From 61c7bf4567249da0d13c2f738e56754a2c181c99 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 16:52:04 +0200 Subject: fix(db): wrong relation and bad where condition --- user/member.go | 16 ++++++++-------- user/xp.go | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 15 deletions(-) (limited to 'user') diff --git a/user/member.go b/user/member.go index cf7c4a4..71a369b 100644 --- a/user/member.go +++ b/user/member.go @@ -8,16 +8,16 @@ import ( ) type Copaing struct { - ID uint `gorm:"primarykey"` - DiscordID string `gorm:"not null"` - XP []CopaingXP `gorm:"constraint:OnDelete:SET NULL;"` - GuildID string `gorm:"not null"` + ID uint `gorm:"primarykey"` + DiscordID string `gorm:"not null"` + CopaingXPs []CopaingXP `gorm:"constraint:OnDelete:SET NULL;"` + GuildID string `gorm:"not null"` } type CopaingXP struct { - ID uint `gorm:"primarykey"` - XP uint `gorm:"default:0"` - CopaingID uint `gorm:"not null;constraint:OnDelete:CASCADE;"` + ID uint `gorm:"primarykey"` + XP uint `gorm:"default:0"` + CopaingID uint GuildID string `gorm:"not null;"` CreatedAt time.Time } @@ -51,7 +51,7 @@ func GetCopaing(discordID string, guildID string) *Copaing { func (c *Copaing) Load() error { return gokord.DB. Where("discord_id = ? and guild_id = ?", c.DiscordID, c.GuildID). - Preload("XP"). + Preload("CopaingXPs"). FirstOrCreate(c). Error } diff --git a/user/xp.go b/user/xp.go index c2e4d08..fca5754 100644 --- a/user/xp.go +++ b/user/xp.go @@ -28,13 +28,13 @@ func (c *cXP) GetXP() uint { func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { old, err := c.GetXP() pastLevel := exp.Level(old) - c.XP = append(c.XP, CopaingXP{CopaingID: c.ID, XP: xp, GuildID: c.GuildID}) + c.CopaingXPs = append(c.CopaingXPs, CopaingXP{CopaingID: c.ID, XP: xp, GuildID: c.GuildID}) if err = c.Save(); err != nil { utils.SendAlert( "user/xp.go - Saving user", err.Error(), - "exp", - c.XP, + "xp", + c.CopaingXPs, "discord_id", c.DiscordID, "guild_id", @@ -56,10 +56,16 @@ func (c *Copaing) GetXP() (uint, error) { func (c *Copaing) GetXPForDays(n uint) (uint, error) { xp := uint(0) - y, m, d := time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() + var y, d int + var m time.Month + if gokord.Debug { + y, m, d = time.Unix(time.Now().Unix()-int64(n*24), 0).Date() // reduce time for debug + } else { + y, m, d = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() + } rows, err := gokord.DB. Model(&CopaingXP{}). - Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and discord_id = ?", y, m, d), c.GuildID, c.DiscordID). + Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and copaing_id = ?", y, m, d), c.GuildID, c.DiscordID). Rows() defer rows.Close() if err != nil { @@ -69,7 +75,7 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) { var cXP CopaingXP err = gokord.DB.ScanRows(rows, &cXP) if err != nil { - utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID) + utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "copaing_id", c.DiscordID, "guild_id", c.GuildID) continue } xp += cXP.XP @@ -96,7 +102,7 @@ func GetBestXP(guildId string, n uint, d int) ([]CopaingAccess, error) { var c Copaing err = gokord.DB.ScanRows(rows, &c) if err != nil { - utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "discord_id", c.DiscordID, "guild_id", guildId) + utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "guild_id", guildId) continue } wg.Add(1) -- cgit v1.2.3 From 067e149e72da10790e347b82045d82a96397d6cf Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 17:18:33 +0200 Subject: fix(db): using discord_id instead of db's id --- user/xp.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'user') diff --git a/user/xp.go b/user/xp.go index fca5754..7501139 100644 --- a/user/xp.go +++ b/user/xp.go @@ -27,7 +27,12 @@ func (c *cXP) GetXP() uint { func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { old, err := c.GetXP() + if err != nil { + utils.SendAlert("user/xp.go - Getting xp", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID) + return + } pastLevel := exp.Level(old) + utils.SendDebug("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: xp, GuildID: c.GuildID}) if err = c.Save(); err != nil { utils.SendAlert( @@ -59,13 +64,13 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) { var y, d int var m time.Month if gokord.Debug { - y, m, d = time.Unix(time.Now().Unix()-int64(n*24), 0).Date() // reduce time for debug + y, m, d = time.Unix(time.Now().Unix()-int64(24*60*60), 0).Date() // reduce time for debug } else { y, m, d = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() } rows, err := gokord.DB. Model(&CopaingXP{}). - Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and copaing_id = ?", y, m, d), c.GuildID, c.DiscordID). + Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and copaing_id = ?", y, m, d), c.GuildID, c.ID). Rows() defer rows.Close() if err != nil { @@ -75,7 +80,7 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) { var cXP CopaingXP err = gokord.DB.ScanRows(rows, &cXP) if err != nil { - utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "copaing_id", c.DiscordID, "guild_id", c.GuildID) + utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "copaing_id", c.ID, "guild_id", c.GuildID) continue } xp += cXP.XP -- cgit v1.2.3 From eaf9fa51cdd9509c5d075633b712ec9b5ea712c7 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 17:35:03 +0200 Subject: perf(db): remove useless XP data --- user/level.go | 73 +++++++++++------------------------------------------------ user/xp.go | 16 +++++-------- 2 files changed, 19 insertions(+), 70 deletions(-) (limited to 'user') diff --git a/user/level.go b/user/level.go index 1143c59..5f847b6 100644 --- a/user/level.go +++ b/user/level.go @@ -8,7 +8,6 @@ import ( "github.com/bwmarrin/discordgo" "slices" "sync" - "time" ) func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { @@ -55,68 +54,22 @@ func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { } func PeriodicReducer(dg *discordgo.Session) { - var wg sync.WaitGroup + wg := &sync.WaitGroup{} for _, g := range dg.State.Guilds { - var cs []*Copaing - err := gokord.DB.Where("guild_id = ?", g.ID).Find(&cs).Error - if err != nil { - utils.SendAlert("user/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) - continue - } - for i, c := range cs { - if i%50 == 49 { - time.Sleep(15 * time.Second) // sleep prevents from spamming the Discord API and the database - } - var u *discordgo.User - u, err = dg.User(c.DiscordID) + wg.Add(1) + go func() { + defer wg.Done() + cfg := config.GetGuildConfig(g.ID) + err := gokord.DB. + Model(&CopaingXP{}). + Where("guild_id = ? and created_at < ?", g.ID, exp.TimeStampNDaysBefore(cfg.DaysXPRemains)). + Delete(&CopaingXP{}). + Error if err != nil { - utils.SendAlert( - "user/level.go - Fetching user", err.Error(), - "discord_id", c.DiscordID, - "guild_id", g.ID, - ) - utils.SendWarn("Removing user from database", "discord_id", c.DiscordID) - if err = gokord.DB.Delete(c).Error; err != nil { - utils.SendAlert( - "user/level.go - Removing user from database", err.Error(), - "discord_id", c.DiscordID, - "guild_id", g.ID, - ) - } - continue - } - if u.Bot { - continue + utils.SendAlert("user/level.go - Removing old XP", err.Error(), "guild_id", g.ID) } - if _, err = dg.GuildMember(g.ID, c.DiscordID); err != nil { - utils.SendAlert( - "user/level.go - Fetching member", err.Error(), - "discord_id", c.DiscordID, - "guild_id", g.ID, - ) - utils.SendWarn( - "Removing user from guild in database", - "discord_id", c.DiscordID, - "guild_id", g.ID, - ) - if err = gokord.DB.Where("guild_id = ?", g.ID).Delete(c).Error; err != nil { - utils.SendAlert( - "user/level.go - Removing user from guild in database", err.Error(), - "discord_id", c.DiscordID, - "guild_id", g.ID, - ) - } - continue - } - wg.Add(1) - go func() { - //do things - wg.Done() - }() - } - wg.Wait() // finish the entire guild before starting another - utils.SendDebug("Periodic reduce, guild finished", "guild", g.Name) - time.Sleep(15 * time.Second) // sleep prevents from spamming the Discord API and the database + }() } + wg.Wait() utils.SendDebug("Periodic reduce finished", "len(guilds)", len(dg.State.Guilds)) } diff --git a/user/xp.go b/user/xp.go index 7501139..d88e3ce 100644 --- a/user/xp.go +++ b/user/xp.go @@ -1,7 +1,6 @@ package user import ( - "fmt" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/config" @@ -9,7 +8,6 @@ import ( "github.com/bwmarrin/discordgo" "slices" "sync" - "time" ) type cXP struct { @@ -61,16 +59,14 @@ func (c *Copaing) GetXP() (uint, error) { func (c *Copaing) GetXPForDays(n uint) (uint, error) { xp := uint(0) - var y, d int - var m time.Month - if gokord.Debug { - y, m, d = time.Unix(time.Now().Unix()-int64(24*60*60), 0).Date() // reduce time for debug - } else { - y, m, d = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() - } rows, err := gokord.DB. Model(&CopaingXP{}). - Where(fmt.Sprintf("created_at >= '%d-%d-%d' and guild_id = ? and copaing_id = ?", y, m, d), c.GuildID, c.ID). + Where( + "created_at >= '?' and guild_id = ? and copaing_id = ?", + exp.TimeStampNDaysBefore(n), + c.GuildID, + c.ID, + ). Rows() defer rows.Close() if err != nil { -- cgit v1.2.3 From f2fbf1db929e4bd2b3af014fb8d9af4c0f934a1e Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 18:06:17 +0200 Subject: feat(xp): update role after removing all old xp --- user/level.go | 41 +++++++++++++++++++++++++++++++++++++++++ user/xp.go | 8 ++++---- 2 files changed, 45 insertions(+), 4 deletions(-) (limited to 'user') diff --git a/user/level.go b/user/level.go index 5f847b6..6d9b674 100644 --- a/user/level.go +++ b/user/level.go @@ -8,6 +8,7 @@ import ( "github.com/bwmarrin/discordgo" "slices" "sync" + "time" ) func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { @@ -55,6 +56,31 @@ func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { func PeriodicReducer(dg *discordgo.Session) { wg := &sync.WaitGroup{} + var cs []*Copaing + if err := gokord.DB.Find(&cs).Error; err != nil { + utils.SendAlert("user/level.go - Fetching all copaings", err.Error()) + return + } + cxps := make([]*cXP, len(cs)) + for i, c := range cs { + if i%10 == 9 { + wg.Wait() // prevents spamming the DB + } + wg.Add(1) + go func() { + defer wg.Done() + xp, err := c.GetXP() + if err != nil { + utils.SendAlert("user/level.go - Getting XP", err.Error(), "copaing_id", c.ID, "guild_id", c.GuildID) + xp = 0 + } + cxps[i] = &cXP{ + Cxp: xp, + Copaing: c, + } + }() + } + wg.Wait() for _, g := range dg.State.Guilds { wg.Add(1) go func() { @@ -71,5 +97,20 @@ func PeriodicReducer(dg *discordgo.Session) { }() } wg.Wait() + for i, c := range cxps { + if i%50 == 49 { + utils.SendDebug("Sleeping...") + time.Sleep(15 * time.Second) // prevents spamming the API + } + oldXp := c.GetXP() + xp, err := c.ToCopaing().GetXP() + if err != nil { + utils.SendAlert("user/level.go - Getting XP", err.Error(), "guild_id", c.ID, "discord_id", c.DiscordID) + continue + } + if exp.Level(oldXp) != exp.Level(xp) { + c.OnNewLevel(dg, exp.Level(xp)) + } + } utils.SendDebug("Periodic reduce finished", "len(guilds)", len(dg.State.Guilds)) } diff --git a/user/xp.go b/user/xp.go index d88e3ce..71a1ced 100644 --- a/user/xp.go +++ b/user/xp.go @@ -62,7 +62,7 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) { rows, err := gokord.DB. Model(&CopaingXP{}). Where( - "created_at >= '?' and guild_id = ? and copaing_id = ?", + "created_at >= ? and guild_id = ? and copaing_id = ?", exp.TimeStampNDaysBefore(n), c.GuildID, c.ID, @@ -73,13 +73,13 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) { return 0, err } for rows.Next() { - var cXP CopaingXP - err = gokord.DB.ScanRows(rows, &cXP) + var cxp CopaingXP + err = gokord.DB.ScanRows(rows, &cxp) if err != nil { utils.SendAlert("user/xp.go - Scanning rows", err.Error(), "copaing_id", c.ID, "guild_id", c.GuildID) continue } - xp += cXP.XP + xp += cxp.XP } return xp, nil } -- cgit v1.2.3