Compare commits
	
		
			2 commits
		
	
	
		
			4803122aad
			...
			8dbe61da21
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8dbe61da21 | |||
| 11c96d20d0 | 
					 3 changed files with 311 additions and 246 deletions
				
			
		| 
						 | 
				
			
			@ -2,8 +2,8 @@ package irc
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
| 
						 | 
				
			
			@ -16,17 +16,23 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	rgb "github.com/foresthoffman/rgblog"
 | 
			
		||||
	scribble "github.com/nanobox-io/golang-scribble"
 | 
			
		||||
	uuid "github.com/google/uuid"
 | 
			
		||||
	scribble "github.com/nanobox-io/golang-scribble"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const PSTFormat = "Jan 2 15:04:05 PST"
 | 
			
		||||
 | 
			
		||||
// Regex for parsing connection messages
 | 
			
		||||
//
 | 
			
		||||
// First matched group is our real username - twitch doesn't complain at using NICK command but doesn't honor it.
 | 
			
		||||
var ConnectRegex *regexp.Regexp = regexp.MustCompile(`^:tmi.twitch.tv 001 ([^ ]+) .*`)
 | 
			
		||||
 | 
			
		||||
// Regex for parsing PRIVMSG strings.
 | 
			
		||||
//
 | 
			
		||||
// First matched group is the user's name, second is the channel? and the third matched group is the content of the
 | 
			
		||||
// user's message.
 | 
			
		||||
var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #(\w+)(?: :(.*))?$`)
 | 
			
		||||
var DirectMsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) (\w+)(?: :(.*))?$`)
 | 
			
		||||
 | 
			
		||||
// Regex for parsing user commands, from already parsed PRIVMSG strings.
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			@ -54,16 +60,17 @@ type KardBot struct {
 | 
			
		|||
	Server      string
 | 
			
		||||
	startTime   time.Time
 | 
			
		||||
	Prompts     []string
 | 
			
		||||
	Database	scribble.Driver
 | 
			
		||||
	Database    scribble.Driver
 | 
			
		||||
	ChannelData map[string]ChannelData
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ChannelData struct {
 | 
			
		||||
	Name          string `json:"name"`
 | 
			
		||||
	AdminKey	  string `json:"value,omitempty"`
 | 
			
		||||
	Command       string `json:"customcommand,omitempty"`
 | 
			
		||||
	ExtraStrings  string `json:"extrastrings,omitempty"`
 | 
			
		||||
	JoinTime      time.Time `json:"jointime"`
 | 
			
		||||
	Name           string    `json:"name"`
 | 
			
		||||
	AdminKey       string    `json:"value,omitempty"`
 | 
			
		||||
	Command        string    `json:"customcommand,omitempty"`
 | 
			
		||||
	ExtraStrings   string    `json:"extrastrings,omitempty"`
 | 
			
		||||
	JoinTime       time.Time `json:"jointime"`
 | 
			
		||||
	ControlChannel bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it
 | 
			
		||||
| 
						 | 
				
			
			@ -118,9 +125,29 @@ func (bb *KardBot) HandleChat() error {
 | 
			
		|||
			bb.conn.Write([]byte("PONG :tmi.twitch.tv\r\n"))
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			matches := ConnectRegex.FindStringSubmatch(line)
 | 
			
		||||
			if nil != matches {
 | 
			
		||||
				realUserName := matches[1]
 | 
			
		||||
				if bb.ChannelData[realUserName].Name == "" {
 | 
			
		||||
					record := ChannelData{Name: realUserName, JoinTime: time.Now(), Command: "card", ControlChannel: true}
 | 
			
		||||
					bb.Database.Write("channelData", realUserName, record)
 | 
			
		||||
					bb.ChannelData[realUserName] = record
 | 
			
		||||
				}
 | 
			
		||||
				bb.JoinChannel(realUserName)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			matches = DirectMsgRegex.FindStringSubmatch(line)
 | 
			
		||||
			if nil != matches {
 | 
			
		||||
				userName := matches[1]
 | 
			
		||||
//				msgType := matches[2]
 | 
			
		||||
//				channel := matches[3]
 | 
			
		||||
				msg := matches[4]
 | 
			
		||||
				rgb.GPrintf("[%s] Direct message %s: %s\n", TimeStamp(), userName, msg)
 | 
			
		||||
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// handle a PRIVMSG message
 | 
			
		||||
			matches := MsgRegex.FindStringSubmatch(line)
 | 
			
		||||
			matches = MsgRegex.FindStringSubmatch(line)
 | 
			
		||||
			if nil != matches {
 | 
			
		||||
				userName := matches[1]
 | 
			
		||||
				msgType := matches[2]
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +169,16 @@ func (bb *KardBot) HandleChat() error {
 | 
			
		|||
							rgb.CPrintf("[%s] Card asked for by %s on %s' channel!\n", TimeStamp(), userName, channel)
 | 
			
		||||
 | 
			
		||||
							bb.Say("Your prompt is : "+bb.Prompts[rand.Intn(len(bb.Prompts))], channel)
 | 
			
		||||
						case "join":
 | 
			
		||||
							if bb.ChannelData[channel].ControlChannel {
 | 
			
		||||
								rgb.CPrintf("[%s] Join asked for by %s on %s' channel!\n", TimeStamp(), userName, channel)
 | 
			
		||||
								if bb.ChannelData[userName].Name == "" {
 | 
			
		||||
									record := ChannelData{Name: userName, JoinTime: time.Now(), Command: "card", ControlChannel: true}
 | 
			
		||||
									bb.Database.Write("channelData", userName, record)
 | 
			
		||||
									bb.ChannelData[userName] = record
 | 
			
		||||
								}
 | 
			
		||||
								bb.JoinChannel(userName)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						// channel-owner specific commands
 | 
			
		||||
| 
						 | 
				
			
			@ -155,13 +192,17 @@ func (bb *KardBot) HandleChat() error {
 | 
			
		|||
 | 
			
		||||
								bb.Disconnect()
 | 
			
		||||
								return nil
 | 
			
		||||
							case "wat":
 | 
			
		||||
							case "kcardadmin":
 | 
			
		||||
								magicCode := bb.readOrCreateChannelKey(channel)
 | 
			
		||||
								rgb.CPrintf(
 | 
			
		||||
									"[%s] Magic code is %s - https://karaokards.ing.martyn.berlin/admin/%s/%s\n",
 | 
			
		||||
									TimeStamp(),
 | 
			
		||||
									magicCode, userName, magicCode,
 | 
			
		||||
								)
 | 
			
		||||
								err := bb.Msg("Welcome to Karaokards, your admin panel is https://karaokards.ing.martyn.berlin/admin/"+userName+"/"+magicCode, userName)
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									rgb.RPrintf("[%s] ERROR %s\n",err)
 | 
			
		||||
								}
 | 
			
		||||
								bb.Say("Ack.")
 | 
			
		||||
							default:
 | 
			
		||||
								// do nothing
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +226,6 @@ func (bb *KardBot) Login() {
 | 
			
		|||
	bb.conn.Write([]byte("NICK " + bb.Name + "\r\n"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func (bb *KardBot) LeaveChannel(channels ...string) {
 | 
			
		||||
	for _, channel := range channels {
 | 
			
		||||
		rgb.YPrintf("[%s] Leaving #%s...\n", TimeStamp(), channel)
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +267,31 @@ func (bb *KardBot) ReadCredentials() error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (bb *KardBot) Msg(msg string, users ...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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(users) == 0 {
 | 
			
		||||
		return errors.New("BasicBot.Say: users was empty.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rgb.YPrintf("[%s] sending %s to users %v as @%s!\n", TimeStamp(), msg, users, bb.Name)
 | 
			
		||||
	for _, channel := range users {
 | 
			
		||||
		_, err := bb.conn.Write([]byte(fmt.Sprintf("PRIVMSG %s :%s\r\n", channel, msg)))
 | 
			
		||||
		rgb.YPrintf("[%s] PRIVMSG %s :%s\r\n", TimeStamp(), channel, msg)
 | 
			
		||||
		if nil != err {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Makes the bot send a message to the chat channel.
 | 
			
		||||
func (bb *KardBot) Say(msg string, channels ...string) error {
 | 
			
		||||
	if "" == msg {
 | 
			
		||||
| 
						 | 
				
			
			@ -263,7 +328,7 @@ func (bb *KardBot) Start() {
 | 
			
		|||
		fmt.Println("Aborting!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	err = bb.readChannelData()
 | 
			
		||||
	if nil != err {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
| 
						 | 
				
			
			@ -275,7 +340,7 @@ func (bb *KardBot) Start() {
 | 
			
		|||
		bb.Connect()
 | 
			
		||||
		bb.Login()
 | 
			
		||||
		if len(bb.ChannelData) > 0 {
 | 
			
		||||
			for channelName := range(bb.ChannelData) {
 | 
			
		||||
			for channelName := range bb.ChannelData {
 | 
			
		||||
				bb.JoinChannel(channelName)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
| 
						 | 
				
			
			@ -304,20 +369,20 @@ func (bb *KardBot) readChannelData() error {
 | 
			
		|||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		bb.ChannelData = make(map[string]ChannelData)
 | 
			
		||||
		bb.ChannelData[bb.Channel] = record;
 | 
			
		||||
		bb.ChannelData[bb.Channel] = record
 | 
			
		||||
	} else {
 | 
			
		||||
		bb.ChannelData = make(map[string]ChannelData)
 | 
			
		||||
	}
 | 
			
		||||
	for _, data := range records {
 | 
			
		||||
		record := ChannelData{}
 | 
			
		||||
		err := json.Unmarshal([]byte(data), &record);
 | 
			
		||||
		err := json.Unmarshal([]byte(data), &record)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if record.Name != "" {
 | 
			
		||||
			if record.Command == "" {
 | 
			
		||||
				record.Command = "card"
 | 
			
		||||
				
 | 
			
		||||
 | 
			
		||||
				rgb.YPrintf("[%s] Rewriting data for #%s...\n", TimeStamp(), bb.Channel)
 | 
			
		||||
				if err := bb.Database.Write("channelData", record.Name, record); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
| 
						 | 
				
			
			@ -346,7 +411,7 @@ func (bb *KardBot) readOrCreateChannelKey(channel string) string {
 | 
			
		|||
	var record ChannelData
 | 
			
		||||
	if record, ok := bb.ChannelData[channel]; !ok {
 | 
			
		||||
		rgb.YPrintf("[%s] No channel data for #%s exists, creating\n", TimeStamp(), channel)
 | 
			
		||||
		err = bb.Database.Read("channelData", channel, &record);
 | 
			
		||||
		err = bb.Database.Read("channelData", channel, &record)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			bb.ChannelData[channel] = record
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -378,4 +443,4 @@ func TimeStamp() string {
 | 
			
		|||
 | 
			
		||||
func TimeStampFmt(format string) string {
 | 
			
		||||
	return time.Now().Format(format)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -91,40 +91,40 @@ func AdminHandler(response http.ResponseWriter, request *http.Request) {
 | 
			
		|||
	if vars["key"] != ircBot.ChannelData[vars["channel"]].AdminKey {
 | 
			
		||||
		UnauthorizedHandler(response, request)
 | 
			
		||||
		return
 | 
			
		||||
	}	
 | 
			
		||||
	}
 | 
			
		||||
	type TemplateData struct {
 | 
			
		||||
		Channel       string
 | 
			
		||||
		Command       string
 | 
			
		||||
		ExtraStrings  string
 | 
			
		||||
		SinceTime     time.Time
 | 
			
		||||
		Leaving       bool
 | 
			
		||||
		Channel      string
 | 
			
		||||
		Command      string
 | 
			
		||||
		ExtraStrings string
 | 
			
		||||
		SinceTime    time.Time
 | 
			
		||||
		Leaving      bool
 | 
			
		||||
	}
 | 
			
		||||
	channelData := ircBot.ChannelData[vars["channel"]]
 | 
			
		||||
	var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, false}
 | 
			
		||||
 | 
			
		||||
	if request.Method == "POST" {
 | 
			
		||||
		request.ParseForm()
 | 
			
		||||
		if strings.Join(request.PostForm["leave"],",") == "Leave twitch channel" {
 | 
			
		||||
		if strings.Join(request.PostForm["leave"], ",") == "Leave twitch channel" {
 | 
			
		||||
			td.Leaving = true
 | 
			
		||||
		} else if strings.Join(request.PostForm["reallyleave"],",") == "Really leave twitch channel" {
 | 
			
		||||
			delete(ircBot.ChannelData,vars["channel"])
 | 
			
		||||
		} else if strings.Join(request.PostForm["reallyleave"], ",") == "Really leave twitch channel" {
 | 
			
		||||
			delete(ircBot.ChannelData, vars["channel"])
 | 
			
		||||
			ircBot.Database.Delete("channelData", vars["channel"])
 | 
			
		||||
			ircBot.LeaveChannel(vars["channel"])
 | 
			
		||||
			LeaveHandler(response, request)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		sourceData := ircBot.ChannelData[vars["channel"]]
 | 
			
		||||
		if strings.Join(request.PostForm["Command"],",") != ""  {
 | 
			
		||||
			sourceData.Command = strings.Join(request.PostForm["Command"],",")
 | 
			
		||||
		if strings.Join(request.PostForm["Command"], ",") != "" {
 | 
			
		||||
			sourceData.Command = strings.Join(request.PostForm["Command"], ",")
 | 
			
		||||
			td.Command = sourceData.Command
 | 
			
		||||
			ircBot.ChannelData[vars["channel"]] = sourceData
 | 
			
		||||
		}
 | 
			
		||||
		if strings.Join(request.PostForm["ExtraStrings"],",") != sourceData.ExtraStrings {
 | 
			
		||||
			sourceData.ExtraStrings = strings.Join(request.PostForm["ExtraStrings"],",")
 | 
			
		||||
		if strings.Join(request.PostForm["ExtraStrings"], ",") != sourceData.ExtraStrings {
 | 
			
		||||
			sourceData.ExtraStrings = strings.Join(request.PostForm["ExtraStrings"], ",")
 | 
			
		||||
			td.ExtraStrings = sourceData.ExtraStrings
 | 
			
		||||
			ircBot.ChannelData[vars["channel"]] = sourceData
 | 
			
		||||
		}
 | 
			
		||||
		ircBot.Database.Write("channelData", vars["channel"], sourceData);
 | 
			
		||||
		ircBot.Database.Write("channelData", vars["channel"], sourceData)
 | 
			
		||||
	}
 | 
			
		||||
	tmpl := template.Must(template.ParseFiles("web/admin.html"))
 | 
			
		||||
	tmpl.Execute(response, td)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										428
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										428
									
								
								main.go
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,214 +1,214 @@
 | 
			
		|||
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()
 | 
			
		||||
}
 | 
			
		||||
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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue