mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-27 16:09:07 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RadicadeServer.API.Notifications;
|
||||
using RadicadeServer.DB;
|
||||
using RadicadeServer.RecNet.Auth;
|
||||
using RadicadeServer.RecNet.Enums.Messages;
|
||||
using RadicadeServer.RecNet.Enums.Moderation;
|
||||
using RadicadeServer.RecNet.Moderation;
|
||||
using RadicadeServer.RecNet.Notifications;
|
||||
|
||||
namespace RadicadeServer.Utils
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public class BanStatusMonitor : BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(15);
|
||||
private readonly IDbContextFactory<RecDBContext> _dbFactory;
|
||||
|
||||
|
||||
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<long, string> LastSeenBan = new();
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<long, long> LastSeenRoom = new();
|
||||
|
||||
private static readonly System.Collections.Concurrent.ConcurrentDictionary<long, string> LastSeenVoiceRestriction = new();
|
||||
|
||||
public BanStatusMonitor(IDbContextFactory<RecDBContext> dbFactory) {
|
||||
_dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await CheckConnectedPlayersAsync(stoppingToken);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
await Task.Delay(Interval, stoppingToken);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckConnectedPlayersAsync(CancellationToken ct)
|
||||
{
|
||||
var clients = Notify.SnapshotClients();
|
||||
if (clients.Count == 0) return;
|
||||
|
||||
var accountIds = clients.Select(c => c.AccountId).Distinct().ToList();
|
||||
|
||||
await using var db = await _dbFactory.CreateDbContextAsync(ct);
|
||||
|
||||
|
||||
var players = await db.Players.AsNoTracking()
|
||||
.Where(x => accountIds.Contains(x.AccountId)).ToListAsync(ct);
|
||||
var presences = await db.Presences.AsNoTracking()
|
||||
.Where(x => accountIds.Contains(x.playerId)).ToListAsync(ct);
|
||||
var moderationBans = await db.Moderation
|
||||
.Where(x => x.IsBan && x.BanMethod != AccountLock.BanMethod && accountIds.Contains(x.PlayerId))
|
||||
.ToListAsync(ct);
|
||||
var hardwareBans = await db.HardwareBans.ToListAsync(ct);
|
||||
var ipBans = await db.IpBans.ToListAsync(ct);
|
||||
var voiceRestrictions = await db.VoiceChatRestrictions.AsNoTracking()
|
||||
.Where(x => accountIds.Contains(x.PlayerId) && x.ClearedAt == null && x.ExpiresAt > DateTime.UtcNow)
|
||||
.ToListAsync(ct);
|
||||
|
||||
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
bool cleaned = false;
|
||||
foreach (var m in moderationBans.Where(m => m.ExpireDuration < now).ToList())
|
||||
{
|
||||
db.Moderation.Remove(m);
|
||||
moderationBans.Remove(m);
|
||||
cleaned = true;
|
||||
}
|
||||
foreach (var h in hardwareBans.Where(h => h.ExpiresAt.HasValue && h.ExpiresAt.Value < now).ToList())
|
||||
{
|
||||
db.HardwareBans.Remove(h);
|
||||
hardwareBans.Remove(h);
|
||||
cleaned = true;
|
||||
}
|
||||
foreach (var i in ipBans.Where(i => i.ExpiresAt.HasValue && i.ExpiresAt.Value < now).ToList())
|
||||
{
|
||||
db.IpBans.Remove(i);
|
||||
ipBans.Remove(i);
|
||||
cleaned = true;
|
||||
}
|
||||
if (cleaned) await db.SaveChangesAsync(ct);
|
||||
|
||||
var connected = new HashSet<long>(accountIds);
|
||||
foreach (var client in clients)
|
||||
{
|
||||
var id = client.AccountId;
|
||||
var player = players.FirstOrDefault(p => p.AccountId == id);
|
||||
if (player == null) continue;
|
||||
|
||||
|
||||
var moderation = moderationBans.FirstOrDefault(m => m.PlayerId == id);
|
||||
var hwBan = !string.IsNullOrWhiteSpace(player.DeviceId)
|
||||
? hardwareBans.FirstOrDefault(h => h.DeviceId == player.DeviceId)
|
||||
: null;
|
||||
var ipBan = ResolveIpBan(ipBans, player);
|
||||
|
||||
string? signature = null;
|
||||
string? reason = null;
|
||||
DateTime? startedAt = null;
|
||||
DateTime expiry = DateTime.MaxValue;
|
||||
var banCategory = ReportCategory.Misc;
|
||||
if (moderation != null)
|
||||
{
|
||||
signature = "mod:" + moderation.Id;
|
||||
reason = moderation.Message ?? "";
|
||||
startedAt = moderation.CreatedAt;
|
||||
expiry = moderation.ExpireDuration;
|
||||
banCategory = moderation.Category;
|
||||
}
|
||||
else if (hwBan != null)
|
||||
{
|
||||
signature = "hw:" + hwBan.Id;
|
||||
reason = hwBan.Reason ?? "";
|
||||
startedAt = hwBan.CreatedAt;
|
||||
expiry = hwBan.ExpiresAt ?? DateTime.MaxValue;
|
||||
}
|
||||
else if (ipBan != null)
|
||||
{
|
||||
signature = "ip:" + ipBan.IpAddress;
|
||||
reason = ipBan.Reason ?? "";
|
||||
startedAt = ipBan.CreatedAt;
|
||||
expiry = ipBan.ExpiresAt ?? DateTime.MaxValue;
|
||||
}
|
||||
|
||||
var presence = presences.FirstOrDefault(p => p.playerId == id);
|
||||
var last = LastSeenBan.GetValueOrDefault(id);
|
||||
var roomKey = presence?.roomInstance ?? -1;
|
||||
var lastRoom = LastSeenRoom.GetValueOrDefault(id, long.MinValue);
|
||||
|
||||
if (signature != null)
|
||||
{
|
||||
var banChanged = last != signature;
|
||||
var roomChanged = lastRoom != roomKey;
|
||||
|
||||
LastSeenBan[id] = signature;
|
||||
LastSeenRoom[id] = roomKey;
|
||||
|
||||
if (banChanged || roomChanged)
|
||||
{
|
||||
if (!await db.IsDeveloper(id))
|
||||
await KickIfInRoomAsync(db, id, reason ?? "", expiry, startedAt, hwBan?.BannedAccountId, banChanged, banCategory);
|
||||
}
|
||||
}
|
||||
else if (last != null)
|
||||
{
|
||||
LastSeenBan.TryRemove(id, out _);
|
||||
LastSeenRoom.TryRemove(id, out _);
|
||||
|
||||
await PlayerUtils.ReleaseModerationHoldAsync(id, await db.IsDeveloper(id));
|
||||
|
||||
var profile = await db.Profiles.FirstOrDefaultAsync(x => x.accountId == id);
|
||||
if (profile != null)
|
||||
await Notify.SendNotify(id, new Notification { Id = NotificationType.RefreshLogin, Msg = profile });
|
||||
}
|
||||
|
||||
|
||||
var restriction = voiceRestrictions.FirstOrDefault(r => r.PlayerId == id);
|
||||
var restrictionSignature = restriction == null
|
||||
? null
|
||||
: $"vr:{restriction.Id}:{restriction.ExpiresAt.Ticks}";
|
||||
var known = LastSeenVoiceRestriction.TryGetValue(id, out var lastRestriction)
|
||||
? lastRestriction
|
||||
: null;
|
||||
if (known != restrictionSignature)
|
||||
{
|
||||
if (restrictionSignature == null)
|
||||
LastSeenVoiceRestriction.TryRemove(id, out _);
|
||||
else
|
||||
LastSeenVoiceRestriction[id] = restrictionSignature;
|
||||
|
||||
await VoiceChatRestrictions.PushAsync(id, restriction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
foreach (var key in LastSeenBan.Keys)
|
||||
{
|
||||
if (!connected.Contains(key))
|
||||
LastSeenBan.TryRemove(key, out _);
|
||||
LastSeenRoom.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
foreach (var key in LastSeenVoiceRestriction.Keys)
|
||||
{
|
||||
if (!connected.Contains(key))
|
||||
LastSeenVoiceRestriction.TryRemove(key, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private static IpBan? ResolveIpBan(List<IpBan> ipBans, PlayerData player)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(player.LastIpAddress)) return null;
|
||||
return ipBans.FirstOrDefault(b => b.IpAddress == player.LastIpAddress);
|
||||
}
|
||||
|
||||
private async Task KickIfInRoomAsync(RecDBContext db, long accountId, string reason, DateTime expiry, DateTime? startedAt = null, long? relatedAccountId = null, bool banChanged = false, ReportCategory category = ReportCategory.Misc)
|
||||
{
|
||||
var presence = await db.Presences.FirstOrDefaultAsync(x => x.playerId == accountId);
|
||||
|
||||
if (presence?.roomInstance != null)
|
||||
{
|
||||
var roomInstance = await db.RoomInstances.FirstOrDefaultAsync(x => x.roomInstanceId == presence.roomInstance);
|
||||
var room = roomInstance != null ? await db.Rooms.FirstOrDefaultAsync(x => x.RoomId == roomInstance.roomId) : null;
|
||||
bool isOwnDorm = room != null && room.IsDorm && room.CreatorAccountId == accountId;
|
||||
|
||||
if (isOwnDorm)
|
||||
{
|
||||
|
||||
bool relatedBan = relatedAccountId.HasValue && relatedAccountId.Value != accountId;
|
||||
if (!relatedBan || !banChanged) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
await PlayerUtils.EjectToDormAsync(db, accountId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await Notify.SendNotify(accountId, new Notification
|
||||
{
|
||||
Id = NotificationType.ModerationKick,
|
||||
Msg = PlayerUtils.BuildBlockDetails(true, category, reason, expiry, startedAtUtc: startedAt,
|
||||
relatedUsername: await PlayerUtils.RelatedAccountUsernameAsync(db, relatedAccountId, accountId))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user