From 95abc4ff3ffc99a4c0e635cd2f332a93142cb42c Mon Sep 17 00:00:00 2001 From: Marco Paganini Date: Thu, 20 Aug 2026 13:24:07 -0700 Subject: [PATCH] Revert "feat: Add 3-strike anti-spam system for captchas" This reverts commit e154dafb0ef762b3a313c6ce86a2e4ceeecde498. --- src/ban.go | 11 ----- src/bot.go | 8 ---- src/captcha.go | 108 +++++++++++-------------------------------------- 3 files changed, 24 insertions(+), 103 deletions(-) diff --git a/src/ban.go b/src/ban.go index 7d59341..f9b9a44 100644 --- a/src/ban.go +++ b/src/ban.go @@ -5,7 +5,6 @@ import ( "log" "strings" "sync" - "time" tgbotapi "github.com/osprogramadores/telegram-bot-api" ) @@ -325,13 +324,3 @@ func kickUser(bot kickChatMemberer, chatID int64, userID int) error { _, err := bot.KickChatMember(tgbotapi.KickChatMemberConfig{ChatMemberConfig: memberConfig}) return err } - -// kickUserUntil bans a user until a specific time. -func kickUserUntil(bot kickChatMemberer, chatID int64, userID int, until time.Time) error { - memberConfig := tgbotapi.ChatMemberConfig{ChatID: chatID, UserID: userID} - _, err := bot.KickChatMember(tgbotapi.KickChatMemberConfig{ - ChatMemberConfig: memberConfig, - UntilDate: until.Unix(), - }) - return err -} diff --git a/src/bot.go b/src/bot.go index 649c8b0..b14e387 100644 --- a/src/bot.go +++ b/src/bot.go @@ -33,9 +33,6 @@ type opBot struct { // List of users not yet validated by captcha. pendingCaptcha *pendingCaptchaType - // Track captcha failures across sessions - captchaFails *captchaFailures - // Don't send warning messages to new users on every infraction. newUserWarningCache *cache.Cache @@ -86,7 +83,6 @@ func newOpBot(config botConfig) (opBot, error) { newUserCache: cache.New(duration, duration), pendingCaptcha: newPendingCaptchaType(), - captchaFails: newCaptchaFailures(), // How often will re-send warning messages to offending new users. newUserWarningCache: cache.New(30*time.Minute, time.Hour), @@ -225,11 +221,7 @@ func (x *opBot) Run(bot *tgbotapi.BotAPI) { // and exit normally. promCaptchaValidatedCount.Inc() x.pendingCaptcha.del(userid) - x.captchaFails.reset(userid) x.sendWelcome(bot, update, *update.Message.From) - } else { - promCaptchaFailedCount.Inc() - x.handleCaptchaFailure(bot, chatid, msgid, *update.Message.From) } continue } diff --git a/src/captcha.go b/src/captcha.go index 203784d..9b3c744 100644 --- a/src/captcha.go +++ b/src/captcha.go @@ -143,7 +143,7 @@ func (x *opBot) captchaReaper(bot tgbotInterface, chatID int64, user tgbotapi.Us name := nameRef(user) // check if this user is already banned and possibly skip some of the following steps. - _, err := isBanned(bot, chatID, user.ID) + banned, err := isBanned(bot, chatID, user.ID) if err != nil { log.Printf("Warning: Unable to get information for user %s (uid=%d): %v", name, user.ID, err) } @@ -152,7 +152,29 @@ func (x *opBot) captchaReaper(bot tgbotInterface, chatID int64, user tgbotapi.Us // in the pending captcha list, meaning they didn't confirm the // captcha. - x.handleCaptchaFailure(bot, chatID, 0, user) + // Let's remove the pending request. + defer x.pendingCaptcha.del(user.ID) + + // Do nothing if user is already kicked (probably by an admin). + if banned { + log.Printf("User %s (uid=%d) has already been banned (by admin?) Not unbanning.", name, user.ID) + return + } + + // As the user is not yet banned, proceed to kick+unban. + // Kick user and remove from list. + _, err = sendMessage(bot, chatID, fmt.Sprintf(T("no_captcha_received"), name)) + if err != nil { + log.Printf("Warning: Unable to send 'invalid captcha' message.") + } + + if err = kickUser(bot, chatID, user.ID); err != nil { + log.Printf("Warning: Unable to kick user %s (uid=%d) out of the channel.", name, user.ID) + } + + if err = unBanUser(bot, chatID, user.ID); err != nil { + log.Printf("Warning: Unable to UNBAN user %s (uid=%d) (May be locked out.)", name, user.ID) + } }) } @@ -221,85 +243,3 @@ func bincode(s string) []byte { func captchaResendRequest(s string) bool { return strings.EqualFold(s, T("another_captcha")) } - -// handleCaptchaFailure deals with users who failed the captcha (timeout or wrong answer). -func (x *opBot) handleCaptchaFailure(bot tgbotInterface, chatID int64, messageID int, user tgbotapi.User) { - name := nameRef(user) - fails := x.captchaFails.increment(user.ID) - - log.Printf("User %s (uid=%d) failed captcha. Total fails: %d", name, user.ID, fails) - - // Remove from pending - x.pendingCaptcha.del(user.ID) - - banned, err := isBanned(bot, chatID, user.ID) - if err != nil { - log.Printf("Warning: Unable to get information for user %s (uid=%d): %v", name, user.ID, err) - } - - if banned { - log.Printf("User %s (uid=%d) has already been banned. Not doing anything.", name, user.ID) - return - } - - switch fails { - case 1: - sendMessage(bot, chatID, fmt.Sprintf(T("captcha_fail_1"), name)) - kickUser(bot, chatID, user.ID) - unBanUser(bot, chatID, user.ID) - case 2: - sendMessage(bot, chatID, fmt.Sprintf(T("captcha_fail_2"), name)) - kickUser(bot, chatID, user.ID) - unBanUser(bot, chatID, user.ID) - case 3: - sendMessage(bot, chatID, fmt.Sprintf(T("captcha_fail_3"), name)) - kickUserUntil(bot, chatID, user.ID, time.Now().Add(24*time.Hour)) - default: - sendMessage(bot, chatID, fmt.Sprintf(T("captcha_fail_max"), name)) - banUser(bot, chatID, user.ID) - } -} - -const captchaFailuresDB = "captcha_failures.json" - -type captchaFailures struct { - sync.RWMutex - Failures map[int]int `json:"failures"` -} - -func newCaptchaFailures() *captchaFailures { - cf := &captchaFailures{ - Failures: map[int]int{}, - } - cf.load() - return cf -} - -func (c *captchaFailures) load() { - c.Lock() - defer c.Unlock() - readJSONFromDataDir(&c.Failures, captchaFailuresDB) - if c.Failures == nil { - c.Failures = map[int]int{} - } -} - -func (c *captchaFailures) save() error { - return safeWriteJSON(c.Failures, captchaFailuresDB) -} - -func (c *captchaFailures) increment(userID int) int { - c.Lock() - defer c.Unlock() - c.Failures[userID]++ - fails := c.Failures[userID] - c.save() - return fails -} - -func (c *captchaFailures) reset(userID int) { - c.Lock() - defer c.Unlock() - delete(c.Failures, userID) - c.save() -}