Implement bidirectional customer conversations with customer-based grouping and order tagging
This commit is contained in:
@@ -124,10 +124,18 @@ namespace TeleBot.Handlers
|
||||
await HandleHelp(bot, callbackQuery.Message);
|
||||
break;
|
||||
|
||||
case "support":
|
||||
await HandleSupportCallback(bot, callbackQuery, session);
|
||||
break;
|
||||
|
||||
case "noop":
|
||||
// No operation - used for display-only buttons
|
||||
break;
|
||||
|
||||
case "cancel_support":
|
||||
await HandleCancelSupport(bot, callbackQuery, session);
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogWarning("Unknown callback action: {Action}", action);
|
||||
break;
|
||||
@@ -592,5 +600,36 @@ namespace TeleBot.Handlers
|
||||
replyMarkup: MenuBuilder.MainMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleSupportCallback(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session)
|
||||
{
|
||||
session.State = SessionState.CustomerSupport;
|
||||
|
||||
await bot.EditMessageTextAsync(
|
||||
callbackQuery.Message!.Chat.Id,
|
||||
callbackQuery.Message.MessageId,
|
||||
"🎧 *Customer Support*\n\n" +
|
||||
"You can now send a message to our support team. Simply type your message and we'll respond as soon as possible.\n\n" +
|
||||
"_Type your message below, or use the Cancel button to return to the main menu._",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.SupportMenu()
|
||||
);
|
||||
|
||||
await bot.AnswerCallbackQueryAsync(callbackQuery.Id);
|
||||
}
|
||||
|
||||
private async Task HandleCancelSupport(ITelegramBotClient bot, CallbackQuery callbackQuery, UserSession session)
|
||||
{
|
||||
session.State = SessionState.MainMenu;
|
||||
|
||||
await bot.EditMessageTextAsync(
|
||||
callbackQuery.Message!.Chat.Id,
|
||||
callbackQuery.Message.MessageId,
|
||||
"Customer support cancelled. How can I help you today?",
|
||||
replyMarkup: MenuBuilder.MainMenu()
|
||||
);
|
||||
|
||||
await bot.AnswerCallbackQueryAsync(callbackQuery.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,14 @@ namespace TeleBot.Handlers
|
||||
await HandleClearCommand(bot, message, session);
|
||||
break;
|
||||
|
||||
case "/support":
|
||||
await HandleSupportCommand(bot, message, session);
|
||||
break;
|
||||
|
||||
case "/cancel":
|
||||
await HandleCancelCommand(bot, message, session);
|
||||
break;
|
||||
|
||||
default:
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
@@ -297,5 +305,30 @@ namespace TeleBot.Handlers
|
||||
replyMarkup: MenuBuilder.MainMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleSupportCommand(ITelegramBotClient bot, Message message, Models.UserSession session)
|
||||
{
|
||||
session.State = Models.SessionState.CustomerSupport;
|
||||
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"🎧 *Customer Support*\n\n" +
|
||||
"You can now send a message to our support team. Simply type your message and we'll respond as soon as possible.\n\n" +
|
||||
"_Type your message below, or use /cancel to return to the main menu._",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.SupportMenu()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleCancelCommand(ITelegramBotClient bot, Message message, Models.UserSession session)
|
||||
{
|
||||
session.State = Models.SessionState.MainMenu;
|
||||
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"Operation cancelled. How can I help you today?",
|
||||
replyMarkup: MenuBuilder.MainMenu()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,16 @@ namespace TeleBot.Handlers
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger<MessageHandler> _logger;
|
||||
private readonly ILittleShopService _shopService;
|
||||
|
||||
public MessageHandler(
|
||||
ISessionManager sessionManager,
|
||||
ILogger<MessageHandler> logger)
|
||||
ILogger<MessageHandler> logger,
|
||||
ILittleShopService shopService)
|
||||
{
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
_shopService = shopService;
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(ITelegramBotClient bot, Message message)
|
||||
@@ -41,6 +44,11 @@ namespace TeleBot.Handlers
|
||||
{
|
||||
await HandleCheckoutInput(bot, message, session);
|
||||
}
|
||||
// Handle customer support messages
|
||||
else if (session.State == SessionState.CustomerSupport)
|
||||
{
|
||||
await HandleCustomerSupportMessage(bot, message, session);
|
||||
}
|
||||
else if (message.Text.StartsWith("/pgpkey "))
|
||||
{
|
||||
// Handle PGP key input
|
||||
@@ -221,5 +229,53 @@ namespace TeleBot.Handlers
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCustomerSupportMessage(ITelegramBotClient bot, Message message, UserSession session)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Send the customer's message to the business
|
||||
var success = await _shopService.SendCustomerMessageAsync(
|
||||
message.From!.Id,
|
||||
message.From.Username ?? "",
|
||||
$"{message.From.FirstName} {message.From.LastName}".Trim(),
|
||||
message.From.FirstName ?? "",
|
||||
message.From.LastName ?? "",
|
||||
"Customer Message", // Simple subject
|
||||
message.Text!
|
||||
);
|
||||
|
||||
if (success)
|
||||
{
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"✅ *Message sent to support team*\n\n" +
|
||||
"We've received your message and will respond as soon as possible.\n\n" +
|
||||
"You can continue shopping or send additional messages.",
|
||||
parseMode: Telegram.Bot.Types.Enums.ParseMode.Markdown,
|
||||
replyMarkup: MenuBuilder.MainMenu()
|
||||
);
|
||||
|
||||
session.State = SessionState.MainMenu;
|
||||
}
|
||||
else
|
||||
{
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"❌ Sorry, there was an error sending your message. Please try again.",
|
||||
replyMarkup: MenuBuilder.SupportMenu()
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error handling customer support message");
|
||||
await bot.SendTextMessageAsync(
|
||||
message.Chat.Id,
|
||||
"❌ An error occurred. Please try again or use /cancel to return to the menu.",
|
||||
replyMarkup: MenuBuilder.SupportMenu()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,7 @@ namespace TeleBot.Models
|
||||
ViewingOrders,
|
||||
ViewingOrder,
|
||||
PrivacySettings,
|
||||
CustomerSupport,
|
||||
Help
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,11 @@ builder.Services.AddHttpClient<BotManagerService>();
|
||||
builder.Services.AddSingleton<BotManagerService>();
|
||||
builder.Services.AddHostedService<BotManagerService>();
|
||||
|
||||
// Message Delivery Service - Single instance
|
||||
builder.Services.AddSingleton<MessageDeliveryService>();
|
||||
builder.Services.AddSingleton<IMessageDeliveryService>(sp => sp.GetRequiredService<MessageDeliveryService>());
|
||||
builder.Services.AddHostedService<MessageDeliveryService>(sp => sp.GetRequiredService<MessageDeliveryService>());
|
||||
|
||||
// Bot Service
|
||||
builder.Services.AddHostedService<TelegramBotService>();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace TeleBot.Services
|
||||
Task<List<CustomerMessage>?> GetPendingMessagesAsync();
|
||||
Task<bool> MarkMessageAsSentAsync(Guid messageId, string? platformMessageId = null);
|
||||
Task<bool> MarkMessageAsFailedAsync(Guid messageId, string reason);
|
||||
Task<bool> SendCustomerMessageAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName, string subject, string content);
|
||||
}
|
||||
|
||||
public class LittleShopService : ILittleShopService
|
||||
@@ -331,30 +332,22 @@ namespace TeleBot.Services
|
||||
if (!await AuthenticateAsync())
|
||||
return null;
|
||||
|
||||
// Call API to get pending messages for Telegram platform
|
||||
var response = await _client.HttpClient.GetAsync("api/messages/pending?platform=Telegram");
|
||||
// Use the client messages service
|
||||
var messages = await _client.Messages.GetPendingMessagesAsync("Telegram");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
// Convert to TeleBot CustomerMessage format
|
||||
return messages.Select(m => new CustomerMessage
|
||||
{
|
||||
var messages = await response.Content.ReadFromJsonAsync<List<PendingMessageDto>>();
|
||||
|
||||
// Convert to simplified CustomerMessage format
|
||||
return messages?.Select(m => new CustomerMessage
|
||||
{
|
||||
Id = m.Id,
|
||||
CustomerId = m.CustomerId,
|
||||
TelegramUserId = m.TelegramUserId,
|
||||
Subject = m.Subject,
|
||||
Content = m.Content,
|
||||
Type = (MessageType)m.Type,
|
||||
IsUrgent = m.IsUrgent,
|
||||
OrderReference = m.OrderReference,
|
||||
CreatedAt = m.CreatedAt
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
_logger.LogWarning("Failed to get pending messages: {StatusCode}", response.StatusCode);
|
||||
return new List<CustomerMessage>();
|
||||
Id = m.Id,
|
||||
CustomerId = m.CustomerId,
|
||||
TelegramUserId = m.TelegramUserId,
|
||||
Subject = m.Subject,
|
||||
Content = m.Content,
|
||||
Type = (MessageType)m.Type,
|
||||
IsUrgent = m.IsUrgent,
|
||||
OrderReference = m.OrderReference,
|
||||
CreatedAt = m.CreatedAt
|
||||
}).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -370,14 +363,7 @@ namespace TeleBot.Services
|
||||
if (!await AuthenticateAsync())
|
||||
return false;
|
||||
|
||||
var url = $"api/messages/{messageId}/mark-sent";
|
||||
if (!string.IsNullOrEmpty(platformMessageId))
|
||||
{
|
||||
url += $"?platformMessageId={platformMessageId}";
|
||||
}
|
||||
|
||||
var response = await _client.HttpClient.PostAsync(url, null);
|
||||
return response.IsSuccessStatusCode;
|
||||
return await _client.Messages.MarkMessageAsSentAsync(messageId, platformMessageId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -393,8 +379,7 @@ namespace TeleBot.Services
|
||||
if (!await AuthenticateAsync())
|
||||
return false;
|
||||
|
||||
var response = await _client.HttpClient.PostAsJsonAsync($"api/messages/{messageId}/mark-failed", reason);
|
||||
return response.IsSuccessStatusCode;
|
||||
return await _client.Messages.MarkMessageAsFailedAsync(messageId, reason);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -418,5 +403,52 @@ namespace TeleBot.Services
|
||||
_ => 0 // Default to BTC
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<bool> SendCustomerMessageAsync(long telegramUserId, string telegramUsername, string displayName, string firstName, string lastName, string subject, string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await AuthenticateAsync())
|
||||
return false;
|
||||
|
||||
// First, get or create the customer
|
||||
var customer = await _client.Customers.GetOrCreateCustomerAsync(new CreateCustomerRequest
|
||||
{
|
||||
TelegramUserId = telegramUserId,
|
||||
TelegramUsername = telegramUsername,
|
||||
TelegramDisplayName = displayName,
|
||||
TelegramFirstName = firstName,
|
||||
TelegramLastName = lastName,
|
||||
AllowOrderUpdates = true,
|
||||
AllowMarketing = false
|
||||
});
|
||||
|
||||
if (!customer.IsSuccess || customer.Data == null)
|
||||
{
|
||||
_logger.LogError("Failed to get or create customer for support message");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the customer message
|
||||
var messageData = new
|
||||
{
|
||||
CustomerId = customer.Data.Id,
|
||||
Type = 3, // CustomerService
|
||||
Subject = subject,
|
||||
Content = content,
|
||||
Direction = 1, // CustomerToAdmin
|
||||
Priority = 5,
|
||||
IsUrgent = false
|
||||
};
|
||||
|
||||
var response = await _client.Messages.CreateCustomerMessageAsync(messageData);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending customer message");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace TeleBot.Services
|
||||
public interface IMessageDeliveryService
|
||||
{
|
||||
Task DeliverPendingMessagesAsync(ITelegramBotClient bot);
|
||||
void SetBotClient(ITelegramBotClient botClient);
|
||||
}
|
||||
|
||||
public class MessageDeliveryService : IMessageDeliveryService, IHostedService
|
||||
@@ -22,6 +23,7 @@ namespace TeleBot.Services
|
||||
private readonly IConfiguration _configuration;
|
||||
private Timer? _deliveryTimer;
|
||||
private readonly int _pollIntervalSeconds;
|
||||
private ITelegramBotClient? _botClient;
|
||||
|
||||
public MessageDeliveryService(
|
||||
ILittleShopService shopService,
|
||||
@@ -34,6 +36,12 @@ namespace TeleBot.Services
|
||||
_pollIntervalSeconds = configuration.GetValue<int>("MessageDelivery:PollIntervalSeconds", 30);
|
||||
}
|
||||
|
||||
public void SetBotClient(ITelegramBotClient botClient)
|
||||
{
|
||||
_botClient = botClient;
|
||||
_logger.LogInformation("Bot client set for message delivery service");
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Starting Message Delivery Service - polling every {Interval} seconds", _pollIntervalSeconds);
|
||||
@@ -55,12 +63,16 @@ namespace TeleBot.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// This will be called by the timer, but we need bot instance
|
||||
// For now, just log that we're checking
|
||||
_logger.LogDebug("Checking for pending messages to deliver...");
|
||||
_logger.LogInformation("=== Checking for pending messages to deliver ===");
|
||||
|
||||
// Note: We'll need to integrate this with the bot instance
|
||||
// This is a placeholder for the polling mechanism
|
||||
if (_botClient != null)
|
||||
{
|
||||
await DeliverPendingMessagesAsync(_botClient);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Bot client not available for message delivery");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -77,7 +89,7 @@ namespace TeleBot.Services
|
||||
|
||||
if (pendingMessages?.Any() != true)
|
||||
{
|
||||
_logger.LogDebug("No pending messages to deliver");
|
||||
_logger.LogInformation("No pending messages to deliver");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace TeleBot
|
||||
private readonly ICommandHandler _commandHandler;
|
||||
private readonly ICallbackHandler _callbackHandler;
|
||||
private readonly IMessageHandler _messageHandler;
|
||||
private readonly IMessageDeliveryService _messageDeliveryService;
|
||||
private ITelegramBotClient? _botClient;
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
|
||||
@@ -32,7 +33,8 @@ namespace TeleBot
|
||||
IServiceProvider serviceProvider,
|
||||
ICommandHandler commandHandler,
|
||||
ICallbackHandler callbackHandler,
|
||||
IMessageHandler messageHandler)
|
||||
IMessageHandler messageHandler,
|
||||
IMessageDeliveryService messageDeliveryService)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
@@ -40,6 +42,7 @@ namespace TeleBot
|
||||
_commandHandler = commandHandler;
|
||||
_callbackHandler = callbackHandler;
|
||||
_messageHandler = messageHandler;
|
||||
_messageDeliveryService = messageDeliveryService;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -69,6 +72,12 @@ namespace TeleBot
|
||||
|
||||
var me = await _botClient.GetMeAsync(cancellationToken);
|
||||
_logger.LogInformation("Bot started: @{Username} ({Id})", me.Username, me.Id);
|
||||
|
||||
// Set bot client for message delivery service
|
||||
if (_messageDeliveryService is MessageDeliveryService deliveryService)
|
||||
{
|
||||
deliveryService.SetBotClient(_botClient);
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace TeleBot.UI
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🛍️ Browse Products", "browse") },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🛒 View Cart", "cart") },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("📦 My Orders", "orders") },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🎧 Customer Support", "support") },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🔒 Privacy Settings", "privacy") },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❓ Help", "help") }
|
||||
});
|
||||
@@ -277,6 +278,19 @@ namespace TeleBot.UI
|
||||
};
|
||||
}
|
||||
|
||||
public static InlineKeyboardMarkup SupportMenu()
|
||||
{
|
||||
var markup = new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
InlineKeyboardButton.WithCallbackData("❌ Cancel", "cancel_support")
|
||||
}
|
||||
});
|
||||
|
||||
return markup;
|
||||
}
|
||||
|
||||
private static string GetOrderStatusEmoji(int status)
|
||||
{
|
||||
return status switch
|
||||
|
||||
@@ -256,9 +256,11 @@ namespace TeleBot.UI
|
||||
"/browse - Browse products\n" +
|
||||
"/cart - View shopping cart\n" +
|
||||
"/orders - View your orders\n" +
|
||||
"/support - Contact customer support\n" +
|
||||
"/privacy - Privacy settings\n" +
|
||||
"/pgpkey - Set PGP public key\n" +
|
||||
"/ephemeral - Toggle ephemeral mode\n" +
|
||||
"/cancel - Cancel current operation\n" +
|
||||
"/delete - Delete all your data\n" +
|
||||
"/tor - Get Tor onion address\n" +
|
||||
"/help - Show this help message\n\n" +
|
||||
|
||||
Reference in New Issue
Block a user