This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Discord Configuration
|
# Discord Configuration
|
||||||
DISCORD_TOKEN=""
|
DISCORD_TOKEN=""
|
||||||
|
ADMIN_USER_IDS="83679718401904640,123456789012345678"
|
||||||
|
|
||||||
# Container configuration
|
# Container configuration
|
||||||
IMAGE=""
|
IMAGE=""
|
||||||
|
|||||||
@@ -87,3 +87,61 @@ func BalanceSendCommand(s *discordgo.Session, i *discordgo.InteractionCreate) (s
|
|||||||
|
|
||||||
return fmt.Sprintf("💸 Successfully sent %d Himbucks to %s! 💸", amount, recipient.Username), nil
|
return fmt.Sprintf("💸 Successfully sent %d Himbucks to %s! 💸", amount, recipient.Username), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GiveCommand(s *discordgo.Session, i *discordgo.InteractionCreate) (string, error) {
|
||||||
|
if !isAdmin(i.Member.User.ID) {
|
||||||
|
s.InteractionResponseDelete(i.Interaction)
|
||||||
|
|
||||||
|
s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
||||||
|
Content: "You do not have permission to use this command.",
|
||||||
|
Flags: discordgo.MessageFlagsEphemeral,
|
||||||
|
})
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
options := i.ApplicationCommandData().Options
|
||||||
|
var recipientID string
|
||||||
|
var amount int
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
switch opt.Name {
|
||||||
|
case "user":
|
||||||
|
recipientID = opt.UserValue(nil).ID
|
||||||
|
case "amount":
|
||||||
|
amount = int(opt.IntValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recipient, err := s.User(recipientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get recipient info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = lib.GetOrCreateUserWithGuild(recipientID, recipient.Username, i.GuildID)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to initialize recipient profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = lib.GiveHimbucks(recipientID, i.GuildID, amount)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to give himbucks: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
action := "given to"
|
||||||
|
if amount < 0 {
|
||||||
|
action = "taken from"
|
||||||
|
amount = -amount
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("✅ Successfully %s %s %d Himbucks.", action, recipient.Username, amount), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAdmin(userID string) bool {
|
||||||
|
for _, adminID := range lib.AppConfig.AdminIDs {
|
||||||
|
if userID == adminID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
59
command/shop.go
Normal file
59
command/shop.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"himbot/lib"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShopCommand(s *discordgo.Session, i *discordgo.InteractionCreate) (string, error) {
|
||||||
|
options := i.ApplicationCommandData().Options
|
||||||
|
|
||||||
|
if len(options) == 0 {
|
||||||
|
return "Welcome to the Himbucks Shop! Use `/shop buy` to purchase items.", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if options[0].Name == "buy" {
|
||||||
|
subOptions := options[0].Options
|
||||||
|
if len(subOptions) == 0 {
|
||||||
|
return "Please specify an item to buy.", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item := subOptions[0].StringValue()
|
||||||
|
user, err := lib.GetUser(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case "pizza":
|
||||||
|
cost, newBalance, err := lib.BuyPizza(user.ID, i.GuildID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("❌ %s", err.Error()), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Here is your pizza, %s!\n**Cost:** %d himbucks\n**Remaining Balance:** %d himbucks\n```\n%s\n```", user.Username, cost, newBalance, getPizzaArt()), nil
|
||||||
|
|
||||||
|
case "multiplier":
|
||||||
|
newMult, cost, newBalance, err := lib.BuyMultiplier(user.ID, i.GuildID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("❌ %s", err.Error()), nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Multiplier upgraded!\nYou spent **%d** himbucks.\nYour new earning multiplier is **%.1fx**!\n**Remaining Balance:** %d himbucks", cost, newMult, newBalance), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Unknown item: %s", item), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown subcommand.", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPizzaArt() string {
|
||||||
|
return `
|
||||||
|
// ""--.._
|
||||||
|
|| (_) _ "-._
|
||||||
|
|| _ (_) '-.
|
||||||
|
|| (_) __..-'
|
||||||
|
\__..--"`
|
||||||
|
}
|
||||||
@@ -43,7 +43,13 @@ func HandleCommand(commandName string, cooldownDuration time.Duration, handler C
|
|||||||
response, handlerErr := handler(s, i)
|
response, handlerErr := handler(s, i)
|
||||||
|
|
||||||
if handlerErr != nil {
|
if handlerErr != nil {
|
||||||
RespondWithError(s, i, "Error processing command: "+handlerErr.Error())
|
s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
|
||||||
|
Content: "Error processing command: " + handlerErr.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if response == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
// Discord settings
|
// Discord settings
|
||||||
DiscordToken string
|
DiscordToken string
|
||||||
|
AdminIDs []string
|
||||||
|
|
||||||
// Himbucks settings
|
// Himbucks settings
|
||||||
HimbucksPerReward int
|
HimbucksPerReward int
|
||||||
@@ -36,6 +37,8 @@ type Config struct {
|
|||||||
HimbucksCooldown int
|
HimbucksCooldown int
|
||||||
HimboardCooldown int
|
HimboardCooldown int
|
||||||
SendbucksCooldown int
|
SendbucksCooldown int
|
||||||
|
ShopCooldown int
|
||||||
|
GivebucksCooldown int
|
||||||
}
|
}
|
||||||
|
|
||||||
var AppConfig *Config
|
var AppConfig *Config
|
||||||
@@ -45,6 +48,7 @@ func LoadConfig() *Config {
|
|||||||
config := &Config{
|
config := &Config{
|
||||||
// Discord settings
|
// Discord settings
|
||||||
DiscordToken: getEnv("DISCORD_TOKEN", ""),
|
DiscordToken: getEnv("DISCORD_TOKEN", ""),
|
||||||
|
AdminIDs: getEnvSlice("ADMIN_USER_IDS", []string{}),
|
||||||
|
|
||||||
// Himbucks settings
|
// Himbucks settings
|
||||||
HimbucksPerReward: getEnvInt("HIMBUCKS_PER_REWARD", 10),
|
HimbucksPerReward: getEnvInt("HIMBUCKS_PER_REWARD", 10),
|
||||||
@@ -71,6 +75,8 @@ func LoadConfig() *Config {
|
|||||||
HimbucksCooldown: getEnvInt("HIMBUCKS_COOLDOWN_SECONDS", 5),
|
HimbucksCooldown: getEnvInt("HIMBUCKS_COOLDOWN_SECONDS", 5),
|
||||||
HimboardCooldown: getEnvInt("HIMBOARD_COOLDOWN_SECONDS", 5),
|
HimboardCooldown: getEnvInt("HIMBOARD_COOLDOWN_SECONDS", 5),
|
||||||
SendbucksCooldown: getEnvInt("SENDBUCKS_COOLDOWN_SECONDS", 1800),
|
SendbucksCooldown: getEnvInt("SENDBUCKS_COOLDOWN_SECONDS", 1800),
|
||||||
|
ShopCooldown: getEnvInt("SHOP_COOLDOWN_SECONDS", 5),
|
||||||
|
GivebucksCooldown: getEnvInt("GIVEBUCKS_COOLDOWN_SECONDS", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig = config
|
AppConfig = config
|
||||||
@@ -93,4 +99,29 @@ func getEnvInt(key string, defaultValue int) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getEnvSlice(key string, defaultValue []string) []string {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
return importStrings(value)
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func importStrings(s string) []string {
|
||||||
|
var strings []string
|
||||||
|
current := ""
|
||||||
|
for _, c := range s {
|
||||||
|
if c == ',' {
|
||||||
|
strings = append(strings, current)
|
||||||
|
current = ""
|
||||||
|
} else {
|
||||||
|
current += string(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current != "" {
|
||||||
|
strings = append(strings, current)
|
||||||
|
}
|
||||||
|
return strings
|
||||||
|
}
|
||||||
|
|
||||||
143
lib/himbucks.go
143
lib/himbucks.go
@@ -24,12 +24,13 @@ func ProcessHimbucks(s *discordgo.Session, m *discordgo.MessageCreate, ctx *Proc
|
|||||||
// Get current state
|
// Get current state
|
||||||
var messageCount int
|
var messageCount int
|
||||||
var lastRewardAt sql.NullTime
|
var lastRewardAt sql.NullTime
|
||||||
|
var multiplier float64
|
||||||
|
|
||||||
err = tx.QueryRow(`
|
err = tx.QueryRow(`
|
||||||
SELECT message_count, last_reward_at
|
SELECT message_count, last_reward_at, COALESCE(multiplier, 1.0)
|
||||||
FROM guild_profiles
|
FROM guild_profiles
|
||||||
WHERE user_id = ? AND guild_id = ?`,
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt)
|
ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt, &multiplier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get message count: %w", err)
|
return fmt.Errorf("failed to get message count: %w", err)
|
||||||
}
|
}
|
||||||
@@ -39,13 +40,14 @@ func ProcessHimbucks(s *discordgo.Session, m *discordgo.MessageCreate, ctx *Proc
|
|||||||
(!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= AppConfig.CooldownPeriod)
|
(!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= AppConfig.CooldownPeriod)
|
||||||
|
|
||||||
if shouldReward {
|
if shouldReward {
|
||||||
|
reward := int(float64(AppConfig.HimbucksPerReward) * multiplier)
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
UPDATE guild_profiles
|
UPDATE guild_profiles
|
||||||
SET currency_balance = currency_balance + ?,
|
SET currency_balance = currency_balance + ?,
|
||||||
message_count = 0,
|
message_count = 0,
|
||||||
last_reward_at = CURRENT_TIMESTAMP
|
last_reward_at = CURRENT_TIMESTAMP
|
||||||
WHERE user_id = ? AND guild_id = ?`,
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
AppConfig.HimbucksPerReward, ctx.UserID, ctx.GuildID)
|
reward, ctx.UserID, ctx.GuildID)
|
||||||
} else {
|
} else {
|
||||||
_, err = tx.Exec(`
|
_, err = tx.Exec(`
|
||||||
UPDATE guild_profiles
|
UPDATE guild_profiles
|
||||||
@@ -179,3 +181,138 @@ func GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) {
|
|||||||
}
|
}
|
||||||
return entries, nil
|
return entries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuyMultiplier(discordID, guildID string) (float64, int, int, error) {
|
||||||
|
tx, err := DBClient.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("failed to start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var userID int
|
||||||
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("user not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var balance int
|
||||||
|
var currentMultiplier float64
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT currency_balance, COALESCE(multiplier, 1.0)
|
||||||
|
FROM guild_profiles
|
||||||
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
|
userID, guildID).Scan(&balance, ¤tMultiplier)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("failed to get profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cost := int(1000 * currentMultiplier)
|
||||||
|
if balance < cost {
|
||||||
|
return 0, 0, 0, fmt.Errorf("insufficient funds: have %d, need %d", balance, cost)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBalance := balance - cost
|
||||||
|
newMultiplier := currentMultiplier + 0.5
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
UPDATE guild_profiles
|
||||||
|
SET currency_balance = ?,
|
||||||
|
multiplier = ?
|
||||||
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
|
newBalance, newMultiplier, userID, guildID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("failed to update profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("failed to commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMultiplier, cost, newBalance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuyPizza(discordID, guildID string) (int, int, error) {
|
||||||
|
tx, err := DBClient.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var userID int
|
||||||
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("user not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var balance int
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT currency_balance
|
||||||
|
FROM guild_profiles
|
||||||
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
|
userID, guildID).Scan(&balance)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to get profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cost := 100
|
||||||
|
if balance < cost {
|
||||||
|
return 0, 0, fmt.Errorf("insufficient funds: have %d, need %d", balance, cost)
|
||||||
|
}
|
||||||
|
|
||||||
|
newBalance := balance - cost
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
UPDATE guild_profiles
|
||||||
|
SET currency_balance = ?
|
||||||
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
|
newBalance, userID, guildID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to update balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tx.Commit(); err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("failed to commit transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cost, newBalance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GiveHimbucks(discordID, guildID string, amount int) error {
|
||||||
|
tx, err := DBClient.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start transaction: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var userID int
|
||||||
|
// Get user ID
|
||||||
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("user not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := tx.Exec(`
|
||||||
|
UPDATE guild_profiles
|
||||||
|
SET currency_balance = currency_balance + ?
|
||||||
|
WHERE user_id = ? AND guild_id = ?`,
|
||||||
|
amount, userID, guildID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check rows affected: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows == 0 {
|
||||||
|
_, err = tx.Exec(`
|
||||||
|
INSERT INTO guild_profiles (user_id, guild_id, currency_balance, message_count)
|
||||||
|
VALUES (?, ?, ?, 0)`,
|
||||||
|
userID, guildID, amount)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create profile: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|||||||
49
main.go
49
main.go
@@ -251,6 +251,53 @@ func initCommands(config *lib.Config) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "shop",
|
||||||
|
Description: "Spend your himbucks on cool stuff",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "buy",
|
||||||
|
Description: "Buy an item",
|
||||||
|
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Name: "item",
|
||||||
|
Description: "The item to buy",
|
||||||
|
Type: discordgo.ApplicationCommandOptionString,
|
||||||
|
Required: true,
|
||||||
|
Choices: []*discordgo.ApplicationCommandOptionChoice{
|
||||||
|
{
|
||||||
|
Name: "pizza",
|
||||||
|
Value: "pizza",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "multiplier",
|
||||||
|
Value: "multiplier",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "givebucks",
|
||||||
|
Description: "Admin: Give himbucks to a user",
|
||||||
|
Options: []*discordgo.ApplicationCommandOption{
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionUser,
|
||||||
|
Name: "user",
|
||||||
|
Description: "The user to give himbucks to",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: discordgo.ApplicationCommandOptionInteger,
|
||||||
|
Name: "amount",
|
||||||
|
Description: "Amount of himbucks to give (negative to take)",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,5 +311,7 @@ func initCommandHandlers(config *lib.Config) {
|
|||||||
"himbucks": lib.HandleCommand("himbucks", time.Duration(config.HimbucksCooldown)*time.Second, command.BalanceGetCommand),
|
"himbucks": lib.HandleCommand("himbucks", time.Duration(config.HimbucksCooldown)*time.Second, command.BalanceGetCommand),
|
||||||
"himboard": lib.HandleCommand("himboard", time.Duration(config.HimboardCooldown)*time.Second, command.LeaderboardCommand),
|
"himboard": lib.HandleCommand("himboard", time.Duration(config.HimboardCooldown)*time.Second, command.LeaderboardCommand),
|
||||||
"sendbucks": lib.HandleCommand("sendbucks", time.Duration(config.SendbucksCooldown)*time.Second, command.BalanceSendCommand),
|
"sendbucks": lib.HandleCommand("sendbucks", time.Duration(config.SendbucksCooldown)*time.Second, command.BalanceSendCommand),
|
||||||
|
"shop": lib.HandleCommand("shop", time.Duration(config.ShopCooldown)*time.Second, command.ShopCommand),
|
||||||
|
"givebucks": lib.HandleCommand("givebucks", time.Duration(config.GivebucksCooldown)*time.Second, command.GiveCommand),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE guild_profiles DROP COLUMN multiplier;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE guild_profiles ADD COLUMN multiplier REAL DEFAULT 1.0;
|
||||||
Reference in New Issue
Block a user