From aa565a86e9c50cd75993723f80a169a63b0d0784 Mon Sep 17 00:00:00 2001 From: lyx0 <66651385+lyx0@users.noreply.github.com> Date: Tue, 21 Jun 2022 00:31:17 +0200 Subject: [PATCH] start rewrite --- cmd/bot/main.go | 1 + pkg/bot/bot.go | 68 ++++++++++++++++++++++ pkg/bot/command.go | 49 ++++++++++++++++ pkg/bot/privatemessage.go | 28 +++++++++ pkg/bot/send.go | 119 ++++++++++++++++++++++++++++++++++++++ pkg/bot/whispermessage.go | 9 +++ 6 files changed, 274 insertions(+) create mode 100644 cmd/bot/main.go create mode 100644 pkg/bot/bot.go create mode 100644 pkg/bot/command.go create mode 100644 pkg/bot/privatemessage.go create mode 100644 pkg/bot/send.go create mode 100644 pkg/bot/whispermessage.go diff --git a/cmd/bot/main.go b/cmd/bot/main.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/bot/main.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/pkg/bot/bot.go b/pkg/bot/bot.go new file mode 100644 index 0000000..98c3376 --- /dev/null +++ b/pkg/bot/bot.go @@ -0,0 +1,68 @@ +package bot + +import ( + "os" + + "github.com/gempir/go-twitch-irc/v3" + "github.com/joho/godotenv" + "github.com/sirupsen/logrus" +) + +type config struct { + env string + botUsername string + botOauth string +} + +type Bot struct { + config config + twitchClient *twitch.Client + logger *logrus.Logger +} + +func New() *Bot { + var cfg config + + // Initialize a new logger we attach to our application struct. + lgr := logrus.New() + + // Load the .env file and check for errors. + err := godotenv.Load() + if err != nil { + lgr.Fatal("Error loading .env file") + } + + // Load bot credentials from the .env file. + cfg.botUsername = os.Getenv("BOT_USER") + cfg.botOauth = os.Getenv("BOT_OAUTH") + + // Initialize a new twitch client which we attach to our + // application struct. + twitchClient := twitch.NewClient(cfg.botUsername, cfg.botOauth) + + bot := &Bot{ + config: cfg, + twitchClient: twitchClient, + logger: lgr, + } + // Received a PrivateMessage (normal chat message), pass it to + // the handler who checks for further action. + bot.twitchClient.OnPrivateMessage(func(message twitch.PrivateMessage) { + bot.handlePrivateMessage(message) + }) + + // Received a WhisperMessage (Twitch DM), pass it to + // the handler who checks for further action. + bot.twitchClient.OnWhisperMessage(func(message twitch.WhisperMessage) { + bot.handleWhisperMessage(message) + }) + + // Successfully connected to Twitch so we log a message with the + // mode we are currently running in.. + bot.twitchClient.OnConnect(func() { + bot.logger.Infof("Successfully connected to Twitch Servers in %s mode!", bot.config.env) + }) + + return bot + +} diff --git a/pkg/bot/command.go b/pkg/bot/command.go new file mode 100644 index 0000000..77d53e5 --- /dev/null +++ b/pkg/bot/command.go @@ -0,0 +1,49 @@ +package bot + +import ( + "strings" + + "github.com/gempir/go-twitch-irc/v3" +) + +func (bot *Bot) handleCommand(message twitch.PrivateMessage) { + bot.logger.Info("[COMMAND HANDLER]", message) + + // commandName is the actual name of the command without the prefix. + // e.g. `()ping` would be `ping`. + commandName := strings.ToLower(strings.SplitN(message.Message, " ", 3)[0][2:]) + + // cmdParams are additional command parameters. + // e.g. `()weather san antonio` + // cmdParam[0] is `san` and cmdParam[1] = `antonio`. + // + // Since Twitch messages are at most 500 characters I use a + // maximum count of 500+10 just to be safe. + // https://discuss.dev.twitch.tv/t/missing-client-side-message-length-check/21316 + cmdParams := strings.SplitN(message.Message, " ", 510) + _ = cmdParams + + // msgLen is the amount of words in a message without the prefix. + // Useful to check if enough cmdParams are provided. + msgLen := len(strings.SplitN(message.Message, " ", -2)) + + // target is the channelname the message originated from and + // where we are responding. + target := message.Channel + + switch commandName { + case "": + if msgLen == 1 { + bot.Send(target, "xd") + return + } + case "echo": + if msgLen < 2 { + bot.Send(target, "Not enough arguments provided.") + return + } else { + bot.Send(target, message.Message[7:len(message.Message)]) + return + } + } +} diff --git a/pkg/bot/privatemessage.go b/pkg/bot/privatemessage.go new file mode 100644 index 0000000..39be731 --- /dev/null +++ b/pkg/bot/privatemessage.go @@ -0,0 +1,28 @@ +package bot + +import "github.com/gempir/go-twitch-irc/v3" + +func (bot *Bot) handlePrivateMessage(message twitch.PrivateMessage) { + // roomId is the Twitch UserID of the channel the + // message originated from. + roomId := message.Tags["room-id"] + + // If there is no roomId something went wrong. + if roomId == "" { + bot.logger.Error("Missing room-id in message tag ", roomId) + return + } + + if len(message.Message) >= 2 { + if message.Message[:2] == "()" { + // TODO: Command Handling + bot.handleCommand(message) + // app.logger.Infof("[Command detected]: ", message.Message) + return + } + } + + // Message was no command so we just print it. + bot.logger.Infof("[#%s]:%s: %s", message.Channel, message.User.DisplayName, message.Message) + +} diff --git a/pkg/bot/send.go b/pkg/bot/send.go new file mode 100644 index 0000000..c6b32e5 --- /dev/null +++ b/pkg/bot/send.go @@ -0,0 +1,119 @@ +package bot + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// banphraseResponse is the data we receive back from +// the banphrase API +type banphraseResponse struct { + Banned bool `json:"banned"` + InputMessage string `json:"input_message"` + BanphraseData banphraseData `json:"banphrase_data"` +} + +// banphraseData contains details about why a message +// was banphrased. +type banphraseData struct { + Id int `json:"id"` + Name string `json:"name"` + Phrase string `json:"phrase"` + Length int `json:"length"` + Permanent bool `json:"permanent"` +} + +var ( + banPhraseUrl = "https://pajlada.pajbot.com/api/v1/banphrases/test" +) + +// CheckMessage checks a given message against the banphrase api. +// returns false, "okay" if a message is allowed +// returns true and a string with the reason if it was banned. +// More information: +// https://gist.github.com/pajlada/57464e519ba8d195a97ddcd0755f9715 +func (bot *Bot) checkMessage(text string) (bool, string) { + + // {"message": "AHAHAHAHA LUL"} + reqBody, err := json.Marshal(map[string]string{ + "message": text, + }) + if err != nil { + bot.logger.Error(err) + } + + resp, err := http.Post(banPhraseUrl, "application/json", bytes.NewBuffer(reqBody)) + if err != nil { + bot.logger.Error(err) + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + bot.logger.Error(err) + } + + var responseObject banphraseResponse + json.Unmarshal(body, &responseObject) + + // Bad Message + // + // {"phrase": "No gyazo allowed"} + reason := responseObject.BanphraseData.Name + if responseObject.Banned { + return true, fmt.Sprint(reason) + } else if !responseObject.Banned { + // Good message + return false, "okay" + } + + // Couldn't contact api so assume it was a bad message + return true, "Banphrase API couldn't be reached monkaS" +} + +// Send is used to send twitch replies and contains the necessary +// safeguards and logic for that. +func (bot *Bot) Send(target, message string) { + // Message we are trying to send is empty. + if len(message) == 0 { + return + } + + // Since messages starting with `.` or `/` are used for special actions + // (ban, whisper, timeout) and so on, we place a emote infront of it so + // the actions wouldn't execute. `!` and `$` are common bot prefixes so we + // don't allow them either. + if message[0] == '.' || message[0] == '/' || message[0] == '!' || message[0] == '$' { + message = ":tf: " + message + } + + // check the message for bad words before we say it + messageBanned, banReason := bot.checkMessage(message) + if messageBanned { + // Bad message, replace message and log it. + bot.twitchClient.Say(target, "[BANPHRASED] monkaS") + bot.logger.Info("Banned message detected: ", banReason) + + return + } else { + // In case the message we are trying to send is longer than the + // maximum allowed message length on twitch. We split the message + // in two parts. + if len(message) > 500 { + firstMessage := message[0:499] + secondMessage := message[499:] + + bot.twitchClient.Say(target, firstMessage) + bot.twitchClient.Say(target, secondMessage) + + return + } + // Message was fine. + bot.twitchClient.Say(target, message) + return + } +} diff --git a/pkg/bot/whispermessage.go b/pkg/bot/whispermessage.go new file mode 100644 index 0000000..716dda1 --- /dev/null +++ b/pkg/bot/whispermessage.go @@ -0,0 +1,9 @@ +package bot + +import "github.com/gempir/go-twitch-irc/v3" + +func (bot *Bot) handleWhisperMessage(message twitch.WhisperMessage) { + // Print the whisper message for now. + // TODO: Implement a basic whisper handler. + bot.logger.Infof("[#whisper]:%s: %s", message.User.DisplayName, message.Message) +}