lightningtipbot/internal/telegram/interceptor.go
2022-08-27 16:39:35 +02:00

253 lines
8.2 KiB
Go

package telegram
import (
"context"
"fmt"
"strconv"
"github.com/LightningTipBot/LightningTipBot/internal/errors"
"github.com/LightningTipBot/LightningTipBot/internal/runtime/mutex"
"github.com/LightningTipBot/LightningTipBot/internal/runtime/once"
"github.com/LightningTipBot/LightningTipBot/internal/i18n"
i18n2 "github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/LightningTipBot/LightningTipBot/internal/lnbits"
"github.com/LightningTipBot/LightningTipBot/internal/telegram/intercept"
log "github.com/sirupsen/logrus"
tb "gopkg.in/lightningtipbot/telebot.v3"
)
type Interceptor struct {
Before []intercept.Func
After []intercept.Func
OnDefer []intercept.Func
}
// singletonClickInterceptor uses the onceMap to determine whether the object k1 already interacted
// with the user k2. If so, it will return an error.
func (bot TipBot) singletonCallbackInterceptor(ctx intercept.Context) (intercept.Context, error) {
if ctx.Callback() != nil {
return ctx, once.Once(ctx.Callback().Data, strconv.FormatInt(ctx.Callback().Sender.ID, 10))
}
return ctx, errors.Create(errors.InvalidTypeError)
}
// lockInterceptor invoked as first before interceptor
func (bot TipBot) lockInterceptor(ctx intercept.Context) (intercept.Context, error) {
user := ctx.Sender()
if user != nil {
mutex.Lock(strconv.FormatInt(user.ID, 10))
return ctx, nil
}
return ctx, errors.Create(errors.InvalidTypeError)
}
// unlockInterceptor invoked as onDefer interceptor
func (bot TipBot) unlockInterceptor(ctx intercept.Context) (intercept.Context, error) {
user := ctx.Sender()
if user != nil {
mutex.Unlock(strconv.FormatInt(user.ID, 10))
return ctx, nil
}
return ctx, errors.Create(errors.InvalidTypeError)
}
func (bot TipBot) idInterceptor(ctx intercept.Context) (intercept.Context, error) {
ctx.Context = context.WithValue(ctx, "uid", RandStringRunes(64))
return ctx, nil
}
// answerCallbackInterceptor will answer the callback with the given text in the context
func (bot TipBot) answerCallbackInterceptor(ctx intercept.Context) (intercept.Context, error) {
c := ctx.Callback()
if c != nil {
ctxcr := ctx.Value("callback_response")
var res []*tb.CallbackResponse
if ctxcr != nil {
res = append(res, &tb.CallbackResponse{CallbackID: c.ID, Text: ctxcr.(string)})
}
// if the context wasn't set, still respond with an empty callback response
if len(res) == 0 {
res = append(res, &tb.CallbackResponse{CallbackID: c.ID, Text: ""})
}
var err error
err = bot.Telegram.Respond(c, res...)
return ctx, err
}
return ctx, errors.Create(errors.InvalidTypeError)
}
// requireUserInterceptor will return an error if user is not found
// user is here an lnbits.User
func (bot TipBot) requireUserInterceptor(ctx intercept.Context) (intercept.Context, error) {
var user *lnbits.User
var err error
u := ctx.Sender()
if u != nil {
user, err = GetUser(u, bot)
// do not respond to banned users
if bot.UserIsBanned(user) {
ctx.Context = context.WithValue(ctx, "banned", true)
ctx.Context = context.WithValue(ctx, "user", user)
return ctx, errors.Create(errors.InvalidTypeError)
}
if user != nil {
ctx.Context = context.WithValue(ctx, "user", user)
return ctx, err
}
}
return ctx, errors.Create(errors.InvalidTypeError)
}
// startUserInterceptor will invoke /start if user not exists.
func (bot TipBot) startUserInterceptor(ctx intercept.Context) (intercept.Context, error) {
handler, err := bot.loadUserInterceptor(ctx)
if err != nil {
// user banned
return handler, err
}
// load user
u := ctx.Value("user")
// check user nil
if u != nil {
user := u.(*lnbits.User)
// check wallet nil or !initialized
if user.Wallet == nil || !user.Initialized {
handler, err = bot.startHandler(handler)
if err != nil {
return handler, err
}
return handler, nil
}
}
return handler, nil
}
func (bot TipBot) loadUserInterceptor(ctx intercept.Context) (intercept.Context, error) {
ctx, _ = bot.requireUserInterceptor(ctx)
// if user is banned, also loadUserInterceptor will return an error
if ctx.Value("banned") != nil && ctx.Value("banned").(bool) {
return ctx, errors.Create(errors.InvalidTypeError)
}
return ctx, nil
}
// loadReplyToInterceptor Loading the Telegram user with message intercept
func (bot TipBot) loadReplyToInterceptor(ctx intercept.Context) (intercept.Context, error) {
if ctx.Message() != nil {
if ctx.Message().ReplyTo != nil {
if ctx.Message().ReplyTo.Sender != nil {
user, _ := GetUser(ctx.Message().ReplyTo.Sender, bot)
user.Telegram = ctx.Message().ReplyTo.Sender
ctx.Context = context.WithValue(ctx, "reply_to_user", user)
return ctx, nil
}
}
return ctx, nil
}
return ctx, errors.Create(errors.InvalidTypeError)
}
func (bot TipBot) localizerInterceptor(ctx intercept.Context) (intercept.Context, error) {
var userLocalizer *i18n2.Localizer
var publicLocalizer *i18n2.Localizer
// default language is english
publicLocalizer = i18n2.NewLocalizer(i18n.Bundle, "en")
ctx.Context = context.WithValue(ctx, "publicLanguageCode", "en")
ctx.Context = context.WithValue(ctx, "publicLocalizer", publicLocalizer)
if ctx.Message() != nil {
userLocalizer = i18n2.NewLocalizer(i18n.Bundle, ctx.Message().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLanguageCode", ctx.Message().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLocalizer", userLocalizer)
if ctx.Message().Private() {
// in pm overwrite public localizer with user localizer
ctx.Context = context.WithValue(ctx, "publicLanguageCode", ctx.Message().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "publicLocalizer", userLocalizer)
}
return ctx, nil
} else if ctx.Callback() != nil {
userLocalizer = i18n2.NewLocalizer(i18n.Bundle, ctx.Callback().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLanguageCode", ctx.Callback().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLocalizer", userLocalizer)
return ctx, nil
} else if ctx.Query() != nil {
userLocalizer = i18n2.NewLocalizer(i18n.Bundle, ctx.Query().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLanguageCode", ctx.Query().Sender.LanguageCode)
ctx.Context = context.WithValue(ctx, "userLocalizer", userLocalizer)
return ctx, nil
}
return ctx, nil
}
func (bot TipBot) requirePrivateChatInterceptor(ctx intercept.Context) (intercept.Context, error) {
if ctx.Message() != nil {
if ctx.Message().Chat.Type != tb.ChatPrivate {
return ctx, fmt.Errorf("[requirePrivateChatInterceptor] no private chat")
}
return ctx, nil
}
return ctx, errors.Create(errors.InvalidTypeError)
}
const photoTag = "<Photo>"
func (bot TipBot) logMessageInterceptor(ctx intercept.Context) (intercept.Context, error) {
if ctx.Message() != nil {
if ctx.Message().Text != "" {
log_string := fmt.Sprintf("[%s:%d %s:%d] %s", ctx.Message().Chat.Title, ctx.Message().Chat.ID, GetUserStr(ctx.Message().Sender), ctx.Message().Sender.ID, ctx.Message().Text)
if ctx.Message().IsReply() {
log_string = fmt.Sprintf("%s -> %s", log_string, GetUserStr(ctx.Message().ReplyTo.Sender))
}
log.Infof(log_string)
} else if ctx.Message().Photo != nil {
log.Infof("[%s:%d %s:%d] %s", ctx.Message().Chat.Title, ctx.Message().Chat.ID, GetUserStr(ctx.Message().Sender), ctx.Message().Sender.ID, photoTag)
}
return ctx, nil
} else if ctx.Callback() != nil {
log.Infof("[Callback %s:%d] Data: %s", GetUserStr(ctx.Callback().Sender), ctx.Callback().Sender.ID, ctx.Callback().Data)
return ctx, nil
}
return ctx, errors.Create(errors.InvalidTypeError)
}
// LoadUser from context
func LoadUserLocalizer(ctx context.Context) *i18n2.Localizer {
u := ctx.Value("userLocalizer")
if u != nil {
return u.(*i18n2.Localizer)
}
return nil
}
// LoadUser from context
func LoadPublicLocalizer(ctx context.Context) *i18n2.Localizer {
u := ctx.Value("publicLocalizer")
if u != nil {
return u.(*i18n2.Localizer)
}
return nil
}
// LoadUser from context
func LoadUser(ctx context.Context) *lnbits.User {
u := ctx.Value("user")
if u != nil {
return u.(*lnbits.User)
}
return nil
}
// LoadReplyToUser from context
func LoadReplyToUser(ctx context.Context) *lnbits.User {
u := ctx.Value("reply_to_user")
if u != nil {
return u.(*lnbits.User)
}
return nil
}