diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 65aab90..4f02f5c 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -14,6 +14,7 @@ var AllCommands = []*discordgo.ApplicationCommand{ cmdlevel.LevelConfigCommand, cmdlevel.RankCommand, cmdlevel.LeaderboardCommand, + cmdlevel.RewardsCommand, } var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ @@ -22,6 +23,7 @@ var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCre "slvl": cmdlevel.HandleLevelConfig, "rank": cmdlevel.HandleRank, "leaderboard": cmdlevel.HandleLeaderboard, + "rewards": cmdlevel.HandleRewards, } func HandleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) { diff --git a/internal/commands/level/helpers.go b/internal/commands/level/helpers.go new file mode 100644 index 0000000..9e471dc --- /dev/null +++ b/internal/commands/level/helpers.go @@ -0,0 +1,95 @@ +package level + +import ( + "context" + "strconv" + + "velox-bot/internal/db/services" + + "github.com/bwmarrin/discordgo" +) + +func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) { + _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: msg, + Flags: discordgo.MessageFlagsEphemeral, + }, + }) +} + +func requireGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool { + if i.GuildID == "" { + respondEphemeral(s, i, "This command can only be used in a server.") + return false + } + return true +} + +func requireManageGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool { + if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { + respondEphemeral(s, i, "You don't have permission to manage leveling settings.") + return false + } + return true +} + +func requireLevelServices(s *discordgo.Session, i *discordgo.InteractionCreate) bool { + if services.Global == nil || services.Global.Level == nil || services.Global.LevelSettings == nil { + respondEphemeral(s, i, "Leveling service is not available.") + return false + } + return true +} + +func requireLevelSettingsService(s *discordgo.Session, i *discordgo.InteractionCreate) bool { + if services.Global == nil || services.Global.LevelSettings == nil { + respondEphemeral(s, i, "Leveling service is not available.") + return false + } + return true +} + +func parseGuildID(s *discordgo.Session, i *discordgo.InteractionCreate) (int64, bool) { + guildID, err := strconv.ParseInt(i.GuildID, 10, 64) + if err != nil { + respondEphemeral(s, i, "Invalid guild ID.") + return 0, false + } + return guildID, true +} + +func requireLevelSystemEnabled(s *discordgo.Session, i *discordgo.InteractionCreate) bool { + if !requireLevelSettingsService(s, i) { + return false + } + guildID, ok := parseGuildID(s, i) + if !ok { + return false + } + enabled, err := services.Global.LevelSettings.IsLevelSystemEnabled(context.Background(), guildID) + if err != nil { + respondEphemeral(s, i, "Failed to load leveling settings.") + return false + } + if !enabled { + respondEphemeral(s, i, "Leveling system is disabled on this server. Use /slvl toggle to enable it.") + return false + } + return true +} + +func subcommandOptionMap(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption { + data := i.ApplicationCommandData() + if len(data.Options) == 0 { + return map[string]*discordgo.ApplicationCommandInteractionDataOption{} + } + opts := data.Options[0].Options + m := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(opts)) + for _, opt := range opts { + m[opt.Name] = opt + } + return m +} + diff --git a/internal/commands/level/level.go b/internal/commands/level/level.go index 8e0da27..4cad7dc 100644 --- a/internal/commands/level/level.go +++ b/internal/commands/level/level.go @@ -30,15 +30,18 @@ var LeaderboardCommand = &discordgo.ApplicationCommand{ Description: "Top 10 levels in this server", } +var RewardsCommand = &discordgo.ApplicationCommand{ + Name: "rewards", + Description: "View level rewards in this server", +} + func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) { - if services.Global == nil || services.Global.Level == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelServices(s, i) { + return + } + + if i.GuildID != "" && !requireLevelSystemEnabled(s, i) { + respondEphemeral(s, i, "Leveling system is disabled on this server.") return } @@ -58,18 +61,20 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { + guildID, ok := parseGuildID(s, i) + if !ok { return } userID, err := strconv.ParseInt(target.ID, 10, 64) if err != nil { + respondEphemeral(s, i, "Invalid user ID.") return } ctx := context.Background() lvl, err := services.Global.Level.GetLevel(ctx, guildID, userID) if err != nil { + respondEphemeral(s, i, "Failed to load level.") return } @@ -84,13 +89,7 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) { Avatar: avatarURL, }) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to render rank card.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to render rank card.") return } @@ -108,61 +107,31 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) { } func HandleLeaderboard(s *discordgo.Session, i *discordgo.InteractionCreate) { - if i.GuildID == "" { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "This command can only be used in a server.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireGuild(s, i) { return } - if services.Global == nil || services.Global.Level == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelServices(s, i) { return } - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } ctx := context.Background() + if !requireLevelSystemEnabled(s, i) { + return + } top, err := services.Global.Level.TopLevels(ctx, guildID, 10) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to load leaderboard.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to load leaderboard.") return } if len(top) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "No leaderboard data yet.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "No leaderboard data yet.") return } @@ -194,3 +163,56 @@ func HandleLeaderboard(s *discordgo.Session, i *discordgo.InteractionCreate) { }, }) } + +func HandleRewards(s *discordgo.Session, i *discordgo.InteractionCreate) { + if !requireGuild(s, i) { + return + } + + if !requireLevelSettingsService(s, i) { + return + } + + guildID, ok := parseGuildID(s, i) + if !ok { + return + } + + ctx := context.Background() + if !requireLevelSystemEnabled(s, i) { + return + } + rewards, err := services.Global.LevelSettings.ListRoleRewards(ctx, guildID, 25) + if err != nil { + respondEphemeral(s, i, "Failed to load rewards.") + return + } + + if len(rewards) == 0 { + respondEphemeral(s, i, "No rewards configured yet.") + return + } + + fields := make([]*discordgo.MessageEmbedField, 0, len(rewards)) + for _, r := range rewards { + fields = append(fields, &discordgo.MessageEmbedField{ + Name: fmt.Sprintf("Level %d", r.Level), + Value: fmt.Sprintf("<@&%d>", r.RoleID), + Inline: true, + }) + } + + embed := &discordgo.MessageEmbed{ + Title: "Level Rewards", + Description: "Roles granted when you reach a level.", + Fields: fields, + Color: 0xF59E0B, + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Embeds: []*discordgo.MessageEmbed{embed}, + }, + }) +} diff --git a/internal/commands/level/level_config.go b/internal/commands/level/level_config.go index 3f2d09a..b8a3ac6 100644 --- a/internal/commands/level/level_config.go +++ b/internal/commands/level/level_config.go @@ -102,63 +102,29 @@ func HandleLevelConfig(s *discordgo.Session, i *discordgo.InteractionCreate) { case "set-reward": HandleLevelSetReward(s, i) default: - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid subcommand.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid subcommand.") } } func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Basic permission check: require Manage Guild permission - if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You don't have permission to manage leveling settings.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireManageGuild(s, i) { return } - if services.Global == nil || services.Global.LevelSettings == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelSettingsService(s, i) { return } ctx := context.Background() - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } enabled, err := services.Global.LevelSettings.ToggleLevelSystem(ctx, guildID) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to toggle leveling system.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to toggle leveling system.") return } @@ -167,294 +133,131 @@ func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) { status = "enabled" } - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling system " + status + " for this server.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Leveling system "+status+" for this server.") } func HandleLevelSet(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Basic permission check: require Manage Guild permission - if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You don't have permission to manage leveling settings.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) { return } - // check if the leveling service is available if services.Global == nil || services.Global.Level == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Leveling service is not available.") return } - data := i.ApplicationCommandData() - optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options)) - for _, opt := range data.Options[0].Options { - optMap[opt.Name] = opt - } + optMap := subcommandOptionMap(i) userOpt, ok := optMap["user"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing user option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing user option.") return } levelOpt, ok := optMap["level"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing level option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing level option.") return } targetUser := userOpt.UserValue(s) if targetUser == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid user.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid user.") return } // prevent setting bots (including ourselves) if targetUser.Bot { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You can't set a bot's level.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "You can't set a bot's level.") return } newLevel := int(levelOpt.IntValue()) ctx := context.Background() - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } userID, err := strconv.ParseInt(targetUser.ID, 10, 64) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid user ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid user ID.") return } err = services.Global.Level.SetLevel(ctx, guildID, userID, newLevel) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to set level.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to set level.") return } - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Level set to " + strconv.Itoa(newLevel) + " for " + targetUser.Mention() + ".", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Level set to "+strconv.Itoa(newLevel)+" for "+targetUser.Mention()+".") } func HandleLevelSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Basic permission check: require Manage Guild permission - if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You don't have permission to manage leveling settings.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) { return } - // check if the leveling service is available - if services.Global == nil || services.Global.Level == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelSettingsService(s, i) { return } - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } - data := i.ApplicationCommandData() - optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options)) - for _, opt := range data.Options[0].Options { - optMap[opt.Name] = opt - } + optMap := subcommandOptionMap(i) channelOpt, ok := optMap["channel"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing channel option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing channel option.") return } channelID := channelOpt.ChannelValue(s) if channelID == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid channel.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid channel.") return } ctx := context.Background() channelIDInt, err := strconv.ParseInt(channelID.ID, 10, 64) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid channel ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid channel ID.") return } err = services.Global.LevelSettings.SetLevelUpChannel(ctx, guildID, channelIDInt) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to set level up channel.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to set level up channel.") + return } - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Level up channel set to " + channelID.Mention() + ".", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Level up channel set to "+channelID.Mention()+".") } func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Basic permission check: require Manage Guild permission - if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You don't have permission to manage leveling settings.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) { return } - // check if the leveling service is available - if services.Global == nil || services.Global.Level == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelSettingsService(s, i) { return } - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } - data := i.ApplicationCommandData() - optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options)) - for _, opt := range data.Options[0].Options { - optMap[opt.Name] = opt - } + optMap := subcommandOptionMap(i) messageOpt, ok := optMap["message"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing message option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing message option.") return } @@ -464,134 +267,60 @@ func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate) } ctx := context.Background() - err = services.Global.LevelSettings.SetLevelUpMessage(ctx, guildID, message) + err := services.Global.LevelSettings.SetLevelUpMessage(ctx, guildID, message) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to set level up channel.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to set level up message.") + return } - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Level up message set to " + message + ".", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Level up message set.") } func HandleLevelSetReward(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Basic permission check: require Manage Guild permission - if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "You don't have permission to manage leveling settings.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) { return } - // check if the leveling service is available - if services.Global == nil || services.Global.LevelSettings == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Leveling service is not available.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + if !requireLevelSettingsService(s, i) { return } - guildID, err := strconv.ParseInt(i.GuildID, 10, 64) - if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid guild ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + guildID, ok := parseGuildID(s, i) + if !ok { return } - data := i.ApplicationCommandData() - optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options)) - for _, opt := range data.Options[0].Options { - optMap[opt.Name] = opt - } + optMap := subcommandOptionMap(i) levelOpt, ok := optMap["level"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing level option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing level option.") return } roleOpt, ok := optMap["role"] if !ok { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Missing role option.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Missing role option.") return } level := int(levelOpt.IntValue()) role := roleOpt.RoleValue(s, i.GuildID) if role == nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid role.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid role.") return } roleID, err := strconv.ParseInt(role.ID, 10, 64) if err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Invalid role ID.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Invalid role ID.") return } ctx := context.Background() if err := services.Global.LevelSettings.SetRoleRewardForLevel(ctx, guildID, level, roleID); err != nil { - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Failed to set reward.", - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Failed to set reward.") return } - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Reward set: level " + strconv.Itoa(level) + " -> " + role.Mention(), - Flags: discordgo.MessageFlagsEphemeral, - }, - }) + respondEphemeral(s, i, "Reward set: level "+strconv.Itoa(level)+" -> "+role.Mention()) } diff --git a/internal/db/repos/levelrepo/repo.go b/internal/db/repos/levelrepo/repo.go index f26043a..acf7ccd 100644 --- a/internal/db/repos/levelrepo/repo.go +++ b/internal/db/repos/levelrepo/repo.go @@ -56,9 +56,9 @@ func (r *Repo) UpsertLevel(ctx context.Context, lvl *Level) error { func (r *Repo) SetLevel(ctx context.Context, guildID, userID int64, level int) error { const q = ` INSERT INTO levels (guild, user_id, level, xp) - VALUES ($1, $2, $3, 99) + VALUES ($1, $2, $3, 0) ON CONFLICT (guild, user_id) - DO UPDATE SET level = EXCLUDED.level, xp = 99 + DO UPDATE SET level = EXCLUDED.level, xp = 0 ` _, err := r.db.ExecContext(ctx, q, guildID, userID, level) return err diff --git a/internal/db/repos/settingsrepo/repo.go b/internal/db/repos/settingsrepo/repo.go index 81ba8cb..198034c 100644 --- a/internal/db/repos/settingsrepo/repo.go +++ b/internal/db/repos/settingsrepo/repo.go @@ -9,6 +9,12 @@ type Repo struct { db *sql.DB } +type LevelReward struct { + GuildID int64 + Level int + RoleID int64 +} + func NewRepo(db *sql.DB) *Repo { return &Repo{db: db} } @@ -95,6 +101,34 @@ func (r *Repo) SetRoleRewardForLevel(ctx context.Context, guildID int64, level i return err } +func (r *Repo) ListRoleRewards(ctx context.Context, guildID int64, limit int) ([]*LevelReward, error) { + const q = ` + SELECT levelreq, role + FROM levelrewards + WHERE guild_id = $1 + ORDER BY levelreq ASC + LIMIT $2 + ` + rows, err := r.db.QueryContext(ctx, q, guildID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]*LevelReward, 0, limit) + for rows.Next() { + rw := &LevelReward{GuildID: guildID} + if err := rows.Scan(&rw.Level, &rw.RoleID); err != nil { + return nil, err + } + out = append(out, rw) + } + if err := rows.Err(); err != nil { + return nil, err + } + return out, nil +} + func (r *Repo) SetLevelUpChannel(ctx context.Context, guildID int64, channelID int64) error { const q = ` INSERT INTO levelup (guild_id, levelup_channel_id) diff --git a/internal/db/services/levelsettings/service.go b/internal/db/services/levelsettings/service.go index f2dcae4..a97eb00 100644 --- a/internal/db/services/levelsettings/service.go +++ b/internal/db/services/levelsettings/service.go @@ -47,3 +47,11 @@ func (s *Service) GetLevelUpMessage(ctx context.Context, guildID int64) (string, func (s *Service) SetRoleRewardForLevel(ctx context.Context, guildID int64, level int, roleID int64) error { return s.settings.SetRoleRewardForLevel(ctx, guildID, level, roleID) } + +func (s *Service) ListRoleRewards(ctx context.Context, guildID int64, limit int) ([]*settingsrepo.LevelReward, error) { + return s.settings.ListRoleRewards(ctx, guildID, limit) +} + +func (s *Service) IsLevelSystemEnabled(ctx context.Context, guildID int64) (bool, error) { + return s.settings.IsLevelSystemEnabled(ctx, guildID) +} diff --git a/tmp/bot b/tmp/bot index b8599fc..c17d945 100755 Binary files a/tmp/bot and b/tmp/bot differ diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 680689a..38667d6 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file