handlers: split presence handler
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
28186d77d2
commit
6e73112251
2 changed files with 188 additions and 120 deletions
|
@ -13,12 +13,15 @@
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
mod presence;
|
||||||
|
|
||||||
|
use crate::handlers::presence::handle_presence;
|
||||||
|
|
||||||
use crate::component::ComponentTrait;
|
use crate::component::ComponentTrait;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::room::Room;
|
use crate::room::Room;
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::ops::ControlFlow;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
|
@ -27,13 +30,9 @@ use xmpp_parsers::{
|
||||||
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
|
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
|
||||||
iq::{Iq, IqType},
|
iq::{Iq, IqType},
|
||||||
message::Message,
|
message::Message,
|
||||||
muc::{
|
|
||||||
user::{Affiliation, Item as MucItem, Reason, Role, Status as MucStatus},
|
|
||||||
Muc, MucUser,
|
|
||||||
},
|
|
||||||
ns,
|
ns,
|
||||||
ping::Ping,
|
ping::Ping,
|
||||||
presence::{Presence, Type as PresenceType},
|
presence::Presence,
|
||||||
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||||
BareJid, Element, Jid,
|
BareJid, Element, Jid,
|
||||||
};
|
};
|
||||||
|
@ -215,120 +214,6 @@ async fn handle_unhandled_iq<C: ComponentTrait>(
|
||||||
component.send_stanza(iq).await
|
component.send_stanza(iq).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_presence<C: ComponentTrait>(
|
|
||||||
component: &mut C,
|
|
||||||
presence: Presence,
|
|
||||||
rooms: &mut HashMap<BareJid, Room>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if presence.type_ == PresenceType::None {
|
|
||||||
let muc =
|
|
||||||
presence
|
|
||||||
.payloads
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.try_for_each(|payload| match Muc::try_from(payload) {
|
|
||||||
Ok(muc) => ControlFlow::Break(muc),
|
|
||||||
_ => ControlFlow::Continue(()),
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: check for features in the Muc element.
|
|
||||||
|
|
||||||
// Presences to MUC come from resources not accounts
|
|
||||||
if let Jid::Full(realjid) = presence.from.clone().unwrap() &&
|
|
||||||
let Jid::Full(participant) = presence.to.clone().unwrap() {
|
|
||||||
|
|
||||||
let roomjid = BareJid::from(participant.clone());
|
|
||||||
|
|
||||||
// Room already exists
|
|
||||||
if let Some(room) = rooms.get_mut(&roomjid) {
|
|
||||||
debug!("Presence received to existing room: {}", &roomjid);
|
|
||||||
if let ControlFlow::Break(_) = muc {
|
|
||||||
match room.add_session(component, presence.clone()).await {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(Error::NickAlreadyAssigned(nick)) => {
|
|
||||||
let error = Presence::new(PresenceType::Error)
|
|
||||||
.with_from(participant.clone())
|
|
||||||
.with_to(realjid.clone())
|
|
||||||
.with_payloads(vec![
|
|
||||||
StanzaError::new(
|
|
||||||
ErrorType::Cancel,
|
|
||||||
DefinedCondition::Conflict,
|
|
||||||
"en",
|
|
||||||
format!("Nickname conflict: {}", nick),
|
|
||||||
).into()
|
|
||||||
]);
|
|
||||||
component.send_stanza(error).await?;
|
|
||||||
},
|
|
||||||
err => err.unwrap(),
|
|
||||||
}
|
|
||||||
} else if let ControlFlow::Continue(_) = muc {
|
|
||||||
match room.update_presence(component, presence.clone()).await {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(Error::ParticipantNotFound(_)) => {
|
|
||||||
let error = Presence::new(PresenceType::Unavailable)
|
|
||||||
.with_from(participant)
|
|
||||||
.with_to(realjid)
|
|
||||||
.with_payloads(vec![MucUser {
|
|
||||||
status: vec![
|
|
||||||
MucStatus::SelfPresence,
|
|
||||||
MucStatus::Kicked,
|
|
||||||
MucStatus::ServiceErrorKick,
|
|
||||||
],
|
|
||||||
items: {
|
|
||||||
let mut item = MucItem::new(Affiliation::None, Role::None);
|
|
||||||
item.reason = Some(Reason(String::from("You are not in the room.")));
|
|
||||||
vec![item]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
.into()]);
|
|
||||||
component.send_stanza(error).await?
|
|
||||||
},
|
|
||||||
err => err?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug!("Presence received to new room: {}", &roomjid);
|
|
||||||
let mut room = Room::new(roomjid.clone());
|
|
||||||
room.add_session(component, presence.clone()).await.unwrap();
|
|
||||||
let _ = rooms.insert(roomjid, room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if presence.type_ == PresenceType::Unavailable &&
|
|
||||||
let Jid::Full(realjid) = presence.from.clone().unwrap() &&
|
|
||||||
let Jid::Full(participant) = presence.to.clone().unwrap() {
|
|
||||||
|
|
||||||
let roomjid = BareJid::from(participant.clone());
|
|
||||||
|
|
||||||
let error = Presence::new(PresenceType::Error)
|
|
||||||
.with_from(participant.clone())
|
|
||||||
.with_to(realjid.clone())
|
|
||||||
.with_payloads(vec![
|
|
||||||
StanzaError::new(
|
|
||||||
ErrorType::Cancel,
|
|
||||||
DefinedCondition::ServiceUnavailable,
|
|
||||||
"en",
|
|
||||||
"Occupant does not exist",
|
|
||||||
).into()
|
|
||||||
]);
|
|
||||||
if let Some(mut room) = rooms.remove(&roomjid) {
|
|
||||||
match room.remove_session(component, presence.clone()).await {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(Error::NonexistantSession(_)) => {
|
|
||||||
component.send_stanza(error).await.unwrap();
|
|
||||||
},
|
|
||||||
Err(err) => Err(err).unwrap(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the room back
|
|
||||||
if !room.occupants.is_empty() {
|
|
||||||
rooms.insert(roomjid, room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_message<C: ComponentTrait>(
|
async fn handle_message<C: ComponentTrait>(
|
||||||
_component: &mut C,
|
_component: &mut C,
|
||||||
_message: Message,
|
_message: Message,
|
183
src/handlers/presence.rs
Normal file
183
src/handlers/presence.rs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright (C) 2022-2099 The crate authors.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::component::ComponentTrait;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::presence::PresenceFull;
|
||||||
|
use crate::room::Room;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
use xmpp_parsers::{
|
||||||
|
muc::{
|
||||||
|
user::{Affiliation, Item as MucItem, Reason, Role, Status as MucStatus},
|
||||||
|
Muc, MucUser,
|
||||||
|
},
|
||||||
|
presence::{Presence, Type as PresenceType},
|
||||||
|
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||||
|
BareJid, FullJid,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn handle_presence<C: ComponentTrait>(
|
||||||
|
component: &mut C,
|
||||||
|
presence: Presence,
|
||||||
|
rooms: &mut HashMap<BareJid, Room>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if let Ok(presence) = PresenceFull::try_from(presence.clone()) {
|
||||||
|
match presence.type_ {
|
||||||
|
PresenceType::None => {
|
||||||
|
handle_presence_full_available(component, presence.clone(), rooms).await?
|
||||||
|
}
|
||||||
|
PresenceType::Unavailable => {
|
||||||
|
handle_presence_full_unavailable(component, presence.clone(), rooms).await?
|
||||||
|
}
|
||||||
|
_ => debug!("Unhandled Presence Type: {:?}", presence),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("Unhandled Presence: {:?}", presence)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_presence_full_available<C: ComponentTrait>(
|
||||||
|
component: &mut C,
|
||||||
|
presence: PresenceFull,
|
||||||
|
rooms: &mut HashMap<BareJid, Room>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let realjid: FullJid = presence
|
||||||
|
.from
|
||||||
|
.clone()
|
||||||
|
.map(FullJid::try_from)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let participant: FullJid = presence.to.clone().map(FullJid::try_from).unwrap().unwrap();
|
||||||
|
let roomjid: BareJid = BareJid::from(participant.clone());
|
||||||
|
|
||||||
|
let muc = presence
|
||||||
|
.payloads
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.try_for_each(|payload| match Muc::try_from(payload) {
|
||||||
|
Ok(muc) => ControlFlow::Break(muc),
|
||||||
|
_ => ControlFlow::Continue(()),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: check for features in the Muc element.
|
||||||
|
|
||||||
|
// Room already exists
|
||||||
|
if let Some(room) = rooms.get_mut(&roomjid) {
|
||||||
|
debug!("Presence received to existing room: {}", &roomjid);
|
||||||
|
if let ControlFlow::Break(_) = muc {
|
||||||
|
// <{muc}x/> was found
|
||||||
|
match room.add_session(component, *presence.clone()).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(Error::NickAlreadyAssigned(nick)) => {
|
||||||
|
let error = Presence::new(PresenceType::Error)
|
||||||
|
.with_from(participant.clone())
|
||||||
|
.with_to(realjid.clone())
|
||||||
|
.with_payloads(vec![StanzaError::new(
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::Conflict,
|
||||||
|
"en",
|
||||||
|
format!("Nickname conflict: {}", nick),
|
||||||
|
)
|
||||||
|
.into()]);
|
||||||
|
component.send_stanza(error).await?;
|
||||||
|
}
|
||||||
|
err => err.unwrap(),
|
||||||
|
}
|
||||||
|
} else if let ControlFlow::Continue(_) = muc {
|
||||||
|
// <{muc}x/> wasn't found
|
||||||
|
match room.update_presence(component, *presence.clone()).await {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(Error::ParticipantNotFound(_)) => {
|
||||||
|
let error = Presence::new(PresenceType::Unavailable)
|
||||||
|
.with_from(participant)
|
||||||
|
.with_to(realjid)
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![
|
||||||
|
MucStatus::SelfPresence,
|
||||||
|
MucStatus::Kicked,
|
||||||
|
MucStatus::ServiceErrorKick,
|
||||||
|
],
|
||||||
|
items: {
|
||||||
|
let mut item = MucItem::new(Affiliation::None, Role::None);
|
||||||
|
item.reason =
|
||||||
|
Some(Reason(String::from("You are not in the room.")));
|
||||||
|
vec![item]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into()]);
|
||||||
|
component.send_stanza(error).await?
|
||||||
|
}
|
||||||
|
err => err?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("Presence received to new room: {}", &roomjid);
|
||||||
|
let mut room = Room::new(roomjid.clone());
|
||||||
|
room.add_session(component, *presence.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let _ = rooms.insert(roomjid, room);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_presence_full_unavailable<C: ComponentTrait>(
|
||||||
|
component: &mut C,
|
||||||
|
presence: PresenceFull,
|
||||||
|
rooms: &mut HashMap<BareJid, Room>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let realjid: FullJid = presence
|
||||||
|
.from
|
||||||
|
.clone()
|
||||||
|
.map(FullJid::try_from)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
let participant: FullJid = presence.to.clone().map(FullJid::try_from).unwrap().unwrap();
|
||||||
|
let roomjid: BareJid = BareJid::from(participant.clone());
|
||||||
|
|
||||||
|
let error = Presence::new(PresenceType::Error)
|
||||||
|
.with_from(participant.clone())
|
||||||
|
.with_to(realjid.clone())
|
||||||
|
.with_payloads(vec![StanzaError::new(
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"en",
|
||||||
|
"Occupant does not exist",
|
||||||
|
)
|
||||||
|
.into()]);
|
||||||
|
if let Some(mut room) = rooms.remove(&roomjid) {
|
||||||
|
match room.remove_session(component, *presence.clone()).await {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(Error::NonexistantSession(_)) => {
|
||||||
|
component.send_stanza(error).await.unwrap();
|
||||||
|
}
|
||||||
|
Err(err) => Err(err).unwrap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the room back
|
||||||
|
if !room.occupants.is_empty() {
|
||||||
|
rooms.insert(roomjid, room);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue