Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions src/ban.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"log"
"strings"
"sync"
"time"

tgbotapi "github.com/osprogramadores/telegram-bot-api"
)
Expand Down Expand Up @@ -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
}
8 changes: 0 additions & 8 deletions src/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
}
Expand Down
108 changes: 24 additions & 84 deletions src/captcha.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
})
}

Expand Down Expand Up @@ -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()
}
Loading