aboutsummaryrefslogtreecommitdiff
path: root/config
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2026-03-08 18:51:05 +0100
committerAnhgelus Morhtuuzh <william@herges.fr>2026-03-08 18:51:05 +0100
commit6e92feaba23a4992e0ec4b529660921a6bcb492a (patch)
tree05024b52d4c8ede041facdc48ac65af5ba0259da /config
parent3bcf74c47d1597ba650dc5a55868f83f5f547ad7 (diff)
feat(config): leave gorm
Diffstat (limited to 'config')
-rw-r--r--config/channel.go4
-rw-r--r--config/guild.go136
-rw-r--r--config/xp_reduce.go4
-rw-r--r--config/xp_role.go368
-rw-r--r--config/xp_role_events.go358
5 files changed, 485 insertions, 385 deletions
diff --git a/config/channel.go b/config/channel.go
index 5cc88aa..a015aef 100644
--- a/config/channel.go
+++ b/config/channel.go
@@ -21,7 +21,7 @@ const (
)
func HandleModifyFallbackChannel(ctx context.Context, dg bot.Session, i *interaction.MessageComponent) bool {
- cfg := GetGuildConfig(ctx, i.GuildID)
+ cfg := GetGuild(ctx, i.GuildID)
var channelID uint64
if len(i.Data.Values) > 0 {
var err error
@@ -40,7 +40,7 @@ func HandleModifyFallbackChannel(ctx context.Context, dg bot.Session, i *interac
}
func HandleModifyDisChannel(ctx context.Context, dg bot.Session, i *interaction.MessageComponent) bool {
- cfg := GetGuildConfig(ctx, i.GuildID)
+ cfg := GetGuild(ctx, i.GuildID)
cfg.DisabledChannels = strings.Join(i.Data.Values, ";")
err := cfg.Save(ctx)
if err != nil {
diff --git a/config/guild.go b/config/guild.go
index dc8251f..aec78ed 100644
--- a/config/guild.go
+++ b/config/guild.go
@@ -2,31 +2,33 @@ package config
import (
"context"
+ "database/sql"
+ "errors"
"fmt"
+ "slices"
"strings"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/common"
"github.com/nyttikord/gokord/bot"
"github.com/nyttikord/gokord/channel"
)
type Guild struct {
- ID uint `gorm:"primarykey"`
- GuildID uint64 `gorm:"not null;unique"`
- XpRoles []XpRole `gorm:"foreignKey:GuildConfigID"`
+ ID uint64
+ XpRoles []XpRole
DisabledChannels string
FallbackChannel uint64
- DaysXPRemains uint `gorm:"default:90"` // 30 * 3 = 90 (three months)
+ DaysXPRemains uint
RrMessages []RoleReactMessage
}
type RoleReactMessage struct {
- ID uint `gorm:"primarykey"`
- MessageID uint64 `gorm:"not null;unique"`
- ChannelID uint64
- GuildID uint64
- Note string
- Roles []*RoleReact
- GuildConfigID uint
+ ID uint `gorm:"primarykey"`
+ MessageID uint64 `gorm:"not null;unique"`
+ ChannelID uint64
+ GuildID uint64
+ Note string
+ Roles []*RoleReact
}
type RoleReact struct {
@@ -37,45 +39,102 @@ type RoleReact struct {
CounterID uint `gorm:"-"`
}
-func GetGuildConfig(ctx context.Context, guildID uint64) *Guild {
- cfg := Guild{GuildID: guildID}
+func GetGuild(ctx context.Context, guildID uint64) *Guild {
+ cfg := Guild{ID: guildID}
if err := cfg.Load(ctx); err != nil {
panic(err)
}
return &cfg
}
-func (cfg *Guild) Load(ctx context.Context) error {
- return nil //common.GetDB(ctx).Where("guild_id = ?", cfg.GuildID).Preload("XpRoles").FirstOrCreate(cfg).Error
+func (g *Guild) Load(ctx context.Context) error {
+ db := common.GetDB(ctx)
+ row := db.QueryRowContext(
+ ctx,
+ `SELECT disabled_channels, fallback_channel, days_xp_remains FROM guilds WHERE id = ?`,
+ g.ID,
+ )
+ g.RrMessages = nil
+ g.XpRoles = nil
+ err := row.Err()
+ if err != nil {
+ if !errors.Is(err, sql.ErrNoRows) {
+ return err
+ }
+ _, err = db.ExecContext(
+ ctx,
+ `INSERT INTO guilds (id) VALUES (?)`,
+ g.ID,
+ )
+ return err
+ }
+ err = row.Scan(&g.DisabledChannels, &g.FallbackChannel, &g.DaysXPRemains)
+ if err != nil {
+ return err
+ }
+ g.XpRoles, err = getXpRoles(ctx, g.ID)
+ return err
}
-func (cfg *Guild) Save(ctx context.Context) error {
- return nil //common.GetDB(ctx).Save(cfg).Error
+func (g *Guild) Save(ctx context.Context) error {
+ db := common.GetDB(ctx)
+ _, err := db.ExecContext(
+ ctx,
+ `UPDATE guilds SET disabled_channels = ?, fallback_channel = ?, days_xp_remains = ? WHERE id = ?`,
+ g.DisabledChannels, g.FallbackChannel, g.DaysXPRemains, g.ID,
+ )
+ if err != nil {
+ return err
+ }
+ savedRoles, err := getXpRoles(ctx, g.ID)
+ if err != nil {
+ return err
+ }
+ roleEqual := func(base XpRole) func(XpRole) bool {
+ return func(v XpRole) bool {
+ return v.GuildID == base.GuildID && v.RoleID == base.RoleID && v.XP == base.XP
+ }
+ }
+ // super slow code, but I don't want to implement a data structure to optimize this
+ for _, role := range g.XpRoles {
+ if !slices.ContainsFunc(savedRoles, roleEqual(role)) {
+ err = role.Save(ctx)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ for _, role := range savedRoles {
+ if !slices.ContainsFunc(g.XpRoles, roleEqual(role)) {
+ err = role.Delete(ctx)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
}
-func (cfg *Guild) IsDisabled(ctx context.Context, dg bot.Session, channelID uint64) bool {
+func (g *Guild) IsDisabled(ctx context.Context, dg bot.Session, channelID uint64) bool {
ok := true
for channelID != 0 && ok {
- ok = !strings.Contains(cfg.DisabledChannels, fmt.Sprintf("%d", channelID))
+ ok = !strings.Contains(g.DisabledChannels, fmt.Sprintf("%d", channelID))
c, err := dg.ChannelState().GetChannel(channelID)
if err != nil {
- bot.Logger(ctx).Error("unable to find channel %s in state", "error", err, "channel", c)
+ bot.Logger(ctx).Warn("unable to find channel %s in state", "error", err, "channel", c)
c, err = channel.Get(channelID).Do(ctx)
if err != nil {
bot.Logger(ctx).Error("unable to fetch channel", "error", err, "channel", c)
return false
}
}
- if err != nil {
- return false
- }
channelID = c.ParentID
}
return !ok
}
-func (cfg *Guild) FindXpRole(roleID uint64) (int, *XpRole) {
- for i, r := range cfg.XpRoles {
+func (g *Guild) FindXpRole(roleID uint64) (int, *XpRole) {
+ for i, r := range g.XpRoles {
if r.RoleID == roleID {
return i, &r
}
@@ -83,11 +142,28 @@ func (cfg *Guild) FindXpRole(roleID uint64) (int, *XpRole) {
return 0, nil
}
-func (cfg *Guild) FindXpRoleID(ID uint) (int, *XpRole) {
- for i, r := range cfg.XpRoles {
- if r.ID == ID {
- return i, &r
+func getXpRoles(ctx context.Context, gID uint64) ([]XpRole, error) {
+ roles := make([]XpRole, 0)
+ rows, err := common.GetDB(ctx).QueryContext(
+ ctx,
+ `SELECT xp, role FROM xp_roles WHERE guild_id = ?`,
+ gID,
+ )
+ if err == nil {
+ defer rows.Close()
+ for rows.Next() {
+ var role XpRole
+ err = rows.Scan(&role.XP, &role.RoleID)
+ if err != nil {
+ return roles, err
+ }
+ role.GuildID = gID
+ roles = append(roles, role)
+ }
+ } else {
+ if !errors.Is(err, sql.ErrNoRows) {
+ return roles, err
}
}
- return -1, nil
+ return roles, nil
}
diff --git a/config/xp_reduce.go b/config/xp_reduce.go
index 36e64ed..baa4586 100644
--- a/config/xp_reduce.go
+++ b/config/xp_reduce.go
@@ -16,7 +16,7 @@ const (
)
func HandleModifyPeriodicReduceCommand(ctx context.Context, dg bot.Session, i *interaction.MessageComponent) {
- cfg := GetGuildConfig(ctx, i.GuildID)
+ cfg := GetGuild(ctx, i.GuildID)
resp := interaction.NewModalResponse().
CustomID(TimeReduceSet).
Title("Modifier la durée de l'expérience").
@@ -66,7 +66,7 @@ func HandleTimeReduceSet(ctx context.Context, dg bot.Session, i *interaction.Mod
}
return false
}
- cfg := GetGuildConfig(ctx, i.GuildID)
+ cfg := GetGuild(ctx, i.GuildID)
cfg.DaysXPRemains = uint(days)
err = cfg.Save(ctx)
if err != nil {
diff --git a/config/xp_role.go b/config/xp_role.go
index 81082e8..f0d57c8 100644
--- a/config/xp_role.go
+++ b/config/xp_role.go
@@ -2,364 +2,30 @@ package config
import (
"context"
- "fmt"
- "slices"
- "strconv"
- "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
- "git.anhgelus.world/anhgelus/les-copaings-bot/exp"
- "github.com/nyttikord/gokord/bot"
- "github.com/nyttikord/gokord/channel"
- "github.com/nyttikord/gokord/component"
- "github.com/nyttikord/gokord/discord/types"
- "github.com/nyttikord/gokord/interaction"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/common"
)
type XpRole struct {
- ID uint `gorm:"primarykey"`
- XP uint
- RoleID uint64
- GuildConfigID uint
+ XP uint
+ RoleID uint64
+ GuildID uint64
}
-type XpRoleId struct {
- ID uint
-}
-
-const (
- ModifyXpRole = "xp_role"
- XpRoleNew = "xp_role_add"
- XpRoleAdd = "xp_role_add_level"
- XpRoleEdit = `xp_role_edit`
- XpRoleEditLevel = `xp_role_edit_level`
- XpRoleEditLevelStart = `xp_role_edit_level_start`
- XpRoleEditRole = `xp_role_edit_role`
- XpRoleDel = `xp_role_del`
-)
-
-func HandleXpRole(ctx context.Context, dg bot.Session, i *interaction.Interaction) {
- cfg := GetGuildConfig(ctx, i.GuildID)
- container := component.Container{
- Components: []component.Message{
- &component.TextDisplay{Content: "## Configuration / Rôles de niveaux"},
- &component.TextDisplay{Content: "Ces rôles seront donnés et retirés en fonction du niveau de chacun"},
- &component.Separator{},
- },
- }
- slices.SortFunc(cfg.XpRoles, func(xp1, xp2 XpRole) int {
- return int(xp2.XP) - int(xp1.XP)
- })
- for _, r := range cfg.XpRoles {
- container.Components = append(container.Components, &component.Section{
- Components: []component.Message{
- &component.TextDisplay{
- Content: fmt.Sprintf("<@&%s> - Niveau %d", r.RoleID, exp.Level(r.XP)),
- },
- },
- Accessory: &component.Button{
- CustomID: dynamicid.FormatCustomID(XpRoleEdit, XpRoleId{ID: r.ID}),
- Style: component.ButtonStyleSecondary,
- Label: "Modifier",
- },
- })
- }
- container.Components = append(container.Components,
- &component.ActionsRow{
- Components: []component.Message{
- &component.Button{
- CustomID: XpRoleNew,
- Style: component.ButtonStylePrimary,
- Label: "Nouveau rôle",
- },
- },
- },
- &component.Separator{},
- &component.ActionsRow{
- Components: []component.Message{
- &component.Button{CustomID: "config", Style: component.ButtonStyleSecondary, Label: "Retour"},
- },
- },
+func (xp XpRole) Save(ctx context.Context) error {
+ _, err := common.GetDB(ctx).ExecContext(
+ ctx,
+ `INSERT INTO xp_roles (xp, role, guild_id) VALUES (?, ?, ?)`,
+ xp.XP, xp.RoleID, xp.GuildID,
)
-
- response := &interaction.Response{
- Type: types.InteractionResponseUpdateMessage,
- Data: &interaction.ResponseData{
- Components: []component.Component{&container},
- Flags: channel.MessageFlagsIsComponentsV2,
- },
- }
- err := interaction.Respond(i, response).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending config", "error", err)
- }
-}
-
-func HandleXpRoleNew(ctx context.Context, dg bot.Session, i *interaction.MessageComponent) {
- one := 1
- resp := interaction.NewModalResponse().
- Title("Nouveau rôle de niveau").
- CustomID(XpRoleAdd).
- AddComponent(&component.Label{
- Label: "Niveau",
- Component: &component.TextInput{
- CustomID: "level",
- Style: component.TextInputShort,
- Placeholder: "5",
- MinLength: 1,
- MaxLength: 5,
- Required: true,
- },
- }).
- AddComponent(&component.Label{
- Label: "Rôle",
- Component: &component.SelectMenu{
- MenuType: types.SelectMenuRole,
- CustomID: "role",
- MinValues: &one,
- MaxValues: one,
- },
- }).
- Response()
- err := interaction.Respond(i.Interaction, resp).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending modal to add", "error", err)
- }
-}
-
-func HandleXpRoleEdit(ctx context.Context, dg bot.Session, i *interaction.Interaction, params *XpRoleId) {
- config := GetGuildConfig(ctx, i.GuildID)
- id := params.ID
- _, role := config.FindXpRoleID(id)
- if role == nil {
- HandleXpRole(ctx, dg, i)
- return
- }
-
- roleSelect := &component.SelectMenu{
- MenuType: types.SelectMenuRole,
- CustomID: dynamicid.FormatCustomID(XpRoleEditRole, XpRoleId{ID: id}),
- DefaultValues: []component.SelectMenuDefaultValue{
- {ID: role.RoleID, Type: types.SelectMenuDefaultValueRole},
- },
- }
-
- container := &component.Container{
- Components: []component.Message{
- &component.TextDisplay{Content: "## Configuration / Rôles de niveaux"},
- &component.Separator{},
- &component.Section{
- Components: []component.Message{
- &component.TextDisplay{Content: fmt.Sprintf("Niveau **%d**", exp.Level(role.XP))},
- },
- Accessory: &component.Button{
- CustomID: dynamicid.FormatCustomID(XpRoleEditLevelStart, XpRoleId{ID: id}),
- Style: component.ButtonStyleSecondary,
- Label: "Modifier",
- },
- },
- &component.ActionsRow{Components: []component.Message{roleSelect}},
- &component.ActionsRow{Components: []component.Message{
- &component.Button{
- CustomID: dynamicid.FormatCustomID(XpRoleDel, XpRoleId{ID: id}),
- Style: component.ButtonStyleDanger,
- Label: "Supprimer",
- },
- }},
- &component.Separator{},
- &component.ActionsRow{Components: []component.Message{
- &component.Button{Label: "Retour", CustomID: ModifyXpRole, Style: component.ButtonStyleSecondary},
- }},
- },
- }
-
- response := &interaction.Response{
- Type: types.InteractionResponseUpdateMessage,
- Data: &interaction.ResponseData{
- Components: []component.Component{container},
- Flags: channel.MessageFlagsIsComponentsV2,
- },
- }
-
- err := interaction.Respond(i, response).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending xp_role config", "error", err)
- }
-}
-
-func HandleXpRoleEditRole(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, params *XpRoleId) {
- id := params.ID
- role, err := strconv.ParseUint(i.Data.Values[0], 10, 64)
- if err != nil {
- // panic because must ensure that the role is valid before
- panic(err)
- }
- cfg := GetGuildConfig(ctx, i.GuildID)
- _, xpRole := cfg.FindXpRoleID(id)
- if xpRole == nil {
- err := interaction.Respond(i.Interaction, &interaction.Response{
- Type: types.InteractionResponseChannelMessageWithSource,
- Data: &interaction.ResponseData{
- Flags: channel.MessageFlagsEphemeral,
- Content: "Impossible de modifier le rôle. Peut-être a-t-il été supprimé ?",
- },
- }).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending unable to get role message", "error", err)
- }
- return
- }
- xpRole.RoleID = role
- err = nil // common.GetDB(ctx).Save(xpRole).Error
- if err != nil {
- bot.Logger(ctx).Error("saving config", "error", err, "guild", i.GuildID, "id", id, "type", "add")
- }
- HandleXpRoleEdit(ctx, dg, i.Interaction, params)
+ return err
}
-func HandleXpRoleEditLevelStart(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, params *XpRoleId) {
- id := params.ID
- cfg := GetGuildConfig(ctx, i.GuildID)
- _, xpRole := cfg.FindXpRoleID(id)
- if xpRole == nil {
- err := interaction.Respond(i.Interaction, &interaction.Response{
- Type: types.InteractionResponseChannelMessageWithSource,
- Data: &interaction.ResponseData{
- Flags: channel.MessageFlagsEphemeral,
- Content: "Impossible de trouver le rôle. Peut-être a-t-il été supprimé ?",
- },
- }).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending unable to get role message", "error", err)
- }
- return
- }
- response := &interaction.Response{
- Type: types.InteractionResponseModal,
- Data: &interaction.ResponseData{
- Title: "Modification du niveau lié au rôle",
- CustomID: dynamicid.FormatCustomID(XpRoleEditLevel, XpRoleId{ID: id}),
- Components: []component.Component{
- &component.Label{
- Label: "Nouveau niveau",
- Component: &component.TextInput{
- Style: component.TextInputShort,
- Required: true,
- CustomID: "level",
- MinLength: 1,
- MaxLength: 5,
- Placeholder: "5",
- Value: strconv.FormatUint(uint64(exp.Level(xpRole.XP)), 10),
- },
- },
- },
- },
- }
- err := interaction.Respond(i.Interaction, response).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending edit level modal", "error", err)
- }
-}
-
-func HandleXpRoleEditLevel(ctx context.Context, dg bot.Session, i *interaction.ModalSubmit, params *XpRoleId) {
- id := params.ID
-
- levelInput := i.Data.Components[0].(*component.Label).Component.(*component.TextInput)
- level, err := strconv.Atoi(levelInput.Value)
- if err != nil || level < 0 {
- resp := interaction.NewMessageResponse().
- IsEphemeral().
- Message(fmt.Sprintf("Le niveau doit être un nombre entier positif.\n-# Trouvé : %s", levelInput.Value)).
- Response()
- err = interaction.Respond(i.Interaction, resp).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending bad number warning message", "error", err)
- }
- return
- }
- xp := exp.LevelXP(uint(level))
-
- cfg := GetGuildConfig(ctx, i.GuildID)
- _, xpRole := cfg.FindXpRoleID(id)
- if xpRole == nil {
- err = interaction.Respond(i.Interaction, &interaction.Response{
- Type: types.InteractionResponseChannelMessageWithSource,
- Data: &interaction.ResponseData{
- Flags: channel.MessageFlagsEphemeral,
- Content: "Impossible de modifier le rôle. Peut-être a-t-il été supprimé ?",
- },
- }).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending unable to modify role message", "error", err)
- }
- return
- }
- xpRole.XP = xp
- err = nil //common.GetDB(ctx).Save(xpRole).Error
- if err != nil {
- bot.Logger(ctx).Error("saving config", "guild", i.GuildID, "id", id, "type", "edit")
- }
- HandleXpRoleEdit(ctx, dg, i.Interaction, params)
-}
-
-func HandleXpRoleDel(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, parameters *XpRoleId) {
- id := parameters.ID
- cfg := GetGuildConfig(ctx, i.GuildID)
- _, role := cfg.FindXpRoleID(id)
- if role == nil {
- err := interaction.Respond(i.Interaction, &interaction.Response{
- Type: types.InteractionResponseChannelMessageWithSource,
- Data: &interaction.ResponseData{
- Content: "Rôle introuvable. Peut-être a-t-il déjà été supprimé ?",
- Flags: channel.MessageFlagsEphemeral,
- },
- }).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending role not found message", "error", err)
- }
- return
- }
- var err error = nil //common.GetDB(ctx).Delete(role).Error
- if err != nil {
- bot.Logger(ctx).Error("deleting entry", "error", err, "guild", i.GuildID, "id", id, "type", "del")
- }
-
- HandleXpRole(ctx, dg, i.Interaction)
-}
-
-func HandleXpRoleAdd(ctx context.Context, dg bot.Session, i *interaction.ModalSubmit) {
- levelInput := i.Data.Components[0].(*component.Label).Component.(*component.TextInput)
-
- in, err := strconv.Atoi(levelInput.Value)
- if err != nil || in < 0 {
- resp := interaction.NewMessageResponse().
- IsEphemeral().
- Message(fmt.Sprintf("Le niveau doit être un nombre entier positif.\n-# Trouvé : %s", levelInput.Value)).
- Response()
- err = interaction.Respond(i.Interaction, resp).Do(ctx)
- if err != nil {
- bot.Logger(ctx).Error("sending bad number warning message", "error", err)
- }
- return
- }
- xp := exp.LevelXP(uint(in))
-
- rawRoleId := i.Data.Components[1].(*component.Label).Component.(*component.SelectMenu).Values[0]
- roleId, err := strconv.ParseUint(rawRoleId, 10, 64)
- if err != nil {
- // panic because select menu must ensure that the value is valid
- panic(err)
- }
-
- cfg := GetGuildConfig(ctx, i.GuildID)
- cfg.XpRoles = append(cfg.XpRoles, XpRole{
- XP: xp,
- RoleID: roleId,
- })
- err = cfg.Save(ctx)
- if err != nil {
- bot.Logger(ctx).Error("saving config", "error", err, "role", roleId, "guild", i.GuildID)
- return
- }
-
- HandleXpRole(ctx, dg, i.Interaction)
+func (xp XpRole) Delete(ctx context.Context) error {
+ _, err := common.GetDB(ctx).ExecContext(
+ ctx,
+ `DELETE FROM xp_roles WHERE xp = ? AND role = ? AND guild_id = ?`,
+ xp.XP, xp.RoleID, xp.GuildID,
+ )
+ return err
}
diff --git a/config/xp_role_events.go b/config/xp_role_events.go
new file mode 100644
index 0000000..cda90d5
--- /dev/null
+++ b/config/xp_role_events.go
@@ -0,0 +1,358 @@
+package config
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "strconv"
+
+ "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/exp"
+ "github.com/nyttikord/gokord/bot"
+ "github.com/nyttikord/gokord/channel"
+ "github.com/nyttikord/gokord/component"
+ "github.com/nyttikord/gokord/discord/types"
+ "github.com/nyttikord/gokord/interaction"
+)
+
+type XpRoleId struct {
+ ID uint64
+}
+
+const (
+ ModifyXpRole = "xp_role"
+ XpRoleNew = "xp_role_add"
+ XpRoleAdd = "xp_role_add_level"
+ XpRoleEdit = `xp_role_edit`
+ XpRoleEditLevel = `xp_role_edit_level`
+ XpRoleEditLevelStart = `xp_role_edit_level_start`
+ XpRoleEditRole = `xp_role_edit_role`
+ XpRoleDel = `xp_role_del`
+)
+
+func HandleXpRole(ctx context.Context, dg bot.Session, i *interaction.Interaction) {
+ cfg := GetGuild(ctx, i.GuildID)
+ container := component.Container{
+ Components: []component.Message{
+ &component.TextDisplay{Content: "## Configuration / Rôles de niveaux"},
+ &component.TextDisplay{Content: "Ces rôles seront donnés et retirés en fonction du niveau de chacun"},
+ &component.Separator{},
+ },
+ }
+ slices.SortFunc(cfg.XpRoles, func(xp1, xp2 XpRole) int {
+ return int(xp2.XP) - int(xp1.XP)
+ })
+ for _, r := range cfg.XpRoles {
+ container.Components = append(container.Components, &component.Section{
+ Components: []component.Message{
+ &component.TextDisplay{
+ Content: fmt.Sprintf("<@&%d> - Niveau %d", r.RoleID, exp.Level(r.XP)),
+ },
+ },
+ Accessory: &component.Button{
+ CustomID: dynamicid.FormatCustomID(XpRoleEdit, XpRoleId{ID: r.RoleID}),
+ Style: component.ButtonStyleSecondary,
+ Label: "Modifier",
+ },
+ })
+ }
+ container.Components = append(container.Components,
+ &component.ActionsRow{
+ Components: []component.Message{
+ &component.Button{
+ CustomID: XpRoleNew,
+ Style: component.ButtonStylePrimary,
+ Label: "Nouveau rôle",
+ },
+ },
+ },
+ &component.Separator{},
+ &component.ActionsRow{
+ Components: []component.Message{
+ &component.Button{CustomID: "config", Style: component.ButtonStyleSecondary, Label: "Retour"},
+ },
+ },
+ )
+
+ response := &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Components: []component.Component{&container},
+ Flags: channel.MessageFlagsIsComponentsV2,
+ },
+ }
+ err := interaction.Respond(i, response).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending config", "error", err)
+ }
+}
+
+func HandleXpRoleNew(ctx context.Context, dg bot.Session, i *interaction.MessageComponent) {
+ one := 1
+ resp := interaction.NewModalResponse().
+ Title("Nouveau rôle de niveau").
+ CustomID(XpRoleAdd).
+ AddComponent(&component.Label{
+ Label: "Niveau",
+ Component: &component.TextInput{
+ CustomID: "level",
+ Style: component.TextInputShort,
+ Placeholder: "5",
+ MinLength: 1,
+ MaxLength: 5,
+ Required: true,
+ },
+ }).
+ AddComponent(&component.Label{
+ Label: "Rôle",
+ Component: &component.SelectMenu{
+ MenuType: types.SelectMenuRole,
+ CustomID: "role",
+ MinValues: &one,
+ MaxValues: one,
+ },
+ }).
+ Response()
+ err := interaction.Respond(i.Interaction, resp).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending modal to add", "error", err)
+ }
+}
+
+func HandleXpRoleEdit(ctx context.Context, dg bot.Session, i *interaction.Interaction, params *XpRoleId) {
+ config := GetGuild(ctx, i.GuildID)
+ id := params.ID
+ _, role := config.FindXpRole(id)
+ if role == nil {
+ HandleXpRole(ctx, dg, i)
+ return
+ }
+
+ roleSelect := &component.SelectMenu{
+ MenuType: types.SelectMenuRole,
+ CustomID: dynamicid.FormatCustomID(XpRoleEditRole, XpRoleId{ID: id}),
+ DefaultValues: []component.SelectMenuDefaultValue{
+ {ID: role.RoleID, Type: types.SelectMenuDefaultValueRole},
+ },
+ }
+
+ container := &component.Container{
+ Components: []component.Message{
+ &component.TextDisplay{Content: "## Configuration / Rôles de niveaux"},
+ &component.Separator{},
+ &component.Section{
+ Components: []component.Message{
+ &component.TextDisplay{Content: fmt.Sprintf("Niveau **%d**", exp.Level(role.XP))},
+ },
+ Accessory: &component.Button{
+ CustomID: dynamicid.FormatCustomID(XpRoleEditLevelStart, XpRoleId{ID: id}),
+ Style: component.ButtonStyleSecondary,
+ Label: "Modifier",
+ },
+ },
+ &component.ActionsRow{Components: []component.Message{roleSelect}},
+ &component.ActionsRow{Components: []component.Message{
+ &component.Button{
+ CustomID: dynamicid.FormatCustomID(XpRoleDel, XpRoleId{ID: id}),
+ Style: component.ButtonStyleDanger,
+ Label: "Supprimer",
+ },
+ }},
+ &component.Separator{},
+ &component.ActionsRow{Components: []component.Message{
+ &component.Button{Label: "Retour", CustomID: ModifyXpRole, Style: component.ButtonStyleSecondary},
+ }},
+ },
+ }
+
+ response := &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Components: []component.Component{container},
+ Flags: channel.MessageFlagsIsComponentsV2,
+ },
+ }
+
+ err := interaction.Respond(i, response).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending xp_role config", "error", err)
+ }
+}
+
+func HandleXpRoleEditRole(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, params *XpRoleId) {
+ id := params.ID
+ role, err := strconv.ParseUint(i.Data.Values[0], 10, 64)
+ if err != nil {
+ // panic because must ensure that the role is valid before
+ panic(err)
+ }
+ cfg := GetGuild(ctx, i.GuildID)
+ _, xpRole := cfg.FindXpRole(id)
+ if xpRole == nil {
+ err := interaction.Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral,
+ Content: "Impossible de modifier le rôle. Peut-être a-t-il été supprimé ?",
+ },
+ }).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending unable to get role message", "error", err)
+ }
+ return
+ }
+ xpRole.RoleID = role
+ err = xpRole.Save(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("saving config", "error", err, "guild", i.GuildID, "id", id, "type", "add")
+ }
+ HandleXpRoleEdit(ctx, dg, i.Interaction, params)
+}
+
+func HandleXpRoleEditLevelStart(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, params *XpRoleId) {
+ id := params.ID
+ cfg := GetGuild(ctx, i.GuildID)
+ _, xpRole := cfg.FindXpRole(id)
+ if xpRole == nil {
+ err := interaction.Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral,
+ Content: "Impossible de trouver le rôle. Peut-être a-t-il été supprimé ?",
+ },
+ }).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending unable to get role message", "error", err)
+ }
+ return
+ }
+ response := &interaction.Response{
+ Type: types.InteractionResponseModal,
+ Data: &interaction.ResponseData{
+ Title: "Modification du niveau lié au rôle",
+ CustomID: dynamicid.FormatCustomID(XpRoleEditLevel, XpRoleId{ID: id}),
+ Components: []component.Component{
+ &component.Label{
+ Label: "Nouveau niveau",
+ Component: &component.TextInput{
+ Style: component.TextInputShort,
+ Required: true,
+ CustomID: "level",
+ MinLength: 1,
+ MaxLength: 5,
+ Placeholder: "5",
+ Value: strconv.FormatUint(uint64(exp.Level(xpRole.XP)), 10),
+ },
+ },
+ },
+ },
+ }
+ err := interaction.Respond(i.Interaction, response).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending edit level modal", "error", err)
+ }
+}
+
+func HandleXpRoleEditLevel(ctx context.Context, dg bot.Session, i *interaction.ModalSubmit, params *XpRoleId) {
+ id := params.ID
+
+ levelInput := i.Data.Components[0].(*component.Label).Component.(*component.TextInput)
+ level, err := strconv.Atoi(levelInput.Value)
+ if err != nil || level < 0 {
+ resp := interaction.NewMessageResponse().
+ IsEphemeral().
+ Message(fmt.Sprintf("Le niveau doit être un nombre entier positif.\n-# Trouvé : %s", levelInput.Value)).
+ Response()
+ err = interaction.Respond(i.Interaction, resp).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending bad number warning message", "error", err)
+ }
+ return
+ }
+ xp := exp.LevelXP(uint(level))
+
+ cfg := GetGuild(ctx, i.GuildID)
+ _, xpRole := cfg.FindXpRole(id)
+ if xpRole == nil {
+ err = interaction.Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral,
+ Content: "Impossible de modifier le rôle. Peut-être a-t-il été supprimé ?",
+ },
+ }).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending unable to modify role message", "error", err)
+ }
+ return
+ }
+ xpRole.XP = xp
+ err = xpRole.Save(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("saving config", "guild", i.GuildID, "id", id, "type", "edit")
+ }
+ HandleXpRoleEdit(ctx, dg, i.Interaction, params)
+}
+
+func HandleXpRoleDel(ctx context.Context, dg bot.Session, i *interaction.MessageComponent, parameters *XpRoleId) {
+ id := parameters.ID
+ cfg := GetGuild(ctx, i.GuildID)
+ _, role := cfg.FindXpRole(id)
+ if role == nil {
+ err := interaction.Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{
+ Content: "Rôle introuvable. Peut-être a-t-il déjà été supprimé ?",
+ Flags: channel.MessageFlagsEphemeral,
+ },
+ }).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending role not found message", "error", err)
+ }
+ return
+ }
+ err := role.Delete(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("deleting entry", "error", err, "guild", i.GuildID, "id", id, "type", "del")
+ }
+
+ HandleXpRole(ctx, dg, i.Interaction)
+}
+
+func HandleXpRoleAdd(ctx context.Context, dg bot.Session, i *interaction.ModalSubmit) {
+ levelInput := i.Data.Components[0].(*component.Label).Component.(*component.TextInput)
+
+ in, err := strconv.Atoi(levelInput.Value)
+ if err != nil || in < 0 {
+ resp := interaction.NewMessageResponse().
+ IsEphemeral().
+ Message(fmt.Sprintf("Le niveau doit être un nombre entier positif.\n-# Trouvé : %s", levelInput.Value)).
+ Response()
+ err = interaction.Respond(i.Interaction, resp).Do(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("sending bad number warning message", "error", err)
+ }
+ return
+ }
+ xp := exp.LevelXP(uint(in))
+
+ rawRoleId := i.Data.Components[1].(*component.Label).Component.(*component.SelectMenu).Values[0]
+ roleId, err := strconv.ParseUint(rawRoleId, 10, 64)
+ if err != nil {
+ // panic because select menu must ensure that the value is valid
+ panic(err)
+ }
+
+ cfg := GetGuild(ctx, i.GuildID)
+ cfg.XpRoles = append(cfg.XpRoles, XpRole{
+ XP: xp,
+ RoleID: roleId,
+ })
+ err = cfg.Save(ctx)
+ if err != nil {
+ bot.Logger(ctx).Error("saving config", "error", err, "role", roleId, "guild", i.GuildID)
+ return
+ }
+
+ HandleXpRole(ctx, dg, i.Interaction)
+}