From d202700c3593efba4f32f5cae49edc356e1debc0 Mon Sep 17 00:00:00 2001
From: Louyijun <35680570+Louyijun@users.noreply.github.com>
Date: Mon, 11 May 2026 10:12:32 +0800
Subject: [PATCH] Replace ban notice text with structured banned message
---
server/exceptions.py | 12 ++++++++++++
server/lobbyconnection.py | 6 +-----
tests/integration_tests/test_login.py | 24 ++++++------------------
tests/integration_tests/test_server.py | 12 +++---------
tests/unit_tests/test_lobbyconnection.py | 17 ++++++-----------
5 files changed, 28 insertions(+), 43 deletions(-)
diff --git a/server/exceptions.py b/server/exceptions.py
index ff7a7cc57..3a7f0aa1e 100644
--- a/server/exceptions.py
+++ b/server/exceptions.py
@@ -2,6 +2,8 @@
Common exception definitions
"""
+from datetime import timezone
+
import humanize
from server.timing import datetime_now
@@ -39,6 +41,16 @@ def message(self):
"moderation@faforever.com"
)
+ def to_dict(self):
+ expires_at = self.ban_expiry
+ if expires_at.tzinfo is None:
+ expires_at = expires_at.replace(tzinfo=timezone.utc)
+ return {
+ "command": "banned",
+ "expires_at": expires_at.astimezone(timezone.utc).isoformat(),
+ "reason": self.ban_reason,
+ }
+
def _ban_duration_text(self):
ban_duration = self.ban_expiry - datetime_now()
if ban_duration.days > 365 * 100:
diff --git a/server/lobbyconnection.py b/server/lobbyconnection.py
index 5b5ec2e10..c1173c7c2 100644
--- a/server/lobbyconnection.py
+++ b/server/lobbyconnection.py
@@ -222,11 +222,7 @@ async def on_message_received(self, message):
"text": e.message
})
except BanError as e:
- await self.send({
- "command": "notice",
- "style": "error",
- "text": e.message()
- })
+ await self.send(e.to_dict())
await self.abort(e.message())
except ClientError as e:
self._logger.warning(
diff --git a/tests/integration_tests/test_login.py b/tests/integration_tests/test_login.py
index 0d7c5c843..2bf4cc942 100644
--- a/tests/integration_tests/test_login.py
+++ b/tests/integration_tests/test_login.py
@@ -38,15 +38,9 @@ async def test_server_ban(lobby_server, user):
proto = await connect_client(lobby_server)
await perform_login(proto, user)
msg = await proto.read_message()
- assert msg == {
- "command": "notice",
- "style": "error",
- "text": (
- "You are banned from FAF forever.
Reason:
Test permanent ban"
- "
If you would like to appeal this ban, please send an "
- "email to: moderation@faforever.com"
- )
- }
+ assert msg["command"] == "banned"
+ assert msg["reason"] == "Test permanent ban"
+ assert "expires_at" in msg
@pytest.mark.parametrize("user", [
@@ -73,15 +67,9 @@ async def test_server_ban_token(lobby_server, user, jwk_priv_key, jwk_kid):
"unique_id": "some_id"
})
msg = await proto.read_message()
- assert msg == {
- "command": "notice",
- "style": "error",
- "text": (
- "You are banned from FAF forever.
Reason:
Test permanent ban"
- "
If you would like to appeal this ban, please send an "
- "email to: moderation@faforever.com"
- )
- }
+ assert msg["command"] == "banned"
+ assert msg["reason"] == "Test permanent ban"
+ assert "expires_at" in msg
@pytest.mark.parametrize("user", ["ban_revoked", "ban_expired"])
diff --git a/tests/integration_tests/test_server.py b/tests/integration_tests/test_server.py
index ae00663bb..5f595e633 100644
--- a/tests/integration_tests/test_server.py
+++ b/tests/integration_tests/test_server.py
@@ -845,15 +845,9 @@ async def test_server_ban_prevents_hosting(lobby_server, database, command):
await proto.send_message({"command": command})
msg = await proto.read_message()
- assert msg == {
- "command": "notice",
- "style": "error",
- "text": (
- "You are banned from FAF forever.
Reason:
Test live ban
"
- "
If you would like to appeal this ban, please send an email "
- "to: moderation@faforever.com"
- )
- }
+ assert msg["command"] == "banned"
+ assert msg["reason"] == "Test live ban"
+ assert "expires_at" in msg
@fast_forward(5)
diff --git a/tests/unit_tests/test_lobbyconnection.py b/tests/unit_tests/test_lobbyconnection.py
index ea764e84d..2e4cbf33c 100644
--- a/tests/unit_tests/test_lobbyconnection.py
+++ b/tests/unit_tests/test_lobbyconnection.py
@@ -1,4 +1,3 @@
-import re
from hashlib import sha256
from unittest import mock
@@ -1303,18 +1302,14 @@ async def test_abort_connection_if_banned(
lobbyconnection.player.id = 203
with pytest.raises(BanError) as banned_error:
await lobbyconnection.abort_connection_if_banned()
- assert banned_error.value.message() == (
- "You are banned from FAF forever.
Reason:
Test permanent ban"
- "
If you would like to appeal this ban, please send an email "
- "to: moderation@faforever.com"
- )
+ assert banned_error.value.to_dict()["command"] == "banned"
+ assert banned_error.value.to_dict()["reason"] == "Test permanent ban"
+ assert "expires_at" in banned_error.value.to_dict()
# test user who is banned for another 46 hours
lobbyconnection.player.id = 204
with pytest.raises(BanError) as banned_error:
await lobbyconnection.abort_connection_if_banned()
- assert re.match(
- r"You are banned from FAF for 1 day and 2[12]\.[0-9]+ hours.
"
- "Reason:
Test ongoing ban with 46 hours left",
- banned_error.value.message()
- )
+ assert banned_error.value.to_dict()["command"] == "banned"
+ assert banned_error.value.to_dict()["reason"] == "Test ongoing ban with 46 hours left"
+ assert "expires_at" in banned_error.value.to_dict()