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;
}
}
}