diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | commands/config.go | 397 | ||||
| -rw-r--r-- | commands/credits.go | 39 | ||||
| -rw-r--r-- | commands/rank.go | 24 | ||||
| -rw-r--r-- | commands/reset.go | 23 | ||||
| -rw-r--r-- | commands/stats.go | 224 | ||||
| -rw-r--r-- | commands/top.go | 24 | ||||
| -rw-r--r-- | config.go | 1 | ||||
| -rw-r--r-- | config/channel.go | 127 | ||||
| -rw-r--r-- | config/guild.go | 7 | ||||
| -rw-r--r-- | config/xp_reduce.go | 60 | ||||
| -rw-r--r-- | config/xp_role.go | 209 | ||||
| -rw-r--r-- | docker-compose.yml | 22 | ||||
| -rw-r--r-- | events.go | 31 | ||||
| -rw-r--r-- | exp/functions.go | 27 | ||||
| -rw-r--r-- | go.mod | 28 | ||||
| -rw-r--r-- | go.sum | 114 | ||||
| -rw-r--r-- | justfile | 18 | ||||
| -rw-r--r-- | main.go | 189 | ||||
| -rw-r--r-- | restart.sh | 3 | ||||
| -rw-r--r-- | updates.json | 12 | ||||
| -rw-r--r-- | user/level.go | 41 | ||||
| -rw-r--r-- | user/member.go | 14 | ||||
| -rw-r--r-- | user/xp.go | 29 |
25 files changed, 1043 insertions, 632 deletions
@@ -26,3 +26,6 @@ go.work tmp config/**.toml data +docker-compose.yml + +les-copaings-bot @@ -25,14 +25,11 @@ There are two ways to install the bot: docker and build. 1. Clone the repository ```bash -$ git clone https://github.com/anhgelus/les-copaings-bot.git +$ git clone https://git.anhgelus.world/anhgelus/les-copaings-bot.git ``` 2. Go into the repository, rename `.env.example` into `.env` and customize it: add your token, change the user and the password of the database -3. Start the compose file -```bash -$ docker compose up -d --build -``` +3. Build the image and start it Now you have to edit `config/config.toml`. You can understand how this config file works below. @@ -45,7 +42,7 @@ You can stop the compose file with `docker compose down` 1. Clone the repository ```bash -$ git clone https://github.com/anhgelus/les-copaings-bot.git +$ git clone https://git.anhgelus.world/anhgelus/les-copaings-bot.git ``` 2. Install Go 1.24+ 3. Go into the repository and build the program diff --git a/commands/config.go b/commands/config.go index 48d7ab8..c4644bc 100644 --- a/commands/config.go +++ b/commands/config.go @@ -2,15 +2,21 @@ package commands 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" "strings" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/component" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" +) + +const ( + ConfigModify = "config_modify" ) -func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { +func Config(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) { cfg := config.GetGuildConfig(i.GuildID) roles := "" l := len(cfg.XpRoles) - 1 @@ -43,318 +49,73 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate, optMap uti } else { defaultChan = fmt.Sprintf("<#%s>", cfg.FallbackChannel) } - err := resp.AddEmbed(&discordgo.MessageEmbed{ - Type: discordgo.EmbedTypeRich, - Title: "Config", - Color: utils.Success, - Fields: []*discordgo.MessageEmbedField{ - { - Name: "Salon par défaut", - Value: defaultChan, - Inline: false, - }, - { - Name: "Rôles liés aux niveaux", - Value: roles, - Inline: false, - }, - { - Name: "Salons désactivés", - Value: chans, - Inline: false, - }, - { - Name: "Jours avant la réduction", - Value: fmt.Sprintf("%d", cfg.DaysXPRemains), - Inline: false, - }, - }, - }).Send() - if err != nil { - utils.SendAlert("config/guild.go - Sending config", err.Error()) - } -} - -func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { - resp.IsEphemeral() - // verify every args - t, ok := optMap["type"] - if !ok { - err := resp.SetMessage("Le type d'action n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Action type not set", err.Error()) - } - return - } - ts := t.StringValue() - lvl, ok := optMap["level"] - if !ok { - err := resp.SetMessage("Le niveau n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Level not set", err.Error()) - } - return - } - level := lvl.IntValue() - if level < 1 { - err := resp.SetMessage("Le niveau doit forcément être supérieur à 0.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Invalid level", err.Error()) - } - return - } - xp := exp.LevelXP(uint(level)) - r, ok := optMap["role"] - if !ok { - err := resp.SetMessage("Le rôle n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Role not set", err.Error()) - } - return - } - role := r.RoleValue(s, i.GuildID) - cfg := config.GetGuildConfig(i.GuildID) - - // add or delete or edit - var err error - switch ts { - case "add": - for _, r := range cfg.XpRoles { - if r.RoleID == role.ID { - err = resp.SetMessage("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()) - } - return - } - } - cfg.XpRoles = append(cfg.XpRoles, config.XpRole{ - XP: xp, - RoleID: role.ID, - }) - err = cfg.Save() - if err != nil { - utils.SendAlert( - "commands/config.go - Saving config", - err.Error(), - "guild_id", - i.GuildID, - "role_id", - role.ID, - "type", - "add", - ) - } - case "del": - _, r := cfg.FindXpRole(role.ID) - if r == nil { - err = resp.SetMessage("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()) - } - return - } - err = gokord.DB.Delete(r).Error - if err != nil { - utils.SendAlert( - "commands/config.go - Deleting entry", - err.Error(), - "guild_id", - i.GuildID, - "role_id", - role.ID, - "type", - "del", - ) - } - case "edit": - _, r := cfg.FindXpRole(role.ID) - if r == nil { - err = resp.SetMessage("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 = xp - err = gokord.DB.Save(r).Error - if err != nil { - utils.SendAlert( - "commands/config.go - Saving config", - err.Error(), - "guild_id", - i.GuildID, - "role_id", - role.ID, - "type", - "edit", - ) - } - default: - err = resp.SetMessage("Le type d'action n'est pas valide.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Invalid action type", err.Error()) - } - return - } - if err != nil { - err = resp.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send() - } else { - err = resp.SetMessage("La configuration a bien été mise à jour.").Send() - } - if err != nil { - utils.SendAlert("commands/config.go - Config updated message", err.Error()) - } -} - -func ConfigChannel(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { - resp.IsEphemeral() - // verify every args - t, ok := optMap["type"] - if !ok { - err := resp.SetMessage("Le type d'action n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Action type not set", err.Error()) - } - return - } - ts := t.StringValue() - salon, ok := optMap["channel"] - if !ok { - err := resp.SetMessage("Le salon n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Channel not set (disabled)", err.Error()) - } - return - } - channel := salon.ChannelValue(s) - cfg := config.GetGuildConfig(i.GuildID) - switch ts { - case "add": - if strings.Contains(cfg.DisabledChannels, channel.ID) { - err := resp.SetMessage("Le salon est déjà dans la liste des salons désactivés").Send() - if err != nil { - utils.SendAlert("commands/config.go - Channel already disabled", err.Error()) - } - return - } - cfg.DisabledChannels += channel.ID + ";" - case "del": - if !strings.Contains(cfg.DisabledChannels, channel.ID) { - err := resp.SetMessage("Le salon n'est pas désactivé").Send() - if err != nil { - utils.SendAlert("commands/config.go - Channel not disabled", err.Error()) - } - return - } - cfg.DisabledChannels = strings.ReplaceAll(cfg.DisabledChannels, channel.ID+";", "") - default: - err := resp.SetMessage("Le type d'action n'est pas valide.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Invalid action type", err.Error()) - } - return - } - // save - err := cfg.Save() - if err != nil { - utils.SendAlert( - "commands/config.go - Saving config", - err.Error(), - "guild_id", - i.GuildID, - "type", - ts, - "channel_id", - channel.ID, - ) - err = resp.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send() - } else { - err = resp.SetMessage("Modification sauvegardé.").Send() - } - if err != nil { - utils.SendAlert("commands/config.go - Modification saved message", err.Error()) - } -} - -func ConfigFallbackChannel(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { - resp.IsEphemeral() - // verify every args - salon, ok := optMap["channel"] - if !ok { - err := resp.SetMessage("Le salon n'a pas été renseigné.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Channel not set (fallback)", err.Error()) - } - return - } - channel := salon.ChannelValue(s) - if channel.Type != discordgo.ChannelTypeGuildText { - err := resp.SetMessage("Le salon n'est pas un salon textuel.").Send() - if err != nil { - utils.SendAlert("commands/config.go - Invalid channel type", err.Error()) - } - return - } - cfg := config.GetGuildConfig(i.GuildID) - cfg.FallbackChannel = channel.ID - // save - err := cfg.Save() - if err != nil { - utils.SendAlert( - "commands/config.go - Saving config", - err.Error(), - "guild_id", - i.GuildID, - "channel_id", - channel.ID, - ) - err = resp.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send() - } else { - err = resp.SetMessage("Salon enregistré.").Send() - } - if err != nil { - utils.SendAlert("commands/config.go - Channel saved message", err.Error()) - } -} - -func ConfigPeriodBeforeReduce(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { - resp.IsEphemeral() - // verify every args - days, ok := optMap["days"] - if !ok { - err := resp.SetMessage("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.SetMessage("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.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send() - } else { - err = resp.SetMessage("Nombre de jours enregistré.").Send() - } + //comp := component.New(). + // Add(component.NewTextDisplay("# Config")). + // Add(component.NewTextDisplay("**Salon par défaut**\n" + defaultChan)). + // Add(component.NewSeparator()). + // Add(component.NewTextDisplay("**Rôles liés aux niveaux**\n" + roles)). + // Add(component.NewSeparator()). + // Add(component.NewTextDisplay("**Salons désactivés**\n" + chans)). + // Add(component.NewSeparator()). + // Add(component.NewTextDisplay(fmt.Sprintf("**%s**\n%d", "Jours avant la réduction", cfg.DaysXPRemains))). + // Add(component.NewActionRow().Add(component.NewStringSelect(ConfigModify). + // SetPlaceholder("Modifier..."). + // AddOption( + // component.NewSelectOption("Rôles liés à l'XP", config.ModifyXpRole). + // SetDescription("Gère les rôles liés à l'XP"). + // SetEmoji(&discordgo.ComponentEmoji{Name: "🏅"}), + // ). + // AddOption( + // component.NewSelectOption("Salons désactivés", config.ModifyDisChannel). + // SetDescription("Gère les salons désactivés"). + // SetEmoji(&discordgo.ComponentEmoji{Name: "❌"}), + // ). + // AddOption( + // // I don't have a better idea for this... + // component.NewSelectOption("Salons par défaut", config.ModifyFallbackChannel). + // SetDescription("Spécifie le salon par défaut"). + // SetEmoji(&discordgo.ComponentEmoji{Name: "💾"}), + // ). + // AddOption( + // component.NewSelectOption("Temps avec la réduction", config.ModifyTimeReduce). + // SetDescription("Gère le temps avant la réduction d'XP"). + // SetEmoji(&discordgo.ComponentEmoji{Name: "⌛"}), + // ), + // )) + comp := component.New(). + Add(component.NewActionRow().Add(component.NewStringSelect(ConfigModify). + SetPlaceholder("Modifier..."). + AddOption( + component.NewSelectOption("Rôles liés à l'XP", config.ModifyXpRole). + SetDescription("Gère les rôles liés à l'XP"). + SetEmoji(&discordgo.ComponentEmoji{Name: "🏅"}), + ). + AddOption( + component.NewSelectOption("Salons désactivés", config.ModifyDisChannel). + SetDescription("Gère les salons désactivés"). + SetEmoji(&discordgo.ComponentEmoji{Name: "❌"}), + ). + AddOption( + // I don't have a better idea for this... + component.NewSelectOption("Salons par défaut", config.ModifyFallbackChannel). + SetDescription("Spécifie le salon par défaut"). + SetEmoji(&discordgo.ComponentEmoji{Name: "💾"}), + ). + AddOption( + component.NewSelectOption("Temps avec la réduction", config.ModifyTimeReduce). + SetDescription("Gère le temps avant la réduction d'XP"). + SetEmoji(&discordgo.ComponentEmoji{Name: "⌛"}), + ), + )) + msg := fmt.Sprintf( + "# Config\n**Salon par défaut**\n%s\n\n**Rôles liés aux niveaux**\n%s\n\n**Salons désactivés**\n%s\n\n**Jours avant la réduction**\n%d", + defaultChan, + roles, + chans, + cfg.DaysXPRemains, + ) + err := resp.SetComponents(comp).SetMessage(msg).IsEphemeral().Send() if err != nil { - utils.SendAlert("commands/config.go - Days saved message", err.Error()) + logger.Alert("config/guild.go - Sending config", err.Error()) } } diff --git a/commands/credits.go b/commands/credits.go index 0943761..f0a8c46 100644 --- a/commands/credits.go +++ b/commands/credits.go @@ -1,36 +1,19 @@ package commands import ( - "github.com/anhgelus/gokord/utils" - "github.com/bwmarrin/discordgo" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) -func Credits(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { - err := resp.AddEmbed(&discordgo.MessageEmbed{ - - Type: discordgo.EmbedTypeRich, - Title: "Crédits", - 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.10.0 - MPL 2.0", - Inline: true, - }, - { - Name: "bwmarrin/discordgo", - Value: "v0.29.0 - BSD-3-Clause", - Inline: true, - }, - { - Name: "gorm", - Value: "v1.30.0 - MIT", - Inline: true, - }, - }, - }).Send() +func Credits(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) { + msg := "**Les Copaings**, le bot gérant les serveurs privés de [anhgelus](<https://anhgelus.world/>).\n" + msg += "Code source : <https://git.anhgelus.world/anhgelus/les-copaings-bot>\n\n" + msg += "Host du bot : " + gokord.BaseCfg.GetAuthor() + ".\n\n" + msg += "Utilise :\n- [anhgelus/gokord](<https://github.com/anhgelus/gokord>)" + err := resp.SetMessage(msg).Send() if err != nil { - utils.SendAlert("commands/credits.go - Sending credits", err.Error(), "guild_id", i.GuildID) + logger.Alert("commands/credits.go - Sending credits", err.Error(), "guild_id", i.GuildID) } } diff --git a/commands/rank.go b/commands/rank.go index dd5859a..94dabf2 100644 --- a/commands/rank.go +++ b/commands/rank.go @@ -2,13 +2,15 @@ package commands import ( "fmt" - "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/exp" - "github.com/anhgelus/les-copaings-bot/user" - "github.com/bwmarrin/discordgo" + + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) -func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { +func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { c := user.GetCopaing(i.Member.User.ID, i.GuildID) // current user = member who used /rank msg := "Votre niveau" m := i.Member @@ -18,12 +20,12 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt if u.Bot { err = resp.SetMessage("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() if err != nil { - utils.SendAlert("commands/rank.go - Reply error user is a bot", err.Error()) + logger.Alert("commands/rank.go - Reply error user is a bot", err.Error()) } } m, err = s.GuildMember(i.GuildID, u.ID) if err != nil { - utils.SendAlert( + logger.Alert( "commands/rank.go - Fetching guild member", err.Error(), "discord_id", @@ -33,7 +35,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt ) err = resp.SetMessage("Erreur : impossible de récupérer le membre").IsEphemeral().Send() if err != nil { - utils.SendAlert("commands/rank.go - Reply error fetching guild member", err.Error()) + logger.Alert("commands/rank.go - Reply error fetching guild member", err.Error()) } return } @@ -42,7 +44,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt } xp, err := c.GetXP() if err != nil { - utils.SendAlert( + logger.Alert( "commands/rank.go - Fetching xp", err.Error(), "discord_id", @@ -52,7 +54,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt ) err = resp.SetMessage("Erreur : impossible de récupérer l'XP").IsEphemeral().Send() if err != nil { - utils.SendAlert("commands/rank.go - Reply error fetching xp", err.Error()) + logger.Alert("commands/rank.go - Reply error fetching xp", err.Error()) } return } @@ -66,6 +68,6 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt nxtLvlXP-xp, )).Send() if err != nil { - utils.SendAlert("commands/rank.go - Sending rank", err.Error()) + logger.Alert("commands/rank.go - Sending rank", err.Error()) } } diff --git a/commands/reset.go b/commands/reset.go index c4275a0..07e3ba3 100644 --- a/commands/reset.go +++ b/commands/reset.go @@ -1,45 +1,46 @@ package commands import ( + "git.anhgelus.world/anhgelus/les-copaings-bot/user" "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/user" - "github.com/bwmarrin/discordgo" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) -func Reset(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { +func Reset(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) { var copaings []*user.Copaing gokord.DB.Where("guild_id = ?", i.GuildID).Delete(&copaings) if err := resp.IsEphemeral().SetMessage("L'XP a été reset.").Send(); err != nil { - utils.SendAlert("commands/reset.go - Sending success (all)", err.Error()) + logger.Alert("commands/reset.go - Sending success (all)", err.Error()) } } -func ResetUser(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { +func ResetUser(s *discordgo.Session, i *discordgo.InteractionCreate, optMap cmd.OptionMap, resp *cmd.ResponseBuilder) { resp.IsEphemeral() v, ok := optMap["user"] if !ok { if err := resp.SetMessage("Le user n'a pas été renseigné.").Send(); err != nil { - utils.SendAlert("commands/reset.go - Copaing not set", err.Error()) + logger.Alert("commands/reset.go - Copaing not set", err.Error()) } return } m := v.UserValue(s) if m.Bot { if err := resp.SetMessage("Les bots n'ont pas de niveau :upside_down:").Send(); err != nil { - utils.SendAlert("commands/reset.go - Copaing not set", err.Error()) + logger.Alert("commands/reset.go - Copaing not set", err.Error()) } return } 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) + logger.Alert("commands/reset.go - Copaing not deleted", err.Error(), "discord_id", m.ID, "guild_id", i.GuildID) err = resp.SetMessage("Erreur : impossible de reset l'utilisateur").Send() if err != nil { - utils.SendAlert("commands/reset.go - Error deleting", err.Error()) + logger.Alert("commands/reset.go - Error deleting", err.Error()) } } if err = resp.SetMessage("Le user bien été reset.").Send(); err != nil { - utils.SendAlert("commands/reset.go - Sending success (user)", err.Error()) + logger.Alert("commands/reset.go - Sending success (user)", err.Error()) } } diff --git a/commands/stats.go b/commands/stats.go new file mode 100644 index 0000000..f93f6a0 --- /dev/null +++ b/commands/stats.go @@ -0,0 +1,224 @@ +package commands + +import ( + "bytes" + "errors" + "gorm.io/gorm" + "image/color" + "io" + "math" + "math/rand/v2" + "slices" + "time" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + "github.com/jackc/pgx/v5/pgtype" + discordgo "github.com/nyttikord/gokord" + "gonum.org/v1/plot" + "gonum.org/v1/plot/plotter" + "gonum.org/v1/plot/vg" +) + +type data struct { + CreatedAt time.Time + XP int + CopaingID int +} + +type dbData struct { + CreatedAt *pgtype.Date + XP int + CopaingID int +} + +func Stats(s *discordgo.Session, i *discordgo.InteractionCreate, opt cmd.OptionMap, resp *cmd.ResponseBuilder) { + cfg := config.GetGuildConfig(i.GuildID) + days := cfg.DaysXPRemains + if v, ok := opt["days"]; ok { + in := v.IntValue() + if in < 0 || uint(in) > days { + if err := resp.SetMessage("Nombre de jours invalide").IsEphemeral().Send(); err != nil { + logger.Alert("commands/stats.go - Sending invalid days", err.Error()) + } + return + } + days = uint(in) + } + err := resp.IsDeferred().Send() + if err != nil { + logger.Alert("commands/stats.go - Sending deferred", err.Error()) + return + } + go func() { + var w io.WriterTo + if v, ok := opt["user"]; ok { + w, err = statsMember(s, i, days, v.UserValue(s).ID) + } else { + w, err = statsAll(s, i, days) + } + if err != nil { + if err = resp.IsEphemeral().SetMessage("Il y a eu une erreur...").Send(); err != nil { + logger.Alert("commands/stats.go - Sending error occurred", err.Error()) + } + return + } + b := new(bytes.Buffer) + _, err = w.WriteTo(b) + if err != nil { + logger.Alert("commands/stats.go - Writing png", err.Error()) + } + err = resp.AddFile(&discordgo.File{ + Name: "plot.png", + ContentType: "image/png", + Reader: b, + }).Send() + if err != nil { + logger.Alert("commands/stats.go - Sending response", err.Error()) + } + }() +} + +func statsAll(s *discordgo.Session, i *discordgo.InteractionCreate, days uint) (io.WriterTo, error) { + return stats(s, i, days, func(before, after string) *gorm.DB { + return gokord.DB.Raw(before+"WHERE guild_id = ? and created_at > ?"+after, i.GuildID, exp.TimeStampNDaysBefore(days)) + }) +} + +func statsMember(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, discordID string) (io.WriterTo, error) { + _, err := s.GuildMember(i.GuildID, discordID) + if err != nil { + return nil, err + } + return stats(s, i, days, func(before, after string) *gorm.DB { + return gokord.DB.Raw( + before+"WHERE guild_id = ? and created_at > ? and copaing_id = ?"+after, + i.GuildID, exp.TimeStampNDaysBefore(days), user.GetCopaing(discordID, i.GuildID).ID, + ) + }) +} + +func stats(s *discordgo.Session, i *discordgo.InteractionCreate, days uint, execSql func(before, after string) *gorm.DB) (io.WriterTo, error) { + var rawData []*data + if gokord.Debug { + var rawCopaingData []*user.CopaingXP + if err := execSql("SELECT * FROM copaing_xps ", "").Scan(&rawCopaingData).Error; err != nil { + logger.Alert("commands/stats.go - Fetching result", err.Error()) + return nil, err + } + rawData = make([]*data, len(rawCopaingData)) + for in, d := range rawCopaingData { + rawData[in] = &data{ + CreatedAt: d.CreatedAt, + XP: int(d.XP), + CopaingID: int(d.CopaingID), + } + } + } else { + var rawDbData []dbData + if err := execSql( + `SELECT "created_at"::date::text, sum("xp") as xp, "copaing_id" FROM copaing_xps `, ` GROUP BY "created_at"::date, "copaing_id"`, + ).Scan(&rawDbData).Error; err != nil { + logger.Alert("commands/stats.go - Fetching result", err.Error()) + return nil, err + } + rawData = make([]*data, len(rawDbData)) + for in, d := range rawDbData { + rawData[in] = &data{ + CreatedAt: d.CreatedAt.Time, + XP: d.XP, + CopaingID: d.CopaingID, + } + } + } + + copaings := map[int]*user.Copaing{} + stats := map[int][]plotter.XY{} + + for _, raw := range rawData { + _, ok := copaings[raw.CopaingID] + if !ok { + var cp user.Copaing + if err := gokord.DB.First(&cp, raw.CopaingID).Error; err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Alert("commands/stats.go - Finding copaing", err.Error(), "id", raw.CopaingID) + return nil, err + } + logger.Warn("Copaing not found, skipping entry", "old_id", raw.CopaingID) + continue + } + copaings[raw.CopaingID] = &cp + } + pts, ok := stats[raw.CopaingID] + now := time.Now().Unix() + if !ok { + pts = make([]plotter.XY, days+1) + for i := 0; i < len(pts); i++ { + pts[i] = plotter.XY{ + X: float64(i - int(days)), + Y: 0, + } + } + stats[raw.CopaingID] = pts + } + t := raw.CreatedAt.Unix() - now + if !gokord.Debug { + t = int64(math.Ceil(float64(t) / (24 * 60 * 60))) + } + pts[int64(days)+t] = plotter.XY{ // because t <= 0 + X: float64(t), + Y: float64(raw.XP), + } + } + return generatePlot(s, i, copaings, stats) +} + +func generatePlot(s *discordgo.Session, i *discordgo.InteractionCreate, copaings map[int]*user.Copaing, stats map[int][]plotter.XY) (io.WriterTo, error) { + p := plot.New() + p.Title.Text = "Évolution de l'XP" + p.X.Label.Text = "Jours" + if gokord.Debug { + p.X.Label.Text = "Secondes" + } + p.Y.Label.Text = "XP" + + p.Add(plotter.NewGrid()) + + r := rand.New(rand.NewPCG(uint64(time.Now().Unix()), uint64(time.Now().Unix()))) + for in, c := range copaings { + m, err := s.GuildMember(i.GuildID, c.DiscordID) + if err != nil { + logger.Alert("commands/stats.go - Fetching guild member", err.Error()) + return nil, err + } + slices.SortFunc(stats[in], func(a, b plotter.XY) int { + if a.X < b.X { + return -1 + } + if a.X > b.X { + return 1 + } + return 0 + }) + l, err := plotter.NewLine(plotter.XYs(stats[in])) + if err != nil { + logger.Alert("commands/stats.go - Adding line points", err.Error()) + return nil, err + } + l.LineStyle.Width = vg.Points(1) + l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)} + l.LineStyle.Color = color.RGBA{R: uint8(r.UintN(255)), G: uint8(r.UintN(255)), B: uint8(r.UintN(255)), A: 255} + p.Add(l) + p.Legend.Add(m.DisplayName(), l) + } + w, err := p.WriterTo(8*vg.Inch, 6*vg.Inch, "png") + if err != nil { + logger.Alert("commands/stats.go - Generating png", err.Error()) + return nil, err + } + return w, nil +} diff --git a/commands/top.go b/commands/top.go index daa1ccb..ecbf6f4 100644 --- a/commands/top.go +++ b/commands/top.go @@ -2,18 +2,20 @@ package commands import ( "fmt" - "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/config" - "github.com/anhgelus/les-copaings-bot/exp" - "github.com/anhgelus/les-copaings-bot/user" - "github.com/bwmarrin/discordgo" "sync" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) -func Top(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { +func Top(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) { err := resp.IsDeferred().Send() if err != nil { - utils.SendAlert("commands/top.go - Sending deferred", err.Error()) + logger.Alert("commands/top.go - Sending deferred", err.Error()) return } embeds := make([]*discordgo.MessageEmbed, 3) @@ -23,18 +25,18 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opti 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) + logger.Alert("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, + Color: 0x831010, } return } embeds[id] = &discordgo.MessageEmbed{ Title: s, Description: genTopsMessage(tops), - Color: utils.Success, + Color: 0x10E6AD, } } cfg := config.GetGuildConfig(i.GuildID) @@ -57,7 +59,7 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opti } err = resp.Send() if err != nil { - utils.SendAlert("commands/top.go - Sending response top", err.Error()) + logger.Alert("commands/top.go - Sending response top", err.Error()) } }() } @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/anhgelus/gokord" "github.com/pelletier/go-toml/v2" "gorm.io/driver/postgres" diff --git a/config/channel.go b/config/channel.go new file mode 100644 index 0000000..537d586 --- /dev/null +++ b/config/channel.go @@ -0,0 +1,127 @@ +package config + +import ( + "strings" + + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/component" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" +) + +const ( + ModifyFallbackChannel = "fallback_channel" + FallbackChannelSet = "fallback_channel_set" + + ModifyDisChannel = "disabled_channel" + DisChannelAdd = "disabled_channel_add" + DisChannelAddSet = "disabled_channel_add_set" + DisChannelDel = "disabled_channel_del" + DisChannelDelSet = "disabled_channel_del_set" +) + +func HandleModifyFallbackChannel(_ *discordgo.Session, _ *discordgo.InteractionCreate, _ discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + err := resp.IsEphemeral().SetComponents(component.New().Add(component.NewActionRow().Add( + component.NewChannelSelect(FallbackChannelSet).AddChannelType(discordgo.ChannelTypeGuildText), + ))).Send() + if err != nil { + logger.Alert("config/channel.go - Sending channel list for fallback", err.Error()) + } +} + +func HandleFallbackChannelSet(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + + cfg := GetGuildConfig(i.GuildID) + channelID := data.Values[0] + + cfg.FallbackChannel = channelID + err := cfg.Save() + if err != nil { + logger.Alert("config/channel.go - Saving fallback channel", err.Error()) + if err = resp.SetMessage("Erreur lors de la sauvegarde du salon").Send(); err != nil { + logger.Alert("config/channel.go - Sending error while saving channel", err.Error()) + } + return + } + if err = resp.SetMessage("Salon sauvegardé.").Send(); err != nil { + logger.Alert("config/channel.go - Sending channel saved", err.Error()) + } +} + +func HandleModifyDisChannel(_ *discordgo.Session, _ *discordgo.InteractionCreate, _ discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + err := resp.IsEphemeral().SetComponents(component.New().Add(component.NewActionRow(). + Add( + component.NewButton(DisChannelAdd, discordgo.PrimaryButton). + SetLabel("Désactiver un salon"). + SetEmoji(&discordgo.ComponentEmoji{Name: "⬇️"}), + ). + Add( + component.NewButton(DisChannelDel, discordgo.DangerButton). + SetLabel("Réactiver un salon"). + SetEmoji(&discordgo.ComponentEmoji{Name: "⬆️"}), + ), + )).Send() + if err != nil { + logger.Alert("config/channel.go - Sending action type", err.Error()) + } +} + +func HandleDisChannel(_ *discordgo.Session, _ *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral().SetMessage("Salon à désactiver...") + cID := DisChannelAddSet + if data.CustomID == DisChannelDel { + resp.SetMessage("Salon à réactiver...") + cID = DisChannelDelSet + } + err := resp.SetComponents(component.New().Add(component.NewActionRow().Add(component.NewChannelSelect(cID)))).Send() + if err != nil { + logger.Alert("config/channel.go - Sending channel list for disable", err.Error()) + } +} + +func HandleDisChannelAddSet(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + cfg := GetGuildConfig(i.GuildID) + id := data.Values[0] + if strings.Contains(cfg.DisabledChannels, id) { + err := resp.SetMessage("Le salon est déjà dans la liste des salons désactivés").Send() + if err != nil { + logger.Alert("commands/config.go - Channel already disabled", err.Error()) + } + return + } + cfg.DisabledChannels += id + ";" + if err := cfg.Save(); err != nil { + logger.Alert("commands/config.go - Saving config disable add", err.Error()) + if err = resp.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send(); err != nil { + logger.Alert("config/channel.go - Sending error while saving config", err.Error()) + } + } + if err := resp.SetMessage("Modification sauvegardé.").Send(); err != nil { + logger.Alert("commands/config.go - Modification saved message disable add", err.Error()) + } +} + +func HandleDisChannelDelSet(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + cfg := GetGuildConfig(i.GuildID) + id := data.Values[0] + if !strings.Contains(cfg.DisabledChannels, id) { + err := resp.SetMessage("Le salon n'est pas désactivé").Send() + if err != nil { + logger.Alert("commands/config.go - Channel not disabled", err.Error()) + } + return + } + cfg.DisabledChannels = strings.ReplaceAll(cfg.DisabledChannels, id+";", "") + if err := cfg.Save(); err != nil { + logger.Alert("commands/config.go - Saving config disable del", err.Error()) + if err = resp.SetMessage("Il y a eu une erreur lors de la modification de de la base de données.").Send(); err != nil { + logger.Alert("config/channel.go - Sending error while saving config", err.Error()) + } + } + if err := resp.SetMessage("Modification sauvegardé.").Send(); err != nil { + logger.Alert("commands/config.go - Modification saved message disable del", err.Error()) + } +} diff --git a/config/guild.go b/config/guild.go index 63736df..971470d 100644 --- a/config/guild.go +++ b/config/guild.go @@ -1,9 +1,9 @@ package config import ( - "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/utils" "strings" + + "github.com/anhgelus/gokord" ) type GuildConfig struct { @@ -33,8 +33,7 @@ type BoostXpRole struct { func GetGuildConfig(guildID string) *GuildConfig { cfg := GuildConfig{GuildID: guildID} if err := cfg.Load(); err != nil { - utils.SendAlert("config/guild.go - Loading guild config", err.Error(), "guild_id", guildID) - return nil + panic(err) } return &cfg } diff --git a/config/xp_reduce.go b/config/xp_reduce.go new file mode 100644 index 0000000..defc54b --- /dev/null +++ b/config/xp_reduce.go @@ -0,0 +1,60 @@ +package config + +import ( + "strconv" + + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/component" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" +) + +const ( + ModifyTimeReduce = "time_reduce" + TimeReduceSet = "time_reduce_set" +) + +func HandleModifyPeriodicReduce(_ *discordgo.Session, _ *discordgo.InteractionCreate, _ discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + err := resp.IsModal(). + SetCustomID(TimeReduceSet). + SetComponents(component.New().ForModal().Add(component.NewActionRow().ForModal().Add( + component.NewTextInput(TimeReduceSet, "Jours avant la réduction", discordgo.TextInputShort). + SetMinLength(1). + SetMaxLength(3), + ))).Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Sending modal for periodic reduce", err.Error()) + } +} + +func HandleTimeReduceSet(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.ModalSubmitInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + v := data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value + days, err := strconv.Atoi(v) + if err != nil { + logger.Debug(err.Error()) + if err = resp.SetMessage("Nombres de jours invalides. Merci de mettre un entier.").Send(); err != nil { + logger.Alert("config/xp_reduce.go - Sending bad input", err.Error()) + } + return + } + if days < 30 { + err = resp.SetMessage("Le nombre de jours est inférieur à 30.").Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Days < 30 (fallback)", err.Error()) + } + return + } + cfg := GetGuildConfig(i.GuildID) + cfg.DaysXPRemains = uint(days) + if err = cfg.Save(); err != nil { + logger.Alert("config/channel.go - Saving days xp remains", err.Error()) + if err = resp.SetMessage("Erreur lors de la sauvegarde du salon").Send(); err != nil { + logger.Alert("config/xp_reduce.go - Sending error while saving days xp remains", err.Error()) + } + return + } + if err = resp.SetMessage("Modification sauvegardée.").Send(); err != nil { + logger.Alert("config/xp_reduce.go - Sending days saved", err.Error()) + } +} diff --git a/config/xp_role.go b/config/xp_role.go new file mode 100644 index 0000000..6ea89e2 --- /dev/null +++ b/config/xp_role.go @@ -0,0 +1,209 @@ +package config + +import ( + "fmt" + "strconv" + "time" + + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/component" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" +) + +const ( + ModifyXpRole = "xp_role" + XpRoleAdd = "xp_role_add" + XpRoleAddLevel = "xp_role_add_level" + XpRoleAddRole = "xp_role_add_role" + XpRoleDel = "xp_role_del" + XpRoleDelRole = "xp_role_del_role" + XpRoleEdit = "xp_role_edit" + XpRoleEditLevel = "xp_role_edit_level" + XpRoleEditRole = "xp_role_edit_role" +) + +var ( + configModifyMap = map[string]uint{} +) + +func HandleModifyXpRole(_ *discordgo.Session, _ *discordgo.InteractionCreate, _ discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + err := resp.IsEphemeral(). + SetMessage("Action à réaliser"). + SetComponents(component.New().Add(component.NewActionRow(). + Add(component.NewButton(XpRoleAdd, discordgo.PrimaryButton). + SetLabel("Ajouter"). + SetEmoji(&discordgo.ComponentEmoji{Name: "⬆️"}), + ). + Add(component.NewButton(XpRoleEdit, discordgo.SecondaryButton). + SetLabel("Modifier"). + SetEmoji(&discordgo.ComponentEmoji{Name: "📝"}), + ). + Add(component.NewButton(XpRoleDel, discordgo.DangerButton). + SetLabel("Supprimer"). + SetEmoji(&discordgo.ComponentEmoji{Name: "❌"}), + ), + )).Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Sending config", err.Error()) + } +} + +func HandleXpRoleAddEdit(_ *discordgo.Session, _ *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + cID := XpRoleAddLevel + if data.CustomID == XpRoleEdit { + cID = XpRoleEditLevel + } + err := resp.IsModal(). + SetTitle("Role"). + SetCustomID(cID). + SetComponents(component.New().ForModal().Add(component.NewActionRow().ForModal().Add( + component.NewTextInput(cID, "Niveau", discordgo.TextInputShort). + SetPlaceholder("5"). + IsRequired(). + SetMinLength(0). + SetMaxLength(5), + ))). + Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Sending modal to add/edit", err.Error()) + } +} + +func HandleXpRoleAddRole(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + cfg := GetGuildConfig(i.GuildID) + roleId := data.Values[0] + for _, r := range cfg.XpRoles { + if r.RoleID == roleId { + err := resp.SetMessage("Le rôle est déjà présent dans la config").Send() + if err != nil { + logger.Alert("config/xp_role.go - Role already in config", err.Error()) + } + return + } + } + cfg.XpRoles = append(cfg.XpRoles, XpRole{ + XP: configModifyMap[getKeyConfigRole(i)], + RoleID: roleId, + }) + err := cfg.Save() + if err != nil { + logger.Alert( + "config/xp_role.go - Saving config", + err.Error(), + "guild_id", i.GuildID, + "role_id", roleId, + "type", "add", + ) + } + if err = resp.SetMessage("Rôle ajouté.").Send(); err != nil { + logger.Alert("config/xp_role.go - Sending success", err.Error()) + } +} + +func HandleXpRoleEditRole(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + cfg := GetGuildConfig(i.GuildID) + roleId := data.Values[0] + _, r := cfg.FindXpRole(roleId) + if r == nil { + err := resp.SetMessage("Le rôle n'a pas été trouvé dans la config.").Send() + if err != nil { + logger.Alert("config/xp_role.go - Role not found (edit)", err.Error()) + } + return + } + r.XP = configModifyMap[getKeyConfigRole(i)] + err := gokord.DB.Save(r).Error + if err != nil { + logger.Alert( + "config/xp_role.go - Saving config", + err.Error(), + "guild_id", i.GuildID, + "role_id", roleId, + "type", "edit", + ) + } + if err = resp.SetMessage("Rôle modifié.").Send(); err != nil { + logger.Alert("config/xp_role.go - Sending success", err.Error()) + } +} + +func HandleXpRoleDel(_ *discordgo.Session, _ *discordgo.InteractionCreate, _ discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + err := resp.IsEphemeral(). + SetMessage("Rôle à supprimer"). + SetComponents(component.New().Add(component.NewActionRow().Add(component.NewRoleSelect(XpRoleDelRole)))). + Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Sending response to del", err.Error()) + } +} + +func HandleXpRoleDelRole(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + cfg := GetGuildConfig(i.GuildID) + roleId := data.Values[0] + _, r := cfg.FindXpRole(roleId) + if r == nil { + err := resp.SetMessage("Le rôle n'a pas été trouvé dans la config.").Send() + if err != nil { + logger.Alert("config/xp_role.go - Sending role not found (del)", err.Error()) + } + return + } + err := gokord.DB.Delete(r).Error + if err != nil { + logger.Alert( + "config/xp_role.go - Deleting entry", + err.Error(), + "guild_id", i.GuildID, + "role_id", roleId, + "type", "del", + ) + } + if err = resp.SetMessage("Rôle supprimé.").Send(); err != nil { + logger.Alert("config/xp_role.go - Sending success", err.Error()) + } +} + +func HandleXpRoleLevel(_ *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.ModalSubmitInteractionData, resp *cmd.ResponseBuilder) { + resp.IsEphemeral() + input := data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput) + + k := getKeyConfigRole(i) + in, err := strconv.Atoi(input.Value) + if err != nil || in < 0 { + if err = resp. + SetMessage("Impossible de lire le nombre. Il doit s'agit d'un nombre entier positif."). + Send(); err != nil { + logger.Alert("command/config.go - Sending bad number", err.Error()) + } + return + } + configModifyMap[k] = exp.LevelXP(uint(in)) + go func(i *discordgo.InteractionCreate, k string) { + time.Sleep(5 * time.Minute) + delete(configModifyMap, k) + }(i, k) + + cID := XpRoleAddRole + resp.SetMessage("Rôle à ajouter") + if data.CustomID == XpRoleEditLevel { + cID = XpRoleEditRole + resp.SetMessage("Rôle à modifier") + } + + err = resp. + SetComponents(component.New().Add(component.NewActionRow().Add(component.NewRoleSelect(cID)))). + Send() + if err != nil { + logger.Alert("config/xp_reduce.go - Sending response to add/edit", err.Error()) + } +} + +func getKeyConfigRole(i *discordgo.InteractionCreate) string { + return fmt.Sprintf("r:%s:%s", i.GuildID, i.Member.User.ID) +} diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 77b28da..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -services: - bot: - build: . - restart: always - env_file: - - .env - volumes: - - ./config:/app/config - depends_on: - - postgres - postgres: - image: postgres:alpine - env_file: - - .env - volumes: - - ./data:/var/lib/postgresql/data - adminer: - image: docker.io/adminer - ports: - - "8080:8080" - depends_on: - - postgres @@ -2,13 +2,14 @@ package main import ( "fmt" - "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/config" - "github.com/anhgelus/les-copaings-bot/exp" - "github.com/anhgelus/les-copaings-bot/user" - "github.com/bwmarrin/discordgo" "strings" "time" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) const ( @@ -31,13 +32,13 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) { } c := user.GetCopaing(m.Author.ID, m.GuildID) // add exp - trimmed := utils.TrimMessage(strings.ToLower(m.Content)) + trimmed := exp.TrimMessage(strings.ToLower(m.Content)) m.Member.User = m.Author m.Member.GuildID = m.GuildID xp := min(exp.MessageXP(uint(len(trimmed)), exp.CalcDiversity(trimmed)), MaxXpPerMessage) c.AddXP(s, m.Member, xp, func(_ uint, _ uint) { if err := s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil { - utils.SendAlert( + logger.Alert( "events.go - add reaction for new level", err.Error(), "channel id", m.ChannelID, "message id", m.Message.ID, @@ -69,7 +70,7 @@ func genMapKey(guildID string, userID string) string { } func onConnection(_ *discordgo.Session, e *discordgo.VoiceStateUpdate) { - utils.SendDebug("User connected", "username", e.Member.DisplayName()) + logger.Debug("User connected", "username", e.Member.DisplayName()) connectedSince[genMapKey(e.GuildID, e.UserID)] = time.Now().Unix() } @@ -79,17 +80,17 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { // check the validity of user con, ok := connectedSince[genMapKey(e.GuildID, e.UserID)] if !ok || con == NotConnected { - utils.SendWarn(fmt.Sprintf( + logger.Warn(fmt.Sprintf( "User %s diconnect from a vocal but was registered as not connected", e.Member.DisplayName(), )) return } timeInVocal := now - con - utils.SendDebug("User disconnected", "username", e.Member.DisplayName(), "time in vocal", timeInVocal) + logger.Debug("User disconnected", "username", e.Member.DisplayName(), "time in vocal", timeInVocal) connectedSince[genMapKey(e.GuildID, e.UserID)] = NotConnected // add exp if timeInVocal < 0 { - utils.SendAlert( + logger.Alert( "events.go - Calculating time spent in vocal", "the time is negative", "discord_id", e.UserID, "guild_id", e.GuildID, @@ -97,7 +98,7 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { return } if timeInVocal > MaxTimeInVocal { - utils.SendWarn(fmt.Sprintf("User %s spent more than 6 hours in vocal", e.Member.DisplayName())) + logger.Warn(fmt.Sprintf("User %s spent more than 6 hours in vocal", e.Member.DisplayName())) timeInVocal = MaxTimeInVocal } e.Member.GuildID = e.GuildID @@ -110,19 +111,19 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { "%s est maintenant niveau %d", e.Member.Mention(), newLevel, )) if err != nil { - utils.SendAlert("events.go - Sending new level in fallback channel", err.Error()) + logger.Alert("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) + logger.Debug("Leave event", "user_id", e.User.ID) if e.User.Bot { return } c := user.GetCopaing(e.User.ID, e.GuildID) if err := c.Delete(); err != nil { - utils.SendAlert( + logger.Alert( "events.go - deleting user from db", err.Error(), "user_id", e.User.ID, "guild_id", e.GuildID, diff --git a/exp/functions.go b/exp/functions.go index 119b6a8..2608094 100644 --- a/exp/functions.go +++ b/exp/functions.go @@ -2,10 +2,13 @@ package exp import ( "fmt" - "github.com/anhgelus/gokord" "math" + "regexp" "slices" + "strings" "time" + + "github.com/anhgelus/gokord" ) func MessageXP(length uint, diversity uint) uint { @@ -48,12 +51,24 @@ func LevelXP(level uint) uint { // TimeStampNDaysBefore returns the timestamp (year-month-day) n days before today func TimeStampNDaysBefore(n uint) string { - var y, d int - var m time.Month + var unix time.Time if gokord.Debug { - y, m, d = time.Unix(time.Now().Unix()-int64(24*60*60), 0).Date() // reduce time for debug + unix = time.Unix(time.Now().Unix()-int64(n), 0) // reduce time for debug } else { - y, m, d = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0).Date() + unix = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0) } - return fmt.Sprintf("%d-%d-%d", y, m, d) + unix = unix.UTC() + return fmt.Sprintf("%d-%d-%d %d:%d:%d UTC", unix.Year(), unix.Month(), unix.Day(), unix.Hour(), unix.Minute(), unix.Second()) +} + +func TrimMessage(s string) string { + not := regexp.MustCompile("[^a-zA-Z0-9éèêàùûç,;:!.?]") + ping := regexp.MustCompile("<(@&?|#)[0-9]{18}>") + link := regexp.MustCompile("https?://[a-zA-Z0-9.]+[.][a-z]+.*") + + s = ping.ReplaceAllLiteralString(s, "") + s = link.ReplaceAllLiteralString(s, "") + s = not.ReplaceAllLiteralString(s, "") + + return strings.Trim(s, " ") } @@ -1,29 +1,39 @@ -module github.com/anhgelus/les-copaings-bot +module git.anhgelus.world/anhgelus/les-copaings-bot go 1.24 require ( - github.com/anhgelus/gokord v0.10.3 - github.com/bwmarrin/discordgo v0.29.0 + github.com/anhgelus/gokord v0.11.1-0.20250904142107-2d0e3c982bc3 + github.com/jackc/pgx/v5 v5.7.5 github.com/joho/godotenv v1.5.1 + github.com/nyttikord/gokord v0.30.0 github.com/pelletier/go-toml/v2 v2.2.4 + gonum.org/v1/plot v0.16.0 gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.30.1 + gorm.io/gorm v1.30.3 ) require ( + codeberg.org/go-fonts/liberation v0.5.0 // indirect + codeberg.org/go-latex/latex v0.1.0 // indirect + codeberg.org/go-pdf/fpdf v0.11.1 // indirect + git.sr.ht/~sbinet/gg v0.6.0 // indirect + github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect + github.com/campoy/embedmd v1.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 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.5 // 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/redis/go-redis/v9 v9.11.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/redis/go-redis/v9 v9.13.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/image v0.30.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect ) @@ -1,19 +1,30 @@ -github.com/anhgelus/gokord v0.7.0 h1:G9GrxD3/xEreXsiz3etKxbeHsNHrwT5I/VEKSWpyrj4= -github.com/anhgelus/gokord v0.7.0/go.mod h1:SfGKyMMGjNS9F9ehiEb5Cc58P+uoDdLDGGYqXSiMCus= -github.com/anhgelus/gokord v0.9.0 h1:vz7jHZ6papdt/xehe+nx4DxOLquPO6QukW8UzH81bGY= -github.com/anhgelus/gokord v0.9.0/go.mod h1:NSepHjTV61LUnuyGgHxEhZNMnWREErGFyOtRYPgdx/E= -github.com/anhgelus/gokord v0.10.0 h1:FaaMWntaezmSCvarcSMjfWr5OXVVwwzlDMnNX8gXaWE= -github.com/anhgelus/gokord v0.10.0/go.mod h1:NSepHjTV61LUnuyGgHxEhZNMnWREErGFyOtRYPgdx/E= -github.com/anhgelus/gokord v0.10.3 h1:4uKHRXFsRg1wRJQ3Sikz1MpL68cUZxyseRD+5hVe7tE= -github.com/anhgelus/gokord v0.10.3/go.mod h1:NSepHjTV61LUnuyGgHxEhZNMnWREErGFyOtRYPgdx/E= +codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I= +codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg= +codeberg.org/go-fonts/latin-modern v0.4.0 h1:vkRCc1y3whKA7iL9Ep0fSGVuJfqjix0ica9UflHORO8= +codeberg.org/go-fonts/latin-modern v0.4.0/go.mod h1:BF68mZznJ9QHn+hic9ks2DaFl4sR5YhfM6xTYaP9vNw= +codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= +codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= +codeberg.org/go-latex/latex v0.1.0 h1:hoGO86rIbWVyjtlDLzCqZPjNykpWQ9YuTZqAzPcfL3c= +codeberg.org/go-latex/latex v0.1.0/go.mod h1:LA0q/AyWIYrqVd+A9Upkgsb+IqPcmSTKc9Dny04MHMw= +codeberg.org/go-pdf/fpdf v0.11.1 h1:U8+coOTDVLxHIXZgGvkfQEi/q0hYHYvEHFuGNX2GzGs= +codeberg.org/go-pdf/fpdf v0.11.1/go.mod h1:Y0DGRAdZ0OmnZPvjbMp/1bYxmIPxm0ws4tfoPOc4LjU= +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= +git.sr.ht/~sbinet/gg v0.6.0 h1:RIzgkizAk+9r7uPzf/VfbJHBMKUr0F5hRFxTUGMnt38= +git.sr.ht/~sbinet/gg v0.6.0/go.mod h1:uucygbfC9wVPQIfrmwM2et0imr8L7KQWywX0xpFMm94= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/anhgelus/gokord v0.11.1-0.20250904142107-2d0e3c982bc3 h1:j7Im8+Vd7BIhBPQKsK5Hs2BsyO71Q3/XVxgZntNJa5M= +github.com/anhgelus/gokord v0.11.1-0.20250904142107-2d0e3c982bc3/go.mod h1:Tw1djmUOTFCIRhEUI7eG2onnreJbVGA40EWYQgj20bE= 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= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4= -github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= -github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno= -github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -21,15 +32,14 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -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/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= @@ -40,54 +50,64 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/nyttikord/gokord v0.30.0 h1:O4LhpCMyfWcgLqziPXQgMCopb9VO7kwcZG3phblOaTA= +github.com/nyttikord/gokord v0.30.0/go.mod h1:Lhk268VlZ1W6Pb3kYnlU9bIuTCioaumedjHdtw1sxck= 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.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/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM= -github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= -github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= -github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/redis/go-redis/v9 v9.13.0 h1:PpmlVykE0ODh8P43U0HqC+2NXHXwG+GUtQyz+MPKGRg= +github.com/redis/go-redis/v9 v9.13.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= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -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/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= +golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -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/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/plot v0.16.0 h1:dK28Qx/Ky4VmPUN/2zeW0ELyM6ucDnBAj5yun7M9n1g= +gonum.org/v1/plot v0.16.0/go.mod h1:Xz6U1yDMi6Ni6aaXILqmVIb6Vro8E+K7Q/GeeH+Pn0c= 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= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= -gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= -gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= -gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= -gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= -gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= -gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.30.3 h1:QiG8upl0Sg9ba2Zatfjy0fy4It2iNBL2/eMdvEkdXNs= +gorm.io/gorm v1.30.3/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/justfile b/justfile new file mode 100644 index 0000000..f3916dd --- /dev/null +++ b/justfile @@ -0,0 +1,18 @@ +dev: + podman network create db + podman run -p 5432:5432 --rm --network db --name postgres --env-file .env -v ./data:/var/lib/postgresql/data -d postgres:alpine + podman run -p 8080:8080 --rm --network db --name adminer -d adminer + go run . + +update: + git stash + git pull + git stash pop + go run . + +stop: + podman stop postgres adminer + podman network rm db + +build: + go build .
\ No newline at end of file @@ -4,15 +4,17 @@ import ( _ "embed" "errors" "flag" - "github.com/anhgelus/gokord" - "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/user" - "github.com/bwmarrin/discordgo" - "github.com/joho/godotenv" "os" "time" + + "git.anhgelus.world/anhgelus/les-copaings-bot/commands" + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/user" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/cmd" + "github.com/anhgelus/gokord/logger" + "github.com/joho/godotenv" + discordgo "github.com/nyttikord/gokord" ) var ( @@ -21,8 +23,8 @@ var ( updatesData []byte Version = gokord.Version{ Major: 3, - Minor: 1, - Patch: 3, + Minor: 2, + Patch: 0, } stopPeriodicReducer chan<- interface{} @@ -31,7 +33,7 @@ var ( func init() { err := godotenv.Load() if err != nil && !errors.Is(err, os.ErrNotExist) { - utils.SendWarn("Error while loading .env file", "error", err.Error()) + logger.Warn("Error while loading .env file", "error", err.Error()) } flag.StringVar(&token, "token", os.Getenv("TOKEN"), "token of the bot") } @@ -51,87 +53,27 @@ func main() { adm := gokord.AdminPermission - rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un copaing"). - AddOption(gokord.NewOption( + rankCmd := cmd.New("rank", "Affiche le niveau d'un copaing"). + AddOption(cmd.NewOption( discordgo.ApplicationCommandOptionUser, "copaing", "Le niveau du Copaing que vous souhaitez obtenir", )). SetHandler(commands.Rank) - configCmd := gokord.NewCommand("config", "Modifie la config"). - ContainsSub(). - AddSub( - gokord.NewCommand("show", "Affiche la config").SetHandler(commands.ConfigShow), - ). - AddSub( - gokord.NewCommand("xp", "Modifie l'xp"). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionString, - "type", - "Type d'action à effectuer", - ). - AddChoice(gokord.NewChoice("Ajouter", "add")). - AddChoice(gokord.NewChoice("Supprimer", "del")). - AddChoice(gokord.NewChoice("Modifier", "edit")).IsRequired(), - ). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionInteger, - "level", - "Niveau du rôle", - ).IsRequired()). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionRole, - "role", - "Rôle", - ).IsRequired()). - SetHandler(commands.ConfigXP), - ). - AddSub( - gokord.NewCommand("disabled-channels", "Modifie les salons désactivés"). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionString, - "type", - "Type d'action à effectuer", - ). - AddChoice(gokord.NewChoice("Désactiver le salon", "add")). - AddChoice(gokord.NewChoice("Activer le salon", "del")).IsRequired(), - ). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionChannel, - "channel", - "Salon à modifier", - ).IsRequired()). - SetHandler(commands.ConfigChannel), - ). - AddSub( - gokord.NewCommand("period-before-reduce", "Temps avant la perte d'xp (affecte aussi le /top)"). - 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"). - AddOption(gokord.NewOption( - discordgo.ApplicationCommandOptionChannel, - "channel", - "Salon textuel par défaut", - ).IsRequired()). - SetHandler(commands.ConfigFallbackChannel), - ).SetPermission(&adm) - - topCmd := gokord.NewCommand("top", "Copaings les plus actifs"). + configCmd := cmd.New("config", "Modifie la config"). + SetPermission(&adm). + SetHandler(commands.Config) + + topCmd := cmd.New("top", "Copaings les plus actifs"). SetHandler(commands.Top) - resetCmd := gokord.NewCommand("reset", "Reset l'xp"). + resetCmd := cmd.New("reset", "Reset l'xp"). SetHandler(commands.Reset). SetPermission(&adm) - resetUserCmd := gokord.NewCommand("reset-user", "Reset l'xp d'un utilisation"). - AddOption(gokord.NewOption( + resetUserCmd := cmd.New("reset-user", "Reset l'xp d'un utilisation"). + AddOption(cmd.NewOption( discordgo.ApplicationCommandOptionUser, "user", "Copaing a reset", @@ -139,9 +81,22 @@ func main() { SetHandler(commands.ResetUser). SetPermission(&adm) - creditsCmd := gokord.NewCommand("credits", "Crédits"). + creditsCmd := cmd.New("credits", "Crédits"). SetHandler(commands.Credits) + statsCmd := cmd.New("stats", "Affiche des stats :D"). + AddOption(cmd.NewOption( + discordgo.ApplicationCommandOptionInteger, + "days", + "Nombre de jours à afficher dans le graphique", + )). + AddOption(cmd.NewOption( + discordgo.ApplicationCommandOptionUser, + "user", + "Utilisateur à inspecter", + )). + SetHandler(commands.Stats) + innovations, err := gokord.LoadInnovationFromJson(updatesData) if err != nil { panic(err) @@ -167,35 +122,81 @@ func main() { Content: "Les Copaings Bot " + Version.String(), }, }, - Commands: []gokord.CommandBuilder{ + Commands: []cmd.CommandBuilder{ rankCmd, configCmd, topCmd, resetCmd, resetUserCmd, creditsCmd, + statsCmd, + }, + AfterInit: func(dg *discordgo.Session) { + d := 24 * time.Hour + if gokord.Debug { + d = 24 * time.Second + } + + user.PeriodicReducer(dg) + + stopPeriodicReducer = gokord.NewTimer(d, func(stop chan<- interface{}) { + logger.Debug("Periodic reducer") + user.PeriodicReducer(dg) + }) }, - AfterInit: afterInit, Innovations: innovations, Version: &Version, Intents: discordgo.IntentsAllWithoutPrivileged | discordgo.IntentsMessageContent | discordgo.IntentGuildMembers, } + + // interaction: /config + bot.HandleMessageComponent(func(s *discordgo.Session, i *discordgo.InteractionCreate, data discordgo.MessageComponentInteractionData, resp *cmd.ResponseBuilder) { + if len(data.Values) != 1 { + logger.Alert("main.go - Handle config modify", "invalid data values", "values", data.Values) + return + } + switch data.Values[0] { + case config.ModifyXpRole: + config.HandleModifyXpRole(s, i, data, resp) + case config.ModifyFallbackChannel: + config.HandleModifyFallbackChannel(s, i, data, resp) + case config.ModifyDisChannel: + config.HandleModifyDisChannel(s, i, data, resp) + case config.ModifyTimeReduce: + config.HandleModifyPeriodicReduce(s, i, data, resp) + default: + logger.Alert("main.go - Detecting value", "unkown value", "value", data.Values[0]) + return + } + }, commands.ConfigModify) + // xp role related + bot.HandleMessageComponent(config.HandleXpRoleAddEdit, config.XpRoleAdd) + bot.HandleMessageComponent(config.HandleXpRoleAddEdit, config.XpRoleEdit) + bot.HandleMessageComponent(config.HandleXpRoleAddRole, config.XpRoleAddRole) + bot.HandleMessageComponent(config.HandleXpRoleEditRole, config.XpRoleEditRole) + bot.HandleMessageComponent(config.HandleXpRoleDel, config.XpRoleDel) + bot.HandleMessageComponent(config.HandleXpRoleDelRole, config.XpRoleDelRole) + bot.HandleModal(config.HandleXpRoleLevel, config.XpRoleAddLevel) + bot.HandleModal(config.HandleXpRoleLevel, config.XpRoleEditLevel) + // channel related + bot.HandleMessageComponent(config.HandleFallbackChannelSet, config.FallbackChannelSet) + bot.HandleMessageComponent(config.HandleDisChannel, config.DisChannelAdd) + bot.HandleMessageComponent(config.HandleDisChannel, config.DisChannelDel) + bot.HandleMessageComponent(config.HandleDisChannelAddSet, config.DisChannelAddSet) + bot.HandleMessageComponent(config.HandleDisChannelDelSet, config.DisChannelDelSet) + // reduce related + bot.HandleModal(config.HandleTimeReduceSet, config.TimeReduceSet) + + // xp handlers + bot.AddHandler(OnMessage) + bot.AddHandler(OnVoiceUpdate) + bot.AddHandler(OnLeave) + bot.Start() if stopPeriodicReducer != nil { stopPeriodicReducer <- true } } - -func afterInit(dg *discordgo.Session) { - // handlers - dg.AddHandler(OnMessage) - dg.AddHandler(OnVoiceUpdate) - dg.AddHandler(OnLeave) - - stopPeriodicReducer = utils.NewTimer(24*time.Hour, func(stop chan<- interface{}) { - user.PeriodicReducer(dg) - }) -} diff --git a/restart.sh b/restart.sh deleted file mode 100644 index ddb9128..0000000 --- a/restart.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/bash - -podman compose down && podman compose build && podman compose up -d
\ No newline at end of file diff --git a/updates.json b/updates.json index d0d863c..452a306 100644 --- a/updates.json +++ b/updates.json @@ -28,5 +28,17 @@ "ping" ] } + }, + { + "version": "3.2.0", + "commands": { + "added": [ + "stats" + ], + "removed": [], + "updated": [ + "config" + ] + } } ] diff --git a/user/level.go b/user/level.go index 6d9b674..be7d212 100644 --- a/user/level.go +++ b/user/level.go @@ -1,14 +1,15 @@ package user import ( - "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/utils" - "github.com/anhgelus/les-copaings-bot/config" - "github.com/anhgelus/les-copaings-bot/exp" - "github.com/bwmarrin/discordgo" "slices" "sync" "time" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { @@ -16,7 +17,7 @@ func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { xpForLevel := exp.LevelXP(level) for _, role := range cfg.XpRoles { if role.XP <= xpForLevel && !slices.Contains(m.Roles, role.RoleID) { - utils.SendDebug( + logger.Debug( "Add role", "role_id", role.RoleID, "user_id", m.User.ID, @@ -24,10 +25,10 @@ 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("user/level.go - Adding role", err.Error(), "role_id", role.RoleID) + logger.Alert("user/level.go - Adding role", err.Error(), "role_id", role.RoleID) } } else if role.XP > xpForLevel && slices.Contains(m.Roles, role.RoleID) { - utils.SendDebug( + logger.Debug( "Remove role", "role_id", role.RoleID, "user_id", m.User.ID, @@ -35,7 +36,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("user/level.go - Removing role", err.Error(), "role_id", role.RoleID) + logger.Alert("user/level.go - Removing role", err.Error(), "role_id", role.RoleID) } } } @@ -44,7 +45,7 @@ func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) { m, err := dg.GuildMember(c.GuildID, c.DiscordID) if err != nil { - utils.SendAlert( + logger.Alert( "user/level.go - Getting member for new level", err.Error(), "discord_id", c.DiscordID, "guild_id", c.GuildID, @@ -58,7 +59,7 @@ 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()) + logger.Alert("user/level.go - Fetching all copaings", err.Error()) return } cxps := make([]*cXP, len(cs)) @@ -71,7 +72,7 @@ func PeriodicReducer(dg *discordgo.Session) { 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) + logger.Alert("user/level.go - Getting XP", err.Error(), "copaing_id", c.ID, "guild_id", c.GuildID) xp = 0 } cxps[i] = &cXP{ @@ -86,31 +87,31 @@ func PeriodicReducer(dg *discordgo.Session) { go func() { defer wg.Done() cfg := config.GetGuildConfig(g.ID) - err := gokord.DB. + res := 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 - Removing old XP", err.Error(), "guild_id", g.ID) + Delete(&CopaingXP{}) + if res.Error != nil { + logger.Alert("user/level.go - Removing old XP", res.Error.Error(), "guild_id", g.ID) } + logger.Debug("Guild cleaned", "guild", g.Name, "rows affected", res.RowsAffected) }() } wg.Wait() for i, c := range cxps { if i%50 == 49 { - utils.SendDebug("Sleeping...") + logger.Debug("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) + logger.Alert("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)) + logger.Debug("Periodic reduce finished", "len(guilds)", len(dg.State.Guilds)) } diff --git a/user/member.go b/user/member.go index 77ceb2e..ad8762f 100644 --- a/user/member.go +++ b/user/member.go @@ -1,9 +1,9 @@ package user import ( - "github.com/anhgelus/gokord" - "github.com/anhgelus/gokord/utils" "time" + + "github.com/anhgelus/gokord" ) type Copaing struct { @@ -34,15 +34,7 @@ const ( func GetCopaing(discordID string, guildID string) *Copaing { c := Copaing{DiscordID: discordID, GuildID: guildID} if err := c.Load(); err != nil { - utils.SendAlert( - "user/member.go - Loading user", - err.Error(), - "discord_id", - discordID, - "guild_id", - guildID, - ) - return nil + panic(err) } return &c } @@ -1,14 +1,15 @@ 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" "math" "slices" "sync" + + "git.anhgelus.world/anhgelus/les-copaings-bot/config" + "git.anhgelus.world/anhgelus/les-copaings-bot/exp" + "github.com/anhgelus/gokord" + "github.com/anhgelus/gokord/logger" + discordgo "github.com/nyttikord/gokord" ) type cXP struct { @@ -27,18 +28,14 @@ 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) + logger.Alert("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: uint(math.Floor(float64(xp) * c.GetBoost(m))), - GuildID: c.GuildID, - }) + logger.Debug("Adding xp", "member", m.DisplayName(), "old xp", old, "xp to add", xp, "old level", pastLevel) + c.CopaingXPs = append(c.CopaingXPs, CopaingXP{CopaingID: c.ID, XP: uint(math.Floor(float64(xp) * c.GetBoost(m))), GuildID: c.GuildID}) if err = c.Save(); err != nil { - utils.SendAlert( + logger.Alert( "user/xp.go - Saving user", err.Error(), "xp", c.CopaingXPs, @@ -78,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(), "copaing_id", c.ID, "guild_id", c.GuildID) + logger.Alert("user/xp.go - Scanning rows", err.Error(), "copaing_id", c.ID, "guild_id", c.GuildID) continue } xp += cxp.XP @@ -119,7 +116,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(), "guild_id", guildId) + logger.Alert("user/xp.go - Scanning rows", err.Error(), "guild_id", guildId) continue } wg.Add(1) @@ -127,7 +124,7 @@ func GetBestXP(guildId string, n uint, d int) ([]CopaingAccess, error) { 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) + logger.Alert("user/xp.go - Fetching xp", err.Error(), "discord_id", c.DiscordID, "guild_id", guildId) return } l = append(l, &cXP{Cxp: xp, Copaing: &c}) |
