feat: rps commands

This commit is contained in:
2026-03-17 16:44:18 +00:00
parent dee5008c60
commit 0b36bc9e93
10 changed files with 538 additions and 3 deletions
+6
View File
@@ -13,6 +13,9 @@ var AllCommands = []*discordgo.ApplicationCommand{
fun.Joke,
fun.Coinflip,
fun.Dice,
fun.Rps,
fun.RpsStats,
fun.RpsLeaderboard,
help.Help,
cmdlevel.Slvl,
cmdlevel.Rank,
@@ -25,6 +28,9 @@ var handlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCre
"joke": fun.JokeHandler,
"coinflip": fun.CoinflipHandler,
"dice": fun.DiceHandler,
"rps": fun.RpsHandler,
"rpsstats": fun.RpsStatsHandler,
"rpsleaderboard": fun.RpsLeaderboardHandler,
"help": help.HelpHandler,
"slvl": cmdlevel.SlvlHandler,
"rank": cmdlevel.RankHandler,
+106
View File
@@ -0,0 +1,106 @@
package public
import (
"context"
"strconv"
"velox-bot/internal/commands/fun/shared"
"velox-bot/internal/db/services"
rpssvc "velox-bot/internal/db/services/rps"
"github.com/bwmarrin/discordgo"
)
var Rps = &discordgo.ApplicationCommand{
Name: "rps",
Description: "Plays Rock Paper Scissors with you",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "hand",
Description: "Choose between ✌️, ✋ or 🤜",
Required: true,
Choices: []*discordgo.ApplicationCommandOptionChoice{
{Name: "✌️ - Scissors", Value: string(rpssvc.HandScissors)},
{Name: "✋ - Paper", Value: string(rpssvc.HandPaper)},
{Name: "🤜 - Rock", Value: string(rpssvc.HandRock)},
},
},
},
Contexts: &[]discordgo.InteractionContextType{
discordgo.InteractionContextGuild,
},
}
func RpsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.GuildID == "" {
shared.Respond(s, i, "This command can only be used in a server.")
return
}
if services.Global == nil || services.Global.RPS == nil {
shared.RespondEphemeral(s, i, "RPS service is not available.")
return
}
data := i.ApplicationCommandData()
if len(data.Options) == 0 {
shared.RespondEphemeral(s, i, "Invalid hand! Please choose between ✌️, ✋ or 🤜")
return
}
handStr := data.Options[0].StringValue()
userHand, ok := rpssvc.ParseHand(handStr)
if !ok {
shared.RespondEphemeral(s, i, "Invalid hand! Please choose between ✌️, ✋ or 🤜")
return
}
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid guild.")
return
}
user := i.User
if i.Member != nil && i.Member.User != nil {
user = i.Member.User
}
if user == nil {
return
}
userID, err := strconv.ParseInt(user.ID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid user.")
return
}
out, err := services.Global.RPS.Play(context.Background(), guildID, userID, userHand)
if err != nil {
shared.RespondEphemeral(s, i, "Failed to play RPS.")
return
}
color := 0xEF4444
switch out.Result {
case rpssvc.ResultWin:
color = 0x22C55E
case rpssvc.ResultTie:
color = 0xF59E0B
}
embed := &discordgo.MessageEmbed{
Title: string(out.Result),
Description: user.Mention() + " vs bot",
Color: color,
Fields: []*discordgo.MessageEmbedField{
{Name: "Your hand", Value: out.UserHand.String(), Inline: true},
{Name: "Bot hand", Value: out.BotHand.String(), Inline: true},
{Name: "Score", Value: strconv.Itoa(out.Score), Inline: true},
},
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
},
})
}
@@ -0,0 +1,73 @@
package public
import (
"context"
"fmt"
"strconv"
"velox-bot/internal/commands/fun/shared"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
var RpsLeaderboard = &discordgo.ApplicationCommand{
Name: "rpsleaderboard",
Description: "Shows the RPS leaderboard",
Contexts: &[]discordgo.InteractionContextType{
discordgo.InteractionContextGuild,
},
}
func RpsLeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.GuildID == "" {
shared.Respond(s, i, "This command can only be used in a server.")
return
}
if services.Global == nil || services.Global.RPS == nil {
shared.RespondEphemeral(s, i, "RPS service is not available.")
return
}
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid guild.")
return
}
top, err := services.Global.RPS.TopScores(context.Background(), 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("Score: **%d**", entry.Score),
Inline: false,
})
}
embed := &discordgo.MessageEmbed{
Title: "RPS Leaderboard",
Description: fmt.Sprintf("Top %d players in this server", len(top)),
Color: 0xA855F7,
Fields: fields,
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
},
})
}
+73
View File
@@ -0,0 +1,73 @@
package public
import (
"context"
"strconv"
"velox-bot/internal/commands/fun/shared"
"velox-bot/internal/db/services"
"github.com/bwmarrin/discordgo"
)
var RpsStats = &discordgo.ApplicationCommand{
Name: "rpsstats",
Description: "Shows your RPS stats",
Contexts: &[]discordgo.InteractionContextType{
discordgo.InteractionContextGuild,
},
}
func RpsStatsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.GuildID == "" {
shared.Respond(s, i, "This command can only be used in a server.")
return
}
if services.Global == nil || services.Global.RPS == nil {
shared.RespondEphemeral(s, i, "RPS service is not available.")
return
}
guildID, err := strconv.ParseInt(i.GuildID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid guild.")
return
}
user := i.User
if i.Member != nil && i.Member.User != nil {
user = i.Member.User
}
if user == nil {
return
}
userID, err := strconv.ParseInt(user.ID, 10, 64)
if err != nil {
shared.RespondEphemeral(s, i, "Invalid user.")
return
}
score, ok, err := services.Global.RPS.GetScore(context.Background(), guildID, userID)
if err != nil {
shared.RespondEphemeral(s, i, "Failed to load stats.")
return
}
if !ok {
shared.RespondEphemeral(s, i, "You haven't played RPS yet!")
return
}
embed := &discordgo.MessageEmbed{
Title: "RPS Stats",
Description: user.Mention(),
Color: 0x3B82F6,
Fields: []*discordgo.MessageEmbedField{
{Name: "Score", Value: strconv.Itoa(score), Inline: true},
},
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{embed},
},
})
}
+10
View File
@@ -12,6 +12,9 @@ var (
Joke *discordgo.ApplicationCommand = public.Joke
Coinflip *discordgo.ApplicationCommand = public.Coinflip
Dice *discordgo.ApplicationCommand = public.Dice
Rps *discordgo.ApplicationCommand = public.Rps
RpsStats *discordgo.ApplicationCommand = public.RpsStats
RpsLeaderboard *discordgo.ApplicationCommand = public.RpsLeaderboard
)
// Handlers
@@ -21,3 +24,10 @@ func CoinflipHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
public.CoinflipHandler(s, i)
}
func DiceHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.DiceHandler(s, i) }
func RpsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { public.RpsHandler(s, i) }
func RpsStatsHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
public.RpsStatsHandler(s, i)
}
func RpsLeaderboardHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
public.RpsLeaderboardHandler(s, i)
}
+49 -1
View File
@@ -47,12 +47,36 @@ func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
Fields: []*discordgo.MessageEmbedField{
{
Name: "Fun Commands",
Value: "Here are the fun commands for the bot.",
Value: "Small utility + games.",
},
{
Name: "/ping",
Value: "Pong!",
},
{
Name: "/joke [type]",
Value: "Get a random joke (optional category).",
},
{
Name: "/coinflip",
Value: "Flip a coin.",
},
{
Name: "/dice <expr>",
Value: "Roll dice expressions (e.g. `d20`, `2d20+1`, `2#d20+1`).",
},
{
Name: "/rps <hand>",
Value: "Play Rock Paper Scissors (server only).",
},
{
Name: "/rpsstats",
Value: "Show your RPS score (server only).",
},
{
Name: "/rpsleaderboard",
Value: "Show top RPS scores (server only).",
},
{
Name: "/help",
Value: "Get help with the bot.",
@@ -69,6 +93,30 @@ func HelpHandler(s *discordgo.Session, i *discordgo.InteractionCreate) {
Name: "/slvl set",
Value: "Set the level of a user.",
},
{
Name: "/slvl set-channel",
Value: "Set the channel for level-up messages.",
},
{
Name: "/slvl set-message",
Value: "Set the custom level-up message.",
},
{
Name: "/slvl set-reward",
Value: "Set a role reward for a level.",
},
{
Name: "/rank [user]",
Value: "Show a rank card (server only).",
},
{
Name: "/leaderboard",
Value: "Show top levels (server only).",
},
{
Name: "/rewards",
Value: "List configured level rewards (server only).",
},
},
Author: &author,
}