package public import ( "context" "log" "strconv" "strings" "velox-bot/internal/db/services" "github.com/bwmarrin/discordgo" ) var Config = &discordgo.ApplicationCommand{ Name: "config", Description: "Server configuration", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "addstreamer", Description: "Add a Twitch streamer for live notifications", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "username", Description: "Twitch username (without https://)", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "removestreamer", Description: "Remove a Twitch streamer from live notifications", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "username", Description: "Twitch username to remove", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "settwitchnotificationchannel", Description: "Set the channel for Twitch live notifications", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionChannel, Name: "channel", Description: "Text channel for notifications", Required: true, ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews}, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setwelcomechannel", Description: "Set the channel for welcome messages", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionChannel, Name: "channel", Description: "Text channel for welcomes", Required: true, ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews}, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setwelcomemessage", Description: "Set the welcome message template shown in the server", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "message", Description: "Use {user} where the new member mention should appear", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setwelcomedm", Description: "Set the welcome DM template sent to new members", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "message", Description: "Use {user} where the new member mention should appear", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setwelcomegif", Description: "Set the GIF/image URL used in welcome messages", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionString, Name: "url", Description: "Direct image or GIF URL", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setdefaultrole", Description: "Set the default role given to new members", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionRole, Name: "role", Description: "Role to assign on join", Required: true, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "setlogchannel", Description: "Set the moderation log channel", Options: []*discordgo.ApplicationCommandOption{ { Type: discordgo.ApplicationCommandOptionChannel, Name: "channel", Description: "Text channel for logs", Required: true, ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText, discordgo.ChannelTypeGuildNews}, }, }, }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "enablelogging", Description: "Enable server logging events", }, { Type: discordgo.ApplicationCommandOptionSubCommand, Name: "disablelogging", Description: "Disable server logging events", }, }, } func ConfigHandler(s *discordgo.Session, i *discordgo.InteractionCreate) { if i.GuildID == "" { respondEphemeral(s, i, "This command can only be used in a server.") return } if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageGuild) == 0 { respondEphemeral(s, i, "You need the **Manage Server** permission to use this.") return } if services.Global == nil { respondEphemeral(s, i, "Configuration services are not available.") return } data := i.ApplicationCommandData() if len(data.Options) == 0 { respondEphemeral(s, i, "Missing subcommand.") return } switch data.Options[0].Name { case "addstreamer": log.Printf("twitch: /config addstreamer invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleAddStreamer(s, i) case "removestreamer": log.Printf("twitch: /config removestreamer invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleRemoveStreamer(s, i) case "settwitchnotificationchannel": log.Printf("twitch: /config settwitchnotificationchannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetChannel(s, i) case "setwelcomechannel": log.Printf("welcome: /config setwelcomechannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetWelcomeChannel(s, i) case "setwelcomemessage": log.Printf("welcome: /config setwelcomemessage invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetWelcomeMessage(s, i) case "setwelcomedm": log.Printf("welcome: /config setwelcomedm invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetWelcomeDM(s, i) case "setwelcomegif": log.Printf("welcome: /config setwelcomegif invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetWelcomeGIF(s, i) case "setdefaultrole": log.Printf("defaultrole: /config setdefaultrole invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetDefaultRole(s, i) case "setlogchannel": log.Printf("log: /config setlogchannel invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetLogChannel(s, i) case "enablelogging": log.Printf("log: /config enablelogging invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetLoggingEnabled(s, i, true) case "disablelogging": log.Printf("log: /config disablelogging invoked by %s in guild %s", i.Member.User.ID, i.GuildID) handleSetLoggingEnabled(s, i, false) default: respondEphemeral(s, i, "Unknown subcommand.") } } func handleAddStreamer(s *discordgo.Session, i *discordgo.InteractionCreate) { guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var username string for _, o := range opt.Options { if o.Name == "username" { username = strings.TrimSpace(o.StringValue()) } } username = strings.TrimPrefix(username, "https://www.twitch.tv/") username = strings.TrimPrefix(username, "http://www.twitch.tv/") username = strings.TrimPrefix(username, "twitch.tv/") username = strings.TrimSpace(username) if username == "" { respondEphemeral(s, i, "Username cannot be empty.") return } if err := services.Global.Twitch.UpsertStreamer(context.Background(), guildID, username); err != nil { respondEphemeral(s, i, "Failed to add streamer.") return } respondEphemeral(s, i, "Added `"+username+"` for Twitch live notifications.") } func handleRemoveStreamer(s *discordgo.Session, i *discordgo.InteractionCreate) { guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var username string for _, o := range opt.Options { if o.Name == "username" { username = strings.TrimSpace(o.StringValue()) } } username = strings.TrimSpace(username) if username == "" { respondEphemeral(s, i, "Username cannot be empty.") return } if err := services.Global.Twitch.RemoveStreamer(context.Background(), guildID, username); err != nil { respondEphemeral(s, i, "Failed to remove streamer.") return } respondEphemeral(s, i, "Removed `"+username+"` from Twitch live notifications.") } func handleSetChannel(s *discordgo.Session, i *discordgo.InteractionCreate) { guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var chOpt *discordgo.ApplicationCommandInteractionDataOption for _, o := range opt.Options { if o.Name == "channel" { chOpt = o break } } if chOpt == nil { respondEphemeral(s, i, "Missing channel option.") return } ch := chOpt.ChannelValue(s) if ch == nil { respondEphemeral(s, i, "Invalid channel.") return } chID, err := strconv.ParseInt(ch.ID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid channel ID.") return } if err := services.Global.Twitch.SetNotificationChannel(context.Background(), guildID, chID); err != nil { respondEphemeral(s, i, "Failed to set notification channel.") return } respondEphemeral(s, i, "Twitch notifications will be sent to "+ch.Mention()+".") } func handleSetWelcomeChannel(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.Welcome == nil { respondEphemeral(s, i, "Welcome configuration is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var chOpt *discordgo.ApplicationCommandInteractionDataOption for _, o := range opt.Options { if o.Name == "channel" { chOpt = o break } } if chOpt == nil { respondEphemeral(s, i, "Missing channel option.") return } ch := chOpt.ChannelValue(s) if ch == nil { respondEphemeral(s, i, "Invalid channel.") return } chID, err := strconv.ParseInt(ch.ID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid channel ID.") return } if err := services.Global.Welcome.SetChannel(context.Background(), guildID, chID); err != nil { respondEphemeral(s, i, "Failed to set welcome channel.") return } respondEphemeral(s, i, "Welcome messages will be sent to "+ch.Mention()+".") } func handleSetWelcomeMessage(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.Welcome == nil { respondEphemeral(s, i, "Welcome configuration is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var msg string for _, o := range opt.Options { if o.Name == "message" { msg = strings.TrimSpace(o.StringValue()) } } if msg == "" { respondEphemeral(s, i, "Message cannot be empty.") return } if err := services.Global.Welcome.SetMessage(context.Background(), guildID, msg); err != nil { respondEphemeral(s, i, "Failed to set welcome message.") return } respondEphemeral(s, i, "Welcome message updated.") } func handleSetWelcomeDM(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.Welcome == nil { respondEphemeral(s, i, "Welcome configuration is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var msg string for _, o := range opt.Options { if o.Name == "message" { msg = strings.TrimSpace(o.StringValue()) } } if msg == "" { respondEphemeral(s, i, "DM message cannot be empty.") return } if err := services.Global.Welcome.SetDM(context.Background(), guildID, msg); err != nil { respondEphemeral(s, i, "Failed to set welcome DM.") return } respondEphemeral(s, i, "Welcome DM updated.") } func handleSetWelcomeGIF(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.Welcome == nil { respondEphemeral(s, i, "Welcome configuration is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var url string for _, o := range opt.Options { if o.Name == "url" { url = strings.TrimSpace(o.StringValue()) } } if url == "" { respondEphemeral(s, i, "URL cannot be empty.") return } if err := services.Global.Welcome.SetGIF(context.Background(), guildID, url); err != nil { respondEphemeral(s, i, "Failed to set welcome GIF.") return } respondEphemeral(s, i, "Welcome GIF updated.") } func handleSetDefaultRole(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.DefaultRole == nil { respondEphemeral(s, i, "Default role configuration is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var roleOpt *discordgo.ApplicationCommandInteractionDataOption for _, o := range opt.Options { if o.Name == "role" { roleOpt = o break } } if roleOpt == nil { respondEphemeral(s, i, "Missing role option.") return } 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 } if err := services.Global.DefaultRole.SetRole(context.Background(), guildID, roleID); err != nil { respondEphemeral(s, i, "Failed to set default role.") return } respondEphemeral(s, i, "Default role set to "+role.Mention()+".") } func handleSetLogChannel(s *discordgo.Session, i *discordgo.InteractionCreate) { if services.Global.LogSettings == nil { respondEphemeral(s, i, "Log settings service is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } opt := i.ApplicationCommandData().Options[0] var chOpt *discordgo.ApplicationCommandInteractionDataOption for _, o := range opt.Options { if o.Name == "channel" { chOpt = o break } } if chOpt == nil { respondEphemeral(s, i, "Missing channel option.") return } ch := chOpt.ChannelValue(s) if ch == nil { respondEphemeral(s, i, "Invalid channel.") return } channelID, err := strconv.ParseInt(ch.ID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid channel ID.") return } if err := services.Global.LogSettings.SetChannel(context.Background(), guildID, channelID); err != nil { respondEphemeral(s, i, "Failed to set log channel.") return } if err := services.Global.LogSettings.SetEnabled(context.Background(), guildID, true); err != nil { respondEphemeral(s, i, "Log channel set, but failed to enable logging.") return } respondEphemeral(s, i, "Logging enabled in "+ch.Mention()+".") } func handleSetLoggingEnabled(s *discordgo.Session, i *discordgo.InteractionCreate, enabled bool) { if services.Global.LogSettings == nil { respondEphemeral(s, i, "Log settings service is not available.") return } guildID, err := strconv.ParseInt(i.GuildID, 10, 64) if err != nil { respondEphemeral(s, i, "Invalid guild ID.") return } if err := services.Global.LogSettings.SetEnabled(context.Background(), guildID, enabled); err != nil { respondEphemeral(s, i, "Failed to update logging state.") return } if enabled { respondEphemeral(s, i, "Logging is now enabled.") return } respondEphemeral(s, i, "Logging is now disabled.") } func respondEphemeral(s *discordgo.Session, i *discordgo.InteractionCreate, msg string) { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: msg, Flags: discordgo.MessageFlagsEphemeral, }, }) }