forked from lavina/lavina
implement room bans
This commit is contained in:
parent
204126b9fb
commit
e813fb7c69
|
@ -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,
|
//! 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.
|
//! so that they don't overload the room actor.
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ impl PlayerConnection {
|
||||||
.await
|
.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
|
self.player_handle
|
||||||
.join_room(room_id, self.connection_id.clone())
|
.join_room(room_id, self.connection_id.clone())
|
||||||
.await
|
.await
|
||||||
|
@ -96,7 +96,9 @@ impl PlayerConnection {
|
||||||
|
|
||||||
pub async fn get_rooms(&self) -> Result<Vec<RoomInfo>> {
|
pub async fn get_rooms(&self) -> Result<Vec<RoomInfo>> {
|
||||||
let (promise, deferred) = oneshot();
|
let (promise, deferred) = oneshot();
|
||||||
self.player_handle.send(PlayerCommand::GetRooms(promise)).await;
|
self.player_handle
|
||||||
|
.send(PlayerCommand::GetRooms(promise))
|
||||||
|
.await;
|
||||||
Ok(deferred.await?)
|
Ok(deferred.await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +143,7 @@ impl PlayerHandle {
|
||||||
&self,
|
&self,
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
connection_id: ConnectionId,
|
connection_id: ConnectionId,
|
||||||
) -> Result<RoomInfo> {
|
) -> Result<JoinResult> {
|
||||||
let (promise, deferred) = oneshot();
|
let (promise, deferred) = oneshot();
|
||||||
let cmd = Cmd::JoinRoom {
|
let cmd = Cmd::JoinRoom {
|
||||||
room_id,
|
room_id,
|
||||||
|
@ -177,7 +179,7 @@ enum PlayerCommand {
|
||||||
pub enum Cmd {
|
pub enum Cmd {
|
||||||
JoinRoom {
|
JoinRoom {
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
promise: Promise<RoomInfo>,
|
promise: Promise<JoinResult>,
|
||||||
},
|
},
|
||||||
LeaveRoom {
|
LeaveRoom {
|
||||||
room_id: RoomId,
|
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.
|
/// Player update event type which is sent to a player actor and from there to a connection handler.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Updates {
|
pub enum Updates {
|
||||||
|
@ -215,6 +222,8 @@ pub enum Updates {
|
||||||
room_id: RoomId,
|
room_id: RoomId,
|
||||||
former_member_id: PlayerId,
|
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.
|
/// Handle to a player registry — a shared data structure containing information about players.
|
||||||
|
@ -266,6 +275,7 @@ struct Player {
|
||||||
player_id: PlayerId,
|
player_id: PlayerId,
|
||||||
connections: AnonTable<Sender<Updates>>,
|
connections: AnonTable<Sender<Updates>>,
|
||||||
my_rooms: HashMap<RoomId, RoomHandle>,
|
my_rooms: HashMap<RoomId, RoomHandle>,
|
||||||
|
banned_from: HashSet<RoomId>,
|
||||||
rx: Receiver<PlayerCommand>,
|
rx: Receiver<PlayerCommand>,
|
||||||
handle: PlayerHandle,
|
handle: PlayerHandle,
|
||||||
rooms: RoomRegistry,
|
rooms: RoomRegistry,
|
||||||
|
@ -279,6 +289,7 @@ impl Player {
|
||||||
player_id,
|
player_id,
|
||||||
connections: AnonTable::new(),
|
connections: AnonTable::new(),
|
||||||
my_rooms: HashMap::new(),
|
my_rooms: HashMap::new(),
|
||||||
|
banned_from: HashSet::from([RoomId::from_bytes(b"empty".to_vec()).unwrap()]),
|
||||||
rx,
|
rx,
|
||||||
handle,
|
handle,
|
||||||
rooms,
|
rooms,
|
||||||
|
@ -312,6 +323,13 @@ impl Player {
|
||||||
"Player received an update, broadcasting to {} connections",
|
"Player received an update, broadcasting to {} connections",
|
||||||
self.connections.len()
|
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 {
|
for (_, connection) in &self.connections {
|
||||||
connection.send(update.clone()).await;
|
connection.send(update.clone()).await;
|
||||||
}
|
}
|
||||||
|
@ -331,12 +349,17 @@ impl Player {
|
||||||
async fn handle_cmd(&mut self, cmd: Cmd, connection_id: ConnectionId) {
|
async fn handle_cmd(&mut self, cmd: Cmd, connection_id: ConnectionId) {
|
||||||
match cmd {
|
match cmd {
|
||||||
Cmd::JoinRoom { room_id, promise } => {
|
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());
|
let room = self.rooms.get_or_create_room(room_id.clone());
|
||||||
room.subscribe(self.player_id.clone(), self.handle.clone())
|
room.subscribe(self.player_id.clone(), self.handle.clone())
|
||||||
.await;
|
.await;
|
||||||
self.my_rooms.insert(room_id.clone(), room.clone());
|
self.my_rooms.insert(room_id.clone(), room.clone());
|
||||||
let room_info = room.get_room_info().await;
|
let room_info = room.get_room_info().await;
|
||||||
promise.send(room_info);
|
promise.send(JoinResult::Success(room_info));
|
||||||
let update = Updates::RoomJoined {
|
let update = Updates::RoomJoined {
|
||||||
room_id,
|
room_id,
|
||||||
new_member_id: self.player_id.clone(),
|
new_member_id: self.player_id.clone(),
|
||||||
|
|
|
@ -7,7 +7,7 @@ use tokio::net::tcp::{ReadHalf, WriteHalf};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::oneshot::channel;
|
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::core::room::{RoomId, RoomInfo, RoomRegistry};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::protos::irc::client::{client_message, ClientMessage};
|
use crate::protos::irc::client::{client_message, ClientMessage};
|
||||||
|
@ -320,6 +320,17 @@ async fn handle_update(
|
||||||
.await?;
|
.await?;
|
||||||
writer.flush().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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -453,7 +464,10 @@ async fn handle_incoming_message(
|
||||||
ServerMessage {
|
ServerMessage {
|
||||||
tags: vec![],
|
tags: vec![],
|
||||||
sender: Some(config.server_name.as_bytes().to_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)
|
.write_async(writer)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -461,12 +475,12 @@ async fn handle_incoming_message(
|
||||||
} else {
|
} else {
|
||||||
// TODO send 502 (not 401) if the user is not the sender
|
// TODO send 502 (not 401) if the user is not the sender
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Recipient::Chan(_) => {
|
Recipient::Chan(_) => {
|
||||||
// TODO handle chan mode handling
|
// TODO handle chan mode handling
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
cmd => {
|
cmd => {
|
||||||
log::warn!("Not implemented handler for client command: {cmd:?}");
|
log::warn!("Not implemented handler for client command: {cmd:?}");
|
||||||
}
|
}
|
||||||
|
@ -478,7 +492,11 @@ async fn handle_incoming_message(
|
||||||
Ok(())
|
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
|
// Username is equal to nickname
|
||||||
let mut username = Vec::with_capacity(target_user_nickname.len() + 1);
|
let mut username = Vec::with_capacity(target_user_nickname.len() + 1);
|
||||||
username.push(b'~');
|
username.push(b'~');
|
||||||
|
@ -511,8 +529,21 @@ async fn handle_join(
|
||||||
match chan {
|
match chan {
|
||||||
Chan::Global(chan_name) => {
|
Chan::Global(chan_name) => {
|
||||||
let room_id = RoomId::from_bytes(chan_name.clone())?;
|
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?;
|
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?;
|
writer.flush().await?;
|
||||||
}
|
}
|
||||||
Chan::Local(_) => {}
|
Chan::Local(_) => {}
|
||||||
|
|
|
@ -143,7 +143,7 @@ fn client_message_mode(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
||||||
let (input, _) = tag("MODE ")(input)?;
|
let (input, _) = tag("MODE ")(input)?;
|
||||||
let (input, target) = recipient(input)?;
|
let (input, target) = recipient(input)?;
|
||||||
|
|
||||||
Ok((input, ClientMessage::Mode { target } ))
|
Ok((input, ClientMessage::Mode { target }))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_message_who(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
fn client_message_who(input: &[u8]) -> IResult<&[u8], ClientMessage> {
|
||||||
|
|
|
@ -124,6 +124,11 @@ pub enum ServerMessageBody {
|
||||||
client: ByteVec,
|
client: ByteVec,
|
||||||
chan: Chan,
|
chan: Chan,
|
||||||
},
|
},
|
||||||
|
N474BannedFromChan {
|
||||||
|
client: ByteVec,
|
||||||
|
chan: Chan,
|
||||||
|
message: ByteVec,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerMessageBody {
|
impl ServerMessageBody {
|
||||||
|
@ -192,9 +197,7 @@ impl ServerMessageBody {
|
||||||
writer.write_all(&hostname).await?;
|
writer.write_all(&hostname).await?;
|
||||||
writer.write_all(b" ").await?;
|
writer.write_all(b" ").await?;
|
||||||
writer.write_all(&softname).await?;
|
writer.write_all(&softname).await?;
|
||||||
writer
|
writer.write_all(b" r CFILPQbcefgijklmnopqrstvz").await?;
|
||||||
.write_all(b" r CFILPQbcefgijklmnopqrstvz")
|
|
||||||
.await?;
|
|
||||||
// TODO remove hardcoded modes
|
// TODO remove hardcoded modes
|
||||||
}
|
}
|
||||||
ServerMessageBody::N005ISupport { client, params } => {
|
ServerMessageBody::N005ISupport { client, params } => {
|
||||||
|
@ -209,7 +212,7 @@ impl ServerMessageBody {
|
||||||
writer.write_all(&client).await?;
|
writer.write_all(&client).await?;
|
||||||
writer.write_all(b" ").await?;
|
writer.write_all(b" ").await?;
|
||||||
writer.write_all(&modes).await?;
|
writer.write_all(&modes).await?;
|
||||||
},
|
}
|
||||||
ServerMessageBody::N315EndOfWho { client, mask, msg } => {
|
ServerMessageBody::N315EndOfWho { client, mask, msg } => {
|
||||||
writer.write_all(b"315 ").await?;
|
writer.write_all(b"315 ").await?;
|
||||||
writer.write_all(&client).await?;
|
writer.write_all(&client).await?;
|
||||||
|
@ -279,6 +282,18 @@ impl ServerMessageBody {
|
||||||
chan.write_async(writer).await?;
|
chan.write_async(writer).await?;
|
||||||
writer.write_all(b" :End of /NAMES list").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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue