aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorascpial <mail@ascpial.fr>2025-09-27 23:35:32 +0200
committerGitHub <noreply@github.com>2025-09-27 23:35:32 +0200
commitbc86bb4859c4537032f9ca8d57ac32cc14dbd629 (patch)
treee94686d7b091857788fe3f1b582f6ce540e00d71
parentcfdba5f417bb31aac564d13becc09874f17d075d (diff)
[Feat] Role reaction (#15)
* first draft of rolereact * fix(rolereact): fill description when setting it * fix(rolereact): fix some issues * feat(rolereact): split the code in multiple files
-rw-r--r--config/guild.go19
-rw-r--r--config/xp_role.go99
-rw-r--r--dynamicid/encoding.go115
-rw-r--r--dynamicid/handling.go74
-rw-r--r--go.mod2
-rw-r--r--main.go128
-rw-r--r--rolereact/events.go46
-rw-r--r--rolereact/manager.go143
-rw-r--r--rolereact/rolereact.go543
-rw-r--r--rolereact/views.go212
10 files changed, 1257 insertions, 124 deletions
diff --git a/config/guild.go b/config/guild.go
index e046ca7..8799186 100644
--- a/config/guild.go
+++ b/config/guild.go
@@ -14,6 +14,25 @@ type GuildConfig struct {
DisabledChannels string
FallbackChannel string
DaysXPRemains uint `gorm:"default:90"` // 30 * 3 = 90 (three months)
+ RrMessages []RoleReactMessage
+}
+
+type RoleReactMessage struct {
+ ID uint `gorm:"primarykey"`
+ MessageID string `gorm:"not null;unique"`
+ ChannelID string
+ GuildID string
+ Note string
+ Roles []*RoleReact
+ GuildConfigID uint
+}
+
+type RoleReact struct {
+ ID uint `gorm:"primarykey"`
+ Reaction string
+ RoleID string
+ RoleReactMessageID uint
+ CounterID uint `gorm:"-"`
}
func GetGuildConfig(guildID string) *GuildConfig {
diff --git a/config/xp_role.go b/config/xp_role.go
index f857289..2feab59 100644
--- a/config/xp_role.go
+++ b/config/xp_role.go
@@ -5,6 +5,7 @@ import (
"slices"
"strconv"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
"git.anhgelus.world/anhgelus/les-copaings-bot/exp"
"github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/cmd"
@@ -23,15 +24,19 @@ type XpRole struct {
GuildConfigID uint
}
+type XpRoleId struct {
+ ID uint
+}
+
const (
- ModifyXpRole = "xp_role"
- XpRoleNew = "xp_role_add"
- XpRoleAdd = "xp_role_add_level"
- XpRoleEditPattern = `^xp_role_edit_(\d+)$`
- XpRoleEditLevelPattern = `^xp_role_edit_level_(\d+)$`
- XpRoleEditLevelStartPattern = `^xp_role_edit_level_start_(\d+)$`
- XpRoleEditRolePattern = `^xp_role_edit_role_(\d+)$`
- XpRoleDel = `^xp_role_del_(\d+)$`
+ 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(
@@ -59,7 +64,7 @@ func HandleXpRole(
},
},
Accessory: &component.Button{
- CustomID: fmt.Sprintf("xp_role_edit_%d", r.ID),
+ CustomID: dynamicid.FormatCustomID(XpRoleEdit, XpRoleId{ID: r.ID}),
Style: component.ButtonStyleSecondary,
Label: "Modifier",
},
@@ -142,14 +147,10 @@ func HandleXpRoleEdit(
s bot.Session,
i *event.InteractionCreate,
_ *interaction.MessageComponentData,
- parameters []string, resp *cmd.ResponseBuilder,
+ parameters *XpRoleId, resp *cmd.ResponseBuilder,
) {
config := GetGuildConfig(i.GuildID)
- id, err := getRoleLevelID(parameters)
- if err != nil {
- s.Logger().Error("reading dynamic CustomID", "error", err)
- return
- }
+ id := parameters.ID
_, role := config.FindXpRoleID(id)
if role == nil {
HandleXpRole(s, i, &interaction.MessageComponentData{}, resp)
@@ -158,7 +159,7 @@ func HandleXpRoleEdit(
roleSelect := &component.SelectMenu{
MenuType: types.SelectMenuRole,
- CustomID: fmt.Sprintf("xp_role_edit_role_%d", id),
+ CustomID: dynamicid.FormatCustomID(XpRoleEditRole, XpRoleId{ID: id}),
DefaultValues: []component.SelectMenuDefaultValue{
{ID: role.RoleID, Type: types.SelectMenuDefaultValueRole},
},
@@ -173,14 +174,18 @@ func HandleXpRoleEdit(
&component.TextDisplay{Content: fmt.Sprintf("Niveau **%d**", exp.Level(role.XP))},
},
Accessory: &component.Button{
- CustomID: fmt.Sprintf("xp_role_edit_level_start_%d", id),
+ 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: fmt.Sprintf("xp_role_del_%d", id), Style: component.ButtonStyleDanger, Label: "Supprimer"},
+ &component.Button{
+ CustomID: dynamicid.FormatCustomID(XpRoleDel, XpRoleId{ID: id}),
+ Style: component.ButtonStyleDanger,
+ Label: "Supprimer",
+ },
}},
&component.Separator{},
&component.ActionsRow{Components: []component.Message{
@@ -197,7 +202,7 @@ func HandleXpRoleEdit(
},
}
- err = s.InteractionAPI().Respond(i.Interaction, response)
+ err := s.InteractionAPI().Respond(i.Interaction, response)
if err != nil {
s.Logger().Error("sending xp_role config", "error", err)
}
@@ -207,18 +212,14 @@ func HandleXpRoleEditRole(
s bot.Session,
i *event.InteractionCreate,
data *interaction.MessageComponentData,
- parameters []string, resp *cmd.ResponseBuilder,
+ parameters *XpRoleId, resp *cmd.ResponseBuilder,
) {
- id, err := getRoleLevelID(parameters)
- if err != nil {
- s.Logger().Error("reading dynamic CustomID", "error", err)
- return
- }
+ id := parameters.ID
role := data.Values[0]
cfg := GetGuildConfig(i.GuildID)
_, xpRole := cfg.FindXpRoleID(id)
if xpRole == nil {
- err = s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
Type: types.InteractionResponseChannelMessageWithSource,
Data: &interaction.ResponseData{
Flags: channel.MessageFlagsEphemeral,
@@ -231,7 +232,7 @@ func HandleXpRoleEditRole(
return
}
xpRole.RoleID = role
- err = gokord.DB.Save(xpRole).Error
+ err := gokord.DB.Save(xpRole).Error
if err != nil {
s.Logger().Error("saving config", "error", err, "guild", i.GuildID, "id", id, "type", "add")
}
@@ -242,18 +243,14 @@ func HandleXpRoleEditLevelStart(
s bot.Session,
i *event.InteractionCreate,
_ *interaction.MessageComponentData,
- parameters []string,
+ parameters *XpRoleId,
_ *cmd.ResponseBuilder,
) {
- id, err := getRoleLevelID(parameters)
- if err != nil {
- s.Logger().Error("reading dynamic CustomID", "error", err)
- return
- }
+ id := parameters.ID
cfg := GetGuildConfig(i.GuildID)
_, xpRole := cfg.FindXpRoleID(id)
if xpRole == nil {
- err = s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
Type: types.InteractionResponseChannelMessageWithSource,
Data: &interaction.ResponseData{
Flags: channel.MessageFlagsEphemeral,
@@ -269,7 +266,7 @@ func HandleXpRoleEditLevelStart(
Type: types.InteractionResponseModal,
Data: &interaction.ResponseData{
Title: "Modification du niveau lié au rôle",
- CustomID: fmt.Sprintf("xp_role_edit_level_%d", id),
+ CustomID: dynamicid.FormatCustomID(XpRoleEditLevel, XpRoleId{ID: id}),
Components: []component.Component{
&component.Label{
Label: "Nouveau niveau",
@@ -286,7 +283,7 @@ func HandleXpRoleEditLevelStart(
},
},
}
- err = s.InteractionAPI().Respond(i.Interaction, response)
+ err := s.InteractionAPI().Respond(i.Interaction, response)
if err != nil {
s.Logger().Error("sending edit level modal", "error", err)
}
@@ -296,16 +293,11 @@ func HandleXpRoleEditLevel(
s bot.Session,
i *event.InteractionCreate,
data *interaction.ModalSubmitData,
- parameters []string,
+ parameters *XpRoleId,
resp *cmd.ResponseBuilder,
) {
- id, err := getRoleLevelID(parameters)
- if err != nil {
- s.Logger().Error("reading dynamic CustomID", "error", err)
- return
- }
+ id := parameters.ID
- fmt.Printf("Alors?... %#v", data.Components)
levelInput := data.Components[0].(*component.Label).Component.(*component.TextInput)
level, err := strconv.Atoi(levelInput.Value)
if err != nil || level < 0 {
@@ -348,18 +340,14 @@ func HandleXpRoleDel(
s bot.Session,
i *event.InteractionCreate,
_ *interaction.MessageComponentData,
- dynamicValues []string,
+ parameters *XpRoleId,
resp *cmd.ResponseBuilder,
) {
- id, err := getRoleLevelID(dynamicValues)
- if err != nil {
- s.Logger().Error("reading dynamic CustomID", "error", err)
- return
- }
+ id := parameters.ID
cfg := GetGuildConfig(i.GuildID)
_, role := cfg.FindXpRoleID(id)
if role == nil {
- err = s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
Type: types.InteractionResponseChannelMessageWithSource,
Data: &interaction.ResponseData{
Content: "Rôle introuvable. Peut-être a-t-il déjà été supprimé ?",
@@ -371,7 +359,7 @@ func HandleXpRoleDel(
}
return
}
- err = gokord.DB.Delete(role).Error
+ err := gokord.DB.Delete(role).Error
if err != nil {
s.Logger().Error("deleting entry", "error", err, "guild", i.GuildID, "id", id, "type", "del")
}
@@ -416,12 +404,3 @@ func HandleXpRoleAdd(
HandleXpRole(s, i, &interaction.MessageComponentData{}, resp)
}
-
-func getRoleLevelID(dynamic []string) (uint, error) {
- id64, err := strconv.ParseUint(dynamic[0], 10, 0)
- if err != nil {
- return 0, err
- }
-
- return uint(id64), nil
-}
diff --git a/dynamicid/encoding.go b/dynamicid/encoding.go
new file mode 100644
index 0000000..23d00db
--- /dev/null
+++ b/dynamicid/encoding.go
@@ -0,0 +1,115 @@
+package dynamicid
+
+import (
+ "encoding/csv"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+)
+
+var (
+ stringReflectType = reflect.TypeOf(string(""))
+ intReflectType = reflect.TypeOf(int(0))
+ uintReflectType = reflect.TypeOf(uint(0))
+ boolReflectType = reflect.TypeOf(bool(false))
+)
+
+// UnmarshallCSV record into a struct in-place
+func UnmarshallCSV(data string, v any) error {
+ r := csv.NewReader(strings.NewReader(data))
+ record, err := r.Read()
+ if err != nil {
+ return err
+ }
+ s := reflect.ValueOf(v).Elem()
+ t := s.Type()
+ if s.NumField() != len(record) {
+ return &ErrFieldMismatch{s.NumField(), len(record)}
+ }
+ for i := 0; i < s.NumField(); i++ {
+ f := s.Field(i)
+ if t.Field(i).Tag.Get("cid") != "-" {
+ switch f.Type() {
+ case stringReflectType:
+ f.SetString(record[i])
+ case intReflectType:
+ v, err := strconv.ParseInt(record[i], 10, 0)
+ if err != nil {
+ return err
+ }
+ f.SetInt(v)
+ case uintReflectType:
+ v, err := strconv.ParseUint(record[i], 10, 0)
+ if err != nil {
+ return err
+ }
+ f.SetUint(v)
+ case boolReflectType:
+ switch record[i] {
+ case "0":
+ f.SetBool(false)
+ case "1":
+ f.SetBool(true)
+ default:
+ return &ErrUnreadable{"boolean", record[i]}
+ }
+ default:
+ return &ErrUnsupportedType{Type: f.Type().String()}
+ }
+ }
+ }
+ return nil
+}
+
+// MarshallCSV from a struct
+func MarshallCSV(v any) string {
+ s := reflect.ValueOf(v)
+ r := make([]string, 0)
+ for i := 0; i < s.NumField(); i++ {
+ f := s.Field(i)
+ switch f.Type() {
+ case stringReflectType:
+ r = append(r, f.String())
+ case intReflectType:
+ r = append(r, strconv.FormatInt(f.Int(), 10))
+ case uintReflectType:
+ r = append(r, strconv.FormatUint(f.Uint(), 10))
+ case boolReflectType:
+ if f.Bool() {
+ r = append(r, "1")
+ } else {
+ r = append(r, "0")
+ }
+ }
+ }
+ b := new(strings.Builder)
+ w := csv.NewWriter(b)
+ w.Write(r)
+ w.Flush()
+ return b.String()
+}
+
+type ErrFieldMismatch struct {
+ Expected, Found int
+}
+
+func (e *ErrFieldMismatch) Error() string {
+ return fmt.Sprintf("CSV line fields mismatch. Expected %d found %d", e.Expected, e.Found)
+}
+
+type ErrUnreadable struct {
+ Format, Found string
+}
+
+func (e *ErrUnreadable) Error() string {
+ return fmt.Sprintf("Unreadable value as %s. Found %s", e.Format, e.Found)
+}
+
+type ErrUnsupportedType struct {
+ Type string
+}
+
+func (e *ErrUnsupportedType) Error() string {
+ return "Unsupported type: " + e.Type
+}
diff --git a/dynamicid/handling.go b/dynamicid/handling.go
new file mode 100644
index 0000000..8369e27
--- /dev/null
+++ b/dynamicid/handling.go
@@ -0,0 +1,74 @@
+package dynamicid
+
+import (
+ "strings"
+
+ "github.com/anhgelus/gokord"
+ "github.com/anhgelus/gokord/cmd"
+ "github.com/nyttikord/gokord/bot"
+ "github.com/nyttikord/gokord/discord/types"
+ "github.com/nyttikord/gokord/event"
+ "github.com/nyttikord/gokord/interaction"
+)
+
+func HandleDynamicMessageComponent[DynamicData any](
+ b *gokord.Bot,
+ handler func(
+ bot.Session,
+ *event.InteractionCreate,
+ *interaction.MessageComponentData,
+ *DynamicData, *cmd.ResponseBuilder,
+ ),
+ base string,
+) {
+ b.AddHandler(func(s bot.Session, i *event.InteractionCreate) {
+ if i.Type != types.InteractionMessageComponent {
+ return
+ }
+ data := i.MessageComponentData()
+ if !strings.HasPrefix(data.CustomID, base+";") {
+ return
+ }
+ dynamicID := data.CustomID[len(base)+1:]
+ dynamicData := new(DynamicData)
+ err := UnmarshallCSV(dynamicID, dynamicData)
+ if err != nil {
+ s.Logger().Error("Unable to parse CustomID", "error", err, "CustomID", data.CustomID, "base", base)
+ return
+ }
+ handler(s, i, data, dynamicData, cmd.NewResponseBuilder(s, i))
+ })
+}
+
+func HandleDynamicModalComponent[DynamicData any](
+ b *gokord.Bot,
+ handler func(
+ bot.Session,
+ *event.InteractionCreate,
+ *interaction.ModalSubmitData,
+ *DynamicData,
+ *cmd.ResponseBuilder,
+ ),
+ base string,
+) {
+ b.AddHandler(func(s bot.Session, i *event.InteractionCreate) {
+ if i.Type != types.InteractionModalSubmit {
+ return
+ }
+ data := i.ModalSubmitData()
+ if strings.HasPrefix(data.CustomID, base+";") {
+ dynamicID := data.CustomID[len(base)+1:]
+ dynamicData := new(DynamicData)
+ err := UnmarshallCSV(dynamicID, dynamicData)
+ if err != nil {
+ s.Logger().Error("Unable to parse CustomID", "error", err, "CustomID", data.CustomID, "base", base)
+ return
+ }
+ handler(s, i, data, dynamicData, cmd.NewResponseBuilder(s, i))
+ }
+ })
+}
+
+func FormatCustomID(base string, dynamicData any) string {
+ return base + ";" + MarshallCSV(dynamicData)
+}
diff --git a/go.mod b/go.mod
index 8e1ebe9..b7bc5af 100644
--- a/go.mod
+++ b/go.mod
@@ -33,7 +33,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/redis/go-redis/v9 v9.15.0 // indirect
+ github.com/redis/go-redis/v9 v9.14.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
diff --git a/main.go b/main.go
index 56753e1..6a89e44 100644
--- a/main.go
+++ b/main.go
@@ -2,17 +2,17 @@ package main
import (
_ "embed"
- "encoding/json"
"errors"
"flag"
"log/slog"
"os"
- "regexp"
"time"
"git.anhgelus.world/anhgelus/les-copaings-bot/commands"
"git.anhgelus.world/anhgelus/les-copaings-bot/config"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
"git.anhgelus.world/anhgelus/les-copaings-bot/exp"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/rolereact"
"git.anhgelus.world/anhgelus/les-copaings-bot/user"
"github.com/anhgelus/gokord"
"github.com/anhgelus/gokord/cmd"
@@ -68,61 +68,6 @@ func init() {
}
-func handleDynamicMessageComponent(
- b *gokord.Bot,
- handler func(
- bot.Session,
- *event.InteractionCreate,
- *interaction.MessageComponentData,
- []string, *cmd.ResponseBuilder,
- ),
- pattern string,
-) {
- compiledPattern := regexp.MustCompile(pattern)
- b.AddHandler(func(s bot.Session, i *event.InteractionCreate) {
- if i.Type != types.InteractionMessageComponent {
- return
- }
-
- data := i.MessageComponentData()
- parameters := compiledPattern.FindStringSubmatch(data.CustomID)
- if parameters == nil {
- return
- }
- parameters = parameters[1:]
- handler(s, i, data, parameters, cmd.NewResponseBuilder(s, i))
- })
-}
-
-func handleDynamicModalComponent(
- b *gokord.Bot,
- handler func(
- bot.Session,
- *event.InteractionCreate,
- *interaction.ModalSubmitData,
- []string,
- *cmd.ResponseBuilder,
- ),
- pattern string,
-) {
- compiledPattern := regexp.MustCompile(pattern)
- b.AddHandler(func(s bot.Session, i *event.InteractionCreate) {
- if i.Type != types.InteractionModalSubmit {
- return
- }
-
- data := i.ModalSubmitData()
- content, _ := json.Marshal(data)
- s.Logger().Debug(string(content))
- parameters := compiledPattern.FindStringSubmatch(data.CustomID)
- if parameters == nil {
- return
- }
- parameters = parameters[1:]
- handler(s, i, data, parameters, cmd.NewResponseBuilder(s, i))
- })
-}
-
func main() {
flag.Parse()
gokord.UseRedis = false
@@ -131,7 +76,7 @@ func main() {
panic(err)
}
- err = gokord.DB.AutoMigrate(&user.Copaing{}, &config.GuildConfig{}, &config.XpRole{}, &user.CopaingXP{})
+ err = gokord.DB.AutoMigrate(&user.Copaing{}, &config.GuildConfig{}, &config.XpRole{}, &user.CopaingXP{}, &config.RoleReactMessage{}, &config.RoleReact{})
if err != nil {
panic(err)
}
@@ -182,6 +127,15 @@ func main() {
)).
SetHandler(commands.Stats)
+ rolereactCmd := cmd.New("rolereact", "Envoie un message permettant de récupérer des rôles grâce à des réactions").
+ SetPermission(&adm).
+ AddOption(cmd.NewOption(
+ types.CommandOptionChannel,
+ "salon",
+ "Destination du message",
+ )).
+ SetHandler(rolereact.HandleCommand)
+
innovations, err := gokord.LoadInnovationFromJson(updatesData)
if err != nil {
panic(err)
@@ -215,6 +169,7 @@ func main() {
resetUserCmd,
creditsCmd,
statsCmd,
+ rolereactCmd,
},
AfterInit: func(dg *discordgo.Session) {
d := 24 * time.Hour
@@ -236,17 +191,64 @@ func main() {
discord.IntentGuildMembers,
}
+ // related to rolereact
+ b.AddHandler(func(s bot.Session, e *event.Ready) {
+ var guildID string
+ gs, err := s.GuildAPI().UserGuilds(1, "", "", false)
+ if err != nil {
+ s.Logger().Error("fetching guilds for debug", "error", err)
+ return
+ } else {
+ guildID = gs[0].ID
+ }
+
+ handleRolereactionMessageCmd := interaction.Command{
+ Type: types.CommandMessage,
+ Name: "Modifier",
+ DefaultMemberPermissions: &adm,
+ }
+ c, err := s.InteractionAPI().CommandCreate(s.SessionState().User().ID, guildID, &handleRolereactionMessageCmd)
+ if err != nil {
+ s.Logger().Error("unable to push rolereaction message command", "error", err)
+ return
+ }
+ s.Logger().Debug("pushed rolereaction message command", "CommandID", c.ID)
+ })
+ b.AddHandler(func(s bot.Session, i *event.InteractionCreate) {
+ if i.Type != types.InteractionApplicationCommand {
+ return
+ }
+ data := i.CommandData()
+ if data.Name == "Modifier" {
+ resp := cmd.NewResponseBuilder(s, i)
+ rolereact.HandleModifyCommand(s, i, data, resp)
+ }
+ })
+ b.AddHandler(rolereact.HandleReactionAdd)
+ b.AddHandler(rolereact.HandleReactionRemove)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleModifyComponent, rolereact.OpenMessage)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleApplyMessage, rolereact.ApplyMessage)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleResetMessage, rolereact.ResetMessage)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleStartSetNote, rolereact.SetNote)
+ dynamicid.HandleDynamicModalComponent(&b, rolereact.HandleSetNote, rolereact.SetNote)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleNewRole, rolereact.NewRole)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleOpenRole, rolereact.OpenRole)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleSetRole, rolereact.SetRoleRoleID)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleSetReaction, rolereact.SetRoleReaction)
+ dynamicid.HandleDynamicMessageComponent(&b, rolereact.HandleDelRole, rolereact.DelRole)
+
// interaction: /config
b.HandleMessageComponent(commands.ConfigMessageComponent, commands.OpenConfig)
// xp role related
b.HandleMessageComponent(config.HandleXpRole, config.ModifyXpRole)
b.HandleMessageComponent(config.HandleXpRoleNew, config.XpRoleNew)
b.HandleModal(config.HandleXpRoleAdd, config.XpRoleAdd)
- handleDynamicMessageComponent(&b, config.HandleXpRoleEdit, config.XpRoleEditPattern)
- handleDynamicMessageComponent(&b, config.HandleXpRoleEditRole, config.XpRoleEditRolePattern)
- handleDynamicMessageComponent(&b, config.HandleXpRoleEditLevelStart, config.XpRoleEditLevelStartPattern)
- handleDynamicModalComponent(&b, config.HandleXpRoleEditLevel, config.XpRoleEditLevelPattern)
- handleDynamicMessageComponent(&b, config.HandleXpRoleDel, config.XpRoleDel)
+ dynamicid.HandleDynamicMessageComponent(&b, config.HandleXpRoleEdit, config.XpRoleEdit)
+ dynamicid.HandleDynamicMessageComponent(&b, config.HandleXpRoleEdit, config.XpRoleEdit)
+ dynamicid.HandleDynamicMessageComponent(&b, config.HandleXpRoleEditRole, config.XpRoleEditRole)
+ dynamicid.HandleDynamicMessageComponent(&b, config.HandleXpRoleEditLevelStart, config.XpRoleEditLevelStart)
+ dynamicid.HandleDynamicModalComponent(&b, config.HandleXpRoleEditLevel, config.XpRoleEditLevel)
+ dynamicid.HandleDynamicMessageComponent(&b, config.HandleXpRoleDel, config.XpRoleDel)
// channel related
b.HandleMessageComponent(func(s bot.Session, i *event.InteractionCreate, data *interaction.MessageComponentData, resp *cmd.ResponseBuilder) {
if config.HandleModifyFallbackChannel(s, i, data, resp) {
diff --git a/rolereact/events.go b/rolereact/events.go
new file mode 100644
index 0000000..1de3fe0
--- /dev/null
+++ b/rolereact/events.go
@@ -0,0 +1,46 @@
+package rolereact
+
+import (
+ "git.anhgelus.world/anhgelus/les-copaings-bot/config"
+ oldGokord "github.com/anhgelus/gokord"
+ "github.com/nyttikord/gokord/bot"
+ "github.com/nyttikord/gokord/event"
+)
+
+type RoleReact struct {
+ RoleID string
+}
+
+func HandleReactionAdd(
+ s bot.Session,
+ e *event.MessageReactionAdd,
+) {
+ results := []RoleReact{}
+ oldGokord.DB.Model(&config.RoleReact{}).
+ Joins("JOIN role_react_messages ON role_reacts.role_react_message_id = role_react_messages.id").
+ Where("role_react_messages.message_id = ? AND role_reacts.reaction = ?", e.MessageID, e.MessageReaction.Emoji.APIName()).
+ Scan(&results)
+ for _, role := range results {
+ err := s.GuildAPI().MemberRoleAdd(e.GuildID, e.UserID, role.RoleID)
+ if err != nil {
+ s.Logger().Error("Unable to add role after member added reaction", "error", err)
+ }
+ }
+}
+
+func HandleReactionRemove(
+ s bot.Session,
+ e *event.MessageReactionRemove,
+) {
+ results := []RoleReact{}
+ oldGokord.DB.Model(&config.RoleReact{}).
+ Joins("JOIN role_react_messages ON role_reacts.role_react_message_id = role_react_messages.id").
+ Where("role_react_messages.message_id = ? AND role_reacts.reaction = ?", e.MessageID, e.MessageReaction.Emoji.APIName()).
+ Scan(&results)
+ for _, role := range results {
+ err := s.GuildAPI().MemberRoleRemove(e.GuildID, e.UserID, role.RoleID)
+ if err != nil {
+ s.Logger().Error("Unable to remove role after member removed reaction", "error", err)
+ }
+ }
+}
diff --git a/rolereact/manager.go b/rolereact/manager.go
new file mode 100644
index 0000000..2dadb7d
--- /dev/null
+++ b/rolereact/manager.go
@@ -0,0 +1,143 @@
+package rolereact
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "strings"
+ "time"
+
+ "git.anhgelus.world/anhgelus/les-copaings-bot/config"
+ oldGokord "github.com/anhgelus/gokord"
+ "github.com/nyttikord/gokord/bot"
+ "github.com/nyttikord/gokord/channel"
+ "github.com/nyttikord/gokord/emoji"
+ "github.com/nyttikord/gokord/event"
+)
+
+func MessageContent(message *config.RoleReactMessage) string {
+ content := "## Réagis pour obtenir un rôle"
+ if message.Note != "" {
+ content = fmt.Sprintf("%s\n%s", content, message.Note)
+ }
+ for _, role := range message.Roles {
+ if role.Reaction != "" && role.RoleID != "" {
+ content += fmt.Sprintf("\n> -# %s <@&%s>", FormatEmoji(role.Reaction), role.RoleID)
+ }
+ }
+ if len(message.Roles) == 0 {
+ content += "\n*Pas de rôles pour le moment*"
+ }
+ return content
+}
+
+func ApplyMessageChange(s bot.Session, i *event.InteractionCreate, message *config.RoleReactMessage) string {
+ messageContent := MessageContent(message)
+ _, err := s.ChannelAPI().MessageEditComplex(
+ &channel.MessageEdit{
+ Content: &messageContent,
+ AllowedMentions: &channel.MessageAllowedMentions{},
+ Channel: message.ChannelID,
+ ID: message.MessageID,
+ },
+ )
+ if err != nil {
+ s.Logger().Error("unable to update rolereact message", "error", err)
+ return "Impossible de mettre à jour le message."
+ }
+ for _, role := range message.Roles {
+ if role.Reaction != "" && role.RoleID != "" && err == nil {
+ err = s.ChannelAPI().MessageReactionAdd(
+ message.ChannelID,
+ message.MessageID,
+ role.Reaction,
+ )
+ }
+ }
+ if err != nil {
+ s.Logger().Error("unable to update reactions on rolereact message", "error", err)
+ return "Impossible de mettre à jour le message."
+ }
+ cfg := GetGuildConfigPreloaded(i.GuildID)
+ messageIndex := slices.IndexFunc(cfg.RrMessages, func(m config.RoleReactMessage) bool { return m.ID == message.ID })
+ if messageIndex != -1 {
+ oldMessage := cfg.RrMessages[messageIndex]
+ roles := make(map[uint]config.RoleReact, len(message.Roles))
+ for _, role := range message.Roles {
+ roles[role.ID] = *role
+ }
+ for _, role := range oldMessage.Roles {
+ _, ok := roles[role.ID]
+ if !ok {
+ err := oldGokord.DB.Delete(role).Error
+ if err != nil {
+ s.Logger().Error("unable to delete reaction role from database", "error", err)
+ return "Impossible de sauvegarder le message de rôle. Merci de contacter l'administrateur du bot."
+ }
+ }
+ }
+ cfg.RrMessages[messageIndex] = *message
+ err := oldGokord.DB.Save(cfg.RrMessages[messageIndex]).Error
+ if err != nil {
+ s.Logger().Error("unable to save rolereaction message in database", "error", err)
+ return "Impossible de sauvegarder le message de rôle. Merci de contacter l'administrateur du bot."
+ }
+ for _, role := range cfg.RrMessages[messageIndex].Roles {
+ err = oldGokord.DB.Save(role).Error
+ if err != nil {
+ s.Logger().Error("unable to save rolereaction role in database", "error", err)
+ return "Impossible de sauvegarder le message de rôle. Merci de contacter l'administrateur du bot."
+ }
+ }
+ }
+ return "Message de réaction mis à jour avec succès !"
+}
+
+func WaitForEmoji(s bot.Session, userID string, messageID string) (string, bool) {
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
+ defer cancel()
+
+ emojiChann := make(chan emoji.Emoji)
+
+ cancelHandler := s.EventManager().AddHandler(func(s bot.Session, e *event.MessageReactionAdd) {
+ if e.MessageID == messageID && e.UserID == userID {
+ emojiChann <- e.Emoji
+ }
+ })
+ defer cancelHandler()
+
+ select {
+ case emoji := <-emojiChann:
+ emojiName := emoji.APIName()
+ return emojiName, true
+ case <-ctx.Done():
+ return "", false
+ }
+}
+
+func GetMessageFromEditID(i *event.InteractionCreate, editID uint) (*config.RoleReactMessage, bool) {
+ cfg := config.GetGuildConfig(i.GuildID)
+ m, ok := messageEdits[editID]
+ if !ok || m.GuildConfigID != cfg.ID {
+ return &config.RoleReactMessage{}, false
+ }
+ return m, true
+}
+
+func GetGuildConfigPreloaded(guildID string) *config.GuildConfig {
+ cfg := config.GuildConfig{GuildID: guildID}
+ // err := oldGokord.DB.Where("guild_id = ?", cfg.GuildID).Preload("XpRoles").Preload("RrMessages.Roles").FirstOrCreate(cfg).Error
+ err := oldGokord.DB.Where("guild_id = ?", cfg.GuildID).Preload("RrMessages.Roles").FirstOrCreate(&cfg).Error
+ if err != nil {
+ panic(err)
+ }
+ return &cfg
+}
+
+func FormatEmoji(apiName string) string {
+ if strings.Contains(apiName, ":") {
+ return fmt.Sprintf("<:%s>", apiName)
+ } else {
+ return apiName
+ }
+}
diff --git a/rolereact/rolereact.go b/rolereact/rolereact.go
new file mode 100644
index 0000000..ebc09be
--- /dev/null
+++ b/rolereact/rolereact.go
@@ -0,0 +1,543 @@
+package rolereact
+
+import (
+ "fmt"
+ "slices"
+
+ "git.anhgelus.world/anhgelus/les-copaings-bot/config"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
+ "github.com/anhgelus/gokord/cmd"
+ "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/event"
+ "github.com/nyttikord/gokord/interaction"
+)
+
+const (
+ OpenMessage = "rolereact_message"
+ ResetMessage = "rolereact_reset_message"
+ ApplyMessage = "rolereact_apply_message"
+ SetNote = "rolereact_set_note"
+ NewRole = "rolereact_new_role"
+ AddRole = "rolereact_add_role"
+ OpenRole = "rolereact_open_role"
+ SetRoleRoleID = "rolereact_set_role_roleid"
+ SetRoleReaction = "rolereact_set_role_reaction"
+ DelRole = "rolereact_del_role"
+)
+
+type EditID struct {
+ MessageEditID uint
+}
+
+type EditIDWithRole struct {
+ MessageEditID uint
+ RoleCounterID uint
+}
+
+var (
+ messageCounter uint = 1
+ roleCounter uint = 1
+ messageEdits map[uint]*config.RoleReactMessage = make(map[uint]*config.RoleReactMessage)
+)
+
+func HandleCommand(
+ s bot.Session,
+ i *event.InteractionCreate,
+ o cmd.OptionMap,
+ resp *cmd.ResponseBuilder,
+) {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseDeferredMessageUpdate,
+ Data: &interaction.ResponseData{Flags: channel.MessageFlagsEphemeral},
+ })
+ if err != nil {
+ s.Logger().Error("unable to defer interaction", "error", err)
+ return
+ }
+ c := o["salon"]
+ var channelID string
+ if c != nil {
+ channelID = c.Value.(string)
+ } else {
+ channelID = i.ChannelID
+ }
+
+ message := config.RoleReactMessage{
+ ChannelID: channelID,
+ GuildID: i.GuildID,
+ }
+ messageContent := MessageContent(&message)
+ m, err := s.ChannelAPI().MessageSendComplex(
+ channelID, &channel.MessageSend{
+ Content: messageContent,
+ AllowedMentions: &channel.MessageAllowedMentions{},
+ },
+ )
+ if err != nil {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{Content: fmt.Sprintf("Error: %s", err.Error())},
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send message", "error", err)
+ }
+ return
+ }
+ message.MessageID = m.ID
+ cfg := GetGuildConfigPreloaded(i.GuildID)
+ cfg.RrMessages = append(cfg.RrMessages, message)
+ err = cfg.Save()
+ if err != nil {
+ s.Logger().Error("Unable to save rolereact message in database", "error", err)
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource | types.InteractionResponseDeferredChannelMessageWithSource,
+ Data: &interaction.ResponseData{Content: "Unable to save message in database. Please retry later."},
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send message", "error", err)
+ }
+ return
+ }
+
+ messageEdits[messageCounter] = &cfg.RrMessages[len(cfg.RrMessages)-1]
+ editID := messageCounter
+ messageCounter++
+
+ err = s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: MessageModifyData(i, &EditID{MessageEditID: editID}),
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send edit rolereact message", "error", err)
+ }
+}
+
+func HandleModifyCommand(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.CommandInteractionData,
+ resp *cmd.ResponseBuilder,
+) {
+ messageId := data.TargetID
+ cfg := GetGuildConfigPreloaded(i.GuildID)
+ var target *config.RoleReactMessage
+ var targetEditID uint
+ for editID, message := range messageEdits {
+ if message.MessageID == messageId {
+ targetEditID = editID
+ target = message
+ }
+ }
+ if targetEditID == 0 {
+ for _, message := range cfg.RrMessages {
+ if message.MessageID == messageId {
+ target = &message
+ }
+ }
+ if target == nil {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral,
+ Content: "Le message sélectionné n'est pas un message de rôles de réaction.",
+ },
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send rolereact message not found", "error", err)
+ }
+ return
+ }
+ messageEdits[messageCounter] = target
+ targetEditID = messageCounter
+ messageCounter++
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseChannelMessageWithSource,
+ Data: MessageModifyData(i, &EditID{MessageEditID: targetEditID}),
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send modify rolereact message", "error", err)
+ }
+}
+
+func HandleModifyComponent(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: MessageModifyData(i, parameters),
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send modify rolereact message", "error", err)
+ }
+}
+
+func HandleResetMessage(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var responseData interaction.ResponseData
+ if !ok {
+ responseData = interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ }
+ } else {
+ cfg := GetGuildConfigPreloaded(i.GuildID)
+ for _, m := range cfg.RrMessages {
+ if m.ID == message.ID {
+ messageEdits[parameters.MessageEditID] = &m
+ }
+ }
+ responseData = *MessageModifyData(i, parameters)
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send reset message message", "error", err)
+ }
+}
+
+func HandleStartSetNote(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ if !ok {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ },
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send message edit not found message", "error", err)
+ }
+ return
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseModal,
+ Data: &interaction.ResponseData{
+ Title: "Changer la description",
+ CustomID: dynamicid.FormatCustomID(SetNote, *parameters),
+ Components: []component.Component{
+ &component.Label{
+ Label: "Nouvelle description",
+ Description: "Description affichée sur votre message de réaction",
+ Component: &component.TextInput{
+ Style: component.TextInputParagraph,
+ MaxLength: 2000,
+ CustomID: "note",
+ Value: message.Note,
+ },
+ },
+ },
+ },
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send edit note modal", "error", err)
+ }
+}
+
+func HandleSetNote(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.ModalSubmitData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ if !ok {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ },
+ })
+ if err != nil {
+ s.Logger().Error("unable to send set note error message", "error", err)
+ }
+ return
+ }
+ message.Note = data.Components[0].(*component.Label).Component.(*component.TextInput).Value
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: MessageModifyData(i, parameters),
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send updated note message", "error", err)
+ }
+}
+
+func HandleApplyMessage(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var responseData interaction.ResponseData
+ if !ok {
+ responseData = interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ if err != nil {
+ s.Logger().Error("unable to send apply message error message", "error", err)
+ }
+ return
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseDeferredChannelMessageWithSource,
+ Data: &interaction.ResponseData{Flags: channel.MessageFlagsEphemeral},
+ })
+ if err != nil {
+ s.Logger().Error("Unable to defer interaction", "error", err)
+ return
+ }
+ m := ApplyMessageChange(s, i, message)
+ _, err = s.InteractionAPI().ResponseEdit(i.Interaction, &channel.WebhookEdit{
+ Content: &m,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send apply rolereaction message changes", "error", err)
+ }
+}
+
+func HandleNewRole(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditID,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var responseData interaction.ResponseData
+ if !ok {
+ responseData = interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ }
+ } else {
+ message.Roles = append(message.Roles, &config.RoleReact{CounterID: roleCounter})
+ responseData = MessageModifyRoleData(i, &EditIDWithRole{MessageEditID: parameters.MessageEditID, RoleCounterID: roleCounter}, "")
+ roleCounter++
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send modify reaction role message", "error", err)
+ }
+}
+
+func HandleOpenRole(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditIDWithRole,
+ resp *cmd.ResponseBuilder,
+) {
+ _, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var responseData interaction.ResponseData
+ if !ok {
+ responseData = interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ }
+ } else {
+ responseData = MessageModifyRoleData(i, parameters, "")
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send open reaction role message", "error", err)
+ }
+}
+
+func HandleSetRole(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditIDWithRole,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var responseData interaction.ResponseData
+ var role *config.RoleReact
+ if ok {
+ roleIndex := slices.IndexFunc(message.Roles, func(role *config.RoleReact) bool { return role.CounterID == parameters.RoleCounterID })
+ if roleIndex != -1 {
+ role = message.Roles[roleIndex]
+ }
+ }
+ if !ok || role == nil {
+ responseData = interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ }
+ } else {
+ role.RoleID = data.Values[0]
+ responseData = MessageModifyRoleData(i, parameters, "")
+ }
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send open reaction role message", "error", err)
+ }
+}
+
+func HandleSetReaction(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditIDWithRole,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var role *config.RoleReact
+ if ok {
+ roleIndex := slices.IndexFunc(message.Roles, func(role *config.RoleReact) bool { return role.CounterID == parameters.RoleCounterID })
+ if roleIndex != -1 {
+ role = message.Roles[roleIndex]
+ }
+ }
+ if !ok || role == nil {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ },
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send open reaction role message", "error", err)
+ }
+ return
+ }
+ responseData := MessageModifyRoleData(i, parameters, "Ajoute la réaction que tu veux choisir au message de rôle de réaction (tu peux y accéder avec le bouton ci-dessous)")
+ s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &responseData,
+ })
+ emojiName, ok := WaitForEmoji(s, i.Member.User.ID, message.MessageID)
+ if !ok {
+ editResponseComponents := MessageModifyRoleComponents(i, parameters, "Le temps d'attente a été dépassé")
+ _, err := s.InteractionAPI().ResponseEdit(i.Interaction, &channel.WebhookEdit{
+ Components: &editResponseComponents,
+ })
+ if err != nil {
+ s.Logger().Error("unable to send timed out reaction message", "error", err)
+ }
+ return
+ }
+
+ err := s.ChannelAPI().MessageReactionAdd(message.ChannelID, message.MessageID, emojiName)
+ if err != nil {
+ editResponseComponents := MessageModifyRoleComponents(i, parameters, "La réaction n'est pas utilisable. Cela peut être résolu en l'ajoutant à ce serveur")
+ _, err := s.InteractionAPI().ResponseEdit(i.Interaction, &channel.WebhookEdit{
+ Components: &editResponseComponents,
+ })
+ if err != nil {
+ s.Logger().Error("unable to send unusable reaction message", "error", err)
+ }
+ return
+ }
+ err = s.ChannelAPI().MessageReactionRemove(message.ChannelID, message.MessageID, emojiName, i.Member.User.ID)
+ if err != nil {
+ s.Logger().Warn("unable to remove author reaction from message", "error", err)
+ }
+ role.Reaction = emojiName
+ components := MessageModifyRoleComponents(i, parameters, "")
+ _, err = s.InteractionAPI().ResponseEdit(i.Interaction, &channel.WebhookEdit{
+ Flags: channel.MessageFlagsIsComponentsV2 | channel.MessageFlagsEphemeral,
+ Components: &components,
+ })
+ if err != nil {
+ s.Logger().Error("Unable to edit original response", "error", err)
+ }
+}
+
+func HandleDelRole(
+ s bot.Session,
+ i *event.InteractionCreate,
+ data *interaction.MessageComponentData,
+ parameters *EditIDWithRole,
+ resp *cmd.ResponseBuilder,
+) {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ roleIndex := -1
+ if ok {
+ roleIndex = slices.IndexFunc(message.Roles, func(role *config.RoleReact) bool { return role.CounterID == parameters.RoleCounterID })
+ }
+ if !ok || roleIndex == -1 {
+ err := s.InteractionAPI().Respond(i.Interaction, &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: &interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ },
+ },
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send open reaction role message", "error", err)
+ }
+ return
+ }
+ message.Roles = append(message.Roles[:roleIndex],
+ message.Roles[roleIndex+1:]...,
+ )
+ err := s.InteractionAPI().Respond(i.Interaction,
+ &interaction.Response{
+ Type: types.InteractionResponseUpdateMessage,
+ Data: MessageModifyData(i, &EditID{MessageEditID: parameters.MessageEditID}),
+ })
+ if err != nil {
+ s.Logger().Error("Unable to send modify message message", "error", err)
+ }
+}
diff --git a/rolereact/views.go b/rolereact/views.go
new file mode 100644
index 0000000..eafa4dc
--- /dev/null
+++ b/rolereact/views.go
@@ -0,0 +1,212 @@
+package rolereact
+
+import (
+ "fmt"
+ "slices"
+
+ "git.anhgelus.world/anhgelus/les-copaings-bot/config"
+ "git.anhgelus.world/anhgelus/les-copaings-bot/dynamicid"
+ "github.com/nyttikord/gokord/channel"
+ "github.com/nyttikord/gokord/component"
+ "github.com/nyttikord/gokord/discord/types"
+ "github.com/nyttikord/gokord/event"
+ "github.com/nyttikord/gokord/interaction"
+)
+
+func MessageModifyData(i *event.InteractionCreate, parameters *EditID) *interaction.ResponseData {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ if !ok {
+ return &interaction.ResponseData{
+ Flags: channel.MessageFlagsIsComponentsV2,
+ Components: []component.Component{
+ &component.TextDisplay{Content: "Cette modification est trop vieille et a été oubliée."},
+ },
+ }
+ }
+ var note string
+ if message.Note != "" {
+ note = message.Note
+ } else {
+ note = "*Pas de note*"
+ }
+ components := []component.Message{
+ &component.TextDisplay{Content: "## Modifier un message de réaction"},
+ &component.Separator{},
+ &component.Section{
+ Components: []component.Message{&component.TextDisplay{Content: note}},
+ Accessory: &component.Button{
+ Label: "Modifier",
+ Style: component.ButtonStyleSecondary,
+ CustomID: dynamicid.FormatCustomID(SetNote, *parameters),
+ },
+ },
+ &component.Separator{},
+ }
+ for _, role := range message.Roles {
+ var reaction string
+ if role.Reaction != "" {
+ reaction = FormatEmoji(role.Reaction)
+ } else {
+ reaction = ":no_entry_sign:"
+ }
+ var roleMention string
+ if role.RoleID != "" {
+ roleMention = fmt.Sprintf("<@&%s>", role.RoleID)
+ } else {
+ roleMention = "*Pas de rôle sélectionné*"
+ }
+ if role.CounterID == 0 {
+ role.CounterID = roleCounter
+ roleCounter++
+ }
+ components = append(components, &component.Section{
+ Components: []component.Message{&component.TextDisplay{Content: fmt.Sprintf("%s %s", reaction, roleMention)}},
+ Accessory: &component.Button{
+ Label: "Modifier",
+ Style: component.ButtonStyleSecondary,
+ CustomID: dynamicid.FormatCustomID(OpenRole, EditIDWithRole{parameters.MessageEditID, role.CounterID}),
+ },
+ })
+ }
+ if len(message.Roles) == 0 {
+ components = append(components, &component.TextDisplay{
+ Content: "*Pas de rôles de réaction défini*",
+ })
+ }
+ components = append(components, []component.Message{
+ &component.ActionsRow{
+ Components: []component.Message{
+ &component.Button{
+ Style: component.ButtonStylePrimary,
+ Label: "Ajouter",
+ CustomID: dynamicid.FormatCustomID(NewRole, EditID{MessageEditID: parameters.MessageEditID}),
+ Disabled: len(message.Roles) >= 20,
+ },
+ },
+ },
+ &component.Separator{},
+ &component.ActionsRow{
+ Components: []component.Message{
+ &component.Button{
+ Label: "Appliquer",
+ Style: component.ButtonStylePrimary,
+ CustomID: dynamicid.FormatCustomID(ApplyMessage, EditID{MessageEditID: parameters.MessageEditID}),
+ },
+ &component.Button{
+ Label: "Réinitialiser",
+ Style: component.ButtonStyleDanger,
+ CustomID: dynamicid.FormatCustomID(ResetMessage, *parameters),
+ },
+ &component.Button{
+ Label: "Message",
+ Style: component.ButtonStyleLink,
+ URL: fmt.Sprintf("https://discord.com/channels/%s/%s/%s", message.GuildID, message.ChannelID, message.MessageID),
+ },
+ },
+ }}...)
+ responseData := &interaction.ResponseData{
+ Flags: channel.MessageFlagsIsComponentsV2 | channel.MessageFlagsEphemeral,
+ Components: []component.Component{
+ &component.Container{
+ Components: components,
+ },
+ },
+ }
+ return responseData
+}
+
+func MessageModifyRoleComponents(i *event.InteractionCreate, parameters *EditIDWithRole, emojiMessage string) []component.Message {
+ message, ok := GetMessageFromEditID(i, parameters.MessageEditID)
+ var role *config.RoleReact
+ if ok {
+ roleIndex := slices.IndexFunc(message.Roles, func(role *config.RoleReact) bool { return role.CounterID == parameters.RoleCounterID })
+ if roleIndex != -1 {
+ role = message.Roles[roleIndex]
+ }
+ }
+ if !ok || role == nil {
+ return []component.Message{
+ &component.TextDisplay{Content: "Impossible de trouver la modification de message. Veuillez réessayer."},
+ }
+ }
+ disableBack := false
+ var reactionDescription string
+ var reactionButton component.Button
+ if role.Reaction != "" {
+ reactionDescription = fmt.Sprintf("**Réaction : ** %s", FormatEmoji(role.Reaction))
+ reactionButton = component.Button{Label: "Modifier", Style: component.ButtonStyleSecondary}
+ } else {
+ reactionDescription = "*Aucune réaction pour le moment*"
+ reactionButton = component.Button{Label: "Ajouter", Style: component.ButtonStylePrimary}
+ disableBack = true
+ }
+ reactionButton.CustomID = dynamicid.FormatCustomID(SetRoleReaction, *parameters)
+ defaultRoleValues := make([]component.SelectMenuDefaultValue, 0)
+ if role.RoleID != "" {
+ defaultRoleValues = append(defaultRoleValues, component.SelectMenuDefaultValue{
+ Type: types.SelectMenuDefaultValueRole,
+ ID: role.RoleID,
+ })
+ }
+ disableBack = disableBack || (role.RoleID == "")
+ one := 1
+ components := []component.Message{
+ &component.TextDisplay{Content: "## Modifier un message de réaction"},
+ &component.Separator{},
+ &component.Section{
+ Components: []component.Message{
+ &component.TextDisplay{Content: reactionDescription},
+ },
+ Accessory: &reactionButton,
+ },
+ }
+ if emojiMessage != "" {
+ components = append(components, &component.TextDisplay{Content: "-# " + emojiMessage})
+ }
+ components = append(components,
+ []component.Message{
+ &component.ActionsRow{Components: []component.Message{
+ &component.SelectMenu{
+ MenuType: types.SelectMenuRole,
+ CustomID: dynamicid.FormatCustomID(SetRoleRoleID, *parameters),
+ MinValues: &one, MaxValues: 1,
+ Placeholder: "Sélectionner un rôle",
+ DefaultValues: defaultRoleValues,
+ },
+ }},
+ &component.ActionsRow{Components: []component.Message{
+ &component.Button{
+ Style: component.ButtonStyleDanger,
+ Label: "Supprimer",
+ CustomID: dynamicid.FormatCustomID(DelRole, *parameters),
+ },
+ }},
+ &component.Separator{},
+ &component.ActionsRow{Components: []component.Message{
+ &component.Button{
+ Label: "Retour",
+ Style: component.ButtonStyleSecondary,
+ Disabled: disableBack,
+ CustomID: dynamicid.FormatCustomID(OpenMessage, EditID{MessageEditID: parameters.MessageEditID}),
+ },
+ &component.Button{
+ Label: "Message", Style: component.ButtonStyleLink,
+ URL: fmt.Sprintf("https://discord.com/channels/%s/%s/%s", message.GuildID, message.ChannelID, message.MessageID),
+ },
+ }},
+ }...)
+ return []component.Message{&component.Container{
+ Components: components,
+ }}
+}
+
+func MessageModifyRoleData(i *event.InteractionCreate, parameters *EditIDWithRole, emojiMessage string) interaction.ResponseData {
+ components := []component.Component{}
+ for _, component := range MessageModifyRoleComponents(i, parameters, emojiMessage) {
+ components = append(components, component)
+ }
+ return interaction.ResponseData{
+ Flags: channel.MessageFlagsEphemeral | channel.MessageFlagsIsComponentsV2,
+ Components: components,
+ }
+}