diff --git a/Dockerfile b/Dockerfile index dc3fe27..5966f4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM golang:1.17-alpine AS builder +FROM --platform=${BUILDPLATFORM} golang:1.18-alpine AS builder +ARG TARGETOS +ARG TARGETARCH # Git is required for getting the dependencies. # hadolint ignore=DL3018 @@ -19,6 +21,8 @@ COPY ./ ./ RUN export TAG=$(git describe --tags "$(git rev-list --tags --max-count=1)") && \ export COMMIT=$(git rev-parse --short HEAD) && \ CGO_ENABLED=0 \ + GOOS=${TARGETOS} \ + GOARCH=${TARGETARCH} \ go build -installsuffix 'static' \ -ldflags="-X main.version=${TAG} -X main.commit=${COMMIT}" \ -o /app . diff --git a/galactus/modify.go b/galactus/modify.go index ae59d21..2106671 100644 --- a/galactus/modify.go +++ b/galactus/modify.go @@ -9,17 +9,23 @@ import ( "time" ) -func (tokenProvider *TokenProvider) attemptOnSecondaryTokens(guildID, userID string, tokens []string, limit int, request task.UserModify) bool { - if tokens != nil && limit > 0 { - sess, hToken := tokenProvider.getAnySession(guildID, tokens, limit) +func (tokenProvider *TokenProvider) attemptOnSecondaryTokens(guildID, userID string, tokenSubset map[string]struct{}, request task.UserModify) string { + if len(tokenProvider.activeSessions) > 0 { + sess, hToken := tokenProvider.getSession(guildID, tokenSubset) if sess != nil { err := task.ApplyMuteDeaf(sess, guildID, userID, request.Mute, request.Deaf) if err != nil { log.Println("Failed to apply mute to player with error:") log.Println(err) + + // don't attempt this token for this guild for another 5 minutes + err = tokenProvider.BlacklistTokenForDuration(guildID, hToken, UnresponsiveCaptureBlacklistDuration) + if err != nil { + log.Println(err) + } } else { log.Printf("Successfully applied mute=%v, deaf=%v to User %d using secondary bot: %s\n", request.Mute, request.Deaf, request.UserID, hToken) - return true + return hToken } } else { log.Println("No secondary bot tokens found. Trying other methods") @@ -27,7 +33,7 @@ func (tokenProvider *TokenProvider) attemptOnSecondaryTokens(guildID, userID str } else { log.Println("Guild has no access to secondary bot tokens; skipping") } - return false + return "" } func (tokenProvider *TokenProvider) attemptOnCaptureBot(guildID, connectCode string, gid uint64, timeout time.Duration, request task.UserModify) bool { diff --git a/galactus/server.go b/galactus/server.go index a2a2293..e72de16 100644 --- a/galactus/server.go +++ b/galactus/server.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" - "errors" "github.com/automuteus/utils/pkg/premium" "github.com/automuteus/utils/pkg/rediskey" "github.com/automuteus/utils/pkg/task" @@ -13,12 +12,12 @@ import ( "github.com/bwmarrin/discordgo" "github.com/go-redis/redis/v8" "github.com/gorilla/mux" + "golang.org/x/exp/constraints" "io/ioutil" "log" "net/http" "os" "strconv" - "strings" "sync" "time" ) @@ -34,8 +33,6 @@ var PremiumBotConstraints = map[premium.Tier]int{ const DefaultCaptureBotTimeout = time.Second -var ctx = context.Background() - type TokenProvider struct { client *redis.Client primarySession *discordgo.Session @@ -44,6 +41,8 @@ type TokenProvider struct { activeSessions map[string]*discordgo.Session maxRequests5Seconds int64 sessionLock sync.RWMutex + + botVerificationQueue chan botVerifyTask } func NewTokenProvider(botToken, redisAddr, redisUser, redisPass string, maxReq int64) *TokenProvider { @@ -79,11 +78,32 @@ func NewTokenProvider(botToken, redisAddr, redisUser, redisPass string, maxReq i } return &TokenProvider{ - client: rdb, - primarySession: dg, - activeSessions: make(map[string]*discordgo.Session), - maxRequests5Seconds: maxReq, - sessionLock: sync.RWMutex{}, + client: rdb, + primarySession: dg, + activeSessions: make(map[string]*discordgo.Session), + maxRequests5Seconds: maxReq, + sessionLock: sync.RWMutex{}, + botVerificationQueue: make(chan botVerifyTask), + } +} + +func (tokenProvider *TokenProvider) BotVerificationWorker() { + log.Println("Premium bot verification worker started") + for { + verifyTask := <-tokenProvider.botVerificationQueue + + if tokenProvider.canRunBotVerification(verifyTask.guildID) { + // always send nil tokens used; we can't populate this info from anywhere anyways + tokenProvider.verifyBotMembership(verifyTask.guildID, verifyTask.limit, nil) + + err := tokenProvider.markBotVerificationLockout(verifyTask.guildID) + if err != nil { + log.Println(err) + } + + // cheap ratelimiting; only process verifications once per second + time.Sleep(time.Second) + } } } @@ -91,14 +111,8 @@ func rateLimitEventCallback(sess *discordgo.Session, rl *discordgo.RateLimit) { log.Println(rl.Message) } -func (tokenProvider *TokenProvider) PopulateAndStartSessions() { - keys, err := tokenProvider.client.HGetAll(ctx, rediskey.AllTokensHSet).Result() - if err != nil { - log.Println(err) - return - } - - for _, v := range keys { +func (tokenProvider *TokenProvider) PopulateAndStartSessions(tokens []string) { + for _, v := range tokens { tokenProvider.openAndStartSessionWithToken(v) } } @@ -123,7 +137,7 @@ func (tokenProvider *TokenProvider) openAndStartSessionWithToken(botToken string return false } // associates the guilds with this token to be used for requests - sess.AddHandler(tokenProvider.newGuild(k)) + sess.AddHandler(tokenProvider.newGuild) log.Println("Opened session on startup for " + k) tokenProvider.activeSessions[k] = sess return true @@ -131,45 +145,40 @@ func (tokenProvider *TokenProvider) openAndStartSessionWithToken(botToken string return false } -func (tokenProvider *TokenProvider) getAllTokensForGuild(guildID string) []string { - hTokens, err := tokenProvider.client.SMembers(context.Background(), rediskey.GuildTokensKey(guildID)).Result() - if err != nil { - return nil - } - return hTokens -} - -func (tokenProvider *TokenProvider) getAnySession(guildID string, tokens []string, limit int) (*discordgo.Session, string) { +func (tokenProvider *TokenProvider) getSession(guildID string, hTokenSubset map[string]struct{}) (*discordgo.Session, string) { tokenProvider.sessionLock.RLock() defer tokenProvider.sessionLock.RUnlock() - for i, hToken := range tokens { - if i == limit { - return nil, "" - } - // if this token isn't potentially rate-limited - if tokenProvider.IncrAndTestGuildTokenComboLock(guildID, hToken) { - sess, ok := tokenProvider.activeSessions[hToken] - if ok { + for hToken, sess := range tokenProvider.activeSessions { + // if we have already used this token successfully, or haven't set any restrictions + if hTokenSubset == nil || mapHasEntry(hTokenSubset, hToken) { + // if this token isn't potentially rate-limited + if tokenProvider.IncrAndTestGuildTokenComboLock(guildID, hToken) { return sess, hToken + } else { + log.Println("Secondary token is potentially rate-limited. Skipping") } - // remove this key from our records and keep going - tokenProvider.client.SRem(context.Background(), rediskey.GuildTokensKey(guildID), hToken) - } else { - log.Println("Secondary token is potentially rate-limited. Skipping") } } return nil, "" } +func mapHasEntry[T constraints.Ordered, K any](dict map[T]K, key T) bool { + if dict == nil { + return false + } + _, ok := dict[key] + return ok +} + func (tokenProvider *TokenProvider) IncrAndTestGuildTokenComboLock(guildID, hashToken string) bool { i, err := tokenProvider.client.Incr(context.Background(), rediskey.GuildTokenLock(guildID, hashToken)).Result() if err != nil { log.Println(err) } usable := i < tokenProvider.maxRequests5Seconds - log.Printf("Token %s on guild %s is at count %d. Using: %v", hashToken, guildID, i, usable) + log.Printf("Token/capture %s on guild %s is at count %d. Using?: %v", hashToken, guildID, i, usable) if !usable { return false } @@ -182,6 +191,11 @@ func (tokenProvider *TokenProvider) IncrAndTestGuildTokenComboLock(guildID, hash return true } +// BlacklistTokenForDuration sets a guild token (or connect code ala capture bot) to the maximum value allowed before +// attempting other non-rate-limited mute/deafen methods. +// NOTE: this will manifest as the capture/token in question appearing like it "has been used times" in logs, +// even if this is not technically accurate. A more accurate approach would probably use a totally separate Redis key, +// as opposed to this approach, which simply uses the ratelimiting counter key(s) to achieve blacklisting func (tokenProvider *TokenProvider) BlacklistTokenForDuration(guildID, hashToken string, duration time.Duration) error { return tokenProvider.client.Set(context.Background(), rediskey.GuildTokenLock(guildID, hashToken), tokenProvider.maxRequests5Seconds, duration).Err() } @@ -240,7 +254,6 @@ func (tokenProvider *TokenProvider) Run(port string) { } limit := PremiumBotConstraints[userModifications.Premium] - tokens := tokenProvider.getAllTokensForGuild(guildID) tasksChannel := make(chan task.UserModify, len(userModifications.Users)) wg := sync.WaitGroup{} @@ -251,7 +264,9 @@ func (tokenProvider *TokenProvider) Run(port string) { Official: 0, RateLimit: 0, } + uniqueTokensUsed := make(map[string]struct{}) lock := sync.Mutex{} + tokenLock := sync.RWMutex{} errors := 0 // start a handful of workers to handle the tasks @@ -259,13 +274,27 @@ func (tokenProvider *TokenProvider) Run(port string) { go func() { for request := range tasksChannel { userIDStr := strconv.FormatUint(request.UserID, 10) - success := tokenProvider.attemptOnSecondaryTokens(guildID, userIDStr, tokens, limit, request) - if success { + hToken := "" + if limit > 0 { + tokenLock.RLock() + if len(uniqueTokensUsed) >= limit { + hToken = tokenProvider.attemptOnSecondaryTokens(guildID, userIDStr, uniqueTokensUsed, request) + tokenLock.RUnlock() + } else { + tokenLock.RUnlock() + hToken = tokenProvider.attemptOnSecondaryTokens(guildID, userIDStr, nil, request) + } + } + if hToken != "" { lock.Lock() mdsc.Worker++ lock.Unlock() + + tokenLock.Lock() + uniqueTokensUsed[hToken] = struct{}{} + tokenLock.Unlock() } else { - success = tokenProvider.attemptOnCaptureBot(guildID, connectCode, gid, taskTimeoutms, request) + success := tokenProvider.attemptOnCaptureBot(guildID, connectCode, gid, taskTimeoutms, request) if success { lock.Lock() mdsc.Capture++ @@ -313,66 +342,31 @@ func (tokenProvider *TokenProvider) Run(port string) { log.Println(err) } } + + // note, this should probably be more systematic on startup, not when a mute/deafen task comes in. But this is a + // context in which we already have the guildID, successful tokens, AND the premium limit... + go tokenProvider.verifyBotMembership(guildID, limit, uniqueTokensUsed) }).Methods("POST") - r.HandleFunc("/addtoken", func(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Println(err) + r.HandleFunc("/verify/{guildID}/{premiumTier}", func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + guildID := vars["guildID"] + tierStr := vars["premiumTier"] + _, gerr := strconv.ParseUint(guildID, 10, 64) + if gerr != nil { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + w.Write([]byte("Invalid guildID (non-numeric) received. Query should be of the form POST `/verify//`")) return } - defer r.Body.Close() - - botToken := string(body) - log.Println(botToken) - - k := hashToken(botToken) - log.Println(k) - tokenProvider.sessionLock.RLock() - if _, ok := tokenProvider.activeSessions[k]; ok { - log.Println("Token already exists on the server") - w.WriteHeader(http.StatusAlreadyReported) - w.Write([]byte("Token already exists on the server")) - tokenProvider.sessionLock.RUnlock() - return - } - tokenProvider.sessionLock.RUnlock() - - token.WaitForToken(tokenProvider.client, botToken) - token.LockForToken(tokenProvider.client, botToken) - sess, err := discordgo.New("Bot " + botToken) - if err != nil { + tier, perr := strconv.ParseUint(tierStr, 10, 64) + if perr != nil || tier < 0 || tier > 5 { w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + w.Write([]byte("Invalid premium tier (not [0,5]) received. Query should be of the form POST `/verify//`")) return } - sess.AddHandler(tokenProvider.newGuild(k)) - err = sess.Open() - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err.Error())) - return - } - - tokenProvider.sessionLock.Lock() - tokenProvider.activeSessions[k] = sess - tokenProvider.sessionLock.Unlock() - - err = tokenProvider.client.HSet(ctx, rediskey.AllTokensHSet, k, botToken).Err() - if err != nil { - log.Println(err) - } - - for _, v := range sess.State.Guilds { - err := tokenProvider.client.SAdd(ctx, rediskey.GuildTokensKey(v.ID), k).Err() - if !errors.Is(err, redis.Nil) && err != nil { - log.Println(strings.ReplaceAll(err.Error(), botToken, "")) - } else { - log.Println("Added token for guild " + v.ID) - } - } + limit := PremiumBotConstraints[premium.Tier(tier)] + tokenProvider.enqueueBotMembershipVerifyTask(guildID, limit) + w.WriteHeader(http.StatusOK) }).Methods("POST") r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { @@ -424,20 +418,6 @@ func (tokenProvider *TokenProvider) Close() { tokenProvider.primarySession.Close() } -func (tokenProvider *TokenProvider) newGuild(hashedToken string) func(s *discordgo.Session, m *discordgo.GuildCreate) { - return func(s *discordgo.Session, m *discordgo.GuildCreate) { - tokenProvider.sessionLock.RLock() - for test := range tokenProvider.activeSessions { - if hashedToken == test { - err := tokenProvider.client.SAdd(ctx, rediskey.GuildTokensKey(m.Guild.ID), hashedToken) - if err != nil { - log.Println(err) - } else { - log.Println("Token added for running guild " + m.Guild.ID) - } - } - } - - tokenProvider.sessionLock.RUnlock() - } +func (tokenProvider *TokenProvider) newGuild(s *discordgo.Session, m *discordgo.GuildCreate) { + log.Println("added to " + m.ID) } diff --git a/galactus/verify.go b/galactus/verify.go new file mode 100644 index 0000000..6073fb2 --- /dev/null +++ b/galactus/verify.go @@ -0,0 +1,62 @@ +package galactus + +import ( + "context" + "github.com/automuteus/utils/pkg/rediskey" + "log" + "time" +) + +type botVerifyTask struct { + guildID string + limit int +} + +func (tokenProvider *TokenProvider) enqueueBotMembershipVerifyTask(guildID string, limit int) { + tokenProvider.botVerificationQueue <- botVerifyTask{ + guildID: guildID, + limit: limit, + } +} + +func (tokenProvider *TokenProvider) verifyBotMembership(guildID string, limit int, uniqueTokensUsed map[string]struct{}) { + tokenProvider.sessionLock.RLock() + defer tokenProvider.sessionLock.RUnlock() + + i := 0 + for hToken, sess := range tokenProvider.activeSessions { + // only check tokens that weren't used successfully already (obv we're members if mute/deafen was successful earlier) + if !mapHasEntry(uniqueTokensUsed, hToken) { + _, err := sess.GuildMember(guildID, sess.State.User.ID) + if err != nil { + //log.Println(err) + } else { + i++ // successfully checked self's membership; we are a member of this server + } + + // if the bot is verified as a member of too many servers for the premium status, then we should leave them + if i > limit { + log.Println("Token/Bot " + hToken + " leaving server " + guildID + " due to lack of premium membership") + + err = sess.GuildLeave(guildID) + if err != nil { + log.Println(err) + } + } + } + } +} + +func (tokenProvider *TokenProvider) canRunBotVerification(guildID string) bool { + v, err := tokenProvider.client.Exists(context.Background(), rediskey.GuildPremiumMembershipVerify(guildID)).Result() + if err != nil { + log.Println(err) + return true + } + return v != 1 // 1 = exists, therefore don't run +} + +func (tokenProvider *TokenProvider) markBotVerificationLockout(guildID string) error { + // we only need to check the + return tokenProvider.client.Set(context.Background(), rediskey.GuildPremiumMembershipVerify(guildID), 1, time.Hour*24).Err() +} diff --git a/go.mod b/go.mod index 381d1d9..b1ef28a 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,14 @@ module github.com/automuteus/galactus -go 1.17 +go 1.18 require ( - github.com/automuteus/utils v0.0.31 - github.com/bwmarrin/discordgo v0.22.0 + github.com/automuteus/utils v0.1.1 + github.com/bwmarrin/discordgo v0.24.0 github.com/go-redis/redis/v8 v8.4.2 github.com/googollee/go-socket.io v1.4.4 github.com/gorilla/mux v1.8.0 + golang.org/x/exp v0.0.0-20220323121947-b445f275a754 ) require ( @@ -15,7 +16,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/georgysavva/scany v0.2.7 // indirect - github.com/gorilla/websocket v1.4.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.8.0 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -30,9 +31,9 @@ require ( github.com/stretchr/objx v0.2.0 // indirect github.com/stretchr/testify v1.6.1 // indirect go.opentelemetry.io/otel v0.15.0 // indirect - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index cb32a51..261f77c 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/automuteus/utils v0.0.31 h1:G8hD2a1Lbi4L/U6O4R2WzgE3gcp6GgDJbSJUq+McBkk= -github.com/automuteus/utils v0.0.31/go.mod h1:zNylt7uNde2GKz6SEXT3DKNVcqz8KVhjhDuXnhFT7XQ= -github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= -github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/automuteus/utils v0.1.1 h1:AswW4ehRZkpPn8/1v14nDSi06ywJz2zBIK3BDsIboyk= +github.com/automuteus/utils v0.1.1/go.mod h1:KyrQuXlRB65QWD5qP1ympRTk7RQlYBvPmklgrLgQVf0= +github.com/bwmarrin/discordgo v0.24.0 h1:Gw4MYxqHdvhO99A3nXnSLy97z5pmIKHZVJ1JY5ZDPqY= +github.com/bwmarrin/discordgo v0.24.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= @@ -52,11 +51,10 @@ github.com/googollee/go-socket.io v1.4.4 h1:UWOy//wzcT1ENMDeeVsrXwcCY49XOvC/YHVM github.com/googollee/go-socket.io v1.4.4/go.mod h1:2lMkHRm5GLg158lACi6Zj6535AQaXuyA+IKbfqKzTOM= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -75,7 +73,6 @@ github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye47 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -97,7 +94,6 @@ github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/V github.com/jackc/pgtype v1.3.1-0.20200612023650-09efc3839047/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8= github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= @@ -146,7 +142,6 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -197,7 +192,6 @@ go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKY go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= @@ -206,8 +200,11 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20220323121947-b445f275a754 h1:YxbFk+w0Y3jBsgqHxFbybmS/lViCPBkS5BIVxE+eLZM= +golang.org/x/exp v0.0.0-20220323121947-b445f275a754/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -218,8 +215,9 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -238,8 +236,11 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -255,8 +256,9 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/main.go b/main.go index e3e11d2..d39f427 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "os" "os/signal" "strconv" + "strings" "syscall" ) @@ -20,6 +21,12 @@ func main() { log.Fatal("No DISCORD_BOT_TOKEN specified. Exiting.") } + var extraTokens []string + extraTokenStr := strings.ReplaceAll(os.Getenv("WORKER_BOT_TOKENS"), " ", "") + if extraTokenStr != "" { + extraTokens = strings.Split(extraTokenStr, ",") + } + redisAddr := os.Getenv("REDIS_ADDR") if redisAddr == "" { log.Fatal("No REDIS_ADDR specified. Exiting.") @@ -58,9 +65,13 @@ func main() { } tp := galactus.NewTokenProvider(botToken, redisAddr, redisUser, redisPass, maxReq) - tp.PopulateAndStartSessions() + tp.PopulateAndStartSessions(extraTokens) msgBroker := broker.NewBroker(redisAddr, redisUser, redisPass) + if len(extraTokens) > 0 { + go tp.BotVerificationWorker() + } + sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)