diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8d40c24..34c590c 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -13,4 +13,16 @@ [Version-1 : The real bridge](./chapter_5.md) [Version-1 : Configuring bridge](./chapter_6.md) [Version-2 : Group/Room Creation](./chatper_7.md) -[Version-2 : DM from qaul to matrix](./chatper_8.md) \ No newline at end of file +[Version-2 : DM from qaul to matrix](./chatper_8.md) +[Version-2 : Invite/Remove from group](./chapter_9.md) +[Version-2 : Sending/Receiving files](./chapter_10.md) +[Version-2 : Polishing with user menu](./chapter_11.md) + +--- + +[Chaos Communication Camp](./chapter_12.md) +[GSoC Lightening Talks 2023](./chapter_13.md) +[Matrix Communication Camp](./chapter_14.md) + +--- +[Closing Notes](./chapter_15.md) diff --git a/src/chapter_10.md b/src/chapter_10.md new file mode 100644 index 0000000..b64cc3b --- /dev/null +++ b/src/chapter_10.md @@ -0,0 +1 @@ +# Version-2 : Sending files and receiving files diff --git a/src/chapter_11.md b/src/chapter_11.md new file mode 100644 index 0000000..cf00768 --- /dev/null +++ b/src/chapter_11.md @@ -0,0 +1,187 @@ +# Version-2 : Polishing with user menu + +Now we were all set functionality wise to launch the very first usable bot version on the qaul community. To improve it further for the users, I have worked a bit on improving the logs on the binary, write comments in code, format the response messages to be sent over the matrix. + +One major thing which we did was to improve the matrix menu for the matrix users by allowing them to write the commands to trigger qaul events from matrix. + +```markdown +!qaul : Ping to check if the bot is active or not. +!help : Get the help menu in matrix for possible events. +!users : Get list of all the users on the network. +!invite {qaul_user_id} : To invite a user from the qaul into this matrix room. +!remove {qaul_user_id} : To remove a user from the qaul into this matrix room. +!group-info : Get details for the qaul group with which this matrix room is connected. +``` + +Here is the code explaining how we took the commands from matrix world and processed things in Qaul world. + +```rust +// We don't consider message in matrix from the bot +// since it would be the response being sent from qaul. +if msg_sender != bot_matrix_id { + let msg_text = format!("{} : {}", msg_sender, msg_body); + + // Send the text to qaul to process the incoming matrix message + send_qaul(msg_text, room.room_id()); + + // on receiving !qaul from matrix, Send message + if msg_body.contains("!qaul") { + let content = AnyMessageEventContent::RoomMessage( + MessageEventContent::text_plain( + "I am a message sent from qaul network\n", + ), + ); + room.send(content, None).await.unwrap(); + } + + // on receiving !help from matrix, Give brief of all possible commands. + if msg_body.contains("!help") { + let content = AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain( + "!qaul : Ping to check if the bot is active or not.\n!users : Get list of all the users on the network.\n!invite {qaul_user_id} : To invite a user from the qaul into this matrix room.\n !remove {qaul_user_id} : To remove a user from the qaul into this matrix room.\n!group-info : Get details for the qaul group with which this matrix room is connected.", + )); + room.send(content, None).await.unwrap(); + } + + // on receiving !qaul in matrix, Send message + if msg_body.contains("!invite") { + let matrix_user = + room.get_member(&msg_sender).await.unwrap().unwrap(); + // Admin Powers + if matrix_user.power_level() == 100 { + let mut iter = msg_body.split_whitespace(); + let _command = iter.next().unwrap(); + // TODO : Try to return an error if userID is wrong. + let qaul_user_id = iter.next().unwrap().to_string(); + let room_id_string = room.room_id().to_string(); + let sender_string = msg_sender.to_string(); + let request_id = format!( + "invite#{}#{}#{}", + room_id_string, sender_string, qaul_user_id + ); + println!("{}", request_id); + // Create group only if the mapping between a qaul grp and matrix room doesn't exist. + // If it exist then please check if user already exist or not. If not then invite + let config = MATRIX_CONFIG.get().write().unwrap().clone(); + let room_id = room.room_id(); + let qaul_group_id: Option = find_key_for_value( + config.room_map.clone(), + room_id.clone(), + ); + if qaul_group_id == None { + group::Group::create_group( + format!("{}", msg_sender.to_owned()).to_owned(), + request_id, + ); + // Acknowledge about sent invitation to qaul user. + let content = AnyMessageEventContent::RoomMessage( + MessageEventContent::text_plain("User has been invited. Please wait until user accepts the invitation."), + ); + room.send(content, None).await.unwrap(); + } else { + // Get the list of users who are members to the given room. + group::Group::group_info( + chat::Chat::uuid_string_to_bin( + qaul_group_id.unwrap().to_string(), + ) + .unwrap(), + request_id, + ); + println!("The Room Mapping already exist for this room"); + // Else Invite the given user in same mapping of the matrix room. + } + } else { + // Not Admin + let content = AnyMessageEventContent::RoomMessage( + MessageEventContent::text_plain( + "Only Admins can perform this operation.", + ), + ); + room.send(content, None).await.unwrap(); + } + } + + // on receiving !users-list in matrix, Send it to command line + if msg_body.contains("!users") { + users::Users::request_user_list(room.room_id().to_string()); + } + + // remove the people from the matrix room + if msg_body.contains("!remove") { + let matrix_user = + room.get_member(&msg_sender).await.unwrap().unwrap(); + // Admin Powers + if matrix_user.power_level() == 100 { + let mut iter = msg_body.split_whitespace(); + let _command = iter.next().unwrap(); + // TODO : Try to return an error if userID is wrong. + let qaul_user_id = iter.next().unwrap().to_string(); + let room_id_string = room.room_id().to_string(); + let sender_string = msg_sender.to_string(); + let request_id = format!( + "remove#{}#{}#{}", + room_id_string, sender_string, qaul_user_id + ); + println!("{}", request_id); + + let config = MATRIX_CONFIG.get().write().unwrap().clone(); + let room_id = room.room_id(); + let qaul_group_id: Option = find_key_for_value( + config.room_map.clone(), + room_id.clone(), + ); + if qaul_group_id == None { + // No room mapping exist + let content = + AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain( + "No qaul group is mapped to this Matrix room. Please invite qaul users to this room.", + )); + room.send(content, None).await.unwrap(); + } else { + // Check for the group information to see if user is member of the Qaul Room or not + group::Group::group_info( + chat::Chat::uuid_string_to_bin( + qaul_group_id.unwrap().to_string(), + ) + .unwrap(), + request_id, + ); + } + } else { + // Not Admin + let content = AnyMessageEventContent::RoomMessage( + MessageEventContent::text_plain( + "Only Admins can perform this operation.", + ), + ); + room.send(content, None).await.unwrap(); + } + } + + // on receiving !group-info in matrix, You get the details of the group information. + if msg_body.contains("!group-info") { + let config = MATRIX_CONFIG.get().write().unwrap().clone(); + let room_id = room.room_id(); + let qaul_group_id: Option = + find_key_for_value(config.room_map.clone(), room_id.clone()); + if qaul_group_id == None { + // No room mapping exist + let content = + AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain( + "No qaul group is mapped to this Matrix room. Please invite qaul users to this room.", + )); + room.send(content, None).await.unwrap(); + } else { + let request_id = format!("info#{}#_#_", room_id).to_string(); + group::Group::group_info( + chat::Chat::uuid_string_to_bin( + qaul_group_id.unwrap().to_string(), + ) + .unwrap(), + request_id, + ); + } + } +} else { + println!("Sent the message in the matrix room by !qaul-bot"); +} +``` diff --git a/src/chapter_12.md b/src/chapter_12.md new file mode 100644 index 0000000..872c724 --- /dev/null +++ b/src/chapter_12.md @@ -0,0 +1,15 @@ +# CHAOS Communication Camp + +This is a camp for wireless mesh networks happening in Berlin. There was a Matrix Camp with few members from Element being the part of the camp. My mentor has a friend in `Element` who invited us to present our bridge. + +My mentor was into the organizing team and had got no time to test the bridge. And I always tested it locally on my personal network. On the day of presentation, We were doing a live demo in front of people from Matrix on real community node which is a global node in Qaul and interconnects you with the entire qaul community. + +As soon as my mentor created a new user into the community node, there was a crytography error in creating a user and the malicious peer ID was introduced into the network. By default CLI client ignores such uncertain peerID's but the code which I had written by that time was expecting correctness and was filled with `unwrap()`'s. As soon as bad ID entered into my bridge binary's database, All of my code just broke that too in a live presentation. + +I took a moment and I was converting every `unwrap()` into `match` statement to just discard wrong things and not panic. But how long can you do that in a live presentation isn't it ? It was an embrassing feeling. I really thank my mentor who kept the viewers engaged into the conversation by asking matrix specific questions and explaining them out approach to create a bridge. + +Then as a part of very quick solution, I have deleted the database and created a new binary user and re-run whole bridge and showed it in front of presenters all our basic working functionalities. + +This presentation was a bit messy and embrassing since we have never tested on community node and were completely unaware about how a wrong peerID can create this huge of a problem for us. + +In the end, The feedback which we received was kind enough and they said that the direction we have choosen is correct. We just need to fix the bugs which we just introduced for the very first time. diff --git a/src/chapter_13.md b/src/chapter_13.md new file mode 100644 index 0000000..3e2d8d1 --- /dev/null +++ b/src/chapter_13.md @@ -0,0 +1,13 @@ +# Google Summer of Code Lightening Talks + +Starting from this year, Google has started an event where the Google Summer of Code contributors from various organisations would show up in an presentation and give a 3-min session about how beautiful their journey had been with the program. + +I too applied for this Lightening Talk session to talk about my project and I was lucky enough to get selected as one of the 36 contributor out of 106 applications received for the talk. To be honest it was pure luck since they have mentioned that they have randomly selected the contributors. + +On 19th Sept 2023, I have given my talk for 3 mins. You can find my slides below for my explaination about my work within 3 mins and my experience. + +![Slide-1](./images/slide_1.png) +![Slide-2](./images/slide_2.png) +![Slide-3](./images/slide_3.png) + +And I am too happy to appreciate `Google Open Source` organization to give me a gift for the lightening talk session. XD This year's contributors teased a lot to google about giving us swags or T-Shirt and they finally gave after the talk. There were options among many different items but I choosed an Insulated Travel Tumbler. Looking forward to have my hot/cold drinks into the tumbler. diff --git a/src/chapter_14.md b/src/chapter_14.md new file mode 100644 index 0000000..63b0ce7 --- /dev/null +++ b/src/chapter_14.md @@ -0,0 +1,3 @@ +# Matrix Communication Camp + +To be given on 23rd Sept 2023 \ No newline at end of file diff --git a/src/chapter_15.md b/src/chapter_15.md new file mode 100644 index 0000000..c668b4f --- /dev/null +++ b/src/chapter_15.md @@ -0,0 +1,15 @@ +# Closing Notes + +This marks the completion of my Google Summer of Code 2023 project. With all the project work and the conferences I have been part of, I am thankful to my mentor who have helped me in each stage to achieve better success. + +As an individual, I have evolved a lot as a different person which stormy winds flowing through my life span. But towards the end it was all worth it since I found things that gave me the correct direction and purpose [Not talking about Career]. + +I am happy that this GSoC project was not another bro project which was just out there for playing around and checking that if it works then it works else leave. This was a project which I started with very little knowledge but gained massive insights of various concepts. Being from a non computer science domain, I got to learn and apply core concepts which my peers never even talk about. + +The best achievement in open source world in my life is that "I wrote the world's first Matrix Bridge in Rust which made it to Production." And We were invited my `Matrix Community` to showcase our work. + +I would urge developers to focus on FOSS principles and keep the source code as open as possible since the growth in computer science is research oriented and that's the main reason it has the term 'science' in it. Life is too short to earn money from code. Prefer farming, cooking, gardening, plumbing etc. to earn side money but for sake of FOSS please do not write proprietary codes. + +Cheers and Regards + +[Harshil Jani](https://harshil.prose.sh/) diff --git a/src/chapter_9.md b/src/chapter_9.md new file mode 100644 index 0000000..587ad3b --- /dev/null +++ b/src/chapter_9.md @@ -0,0 +1 @@ +# Version-2 : Invite and remove from group \ No newline at end of file diff --git a/src/chatper_8.md b/src/chatper_8.md index 74ef818..9df35c5 100644 --- a/src/chatper_8.md +++ b/src/chatper_8.md @@ -1,3 +1,187 @@ # Version-2 : Send DM from qaul to matrix -Under Developement :) Stay tuned. \ No newline at end of file +The very first part of this work was to figure out how we can create a new matrix room based on willingness of a Qaul user. We get RPC event for invited groups in Qaul. The steps are simple +- Qaul user creates a new group with the matrix ID of matrix user. +- Invites the Qaul Bridge user into the group. +- Bridge user accepts the invitation automatically and joins the group. +- Bridge user triggers the room creation on matrix and invites the matrix user. + +This is very simple auto-joining and invitation part in Qaul which can be explained as below. + +```rust +_invited_ticker = invited_fut => { + group::Group::group_invited(); + None +} +``` +This ticker keeps running after few miliseconds and we keep listening to new group invitation on the bridge user. + +Once the invitation is received we invite matrix user into the group by creating a new room. But we always check that the user is an admin of the qaul group who is trying to create a new room. We actually also have a similar check on Matrix side too where we check the person inviting is admin or not. If admin then only we proceed further. + +```rust +let all_groups = group_list_response.groups.clone(); +let mut config = MATRIX_CONFIG.get().write().unwrap(); +for group in all_groups { + // If Mapping exist let it be. Else create new room. + let group_id = + uuid::Uuid::from_bytes(group.group_id.try_into().unwrap()); + // qaul_groups.insert(group_id, group.group_name.clone()); + let mut qaul_room_admin = format!("@qaul://{}", "[username]"); + for member in group.members { + if member.role == 255 { + let user_id = PeerId::from_bytes(&member.user_id).unwrap(); + qaul_room_admin.push_str(&user_id.to_string()); + } + + // If group mapping does not exist + // If group_name contains matrix user name then only do this. + if let Ok(user) = UserId::try_from(group.group_name.clone()) { + if !config.room_map.contains_key(&group_id) { + let matrix_client = MATRIX_CLIENT.get(); + let rt = Runtime::new().unwrap(); + rt.block_on(async { + println!("{:#?}", group_id); + // Check if user exist on matrix + // Create a group on matrix with qaul user name. + let mut request = CreateRoomRequest::new(); + let room_name = + RoomNameBox::try_from(qaul_room_admin).unwrap(); + request.name = Some(&room_name); + let room_id = matrix_client + .create_room(request) + .await + .expect("Room creation failed") + .room_id; + + // Check if the room is joined + if let Some(joined_room) = matrix_client.get_joined_room(&room_id) { + joined_room.invite_user_by_id(&user).await.unwrap(); + } else { + println!("Wait till the bot joins the room"); + } + + // Save things to Config file + let room_info = MatrixRoom { + matrix_room_id: room_id, + qaul_group_name: group.group_name, + last_index: 0, + }; + config.room_map.insert(group_id, room_info); + MatrixConfiguration::save(config.clone()); + }); + } + } + } +} +``` + +The second part of this Direct communication messages was to allow the messages sent from the qaul private DM into the matrix world. + +Luckily, In qaul we already had a mechanism to detect the message in a qaul group over the network. Each message would contain its own meta-data like the group-id, sender-id and the message content. We can map with the group-id to check in which group it is contained in and from our matrix configuration we can figure the matrix room which contains the mapping and send the message over the network. + +The qaul way of detecting the message is that for every few milliseconds we constantly poll over the message which we receive on our network +```rust +if let Ok(config) = MATRIX_CONFIG.get().read() { + group::Group::group_list(); + let qaul_groups: Vec = config.room_map.keys().cloned().collect(); + + // Check unread messages from Libqaul groups + for group in qaul_groups { + let matrix_room = config.room_map.get(&group).unwrap(); + let last_index_grp = matrix_room.last_index; + let group_id = group.as_bytes().to_vec(); + chat::Chat::request_chat_conversation(group_id,last_index_grp); + } +} else { + println!("Waiting for the configuration to Sync") +} +None +``` + +Here the function method `request_chat_conversation()` is responsible for giving us the list of unread messages in a given group for all the groups in network. + +From the RPC event of all the unread messages which we receive, We extract the details for formatting our matrix message and prepare it to send it on the matrix bridge. +Details like `Qaul User Name`, `Mapped Room ID` and `Message Content` are used to be sent over the network using `matrix_send()` method which I will elaborate below after the following snippet. + +```rust +Some(proto::chat::Message::ConversationList(proto_conversation)) => { + // Conversation table + let group_id = + uuid::Uuid::from_bytes(proto_conversation.group_id.try_into().unwrap()); + let mut config = MATRIX_CONFIG.get().write().unwrap(); + if !config.room_map.contains_key(&group_id) { + println!("No Mapping found"); + } else { + let matrix_room = config.room_map.get_mut(&group_id).unwrap(); + let last_index_grp = matrix_room.last_index; + let room_id = matrix_room.clone().matrix_room_id; + for message in proto_conversation.message_list { + if message.index > last_index_grp { + if let Ok(ss) = Self::analyze_content(&message, &room_id) { + print! {"{} | ", message.index}; + // message.sender_id is same as user.id + match proto::MessageStatus::from_i32(message.status) + .unwrap() + { + proto::MessageStatus::Sending => print!(".. | "), + proto::MessageStatus::Sent => print!("✓. | "), + proto::MessageStatus::Confirmed => print!("✓✓ | "), + proto::MessageStatus::ConfirmedByAll => print!("✓✓✓| "), + proto::MessageStatus::Receiving => print!("🚚 | "), + proto::MessageStatus::Received => print!("📨 | "), + } + + print!("{} | ", message.sent_at); + let users = QAUL_USERS.get().read().unwrap(); + println!("{:#?}", users); + let sender_id = + bs58::encode(message.sender_id).into_string(); + println!("{}", sender_id); + let user_name = + Self::find_user_for_given_id(users.clone(), sender_id) + .unwrap(); + println!( + " [{}] {}", + bs58::encode(message.message_id).into_string(), + message.received_at + ); + + for s in ss { + // This part is mapped with the matrix room. + // Allow inviting the users or removing them. + Self::matrix_send(&s, &room_id, user_name.clone()); + println!("\t{}", s); + } + println!(""); + matrix_room.update_last_index(message.index); + } + } + } + MatrixConfiguration::save(config.clone()); + } +} +``` + +This is how we analyse an unread message in qaul group and then send to matrix bridge using `matrix_send()` method. + +```rust +fn matrix_send(message: &String, room_id: &RoomId, user: String) { + // Get the Room based on RoomID from the client information + let matrix_client = MATRIX_CLIENT.get(); + let room = matrix_client.get_room(&room_id).unwrap(); + // Check if the room is already joined or not + if let Room::Joined(room) = room { + // Build the message content to send to matrix + let final_msg = format!("{} : {}", user, message); + let content = AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain(final_msg)); + + let rt = Runtime::new().unwrap(); + rt.block_on(async { + // Sends messages into the matrix room + room.send(content, None).await.unwrap(); + }); + } +} +``` + +With this chunk of code, We have successfully achieved the integration of private group messages from both ends Qaul->Matrix and Matrix->Qaul. diff --git a/src/images/slide_1.png b/src/images/slide_1.png new file mode 100644 index 0000000..67d6174 Binary files /dev/null and b/src/images/slide_1.png differ diff --git a/src/images/slide_2.png b/src/images/slide_2.png new file mode 100644 index 0000000..55c4901 Binary files /dev/null and b/src/images/slide_2.png differ diff --git a/src/images/slide_3.png b/src/images/slide_3.png new file mode 100644 index 0000000..14f4c55 Binary files /dev/null and b/src/images/slide_3.png differ