add opt out option

This commit is contained in:
gempir 2021-06-05 16:23:52 +02:00
parent ec9fc8fa18
commit f9eeebd037
25 changed files with 236 additions and 154 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)
})
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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)

4
go.mod
View file

@ -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
)

7
go.sum
View file

@ -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=

View file

@ -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"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#090A0B"/><title>justlog</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/><style>:root{--bg:#0e0e10;--bg-bright:#18181b;--bg-brighter:#3d4146;--bg-dark:#121416;--theme:#00CC66;--theme-bright:#00FF80;--theme2:#2980b9;--theme2-bright:#3498db;--text:#F5F5F5;--text-dark:#616161}body{margin:0;padding:0;background:var(--bg);font-family:Helvetica,Arial,sans-serif;height:100%;width:100%}*{box-sizing:border-box}</style><link href="/static/css/2.0a74b1fe.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var f=this.webpackJsonpweb=this.webpackJsonpweb||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([])</script><script src="/static/js/2.b6702176.chunk.js"></script><script src="/static/js/main.7390b87b.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#090A0B"/><title>justlog</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/><style>:root{--bg:#0e0e10;--bg-bright:#18181b;--bg-brighter:#3d4146;--bg-dark:#121416;--theme:#00CC66;--theme-bright:#00FF80;--theme2:#2980b9;--theme2-bright:#3498db;--text:#F5F5F5;--text-dark:#616161;--danger:#e74c3c}body{margin:0;padding:0;background:var(--bg);font-family:Helvetica,Arial,sans-serif;height:100%;width:100%}*{box-sizing:border-box}</style><link href="/static/css/2.0a74b1fe.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c<f.length;c++)l=f[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,f=1;f<t.length;f++){var i=t[f];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var f=this.webpackJsonpweb=this.webpackJsonpweb||[],i=f.push.bind(f);f.push=r,f=f.slice();for(var a=0;a<f.length;a++)r(f[a]);var p=i;t()}([])</script><script src="/static/js/2.0dc1648a.chunk.js"></script><script src="/static/js/main.fb71d3bb.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -40,9 +40,15 @@
],
"operationId": "channelConfigs",
"responses": {
"200": {},
"400": {},
"405": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
}
}
}
},
@ -78,10 +84,18 @@
}
],
"responses": {
"200": {},
"400": {},
"405": {},
"500": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
},
"500": {
"description": ""
}
}
},
"delete": {
@ -115,10 +129,18 @@
}
],
"responses": {
"200": {},
"400": {},
"405": {},
"500": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
},
"500": {
"description": ""
}
}
}
},

View file

@ -21,6 +21,7 @@
--theme2-bright: #3498db;
--text: #F5F5F5;
--text-dark: #616161;
--danger: #e74c3c;
}
body {

View file

@ -40,9 +40,15 @@
],
"operationId": "channelConfigs",
"responses": {
"200": {},
"400": {},
"405": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
}
}
}
},
@ -78,10 +84,18 @@
}
],
"responses": {
"200": {},
"400": {},
"405": {},
"500": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
},
"500": {
"description": ""
}
}
},
"delete": {
@ -115,10 +129,18 @@
}
],
"responses": {
"200": {},
"400": {},
"405": {},
"500": {}
"200": {
"description": ""
},
"400": {
"description": ""
},
"405": {
"description": ""
},
"500": {
"description": ""
}
}
}
},

View file

@ -1,8 +1,10 @@
import React, { useContext, useEffect } from "react";
import styled from "styled-components";
import { OptOutError } from "../errors/OptOutError";
import { useAvailableLogs } from "../hooks/useAvailableLogs";
import { store } from "../store";
import { Log } from "./Log";
import { OptOutMessage } from "./OptOutMessage";
const LogContainerDiv = styled.div`
color: white;
@ -32,7 +34,10 @@ export function LogContainer() {
return () => window.removeEventListener("keydown", listener);
}, [state.activeSearchField, state.settings.twitchChatMode.value, ctrlKey]);
const availableLogs = useAvailableLogs(state.currentChannel, state.currentUsername);
const [availableLogs, err] = useAvailableLogs(state.currentChannel, state.currentUsername);
if (err instanceof OptOutError) {
return <OptOutMessage />;
}
return <LogContainerDiv>
{availableLogs.map((log, index) => <Log key={`${log.year}:${log.month}`} year={log.year} month={log.month} initialLoad={index === 0} />)}

View file

@ -0,0 +1,15 @@
import styled from "styled-components";
import React from "react";
const OptOutContainer = styled.div`
display: block;
font-weight: bold;
color: var(--danger);
font-size: 2rem;
text-align: center;
padding: 2rem;
`;
export function OptOutMessage() {
return <OptOutContainer>User or channel has opted out</OptOutContainer>
}

View file

@ -0,0 +1 @@
export class OptOutError extends Error { }

View file

@ -1,14 +1,16 @@
import { useContext } from "react";
import { useQuery } from "react-query";
import { OptOutError } from "../errors/OptOutError";
import { getUserId, isUserId } from "../services/isUserId";
import { store } from "../store";
export type AvailableLogs = Array<{ month: string, year: string }>;
export function useAvailableLogs(channel: string | null, username: string | null): AvailableLogs {
export function useAvailableLogs(channel: string | null, username: string | null): [AvailableLogs, Error | undefined] {
const { state, setState } = useContext(store);
const { data } = useQuery<AvailableLogs>(["availableLogs", { channel: channel, username: username }], () => {
// @ts-ignore
const { data } = useQuery<[AvailableLogs, Error | undefined]>(["availableLogs", { channel: channel, username: username }], () => {
if (channel && username) {
const channelIsId = isUserId(channel);
const usernameIsId = isUserId(username);
@ -29,18 +31,22 @@ export function useAvailableLogs(channel: string | null, username: string | null
return response;
}
setState({ ...state, error: true });
if (response.status === 403) {
throw new OptOutError();
}
throw Error(response.statusText);
}).then(response => response.json())
.then((data: { availableLogs: AvailableLogs }) => data.availableLogs)
.catch(() => {
setState({ ...state, error: true });
return [];
.then((data: { availableLogs: AvailableLogs }) => [data.availableLogs, undefined])
.catch((err) => {
return [[], err];
});
}
return [];
return [[], undefined];
}, { refetchOnWindowFocus: false, refetchOnReconnect: false });
return data ?? [];
return data ?? [[], undefined];
}