Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
This commit is contained in:
Martyn 2020-02-22 14:08:01 +01:00
parent 4803122aad
commit 11c96d20d0
3 changed files with 244 additions and 244 deletions

View File

@ -2,8 +2,8 @@ package irc
import ( import (
"bufio" "bufio"
"encoding/json"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -16,8 +16,8 @@ import (
"time" "time"
rgb "github.com/foresthoffman/rgblog" rgb "github.com/foresthoffman/rgblog"
scribble "github.com/nanobox-io/golang-scribble"
uuid "github.com/google/uuid" uuid "github.com/google/uuid"
scribble "github.com/nanobox-io/golang-scribble"
) )
const PSTFormat = "Jan 2 15:04:05 PST" const PSTFormat = "Jan 2 15:04:05 PST"
@ -54,16 +54,17 @@ type KardBot struct {
Server string Server string
startTime time.Time startTime time.Time
Prompts []string Prompts []string
Database scribble.Driver Database scribble.Driver
ChannelData map[string]ChannelData ChannelData map[string]ChannelData
} }
type ChannelData struct { type ChannelData struct {
Name string `json:"name"` Name string `json:"name"`
AdminKey string `json:"value,omitempty"` AdminKey string `json:"value,omitempty"`
Command string `json:"customcommand,omitempty"` Command string `json:"customcommand,omitempty"`
ExtraStrings string `json:"extrastrings,omitempty"` ExtraStrings string `json:"extrastrings,omitempty"`
JoinTime time.Time `json:"jointime"` 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 // Connects the bot to the Twitch IRC server. The bot will continue to try to connect until it
@ -185,7 +186,6 @@ func (bb *KardBot) Login() {
bb.conn.Write([]byte("NICK " + bb.Name + "\r\n")) bb.conn.Write([]byte("NICK " + bb.Name + "\r\n"))
} }
func (bb *KardBot) LeaveChannel(channels ...string) { func (bb *KardBot) LeaveChannel(channels ...string) {
for _, channel := range channels { for _, channel := range channels {
rgb.YPrintf("[%s] Leaving #%s...\n", TimeStamp(), channel) rgb.YPrintf("[%s] Leaving #%s...\n", TimeStamp(), channel)
@ -263,7 +263,7 @@ func (bb *KardBot) Start() {
fmt.Println("Aborting!") fmt.Println("Aborting!")
return return
} }
err = bb.readChannelData() err = bb.readChannelData()
if nil != err { if nil != err {
fmt.Println(err) fmt.Println(err)
@ -275,7 +275,7 @@ func (bb *KardBot) Start() {
bb.Connect() bb.Connect()
bb.Login() bb.Login()
if len(bb.ChannelData) > 0 { if len(bb.ChannelData) > 0 {
for channelName := range(bb.ChannelData) { for channelName := range bb.ChannelData {
bb.JoinChannel(channelName) bb.JoinChannel(channelName)
} }
} else { } else {
@ -304,20 +304,20 @@ func (bb *KardBot) readChannelData() error {
return err return err
} }
bb.ChannelData = make(map[string]ChannelData) bb.ChannelData = make(map[string]ChannelData)
bb.ChannelData[bb.Channel] = record; bb.ChannelData[bb.Channel] = record
} else { } else {
bb.ChannelData = make(map[string]ChannelData) bb.ChannelData = make(map[string]ChannelData)
} }
for _, data := range records { for _, data := range records {
record := ChannelData{} record := ChannelData{}
err := json.Unmarshal([]byte(data), &record); err := json.Unmarshal([]byte(data), &record)
if err != nil { if err != nil {
return err return err
} }
if record.Name != "" { if record.Name != "" {
if record.Command == "" { if record.Command == "" {
record.Command = "card" record.Command = "card"
rgb.YPrintf("[%s] Rewriting data for #%s...\n", TimeStamp(), bb.Channel) rgb.YPrintf("[%s] Rewriting data for #%s...\n", TimeStamp(), bb.Channel)
if err := bb.Database.Write("channelData", record.Name, record); err != nil { if err := bb.Database.Write("channelData", record.Name, record); err != nil {
return err return err
@ -346,7 +346,7 @@ func (bb *KardBot) readOrCreateChannelKey(channel string) string {
var record ChannelData var record ChannelData
if record, ok := bb.ChannelData[channel]; !ok { if record, ok := bb.ChannelData[channel]; !ok {
rgb.YPrintf("[%s] No channel data for #%s exists, creating\n", TimeStamp(), channel) 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 { if err == nil {
bb.ChannelData[channel] = record bb.ChannelData[channel] = record
} }
@ -378,4 +378,4 @@ func TimeStamp() string {
func TimeStampFmt(format string) string { func TimeStampFmt(format string) string {
return time.Now().Format(format) return time.Now().Format(format)
} }

View File

@ -91,40 +91,40 @@ func AdminHandler(response http.ResponseWriter, request *http.Request) {
if vars["key"] != ircBot.ChannelData[vars["channel"]].AdminKey { if vars["key"] != ircBot.ChannelData[vars["channel"]].AdminKey {
UnauthorizedHandler(response, request) UnauthorizedHandler(response, request)
return return
} }
type TemplateData struct { type TemplateData struct {
Channel string Channel string
Command string Command string
ExtraStrings string ExtraStrings string
SinceTime time.Time SinceTime time.Time
Leaving bool Leaving bool
} }
channelData := ircBot.ChannelData[vars["channel"]] channelData := ircBot.ChannelData[vars["channel"]]
var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, false} var td = TemplateData{channelData.Name, channelData.Command, channelData.ExtraStrings, channelData.JoinTime, false}
if request.Method == "POST" { if request.Method == "POST" {
request.ParseForm() request.ParseForm()
if strings.Join(request.PostForm["leave"],",") == "Leave twitch channel" { if strings.Join(request.PostForm["leave"], ",") == "Leave twitch channel" {
td.Leaving = true td.Leaving = true
} else if strings.Join(request.PostForm["reallyleave"],",") == "Really leave twitch channel" { } else if strings.Join(request.PostForm["reallyleave"], ",") == "Really leave twitch channel" {
delete(ircBot.ChannelData,vars["channel"]) delete(ircBot.ChannelData, vars["channel"])
ircBot.Database.Delete("channelData", vars["channel"]) ircBot.Database.Delete("channelData", vars["channel"])
ircBot.LeaveChannel(vars["channel"]) ircBot.LeaveChannel(vars["channel"])
LeaveHandler(response, request) LeaveHandler(response, request)
return return
} }
sourceData := ircBot.ChannelData[vars["channel"]] sourceData := ircBot.ChannelData[vars["channel"]]
if strings.Join(request.PostForm["Command"],",") != "" { if strings.Join(request.PostForm["Command"], ",") != "" {
sourceData.Command = strings.Join(request.PostForm["Command"],",") sourceData.Command = strings.Join(request.PostForm["Command"], ",")
td.Command = sourceData.Command td.Command = sourceData.Command
ircBot.ChannelData[vars["channel"]] = sourceData ircBot.ChannelData[vars["channel"]] = sourceData
} }
if strings.Join(request.PostForm["ExtraStrings"],",") != sourceData.ExtraStrings { if strings.Join(request.PostForm["ExtraStrings"], ",") != sourceData.ExtraStrings {
sourceData.ExtraStrings = strings.Join(request.PostForm["ExtraStrings"],",") sourceData.ExtraStrings = strings.Join(request.PostForm["ExtraStrings"], ",")
td.ExtraStrings = sourceData.ExtraStrings td.ExtraStrings = sourceData.ExtraStrings
ircBot.ChannelData[vars["channel"]] = sourceData 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 := template.Must(template.ParseFiles("web/admin.html"))
tmpl.Execute(response, td) tmpl.Execute(response, td)

428
main.go
View File

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