now using go-twitch-irc and simplified the bot a lot
This commit is contained in:
parent
dc3a4f721a
commit
bf56fc2c47
14 changed files with 205 additions and 595 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
|||
gempbotgo
|
||||
vendor/
|
||||
glide.lock
|
||||
.idea/
|
||||
config.json
|
||||
logs/
|
||||
|
|
1
Makefile
1
Makefile
|
@ -2,7 +2,6 @@ PACKAGES = $(shell go list ./... | grep -v /vendor/)
|
|||
|
||||
build:
|
||||
glide install
|
||||
glide update
|
||||
go build
|
||||
|
||||
install:
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gempir/gempbotgo/modules"
|
||||
"github.com/gempir/gempbotgo/twitch"
|
||||
"github.com/op/go-logging"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
admin string
|
||||
bot *twitch.Bot
|
||||
startTime time.Time
|
||||
log logging.Logger
|
||||
}
|
||||
|
||||
func NewHandler(admin string, bot *twitch.Bot, startTime time.Time, apiUser string, apiKey string, logger logging.Logger) Handler {
|
||||
logger = logger
|
||||
return Handler{
|
||||
admin: strings.ToLower(admin),
|
||||
bot: bot,
|
||||
startTime: startTime,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) HandleCommand(msg twitch.Message) error {
|
||||
switch msg.Command.Name {
|
||||
case "!status":
|
||||
h.handleStatusCommand(msg)
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) handleStatusCommand(msg twitch.Message) {
|
||||
if msg.Username != h.admin {
|
||||
return
|
||||
}
|
||||
uptime := formatDiff(diff(h.startTime, time.Now()))
|
||||
h.bot.Say(msg.Channel, h.admin+", uptime: "+uptime, modules.STATUS)
|
||||
}
|
||||
|
||||
func formatDiff(years, months, days, hours, mins, secs int) string {
|
||||
since := ""
|
||||
if years > 0 {
|
||||
switch years {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d year ", years)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d years ", years)
|
||||
break
|
||||
}
|
||||
}
|
||||
if months > 0 {
|
||||
switch months {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d month ", months)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d months ", months)
|
||||
break
|
||||
}
|
||||
}
|
||||
if days > 0 {
|
||||
switch days {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d day ", days)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d days ", days)
|
||||
break
|
||||
}
|
||||
}
|
||||
if hours > 0 {
|
||||
switch hours {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d hour ", hours)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d hours ", hours)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mins > 0 && days == 0 && months == 0 && years == 0 {
|
||||
switch mins {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d min ", mins)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d mins ", mins)
|
||||
break
|
||||
}
|
||||
}
|
||||
if secs > 0 && days == 0 && months == 0 && years == 0 && hours == 0 {
|
||||
switch secs {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d sec ", secs)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d secs ", secs)
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(since)
|
||||
}
|
||||
|
||||
func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
|
||||
if a.After(b) {
|
||||
a, b = b, a
|
||||
}
|
||||
y1, M1, d1 := a.Date()
|
||||
y2, M2, d2 := b.Date()
|
||||
|
||||
h1, m1, s1 := a.Clock()
|
||||
h2, m2, s2 := b.Clock()
|
||||
|
||||
year = int(y2 - y1)
|
||||
month = int(M2 - M1)
|
||||
day = int(d2 - d1)
|
||||
hour = int(h2 - h1)
|
||||
min = int(m2 - m1)
|
||||
sec = int(s2 - s1)
|
||||
|
||||
// Normalize negative values
|
||||
if sec < 0 {
|
||||
sec += 60
|
||||
min--
|
||||
}
|
||||
if min < 0 {
|
||||
min += 60
|
||||
hour--
|
||||
}
|
||||
if hour < 0 {
|
||||
hour += 24
|
||||
day--
|
||||
}
|
||||
if day < 0 {
|
||||
// days in month:
|
||||
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
|
||||
day += 32 - t.Day()
|
||||
month--
|
||||
}
|
||||
if month < 0 {
|
||||
month += 12
|
||||
year--
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"github.com/gempir/gempbotgo/modules"
|
||||
"gopkg.in/redis.v5"
|
||||
)
|
||||
|
||||
type UserConfig struct {
|
||||
rClient redis.Client
|
||||
}
|
||||
|
||||
func NewUserConfig(rClient redis.Client) UserConfig {
|
||||
return UserConfig{
|
||||
rClient: rClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (uCfg *UserConfig) IsEnabled(channel, key string) bool {
|
||||
if key == modules.STATUS {
|
||||
return true
|
||||
}
|
||||
|
||||
res, err := uCfg.rClient.HGet(channel+":config", key).Result()
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if res == "true" || res == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -2,16 +2,14 @@ package filelog
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gempir/gempbotgo/twitch"
|
||||
"os"
|
||||
"strings"
|
||||
"github.com/gempir/go-twitch-irc"
|
||||
)
|
||||
|
||||
func (l *Logger) LogMessageForChannel(msg twitch.Message) error {
|
||||
year := msg.Time.Year()
|
||||
month := msg.Time.Month()
|
||||
channel := strings.Replace(msg.Channel.Name, "#", "", 1)
|
||||
day := msg.Time.Day()
|
||||
func (l *Logger) LogMessageForChannel(channel string, user twitch.User, message twitch.Message) error {
|
||||
year := message.Time.Year()
|
||||
month := message.Time.Month()
|
||||
day := message.Time.Day()
|
||||
err := os.MkdirAll(fmt.Sprintf(l.logPath+"%s/%d/%s/%d", channel, year, month, day), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -24,7 +22,7 @@ func (l *Logger) LogMessageForChannel(msg twitch.Message) error {
|
|||
}
|
||||
defer file.Close()
|
||||
|
||||
contents := fmt.Sprintf("[%s] %s: %s\r\n", msg.Time.Format("2006-01-2 15:04:05"), msg.Username, msg.Text)
|
||||
contents := fmt.Sprintf("[%s] %s: %s\r\n", message.Time.Format("2006-01-2 15:04:05"), user.Username, message.Text)
|
||||
if _, err = file.WriteString(contents); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package filelog
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gempir/gempbotgo/twitch"
|
||||
"os"
|
||||
"strings"
|
||||
"github.com/gempir/go-twitch-irc"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
|
@ -17,15 +16,14 @@ func NewFileLogger(logPath string) Logger {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *Logger) LogMessageForUser(msg twitch.Message) error {
|
||||
year := msg.Time.Year()
|
||||
month := msg.Time.Month()
|
||||
channel := strings.Replace(msg.Channel.Name, "#", "", 1)
|
||||
func (l *Logger) LogMessageForUser(channel string, user twitch.User, message twitch.Message) error {
|
||||
year := message.Time.Year()
|
||||
month := message.Time.Month()
|
||||
err := os.MkdirAll(fmt.Sprintf(l.logPath+"%s/%d/%s/", channel, year, month), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := fmt.Sprintf(l.logPath+"%s/%d/%s/%s.txt", channel, year, month, msg.Username)
|
||||
filename := fmt.Sprintf(l.logPath+"%s/%d/%s/%s.txt", channel, year, month, user.Username)
|
||||
|
||||
file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
|
@ -33,7 +31,7 @@ func (l *Logger) LogMessageForUser(msg twitch.Message) error {
|
|||
}
|
||||
defer file.Close()
|
||||
|
||||
contents := fmt.Sprintf("[%s] %s: %s\r\n", msg.Time.Format("2006-01-2 15:04:05"), msg.Username, msg.Text)
|
||||
contents := fmt.Sprintf("[%s] %s: %s\r\n", message.Time.Format("2006-01-2 15:04:05"), user.Username, message.Text)
|
||||
if _, err = file.WriteString(contents); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
59
glide.lock
generated
Normal file
59
glide.lock
generated
Normal file
|
@ -0,0 +1,59 @@
|
|||
hash: 3d70255a398ee1bb9a350b617330de4610870fd94e2aabff1b6af6d7e979f86f
|
||||
updated: 2017-04-14T17:25:52.75741782+02:00
|
||||
imports:
|
||||
- name: github.com/CleverbotIO/go-cleverbot.io
|
||||
version: 2b334e3eb17cf9556f3d9dc1d42eddc7b2e37007
|
||||
- name: github.com/gempir/go-twitch-irc
|
||||
version: e2aaa1468dee36fd689f30f56ac0ce2654f45622
|
||||
- name: github.com/labstack/echo
|
||||
version: 948d607539e92f3974d8f24951c8975d7342e24a
|
||||
- name: github.com/labstack/gommon
|
||||
version: e8995fb26e646187d33cff439b18609cfba23088
|
||||
subpackages:
|
||||
- color
|
||||
- log
|
||||
- name: github.com/mattn/go-colorable
|
||||
version: acb9493f2794fd0f820de7a27a217dafbb1b65ea
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 57fdcb988a5c543893cc61bce354a6e24ab70022
|
||||
- name: github.com/op/go-logging
|
||||
version: b2cb9fa56473e98db8caba80237377e83fe44db5
|
||||
- name: github.com/stretchr/testify
|
||||
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
|
||||
subpackages:
|
||||
- assert
|
||||
- name: github.com/valyala/bytebufferpool
|
||||
version: e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7
|
||||
- name: github.com/valyala/fasttemplate
|
||||
version: dcecefd839c4193db0d35b88ec65b4c12d360ab0
|
||||
- name: golang.org/x/crypto
|
||||
version: 728b753d0135da6801d45a38e6f43ff55779c5c2
|
||||
subpackages:
|
||||
- acme
|
||||
- acme/autocert
|
||||
- name: golang.org/x/net
|
||||
version: a6577fac2d73be281a500b310739095313165611
|
||||
subpackages:
|
||||
- context
|
||||
- context/ctxhttp
|
||||
- name: golang.org/x/sys
|
||||
version: 99f16d856c9836c42d24e7ab64ea72916925fa97
|
||||
subpackages:
|
||||
- unix
|
||||
- name: gopkg.in/redis.v5
|
||||
version: a16aeec10ff407b1e7be6dd35797ccf5426ef0f0
|
||||
subpackages:
|
||||
- internal
|
||||
- internal/consistenthash
|
||||
- internal/hashtag
|
||||
- internal/pool
|
||||
- internal/proto
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
|
@ -9,3 +9,5 @@ import:
|
|||
- package: github.com/labstack/echo
|
||||
version: ^3.1.0-rc.1
|
||||
- package: github.com/CleverbotIO/go-cleverbot.io
|
||||
- package: github.com/gempir/go-twitch-irc
|
||||
version: ~0.2.0
|
||||
|
|
161
main.go
161
main.go
|
@ -10,10 +10,10 @@ import (
|
|||
"gopkg.in/redis.v5"
|
||||
|
||||
"github.com/gempir/gempbotgo/api"
|
||||
"github.com/gempir/gempbotgo/command"
|
||||
"github.com/gempir/gempbotgo/config"
|
||||
"github.com/gempir/gempbotgo/filelog"
|
||||
"github.com/gempir/gempbotgo/twitch"
|
||||
"github.com/gempir/go-twitch-irc"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,7 +37,6 @@ type sysConfig struct {
|
|||
|
||||
var (
|
||||
fileLogger filelog.Logger
|
||||
cmdHandler command.Handler
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -49,55 +48,51 @@ func main() {
|
|||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
apiServer := api.NewServer(cfg.APIPort, cfg.LogPath)
|
||||
go apiServer.Init()
|
||||
|
||||
rClient := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisAddress,
|
||||
Password: cfg.RedisPassword,
|
||||
DB: cfg.RedisDatabase,
|
||||
})
|
||||
|
||||
apiServer := api.NewServer(cfg.APIPort, cfg.LogPath)
|
||||
go apiServer.Init()
|
||||
|
||||
userConfig := config.NewUserConfig(*rClient)
|
||||
|
||||
bot := twitch.NewBot(cfg.IrcAddress, cfg.IrcUser, cfg.IrcToken, userConfig, *rClient, logger)
|
||||
go func() {
|
||||
err := bot.CreatePersistentConnection()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
twitchClient := twitch.NewClient(cfg.IrcUser, cfg.IrcToken)
|
||||
twitchClient.SetIrcAddress(cfg.IrcAddress)
|
||||
|
||||
fileLogger = filelog.NewFileLogger(cfg.LogPath)
|
||||
cmdHandler = command.NewHandler(cfg.Admin, &bot, startTime, cfg.CleverBotUser, cfg.CleverBotKey, logger)
|
||||
|
||||
for msg := range bot.Messages {
|
||||
val, _ := rClient.HGetAll("channels").Result()
|
||||
for channelStr := range val {
|
||||
fmt.Println("Joining " + channelStr)
|
||||
go twitchClient.Join(strings.TrimPrefix(channelStr, "#"))
|
||||
}
|
||||
|
||||
if msg.Type == twitch.PRIVMSG || msg.Type == twitch.CLEARCHAT {
|
||||
twitchClient.OnNewMessage(func(channel string, user twitch.User, message twitch.Message) {
|
||||
|
||||
if message.Type == twitch.PRIVMSG || message.Type == twitch.CLEARCHAT {
|
||||
go func() {
|
||||
err := fileLogger.LogMessageForUser(msg)
|
||||
err := fileLogger.LogMessageForUser(channel, user, message)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := fileLogger.LogMessageForChannel(msg)
|
||||
err := fileLogger.LogMessageForChannel(channel, user, message)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if msg.Command.IsCommand {
|
||||
go func() {
|
||||
err := cmdHandler.HandleCommand(msg)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
if user.Username == cfg.Admin && strings.HasPrefix(message.Text, "!status") {
|
||||
uptime := formatDiff(diff(startTime, time.Now()))
|
||||
twitchClient.Say(channel, cfg.Admin+", uptime: "+uptime)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Println(twitchClient.Connect())
|
||||
}
|
||||
|
||||
func initLogger() logging.Logger {
|
||||
|
@ -129,3 +124,111 @@ func unmarshalConfig(file []byte) (sysConfig, error) {
|
|||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func formatDiff(years, months, days, hours, mins, secs int) string {
|
||||
since := ""
|
||||
if years > 0 {
|
||||
switch years {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d year ", years)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d years ", years)
|
||||
break
|
||||
}
|
||||
}
|
||||
if months > 0 {
|
||||
switch months {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d month ", months)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d months ", months)
|
||||
break
|
||||
}
|
||||
}
|
||||
if days > 0 {
|
||||
switch days {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d day ", days)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d days ", days)
|
||||
break
|
||||
}
|
||||
}
|
||||
if hours > 0 {
|
||||
switch hours {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d hour ", hours)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d hours ", hours)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mins > 0 && days == 0 && months == 0 && years == 0 {
|
||||
switch mins {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d min ", mins)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d mins ", mins)
|
||||
break
|
||||
}
|
||||
}
|
||||
if secs > 0 && days == 0 && months == 0 && years == 0 && hours == 0 {
|
||||
switch secs {
|
||||
case 1:
|
||||
since += fmt.Sprintf("%d sec ", secs)
|
||||
break
|
||||
default:
|
||||
since += fmt.Sprintf("%d secs ", secs)
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(since)
|
||||
}
|
||||
|
||||
func diff(a, b time.Time) (year, month, day, hour, min, sec int) {
|
||||
if a.After(b) {
|
||||
a, b = b, a
|
||||
}
|
||||
y1, M1, d1 := a.Date()
|
||||
y2, M2, d2 := b.Date()
|
||||
|
||||
h1, m1, s1 := a.Clock()
|
||||
h2, m2, s2 := b.Clock()
|
||||
|
||||
year = int(y2 - y1)
|
||||
month = int(M2 - M1)
|
||||
day = int(d2 - d1)
|
||||
hour = int(h2 - h1)
|
||||
min = int(m2 - m1)
|
||||
sec = int(s2 - s1)
|
||||
|
||||
// Normalize negative values
|
||||
if sec < 0 {
|
||||
sec += 60
|
||||
min--
|
||||
}
|
||||
if min < 0 {
|
||||
min += 60
|
||||
hour--
|
||||
}
|
||||
if hour < 0 {
|
||||
hour += 24
|
||||
day--
|
||||
}
|
||||
if day < 0 {
|
||||
// days in month:
|
||||
t := time.Date(y1, M1, 32, 0, 0, 0, 0, time.UTC)
|
||||
day += 32 - t.Day()
|
||||
month--
|
||||
}
|
||||
if month < 0 {
|
||||
month += 12
|
||||
year--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package modules
|
||||
|
||||
const (
|
||||
STATUS = "STATUS"
|
||||
)
|
139
twitch/bot.go
139
twitch/bot.go
|
@ -1,139 +0,0 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/gempir/gempbotgo/config"
|
||||
"github.com/op/go-logging"
|
||||
"gopkg.in/redis.v5"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
logger logging.Logger
|
||||
)
|
||||
|
||||
type Bot struct {
|
||||
Messages chan Message
|
||||
ircAddress string
|
||||
ircUser string
|
||||
ircToken string
|
||||
userConfig config.UserConfig
|
||||
rClient redis.Client
|
||||
logger logging.Logger
|
||||
connection net.Conn
|
||||
}
|
||||
|
||||
func NewBot(ircAddress string, ircUser string, ircToken string, uCfg config.UserConfig, rClient redis.Client, loggerMain logging.Logger) Bot {
|
||||
channels := make(map[Channel]bool)
|
||||
channels[NewChannel(ircUser)] = true
|
||||
|
||||
logger = logger
|
||||
|
||||
return Bot{
|
||||
Messages: make(chan Message),
|
||||
ircAddress: ircAddress,
|
||||
ircUser: strings.ToLower(ircUser),
|
||||
ircToken: ircToken,
|
||||
userConfig: uCfg,
|
||||
rClient: rClient,
|
||||
logger: loggerMain,
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) Say(channel Channel, text string, responseType string) {
|
||||
if bot.userConfig.IsEnabled(channel.Name, responseType) {
|
||||
bot.send(fmt.Sprintf("PRIVMSG %s :%s", channel.Name, text))
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) CreatePersistentConnection() error {
|
||||
for {
|
||||
conn, err := net.Dial("tcp", bot.ircAddress)
|
||||
bot.connection = conn
|
||||
if err != nil {
|
||||
bot.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
bot.setupConnection()
|
||||
bot.joinDefault()
|
||||
|
||||
err = bot.readConnection(conn)
|
||||
if err != nil {
|
||||
bot.logger.Error("connection read error, redialing")
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bot *Bot) readConnection(conn net.Conn) error {
|
||||
reader := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(reader)
|
||||
for {
|
||||
line, err := tp.ReadLine()
|
||||
if err != nil {
|
||||
bot.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
messages := strings.Split(line, "\r\n")
|
||||
if len(messages) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, msg := range messages {
|
||||
bot.handleLine(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) setupConnection() {
|
||||
bot.send(fmt.Sprintf("PASS %s", bot.ircToken))
|
||||
bot.send(fmt.Sprintf("NICK %s", bot.ircUser))
|
||||
bot.send("CAP REQ :twitch.tv/tags")
|
||||
bot.send("CAP REQ :twitch.tv/commands")
|
||||
bot.send(fmt.Sprintf("JOIN %s", "#"+bot.ircUser))
|
||||
}
|
||||
|
||||
func (bot *Bot) send(line string) {
|
||||
fmt.Fprint(bot.connection, line+"\r\n")
|
||||
}
|
||||
|
||||
func (bot *Bot) handleLine(line string) {
|
||||
if strings.HasPrefix(line, "PING") {
|
||||
bot.send(fmt.Sprintf(strings.Replace(line, "PING", "PONG", 1)))
|
||||
}
|
||||
if strings.HasPrefix(line, "@") {
|
||||
bot.Messages <- *bot.parseMessage(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) joinDefault() {
|
||||
val, _ := bot.rClient.HGetAll("channels").Result()
|
||||
for channelStr := range val {
|
||||
channel := NewChannel(channelStr)
|
||||
go bot.join(channel)
|
||||
}
|
||||
}
|
||||
|
||||
func (bot *Bot) join(channel Channel) {
|
||||
bot.send(fmt.Sprintf("JOIN %s", channel.Name))
|
||||
}
|
||||
|
||||
func (bot *Bot) httpRequest(url string) ([]byte, error) {
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
contents, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
bot.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return contents, nil
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package twitch
|
||||
|
||||
import "strings"
|
||||
|
||||
type Channel struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func NewChannel(channel string) Channel {
|
||||
if !strings.HasPrefix(channel, "#") {
|
||||
channel = "#" + channel
|
||||
}
|
||||
return Channel{
|
||||
Name: strings.ToLower(channel),
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package twitch
|
||||
|
||||
type Command struct {
|
||||
IsCommand bool
|
||||
Name string
|
||||
Args []string
|
||||
}
|
196
twitch/parser.go
196
twitch/parser.go
|
@ -1,196 +0,0 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type msgType int
|
||||
|
||||
const (
|
||||
PRIVMSG msgType = iota + 1
|
||||
CLEARCHAT
|
||||
RANDOM
|
||||
EMOTE = "EMOTE"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Type msgType
|
||||
Time time.Time `json:"time"`
|
||||
Channel Channel `json:"channel"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"displayName"`
|
||||
UserType string `json:"userType"`
|
||||
Color string `json:"color"`
|
||||
Badges map[string]int `json:"badges"`
|
||||
Emotes []*Emote `json:"emotes"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
Text string `json:"text"`
|
||||
Command Command
|
||||
}
|
||||
|
||||
type Emote struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
func (bot *Bot) parseMessage(line string) *Message {
|
||||
if !strings.HasPrefix(line, "@") {
|
||||
return &Message{
|
||||
Text: line,
|
||||
}
|
||||
}
|
||||
spl := strings.SplitN(line, " :", 3)
|
||||
if len(spl) < 3 {
|
||||
return &Message{
|
||||
Text: line,
|
||||
}
|
||||
}
|
||||
tags, middle, text := spl[0], spl[1], spl[2]
|
||||
if strings.HasPrefix(text, "\u0001ACTION") {
|
||||
text = text[8 : len(text)-1]
|
||||
}
|
||||
msg := &Message{
|
||||
Time: time.Now(),
|
||||
Text: text,
|
||||
Tags: map[string]string{},
|
||||
}
|
||||
parseMiddle(msg, middle)
|
||||
parseTags(msg, tags[1:])
|
||||
if msg.Type == CLEARCHAT {
|
||||
msg.Username = "twitch"
|
||||
targetUser := msg.Text
|
||||
seconds, _ := strconv.Atoi(msg.Tags["ban-duration"])
|
||||
|
||||
msg.Text = fmt.Sprintf("%s was timed out for %s: %s",
|
||||
targetUser,
|
||||
time.Duration(time.Duration(seconds)*time.Second),
|
||||
msg.Tags["ban-reason"])
|
||||
}
|
||||
msg.Command = parseCommand(msg.Text)
|
||||
return msg
|
||||
}
|
||||
|
||||
func parseMiddle(msg *Message, middle string) {
|
||||
for i, c := range middle {
|
||||
if c == '!' {
|
||||
msg.Username = middle[:i]
|
||||
middle = middle[i:]
|
||||
}
|
||||
}
|
||||
start := -1
|
||||
for i, c := range middle {
|
||||
if c == ' ' {
|
||||
if start == -1 {
|
||||
start = i + 1
|
||||
} else {
|
||||
typ := middle[start:i]
|
||||
switch typ {
|
||||
case "PRIVMSG":
|
||||
msg.Type = PRIVMSG
|
||||
case "CLEARCHAT":
|
||||
msg.Type = CLEARCHAT
|
||||
default:
|
||||
msg.Type = RANDOM
|
||||
}
|
||||
middle = middle[i:]
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, c := range middle {
|
||||
if c == '#' {
|
||||
msg.Channel = NewChannel(middle[i+1:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseTags(msg *Message, tagsRaw string) {
|
||||
tags := strings.Split(tagsRaw, ";")
|
||||
for _, tag := range tags {
|
||||
spl := strings.SplitN(tag, "=", 2)
|
||||
if len(spl) < 2 {
|
||||
return
|
||||
}
|
||||
value := strings.Replace(spl[1], "\\:", ";", -1)
|
||||
value = strings.Replace(value, "\\s", " ", -1)
|
||||
value = strings.Replace(value, "\\\\", "\\", -1)
|
||||
switch spl[0] {
|
||||
case "badges":
|
||||
msg.Badges = parseBadges(value)
|
||||
case "color":
|
||||
msg.Color = value
|
||||
case "display-name":
|
||||
msg.DisplayName = value
|
||||
case "emotes":
|
||||
msg.Emotes = parseTwitchEmotes(value, msg.Text)
|
||||
case "user-type":
|
||||
msg.UserType = value
|
||||
default:
|
||||
msg.Tags[spl[0]] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseBadges(badges string) map[string]int {
|
||||
m := map[string]int{}
|
||||
spl := strings.Split(badges, ",")
|
||||
for _, badge := range spl {
|
||||
s := strings.SplitN(badge, "/", 2)
|
||||
if len(s) < 2 {
|
||||
continue
|
||||
}
|
||||
n, _ := strconv.Atoi(s[1])
|
||||
m[s[0]] = n
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func parseTwitchEmotes(emoteTag, text string) []*Emote {
|
||||
emotes := []*Emote{}
|
||||
|
||||
if emoteTag == "" {
|
||||
return emotes
|
||||
}
|
||||
|
||||
runes := []rune(text)
|
||||
|
||||
emoteSlice := strings.Split(emoteTag, "/")
|
||||
for i := range emoteSlice {
|
||||
spl := strings.Split(emoteSlice[i], ":")
|
||||
pos := strings.Split(spl[1], ",")
|
||||
sp := strings.Split(pos[0], "-")
|
||||
start, _ := strconv.Atoi(sp[0])
|
||||
end, _ := strconv.Atoi(sp[1])
|
||||
id := spl[0]
|
||||
e := &Emote{
|
||||
Type: EMOTE,
|
||||
ID: id,
|
||||
Count: strings.Count(emoteSlice[i], "-"),
|
||||
Name: string(runes[start : end+1]),
|
||||
}
|
||||
|
||||
emotes = append(emotes, e)
|
||||
}
|
||||
return emotes
|
||||
}
|
||||
|
||||
func parseCommand(text string) Command {
|
||||
cmd := new(Command)
|
||||
|
||||
if !strings.HasPrefix(text, "!") {
|
||||
cmd.IsCommand = false
|
||||
return *cmd
|
||||
}
|
||||
cmd.IsCommand = true
|
||||
|
||||
argsFull := strings.Split(text, " ")
|
||||
cmd.Name = argsFull[0]
|
||||
args := append(argsFull[:0], argsFull[0+1:]...)
|
||||
cmd.Args = args
|
||||
|
||||
return *cmd
|
||||
}
|
Loading…
Add table
Reference in a new issue