feat: leveling system

This commit is contained in:
2026-03-17 15:35:25 +00:00
parent 2bce787c05
commit 8ba26f8bb9
9 changed files with 271 additions and 381 deletions
+2
View File
@@ -14,6 +14,7 @@ var AllCommands = []*discordgo.ApplicationCommand{
cmdlevel.LevelConfigCommand, cmdlevel.LevelConfigCommand,
cmdlevel.RankCommand, cmdlevel.RankCommand,
cmdlevel.LeaderboardCommand, cmdlevel.LeaderboardCommand,
cmdlevel.RewardsCommand,
} }
var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ 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, "slvl": cmdlevel.HandleLevelConfig,
"rank": cmdlevel.HandleRank, "rank": cmdlevel.HandleRank,
"leaderboard": cmdlevel.HandleLeaderboard, "leaderboard": cmdlevel.HandleLeaderboard,
"rewards": cmdlevel.HandleRewards,
} }
func HandleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
+95
View File
@@ -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
}
+78 -56
View File
@@ -30,15 +30,18 @@ var LeaderboardCommand = &discordgo.ApplicationCommand{
Description: "Top 10 levels in this server", 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) { func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) {
if services.Global == nil || services.Global.Level == nil { if !requireLevelServices(s, i) {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ return
Type: discordgo.InteractionResponseChannelMessageWithSource, }
Data: &discordgo.InteractionResponseData{
Content: "Leveling service is not available.", if i.GuildID != "" && !requireLevelSystemEnabled(s, i) {
Flags: discordgo.MessageFlagsEphemeral, respondEphemeral(s, i, "Leveling system is disabled on this server.")
},
})
return return
} }
@@ -58,18 +61,20 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) {
return return
} }
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
return return
} }
userID, err := strconv.ParseInt(target.ID, 10, 64) userID, err := strconv.ParseInt(target.ID, 10, 64)
if err != nil { if err != nil {
respondEphemeral(s, i, "Invalid user ID.")
return return
} }
ctx := context.Background() ctx := context.Background()
lvl, err := services.Global.Level.GetLevel(ctx, guildID, userID) lvl, err := services.Global.Level.GetLevel(ctx, guildID, userID)
if err != nil { if err != nil {
respondEphemeral(s, i, "Failed to load level.")
return return
} }
@@ -84,13 +89,7 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) {
Avatar: avatarURL, Avatar: avatarURL,
}) })
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to render rank card.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to render rank card.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
@@ -108,61 +107,31 @@ func HandleRank(s *discordgo.Session, i *discordgo.InteractionCreate) {
} }
func HandleLeaderboard(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLeaderboard(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.GuildID == "" { if !requireGuild(s, i) {
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,
},
})
return return
} }
if services.Global == nil || services.Global.Level == nil { if !requireLevelServices(s, i) {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Leveling service is not available.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
ctx := context.Background() ctx := context.Background()
if !requireLevelSystemEnabled(s, i) {
return
}
top, err := services.Global.Level.TopLevels(ctx, guildID, 10) top, err := services.Global.Level.TopLevels(ctx, guildID, 10)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to load leaderboard.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to load leaderboard.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
if len(top) == 0 { if len(top) == 0 {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "No leaderboard data yet.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "No leaderboard data yet.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return 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},
},
})
}
+51 -322
View File
@@ -102,63 +102,29 @@ func HandleLevelConfig(s *discordgo.Session, i *discordgo.InteractionCreate) {
case "set-reward": case "set-reward":
HandleLevelSetReward(s, i) HandleLevelSetReward(s, i)
default: default:
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid subcommand.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid subcommand.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
} }
func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Basic permission check: require Manage Guild permission if !requireManageGuild(s, i) {
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,
},
})
return return
} }
if services.Global == nil || services.Global.LevelSettings == nil { if !requireLevelSettingsService(s, i) {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Leveling service is not available.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
ctx := context.Background() ctx := context.Background()
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
enabled, err := services.Global.LevelSettings.ToggleLevelSystem(ctx, guildID) enabled, err := services.Global.LevelSettings.ToggleLevelSystem(ctx, guildID)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to toggle leveling system.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to toggle leveling system.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
@@ -167,294 +133,131 @@ func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) {
status = "enabled" status = "enabled"
} }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Leveling system "+status+" for this server.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Leveling system " + status + " for this server.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
func HandleLevelSet(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLevelSet(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Basic permission check: require Manage Guild permission if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
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,
},
})
return return
} }
// check if the leveling service is available
if services.Global == nil || services.Global.Level == nil { if services.Global == nil || services.Global.Level == nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Leveling service is not available.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Leveling service is not available.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
data := i.ApplicationCommandData() optMap := subcommandOptionMap(i)
optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options))
for _, opt := range data.Options[0].Options {
optMap[opt.Name] = opt
}
userOpt, ok := optMap["user"] userOpt, ok := optMap["user"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing user option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing user option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
levelOpt, ok := optMap["level"] levelOpt, ok := optMap["level"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing level option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing level option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
targetUser := userOpt.UserValue(s) targetUser := userOpt.UserValue(s)
if targetUser == nil { if targetUser == nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid user.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid user.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
// prevent setting bots (including ourselves) // prevent setting bots (including ourselves)
if targetUser.Bot { if targetUser.Bot {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "You can't set a bot's level.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "You can't set a bot's level.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
newLevel := int(levelOpt.IntValue()) newLevel := int(levelOpt.IntValue())
ctx := context.Background() ctx := context.Background()
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
userID, err := strconv.ParseInt(targetUser.ID, 10, 64) userID, err := strconv.ParseInt(targetUser.ID, 10, 64)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid user ID.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid user ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
err = services.Global.Level.SetLevel(ctx, guildID, userID, newLevel) err = services.Global.Level.SetLevel(ctx, guildID, userID, newLevel)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to set level.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to set level.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Level set to "+strconv.Itoa(newLevel)+" for "+targetUser.Mention()+".")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Level set to " + strconv.Itoa(newLevel) + " for " + targetUser.Mention() + ".",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
func HandleLevelSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLevelSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Basic permission check: require Manage Guild permission if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
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,
},
})
return return
} }
// check if the leveling service is available if !requireLevelSettingsService(s, i) {
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,
},
})
return return
} }
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
data := i.ApplicationCommandData() optMap := subcommandOptionMap(i)
optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options))
for _, opt := range data.Options[0].Options {
optMap[opt.Name] = opt
}
channelOpt, ok := optMap["channel"] channelOpt, ok := optMap["channel"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing channel option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing channel option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
channelID := channelOpt.ChannelValue(s) channelID := channelOpt.ChannelValue(s)
if channelID == nil { if channelID == nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid channel.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid channel.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
ctx := context.Background() ctx := context.Background()
channelIDInt, err := strconv.ParseInt(channelID.ID, 10, 64) channelIDInt, err := strconv.ParseInt(channelID.ID, 10, 64)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid channel ID.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid channel ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
err = services.Global.LevelSettings.SetLevelUpChannel(ctx, guildID, channelIDInt) err = services.Global.LevelSettings.SetLevelUpChannel(ctx, guildID, channelIDInt)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to set level up channel.")
Type: discordgo.InteractionResponseChannelMessageWithSource, return
Data: &discordgo.InteractionResponseData{
Content: "Failed to set level up channel.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Level up channel set to "+channelID.Mention()+".")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Level up channel set to " + channelID.Mention() + ".",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Basic permission check: require Manage Guild permission if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
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,
},
})
return return
} }
// check if the leveling service is available if !requireLevelSettingsService(s, i) {
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,
},
})
return return
} }
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
data := i.ApplicationCommandData() optMap := subcommandOptionMap(i)
optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options))
for _, opt := range data.Options[0].Options {
optMap[opt.Name] = opt
}
messageOpt, ok := optMap["message"] messageOpt, ok := optMap["message"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing message option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing message option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
@@ -464,134 +267,60 @@ func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate)
} }
ctx := context.Background() ctx := context.Background()
err = services.Global.LevelSettings.SetLevelUpMessage(ctx, guildID, message) err := services.Global.LevelSettings.SetLevelUpMessage(ctx, guildID, message)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to set level up message.")
Type: discordgo.InteractionResponseChannelMessageWithSource, return
Data: &discordgo.InteractionResponseData{
Content: "Failed to set level up channel.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Level up message set.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Level up message set to " + message + ".",
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
func HandleLevelSetReward(s *discordgo.Session, i *discordgo.InteractionCreate) { func HandleLevelSetReward(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Basic permission check: require Manage Guild permission if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
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,
},
})
return return
} }
// check if the leveling service is available if !requireLevelSettingsService(s, i) {
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,
},
})
return return
} }
guildID, err := strconv.ParseInt(i.GuildID, 10, 64) guildID, ok := parseGuildID(s, i)
if err != nil { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid guild ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
data := i.ApplicationCommandData() optMap := subcommandOptionMap(i)
optMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(data.Options[0].Options))
for _, opt := range data.Options[0].Options {
optMap[opt.Name] = opt
}
levelOpt, ok := optMap["level"] levelOpt, ok := optMap["level"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing level option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing level option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
roleOpt, ok := optMap["role"] roleOpt, ok := optMap["role"]
if !ok { if !ok {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Missing role option.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Missing role option.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
level := int(levelOpt.IntValue()) level := int(levelOpt.IntValue())
role := roleOpt.RoleValue(s, i.GuildID) role := roleOpt.RoleValue(s, i.GuildID)
if role == nil { if role == nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid role.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid role.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
roleID, err := strconv.ParseInt(role.ID, 10, 64) roleID, err := strconv.ParseInt(role.ID, 10, 64)
if err != nil { if err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Invalid role ID.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Invalid role ID.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
ctx := context.Background() ctx := context.Background()
if err := services.Global.LevelSettings.SetRoleRewardForLevel(ctx, guildID, level, roleID); err != nil { if err := services.Global.LevelSettings.SetRoleRewardForLevel(ctx, guildID, level, roleID); err != nil {
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Failed to set reward.")
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Failed to set reward.",
Flags: discordgo.MessageFlagsEphemeral,
},
})
return return
} }
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ respondEphemeral(s, i, "Reward set: level "+strconv.Itoa(level)+" -> "+role.Mention())
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Reward set: level " + strconv.Itoa(level) + " -> " + role.Mention(),
Flags: discordgo.MessageFlagsEphemeral,
},
})
} }
+2 -2
View File
@@ -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 { func (r *Repo) SetLevel(ctx context.Context, guildID, userID int64, level int) error {
const q = ` const q = `
INSERT INTO levels (guild, user_id, level, xp) INSERT INTO levels (guild, user_id, level, xp)
VALUES ($1, $2, $3, 99) VALUES ($1, $2, $3, 0)
ON CONFLICT (guild, user_id) 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) _, err := r.db.ExecContext(ctx, q, guildID, userID, level)
return err return err
+34
View File
@@ -9,6 +9,12 @@ type Repo struct {
db *sql.DB db *sql.DB
} }
type LevelReward struct {
GuildID int64
Level int
RoleID int64
}
func NewRepo(db *sql.DB) *Repo { func NewRepo(db *sql.DB) *Repo {
return &Repo{db: db} return &Repo{db: db}
} }
@@ -95,6 +101,34 @@ func (r *Repo) SetRoleRewardForLevel(ctx context.Context, guildID int64, level i
return err 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 { func (r *Repo) SetLevelUpChannel(ctx context.Context, guildID int64, channelID int64) error {
const q = ` const q = `
INSERT INTO levelup (guild_id, levelup_channel_id) INSERT INTO levelup (guild_id, levelup_channel_id)
@@ -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 { func (s *Service) SetRoleRewardForLevel(ctx context.Context, guildID int64, level int, roleID int64) error {
return s.settings.SetRoleRewardForLevel(ctx, guildID, level, roleID) 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)
}
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -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 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