|
|
@ -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.
|
|
|
|
//
|
|
|
|
//
|
|
|
@ -64,6 +70,7 @@ type ChannelData struct {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|