feat: rps commands
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package rpsrepo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Repo struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRepo(db *sql.DB) *Repo {
|
||||
return &Repo{db: db}
|
||||
}
|
||||
|
||||
func (r *Repo) GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error) {
|
||||
var score int
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT score
|
||||
FROM rps
|
||||
WHERE guild_id = $1 AND user_id = $2
|
||||
`, guildID, userID).Scan(&score)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
return score, true, nil
|
||||
}
|
||||
|
||||
func (r *Repo) SetScore(ctx context.Context, guildID int64, userID int64, score int) error {
|
||||
_, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO rps (guild_id, user_id, score)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (guild_id, user_id)
|
||||
DO UPDATE SET score = EXCLUDED.score
|
||||
`, guildID, userID, score)
|
||||
return err
|
||||
}
|
||||
|
||||
type ScoreEntry struct {
|
||||
UserID int64
|
||||
Score int
|
||||
}
|
||||
|
||||
func (r *Repo) TopScores(ctx context.Context, guildID int64, limit int) ([]ScoreEntry, error) {
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT user_id, score
|
||||
FROM rps
|
||||
WHERE guild_id = $1
|
||||
ORDER BY score DESC, user_id ASC
|
||||
LIMIT $2
|
||||
`, guildID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]ScoreEntry, 0, limit)
|
||||
for rows.Next() {
|
||||
var e ScoreEntry
|
||||
if err := rows.Scan(&e.UserID, &e.Score); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, e)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package rps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"velox-bot/internal/db/repos/rpsrepo"
|
||||
)
|
||||
|
||||
type Repo interface {
|
||||
GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error)
|
||||
SetScore(ctx context.Context, guildID int64, userID int64, score int) error
|
||||
TopScores(ctx context.Context, guildID int64, limit int) ([]rpsrepo.ScoreEntry, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repo Repo
|
||||
}
|
||||
|
||||
func New(repo Repo) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
type Hand string
|
||||
|
||||
const (
|
||||
HandScissors Hand = "✌️"
|
||||
HandPaper Hand = "✋"
|
||||
HandRock Hand = "🤜"
|
||||
)
|
||||
|
||||
func (h Hand) String() string { return string(h) }
|
||||
|
||||
func ParseHand(s string) (Hand, bool) {
|
||||
switch strings.TrimSpace(s) {
|
||||
case string(HandScissors):
|
||||
return HandScissors, true
|
||||
case string(HandPaper):
|
||||
return HandPaper, true
|
||||
case string(HandRock):
|
||||
return HandRock, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func RandomHand() (Hand, error) {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(3))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch n.Int64() {
|
||||
case 0:
|
||||
return HandScissors, nil
|
||||
case 1:
|
||||
return HandPaper, nil
|
||||
default:
|
||||
return HandRock, nil
|
||||
}
|
||||
}
|
||||
|
||||
type GameResult string
|
||||
|
||||
const (
|
||||
ResultWin GameResult = "You won!"
|
||||
ResultLose GameResult = "You lost!"
|
||||
ResultTie GameResult = "It's a tie!"
|
||||
)
|
||||
|
||||
func DetermineResult(user Hand, bot Hand) GameResult {
|
||||
if user == bot {
|
||||
return ResultTie
|
||||
}
|
||||
switch user {
|
||||
case HandRock:
|
||||
if bot == HandScissors {
|
||||
return ResultWin
|
||||
}
|
||||
case HandPaper:
|
||||
if bot == HandRock {
|
||||
return ResultWin
|
||||
}
|
||||
case HandScissors:
|
||||
if bot == HandPaper {
|
||||
return ResultWin
|
||||
}
|
||||
}
|
||||
return ResultLose
|
||||
}
|
||||
|
||||
type PlayOutput struct {
|
||||
UserHand Hand
|
||||
BotHand Hand
|
||||
Result GameResult
|
||||
Score int
|
||||
}
|
||||
|
||||
func (s *Service) Play(ctx context.Context, guildID int64, userID int64, userHand Hand) (PlayOutput, error) {
|
||||
botHand, err := RandomHand()
|
||||
if err != nil {
|
||||
return PlayOutput{}, err
|
||||
}
|
||||
result := DetermineResult(userHand, botHand)
|
||||
|
||||
score, _, err := s.repo.GetScore(ctx, guildID, userID)
|
||||
if err != nil {
|
||||
return PlayOutput{}, err
|
||||
}
|
||||
if result == ResultWin {
|
||||
score++
|
||||
if err := s.repo.SetScore(ctx, guildID, userID, score); err != nil {
|
||||
return PlayOutput{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return PlayOutput{
|
||||
UserHand: userHand,
|
||||
BotHand: botHand,
|
||||
Result: result,
|
||||
Score: score,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetScore(ctx context.Context, guildID int64, userID int64) (int, bool, error) {
|
||||
return s.repo.GetScore(ctx, guildID, userID)
|
||||
}
|
||||
|
||||
func (s *Service) TopScores(ctx context.Context, guildID int64, limit int) ([]rpsrepo.ScoreEntry, error) {
|
||||
return s.repo.TopScores(ctx, guildID, limit)
|
||||
}
|
||||
|
||||
var ErrGuildOnly = errors.New("guild only")
|
||||
|
||||
@@ -3,19 +3,22 @@ package services
|
||||
import (
|
||||
"velox-bot/internal/db/services/level"
|
||||
"velox-bot/internal/db/services/levelsettings"
|
||||
"velox-bot/internal/db/services/rps"
|
||||
)
|
||||
|
||||
type Services struct {
|
||||
Level *level.Service
|
||||
LevelSettings *levelsettings.Service
|
||||
RPS *rps.Service
|
||||
}
|
||||
|
||||
var Global *Services
|
||||
|
||||
func NewServices(level *level.Service, levelSettings *levelsettings.Service) *Services {
|
||||
func NewServices(level *level.Service, levelSettings *levelsettings.Service, rps *rps.Service) *Services {
|
||||
s := &Services{
|
||||
Level: level,
|
||||
LevelSettings: levelSettings,
|
||||
RPS: rps,
|
||||
}
|
||||
Global = s
|
||||
return s
|
||||
|
||||
@@ -11,10 +11,12 @@ import (
|
||||
"velox-bot/internal/config"
|
||||
"velox-bot/internal/db"
|
||||
"velox-bot/internal/db/repos/levelrepo"
|
||||
"velox-bot/internal/db/repos/rpsrepo"
|
||||
"velox-bot/internal/db/repos/settingsrepo"
|
||||
"velox-bot/internal/db/services"
|
||||
"velox-bot/internal/db/services/level"
|
||||
"velox-bot/internal/db/services/levelsettings"
|
||||
"velox-bot/internal/db/services/rps"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -33,9 +35,11 @@ func main() {
|
||||
|
||||
levelRepo := levelrepo.NewRepo(db)
|
||||
settingsRepo := settingsrepo.NewRepo(db)
|
||||
rpsRepo := rpsrepo.NewRepo(db)
|
||||
levelService := level.New(levelRepo, settingsRepo)
|
||||
levelSettingsService := levelsettings.New(settingsRepo)
|
||||
services := services.NewServices(levelService, levelSettingsService)
|
||||
rpsService := rps.New(rpsRepo)
|
||||
services := services.NewServices(levelService, levelSettingsService, rpsService)
|
||||
|
||||
bot, err := bot.NewBot(config.BotToken, config.AppID, config.GuildID, commands.AllCommands, services)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user