From bc86bb4859c4537032f9ca8d57ac32cc14dbd629 Mon Sep 17 00:00:00 2001 From: ascpial Date: Sat, 27 Sep 2025 23:35:32 +0200 Subject: [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 --- config/guild.go | 19 ++ config/xp_role.go | 99 ++++----- dynamicid/encoding.go | 115 +++++++++++ dynamicid/handling.go | 74 +++++++ go.mod | 2 +- main.go | 128 ++++++------ rolereact/events.go | 46 +++++ rolereact/manager.go | 143 +++++++++++++ rolereact/rolereact.go | 543 +++++++++++++++++++++++++++++++++++++++++++++++++ rolereact/views.go | 212 +++++++++++++++++++ 10 files changed, 1257 insertions(+), 124 deletions(-) create mode 100644 dynamicid/encoding.go create mode 100644 dynamicid/handling.go create mode 100644 rolereact/events.go create mode 100644 rolereact/manager.go create mode 100644 rolereact/rolereact.go create mode 100644 rolereact/views.go 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, + } +} -- cgit v1.2.3