diff --git a/README.MD b/README.MD index 51c01f0..2268bb8 100644 --- a/README.MD +++ b/README.MD @@ -15,6 +15,8 @@ docker run -p 8025:8025 --restart=unless-stopped --user $(id -u):$(id -g) -v $PW - `!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) - `!justlog messageType gempir reset` will reset to default +- `!justlog optout gempir,gempbot` will opt out users of message logging or querying previous logs of that user, same applies to users own channel +- `!justlog optin gempir,gempbot` will revert the opt out ### Config diff --git a/api/server.go b/api/server.go index 6eee653..1205597 100644 --- a/api/server.go +++ b/api/server.go @@ -117,6 +117,11 @@ func (s *Server) route(w http.ResponseWriter, r *http.Request) { query := s.fillUserids(w, r) if url == "/list" { + if s.cfg.IsOptedOut(query.Get("userid")) || s.cfg.IsOptedOut(query.Get("channelid")) { + http.Error(w, "User or channel has opted out", http.StatusForbidden) + return + } + s.writeAvailableLogs(w, r, query) return } @@ -179,6 +184,11 @@ func (s *Server) routeLogs(w http.ResponseWriter, r *http.Request) bool { return true } + if s.cfg.IsOptedOut(request.channelid) || s.cfg.IsOptedOut(request.userid) { + http.Error(w, "User or channel has opted out", http.StatusForbidden) + return true + } + var logs *chatLog if request.time.random { logs, err = s.getRandomQuote(request) diff --git a/api/server_test.go b/api/server_test.go deleted file mode 100644 index ca0dfab..0000000 --- a/api/server_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package api - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/gempir/justlog/bot" - "github.com/gempir/justlog/filelog" - "github.com/gempir/justlog/helix" - - "github.com/gempir/justlog/config" -) - -type helixClientMock struct { -} - -func (m *helixClientMock) GetUsersByUserIds(channels []string) (map[string]helix.UserData, error) { - data := make(map[string]helix.UserData) - data["77829817"] = helix.UserData{Login: "gempir", ID: "77829817"} - - return data, nil -} - -func (m *helixClientMock) GetUsersByUsernames(channels []string) (map[string]helix.UserData, error) { - data := make(map[string]helix.UserData) - data["gempir"] = helix.UserData{Login: "gempir", ID: "77829817"} - - return data, nil -} - -func createTestServer() *Server { - cfg := &config.Config{LogsDirectory: "./logs", Channels: []string{"gempir"}, ClientID: "123"} - - fileLogger := filelog.NewFileLogger(cfg.LogsDirectory) - helixClient := new(helixClientMock) - - bot := bot.NewBot(cfg, helixClient, &fileLogger) - - apiServer := NewServer(cfg, bot, &fileLogger, helixClient, cfg.Channels) - - return &apiServer -} - -func TestApiServer(t *testing.T) { - server := createTestServer() - - t.Run("get channels", func(t *testing.T) { - r, _ := http.NewRequest(http.MethodGet, "/channels", nil) - w := httptest.NewRecorder() - - server.route(w, r) - assert.Contains(t, w.Body.String(), "gempir") - }) - - t.Run("get user logs", func(t *testing.T) { - r, _ := http.NewRequest(http.MethodGet, "/channel/gempir/user/gempir", nil) - w := httptest.NewRecorder() - - server.route(w, r) - assert.Equal(t, w.Code, 302) - }) - - t.Run("get channel logs", func(t *testing.T) { - r, _ := http.NewRequest(http.MethodGet, "/channel/gempir", nil) - w := httptest.NewRecorder() - - server.route(w, r) - assert.Equal(t, w.Code, 302) - }) -} diff --git a/bot/commands.go b/bot/commands.go index 2a9436c..b8565cf 100644 --- a/bot/commands.go +++ b/bot/commands.go @@ -15,12 +15,18 @@ func (b *Bot) handlePrivateMessageCommands(message twitch.PrivateMessage) { uptime := humanize.TimeSince(b.startTime) b.Say(message.Channel, message.User.DisplayName+", uptime: "+uptime) } - if strings.HasPrefix(message.Message, "!justlog join ") { + if strings.HasPrefix(strings.ToLower(message.Message), "!justlog join ") { b.handleJoin(message) } - if strings.HasPrefix(message.Message, "!justlog part ") { + if strings.HasPrefix(strings.ToLower(message.Message), "!justlog part ") { b.handlePart(message) } + if strings.HasPrefix(strings.ToLower(message.Message), "!justlog optout ") { + b.handleOptOut(message) + } + if strings.HasPrefix(strings.ToLower(message.Message), "!justlog optin ") { + b.handleOptIn(message) + } } } @@ -60,6 +66,41 @@ func (b *Bot) handlePart(message twitch.PrivateMessage) { b.Say(message.Channel, fmt.Sprintf("%s, removed channels: %v", message.User.DisplayName, ids)) } +func (b *Bot) handleOptOut(message twitch.PrivateMessage) { + input := strings.TrimPrefix(strings.ToLower(message.Message), "!justlog optout ") + + users, err := b.helixClient.GetUsersByUsernames(strings.Split(input, ",")) + if err != nil { + log.Error(err) + b.Say(message.Channel, message.User.DisplayName+", something went wrong requesting the userids") + } + + ids := []string{} + for _, user := range users { + ids = append(ids, user.ID) + } + b.cfg.OptOutUsers(ids...) + b.Say(message.Channel, fmt.Sprintf("%s, opted out channels: %v", message.User.DisplayName, ids)) +} + +func (b *Bot) handleOptIn(message twitch.PrivateMessage) { + input := strings.TrimPrefix(strings.ToLower(message.Message), "!justlog optin ") + + users, err := b.helixClient.GetUsersByUsernames(strings.Split(input, ",")) + if err != nil { + log.Error(err) + b.Say(message.Channel, message.User.DisplayName+", something went wrong requesting the userids") + } + + ids := []string{} + for _, user := range users { + ids = append(ids, user.ID) + } + + b.cfg.RemoveOptOut(ids...) + b.Say(message.Channel, fmt.Sprintf("%s, opted in channels: %v", message.User.DisplayName, ids)) +} + func contains(arr []string, str string) bool { for _, x := range arr { if x == str { diff --git a/bot/main.go b/bot/main.go index 8271710..0779951 100644 --- a/bot/main.go +++ b/bot/main.go @@ -116,6 +116,12 @@ func (b *Bot) initialJoins() { } func (b *Bot) handlePrivateMessage(message twitch.PrivateMessage) { + b.handlePrivateMessageCommands(message) + + if b.cfg.IsOptedOut(message.User.ID) || b.cfg.IsOptedOut(message.RoomID) { + return + } + go func() { err := b.fileLogger.LogPrivateMessageForUser(message.User, message) if err != nil { @@ -129,11 +135,13 @@ func (b *Bot) handlePrivateMessage(message twitch.PrivateMessage) { log.Error(err.Error()) } }() - - b.handlePrivateMessageCommands(message) } func (b *Bot) handleUserNotice(message twitch.UserNoticeMessage) { + if b.cfg.IsOptedOut(message.User.ID) || b.cfg.IsOptedOut(message.RoomID) { + return + } + go func() { err := b.fileLogger.LogUserNoticeMessageForUser(message.User.ID, message) if err != nil { @@ -159,6 +167,10 @@ func (b *Bot) handleUserNotice(message twitch.UserNoticeMessage) { } func (b *Bot) handleClearChat(message twitch.ClearChatMessage) { + if b.cfg.IsOptedOut(message.TargetUserID) || b.cfg.IsOptedOut(message.RoomID) { + return + } + if message.BanDuration == 0 { count, ok := b.clearchats.Load(message.RoomID) if !ok { diff --git a/config/main.go b/config/main.go index 398f96d..ddeee04 100644 --- a/config/main.go +++ b/config/main.go @@ -13,17 +13,18 @@ import ( type Config struct { configFile string configFilePermissions os.FileMode - LogsDirectory string `json:"logsDirectory"` - Archive bool `json:"archive"` - AdminAPIKey string `json:"adminAPIKey"` - Username string `json:"username"` - OAuth string `json:"oauth"` - ListenAddress string `json:"listenAddress"` - Admins []string `json:"admins"` - Channels []string `json:"channels"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - LogLevel string `json:"logLevel"` + LogsDirectory string `json:"logsDirectory"` + Archive bool `json:"archive"` + AdminAPIKey string `json:"adminAPIKey"` + Username string `json:"username"` + OAuth string `json:"oauth"` + ListenAddress string `json:"listenAddress"` + Admins []string `json:"admins"` + Channels []string `json:"channels"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + LogLevel string `json:"logLevel"` + OptOut map[string]bool `json:"optOut"` } // NewConfig create configuration from file @@ -45,6 +46,31 @@ func (cfg *Config) AddChannels(channelIDs ...string) { cfg.persistConfig() } +// OptOutUsers will opt out a user +func (cfg *Config) OptOutUsers(userIDs ...string) { + for _, id := range userIDs { + cfg.OptOut[id] = true + } + + cfg.persistConfig() +} + +// IsOptedOut check if a user is opted out +func (cfg *Config) IsOptedOut(userID string) bool { + _, ok := cfg.OptOut[userID] + + return ok +} + +// AddChannels remove user from opt out +func (cfg *Config) RemoveOptOut(userIDs ...string) { + for _, id := range userIDs { + delete(cfg.OptOut, id) + } + + cfg.persistConfig() +} + // RemoveChannels removes channels from the config func (cfg *Config) RemoveChannels(channelIDs ...string) { channels := cfg.Channels @@ -88,15 +114,16 @@ func (cfg *Config) persistConfig() { func loadConfiguration(filePath string) *Config { // setup defaults cfg := Config{ - configFile: filePath, - LogsDirectory: "./logs", - ListenAddress: ":8025", - Username: "justinfan777777", - OAuth: "oauth:777777777", - Channels: []string{}, - Admins: []string{"gempir"}, - LogLevel: "info", - Archive: true, + configFile: filePath, + LogsDirectory: "./logs", + ListenAddress: ":8025", + Username: "justinfan777777", + OAuth: "oauth:777777777", + Channels: []string{}, + Admins: []string{"gempir"}, + LogLevel: "info", + Archive: true, + OptOut: map[string]bool{}, } info, err := os.Stat(filePath) diff --git a/go.mod b/go.mod index 0a54499..00559d5 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.16 require ( github.com/gempir/go-twitch-irc/v2 v2.5.0 github.com/nicklaw5/helix v1.0.0 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/sirupsen/logrus v1.7.0 - github.com/stretchr/testify v1.6.1 - gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect + github.com/stretchr/testify v1.6.1 // indirect ) diff --git a/go.sum b/go.sum index 5a12706..0170dca 100644 --- a/go.sum +++ b/go.sum @@ -3,13 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gempir/go-twitch-irc/v2 v2.5.0 h1:aybXNoyDNQaa4vHhXb0UpIDmspqutQUmXIYUFsjgecU= github.com/gempir/go-twitch-irc/v2 v2.5.0/go.mod h1:120d2SdlRYg8tRnZwsyNPeS+mWPn+YmNEzB7Bv/CDGE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/nicklaw5/helix v1.0.0 h1:PvoDKOYI5AYA8dI+Lm4/xqcnZ/CvcUVlOJ9pziiqYsk= github.com/nicklaw5/helix v1.0.0/go.mod h1:XeeXY7oY5W+MVMu6wF4qGm8uvjZ1/Nss0FqprVkXKrg= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= @@ -21,7 +16,5 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/web/build/asset-manifest.json b/web/build/asset-manifest.json index ae604cc..60405b9 100644 --- a/web/build/asset-manifest.json +++ b/web/build/asset-manifest.json @@ -1,20 +1,20 @@ { "files": { - "main.js": "/static/js/main.7390b87b.chunk.js", - "main.js.map": "/static/js/main.7390b87b.chunk.js.map", + "main.js": "/static/js/main.fb71d3bb.chunk.js", + "main.js.map": "/static/js/main.fb71d3bb.chunk.js.map", "runtime-main.js": "/static/js/runtime-main.aef01254.js", "runtime-main.js.map": "/static/js/runtime-main.aef01254.js.map", "static/css/2.0a74b1fe.chunk.css": "/static/css/2.0a74b1fe.chunk.css", - "static/js/2.b6702176.chunk.js": "/static/js/2.b6702176.chunk.js", - "static/js/2.b6702176.chunk.js.map": "/static/js/2.b6702176.chunk.js.map", + "static/js/2.0dc1648a.chunk.js": "/static/js/2.0dc1648a.chunk.js", + "static/js/2.0dc1648a.chunk.js.map": "/static/js/2.0dc1648a.chunk.js.map", "index.html": "/index.html", "static/css/2.0a74b1fe.chunk.css.map": "/static/css/2.0a74b1fe.chunk.css.map", - "static/js/2.b6702176.chunk.js.LICENSE.txt": "/static/js/2.b6702176.chunk.js.LICENSE.txt" + "static/js/2.0dc1648a.chunk.js.LICENSE.txt": "/static/js/2.0dc1648a.chunk.js.LICENSE.txt" }, "entrypoints": [ "static/js/runtime-main.aef01254.js", "static/css/2.0a74b1fe.chunk.css", - "static/js/2.b6702176.chunk.js", - "static/js/main.7390b87b.chunk.js" + "static/js/2.0dc1648a.chunk.js", + "static/js/main.fb71d3bb.chunk.js" ] } \ No newline at end of file diff --git a/web/build/index.html b/web/build/index.html index 25678d0..c98fb8b 100644 --- a/web/build/index.html +++ b/web/build/index.html @@ -1 +1 @@ -