From 23a5cdc98c04cec49a586e5c1646fbe1a0c7ed6d Mon Sep 17 00:00:00 2001 From: Martyn Ranyard Date: Thu, 13 Feb 2020 16:18:44 +0100 Subject: [PATCH] move irc to a package --- internal/irc/irc.go | 449 +++++++++++++++++++++++++++++++++++++++++ main.go | 472 ++------------------------------------------ 2 files changed, 465 insertions(+), 456 deletions(-) create mode 100644 internal/irc/irc.go diff --git a/internal/irc/irc.go b/internal/irc/irc.go new file mode 100644 index 0000000..6c22a0d --- /dev/null +++ b/internal/irc/irc.go @@ -0,0 +1,449 @@ +package irc + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net" + "net/textproto" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + rgb "github.com/foresthoffman/rgblog" +) + +var karaokards = [...]string{ + "Chorus contains a name", + "Refers to an animal", + "Aggressive", + "Contains instructions", + "Refers to relationships", + "Folk", + "Novelty", + "Pre-sixties", + "1960s", + "Ballad", + "Musical", + "Heartbroken", + "Contains made-up words", + "Refers to a colour", + "Chorus contains me, mine or my", + "Chorus contains want, need or have", + "Chorus contains how, when or why", + "Refers to money", + "Chorus contains love, like or hate", + "Chorus contains man, woman or everybody", + "Anthemic", + "Refers to explosives", + "Chorus contains a number", + "1970s", + "2000s", + "Love Song", + "Country-pop", + "Grunge", + "Indie", + "This is a morality tale", + "I've never sung this before", + "They'd beat me in a fight", + "My mortal enemy", + "Their name starts with the same letter as mine", + "This is NOT my era", + "Chorus contains oh, ooh or baby", + "Samples another song", + "Chorus contains don't, won't or can't", + "Euphoric", + "Title contains day, night or tomorrow", + "Chorus contains boy, girl or child", + "Refers to space", + "Soul", + "R&B", + "Dance", + "Electronie", + "Pop", + "1990s", + "Rock", + "Title contains brackets", + "Refers to religion", + "Refers to music", + "Chorus contains this, that or there", + "Chorus contains up, down or over", + "Beautiful", + "Mean about someone", + "Refers to weather", + "Chorus contains you, your or you're", + "Gloomy", + "Contains questions", + "Refers to death", + "Refers to sleep", + "Chorus contains heart, head or soul", + "Rock", + "Pop", + "Country", + "Punk", + "Rap", + "Christmas", + "Motown", + "This is their Second-best song", + "I shouldn't know this song. But I do!", + "I don't need the screen!", + "I'm too old for this song", + "The story of my life", + "I love this song SO much", + "They're twice my age!", + "Chorus contains I, I’m or I’ve", + "Title is one word long", + "Soundtrack", + "Pop", + "Is a metaphor", + "Title is at least five words long", + "Chorus contains move, stay or go", + "Refers to a place", + "R&B", + "Metal", + "Good workout music", + "Requires audience participation", + "Power Ballad", + "1980s", + "Rock", + "Rap", + "Pop-punk", + "Alternative", + "Soul", + "My secret shame", + "This gets me a bit emotional", + "Out of my range", + "This person is bad at their job", + "They look like someone here", + "I don't know the verse", + "They're half my age!", + "Play this at my funeral", + "This is NOT my genre", + "I hate this song so much", + "Girl band", + "Male solo", + "Artist begins with A", + "Just one word", + "Mixed gender band", + "Artist begins with C", + "Artist begins with G", + "Two people", + "TV contestant", + "European", + "Artist begins with P", + "Artist begins with M", + "In a famous family", + "Male-fronted band", + "Australian", + "One-hit wonder", + "Female-fronted band", + "Artist begins with T", + "Rock", + "Indie-rock", + "R&B", + "Latin", + "Disco", + "Britpop", + "2010s", + "I was a teenager!", + "The music video is so good", + "I’m younger than this song", + "First dance at my imaginary wedding", + "I'm worried about them", + "This person is the best dancer", + "I know the dance", + "Mostly shouting", + "They're not my gender", + "I wish we were married", + "I played this song too often", + "They are so influential", + "My favourite song of theirs", + "Perfect montage music", + "Artist uses their surname", + "Famous partner", + "Artist begins with E", + "Troubled artist", + "Actor", + "Artist begins with F", + "Solo artist", + "Artist begins with R", + "Hellraiser", + "Artist begins with ‘The’", + "10+ year career", + "Boy band", + "Female solo", + "British", + "Inappropriately clothed", + "Award winners", + "Artist begins with S", + "Artist begins with W", + "Asian", + "They split up :(", + "North American", + "Pop", + "Goth or Emo", + "This is a bit creepy, frankly", + "I’ve seen them in real life", + "A beautiful love story", + "OK, this is just ridiculous", + "Artist begins with B", + "Artist begins with D", + "They’re dead :(", + "Artist begins with L", + "Amazing hair", + "Artist begins with J"} + +var selectablePrompts []string + +const PSTFormat = "Jan 2 15:04:05 PST" + +// Regex for parsing PRIVMSG strings. +// +// First matched group is the user's name and the second matched group is the content of the +// user's message. +var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #\w+(?: :(.*))?$`) + +// Regex for parsing user commands, from already parsed PRIVMSG strings. +// +// First matched group is the command name and the second matched group is the argument for the +// command. +var CmdRegex *regexp.Regexp = regexp.MustCompile(`^!(\w+)\s?(\w+)?`) + +type OAuthCred struct { + + // The bot account's OAuth password. + Password string `json:"password,omitempty"` + + // The developer application client ID. Used for API calls to Twitch. + ClientID string `json:"client_id,omitempty"` +} + +type KardBot struct { + Channel string + conn net.Conn + Credentials *OAuthCred + MsgRate time.Duration + Name string + Port string + PrivatePath string + Server string + startTime time.Time +} + +// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it +// succeeds or is forcefully shutdown. +func (bb *kardBot) Connect() { + var err error + rgb.YPrintf("[%s] Connecting to %s...\n", timeStamp(), bb.Server) + + // makes connection to Twitch IRC server + bb.conn, err = net.Dial("tcp", bb.Server+":"+bb.Port) + if nil != err { + rgb.YPrintf("[%s] Cannot connect to %s, retrying.\n", timeStamp(), bb.Server) + bb.Connect() + return + } + rgb.YPrintf("[%s] Connected to %s!\n", timeStamp(), bb.Server) + bb.startTime = time.Now() +} + +// Officially disconnects the bot from the Twitch IRC server. +func (bb *kardBot) Disconnect() { + bb.conn.Close() + upTime := time.Now().Sub(bb.startTime).Seconds() + rgb.YPrintf("[%s] Closed connection from %s! | Live for: %fs\n", timeStamp(), bb.Server, upTime) +} + +// Listens for and logs messages from chat. Responds to commands from the channel owner. The bot +// continues until it gets disconnected, told to shutdown, or forcefully shutdown. +func (bb *kardBot) HandleChat() error { + rgb.YPrintf("[%s] Watching #%s...\n", timeStamp(), bb.Channel) + + // reads from connection + tp := textproto.NewReader(bufio.NewReader(bb.conn)) + + // listens for chat messages + for { + line, err := tp.ReadLine() + if nil != err { + + // officially disconnects the bot from the server + bb.Disconnect() + + return errors.New("bb.Bot.HandleChat: Failed to read line from channel. Disconnected.") + } + + // logs the response from the IRC server + rgb.YPrintf("[%s] %s\n", timeStamp(), line) + + if "PING :tmi.twitch.tv" == line { + + // respond to PING message with a PONG message, to maintain the connection + bb.conn.Write([]byte("PONG :tmi.twitch.tv\r\n")) + continue + } else { + + // handle a PRIVMSG message + matches := MsgRegex.FindStringSubmatch(line) + if nil != matches { + userName := matches[1] + msgType := matches[2] + + switch msgType { + case "PRIVMSG": + msg := matches[3] + rgb.GPrintf("[%s] %s: %s\n", timeStamp(), userName, msg) + + // parse commands from user message + cmdMatches := CmdRegex.FindStringSubmatch(msg) + if nil != cmdMatches { + cmd := cmdMatches[1] + + switch cmd { + case "card": + rgb.CPrintf("[%s] Card asked for!\n", timeStamp()) + + bb.Say("Your prompt is : " + selectablePrompts[rand.Intn(len(selectablePrompts))]) + } + + // channel-owner specific commands + if userName == bb.Channel { + switch cmd { + case "tbdown": + rgb.CPrintf( + "[%s] Shutdown command received. Shutting down now...\n", + timeStamp(), + ) + + bb.Disconnect() + return nil + default: + // do nothing + } + } + } + default: + // do nothing + } + } + } + time.Sleep(bb.MsgRate) + } +} + +// Makes the bot join its pre-specified channel. +func (bb *kardBot) JoinChannel() { + rgb.YPrintf("[%s] Joining #%s...\n", timeStamp(), bb.Channel) + bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n")) + bb.conn.Write([]byte("NICK " + bb.Name + "\r\n")) + bb.conn.Write([]byte("JOIN #" + bb.Channel + "\r\n")) + + rgb.YPrintf("[%s] Joined #%s as @%s!\n", timeStamp(), bb.Channel, bb.Name) +} + +// Reads from the private credentials file and stores the data in the bot's Credentials field. +func (bb *kardBot) ReadCredentials() error { + + // reads from the file + credFile, err := ioutil.ReadFile(bb.PrivatePath) + if nil != err { + return err + } + + bb.Credentials = &OAuthCred{} + + // parses the file contents + dec := json.NewDecoder(strings.NewReader(string(credFile))) + if err = dec.Decode(bb.Credentials); nil != err && io.EOF != err { + return err + } + + return nil +} + +// Makes the bot send a message to the chat channel. +func (bb *kardBot) Say(msg string) error { + if "" == msg { + return errors.New("BasicBot.Say: msg was empty.") + } + + // check if message is too large for IRC + if len(msg) > 512 { + return errors.New("BasicBot.Say: msg exceeded 512 bytes") + } + + _, err := bb.conn.Write([]byte(fmt.Sprintf("PRIVMSG #%s :%s\r\n", bb.Channel, msg))) + if nil != err { + return err + } + return nil +} + +// Starts a loop where the bot will attempt to connect to the Twitch IRC server, then connect to the +// pre-specified channel, and then handle the chat. It will attempt to reconnect until it is told to +// shut down, or is forcefully shutdown. +func (bb *kardBot) Start() { + err := bb.ReadCredentials() + if nil != err { + fmt.Println(err) + fmt.Println("Aborting...") + return + } + + for { + bb.Connect() + bb.JoinChannel() + err = bb.HandleChat() + if nil != err { + + // attempts to reconnect upon unexpected chat error + time.Sleep(1000 * time.Millisecond) + fmt.Println(err) + fmt.Println("Starting bot again...") + } else { + return + } + } +} + +func timeStamp() string { + return TimeStamp(PSTFormat) +} + +func TimeStamp(format string) string { + return time.Now().Format(format) +} + +type customStringsStruct struct { + Strings []string `json:"strings,omitempty"` +} + +var customStrings customStringsStruct + +func readBonusStrings() []string { + + ex, err := os.Executable() + if err != nil { + return []string{} + fmt.Println("Could not read `strings.json`, will only have builtin prompts. File reading error", err) + } + exPath := filepath.Dir(ex) + data, err := ioutil.ReadFile(exPath + "/strings.json") + if err != nil { + fmt.Println("Could not read `strings.json`, will only have builtin prompts. File reading error", err) + return []string{} + } + err = json.Unmarshal(data, &customStrings) + if err != nil { + fmt.Println("Could not unmarshal `strings.json`, will only have builtin prompts. Unmarshal error", err) + return []string{} + } + fmt.Println("Read ", len(customStrings.Strings), " prompts from `strings.json`") + return customStrings.Strings +} diff --git a/main.go b/main.go index 3b7a91b..57ae988 100755 --- a/main.go +++ b/main.go @@ -1,485 +1,45 @@ package main import ( - "bufio" - "encoding/json" - "errors" "fmt" - "io" - "io/ioutil" - "net" - "net/textproto" - "regexp" - "strings" - "time" "math/rand" "os" - "path/filepath" + "time" - rgb "github.com/foresthoffman/rgblog" + irc "git.martyn.berlin/martyn/karaokards/internal/irc" ) -var karaokards = [...]string { - "Chorus contains a name", - "Refers to an animal", - "Aggressive", - "Contains instructions", - "Refers to relationships", - "Folk", - "Novelty", - "Pre-sixties", - "1960s", - "Ballad", - "Musical", - "Heartbroken", - "Contains made-up words", - "Refers to a colour", - "Chorus contains me, mine or my", - "Chorus contains want, need or have", - "Chorus contains how, when or why", - "Refers to money", - "Chorus contains love, like or hate", - "Chorus contains man, woman or everybody", - "Anthemic", - "Refers to explosives", - "Chorus contains a number", - "1970s", - "2000s", - "Love Song", - "Country-pop", - "Grunge", - "Indie", - "This is a morality tale", - "I've never sung this before", - "They'd beat me in a fight", - "My mortal enemy", - "Their name starts with the same letter as mine", - "This is NOT my era", - "Chorus contains oh, ooh or baby", - "Samples another song", - "Chorus contains don't, won't or can't", - "Euphoric", - "Title contains day, night or tomorrow", - "Chorus contains boy, girl or child", - "Refers to space", - "Soul", - "R&B", - "Dance", - "Electronie", - "Pop", - "1990s", - "Rock", - "Title contains brackets", - "Refers to religion", - "Refers to music", - "Chorus contains this, that or there", - "Chorus contains up, down or over", - "Beautiful", - "Mean about someone", - "Refers to weather", - "Chorus contains you, your or you're", - "Gloomy", - "Contains questions", - "Refers to death", - "Refers to sleep", - "Chorus contains heart, head or soul", - "Rock", - "Pop", - "Country", - "Punk", - "Rap", - "Christmas", - "Motown", - "This is their Second-best song", - "I shouldn't know this song. But I do!", - "I don't need the screen!", - "I'm too old for this song", - "The story of my life", - "I love this song SO much", - "They're twice my age!", - "Chorus contains I, I’m or I’ve", - "Title is one word long", - "Soundtrack", - "Pop", - "Is a metaphor", - "Title is at least five words long", - "Chorus contains move, stay or go", - "Refers to a place", - "R&B", - "Metal", - "Good workout music", - "Requires audience participation", - "Power Ballad", - "1980s", - "Rock", - "Rap", - "Pop-punk", - "Alternative", - "Soul", - "My secret shame", - "This gets me a bit emotional", - "Out of my range", - "This person is bad at their job", - "They look like someone here", - "I don't know the verse", - "They're half my age!", - "Play this at my funeral", - "This is NOT my genre", - "I hate this song so much", - "Girl band", - "Male solo", - "Artist begins with A", - "Just one word", - "Mixed gender band", - "Artist begins with C", - "Artist begins with G", - "Two people", - "TV contestant", - "European", - "Artist begins with P", - "Artist begins with M", - "In a famous family", - "Male-fronted band", - "Australian", - "One-hit wonder", - "Female-fronted band", - "Artist begins with T", - "Rock", - "Indie-rock", - "R&B", - "Latin", - "Disco", - "Britpop", - "2010s", - "I was a teenager!", - "The music video is so good", - "I’m younger than this song", - "First dance at my imaginary wedding", - "I'm worried about them", - "This person is the best dancer", - "I know the dance", - "Mostly shouting", - "They're not my gender", - "I wish we were married", - "I played this song too often", - "They are so influential", - "My favourite song of theirs", - "Perfect montage music", - "Artist uses their surname", - "Famous partner", - "Artist begins with E", - "Troubled artist", - "Actor", - "Artist begins with F", - "Solo artist", - "Artist begins with R", - "Hellraiser", - "Artist begins with ‘The’", - "10+ year career", - "Boy band", - "Female solo", - "British", - "Inappropriately clothed", - "Award winners", - "Artist begins with S", - "Artist begins with W", - "Asian", - "They split up :(", - "North American", - "Pop", - "Goth or Emo", - "This is a bit creepy, frankly", - "I’ve seen them in real life", - "A beautiful love story", - "OK, this is just ridiculous", - "Artist begins with B", - "Artist begins with D", - "They’re dead :(", - "Artist begins with L", - "Amazing hair", - "Artist begins with J"} - -var selectablePrompts []string - -const PSTFormat = "Jan 2 15:04:05 PST" - -// Regex for parsing PRIVMSG strings. -// -// First matched group is the user's name and the second matched group is the content of the -// user's message. -var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #\w+(?: :(.*))?$`) - -// Regex for parsing user commands, from already parsed PRIVMSG strings. -// -// First matched group is the command name and the second matched group is the argument for the -// command. -var CmdRegex *regexp.Regexp = regexp.MustCompile(`^!(\w+)\s?(\w+)?`) - -type OAuthCred struct { - - // The bot account's OAuth password. - Password string `json:"password,omitempty"` - - // The developer application client ID. Used for API calls to Twitch. - ClientID string `json:"client_id,omitempty"` -} - -type kardBot struct { - Channel string - conn net.Conn - Credentials *OAuthCred - MsgRate time.Duration - Name string - Port string - PrivatePath string - Server string - startTime time.Time -} - -// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it -// succeeds or is forcefully shutdown. -func (bb *kardBot) Connect() { - var err error - rgb.YPrintf("[%s] Connecting to %s...\n", timeStamp(), bb.Server) - - // makes connection to Twitch IRC server - bb.conn, err = net.Dial("tcp", bb.Server+":"+bb.Port) - if nil != err { - rgb.YPrintf("[%s] Cannot connect to %s, retrying.\n", timeStamp(), bb.Server) - bb.Connect() - return - } - rgb.YPrintf("[%s] Connected to %s!\n", timeStamp(), bb.Server) - bb.startTime = time.Now() -} - -// Officially disconnects the bot from the Twitch IRC server. -func (bb *kardBot) Disconnect() { - bb.conn.Close() - upTime := time.Now().Sub(bb.startTime).Seconds() - rgb.YPrintf("[%s] Closed connection from %s! | Live for: %fs\n", timeStamp(), bb.Server, upTime) -} - -// Listens for and logs messages from chat. Responds to commands from the channel owner. The bot -// continues until it gets disconnected, told to shutdown, or forcefully shutdown. -func (bb *kardBot) HandleChat() error { - rgb.YPrintf("[%s] Watching #%s...\n", timeStamp(), bb.Channel) - - // reads from connection - tp := textproto.NewReader(bufio.NewReader(bb.conn)) - - // listens for chat messages - for { - line, err := tp.ReadLine() - if nil != err { - - // officially disconnects the bot from the server - bb.Disconnect() - - return errors.New("bb.Bot.HandleChat: Failed to read line from channel. Disconnected.") - } - - // logs the response from the IRC server - rgb.YPrintf("[%s] %s\n", timeStamp(), line) - - if "PING :tmi.twitch.tv" == line { - - // respond to PING message with a PONG message, to maintain the connection - bb.conn.Write([]byte("PONG :tmi.twitch.tv\r\n")) - continue - } else { - - // handle a PRIVMSG message - matches := MsgRegex.FindStringSubmatch(line) - if nil != matches { - userName := matches[1] - msgType := matches[2] - - switch msgType { - case "PRIVMSG": - msg := matches[3] - rgb.GPrintf("[%s] %s: %s\n", timeStamp(), userName, msg) - - // parse commands from user message - cmdMatches := CmdRegex.FindStringSubmatch(msg) - if nil != cmdMatches { - cmd := cmdMatches[1] - - switch cmd { - case "card": - rgb.CPrintf("[%s] Card asked for!\n", timeStamp(),) - - bb.Say("Your prompt is : "+selectablePrompts[rand.Intn(len(selectablePrompts))]) - } - - // channel-owner specific commands - if userName == bb.Channel { - switch cmd { - case "tbdown": - rgb.CPrintf( - "[%s] Shutdown command received. Shutting down now...\n", - timeStamp(), - ) - - bb.Disconnect() - return nil - default: - // do nothing - } - } - } - default: - // do nothing - } - } - } - time.Sleep(bb.MsgRate) - } -} - -// Makes the bot join its pre-specified channel. -func (bb *kardBot) JoinChannel() { - rgb.YPrintf("[%s] Joining #%s...\n", timeStamp(), bb.Channel) - bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n")) - bb.conn.Write([]byte("NICK " + bb.Name + "\r\n")) - bb.conn.Write([]byte("JOIN #" + bb.Channel + "\r\n")) - - rgb.YPrintf("[%s] Joined #%s as @%s!\n", timeStamp(), bb.Channel, bb.Name) -} - -// Reads from the private credentials file and stores the data in the bot's Credentials field. -func (bb *kardBot) ReadCredentials() error { - - // reads from the file - credFile, err := ioutil.ReadFile(bb.PrivatePath) - if nil != err { - return err - } - - bb.Credentials = &OAuthCred{} - - // parses the file contents - dec := json.NewDecoder(strings.NewReader(string(credFile))) - if err = dec.Decode(bb.Credentials); nil != err && io.EOF != err { - return err - } - - return nil -} - -// Makes the bot send a message to the chat channel. -func (bb *kardBot) Say(msg string) error { - if "" == msg { - return errors.New("BasicBot.Say: msg was empty.") - } - - // check if message is too large for IRC - if len(msg) > 512 { - return errors.New("BasicBot.Say: msg exceeded 512 bytes") - } - - _, err := bb.conn.Write([]byte(fmt.Sprintf("PRIVMSG #%s :%s\r\n", bb.Channel, msg))) - if nil != err { - return err - } - return nil -} - -// Starts a loop where the bot will attempt to connect to the Twitch IRC server, then connect to the -// pre-specified channel, and then handle the chat. It will attempt to reconnect until it is told to -// shut down, or is forcefully shutdown. -func (bb *kardBot) Start() { - err := bb.ReadCredentials() - if nil != err { - fmt.Println(err) - fmt.Println("Aborting...") - return - } - - for { - bb.Connect() - bb.JoinChannel() - err = bb.HandleChat() - if nil != err { - - // attempts to reconnect upon unexpected chat error - time.Sleep(1000 * time.Millisecond) - fmt.Println(err) - fmt.Println("Starting bot again...") - } else { - return - } - } -} - -func timeStamp() string { - return TimeStamp(PSTFormat) -} - -func TimeStamp(format string) string { - return time.Now().Format(format) -} - -type customStringsStruct struct { - Strings []string `json:"strings,omitempty"` -} - - -var customStrings customStringsStruct; - -func readBonusStrings() []string { - - ex, err := os.Executable() - if err != nil { - return []string{} - fmt.Println("Could not read `strings.json`, will only have builtin prompts. File reading error", err) - } - exPath := filepath.Dir(ex) - data, err := ioutil.ReadFile(exPath+"/strings.json") - if err != nil { - fmt.Println("Could not read `strings.json`, will only have builtin prompts. File reading error", err) - return []string{} - } - err = json.Unmarshal(data, &customStrings); - if err != nil { - fmt.Println("Could not unmarshal `strings.json`, will only have builtin prompts. Unmarshal error", err) - return []string{} - } - fmt.Println("Read ",len(customStrings.Strings)," prompts from `strings.json`"); - return customStrings.Strings -} - func main() { rand.Seed(time.Now().UnixNano()) for _, val := range karaokards { - selectablePrompts = append(selectablePrompts,val); + selectablePrompts = append(selectablePrompts, val) } for _, val := range readBonusStrings() { - selectablePrompts = append(selectablePrompts,val); + selectablePrompts = append(selectablePrompts, val) } - fmt.Println(len(selectablePrompts)," prompts available."); - oauthPath := "" - if (os.Getenv("TWITCH_OAUTH_JSON") != "") { + fmt.Println(len(selectablePrompts), " prompts available.") + oauthPath := "" + if os.Getenv("TWITCH_OAUTH_JSON") != "" { if _, err := os.Stat(os.Getenv("TWITCH_OAUTH_JSON")); os.IsNotExist(err) { - os.Exit(1); + os.Exit(1) } oauthPath = os.Getenv("TWITCH_OAUTH_JSON") } else { - if _, err := os.Stat(os.Getenv("HOME")+"/.twitch/oauth.json"); os.IsNotExist(err) { - rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", timeStamp(), os.Getenv("HOME")+"/.twitch/oauth.json", "/etc/twitch/oauth.json"); + if _, err := os.Stat(os.Getenv("HOME") + "/.twitch/oauth.json"); os.IsNotExist(err) { + rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", timeStamp(), os.Getenv("HOME")+"/.twitch/oauth.json", "/etc/twitch/oauth.json") if _, err := os.Stat("/etc/twitch/oauth.json"); os.IsNotExist(err) { - rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", timeStamp(), "/etc/twitch/oauth.json"); - os.Exit(1); + rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", timeStamp(), "/etc/twitch/oauth.json") + os.Exit(1) } - oauthPath = "/etc/twitch/oauth.json"; + oauthPath = "/etc/twitch/oauth.json" } else { - oauthPath = os.Getenv("HOME")+"/.twitch/oauth.json"; + oauthPath = os.Getenv("HOME") + "/.twitch/oauth.json" } } // Replace the channel name, bot name, and the path to the private directory with your respective // values. - myBot := kardBot{ + myBot := irc.KardBot{ Channel: "imartynontwitch", MsgRate: time.Duration(20/30) * time.Millisecond, Name: "Karaokards", @@ -488,4 +48,4 @@ func main() { Server: "irc.chat.twitch.tv", } myBot.Start() -} \ No newline at end of file +}