Merge pull request '[Refactor] Config command' (#1) from refactor/config-command into main

Reviewed-on: #1
This commit is contained in:
Anhgelus Morhtuuzh 2025-08-21 12:54:52 +00:00
commit 166aadabfb
20 changed files with 697 additions and 529 deletions

View file

@ -2,15 +2,21 @@ package commands
import ( import (
"fmt" "fmt"
"github.com/anhgelus/gokord" "strings"
"github.com/anhgelus/gokord/utils"
"github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/component"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/exp"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"strings"
) )
func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { const (
ConfigModify = "config_modify"
)
func Config(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) {
cfg := config.GetGuildConfig(i.GuildID) cfg := config.GetGuildConfig(i.GuildID)
roles := "" roles := ""
l := len(cfg.XpRoles) - 1 l := len(cfg.XpRoles) - 1
@ -43,318 +49,73 @@ func ConfigShow(s *discordgo.Session, i *discordgo.InteractionCreate, optMap uti
} else { } else {
defaultChan = fmt.Sprintf("<#%s>", cfg.FallbackChannel) defaultChan = fmt.Sprintf("<#%s>", cfg.FallbackChannel)
} }
err := resp.AddEmbed(&discordgo.MessageEmbed{ //comp := component.New().
Type: discordgo.EmbedTypeRich, // Add(component.NewTextDisplay("# Config")).
Title: "Config", // Add(component.NewTextDisplay("**Salon par défaut**\n" + defaultChan)).
Color: utils.Success, // Add(component.NewSeparator()).
Fields: []*discordgo.MessageEmbedField{ // Add(component.NewTextDisplay("**Rôles liés aux niveaux**\n" + roles)).
{ // Add(component.NewSeparator()).
Name: "Salon par défaut", // Add(component.NewTextDisplay("**Salons désactivés**\n" + chans)).
Value: defaultChan, // Add(component.NewSeparator()).
Inline: false, // Add(component.NewTextDisplay(fmt.Sprintf("**%s**\n%d", "Jours avant la réduction", cfg.DaysXPRemains))).
}, // Add(component.NewActionRow().Add(component.NewStringSelect(ConfigModify).
{ // SetPlaceholder("Modifier...").
Name: "Rôles liés aux niveaux", // AddOption(
Value: roles, // component.NewSelectOption("Rôles liés à l'XP", config.ModifyXpRole).
Inline: false, // SetDescription("Gère les rôles liés à l'XP").
}, // SetEmoji(&discordgo.ComponentEmoji{Name: "🏅"}),
{ // ).
Name: "Salons désactivés", // AddOption(
Value: chans, // component.NewSelectOption("Salons désactivés", config.ModifyDisChannel).
Inline: false, // SetDescription("Gère les salons désactivés").
}, // SetEmoji(&discordgo.ComponentEmoji{Name: "❌"}),
{ // ).
Name: "Jours avant la réduction", // AddOption(
Value: fmt.Sprintf("%d", cfg.DaysXPRemains), // // I don't have a better idea for this...
Inline: false, // component.NewSelectOption("Salons par défaut", config.ModifyFallbackChannel).
}, // SetDescription("Spécifie le salon par défaut").
}, // SetEmoji(&discordgo.ComponentEmoji{Name: "💾"}),
}).Send() // ).
if err != nil { // AddOption(
utils.SendAlert("config/guild.go - Sending config", err.Error()) // component.NewSelectOption("Temps avec la réduction", config.ModifyTimeReduce).
} // SetDescription("Gère le temps avant la réduction d'XP").
} // SetEmoji(&discordgo.ComponentEmoji{Name: "⌛"}),
// ),
func ConfigXP(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { // ))
resp.IsEphemeral() comp := component.New().
// verify every args Add(component.NewActionRow().Add(component.NewStringSelect(ConfigModify).
t, ok := optMap["type"] SetPlaceholder("Modifier...").
if !ok { AddOption(
err := resp.SetMessage("Le type d'action n'a pas été renseigné.").Send() component.NewSelectOption("Rôles liés à l'XP", config.ModifyXpRole).
if err != nil { SetDescription("Gère les rôles liés à l'XP").
utils.SendAlert("commands/config.go - Action type not set", err.Error()) SetEmoji(&discordgo.ComponentEmoji{Name: "🏅"}),
} ).
return AddOption(
} component.NewSelectOption("Salons désactivés", config.ModifyDisChannel).
ts := t.StringValue() SetDescription("Gère les salons désactivés").
lvl, ok := optMap["level"] SetEmoji(&discordgo.ComponentEmoji{Name: "❌"}),
if !ok { ).
err := resp.SetMessage("Le niveau n'a pas été renseigné.").Send() AddOption(
if err != nil { // I don't have a better idea for this...
utils.SendAlert("commands/config.go - Level not set", err.Error()) component.NewSelectOption("Salons par défaut", config.ModifyFallbackChannel).
} SetDescription("Spécifie le salon par défaut").
return SetEmoji(&discordgo.ComponentEmoji{Name: "💾"}),
} ).
level := lvl.IntValue() AddOption(
if level < 1 { component.NewSelectOption("Temps avec la réduction", config.ModifyTimeReduce).
err := resp.SetMessage("Le niveau doit forcément être supérieur à 0.").Send() SetDescription("Gère le temps avant la réduction d'XP").
if err != nil { SetEmoji(&discordgo.ComponentEmoji{Name: "⌛"}),
utils.SendAlert("commands/config.go - Invalid level", err.Error()) ),
} ))
return 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",
xp := exp.LevelXP(uint(level)) defaultChan,
r, ok := optMap["role"] roles,
if !ok { chans,
err := resp.SetMessage("Le rôle n'a pas été renseigné.").Send() cfg.DaysXPRemains,
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",
) )
} err := resp.SetComponents(comp).SetMessage(msg).IsEphemeral().Send()
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 { if err != nil {
utils.SendAlert("commands/config.go - Role not found (del)", err.Error()) logger.Alert("config/guild.go - Sending config", 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()
}
if err != nil {
utils.SendAlert("commands/config.go - Days saved message", err.Error())
} }
} }

