mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Rewrite from TypeScript to Go
Complete rewrite of the Freebee Matrix bot as GogoBee using mautrix-go. - E2EE with goolm (pure Go, no CGo/libolm) and cross-signing bootstrap - 35+ plugins with dependency injection and ordered registration - SQLite storage via modernc.org/sqlite (no CGo) - Scheduler via robfig/cron for WOTD, holidays, birthdays, releases, etc. - Optional LLM integration (Ollama) for sentiment, profanity, roasts, vibes - Threaded trivia, three-tier profanity tracking, WOTD usage verification - Multi-country holiday support with deduplication - Quadratic XP curve, configurable bot display name, encrypted DMs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
95
internal/plugin/ratelimits.go
Normal file
95
internal/plugin/ratelimits.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"gogobee/internal/db"
|
||||
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
// RateLimitsPlugin provides rate-limiting utilities for other plugins.
|
||||
type RateLimitsPlugin struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// NewRateLimitsPlugin creates a new rate limits plugin.
|
||||
func NewRateLimitsPlugin(client *mautrix.Client) *RateLimitsPlugin {
|
||||
return &RateLimitsPlugin{
|
||||
Base: NewBase(client),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *RateLimitsPlugin) Name() string { return "ratelimits" }
|
||||
|
||||
func (p *RateLimitsPlugin) Commands() []CommandDef { return nil }
|
||||
|
||||
func (p *RateLimitsPlugin) Init() error { return nil }
|
||||
|
||||
func (p *RateLimitsPlugin) OnMessage(_ MessageContext) error { return nil }
|
||||
|
||||
func (p *RateLimitsPlugin) OnReaction(_ ReactionContext) error { return nil }
|
||||
|
||||
// CheckLimit returns true if the user is under the rate limit for the given action.
|
||||
// Admin users always bypass rate limits.
|
||||
func (p *RateLimitsPlugin) CheckLimit(userID id.UserID, action string, maxPerDay int) bool {
|
||||
if p.IsAdmin(userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
d := db.Get()
|
||||
today := time.Now().UTC().Format("2006-01-02")
|
||||
|
||||
// Atomic check-and-increment: only increment if under limit
|
||||
// This avoids the TOCTOU race between SELECT and UPDATE
|
||||
_, err := d.Exec(
|
||||
`INSERT INTO rate_limits (user_id, action, date, count) VALUES (?, ?, ?, 1)
|
||||
ON CONFLICT(user_id, action, date) DO UPDATE SET count = count + 1
|
||||
WHERE count < ?`,
|
||||
string(userID), action, today, maxPerDay,
|
||||
)
|
||||
if err != nil {
|
||||
slog.Error("ratelimits: increment", "err", err)
|
||||
return true // Fail open on error
|
||||
}
|
||||
|
||||
// Check current count to determine if we're over limit
|
||||
var count int
|
||||
err = d.QueryRow(
|
||||
`SELECT count FROM rate_limits WHERE user_id = ? AND action = ? AND date = ?`,
|
||||
string(userID), action, today,
|
||||
).Scan(&count)
|
||||
if err != nil {
|
||||
return true // Fail open
|
||||
}
|
||||
|
||||
return count <= maxPerDay
|
||||
}
|
||||
|
||||
// Remaining returns how many uses remain for the given action today.
|
||||
// Admin users get maxPerDay (effectively unlimited).
|
||||
func (p *RateLimitsPlugin) Remaining(userID id.UserID, action string, maxPerDay int) int {
|
||||
if p.IsAdmin(userID) {
|
||||
return maxPerDay
|
||||
}
|
||||
|
||||
d := db.Get()
|
||||
today := time.Now().UTC().Format("2006-01-02")
|
||||
|
||||
var count int
|
||||
err := d.QueryRow(
|
||||
`SELECT count FROM rate_limits WHERE user_id = ? AND action = ? AND date = ?`,
|
||||
string(userID), action, today,
|
||||
).Scan(&count)
|
||||
if err != nil {
|
||||
count = 0
|
||||
}
|
||||
|
||||
remaining := maxPerDay - count
|
||||
if remaining < 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
Reference in New Issue
Block a user