add opt out option
This commit is contained in:
parent
ec9fc8fa18
commit
f9eeebd037
25 changed files with 236 additions and 154 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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 {
|
||||
|
|
16
bot/main.go
16
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 {
|
||||
|
|
|
@ -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
4
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
|
||||
)
|
||||
|
|
7
go.sum
7
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=
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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>
|
3
web/build/static/js/2.0dc1648a.chunk.js
Normal file
3
web/build/static/js/2.0dc1648a.chunk.js
Normal file
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
2
web/build/static/js/main.fb71d3bb.chunk.js
Normal file
2
web/build/static/js/main.fb71d3bb.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
web/build/static/js/main.fb71d3bb.chunk.js.map
Normal file
1
web/build/static/js/main.fb71d3bb.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
--theme2-bright: #3498db;
|
||||
--text: #F5F5F5;
|
||||
--text-dark: #616161;
|
||||
--danger: #e74c3c;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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} />)}
|
||||
|
|
15
web/src/components/OptOutMessage.tsx
Normal file
15
web/src/components/OptOutMessage.tsx
Normal 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>
|
||||
}
|
1
web/src/errors/OptOutError.ts
Normal file
1
web/src/errors/OptOutError.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export class OptOutError extends Error { }
|
|
@ -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];
|
||||
}
|
Loading…
Add table
Reference in a new issue