Refactored to a package
continuous-integration/drone/push Build is passing Details

Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
This commit is contained in:
Martyn 2020-02-13 16:39:57 +01:00
parent 23a5cdc98c
commit 3f358aa1fa
5 changed files with 244 additions and 237 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
karaokards

181
internal/builtins/kards.go Normal file
View File

@ -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, Im or Ive",
"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",
"Im 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",
"Ive seen them in real life",
"A beautiful love story",
"OK, this is just ridiculous",
"Artist begins with B",
"Artist begins with D",
"Theyre dead :(",
"Artist begins with L",
"Amazing hair",
"Artist begins with J"}

View File

@ -10,8 +10,6 @@ import (
"math/rand" "math/rand"
"net" "net"
"net/textproto" "net/textproto"
"os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -19,188 +17,6 @@ import (
rgb "github.com/foresthoffman/rgblog" 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, Im or Ive",
"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",
"Im 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",
"Ive seen them in real life",
"A beautiful love story",
"OK, this is just ridiculous",
"Artist begins with B",
"Artist begins with D",
"Theyre dead :(",
"Artist begins with L",
"Amazing hair",
"Artist begins with J"}
var selectablePrompts []string
const PSTFormat = "Jan 2 15:04:05 PST" const PSTFormat = "Jan 2 15:04:05 PST"
// Regex for parsing PRIVMSG strings. // Regex for parsing PRIVMSG strings.
@ -234,36 +50,37 @@ type KardBot struct {
PrivatePath string PrivatePath string
Server string Server string
startTime time.Time startTime time.Time
Prompts []string
} }
// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it // Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it
// succeeds or is forcefully shutdown. // succeeds or is forcefully shutdown.
func (bb *kardBot) Connect() { func (bb *KardBot) Connect() {
var err error 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 // makes connection to Twitch IRC server
bb.conn, err = net.Dial("tcp", bb.Server+":"+bb.Port) bb.conn, err = net.Dial("tcp", bb.Server+":"+bb.Port)
if nil != err { 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() bb.Connect()
return 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() bb.startTime = time.Now()
} }
// Officially disconnects the bot from the Twitch IRC server. // Officially disconnects the bot from the Twitch IRC server.
func (bb *kardBot) Disconnect() { func (bb *KardBot) Disconnect() {
bb.conn.Close() bb.conn.Close()
upTime := time.Now().Sub(bb.startTime).Seconds() 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 // 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. // continues until it gets disconnected, told to shutdown, or forcefully shutdown.
func (bb *kardBot) HandleChat() error { func (bb *KardBot) HandleChat() error {
rgb.YPrintf("[%s] Watching #%s...\n", timeStamp(), bb.Channel) rgb.YPrintf("[%s] Watching #%s...\n", TimeStamp(), bb.Channel)
// reads from connection // reads from connection
tp := textproto.NewReader(bufio.NewReader(bb.conn)) tp := textproto.NewReader(bufio.NewReader(bb.conn))
@ -280,7 +97,7 @@ func (bb *kardBot) HandleChat() error {
} }
// logs the response from the IRC server // 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 { if "PING :tmi.twitch.tv" == line {
@ -298,7 +115,7 @@ func (bb *kardBot) HandleChat() error {
switch msgType { switch msgType {
case "PRIVMSG": case "PRIVMSG":
msg := matches[3] 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 // parse commands from user message
cmdMatches := CmdRegex.FindStringSubmatch(msg) cmdMatches := CmdRegex.FindStringSubmatch(msg)
@ -307,9 +124,9 @@ func (bb *kardBot) HandleChat() error {
switch cmd { switch cmd {
case "card": 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 // channel-owner specific commands
@ -318,7 +135,7 @@ func (bb *kardBot) HandleChat() error {
case "tbdown": case "tbdown":
rgb.CPrintf( rgb.CPrintf(
"[%s] Shutdown command received. Shutting down now...\n", "[%s] Shutdown command received. Shutting down now...\n",
timeStamp(), TimeStamp(),
) )
bb.Disconnect() bb.Disconnect()
@ -338,17 +155,17 @@ func (bb *kardBot) HandleChat() error {
} }
// Makes the bot join its pre-specified channel. // Makes the bot join its pre-specified channel.
func (bb *kardBot) JoinChannel() { func (bb *KardBot) JoinChannel() {
rgb.YPrintf("[%s] Joining #%s...\n", timeStamp(), bb.Channel) rgb.YPrintf("[%s] Joining #%s...\n", TimeStamp(), bb.Channel)
bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n")) bb.conn.Write([]byte("PASS " + bb.Credentials.Password + "\r\n"))
bb.conn.Write([]byte("NICK " + bb.Name + "\r\n")) bb.conn.Write([]byte("NICK " + bb.Name + "\r\n"))
bb.conn.Write([]byte("JOIN #" + bb.Channel + "\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. // 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 // reads from the file
credFile, err := ioutil.ReadFile(bb.PrivatePath) 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. // 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 { if "" == msg {
return errors.New("BasicBot.Say: msg was empty.") 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 // 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 // pre-specified channel, and then handle the chat. It will attempt to reconnect until it is told to
// shut down, or is forcefully shutdown. // shut down, or is forcefully shutdown.
func (bb *kardBot) Start() { func (bb *KardBot) Start() {
err := bb.ReadCredentials() err := bb.ReadCredentials()
if nil != err { if nil != err {
fmt.Println(err) fmt.Println(err)
@ -412,38 +229,10 @@ func (bb *kardBot) Start() {
} }
} }
func timeStamp() string { func TimeStamp() string {
return TimeStamp(PSTFormat) return TimeStampFmt(PSTFormat)
} }
func TimeStamp(format string) string { func TimeStampFmt(format string) string {
return time.Now().Format(format) 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
}

Binary file not shown.

42
main.go
View File

@ -1,18 +1,53 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/rand" "math/rand"
"os" "os"
"path/filepath"
"time" "time"
builtins "git.martyn.berlin/martyn/karaokards/internal/builtins"
irc "git.martyn.berlin/martyn/karaokards/internal/irc" 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() { func main() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
for _, val := range karaokards { for _, val := range builtins.Karaokards {
selectablePrompts = append(selectablePrompts, val) selectablePrompts = append(selectablePrompts, val)
} }
for _, val := range readBonusStrings() { for _, val := range readBonusStrings() {
@ -27,9 +62,9 @@ func main() {
oauthPath = os.Getenv("TWITCH_OAUTH_JSON") oauthPath = os.Getenv("TWITCH_OAUTH_JSON")
} else { } else {
if _, err := os.Stat(os.Getenv("HOME") + "/.twitch/oauth.json"); os.IsNotExist(err) { 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) { 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) os.Exit(1)
} }
oauthPath = "/etc/twitch/oauth.json" oauthPath = "/etc/twitch/oauth.json"
@ -46,6 +81,7 @@ func main() {
Port: "6667", Port: "6667",
PrivatePath: oauthPath, PrivatePath: oauthPath,
Server: "irc.chat.twitch.tv", Server: "irc.chat.twitch.tv",
Prompts: selectablePrompts,
} }
myBot.Start() myBot.Start()
} }