Compare commits

..

2 Commits

Author SHA1 Message Date
Martyn 8dbe61da21 [not useful] IRC whisper code and join command
continuous-integration/drone/tag Build was killed Details
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-23 15:32:30 +01:00
Martyn 11c96d20d0 go fmt
Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
2020-02-22 14:08:01 +01:00
3 changed files with 311 additions and 246 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,17 +16,23 @@ 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"
// 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. // 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 // 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. // user's message.
var MsgRegex *regexp.Regexp = regexp.MustCompile(`^:(\w+)!\w+@\w+\.tmi\.twitch\.tv (PRIVMSG) #(\w+)(?: :(.*))?$`) 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. // Regex for parsing user commands, from already parsed PRIVMSG strings.
// //
@ -54,16 +60,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
@ -118,9 +125,29 @@ func (bb *KardBot) HandleChat() error {
bb.conn.Write([]byte("PONG :tmi.twitch.tv\r\n")) bb.conn.Write([]byte("PONG :tmi.twitch.tv\r\n"))
continue continue
} else { } 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 // handle a PRIVMSG message
matches := MsgRegex.FindStringSubmatch(line) matches = MsgRegex.FindStringSubmatch(line)
if nil != matches { if nil != matches {
userName := matches[1] userName := matches[1]
msgType := matches[2] 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) 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) 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 // channel-owner specific commands
@ -155,13 +192,17 @@ func (bb *KardBot) HandleChat() error {
bb.Disconnect() bb.Disconnect()
return nil return nil
case "wat": case "kcardadmin":
magicCode := bb.readOrCreateChannelKey(channel) magicCode := bb.readOrCreateChannelKey(channel)
rgb.CPrintf( rgb.CPrintf(
"[%s] Magic code is %s - https://karaokards.ing.martyn.berlin/admin/%s/%s\n", "[%s] Magic code is %s - https://karaokards.ing.martyn.berlin/admin/%s/%s\n",
TimeStamp(), TimeStamp(),
magicCode, userName, magicCode, 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.") bb.Say("Ack.")
default: default:
// do nothing // do nothing
@ -185,7 +226,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)
@ -227,6 +267,31 @@ func (bb *KardBot) ReadCredentials() error {
return nil 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. // Makes the bot send a message to the chat channel.
func (bb *KardBot) Say(msg string, channels ...string) error { func (bb *KardBot) Say(msg string, channels ...string) error {
if "" == msg { if "" == msg {
@ -275,7 +340,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,13 +369,13 @@ 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
} }
@ -346,7 +411,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
} }

View File

@ -93,38 +93,38 @@ func AdminHandler(response http.ResponseWriter, request *http.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)

View File

@ -51,7 +51,7 @@ func readConfig() {
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"
@ -92,10 +92,10 @@ func openDatabase() *scribble.Driver {
} }
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)
} }
} }