diff --git a/src/chat_pinnd.rs b/src/.chat_pinnd.rs similarity index 97% rename from src/chat_pinnd.rs rename to src/.chat_pinnd.rs index 15c224b..154c032 100644 --- a/src/chat_pinnd.rs +++ b/src/.chat_pinnd.rs @@ -15,6 +15,7 @@ pub struct ChatPinnd { pub struct Kaksh { length: Option, + last_message_id: u128, loog: Vec } @@ -95,6 +96,7 @@ impl Handler for ChatPinnd { }); self.kaksh.insert(msg.kaksh_kunjika, Kaksh { length: msg.length, + last_message_id: 0, loog: vec![Loog::new(msg.addr,msg.kunjika,msg.name, None)] }); } @@ -161,6 +163,7 @@ impl Handler for ChatPinnd { let group_kunjika = format!("gupt_{}>{}",msg.kunjika.to_owned(), vayakti_watchlist.kunjika); self.kaksh.insert(group_kunjika.to_owned(), Kaksh { length: Some(2), + last_message_id: 0, loog: vec![Loog::new(msg.addr.clone(), msg.kunjika.to_owned(), msg.name.to_owned(), Some(msg.tags.clone())), Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] }); @@ -266,6 +269,7 @@ impl Handler for ChatPinnd { } self.kaksh.insert(group_kunjika.to_owned(), Kaksh { length: Some(2), + last_message_id: 0, loog: vec![Loog::new(addr.clone(), msg.kunjika.to_owned(), name.to_owned(), Some(tags.clone())), Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] }); @@ -291,12 +295,15 @@ impl Handler for ChatPinnd { type Result = (); fn handle(&mut self, msg: ms::SendText, _: &mut Self::Context) -> Self::Result { - if let Some(kaksh) = self.kaksh.get(&msg.kaksh_kunjika) { + if let Some(kaksh) = self.kaksh.get_mut(&msg.kaksh_kunjika) { + kaksh.last_message_id += 1; + let msg_id = kaksh.last_message_id; kaksh.loog.iter().for_each(|c| { c.addr.do_send(ms::WsText { sender_kunjika: msg.kunjika.to_owned(), text: msg.text.to_owned(), - reply: msg.reply.to_owned() + reply: msg.reply.to_owned(), + msg_id }); }); } diff --git a/src/messages.rs b/src/.messages.rs similarity index 88% rename from src/messages.rs rename to src/.messages.rs index 54834bf..05cf339 100644 --- a/src/messages.rs +++ b/src/.messages.rs @@ -35,7 +35,7 @@ pub struct JoinRandomNext { pub kunjika: String } -/// Request to send text t +/// Request to send text #[derive(Clone, Message)] #[rtype(result = "()")] pub struct SendText { @@ -44,6 +44,15 @@ pub struct SendText { pub text: String, pub reply: Option, } +/// Request to send image t +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct SendImage { + pub kaksh_kunjika: String, + pub kunjika: String, + pub part: String, + pub image_id: i32 +} // Request to send text t #[derive(Clone, Message)] @@ -81,6 +90,15 @@ pub struct WsKunjikaHash { #[derive(Clone, Message)] #[rtype(result = "()")] pub struct WsText { + pub text: String, + pub reply: Option, + pub sender_kunjika: String, + pub msg_id: u128 +} +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsImage { pub text: String, pub reply: Option, pub sender_kunjika: String diff --git a/src/ws_sansad.rs b/src/.ws_sansad.rs similarity index 99% rename from src/ws_sansad.rs rename to src/.ws_sansad.rs index d8bec21..fc06270 100644 --- a/src/ws_sansad.rs +++ b/src/.ws_sansad.rs @@ -76,7 +76,8 @@ impl Handler for WsSansad { "cmd": "text", "text": msg.text, "reply": msg.reply, - "kunjika": msg.sender_kunjika // Sender's kunjuka + "kunjika": msg.sender_kunjika, // Sender's kunjuka + "msg_id": msg.msg_id.to_string() }); ctx.text(json.to_string()); } diff --git a/src/broker_messages/mod.rs b/src/broker_messages/mod.rs new file mode 100644 index 0000000..48d642f --- /dev/null +++ b/src/broker_messages/mod.rs @@ -0,0 +1,7 @@ +use actix::prelude::*; +use dev::{MessageResponse, ResponseChannel}; +use crate::ws_sansad::WsSansad; + +pub mod pind; +pub mod sansad; +pub mod util; \ No newline at end of file diff --git a/src/broker_messages/pind.rs b/src/broker_messages/pind.rs new file mode 100644 index 0000000..7f035c8 --- /dev/null +++ b/src/broker_messages/pind.rs @@ -0,0 +1,78 @@ +//! Messages to be sent between Actors +use super::*; +use super::util::Resp; + +//################################################## For ChatPinnd ################################################## +/// Request to change information of vayakti to list of vayakti im ChatPind + +/// Request to Kaksh with its kunjika +#[derive(Clone, Message)] +#[rtype(result = "Resp")] +pub struct JoinKaksh { + pub kaksh_kunjika: String, + pub length: Option, + pub addr: Addr, + pub kunjika: String, + pub name: String, +} + +/// Request to connect Random vayakti +#[derive(Clone, Message)] +#[rtype(result = "Resp")] +pub struct JoinRandom { + pub addr: Addr, + pub kunjika: String, + pub name: String, + pub tags: Vec, +} +/// Request to connect Random vayakti +#[derive(Clone, Message)] +#[rtype(result = "Resp")] +pub struct JoinRandomNext { + pub kaksh_kunjika: String, + pub kunjika: String +} + +/// Request to send text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct SendText { + pub kaksh_kunjika: String, + pub kunjika: String, + pub text: String, + pub reply: Option, +} +/// Request to send image t +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct SendImage { + pub kaksh_kunjika: String, + pub kunjika: String, + pub part: String, + pub image_id: i32 +} + +// Request to send text t +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct SendStatus { + pub kaksh_kunjika: String, + pub kunjika: String, + pub status: String +} + +// Request to send text t +#[derive(Clone, Message)] +#[rtype(result = "String")] +pub struct List { + pub kaksh_kunjika: String +} + +/// Request to leave kaksh +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct LeaveUser { + pub kaksh_kunjika: Option, + pub kunjika: String, + pub addr: Addr +} \ No newline at end of file diff --git a/src/broker_messages/sansad.rs b/src/broker_messages/sansad.rs new file mode 100644 index 0000000..525ac13 --- /dev/null +++ b/src/broker_messages/sansad.rs @@ -0,0 +1,76 @@ + +use super::*; + + +//################################################## For WsSansad ################################################## +// Request to send own kunjika hash +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsKunjikaHash { + pub kunjika: String +} +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsText { + pub text: String, + pub reply: Option, + pub sender_kunjika: String, + pub msg_id: u128 +} +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsImage { + pub text: String, + pub reply: Option, + pub sender_kunjika: String +} + +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsStatus { + pub status: String, + pub sender_kunjika: String +} + +// Request to send transfer text +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsList { + pub json: String +} + +// Notify Someone connected +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsConnected { + pub name: String, + pub kunjika: String +} + +// Notify someone disconnected +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsDisconnected { + pub kunjika: String, + pub name: String +} + +// Give response message +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsResponse { + pub result: String, + pub message: String +} + +// Got connected to random vayakti +#[derive(Clone, Message)] +#[rtype(result = "()")] +pub struct WsConnectedRandom { + pub name: String, + pub kunjika: String, + pub kaksh_kunjika: String +} diff --git a/src/broker_messages/util.rs b/src/broker_messages/util.rs new file mode 100644 index 0000000..cbfc38b --- /dev/null +++ b/src/broker_messages/util.rs @@ -0,0 +1,21 @@ +use super::*; + +//################################################## Helper ################################################## +#[derive(Debug)] +pub enum Resp { + Ok, + Err(String), + None +} + +impl MessageResponse for Resp +where + A: Actor, + M: Message, +{ + fn handle>(self, _: &mut A::Context, tx: Option) { + if let Some(tx) = tx { + tx.send(self); + } + } +} \ No newline at end of file diff --git a/src/chat_pinnd/message.rs b/src/chat_pinnd/message.rs new file mode 100644 index 0000000..0a913f4 --- /dev/null +++ b/src/chat_pinnd/message.rs @@ -0,0 +1,41 @@ + +use super::*; + +/// send text to everyone +impl Handler for ChatPinnd { + type Result = (); + + fn handle(&mut self, msg: ms::pind::SendText, _: &mut Self::Context) -> Self::Result { + if let Some(kaksh) = self.kaksh.get_mut(&msg.kaksh_kunjika) { + kaksh.last_message_id += 1; + let msg_id = kaksh.last_message_id; + kaksh.loog.iter().for_each(|c| { + c.addr.do_send(ms::sansad::WsText { + sender_kunjika: msg.kunjika.to_owned(), + text: msg.text.to_owned(), + reply: msg.reply.to_owned(), + msg_id + }); + }); + } + } +} + +/// send status to everyone +impl Handler for ChatPinnd { + type Result = (); + + fn handle(&mut self, msg: ms::pind::SendStatus, _: &mut Self::Context) -> Self::Result { + if let Some(kaksh) = self.kaksh.get(&msg.kaksh_kunjika) { + kaksh.loog.iter().for_each(|c| { + if c.kunjika == msg.kunjika { + return; + } + c.addr.do_send(ms::sansad::WsStatus { + sender_kunjika: msg.kunjika.to_owned(), + status: msg.status.to_owned(), + }); + }); + } + } +} \ No newline at end of file diff --git a/src/chat_pinnd/mod.rs b/src/chat_pinnd/mod.rs new file mode 100644 index 0000000..fcf3e2a --- /dev/null +++ b/src/chat_pinnd/mod.rs @@ -0,0 +1,82 @@ +//! Chat Pinnd(पिण्ड) is Actor to manage Websocket Chat related action + +mod user; +mod message; + +use std::{collections::HashMap, vec}; + +use actix::prelude::*; +use actix_broker::BrokerSubscribe; + +use crate::{ws_sansad, broker_messages as ms, broker_messages::util::Resp}; + +#[allow(dead_code)] +pub struct ChatPinnd { + kaksh: HashMap, // kunjika, Kaksh + vyaktigat_waitlist: Vec, +} + +pub struct Kaksh { + length: Option, + last_message_id: u128, + loog: Vec +} + +pub struct Loog { + addr: Addr, + kunjika: String, + name: String, + tags: Option> +} + +#[derive(Debug, Clone)] +pub struct Vyakti { + name: String, + tags: Vec +} +pub struct VyaktiWatchlist { + kunjika: String, + name: String, + tags: Vec, + addr: Addr +} + +impl Actor for ChatPinnd { + type Context = Context; + + fn started(&mut self, ctx: &mut Self::Context) { + // for actix broker + self.subscribe_system_async::(ctx); + self.subscribe_system_async::(ctx); + self.subscribe_system_async::(ctx); + } +} + + +impl Default for ChatPinnd { + fn default() -> Self { + ChatPinnd { + kaksh: HashMap::new(), + vyaktigat_waitlist: Vec::new() + } + } +} + +impl Loog { + fn new(addr: Addr, + kunjika: String, + name: String, + tags: Option>) -> Self { + + Loog { + addr, + kunjika, + name, + tags + } + } +} + +impl SystemService for ChatPinnd {} +impl Supervised for ChatPinnd {} + diff --git a/src/chat_pinnd/user.rs b/src/chat_pinnd/user.rs new file mode 100644 index 0000000..152b1f2 --- /dev/null +++ b/src/chat_pinnd/user.rs @@ -0,0 +1,290 @@ + +use super::*; + +/// Join kaksh +impl Handler for ChatPinnd { + type Result = Resp; + + fn handle(&mut self, msg: ms::pind::JoinKaksh, _: &mut Self::Context) -> Self::Result { + // check if user exist + if let Some(_) = self.vyaktigat_waitlist.iter().position(|vk| vk.kunjika == msg.kunjika) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + if let Some(_) = self.kaksh.iter().position(|(_,g)| { + match g.loog.iter().position(|a| a.kunjika == msg.kunjika) { + Some(_) => true, + None => false + } + }) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + // check if kaksh exist and add user + match self.kaksh.get_mut(&msg.kaksh_kunjika) { + Some(kaksh) =>{ // exist + // check if kaksh have no space left + if let Some(n) = kaksh.length { + if kaksh.loog.len() >= n { + return Resp::Err("Kaksh have no space".to_owned()); + } + } + + kaksh.loog.iter().for_each(|a: &Loog| { + a.addr.do_send(ms::sansad::WsConnected { + name: msg.name.to_owned(), + kunjika: msg.kunjika.to_owned() + }) + }); + + kaksh.loog.push(Loog::new(msg.addr, msg.kunjika,msg.name, None)); + + + }, None => { // don't exist + // add kaksh and notify + msg.addr.do_send(ms::sansad::WsConnected { + name: msg.name.to_owned(), + kunjika: msg.kunjika.to_owned() + }); + self.kaksh.insert(msg.kaksh_kunjika, Kaksh { + length: msg.length, + last_message_id: 0, + loog: vec![Loog::new(msg.addr,msg.kunjika,msg.name, None)] + }); + } + } + + Resp::Ok + } +} + +/// Join random vayakti +/// Works as: +/// Check if watchlist is empty, if yes add the kunjika andaddr to watchlist +/// if watchlist have people get 0th person an connect it +impl Handler for ChatPinnd { + type Result = Resp; + fn handle(&mut self, msg: ms::pind::JoinRandom, _: &mut Self::Context) -> Self::Result { + // check if user exist + if let Some(_) = self.vyaktigat_waitlist.iter().position(|vk| vk.kunjika == msg.kunjika) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + if let Some(_) = self.kaksh.iter().position(|(_,g)| { + match g.loog.iter().position(|a| a.kunjika == msg.kunjika) { + Some(_) => true, + None => false + } + }) { + return Resp::Err("Kunjika already exist".to_owned()); + } + + // Check if watch list is empty + if self.vyaktigat_waitlist.len() == 0 { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr: msg.addr, + name: msg.name, + tags: msg.tags + }); + return Resp::None; + } + + // connect person with tag + let pos = if msg.tags.len() > 0 { + match self.vyaktigat_waitlist.iter().position(|vk| { + match vk.tags.iter().position(|t| msg.tags.contains(t)) { + Some(_) => true, + None => false + } + }) { + Some(i) => i, + None => { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr: msg.addr, + name: msg.name, + tags: msg.tags + }); + return Resp::None; + } + } + } else { 0 }; + + let vayakti_watchlist = self.vyaktigat_waitlist.remove(pos); + let group_kunjika = format!("gupt_{}>{}",msg.kunjika.to_owned(), vayakti_watchlist.kunjika); + self.kaksh.insert(group_kunjika.to_owned(), Kaksh { + length: Some(2), + last_message_id: 0, + loog: vec![Loog::new(msg.addr.clone(), msg.kunjika.to_owned(), msg.name.to_owned(), Some(msg.tags.clone())), + Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] + }); + + // notify about connection + msg.addr.do_send(ms::sansad::WsConnectedRandom { + name: vayakti_watchlist.name, + kunjika: vayakti_watchlist.kunjika, + kaksh_kunjika: group_kunjika.to_owned() + }); + vayakti_watchlist.addr.do_send(ms::sansad::WsConnectedRandom { + name: msg.name, + kunjika: msg.kunjika.to_owned(), + kaksh_kunjika: group_kunjika + }); + + Resp::Ok + } +} + +/// Next Random user +impl Handler for ChatPinnd { + type Result = Resp; + fn handle(&mut self, msg: ms::pind::JoinRandomNext, _: &mut Self::Context) -> Self::Result { + let kaksh = match self.kaksh.get_mut(&msg.kaksh_kunjika) { + Some(v) => v, + None => return Resp::Err("Failed to join, check entries!".to_owned()) + }; + + let loog_i = match kaksh.loog.iter().position(|a| a.kunjika == msg.kunjika) { + Some(v) => v, + None => return Resp::Err("Failed to join, check entries!".to_owned()) + }; + + let addr; + let name; + let tags; + + { + let loog = match kaksh.loog.get(loog_i) { + Some(v) => v, + None => return Resp::Err("Failed to join, check entries!".to_owned()) + }; + + if let None = loog.tags { + return Resp::Err("You are not a randome vyakti!".to_owned()); + } + + addr = loog.addr.clone(); + name = loog.name.to_owned(); + tags = match loog.tags.clone() { + Some(v) => v, + None => return Resp::Err("Failed to join, check entries!".to_owned()) + }; + } + + // remove from old kaksh + kaksh.loog.remove(loog_i); + kaksh.loog.iter().for_each(|a| { + a.addr.do_send(ms::sansad::WsDisconnected { + kunjika: msg.kunjika.to_owned(), + name: name.to_owned() + }) + }); + + // Check if watch list is empty + if self.vyaktigat_waitlist.len() == 0 { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr, + name, + tags + }); + return Resp::None; + } + // connect person with tag or to zero + let pos = if tags.len() > 0 { + match self.vyaktigat_waitlist.iter().position(|vk| { + match vk.tags.iter().position(|t| tags.contains(t)) { + Some(_) => true, + None => false + } + }) { + Some(i) => i, + None => { + self.vyaktigat_waitlist.push(VyaktiWatchlist { + kunjika: msg.kunjika, + addr, + name, + tags + }); + return Resp::None; + } + } + } else { 0 }; + let vayakti_watchlist = self.vyaktigat_waitlist.remove(pos); + let group_kunjika = format!("gupt_{}>{}",msg.kunjika.to_owned(), vayakti_watchlist.kunjika); + + let log_count = kaksh.loog.len(); + drop(kaksh); + if log_count == 0 { + self.kaksh.remove(&msg.kaksh_kunjika); + } + self.kaksh.insert(group_kunjika.to_owned(), Kaksh { + length: Some(2), + last_message_id: 0, + loog: vec![Loog::new(addr.clone(), msg.kunjika.to_owned(), name.to_owned(), Some(tags.clone())), + Loog::new(vayakti_watchlist.addr.clone(), vayakti_watchlist.kunjika.to_owned(), vayakti_watchlist.name.to_owned(), Some(vayakti_watchlist.tags.clone()))] + }); + // notify about connection + addr.do_send(ms::sansad::WsConnectedRandom { + name: vayakti_watchlist.name, + kunjika: vayakti_watchlist.kunjika, + kaksh_kunjika: group_kunjika.to_owned() + }); + + vayakti_watchlist.addr.do_send(ms::sansad::WsConnectedRandom { + name, + kunjika: msg.kunjika.to_owned(), + kaksh_kunjika: group_kunjika + }); + + Resp::Ok + } +} + +/// send list of users +impl Handler for ChatPinnd { + type Result = String; + + fn handle(&mut self, msg: ms::pind::List, _: &mut Self::Context) -> Self::Result { + if let Some(kaksh) = self.kaksh.get(&msg.kaksh_kunjika) { + let mut list = Vec::new(); + for x in kaksh.loog.iter() { + list.push((x.kunjika.to_owned(),x.name.to_owned())); + } + serde_json::json!(list).to_string() + } else { + "".to_string() + } + } +} + +/// Notifiy a user disconnected and trim kaksh +impl Handler for ChatPinnd { + type Result = (); + + fn handle(&mut self, msg: ms::pind::LeaveUser, _: &mut Self::Context) -> Self::Result { + if let Some(kaksh_kunjika) = &msg.kaksh_kunjika { + if let Some(kaksh) = self.kaksh.get_mut(kaksh_kunjika) { + let name = if let Some(i) = kaksh.loog.iter().position(|x| x.addr == msg.addr) { + kaksh.loog.remove(i).name + } else { "".to_owned() }; + + if kaksh.loog.len() == 0 { + self.kaksh.remove(kaksh_kunjika); + } else { + kaksh.loog.iter().for_each(|a| { + a.addr.do_send(ms::sansad::WsDisconnected { + kunjika: msg.kunjika.to_owned(), + name: name.to_owned() + }) + }); + } + } + } + + if let Some(i) = self.vyaktigat_waitlist.iter().position(|a| a.kunjika == msg.kunjika) { + self.vyaktigat_waitlist.remove(i); + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f59afe6..7c9dd80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use ws_sansad::WsSansad; mod config; mod errors; -mod messages; +mod broker_messages; mod ws_sansad; mod chat_pinnd; mod validator; diff --git a/src/ws_sansad/handlers.rs b/src/ws_sansad/handlers.rs new file mode 100644 index 0000000..c0a9cae --- /dev/null +++ b/src/ws_sansad/handlers.rs @@ -0,0 +1,108 @@ + +use super::*; + +/// send text message +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsText, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "text", + "text": msg.text, + "reply": msg.reply, + "kunjika": msg.sender_kunjika, // Sender's kunjuka + "msg_id": msg.msg_id.to_string() + }); + ctx.text(json.to_string()); + } +} + + +/// send text status +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsStatus, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "status", + "status": msg.status, + "kunjika": msg.sender_kunjika // Sender's kunjuka + }); + ctx.text(json.to_string()); + } +} + +/// List Vayakti +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsList, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "list", + "vayakti": msg.json + }); + ctx.text(json.to_string()); + } +} + +/// Own Kunjika hash +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsKunjikaHash, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "kunjika", + "kunjika": msg.kunjika + }); + ctx.text(json.to_string()); + } +} + +/// send response ok, error +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsResponse, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "resp", + "result": msg.result, + "message": msg.message + }); + ctx.text(json.to_string()); + } +} + +/// notify someone got connected +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsConnected, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "connected", + "name": msg.name, + "kunjika": msg.kunjika + }); + ctx.text(json.to_string()); + } +} + +/// notify someone got disconnected +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsDisconnected, ctx: &mut Self::Context) -> Self::Result { + let json = json!({ + "cmd": "disconnected", + "name": msg.name, + "kunjika": msg.kunjika + }); + ctx.text(json.to_string()); + } +} + +/// notify got connected to random person +impl Handler for WsSansad { + type Result = (); + fn handle(&mut self, msg: ms::sansad::WsConnectedRandom, ctx: &mut Self::Context) -> Self::Result { + self.isthiti = Isthiti::Kaksh(msg.kaksh_kunjika); + let json = json!({ + "cmd": "random", + "name": msg.name, + "kunjika": msg.kunjika + }); + ctx.text(json.to_string()); + } +} diff --git a/src/ws_sansad/messages.rs b/src/ws_sansad/messages.rs new file mode 100644 index 0000000..cd8101a --- /dev/null +++ b/src/ws_sansad/messages.rs @@ -0,0 +1,89 @@ +use super::*; + +impl WsSansad { + + /// send text to vayakti in kaksh + pub async fn send_text(&mut self, val: Value) { + // check if vayakti exist + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Kaksh"); + return; + } + + // check if connected to any kaksh + match self.isthiti { + Isthiti::Kaksh(_) => (), + _ => { + self.send_err_response("Kaksh not connected"); + return; + } + } + + // sent text + let text = match val.get("text") { + Some(val) => val, + None => { + self.send_err_response("Invalid request"); + return; + } + }.as_str().unwrap().to_owned(); + + let reply: Option = match val.get("reply") { + Some(val) => Some(val.as_str().unwrap().to_owned()), + None => None + }; + + let kaksh_kunjika = match &self.isthiti { + Isthiti::Kaksh(kaksh_kunjika) => { + kaksh_kunjika.to_owned() + }, _ => { + return; + } + }; + Broker::::issue_async(ms::pind::SendText { + kaksh_kunjika, + kunjika: self.kunjika.to_owned(), + text, + reply + }); + } + + /// send status to vayakti in kaksh + pub async fn send_status(&mut self, val: Value) { + // check if vayakti exist + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Kaksh"); + return; + } + + // check if connected to any kaksh + match self.isthiti { + Isthiti::Kaksh(_) => (), + _ => { + self.send_err_response("Kaksh not connected"); + return; + } + } + + // sent status + let status = match val.get("status") { + Some(val) => val, + None => { + self.send_err_response("Invalid request"); + return; + } + }.as_str().unwrap().to_owned(); + let kaksh_kunjika = match &self.isthiti { + Isthiti::Kaksh(kaksh_kunjika) => { + kaksh_kunjika.to_owned() + }, _ => { + return; + } + }; + Broker::::issue_async(ms::pind::SendStatus { + kaksh_kunjika, + kunjika: self.kunjika.to_owned(), + status + }); + } +} \ No newline at end of file diff --git a/src/ws_sansad/mod.rs b/src/ws_sansad/mod.rs new file mode 100644 index 0000000..66c0c6e --- /dev/null +++ b/src/ws_sansad/mod.rs @@ -0,0 +1,141 @@ +//! Ws Sansad manage websocket of each client + +mod handlers; +mod users; +mod messages; + +use actix::prelude::*; +use actix_broker::{Broker, SystemBroker}; +use actix_web_actors::ws; +use serde_json::{json, Value}; +use std::time::{Duration, Instant}; + +use crate::{chat_pinnd::ChatPinnd, broker_messages as ms, broker_messages::util::Resp, validator::{Validation as vl, validate}}; + +/// How often heartbeat pings are sent +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +/// How long before lack of client response causes a timeout +const CLIENT_TIMEOUT: Duration = Duration::from_secs(15); + +pub struct WsSansad { + kunjika: String, + isthiti: Isthiti, + addr: Option>, + hb: Instant +} + +#[derive(Debug)] +enum Isthiti { + None, + Kaksh(String), + VraktigatWaitlist +} + + +impl Actor for WsSansad { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + self.addr = Some(ctx.address().clone()); // own addr + self.hb(ctx); + } + + fn stopping(&mut self, _: &mut Self::Context) -> Running { + futures::executor::block_on(self.leave_kaksh()); // notify leaving + Running::Stop + } +} + + +/// manage stream +impl StreamHandler> for WsSansad { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Ok(ws::Message::Ping(msg)) => { + ctx.ping(&msg); + self.hb = Instant::now(); + }, Ok(ws::Message::Pong(_)) => { + self.hb = Instant::now(); + }, Ok(ws::Message::Text(msg)) => { + futures::executor::block_on(self.parse_text_handle(msg)); + }, Ok(ws::Message::Close(msg)) => { + ctx.close(msg); + ctx.stop(); + } + _ => ctx.stop() + } + } +} + + +impl WsSansad { + pub fn new() -> Self { + WsSansad { + kunjika: String::new(), + isthiti: Isthiti::None, + addr: None, + hb: Instant::now() + } + } + + /// helper method that sends ping to client every second. + /// + /// also this method checks heartbeats from client + fn hb(&self, ctx: &mut ::Context) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + // check client heartbeats + if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { + // heartbeat timed out + + // stop actor + futures::executor::block_on(act.leave_kaksh()); // notify leaving + ctx.stop(); + // don't try to send a ping + return; + } + + ctx.ping(b""); + }); + } + + /// parse the request text from client + async fn parse_text_handle(&mut self, msg: String) { + if let Ok(val) = serde_json::from_str::(&msg) { + // let cmd = match val.get("cmd") { + // Some(v) => v, + // None => return + // }; + // let cmd = match cmd.as_str() { + // Some(v) => v, + // None => return + // }; + + match val.get("cmd").unwrap().as_str().unwrap() { + "join" => { self.join_kaksh(val).await }, + "rand" => { self.join_random(val).await }, + "randnext" => { self.join_random_next().await }, + "text" => { self.send_text(val).await }, + "status" => { self.send_status(val).await }, + "list" => { self.list().await }, + "leave" => { self.leave_kaksh().await }, + _ => () + } + } + } + + /// send ok response + fn send_ok_response(&self, text: &str) { + self.addr.clone().unwrap().do_send(ms::sansad::WsResponse { + result: "Ok".to_owned(), + message: text.to_owned() + }); + } + + /// send error response + fn send_err_response(&self, text: &str) { + self.addr.clone().unwrap().do_send(ms::sansad::WsResponse { + result: "Err".to_owned(), + message: text.to_owned() + }); + } +} \ No newline at end of file diff --git a/src/ws_sansad/users.rs b/src/ws_sansad/users.rs new file mode 100644 index 0000000..2e35d3a --- /dev/null +++ b/src/ws_sansad/users.rs @@ -0,0 +1,244 @@ +use super::*; + +impl WsSansad { + /// Request for joining to random person + pub async fn join_random(&mut self, val: Value) { + // Check is already joined + match self.isthiti { + Isthiti::None => (), + Isthiti::VraktigatWaitlist => { + self.send_ok_response("watchlist"); + return; + }, Isthiti::Kaksh(_) => return + } + + let kunjika = match val.get("kunjika") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + // kunjika to hash + let mut m = sha1::Sha1::new(); + m.update(format!("{}{}",kunjika, + std::env::var("SALT").unwrap_or("".to_owned())).as_bytes()); + let kunjika = m.digest().to_string(); + + let name = match val.get("name") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + let tags = match val.get("tags") { + Some(val ) => { + let mut v = Vec::new(); + for x in val.as_str().unwrap().split_ascii_whitespace() { + v.push(x.to_owned()); + } + v + }, + None => { + Vec::new() + } + }; + + // Validate + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoSpace, vl::NoHashtag], &kunjika, "Kunjika") { + self.send_err_response(&val); + return; + } else if let Some(val ) = validate(vec![vl::NonEmpty], &name, "Name") { + self.send_err_response(&val); + return; + } + + // request + let result: Resp = ChatPinnd::from_registry().send(ms::pind::JoinRandom{ + addr: self.addr.clone().unwrap(), + kunjika: kunjika.to_owned(), + name, + tags + }).await.unwrap(); + + match result { + Resp::Err(err) => self.send_err_response(&err), + Resp::Ok => { + self.addr.clone().unwrap().do_send(ms::sansad::WsKunjikaHash{ kunjika: kunjika.clone() }); + self.kunjika = kunjika; + }, + Resp::None => { + self.addr.clone().unwrap().do_send(ms::sansad::WsResponse{ + result: "watch".to_owned() , + message: "Watchlist".to_owned() + }); + self.isthiti = Isthiti::VraktigatWaitlist; + self.addr.clone().unwrap().do_send(ms::sansad::WsKunjikaHash{ kunjika: kunjika.clone() }); + self.kunjika = kunjika + } + } + } + + /// Request for joining to random person + pub async fn join_random_next(&mut self) { + // Check is already joined + let kaksh_kunjika = match &self.isthiti { + Isthiti::VraktigatWaitlist => { + self.send_ok_response("watchlist"); + return; + }, + Isthiti::Kaksh(kaksh_kunjika) => kaksh_kunjika, + Isthiti::None => { + self.send_ok_response("Not allowed"); + return; + } + }; + + // request + let result: Resp = ChatPinnd::from_registry().send(ms::pind::JoinRandomNext { + kunjika: self.kunjika.to_owned(), + kaksh_kunjika: kaksh_kunjika.to_owned(), + }).await.unwrap(); + + match result { + Resp::Err(err) => self.send_err_response(&err), + Resp::None => { + self.addr.clone().unwrap().do_send(ms::sansad::WsResponse{ + result: "watch".to_owned() , + message: "Watchlist".to_owned() + }); + self.isthiti = Isthiti::VraktigatWaitlist; + self.kunjika = self.kunjika.to_owned() + } + _ => () + } + } + + /// Request to join to kaksh + pub async fn join_kaksh(&mut self, val: Value) { + // Check is already joined + match self.isthiti { + Isthiti::None => (), + _ => return + } + + // is vayakti in watch list + if let Isthiti::VraktigatWaitlist = self.isthiti { + self.send_ok_response("watchlist"); + return; + } + + let kunjika = match val.get("kunjika") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + // kunjika to hash + let mut m = sha1::Sha1::new(); + m.update(format!("{}{}",kunjika, + std::env::var("SALT").unwrap_or("".to_owned())).as_bytes()); + let kunjika = m.digest().to_string(); + + let name = match val.get("name") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + let kaksh_kunjika = match val.get("kaksh_kunjika") { + Some(val ) => val.as_str().unwrap().to_owned(), + None => { + self.send_err_response("Invalid request"); + return; + } + }; + let length: Option = match val.get("length") { + Some(val) => match val.as_i64(){ + Some(val) => Some(val as usize), + None => None + }, + None => None + }; + + + // Validate + if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoGupt, vl::NoSpace], &kaksh_kunjika, "Kaksh Kunjika") { + self.send_err_response(&val); + return; + } else if let Some(val ) = validate(vec![vl::NonEmpty, vl::NoSpace, vl::NoHashtag], &kunjika, "Kunjika") { + self.send_err_response(&val); + return; + } else if let Some(val ) = validate(vec![vl::NonEmpty], &name, "Name") { + self.send_err_response(&val); + return; + } + + // request + let result: Resp = ChatPinnd::from_registry().send(ms::pind::JoinKaksh { + kaksh_kunjika: kaksh_kunjika.to_owned(), + length, + addr: self.addr.clone().unwrap(), + kunjika: kunjika.to_owned(), + name + }).await.unwrap(); + + + match result { + Resp::Err(err) => self.send_err_response(&err), + Resp::Ok => { + self.isthiti = Isthiti::Kaksh(kaksh_kunjika); + self.addr.clone().unwrap().do_send(ms::sansad::WsKunjikaHash{ kunjika: kunjika.clone() }); + self.kunjika = kunjika; + self.send_ok_response("joined") + } + _ => () + } + } + + /// Request to join to kaksh + pub async fn list(&mut self) { + // check if vayakti exist + if let Isthiti::None = self.isthiti { + self.send_err_response("Not in any Kaksh"); + return; + } + + // check if connected to any kaksh + match &self.isthiti { + Isthiti::Kaksh(kunjika) => { + let json: String = ChatPinnd::from_registry().send(ms::pind::List { + kaksh_kunjika: kunjika.to_owned() + }).await.unwrap(); + + self.addr.clone().unwrap().do_send(ms::sansad::WsList { + json + }) + }, + _ => { + self.send_err_response("Kaksh not connected"); + return; + } + } + } + + /// notify leaving + pub async fn leave_kaksh(&mut self) { + let kaksh_kunjika = match &self.isthiti { + Isthiti::Kaksh(val) => Some(val.to_owned()), + _ => None + }; + + Broker::::issue_async(ms::pind::LeaveUser { + kaksh_kunjika, + kunjika: self.kunjika.to_owned(), + addr: self.addr.clone().unwrap() + }); + + self.isthiti = Isthiti::None; + self.send_ok_response("left"); + } +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index 7491d8a..1ed7193 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -9,8 +9,10 @@ :root { --bg-color: #ffffff; --bg-secondary-color: #f3f3f6; + --bg-tertiary-color: #cacad8; --color-primary: #14854F; --color-lightGrey: #d2d6dd; + --color-border: #d2d6dd; --color-grey: #747681; --color-darkGrey: #3f4144; --color-error: #d43939; @@ -21,6 +23,39 @@ --font-color: #333333; --font-family-sans: "Itim"; --font-family-mono: monaco, "Consolas", "Lucida Console", monospace; + + --msg-other-bg-color : rgb(75, 147, 196); + --msg-me-bg-color : rgb(101, 187, 130); + --msg-reply-bg-color : rgb(198, 203, 207); + --msg-other-border-color : #fff; + --msg-me-border-color : #fff; + --msg-reply-border-color : #fff; + --msg-other-border-sel-color : #fff; + --msg-me-border-sel-color : #fff; +} + +* { + scrollbar-width: auto; +} + +body.dark { + --bg-color: rgb(25, 25, 27); + --bg-secondary-color: #1f1f20; + --bg-tertiary-color: #28282b; + --font-color: #e5e7eb; + --color-grey: #ccc; + --color-lightGrey: #959ba5; + --color-darkGrey: rgb(140, 142, 146); + --color-border: rgb(140, 142, 146); + + --msg-other-bg-color : #2C5AA0; + --msg-me-bg-color : #005544; + --msg-reply-bg-color : rgb(45, 46, 53); + --msg-other-border-color : #3771C8; + --msg-me-border-color : #008066; + --msg-reply-border-color : #717479; + --msg-other-border-sel-color : #ACDBFF; + --msg-me-border-sel-color : #8CE7A9; } body > .container { @@ -46,10 +81,30 @@ textarea { font-size: 0.9em; margin-top: 0; margin-bottom: 0; + scrollbar-width: none; + background-color: var(--bg-secondary-color); + color: var(--font-color); +} + +input { + background-color: var(--bg-secondary-color); + color: var(--font-color); +} + +.bg-sec { + background-color: var(--bg-secondary-color) !important; +} + +.bg-ter { + background-color: var(--bg-tertiary-color) !important; } .bg-white { - background-color: white; + background-color: white !important; +} + +.button { + background-color: var(--bg-tertiary-color); } .cover-screen { @@ -137,6 +192,10 @@ textarea { text-align: center; } +.status .tag { + background-color: var(--bg-tertiary-color); +} + .message { padding: 0px 8px 3px 8px; margin: 2px; @@ -144,18 +203,18 @@ textarea { } .message-other { - border-left: 5px solid #1E86D7; - background-color: #95C1E2; + border-left: 5px solid var(--msg-other-border-color); + background-color: var(--msg-other-bg-color); } .message-me { - border-left: 5px solid #2F848D; - background-color: #99C5C9; + border-left: 5px solid var(--msg-me-border-color); + background-color: var(--msg-me-bg-color); } .message-reply { - border-left: 5px solid #383C4A; - background-color: #DEDEDE; + border-left: 5px solid var(--msg-reply-border-color); + background-color: var(--msg-reply-bg-color); } .message .message-by { @@ -166,13 +225,13 @@ textarea { } .message-me.active { - border: 2px dashed #11603F; - border-left: 5px solid #11603F; + border: 3px dashed var(--msg-me-border-sel-color); + border-left: 5px solid var(--msg-me-border-sel-color); } .message-other.active { - border: 2px dashed #104975; - border-left: 5px solid #104975; + border: 3px dashed var(--msg-other-border-sel-color); + border-left: 5px solid var(--msg-other-border-sel-color); } .clip-win { @@ -182,6 +241,7 @@ textarea { margin: 5px; bottom: 50px; border: 1px dashed #1E86D7; + background-color: var(--bg-secondary-color); } .clip-win.reply-clip { @@ -208,6 +268,17 @@ textarea { max-width: 720px; } +.clip-win.action-clip button { + padding: 1rem 1rem; + margin-left: 0.5rem; + margin-bottom: 0.5rem; +} + +.clip-win.camera-clip { + width: 314px; +} + + @media screen and (max-width: 600px) { div[name="error_msg"] { width: 100%; diff --git a/static/img/color.svg b/static/img/color.svg new file mode 100644 index 0000000..ccc3c51 --- /dev/null +++ b/static/img/color.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/loading.svg b/static/img/loading.svg index bf06422..451a609 100644 --- a/static/img/loading.svg +++ b/static/img/loading.svg @@ -10,6 +10,5 @@ from="0 50 48" to="360 50 52" repeatCount="indefinite" /> - diff --git a/static/img/pattern.svg b/static/img/pattern.svg index 0bc97f0..e65cecc 100644 --- a/static/img/pattern.svg +++ b/static/img/pattern.svg @@ -28,28 +28,28 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1721" - inkscape:window-height="1019" + inkscape:window-width="1920" + inkscape:window-height="1021" id="namedview39" showgrid="false" - inkscape:zoom="0.20873984" - inkscape:cx="-1382.0481" - inkscape:cy="-167.56932" + inkscape:zoom="0.41747968" + inkscape:cx="538.744" + inkscape:cy="247.6787" inkscape:window-x="0" inkscape:window-y="0" - inkscape:window-maximized="0" + inkscape:window-maximized="1" inkscape:current-layer="Layer_1" inkscape:document-rotation="0" /> + style="fill:#938d71;fill-opacity:0.15649126"> + style="fill:#938d71;fill-opacity:0.15649126"> + style="fill:#938d71;fill-opacity:0.15649126" /> Lupt Chat -
+
@@ -108,7 +109,7 @@