Merge branch 'main' into geoip

This commit is contained in:
咕谷酱
2025-08-18 00:49:00 +08:00
committed by GitHub
7 changed files with 143 additions and 138 deletions

5
.gitignore vendored
View File

@@ -52,7 +52,7 @@ coverage.xml
.pytest_cache/
cover/
# 测试用的证书
test-cert/
test-cert/
# Translations
*.mo
@@ -217,4 +217,5 @@ replays/
osu-master/*
geoip/*
newrelic.ini
newrelic.ini
logs/

View File

@@ -127,6 +127,16 @@ logger.add(
level=settings.log_level,
diagnose=settings.debug,
)
logger.add(
"logs/{time:YYYY-MM-DD}.log",
rotation="00:00",
retention="30 days",
colorize=False,
format="{time:YYYY-MM-DD HH:mm:ss} {level} | {message}",
level=settings.log_level,
diagnose=settings.debug,
encoding="utf8",
)
logging.basicConfig(handlers=[InterceptHandler()], level=settings.log_level, force=True)
uvicorn_loggers = [
@@ -140,3 +150,5 @@ for logger_name in uvicorn_loggers:
uvicorn_logger = logging.getLogger(logger_name)
uvicorn_logger.handlers = [InterceptHandler()]
uvicorn_logger.propagate = False
logging.getLogger("httpx").setLevel("WARNING")

View File

@@ -167,7 +167,8 @@ async def mark_as_read(
if db_channel is None:
raise HTTPException(status_code=404, detail="Channel not found")
assert db_channel.channel_id
await server.mark_as_read(db_channel.channel_id, message)
assert current_user.id
await server.mark_as_read(db_channel.channel_id, current_user.id, message)
class PMReq(BaseModel):

View File

@@ -69,8 +69,8 @@ class ChatServer:
if client:
await self.send_event(client, event)
async def mark_as_read(self, channel_id: int, message_id: int):
await self.redis.set(f"chat:{channel_id}:last_msg", message_id)
async def mark_as_read(self, channel_id: int, user_id: int, message_id: int):
await self.redis.set(f"chat:{channel_id}:last_read:{user_id}", message_id)
async def send_message_to_channel(
self, message: ChatMessageResp, is_bot_command: bool = False
@@ -91,7 +91,10 @@ class ChatServer:
)
)
assert message.message_id
await self.mark_as_read(message.channel_id, message.message_id)
await self.mark_as_read(
message.channel_id, message.sender_id, message.message_id
)
await self.redis.set(f"chat:{message.channel_id}:last_msg", message.message_id)
async def batch_join_channel(
self, users: list[User], channel: ChatChannel, session: AsyncSession

View File

@@ -273,12 +273,16 @@ class Hub[TState: UserState]:
result = await self.invoke_method(client, packet.target, args)
except InvokeException as e:
error = e.message
except Exception as e:
logger.debug(
f"Client {client.connection_token} call {packet.target}"
f" failed: {error}"
)
except Exception:
logger.exception(
f"Error invoking method {packet.target} for "
f"client {client.connection_id}"
)
error = str(e)
error = "Unknown error occured in server"
if packet.invocation_id is not None:
await client.send_packet(
CompletionPacket(

View File

@@ -336,12 +336,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
async def ChangeBeatmapAvailability(
self, client: Client, beatmap_availability: BeatmapAvailability
):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -353,60 +348,74 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
)
async def AddPlaylistItem(self, client: Client, item: PlaylistItem):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
raise InvokeException("You are not in this room")
logger.info(
f"[MultiplayerHub] {client.user_id} adding "
f"beatmap {item.beatmap_id} to room {room.room_id}"
)
await server_room.queue.add_item(
item,
user,
)
async def EditPlaylistItem(self, client: Client, item: PlaylistItem):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
raise InvokeException("You are not in this room")
logger.info(
f"[MultiplayerHub] {client.user_id} editing "
f"item {item.id} in room {room.room_id}"
)
await server_room.queue.edit_item(
item,
user,
)
async def RemovePlaylistItem(self, client: Client, item_id: int):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
raise InvokeException("You are not in this room")
logger.info(
f"[MultiplayerHub] {client.user_id} removing "
f"item {item_id} from room {room.room_id}"
)
await server_room.queue.remove_item(
item_id,
user,
)
async def change_db_settings(self, room: ServerMultiplayerRoom):
async with AsyncSession(engine) as session:
await session.execute(
update(Room)
.where(col(Room.id) == room.room.room_id)
.values(
name=room.room.settings.name,
type=room.room.settings.match_type,
queue_mode=room.room.settings.queue_mode,
auto_skip=room.room.settings.auto_skip,
auto_start_duration=int(
room.room.settings.auto_start_duration.total_seconds()
),
host_id=room.room.host.user_id,
)
)
async def setting_changed(self, room: ServerMultiplayerRoom, beatmap_changed: bool):
await self.change_db_settings(room)
await self.validate_styles(room)
await self.unready_all_users(room, beatmap_changed)
await self.broadcast_group_call(
@@ -444,12 +453,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
async def ChangeUserStyle(
self, client: Client, beatmap_id: int | None, ruleset_id: int | None
):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -572,12 +576,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
)
async def ChangeUserMods(self, client: Client, new_mods: list[APIMod]):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -634,7 +633,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
"Cannot ready up while all items have been played."
)
case MultiplayerUserState.WAITING_FOR_LOAD:
raise InvokeException("Cannot change state from {old} to {new}")
raise InvokeException(f"Cannot change state from {old} to {new}")
case MultiplayerUserState.LOADED:
if old != MultiplayerUserState.WAITING_FOR_LOAD:
raise InvokeException(f"Cannot change state from {old} to {new}")
@@ -647,18 +646,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
if old != MultiplayerUserState.PLAYING:
raise InvokeException(f"Cannot change state from {old} to {new}")
case MultiplayerUserState.RESULTS:
raise InvokeException("Cannot change state from {old} to {new}")
raise InvokeException(f"Cannot change state from {old} to {new}")
case MultiplayerUserState.SPECTATING:
if old not in (MultiplayerUserState.IDLE, MultiplayerUserState.READY):
raise InvokeException(f"Cannot change state from {old} to {new}")
async def ChangeState(self, client: Client, state: MultiplayerUserState):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -666,6 +660,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
if user.state == state:
return
logger.info(
f"[MultiplayerHub] {user.user_id}'s state "
f"changed from {user.state} to {state}"
)
match state:
case MultiplayerUserState.IDLE:
if user.state.is_playing:
@@ -684,6 +682,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
or room.state == MultiplayerRoomState.WAITING_FOR_LOAD
):
await self.call_noblock(client, "LoadRequested")
await self.update_room_state(server_room)
async def change_user_state(
@@ -767,6 +766,10 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
async def change_room_state(
self, room: ServerMultiplayerRoom, state: MultiplayerRoomState
):
logger.debug(
f"[MultiplayerHub] Room {room.room.room_id} state "
f"changed from {room.room.state} to {state}"
)
room.room.state = state
await self.broadcast_group_call(
self.group_id(room.room.room_id),
@@ -775,18 +778,12 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
)
async def StartMatch(self, client: Client):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
raise InvokeException("You are not in this room")
if room.host is None or room.host.user_id != client.user_id:
raise InvokeException("You are not the host of this room")
self._ensure_host(client, server_room)
# Check host state - host must be ready or spectating
if room.host.state not in (
@@ -799,6 +796,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
if all(u.state != MultiplayerUserState.READY for u in room.users):
raise InvokeException("Can't start match when no users are ready.")
logger.info(f"[MultiplayerHub] Room {room.room_id} match started")
await self.start_match(server_room)
async def start_match(self, room: ServerMultiplayerRoom):
@@ -894,34 +892,21 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
async def make_user_leave(
self,
client: Client,
client: Client | None,
room: ServerMultiplayerRoom,
user: MultiplayerRoomUser,
kicked: bool = False,
):
self.remove_from_group(client, self.group_id(room.room.room_id))
if client:
self.remove_from_group(client, self.group_id(room.room.room_id))
room.room.users.remove(user)
if len(room.room.users) == 0:
await self.end_room(room)
await self.update_room_state(room)
if (
len(room.room.users) != 0
and room.room.host
and room.room.host.user_id == user.user_id
):
next_host = room.room.users[0]
await self.set_host(room, next_host)
target_store = self.state.get(user.user_id)
if target_store:
target_store.room_id = 0
if kicked:
await self.call_noblock(client, "UserKicked", user)
await self.broadcast_group_call(
self.group_id(room.room.room_id), "UserKicked", user
)
else:
await self.broadcast_group_call(
self.group_id(room.room.room_id), "UserLeft", user
)
redis = get_redis()
await redis.publish("chat:room:left", f"{room.room.channel_id}:{user.user_id}")
async with AsyncSession(engine) as session:
async with session.begin():
@@ -941,12 +926,28 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
raise InvokeException("Room does not exist in database")
db_room.participant_count -= 1
target_store = self.state.get(user.user_id)
if target_store:
target_store.room_id = 0
if len(room.room.users) == 0:
await self.end_room(room)
return
await self.update_room_state(room)
if (
len(room.room.users) != 0
and room.room.host
and room.room.host.user_id == user.user_id
):
next_host = room.room.users[0]
await self.set_host(room, next_host)
redis = get_redis()
await redis.publish("chat:room:left", f"{room.room.channel_id}:{user.user_id}")
if kicked:
if client:
await self.call_noblock(client, "UserKicked", user)
await self.broadcast_group_call(
self.group_id(room.room.room_id), "UserKicked", user
)
else:
await self.broadcast_group_call(
self.group_id(room.room.room_id), "UserLeft", user
)
async def end_room(self, room: ServerMultiplayerRoom):
assert room.room.host
@@ -971,14 +972,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
room.room.host.user_id,
)
del self.rooms[room.room.room_id]
logger.info(f"[MultiplayerHub] Room {room.room.room_id} ended")
async def LeaveRoom(self, client: Client):
store = self.get_or_create_state(client)
if store.room_id == 0:
return
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -989,18 +989,12 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
user.user_id,
)
await self.make_user_leave(client, server_room, user)
logger.info(f"[MultiplayerHub] {client.user_id} left room {room.room_id}")
async def KickUser(self, client: Client, user_id: int):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
if room.host is None or room.host.user_id != client.user_id:
raise InvokeException("You are not the host of this room")
self._ensure_host(client, server_room)
if user_id == client.user_id:
raise InvokeException("Can't kick self")
@@ -1014,12 +1008,15 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
user.user_id,
)
target_client = self.get_client_by_id(str(user.user_id))
if target_client is None:
return
await self.make_user_leave(target_client, server_room, user, kicked=True)
logger.info(
f"[MultiplayerHub] {user.user_id} was kicked from room {room.room_id}"
f"by {client.user_id}"
)
async def set_host(self, room: ServerMultiplayerRoom, user: MultiplayerRoomUser):
room.room.host = user
await self.change_db_settings(room)
await self.broadcast_group_call(
self.group_id(room.room.room_id),
"HostChanged",
@@ -1027,16 +1024,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
)
async def TransferHost(self, client: Client, user_id: int):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
if room.host is None or room.host.user_id != client.user_id:
raise InvokeException("You are not the host of this room")
self._ensure_host(client, server_room)
new_host = next((u for u in room.users if u.user_id == user_id), None)
if new_host is None:
@@ -1046,14 +1036,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
new_host.user_id,
)
await self.set_host(server_room, new_host)
logger.info(
f"[MultiplayerHub] {client.user_id} transferred host to {new_host.user_id}"
f" in room {room.room_id}"
)
async def AbortGameplay(self, client: Client):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -1070,16 +1059,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
await self.update_room_state(server_room)
async def AbortMatch(self, client: Client):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
if room.host is None or room.host.user_id != client.user_id:
raise InvokeException("You are not the host of this room")
self._ensure_host(client, server_room)
if (
room.state != MultiplayerRoomState.PLAYING
@@ -1100,6 +1082,9 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
GameplayAbortReason.HOST_ABORTED,
)
await self.update_room_state(server_room)
logger.info(
f"[MultiplayerHub] {client.user_id} aborted match in room {room.room_id}"
)
async def change_user_match_state(
self, room: ServerMultiplayerRoom, user: MultiplayerRoomUser
@@ -1141,12 +1126,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
await self.update_room_state(server_room)
async def SendMatchRequest(self, client: Client, request: MatchRequest):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:
@@ -1156,7 +1136,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
if room.host and room.host.user_id != user.user_id:
raise InvokeException("You are not the host of this room")
if room.state != MultiplayerRoomState.OPEN:
raise InvokeException("Cannot start a countdown during ongoing play")
raise InvokeException("Cannot start match countdown when not open")
await server_room.start_countdown(
MatchStartCountdown(time_remaining=request.duration),
self.start_match,
@@ -1181,12 +1161,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
await server_room.match_type_handler.handle_request(user, request)
async def InvitePlayer(self, client: Client, user_id: int):
store = self.get_or_create_state(client)
if store.room_id == 0:
raise InvokeException("You are not in a room")
if store.room_id not in self.rooms:
raise InvokeException("Room does not exist")
server_room = self.rooms[store.room_id]
server_room = self._ensure_in_room(client)
room = server_room.room
user = next((u for u in room.users if u.user_id == client.user_id), None)
if user is None:

View File

@@ -6,6 +6,7 @@ import lzma
import struct
import time
from typing import override
from venv import logger
from app.config import settings
from app.database import Beatmap, User
@@ -217,6 +218,7 @@ class SpectatorHub(Hub[StoreClientState]):
maximum_statistics=state.maximum_statistics,
)
)
logger.info(f"[SpectatorHub] {client.user_id} began playing {state.beatmap_id}")
await self.broadcast_group_call(
self.group_id(user_id),
"UserBeganPlaying",
@@ -320,6 +322,10 @@ class SpectatorHub(Hub[StoreClientState]):
async def _end_session(self, user_id: int, state: SpectatorState) -> None:
if state.state == SpectatedUserState.Playing:
state.state = SpectatedUserState.Quit
logger.info(
f"[SpectatorHub] {user_id} finished playing {state.beatmap_id} "
f"with {state.state}"
)
await self.broadcast_group_call(
self.group_id(user_id),
"UserFinishedPlaying",
@@ -330,7 +336,9 @@ class SpectatorHub(Hub[StoreClientState]):
async def StartWatchingUser(self, client: Client, target_id: int) -> None:
user_id = int(client.connection_id)
target_store = self.state.get(target_id)
logger.info(f"[SpectatorHub] {user_id} started watching {target_id}")
if target_store and target_store.state:
logger.debug(f"[SpectatorHub] {target_id} is {target_store.state}")
await self.call_noblock(
client,
"UserBeganPlaying",
@@ -361,3 +369,4 @@ class SpectatorHub(Hub[StoreClientState]):
store.watched_user.discard(target_id)
if (target_client := self.get_client_by_id(str(target_id))) is not None:
await self.call_noblock(target_client, "UserEndedWatching", user_id)
logger.info(f"[SpectatorHub] {user_id} ended watching {target_id}")