From b088cfb7b0c0ecf0cb63551dd5942a53247fb59f Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 12:08:16 +0200 Subject: [PATCH 01/18] build(go): updapte gokords --- go.mod | 22 ++++++++++------------ go.sum | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1950e11..f11cdd8 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,12 @@ module github.com/anhgelus/les-copaings-bot -go 1.23.0 - -toolchain go1.24.0 +go 1.24 require ( - github.com/anhgelus/gokord v0.6.2 + github.com/anhgelus/gokord v0.6.3 github.com/bwmarrin/discordgo v0.28.1 - github.com/redis/go-redis/v9 v9.7.1 - gorm.io/gorm v1.25.12 + github.com/redis/go-redis/v9 v9.8.0 + gorm.io/gorm v1.26.1 ) require ( @@ -17,14 +15,14 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect gorm.io/driver/postgres v1.5.11 // indirect ) diff --git a/go.sum b/go.sum index 9b7206e..a188c46 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/anhgelus/gokord v0.6.1 h1:zgNeFMyp+jXyaQH3CzOJGhE6y+NMfFHVTgZK7uT18ek github.com/anhgelus/gokord v0.6.1/go.mod h1:R1SMf1+C8FshZ1/fDyBBt1Y9ob097UShHON21btlw2s= github.com/anhgelus/gokord v0.6.2 h1:jR5l6NVGio+yChg8kxeNJSm6mbBfWxSLkvR6FPoh5E4= github.com/anhgelus/gokord v0.6.2/go.mod h1:R1SMf1+C8FshZ1/fDyBBt1Y9ob097UShHON21btlw2s= +github.com/anhgelus/gokord v0.6.3 h1:4Z57e64YcecI6gokGVpIq8YOfvybJofX2Kx1HYRTuwo= +github.com/anhgelus/gokord v0.6.3/go.mod h1:UIpun+/+pgtvMQZdXvsy3qBhNSPG+q18shwDShDEI3Y= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -36,6 +38,8 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg= +github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -44,12 +48,16 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc= github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -62,6 +70,8 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -69,6 +79,8 @@ golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -76,6 +88,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= @@ -84,6 +98,8 @@ golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -93,3 +109,5 @@ gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= +gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= From 0a445aa1c73bc1410899c53778ae090a24c38dac Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 12:13:55 +0200 Subject: [PATCH 02/18] fix(config): limits one config per guild --- config/guild.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/guild.go b/config/guild.go index 242cc89..b4b7014 100644 --- a/config/guild.go +++ b/config/guild.go @@ -9,8 +9,8 @@ import ( type GuildConfig struct { gorm.Model - GuildID string `gorm:"not null"` - XpRoles []XpRole + GuildID string `gorm:"not null;unique"` + XpRoles []*XpRole DisabledChannels string FallbackChannel string } @@ -46,7 +46,7 @@ func (cfg *GuildConfig) IsDisabled(channelID string) bool { func (cfg *GuildConfig) FindXpRole(roleID string) (int, *XpRole) { for i, r := range cfg.XpRoles { if r.RoleID == roleID { - return i, &r + return i, r } } return 0, nil From c408afc8797b0da5e1d73d190a8f5884870b510c Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 12:50:20 +0200 Subject: [PATCH 03/18] style(files): reorganize everything --- commands/config.go | 8 +- commands/rank.go | 17 +-- commands/reset.go | 14 +- commands/top.go | 13 +- config/guild.go | 4 +- config/redis.go | 28 ++++ xp/events.go => events.go | 46 +++--- {xp => exp}/functions.go | 14 +- main.go | 26 ++-- {xp => user}/level.go | 53 +++---- user/member.go | 107 ++++++++++++++ user/xp.go | 185 ++++++++++++++++++++++++ xp/member.go | 296 -------------------------------------- 13 files changed, 422 insertions(+), 389 deletions(-) create mode 100644 config/redis.go rename xp/events.go => events.go (72%) rename {xp => exp}/functions.go (66%) rename {xp => user}/level.go (76%) create mode 100644 user/member.go create mode 100644 user/xp.go delete mode 100644 xp/member.go diff --git a/commands/config.go b/commands/config.go index 1e680c7..32da9c6 100644 --- a/commands/config.go +++ b/commands/config.go @@ -5,7 +5,7 @@ import ( "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/config" - "github.com/anhgelus/les-copaings-bot/xp" + "github.com/anhgelus/les-copaings-bot/exp" "github.com/bwmarrin/discordgo" "strings" ) @@ -17,9 +17,9 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate) { l := len(cfg.XpRoles) - 1 for i, r := range cfg.XpRoles { if i == l { - roles += fmt.Sprintf("> Niveau %d - <@&%s>", xp.Level(r.XP), r.RoleID) + roles += fmt.Sprintf("> Niveau %d - <@&%s>", exp.Level(r.XP), r.RoleID) } else { - roles += fmt.Sprintf("> Niveau %d - <@&%s>\n", xp.Level(r.XP), r.RoleID) + roles += fmt.Sprintf("> Niveau %d - <@&%s>\n", exp.Level(r.XP), r.RoleID) } } if len(roles) == 0 { @@ -98,7 +98,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { } return } - exp := xp.XPForLevel(uint(level)) + exp := exp.LevelXP(uint(level)) r, ok := optMap["role"] if !ok { err := resp.Message("Le rôle n'a pas été renseigné.").Send() diff --git a/commands/rank.go b/commands/rank.go index 4d6f817..c659f2b 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -3,19 +3,20 @@ package commands import ( "fmt" "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/xp" + "github.com/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" ) func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { optMap := utils.GenerateOptionMap(i) - c := xp.GetCopaing(i.Member.User.ID, i.GuildID) // current copaing = member who used /rank - xp.LastEventUpdate(s, c) // update xp and reset last event + c := user.GetCopaing(i.Member.User.ID, i.GuildID) // current user = member who used /rank + user.LastEventUpdate(s, c) // update exp and reset last event msg := "Votre niveau" m := i.Member var err error resp := utils.ResponseBuilder{C: s, I: i} - if v, ok := optMap["copaing"]; ok { + if v, ok := optMap["user"]; ok { u := v.UserValue(s) if u.Bot { err = resp.Message("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() @@ -39,12 +40,12 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { } return } - c = xp.GetCopaing(u.ID, i.GuildID) // current copaing = member targeted by member who wrote /rank - xp.XPUpdate(s, c) // update xp without resetting event + c = user.GetCopaing(u.ID, i.GuildID) // current user = member targeted by member who wrote /rank + user.UpdateXP(s, c) // update exp without resetting event msg = fmt.Sprintf("Le niveau de %s", m.DisplayName()) } - lvl := xp.Level(c.XP) - nxtLvlXP := xp.XPForLevel(lvl + 1) + lvl := exp.Level(c.XP) + nxtLvlXP := exp.LevelXP(lvl + 1) err = resp.Message(fmt.Sprintf( "%s : **%d**\n> XP : %d\n> Prochain niveau dans %d XP", msg, diff --git a/commands/reset.go b/commands/reset.go index 0c18044..1e8df6a 100644 --- a/commands/reset.go +++ b/commands/reset.go @@ -3,12 +3,12 @@ package commands import ( "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/xp" + "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" ) func Reset(s *discordgo.Session, i *discordgo.InteractionCreate) { - var copaings []*xp.Copaing + var copaings []*user.Copaing gokord.DB.Where("guild_id = ?", i.GuildID).Delete(&copaings) resp := utils.ResponseBuilder{C: s, I: i} if err := resp.IsEphemeral().Message("L'XP a été reset.").Send(); err != nil { @@ -20,9 +20,9 @@ func ResetUser(s *discordgo.Session, i *discordgo.InteractionCreate) { resp := utils.ResponseBuilder{C: s, I: i} resp.IsEphemeral() optMap := utils.GenerateOptionMap(i) - v, ok := optMap["copaing"] + v, ok := optMap["user"] if !ok { - if err := resp.Message("Le copaing n'a pas été renseigné.").Send(); err != nil { + if err := resp.Message("Le user n'a pas été renseigné.").Send(); err != nil { utils.SendAlert("commands/reset.go - Copaing not set", err.Error()) } return @@ -34,8 +34,8 @@ func ResetUser(s *discordgo.Session, i *discordgo.InteractionCreate) { } return } - xp.GetCopaing(m.ID, i.GuildID).Reset() - if err := resp.Message("Le copaing bien été reset.").Send(); err != nil { - utils.SendAlert("commands/reset.go - Sending success (copaing)", err.Error()) + user.GetCopaing(m.ID, i.GuildID).Reset() + if err := resp.Message("Le user bien été reset.").Send(); err != nil { + utils.SendAlert("commands/reset.go - Sending success (user)", err.Error()) } } diff --git a/commands/top.go b/commands/top.go index 8320fe9..22574ce 100644 --- a/commands/top.go +++ b/commands/top.go @@ -4,12 +4,13 @@ import ( "fmt" "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/xp" + "github.com/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" ) func Top(s *discordgo.Session, i *discordgo.InteractionCreate) { - xp.LastEventUpdate(s, xp.GetCopaing(i.Member.User.ID, i.GuildID)) + user.LastEventUpdate(s, user.GetCopaing(i.Member.User.ID, i.GuildID)) resp := utils.ResponseBuilder{C: s, I: i} err := resp.IsDeferred().Send() if err != nil { @@ -18,14 +19,14 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate) { } resp.NotDeferred().IsEdit() go func() { - var tops []xp.Copaing - gokord.DB.Where("guild_id = ?", i.GuildID).Limit(10).Order("xp desc").Find(&tops) + var tops []user.Copaing + gokord.DB.Where("guild_id = ?", i.GuildID).Limit(10).Order("exp desc").Find(&tops) msg := "" for i, c := range tops { if i == 9 { - msg += fmt.Sprintf("%d. **<@%s>** - niveau %d", i+1, c.DiscordID, xp.Level(c.XP)) + msg += fmt.Sprintf("%d. **<@%s>** - niveau %d", i+1, c.DiscordID, exp.Level(c.XP)) } else { - msg += fmt.Sprintf("%d. **<@%s>** - niveau %d\n", i+1, c.DiscordID, xp.Level(c.XP)) + msg += fmt.Sprintf("%d. **<@%s>** - niveau %d\n", i+1, c.DiscordID, exp.Level(c.XP)) } } err = resp.Embeds([]*discordgo.MessageEmbed{ diff --git a/config/guild.go b/config/guild.go index b4b7014..0971837 100644 --- a/config/guild.go +++ b/config/guild.go @@ -10,7 +10,7 @@ import ( type GuildConfig struct { gorm.Model GuildID string `gorm:"not null;unique"` - XpRoles []*XpRole + XpRoles []XpRole DisabledChannels string FallbackChannel string } @@ -46,7 +46,7 @@ func (cfg *GuildConfig) IsDisabled(channelID string) bool { func (cfg *GuildConfig) FindXpRole(roleID string) (int, *XpRole) { for i, r := range cfg.XpRoles { if r.RoleID == roleID { - return i, r + return i, &r } } return 0, nil diff --git a/config/redis.go b/config/redis.go new file mode 100644 index 0000000..8aa370d --- /dev/null +++ b/config/redis.go @@ -0,0 +1,28 @@ +package config + +import ( + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/utils" + "github.com/redis/go-redis/v9" +) + +var redisClient *redis.Client + +func GetRedisClient() (*redis.Client, error) { + if redisClient == nil { + var err error + redisClient, err = gokord.BaseCfg.GetRedisCredentials().Connect() + return redisClient, err + } + return redisClient, nil +} + +func CloseRedisClient() { + if redisClient == nil { + return + } + err := redisClient.Close() + if err != nil { + utils.SendAlert("exp/member.go - Closing redis client", err.Error()) + } +} diff --git a/xp/events.go b/events.go similarity index 72% rename from xp/events.go rename to events.go index 94d8889..e35ced0 100644 --- a/xp/events.go +++ b/events.go @@ -1,4 +1,4 @@ -package xp +package main import ( "context" @@ -7,6 +7,8 @@ import ( "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/config" + xp2 "github.com/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" "github.com/redis/go-redis/v9" "slices" @@ -30,20 +32,20 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { if cfg.IsDisabled(m.ChannelID) { return } - c := GetCopaing(m.Author.ID, m.GuildID) - LastEventUpdate(s, c) - // add xp + c := user.GetCopaing(m.Author.ID, m.GuildID) + user.LastEventUpdate(s, c) + // add exp trimmed := utils.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author m.Member.GuildID = m.GuildID - xp := XPMessage(uint(len(trimmed)), calcDiversity(trimmed)) + xp := xp2.MessageXP(uint(len(trimmed)), calcDiversity(trimmed)) if xp > MaxXpPerMessage { xp = MaxXpPerMessage } c.AddXP(s, m.Member, xp, func(_ uint, _ uint) { if err := s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil { utils.SendAlert( - "xp/events.go - add reaction for new level", err.Error(), + "exp/events.go - add reaction for new level", err.Error(), "channel id", m.ChannelID, "message id", m.Message.ID, ) @@ -65,11 +67,11 @@ func OnVoiceUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { if e.Member.User.Bot { return } - LastEventUpdate(s, GetCopaing(e.UserID, e.GuildID)) + user.LastEventUpdate(s, user.GetCopaing(e.UserID, e.GuildID)) cfg := config.GetGuildConfig(e.GuildID) - client, err := getRedisClient() + client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("xp/events.go - Getting redis client", err.Error()) + utils.SendAlert("exp/events.go - Getting redis client", err.Error()) return } if e.BeforeUpdate == nil && e.ChannelID != "" { @@ -87,7 +89,7 @@ func OnVoiceUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { func onConnection(_ *discordgo.Session, e *discordgo.VoiceStateUpdate, client *redis.Client) { utils.SendDebug("User connected", "username", e.Member.DisplayName()) - c := GetCopaing(e.UserID, e.GuildID) + c := user.GetCopaing(e.UserID, e.GuildID) err := client.Set( context.Background(), c.GenKey(ConnectedSince), @@ -95,13 +97,13 @@ func onConnection(_ *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r 0, ).Err() if err != nil { - utils.SendAlert("xp/events.go - Setting connected_since", err.Error()) + utils.SendAlert("exp/events.go - Setting connected_since", err.Error()) } } func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *redis.Client) { now := time.Now().Unix() - c := GetCopaing(e.UserID, e.GuildID) + c := user.GetCopaing(e.UserID, e.GuildID) key := c.GenKey(ConnectedSince) res := client.Get(context.Background(), key) // check validity of user (1) @@ -112,16 +114,16 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r return } if res.Err() != nil { - utils.SendAlert("xp/events.go - Getting connected_since", res.Err().Error()) + utils.SendAlert("exp/events.go - Getting connected_since", res.Err().Error()) err := client.Set(context.Background(), key, strconv.Itoa(NotConnected), 0).Err() if err != nil { - utils.SendAlert("xp/events.go - Set connected_since to not connected after get err", err.Error()) + utils.SendAlert("exp/events.go - Set connected_since to not connected after get err", err.Error()) } return } con, err := res.Int64() if err != nil { - utils.SendAlert("xp/events.go - Converting result to int64", err.Error()) + utils.SendAlert("exp/events.go - Converting result to int64", err.Error()) return } // check validity of user (2) @@ -134,12 +136,12 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r utils.SendDebug("User disconnected", "username", e.Member.DisplayName(), "since", con) err = client.Set(context.Background(), key, strconv.Itoa(NotConnected), 0).Err() if err != nil { - utils.SendAlert("xp/events.go - Set connected_since to not connected", err.Error()) + utils.SendAlert("exp/events.go - Set connected_since to not connected", err.Error()) } - // add xp + // add exp timeInVocal := now - con if timeInVocal < 0 { - utils.SendAlert("xp/events.go - Calculating time spent in vocal", "the time is negative") + utils.SendAlert("exp/events.go - Calculating time spent in vocal", "the time is negative") return } if timeInVocal > MaxTimeInVocal { @@ -147,23 +149,23 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r timeInVocal = MaxTimeInVocal } e.Member.GuildID = e.GuildID - c.AddXP(s, e.Member, XPVocal(uint(timeInVocal)), func(_ uint, newLevel uint) { + c.AddXP(s, e.Member, xp2.VocalXP(uint(timeInVocal)), func(_ uint, newLevel uint) { cfg := config.GetGuildConfig(e.GuildID) _, err = s.ChannelMessageSend(cfg.FallbackChannel, fmt.Sprintf( "%s est maintenant niveau %d", e.Member.Mention(), newLevel, )) if err != nil { - utils.SendAlert("xp/events.go - Sending new level in fallback channel", err.Error()) + utils.SendAlert("exp/events.go - Sending new level in fallback channel", err.Error()) } }) } func OnLeave(_ *discordgo.Session, e *discordgo.GuildMemberRemove) { utils.SendDebug("Leave event", "user_id", e.User.ID) - c := GetCopaing(e.User.ID, e.GuildID) + c := user.GetCopaing(e.User.ID, e.GuildID) if err := gokord.DB.Where("guild_id = ?", e.GuildID).Delete(c).Error; err != nil { utils.SendAlert( - "xp/events.go - deleting copaing from db", err.Error(), + "exp/events.go - deleting user from db", err.Error(), "user_id", e.User.ID, "guild_id", e.GuildID, ) diff --git a/xp/functions.go b/exp/functions.go similarity index 66% rename from xp/functions.go rename to exp/functions.go index 7ab57f2..da535c4 100644 --- a/xp/functions.go +++ b/exp/functions.go @@ -1,29 +1,33 @@ -package xp +package exp import ( "github.com/anhgelus/gokord" "math" ) -func XPMessage(length uint, diversity uint) uint { +func MessageXP(length uint, diversity uint) uint { return uint(math.Floor( 0.025*math.Pow(float64(length), 1.25)*math.Sqrt(float64(diversity)) + 1, )) } -func XPVocal(time uint) uint { +func VocalXP(time uint) uint { return uint(math.Floor( 0.01*math.Pow(float64(time), 1.3) + 1, )) } +// Level gives the level with the given XP. +// See LevelXP to get the XP required to get a level. func Level(xp uint) uint { return uint(math.Floor( 0.2 * math.Sqrt(float64(xp)), )) } -func XPForLevel(level uint) uint { +// LevelXP gives the XP required to get this level. +// See Level to get the level with the given XP. +func LevelXP(level uint) uint { return uint(math.Floor( math.Pow(float64(5*level), 2), )) @@ -33,7 +37,7 @@ func Lose(time uint, xp uint) uint { if gokord.Debug { return uint(math.Floor( math.Pow(float64(time), 3) * math.Pow(10, -2+math.Log(float64(time))) * math.Floor(float64(xp/500)+1), - )) // a little bit faster to lose xp + )) // a little bit faster to lose exp } return uint(math.Floor( math.Pow(float64(time), 2) * math.Pow(10, -2+math.Log(float64(time/85))) * math.Floor(float64(xp/500)+1), diff --git a/main.go b/main.go index 5bbc289..7b8e75f 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/commands" "github.com/anhgelus/les-copaings-bot/config" - "github.com/anhgelus/les-copaings-bot/xp" + "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" "time" ) @@ -36,18 +36,18 @@ func main() { panic(err) } - err = gokord.DB.AutoMigrate(&xp.Copaing{}, &config.GuildConfig{}, &config.XpRole{}) + err = gokord.DB.AutoMigrate(&user.Copaing{}, &config.GuildConfig{}, &config.XpRole{}) if err != nil { panic(err) } adm := gokord.AdminPermission - rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un copaing"). + rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un user"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionUser, - "copaing", + "user", "Le niveau du Copaing que vous souhaitez obtenir", )). SetHandler(commands.Rank) @@ -58,7 +58,7 @@ func main() { gokord.NewCommand("show", "Affiche la config").SetHandler(commands.ConfigShow), ). AddSub( - gokord.NewCommand("xp", "Modifie l'xp"). + gokord.NewCommand("exp", "Modifie l'exp"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionString, @@ -114,16 +114,16 @@ func main() { HasOption(). SetHandler(commands.Top) - resetCmd := gokord.NewCommand("reset", "Reset l'xp"). + resetCmd := gokord.NewCommand("reset", "Reset l'exp"). HasOption(). SetHandler(commands.Reset). SetPermission(&adm) - resetUserCmd := gokord.NewCommand("reset-user", "Reset l'xp d'un utilisation"). + resetUserCmd := gokord.NewCommand("reset-user", "Reset l'exp d'un utilisation"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionUser, - "copaing", + "user", "Copaing a reset", ).IsRequired()). SetHandler(commands.ResetUser). @@ -176,14 +176,14 @@ func main() { stopPeriodicReducer <- true } - xp.CloseRedisClient() + config.CloseRedisClient() } func afterInit(dg *discordgo.Session) { // handlers - dg.AddHandler(xp.OnMessage) - dg.AddHandler(xp.OnVoiceUpdate) - dg.AddHandler(xp.OnLeave) + dg.AddHandler(OnMessage) + dg.AddHandler(OnVoiceUpdate) + dg.AddHandler(OnLeave) // setup timer for periodic reducer d := 24 * time.Hour @@ -192,6 +192,6 @@ func afterInit(dg *discordgo.Session) { d = time.Minute } stopPeriodicReducer = utils.NewTimer(d, func(stop chan<- interface{}) { - xp.PeriodicReducer(dg) + user.PeriodicReducer(dg) }) } diff --git a/xp/level.go b/user/level.go similarity index 76% rename from xp/level.go rename to user/level.go index a20d5cf..0abd642 100644 --- a/xp/level.go +++ b/user/level.go @@ -1,9 +1,10 @@ -package xp +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" @@ -12,7 +13,7 @@ import ( func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { cfg := config.GetGuildConfig(m.GuildID) - xpForLevel := XPForLevel(level) + xpForLevel := exp.LevelXP(level) for _, role := range cfg.XpRoles { if role.XP <= xpForLevel && !slices.Contains(m.Roles, role.RoleID) { utils.SendDebug( @@ -23,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("xp/level.go - Adding role", err.Error(), "role_id", role.RoleID) + 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( @@ -34,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("xp/level.go - Removing role", err.Error(), "role_id", role.RoleID) + utils.SendAlert("exp/level.go - Removing role", err.Error(), "role_id", role.RoleID) } } } @@ -44,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( - "xp/level.go - Getting member for new level", err.Error(), + "exp/level.go - Getting member for new level", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID, ) @@ -55,11 +56,11 @@ func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { func LastEventUpdate(dg *discordgo.Session, c *Copaing) { h := c.HourSinceLastEvent() - l := Lose(h, c.XP) + 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) + 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()) @@ -70,11 +71,11 @@ func LastEventUpdate(dg *discordgo.Session, c *Copaing) { } } if oldXP != c.XP { - lvl := Level(c.XP) - if Level(oldXP) != lvl { + lvl := exp.Level(c.XP) + if exp.Level(oldXP) != lvl { utils.SendDebug( "Level changed", - "old", Level(oldXP), + "old", exp.Level(oldXP), "new", lvl, "discord_id", c.DiscordID, "guild_id", c.GuildID, @@ -83,8 +84,8 @@ func LastEventUpdate(dg *discordgo.Session, c *Copaing) { } if err := c.Save(); err != nil { utils.SendAlert( - "xp/level.go - Saving copaing", err.Error(), - "xp", c.XP, + "exp/level.go - Saving user", err.Error(), + "exp", c.XP, "discord_id", c.DiscordID, "guild_id", c.GuildID, ) @@ -93,16 +94,16 @@ func LastEventUpdate(dg *discordgo.Session, c *Copaing) { c.SetLastEvent() } -func XPUpdate(dg *discordgo.Session, c *Copaing) { +func UpdateXP(dg *discordgo.Session, c *Copaing) { oldXP := c.XP if oldXP == 0 { return } h := c.HourSinceLastEvent() - l := Lose(h, c.XP) + 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) + utils.SendWarn("lose - xp_removed is negative", "lose", l, "exp removed", xp) c.AddXPAlreadyRemoved(0) } else { calc := int(c.XP) - int(l) + int(xp) @@ -115,11 +116,11 @@ func XPUpdate(dg *discordgo.Session, c *Copaing) { } } if oldXP != c.XP { - lvl := Level(c.XP) - if Level(oldXP) != lvl { + lvl := exp.Level(c.XP) + if exp.Level(oldXP) != lvl { utils.SendDebug( "Level updated", - "old", Level(oldXP), + "old", exp.Level(oldXP), "new", lvl, "discord_id", c.DiscordID, "guild_id", c.GuildID, @@ -129,8 +130,8 @@ func XPUpdate(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( - "xp/level.go - Saving copaing", err.Error(), - "xp", c.XP, + "exp/level.go - Saving user", err.Error(), + "exp", c.XP, "discord_id", c.DiscordID, "guild_id", c.GuildID, ) @@ -144,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("xp/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) + utils.SendAlert("exp/level.go - Querying all copaings in Guild", err.Error(), "guild_id", g.ID) continue } for i, c := range cs { @@ -155,14 +156,14 @@ func PeriodicReducer(dg *discordgo.Session) { u, err = dg.User(c.DiscordID) if err != nil { utils.SendAlert( - "xp/level.go - Fetching user", err.Error(), + "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( - "xp/level.go - Removing user from database", err.Error(), + "exp/level.go - Removing user from database", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) @@ -174,7 +175,7 @@ func PeriodicReducer(dg *discordgo.Session) { } if _, err = dg.GuildMember(g.ID, c.DiscordID); err != nil { utils.SendAlert( - "xp/level.go - Fetching member", err.Error(), + "exp/level.go - Fetching member", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) @@ -185,7 +186,7 @@ func PeriodicReducer(dg *discordgo.Session) { ) if err = gokord.DB.Where("guild_id = ?", g.ID).Delete(c).Error; err != nil { utils.SendAlert( - "xp/level.go - Removing user from guild in database", err.Error(), + "exp/level.go - Removing user from guild in database", err.Error(), "discord_id", c.DiscordID, "guild_id", g.ID, ) @@ -194,7 +195,7 @@ func PeriodicReducer(dg *discordgo.Session) { } wg.Add(1) go func() { - XPUpdate(dg, c) + UpdateXP(dg, c) wg.Done() }() } 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 +} diff --git a/xp/member.go b/xp/member.go deleted file mode 100644 index ad9e6c5..0000000 --- a/xp/member.go +++ /dev/null @@ -1,296 +0,0 @@ -package xp - -import ( - "context" - "errors" - "fmt" - "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/utils" - "github.com/bwmarrin/discordgo" - "github.com/redis/go-redis/v9" - "gorm.io/gorm" - "math" - "strconv" - "time" -) - -type Copaing struct { - gorm.Model - DiscordID string `gorm:"not null"` - XP uint `gorm:"default:0"` - GuildID string `gorm:"not null"` -} - -type leftCopaing struct { - ID uint - StopDelete chan<- interface{} -} - -var ( - redisClient *redis.Client - - 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( - "xp/member.go - Loading copaing", - 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( - "xp/member.go - Getting copaing 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( - "xp/member.go - Deleting copaing 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) -} - -func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { - pastLevel := Level(c.XP) - old := c.XP - c.XP += xp - if err := c.Save(); err != nil { - utils.SendAlert( - "xp/level.go - Saving copaing", - err.Error(), - "xp", - c.XP, - "discord_id", - c.DiscordID, - "guild_id", - c.GuildID, - ) - c.XP = old - return - } - newLevel := Level(c.XP) - if newLevel > pastLevel { - fn(c.XP, newLevel) - onNewLevel(s, m, newLevel) - } -} - -func (c *Copaing) SetLastEvent() { - client, err := getRedisClient() - if err != nil { - utils.SendAlert("xp/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("xp/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( - "xp/member.go - Setting already removed to 0", - err.Error(), - "time", - t, - "base_key", - c.GenKey(""), - ) - return - } -} - -func (c *Copaing) HourSinceLastEvent() uint { - client, err := getRedisClient() - if err != nil { - utils.SendAlert("xp/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("xp/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( - "xp/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 := getRedisClient() - if err != nil { - utils.SendAlert("xp/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( - "xp/member.go - Setting already removed", - err.Error(), - "xp already removed", - exp, - "base_key", - c.GenKey(""), - ) - return 0 - } - return exp -} - -func (c *Copaing) XPAlreadyRemoved() uint { - client, err := getRedisClient() - if err != nil { - utils.SendAlert("xp/member.go - Getting redis client (xp)", 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("xp/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( - "xp/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( - "xp/member.go - Assertion xp >= 0", - "xp is negative", - "base_key", - c.GenKey(""), - "xp", - 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( - "xp/member.go - Removing copaing from database", err.Error(), - "discord_id", dID, - "guild_id", gID, - ) - } - stop <- true - leftCopaingsMap[k] = nil - }) - leftCopaingsMap[k] = &leftCopaing{id, ch} - return nil -} - -func getRedisClient() (*redis.Client, error) { - if redisClient == nil { - var err error - redisClient, err = gokord.BaseCfg.GetRedisCredentials().Connect() - return redisClient, err - } - return redisClient, nil -} - -func CloseRedisClient() { - if redisClient == nil { - return - } - err := redisClient.Close() - if err != nil { - utils.SendAlert("xp/member.go - Closing redis client", err.Error()) - } -} From d38c57b83009bf0a8b22bede7ee786d6f54fc66a Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:00:09 +0200 Subject: [PATCH 04/18] fix(log): wrong pos in many alerts --- config/redis.go | 2 +- events.go | 26 +++++++++++++------------- user/level.go | 26 +++++++++++++------------- user/member.go | 6 +++--- user/xp.go | 32 ++++++++++++++++---------------- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/config/redis.go b/config/redis.go index 8aa370d..bfec5a0 100644 --- a/config/redis.go +++ b/config/redis.go @@ -23,6 +23,6 @@ func CloseRedisClient() { } err := redisClient.Close() if err != nil { - utils.SendAlert("exp/member.go - Closing redis client", err.Error()) + utils.SendAlert("config/redis.go - Closing redis client", err.Error()) } } diff --git a/events.go b/events.go index e35ced0..a00fe6c 100644 --- a/events.go +++ b/events.go @@ -7,7 +7,7 @@ import ( "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/config" - xp2 "github.com/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" "github.com/redis/go-redis/v9" @@ -38,14 +38,14 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { trimmed := utils.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author m.Member.GuildID = m.GuildID - xp := xp2.MessageXP(uint(len(trimmed)), calcDiversity(trimmed)) + xp := exp.MessageXP(uint(len(trimmed)), calcDiversity(trimmed)) if xp > MaxXpPerMessage { xp = MaxXpPerMessage } c.AddXP(s, m.Member, xp, func(_ uint, _ uint) { if err := s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil { utils.SendAlert( - "exp/events.go - add reaction for new level", err.Error(), + "events.go - add reaction for new level", err.Error(), "channel id", m.ChannelID, "message id", m.Message.ID, ) @@ -71,7 +71,7 @@ func OnVoiceUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { cfg := config.GetGuildConfig(e.GuildID) client, err := config.GetRedisClient() if err != nil { - utils.SendAlert("exp/events.go - Getting redis client", err.Error()) + utils.SendAlert("events.go - Getting redis client", err.Error()) return } if e.BeforeUpdate == nil && e.ChannelID != "" { @@ -97,7 +97,7 @@ func onConnection(_ *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r 0, ).Err() if err != nil { - utils.SendAlert("exp/events.go - Setting connected_since", err.Error()) + utils.SendAlert("events.go - Setting connected_since", err.Error()) } } @@ -114,16 +114,16 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r return } if res.Err() != nil { - utils.SendAlert("exp/events.go - Getting connected_since", res.Err().Error()) + utils.SendAlert("events.go - Getting connected_since", res.Err().Error()) err := client.Set(context.Background(), key, strconv.Itoa(NotConnected), 0).Err() if err != nil { - utils.SendAlert("exp/events.go - Set connected_since to not connected after get err", err.Error()) + utils.SendAlert("events.go - Set connected_since to not connected after get err", err.Error()) } return } con, err := res.Int64() if err != nil { - utils.SendAlert("exp/events.go - Converting result to int64", err.Error()) + utils.SendAlert("events.go - Converting result to int64", err.Error()) return } // check validity of user (2) @@ -136,12 +136,12 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r utils.SendDebug("User disconnected", "username", e.Member.DisplayName(), "since", con) err = client.Set(context.Background(), key, strconv.Itoa(NotConnected), 0).Err() if err != nil { - utils.SendAlert("exp/events.go - Set connected_since to not connected", err.Error()) + utils.SendAlert("events.go - Set connected_since to not connected", err.Error()) } // add exp timeInVocal := now - con if timeInVocal < 0 { - utils.SendAlert("exp/events.go - Calculating time spent in vocal", "the time is negative") + utils.SendAlert("events.go - Calculating time spent in vocal", "the time is negative") return } if timeInVocal > MaxTimeInVocal { @@ -149,13 +149,13 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r timeInVocal = MaxTimeInVocal } e.Member.GuildID = e.GuildID - c.AddXP(s, e.Member, xp2.VocalXP(uint(timeInVocal)), func(_ uint, newLevel uint) { + c.AddXP(s, e.Member, exp.VocalXP(uint(timeInVocal)), func(_ uint, newLevel uint) { cfg := config.GetGuildConfig(e.GuildID) _, err = s.ChannelMessageSend(cfg.FallbackChannel, fmt.Sprintf( "%s est maintenant niveau %d", e.Member.Mention(), newLevel, )) if err != nil { - utils.SendAlert("exp/events.go - Sending new level in fallback channel", err.Error()) + utils.SendAlert("events.go - Sending new level in fallback channel", err.Error()) } }) } @@ -165,7 +165,7 @@ func OnLeave(_ *discordgo.Session, e *discordgo.GuildMemberRemove) { c := user.GetCopaing(e.User.ID, e.GuildID) if err := gokord.DB.Where("guild_id = ?", e.GuildID).Delete(c).Error; err != nil { utils.SendAlert( - "exp/events.go - deleting user from db", err.Error(), + "events.go - deleting user from db", err.Error(), "user_id", e.User.ID, "guild_id", e.GuildID, ) 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, ) From 6b6c775ab3af659e26f0c79dbabd86d88d2451d5 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:15:59 +0200 Subject: [PATCH 05/18] refactor(gorm): remove ...At fields from model --- PRIVACY.md | 4 +-- config/guild.go | 5 ++-- events.go | 6 ++-- user/member.go | 80 +++++++++++-------------------------------------- user/xp.go | 33 +++----------------- 5 files changed, 29 insertions(+), 99 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 87f3f69..eb60bac 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -32,9 +32,9 @@ En tant qu'utilisateur, vous avez le droit de : - avoir accès à vos données sauvegardées - modifier ces données -Pour exercer vos droits concernant l'accès aux données ou à la modification des données, contacter me@anhgelus.world. +Pour exercer vos droits concernant l'accès aux données ou à la modification des données, contactez me@anhgelus.world. -Concernant la suppression des données, entraîne de facto une remise à zéro de votre profile sur un serveur, contacter le propriétaire du serveur. +La suppression des données entraîne de facto une remise à zéro de votre profile sur un serveur, contactez le propriétaire du serveur pour exercer ce droit. Concernant la suppression intégrale des données, contacter me@anhgelus.world. diff --git a/config/guild.go b/config/guild.go index 0971837..5e41465 100644 --- a/config/guild.go +++ b/config/guild.go @@ -3,12 +3,11 @@ package config import ( "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" - "gorm.io/gorm" "strings" ) type GuildConfig struct { - gorm.Model + ID uint `gorm:"primarykey"` GuildID string `gorm:"not null;unique"` XpRoles []XpRole DisabledChannels string @@ -16,7 +15,7 @@ type GuildConfig struct { } type XpRole struct { - gorm.Model + ID uint `gorm:"primarykey"` XP uint RoleID string GuildConfigID uint diff --git a/events.go b/events.go index a00fe6c..4e23b69 100644 --- a/events.go +++ b/events.go @@ -4,7 +4,6 @@ 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" @@ -162,8 +161,11 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate, client *r func OnLeave(_ *discordgo.Session, e *discordgo.GuildMemberRemove) { utils.SendDebug("Leave event", "user_id", e.User.ID) + if e.User.Bot { + return + } c := user.GetCopaing(e.User.ID, e.GuildID) - if err := gokord.DB.Where("guild_id = ?", e.GuildID).Delete(c).Error; err != nil { + if err := c.Delete(); err != nil { utils.SendAlert( "events.go - deleting user from db", err.Error(), "user_id", e.User.ID, 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 -} From 5af6bd672c342fc4e7c4cc7bf61efd5026433ead Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:19:59 +0200 Subject: [PATCH 06/18] style(event): move calcdiversity in exp --- events.go | 13 +------------ exp/functions.go | 11 +++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/events.go b/events.go index 4e23b69..6974606 100644 --- a/events.go +++ b/events.go @@ -10,7 +10,6 @@ import ( "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" "github.com/redis/go-redis/v9" - "slices" "strconv" "strings" "time" @@ -37,7 +36,7 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { trimmed := utils.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author m.Member.GuildID = m.GuildID - xp := exp.MessageXP(uint(len(trimmed)), calcDiversity(trimmed)) + xp := exp.MessageXP(uint(len(trimmed)), exp.CalcDiversity(trimmed)) if xp > MaxXpPerMessage { xp = MaxXpPerMessage } @@ -52,16 +51,6 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { }) } -func calcDiversity(msg string) uint { - var chars []rune - for _, c := range []rune(msg) { - if !slices.Contains(chars, c) { - chars = append(chars, c) - } - } - return uint(len(chars)) -} - func OnVoiceUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { if e.Member.User.Bot { return diff --git a/exp/functions.go b/exp/functions.go index da535c4..c69ef26 100644 --- a/exp/functions.go +++ b/exp/functions.go @@ -3,6 +3,7 @@ package exp import ( "github.com/anhgelus/gokord" "math" + "slices" ) func MessageXP(length uint, diversity uint) uint { @@ -11,6 +12,16 @@ func MessageXP(length uint, diversity uint) uint { )) } +func CalcDiversity(msg string) uint { + var chars []rune + for _, c := range []rune(msg) { + if !slices.Contains(chars, c) { + chars = append(chars, c) + } + } + return uint(len(chars)) +} + func VocalXP(time uint) uint { return uint(math.Floor( 0.01*math.Pow(float64(time), 1.3) + 1, From e0a8f6634424f10a22b0a0740e0bbc17534eaa0e Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 13:26:32 +0200 Subject: [PATCH 07/18] refactor(xp): remove reducer --- events.go | 2 - exp/functions.go | 12 ----- user/level.go | 87 +------------------------------- user/xp.go | 128 ----------------------------------------------- 4 files changed, 1 insertion(+), 228 deletions(-) diff --git a/events.go b/events.go index 6974606..54c444e 100644 --- a/events.go +++ b/events.go @@ -31,7 +31,6 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { return } c := user.GetCopaing(m.Author.ID, m.GuildID) - user.LastEventUpdate(s, c) // add exp trimmed := utils.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author @@ -55,7 +54,6 @@ func OnVoiceUpdate(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { if e.Member.User.Bot { return } - user.LastEventUpdate(s, user.GetCopaing(e.UserID, e.GuildID)) cfg := config.GetGuildConfig(e.GuildID) client, err := config.GetRedisClient() if err != nil { diff --git a/exp/functions.go b/exp/functions.go index c69ef26..2cbe7bf 100644 --- a/exp/functions.go +++ b/exp/functions.go @@ -1,7 +1,6 @@ package exp import ( - "github.com/anhgelus/gokord" "math" "slices" ) @@ -43,14 +42,3 @@ func LevelXP(level uint) uint { math.Pow(float64(5*level), 2), )) } - -func Lose(time uint, xp uint) uint { - if gokord.Debug { - return uint(math.Floor( - math.Pow(float64(time), 3) * math.Pow(10, -2+math.Log(float64(time))) * math.Floor(float64(xp/500)+1), - )) // a little bit faster to lose exp - } - return uint(math.Floor( - math.Pow(float64(time), 2) * math.Pow(10, -2+math.Log(float64(time/85))) * math.Floor(float64(xp/500)+1), - )) -} 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) -} From 799df74fcda5266fd295b49fc759c605c815cad9 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 14:11:11 +0200 Subject: [PATCH 08/18] feat(xp): new add --- commands/config.go | 14 +++++++------- config/guild.go | 1 + user/member.go | 34 +++++++++++++++++++++++++++++++--- user/xp.go | 13 ++++++------- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/commands/config.go b/commands/config.go index 32da9c6..4615c36 100644 --- a/commands/config.go +++ b/commands/config.go @@ -98,7 +98,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { } return } - exp := exp.LevelXP(uint(level)) + xp := exp.LevelXP(uint(level)) r, ok := optMap["role"] if !ok { err := resp.Message("Le rôle n'a pas été renseigné.").Send() @@ -116,7 +116,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { case "add": for _, r := range cfg.XpRoles { if r.RoleID == role.ID { - err := resp.Message("Le rôle est déjà présent dans la config").Send() + err = resp.Message("Le rôle est déjà présent dans la config").Send() if err != nil { utils.SendAlert("commands/config.go - Role already in config", err.Error()) } @@ -124,7 +124,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { } } cfg.XpRoles = append(cfg.XpRoles, config.XpRole{ - XP: exp, + XP: xp, RoleID: role.ID, }) err = cfg.Save() @@ -143,7 +143,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { case "del": _, r := cfg.FindXpRole(role.ID) if r == nil { - err := resp.Message("Le rôle n'a pas été trouvé dans la config.").Send() + err = resp.Message("Le rôle n'a pas été trouvé dans la config.").Send() if err != nil { utils.SendAlert("commands/config.go - Role not found (del)", err.Error()) } @@ -165,13 +165,13 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { case "edit": _, r := cfg.FindXpRole(role.ID) if r == nil { - err := resp.Message("Le rôle n'a pas été trouvé dans la config.").Send() + err = resp.Message("Le rôle n'a pas été trouvé dans la config.").Send() if err != nil { utils.SendAlert("commands/config.go - Role not found (edit)", err.Error()) } return } - r.XP = exp + r.XP = xp err = gokord.DB.Save(r).Error if err != nil { utils.SendAlert( @@ -186,7 +186,7 @@ func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate) { ) } default: - err := resp.Message("Le type d'action n'est pas valide.").Send() + err = resp.Message("Le type d'action n'est pas valide.").Send() if err != nil { utils.SendAlert("commands/config.go - Invalid action type", err.Error()) } diff --git a/config/guild.go b/config/guild.go index 5e41465..fb7eaef 100644 --- a/config/guild.go +++ b/config/guild.go @@ -12,6 +12,7 @@ type GuildConfig struct { XpRoles []XpRole DisabledChannels string FallbackChannel string + DaysXPRemains uint `gorm:"default:90"` // 30 * 3 = 90 (three months) } type XpRole struct { 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) } } From 01bafe9bf1de5be4e770b9500480807d4973d8d6 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 16:15:47 +0200 Subject: [PATCH 09/18] feat(top): implements new kind of tops --- Dockerfile | 2 +- commands/rank.go | 32 ++++++++++----- commands/reset.go | 11 +++++- commands/top.go | 57 ++++++++++++++++++--------- main.go | 6 +-- user/member.go | 30 +++----------- user/xp.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 179 insertions(+), 58 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca8d3f8..0d8aee4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.23-alpine +FROM docker.io/golang:1.24-alpine WORKDIR /app diff --git a/commands/rank.go b/commands/rank.go index c659f2b..785b8c8 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -11,7 +11,6 @@ import ( func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { optMap := utils.GenerateOptionMap(i) c := user.GetCopaing(i.Member.User.ID, i.GuildID) // current user = member who used /rank - user.LastEventUpdate(s, c) // update exp and reset last event msg := "Votre niveau" m := i.Member var err error @@ -21,13 +20,13 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { if u.Bot { err = resp.Message("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() if err != nil { - utils.SendAlert("rank.go - Reply error user is a bot", err.Error()) + utils.SendAlert("commands/rank.go - Reply error user is a bot", err.Error()) } } m, err = s.GuildMember(i.GuildID, u.ID) if err != nil { utils.SendAlert( - "rank.go - Fetching guild member", + "commands/rank.go - Fetching guild member", err.Error(), "discord_id", u.ID, @@ -36,24 +35,39 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { ) err = resp.Message("Erreur : impossible de récupérer le membre").IsEphemeral().Send() if err != nil { - utils.SendAlert("rank.go - Reply error fetching guild member", err.Error()) + utils.SendAlert("commands/rank.go - Reply error fetching guild member", err.Error()) } return } c = user.GetCopaing(u.ID, i.GuildID) // current user = member targeted by member who wrote /rank - user.UpdateXP(s, c) // update exp without resetting event msg = fmt.Sprintf("Le niveau de %s", m.DisplayName()) } - lvl := exp.Level(c.XP) + xp, err := c.GetXP() + if err != nil { + utils.SendAlert( + "commands/rank.go - Fetching XP", + err.Error(), + "discord_id", + c.ID, + "guild_id", + i.GuildID, + ) + err = resp.Message("Erreur : impossible de récupérer l'XP").IsEphemeral().Send() + if err != nil { + utils.SendAlert("commands/rank.go - Reply error fetching xp", err.Error()) + } + return + } + lvl := exp.Level(xp) nxtLvlXP := exp.LevelXP(lvl + 1) err = resp.Message(fmt.Sprintf( "%s : **%d**\n> XP : %d\n> Prochain niveau dans %d XP", msg, lvl, - c.XP, - nxtLvlXP-c.XP, + xp, + nxtLvlXP-xp, )).Send() if err != nil { - utils.SendAlert("rank.go - Sending rank", err.Error()) + utils.SendAlert("commands/rank.go - Sending rank", err.Error()) } } diff --git a/commands/reset.go b/commands/reset.go index 1e8df6a..3f8af03 100644 --- a/commands/reset.go +++ b/commands/reset.go @@ -34,8 +34,15 @@ func ResetUser(s *discordgo.Session, i *discordgo.InteractionCreate) { } return } - user.GetCopaing(m.ID, i.GuildID).Reset() - if err := resp.Message("Le user bien été reset.").Send(); err != nil { + err := user.GetCopaing(m.ID, i.GuildID).Delete() + if err != nil { + utils.SendAlert("commands/reset.go - Copaing not deleted", err.Error(), "discord_id", m.ID, "guild_id", i.GuildID) + err = resp.Message("Erreur : impossible de reset l'utilisateur").Send() + if err != nil { + utils.SendAlert("commands/reset.go - Error deleting", err.Error()) + } + } + if err = resp.Message("Le user bien été reset.").Send(); err != nil { utils.SendAlert("commands/reset.go - Sending success (user)", err.Error()) } } diff --git a/commands/top.go b/commands/top.go index 22574ce..a43e055 100644 --- a/commands/top.go +++ b/commands/top.go @@ -2,15 +2,14 @@ package commands import ( "fmt" - "github.com/anhgelus/gokord" "github.com/anhgelus/gokord/utils" "github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/user" "github.com/bwmarrin/discordgo" + "sync" ) func Top(s *discordgo.Session, i *discordgo.InteractionCreate) { - user.LastEventUpdate(s, user.GetCopaing(i.Member.User.ID, i.GuildID)) resp := utils.ResponseBuilder{C: s, I: i} err := resp.IsDeferred().Send() if err != nil { @@ -18,26 +17,48 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate) { return } resp.NotDeferred().IsEdit() - go func() { - var tops []user.Copaing - gokord.DB.Where("guild_id = ?", i.GuildID).Limit(10).Order("exp desc").Find(&tops) - msg := "" - for i, c := range tops { - if i == 9 { - msg += fmt.Sprintf("%d. **<@%s>** - niveau %d", i+1, c.DiscordID, exp.Level(c.XP)) - } else { - msg += fmt.Sprintf("%d. **<@%s>** - niveau %d\n", i+1, c.DiscordID, exp.Level(c.XP)) + embeds := make([]*discordgo.MessageEmbed, 3) + wg := sync.WaitGroup{} + + fn := func(s string, n uint, d int, id int) { + defer wg.Done() + tops, err := user.GetBestXP(i.GuildID, n, d) + if err != nil { + utils.SendAlert("commands/top.go - Fetching best xp", err.Error(), "n", n, "d", d, "id", id, "guild_id", i.GuildID) + embeds[id] = &discordgo.MessageEmbed{ + Title: s, + Description: "Erreur : impossible de récupérer la liste", + Color: utils.Error, } + return } - err = resp.Embeds([]*discordgo.MessageEmbed{ - { - Title: "Top", - Description: msg, - Color: utils.Success, - }, - }).Send() + embeds[id] = &discordgo.MessageEmbed{ + Title: s, + Description: genTopsMessage(tops), + Color: utils.Success, + } + } + + wg.Add(3) + go fn("Top full time", 10, -1, 0) + go fn("Top 30 jours", 5, 30, 1) + go fn("Top 7 jours", 5, 7, 2) + go func() { + wg.Wait() + err = resp.Embeds(embeds).Send() if err != nil { utils.SendAlert("commands/top.go - Sending response top", err.Error()) } }() } + +func genTopsMessage(tops []user.CopaingAccess) string { + msg := "" + for i, c := range tops { + msg += fmt.Sprintf("%d. **<@%s>** - niveau %d", i+1, c.ToCopaing().DiscordID, exp.Level(c.GetXP())) + if i != len(tops)-1 { + msg += "\n" + } + } + return msg +} diff --git a/main.go b/main.go index 7b8e75f..b1f137d 100644 --- a/main.go +++ b/main.go @@ -17,10 +17,10 @@ var ( //go:embed updates.json updatesData []byte Version = gokord.Version{ - Major: 2, - Minor: 4, + Major: 3, + Minor: 0, Patch: 0, - } // git version: 0.4.0 (it's the v2 of the bot) + } stopPeriodicReducer chan<- interface{} ) 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 +} From 61c7bf4567249da0d13c2f738e56754a2c181c99 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 16:52:04 +0200 Subject: [PATCH 10/18] fix(db): wrong relation and bad where condition --- Dockerfile | 1 + README.md | 4 ++-- commands/rank.go | 2 +- main.go | 2 +- user/member.go | 16 ++++++++-------- user/xp.go | 20 +++++++++++++------- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0d8aee4..7ebb090 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,5 +9,6 @@ COPY . . RUN go mod tidy && go build -o app . ENV TOKEN="" +ENV TZ="Europe/Paris" CMD ./app -token $TOKEN diff --git a/README.md b/README.md index fe7bc74..7fa11a5 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ Bot for the private server Discord "Les Copaings" ## Features -- Levels & XP +- Levels & CopaingXPs - Roles management - Purge command -### XP +### CopaingXPs Functions: - $xp-message(x;y) = 0.025 x^{1.25}\sqrt{y}+1$ where $x$ is the length of the message and $y$ is the diversity of the diff --git a/commands/rank.go b/commands/rank.go index 785b8c8..d079a84 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -45,7 +45,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { xp, err := c.GetXP() if err != nil { utils.SendAlert( - "commands/rank.go - Fetching XP", + "commands/rank.go - Fetching xp", err.Error(), "discord_id", c.ID, diff --git a/main.go b/main.go index b1f137d..a1fc1af 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { panic(err) } - err = gokord.DB.AutoMigrate(&user.Copaing{}, &config.GuildConfig{}, &config.XpRole{}) + err = gokord.DB.AutoMigrate(&user.Copaing{}, &config.GuildConfig{}, &config.XpRole{}, &user.CopaingXP{}) if err != nil { panic(err) } 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) From 067e149e72da10790e347b82045d82a96397d6cf Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 17:18:33 +0200 Subject: [PATCH 11/18] fix(db): using discord_id instead of db's id --- docker-compose.yml | 9 ++++++++- user/xp.go | 11 ++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e6b9b89..2575340 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ services: bot: build: . + restart: always env_file: - .env volumes: @@ -15,4 +16,10 @@ services: env_file: - .env volumes: - - ./data:/var/lib/postgresql/data \ No newline at end of file + - ./data:/var/lib/postgresql/data + adminer: + image: docker.io/adminer + ports: + - "8080:8080" + depends_on: + - postgres 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 From eaf9fa51cdd9509c5d075633b712ec9b5ea712c7 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 17:35:03 +0200 Subject: [PATCH 12/18] perf(db): remove useless XP data --- exp/functions.go | 15 ++++++++++ user/level.go | 73 +++++++++--------------------------------------- user/xp.go | 16 ++++------- 3 files changed, 34 insertions(+), 70 deletions(-) diff --git a/exp/functions.go b/exp/functions.go index 2cbe7bf..720a755 100644 --- a/exp/functions.go +++ b/exp/functions.go @@ -1,8 +1,11 @@ package exp import ( + "fmt" + "github.com/anhgelus/gokord" "math" "slices" + "time" ) func MessageXP(length uint, diversity uint) uint { @@ -42,3 +45,15 @@ func LevelXP(level uint) uint { math.Pow(float64(5*level), 2), )) } + +// TimeStampNDaysBefore returns the timestamp (year-month-day) n days before today +func TimeStampNDaysBefore(n uint) string { + 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() + } + return fmt.Sprintf("%d-%d-%d", y, m, d) +} 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 + utils.SendAlert("user/level.go - Removing old XP", err.Error(), "guild_id", g.ID) } - if u.Bot { - continue - } - 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 { From f2fbf1db929e4bd2b3af014fb8d9af4c0f934a1e Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 18:06:17 +0200 Subject: [PATCH 13/18] feat(xp): update role after removing all old xp --- user/level.go | 41 +++++++++++++++++++++++++++++++++++++++++ user/xp.go | 8 ++++---- 2 files changed, 45 insertions(+), 4 deletions(-) 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 } From a02a0d8e3e45d738c5a1a07dbc925c329ddf6278 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 18:24:27 +0200 Subject: [PATCH 14/18] fix(command): using wrong key for /rank due to previous automatic refactor --- commands/rank.go | 2 +- main.go | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/commands/rank.go b/commands/rank.go index d079a84..70c0222 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -15,7 +15,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate) { m := i.Member var err error resp := utils.ResponseBuilder{C: s, I: i} - if v, ok := optMap["user"]; ok { + if v, ok := optMap["copaing"]; ok { u := v.UserValue(s) if u.Bot { err = resp.Message("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() diff --git a/main.go b/main.go index a1fc1af..6b714f1 100644 --- a/main.go +++ b/main.go @@ -43,11 +43,11 @@ func main() { adm := gokord.AdminPermission - rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un user"). + rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un copaing"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionUser, - "user", + "copaing", "Le niveau du Copaing que vous souhaitez obtenir", )). SetHandler(commands.Rank) @@ -185,13 +185,7 @@ func afterInit(dg *discordgo.Session) { dg.AddHandler(OnVoiceUpdate) dg.AddHandler(OnLeave) - // setup timer for periodic reducer - d := 24 * time.Hour - if gokord.Debug { - // reduce time for debug - d = time.Minute - } - stopPeriodicReducer = utils.NewTimer(d, func(stop chan<- interface{}) { + stopPeriodicReducer = utils.NewTimer(24*time.Hour, func(stop chan<- interface{}) { user.PeriodicReducer(dg) }) } From 8b174209e193746216e711423cb26cebd0786581 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 20:50:32 +0200 Subject: [PATCH 15/18] fix(command): automatic refactor changing names at wrong place --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 6b714f1..2035cb5 100644 --- a/main.go +++ b/main.go @@ -58,7 +58,7 @@ func main() { gokord.NewCommand("show", "Affiche la config").SetHandler(commands.ConfigShow), ). AddSub( - gokord.NewCommand("exp", "Modifie l'exp"). + gokord.NewCommand("xp", "Modifie l'xp"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionString, @@ -114,12 +114,12 @@ func main() { HasOption(). SetHandler(commands.Top) - resetCmd := gokord.NewCommand("reset", "Reset l'exp"). + resetCmd := gokord.NewCommand("reset", "Reset l'xp"). HasOption(). SetHandler(commands.Reset). SetPermission(&adm) - resetUserCmd := gokord.NewCommand("reset-user", "Reset l'exp d'un utilisation"). + resetUserCmd := gokord.NewCommand("reset-user", "Reset l'xp d'un utilisation"). HasOption(). AddOption(gokord.NewOption( discordgo.ApplicationCommandOptionUser, From f56e10514e0fe74599fcdaea553f59cfe0cf5f70 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 21:07:04 +0200 Subject: [PATCH 16/18] feat(command): /config for days before reduction --- commands/config.go | 55 ++++++++++++++++++++++++++++++++++++++++++---- main.go | 10 +++++++++ updates.json | 10 +++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/commands/config.go b/commands/config.go index 4615c36..d31b929 100644 --- a/commands/config.go +++ b/commands/config.go @@ -40,10 +40,9 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate) { } err := resp.Embeds([]*discordgo.MessageEmbed{ { - Type: discordgo.EmbedTypeRich, - Title: "Config", - Description: "Configuration", - Color: utils.Success, + Type: discordgo.EmbedTypeRich, + Title: "Config", + Color: utils.Success, Fields: []*discordgo.MessageEmbedField{ { Name: "Salons par défaut", @@ -60,6 +59,11 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate) { Value: chans, Inline: false, }, + { + Name: "Jours avant la réduction", + Value: fmt.Sprintf("%d", cfg.DaysXPRemains), + Inline: false, + }, }, }, }).Send() @@ -316,3 +320,46 @@ func ConfigFallbackChannel(s *discordgo.Session, i *discordgo.InteractionCreate) utils.SendAlert("commands/config.go - Channel saved message", err.Error()) } } + +func ConfigPeriodBeforeReduce(s *discordgo.Session, i *discordgo.InteractionCreate) { + optMap := utils.GenerateOptionMapForSubcommand(i) + resp := utils.ResponseBuilder{C: s, I: i} + resp.IsEphemeral() + // verify every args + days, ok := optMap["days"] + if !ok { + err := resp.Message("Le nombre de jours n'a pas été renseigné.").Send() + if err != nil { + utils.SendAlert("commands/config.go - Days not set (fallback)", err.Error()) + } + return + } + d := days.IntValue() + if d < 30 { + err := resp.Message("Le nombre de jours est inférieur à 30.").Send() + if err != nil { + utils.SendAlert("commands/config.go - Days < 30 (fallback)", err.Error()) + } + return + } + // save + cfg := config.GetGuildConfig(i.GuildID) + cfg.DaysXPRemains = uint(d) + err := cfg.Save() + if err != nil { + utils.SendAlert( + "commands/config.go - Saving config", + err.Error(), + "guild_id", + i.GuildID, + "days", + d, + ) + err = resp.Message("Il y a eu une erreur lors de la modification de de la base de données.").Send() + } else { + err = resp.Message("Nombre de jours enregistré.").Send() + } + if err != nil { + utils.SendAlert("commands/config.go - Days saved message", err.Error()) + } +} diff --git a/main.go b/main.go index 2035cb5..4b4062e 100644 --- a/main.go +++ b/main.go @@ -99,6 +99,16 @@ func main() { ).IsRequired()). SetHandler(commands.ConfigChannel), ). + AddSub( + gokord.NewCommand("period-before-reduce", "Temps avant la perte d'xp (affecte aussi le /top)"). + HasOption(). + AddOption(gokord.NewOption( + discordgo.ApplicationCommandOptionInteger, + "days", + "Nombre de jours avant la perte d'xp (doit être égal ou plus grand que 30)", + ).IsRequired()). + SetHandler(commands.ConfigPeriodBeforeReduce), + ). AddSub( gokord.NewCommand("fallback-channel", "Modifie le salon textuel par défaut"). HasOption(). diff --git a/updates.json b/updates.json index c378f8b..fd73d8f 100644 --- a/updates.json +++ b/updates.json @@ -8,5 +8,15 @@ "config" ] } + }, + { + "version": "3.0.0", + "commands": { + "added": [], + "removed": [], + "updated": [ + "config" + ] + } } ] From 347d5b826703a20cb046ccfc86f82fecc2a88efb Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 21:10:01 +0200 Subject: [PATCH 17/18] feat(command): update /credits --- commands/credits.go | 26 +++----------------------- go.mod | 1 - 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/commands/credits.go b/commands/credits.go index fb26530..d5aa42e 100644 --- a/commands/credits.go +++ b/commands/credits.go @@ -11,12 +11,12 @@ func Credits(s *discordgo.Session, i *discordgo.InteractionCreate) { { Type: discordgo.EmbedTypeRich, Title: "Crédits", - Description: "Auteur du bot : @anhgelus (https://github.com/anhgelus)\nLangage : Go 1.22\nLicence : AGPLv3", + Description: "Auteur du bot : @anhgelus (https://github.com/anhgelus)\nLangage : Go 1.24\nLicence : AGPLv3", Color: utils.Success, Fields: []*discordgo.MessageEmbedField{ { Name: "anhgelus/gokord", - Value: "v0.3.0 - MPL 2.0", + Value: "v0.6.3 - MPL 2.0", Inline: true, }, { @@ -24,29 +24,9 @@ func Credits(s *discordgo.Session, i *discordgo.InteractionCreate) { Value: "v0.28.1 - BSD-3-Clause", Inline: true, }, - { - Name: "pelletier/go-toml/v2", - Value: "v2.2.1 - MIT", - Inline: true, - }, { Name: "redis/go-redis/v9", - Value: "v9.5.1 - BSD-2-Clause", - Inline: true, - }, - { - Name: "gorm.io/gorm", - Value: "v1.25.9 - MIT", - Inline: true, - }, - { - Name: "gorm.io/driver/postgres", - Value: "v1.5.7 - MIT", - Inline: true, - }, - { - Name: "other", - Value: "Et leurs dépendances !", + Value: "v9.8.0 - BSD-2-Clause", Inline: true, }, }, diff --git a/go.mod b/go.mod index f11cdd8..c80b3b4 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/anhgelus/gokord v0.6.3 github.com/bwmarrin/discordgo v0.28.1 github.com/redis/go-redis/v9 v9.8.0 - gorm.io/gorm v1.26.1 ) require ( From 75ca960199b85f3f4b491652c837d297827e40ce Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Tue, 13 May 2025 21:12:19 +0200 Subject: [PATCH 18/18] fix(command): bad display for no default channel --- commands/config.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/commands/config.go b/commands/config.go index d31b929..a3857c6 100644 --- a/commands/config.go +++ b/commands/config.go @@ -38,6 +38,12 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate) { if len(chans) == 0 { chans = "Aucun salon désactivé :)" } + var defaultChan string + if len(cfg.FallbackChannel) == 0 { + defaultChan = "Pas de valeur" + } else { + defaultChan = fmt.Sprintf("<#%s>", cfg.FallbackChannel) + } err := resp.Embeds([]*discordgo.MessageEmbed{ { Type: discordgo.EmbedTypeRich, @@ -46,7 +52,7 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate) { Fields: []*discordgo.MessageEmbedField{ { Name: "Salons par défaut", - Value: fmt.Sprintf("<#%s>", cfg.FallbackChannel), + Value: defaultChan, Inline: false, }, {