View file

@ -1,17 +1,18 @@
package commands package commands
import ( import (
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/logger"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
func Credits(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.OptionMap, resp *utils.ResponseBuilder) { func Credits(_ *discordgo.Session, i *discordgo.InteractionCreate, _ cmd.OptionMap, resp *cmd.ResponseBuilder) {
err := resp.AddEmbed(&discordgo.MessageEmbed{ err := resp.AddEmbed(&discordgo.MessageEmbed{
Type: discordgo.EmbedTypeRich, Type: discordgo.EmbedTypeRich,
Title: "Crédits", Title: "Crédits",
Description: "Auteur du bot : @anhgelus (https://github.com/anhgelus)\nLangage : Go 1.24\nLicence : AGPLv3", Description: "Auteur du bot : @anhgelus (https://github.com/anhgelus)\nLangage : Go 1.24\nLicence : AGPLv3",
Color: utils.Success, Color: 0x10E6AD,
Fields: []*discordgo.MessageEmbedField{ Fields: []*discordgo.MessageEmbedField{
{ {
Name: "anhgelus/gokord", Name: "anhgelus/gokord",
@ -31,6 +32,6 @@ func Credits(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.
}, },
}).Send() }).Send()
if err != nil { 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)
} }
} }

View file

@ -2,13 +2,14 @@ package commands
import ( import (
"fmt" "fmt"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/exp"
"github.com/anhgelus/les-copaings-bot/user" "github.com/anhgelus/les-copaings-bot/user"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
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 c := user.GetCopaing(i.Member.User.ID, i.GuildID) // current user = member who used /rank
msg := "Votre niveau" msg := "Votre niveau"
m := i.Member m := i.Member
@ -18,12 +19,12 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt
if u.Bot { if u.Bot {
err = resp.SetMessage("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send() err = resp.SetMessage("Imagine si les bots avaient un niveau :rolling_eyes:").IsEphemeral().Send()
if err != nil { 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) m, err = s.GuildMember(i.GuildID, u.ID)
if err != nil { if err != nil {
utils.SendAlert( logger.Alert(
"commands/rank.go - Fetching guild member", "commands/rank.go - Fetching guild member",
err.Error(), err.Error(),
"discord_id", "discord_id",
@ -33,7 +34,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() err = resp.SetMessage("Erreur : impossible de récupérer le membre").IsEphemeral().Send()
if err != nil { 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 return
} }
@ -42,7 +43,7 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt
} }
xp, err := c.GetXP() xp, err := c.GetXP()
if err != nil { if err != nil {
utils.SendAlert( logger.Alert(
"commands/rank.go - Fetching xp", "commands/rank.go - Fetching xp",
err.Error(), err.Error(),
"discord_id", "discord_id",
@ -52,7 +53,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() err = resp.SetMessage("Erreur : impossible de récupérer l'XP").IsEphemeral().Send()
if err != nil { 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 return
} }
@ -66,6 +67,6 @@ func Rank(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opt
nxtLvlXP-xp, nxtLvlXP-xp,
)).Send() )).Send()
if err != nil { if err != nil {
utils.SendAlert("commands/rank.go - Sending rank", err.Error()) logger.Alert("commands/rank.go - Sending rank", err.Error())
} }
} }

View file

@ -2,44 +2,45 @@ package commands
import ( import (
"github.com/anhgelus/gokord" "github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/user" "github.com/anhgelus/les-copaings-bot/user"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
) )
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 var copaings []*user.Copaing
gokord.DB.Where("guild_id = ?", i.GuildID).Delete(&copaings) gokord.DB.Where("guild_id = ?", i.GuildID).Delete(&copaings)
if err := resp.IsEphemeral().SetMessage("L'XP a été reset.").Send(); err != nil { 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() resp.IsEphemeral()
v, ok := optMap["user"] v, ok := optMap["user"]
if !ok { if !ok {
if err := resp.SetMessage("Le user n'a pas été renseigné.").Send(); err != nil { 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 return
} }
m := v.UserValue(s) m := v.UserValue(s)
if m.Bot { if m.Bot {
if err := resp.SetMessage("Les bots n'ont pas de niveau :upside_down:").Send(); err != nil { 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 return
} }
err := user.GetCopaing(m.ID, i.GuildID).Delete() err := user.GetCopaing(m.ID, i.GuildID).Delete()
if err != nil { 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() err = resp.SetMessage("Erreur : impossible de reset l'utilisateur").Send()
if err != nil { 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 { 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())
} }
} }

View file

@ -2,7 +2,8 @@ package commands
import ( import (
"fmt" "fmt"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/exp"
"github.com/anhgelus/les-copaings-bot/user" "github.com/anhgelus/les-copaings-bot/user"
@ -10,10 +11,10 @@ import (
"sync" "sync"
) )
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() err := resp.IsDeferred().Send()
if err != nil { if err != nil {
utils.SendAlert("commands/top.go - Sending deferred", err.Error()) logger.Alert("commands/top.go - Sending deferred", err.Error())
return return
} }
embeds := make([]*discordgo.MessageEmbed, 3) embeds := make([]*discordgo.MessageEmbed, 3)
@ -23,18 +24,18 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opti
defer wg.Done() defer wg.Done()
tops, err := user.GetBestXP(i.GuildID, n, d) tops, err := user.GetBestXP(i.GuildID, n, d)
if err != nil { 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{ embeds[id] = &discordgo.MessageEmbed{
Title: s, Title: s,
Description: "Erreur : impossible de récupérer la liste", Description: "Erreur : impossible de récupérer la liste",
Color: utils.Error, Color: 0x831010,
} }
return return
} }
embeds[id] = &discordgo.MessageEmbed{ embeds[id] = &discordgo.MessageEmbed{
Title: s, Title: s,
Description: genTopsMessage(tops), Description: genTopsMessage(tops),
Color: utils.Success, Color: 0x10E6AD,
} }
} }
cfg := config.GetGuildConfig(i.GuildID) cfg := config.GetGuildConfig(i.GuildID)
@ -57,7 +58,7 @@ func Top(s *discordgo.Session, i *discordgo.InteractionCreate, optMap utils.Opti
} }
err = resp.Send() err = resp.Send()
if err != nil { if err != nil {
utils.SendAlert("commands/top.go - Sending response top", err.Error()) logger.Alert("commands/top.go - Sending response top", err.Error())
} }
}() }()
} }

126
config/channel.go Normal file
View file

@ -0,0 +1,126 @@
package config
import (
"github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/component"
"github.com/anhgelus/gokord/logger"
"github.com/bwmarrin/discordgo"
"strings"
)
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())
}
}

