package main import ( "encoding/json" "fmt" "io/ioutil" "math/rand" "os" "path/filepath" "time" builtins "git.martyn.berlin/martyn/twitchsingstools/internal/builtins" data "git.martyn.berlin/martyn/twitchsingstools/internal/data" irc "git.martyn.berlin/martyn/twitchsingstools/internal/irc" webserver "git.martyn.berlin/martyn/twitchsingstools/internal/webserver" rgb "github.com/foresthoffman/rgblog" scribble "github.com/nanobox-io/golang-scribble" ) type customStringsStruct struct { Strings []string `json:"strings,omitempty"` } var selectablePrompts []string var customStrings customStringsStruct var config data.ConfigStruct func readConfig() { var data []byte var err error configFile := "" if os.Getenv("TSTOOLS_CONFIGFILE") != "" { if _, err := os.Stat(os.Getenv("TSTOOLS_CONFIGFILE")); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, TSTOOLS_CONFIGFILE env var set and '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("TSTOOLS_CONFIGFILE")) os.Exit(1) } configFile = os.Getenv("TSTOOLS_CONFIGFILE") } else { ex, err := os.Executable() if err != nil { rgb.YPrintf("[%s] Warning, TSTOOLS_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, TSTOOLS_CONFIGFILE env var unset and `config.json` not alongside executable!\n", irc.TimeStamp()) if _, err := os.Stat("/etc/tstools/config.json"); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, TSTOOLS_CONFIGFILE env var unset and neither '%s' nor '%s' exist!\n", irc.TimeStamp(), exPath+"/config.json", "/etc/tstools/config.json") os.Exit(1) } else { configFile = "/etc/tstools/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, &config) 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) rgb.YPrintf("[%s] config %v\n", irc.TimeStamp(), config) return } //openDatabase "database" in this sense being a scribble db func openDatabase() *scribble.Driver { dataPath := "" if config.DataPath == "" { if os.Getenv("TSTOOLS_DATA_FOLDER") != "" { if _, err := os.Stat(os.Getenv("TSTOOLS_DATA_FOLDER")); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, TSTOOLS_DATA_FOLDER env var set and '%s' doesn't exist!\n", irc.TimeStamp(), os.Getenv("TSTOOLS_DATA_FOLDER")) os.Exit(1) } dataPath = os.Getenv("TSTOOLS_DATA_FOLDER") } else { ex, err := os.Executable() if err != nil { rgb.RPrintf("[%s] Error, TSTOOLS_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.DataPath); os.IsNotExist(err) { rgb.RPrintf("[%s] Error, config-specified path '%s' doesn't exist!\n", irc.TimeStamp(), config.DataPath) 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 } var buildDate string func main() { rgb.YPrintf("[%s] starting twitchsingstools bot build %s\n", irc.TimeStamp(), buildDate) readConfig() rand.Seed(time.Now().UnixNano()) for _, val := range builtins.Karaokards { 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)) ircOauthPath := "" if config.IrcOAuthPath == "" { if os.Getenv("TSTOOLS_OAUTH_JSON") != "" { if _, err := os.Stat(os.Getenv("TSTOOLS_OAUTH_JSON")); os.IsNotExist(err) { os.Exit(1) } ircOauthPath = os.Getenv("TSTOOLS_OAUTH_JSON") } else { if _, err := os.Stat(os.Getenv("HOME") + "/.tstools/ircoauth.json"); os.IsNotExist(err) { rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.TimeStamp(), os.Getenv("HOME")+"/.tstools/ircoauth.json", "/etc/tstools/ircoauth.json") if _, err := os.Stat("/etc/tstools/ircoauth.json"); os.IsNotExist(err) { rgb.YPrintf("[%s] Error %s doesn't exist either, cannot connect to irc!\n", irc.TimeStamp(), "/etc/tstools/ircoauth.json") } else { ircOauthPath = "/etc/tstools/ircoauth.json" } } else { ircOauthPath = os.Getenv("HOME") + "/.tstools/ircoauth.json" } } } else { if _, err := os.Stat(config.IrcOAuthPath); os.IsNotExist(err) { rgb.YPrintf("[%s] Error config-specified oauth file %s doesn't exist, cannot connect to irc!\n", irc.TimeStamp(), config.IrcOAuthPath) } else { ircOauthPath = config.IrcOAuthPath } } appOauthPath := "" if config.AppOAuthPath == "" { if os.Getenv("TSTOOLS_OAUTH_JSON") != "" { if _, err := os.Stat(os.Getenv("TSTOOLS_OAUTH_JSON")); os.IsNotExist(err) { os.Exit(1) } appOauthPath = os.Getenv("TSTOOLS_OAUTH_JSON") } else { if _, err := os.Stat(os.Getenv("HOME") + "/.tstools/appoauth.json"); os.IsNotExist(err) { rgb.YPrintf("[%s] Warning %s doesn't exist, trying %s next!\n", irc.TimeStamp(), os.Getenv("HOME")+"/.tstools/appoauth.json", "/etc/tstools/appoauth.json") if _, err := os.Stat("/etc/tstools/appoauth.json"); os.IsNotExist(err) { rgb.YPrintf("[%s] Error %s doesn't exist either, bailing!\n", irc.TimeStamp(), "/etc/tstools/appoauth.json") os.Exit(1) } appOauthPath = "/etc/tstools/appoauth.json" } else { appOauthPath = os.Getenv("HOME") + "/.tstools/appoauth.json" } } } else { if _, err := os.Stat(config.AppOAuthPath); os.IsNotExist(err) { rgb.RPrintf("[%s] Error config-specified oauth file %s doesn't exist, bailing!\n", irc.TimeStamp(), config.AppOAuthPath) os.Exit(1) } appOauthPath = config.AppOAuthPath } rgb.YPrintf("[%s] Starting connection to redis...\n", irc.TimeStamp()) //TODO: unhardcode this if os.Getenv("TSTOOLS_REDIS_HOST") != "" { config.DatabaseSVC = os.Getenv("TSTOOLS_REDIS_HOST") } else { // assume localhost, which should fail. config.DatabaseSVC = "localhost" } var globalData data.GlobalData globalData.Config = config globalData.ConnectDatabase() defer globalData.Database.Close() rgb.GPrintf("[%s] Connected to \"redis\" %s\n", irc.TimeStamp(), "config.DatabaseSVC") err := globalData.ReadChannelData() if nil != err { fmt.Println(err) fmt.Println("Aborting!") os.Exit(1) } rgb.GPrintf("[%s] Read the channel data from \"redis\" successfully, now have %d records\n", irc.TimeStamp(), len(globalData.ChannelData)) // Replace the channel name, bot name, and the path to the private directory with your respective // values. var myBot irc.KardBot if ircOauthPath != "" { myBot = irc.KardBot{ Channel: "twitchsingstools", MsgRate: time.Duration(20/30) * time.Millisecond, Name: "twitchsingstools", Port: "6667", IrcPrivatePath: ircOauthPath, AppPrivatePath: appOauthPath, Server: "irc.chat.twitch.tv", Prompts: selectablePrompts, GlobalData: globalData, } } go func() { rgb.YPrintf("[%s] Starting webserver on port %s\n", irc.TimeStamp(), "5353") webserver.HandleHTTP(&myBot, &globalData) }() if ircOauthPath != "" { myBot.Start() } }