Merge MSN sessions on nick changes

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2023-01-02 21:08:51 +01:00
parent 3f90060c08
commit 18050c1185
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
2 changed files with 174 additions and 26 deletions

View file

@ -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::<Vec<_>>()
},
};
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

View file

@ -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<BareJid, Room> = 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();
}