Fixed bot verbosity
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package bot
|
||||
|
||||
import "strings"
|
||||
|
||||
type Command string
|
||||
|
||||
const (
|
||||
CommandStart Command = "start"
|
||||
CommandFund Command = "fund"
|
||||
CommandWithdraw Command = "withdraw"
|
||||
CommandConfirm Command = "confirm"
|
||||
CommandCancel Command = "cancel"
|
||||
)
|
||||
|
||||
var supportedCommands = []Command{
|
||||
CommandStart,
|
||||
CommandFund,
|
||||
CommandWithdraw,
|
||||
CommandConfirm,
|
||||
CommandCancel,
|
||||
}
|
||||
|
||||
func (c Command) Slash() string {
|
||||
name := strings.TrimSpace(string(c))
|
||||
if name == "" {
|
||||
return ""
|
||||
}
|
||||
return "/" + name
|
||||
}
|
||||
|
||||
func parseCommand(text string) Command {
|
||||
text = strings.TrimSpace(text)
|
||||
if !strings.HasPrefix(text, "/") {
|
||||
return ""
|
||||
}
|
||||
token := text
|
||||
if idx := strings.IndexAny(token, " \t\n\r"); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
token = strings.TrimPrefix(token, "/")
|
||||
if idx := strings.Index(token, "@"); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
return Command(strings.ToLower(strings.TrimSpace(token)))
|
||||
}
|
||||
|
||||
func supportedCommandsMessage() string {
|
||||
lines := make([]string, 0, len(supportedCommands)+1)
|
||||
lines = append(lines, "Supported commands:")
|
||||
for _, cmd := range supportedCommands {
|
||||
lines = append(lines, cmd.Slash())
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func confirmationCommandsMessage() string {
|
||||
return "Confirm operation?\n\n" + CommandConfirm.Slash() + "\n" + CommandCancel.Slash()
|
||||
}
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
)
|
||||
|
||||
const unauthorizedMessage = "Sorry, your Telegram account is not authorized to perform treasury operations."
|
||||
const welcomeMessage = "Welcome to tgsettle treasury bot.\n\nUse /fund to credit your account and /withdraw to debit it.\nAfter entering an amount, use /confirm or /cancel."
|
||||
const unauthorizedChatMessage = "Sorry, this Telegram chat is not authorized to perform treasury operations."
|
||||
|
||||
var welcomeMessage = "Welcome to tgsettle treasury bot.\n\nUse " + CommandFund.Slash() + " to credit your account and " + CommandWithdraw.Slash() + " to debit it.\nAfter entering an amount, use " + CommandConfirm.Slash() + " or " + CommandCancel.Slash() + "."
|
||||
|
||||
type SendTextFunc func(ctx context.Context, chatID string, text string) error
|
||||
|
||||
@@ -119,6 +121,8 @@ func (r *Router) HandleUpdate(ctx context.Context, update *model.TelegramWebhook
|
||||
}
|
||||
if !r.allowAnyChat {
|
||||
if _, ok := r.allowedChats[chatID]; !ok {
|
||||
r.logUnauthorized(update)
|
||||
_ = r.sendText(ctx, chatID, unauthorizedChatMessage)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -132,19 +136,19 @@ func (r *Router) HandleUpdate(ctx context.Context, update *model.TelegramWebhook
|
||||
|
||||
command := parseCommand(text)
|
||||
switch command {
|
||||
case "start":
|
||||
case CommandStart:
|
||||
_ = r.sendText(ctx, chatID, welcomeMessage)
|
||||
return true
|
||||
case "fund":
|
||||
case CommandFund:
|
||||
r.startAmountDialog(ctx, userID, accountID, chatID, storagemodel.TreasuryOperationFund)
|
||||
return true
|
||||
case "withdraw":
|
||||
case CommandWithdraw:
|
||||
r.startAmountDialog(ctx, userID, accountID, chatID, storagemodel.TreasuryOperationWithdraw)
|
||||
return true
|
||||
case "confirm":
|
||||
case CommandConfirm:
|
||||
r.confirm(ctx, userID, accountID, chatID)
|
||||
return true
|
||||
case "cancel":
|
||||
case CommandCancel:
|
||||
r.cancel(ctx, userID, accountID, chatID)
|
||||
return true
|
||||
}
|
||||
@@ -156,13 +160,20 @@ func (r *Router) HandleUpdate(ctx context.Context, update *model.TelegramWebhook
|
||||
r.captureAmount(ctx, userID, accountID, chatID, session.OperationType, text)
|
||||
return true
|
||||
case DialogStateWaitingConfirmation:
|
||||
_ = r.sendText(ctx, chatID, "Confirm operation?\n\n/confirm\n/cancel")
|
||||
_ = r.sendText(ctx, chatID, confirmationCommandsMessage())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(text, "/") {
|
||||
_ = r.sendText(ctx, chatID, "Supported commands:\n/start\n/fund\n/withdraw\n/confirm\n/cancel")
|
||||
_ = r.sendText(ctx, chatID, supportedCommandsMessage())
|
||||
return true
|
||||
}
|
||||
if strings.TrimSpace(message.ReplyToMessageID) != "" {
|
||||
return false
|
||||
}
|
||||
if text != "" {
|
||||
_ = r.sendText(ctx, chatID, supportedCommandsMessage())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -212,22 +223,22 @@ func (r *Router) captureAmount(ctx context.Context, userID, accountID, chatID st
|
||||
if typed, ok := err.(limitError); ok {
|
||||
switch typed.LimitKind() {
|
||||
case "per_operation":
|
||||
_ = r.sendText(ctx, chatID, "Amount exceeds allowed limit.\n\nMax per operation: "+typed.LimitMax()+"\n\nEnter another amount or /cancel")
|
||||
_ = r.sendText(ctx, chatID, "Amount exceeds allowed limit.\n\nMax per operation: "+typed.LimitMax()+"\n\nEnter another amount or "+CommandCancel.Slash())
|
||||
return
|
||||
case "daily":
|
||||
_ = r.sendText(ctx, chatID, "Daily amount limit exceeded.\n\nMax per day: "+typed.LimitMax()+"\n\nEnter another amount or /cancel")
|
||||
_ = r.sendText(ctx, chatID, "Daily amount limit exceeded.\n\nMax per day: "+typed.LimitMax()+"\n\nEnter another amount or "+CommandCancel.Slash())
|
||||
return
|
||||
}
|
||||
}
|
||||
if errors.Is(err, merrors.ErrInvalidArg) {
|
||||
_ = r.sendText(ctx, chatID, "Invalid amount.\n\nEnter another amount or /cancel")
|
||||
_ = r.sendText(ctx, chatID, "Invalid amount.\n\nEnter another amount or "+CommandCancel.Slash())
|
||||
return
|
||||
}
|
||||
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or /cancel")
|
||||
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or "+CommandCancel.Slash())
|
||||
return
|
||||
}
|
||||
if record == nil {
|
||||
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or /cancel")
|
||||
_ = r.sendText(ctx, chatID, "Failed to create treasury request.\n\nEnter another amount or "+CommandCancel.Slash())
|
||||
return
|
||||
}
|
||||
r.dialogs.Set(userID, DialogSession{
|
||||
@@ -254,7 +265,7 @@ func (r *Router) confirm(ctx context.Context, userID string, accountID string, c
|
||||
}
|
||||
record, err := r.service.ConfirmRequest(ctx, requestID, userID)
|
||||
if err != nil {
|
||||
_ = r.sendText(ctx, chatID, "Unable to confirm treasury request.\n\nUse /cancel or create a new request with /fund or /withdraw.")
|
||||
_ = r.sendText(ctx, chatID, "Unable to confirm treasury request.\n\nUse "+CommandCancel.Slash()+" or create a new request with "+CommandFund.Slash()+" or "+CommandWithdraw.Slash()+".")
|
||||
return
|
||||
}
|
||||
if r.tracker != nil {
|
||||
@@ -323,18 +334,18 @@ func (r *Router) logUnauthorized(update *model.TelegramWebhookUpdate) {
|
||||
|
||||
func pendingRequestMessage(record *storagemodel.TreasuryRequest) string {
|
||||
if record == nil {
|
||||
return "You already have a pending treasury operation.\n\n/cancel"
|
||||
return "You already have a pending treasury operation.\n\n" + CommandCancel.Slash()
|
||||
}
|
||||
return "You already have a pending treasury operation.\n\n" +
|
||||
"Request ID: " + strings.TrimSpace(record.RequestID) + "\n" +
|
||||
"Status: " + strings.TrimSpace(string(record.Status)) + "\n" +
|
||||
"Amount: " + strings.TrimSpace(record.Amount) + " " + strings.TrimSpace(record.Currency) + "\n\n" +
|
||||
"Wait for execution or cancel it.\n\n/cancel"
|
||||
"Wait for execution or cancel it.\n\n" + CommandCancel.Slash()
|
||||
}
|
||||
|
||||
func confirmationPrompt(record *storagemodel.TreasuryRequest) string {
|
||||
if record == nil {
|
||||
return "Request created.\n\n/confirm\n/cancel"
|
||||
return "Request created.\n\n" + CommandConfirm.Slash() + "\n" + CommandCancel.Slash()
|
||||
}
|
||||
title := "Funding request created."
|
||||
if record.OperationType == storagemodel.TreasuryOperationWithdraw {
|
||||
@@ -343,23 +354,7 @@ func confirmationPrompt(record *storagemodel.TreasuryRequest) string {
|
||||
return title + "\n\n" +
|
||||
"Account: " + strings.TrimSpace(record.LedgerAccountID) + "\n" +
|
||||
"Amount: " + strings.TrimSpace(record.Amount) + " " + strings.TrimSpace(record.Currency) + "\n\n" +
|
||||
"Confirm operation?\n\n/confirm\n/cancel"
|
||||
}
|
||||
|
||||
func parseCommand(text string) string {
|
||||
text = strings.TrimSpace(text)
|
||||
if !strings.HasPrefix(text, "/") {
|
||||
return ""
|
||||
}
|
||||
token := text
|
||||
if idx := strings.IndexAny(token, " \t\n\r"); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
token = strings.TrimPrefix(token, "/")
|
||||
if idx := strings.Index(token, "@"); idx >= 0 {
|
||||
token = token[:idx]
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(token))
|
||||
confirmationCommandsMessage()
|
||||
}
|
||||
|
||||
func formatSeconds(value int64) string {
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestRouterUnauthorizedInAllowedChatSendsAccessDenied(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterUnknownChatIsIgnored(t *testing.T) {
|
||||
func TestRouterUnknownChatGetsDenied(t *testing.T) {
|
||||
var sent []string
|
||||
router := NewRouter(
|
||||
mloggerfactory.NewLogger(false),
|
||||
@@ -90,8 +90,11 @@ func TestRouterUnknownChatIsIgnored(t *testing.T) {
|
||||
if !handled {
|
||||
t.Fatalf("expected update to be handled")
|
||||
}
|
||||
if len(sent) != 0 {
|
||||
t.Fatalf("expected no messages, got %d", len(sent))
|
||||
if len(sent) != 1 {
|
||||
t.Fatalf("expected one message, got %d", len(sent))
|
||||
}
|
||||
if sent[0] != unauthorizedChatMessage {
|
||||
t.Fatalf("unexpected message: %q", sent[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,3 +221,34 @@ func TestRouterStartUnauthorizedGetsDenied(t *testing.T) {
|
||||
t.Fatalf("unexpected message: %q", sent[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterPlainTextWithoutSession_ShowsSupportedCommands(t *testing.T) {
|
||||
var sent []string
|
||||
router := NewRouter(
|
||||
mloggerfactory.NewLogger(false),
|
||||
fakeService{},
|
||||
func(_ context.Context, _ string, text string) error {
|
||||
sent = append(sent, text)
|
||||
return nil
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
map[string]string{"123": "acct-1"},
|
||||
)
|
||||
handled := router.HandleUpdate(context.Background(), &model.TelegramWebhookUpdate{
|
||||
Message: &model.TelegramMessage{
|
||||
ChatID: "777",
|
||||
FromUserID: "123",
|
||||
Text: "hello",
|
||||
},
|
||||
})
|
||||
if !handled {
|
||||
t.Fatalf("expected update to be handled")
|
||||
}
|
||||
if len(sent) != 1 {
|
||||
t.Fatalf("expected one message, got %d", len(sent))
|
||||
}
|
||||
if sent[0] != supportedCommandsMessage() {
|
||||
t.Fatalf("unexpected message: %q", sent[0])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user