mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 01:46:07 +00:00
Enhanced Ban
This commit is contained in:
1 parent
40bbc2027b
commit
f8eeeaae8b
23 files changed
+403
-121
No files matched your search
@@ -150,6 +150,7 @@
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
".NETStandard,Version=v2.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,6 @@ public sealed class LanguageService
|
||||
"command.note.set": "Note set: {0}",
|
||||
"command.color.invalid": "Invalid color ID. Valid range: 0–17.",
|
||||
"command.color.set": "Color changed to {0} ({1}).",
|
||||
|
||||
"command.title.too_long": "Title too long. Max 12 characters.",
|
||||
"command.title.cleared": "Title removed.",
|
||||
"command.title.set": "Title set: {0}",
|
||||
@@ -205,7 +204,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "Set or clear a lobby note (host only).",
|
||||
"command.note.usage": "note <text> | note clear",
|
||||
"command.max.description": "Set max players for the lobby (host only).",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "You are banned from this server.",
|
||||
"ban.notice.reason": "Reason: {0}",
|
||||
"ban.notice.unban": "Unban time: {0}",
|
||||
"ban.notice.unban_permanent": "Unban time: Permanent",
|
||||
"ban.notice.contact": "If you have any questions, please contact the administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -260,7 +264,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "设置或清除房间备注(仅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "设置房间最大人数(仅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被服务器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封时间:{0}",
|
||||
"ban.notice.unban_permanent": "解封时间:永久",
|
||||
"ban.notice.contact": "如有问题请联系管理员"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -315,7 +324,12 @@ public sealed class LanguageService
|
||||
"command.note.description": "設置或清除房間備註(僅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "設置房間最大人數(僅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被伺服器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封時間:{0}",
|
||||
"ban.notice.unban_permanent": "解封時間:永久",
|
||||
"ban.notice.contact": "如有問題請聯絡管理員"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -356,7 +370,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "이 명령어는 로비에서만 사용할 수 있습니다.",
|
||||
"command.players.header": "=== 플레이어 ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "내 핑: {0}ms"
|
||||
"command.ping.result": "내 핑: {0}ms",
|
||||
"ban.notice.header": "이 서버에서 차단되었습니다.",
|
||||
"ban.notice.reason": "사유: {0}",
|
||||
"ban.notice.unban": "차단 해제 시간: {0}",
|
||||
"ban.notice.unban_permanent": "차단 해제 시간: 영구",
|
||||
"ban.notice.contact": "문의 사항이 있으면 관리자에게 연락해 주세요."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -397,7 +416,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Эта команда может использоваться только в лобби.",
|
||||
"command.players.header": "=== Игроки ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}мс",
|
||||
"command.ping.result": "Ваш пинг: {0}мс"
|
||||
"command.ping.result": "Ваш пинг: {0}мс",
|
||||
"ban.notice.header": "Вы забанены на этом сервере.",
|
||||
"ban.notice.reason": "Причина: {0}",
|
||||
"ban.notice.unban": "Время разбана: {0}",
|
||||
"ban.notice.unban_permanent": "Время разбана: навсегда",
|
||||
"ban.notice.contact": "Если у вас есть вопросы, обратитесь к администратору."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -438,7 +462,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dieser Befehl kann nur in der Lobby verwendet werden.",
|
||||
"command.players.header": "=== Spieler ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Dein Ping: {0}ms"
|
||||
"command.ping.result": "Dein Ping: {0}ms",
|
||||
"ban.notice.header": "Du wurdest von diesem Server gebannt.",
|
||||
"ban.notice.reason": "Grund: {0}",
|
||||
"ban.notice.unban": "Entbannungszeit: {0}",
|
||||
"ban.notice.unban_permanent": "Entbannungszeit: Dauerhaft",
|
||||
"ban.notice.contact": "Bei Fragen wende dich an den Administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -479,7 +508,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Cette commande ne peut être utilisée que dans le lobby.",
|
||||
"command.players.header": "=== Joueurs ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Votre ping : {0}ms"
|
||||
"command.ping.result": "Votre ping : {0}ms",
|
||||
"ban.notice.header": "Vous êtes banni de ce serveur.",
|
||||
"ban.notice.reason": "Raison : {0}",
|
||||
"ban.notice.unban": "Débannissement : {0}",
|
||||
"ban.notice.unban_permanent": "Débannissement : Permanent",
|
||||
"ban.notice.contact": "En cas de question, contactez un administrateur."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -520,7 +554,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "このコマンドはロビーでのみ使用できます。",
|
||||
"command.players.header": "=== プレイヤー ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "あなたのPing: {0}ms"
|
||||
"command.ping.result": "あなたのPing: {0}ms",
|
||||
"ban.notice.header": "このサーバーからBANされています。",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "BAN解除日時:{0}",
|
||||
"ban.notice.unban_permanent": "BAN解除日時:永久",
|
||||
"ban.notice.contact": "ご不明な点があれば管理者にお問い合わせください。"
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -561,7 +600,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contacte o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -600,7 +644,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contate o administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -639,7 +688,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Este comando solo puede usarse en el lobby.",
|
||||
"command.players.header": "=== Jugadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Tu ping: {0}ms"
|
||||
"command.ping.result": "Tu ping: {0}ms",
|
||||
"ban.notice.header": "Has sido baneado de este servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Fin del baneo: {0}",
|
||||
"ban.notice.unban_permanent": "Fin del baneo: Permanente",
|
||||
"ban.notice.contact": "Si tienes dudas, contacta con un administrador."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -678,7 +732,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Questo comando può essere usato solo nella lobby.",
|
||||
"command.players.header": "=== Giocatori ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Il tuo ping: {0}ms"
|
||||
"command.ping.result": "Il tuo ping: {0}ms",
|
||||
"ban.notice.header": "Sei stato bannato da questo server.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Sbanno: {0}",
|
||||
"ban.notice.unban_permanent": "Sbanno: Permanente",
|
||||
"ban.notice.contact": "Per qualsiasi domanda, contatta l'amministratore."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -717,7 +776,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Dit commando kan alleen in de lobby worden gebruikt.",
|
||||
"command.players.header": "=== Spelers ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Jouw ping: {0}ms"
|
||||
"command.ping.result": "Jouw ping: {0}ms",
|
||||
"ban.notice.header": "Je bent geband van deze server.",
|
||||
"ban.notice.reason": "Reden: {0}",
|
||||
"ban.notice.unban": "Ontbantijd: {0}",
|
||||
"ban.notice.unban_permanent": "Ontbantijd: Permanent",
|
||||
"ban.notice.contact": "Neem bij vragen contact op met de beheerder."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -756,7 +820,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ang command na ito ay maaari lamang gamitin sa lobby.",
|
||||
"command.players.header": "=== Mga Manlalaro ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Ang iyong ping: {0}ms"
|
||||
"command.ping.result": "Ang iyong ping: {0}ms",
|
||||
"ban.notice.header": "Banned ka sa server na ito.",
|
||||
"ban.notice.reason": "Dahilan: {0}",
|
||||
"ban.notice.unban": "Oras ng pag-unban: {0}",
|
||||
"ban.notice.unban_permanent": "Oras ng pag-unban: Permanente",
|
||||
"ban.notice.contact": "Kung may tanong, makipag-ugnayan sa administrator."
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -795,7 +864,12 @@ public sealed class LanguageService
|
||||
"command.players.not_in_lobby": "Ní féidir an t-ordú seo a úsáid ach sa stocaireacht.",
|
||||
"command.players.header": "=== Imreoirí ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Do phing: {0}ms"
|
||||
"command.ping.result": "Do phing: {0}ms",
|
||||
"ban.notice.header": "Tá cosc ort ar an bhfreastalaí seo.",
|
||||
"ban.notice.reason": "Cúis: {0}",
|
||||
"ban.notice.unban": "Am dí-chosc: {0}",
|
||||
"ban.notice.unban_permanent": "Am dí-chosc: Buan",
|
||||
"ban.notice.contact": "Má tá ceist agat, déan teagmháil le riarthóir."
|
||||
}
|
||||
""";
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -487,7 +487,7 @@ namespace Empostor.Server.Http
|
||||
return BadRequest(Err("Invalid IP"));
|
||||
}
|
||||
|
||||
var entry = _bans.BanIp(ip, req.Reason ?? "Banned by admin");
|
||||
var entry = _bans.BanIp(ip, req.Reason ?? "Banned by admin", BanStore.ParseDuration(req.Duration));
|
||||
var kicked = 0;
|
||||
foreach (var c in _clientManager.Clients.ToList())
|
||||
{
|
||||
@@ -521,7 +521,7 @@ namespace Empostor.Server.Http
|
||||
return BadRequest(Err("FriendCode required"));
|
||||
}
|
||||
|
||||
var entry = _bans.BanFriendCode(req.FriendCode, req.Reason ?? "Banned by admin");
|
||||
var entry = _bans.BanFriendCode(req.FriendCode, req.Reason ?? "Banned by admin", BanStore.ParseDuration(req.Duration));
|
||||
var kicked = 0;
|
||||
foreach (var c in _clientManager.Clients.ToList())
|
||||
{
|
||||
@@ -901,9 +901,9 @@ namespace Empostor.Server.Http
|
||||
|
||||
public sealed record ClientIdReq(int ClientId, string? Reason = null);
|
||||
|
||||
public sealed record BanIpReq(string Ip, string? Reason);
|
||||
public sealed record BanIpReq(string Ip, string? Reason, string? Duration = null);
|
||||
|
||||
public sealed record BanFcReq(string FriendCode, string? Reason);
|
||||
public sealed record BanFcReq(string FriendCode, string? Reason, string? Duration = null);
|
||||
|
||||
public sealed record UnbanReq(string Value);
|
||||
|
||||
|
||||
@@ -998,6 +998,7 @@ internal static class AdminTemplateDefaults
|
||||
<h3><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94L5.62 21a2 2 0 01-2.83-2.83l7.91-7.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg><span data-i18n="ban.title_ip">Ban IP</span></h3>
|
||||
<div class="field"><label data-i18n="table.ip">IP</label><input id="bi-v" data-i18n-placeholder="ban.ip_placeholder" placeholder="1.2.3.4"></div>
|
||||
<div class="field"><label data-i18n="table.reason">Reason</label><input id="bi-r" data-i18n-placeholder="ban.reason_placeholder" placeholder="Optional..."></div>
|
||||
<div class="field"><label data-i18n="ban.duration">Duration</label><div class="row"><input id="bi-dv" type="number" min="1" value="1" style="flex:0 0 64px"><select id="bi-du" style="flex:1"><option value="h" data-i18n="ban.unit_hours">Hours (1-24)</option><option value="d" data-i18n="ban.unit_days">Days (1-30)</option><option value="mo" data-i18n="ban.unit_months">Months (1-12)</option><option value="y" data-i18n="ban.unit_years">Years (1-10)</option><option value="permanent" selected data-i18n="ban.unit_permanent">Permanent</option></select></div></div>
|
||||
<button class="bd" onclick="doBanIp()"><span data-i18n="ban.button_ip">Ban IP</span></button>
|
||||
<div id="bi-msg" class="msg"></div>
|
||||
</div>
|
||||
@@ -1005,6 +1006,7 @@ internal static class AdminTemplateDefaults
|
||||
<h3><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94L5.62 21a2 2 0 01-2.83-2.83l7.91-7.91a6 6 0 017.94-7.94l-3.76 3.76z"/></svg><span data-i18n="ban.title_fc">Ban Friend Code</span></h3>
|
||||
<div class="field"><label data-i18n="table.friend_code">Friend Code</label><input id="bf-v" data-i18n-placeholder="ban.fc_placeholder" placeholder="Name#1234"></div>
|
||||
<div class="field"><label data-i18n="table.reason">Reason</label><input id="bf-r" data-i18n-placeholder="ban.reason_placeholder" placeholder="Optional..."></div>
|
||||
<div class="field"><label data-i18n="ban.duration">Duration</label><div class="row"><input id="bf-dv" type="number" min="1" value="1" style="flex:0 0 64px"><select id="bf-du" style="flex:1"><option value="h" data-i18n="ban.unit_hours">Hours (1-24)</option><option value="d" data-i18n="ban.unit_days">Days (1-30)</option><option value="mo" data-i18n="ban.unit_months">Months (1-12)</option><option value="y" data-i18n="ban.unit_years">Years (1-10)</option><option value="permanent" selected data-i18n="ban.unit_permanent">Permanent</option></select></div></div>
|
||||
<button class="bd" onclick="doBanFc()"><span data-i18n="ban.button_fc">Ban FC</span></button>
|
||||
<div id="bf-msg" class="msg"></div>
|
||||
</div>
|
||||
@@ -1280,7 +1282,8 @@ internal static class AdminTemplateDefaults
|
||||
document.getElementById('bl-fc').innerHTML = d.friendCodes.length ? d.friendCodes.map(b => bi(b, 'fc')).join('') : '<div class="empty">' + _('ban.none', 'None') + '</div>';
|
||||
}
|
||||
function bi(b, t) {
|
||||
return `<div class="bi"><div><div class="bv">${e(b.value)}</div><div class="br2">${e(b.reason)}</div></div><div class="bt">${new Date(b.bannedAt).toLocaleString()}</div><button class="bsm" style="background:rgba(248,81,73,.15);color:var(--r);border:1px solid rgba(248,81,73,.3)" onclick="doUnban('${t}','${e(b.value)}')">${_('ban.unban', 'Unban')}</button></div>`;
|
||||
const until = b.bannedUntil ? new Date(b.bannedUntil).toLocaleString() : _('ban.permanent', 'Permanent');
|
||||
return `<div class="bi"><div><div class="bv">${e(b.value)}</div><div class="br2">${e(b.reason)}</div></div><div class="bt">${new Date(b.bannedAt).toLocaleString()}<br><span style="opacity:.7">${_('ban.until', 'Until')}: ${until}</span></div><button class="bsm" style="background:rgba(248,81,73,.15);color:var(--r);border:1px solid rgba(248,81,73,.3)" onclick="doUnban('${t}','${e(b.value)}')">${_('ban.unban', 'Unban')}</button></div>`;
|
||||
}
|
||||
|
||||
async function fGamesEnd() {
|
||||
@@ -1503,17 +1506,29 @@ internal static class AdminTemplateDefaults
|
||||
fKickList();
|
||||
}
|
||||
|
||||
function banDuration(vId, uId) {
|
||||
const u = document.getElementById(uId).value;
|
||||
if (u === 'permanent') return 'permanent';
|
||||
let v = parseInt(document.getElementById(vId).value, 10);
|
||||
if (isNaN(v) || v < 1) v = 1;
|
||||
const max = { h: 24, d: 30, mo: 12, y: 10 }[u] || 24;
|
||||
if (v > max) v = max;
|
||||
return v + u;
|
||||
}
|
||||
|
||||
async function doBanIp() {
|
||||
const v = document.getElementById('bi-v').value.trim(), r = document.getElementById('bi-r').value.trim();
|
||||
if (!v) return msg('bi-msg', false, _('alert.ip_required', 'IP required'));
|
||||
const { ok, data } = await api('POST', '/api/admin/ban/ip', { ip: v, reason: r });
|
||||
const duration = banDuration('bi-dv', 'bi-du');
|
||||
const { ok, data } = await api('POST', '/api/admin/ban/ip', { ip: v, reason: r, duration });
|
||||
msg('bi-msg', ok, ok ? _('alert.banned', 'Banned {0} ({1} disconnected)').replace('{0}', data.banned).replace('{1}', data.disconnected) : (data.error ?? 'Error'));
|
||||
}
|
||||
|
||||
async function doBanFc() {
|
||||
const v = document.getElementById('bf-v').value.trim(), r = document.getElementById('bf-r').value.trim();
|
||||
if (!v) return msg('bf-msg', false, _('alert.fc_required', 'FC required'));
|
||||
const { ok, data } = await api('POST', '/api/admin/ban/fc', { friendCode: v, reason: r });
|
||||
const duration = banDuration('bf-dv', 'bf-du');
|
||||
const { ok, data } = await api('POST', '/api/admin/ban/fc', { friendCode: v, reason: r, duration });
|
||||
msg('bf-msg', ok, ok ? _('alert.banned', 'Banned {0} ({1} disconnected)').replace('{0}', data.banned).replace('{1}', data.disconnected) : (data.error ?? 'Error'));
|
||||
}
|
||||
|
||||
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "Dieser Befehl kann nur in der Lobby verwendet werden.",
|
||||
"command.players.header": "=== Spieler ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Dein Ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Dein Ping: {0}ms",
|
||||
"ban.notice.header": "Du wurdest von diesem Server gebannt.",
|
||||
"ban.notice.reason": "Grund: {0}",
|
||||
"ban.notice.unban": "Entbannungszeit: {0}",
|
||||
"ban.notice.unban_permanent": "Entbannungszeit: Dauerhaft",
|
||||
"ban.notice.contact": "Bei Fragen wende dich an den Administrator."
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
"command.note.set": "Note set: {0}",
|
||||
"command.color.invalid": "Invalid color ID. Valid range: 0–17.",
|
||||
"command.color.set": "Color changed to {0} ({1}).",
|
||||
|
||||
"command.title.too_long": "Title too long. Max 12 characters.",
|
||||
"command.title.cleared": "Title removed.",
|
||||
"command.title.set": "Title set: {0}",
|
||||
@@ -66,5 +65,10 @@
|
||||
"command.ban.cannot_ban_self": "You cannot ban yourself.",
|
||||
"command.ban.banned": "{0} has been banned. Reason: {1}",
|
||||
"command.ban.description": "Ban a player by friend code or name. Host only.",
|
||||
"command.ban.usage_cmd": "ban <player> [reason]"
|
||||
}
|
||||
"command.ban.usage_cmd": "ban <player> [reason]",
|
||||
"ban.notice.header": "You are banned from this server.",
|
||||
"ban.notice.reason": "Reason: {0}",
|
||||
"ban.notice.unban": "Unban time: {0}",
|
||||
"ban.notice.unban_permanent": "Unban time: Permanent",
|
||||
"ban.notice.contact": "If you have any questions, please contact the administrator."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Este comando solo puede usarse en el lobby.",
|
||||
"command.players.header": "=== Jugadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Tu ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Tu ping: {0}ms",
|
||||
"ban.notice.header": "Has sido baneado de este servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Fin del baneo: {0}",
|
||||
"ban.notice.unban_permanent": "Fin del baneo: Permanente",
|
||||
"ban.notice.contact": "Si tienes dudas, contacta con un administrador."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Ang command na ito ay maaari lamang gamitin sa lobby.",
|
||||
"command.players.header": "=== Mga Manlalaro ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Ang iyong ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Ang iyong ping: {0}ms",
|
||||
"ban.notice.header": "Banned ka sa server na ito.",
|
||||
"ban.notice.reason": "Dahilan: {0}",
|
||||
"ban.notice.unban": "Oras ng pag-unban: {0}",
|
||||
"ban.notice.unban_permanent": "Oras ng pag-unban: Permanente",
|
||||
"ban.notice.contact": "Kung may tanong, makipag-ugnayan sa administrator."
|
||||
}
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "Cette commande ne peut être utilisée que dans le lobby.",
|
||||
"command.players.header": "=== Joueurs ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Votre ping : {0}ms"
|
||||
}
|
||||
"command.ping.result": "Votre ping : {0}ms",
|
||||
"ban.notice.header": "Vous êtes banni de ce serveur.",
|
||||
"ban.notice.reason": "Raison : {0}",
|
||||
"ban.notice.unban": "Débannissement : {0}",
|
||||
"ban.notice.unban_permanent": "Débannissement : Permanent",
|
||||
"ban.notice.contact": "En cas de question, contactez un administrateur."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Ní féidir an t-ordú seo a úsáid ach sa stocaireacht.",
|
||||
"command.players.header": "=== Imreoirí ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Do phing: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Do phing: {0}ms",
|
||||
"ban.notice.header": "Tá cosc ort ar an bhfreastalaí seo.",
|
||||
"ban.notice.reason": "Cúis: {0}",
|
||||
"ban.notice.unban": "Am dí-chosc: {0}",
|
||||
"ban.notice.unban_permanent": "Am dí-chosc: Buan",
|
||||
"ban.notice.contact": "Má tá ceist agat, déan teagmháil le riarthóir."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Questo comando può essere usato solo nella lobby.",
|
||||
"command.players.header": "=== Giocatori ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Il tuo ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Il tuo ping: {0}ms",
|
||||
"ban.notice.header": "Sei stato bannato da questo server.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Sbanno: {0}",
|
||||
"ban.notice.unban_permanent": "Sbanno: Permanente",
|
||||
"ban.notice.contact": "Per qualsiasi domanda, contatta l'amministratore."
|
||||
}
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "このコマンドはロビーでのみ使用できます。",
|
||||
"command.players.header": "=== プレイヤー ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "あなたのPing: {0}ms"
|
||||
}
|
||||
"command.ping.result": "あなたのPing: {0}ms",
|
||||
"ban.notice.header": "このサーバーからBANされています。",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "BAN解除日時:{0}",
|
||||
"ban.notice.unban_permanent": "BAN解除日時:永久",
|
||||
"ban.notice.contact": "ご不明な点があれば管理者にお問い合わせください。"
|
||||
}
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "이 명령어는 로비에서만 사용할 수 있습니다.",
|
||||
"command.players.header": "=== 플레이어 ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "내 핑: {0}ms"
|
||||
}
|
||||
"command.ping.result": "내 핑: {0}ms",
|
||||
"ban.notice.header": "이 서버에서 차단되었습니다.",
|
||||
"ban.notice.reason": "사유: {0}",
|
||||
"ban.notice.unban": "차단 해제 시간: {0}",
|
||||
"ban.notice.unban_permanent": "차단 해제 시간: 영구",
|
||||
"ban.notice.contact": "문의 사항이 있으면 관리자에게 연락해 주세요."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Dit commando kan alleen in de lobby worden gebruikt.",
|
||||
"command.players.header": "=== Spelers ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Jouw ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Jouw ping: {0}ms",
|
||||
"ban.notice.header": "Je bent geband van deze server.",
|
||||
"ban.notice.reason": "Reden: {0}",
|
||||
"ban.notice.unban": "Ontbantijd: {0}",
|
||||
"ban.notice.unban_permanent": "Ontbantijd: Permanent",
|
||||
"ban.notice.contact": "Neem bij vragen contact op met de beheerder."
|
||||
}
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contacte o administrador."
|
||||
}
|
||||
@@ -32,5 +32,10 @@
|
||||
"command.players.not_in_lobby": "Este comando só pode ser usado no lobby.",
|
||||
"command.players.header": "=== Jogadores ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}ms",
|
||||
"command.ping.result": "Seu ping: {0}ms"
|
||||
}
|
||||
"command.ping.result": "Seu ping: {0}ms",
|
||||
"ban.notice.header": "Você foi banido deste servidor.",
|
||||
"ban.notice.reason": "Motivo: {0}",
|
||||
"ban.notice.unban": "Desbanimento: {0}",
|
||||
"ban.notice.unban_permanent": "Desbanimento: Permanente",
|
||||
"ban.notice.contact": "Em caso de dúvidas, contate o administrador."
|
||||
}
|
||||
@@ -34,5 +34,10 @@
|
||||
"command.players.not_in_lobby": "Эта команда может использоваться только в лобби.",
|
||||
"command.players.header": "=== Игроки ({0}) ===",
|
||||
"command.players.entry": "{0} | {1} | {2}мс",
|
||||
"command.ping.result": "Ваш пинг: {0}мс"
|
||||
}
|
||||
"command.ping.result": "Ваш пинг: {0}мс",
|
||||
"ban.notice.header": "Вы забанены на этом сервере.",
|
||||
"ban.notice.reason": "Причина: {0}",
|
||||
"ban.notice.unban": "Время разбана: {0}",
|
||||
"ban.notice.unban_permanent": "Время разбана: навсегда",
|
||||
"ban.notice.contact": "Если у вас есть вопросы, обратитесь к администратору."
|
||||
}
|
||||
@@ -65,5 +65,10 @@
|
||||
"command.ban.cannot_ban_self": "你不能封禁自己。",
|
||||
"command.ban.banned": "{0} 已被封禁。原因:{1}",
|
||||
"command.ban.description": "通过好友代码或名称封禁玩家(仅房主)。",
|
||||
"command.ban.usage_cmd": "ban <玩家> [原因]"
|
||||
}
|
||||
"command.ban.usage_cmd": "ban <玩家> [原因]",
|
||||
"ban.notice.header": "您已被服务器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封时间:{0}",
|
||||
"ban.notice.unban_permanent": "解封时间:永久",
|
||||
"ban.notice.contact": "如有问题请联系管理员"
|
||||
}
|
||||
@@ -48,5 +48,10 @@
|
||||
"command.note.description": "設置或清除房間備註(僅房主)。",
|
||||
"command.note.usage": "note <文字> | note clear",
|
||||
"command.max.description": "設置房間最大人數(僅房主)。",
|
||||
"command.max.usage": "max <1-15>"
|
||||
}
|
||||
"command.max.usage": "max <1-15>",
|
||||
"ban.notice.header": "您已被伺服器封禁",
|
||||
"ban.notice.reason": "理由:{0}",
|
||||
"ban.notice.unban": "解封時間:{0}",
|
||||
"ban.notice.unban_permanent": "解封時間:永久",
|
||||
"ban.notice.contact": "如有問題請聯絡管理員"
|
||||
}
|
||||
@@ -27,9 +27,6 @@
|
||||
"nav.extend": "Extend",
|
||||
"nav.system": "System",
|
||||
"nav.player_logs": "Player Logs",
|
||||
"nav.statistics": "Statistics",
|
||||
"nav.chat_filter": "Chat Filter",
|
||||
"nav.discord": "Discord",
|
||||
"nav.hplp": "HPLP",
|
||||
"nav.reload_lang": "Reload Lang",
|
||||
|
||||
@@ -85,6 +82,14 @@
|
||||
"ban.banned_fcs": "Banned Friend Codes",
|
||||
"ban.none": "None",
|
||||
"ban.unban": "Unban",
|
||||
"ban.duration": "Duration",
|
||||
"ban.unit_hours": "Hours (1-24)",
|
||||
"ban.unit_days": "Days (1-30)",
|
||||
"ban.unit_months": "Months (1-12)",
|
||||
"ban.unit_years": "Years (1-10)",
|
||||
"ban.unit_permanent": "Permanent",
|
||||
"ban.permanent": "Permanent",
|
||||
"ban.until": "Until",
|
||||
|
||||
"message.title": "Message to Game",
|
||||
"message.code_placeholder": "ABCDEF",
|
||||
@@ -185,48 +190,6 @@
|
||||
"table.player": "Player",
|
||||
"table.detail": "Detail",
|
||||
|
||||
"stats.title": "Player Statistics",
|
||||
"stats.refresh": "Refresh",
|
||||
"stats.reset": "Reset All",
|
||||
"stats.rank": "#",
|
||||
"stats.games": "Games",
|
||||
"stats.wins": "Wins",
|
||||
"stats.losses": "Losses",
|
||||
"stats.impostor_wins": "Imp. Wins",
|
||||
"stats.kills": "Kills",
|
||||
"stats.deaths": "Deaths",
|
||||
"stats.tasks": "Tasks",
|
||||
"stats.exiled": "Exiled",
|
||||
"stats.empty": "Statistics not enabled in config.",
|
||||
"stats.no_players": "No player stats recorded yet.",
|
||||
"stats.confirm_reset": "Reset all player statistics? This cannot be undone.",
|
||||
|
||||
"chatfilter.title": "Chat Filter",
|
||||
"chatfilter.enabled": "Enable Filtering",
|
||||
"chatfilter.block_message": "Block messages (uncheck = log only)",
|
||||
"chatfilter.words_title": "Blocked Words",
|
||||
"chatfilter.words_placeholder": "Add a word...",
|
||||
"chatfilter.add_word": "Add",
|
||||
"chatfilter.no_words": "No blocked words added.",
|
||||
"chatfilter.spam_title": "Spam Protection",
|
||||
"chatfilter.threshold": "Message threshold",
|
||||
"chatfilter.window": "Window (seconds)",
|
||||
"chatfilter.save": "Save Settings",
|
||||
"chatfilter.saved": "Settings saved.",
|
||||
"chatfilter.word_added": "Word added.",
|
||||
"chatfilter.word_removed": "Word removed.",
|
||||
|
||||
"discord.title": "Discord Webhook",
|
||||
"discord.save": "Save Settings",
|
||||
"discord.matchmaker_url": "Matchmaker Webhook URL",
|
||||
"discord.matchmaker_url_placeholder": "https://discord.com/api/webhooks/...",
|
||||
"discord.matchmaker_desc": "Game Created, Game Started, Game Ended, Game Destroyed",
|
||||
"discord.admin_url": "Admin Webhook URL",
|
||||
"discord.admin_url_placeholder": "https://discord.com/api/webhooks/...",
|
||||
"discord.admin_desc": "Player Banned, Player Reported",
|
||||
"discord.hint": "Leave a URL empty to disable its notifications. Leave both empty to disable all webhooks.",
|
||||
"discord.saved": "Discord webhook settings saved.",
|
||||
|
||||
"hplp.title": "HPLP (Public Lobby List)",
|
||||
"hplp.save": "Save Settings",
|
||||
"hplp.enabled": "Enable HPLP endpoint (GET /x-api/games)",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api.Events;
|
||||
using Empostor.Api.Events.Client;
|
||||
using Empostor.Api.Languages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Service.Admin.Ban;
|
||||
@@ -9,11 +11,13 @@ public sealed class BanEnforcementListener : IEventListener
|
||||
{
|
||||
private readonly ILogger<BanEnforcementListener> _logger;
|
||||
private readonly BanStore _bans;
|
||||
private readonly LanguageService _language;
|
||||
|
||||
public BanEnforcementListener(ILogger<BanEnforcementListener> logger, BanStore bans)
|
||||
public BanEnforcementListener(ILogger<BanEnforcementListener> logger, BanStore bans, LanguageService language)
|
||||
{
|
||||
_logger = logger;
|
||||
_bans = bans;
|
||||
_language = language;
|
||||
}
|
||||
|
||||
[EventListener]
|
||||
@@ -23,18 +27,34 @@ public sealed class BanEnforcementListener : IEventListener
|
||||
var ip = client.Connection?.EndPoint?.Address;
|
||||
|
||||
// IP 封禁检查
|
||||
if (ip != null && _bans.IsIpBanned(ip))
|
||||
if (ip != null && _bans.GetIpBan(ip) is { } ipBan)
|
||||
{
|
||||
_logger.LogWarning("BanRejecting banned IP {Ip} ({Name})", ip, client.Name);
|
||||
await client.DisconnectAsync(DisconnectReason.Banned, "You are banned from this server.");
|
||||
await client.DisconnectAsync(DisconnectReason.Custom, BuildMessage(client.Language, ipBan.Reason, ipBan.BannedUntil));
|
||||
return;
|
||||
}
|
||||
|
||||
// FriendCode 封禁检查
|
||||
if (_bans.IsFriendCodeBanned(client.FriendCode))
|
||||
if (_bans.GetFriendCodeBan(client.FriendCode) is { } fcBan)
|
||||
{
|
||||
_logger.LogWarning("BanRejecting banned FriendCode {FC} ({Name})", client.FriendCode, client.Name);
|
||||
await client.DisconnectAsync(DisconnectReason.Banned, "You are banned from this server.");
|
||||
await client.DisconnectAsync(DisconnectReason.Custom, BuildMessage(client.Language, fcBan.Reason, fcBan.BannedUntil));
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildMessage(Language language, string reason, DateTime? bannedUntil)
|
||||
{
|
||||
var header = _language.Get("ban.notice.header", language).Get();
|
||||
var reasonLine = _language.Get("ban.notice.reason", language)
|
||||
.Format(string.IsNullOrWhiteSpace(reason) ? "-" : reason).Get();
|
||||
var unbanLine = bannedUntil.HasValue
|
||||
? _language.Get("ban.notice.unban", language).Format(FormatExpiry(bannedUntil.Value)).Get()
|
||||
: _language.Get("ban.notice.unban_permanent", language).Get();
|
||||
var contactLine = _language.Get("ban.notice.contact", language).Get();
|
||||
|
||||
return $"{header}\n{reasonLine}\n{unbanLine}\n{contactLine}";
|
||||
}
|
||||
|
||||
private static string FormatExpiry(DateTime utc)
|
||||
=> utc.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Empostor.Api.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -20,14 +21,49 @@ namespace Empostor.Server.Service.Admin.Ban
|
||||
Load();
|
||||
}
|
||||
|
||||
public bool IsIpBanned(IPAddress ip) => _ips.ContainsKey(Normalize(ip));
|
||||
public bool IsIpBanned(IPAddress ip) => GetIpBan(ip) != null;
|
||||
|
||||
public bool IsFriendCodeBanned(string? fc) => fc != null && _friendCodes.ContainsKey(fc);
|
||||
public bool IsFriendCodeBanned(string? fc) => GetFriendCodeBan(fc) != null;
|
||||
|
||||
public BanEntry BanIp(IPAddress ip, string reason)
|
||||
public BanEntry? GetIpBan(IPAddress ip)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
var entry = new BanEntry { Value = key, Reason = reason, BannedAt = DateTime.UtcNow };
|
||||
if (!_ips.TryGetValue(key, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_ips.TryRemove(key, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry? GetFriendCodeBan(string? fc)
|
||||
{
|
||||
if (fc == null || !_friendCodes.TryGetValue(fc, out var entry))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.IsExpired)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
_friendCodes.TryRemove(fc, out _);
|
||||
SaveFireAndForget();
|
||||
return null;
|
||||
}
|
||||
|
||||
public BanEntry BanIp(IPAddress ip, string reason, DateTime? bannedUntil = null)
|
||||
{
|
||||
var key = Normalize(ip);
|
||||
var entry = new BanEntry { Value = key, Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = bannedUntil };
|
||||
_ips[key] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
@@ -44,9 +80,9 @@ namespace Empostor.Server.Service.Admin.Ban
|
||||
return r;
|
||||
}
|
||||
|
||||
public BanEntry BanFriendCode(string fc, string reason)
|
||||
public BanEntry BanFriendCode(string fc, string reason, DateTime? bannedUntil = null)
|
||||
{
|
||||
var entry = new BanEntry { Value = fc, Reason = reason, BannedAt = DateTime.UtcNow };
|
||||
var entry = new BanEntry { Value = fc, Reason = reason, BannedAt = DateTime.UtcNow, BannedUntil = bannedUntil };
|
||||
_friendCodes[fc] = entry;
|
||||
SaveFireAndForget();
|
||||
return entry;
|
||||
@@ -64,18 +100,85 @@ namespace Empostor.Server.Service.Admin.Ban
|
||||
}
|
||||
|
||||
public IReadOnlyList<BanEntry> AllIpBans()
|
||||
=> _ips.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
{
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return _ips.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
}
|
||||
|
||||
public IReadOnlyList<BanEntry> AllFriendCodeBans()
|
||||
=> _friendCodes.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
|
||||
public (int IpCount, int FcCount) Stats() => (_ips.Count, _friendCodes.Count);
|
||||
|
||||
protected override BanData GetSnapshot() => new()
|
||||
{
|
||||
Ips = new(_ips),
|
||||
FriendCodes = new(_friendCodes),
|
||||
};
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return _friendCodes.Values.OrderByDescending(b => b.BannedAt).ToList();
|
||||
}
|
||||
|
||||
public (int IpCount, int FcCount) Stats()
|
||||
{
|
||||
if (PruneExpired())
|
||||
{
|
||||
SaveFireAndForget();
|
||||
}
|
||||
|
||||
return (_ips.Count, _friendCodes.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ban duration string into a UTC expiry timestamp.
|
||||
/// Supported formats: "permanent"/"forever" (or empty) for a permanent ban,
|
||||
/// or "<number><unit>" where unit is h (hours), d (days), mo (months) or y (years).
|
||||
/// Returns null for a permanent ban.
|
||||
/// </summary>
|
||||
public static DateTime? ParseDuration(string? duration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(duration))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = duration.Trim();
|
||||
if (value.Equals("permanent", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("forever", StringComparison.OrdinalIgnoreCase)
|
||||
|| value.Equals("perm", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = Regex.Match(value, @"^(\d+)(h|d|mo|y)$", RegexOptions.IgnoreCase);
|
||||
if (!match.Success)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var amount = int.Parse(match.Groups[1].Value);
|
||||
var unit = match.Groups[2].Value.ToLowerInvariant();
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return unit switch
|
||||
{
|
||||
"h" => now.AddHours(amount),
|
||||
"d" => now.AddDays(amount),
|
||||
"mo" => now.AddMonths(amount),
|
||||
"y" => now.AddYears(amount),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
protected override BanData GetSnapshot()
|
||||
{
|
||||
PruneExpired();
|
||||
return new()
|
||||
{
|
||||
Ips = new(_ips),
|
||||
FriendCodes = new(_friendCodes),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void ApplySnapshot(BanData data)
|
||||
{
|
||||
@@ -83,6 +186,28 @@ namespace Empostor.Server.Service.Admin.Ban
|
||||
_friendCodes = new(data.FriendCodes ?? new());
|
||||
}
|
||||
|
||||
private bool PruneExpired()
|
||||
{
|
||||
var changed = false;
|
||||
foreach (var (key, entry) in _ips)
|
||||
{
|
||||
if (entry.IsExpired && _ips.TryRemove(key, out _))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, entry) in _friendCodes)
|
||||
{
|
||||
if (entry.IsExpired && _friendCodes.TryRemove(key, out _))
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static string Normalize(IPAddress ip)
|
||||
=> ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4().ToString() : ip.ToString();
|
||||
|
||||
@@ -106,5 +231,14 @@ namespace Empostor.Server.Service.Admin.Ban
|
||||
|
||||
[JsonPropertyName("bannedAt")]
|
||||
public required DateTime BannedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("bannedUntil")]
|
||||
public DateTime? BannedUntil { get; init; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsExpired => BannedUntil.HasValue && BannedUntil.Value <= DateTime.UtcNow;
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsPermanent => !BannedUntil.HasValue;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user