feat: leveling system refractor
This commit is contained in:
@@ -9,21 +9,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AllCommands = []*discordgo.ApplicationCommand{
|
var AllCommands = []*discordgo.ApplicationCommand{
|
||||||
fun.PingCommand,
|
fun.Ping,
|
||||||
help.HelpCommand,
|
help.Help,
|
||||||
cmdlevel.LevelConfigCommand,
|
cmdlevel.Slvl,
|
||||||
cmdlevel.RankCommand,
|
cmdlevel.Rank,
|
||||||
cmdlevel.LeaderboardCommand,
|
cmdlevel.Leaderboard,
|
||||||
cmdlevel.RewardsCommand,
|
cmdlevel.Rewards,
|
||||||
}
|
}
|
||||||
|
|
||||||
var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||||
"ping": fun.HandlePing,
|
"ping": fun.PingHandler,
|
||||||
"help": help.HandleHelp,
|
"help": help.HelpHandler,
|
||||||
"slvl": cmdlevel.HandleLevelConfig,
|
"slvl": cmdlevel.SlvlHandler,
|
||||||
"rank": cmdlevel.HandleRank,
|
"rank": cmdlevel.RankHandler,
|
||||||
"leaderboard": cmdlevel.HandleLeaderboard,
|
"leaderboard": cmdlevel.LeaderboardHandler,
|
||||||
"rewards": cmdlevel.HandleRewards,
|
"rewards": cmdlevel.RewardsHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func HandleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package fun
|
|||||||
|
|
||||||
import "github.com/bwmarrin/discordgo"
|
import "github.com/bwmarrin/discordgo"
|
||||||
|
|
||||||
var PingCommand = &discordgo.ApplicationCommand{
|
var Ping = &discordgo.ApplicationCommand{
|
||||||
Name: "ping",
|
Name: "ping",
|
||||||
Description: "Ping the bot",
|
Description: "Ping the bot",
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandlePing(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func PingHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HelpCommand = &discordgo.ApplicationCommand{
|
var Help = &discordgo.ApplicationCommand{
|
||||||
Name: "help",
|
Name: "help",
|
||||||
Description: "Get help with the bot",
|
Description: "Get help with the bot",
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleHelp(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Slvl = &discordgo.ApplicationCommand{
|
||||||
|
Name: "slvl",
|
||||||
|
Description: "Leveling System",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
slvlToggleOption(),
|
||||||
|
slvlSetLevelOption(),
|
||||||
|
slvlSetChannelOption(),
|
||||||
|
slvlSetMessageOption(),
|
||||||
|
slvlSetRewardOption(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func SlvlHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
data := i.ApplicationCommandData()
|
||||||
|
if len(data.Options) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch data.Options[0].Name {
|
||||||
|
case "toggle":
|
||||||
|
slvlToggleHandler(s, i)
|
||||||
|
case "set":
|
||||||
|
slvlSetLevelHandler(s, i)
|
||||||
|
case "set-channel":
|
||||||
|
slvlSetChannelHandler(s, i)
|
||||||
|
case "set-message":
|
||||||
|
slvlSetMessageHandler(s, i)
|
||||||
|
case "set-reward":
|
||||||
|
slvlSetRewardHandler(s, i)
|
||||||
|
default:
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid subcommand.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slvlSetChannelOption() *discordgo.ApplicationCommandOption {
|
||||||
|
return &discordgo.ApplicationCommandOption{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "set-channel",
|
||||||
|
Description: "Set the level up channel",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionChannel,
|
||||||
|
Name: "channel",
|
||||||
|
Description: "Channel for level-up messages",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slvlSetChannelHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireManageGuild(s, i) || !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSettingsService(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
optMap := shared.SubcommandOptionMap(i)
|
||||||
|
channelOpt, ok := optMap["channel"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing channel option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := channelOpt.ChannelValue(s)
|
||||||
|
if ch == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid channel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelID, err := strconv.ParseInt(ch.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid channel ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.LevelSettings.SetLevelUpChannel(context.Background(), guildID, channelID); err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to set level up channel.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.RespondEphemeral(s, i, "Level up channel set to "+ch.Mention()+".")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slvlSetLevelOption() *discordgo.ApplicationCommandOption {
|
||||||
|
return &discordgo.ApplicationCommandOption{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "set",
|
||||||
|
Description: "Set the level of a user",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "The user to set the level of",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "level",
|
||||||
|
Description: "The level to set the user to",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slvlSetLevelHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireManageGuild(s, i) || !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if services.Global == nil || services.Global.Level == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Leveling service is not available.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
optMap := shared.SubcommandOptionMap(i)
|
||||||
|
userOpt, ok := optMap["user"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing user option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
levelOpt, ok := optMap["level"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing level option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUser := userOpt.UserValue(s)
|
||||||
|
if targetUser == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid user.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if targetUser.Bot {
|
||||||
|
shared.RespondEphemeral(s, i, "You can't set a bot's level.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newLevel := int(levelOpt.IntValue())
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(targetUser.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid user ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.Level.SetLevel(context.Background(), guildID, userID, newLevel); err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to set level.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.RespondEphemeral(s, i, "Level set to "+strconv.Itoa(newLevel)+" for "+targetUser.Mention()+".")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slvlSetMessageOption() *discordgo.ApplicationCommandOption {
|
||||||
|
return &discordgo.ApplicationCommandOption{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "set-message",
|
||||||
|
Description: "Set the level up message",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Name: "message",
|
||||||
|
Description: "Level-up message. Variables: {user}, {level}",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slvlSetMessageHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireManageGuild(s, i) || !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSettingsService(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
optMap := shared.SubcommandOptionMap(i)
|
||||||
|
messageOpt, ok := optMap["message"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing message option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message := messageOpt.StringValue()
|
||||||
|
if message == "" {
|
||||||
|
message = "GG {user}, you reached level {level}!"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.LevelSettings.SetLevelUpMessage(context.Background(), guildID, message); err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to set level up message.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.RespondEphemeral(s, i, "Level up message set.")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slvlSetRewardOption() *discordgo.ApplicationCommandOption {
|
||||||
|
return &discordgo.ApplicationCommandOption{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "set-reward",
|
||||||
|
Description: "Set role reward for a level",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "level",
|
||||||
|
Description: "Level to reward",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionRole,
|
||||||
|
Name: "role",
|
||||||
|
Description: "Role to grant",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slvlSetRewardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireManageGuild(s, i) || !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSettingsService(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
optMap := shared.SubcommandOptionMap(i)
|
||||||
|
levelOpt, ok := optMap["level"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing level option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roleOpt, ok := optMap["role"]
|
||||||
|
if !ok {
|
||||||
|
shared.RespondEphemeral(s, i, "Missing role option.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
level := int(levelOpt.IntValue())
|
||||||
|
role := roleOpt.RoleValue(s, i.GuildID)
|
||||||
|
if role == nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid role.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
roleID, err := strconv.ParseInt(role.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid role ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Global.LevelSettings.SetRoleRewardForLevel(context.Background(), guildID, level, roleID); err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to set reward.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.RespondEphemeral(s, i, "Reward set: level "+strconv.Itoa(level)+" -> "+role.Mention())
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func slvlToggleOption() *discordgo.ApplicationCommandOption {
|
||||||
|
return &discordgo.ApplicationCommandOption{
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Name: "toggle",
|
||||||
|
Description: "Toggle the leveling system",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func slvlToggleHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireManageGuild(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSettingsService(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled, err := services.Global.LevelSettings.ToggleLevelSystem(context.Background(), guildID)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to toggle leveling system.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status := "disabled"
|
||||||
|
if enabled {
|
||||||
|
status = "enabled"
|
||||||
|
}
|
||||||
|
shared.RespondEphemeral(s, i, "Leveling system "+status+" for this server.")
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
package level
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"bytes"
|
|
||||||
"strconv"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"velox-bot/internal/db/services"
|
|
||||||
"velox-bot/internal/rankcard"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
var RankCommand = &discordgo.ApplicationCommand{
|
|
||||||
Name: "rank",
|
|
||||||
Description: "Get your level card",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionUser,
|
|
||||||
Name: "user",
|
|
||||||
Description: "The user to get the rank card of",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var LeaderboardCommand = &discordgo.ApplicationCommand{
|
|
||||||
Name: "leaderboard",
|
|
||||||
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 !requireLevelServices(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.GuildID != "" && !requireLevelSystemEnabled(s, i) {
|
|
||||||
respondEphemeral(s, i, "Leveling system is disabled on this server.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine target user: option or caller.
|
|
||||||
target := i.User
|
|
||||||
if i.Member != nil && i.Member.User != nil {
|
|
||||||
target = i.Member.User
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(i.ApplicationCommandData().Options) > 0 {
|
|
||||||
if u := i.ApplicationCommandData().Options[0].UserValue(s); u != nil {
|
|
||||||
target = u
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if target == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
percent := float64(lvl.XP) / 100.0
|
|
||||||
avatarURL := target.AvatarURL("256")
|
|
||||||
|
|
||||||
pngBytes, err := rankcard.RenderPNG(ctx, rankcard.Data{
|
|
||||||
Name: target.Username,
|
|
||||||
Level: lvl.Level,
|
|
||||||
XP: lvl.XP,
|
|
||||||
Percent: percent,
|
|
||||||
Avatar: avatarURL,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to render rank card.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Files: []*discordgo.File{
|
|
||||||
{
|
|
||||||
Name: "levelcard.png",
|
|
||||||
Reader: bytes.NewReader(pngBytes),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLeaderboard(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireGuild(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !requireLevelServices(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
respondEphemeral(s, i, "Failed to load leaderboard.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(top) == 0 {
|
|
||||||
respondEphemeral(s, i, "No leaderboard data yet.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := make([]*discordgo.MessageEmbedField, 0, len(top))
|
|
||||||
for idx, entry := range top {
|
|
||||||
display := fmt.Sprintf("<@%d>", entry.UserID)
|
|
||||||
if u, err := s.User(strconv.FormatInt(entry.UserID, 10)); err == nil && u != nil && u.Username != "" {
|
|
||||||
display = u.Username
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = append(fields, &discordgo.MessageEmbedField{
|
|
||||||
Name: fmt.Sprintf("%d. %s", idx+1, display),
|
|
||||||
Value: fmt.Sprintf("Level: **%d** | XP: **%d**", entry.Level, entry.XP),
|
|
||||||
Inline: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
embed := &discordgo.MessageEmbed{
|
|
||||||
Title: "Leaderboard",
|
|
||||||
Description: fmt.Sprintf("These are the top %d users in the server", len(top)),
|
|
||||||
Fields: fields,
|
|
||||||
Color: 0xF59E0B,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
||||||
Data: &discordgo.InteractionResponseData{
|
|
||||||
Embeds: []*discordgo.MessageEmbed{embed},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
package level
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"velox-bot/internal/db/services"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
)
|
|
||||||
|
|
||||||
var LevelConfigCommand = &discordgo.ApplicationCommand{
|
|
||||||
Name: "slvl",
|
|
||||||
Description: "Leveling System",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Name: "toggle",
|
|
||||||
Description: "Toggle the leveling system",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Name: "set",
|
|
||||||
Description: "Set the level of a user",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionUser,
|
|
||||||
Name: "user",
|
|
||||||
Description: "The user to set the level of",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionInteger,
|
|
||||||
Name: "level",
|
|
||||||
Description: "The level to set the user to",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Name: "set-channel",
|
|
||||||
Description: "Set the level up channel",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionChannel,
|
|
||||||
Name: "channel",
|
|
||||||
Description: "The channel to set the level up channel to. If not provided, the current channel will be used.",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Name: "set-message",
|
|
||||||
Description: "Set the level up message",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionString,
|
|
||||||
Name: "message",
|
|
||||||
Description: "Level-up message. Variables: {user}, {level}",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
|
||||||
Name: "set-reward",
|
|
||||||
Description: "Set role reward for a level",
|
|
||||||
Options: []*discordgo.ApplicationCommandOption{
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionInteger,
|
|
||||||
Name: "level",
|
|
||||||
Description: "Level to reward",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: discordgo.ApplicationCommandOptionRole,
|
|
||||||
Name: "role",
|
|
||||||
Description: "Role to grant",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelConfig(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
|
|
||||||
data := i.ApplicationCommandData()
|
|
||||||
subCommand := data.Options[0].Name
|
|
||||||
|
|
||||||
switch subCommand {
|
|
||||||
case "toggle":
|
|
||||||
HandleLevelToggle(s, i)
|
|
||||||
case "set":
|
|
||||||
HandleLevelSet(s, i)
|
|
||||||
case "set-channel":
|
|
||||||
HandleLevelSetChannel(s, i)
|
|
||||||
case "set-message":
|
|
||||||
HandleLevelSetMessage(s, i)
|
|
||||||
case "set-reward":
|
|
||||||
HandleLevelSetReward(s, i)
|
|
||||||
default:
|
|
||||||
respondEphemeral(s, i, "Invalid subcommand.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelToggle(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireManageGuild(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !requireLevelSettingsService(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
guildID, ok := parseGuildID(s, i)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled, err := services.Global.LevelSettings.ToggleLevelSystem(ctx, guildID)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to toggle leveling system.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
status := "disabled"
|
|
||||||
if enabled {
|
|
||||||
status = "enabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
respondEphemeral(s, i, "Leveling system "+status+" for this server.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelSet(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if services.Global == nil || services.Global.Level == nil {
|
|
||||||
respondEphemeral(s, i, "Leveling service is not available.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
optMap := subcommandOptionMap(i)
|
|
||||||
|
|
||||||
userOpt, ok := optMap["user"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing user option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
levelOpt, ok := optMap["level"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing level option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targetUser := userOpt.UserValue(s)
|
|
||||||
if targetUser == nil {
|
|
||||||
respondEphemeral(s, i, "Invalid user.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent setting bots (including ourselves)
|
|
||||||
if targetUser.Bot {
|
|
||||||
respondEphemeral(s, i, "You can't set a bot's level.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
newLevel := int(levelOpt.IntValue())
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
guildID, ok := parseGuildID(s, i)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, err := strconv.ParseInt(targetUser.ID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Invalid user ID.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = services.Global.Level.SetLevel(ctx, guildID, userID, newLevel)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to set level.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
respondEphemeral(s, i, "Level set to "+strconv.Itoa(newLevel)+" for "+targetUser.Mention()+".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !requireLevelSettingsService(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guildID, ok := parseGuildID(s, i)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
optMap := subcommandOptionMap(i)
|
|
||||||
|
|
||||||
channelOpt, ok := optMap["channel"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing channel option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channelID := channelOpt.ChannelValue(s)
|
|
||||||
if channelID == nil {
|
|
||||||
respondEphemeral(s, i, "Invalid channel.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
channelIDInt, err := strconv.ParseInt(channelID.ID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Invalid channel ID.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = services.Global.LevelSettings.SetLevelUpChannel(ctx, guildID, channelIDInt)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to set level up channel.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
respondEphemeral(s, i, "Level up channel set to "+channelID.Mention()+".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelSetMessage(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !requireLevelSettingsService(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guildID, ok := parseGuildID(s, i)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
optMap := subcommandOptionMap(i)
|
|
||||||
|
|
||||||
messageOpt, ok := optMap["message"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing message option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message := messageOpt.StringValue()
|
|
||||||
if message == "" {
|
|
||||||
message = "GG {user}, you reached level {level}!"
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := services.Global.LevelSettings.SetLevelUpMessage(ctx, guildID, message)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to set level up message.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
respondEphemeral(s, i, "Level up message set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleLevelSetReward(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
||||||
if !requireManageGuild(s, i) || !requireLevelSystemEnabled(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !requireLevelSettingsService(s, i) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guildID, ok := parseGuildID(s, i)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
optMap := subcommandOptionMap(i)
|
|
||||||
|
|
||||||
levelOpt, ok := optMap["level"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing level option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
roleOpt, ok := optMap["role"]
|
|
||||||
if !ok {
|
|
||||||
respondEphemeral(s, i, "Missing role option.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
level := int(levelOpt.IntValue())
|
|
||||||
role := roleOpt.RoleValue(s, i.GuildID)
|
|
||||||
if role == nil {
|
|
||||||
respondEphemeral(s, i, "Invalid role.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
roleID, err := strconv.ParseInt(role.ID, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
respondEphemeral(s, i, "Invalid role ID.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := services.Global.LevelSettings.SetRoleRewardForLevel(ctx, guildID, level, roleID); err != nil {
|
|
||||||
respondEphemeral(s, i, "Failed to set reward.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
respondEphemeral(s, i, "Reward set: level "+strconv.Itoa(level)+" -> "+role.Mention())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Leaderboard = &discordgo.ApplicationCommand{
|
||||||
|
Name: "leaderboard",
|
||||||
|
Description: "Top 10 levels in this server",
|
||||||
|
}
|
||||||
|
|
||||||
|
func LeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireGuild(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelServices(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
top, err := services.Global.Level.TopLevels(ctx, guildID, 10)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to load leaderboard.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(top) == 0 {
|
||||||
|
shared.RespondEphemeral(s, i, "No leaderboard data yet.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := make([]*discordgo.MessageEmbedField, 0, len(top))
|
||||||
|
for idx, entry := range top {
|
||||||
|
display := fmt.Sprintf("<@%d>", entry.UserID)
|
||||||
|
if u, err := s.User(strconv.FormatInt(entry.UserID, 10)); err == nil && u != nil && u.Username != "" {
|
||||||
|
display = u.Username
|
||||||
|
}
|
||||||
|
fields = append(fields, &discordgo.MessageEmbedField{
|
||||||
|
Name: fmt.Sprintf("%d. %s", idx+1, display),
|
||||||
|
Value: fmt.Sprintf("Level: **%d** | XP: **%d**", entry.Level, entry.XP),
|
||||||
|
Inline: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
embed := &discordgo.MessageEmbed{
|
||||||
|
Title: "Leaderboard",
|
||||||
|
Description: fmt.Sprintf("These are the top %d users in the server", len(top)),
|
||||||
|
Fields: fields,
|
||||||
|
Color: 0xF59E0B,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Embeds: []*discordgo.MessageEmbed{embed},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
"velox-bot/internal/rankcard"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Rank = &discordgo.ApplicationCommand{
|
||||||
|
Name: "rank",
|
||||||
|
Description: "Get your level card",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "The user to get the rank card of",
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RankHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireLevelServices(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i.GuildID != "" && !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine target user: option or caller.
|
||||||
|
target := i.User
|
||||||
|
if i.Member != nil && i.Member.User != nil {
|
||||||
|
target = i.Member.User
|
||||||
|
}
|
||||||
|
if len(i.ApplicationCommandData().Options) > 0 {
|
||||||
|
if u := i.ApplicationCommandData().Options[0].UserValue(s); u != nil {
|
||||||
|
target = u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID, err := strconv.ParseInt(target.ID, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Invalid user ID.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
lvl, err := services.Global.Level.GetLevel(ctx, guildID, userID)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to load level.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
percent := float64(lvl.XP) / 100.0
|
||||||
|
avatarURL := target.AvatarURL("256")
|
||||||
|
|
||||||
|
pngBytes, err := rankcard.RenderPNG(ctx, rankcard.Data{
|
||||||
|
Name: target.Username,
|
||||||
|
Level: lvl.Level,
|
||||||
|
XP: lvl.XP,
|
||||||
|
Percent: percent,
|
||||||
|
Avatar: avatarURL,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to render rank card.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
Data: &discordgo.InteractionResponseData{
|
||||||
|
Files: []*discordgo.File{
|
||||||
|
{
|
||||||
|
Name: "levelcard.png",
|
||||||
|
Reader: bytes.NewReader(pngBytes),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package public
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"velox-bot/internal/commands/level/shared"
|
||||||
|
"velox-bot/internal/db/services"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Rewards = &discordgo.ApplicationCommand{
|
||||||
|
Name: "rewards",
|
||||||
|
Description: "View level rewards in this server",
|
||||||
|
}
|
||||||
|
|
||||||
|
func RewardsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
|
if !shared.RequireGuild(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSettingsService(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !shared.RequireLevelSystemEnabled(s, i) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guildID, ok := shared.ParseGuildID(s, i)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
rewards, err := services.Global.LevelSettings.ListRoleRewards(ctx, guildID, 25)
|
||||||
|
if err != nil {
|
||||||
|
shared.RespondEphemeral(s, i, "Failed to load rewards.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(rewards) == 0 {
|
||||||
|
shared.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},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package level
|
||||||
|
|
||||||
|
import (
|
||||||
|
"velox-bot/internal/commands/level/config"
|
||||||
|
"velox-bot/internal/commands/level/public"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
var (
|
||||||
|
Rank *discordgo.ApplicationCommand = public.Rank
|
||||||
|
Leaderboard *discordgo.ApplicationCommand = public.Leaderboard
|
||||||
|
Rewards *discordgo.ApplicationCommand = public.Rewards
|
||||||
|
Slvl *discordgo.ApplicationCommand = config.Slvl
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
func RankHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.RankHandler(s, i) }
|
||||||
|
func LeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.LeaderboardHandler(s, i) }
|
||||||
|
func RewardsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.RewardsHandler(s, i) }
|
||||||
|
func SlvlHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { config.SlvlHandler(s, i) }
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package level
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
|
func RespondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) {
|
||||||
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
@@ -19,68 +19,68 @@ func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
func RequireGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
||||||
if i.GuildID == "" {
|
if i.GuildID == "" {
|
||||||
respondEphemeral(s, i, "This command can only be used in a server.")
|
RespondEphemeral(s, i, "This command can only be used in a server.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireManageGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
func RequireManageGuild(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
||||||
if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 {
|
if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 {
|
||||||
respondEphemeral(s, i, "You don't have permission to manage leveling settings.")
|
RespondEphemeral(s, i, "You don't have permission to manage leveling settings.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireLevelServices(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
func RequireLevelServices(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
||||||
if services.Global == nil || services.Global.Level == nil || services.Global.LevelSettings == nil {
|
if services.Global == nil || services.Global.Level == nil || services.Global.LevelSettings == nil {
|
||||||
respondEphemeral(s, i, "Leveling service is not available.")
|
RespondEphemeral(s, i, "Leveling service is not available.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireLevelSettingsService(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
func RequireLevelSettingsService(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
||||||
if services.Global == nil || services.Global.LevelSettings == nil {
|
if services.Global == nil || services.Global.LevelSettings == nil {
|
||||||
respondEphemeral(s, i, "Leveling service is not available.")
|
RespondEphemeral(s, i, "Leveling service is not available.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGuildID(s *discordgo.Session, i *discordgo.InteractionCreate) (int64, bool) {
|
func ParseGuildID(s *discordgo.Session, i *discordgo.InteractionCreate) (int64, bool) {
|
||||||
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondEphemeral(s, i, "Invalid guild ID.")
|
RespondEphemeral(s, i, "Invalid guild ID.")
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
return guildID, true
|
return guildID, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireLevelSystemEnabled(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
func RequireLevelSystemEnabled(s *discordgo.Session, i *discordgo.InteractionCreate) bool {
|
||||||
if !requireLevelSettingsService(s, i) {
|
if !RequireLevelSettingsService(s, i) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guildID, ok := parseGuildID(s, i)
|
guildID, ok := ParseGuildID(s, i)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
enabled, err := services.Global.LevelSettings.IsLevelSystemEnabled(context.Background(), guildID)
|
enabled, err := services.Global.LevelSettings.IsLevelSystemEnabled(context.Background(), guildID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondEphemeral(s, i, "Failed to load leveling settings.")
|
RespondEphemeral(s, i, "Failed to load leveling settings.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !enabled {
|
if !enabled {
|
||||||
respondEphemeral(s, i, "Leveling system is disabled on this server. Use /slvl toggle to enable it.")
|
RespondEphemeral(s, i, "Leveling system is disabled on this server. Use /slvl toggle to enable it.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func subcommandOptionMap(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption {
|
func SubcommandOptionMap(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption {
|
||||||
data := i.ApplicationCommandData()
|
data := i.ApplicationCommandData()
|
||||||
if len(data.Options) == 0 {
|
if len(data.Options) == 0 {
|
||||||
return map[string]*discordgo.ApplicationCommandInteractionDataOption{}
|
return map[string]*discordgo.ApplicationCommandInteractionDataOption{}
|
||||||
@@ -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 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 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
|
||||||
Reference in New Issue
Block a user