aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--README.md9
-rw-r--r--commands/config.go397
-rw-r--r--commands/credits.go39
-rw-r--r--commands/rank.go24
-rw-r--r--commands/reset.go23
-rw-r--r--commands/stats.go224
-rw-r--r--commands/top.go24
-rw-r--r--config.go1
-rw-r--r--config/channel.go127
-rw-r--r--config/guild.go7
-rw-r--r--config/xp_reduce.go60
-rw-r--r--config/xp_role.go209
-rw-r--r--docker-compose.yml22
-rw-r--r--events.go31
-rw-r--r--exp/functions.go27
-rw-r--r--go.mod28
-rw-r--r--go.sum114
-rw-r--r--justfile18
-rw-r--r--main.go189
-rw-r--r--restart.sh3
-rw-r--r--updates.json12
-rw-r--r--user/level.go41
-rw-r--r--user/member.go14
-rw-r--r--user/xp.go29
25 files changed, 1043 insertions, 632 deletions
diff --git a/.gitignore b/.gitignore
index 7cc06cf..b7d2fba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,6 @@ go.work
tmp
config/**.toml
data
+docker-compose.yml
+
+les-copaings-bot
diff --git a/README.md b/README.md
index f902867..2390dc4 100644
--- a/README.md
+++ b/README.md
@@ -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())
}
}()
}
diff --git a/config.go b/config.go
index 7a8c57b..ff0472e 100644
--- a/config.go
+++ b/config.go
@@ -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
diff --git a/events.go b/events.go
index 3e55e4b..6f15fd8 100644
--- a/events.go
+++ b/events.go
@@ -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, " ")
}
diff --git a/go.mod b/go.mod
index 81543a2..540e6d8 100644
--- a/go.mod
+++ b/go.mod
@@ -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
)
diff --git a/go.sum b/go.sum
index 1da8f5f..6b5401a 100644
--- a/go.sum
+++ b/go.sum
@@ -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
diff --git a/main.go b/main.go
index 4610bc4..facf243 100644
--- a/main.go
+++ b/main.go
@@ -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
}
diff --git a/user/xp.go b/user/xp.go
index 6dbed59..feb9cf7 100644
--- a/user/xp.go
+++ b/user/xp.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"
"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})