View file

@ -1,9 +1,9 @@
package config package config
import ( import (
"github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/utils"
"strings" "strings"
"github.com/anhgelus/gokord"
) )
type GuildConfig struct { type GuildConfig struct {
@ -15,18 +15,10 @@ type GuildConfig struct {
DaysXPRemains uint `gorm:"default:90"` // 30 * 3 = 90 (three months) DaysXPRemains uint `gorm:"default:90"` // 30 * 3 = 90 (three months)
} }
type XpRole struct {
ID uint `gorm:"primarykey"`
XP uint
RoleID string
GuildConfigID uint
}
func GetGuildConfig(guildID string) *GuildConfig { func GetGuildConfig(guildID string) *GuildConfig {
cfg := GuildConfig{GuildID: guildID} cfg := GuildConfig{GuildID: guildID}
if err := cfg.Load(); err != nil { if err := cfg.Load(); err != nil {
utils.SendAlert("config/guild.go - Loading guild config", err.Error(), "guild_id", guildID) panic(err)
return nil
} }
return &cfg return &cfg
} }

60
config/xp_reduce.go Normal file
View file

@ -0,0 +1,60 @@
package config
import (
"strconv"
"github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/component"
"github.com/anhgelus/gokord/logger"
"github.com/bwmarrin/discordgo"
)
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())
}
}

