Files
squad-rain-ops-mini/Services/BanService.cs

243 lines
8.5 KiB
C#

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<int> 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<bool> UnbanAsync(string steamId, string operatorName)
{
using (var db = DbHelper.GetInstance())
{
var record = await db.Queryable<BanRecord>()
.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<BanRecord> 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<BanRecord>()
.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<BanRecord>()
.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<string>();
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<object> GetBansAsync(int page, int pageSize, string search)
{
using (var db = DbHelper.GetInstance())
{
var query = db.Queryable<BanRecord>();
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x => x.SteamId.Contains(search) || x.Reason.Contains(search));
}
RefAsync<int> total = 0;
var list = await query.OrderByDescending(x => x.BanTime).ToPageListAsync(page, pageSize, total);
return new { total = total.Value, list };
}
}
}
}