using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using RainOpsMini.Helpers; using RainOpsMini.Models; using SqlSugar; namespace RainOpsMini.Services { public class BanService { private readonly string _bansCfgPath; public BanService() { _bansCfgPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SquadGame", "ServerConfig", "Bans.cfg"); } public async Task BanPlayerAsync(string steamId, string reason, string durationStr, string operatorName) { long durationSeconds = 0; long expiryTimestamp = 0; // 0 means permanent // Parse Duration string dur = durationStr?.ToLower() ?? "perm"; DateTime now = DateTime.UtcNow; if (dur == "perm" || dur == "0") { expiryTimestamp = 0; } else { // Try parsing seconds directly first if (long.TryParse(dur, out long seconds)) { durationSeconds = seconds; } else if (dur.EndsWith("d") && int.TryParse(dur.Replace("d", ""), out int days)) { durationSeconds = days * 86400; } else if (dur.EndsWith("w") && int.TryParse(dur.Replace("w", ""), out int weeks)) { durationSeconds = weeks * 7 * 86400; } else if (dur.EndsWith("m") && int.TryParse(dur.Replace("m", ""), out int months)) { durationSeconds = months * 30 * 86400; // Approx } else if (dur.EndsWith("y") && int.TryParse(dur.Replace("y", ""), out int years)) { durationSeconds = years * 365 * 86400; // Approx } else if (dur.EndsWith("h") && int.TryParse(dur.Replace("h", ""), out int hours)) { durationSeconds = hours * 3600; } if (durationSeconds > 0) { expiryTimestamp = ((DateTimeOffset)now).ToUnixTimeSeconds() + durationSeconds; } } var banRecord = new BanRecord { SteamId = steamId, Reason = reason, DurationString = durationStr, Operator = operatorName, BanTime = now, BanTimestamp = ((DateTimeOffset)now).ToUnixTimeSeconds(), ExpiryTimestamp = expiryTimestamp, Status = 0 // Active }; using (var db = DbHelper.GetInstance()) { var result = await db.Insertable(banRecord).ExecuteCommandAsync(); // Sync to file and reload config await SyncToConfigFileAsync(); await ReloadServerConfigAsync(); return result; } } public async Task UnbanAsync(string steamId, string operatorName) { using (var db = DbHelper.GetInstance()) { var record = await db.Queryable() .Where(x => x.SteamId == steamId && x.Status == 0) .OrderByDescending(x => x.Id) .FirstAsync(); if (record == null) return false; record.Status = 2; // Unbanned manually record.UnbanTime = DateTime.UtcNow; record.UnbanOperator = operatorName; await db.Updateable(record).ExecuteCommandAsync(); // Sync to file and reload config await SyncToConfigFileAsync(); await ReloadServerConfigAsync(); return true; } } private async Task ReloadServerConfigAsync() { if (Program.Client != null && Program.Client.IsConnected) { try { await Program.Client.SendCommandAsync("AdminReloadServerConfig"); } catch (Exception ex) { RainOpsLog.Log($"[BanService] Failed to reload server config: {ex.Message}"); } } } public async Task SyncToConfigFileAsync() { // Ensure directory exists var directory = Path.GetDirectoryName(_bansCfgPath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } if (!File.Exists(_bansCfgPath)) { File.WriteAllText(_bansCfgPath, ""); } // Get active bans from DB List activeBans; long nowTs = ((DateTimeOffset)DateTime.UtcNow).ToUnixTimeSeconds(); using (var db = DbHelper.GetInstance()) { // Update expired bans status first // Status 0 (Active) but ExpiryTimestamp > 0 and < Now await db.Updateable() .SetColumns(it => new BanRecord { Status = 1 }) // Expired .Where(it => it.Status == 0 && it.ExpiryTimestamp > 0 && it.ExpiryTimestamp < nowTs) .ExecuteCommandAsync(); // Get Active Bans activeBans = await db.Queryable() .Where(it => it.Status == 0) .ToListAsync(); } // Format for Bans.cfg: SteamID:Timestamp // [Op: Admin] [Time: 2023-01-01 12:00:00] Reason var sb = new StringBuilder(); sb.AppendLine("//RainOps Mini AutoBan Info START"); foreach (var ban in activeBans) { // If timestamp is 0 (perm), keep it 0. string banTimeStr = ban.BanTime.ToString("yyyy-MM-dd HH:mm:ss"); string opStr = string.IsNullOrWhiteSpace(ban.Operator) ? "System" : ban.Operator; // Construct the comment part // Requirement: 注释操作人和操作时间以及封禁理由 // Example: // [Op: Admin] [Time: 2023-01-01 12:00:00] Cheating string comment = $"[Op: {opStr}] [Time: {banTimeStr}] {ban.Reason}"; sb.AppendLine($"{ban.SteamId}:{ban.ExpiryTimestamp} // {comment}"); } sb.AppendLine("//RainOps Mini AutoBan Info END"); string newBlock = sb.ToString(); // Read existing file string[] lines = await File.ReadAllLinesAsync(_bansCfgPath); var newFileContent = new List(); bool inBlock = false; bool blockFound = false; foreach (var line in lines) { if (line.Trim() == "//RainOps Mini AutoBan Info START") { inBlock = true; blockFound = true; // We will insert our new block here newFileContent.Add(newBlock.TrimEnd()); continue; } if (line.Trim() == "//RainOps Mini AutoBan Info END") { inBlock = false; continue; } if (!inBlock) { newFileContent.Add(line); } } if (!blockFound) { // If block not found, insert at the beginning (as requested: "第一行显示") newFileContent.Insert(0, newBlock.TrimEnd()); } await File.WriteAllLinesAsync(_bansCfgPath, newFileContent); } public async Task GetBansAsync(int page, int pageSize, string search) { using (var db = DbHelper.GetInstance()) { var query = db.Queryable(); if (!string.IsNullOrWhiteSpace(search)) { query = query.Where(x => x.SteamId.Contains(search) || x.Reason.Contains(search)); } RefAsync total = 0; var list = await query.OrderByDescending(x => x.BanTime).ToPageListAsync(page, pageSize, total); return new { total = total.Value, list }; } } } }