diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a16cb12..cdb2725 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -42,10 +42,12 @@ "Bash(/tmp/bypass-hdwallet-unlock.sh:*)", "Bash(/tmp/fix-db-initialization.sh:*)", "SlashCommand(/code-review)", - "Read(//mnt/c/Production/Source/SilverLABS/SilverPAY.NET/**)" + "Read(//mnt/c/Production/Source/SilverLABS/SilverPAY.NET/**)", + "Bash(sqlite3:*)", + "Bash(git checkout:*)" ], "deny": [], "ask": [] }, "outputStyle": "enterprise-full-stack-developer" -} \ No newline at end of file +} diff --git a/LittleShop/Areas/Admin/Controllers/BotsController.cs b/LittleShop/Areas/Admin/Controllers/BotsController.cs index 14670b6..dd8104f 100644 --- a/LittleShop/Areas/Admin/Controllers/BotsController.cs +++ b/LittleShop/Areas/Admin/Controllers/BotsController.cs @@ -307,6 +307,47 @@ public class BotsController : Controller return RedirectToAction(nameof(Details), new { id }); } + // POST: Admin/Bots/UpdateWallets/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UpdateWallets(Guid id, Dictionary wallets) + { + try + { + var bot = await _botService.GetBotByIdAsync(id); + if (bot == null) + { + TempData["Error"] = "Bot not found"; + return RedirectToAction(nameof(Index)); + } + + // Get current settings + var settings = bot.Settings ?? new Dictionary(); + + // Filter out empty wallet addresses + var validWallets = wallets + .Where(w => !string.IsNullOrWhiteSpace(w.Value)) + .ToDictionary(w => w.Key, w => w.Value.Trim()); + + // Update or add wallets section + settings["wallets"] = validWallets; + + // Save settings + await _botService.UpdateBotSettingsAsync(id, new UpdateBotSettingsDto { Settings = settings }); + + _logger.LogInformation("Updated {Count} wallet addresses for bot {BotId}", validWallets.Count, id); + TempData["Success"] = $"Updated {validWallets.Count} wallet address(es) successfully"; + + return RedirectToAction(nameof(Edit), new { id }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to update wallets for bot {BotId}", id); + TempData["Error"] = "Failed to update wallet addresses"; + return RedirectToAction(nameof(Edit), new { id }); + } + } + // GET: Admin/Bots/RegenerateKey/5 public IActionResult RegenerateKey(Guid id) { diff --git a/LittleShop/Areas/Admin/Views/Bots/Edit.cshtml b/LittleShop/Areas/Admin/Views/Bots/Edit.cshtml index 5718b7b..c1ace90 100644 --- a/LittleShop/Areas/Admin/Views/Bots/Edit.cshtml +++ b/LittleShop/Areas/Admin/Views/Bots/Edit.cshtml @@ -49,6 +49,63 @@ +
+
+
💰 Payment Wallets
+
+
+

Configure cryptocurrency wallet addresses for direct payments. These are used as fallback when payment gateway is unavailable.

+ @{ + var wallets = new Dictionary(); + try + { + var settings = System.Text.Json.JsonSerializer.Deserialize>(@settingsJson); + if (settings != null && settings.ContainsKey("wallets")) + { + wallets = System.Text.Json.JsonSerializer.Deserialize>(settings["wallets"].GetRawText()) ?? new Dictionary(); + } + } + catch { } + + var supportedCurrencies = new[] { + new { Code = "BTC", Name = "Bitcoin", Placeholder = "bc1q... or 1... or 3..." }, + new { Code = "XMR", Name = "Monero", Placeholder = "4... (primary address)" }, + new { Code = "LTC", Name = "Litecoin", Placeholder = "ltc1q... or L... or M..." }, + new { Code = "DOGE", Name = "Dogecoin", Placeholder = "D..." }, + new { Code = "ETH", Name = "Ethereum", Placeholder = "0x..." }, + new { Code = "ZEC", Name = "Zcash", Placeholder = "t1... or zs1..." }, + new { Code = "DASH", Name = "Dash", Placeholder = "X..." } + }; + } + +
+ @Html.AntiForgeryToken() + @foreach (var currency in supportedCurrencies) + { +
+ + +
+ } + +
+ Note: Only configured wallets will be available as payment options. Leave blank to disable direct payments for that cryptocurrency. +
+ + +
+
+
+
Bot Configuration (JSON)
diff --git a/LittleShop/Areas/Admin/Views/Bots/Index.cshtml b/LittleShop/Areas/Admin/Views/Bots/Index.cshtml index 572b375..fcff5da 100644 --- a/LittleShop/Areas/Admin/Views/Bots/Index.cshtml +++ b/LittleShop/Areas/Admin/Views/Bots/Index.cshtml @@ -15,6 +15,86 @@

+@{ + // Detect duplicates by platform username + var duplicateGroups = Model + .Where(b => !string.IsNullOrEmpty(b.PlatformUsername)) + .GroupBy(b => b.PlatformUsername) + .Where(g => g.Count() > 1) + .ToList(); + + if (duplicateGroups.Any()) + { +
+
Duplicate Bots Detected
+

Found @duplicateGroups.Count duplicate bot group(s) with multiple entries for the same Telegram username.

+

This usually happens when the bot container restarts without a saved API key. The system now prevents this automatically.

+ + +
+ @foreach (var group in duplicateGroups) + { +
+
+
@@@group.Key (@group.Count() entries)
+ + + + + + + + + + + + @foreach (var bot in group.OrderByDescending(b => b.LastSeenAt ?? b.CreatedAt)) + { + var isNewest = bot == group.OrderByDescending(b => b.LastSeenAt ?? b.CreatedAt).First(); + + + + + + + + } + +
CreatedLast SeenStatusSessionsAction
@bot.CreatedAt.ToString("yyyy-MM-dd HH:mm") + @if (bot.LastSeenAt.HasValue) + { + @bot.LastSeenAt.Value.ToString("yyyy-MM-dd HH:mm") + } + else + { + Never + } + @bot.Status@bot.TotalSessions + @if (isNewest) + { + Keep (Most Recent) + } + else + { +
+ @Html.AntiForgeryToken() + +
+ } +
+
+
+ } +
+
+ } +} + @if (TempData["Success"] != null) {