Global code ovehaul - remove CAS and other, add 15 minute greet check
- Remove CAS, as it no longer works for us - Remove other checks - Implement 15 minute greet timeout check - Fix chat logos - Ditch thirdparty logger, use stdlib log facility wrapper for logging
This commit is contained in:
@@ -2,48 +2,143 @@ package moon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/akyoto/cache"
|
||||
"github.com/cockroachdb/pebble"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
// greetCacheTTL is how long a join event or a chat message is retained. It
|
||||
// is deliberately larger than greetReplyWindow so that a sweep occurring at
|
||||
// the far end of the window still has the data it needs to decide.
|
||||
greetCacheTTL = 17 * time.Minute
|
||||
|
||||
// greetReplyWindow is how long a newcomer has to answer the greeting before
|
||||
// being treated as a spammer.
|
||||
greetReplyWindow = 15 * time.Minute
|
||||
|
||||
// greetSweepInterval is how often the sweeper runs.
|
||||
greetSweepInterval = 1 * time.Minute
|
||||
)
|
||||
|
||||
// signalExitBase is the offset a signal's exit status is derived from. The Unix
|
||||
// convention is 128 + n, where n is the signal number, so SIGINT is 130, SIGTERM
|
||||
// is 143 and SIGQUIT is 131 — what a shell reports for a process killed by a
|
||||
// signal, which is exactly what these statuses imitate.
|
||||
const signalExitBase = 128
|
||||
|
||||
// The outcomes of a squash attempt. Names state what happened, not how it was
|
||||
// detected, so callers never have to reinterpret them.
|
||||
const (
|
||||
// banApplied means Telegram accepted the ban.
|
||||
banApplied banOutcome = iota
|
||||
|
||||
// banSuppressed means we did not call the API at all: the debounce already
|
||||
// holds this user, i.e. we banned them moments ago and they are already out.
|
||||
banSuppressed
|
||||
|
||||
// banFailed means we called the API and it refused. The user is still in the
|
||||
// chat and was not retried.
|
||||
banFailed
|
||||
)
|
||||
|
||||
// The outcomes of retracting a greeting a banned member never answered. The three
|
||||
// cases are distinct because "nothing to delete" and "tried and failed" call for
|
||||
// different log lines, and only the second is worth an operator's attention.
|
||||
const (
|
||||
// nothingToRetract means no remembered greeting id was greater than zero, so
|
||||
// the API was never called.
|
||||
nothingToRetract retractOutcome = iota
|
||||
|
||||
// greetingRetracted means every remembered greeting was deleted.
|
||||
greetingRetracted
|
||||
|
||||
// greetingRetractFailed means at least one deletion was refused by Telegram.
|
||||
// The others still went: a failure never aborts the rest.
|
||||
greetingRetractFailed
|
||||
)
|
||||
|
||||
var (
|
||||
// htmlReplacer escapes the three characters HTML requires, all three pairs at
|
||||
// once so no ordering can be got wrong: "&" is never re-escaped by the "<" or
|
||||
// ">" rules. Created once because a Replacer is immutable and safe to share.
|
||||
htmlReplacer = strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||
|
||||
Cfg MyConfig
|
||||
SigChan = make(chan os.Signal, 1)
|
||||
Log *zap.SugaredLogger
|
||||
Tg echotron.API
|
||||
Tg tgClient
|
||||
|
||||
// Мапка с открытыми дескрипторами баз настроек.
|
||||
DB = make(map[string]*pebble.DB)
|
||||
// allowedUpdates is the set of update types Telegram is asked to deliver.
|
||||
//
|
||||
// It lives here, not inline in Telegram(), because it must stay in step with
|
||||
// the branches of telegramMsgParser: a type missing from this list makes its
|
||||
// branch unreachable, and nothing at compile time or in the test suite says
|
||||
// so. TestEveryDispatchedUpdateTypeIsRequested exists to catch exactly that.
|
||||
//
|
||||
// chat_join_request is deliberately absent. The community is open and join
|
||||
// requests are not moderated; Telegram auto-approves them when the type is
|
||||
// not requested, which is the intended behaviour.
|
||||
allowedUpdates = []echotron.UpdateType{
|
||||
"message",
|
||||
"edited_message",
|
||||
"chat_member",
|
||||
}
|
||||
|
||||
// SquashedMembers struct with banned members.
|
||||
// SquashedMembers holds members recently banned by us, used as a ban debounce.
|
||||
SquashedMembers = cache.New(1 * time.Minute)
|
||||
|
||||
// NewMembers struct with new members appeared via message with new_chat_members field set.
|
||||
NewMembers = cache.New(1 * time.Minute)
|
||||
// PendingGreetings holds recorded joins keyed by join timestamp in
|
||||
// microseconds. Used by the greeting sweeper to find newcomers who never
|
||||
// answered, and to retract the greetings we asked them.
|
||||
PendingGreetings = newEventCache[pendingGreeting](1 * time.Minute)
|
||||
|
||||
// AppearedMembers struct with new members appeared via ChatMemberUpdated event.
|
||||
AppearedMembers = cache.New(60 * time.Minute)
|
||||
// GreetAnswers holds text messages keyed by message timestamp in
|
||||
// microseconds. Used by the greeting sweeper to find proof that a newcomer
|
||||
// answered the greeting.
|
||||
GreetAnswers = newEventCache[*echotron.Message](1 * time.Minute)
|
||||
|
||||
// PendingCASMembers contains newly comed members that pass first cas check, but still waiting second cas check.
|
||||
PendingCASMembers = cache.New(60 * time.Minute)
|
||||
// closeOnce guards closeCaches: cache.Cache.Close() sends on an unbuffered
|
||||
// channel and therefore must be called exactly once per cache; calling it
|
||||
// twice deadlocks. Each cache is guarded so a failure partway through cannot
|
||||
// turn into a hang on the second attempt.
|
||||
closeOnce sync.Once
|
||||
|
||||
// GreetPending contains people that should answer to bot greet.
|
||||
GreetPending = cache.New(1 * time.Minute)
|
||||
// stopRequested is closed once, by shutdown, to ask every goroutine that
|
||||
// loops to return. One signal for all of them, rather than one per worker:
|
||||
// several workers can come and go (and tests start them deliberately), and
|
||||
// a per-worker channel would have to be closed exactly once each, which is a
|
||||
// panic waiting to happen.
|
||||
stopRequested = make(chan struct{})
|
||||
|
||||
// NoMessagePeople contains people that are never say something in chat.
|
||||
NoMessagePeople = cache.New(1 * time.Minute)
|
||||
// workers counts the running background goroutines — the update listener and
|
||||
// the greeting sweeper. shutdown waits on it, so no worker can be touching a
|
||||
// cache, the logs or the API while the teardown closes them.
|
||||
workers sync.WaitGroup
|
||||
|
||||
// People cache with users info.
|
||||
People = cache.New(8 * time.Hour)
|
||||
// workersMu guards workers and stopping. A worker must register before
|
||||
// shutdown starts waiting, and a goroutine that tries to start afterwards is
|
||||
// refused instead of racing WaitGroup.Add against Wait.
|
||||
workersMu sync.Mutex
|
||||
stopping bool
|
||||
|
||||
// Groups cache with groups info.
|
||||
Groups = cache.New(8 * time.Hour)
|
||||
// runningWorkers counts registered, not-yet-finished workers, so a caller
|
||||
// can tell "nothing started yet" from "started and still going".
|
||||
runningWorkers atomic.Int32
|
||||
|
||||
ChatList = make([]int64, 0)
|
||||
// exitCode carries the status the process should exit with on a clean
|
||||
// return from run(): zero while the bot is running and sleeping normally,
|
||||
// 128 + signal number once a termination signal has begun the shutdown.
|
||||
// It is an atomic because the handler goroutine writes it and main reads
|
||||
// it, and plain avoids any question of which goroutine wins.
|
||||
exitCode atomic.Int32
|
||||
|
||||
// shutdownOnce guards shutdown so two termination signals arriving together
|
||||
// cannot run the sequence twice.
|
||||
shutdownOnce sync.Once
|
||||
)
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
|
||||
Reference in New Issue
Block a user