namespace Supercell.Laser.Server.Handler { using Newtonsoft.Json; using Supercell.Laser.Logic.Avatar; using Supercell.Laser.Logic.Club; using Supercell.Laser.Logic.Util; using Supercell.Laser.Server.Database; using Supercell.Laser.Server.Database.Cache; using Supercell.Laser.Server.Database.Models; using Supercell.Laser.Logic.Message.Home; using Supercell.Laser.Server.Logic.Game; using Supercell.Laser.Server.Message; using Supercell.Laser.Server.Networking; using Supercell.Laser.Server.Networking.Session; internal static class AdminConsoleTools { private static readonly string BackupRoot = Path.Combine(AppContext.BaseDirectory, "backups"); private static readonly string LogsRoot = Path.Combine(AppContext.BaseDirectory, "logs"); public static bool TryResolveAccount(string value, out Account account, out long accountId, out string tag) { account = null; accountId = 0; tag = value; if (string.IsNullOrWhiteSpace(value)) { return false; } if (value.StartsWith('#')) { accountId = LogicLongCodeGenerator.ToId(value); tag = value; } else if (!long.TryParse(value, out accountId)) { return false; } account = Accounts.Load(accountId); if (account == null) { return false; } if (!value.StartsWith('#')) { tag = "#" + LogicLongCodeGenerator.ToCode(accountId); } return true; } public static void GrantCurrency(Account account, string currency, int amount, string reason) { string normalizedCurrency = currency?.Trim().ToLowerInvariant(); switch (normalizedCurrency) { case "gems": account.Avatar.AddDiamonds(amount); break; case "gold": account.Avatar.AddGold(amount); break; case "starpoints": account.Avatar.AddStarPoints(amount); break; default: throw new InvalidOperationException($"Unsupported currency: {currency}"); } Accounts.Save(account); RefreshOnlineAccount(account.AccountId, $"{normalizedCurrency}:{amount}"); Logger.Success($"{normalizedCurrency} +{amount} issued to {account.Avatar.Name}#{account.AccountId}"); } public static void RefreshOnlineAccount(long accountId, string reason) { _ = reason; if (!Sessions.IsSessionActive(accountId)) { return; } Session session = Sessions.GetSession(accountId); if (session?.Connection?.Home == null || session.Connection.Avatar == null) { return; } session.Connection.Send(new OwnHomeDataMessage { Home = session.Connection.Home, Avatar = session.Connection.Avatar }); } public static string BuildServerStats() { var payload = new { Uptime = ServerDiagnostics.Uptime.ToString(@"dd\.hh\:mm\:ss"), TcpConnections = Connections.Count, OnlineSessions = Sessions.Count, Maintenance = Sessions.Maintenance, CachedAccounts = AccountCache.Count, CachedAlliances = AllianceCache.Count, TotalAccounts = Accounts.GetTotalCount(), TotalAlliances = Alliances.GetTotalCount(), TopPlayersLoaded = Leaderboards.GetAvatarRankingList().Length, TopAlliancesLoaded = Leaderboards.GetAllianceRankingList().Length, MemoryMb = Math.Round(GC.GetTotalMemory(false) / 1024d / 1024d, 2), TopPackets = ServerDiagnostics.BuildPacketSummary(5) }; return JsonConvert.SerializeObject(payload, Formatting.Indented); } public static string BuildServerStatsForBot() { TimeSpan uptime = ServerDiagnostics.Uptime; string uptimeStr = uptime.TotalDays >= 1 ? $"{(int)uptime.TotalDays}д {uptime.Hours}ч {uptime.Minutes}м" : $"{uptime.Hours}ч {uptime.Minutes}м {uptime.Seconds}с"; double memMb = Math.Round(GC.GetTotalMemory(false) / 1024d / 1024d, 1); string packetSummary = ServerDiagnostics.BuildPacketSummary(4); string packetLines = string.IsNullOrWhiteSpace(packetSummary) ? "• нет данных" : string.Join("\n", packetSummary .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .Select(line => $"• {line}")); return "🖥 Состояние сервера\n\n" + $"• Онлайн: {Sessions.Count}\n" + $"• TCP: {Connections.Count}\n" + $"• Матчмейкинг: {Matchmaking.GetTotalQueueCount()} в очереди / {Matchmaking.SlotCount} слотов\n" + $"• Бои: {Battles.Count}\n" + $"• Команды: {Teams.Count}\n\n" + "🗃 Данные\n\n" + $"• Аккаунты: {Accounts.GetTotalCount()} (кэш {AccountCache.Count})\n" + $"• Клубы: {Alliances.GetTotalCount()} (кэш {AllianceCache.Count})\n" + $"• Топ игроков: {Leaderboards.GetAvatarRankingList().Length}\n" + $"• Топ клубов: {Leaderboards.GetAllianceRankingList().Length}\n\n" + "⚙️ Ресурсы\n\n" + $"• Память: {memMb} МБ\n" + $"• Аптайм: {uptimeStr}\n" + $"• Техобслуживание: {(Sessions.Maintenance ? "вкл" : "выкл")}\n\n" + "📦 Топ пакетов\n" + packetLines; } public static string BackupDatabase() { string backupDirectory = Path.Combine(BackupRoot, $"db_{DateTime.UtcNow:yyyyMMdd_HHmmss}"); Directory.CreateDirectory(backupDirectory); string accountsPath = Path.Combine(backupDirectory, "accounts.json"); string alliancesPath = Path.Combine(backupDirectory, "alliances.json"); string metadataPath = Path.Combine(backupDirectory, "meta.json"); Accounts.ExportAll(accountsPath); Alliances.ExportAll(alliancesPath); File.WriteAllText(metadataPath, JsonConvert.SerializeObject(new { CreatedAtUtc = DateTime.UtcNow, Accounts = Accounts.GetTotalCount(), Alliances = Alliances.GetTotalCount() }, Formatting.Indented)); return backupDirectory; } public static string ExportAudit(int count, string filter, bool suspiciousOnly) { Directory.CreateDirectory(LogsRoot); Directory.CreateDirectory(Path.Combine(LogsRoot, "exports")); string pattern = suspiciousOnly ? "suspicious_*.log" : "audit_*.log"; var files = Directory.GetFiles(LogsRoot, pattern) .OrderByDescending(File.GetLastWriteTimeUtc) .ToArray(); var lines = new List(); foreach (string file in files) { foreach (string line in File.ReadLines(file).Reverse()) { if (!string.IsNullOrWhiteSpace(filter) && line.IndexOf(filter, StringComparison.OrdinalIgnoreCase) < 0) { continue; } lines.Add(line); if (lines.Count >= count) { break; } } if (lines.Count >= count) { break; } } lines.Reverse(); string outputPath = Path.Combine( LogsRoot, "exports", $"{(suspiciousOnly ? "suspicious" : "audit")}_extract_{DateTime.UtcNow:yyyyMMdd_HHmmss}.log"); File.WriteAllLines(outputPath, lines); return outputPath; } public static string BuildLeaderboardReport(string type, int count) { count = Math.Clamp(count, 1, 50); if (string.Equals(type, "alliances", StringComparison.OrdinalIgnoreCase) || string.Equals(type, "clubs", StringComparison.OrdinalIgnoreCase)) { Alliance[] alliances = Leaderboards.GetAllianceRankingList().Take(count).ToArray(); return string.Join(Environment.NewLine, alliances.Select((alliance, index) => $"{index + 1}. {alliance.Name} | trophies={alliance.Trophies} | id={alliance.Id}")); } Account[] accounts = Leaderboards.GetAvatarRankingList().Take(count).ToArray(); return string.Join(Environment.NewLine, accounts.Select((account, index) => $"{index + 1}. {account.Avatar.Name} | trophies={account.Avatar.Trophies} | tag=#{LogicLongCodeGenerator.ToCode(account.AccountId)} | id={account.AccountId}")); } public static string CreateVoucher(string code, IReadOnlyList rewardArgs) { if (string.IsNullOrWhiteSpace(code)) { throw new InvalidOperationException("Voucher code is empty."); } var items = new List(); foreach (string rewardArg in rewardArgs) { string[] parts = rewardArg.Split('=', 2, StringSplitOptions.TrimEntries); if (parts.Length != 2) { throw new InvalidOperationException($"Invalid reward format: {rewardArg}"); } string key = parts[0].ToLowerInvariant(); string value = parts[1]; switch (key) { case "gold": case "coins": items.Add(new VoucherManager.VoucherItemDefinition { ID = 1, Multiplier = ParsePositiveInt(value, key) }); break; case "gems": items.Add(new VoucherManager.VoucherItemDefinition { ID = 16, Multiplier = ParsePositiveInt(value, key) }); break; case "starpoints": case "sp": items.Add(new VoucherManager.VoucherItemDefinition { ID = 17, Multiplier = ParsePositiveInt(value, key) }); break; case "brawler": items.Add(new VoucherManager.VoucherItemDefinition { ID = 3, BrawlerID = ParsePositiveInt(value, key) }); break; case "skin": items.Add(new VoucherManager.VoucherItemDefinition { ID = 4, SkinID = ParsePositiveInt(value, key) }); break; case "emote": items.Add(new VoucherManager.VoucherItemDefinition { ID = 19, SkinID = ParsePositiveInt(value, key) }); break; case "box_small": items.Add(new VoucherManager.VoucherItemDefinition { RewardType = "box_small", Multiplier = ParsePositiveInt(value, key) }); break; case "box_big": items.Add(new VoucherManager.VoucherItemDefinition { RewardType = "box_big", Multiplier = ParsePositiveInt(value, key) }); break; case "box_mega": items.Add(new VoucherManager.VoucherItemDefinition { RewardType = "box_mega", Multiplier = ParsePositiveInt(value, key) }); break; case "brawler_random": items.Add(new VoucherManager.VoucherItemDefinition { RewardType = "brawler_random", RewardData = value }); break; case "skin_random": items.Add(new VoucherManager.VoucherItemDefinition { RewardType = "skin_random", RewardData = value }); break; default: throw new InvalidOperationException($"Unsupported voucher reward type: {key}"); } } if (items.Count == 0) { throw new InvalidOperationException("Voucher must contain at least one reward."); } string path = LogicHelperConfig.vouchersPath; Directory.CreateDirectory(Path.GetDirectoryName(path) ?? AppContext.BaseDirectory); var vouchers = File.Exists(path) ? JsonConvert.DeserializeObject>(File.ReadAllText(path)) ?? new List() : new List(); VoucherManager.VoucherDefinition existing = vouchers.FirstOrDefault(v => string.Equals(v.Voucher, code, StringComparison.OrdinalIgnoreCase)); if (existing != null) { existing.Items = items; existing.WhoUsed ??= new List(); } else { vouchers.Add(new VoucherManager.VoucherDefinition { Voucher = code, Items = items, WhoUsed = new List() }); } File.WriteAllText(path, JsonConvert.SerializeObject(vouchers, Formatting.Indented)); return path; } private static int ParsePositiveInt(string value, string key) { if (!int.TryParse(value, out int parsed) || parsed <= 0) { throw new InvalidOperationException($"Invalid value for {key}: {value}"); } return parsed; } } }