package main import ( "encoding/json" "io/ioutil" "math/rand" "os" "path/filepath" "time" builtins "git.martyn.berlin/martyn/karaokards/internal/builtins" irc "git.martyn.berlin/martyn/karaokards/internal/irc" webserver "git.martyn.berlin/martyn/karaokards/internal/webserver" rgb "github.com/foresthoffman/rgblog" scribble "github.com/nanobox-io/golang-scribble" ) type configStruct struct { InitialChannels []string `json:"channels"` OAuthPath string `json:"oauthpath,omitempty"` StringPath string `json:"authpath,omitempty"` DataPath string `json:"datapath,omitempty"` } type customStringsStruct struct { Strings []string `json:"strings,omitempty"` } var selectablePrompts []string var customStrings customStringsStruct var config configStruct func readConfig() { var data []byte var err error configFile := "" if os.Getenv("KARAOKARDS_CONFIGFILE") != "" { if _, err := os.Stat(os.Getenv("KARAOKARDS_CONFIGFILE")); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, KARAOKARDS_CONFIGFILE env var set and '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("KARAOKARDS_CONFIGFILE")) os.Exit(1) } configFile = os.Getenv("KARAOKARDS_CONFIGFILE") } else { ex, err := os.Executable() if err != nil { rgb.YPrintf("[%s] Warning, KARAOKARDS_CONFIGFILE env var unset and cannot find executable!\n", irc.TimeStamp()) } exPath := filepath.Dir(ex) if _, err := os.Stat(exPath + "/config.json"); os.IsNotExist(err) { rgb.YPrintf("[%s] Warning, KARAOKARDS_CONFIGFILE env var unset and `config.json` not alongside executable!\n", irc.TimeStamp()) if _, err := os.Stat("/etc/karaokards/config.json"); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, KARAOKARDS_CONFIGFILE env var unset and neither '%s' nor '%s' exist!\n", irc.TimeStamp(), exPath+"/config.json", "/etc/karaokards/config.json") os.Exit(1) } else { configFile = "/etc/karaokards/config.json" } } else { configFile = exPath + "/config.json" } } data, err = ioutil.ReadFile(configFile) if err != nil { rgb.RPrintf("[%s] Could not read `%s`. File reading error: %s\n", irc.TimeStamp(), configFile, err) os.Exit(1) } err = json.Unmarshal(data, &customStrings) if err != nil { rgb.RPrintf("[%s] Could not unmarshal `%s`. Unmarshal error: %s\n", irc.TimeStamp(), configFile, err) os.Exit(1) } rgb.YPrintf("[%s] Read config file from `%s`\n", irc.TimeStamp(), configFile) return } //openDatabase "database" in this sense being a scribble db func openDatabase() *scribble.Driver { dataPath := "" if config.DataPath == "" { if os.Getenv("KARAOKARDS_DATA_FOLDER") != "" { if _, err := os.Stat(os.Getenv("KARAOKARDS_DATA_FOLDER")); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, KARAOKARDS_DATA_FOLDER env var set and '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("KARAOKARDS_DATA_FOLDER")) os.Exit(1) } dataPath = os.Getenv("KARAOKARDS_DATA_FOLDER") } else { ex, err := os.Executable() if err != nil { rgb.RPrintf("[%s] Error, KARAOKARDS_DATA_FOLDER env var unset and cannot find executable!\n", irc.TimeStamp()) os.Exit(1) } exPath := filepath.Dir(ex) if _, err := os.Stat(exPath + "/data"); os.IsNotExist(err) { rgb.YPrintf("[%s] Warning %s doesn't exist, trying to create it.\n", irc.TimeStamp(), exPath+"/data") err = os.Mkdir(exPath+"/data", 0770) if err != nil { rgb.RPrintf("[%s] Error cannot create %s: %s!\n", irc.TimeStamp(), exPath+"/data", err) os.Exit(1) } } dataPath = exPath + "/data" } } else { if _, err := os.Stat(config.OAuthPath); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, config-specified path '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("KARAOKARDS_DATA_FOLDER")) os.Exit(1) } dataPath = config.DataPath } db, err := scribble.New(dataPath, nil) if err != nil { rgb.RPrintf("[%s] Error opening database in '%s' : %s\n", irc.TimeStamp(), dataPath, err) os.Exit(1) } return db } func readBonusStrings() []string { var data []byte var err error if config.StringPath == "" { ex, err := os.Executable() if err != nil { rgb.YPrintf("[%s] Could not read `strings.json`, will only have builtin prompts. File reading error: %s\n", irc.TimeStamp(), err) return []string{} } exPath := filepath.Dir(ex) data, err = ioutil.ReadFile(exPath + "/strings.json") if err != nil { rgb.YPrintf("[%s] Could not read `strings.json`, will only have builtin prompts. File reading error: %s\n", irc.TimeStamp(), err) return []string{} } } else { data, err = ioutil.ReadFile(config.StringPath) if err != nil { rgb.YPrintf("[%s] Could not read `strings.json`, will only have builtin prompts. File reading error: %s\n", irc.TimeStamp(), err) return []string{} } } err = json.Unmarshal(data, &customStrings) if err != nil { rgb.YPrintf("[%s] Could not unmarshal `strings.json`, will only have builtin prompts. Unmarshal error: %s\n", irc.TimeStamp(), err) return []string{} } rgb.YPrintf("[%s] Read %d prompts from `strings.json`\n", irc.TimeStamp(), len(customStrings.Strings)) return customStrings.Strings } var buildDate string func main() { rgb.YPrintf("[%s] starting karaokard bot build %s\n", irc.TimeStamp(), buildDate) readConfig() rand.Seed(time.Now().UnixNano()) for _, val := range builtins.Karaokards { selectablePrompts = append(selectablePrompts, val) } for _, val := range readBonusStrings() { selectablePrompts = append(selectablePrompts, val) } persistentData := openDatabase() var dbGlobalPrompts []string if err := persistentData.Read("prompts", "global", &dbGlobalPrompts); err != nil { persistentData.Write("prompts", "common", dbGlobalPrompts) } selectablePrompts := append(selectablePrompts, dbGlobalPrompts...) rgb.YPrintf("[%s] %d prompts available.\n", irc.TimeStamp(), len(selectablePrompts)) oauthPath := "" if config.OAuthPath == "" { if os.Getenv("TWITCH_OAUTH_JSON") != "" { if _, err := os.Stat(os.Getenv("TWITCH_OAUTH_JSON")); os.IsNotExist(err) { 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", 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", irc.TimeStamp(), "/etc/twitch/oauth.json") os.Exit(1) } oauthPath = "/etc/twitch/oauth.json" } else { oauthPath = os.Getenv("HOME") + "/.twitch/oauth.json" } } } else { if _, err := os.Stat(config.OAuthPath); os.IsNotExist(err) { rgb.YPrintf("[%s] Error config-specified oauth file %s doesn't exist, bailing!\n", irc.TimeStamp(), config.OAuthPath) os.Exit(1) } oauthPath = config.OAuthPath } // Replace the channel name, bot name, and the path to the private directory with your respective // values. myBot := irc.KardBot{ Channel: "imartynontwitch", MsgRate: time.Duration(20/30) * time.Millisecond, Name: "Karaokards", Port: "6667", PrivatePath: oauthPath, Server: "irc.chat.twitch.tv", Prompts: selectablePrompts, Database: *persistentData, } go func() { rgb.YPrintf("[%s] Starting webserver on port %s\n", irc.TimeStamp(), "5353") webserver.HandleHTTP(&myBot) }() myBot.Start() }