mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-11-07 12:07:05 +01:00
Migrate menus and votes to Panorama UI Kit
Replace MenuManagerCS2 with Panorama UI Kit for the admin menus and css_vote. Layout names (menu, vote, banner, toast) are configurable under MenuConfig and preloaded on startup; MenuType is removed and ConfigVersion bumped to 26. - Drop MenuManagerApi.dll and the old chat menu classes - Reference PanoramaUiKit.Shared.dll, bump CounterStrikeSharp to 1.0.376 - Add .editorconfig and CODING_CONVENTIONS.md, apply them codebase-wide - Remove the CS2-SimpleAdmin-docs site - Rewrite README for the fork
This commit is contained in:
parent
03a454b1b5
commit
460d8b2b85
112 changed files with 5559 additions and 36371 deletions
|
|
@ -1,12 +1,10 @@
|
|||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
||||
using System.Data.Common;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MySqlConnector;
|
||||
using System.Text;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
|
|
@ -22,19 +20,29 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>The newly created ban ID if successful, otherwise null.</returns>
|
||||
public async Task<int?> BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
try
|
||||
{
|
||||
var sql = databaseProvider.GetAddBanQuery();
|
||||
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
string? playerIp = null;
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 1)
|
||||
{
|
||||
playerIp = player.IpAddress;
|
||||
}
|
||||
|
||||
string sql = databaseProvider.GetAddBanQuery();
|
||||
int? banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
playerName = player.Name,
|
||||
playerIp = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 1 ? player.IpAddress : null,
|
||||
playerIp,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||
banReason = reason,
|
||||
|
|
@ -46,7 +54,7 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
return banId;
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
|
||||
return null;
|
||||
|
|
@ -63,16 +71,19 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>The ID of the newly created ban if successful, otherwise null.</returns>
|
||||
public async Task<int?> AddBanBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddBanBySteamIdQuery();
|
||||
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAddBanBySteamIdQuery();
|
||||
int? banId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
|
|
@ -83,10 +94,10 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
created = now,
|
||||
serverid = CS2_SimpleAdmin.ServerId
|
||||
});
|
||||
|
||||
|
||||
return banId;
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
|
||||
return null;
|
||||
|
|
@ -102,18 +113,24 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
|
||||
public async Task AddBanByIp(string playerIp, PlayerInfo? issuer, string reason, int time = 0)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(playerIp)) return;
|
||||
if (string.IsNullOrEmpty(playerIp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetAddBanByIpQuery();
|
||||
string sql = databaseProvider.GetAddBanByIpQuery();
|
||||
await connection.ExecuteAsync(sql, new
|
||||
{
|
||||
playerIp,
|
||||
|
|
@ -129,179 +146,179 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
catch { }
|
||||
}
|
||||
|
||||
// public async Task<bool> IsPlayerBanned(PlayerInfo player)
|
||||
// {
|
||||
// if (database == null) return false;
|
||||
//
|
||||
// if (player.IpAddress == null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// #if DEBUG
|
||||
// if (CS2_SimpleAdmin._logger != null)
|
||||
// CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}");
|
||||
// #endif
|
||||
//
|
||||
// int banCount;
|
||||
//
|
||||
// DateTime currentTime = Time.ActualDateTime();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// string sql;
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp && !CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress))
|
||||
// {
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
|
||||
// SELECT COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// ), 0)
|
||||
// +
|
||||
// COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
// WHERE sa_bans.status = 'ACTIVE'
|
||||
// AND sa_players_ips.address = @PlayerIP
|
||||
// AND NOT EXISTS (
|
||||
// SELECT 1
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// )
|
||||
// ), 0) AS TotalBanCount;
|
||||
// """ : """
|
||||
// SELECT COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// AND server_id = @ServerId
|
||||
// ), 0)
|
||||
// +
|
||||
// COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
// WHERE sa_bans.status = 'ACTIVE'
|
||||
// AND sa_players_ips.address = @PlayerIP
|
||||
// AND NOT EXISTS (
|
||||
// SELECT 1
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// AND server_id = @ServerId
|
||||
// )
|
||||
// ), 0) AS TotalBanCount;
|
||||
// """;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
|
||||
// UPDATE sa_bans
|
||||
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
|
||||
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime);
|
||||
//
|
||||
// SELECT COUNT(*) FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime);
|
||||
// """ : """
|
||||
// UPDATE sa_bans
|
||||
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
|
||||
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
|
||||
//
|
||||
// SELECT COUNT(*) FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
|
||||
// """;
|
||||
// }
|
||||
//
|
||||
// await using var connection = await database.GetConnectionAsync();
|
||||
//
|
||||
// var parameters = new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
|
||||
// string.IsNullOrEmpty(player.IpAddress) ||
|
||||
// CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress)
|
||||
// ? null
|
||||
// : player.IpAddress,
|
||||
// PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
|
||||
// CurrentTime = currentTime,
|
||||
// CS2_SimpleAdmin.ServerId
|
||||
// };
|
||||
//
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
|
||||
// player.Name, ex.Message);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return banCount > 0;
|
||||
// }
|
||||
//
|
||||
// public async Task<int> GetPlayerBans(PlayerInfo player)
|
||||
// {
|
||||
// if (database == null) return 0;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// string sql;
|
||||
//
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
|
||||
// ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"
|
||||
// : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid";
|
||||
//
|
||||
// int banCount;
|
||||
//
|
||||
// await using var connection = await database.GetConnectionAsync();
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress))
|
||||
// {
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql,
|
||||
// new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = player.IpAddress,
|
||||
// serverid = CS2_SimpleAdmin.ServerId
|
||||
// });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql,
|
||||
// new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = DBNull.Value,
|
||||
// serverid = CS2_SimpleAdmin.ServerId
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return banCount;
|
||||
// }
|
||||
// catch { }
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
// public async Task<bool> IsPlayerBanned(PlayerInfo player)
|
||||
// {
|
||||
// if (database == null) return false;
|
||||
//
|
||||
// if (player.IpAddress == null)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// #if DEBUG
|
||||
// if (CS2_SimpleAdmin._logger != null)
|
||||
// CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}");
|
||||
// #endif
|
||||
//
|
||||
// int banCount;
|
||||
//
|
||||
// DateTime currentTime = Time.ActualDateTime();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// string sql;
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp && !CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress))
|
||||
// {
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
|
||||
// SELECT COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// ), 0)
|
||||
// +
|
||||
// COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
// WHERE sa_bans.status = 'ACTIVE'
|
||||
// AND sa_players_ips.address = @PlayerIP
|
||||
// AND NOT EXISTS (
|
||||
// SELECT 1
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// )
|
||||
// ), 0) AS TotalBanCount;
|
||||
// """ : """
|
||||
// SELECT COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// AND server_id = @ServerId
|
||||
// ), 0)
|
||||
// +
|
||||
// COALESCE((
|
||||
// SELECT COUNT(*)
|
||||
// FROM sa_bans
|
||||
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
|
||||
// WHERE sa_bans.status = 'ACTIVE'
|
||||
// AND sa_players_ips.address = @PlayerIP
|
||||
// AND NOT EXISTS (
|
||||
// SELECT 1
|
||||
// FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime)
|
||||
// AND server_id = @ServerId
|
||||
// )
|
||||
// ), 0) AS TotalBanCount;
|
||||
// """;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
|
||||
// UPDATE sa_bans
|
||||
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
|
||||
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime);
|
||||
//
|
||||
// SELECT COUNT(*) FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime);
|
||||
// """ : """
|
||||
// UPDATE sa_bans
|
||||
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
|
||||
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
|
||||
//
|
||||
// SELECT COUNT(*) FROM sa_bans
|
||||
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
|
||||
// AND status = 'ACTIVE'
|
||||
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
|
||||
// """;
|
||||
// }
|
||||
//
|
||||
// await using var connection = await database.GetConnectionAsync();
|
||||
//
|
||||
// var parameters = new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
|
||||
// string.IsNullOrEmpty(player.IpAddress) ||
|
||||
// CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress)
|
||||
// ? null
|
||||
// : player.IpAddress,
|
||||
// PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
|
||||
// CurrentTime = currentTime,
|
||||
// CS2_SimpleAdmin.ServerId
|
||||
// };
|
||||
//
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
|
||||
// player.Name, ex.Message);
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return banCount > 0;
|
||||
// }
|
||||
//
|
||||
// public async Task<int> GetPlayerBans(PlayerInfo player)
|
||||
// {
|
||||
// if (database == null) return 0;
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// string sql;
|
||||
//
|
||||
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
|
||||
// ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"
|
||||
// : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid";
|
||||
//
|
||||
// int banCount;
|
||||
//
|
||||
// await using var connection = await database.GetConnectionAsync();
|
||||
//
|
||||
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress))
|
||||
// {
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql,
|
||||
// new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = player.IpAddress,
|
||||
// serverid = CS2_SimpleAdmin.ServerId
|
||||
// });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// banCount = await connection.ExecuteScalarAsync<int>(sql,
|
||||
// new
|
||||
// {
|
||||
// PlayerSteamID = player.SteamId.SteamId64,
|
||||
// PlayerIP = DBNull.Value,
|
||||
// serverid = CS2_SimpleAdmin.ServerId
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return banCount;
|
||||
// }
|
||||
// catch { }
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Unbans a player based on a pattern match of SteamID or IP address.
|
||||
|
|
@ -309,46 +326,51 @@ internal class BanManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="playerPattern">Pattern to match against player identifiers (e.g., partial SteamID).</param>
|
||||
/// <param name="adminSteamId">SteamID64 of the admin performing the unban.</param>
|
||||
/// <param name="reason">Optional reason for the unban. If null or empty, the unban reason is not stored.</param>
|
||||
public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
|
||||
if (playerPattern is not { Length: > 1 })
|
||||
public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sqlRetrieveBans = databaseProvider.GetUnbanRetrieveBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
|
||||
var bansList = bans as dynamic[] ?? bans.ToArray();
|
||||
if (bansList.Length == 0)
|
||||
return;
|
||||
|
||||
var sqlAdminId = databaseProvider.GetUnbanAdminIdQuery();
|
||||
var adminId = await connection.ExecuteScalarAsync<int?>(sqlAdminId, new { adminSteamId }) ?? 0;
|
||||
|
||||
foreach (var ban in bansList)
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
int banId = ban.id;
|
||||
|
||||
var sqlInsertUnban = databaseProvider.GetInsertUnbanQuery(reason != null);
|
||||
var unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
|
||||
|
||||
var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
|
||||
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply immediately; the periodic cache refresh would otherwise keep rejecting the player for up to a minute
|
||||
CS2_SimpleAdmin.Instance.CacheManager?.SetBanStatus(bansList.Select(b => (int)b.id), Models.BanStatus.UNBANNED);
|
||||
|
||||
// css_ban also issues a native banid when UnlockedCommands is on; clear it so the engine stops
|
||||
// rejecting the player. Done here, after the rows are updated, and for whichever pattern matched them.
|
||||
if (CS2_SimpleAdmin.UnlockedCommands)
|
||||
if (playerPattern is not { Length: > 1 })
|
||||
{
|
||||
var steamIds = bansList
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sqlRetrieveBans = databaseProvider.GetUnbanRetrieveBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
IEnumerable<dynamic> bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
|
||||
dynamic[] bansList = bans as dynamic[] ?? bans.ToArray();
|
||||
if (bansList.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string sqlAdminId = databaseProvider.GetUnbanAdminIdQuery();
|
||||
int adminId = await connection.ExecuteScalarAsync<int?>(sqlAdminId, new { adminSteamId }) ?? 0;
|
||||
|
||||
foreach (dynamic ban in bansList)
|
||||
{
|
||||
int banId = ban.id;
|
||||
|
||||
string sqlInsertUnban = databaseProvider.GetInsertUnbanQuery(reason != null);
|
||||
int unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
|
||||
|
||||
string sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
|
||||
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
|
||||
}
|
||||
|
||||
// Apply immediately; the periodic cache refresh would otherwise keep rejecting the player for up to a minute
|
||||
CS2_SimpleAdmin.Instance.CacheManager?.SetBanStatus(bansList.Select(b => (int)b.id), Models.BanStatus.UNBANNED);
|
||||
|
||||
// css_ban also issues a native banid when UnlockedCommands is on; clear it so the engine stops
|
||||
// rejecting the player. Done here, after the rows are updated, and for whichever pattern matched them.
|
||||
if (CS2_SimpleAdmin.UnlockedCommands)
|
||||
{
|
||||
List<ulong> steamIds = bansList
|
||||
.Select(b => (object?)b.player_steamid)
|
||||
.Where(id => id != null)
|
||||
.Select(id => Convert.ToUInt64(id))
|
||||
|
|
@ -356,19 +378,23 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string
|
|||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (steamIds.Count > 0)
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
if (steamIds.Count > 0)
|
||||
{
|
||||
foreach (var steamId in steamIds)
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
foreach (ulong steamId in steamIds)
|
||||
{
|
||||
Server.ExecuteCommand($"removeid {new SteamID(steamId).SteamId3}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to unban player: {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to unban player: {exception}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
|
||||
// {
|
||||
|
|
@ -445,17 +471,21 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string
|
|||
/// </summary>
|
||||
public async Task ExpireOldBans()
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
var currentTime = Time.ActualDateTime();
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime currentTime = Time.ActualDateTime();
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetExpireBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetExpireBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { currentTime, serverid = CS2_SimpleAdmin.ServerId });
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans > 0)
|
||||
{
|
||||
var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
DateTime ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
|
||||
|
||||
|
|
@ -468,4 +498,4 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string
|
|||
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired bans");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
using System.Collections.Concurrent;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using System.Data.Common;
|
||||
using CS2_SimpleAdmin.Models;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZLinq;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
internal class CacheManager: IDisposable
|
||||
internal class CacheManager : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
|
||||
// Rebuilt into fresh instances and swapped, so unlocked readers on the game thread never see a half-built index
|
||||
|
|
@ -17,22 +16,33 @@ internal class CacheManager: IDisposable
|
|||
|
||||
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
|
||||
private HashSet<uint> _cachedIgnoredIps = [];
|
||||
|
||||
|
||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||
private DateTime? _lastDatabaseTime = null; // Track actual time from database
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and builds the ban and IP cache from the database. Loads bans, player IP history, and config settings.
|
||||
/// </summary>
|
||||
/// <returns>Asynchronous task representing the initialization process.</returns>
|
||||
public async Task InitializeCacheAsync()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!CS2_SimpleAdmin.ServerLoaded) return;
|
||||
if (_isInitialized) return;
|
||||
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CS2_SimpleAdmin.ServerLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Clear();
|
||||
|
|
@ -41,9 +51,9 @@ internal class CacheManager: IDisposable
|
|||
.Select(IpHelper.IpToUint)
|
||||
.ToHashSet();
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
List<BanRecord> bans;
|
||||
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
bans = (await connection.QueryAsync<BanRecord>(
|
||||
|
|
@ -69,21 +79,21 @@ internal class CacheManager: IDisposable
|
|||
status AS Status
|
||||
FROM sa_bans
|
||||
WHERE server_id = @serverId
|
||||
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
|
||||
""", new { serverId = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
// Optimization: Load IP history and build cache in single pass
|
||||
var ipHistory = await connection.QueryAsync<IpHistoryRow>(
|
||||
IEnumerable<IpHistoryRow> ipHistory = await connection.QueryAsync<IpHistoryRow>(
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY steamid, address, used_at DESC");
|
||||
|
||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var currentSteamId = 0UL;
|
||||
var currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
var latestIpTimestamps = new Dictionary<uint, DateTime>();
|
||||
string unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
ulong currentSteamId = 0UL;
|
||||
HashSet<IpRecord> currentIpSet = new HashSet<IpRecord>(new IpRecordComparer());
|
||||
Dictionary<uint, DateTime> latestIpTimestamps = new Dictionary<uint, DateTime>();
|
||||
|
||||
foreach (var record in ipHistory)
|
||||
foreach (IpHistoryRow record in ipHistory)
|
||||
{
|
||||
// When we encounter a new steamid, save the previous one
|
||||
if ((ulong)record.Steamid != currentSteamId && currentSteamId != 0)
|
||||
|
|
@ -96,14 +106,14 @@ internal class CacheManager: IDisposable
|
|||
currentSteamId = (ulong)record.Steamid;
|
||||
|
||||
// Only keep the latest timestamp for each IP
|
||||
if (!latestIpTimestamps.TryGetValue(record.Address, out var existingTimestamp) ||
|
||||
if (!latestIpTimestamps.TryGetValue(record.Address, out DateTime existingTimestamp) ||
|
||||
record.Used_at > existingTimestamp)
|
||||
{
|
||||
latestIpTimestamps[record.Address] = record.Used_at;
|
||||
currentIpSet.Add(new IpRecord(
|
||||
record.Address,
|
||||
record.Used_at,
|
||||
string.IsNullOrEmpty(record.Name) ? unknownName : record.Name
|
||||
NameOrDefault(record.Name, unknownName)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -115,11 +125,13 @@ internal class CacheManager: IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
foreach (var ban in bans.AsValueEnumerable())
|
||||
foreach (BanRecord? ban in bans.AsValueEnumerable())
|
||||
{
|
||||
_banCache.TryAdd(ban.Id, ban);
|
||||
|
||||
}
|
||||
|
||||
RebuildIndexes();
|
||||
|
||||
|
||||
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
|
@ -128,7 +140,7 @@ internal class CacheManager: IDisposable
|
|||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cached data and reinitializes the cache from the database.
|
||||
/// </summary>
|
||||
|
|
@ -136,15 +148,15 @@ internal class CacheManager: IDisposable
|
|||
public async Task ForceReInitializeCacheAsync()
|
||||
{
|
||||
_isInitialized = false;
|
||||
|
||||
|
||||
_banCache.Clear();
|
||||
_playerIpsCache.Clear();
|
||||
_cachedIgnoredIps = [];
|
||||
_lastUpdateTime = DateTime.MinValue;
|
||||
|
||||
|
||||
await InitializeCacheAsync();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the in-memory cache with updated or new data from the database since the last update time.
|
||||
/// Also updates multi-account IP history if enabled.
|
||||
|
|
@ -152,16 +164,23 @@ internal class CacheManager: IDisposable
|
|||
/// <returns>Asynchronous task representing the refresh operation.</returns>
|
||||
public async Task RefreshCacheAsync()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (!_isInitialized) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<BanRecord> updatedBans;
|
||||
|
||||
// Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC)
|
||||
var currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("SELECT CURRENT_TIMESTAMP");
|
||||
DateTime currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("SELECT CURRENT_TIMESTAMP");
|
||||
|
||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||
// Most of the time bans are just added/updated, not deleted
|
||||
|
|
@ -170,10 +189,10 @@ internal class CacheManager: IDisposable
|
|||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||
{
|
||||
// Use previous database time or start from far past if first run
|
||||
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
DateTime lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
|
||||
// Get recently updated bans by timestamp (using database time to avoid timezone issues)
|
||||
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
List<BanRecord> updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT id AS Id,
|
||||
player_name AS PlayerName,
|
||||
|
|
@ -186,10 +205,10 @@ internal class CacheManager: IDisposable
|
|||
)).ToList();
|
||||
|
||||
// Detect changes: new bans or status changes
|
||||
var updatedList = new List<BanRecord>();
|
||||
foreach (var ban in updatedBans_Query)
|
||||
List<BanRecord> updatedList = new List<BanRecord>();
|
||||
foreach (BanRecord ban in updatedBans_Query)
|
||||
{
|
||||
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||
if (!_banCache.TryGetValue(ban.Id, out BanRecord? cachedBan))
|
||||
{
|
||||
// New ban
|
||||
updatedList.Add(ban);
|
||||
|
|
@ -213,10 +232,10 @@ internal class CacheManager: IDisposable
|
|||
else
|
||||
{
|
||||
// Use previous database time or start from far past if first run
|
||||
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
DateTime lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||
|
||||
// Get recently updated bans for this server by timestamp (using database time to avoid timezone issues)
|
||||
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
List<BanRecord> updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||
"""
|
||||
SELECT id AS Id,
|
||||
player_name AS PlayerName,
|
||||
|
|
@ -229,10 +248,10 @@ internal class CacheManager: IDisposable
|
|||
)).ToList();
|
||||
|
||||
// Detect changes: new bans or status changes
|
||||
var updatedList = new List<BanRecord>();
|
||||
foreach (var ban in updatedBans_Query)
|
||||
List<BanRecord> updatedList = new List<BanRecord>();
|
||||
foreach (BanRecord ban in updatedBans_Query)
|
||||
{
|
||||
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||
if (!_banCache.TryGetValue(ban.Id, out BanRecord? cachedBan))
|
||||
{
|
||||
// New ban
|
||||
updatedList.Add(ban);
|
||||
|
|
@ -260,49 +279,56 @@ internal class CacheManager: IDisposable
|
|||
// Optimization: Only process deletions if we have the full ID list
|
||||
if (allIds != null)
|
||||
{
|
||||
foreach (var id in _banCache.Keys)
|
||||
foreach (int id in _banCache.Keys)
|
||||
{
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
|
||||
if (allIds.Contains(id) || !_banCache.TryRemove(id, out BanRecord? ban))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ban.PlayerSteamId != null &&
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
|
||||
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out List<BanRecord>? steamBans))
|
||||
{
|
||||
steamBans.RemoveAll(b => b.Id == id);
|
||||
if (steamBans.Count == 0)
|
||||
{
|
||||
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipBans))
|
||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out uint ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out List<BanRecord>? ipBans))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ipBans.RemoveAll(b => b.Id == id);
|
||||
if (ipBans.Count == 0)
|
||||
{
|
||||
_ipIndex.TryRemove(ipUInt, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
var ipHistory = (await connection.QueryAsync<IpHistoryRow>(
|
||||
IEnumerable<IpHistoryRow> ipHistory = (await connection.QueryAsync<IpHistoryRow>(
|
||||
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300",
|
||||
new { lastUpdate = _lastUpdateTime }));
|
||||
|
||||
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.Steamid))
|
||||
foreach (IGrouping<long, IpHistoryRow>? group in ipHistory.AsValueEnumerable().GroupBy(x => x.Steamid))
|
||||
{
|
||||
var ipSet = new HashSet<IpRecord>(
|
||||
HashSet<IpRecord> ipSet = new HashSet<IpRecord>(
|
||||
group
|
||||
.GroupBy(x => x.Address)
|
||||
.Select(g =>
|
||||
{
|
||||
var latest = g.MaxBy(x => x.Used_at)!; // group is never empty
|
||||
IpHistoryRow latest = g.MaxBy(x => x.Used_at)!; // group is never empty
|
||||
return new IpRecord(
|
||||
g.Key,
|
||||
latest.Used_at,
|
||||
!string.IsNullOrEmpty(latest.Name)
|
||||
? latest.Name
|
||||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
|
||||
NameOrDefault(latest.Name, CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown")
|
||||
);
|
||||
}),
|
||||
new IpRecordComparer()
|
||||
|
|
@ -313,7 +339,7 @@ internal class CacheManager: IDisposable
|
|||
_ => ipSet,
|
||||
(_, existingSet) =>
|
||||
{
|
||||
foreach (var newEntry in ipSet)
|
||||
foreach (IpRecord newEntry in ipSet)
|
||||
{
|
||||
existingSet.Remove(newEntry);
|
||||
existingSet.Add(newEntry);
|
||||
|
|
@ -325,10 +351,10 @@ internal class CacheManager: IDisposable
|
|||
}
|
||||
|
||||
// Update cache with new/modified bans
|
||||
var needsRebuild = false;
|
||||
foreach (var ban in updatedBans)
|
||||
bool needsRebuild = false;
|
||||
foreach (BanRecord ban in updatedBans)
|
||||
{
|
||||
if (_banCache.TryGetValue(ban.Id, out var oldBan) && oldBan.Status != ban.Status)
|
||||
if (_banCache.TryGetValue(ban.Id, out BanRecord? oldBan) && oldBan.Status != ban.Status)
|
||||
{
|
||||
// Ban status changed (e.g., ACTIVE -> EXPIRED/UNBANNED), need to rebuild indexes
|
||||
needsRebuild = true;
|
||||
|
|
@ -370,36 +396,43 @@ internal class CacheManager: IDisposable
|
|||
/// <param name="status">New status.</param>
|
||||
public void SetBanStatus(IEnumerable<int> banIds, BanStatus status)
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var id in banIds)
|
||||
bool changed = false;
|
||||
foreach (int id in banIds)
|
||||
{
|
||||
if (!_banCache.TryGetValue(id, out var ban) || ban.StatusEnum == status) continue;
|
||||
if (!_banCache.TryGetValue(id, out BanRecord? ban) || ban.StatusEnum == status)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_banCache[id] = ban with { Status = status.ToString() };
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) RebuildIndexes();
|
||||
if (changed)
|
||||
{
|
||||
RebuildIndexes();
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildIndexesCore()
|
||||
{
|
||||
var steamIdIndex = new ConcurrentDictionary<ulong, List<BanRecord>>();
|
||||
var ipIndex = new ConcurrentDictionary<uint, List<BanRecord>>();
|
||||
ConcurrentDictionary<ulong, List<BanRecord>> steamIdIndex = new ConcurrentDictionary<ulong, List<BanRecord>>();
|
||||
ConcurrentDictionary<uint, List<BanRecord>> ipIndex = new ConcurrentDictionary<uint, List<BanRecord>>();
|
||||
|
||||
// Optimization: Cache config value to avoid repeated property access
|
||||
var banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
|
||||
var checkIpBans = banType != 0;
|
||||
int banType = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType;
|
||||
bool checkIpBans = banType != 0;
|
||||
|
||||
// Optimization: Pre-filter only ACTIVE bans to avoid checking status in loop
|
||||
var activeBans = _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
IEnumerable<BanRecord> activeBans = _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
|
||||
foreach (var ban in activeBans)
|
||||
foreach (BanRecord? ban in activeBans)
|
||||
{
|
||||
// Index by Steam ID
|
||||
if (ban.PlayerSteamId.HasValue)
|
||||
{
|
||||
var steamId = ban.PlayerSteamId.Value;
|
||||
if (!steamIdIndex.TryGetValue(steamId, out var steamList))
|
||||
ulong steamId = ban.PlayerSteamId.Value;
|
||||
if (!steamIdIndex.TryGetValue(steamId, out List<BanRecord>? steamList))
|
||||
{
|
||||
steamList = new List<BanRecord>();
|
||||
steamIdIndex[steamId] = steamList;
|
||||
|
|
@ -409,9 +442,9 @@ internal class CacheManager: IDisposable
|
|||
|
||||
// Index by IP (only if IP bans are enabled)
|
||||
if (checkIpBans && !string.IsNullOrEmpty(ban.PlayerIp) &&
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
|
||||
IpHelper.TryConvertIpToUint(ban.PlayerIp, out uint ipUInt))
|
||||
{
|
||||
if (!ipIndex.TryGetValue(ipUInt, out var ipList))
|
||||
if (!ipIndex.TryGetValue(ipUInt, out List<BanRecord>? ipList))
|
||||
{
|
||||
ipList = new List<BanRecord>();
|
||||
ipIndex[ipUInt] = ipList;
|
||||
|
|
@ -424,19 +457,19 @@ internal class CacheManager: IDisposable
|
|||
Volatile.Write(ref _steamIdIndex, steamIdIndex);
|
||||
Volatile.Write(ref _ipIndex, ipIndex);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all ban records currently stored in the cache.
|
||||
/// </summary>
|
||||
/// <returns>List of all <see cref="BanRecord"/> objects.</returns>
|
||||
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves only active ban records from the cache.
|
||||
/// </summary>
|
||||
/// <returns>List of active <see cref="BanRecord"/> objects.</returns>
|
||||
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE).ToList();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all ban records for a specific player by their Steam ID.
|
||||
/// </summary>
|
||||
|
|
@ -446,7 +479,7 @@ internal class CacheManager: IDisposable
|
|||
// ponytail: O(n) over all cached bans per call; add an all-status index if this gets hot.
|
||||
public List<BanRecord> GetPlayerBansBySteamId(ulong steamId) =>
|
||||
_banCache.Values.Where(b => b.PlayerSteamId == steamId).ToList();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets all known Steam accounts that have used the specified IP address.
|
||||
/// </summary>
|
||||
|
|
@ -456,16 +489,16 @@ internal class CacheManager: IDisposable
|
|||
/// </returns>
|
||||
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
|
||||
{
|
||||
var ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
var results = new List<(ulong, DateTime, string)>();
|
||||
uint ipAsUint = IpHelper.IpToUint(ipAddress);
|
||||
List<(ulong, DateTime, string)> results = new List<(ulong, DateTime, string)>();
|
||||
|
||||
// Optimization: Direct lookup using HashSet.Contains instead of TryGetValue
|
||||
var searchRecord = new IpRecord(ipAsUint, default, null!);
|
||||
IpRecord searchRecord = new IpRecord(ipAsUint, default, null!);
|
||||
|
||||
foreach (var (steamId, ipSet) in _playerIpsCache)
|
||||
foreach ((ulong steamId, HashSet<IpRecord>? ipSet) in _playerIpsCache)
|
||||
{
|
||||
// Optimization: Single pass through the set
|
||||
foreach (var entry in ipSet)
|
||||
foreach (IpRecord entry in ipSet)
|
||||
{
|
||||
if (entry.Ip == ipAsUint)
|
||||
{
|
||||
|
|
@ -488,11 +521,15 @@ internal class CacheManager: IDisposable
|
|||
|
||||
private bool IsIpBanned(string ipAddress)
|
||||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
|
||||
var ipUInt = IpHelper.IpToUint(ipAddress);
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint ipUInt = IpHelper.IpToUint(ipAddress);
|
||||
return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
|
||||
}
|
||||
|
||||
|
||||
// public bool IsPlayerBanned(ulong? steamId, string? ipAddress)
|
||||
// {
|
||||
// if (steamId != null && _steamIdIndex.ContainsKey(steamId.Value))
|
||||
|
|
@ -518,13 +555,13 @@ internal class CacheManager: IDisposable
|
|||
public bool IsPlayerBanned(string playerName, ulong? steamId, string? ipAddress)
|
||||
{
|
||||
BanRecord? record;
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out List<BanRecord>? steamRecords))
|
||||
{
|
||||
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record != null)
|
||||
{
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
if (_banCache.TryGetValue(record.Id, out BanRecord? cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
{
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue))
|
||||
|
|
@ -536,21 +573,31 @@ internal class CacheManager: IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(ipAddress) ||
|
||||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
|
||||
!IpHelper.TryConvertIpToUint(ipAddress, out uint ipUInt) ||
|
||||
_cachedIgnoredIps.Contains(ipUInt) ||
|
||||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
|
||||
!_ipIndex.TryGetValue(ipUInt, out List<BanRecord>? ipRecords))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (record == null) return false;
|
||||
if (record == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (!_banCache.TryGetValue(record.Id, out var cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE)
|
||||
if (!_banCache.TryGetValue(record.Id, out BanRecord? cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
||||
|
|
@ -560,7 +607,7 @@ internal class CacheManager: IDisposable
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
|
||||
// {
|
||||
// if (_steamIdIndex.ContainsKey(steamId))
|
||||
|
|
@ -638,16 +685,18 @@ internal class CacheManager: IDisposable
|
|||
/// <returns>True if the player or their known IPs are banned, otherwise false.</returns>
|
||||
public bool IsPlayerOrAnyIpBanned(string playerName, ulong steamId, string? ipAddress)
|
||||
{
|
||||
if (_steamIdIndex.TryGetValue(steamId, out var steamBans))
|
||||
if (_steamIdIndex.TryGetValue(steamId, out List<BanRecord>? steamBans))
|
||||
{
|
||||
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
BanRecord? activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan != null)
|
||||
{
|
||||
// Double-check the ban is still active in cache (handle race conditions)
|
||||
if (_banCache.TryGetValue(activeBan.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
if (_banCache.TryGetValue(activeBan.Id, out BanRecord? cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||
{
|
||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||
{
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -655,25 +704,34 @@ internal class CacheManager: IDisposable
|
|||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out uint ipUInt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_cachedIgnoredIps.Contains(ipUInt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Direct ip ban (ban record has player_ip set)
|
||||
if (_ipIndex.TryGetValue(ipUInt, out var ipBanRecords))
|
||||
if (_ipIndex.TryGetValue(ipUInt, out List<BanRecord>? ipBanRecords))
|
||||
{
|
||||
var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (ipBan != null && _banCache.TryGetValue(ipBan.Id, out var cachedIpBan) && cachedIpBan.StatusEnum == BanStatus.ACTIVE)
|
||||
BanRecord? ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||
if (ipBan != null && _banCache.TryGetValue(ipBan.Id, out BanRecord? cachedIpBan) && cachedIpBan.StatusEnum == BanStatus.ACTIVE)
|
||||
{
|
||||
var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
|
||||
int expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
|
||||
if (expireOldIpBans <= 0 || ipBan.Created >= Time.ActualDateTime().AddDays(-expireOldIpBans))
|
||||
{
|
||||
if (string.IsNullOrEmpty(ipBan.PlayerName))
|
||||
{
|
||||
ipBan.PlayerName = playerName;
|
||||
}
|
||||
|
||||
ipBan.PlayerSteamId ??= steamId;
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
return true;
|
||||
|
|
@ -684,19 +742,32 @@ internal class CacheManager: IDisposable
|
|||
// Multiaccount ban - check if other accounts using current ip are banned
|
||||
if (!_playerIpsCache.IsEmpty)
|
||||
{
|
||||
foreach (var (otherSteamId, ipSet) in _playerIpsCache)
|
||||
foreach ((ulong otherSteamId, HashSet<IpRecord>? ipSet) in _playerIpsCache)
|
||||
{
|
||||
// Skip current player
|
||||
if (otherSteamId == steamId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this ip is in the other accounts ip history
|
||||
if (ipSet.All(record => record.Ip != ipUInt)) continue;
|
||||
if (ipSet.All(record => record.Ip != ipUInt))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Found another account using this ip - check if its banned
|
||||
if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) continue;
|
||||
var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) ||
|
||||
cachedBan.StatusEnum != BanStatus.ACTIVE) continue;
|
||||
if (!_steamIdIndex.TryGetValue(otherSteamId, out List<BanRecord>? otherSteamBans))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BanRecord? activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out BanRecord? cachedBan) ||
|
||||
cachedBan.StatusEnum != BanStatus.ACTIVE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
return true;
|
||||
}
|
||||
|
|
@ -705,32 +776,44 @@ internal class CacheManager: IDisposable
|
|||
// Multiaccount ban - check if this player used any ip where other banned accounts are connected
|
||||
// Search sa_players_ips for all accounts sharing the same ips as current player
|
||||
if (!CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_playerIpsCache.TryGetValue(steamId, out var playerIps))
|
||||
if (!_playerIpsCache.TryGetValue(steamId, out HashSet<IpRecord>? playerIps))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For each ip the player used (current or historical)
|
||||
foreach (var playerIpRecord in playerIps)
|
||||
foreach (IpRecord playerIpRecord in playerIps)
|
||||
{
|
||||
// Search sa_players_ips for other accounts using this same ip (as uint)
|
||||
foreach (var (otherSteamId, otherIpSet) in _playerIpsCache)
|
||||
foreach ((ulong otherSteamId, HashSet<IpRecord>? otherIpSet) in _playerIpsCache)
|
||||
{
|
||||
if (otherSteamId == steamId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this other account used the player ip
|
||||
if (otherIpSet.All(record => record.Ip != playerIpRecord.Ip))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this other account is banned
|
||||
if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans))
|
||||
if (!_steamIdIndex.TryGetValue(otherSteamId, out List<BanRecord>? otherSteamBans))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) ||
|
||||
BanRecord? activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out BanRecord? cachedBan) ||
|
||||
cachedBan.StatusEnum != BanStatus.ACTIVE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||
return true;
|
||||
|
|
@ -749,15 +832,19 @@ internal class CacheManager: IDisposable
|
|||
public bool HasIpForPlayer(ulong steamId, string ipAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
{
|
||||
return false;
|
||||
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUint))
|
||||
return false;
|
||||
}
|
||||
|
||||
return _playerIpsCache.TryGetValue(steamId, out var ipData) &&
|
||||
if (!IpHelper.TryConvertIpToUint(ipAddress, out uint ipUint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _playerIpsCache.TryGetValue(steamId, out HashSet<IpRecord>? ipData) &&
|
||||
ipData.Contains(new IpRecord(ipUint, default, null!));
|
||||
}
|
||||
|
||||
|
||||
// public bool HasIpForPlayer(ulong steamId, string ipAddress)
|
||||
// {
|
||||
// if (string.IsNullOrWhiteSpace(ipAddress))
|
||||
|
|
@ -778,9 +865,11 @@ internal class CacheManager: IDisposable
|
|||
private async Task UpdatePlayerData(string? playerName, ulong? steamId, string? ipAddress)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var baseSql = """
|
||||
string baseSql = """
|
||||
UPDATE sa_bans
|
||||
SET
|
||||
player_ip = COALESCE(player_ip, @PlayerIP),
|
||||
|
|
@ -796,44 +885,56 @@ internal class CacheManager: IDisposable
|
|||
baseSql += " AND server_id = @ServerId;";
|
||||
}
|
||||
|
||||
string? playerIp = ipAddress;
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
|
||||
|| string.IsNullOrEmpty(ipAddress)
|
||||
|| CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(ipAddress))
|
||||
{
|
||||
playerIp = null;
|
||||
}
|
||||
|
||||
var parameters = new
|
||||
{
|
||||
PlayerSteamID = steamId,
|
||||
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
|
||||
|| string.IsNullOrEmpty(ipAddress)
|
||||
|| CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(ipAddress)
|
||||
? null
|
||||
: ipAddress,
|
||||
PlayerName = string.IsNullOrEmpty(playerName) ? string.Empty : playerName,
|
||||
PlayerIP = playerIp,
|
||||
PlayerName = playerName ?? string.Empty,
|
||||
CurrentTime = Time.ActualDateTime(),
|
||||
CS2_SimpleAdmin.ServerId
|
||||
};
|
||||
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await connection.ExecuteAsync(baseSql, parameters);
|
||||
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
|
||||
|
||||
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out List<BanRecord>? steamRecords))
|
||||
{
|
||||
foreach (var rec in steamRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
foreach (BanRecord? rec in steamRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
{
|
||||
if (string.IsNullOrEmpty(rec.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||
{
|
||||
rec.PlayerIp = ipAddress;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
|
||||
{
|
||||
rec.PlayerName = playerName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ipAddress) && IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)
|
||||
&& _ipIndex.TryGetValue(ipUInt, out var ipRecords))
|
||||
if (!string.IsNullOrEmpty(ipAddress) && IpHelper.TryConvertIpToUint(ipAddress, out uint ipUInt)
|
||||
&& _ipIndex.TryGetValue(ipUInt, out List<BanRecord>? ipRecords))
|
||||
{
|
||||
foreach (var rec in ipRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
foreach (BanRecord? rec in ipRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
|
||||
{
|
||||
if (!rec.PlayerSteamId.HasValue && steamId.HasValue)
|
||||
{
|
||||
rec.PlayerSteamId = steamId;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
|
||||
{
|
||||
rec.PlayerName = playerName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -847,16 +948,30 @@ internal class CacheManager: IDisposable
|
|||
_playerIpsCache.Clear();
|
||||
_cachedIgnoredIps.Clear();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clears and disposes of all cached data and marks the object as disposed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private static string NameOrDefault(string? name, string defaultName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return defaultName;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public class IpRecordComparer : IEqualityComparer<IpRecord>
|
||||
|
|
@ -866,4 +981,4 @@ public class IpRecordComparer : IEqualityComparer<IpRecord>
|
|||
|
||||
public int GetHashCode(IpRecord obj)
|
||||
=> obj.Ip.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace CS2_SimpleAdmin.Managers;
|
|||
|
||||
public class DiscordManager(string webhookUrl)
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sends a plain text message asynchronously to the configured Discord webhook URL.
|
||||
/// </summary>
|
||||
|
|
@ -16,23 +16,23 @@ public class DiscordManager(string webhookUrl)
|
|||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||
public async Task SendMessageAsync(string message)
|
||||
{
|
||||
var client = CS2_SimpleAdmin.HttpClient;
|
||||
var payload = new
|
||||
HttpClient client = CS2_SimpleAdmin.HttpClient;
|
||||
Dictionary<string, object> payload = new Dictionary<string, object>
|
||||
{
|
||||
content = message
|
||||
["content"] = message
|
||||
};
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload, options);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
string json = JsonSerializer.Serialize(payload, options);
|
||||
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.PostAsync(webhookUrl, content);
|
||||
HttpResponseMessage response = await client.PostAsync(webhookUrl, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
|
|
@ -54,48 +54,75 @@ public class DiscordManager(string webhookUrl)
|
|||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||
public async Task SendEmbedAsync(Embed embed)
|
||||
{
|
||||
var httpClient = CS2_SimpleAdmin.HttpClient;
|
||||
HttpClient httpClient = CS2_SimpleAdmin.HttpClient;
|
||||
|
||||
var payload = new
|
||||
// Same JSON as before: every key is written, null when the part is empty
|
||||
Dictionary<string, object?> embedPayload = new Dictionary<string, object?>
|
||||
{
|
||||
embeds = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
color = embed.Color,
|
||||
title = !string.IsNullOrEmpty(embed.Title) ? embed.Title : null,
|
||||
description = !string.IsNullOrEmpty(embed.Description) ? embed.Description : null,
|
||||
thumbnail = !string.IsNullOrEmpty(embed.ThumbnailUrl) ? new { url = embed.ThumbnailUrl } : null,
|
||||
image = !string.IsNullOrEmpty(embed.ImageUrl) ? new { url = embed.ImageUrl } : null,
|
||||
footer = !string.IsNullOrEmpty(embed.Footer?.Text) ? new { text = embed.Footer.Text, icon_url = embed.Footer.IconUrl } : null,
|
||||
timestamp = embed.Timestamp,
|
||||
fields = embed.Fields.Count > 0 ? embed.Fields.Select(field => new
|
||||
{
|
||||
name = field.Name,
|
||||
value = field.Value,
|
||||
inline = field.Inline
|
||||
}).ToArray() : null
|
||||
}
|
||||
}
|
||||
["color"] = embed.Color,
|
||||
["title"] = null,
|
||||
["description"] = null,
|
||||
["thumbnail"] = null,
|
||||
["image"] = null,
|
||||
["footer"] = null,
|
||||
["timestamp"] = embed.Timestamp,
|
||||
["fields"] = null
|
||||
};
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
if (!string.IsNullOrEmpty(embed.Title))
|
||||
{
|
||||
embedPayload["title"] = embed.Title;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(embed.Description))
|
||||
{
|
||||
embedPayload["description"] = embed.Description;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(embed.ThumbnailUrl))
|
||||
{
|
||||
embedPayload["thumbnail"] = new Dictionary<string, object?> { ["url"] = embed.ThumbnailUrl };
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(embed.ImageUrl))
|
||||
{
|
||||
embedPayload["image"] = new Dictionary<string, object?> { ["url"] = embed.ImageUrl };
|
||||
}
|
||||
|
||||
if (embed.Footer != null && !string.IsNullOrEmpty(embed.Footer.Text))
|
||||
{
|
||||
embedPayload["footer"] = new Dictionary<string, object?> { ["text"] = embed.Footer.Text, ["icon_url"] = embed.Footer.IconUrl };
|
||||
}
|
||||
|
||||
if (embed.Fields.Count > 0)
|
||||
{
|
||||
embedPayload["fields"] = embed.Fields
|
||||
.Select(field => new Dictionary<string, object?> { ["name"] = field.Name, ["value"] = field.Value, ["inline"] = field.Inline })
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
Dictionary<string, object> payload = new Dictionary<string, object>
|
||||
{
|
||||
["embeds"] = new[] { embedPayload }
|
||||
};
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
var jsonPayload = JsonSerializer.Serialize(payload, options);
|
||||
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
string jsonPayload = JsonSerializer.Serialize(payload, options);
|
||||
StringContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await httpClient.PostAsync(webhookUrl, content);
|
||||
HttpResponseMessage response = await httpClient.PostAsync(webhookUrl, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorMessage = await response.Content.ReadAsStringAsync();
|
||||
string errorMessage = await response.Content.ReadAsStringAsync();
|
||||
CS2_SimpleAdmin._logger?.LogError($"Failed to send embed: {response.StatusCode} - {errorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts a hexadecimal color string (e.g. "#FF0000") to its integer representation.
|
||||
/// </summary>
|
||||
|
|
@ -135,13 +162,13 @@ public class Embed
|
|||
/// <param name="inline">Whether the field should be displayed inline with other fields.</param>
|
||||
public void AddField(string name, string value, bool inline)
|
||||
{
|
||||
var field = new EmbedField
|
||||
EmbedField field = new EmbedField
|
||||
{
|
||||
Name = name,
|
||||
Value = value,
|
||||
Inline = inline
|
||||
};
|
||||
|
||||
|
||||
Fields.Add(field);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using CS2_SimpleAdmin.Database;
|
||||
using System.Data.Common;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -18,12 +19,15 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>Mute ID if successfully added, otherwise null.</returns>
|
||||
public async Task<int?> MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
var muteType = type switch
|
||||
string muteType = type switch
|
||||
{
|
||||
1 => "MUTE",
|
||||
2 => "SILENCE",
|
||||
|
|
@ -32,10 +36,10 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddMuteQuery(true);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAddMuteQuery(true);
|
||||
|
||||
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
int? muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
playerName = player.Name,
|
||||
|
|
@ -69,12 +73,15 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>Mute ID if successful, otherwise null.</returns>
|
||||
public async Task<int?> AddMuteBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
var muteType = type switch
|
||||
string muteType = type switch
|
||||
{
|
||||
1 => "MUTE",
|
||||
2 => "SILENCE",
|
||||
|
|
@ -83,10 +90,10 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddMuteQuery(false);
|
||||
|
||||
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAddMuteQuery(false);
|
||||
|
||||
int? muteId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
|
|
@ -114,7 +121,10 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>List of active mute records; empty list if none or on error.</returns>
|
||||
public async Task<List<dynamic>> IsPlayerMuted(string steamId)
|
||||
{
|
||||
if (databaseProvider == null) return [];
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(steamId))
|
||||
{
|
||||
|
|
@ -123,18 +133,20 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
#if DEBUG
|
||||
if (CS2_SimpleAdmin._logger != null)
|
||||
{
|
||||
CS2_SimpleAdmin._logger.LogCritical($"IsPlayerMuted for {steamId}");
|
||||
}
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var currentTime = Time.ActualDateTime();
|
||||
|
||||
var sql = databaseProvider.GetIsMutedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
DateTime currentTime = Time.ActualDateTime();
|
||||
|
||||
string sql = databaseProvider.GetIsMutedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
|
||||
var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId };
|
||||
var activeMutes = (await connection.QueryAsync(sql, parameters)).ToList();
|
||||
List<dynamic> activeMutes = (await connection.QueryAsync(sql, parameters)).ToList();
|
||||
return activeMutes;
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
@ -153,14 +165,17 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// </returns>
|
||||
public async Task<(int TotalMutes, int TotalGags, int TotalSilences)> GetPlayerMutes(PlayerInfo playerInfo)
|
||||
{
|
||||
if (databaseProvider == null) return (0,0,0);
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return (0, 0, 0);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetMuteStatsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
var result = await connection.QuerySingleAsync<(int TotalMutes, int TotalGags, int TotalSilences)>(sql, new
|
||||
string sql = databaseProvider.GetMuteStatsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
(int TotalMutes, int TotalGags, int TotalSilences) result = await connection.QuerySingleAsync<(int TotalMutes, int TotalGags, int TotalSilences)>(sql, new
|
||||
{
|
||||
PlayerSteamID = playerInfo.SteamId.SteamId64,
|
||||
CS2_SimpleAdmin.ServerId
|
||||
|
|
@ -173,7 +188,7 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
return (0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Processes a batch of online players to update their mute status and remove expired penalties.
|
||||
/// </summary>
|
||||
|
|
@ -181,21 +196,24 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>Task representing the asynchronous operation.</returns>
|
||||
public async Task CheckOnlineModeMutes(List<(ulong SteamID, int? UserId, int Slot)> players)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const int batchSize = 20;
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetUpdateMutePassedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
string sql = databaseProvider.GetUpdateMutePassedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
for (var i = 0; i < players.Count; i += batchSize)
|
||||
for (int i = 0; i < players.Count; i += batchSize)
|
||||
{
|
||||
var batch = players.Skip(i).Take(batchSize);
|
||||
var parametersList = new List<object>();
|
||||
IEnumerable<(ulong SteamID, int? UserId, int Slot)> batch = players.Skip(i).Take(batchSize);
|
||||
List<object> parametersList = new List<object>();
|
||||
|
||||
foreach (var (steamId, _, _) in batch)
|
||||
foreach ((ulong steamId, int? _, int _) in batch)
|
||||
{
|
||||
parametersList.Add(new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
|
|
@ -205,11 +223,11 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
sql = databaseProvider.GetCheckExpiredMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
|
||||
foreach (var (steamId, _, slot) in players)
|
||||
foreach ((ulong steamId, int? _, int slot) in players)
|
||||
{
|
||||
var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
IEnumerable<dynamic> muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
|
||||
foreach (var muteRecord in muteRecords)
|
||||
foreach (dynamic muteRecord in muteRecords)
|
||||
{
|
||||
DateTime endDateTime = muteRecord.ends;
|
||||
PlayerPenaltyManager.RemovePenaltiesByDateTime(slot, endDateTime);
|
||||
|
|
@ -230,7 +248,10 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>Task representing the asynchronous operation.</returns>
|
||||
public async Task UnmutePlayer(string playerPattern, string adminSteamId, string reason, int type = 0)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerPattern.Length <= 1)
|
||||
{
|
||||
|
|
@ -239,36 +260,38 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var muteType = type switch
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string muteType = type switch
|
||||
{
|
||||
1 => "MUTE",
|
||||
2 => "SILENCE",
|
||||
_ => "GAG"
|
||||
};
|
||||
|
||||
var sqlRetrieveMutes =
|
||||
string sqlRetrieveMutes =
|
||||
databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
var mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId });
|
||||
IEnumerable<dynamic> mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId });
|
||||
|
||||
var mutesList = mutes as dynamic[] ?? mutes.ToArray();
|
||||
dynamic[] mutesList = mutes as dynamic[] ?? mutes.ToArray();
|
||||
if (mutesList.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sqlAdmin = databaseProvider.GetUnmuteAdminIdQuery();
|
||||
var sqlInsertUnmute = databaseProvider.GetInsertUnmuteQuery(string.IsNullOrEmpty(reason));
|
||||
string sqlAdmin = databaseProvider.GetUnmuteAdminIdQuery();
|
||||
string sqlInsertUnmute = databaseProvider.GetInsertUnmuteQuery(string.IsNullOrEmpty(reason));
|
||||
|
||||
var sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
|
||||
var adminId = sqlAdminId ?? 0;
|
||||
int? sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
|
||||
int adminId = sqlAdminId ?? 0;
|
||||
|
||||
foreach (var mute in mutesList)
|
||||
foreach (dynamic mute in mutesList)
|
||||
{
|
||||
int muteId = mute.id;
|
||||
|
||||
int? unmuteId =
|
||||
await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId, reason });
|
||||
|
||||
var sqlUpdateMute = databaseProvider.GetUpdateMuteStatusQuery();
|
||||
string sqlUpdateMute = databaseProvider.GetUpdateMuteStatusQuery();
|
||||
await connection.ExecuteAsync(sqlUpdateMute, new { unmuteId, muteId });
|
||||
}
|
||||
}
|
||||
|
|
@ -284,12 +307,15 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>Task representing the asynchronous expiration operation.</returns>
|
||||
public async Task ExpireOldMutes()
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetExpireMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetExpireMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
@ -297,4 +323,4 @@ internal class MuteManager(IDatabaseProvider? databaseProvider)
|
|||
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired mutes");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Data.Common;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using CounterStrikeSharp.API.Modules.Entities;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using CounterStrikeSharp.API.Modules.Admin;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
|
|
@ -18,58 +19,60 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// Retrieves all players' flags and associated data asynchronously.
|
||||
/// </summary>
|
||||
/// <returns>A list of tuples containing player SteamID, name, flags, immunity, and expiration time.</returns>
|
||||
private async Task<List<(ulong, string ,List<string>, int, DateTime?)>> GetAllPlayersFlags()
|
||||
private async Task<List<(ulong, string, List<string>, int, DateTime?)>> GetAllPlayersFlags()
|
||||
{
|
||||
if (databaseProvider == null)
|
||||
return new List<(ulong, string, List<string>, int, DateTime?)>();
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return new List<(ulong, string, List<string>, int, DateTime?)>();
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
DateTime now = Time.ActualDateTime();
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAdminsQuery();
|
||||
var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
try
|
||||
{
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAdminsQuery();
|
||||
List<dynamic> admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
|
||||
var groupedPlayers = admins
|
||||
.GroupBy(r => new { playerSteamId = r.player_steamid, playerName = r.player_name, r.immunity, r.ends })
|
||||
.Select(g =>
|
||||
{
|
||||
ulong steamId = g.Key.playerSteamId switch
|
||||
{
|
||||
long l => (ulong)l,
|
||||
int i => (ulong)i,
|
||||
string s when ulong.TryParse(s, out var parsed) => parsed,
|
||||
_ => 0UL
|
||||
};
|
||||
List<(ulong steamId, string playerName, List<string> flags, int immunity, DateTime? ends)> groupedPlayers = admins
|
||||
.GroupBy(r => new { playerSteamId = r.player_steamid, playerName = r.player_name, r.immunity, r.ends })
|
||||
.Select(g =>
|
||||
{
|
||||
ulong steamId = g.Key.playerSteamId switch
|
||||
{
|
||||
long l => (ulong)l,
|
||||
int i => (ulong)i,
|
||||
string s when ulong.TryParse(s, out ulong parsed) => parsed,
|
||||
_ => 0UL
|
||||
};
|
||||
|
||||
int immunity = g.Key.immunity switch
|
||||
{
|
||||
int i => i,
|
||||
string s when int.TryParse(s, out var parsed) => parsed,
|
||||
_ => 0
|
||||
};
|
||||
int immunity = g.Key.immunity switch
|
||||
{
|
||||
int i => i,
|
||||
string s when int.TryParse(s, out int parsed) => parsed,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
DateTime? ends = g.Key.ends as DateTime?;
|
||||
DateTime? ends = g.Key.ends as DateTime?;
|
||||
|
||||
string playerName = g.Key.playerName as string ?? string.Empty;
|
||||
string playerName = g.Key.playerName as string ?? string.Empty;
|
||||
|
||||
// Dapper returns string here, not dynamic
|
||||
var flags = g.Select(r => r.flag as string ?? string.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
// Dapper returns string here, not dynamic
|
||||
List<string> flags = g.Select(r => r.flag as string ?? string.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return (steamId, playerName, flags, immunity, ends);
|
||||
})
|
||||
.ToList();
|
||||
return (steamId, playerName, flags, immunity, ends);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return groupedPlayers;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
|
||||
throw; // an empty list would strip every admin; abort the reload and keep the current permissions
|
||||
}
|
||||
return groupedPlayers;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
|
||||
throw; // an empty list would strip every admin; abort the reload and keep the current permissions
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -78,24 +81,27 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>A dictionary with group names as keys and tuples of flags and immunity as values.</returns>
|
||||
private async Task<Dictionary<string, (List<string>, int)>> GetAllGroupsData()
|
||||
{
|
||||
if (databaseProvider == null) return [];
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetGroupsQuery();
|
||||
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetGroupsQuery();
|
||||
List<dynamic> groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
||||
if (groupData.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var groupInfoDictionary = new Dictionary<string, (List<string>, int)>();
|
||||
foreach (var row in groupData)
|
||||
Dictionary<string, (List<string>, int)> groupInfoDictionary = new Dictionary<string, (List<string>, int)>();
|
||||
foreach (dynamic row in groupData)
|
||||
{
|
||||
var groupName = (string)row.group_name;
|
||||
var flag = (string)row.flag;
|
||||
var immunity = (int)row.immunity;
|
||||
string groupName = (string)row.group_name;
|
||||
string flag = (string)row.flag;
|
||||
int immunity = (int)row.immunity;
|
||||
|
||||
if (!groupInfoDictionary.TryGetValue(groupName, out (List<string>, int) value))
|
||||
{
|
||||
|
|
@ -121,12 +127,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||
public async Task CreateGroupsJsonFile()
|
||||
{
|
||||
var groupsData = await GetAllGroupsData();
|
||||
var jsonData = new Dictionary<string, object>();
|
||||
Dictionary<string, (List<string>, int)> groupsData = await GetAllGroupsData();
|
||||
Dictionary<string, object> jsonData = new Dictionary<string, object>();
|
||||
|
||||
foreach (var kvp in groupsData)
|
||||
foreach (KeyValuePair<string, (List<string>, int)> kvp in groupsData)
|
||||
{
|
||||
var groupData = new Dictionary<string, object>
|
||||
Dictionary<string, object> groupData = new Dictionary<string, object>
|
||||
{
|
||||
["flags"] = kvp.Value.Item1,
|
||||
["immunity"] = kvp.Value.Item2
|
||||
|
|
@ -135,14 +141,14 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
jsonData[kvp.Key] = groupData;
|
||||
}
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(jsonData, options);
|
||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.json");
|
||||
string json = JsonSerializer.Serialize(jsonData, options);
|
||||
string filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.json");
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
}
|
||||
|
||||
|
|
@ -153,55 +159,55 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
public async Task<List<(SteamID steamId, DateTime? ends, List<string> flags)>> CreateAdminsJsonFile()
|
||||
{
|
||||
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
|
||||
var validPlayers = allPlayers
|
||||
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> validPlayers = allPlayers
|
||||
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
|
||||
.ToList();
|
||||
|
||||
var jsonData = validPlayers
|
||||
.GroupBy(player => player.name)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
object (group) =>
|
||||
{
|
||||
var consolidatedData = group.Aggregate(
|
||||
new
|
||||
{
|
||||
identity = string.Empty,
|
||||
immunity = 0,
|
||||
flags = new List<string>(),
|
||||
groups = new List<string>()
|
||||
},
|
||||
(acc, player) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
||||
{
|
||||
acc = acc with { identity = player.identity.ToString() };
|
||||
}
|
||||
Dictionary<string, object> jsonData = validPlayers
|
||||
.GroupBy(player => player.name)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
object (group) =>
|
||||
{
|
||||
var consolidatedData = group.Aggregate(
|
||||
new
|
||||
{
|
||||
identity = string.Empty,
|
||||
immunity = 0,
|
||||
flags = new List<string>(),
|
||||
groups = new List<string>()
|
||||
},
|
||||
(acc, player) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
||||
{
|
||||
acc = acc with { identity = player.identity.ToString() };
|
||||
}
|
||||
|
||||
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
||||
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
||||
|
||||
acc = acc with
|
||||
{
|
||||
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
||||
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
|
||||
};
|
||||
acc = acc with
|
||||
{
|
||||
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
||||
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
|
||||
};
|
||||
|
||||
return acc;
|
||||
});
|
||||
return acc;
|
||||
});
|
||||
|
||||
return consolidatedData;
|
||||
});
|
||||
return consolidatedData;
|
||||
});
|
||||
|
||||
var newCache = validPlayers.Select(player => (new SteamID(player.identity), player.ends, player.flags)).ToList();
|
||||
List<(SteamID, DateTime? ends, List<string> flags)> newCache = validPlayers.Select(player => (new SteamID(player.identity), player.ends, player.flags)).ToList();
|
||||
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(jsonData, options);
|
||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
||||
string json = JsonSerializer.Serialize(jsonData, options);
|
||||
string filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
|
||||
return newCache;
|
||||
|
|
@ -214,25 +220,34 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="admins">Admins loaded from the database.</param>
|
||||
public static void ApplyAdminCache(List<(SteamID steamId, DateTime? ends, List<string> flags)> admins)
|
||||
{
|
||||
foreach (var steamId in AdminCache.Keys.ToList())
|
||||
foreach (SteamID? steamId in AdminCache.Keys.ToList())
|
||||
{
|
||||
if (!AdminCache.TryRemove(steamId, out var cached)) continue;
|
||||
if (!AdminCache.TryRemove(steamId, out (DateTime? ExpirationTime, List<string> Flags) cached))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null) continue;
|
||||
AdminData? data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var flagsArray = cached.Flags.ToArray();
|
||||
string[] flagsArray = cached.Flags.ToArray();
|
||||
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
|
||||
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
|
||||
|
||||
data = AdminManager.GetPlayerAdminData(steamId);
|
||||
if (data == null || (data.Flags.Count != 0 && data.Groups.Count != 0)) continue;
|
||||
if (data == null || (data.Flags.Count != 0 && data.Groups.Count != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AdminManager.ClearPlayerPermissions(steamId);
|
||||
AdminManager.RemovePlayerAdminData(steamId);
|
||||
}
|
||||
|
||||
foreach (var (steamId, ends, flags) in admins)
|
||||
foreach ((SteamID? steamId, DateTime? ends, List<string>? flags) in admins)
|
||||
{
|
||||
AdminCache.TryAdd(steamId, (ends, flags));
|
||||
}
|
||||
|
|
@ -245,13 +260,20 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="globalDelete">Whether to delete the admin globally or only for the current server.</param>
|
||||
public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(playerSteamId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
|
||||
await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -271,31 +293,52 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="globalAdmin">Whether the admin is global or server-specific.</param>
|
||||
public async Task AddAdminBySteamId(string playerSteamId, string playerName, List<string> flagsList, int immunity = 0, int time = 0, bool globalAdmin = false)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
|
||||
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
DateTime? futureTime = time != 0 ? now.AddMinutes(time) : null;
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime? futureTime;
|
||||
|
||||
if (time != 0)
|
||||
{
|
||||
futureTime = now.AddMinutes(time);
|
||||
}
|
||||
else
|
||||
{
|
||||
futureTime = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var insertAdminSql = databaseProvider.GetAddAdminQuery();
|
||||
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
|
||||
int? serverId = CS2_SimpleAdmin.ServerId;
|
||||
if (globalAdmin)
|
||||
{
|
||||
serverId = null;
|
||||
}
|
||||
|
||||
string insertAdminSql = databaseProvider.GetAddAdminQuery();
|
||||
int adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
|
||||
{
|
||||
playerSteamId,
|
||||
playerName,
|
||||
immunity,
|
||||
ends = futureTime,
|
||||
created = now,
|
||||
serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId
|
||||
serverid = serverId
|
||||
});
|
||||
|
||||
foreach (var flag in flagsList)
|
||||
foreach (string flag in flagsList)
|
||||
{
|
||||
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
|
||||
string insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
|
||||
await connection.ExecuteAsync(insertFlagsSql, new
|
||||
{
|
||||
adminId,
|
||||
|
|
@ -323,24 +366,30 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="globalGroup">Whether the group is global or server-specific.</param>
|
||||
public async Task AddGroup(string groupName, List<string> flagsList, int immunity = 0, bool globalGroup = false)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
|
||||
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var insertGroup = databaseProvider.GetAddGroupQuery();
|
||||
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
|
||||
string insertGroup = databaseProvider.GetAddGroupQuery();
|
||||
int groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
|
||||
{
|
||||
groupName,
|
||||
immunity
|
||||
});
|
||||
|
||||
foreach (var flag in flagsList)
|
||||
foreach (string flag in flagsList)
|
||||
{
|
||||
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
|
||||
string insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
|
||||
await connection.ExecuteAsync(insertFlagsSql, new
|
||||
{
|
||||
groupId,
|
||||
|
|
@ -348,8 +397,14 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
});
|
||||
}
|
||||
|
||||
var insertGroupServer = databaseProvider.GetAddGroupServerQuery();
|
||||
await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = globalGroup ? null : CS2_SimpleAdmin.ServerId });
|
||||
string insertGroupServer = databaseProvider.GetAddGroupServerQuery();
|
||||
int? groupServerId = CS2_SimpleAdmin.ServerId;
|
||||
if (globalGroup)
|
||||
{
|
||||
groupServerId = null;
|
||||
}
|
||||
|
||||
await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = groupServerId });
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||
|
|
@ -367,14 +422,20 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// <param name="groupName">Name of the group to delete.</param>
|
||||
public async Task DeleteGroup(string groupName)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(groupName)) return;
|
||||
if (string.IsNullOrEmpty(groupName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetDeleteGroupQuery();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetDeleteGroupQuery();
|
||||
await connection.ExecuteAsync(sql, new { groupName });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -388,13 +449,16 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||
/// </summary>
|
||||
public async Task DeleteOldAdmins()
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetDeleteOldAdminsQuery();
|
||||
string sql = databaseProvider.GetDeleteOldAdminsQuery();
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime() });
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Data.Common;
|
||||
using System.Globalization;
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
|
|
@ -37,20 +38,30 @@ internal class PlayerManager
|
|||
return;
|
||||
}
|
||||
|
||||
var userId = player.UserId.Value;
|
||||
var slot = player.Slot;
|
||||
var steamId = player.SteamID;
|
||||
var playerName = !string.IsNullOrEmpty(player.PlayerName)
|
||||
? player.PlayerName
|
||||
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var ipAddress = player.IpAddress?.Split(":")[0];
|
||||
int userId = player.UserId.Value;
|
||||
int slot = player.Slot;
|
||||
ulong steamId = player.SteamID;
|
||||
string playerName;
|
||||
|
||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(steamId, out var renamedTo))
|
||||
if (!string.IsNullOrEmpty(player.PlayerName))
|
||||
{
|
||||
playerName = player.PlayerName;
|
||||
}
|
||||
else
|
||||
{
|
||||
playerName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
}
|
||||
string? ipAddress = player.IpAddress?.Split(":")[0];
|
||||
|
||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(steamId, out string? renamedTo))
|
||||
{
|
||||
player.Rename(renamedTo);
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
|
|
@ -62,14 +73,8 @@ internal class PlayerManager
|
|||
await SavePlayerIpAddress(steamId, playerName, ipAddress);
|
||||
|
||||
// Always check bans first, regardless of PlayersInfo state
|
||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||
{
|
||||
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
||||
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId,
|
||||
ipAddress)
|
||||
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress)
|
||||
};
|
||||
bool isBanned = IsBanned(CS2_SimpleAdmin.Instance.CacheManager, CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType,
|
||||
CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp, playerName, steamId, ipAddress);
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Player {playerName} ({steamId}) IP: {ipAddress} - BanType: {CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType} - CheckMultiAccounts: {CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp} - isBanned: {isBanned}");
|
||||
|
||||
|
|
@ -87,7 +92,7 @@ internal class PlayerManager
|
|||
|
||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||
{
|
||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||
PlayerInfo playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||
|
||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null)
|
||||
|
|
@ -109,8 +114,8 @@ internal class PlayerManager
|
|||
|
||||
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(steamId, out PlayerInfo? value)) // Temp skip
|
||||
{
|
||||
var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(value, false);
|
||||
var (totalMutes, totalGags, totalSilences) =
|
||||
List<dynamic> warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(value, false);
|
||||
(int totalMutes, int totalGags, int totalSilences) =
|
||||
await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(value);
|
||||
value.TotalBans = CS2_SimpleAdmin.Instance.CacheManager
|
||||
?.GetPlayerBansBySteamId(value.SteamId.SteamId64)
|
||||
|
|
@ -120,13 +125,13 @@ internal class PlayerManager
|
|||
value.TotalSilences = totalSilences;
|
||||
value.TotalWarns = warns.Count;
|
||||
|
||||
var activeMutes =
|
||||
List<dynamic> activeMutes =
|
||||
await CS2_SimpleAdmin.Instance.MuteManager.IsPlayerMuted(value.SteamId.SteamId64
|
||||
.ToString());
|
||||
|
||||
if (activeMutes.Count > 0)
|
||||
{
|
||||
foreach (var mute in activeMutes)
|
||||
foreach (dynamic mute in activeMutes)
|
||||
{
|
||||
string muteType = mute.type;
|
||||
DateTime ends = mute.ends;
|
||||
|
|
@ -171,7 +176,7 @@ internal class PlayerManager
|
|||
{
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
foreach (var admin in Helper.GetValidPlayers()
|
||||
foreach (CCSPlayerController? admin in Helper.GetValidPlayers()
|
||||
.Where(p => (AdminManager.PlayerHasPermissions(
|
||||
new SteamID(p.SteamID),
|
||||
"@css/kick") ||
|
||||
|
|
@ -182,7 +187,11 @@ internal class PlayerManager
|
|||
!CS2_SimpleAdmin.AdminDisabledJoinComms
|
||||
.Contains(p.SteamID)))
|
||||
{
|
||||
if (CS2_SimpleAdmin._localizer == null || admin == player) continue;
|
||||
if (CS2_SimpleAdmin._localizer == null || admin == player)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer,
|
||||
"sa_admin_penalty_info",
|
||||
player.PlayerName,
|
||||
|
|
@ -195,10 +204,10 @@ internal class PlayerManager
|
|||
|
||||
if (CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.Count >= 2)
|
||||
{
|
||||
var associatedAcccountsChunks =
|
||||
List<IEnumerable<(ulong SteamId, string PlayerName)>> associatedAcccountsChunks =
|
||||
CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.ChunkBy(5)
|
||||
.ToList();
|
||||
foreach (var chunk in associatedAcccountsChunks)
|
||||
foreach (IEnumerable<(ulong SteamId, string PlayerName)> chunk in associatedAcccountsChunks)
|
||||
{
|
||||
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer,
|
||||
"sa_admin_associated_accounts",
|
||||
|
|
@ -235,17 +244,22 @@ internal class PlayerManager
|
|||
/// <param name="type">Optional filter: bans, gags, mutes, silences or warns.</param>
|
||||
public async Task<List<dynamic>> GetPenaltyHistory(ulong steamId, string? type = null)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return [];
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
var sql = CS2_SimpleAdmin.DatabaseProvider.GetPenaltyHistoryQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
var rows = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
string sql = CS2_SimpleAdmin.DatabaseProvider.GetPenaltyHistoryQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
List<dynamic> rows = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId })).ToList();
|
||||
|
||||
// Filter word is the plural (bans, gags, mutes, silences, warns); rows carry BAN/GAG/MUTE/SILENCE/WARN
|
||||
if (type != null)
|
||||
{
|
||||
rows.RemoveAll(r => !type.Equals((string)r.type + "s", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
|
@ -263,7 +277,7 @@ internal class PlayerManager
|
|||
value switch
|
||||
{
|
||||
DateTime d => d,
|
||||
string s when DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed) => parsed,
|
||||
string s when DateTime.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsed) => parsed,
|
||||
_ => null
|
||||
};
|
||||
|
||||
|
|
@ -272,17 +286,22 @@ internal class PlayerManager
|
|||
/// </summary>
|
||||
public async Task LoadRenamedPlayers()
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
var rows = await connection.QueryAsync<(long steamId, string name)>(CS2_SimpleAdmin.DatabaseProvider.GetRenamesQuery());
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
IEnumerable<(long steamId, string name)> rows = await connection.QueryAsync<(long steamId, string name)>(CS2_SimpleAdmin.DatabaseProvider.GetRenamesQuery());
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
foreach (var (steamId, name) in rows)
|
||||
foreach ((long steamId, string? name) in rows)
|
||||
{
|
||||
CS2_SimpleAdmin.RenamedPlayers[(ulong)steamId] = name;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -298,15 +317,22 @@ internal class PlayerManager
|
|||
/// <param name="name">Forced name, or null/empty to remove the rename.</param>
|
||||
public async Task SaveRenamedPlayer(ulong steamId, string? name)
|
||||
{
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetDeleteRenameQuery(), new { steamId });
|
||||
}
|
||||
else
|
||||
{
|
||||
await connection.ExecuteAsync(CS2_SimpleAdmin.DatabaseProvider.GetUpsertRenameQuery(), new { steamId, name });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -321,16 +347,18 @@ internal class PlayerManager
|
|||
private async Task SavePlayerIpAddress(ulong steamId, string playerName, string? ipAddress)
|
||||
{
|
||||
if (!_config.OtherSettings.CheckMultiAccountsByIp || ipAddress == null || CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
|
||||
var steamId64 = steamId;
|
||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||
ulong steamId64 = steamId;
|
||||
uint ipUint = IpHelper.IpToUint(ipAddress);
|
||||
|
||||
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||
string upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||
|
||||
await connection.ExecuteAsync(upsertQuery, new
|
||||
{
|
||||
|
|
@ -365,40 +393,46 @@ internal class PlayerManager
|
|||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(5f, () =>
|
||||
{
|
||||
foreach (var (steamid, name) in CS2_SimpleAdmin.RenamedPlayers)
|
||||
foreach ((ulong steamid, string? name) in CS2_SimpleAdmin.RenamedPlayers)
|
||||
{
|
||||
var player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
if (player == null || !player.IsValid || player.PlayerName == name) continue;
|
||||
CCSPlayerController? player = Helper.GetPlayerFromSteamid64(steamid);
|
||||
if (player == null || !player.IsValid || player.PlayerName == name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
player.Rename(name);
|
||||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
|
||||
|
||||
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
|
||||
{
|
||||
#if DEBUG
|
||||
CS2_SimpleAdmin._logger?.LogCritical("[OnMapStart] Expired check");
|
||||
#endif
|
||||
if (CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Optimization: Get players once and avoid allocating anonymous types
|
||||
var validPlayers = Helper.GetValidPlayers();
|
||||
List<CCSPlayerController> validPlayers = Helper.GetValidPlayers();
|
||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
||||
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||
foreach (var p in validPlayers)
|
||||
List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)> tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||
foreach (CCSPlayerController p in validPlayers)
|
||||
{
|
||||
tempPlayers.Add((p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot));
|
||||
}
|
||||
|
||||
var pluginInstance = CS2_SimpleAdmin.Instance;
|
||||
var config = _config.OtherSettings; // Cache config access
|
||||
CS2_SimpleAdmin pluginInstance = CS2_SimpleAdmin.Instance;
|
||||
OtherSettings config = _config.OtherSettings; // Cache config access
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Run all expire tasks in parallel
|
||||
var expireTasks = new[]
|
||||
Task[] expireTasks = new[]
|
||||
{
|
||||
pluginInstance.BanManager.ExpireOldBans(),
|
||||
pluginInstance.MuteManager.ExpireOldMutes(),
|
||||
|
|
@ -415,7 +449,7 @@ internal class PlayerManager
|
|||
|
||||
if (ex is AggregateException aggregate)
|
||||
{
|
||||
foreach (var inner in aggregate.InnerExceptions)
|
||||
foreach (Exception inner in aggregate.InnerExceptions)
|
||||
{
|
||||
CS2_SimpleAdmin._logger?.LogError($"Inner exception: {inner.Message}");
|
||||
}
|
||||
|
|
@ -423,28 +457,24 @@ internal class PlayerManager
|
|||
}
|
||||
|
||||
if (pluginInstance.CacheManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization: Cache ban type and multi-account check to avoid repeated config access
|
||||
var banType = config.BanType;
|
||||
var checkMultiAccounts = config.CheckMultiAccountsByIp;
|
||||
int banType = config.BanType;
|
||||
bool checkMultiAccounts = config.CheckMultiAccountsByIp;
|
||||
|
||||
var bannedPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>();
|
||||
List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)> bannedPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>();
|
||||
|
||||
// Manual loop instead of LINQ - better performance
|
||||
foreach (var player in tempPlayers)
|
||||
foreach ((string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot) player in tempPlayers)
|
||||
{
|
||||
var playerName = player.PlayerName;
|
||||
var steamId = player.SteamID;
|
||||
var ip = player.IpAddress?.Split(':')[0];
|
||||
string playerName = player.PlayerName;
|
||||
ulong steamId = player.SteamID;
|
||||
string? ip = player.IpAddress?.Split(':')[0];
|
||||
|
||||
bool isBanned = banType switch
|
||||
{
|
||||
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||
_ => checkMultiAccounts
|
||||
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
|
||||
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
|
||||
};
|
||||
bool isBanned = IsBanned(pluginInstance.CacheManager, banType, checkMultiAccounts, playerName, steamId, ip);
|
||||
|
||||
if (isBanned)
|
||||
{
|
||||
|
|
@ -454,23 +484,29 @@ internal class PlayerManager
|
|||
|
||||
if (bannedPlayers.Count > 0)
|
||||
{
|
||||
foreach (var player in bannedPlayers)
|
||||
foreach ((string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot) player in bannedPlayers)
|
||||
{
|
||||
if (!player.UserId.HasValue) continue;
|
||||
if (!player.UserId.HasValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
await Server.NextWorldUpdateAsync(() =>
|
||||
{
|
||||
if (Helper.GetPlayerFromSteamid64(player.SteamID) != null)
|
||||
{
|
||||
Helper.KickPlayer((int)player.UserId,
|
||||
NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (config.TimeMode == 0)
|
||||
{
|
||||
// Optimization: Manual projection instead of LINQ
|
||||
var onlinePlayers = new List<(ulong, int?, int)>(tempPlayers.Count);
|
||||
foreach (var player in tempPlayers)
|
||||
List<(ulong, int?, int)> onlinePlayers = new List<(ulong, int?, int)>(tempPlayers.Count);
|
||||
foreach ((string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot) player in tempPlayers)
|
||||
{
|
||||
onlinePlayers.Add((player.SteamID, player.UserId, player.Slot));
|
||||
}
|
||||
|
|
@ -485,14 +521,16 @@ internal class PlayerManager
|
|||
try
|
||||
{
|
||||
// Optimization: Process penalties without LINQ allocations
|
||||
var players = Helper.GetValidPlayers();
|
||||
foreach (var player in players)
|
||||
List<CCSPlayerController> players = Helper.GetValidPlayers();
|
||||
foreach (CCSPlayerController player in players)
|
||||
{
|
||||
if (!PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _);
|
||||
var isSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _);
|
||||
bool isMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _);
|
||||
bool isSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _);
|
||||
|
||||
// Only reset voice flags if not muted or silenced
|
||||
if (!isMuted && !isSilenced)
|
||||
|
|
@ -509,4 +547,22 @@ internal class PlayerManager
|
|||
}
|
||||
}, TimerFlags.REPEAT);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BanType 0 checks the SteamID only; otherwise the IP too, and every IP the player used when multi-account checks are on.
|
||||
/// </summary>
|
||||
private static bool IsBanned(CacheManager cacheManager, int banType, bool checkMultiAccounts, string playerName, ulong steamId, string? ipAddress)
|
||||
{
|
||||
if (banType == 0)
|
||||
{
|
||||
return cacheManager.IsPlayerBanned(playerName, steamId, null);
|
||||
}
|
||||
|
||||
if (checkMultiAccounts)
|
||||
{
|
||||
return cacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ipAddress);
|
||||
}
|
||||
|
||||
return cacheManager.IsPlayerBanned(playerName, steamId, ipAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using CS2_SimpleAdminApi;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using CS2_SimpleAdminApi;
|
||||
|
||||
namespace CS2_SimpleAdmin.Managers;
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ public static class PlayerPenaltyManager
|
|||
Penalties.AddOrUpdate(slot,
|
||||
(_) =>
|
||||
{
|
||||
var dict = new Dictionary<PenaltyType, List<(DateTime, int, bool)>>
|
||||
Dictionary<PenaltyType, List<(DateTime, int, bool)>> dict = new Dictionary<PenaltyType, List<(DateTime, int, bool)>>
|
||||
{
|
||||
[penaltyType] = [(endDateTime, durationInMinutes, false)]
|
||||
};
|
||||
|
|
@ -28,7 +28,7 @@ public static class PlayerPenaltyManager
|
|||
},
|
||||
(_, existingDict) =>
|
||||
{
|
||||
if (!existingDict.TryGetValue(penaltyType, out var value))
|
||||
if (!existingDict.TryGetValue(penaltyType, out List<(DateTime EndDateTime, int Duration, bool Passed)>? value))
|
||||
{
|
||||
value = new List<(DateTime, int, bool)>();
|
||||
existingDict[penaltyType] = value;
|
||||
|
|
@ -50,21 +50,27 @@ public static class PlayerPenaltyManager
|
|||
{
|
||||
endDateTime = null;
|
||||
|
||||
if (!Penalties.TryGetValue(slot, out var penaltyDict) ||
|
||||
!penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return false;
|
||||
if (!Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict) ||
|
||||
!penaltyDict.TryGetValue(penaltyType, out List<(DateTime EndDateTime, int Duration, bool Passed)>? penaltiesList))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
|
||||
{
|
||||
if (penaltiesList.Count == 0) return false;
|
||||
|
||||
if (penaltiesList.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
endDateTime = penaltiesList.First().EndDateTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
DateTime now = Time.ActualDateTime();
|
||||
|
||||
// Check if any active penalties exist
|
||||
foreach (var penalty in penaltiesList.ToList())
|
||||
foreach ((DateTime EndDateTime, int Duration, bool Passed) penalty in penaltiesList.ToList())
|
||||
{
|
||||
// Check if the penalty is still active
|
||||
if (penalty.Duration > 0 && now >= penalty.EndDateTime)
|
||||
|
|
@ -95,14 +101,14 @@ public static class PlayerPenaltyManager
|
|||
/// <returns>A list of penalties if found, otherwise an empty list.</returns>
|
||||
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType)
|
||||
{
|
||||
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
|
||||
penaltyDict.TryGetValue(penaltyType, out var penaltiesList))
|
||||
if (Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict) &&
|
||||
penaltyDict.TryGetValue(penaltyType, out List<(DateTime EndDateTime, int Duration, bool Passed)>? penaltiesList))
|
||||
{
|
||||
return penaltiesList;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all penalties for a player across multiple penalty types.
|
||||
/// </summary>
|
||||
|
|
@ -113,11 +119,11 @@ public static class PlayerPenaltyManager
|
|||
{
|
||||
List<(DateTime EndDateTime, int Duration, bool Passed)> result = [];
|
||||
|
||||
if (Penalties.TryGetValue(slot, out var penaltyDict))
|
||||
if (Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict))
|
||||
{
|
||||
foreach (var type in penaltyType)
|
||||
foreach (PenaltyType type in penaltyType)
|
||||
{
|
||||
if (penaltyDict.TryGetValue(type, out var penaltiesList))
|
||||
if (penaltyDict.TryGetValue(type, out List<(DateTime EndDateTime, int Duration, bool Passed)>? penaltiesList))
|
||||
{
|
||||
result.AddRange(penaltiesList);
|
||||
}
|
||||
|
|
@ -126,7 +132,7 @@ public static class PlayerPenaltyManager
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all penalties for a player across all penalty types.
|
||||
/// </summary>
|
||||
|
|
@ -134,12 +140,12 @@ public static class PlayerPenaltyManager
|
|||
/// <returns>A dictionary with penalty types as keys and lists of penalties as values.</returns>
|
||||
public static Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetAllPlayerPenalties(int slot)
|
||||
{
|
||||
// Check if the player has any penalties in the dictionary
|
||||
return Penalties.TryGetValue(slot, out var penaltyDict) ?
|
||||
// Return all penalty types and their respective penalties for the player
|
||||
penaltyDict :
|
||||
// If the player has no penalties, return an empty dictionary
|
||||
new Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>();
|
||||
if (Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict))
|
||||
{
|
||||
return penaltyDict;
|
||||
}
|
||||
|
||||
return new Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -179,7 +185,7 @@ public static class PlayerPenaltyManager
|
|||
/// <param name="penaltyType">The penalty type to remove.</param>
|
||||
public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType)
|
||||
{
|
||||
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
|
||||
if (Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict) &&
|
||||
penaltyDict.ContainsKey(penaltyType))
|
||||
{
|
||||
penaltyDict.Remove(penaltyType);
|
||||
|
|
@ -193,15 +199,21 @@ public static class PlayerPenaltyManager
|
|||
/// <param name="dateTime">The end datetime of penalties to mark as passed.</param>
|
||||
public static void RemovePenaltiesByDateTime(int slot, DateTime dateTime)
|
||||
{
|
||||
if (!Penalties.TryGetValue(slot, out var penaltyDict)) return;
|
||||
|
||||
foreach (var penaltiesList in penaltyDict.Values)
|
||||
if (!Penalties.TryGetValue(slot, out Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict))
|
||||
{
|
||||
for (var i = 0; i < penaltiesList.Count; i++)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (List<(DateTime EndDateTime, int Duration, bool Passed)> penaltiesList in penaltyDict.Values)
|
||||
{
|
||||
for (int i = 0; i < penaltiesList.Count; i++)
|
||||
{
|
||||
if (penaltiesList[i].EndDateTime != dateTime) continue;
|
||||
if (penaltiesList[i].EndDateTime != dateTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Create a copy of the penalty
|
||||
var penalty = penaltiesList[i];
|
||||
(DateTime EndDateTime, int Duration, bool Passed) penalty = penaltiesList[i];
|
||||
|
||||
// Update the end datetime of the copied penalty to the current datetime
|
||||
penalty.Passed = true;
|
||||
|
|
@ -223,10 +235,10 @@ public static class PlayerPenaltyManager
|
|||
{
|
||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
|
||||
{
|
||||
foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
|
||||
foreach ((int playerSlot, Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
|
||||
{
|
||||
// Remove expired penalties for the player
|
||||
foreach (var penaltiesList in penaltyDict.Values)
|
||||
foreach (List<(DateTime EndDateTime, int Duration, bool Passed)>? penaltiesList in penaltyDict.Values)
|
||||
{
|
||||
penaltiesList.RemoveAll(p => p is { Duration: > 0, Passed: true });
|
||||
}
|
||||
|
|
@ -241,10 +253,10 @@ public static class PlayerPenaltyManager
|
|||
return;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
|
||||
DateTime now = Time.ActualDateTime();
|
||||
foreach ((int playerSlot, Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>? penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
|
||||
{
|
||||
foreach (var penaltiesList in penaltyDict.Values)
|
||||
foreach (List<(DateTime EndDateTime, int Duration, bool Passed)>? penaltiesList in penaltyDict.Values)
|
||||
{
|
||||
penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime);
|
||||
}
|
||||
|
|
@ -255,4 +267,4 @@ public static class PlayerPenaltyManager
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using CounterStrikeSharp.API;
|
||||
using System.Data.Common;
|
||||
using CounterStrikeSharp.API.Modules.Cvars;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -15,13 +15,15 @@ public class ServerManager
|
|||
/// </summary>
|
||||
public static void CheckHibernationStatus()
|
||||
{
|
||||
var convar = ConVar.Find("sv_hibernate_when_empty");
|
||||
ConVar? convar = ConVar.Find("sv_hibernate_when_empty");
|
||||
if (convar == null || !convar.GetPrimitiveValue<bool>())
|
||||
{
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
CS2_SimpleAdmin._logger?.LogError("Detected setting \"sv_hibernate_when_empty true\", set false to make plugin work properly");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initiates the asynchronous process to load server data such as IP address, port, hostname, and RCON password.
|
||||
/// Handles retry attempts if IP address is not immediately available.
|
||||
|
|
@ -33,11 +35,14 @@ public class ServerManager
|
|||
{
|
||||
CS2_SimpleAdmin.Instance.AddTimer(1.0f, () =>
|
||||
{
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization: Get server IP once and reuse
|
||||
var serverIp = Helper.GetServerIp();
|
||||
var isInvalidIp = string.IsNullOrEmpty(serverIp) || serverIp.StartsWith("0.0.0");
|
||||
string serverIp = Helper.GetServerIp();
|
||||
bool isInvalidIp = string.IsNullOrEmpty(serverIp) || serverIp.StartsWith("0.0.0");
|
||||
|
||||
// Check if we've exceeded retry limit with invalid IP
|
||||
if (_getIpTryCount > 32 && isInvalidIp)
|
||||
|
|
@ -49,8 +54,8 @@ public class ServerManager
|
|||
}
|
||||
|
||||
// Optimization: Cache ConVar lookups
|
||||
var ipConVar = ConVar.Find("ip");
|
||||
var ipAddress = ipConVar?.StringValue;
|
||||
ConVar? ipConVar = ConVar.Find("ip");
|
||||
string? ipAddress = ipConVar?.StringValue;
|
||||
|
||||
// Use Helper IP if ConVar IP is invalid
|
||||
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
|
||||
|
|
@ -67,20 +72,20 @@ public class ServerManager
|
|||
}
|
||||
|
||||
// Optimization: Cache remaining ConVar lookups
|
||||
var hostportConVar = ConVar.Find("hostport");
|
||||
var hostnameConVar = ConVar.Find("hostname");
|
||||
var rconPasswordConVar = ConVar.Find("rcon_password");
|
||||
ConVar? hostportConVar = ConVar.Find("hostport");
|
||||
ConVar? hostnameConVar = ConVar.Find("hostname");
|
||||
ConVar? rconPasswordConVar = ConVar.Find("rcon_password");
|
||||
|
||||
var address = $"{ipAddress}:{hostportConVar?.GetPrimitiveValue<int>()}";
|
||||
var hostname = hostnameConVar?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
var rconPassword = rconPasswordConVar?.StringValue ?? "";
|
||||
string address = $"{ipAddress}:{hostportConVar?.GetPrimitiveValue<int>()}";
|
||||
string hostname = hostnameConVar?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
||||
string rconPassword = rconPasswordConVar?.StringValue ?? "";
|
||||
CS2_SimpleAdmin.IpAddress = address;
|
||||
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||
int? serverId = await connection.ExecuteScalarAsync<int?>(
|
||||
"SELECT id FROM sa_servers WHERE address = @address",
|
||||
new { address });
|
||||
|
|
@ -126,8 +131,8 @@ public class ServerManager
|
|||
|
||||
if (CS2_SimpleAdmin.Instance.Config.EnableMetrics)
|
||||
{
|
||||
var queryString = $"?address={address}&hostname={hostname}";
|
||||
var client = CS2_SimpleAdmin.HttpClient;
|
||||
string queryString = $"?address={address}&hostname={hostname}";
|
||||
HttpClient client = CS2_SimpleAdmin.HttpClient;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -139,8 +144,8 @@ public class ServerManager
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
CS2_SimpleAdmin.SimpleAdminApi?.OnSimpleAdminReadyEvent();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using CS2_SimpleAdmin.Database;
|
||||
using System.Data.Common;
|
||||
using CS2_SimpleAdmin.Database;
|
||||
using CS2_SimpleAdminApi;
|
||||
using Dapper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -17,17 +18,20 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
|
||||
public async Task<int?> WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(true);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAddWarnQuery(true);
|
||||
|
||||
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
int? warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = player.SteamId.SteamId64,
|
||||
playerName = player.Name,
|
||||
|
|
@ -42,7 +46,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
|
||||
return warnId;
|
||||
}
|
||||
catch(Exception)
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
@ -58,17 +62,20 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
|
||||
public async Task<int?> AddWarnBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
|
||||
{
|
||||
if (databaseProvider == null) return null;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var now = Time.ActualDateTime();
|
||||
var futureTime = now.AddMinutes(time);
|
||||
DateTime now = Time.ActualDateTime();
|
||||
DateTime futureTime = now.AddMinutes(time);
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
var sql = databaseProvider.GetAddWarnQuery(false);
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
string sql = databaseProvider.GetAddWarnQuery(false);
|
||||
|
||||
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
int? warnId = await connection.ExecuteScalarAsync<int?>(sql, new
|
||||
{
|
||||
playerSteamid = playerSteamId,
|
||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||
|
|
@ -96,15 +103,18 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>A list of dynamic objects representing warnings, or an empty list if none found or on failure.</returns>
|
||||
public async Task<List<dynamic>> GetPlayerWarns(PlayerInfo player, bool active = true)
|
||||
{
|
||||
if (databaseProvider == null) return [];
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetPlayerWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
|
||||
string sql = databaseProvider.GetPlayerWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
|
||||
var parameters = new { PlayerSteamID = player.SteamId.SteamId64, serverid = CS2_SimpleAdmin.ServerId };
|
||||
var warns = await connection.QueryAsync<dynamic>(sql, parameters);
|
||||
IEnumerable<dynamic> warns = await connection.QueryAsync<dynamic>(sql, parameters);
|
||||
|
||||
return warns.ToList();
|
||||
}
|
||||
|
|
@ -122,14 +132,17 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>The count of warnings as an integer, or 0 if none found or on failure.</returns>
|
||||
public async Task<int> GetPlayerWarnsCount(ulong steamId, bool active = true)
|
||||
{
|
||||
if (databaseProvider == null) return 0;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetPlayerWarnsCountQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
|
||||
var warnsCount = await connection.ExecuteScalarAsync<int>(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
string sql = databaseProvider.GetPlayerWarnsCountQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
|
||||
int warnsCount = await connection.ExecuteScalarAsync<int>(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
return warnsCount;
|
||||
}
|
||||
catch (Exception)
|
||||
|
|
@ -146,13 +159,16 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task UnwarnPlayer(PlayerInfo player, int warnId)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetUnwarnByIdQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
string sql = databaseProvider.GetUnwarnByIdQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64, warnId, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -160,7 +176,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove warn + {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes the most recent warning matching a player pattern (usually SteamID string).
|
||||
/// </summary>
|
||||
|
|
@ -168,13 +184,16 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task UnwarnPlayer(string playerPattern)
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetUnwarnLastQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
string sql = databaseProvider.GetUnwarnLastQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { steamid = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -189,13 +208,16 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task ExpireOldWarns()
|
||||
{
|
||||
if (databaseProvider == null) return;
|
||||
if (databaseProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||
await using DbConnection connection = await databaseProvider.CreateConnectionAsync();
|
||||
|
||||
var sql = databaseProvider.GetExpireWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
string sql = databaseProvider.GetExpireWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -203,4 +225,4 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove expired warns + {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue