diff --git a/src/room.rs b/src/room.rs index e8136db..0625dfe 100644 --- a/src/room.rs +++ b/src/room.rs @@ -347,13 +347,18 @@ impl Room { let newnick = &new_session.participant().resource; let bare = BareJid::from(new_session.real().clone()); + let mut merge = false; for (nick, occupant) in self.occupants.iter_mut() { - if nick == newnick && occupant.real != bare { - return Err(Error::NickAlreadyAssigned(newnick.clone())); + if nick == newnick { + if occupant.real != bare { + return Err(Error::NickAlreadyAssigned(newnick.clone())); + } else { + merge = true; + break; + } } } - // Send unavailable for current session to everyone let presence_leave = Presence::new(PresenceType::Unavailable) @@ -402,13 +407,29 @@ impl Room { } } else { // Other occupants' sessions - component - .send_stanza( - presence_leave_to_others - .clone() - .with_to(Jid::Full(session.real().clone())), - ) - .await?; + if occupant.real == bare { + // Same barejid + let mucuser = MucUser { + status: vec![MucStatus::NewNick], + items: vec![MucItem::new(Affiliation::Owner, Role::None) + .with_nick(newnick.clone()) + .with_jid(joined_session.real().clone())], + }; + + let presence = presence_leave + .clone() + .with_to(Jid::Full(session.real().clone())) + .with_payloads(vec![mucuser.into()]); + component.send_stanza(presence).await?; + } else { + component + .send_stanza( + presence_leave_to_others + .clone() + .with_to(Jid::Full(session.real().clone())), + ) + .await?; + } } } } @@ -424,9 +445,17 @@ impl Room { _ => unreachable!(), } - // Nickname isn't present already in the room. Create new occupant. - let occupant = Occupant::new(new_session.presence.clone())?; - self.occupants.insert(newnick.to_string(), occupant.clone()); + if merge { + // Merge the new session + match self.get_mut_occupant(&new_session) { + Ok(occupant) => occupant.add_session(new_session.presence.clone())?, + _ => unreachable!(), // We've already established that the occupant existed. + } + } else { + // Nickname isn't present already in the room. Create new occupant. + let occupant = Occupant::new(new_session.presence.clone())?; + self.occupants.insert(newnick.to_string(), occupant.clone()); + } // Send available for new session to everyone @@ -439,23 +468,29 @@ impl Room { } .into()]); + let occupant = self.get_occupant(&new_session)?; + let mucuser = MucUser { + status: vec![], + items: { + occupant + .iter() + .map(|session| { + MucItem::new(Affiliation::Owner, Role::Moderator) + .with_jid(session.real().clone()) + }) + .collect::>() + }, + }; + for (nick, occupant) in self.occupants.iter() { for session in occupant.iter() { // Self occupant if nick == newnick { - let mucuser = MucUser { - status: vec![], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator) - .with_jid(new_session.real().clone())], - }; - if session.real() == new_session.real() { // Self session - let mucuser = MucUser { - status: vec![MucStatus::SelfPresence], - items: vec![MucItem::new(Affiliation::Owner, Role::Moderator) - .with_jid(new_session.real().clone())], - }; + let mut mucuser = mucuser.clone(); + mucuser.status = vec![MucStatus::SelfPresence]; + component .send_stanza( presence_join diff --git a/src/tests/presence_msn.rs b/src/tests/presence_msn.rs index e807789..4d13578 100644 --- a/src/tests/presence_msn.rs +++ b/src/tests/presence_msn.rs @@ -17,8 +17,8 @@ use crate::component::TestComponent; use crate::handlers::handle_stanza; use crate::room::Room; use crate::tests::templates::{ - new_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, SUGAKO_FULL1, - SUGAKO_NICK, + new_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_NICK2, LOUISE_ROOM1_PART, + LOUISE_ROOM1_PART2, ROOM1_BARE, SUGAKO_FULL1, SUGAKO_NICK, }; use std::collections::HashMap; @@ -195,3 +195,116 @@ async fn leave() { assert!(louise.is_some()); assert_eq!(louise.unwrap().sessions.len(), 1); } + +#[tokio::test] +async fn nickname_change_merge() { + let mut rooms: HashMap = HashMap::new(); + rooms.insert( + ROOM1_BARE.clone(), + new_room( + ROOM1_BARE.clone(), + vec![ + (LOUISE_NICK, vec![LOUISE_FULL1.clone()]), + (LOUISE_NICK2, vec![LOUISE_FULL2.clone()]), + (SUGAKO_NICK, vec![SUGAKO_FULL1.clone()]), + ], + ) + .await, + ); + + // LOUISE_NICK2 merging with LOUISE_NICK + let update: Element = Presence::new(PresenceType::None) + .with_from(Jid::Full(LOUISE_FULL2.clone())) + .with_to(Jid::Full(LOUISE_ROOM1_PART.clone())) + .into(); + + let mut component = TestComponent::new(vec![update]); + + // Unavailable to self + component.expect( + Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_ROOM1_PART2.clone())) + .with_to(Jid::Full(LOUISE_FULL2.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence, MucStatus::NewNick], + items: vec![MucItem::new(Affiliation::Owner, Role::None) + .with_nick(LOUISE_NICK) + .with_jid(LOUISE_FULL2.clone())], + } + .into()]), + ); + + // Available to self + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(LOUISE_ROOM1_PART.clone())) + .with_to(Jid::Full(LOUISE_FULL2.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence], + items: vec![ + MucItem::new(Affiliation::Owner, Role::Moderator) + .with_jid(LOUISE_FULL1.clone()), + MucItem::new(Affiliation::Owner, Role::Moderator) + .with_jid(LOUISE_FULL2.clone()), + ], + } + .into()]), + ); + + // Unavailable to other MSN session + component.expect( + Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_ROOM1_PART2.clone())) + .with_to(Jid::Full(LOUISE_FULL1.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::NewNick], + items: vec![MucItem::new(Affiliation::Owner, Role::None) + .with_nick(LOUISE_NICK) + .with_jid(LOUISE_FULL2.clone())], + } + .into()]), + ); + + // Available to other MSN session + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(LOUISE_ROOM1_PART.clone())) + .with_to(Jid::Full(LOUISE_FULL1.clone())) + .with_payloads(vec![MucUser { + status: vec![], + items: vec![ + MucItem::new(Affiliation::Owner, Role::Moderator) + .with_jid(LOUISE_FULL1.clone()), + MucItem::new(Affiliation::Owner, Role::Moderator) + .with_jid(LOUISE_FULL2.clone()), + ], + } + .into()]), + ); + + // Unavailable to Sugako + component.expect( + Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_ROOM1_PART2.clone())) + .with_to(Jid::Full(SUGAKO_FULL1.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::NewNick], + items: vec![MucItem::new(Affiliation::Owner, Role::None).with_nick(LOUISE_NICK)], + } + .into()]), + ); + + // Available to Sugako + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(LOUISE_ROOM1_PART.clone())) + .with_to(Jid::Full(SUGAKO_FULL1.clone())) + .with_payloads(vec![MucUser { + status: vec![], + items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + } + .into()]), + ); + + handle_stanza(&mut component, &mut rooms).await.unwrap(); +}