diff --git a/commandStore.go b/commandStore.go index 37fc3d5..4ac95f8 100644 --- a/commandStore.go +++ b/commandStore.go @@ -16,11 +16,34 @@ func commandsJSON() (commandList []discord.ApplicationCommandCreate) { commandList = []discord.ApplicationCommandCreate{ discord.SlashCommandCreate{ Name: "host-stats", - Description: "Returns host statistics like OS, CPU Usage, etc", + Description: "Host statistics like OS, CPU Usage, etc", }, discord.SlashCommandCreate{ - Name: "invite", - Description: "Add the bot to your community server", + Name: "repository", + Description: "A casual button that forwards you to the bot's repository", + }, + discord.SlashCommandCreate{ + Name: "config", + Description: "View or change the bot's configuration", + DMPermission: &FALSE, + Options: []discord.ApplicationCommandOption{ + discord.ApplicationCommandOptionChannel{ + Name: "ban-records", + Description: "Set a channel that will be used to display ban records via Google Sheets", + Required: false, + ChannelTypes: []discord.ChannelType{ + discord.ChannelTypeGuildText, + discord.ChannelTypeGuildForum, + discord.ChannelTypeGuildPublicThread, + discord.ChannelTypeGuildPrivateThread, + }, + }, + discord.ApplicationCommandOptionString{ + Name: "google-sheets", + Description: "Set the Google Sheets URL that will be used to display ban records", + Required: false, + }, + }, }, } return commandList diff --git a/docker-compose.yml b/docker-compose.yml index bd6855d..b18e9c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: image: 'git.toast-server.net/toast/corn-utility:main@sha256:d966d1c466e60617d0059a4d9a3a147baf9228d2a90ed2dbceab4e9ad392e49e' volumes: - ./tokens.json:/home/corn-util/tokens.json:ro + - ./config.json:/home/corn-util/config.json:rw expose: - 23242:23242 restart: unless-stopped diff --git a/events/interaction.go b/events/interaction.go index 4fad155..6fb4605 100644 --- a/events/interaction.go +++ b/events/interaction.go @@ -1,7 +1,9 @@ package botEvents import ( + "corn-util/bot/loaders" "corn-util/bot/toolbox" + "encoding/json" "fmt" "runtime" "strings" @@ -18,8 +20,10 @@ import ( ) var ( - mainEmbedColor = 0xff69b4 //0xf9c62c - main embed color for the bot (saffron / yellow) - noPermText = "You need to have a role with `Manage Server` permission to use this command." + mainEmbedColor = 0xff69b4 //0xf9c62c - main embed color for the bot (saffron / yellow) + noPermText = "You need to have a role with `Manage Server` permission to use this command." + attemptFailText = "There was an attempt..." + noConfigValText = "Unconfigured" ) func ListenForCommand(e *events.ApplicationCommandInteractionCreate) { @@ -83,19 +87,118 @@ func ListenForCommand(e *events.ApplicationCommandInteractionCreate) { DumpErrToConsole(err) } break - case "invite": + case "repository": e.CreateMessage(discord.MessageCreate{ Components: []discord.ContainerComponent{ discord.ActionRowComponent{ discord.ButtonComponent{ - Label: "Invite me!", + Label: "Check out the repsotiry!", Style: discord.ButtonStyleLink, - URL: fmt.Sprintf("https://discord.com/api/oauth2/authorize?client_id=%v&permissions=19456&scope=bot", e.Client().ApplicationID()), + URL: "https://git.toast-server.net/toast/Corn-Utility.git", }, }, }, }) break + case "config": + type jsonDataStruct struct { + BanRecords string `json:"banRecords"` + GoogleSheets string `json:"googleSheets"` + } + readData, _ := loaders.DataLoader.Read(&loaders.JSON{}, "config.json") + jsonData := jsonDataStruct{} + jsonDataBytes, _ := json.Marshal(readData) + json.Unmarshal(jsonDataBytes, &jsonData) + fmt.Println(jsonData) + + banRecordsCh, _ := e.SlashCommandInteractionData().OptChannel("ban-records") + googleSheetsURL, _ := e.SlashCommandInteractionData().OptString("google-sheets") + + if !isGuildManager(e) { + DumpErrToInteraction(e, fmt.Errorf(noPermText)) + return + } + if len(e.SlashCommandInteractionData().Options) == 0 { + e.CreateMessage(discord.MessageCreate{ + Embeds: []discord.Embed{ + { + Title: "Current configuration", + Description: "You can configure the bot using the options below.", + Fields: []discord.EmbedField{ + { + Name: "Ban Records", + Value: func() string { + if jsonData.BanRecords == "" || jsonData.BanRecords == "0" { + return noConfigValText + } else { + return fmt.Sprintf("<#%v>", jsonData.BanRecords) + } + }(), + Inline: &TRUE, + }, + { + Name: "Google Sheets", + Value: func() string { + if jsonData.GoogleSheets == "" { + return noConfigValText + } else { + return fmt.Sprintf("`%v`", jsonData.GoogleSheets) + } + }(), + Inline: &TRUE, + }, + }, + Color: mainEmbedColor, + }, + }, + }) + return + } + + if banRecordsCh.Permissions.Has(discord.PermissionSendMessages, discord.PermissionEmbedLinks) { + // Create a placeholder embed in the configured channel. + if _, err := e.Client().Rest().CreateMessage(banRecordsCh.ID, discord.MessageCreate{ + Embeds: []discord.Embed{ + { + Description: "This is a placeholder embed for ban records.\n" + + "Once the bot is configured, this will be updated with the latest ban records from Google Sheets.", + Color: mainEmbedColor, + }, + }, + }); err != nil { + DumpErrToChannel(e, err) + return + } + } + + fmt.Printf("google-sheets: %v\n", googleSheetsURL) + if err := e.CreateMessage(discord.MessageCreate{ + Embeds: []discord.Embed{{Title: "Config saved!", Color: mainEmbedColor}}, + }); err != nil { + DumpErrToInteraction(e, err) + return + } + + // Update the config file with the new configuration. + data := map[string]interface{}{} + + if banRecordsCh.ID.String() != "0" { + data["banRecords"] = banRecordsCh.ID + } else { + data["banRecords"] = jsonData.BanRecords + } + + if googleSheetsURL != "" { + data["googleSheets"] = googleSheetsURL + } else { + data["googleSheets"] = jsonData.GoogleSheets + } + + err := loaders.DataLoader.Write(&loaders.JSON{}, "config.json", data) + if err != nil { + DumpErrToChannel(e, err) + return + } } } @@ -107,7 +210,7 @@ func DumpErrToInteraction(e *events.ApplicationCommandInteractionCreate, err err if err := e.CreateMessage(discord.MessageCreate{ Embeds: []discord.Embed{ { - Title: "There was an attempt...", + Title: attemptFailText, Description: fmt.Sprintf("```%v```", err.Error()), Color: 0x560000, }, @@ -121,7 +224,7 @@ func DumpErrToChannel(e *events.ApplicationCommandInteractionCreate, err error) if _, err := e.Client().Rest().CreateMessage(e.Channel().ID(), discord.MessageCreate{ Embeds: []discord.Embed{ { - Title: "There was an attempt...", + Title: attemptFailText, Description: fmt.Sprintf("```%v```", err.Error()), Color: 0x560000, }, @@ -132,8 +235,7 @@ func DumpErrToChannel(e *events.ApplicationCommandInteractionCreate, err error) } func isGuildManager(e *events.ApplicationCommandInteractionCreate) bool { - member := e.Member() - if member.Permissions.Has(discord.PermissionManageGuild) { + if e.Member().Permissions.Has(discord.PermissionManageGuild) { return true } else { return false diff --git a/loaders/db.go b/loaders/db.go new file mode 100644 index 0000000..643e3d0 --- /dev/null +++ b/loaders/db.go @@ -0,0 +1,68 @@ +package loaders + +import ( + "encoding/json" + "os" +) + +/* + This is a simple database system that peforms CRUD operations on a JSON file. + It will be a proper database system in the future, but for now it will be a JSON file. + Hope this is understandable for future coders. +*/ + +type DataLoader interface { + Read(filename string) (interface{}, error) + Write(filename string, data interface{}) error +} + +type JSON struct{} + +func (l *JSON) Read(filename string) (interface{}, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + bytes, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + data := make(map[string]interface{}) + err = json.Unmarshal(bytes, &data) + if err != nil { + return nil, err + } + + return data, nil +} + +func (l *JSON) Write(filename string, data interface{}) error { + existingData, err := l.Read(filename) + if err != nil { + return err + } + + newData := data.(map[string]interface{}) + for k, v := range existingData.(map[string]interface{}) { + if newV, ok := newData[k]; ok { + newData[k] = newV + } else { + newData[k] = v + } + } + + bytes, err := json.MarshalIndent(newData, "", " ") + if err != nil { + return err + } + + err = os.WriteFile(filename, bytes, 0644) + if err != nil { + return err + } + + return nil +} diff --git a/main.go b/main.go index 0035f80..fc065be 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( botEvents "corn-util/bot/events" "corn-util/bot/loaders" - "fmt" "os" "os/signal" "runtime" @@ -44,8 +43,8 @@ func main() { log.Infof("Commands deployment is disabled") } - fmt.Printf("Client ready!\n") - fmt.Printf("Running Disgo %v & Go %v\n", disgo.Version, strings.TrimPrefix(runtime.Version(), "go")) + log.Infof("Client ready!") + log.Infof("Running Disgo %v & Go %v", disgo.Version, strings.TrimPrefix(runtime.Version(), "go")) client.Rest().CreateWebhookMessage(snowflake.MustParse(loaders.TokenLoader("hookId")), loaders.TokenLoader("hookToken"), discord.WebhookMessageCreate{ Content: "Container has been reloaded.", }, true, 0) diff --git a/toolbox/system.go b/toolbox/system.go index 934d585..1cd1703 100644 --- a/toolbox/system.go +++ b/toolbox/system.go @@ -2,8 +2,16 @@ package toolbox import "time" +/* + System/OS-related utilities such as uptime. +*/ + var timerStart = time.Now() +/* + Nor the Go or the Disgo library has a built-in + Client/Process uptime function, so I had to get creative. +*/ func GetUptime() string { uptime := time.Since(timerStart) return uptime.Round(time.Second).String()