216
config/xp_role.go Normal file
View file

@ -0,0 +1,216 @@
package config
import (
"fmt"
"strconv"
"time"
"github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/component"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/exp"
"github.com/bwmarrin/discordgo"
)
type XpRole struct {
ID uint `gorm:"primarykey"`
XP uint
RoleID string
GuildConfigID uint
}
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)
}

5
create_db.sh Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/bash
podman network create db
podman run -p 5432:5432 --rm --network db --name postgres --env-file .env -v ./data:/var/lib/postgres/data -d postgres:alpine
podman run -p 8080:8080 --rm --network db --name adminer -d adminer

View file

@ -2,7 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/exp"
"github.com/anhgelus/les-copaings-bot/user" "github.com/anhgelus/les-copaings-bot/user"
@ -37,7 +37,7 @@ func OnMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
xp := min(exp.MessageXP(uint(len(trimmed)), exp.CalcDiversity(trimmed)), MaxXpPerMessage) xp := min(exp.MessageXP(uint(len(trimmed)), exp.CalcDiversity(trimmed)), MaxXpPerMessage)
c.AddXP(s, m.Member, xp, func(_ uint, _ uint) { c.AddXP(s, m.Member, xp, func(_ uint, _ uint) {
if err := s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil { if err := s.MessageReactionAdd(m.ChannelID, m.Message.ID, "⬆"); err != nil {
utils.SendAlert( logger.Alert(
"events.go - add reaction for new level", err.Error(), "events.go - add reaction for new level", err.Error(),
"channel id", m.ChannelID, "channel id", m.ChannelID,
"message id", m.Message.ID, "message id", m.Message.ID,
@ -69,7 +69,7 @@ func genMapKey(guildID string, userID string) string {
} }
func onConnection(_ *discordgo.Session, e *discordgo.VoiceStateUpdate) { 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() connectedSince[genMapKey(e.GuildID, e.UserID)] = time.Now().Unix()
} }
@ -79,17 +79,17 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
// check the validity of user // check the validity of user
con, ok := connectedSince[genMapKey(e.GuildID, e.UserID)] con, ok := connectedSince[genMapKey(e.GuildID, e.UserID)]
if !ok || con == NotConnected { 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(), "User %s diconnect from a vocal but was registered as not connected", e.Member.DisplayName(),
)) ))
return return
} }
timeInVocal := now - con 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 connectedSince[genMapKey(e.GuildID, e.UserID)] = NotConnected
// add exp // add exp
if timeInVocal < 0 { if timeInVocal < 0 {
utils.SendAlert( logger.Alert(
"events.go - Calculating time spent in vocal", "the time is negative", "events.go - Calculating time spent in vocal", "the time is negative",
"discord_id", e.UserID, "discord_id", e.UserID,
"guild_id", e.GuildID, "guild_id", e.GuildID,
@ -97,7 +97,7 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
return return
} }
if timeInVocal > MaxTimeInVocal { 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 timeInVocal = MaxTimeInVocal
} }
e.Member.GuildID = e.GuildID e.Member.GuildID = e.GuildID
@ -110,19 +110,19 @@ func onDisconnect(s *discordgo.Session, e *discordgo.VoiceStateUpdate) {
"%s est maintenant niveau %d", e.Member.Mention(), newLevel, "%s est maintenant niveau %d", e.Member.Mention(), newLevel,
)) ))
if err != nil { 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) { 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 { if e.User.Bot {
return return
} }
c := user.GetCopaing(e.User.ID, e.GuildID) c := user.GetCopaing(e.User.ID, e.GuildID)
if err := c.Delete(); err != nil { if err := c.Delete(); err != nil {
utils.SendAlert( logger.Alert(
"events.go - deleting user from db", err.Error(), "events.go - deleting user from db", err.Error(),
"user_id", e.User.ID, "user_id", e.User.ID,
"guild_id", e.GuildID, "guild_id", e.GuildID,

View file

@ -2,12 +2,13 @@ package exp
import ( import (
"fmt" "fmt"
"github.com/anhgelus/gokord"
"math" "math"
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
"time" "time"
"github.com/anhgelus/gokord"
) )
func MessageXP(length uint, diversity uint) uint { func MessageXP(length uint, diversity uint) uint {
@ -52,7 +53,7 @@ func LevelXP(level uint) uint {
func TimeStampNDaysBefore(n uint) string { func TimeStampNDaysBefore(n uint) string {
var unix time.Time var unix time.Time
if gokord.Debug { if gokord.Debug {
unix = time.Unix(time.Now().Unix()-int64(n), 0) // reduce time for debug unix = time.Unix(time.Now().Unix()-int64(n)*6, 0) // reduce time for debug
} else { } else {
unix = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0) unix = time.Unix(time.Now().Unix()-int64(n*24*60*60), 0)
} }

20
go.mod
View file

@ -3,11 +3,12 @@ module github.com/anhgelus/les-copaings-bot
go 1.24 go 1.24
require ( require (
github.com/anhgelus/gokord v0.10.0 github.com/anhgelus/gokord v0.11.1-0.20250821122244-0aee6c37eef6
github.com/bwmarrin/discordgo v0.29.0 github.com/bwmarrin/discordgo v0.29.0
github.com/joho/godotenv v1.5.1
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.2.4
gorm.io/driver/postgres v1.5.11 gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.30.0 gorm.io/gorm v1.30.1
) )
require ( require (
@ -16,14 +17,13 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.4 // indirect github.com/jackc/pgx/v5 v5.7.5 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/redis/go-redis/v9 v9.12.1 // indirect
github.com/redis/go-redis/v9 v9.9.0 // indirect golang.org/x/crypto v0.41.0 // indirect
golang.org/x/crypto v0.38.0 // indirect golang.org/x/sync v0.16.0 // indirect
golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.35.0 // indirect
golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.28.0 // indirect
golang.org/x/text v0.25.0 // indirect
) )

68
go.sum
View file

@ -1,15 +1,25 @@
github.com/anhgelus/gokord v0.7.0 h1:G9GrxD3/xEreXsiz3etKxbeHsNHrwT5I/VEKSWpyrj4= github.com/anhgelus/gokord v0.11.1-0.20250806000243-ddfebe2ca6f1 h1:irHDC/xUm65yLFx5HnVeCbM0qQRpm0i1vQHsyLXeEbo=
github.com/anhgelus/gokord v0.7.0/go.mod h1:SfGKyMMGjNS9F9ehiEb5Cc58P+uoDdLDGGYqXSiMCus= github.com/anhgelus/gokord v0.11.1-0.20250806000243-ddfebe2ca6f1/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.9.0 h1:vz7jHZ6papdt/xehe+nx4DxOLquPO6QukW8UzH81bGY= github.com/anhgelus/gokord v0.11.1-0.20250806003339-90cf89cde031 h1:56vqHQzCHCcMeBBhAWUyC466BETAhaIl1Qiq93WdrYI=
github.com/anhgelus/gokord v0.9.0/go.mod h1:NSepHjTV61LUnuyGgHxEhZNMnWREErGFyOtRYPgdx/E= github.com/anhgelus/gokord v0.11.1-0.20250806003339-90cf89cde031/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.10.0 h1:FaaMWntaezmSCvarcSMjfWr5OXVVwwzlDMnNX8gXaWE= github.com/anhgelus/gokord v0.11.1-0.20250806003704-21a4238c64a0 h1:Jj4ja4bshyEHOd+stqqB4e0iuggtDx/hzZ8K69+rtsE=
github.com/anhgelus/gokord v0.10.0/go.mod h1:NSepHjTV61LUnuyGgHxEhZNMnWREErGFyOtRYPgdx/E= github.com/anhgelus/gokord v0.11.1-0.20250806003704-21a4238c64a0/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250806004311-9988b375047f h1:OSTlmWDVRGw3bt4uHOBFyUWNNl5VV3zaB6Xj0TWlY0s=
github.com/anhgelus/gokord v0.11.1-0.20250806004311-9988b375047f/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250806122118-84c56722e29d h1:cbhaSdjjipiF5yFijgewtkjE0HyHZ6tJR7zHBpCkkBI=
github.com/anhgelus/gokord v0.11.1-0.20250806122118-84c56722e29d/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250806143823-567c33f63688 h1:0ngeLQxHr80Xup9UnPSjpdRH3ZpIH1mpf0ig3sEFVJk=
github.com/anhgelus/gokord v0.11.1-0.20250806143823-567c33f63688/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250807111049-5de23912c524 h1:mK7UtqJPNYhStRVeZ2N4c89tjlhlRZX0LcLs7TAB34I=
github.com/anhgelus/gokord v0.11.1-0.20250807111049-5de23912c524/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250821115246-50e5f7d17717 h1:KfcBHUpwbffRO6aIITq7iN7cP7KcKmUnIE+eWiwsYYw=
github.com/anhgelus/gokord v0.11.1-0.20250821115246-50e5f7d17717/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/anhgelus/gokord v0.11.1-0.20250821122244-0aee6c37eef6 h1:4eO/9UqTPfrKyss2CeLafeKeR06bgoFihudKOdLpWpI=
github.com/anhgelus/gokord v0.11.1-0.20250821122244-0aee6c37eef6/go.mod h1:4xpwLzIG34/XG9QZiPsnYScQhckiCpQMAI0CjP0Nc2k=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 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/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 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 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 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@ -26,8 +36,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 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 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 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.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= 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= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@ -40,28 +50,34 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/redis/go-redis/v9 v9.12.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.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -69,7 +85,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

177
main.go
View file

@ -4,15 +4,17 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"flag" "flag"
"os"
"time"
"github.com/anhgelus/gokord" "github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/cmd"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/commands" "github.com/anhgelus/les-copaings-bot/commands"
"github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/user" "github.com/anhgelus/les-copaings-bot/user"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"os"
"time"
) )
var ( var (
@ -21,8 +23,8 @@ var (
updatesData []byte updatesData []byte
Version = gokord.Version{ Version = gokord.Version{
Major: 3, Major: 3,
Minor: 1, Minor: 2,
Patch: 3, Patch: 0,
} }
stopPeriodicReducer chan<- interface{} stopPeriodicReducer chan<- interface{}
@ -31,7 +33,7 @@ var (
func init() { func init() {
err := godotenv.Load() err := godotenv.Load()
if err != nil && !errors.Is(err, os.ErrNotExist) { 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") flag.StringVar(&token, "token", os.Getenv("TOKEN"), "token of the bot")
} }
@ -51,87 +53,27 @@ func main() {
adm := gokord.AdminPermission adm := gokord.AdminPermission
rankCmd := gokord.NewCommand("rank", "Affiche le niveau d'un copaing"). rankCmd := cmd.New("rank", "Affiche le niveau d'un copaing").
AddOption(gokord.NewOption( AddOption(cmd.NewOption(
discordgo.ApplicationCommandOptionUser, discordgo.ApplicationCommandOptionUser,
"copaing", "copaing",
"Le niveau du Copaing que vous souhaitez obtenir", "Le niveau du Copaing que vous souhaitez obtenir",
)). )).
SetHandler(commands.Rank) SetHandler(commands.Rank)
configCmd := gokord.NewCommand("config", "Modifie la config"). configCmd := cmd.New("config", "Modifie la config").
ContainsSub(). SetPermission(&adm).
AddSub( SetHandler(commands.Config)
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"). topCmd := cmd.New("top", "Copaings les plus actifs").
SetHandler(commands.Top) SetHandler(commands.Top)
resetCmd := gokord.NewCommand("reset", "Reset l'xp"). resetCmd := cmd.New("reset", "Reset l'xp").
SetHandler(commands.Reset). SetHandler(commands.Reset).
SetPermission(&adm) SetPermission(&adm)
resetUserCmd := gokord.NewCommand("reset-user", "Reset l'xp d'un utilisation"). resetUserCmd := cmd.New("reset-user", "Reset l'xp d'un utilisation").
AddOption(gokord.NewOption( AddOption(cmd.NewOption(
discordgo.ApplicationCommandOptionUser, discordgo.ApplicationCommandOptionUser,
"user", "user",
"Copaing a reset", "Copaing a reset",
@ -139,7 +81,7 @@ func main() {
SetHandler(commands.ResetUser). SetHandler(commands.ResetUser).
SetPermission(&adm) SetPermission(&adm)
creditsCmd := gokord.NewCommand("credits", "Crédits"). creditsCmd := cmd.New("credits", "Crédits").
SetHandler(commands.Credits) SetHandler(commands.Credits)
innovations, err := gokord.LoadInnovationFromJson(updatesData) innovations, err := gokord.LoadInnovationFromJson(updatesData)
@ -167,7 +109,7 @@ func main() {
Content: "Les Copaings Bot " + Version.String(), Content: "Les Copaings Bot " + Version.String(),
}, },
}, },
Commands: []gokord.CommandBuilder{ Commands: []cmd.CommandBuilder{
rankCmd, rankCmd,
configCmd, configCmd,
topCmd, topCmd,
@ -175,26 +117,7 @@ func main() {
resetUserCmd, resetUserCmd,
creditsCmd, creditsCmd,
}, },
AfterInit: afterInit, AfterInit: func(dg *discordgo.Session) {
Innovations: innovations,
Version: &Version,
Intents: discordgo.IntentsAllWithoutPrivileged |
discordgo.IntentsMessageContent |
discordgo.IntentGuildMembers,
}
bot.Start()
if stopPeriodicReducer != nil {
stopPeriodicReducer <- true
}
}
func afterInit(dg *discordgo.Session) {
// handlers
dg.AddHandler(OnMessage)
dg.AddHandler(OnVoiceUpdate)
dg.AddHandler(OnLeave)
d := 24 * time.Hour d := 24 * time.Hour
if gokord.Debug { if gokord.Debug {
d = 24 * time.Second d = 24 * time.Second
@ -202,8 +125,64 @@ func afterInit(dg *discordgo.Session) {
user.PeriodicReducer(dg) user.PeriodicReducer(dg)
stopPeriodicReducer = utils.NewTimer(d, func(stop chan<- interface{}) { stopPeriodicReducer = gokord.NewTimer(d, func(stop chan<- interface{}) {
utils.SendDebug("Periodic reducer") logger.Debug("Periodic reducer")
user.PeriodicReducer(dg) user.PeriodicReducer(dg)
}) })
},
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
}
} }

View file

@ -1,3 +0,0 @@
#!/usr/bin/bash
podman compose down && podman compose build && podman compose up -d

View file

@ -28,5 +28,15 @@
"ping" "ping"
] ]
} }
},
{
"version": "3.2.0",
"commands": {
"added": [],
"removed": [],
"updated": [
"config"
]
}
} }
] ]

View file

@ -1,14 +1,15 @@
package user package user
import ( 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" "slices"
"sync" "sync"
"time" "time"
"github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/exp"
"github.com/bwmarrin/discordgo"
) )
func onNewLevel(dg *discordgo.Session, m *discordgo.Member, level uint) { 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) xpForLevel := exp.LevelXP(level)
for _, role := range cfg.XpRoles { for _, role := range cfg.XpRoles {
if role.XP <= xpForLevel && !slices.Contains(m.Roles, role.RoleID) { if role.XP <= xpForLevel && !slices.Contains(m.Roles, role.RoleID) {
utils.SendDebug( logger.Debug(
"Add role", "Add role",
"role_id", role.RoleID, "role_id", role.RoleID,
"user_id", m.User.ID, "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) err := dg.GuildMemberRoleAdd(m.GuildID, m.User.ID, role.RoleID)
if err != nil { 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) { } else if role.XP > xpForLevel && slices.Contains(m.Roles, role.RoleID) {
utils.SendDebug( logger.Debug(
"Remove role", "Remove role",
"role_id", role.RoleID, "role_id", role.RoleID,
"user_id", m.User.ID, "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) err := dg.GuildMemberRoleRemove(m.GuildID, m.User.ID, role.RoleID)
if err != nil { 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) { func (c *Copaing) OnNewLevel(dg *discordgo.Session, level uint) {
m, err := dg.GuildMember(c.GuildID, c.DiscordID) m, err := dg.GuildMember(c.GuildID, c.DiscordID)
if err != nil { if err != nil {
utils.SendAlert( logger.Alert(
"user/level.go - Getting member for new level", err.Error(), "user/level.go - Getting member for new level", err.Error(),
"discord_id", c.DiscordID, "discord_id", c.DiscordID,
"guild_id", c.GuildID, "guild_id", c.GuildID,
@ -58,7 +59,7 @@ func PeriodicReducer(dg *discordgo.Session) {
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
var cs []*Copaing var cs []*Copaing
if err := gokord.DB.Find(&cs).Error; err != nil { 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 return
} }
cxps := make([]*cXP, len(cs)) cxps := make([]*cXP, len(cs))
@ -71,7 +72,7 @@ func PeriodicReducer(dg *discordgo.Session) {
defer wg.Done() defer wg.Done()
xp, err := c.GetXP() xp, err := c.GetXP()
if err != nil { 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 xp = 0
} }
cxps[i] = &cXP{ cxps[i] = &cXP{
@ -91,26 +92,26 @@ func PeriodicReducer(dg *discordgo.Session) {
Where("guild_id = ? and created_at < ?", g.ID, exp.TimeStampNDaysBefore(cfg.DaysXPRemains)). Where("guild_id = ? and created_at < ?", g.ID, exp.TimeStampNDaysBefore(cfg.DaysXPRemains)).
Delete(&CopaingXP{}) Delete(&CopaingXP{})
if res.Error != nil { if res.Error != nil {
utils.SendAlert("user/level.go - Removing old XP", res.Error.Error(), "guild_id", g.ID) logger.Alert("user/level.go - Removing old XP", res.Error.Error(), "guild_id", g.ID)
} }
utils.SendDebug("Guild cleaned", "guild", g.Name, "rows affected", res.RowsAffected) logger.Debug("Guild cleaned", "guild", g.Name, "rows affected", res.RowsAffected)
}() }()
} }
wg.Wait() wg.Wait()
for i, c := range cxps { for i, c := range cxps {
if i%50 == 49 { if i%50 == 49 {
utils.SendDebug("Sleeping...") logger.Debug("Sleeping...")
time.Sleep(15 * time.Second) // prevents spamming the API time.Sleep(15 * time.Second) // prevents spamming the API
} }
oldXp := c.GetXP() oldXp := c.GetXP()
xp, err := c.ToCopaing().GetXP() xp, err := c.ToCopaing().GetXP()
if err != nil { 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 continue
} }
if exp.Level(oldXp) != exp.Level(xp) { if exp.Level(oldXp) != exp.Level(xp) {
c.OnNewLevel(dg, 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))
} }

View file

@ -2,7 +2,7 @@ package user
import ( import (
"github.com/anhgelus/gokord" "github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/logger"
"time" "time"
) )
@ -34,7 +34,7 @@ const (
func GetCopaing(discordID string, guildID string) *Copaing { func GetCopaing(discordID string, guildID string) *Copaing {
c := Copaing{DiscordID: discordID, GuildID: guildID} c := Copaing{DiscordID: discordID, GuildID: guildID}
if err := c.Load(); err != nil { if err := c.Load(); err != nil {
utils.SendAlert( logger.Alert(
"user/member.go - Loading user", "user/member.go - Loading user",
err.Error(), err.Error(),
"discord_id", "discord_id",

View file

@ -2,7 +2,7 @@ package user
import ( import (
"github.com/anhgelus/gokord" "github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/utils" "github.com/anhgelus/gokord/logger"
"github.com/anhgelus/les-copaings-bot/config" "github.com/anhgelus/les-copaings-bot/config"
"github.com/anhgelus/les-copaings-bot/exp" "github.com/anhgelus/les-copaings-bot/exp"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
@ -26,14 +26,14 @@ func (c *cXP) GetXP() uint {
func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) { func (c *Copaing) AddXP(s *discordgo.Session, m *discordgo.Member, xp uint, fn func(uint, uint)) {
old, err := c.GetXP() old, err := c.GetXP()
if err != nil { 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 return
} }
pastLevel := exp.Level(old) pastLevel := exp.Level(old)
utils.SendDebug("Adding xp", "member", m.DisplayName(), "old xp", old, "xp to add", xp, "old level", pastLevel) 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: xp, GuildID: c.GuildID}) c.CopaingXPs = append(c.CopaingXPs, CopaingXP{CopaingID: c.ID, XP: xp, GuildID: c.GuildID})
if err = c.Save(); err != nil { if err = c.Save(); err != nil {
utils.SendAlert( logger.Alert(
"user/xp.go - Saving user", "user/xp.go - Saving user",
err.Error(), err.Error(),
"xp", "xp",
@ -76,7 +76,7 @@ func (c *Copaing) GetXPForDays(n uint) (uint, error) {
var cxp CopaingXP var cxp CopaingXP
err = gokord.DB.ScanRows(rows, &cxp) err = gokord.DB.ScanRows(rows, &cxp)
if err != nil { 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 continue
} }
xp += cxp.XP xp += cxp.XP
@ -103,7 +103,7 @@ func GetBestXP(guildId string, n uint, d int) ([]CopaingAccess, error) {
var c Copaing var c Copaing
err = gokord.DB.ScanRows(rows, &c) err = gokord.DB.ScanRows(rows, &c)
if err != nil { 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 continue
} }
wg.Add(1) wg.Add(1)
@ -111,7 +111,7 @@ func GetBestXP(guildId string, n uint, d int) ([]CopaingAccess, error) {
defer wg.Done() defer wg.Done()
xp, err := c.GetXPForDays(uint(d)) xp, err := c.GetXPForDays(uint(d))
if err != nil { 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 return
} }
l = append(l, &cXP{Cxp: xp, Copaing: &c}) l = append(l, &cXP{Cxp: xp, Copaing: &c})