all repos — disgord @ 69bc8652d9d19d047e20630cc17f125e5c8a58ba

A simple Discord bot in Go.

add myconfig, help command
Marco Andronaco andronacomarco@gmail.com
Mon, 30 Sep 2024 14:21:09 +0200
commit

69bc8652d9d19d047e20630cc17f125e5c8a58ba

parent

8cb81ef762818bdc9e468ba1f1e5e18f7565aa95

9 files changed, 225 insertions(+), 45 deletions(-)

jump to
D .env.example

@@ -1,2 +0,0 @@

-APP_ID= -AUTH_TOKEN=
M .gitignore.gitignore

@@ -1,1 +1,3 @@

+__debug_bin* .env +config.json
A LICENSE

@@ -0,0 +1,21 @@

+The MIT License (MIT) + +Copyright (c) 2024 BiRabittoh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.
A commands.go

@@ -0,0 +1,58 @@

+package main + +import ( + "strings" + + "github.com/bwmarrin/discordgo" +) + +var ( + handlersMap map[string]BotCommand + shortCommands = map[string]string{} +) + +func InitHandlers() { + handlersMap = map[string]BotCommand{ + "echo": {ShortCode: "e", Handler: handleEcho, Help: "echoes a message"}, + "prefix": {ShortCode: "pre", Handler: handlePrefix, Help: "sets the bot's prefix"}, + "help": {ShortCode: "h", Handler: handleHelp, Help: "shows this help message"}, + } + + for command, botCommand := range handlersMap { + if botCommand.ShortCode == "" { + continue + } + shortCommands[botCommand.ShortCode] = command + } +} + +func handleHelp(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + helpText := "**Bot commands:**\n" + for command, botCommand := range handlersMap { + helpText += "* " + botCommand.FormatHelp(command) + "\n" + } + return helpText +} + +func handleEcho(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + return strings.Join(args, " ") +} + +func handlePrefix(args []string, s *discordgo.Session, m *discordgo.MessageCreate) string { + if len(args) == 0 { + return "Usage: prefix <new prefix>" + } + + newPrefix := args[0] + if len(newPrefix) > 10 { + return "Prefix is too long" + } + + config.Values.Prefix = newPrefix + err := config.Save() + if err != nil { + logger.Errorf("could not save config: %s", err) + } + + return "Prefix set to " + newPrefix +}
A config.example.json

@@ -0,0 +1,13 @@

+{ + "applicationId": "your-application-id-here", + "token": "your-token-here", + "prefix": "$", + "magazineSize": 3, + "outros": + [ + { "name": "TheFatRat - Xenogenesis", "value": "https://www.youtube.com/watch?v=6N8zvi1VNSc" }, + { "name": "OMFG - Hello", "value": "https://www.youtube.com/watch?v=5nYVNTX0Ib8" }, + { "name": "Pegboard Nerds - Disconnected", "value": "https://www.youtube.com/watch?v=YdBtx8qG68w" }, + { "name": "Gym Class Heroes - Stereo Hearts", "value": "https://www.youtube.com/watch?v=ThctmvQ3NGk" } + ] +}
D constants.go

@@ -1,17 +0,0 @@

-package main - -import ( - "os" - - "github.com/BiRabittoh/disgord/mylog" - "github.com/kkdai/youtube/v2" -) - -var ( - discordToken string - appID string - prefix string = "$" - - logger = mylog.NewLogger(os.Stdout, "main", mylog.DEBUG) - yt = youtube.Client{} -)
A globals.go

@@ -0,0 +1,59 @@

