diff --git a/README.MD b/README.MD index 51fbe55..f970b2d 100644 --- a/README.MD +++ b/README.MD @@ -1,9 +1,14 @@ # justlog [![Build Status](https://travis-ci.org/gempir/justlog.svg?branch=master)](https://travis-ci.org/gempir/justlog) -#### What is this? +### What is this? justlog is a bot I maintain for a couple of channels. Its features differ from other bots in that it doesn't support commands, etc. yet, it focuses on logging and providing an api for the logs. +### Commands +- `!justlog status` will respond with uptime +- `!justlog join gempir,pajlada` will join the channels and append them to the config +- `!justlog messageType gempir 1,2` will set the recorded message types to 1 and 2 in channel gempir (will fetch the userid on its own) + ### Config ``` @@ -21,4 +26,4 @@ commands, etc. yet, it focuses on logging and providing an api for the logs. } } } -``` \ No newline at end of file +``` diff --git a/bot/commands.go b/bot/commands.go new file mode 100644 index 0000000..fa342d8 --- /dev/null +++ b/bot/commands.go @@ -0,0 +1,75 @@ +package bot + +import ( + "fmt" + "strconv" + "strings" + + twitch "github.com/gempir/go-twitch-irc/v2" + "github.com/gempir/justlog/humanize" + log "github.com/sirupsen/logrus" +) + +func (b *Bot) handlePrivateMessage(message twitch.PrivateMessage) { + if message.User.Name == b.cfg.Admin { + if strings.HasPrefix(message.Message, "!justlog status") || strings.HasPrefix(message.Message, "!status") { + uptime := humanize.TimeSince(b.startTime) + b.twitchClient.Say(message.Channel, message.User.DisplayName+", uptime: "+uptime) + } + if strings.HasPrefix(message.Message, "!justlog join ") { + b.handleJoin(message) + } + if strings.HasPrefix(message.Message, "!justlog messageType ") { + b.handleMessageType(message) + } + } +} + +func (b *Bot) handleJoin(message twitch.PrivateMessage) { + input := strings.TrimPrefix(message.Message, "!justlog join ") + + users, err := b.helixClient.GetUsersByUsernames(strings.Split(input, ",")) + if err != nil { + log.Error(err) + b.twitchClient.Say(message.Channel, message.User.DisplayName+", something went wrong requesting the userids") + } + + ids := []string{} + for _, user := range users { + ids = append(ids, user.ID) + log.Infof("[bot] joining %s", user.Login) + b.twitchClient.Join(user.Login) + } + b.cfg.AddChannels(ids...) + b.twitchClient.Say(message.Channel, fmt.Sprintf("%s, added channels: %v", message.User.DisplayName, ids)) +} + +func (b *Bot) handleMessageType(message twitch.PrivateMessage) { + input := strings.TrimPrefix(message.Message, "!justlog messageType ") + + parts := strings.Split(input, " ") + if len(parts) < 2 { + return + } + + users, err := b.helixClient.GetUsersByUsernames([]string{parts[0]}) + if err != nil { + log.Error(err) + return + } + + var messageTypes []twitch.MessageType + for _, msgType := range strings.Split(parts[1], ",") { + messageType, err := strconv.Atoi(msgType) + if err != nil { + log.Error(err) + return + } + + messageTypes = append(messageTypes, twitch.MessageType(messageType)) + } + + b.cfg.SetMessageTypes(users[parts[0]].ID, messageTypes) + b.updateMessageTypesToLog() + log.Infof("[bot] setting %s config messageTypes to %v", parts[0], messageTypes) +} diff --git a/bot/main.go b/bot/main.go index 1ec9542..8bd41e0 100644 --- a/bot/main.go +++ b/bot/main.go @@ -4,70 +4,57 @@ import ( "strings" "time" + "github.com/gempir/justlog/config" "github.com/gempir/justlog/filelog" - "github.com/gempir/go-twitch-irc/v2" + twitch "github.com/gempir/go-twitch-irc/v2" "github.com/gempir/justlog/helix" - "github.com/gempir/justlog/humanize" log "github.com/sirupsen/logrus" ) // Bot basic logging bot type Bot struct { - admin string - username string - oauth string - startTime *time.Time + startTime time.Time + cfg *config.Config helixClient *helix.Client + twitchClient *twitch.Client fileLogger *filelog.Logger + channels map[string]helix.UserData messageTypesToLog map[string][]twitch.MessageType } // NewBot create new bot instance -func NewBot(admin, username, oauth string, startTime *time.Time, helixClient *helix.Client, fileLogger *filelog.Logger, messageTypesToLog map[string][]twitch.MessageType) *Bot { +func NewBot(cfg *config.Config, helixClient *helix.Client, fileLogger *filelog.Logger) *Bot { + channels, err := helixClient.GetUsersByUserIds(cfg.Channels) + if err != nil { + log.Fatalf("[bot] failed to load configured channels %s", err.Error()) + } + return &Bot{ - admin: admin, - username: username, - oauth: oauth, - startTime: startTime, - helixClient: helixClient, - fileLogger: fileLogger, - messageTypesToLog: messageTypesToLog, + cfg: cfg, + helixClient: helixClient, + fileLogger: fileLogger, + channels: channels, } } // Connect startup the logger and bot -func (b *Bot) Connect(channelIds []string) { - twitchClient := twitch.NewClient(b.username, "oauth:"+b.oauth) +func (b *Bot) Connect() { + b.startTime = time.Now() + b.twitchClient = twitch.NewClient(b.cfg.Username, "oauth:"+b.cfg.OAuth) + b.updateMessageTypesToLog() + b.initialJoins() - if strings.HasPrefix(b.username, "justinfan") { - log.Info("Bot joining anonymous") + if strings.HasPrefix(b.cfg.Username, "justinfan") { + log.Info("[bot] joining anonymous") } else { - log.Info("Bot joining as user " + b.username) + log.Info("[bot] joining as user " + b.cfg.Username) } - channels, err := b.helixClient.GetUsersByUserIds(channelIds) - if err != nil { - log.Fatalf("Failed to load configured channels %s", err.Error()) - } - - messageTypesToLog := make(map[string][]twitch.MessageType) - - for _, channel := range channels { - log.Info("Joining " + channel.Login) - twitchClient.Join(channel.Login) - - if _, ok := b.messageTypesToLog[channel.ID]; ok { - messageTypesToLog[channel.Login] = b.messageTypesToLog[channel.ID] - } else { - messageTypesToLog[channel.Login] = []twitch.MessageType{twitch.PRIVMSG, twitch.CLEARCHAT, twitch.USERNOTICE} - } - } - - twitchClient.OnPrivateMessage(func(message twitch.PrivateMessage) { + b.twitchClient.OnPrivateMessage(func(message twitch.PrivateMessage) { go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -78,7 +65,7 @@ func (b *Bot) Connect(channelIds []string) { }() go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -88,17 +75,14 @@ func (b *Bot) Connect(channelIds []string) { } }() - if message.User.Name == b.admin && strings.HasPrefix(message.Message, "!status") { - uptime := humanize.TimeSince(*b.startTime) - twitchClient.Say(message.Channel, message.User.DisplayName+", uptime: "+uptime) - } + b.handlePrivateMessage(message) }) - twitchClient.OnUserNoticeMessage(func(message twitch.UserNoticeMessage) { + b.twitchClient.OnUserNoticeMessage(func(message twitch.UserNoticeMessage) { log.Debug(message.Raw) go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -110,7 +94,7 @@ func (b *Bot) Connect(channelIds []string) { if _, ok := message.Tags["msg-param-recipient-id"]; ok { go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -122,7 +106,7 @@ func (b *Bot) Connect(channelIds []string) { } go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -134,10 +118,10 @@ func (b *Bot) Connect(channelIds []string) { }) - twitchClient.OnClearChatMessage(func(message twitch.ClearChatMessage) { + b.twitchClient.OnClearChatMessage(func(message twitch.ClearChatMessage) { go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -148,7 +132,7 @@ func (b *Bot) Connect(channelIds []string) { }() go func() { - if !shouldLog(messageTypesToLog, message.Channel, message.GetType()) { + if !b.shouldLog(message.Channel, message.GetType()) { return } @@ -159,11 +143,11 @@ func (b *Bot) Connect(channelIds []string) { }() }) - log.Fatal(twitchClient.Connect()) + log.Fatal(b.twitchClient.Connect()) } -func shouldLog(messageTypesToLog map[string][]twitch.MessageType, channelName string, receivedMsgType twitch.MessageType) bool { - for _, msgType := range messageTypesToLog[channelName] { +func (b *Bot) shouldLog(channelName string, receivedMsgType twitch.MessageType) bool { + for _, msgType := range b.messageTypesToLog[channelName] { if msgType == receivedMsgType { return true } @@ -171,3 +155,24 @@ func shouldLog(messageTypesToLog map[string][]twitch.MessageType, channelName st return false } + +func (b *Bot) updateMessageTypesToLog() { + messageTypesToLog := make(map[string][]twitch.MessageType) + + for _, channel := range b.channels { + if _, ok := b.cfg.ChannelConfigs[channel.ID]; ok && b.cfg.ChannelConfigs[channel.ID].MessageTypes != nil { + messageTypesToLog[channel.Login] = b.cfg.ChannelConfigs[channel.ID].MessageTypes + } else { + messageTypesToLog[channel.Login] = []twitch.MessageType{twitch.PRIVMSG, twitch.CLEARCHAT, twitch.USERNOTICE} + } + } + + b.messageTypesToLog = messageTypesToLog +} + +func (b *Bot) initialJoins() { + for _, channel := range b.channels { + log.Info("[bot] joining " + channel.Login) + b.twitchClient.Join(channel.Login) + } +} diff --git a/config/main.go b/config/main.go new file mode 100644 index 0000000..d78b2f5 --- /dev/null +++ b/config/main.go @@ -0,0 +1,151 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "os" + "strings" + + twitch "github.com/gempir/go-twitch-irc/v2" + log "github.com/sirupsen/logrus" +) + +// Config application configuratin +type Config struct { + configFile string + configFilePermissions os.FileMode + LogsDirectory string `json:"logsDirectory"` + Username string `json:"username"` + OAuth string `json:"oauth"` + ListenAddress string `json:"listenAddress"` + Admin string `json:"admin"` + Channels []string `json:"channels"` + ClientID string `json:"clientID"` + LogLevel string `json:"logLevel"` + ChannelConfigs map[string]ChannelConfig `json:"channelConfigs"` +} + +// ChannelConfig config for indiviual channels +type ChannelConfig struct { + MessageTypes []twitch.MessageType `json:"messageTypes"` +} + +// NewConfig create configuration from file +func NewConfig(filePath string) *Config { + cfg := loadConfiguration(filePath) + + log.Info("Loaded config from " + filePath) + + return cfg +} + +// AddChannels adds channels to the config +func (cfg *Config) AddChannels(channelIDs ...string) { + cfg.Channels = append(cfg.Channels, channelIDs...) + for _, id := range channelIDs { + cfg.Channels = appendIfMissing(cfg.Channels, id) + } + + cfg.persistConfig() +} + +// SetMessageTypes sets recorded message types for a channel +func (cfg *Config) SetMessageTypes(channelID string, messageTypes []twitch.MessageType) { + if _, ok := cfg.ChannelConfigs[channelID]; ok { + channelCfg := cfg.ChannelConfigs[channelID] + channelCfg.MessageTypes = messageTypes + + cfg.ChannelConfigs[channelID] = channelCfg + } else { + cfg.ChannelConfigs[channelID] = ChannelConfig{ + MessageTypes: messageTypes, + } + } + + cfg.persistConfig() +} + +func appendIfMissing(slice []string, i string) []string { + for _, ele := range slice { + if ele == i { + return slice + } + } + return append(slice, i) +} + +func (cfg *Config) persistConfig() { + fileContents, err := json.MarshalIndent(*cfg, "", " ") + if err != nil { + log.Error(err) + return + } + + err = ioutil.WriteFile(cfg.configFile, fileContents, cfg.configFilePermissions) + if err != nil { + log.Error(err) + } +} + +func loadConfiguration(filePath string) *Config { + // setup defaults + cfg := Config{ + configFile: filePath, + LogsDirectory: "./logs", + ListenAddress: "127.0.0.1:8025", + Username: "justinfan777777", + OAuth: "oauth:777777777", + Channels: []string{}, + ChannelConfigs: make(map[string]ChannelConfig), + Admin: "gempir", + LogLevel: "info", + } + + info, err := os.Stat(filePath) + if err != nil { + log.Fatal(err) + } + cfg.configFilePermissions = info.Mode() + + configFile, err := os.Open(filePath) + if err != nil { + log.Fatal(err) + } + defer configFile.Close() + + jsonParser := json.NewDecoder(configFile) + err = jsonParser.Decode(&cfg) + if err != nil { + log.Fatal(err) + } + + // normalize + cfg.LogsDirectory = strings.TrimSuffix(cfg.LogsDirectory, "/") + cfg.OAuth = strings.TrimPrefix(cfg.OAuth, "oauth:") + cfg.LogLevel = strings.ToLower(cfg.LogLevel) + cfg.setupLogger() + + // ensure required + if cfg.ClientID == "" { + log.Fatal("No clientID specified") + } + + return &cfg +} + +func (cfg *Config) setupLogger() { + switch cfg.LogLevel { + case "fatal": + log.SetLevel(log.FatalLevel) + case "panic": + log.SetLevel(log.PanicLevel) + case "error": + log.SetLevel(log.ErrorLevel) + case "warn": + log.SetLevel(log.WarnLevel) + case "info": + log.SetLevel(log.InfoLevel) + case "debug": + log.SetLevel(log.DebugLevel) + } +} diff --git a/go.mod b/go.mod index 8f49c44..2ff872a 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/nicklaw5/helix v0.5.7 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 // indirect - golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 7bca90c..d4fb167 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index f59bc45..2df2d21 100644 --- a/main.go +++ b/main.go @@ -1,45 +1,23 @@ package main import ( - "encoding/json" "flag" - "os" - "time" - "strings" - - "github.com/gempir/go-twitch-irc/v2" "github.com/gempir/justlog/api" "github.com/gempir/justlog/archiver" "github.com/gempir/justlog/bot" + "github.com/gempir/justlog/config" "github.com/gempir/justlog/filelog" "github.com/gempir/justlog/helix" - - log "github.com/sirupsen/logrus" ) -type config struct { - LogsDirectory string `json:"logsDirectory"` - Username string `json:"username"` - OAuth string `json:"oauth"` - ListenAddress string `json:"listenAddress"` - Admin string `json:"admin"` - Channels []string `json:"channels"` - ClientID string `json:"clientID"` - LogLevel string `json:"logLevel"` - ChannelConfigs map[string]struct { - MessageTypes []twitch.MessageType `json:"messageTypes"` - } `json:"channelConfigs"` -} - func main() { - startTime := time.Now() configFile := flag.String("config", "config.json", "json config file") flag.Parse() - cfg := loadConfiguration(*configFile) - setupLogger(cfg) + cfg := config.NewConfig(*configFile) + fileLogger := filelog.NewFileLogger(cfg.LogsDirectory) helixClient := helix.NewClient(cfg.ClientID) archiver := archiver.NewArchiver(cfg.LogsDirectory) @@ -48,67 +26,6 @@ func main() { apiServer := api.NewServer(cfg.LogsDirectory, cfg.ListenAddress, &fileLogger, &helixClient, cfg.Channels) go apiServer.Init() - messageTypesToLog := make(map[string][]twitch.MessageType) - for userID, config := range cfg.ChannelConfigs { - messageTypesToLog[userID] = config.MessageTypes - } - - bot := bot.NewBot(cfg.Admin, cfg.Username, cfg.OAuth, &startTime, &helixClient, &fileLogger, messageTypesToLog) - bot.Connect(cfg.Channels) -} - -func loadConfiguration(file string) config { - log.Info("Loading config from " + file) - - // setup defaults - cfg := config{ - LogsDirectory: "./logs", - ListenAddress: "127.0.0.1:8025", - Username: "justinfan777777", - OAuth: "oauth:777777777", - Channels: []string{}, - Admin: "gempir", - LogLevel: "info", - } - - configFile, err := os.Open(file) - if err != nil { - log.Fatal(err) - } - defer configFile.Close() - - jsonParser := json.NewDecoder(configFile) - err = jsonParser.Decode(&cfg) - if err != nil { - log.Fatal(err) - } - - // normalize - cfg.LogsDirectory = strings.TrimSuffix(cfg.LogsDirectory, "/") - cfg.OAuth = strings.TrimPrefix(cfg.OAuth, "oauth:") - cfg.LogLevel = strings.ToLower(cfg.LogLevel) - - // ensure required - if cfg.ClientID == "" { - log.Fatal("No clientID specified") - } - - return cfg -} - -func setupLogger(cfg config) { - switch cfg.LogLevel { - case "fatal": - log.SetLevel(log.FatalLevel) - case "panic": - log.SetLevel(log.PanicLevel) - case "error": - log.SetLevel(log.ErrorLevel) - case "warn": - log.SetLevel(log.WarnLevel) - case "info": - log.SetLevel(log.InfoLevel) - case "debug": - log.SetLevel(log.DebugLevel) - } + bot := bot.NewBot(cfg, &helixClient, &fileLogger) + bot.Connect() }