diff --git a/.gitignore b/.gitignore index 9a3a8d8..35028ad 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +karaokards \ No newline at end of file diff --git a/internal/builtins/kards.go b/internal/builtins/kards.go new file mode 100644 index 0000000..93257d5 --- /dev/null +++ b/internal/builtins/kards.go @@ -0,0 +1,181 @@ +package builtins + +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"} diff --git a/internal/irc/irc.go b/internal/irc/irc.go index 6c22a0d..475fd31 100644 --- a/internal/irc/irc.go +++ b/internal/irc/irc.go @@ -10,8 +10,6 @@ import ( "math/rand" "net" "net/textproto" - "os" - "path/filepath" "regexp" "strings" "time" @@ -19,188 +17,6 @@ import ( 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. @@ -234,36 +50,37 @@ type KardBot struct { PrivatePath string Server string startTime time.Time + Prompts []string } // 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() { +func (bb *KardBot) Connect() { var err error - rgb.YPrintf("[%s] Connecting to %s...\n", timeStamp(), bb.Server) + 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) + 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) + 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() { +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) + 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) +func (bb *KardBot) HandleChat() error { + rgb.YPrintf("[%s] Watching #%s...\n", TimeStamp(), bb.Channel) // reads from connection tp := textproto.NewReader(bufio.NewReader(bb.conn)) @@ -280,7 +97,7 @@ func (bb *kardBot) HandleChat() error { } // logs the response from the IRC server - rgb.YPrintf("[%s] %s\n", timeStamp(), line) + rgb.YPrintf("[%s] %s\n", TimeStamp(), line) if "PING :tmi.twitch.tv" == line { @@ -298,7 +115,7 @@ func (bb *kardBot) HandleChat() error { switch msgType { case "PRIVMSG": msg := matches[3] - rgb.GPrintf("[%s] %s: %s\n", timeStamp(), userName, msg) + rgb.GPrintf("[%s] %s: %s\n", TimeStamp(), userName, msg) // parse commands from user message cmdMatches := CmdRegex.FindStringSubmatch(msg) @@ -307,9 +124,9 @@ func (bb *kardBot) HandleChat() error { switch cmd { case "card": - rgb.CPrintf("[%s] Card asked for!\n", timeStamp()) + rgb.CPrintf("[%s] Card asked for!\n", TimeStamp()) - bb.Say("Your prompt is : " + selectablePrompts[rand.Intn(len(selectablePrompts))]) + bb.Say("Your prompt is : " + bb.Prompts[rand.Intn(len(bb.Prompts))]) } // channel-owner specific commands @@ -318,7 +135,7 @@ func (bb *kardBot) HandleChat() error { case "tbdown": rgb.CPrintf( "[%s] Shutdown command received. Shutting down now...\n", - timeStamp(), + TimeStamp(), ) bb.Disconnect() @@ -338,17 +155,17 @@ func (bb *kardBot) HandleChat() error { } // Makes the bot join its pre-specified channel. -func (bb *kardBot) JoinChannel() { - rgb.YPrintf("[%s] Joining #%s...\n", timeStamp(), bb.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) + 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 { +func (bb *KardBot) ReadCredentials() error { // reads from the file credFile, err := ioutil.ReadFile(bb.PrivatePath) @@ -368,7 +185,7 @@ func (bb *kardBot) ReadCredentials() error { } // Makes the bot send a message to the chat channel. -func (bb *kardBot) Say(msg string) error { +func (bb *KardBot) Say(msg string) error { if "" == msg { return errors.New("BasicBot.Say: msg was empty.") } @@ -388,7 +205,7 @@ func (bb *kardBot) Say(msg string) error { // 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() { +func (bb *KardBot) Start() { err := bb.ReadCredentials() if nil != err { fmt.Println(err) @@ -412,38 +229,10 @@ func (bb *kardBot) Start() { } } -func timeStamp() string { - return TimeStamp(PSTFormat) +func TimeStamp() string { + return TimeStampFmt(PSTFormat) } -func TimeStamp(format string) string { +func TimeStampFmt(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/karaokards b/karaokards deleted file mode 100755 index 352f0f1..0000000 Binary files a/karaokards and /dev/null differ diff --git a/main.go b/main.go index 57ae988..969a97b 100755 --- a/main.go +++ b/main.go @@ -1,18 +1,53 @@ package main import ( + "encoding/json" "fmt" + "io/ioutil" "math/rand" "os" + "path/filepath" "time" + builtins "git.martyn.berlin/martyn/karaokards/internal/builtins" irc "git.martyn.berlin/martyn/karaokards/internal/irc" + rgb "github.com/foresthoffman/rgblog" ) +type customStringsStruct struct { + Strings []string `json:"strings,omitempty"` +} + +var selectablePrompts []string + +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 { + for _, val := range builtins.Karaokards { selectablePrompts = append(selectablePrompts, val) } for _, val := range readBonusStrings() { @@ -27,9 +62,9 @@ func main() { 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") + rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.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") + rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", irc.TimeStamp(), "/etc/twitch/oauth.json") os.Exit(1) } oauthPath = "/etc/twitch/oauth.json" @@ -46,6 +81,7 @@ func main() { Port: "6667", PrivatePath: oauthPath, Server: "irc.chat.twitch.tv", + Prompts: selectablePrompts, } myBot.Start() }