264 lines
7.9 KiB
Go
264 lines
7.9 KiB
Go
package middlewares
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.maximotejeda.com/maximo/telegram-base-bot/config"
|
|
"git.maximotejeda.com/maximo/telegram-base-bot/internal/application/helpers"
|
|
"git.maximotejeda.com/maximo/telegram-base-bot/internal/ports"
|
|
"github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
)
|
|
|
|
var log = slog.New(slog.NewJSONHandler(os.Stderr, nil))
|
|
|
|
func ShowMessageWithUserID(next bot.HandlerFunc) bot.HandlerFunc {
|
|
return func(ctx context.Context, bot *bot.Bot, update *models.Update) {
|
|
if update.Message != nil {
|
|
log.Info("User new message", fmt.Sprintf("%d", update.Message.From.ID), update.Message.Text)
|
|
}
|
|
next(ctx, bot, update)
|
|
}
|
|
}
|
|
|
|
// singleFlight is a middleware that ensures that only one callback query is processed at a time.
|
|
// example from https://github.com/go-telegram/bot/blob/main/examples/middleware/main.go
|
|
func SingleFlight(next bot.HandlerFunc) bot.HandlerFunc {
|
|
sf := sync.Map{}
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
if update.CallbackQuery != nil {
|
|
key := update.CallbackQuery.Message.Message.ID
|
|
if _, loaded := sf.LoadOrStore(key, struct{}{}); loaded {
|
|
b.SendMessage(ctx, &bot.SendMessageParams{ChatID: update.CallbackQuery.From.ID, Text: "Query on flight, please wait"})
|
|
return
|
|
}
|
|
defer sf.Delete(key)
|
|
next(ctx, b, update)
|
|
}else if update.Message != nil {
|
|
key := update.Message.From.ID
|
|
if _, loaded := sf.LoadOrStore(key, struct{}{}); loaded{
|
|
log.Debug("key alredy loaded", "key", key)
|
|
b.SendMessage(ctx, &bot.SendMessageParams{ChatID: update.Message.From.ID, Text: "IA esta aun procesando su respuesta, por favor espere!!!"})
|
|
return
|
|
}else{
|
|
log.Debug("key not loaded", "key", key)
|
|
}
|
|
defer sf.Delete(key)
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
}
|
|
|
|
func LogMessage(next bot.HandlerFunc) bot.HandlerFunc {
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
txt := ""
|
|
user := &models.User{}
|
|
if update.CallbackQuery == nil {
|
|
txt = update.Message.Text
|
|
user = update.Message.From
|
|
} else {
|
|
txt = fmt.Sprintf("%#v", update.CallbackQuery)
|
|
user = &update.CallbackQuery.From
|
|
}
|
|
log.Info(txt, "user", user.Username)
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
|
|
func NotifyAdmin(next bot.HandlerFunc) bot.HandlerFunc {
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
|
|
// SetAuthRequired
|
|
// set authorization middleware
|
|
// authorization will be set to an amount of time in minutes
|
|
func SetAuthRequired(svc ports.UserService, log *slog.Logger) func(bot.HandlerFunc) bot.HandlerFunc {
|
|
userLastAuth := sync.Map{}
|
|
return func(next bot.HandlerFunc) bot.HandlerFunc {
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
var key int64
|
|
if update.CallbackQuery == nil {
|
|
if update.MessageReaction == nil {
|
|
key = update.Message.From.ID
|
|
}
|
|
} else {
|
|
key = update.CallbackQuery.From.ID
|
|
}
|
|
log.Debug("executing auth func","user", key)
|
|
authMe := func() {
|
|
k := helpers.Authenticate(ctx, log, b, update, svc)
|
|
if !k {
|
|
log.Info("user not Authenticated", "user", key)
|
|
return
|
|
}
|
|
log.Debug("storing user last auth to map")
|
|
userLastAuth.Store(key, time.Now())
|
|
log.Debug("user Authenticated", "user", key, "time", time.Now())
|
|
next(ctx, b, update)
|
|
}
|
|
|
|
if _, loaded := userLastAuth.LoadOrStore(key, time.Now()); loaded {
|
|
when, _ := userLastAuth.Load(key)
|
|
switch {
|
|
case time.Since(when.(time.Time)).Minutes() < 3: // the time user will remain auth on the bot
|
|
log.Debug("user on cache available", "user", key, "time", when)
|
|
next(ctx, b, update)
|
|
return
|
|
default:
|
|
log.Debug("user last auth is more than x min, auth again")
|
|
authMe()
|
|
|
|
}
|
|
} else {
|
|
log.Debug("user not auth racently, authenticating")
|
|
authMe()
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func RateLimitUser(next bot.HandlerFunc) bot.HandlerFunc {
|
|
rl := sync.Map{}
|
|
type data struct {
|
|
when time.Time
|
|
amount int64
|
|
}
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
var (
|
|
key int64
|
|
rLimitTime = config.GetRateLimitSec()
|
|
rLimitAmnt = config.GetRateLimitAmount()
|
|
)
|
|
|
|
if update.CallbackQuery == nil {
|
|
key = update.Message.From.ID
|
|
} else {
|
|
key = update.CallbackQuery.From.ID
|
|
}
|
|
log.Info("got key ", "key ", key)
|
|
if _, loaded := rl.LoadOrStore(key, data{when: time.Now(), amount: 0}); loaded {
|
|
log.Info("user loaded on map")
|
|
dt, _ := rl.Load(key)
|
|
dtl := dt.(data)
|
|
|
|
dtl.amount++
|
|
amnt := dtl.amount
|
|
rl.Store(key, data{when: dtl.when, amount: amnt})
|
|
switch time.Since(dtl.when).Seconds() < rLimitTime {
|
|
case true:
|
|
switch dtl.amount > rLimitAmnt {
|
|
case true:
|
|
log.Info("user rl execeed", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
rl.Store(key, data{when: time.Now(), amount: amnt})
|
|
b.SendMessage(ctx, &bot.SendMessageParams{ChatID: key, Text: "Rate Limit Exeded"})
|
|
return
|
|
case false:
|
|
|
|
log.Info("user rl not execeed", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
}
|
|
case false:
|
|
rl.Store(key, data{when: time.Now(), amount: 1})
|
|
log.Info("user time", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
}
|
|
|
|
} else {
|
|
rl.Store(key, data{when: time.Now(), amount: 1})
|
|
log.Info("user not loaded", "user", key)
|
|
}
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
|
|
// CreateRateLimitUser
|
|
// Create an specific rate limiting with distincts values of time and hits
|
|
func CreateRateLimitUser(ctx context.Context, log *slog.Logger, delay float64, hits int64) func(bot.HandlerFunc) bot.HandlerFunc {
|
|
return func(next bot.HandlerFunc) bot.HandlerFunc {
|
|
rl := sync.Map{}
|
|
type data struct {
|
|
when time.Time
|
|
amount int64
|
|
}
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
var (
|
|
key int64
|
|
rLimitTime = delay // time in secs to check
|
|
rLimitAmnt = hits // number of hits
|
|
)
|
|
|
|
if update.CallbackQuery == nil {
|
|
key = update.Message.From.ID
|
|
} else {
|
|
key = update.CallbackQuery.From.ID
|
|
}
|
|
log.Info("got key ", "key ", key)
|
|
if _, loaded := rl.LoadOrStore(key, data{when: time.Now(), amount: 0}); loaded {
|
|
log.Debug("user loaded on map")
|
|
dt, _ := rl.Load(key)
|
|
dtl := dt.(data)
|
|
|
|
dtl.amount++
|
|
amnt := dtl.amount
|
|
rl.Store(key, data{when: dtl.when, amount: amnt})
|
|
switch time.Since(dtl.when).Seconds() < rLimitTime {
|
|
case true:
|
|
switch dtl.amount >= rLimitAmnt {
|
|
case true:
|
|
log.Info("user rl execeed", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
rl.Store(key, data{when: time.Now(), amount: amnt})
|
|
b.SendMessage(ctx, &bot.SendMessageParams{ChatID: key, Text: "Rate Limit Exeded"})
|
|
return
|
|
case false:
|
|
|
|
log.Debug("user rl not execeed", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
}
|
|
case false:
|
|
rl.Store(key, data{when: time.Now(), amount: 1})
|
|
log.Debug("user time", "since", time.Since(dtl.when).Seconds(), "amount", dtl.amount)
|
|
}
|
|
|
|
} else {
|
|
log.Info("user not loaded", "user", key)
|
|
}
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
}
|
|
|
|
func CreateLogMiddleWare(ctx context.Context, log *slog.Logger) func(bot.HandlerFunc) bot.HandlerFunc{
|
|
return func(next bot.HandlerFunc)bot.HandlerFunc{
|
|
return func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
start := time.Now()
|
|
txt := ""
|
|
user := &models.User{}
|
|
if update.CallbackQuery == nil {
|
|
if update.MessageReaction == nil {
|
|
txt = update.Message.Text
|
|
user = update.Message.From
|
|
}else {
|
|
user = update.MessageReaction.User
|
|
txt = "reaction"
|
|
log.Info("reaction", "react", update.MessageReaction)
|
|
}
|
|
|
|
|
|
} else {
|
|
txt = fmt.Sprintf("%#v", update.CallbackQuery)
|
|
user = &update.CallbackQuery.From
|
|
}
|
|
|
|
log.Info(txt, "user", user.Username)
|
|
log.Debug("reponse", "user", user.Username, "id", user.ID, "elapsed ms", time.Since(start).Milliseconds())
|
|
next(ctx, b, update)
|
|
}
|
|
}
|
|
}
|