implement room bans

This commit is contained in:
Nikita Vilunov 2023-02-16 22:49:17 +01:00
parent 204126b9fb
commit e813fb7c69
4 changed files with 88 additions and 19 deletions

View File

@ -8,7 +8,7 @@
//! A player actor is a serial handler of commands from a single player. It is preferable to run all per-player validations in the player actor,
//! so that they don't overload the room actor.
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
sync::{Arc, RwLock},
};
@ -58,7 +58,7 @@ impl PlayerConnection {
.await
}
pub async fn join_room(&mut self, room_id: RoomId) -> Result<RoomInfo> {
pub async fn join_room(&mut self, room_id: RoomId) -> Result<JoinResult> {
self.player_handle
.join_room(room_id, self.connection_id.clone())
.await
@ -96,7 +96,9 @@ impl PlayerConnection {
pub async fn get_rooms(&self) -> Result<Vec<RoomInfo>> {
let (promise, deferred) = oneshot();
self.player_handle.send(PlayerCommand::GetRooms(promise)).await;
self.player_handle
.send(PlayerCommand::GetRooms(promise))
.await;
Ok(deferred.await?)
}
}
@ -141,7 +143,7 @@ impl PlayerHandle {
&self,
room_id: RoomId,
connection_id: ConnectionId,
) -> Result<RoomInfo> {
) -> Result<JoinResult> {
let (promise, deferred) = oneshot();
let cmd = Cmd::JoinRoom {
room_id,
@ -177,7 +179,7 @@ enum PlayerCommand {
pub enum Cmd {
JoinRoom {
room_id: RoomId,
promise: Promise<RoomInfo>,
promise: Promise<JoinResult>,
},
LeaveRoom {
room_id: RoomId,
@ -195,6 +197,11 @@ pub enum Cmd {
},
}
pub enum JoinResult {
Success(RoomInfo),
Banned,
}
/// Player update event type which is sent to a player actor and from there to a connection handler.
#[derive(Clone)]
pub enum Updates {
@ -215,6 +222,8 @@ pub enum Updates {
room_id: RoomId,
former_member_id: PlayerId,
},
/// The player was banned from the room and left it immediately.
BannedFrom(RoomId),
}
/// Handle to a player registry — a shared data structure containing information about players.
@ -266,6 +275,7 @@ struct Player {
player_id: PlayerId,
connections: AnonTable<Sender<Updates>>,
my_rooms: HashMap<RoomId, RoomHandle>,
banned_from: HashSet<RoomId>,
rx: Receiver<PlayerCommand>,
handle: PlayerHandle,
rooms: RoomRegistry,
@ -279,6 +289,7 @@ impl Player {
player_id,
connections: AnonTable::new(),
my_rooms: HashMap::new(),
banned_from: HashSet::from([RoomId::from_bytes(b"empty".to_vec()).unwrap()]),
rx,
handle,
rooms,
@ -312,6 +323,13 @@ impl Player {
"Player received an update, broadcasting to {} connections",
self.connections.len()
);
match update {
Updates::BannedFrom(ref room_id) => {
self.banned_from.insert(room_id.clone());
self.my_rooms.remove(room_id);
}
_ => {}
}
for (_, connection) in &self.connections {
connection.send(update.clone()).await;
}
@ -331,12 +349,17 @@ impl Player {
async fn handle_cmd(&mut self, cmd: Cmd, connection_id: ConnectionId) {
match cmd {
Cmd::JoinRoom { room_id, promise } => {
if self.banned_from.contains(&room_id) {
promise.send(JoinResult::Banned);
return;
}
let room = self.rooms.get_or_create_room(room_id.clone());
room.subscribe(self.player_id.clone(), self.handle.clone())
.await;
self.my_rooms.insert(room_id.clone(), room.clone());
let room_info = room.get_room_info().await;
promise.send(room_info);
promise.send(JoinResult::Success(room_info));
let update = Updates::RoomJoined {
room_id,
new_member_id: self.player_id.clone(),

View File

@ -7,7 +7,7 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::oneshot::channel;
use crate::core::player::{PlayerConnection, PlayerId, PlayerRegistry, Updates};
use crate::core::player::*;
use crate::core::room::{RoomId, RoomInfo, RoomRegistry};
use crate::prelude::*;
use crate::protos::irc::client::{client_message, ClientMessage};
@ -320,6 +320,17 @@ async fn handle_update(
.await?;
writer.flush().await?
}
Updates::BannedFrom(room_id) => {
// TODO think about the case when the user was banned, but was not in the room - no need to send PART in this case
ServerMessage {
tags: vec![],
sender: Some(player_id.as_bytes().clone()),
body: ServerMessageBody::Part(Chan::Global(room_id.as_bytes().clone())),
}
.write_async(writer)
.await?;
writer.flush().await?
}
}
Ok(())
}
@ -453,7 +464,10 @@ async fn handle_incoming_message(
ServerMessage {
tags: vec![],
sender: Some(config.server_name.as_bytes().to_vec()),
body: ServerMessageBody::N221UserModeIs { client: user.nickname.clone(), modes: b"+r".to_vec() },
body: ServerMessageBody::N221UserModeIs {
client: user.nickname.clone(),
modes: b"+r".to_vec(),
},
}
.write_async(writer)
.await?;
@ -461,12 +475,12 @@ async fn handle_incoming_message(
} else {
// TODO send 502 (not 401) if the user is not the sender
}
},
}
Recipient::Chan(_) => {
// TODO handle chan mode handling
},
}
},
}
}
cmd => {
log::warn!("Not implemented handler for client command: {cmd:?}");
}
@ -478,7 +492,11 @@ async fn handle_incoming_message(
Ok(())
}
fn user_to_who_msg(config: &ServerConfig, requestor: &RegisteredUser, target_user_nickname: &ByteVec) -> ServerMessageBody {
fn user_to_who_msg(
config: &ServerConfig,
requestor: &RegisteredUser,
target_user_nickname: &ByteVec,
) -> ServerMessageBody {
// Username is equal to nickname
let mut username = Vec::with_capacity(target_user_nickname.len() + 1);
username.push(b'~');
@ -511,8 +529,21 @@ async fn handle_join(
match chan {
Chan::Global(chan_name) => {
let room_id = RoomId::from_bytes(chan_name.clone())?;
let room_info = user_handle.join_room(room_id).await?;
if let JoinResult::Success(room_info) = user_handle.join_room(room_id).await? {
produce_on_join_cmd_messages(&config, &user, chan, &room_info, writer).await?;
} else {
ServerMessage {
tags: vec![],
sender: Some(config.server_name.as_bytes().to_vec()),
body: ServerMessageBody::N474BannedFromChan {
client: user.nickname.clone(),
chan: chan.clone(),
message: b"U dun goofed".to_vec(),
},
}
.write_async(writer)
.await?;
}
writer.flush().await?;
}
Chan::Local(_) => {}

View File

@ -124,6 +124,11 @@ pub enum ServerMessageBody {
client: ByteVec,
chan: Chan,
},
N474BannedFromChan {
client: ByteVec,
chan: Chan,
message: ByteVec,
},
}
impl ServerMessageBody {
@ -192,9 +197,7 @@ impl ServerMessageBody {
writer.write_all(&hostname).await?;
writer.write_all(b" ").await?;
writer.write_all(&softname).await?;
writer
.write_all(b" r CFILPQbcefgijklmnopqrstvz")
.await?;
writer.write_all(b" r CFILPQbcefgijklmnopqrstvz").await?;
// TODO remove hardcoded modes
}
ServerMessageBody::N005ISupport { client, params } => {
@ -209,7 +212,7 @@ impl ServerMessageBody {
writer.write_all(&client).await?;
writer.write_all(b" ").await?;
writer.write_all(&modes).await?;
},
}
ServerMessageBody::N315EndOfWho { client, mask, msg } => {
writer.write_all(b"315 ").await?;
writer.write_all(&client).await?;
@ -279,6 +282,18 @@ impl ServerMessageBody {
chan.write_async(writer).await?;
writer.write_all(b" :End of /NAMES list").await?;
}
ServerMessageBody::N474BannedFromChan {
client,
chan,
message,
} => {
writer.write_all(b"474 ").await?;
writer.write_all(&client).await?;
writer.write_all(b" ").await?;
chan.write_async(writer).await?;
writer.write_all(b" :").await?;
writer.write_all(&message).await?;
}
}
Ok(())
}