mirror of
https://github.com/ChuckNorrison/LightningTipBot.git
synced 2026-08-13 12:33:14 +02:00
* 🚧 minor changes to all handlers... * 🚧 passing callback to tryEditStack * 🚧 edit and delete callback messages * 🚧 refactor again * 🚧 uncomment this * 🚧 using ctx.Message().Text * 🚧 refactor * 🚧 refactor * 🚧 uncomment * context not queruy * 🚧 update forked lightningtipbot * 🚧 reset ArticleResult * fix inline receive * readd shop * remove chat from shopView Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
267 lines
10 KiB
Go
267 lines
10 KiB
Go
package telegram
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/LightningTipBot/LightningTipBot/internal/telegram/intercept"
|
|
"strings"
|
|
|
|
"github.com/LightningTipBot/LightningTipBot/internal/errors"
|
|
|
|
"github.com/LightningTipBot/LightningTipBot/internal/runtime/mutex"
|
|
"github.com/LightningTipBot/LightningTipBot/internal/storage"
|
|
|
|
"github.com/LightningTipBot/LightningTipBot/internal/i18n"
|
|
"github.com/LightningTipBot/LightningTipBot/internal/lnbits"
|
|
"github.com/LightningTipBot/LightningTipBot/internal/runtime"
|
|
|
|
"github.com/LightningTipBot/LightningTipBot/internal/str"
|
|
decodepay "github.com/fiatjaf/ln-decodepay"
|
|
log "github.com/sirupsen/logrus"
|
|
tb "gopkg.in/lightningtipbot/telebot.v3"
|
|
)
|
|
|
|
var (
|
|
paymentConfirmationMenu = &tb.ReplyMarkup{ResizeKeyboard: true}
|
|
btnCancelPay = paymentConfirmationMenu.Data("🚫 Cancel", "cancel_pay")
|
|
btnPay = paymentConfirmationMenu.Data("✅ Pay", "confirm_pay")
|
|
)
|
|
|
|
func helpPayInvoiceUsage(ctx context.Context, errormsg string) string {
|
|
if len(errormsg) > 0 {
|
|
return fmt.Sprintf(Translate(ctx, "payHelpText"), fmt.Sprintf("%s", errormsg))
|
|
} else {
|
|
return fmt.Sprintf(Translate(ctx, "payHelpText"), "")
|
|
}
|
|
}
|
|
|
|
type PayData struct {
|
|
*storage.Base
|
|
From *lnbits.User `json:"from"`
|
|
Invoice string `json:"invoice"`
|
|
Hash string `json:"hash"`
|
|
Proof string `json:"proof"`
|
|
Memo string `json:"memo"`
|
|
Message string `json:"message"`
|
|
Amount int64 `json:"amount"`
|
|
LanguageCode string `json:"languagecode"`
|
|
TelegramMessage *tb.Message `json:"telegrammessage"`
|
|
}
|
|
|
|
// payHandler invoked on "/pay lnbc..." command
|
|
func (bot *TipBot) payHandler(ctx intercept.Context) (intercept.Context, error) {
|
|
// check and print all commands
|
|
bot.anyTextHandler(ctx)
|
|
user := LoadUser(ctx)
|
|
if user.Wallet == nil {
|
|
return ctx, errors.Create(errors.UserNoWalletError)
|
|
}
|
|
if len(strings.Split(ctx.Message().Text, " ")) < 2 {
|
|
NewMessage(ctx.Message(), WithDuration(0, bot))
|
|
bot.trySendMessage(ctx.Sender(), helpPayInvoiceUsage(ctx, ""))
|
|
return ctx, errors.Create(errors.InvalidSyntaxError)
|
|
}
|
|
userStr := GetUserStr(ctx.Sender())
|
|
paymentRequest, err := getArgumentFromCommand(ctx.Message().Text, 1)
|
|
if err != nil {
|
|
NewMessage(ctx.Message(), WithDuration(0, bot))
|
|
bot.trySendMessage(ctx.Sender(), helpPayInvoiceUsage(ctx, Translate(ctx, "invalidInvoiceHelpMessage")))
|
|
errmsg := fmt.Sprintf("[/pay] Error: Could not getArgumentFromCommand: %s", err.Error())
|
|
log.Errorln(errmsg)
|
|
return ctx, errors.New(errors.InvalidSyntaxError, err)
|
|
}
|
|
paymentRequest = strings.ToLower(paymentRequest)
|
|
// get rid of the URI prefix
|
|
paymentRequest = strings.TrimPrefix(paymentRequest, "lightning:")
|
|
|
|
// decode invoice
|
|
bolt11, err := decodepay.Decodepay(paymentRequest)
|
|
if err != nil {
|
|
bot.trySendMessage(ctx.Sender(), helpPayInvoiceUsage(ctx, Translate(ctx, "invalidInvoiceHelpMessage")))
|
|
errmsg := fmt.Sprintf("[/pay] Error: Could not decode invoice: %s", err.Error())
|
|
log.Errorln(errmsg)
|
|
return ctx, errors.New(errors.InvalidSyntaxError, err)
|
|
}
|
|
amount := int64(bolt11.MSatoshi / 1000)
|
|
|
|
if amount <= 0 {
|
|
bot.trySendMessage(ctx.Sender(), Translate(ctx, "invoiceNoAmountMessage"))
|
|
errmsg := fmt.Sprint("[/pay] Error: invoice without amount")
|
|
log.Warnln(errmsg)
|
|
return ctx, errors.Create(errors.InvalidAmountError)
|
|
}
|
|
|
|
// check user balance first
|
|
balance, err := bot.GetUserBalance(user)
|
|
if err != nil {
|
|
NewMessage(ctx.Message(), WithDuration(0, bot))
|
|
errmsg := fmt.Sprintf("[/pay] Error: Could not get user balance: %s", err.Error())
|
|
log.Errorln(errmsg)
|
|
bot.trySendMessage(ctx.Sender(), Translate(ctx, "errorTryLaterMessage"))
|
|
return ctx, errors.New(errors.GetBalanceError, err)
|
|
}
|
|
|
|
if amount > balance {
|
|
NewMessage(ctx.Message(), WithDuration(0, bot))
|
|
bot.trySendMessage(ctx.Sender(), fmt.Sprintf(Translate(ctx, "insufficientFundsMessage"), balance, amount))
|
|
return ctx, errors.Create(errors.InvalidSyntaxError)
|
|
}
|
|
// send warning that the invoice might fail due to missing fee reserve
|
|
if float64(amount) > float64(balance)*0.98 {
|
|
bot.trySendMessage(ctx.Sender(), Translate(ctx, "feeReserveMessage"))
|
|
}
|
|
|
|
confirmText := fmt.Sprintf(Translate(ctx, "confirmPayInvoiceMessage"), amount)
|
|
if len(bolt11.Description) > 0 {
|
|
confirmText = confirmText + fmt.Sprintf(Translate(ctx, "confirmPayAppendMemo"), str.MarkdownEscape(bolt11.Description))
|
|
}
|
|
|
|
log.Infof("[/pay] Invoice entered. User: %s, amount: %d sat.", userStr, amount)
|
|
|
|
// object that holds all information about the send payment
|
|
id := fmt.Sprintf("pay:%d-%d-%s", ctx.Sender().ID, amount, RandStringRunes(5))
|
|
|
|
// // // create inline buttons
|
|
payButton := paymentConfirmationMenu.Data(Translate(ctx, "payButtonMessage"), "confirm_pay", id)
|
|
cancelButton := paymentConfirmationMenu.Data(Translate(ctx, "cancelButtonMessage"), "cancel_pay", id)
|
|
|
|
paymentConfirmationMenu.Inline(
|
|
paymentConfirmationMenu.Row(
|
|
payButton,
|
|
cancelButton),
|
|
)
|
|
payMessage := bot.trySendMessageEditable(ctx.Chat(), confirmText, paymentConfirmationMenu)
|
|
payData := &PayData{
|
|
Base: storage.New(storage.ID(id)),
|
|
From: user,
|
|
Invoice: paymentRequest,
|
|
Amount: int64(amount),
|
|
Memo: bolt11.Description,
|
|
Message: confirmText,
|
|
LanguageCode: ctx.Value("publicLanguageCode").(string),
|
|
TelegramMessage: payMessage,
|
|
}
|
|
// add result to persistent struct
|
|
runtime.IgnoreError(payData.Set(payData, bot.Bunt))
|
|
|
|
SetUserState(user, bot, lnbits.UserStateConfirmPayment, paymentRequest)
|
|
return ctx, nil
|
|
}
|
|
|
|
// confirmPayHandler when user clicked pay on payment confirmation
|
|
func (bot *TipBot) confirmPayHandler(ctx intercept.Context) (intercept.Context, error) {
|
|
tx := &PayData{Base: storage.New(storage.ID(ctx.Data()))}
|
|
mutex.LockWithContext(ctx, tx.ID)
|
|
defer mutex.UnlockWithContext(ctx, tx.ID)
|
|
sn, err := tx.Get(tx, bot.Bunt)
|
|
// immediatelly set intransaction to block duplicate calls
|
|
if err != nil {
|
|
log.Errorf("[confirmPayHandler] %s", err.Error())
|
|
return ctx, err
|
|
}
|
|
payData := sn.(*PayData)
|
|
|
|
// onnly the correct user can press
|
|
if payData.From.Telegram.ID != ctx.Sender().ID {
|
|
return ctx, errors.Create(errors.UnknownError)
|
|
}
|
|
if !payData.Active {
|
|
log.Errorf("[confirmPayHandler] send not active anymore")
|
|
bot.tryEditMessage(ctx.Message(), i18n.Translate(payData.LanguageCode, "errorTryLaterMessage"), &tb.ReplyMarkup{})
|
|
bot.tryDeleteMessage(ctx.Message())
|
|
return ctx, errors.Create(errors.NotActiveError)
|
|
}
|
|
defer payData.Set(payData, bot.Bunt)
|
|
|
|
// remove buttons from confirmation message
|
|
// bot.tryEditMessage(handler.Message(), MarkdownEscape(payData.Message), &tb.ReplyMarkup{})
|
|
|
|
user := LoadUser(ctx)
|
|
if user.Wallet == nil {
|
|
bot.tryDeleteMessage(ctx.Message())
|
|
return ctx, errors.Create(errors.UserNoWalletError)
|
|
}
|
|
|
|
invoiceString := payData.Invoice
|
|
|
|
// reset state immediately
|
|
ResetUserState(user, bot)
|
|
|
|
userStr := GetUserStr(ctx.Sender())
|
|
|
|
// update button text
|
|
bot.tryEditMessage(
|
|
ctx.Message(),
|
|
payData.Message,
|
|
&tb.ReplyMarkup{
|
|
InlineKeyboard: [][]tb.InlineButton{
|
|
{tb.InlineButton{Text: i18n.Translate(payData.LanguageCode, "lnurlGettingUserMessage")}},
|
|
},
|
|
},
|
|
)
|
|
|
|
log.Infof("[/pay] Attempting %s's invoice %s (%d sat)", userStr, payData.ID, payData.Amount)
|
|
// pay invoice
|
|
invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: invoiceString}, bot.Client)
|
|
if err != nil {
|
|
errmsg := fmt.Sprintf("[/pay] Could not pay invoice of %s: %s", userStr, err)
|
|
err = fmt.Errorf(i18n.Translate(payData.LanguageCode, "invoiceUndefinedErrorMessage"))
|
|
bot.tryEditMessage(ctx.Message(), fmt.Sprintf(i18n.Translate(payData.LanguageCode, "invoicePaymentFailedMessage"), err.Error()), &tb.ReplyMarkup{})
|
|
// verbose error message, turned off for now
|
|
// if len(err.Error()) == 0 {
|
|
// err = fmt.Errorf(i18n.Translate(payData.LanguageCode, "invoiceUndefinedErrorMessage"))
|
|
// }
|
|
// bot.tryEditMessage(c.Message, fmt.Sprintf(i18n.Translate(payData.LanguageCode, "invoicePaymentFailedMessage"), str.MarkdownEscape(err.Error())), &tb.ReplyMarkup{})
|
|
log.Errorln(errmsg)
|
|
return ctx, err
|
|
}
|
|
payData.Hash = invoice.PaymentHash
|
|
|
|
// do balance check for keyboard update
|
|
_, err = bot.GetUserBalance(user)
|
|
if err != nil {
|
|
errmsg := fmt.Sprintf("could not get balance of user %s", userStr)
|
|
log.Errorln(errmsg)
|
|
}
|
|
|
|
if ctx.Message().Private() {
|
|
// if the command was invoked in private chat
|
|
// the edit below was cool, but we need to pop up the keyboard again
|
|
// bot.tryEditMessage(c.Message, i18n.Translate(payData.LanguageCode, "invoicePaidMessage"), &tb.ReplyMarkup{})
|
|
bot.tryDeleteMessage(ctx.Message())
|
|
bot.trySendMessage(ctx.Sender(), i18n.Translate(payData.LanguageCode, "invoicePaidMessage"))
|
|
} else {
|
|
// if the command was invoked in group chat
|
|
bot.trySendMessage(ctx.Sender(), i18n.Translate(payData.LanguageCode, "invoicePaidMessage"))
|
|
bot.tryEditMessage(ctx.Message(), fmt.Sprintf(i18n.Translate(payData.LanguageCode, "invoicePublicPaidMessage"), userStr), &tb.ReplyMarkup{})
|
|
}
|
|
log.Infof("[⚡️ pay] User %s paid invoice %s (%d sat)", userStr, payData.ID, payData.Amount)
|
|
return ctx, nil
|
|
}
|
|
|
|
// cancelPaymentHandler invoked when user clicked cancel on payment confirmation
|
|
func (bot *TipBot) cancelPaymentHandler(ctx intercept.Context) (intercept.Context, error) {
|
|
// reset state immediately
|
|
user := LoadUser(ctx)
|
|
ResetUserState(user, bot)
|
|
tx := &PayData{Base: storage.New(storage.ID(ctx.Data()))}
|
|
mutex.LockWithContext(ctx, tx.ID)
|
|
defer mutex.UnlockWithContext(ctx, tx.ID)
|
|
// immediatelly set intransaction to block duplicate calls
|
|
sn, err := tx.Get(tx, bot.Bunt)
|
|
if err != nil {
|
|
log.Errorf("[cancelPaymentHandler] %s", err.Error())
|
|
return ctx, err
|
|
}
|
|
payData := sn.(*PayData)
|
|
// onnly the correct user can press
|
|
if payData.From.Telegram.ID != ctx.Callback().Sender.ID {
|
|
return ctx, errors.Create(errors.UnknownError)
|
|
}
|
|
// delete and send instead of edit for the keyboard to pop up after sending
|
|
bot.tryDeleteMessage(ctx.Message())
|
|
bot.trySendMessage(ctx.Message().Chat, i18n.Translate(payData.LanguageCode, "paymentCancelledMessage"))
|
|
// bot.tryEditMessage(c.Message, i18n.Translate(payData.LanguageCode, "paymentCancelledMessage"), &tb.ReplyMarkup{})
|
|
return ctx, payData.Inactivate(payData, bot.Bunt)
|
|
|
|
}
|