- 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
83 lines
3.1 KiB
Go
83 lines
3.1 KiB
Go
package moon
|
|
|
|
import (
|
|
"github.com/NicoNex/echotron/v3"
|
|
"github.com/akyoto/cache"
|
|
)
|
|
|
|
// MyConfig describes bot config file format.
|
|
type MyConfig struct {
|
|
Token string `json:"token,omitempty"`
|
|
LogLevel string `json:"loglevel,omitempty"`
|
|
LogFile string `json:"logfile,omitempty"`
|
|
|
|
// ChatLogs is the directory for per-chat, per-day chat event logs. Empty
|
|
// disables chat logging. If set, the directory must already exist: a path
|
|
// that does not is a typo, and silently logging nowhere is the worst
|
|
// possible response to one.
|
|
ChatLogs string `json:"chat_logs,omitempty"`
|
|
}
|
|
|
|
// tgClient is the subset of the Telegram API that bunny-bot actually calls.
|
|
// It exists so tests can substitute a fake; echotron.API satisfies it.
|
|
type tgClient interface {
|
|
SendMessage(text string, chatID int64, opts *echotron.MessageOptions) (echotron.APIResponseMessage, error)
|
|
BanChatMember(chatID int64, userID int64, opts *echotron.BanOptions) (echotron.APIResponseBool, error)
|
|
DeleteMessage(chatID int64, messageID int) (echotron.APIResponseBase, error)
|
|
}
|
|
|
|
// chatUser identifies a user's membership of one specific chat. The same user is
|
|
// a different identity in every chat: being silent in one says nothing about
|
|
// another, and answering in one must not rescue them in another.
|
|
type chatUser struct {
|
|
chatID int64
|
|
userID int64
|
|
}
|
|
|
|
// banOutcome is what a squash attempt actually did. The three cases are distinct
|
|
// on purpose: a duplicate ban is a success, not a failure, and reporting it as
|
|
// one makes a working ban look broken in the logs.
|
|
type banOutcome int
|
|
|
|
// eventCache is a typed wrapper over akyoto/cache.Cache that stores values of
|
|
// type T keyed by a Unix timestamp in microseconds. It hides the underlying
|
|
// sync.Map so callers never deal with interface{} keys/values or per-access type
|
|
// conversions.
|
|
type eventCache[T any] struct {
|
|
cache *cache.Cache
|
|
}
|
|
|
|
// joinRef is one stale join, kept together with everything the verdict and the
|
|
// retraction need so neither has to look anything up.
|
|
type joinRef struct {
|
|
micros int64
|
|
|
|
// chat and user come from the join event itself, so a ban can be logged with
|
|
// the newcomer's real name and the chat's real title instead of a cache
|
|
// lookup that may have expired or never been populated.
|
|
chat echotron.Chat
|
|
user *echotron.User
|
|
|
|
// greetingID is the question we asked them, deleted once they are banned for
|
|
// never answering it. Zero means we never managed to ask.
|
|
greetingID int
|
|
}
|
|
|
|
// pendingGreeting is one recorded join: the message that announced it, and the
|
|
// id of the greeting we sent in reply.
|
|
//
|
|
// The greeting id lives here rather than in a second cache, so that recording,
|
|
// forgetting and retracting are all one lookup on one key. A zero greeting id
|
|
// means "no greeting was ever delivered for this join" — never a failure.
|
|
type pendingGreeting struct {
|
|
join *echotron.Message
|
|
greetingID int
|
|
}
|
|
|
|
// retractOutcome is what a retraction pass actually achieved. 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.
|
|
type retractOutcome int
|
|
|
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|