+package main + +import ( + "fmt" + "os" + + "github.com/BiRabittoh/disgord/myconfig" + "github.com/BiRabittoh/disgord/mylog" + "github.com/bwmarrin/discordgo" + "github.com/kkdai/youtube/v2" +) + +const ( + helpFmt = "%s - _%s_" +) + +type KeyValue struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type Config struct { + ApplicationID string `json:"applicationId"` + Token string `json:"token"` + + Prefix string `json:"prefix"` + + Outros []KeyValue `json:"outros"` + Radios []KeyValue `json:"radios"` + + MagazineSize uint `json:"magazineSize"` +} + +type BotCommand struct { + Handler func([]string, *discordgo.Session, *discordgo.MessageCreate) string + ShortCode string + Help string +} + +func formatCommand(command string) string { + return fmt.Sprintf("`%s%s`", config.Values.Prefix, command) +} + +func (bc BotCommand) FormatHelp(command string) string { + var shortCodeStr string + if bc.ShortCode != "" { + shortCodeStr = fmt.Sprintf(" (%s)", formatCommand(bc.ShortCode)) + } + return fmt.Sprintf(helpFmt, formatCommand(command)+shortCodeStr, bc.Help) +} + +var ( + discordToken string + appID string + config *myconfig.Config[Config] + + logger = mylog.NewLogger(os.Stdout, "main", mylog.DEBUG) + yt = youtube.Client{} +)
M main.gomain.go

@@ -5,40 +5,59 @@ "os"

"os/signal" "strings" + "github.com/BiRabittoh/disgord/myconfig" "github.com/bwmarrin/discordgo" - "github.com/joho/godotenv" ) func parseUserMessage(messageContent string) (command string, args []string, ok bool) { - after, found := strings.CutPrefix(messageContent, prefix) + after, found := strings.CutPrefix(messageContent, config.Values.Prefix) if !found { return } userInput := strings.Split(after, " ") - if len(userInput) == 0 { + command = strings.ToLower(userInput[0]) + return command, userInput[1:], len(command) > 0 +} + +func handleCommand(s *discordgo.Session, m *discordgo.MessageCreate) (response string, ok bool, err error) { + command, args, ok := parseUserMessage(m.Content) + if !ok { return } - return userInput[0], userInput[1:], true + longCommand, short := shortCommands[command] + if short { + command = longCommand + } + + botCommand, found := handlersMap[command] + if !found { + response = "unknown command: " + command + return + } + + response = botCommand.Handler(args, s, m) + return } func messageHandler(s *discordgo.Session, m *discordgo.MessageCreate) { logger.Debug("got a message: " + m.Content) - command, args, ok := parseUserMessage(m.Content) + + response, ok, err := handleCommand(s, m) + if err != nil { + logger.Errorf("could not handle command: %s", err) + return + } if !ok { return } - - var response string - switch command { - case "e", "echo": - response = strings.Join(args, " ") - default: - response = "unknown command: " + command + if response == "" { + logger.Debug("got empty response") + return } - _, err := s.ChannelMessageSend(m.ChannelID, response) + _, err = s.ChannelMessageSend(m.ChannelID, response) if err != nil { logger.Errorf("could not send message: %s", err) }

@@ -49,25 +68,18 @@ logger.Infof("Logged in as %s", r.User.String())

} func main() { - err := godotenv.Load() + var err error + config, err = myconfig.New[Config]("config.json") if err != nil { - logger.Errorf("Error loading .env file: %s", err) - } - - appID = os.Getenv("APP_ID") - if appID == "" { - logger.Fatal("Could not find Discord App ID.") - } - - discordToken = os.Getenv("AUTH_TOKEN") - if discordToken == "" { - logger.Fatal("Could not find Discord bot token.") + logger.Errorf("could not load config: %s", err) } - session, err := discordgo.New("Bot " + discordToken) + session, err := discordgo.New("Bot " + config.Values.Token) if err != nil { logger.Fatalf("could not create bot session: %s", err) } + + InitHandlers() session.AddHandler(messageHandler) session.AddHandler(readyHandler)
A myconfig/config.go

@@ -0,0 +1,34 @@

+package myconfig + +import ( + "encoding/json" + "os" +) + +type Config[T any] struct { + filename string + Values T +} + +func New[T any](filename string) (c *Config[T], err error) { + fileContent, err := os.ReadFile(filename) + if err != nil { + return + } + + c = &Config[T]{ + filename: filename, + } + + err = json.Unmarshal(fileContent, &c.Values) + return +} + +func (c *Config[T]) Save() error { + fileContent, err := json.MarshalIndent(c.Values, "", " ") + if err != nil { + return err + } + + return os.WriteFile(c.filename, fileContent, 0644) +}