Files
velox-bot/internal/commands/config/public/config.go
T
FernandoJVideira 035e383db2 feat: implement server logging functionality
- Added a logging service to manage server event logs including message edits, deletions, member joins/leaves, and moderation actions.
- Introduced commands to configure logging settings, including setting the log channel and enabling/disabling logging.
- Updated the README to document the new logging features and commands.
- Enhanced moderation commands to log actions taken on members.
2026-03-18 13:18:12 +00:00

579 lines
16 KiB
Go

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,
},
})
}