diff --git a/src/main.rs b/src/main.rs index a7bf5a5..0b9e917 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,16 +16,13 @@ #![feature(once_cell)] mod error; +mod types; -use crate::error::Error; +use crate::types::{send_stanza, Nick, Room, ROOMS}; -use std::collections::HashMap; -use std::convert::TryFrom; use std::env::args; -use std::iter::IntoIterator; use std::ops::ControlFlow; use std::process::exit; -use std::sync::{LazyLock, Mutex}; use env_logger; use futures::stream::StreamExt; @@ -34,148 +31,14 @@ use tokio_xmpp::Component; use xmpp_parsers::{ disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}, iq::{Iq, IqType}, - message::{Message, Subject, MessageType}, - muc::{ - user::{Affiliation, Item as MucItem, Role, Status as MucStatus}, - Muc, MucUser, - }, + message::Message, + muc::Muc, ns, - presence::{Presence, Type as PresenceType}, + presence::Presence, stanza_error::{DefinedCondition, ErrorType, StanzaError}, - BareJid, Element, FullJid, Jid, + BareJid, Element, Jid, }; -async fn send_stanza>(component: &mut Component, el: E) -> Result<(), Error> { - let el: Element = el.into(); - debug!("SEND: {}", String::from(&el)); - component.send_stanza(el).await?; - Ok(()) -} - -pub type Nick = String; - -#[derive(Debug)] -struct Room { - jid: BareJid, - occupants: HashMap, -} - -impl Room { - fn new(jid: BareJid) -> Self { - Room { - jid, - occupants: HashMap::new(), - } - } - - async fn add_session( - &mut self, - component: &mut Component, - realjid: FullJid, - nick: Nick, - ) -> Result<(), Error> { - let bare = BareJid::from(realjid.clone()); - if let Some(occupant) = self.occupants.get_mut(&bare) { - occupant.add_session(realjid)?; - } else { - debug!("{} is joining {}", realjid, self.jid); - - // Ensure nick isn't already assigned - let _ = self.occupants.iter().try_for_each(|(_, occupant)| { - let nick = nick.clone(); - if occupant.nick == nick { - return Err(Error::NickAlreadyAssigned(nick)); - } - Ok(()) - })?; - - // Send occupants - debug!("Sending occupants for {}", realjid); - let presence = Presence::new(PresenceType::None) - .with_to(realjid.clone()); - for (_, occupant) in self.occupants.iter() { - for session in occupant.iter() { - let presence = presence.clone().with_from(session.clone()); - send_stanza(component, presence).await?; - } - } - - // Add into occupants - let _ = self - .occupants - .insert(bare.clone(), Occupant::new(realjid.clone())); - - // Self-presence - debug!("Sending self-presence for {}", realjid); - let participant: FullJid = self.jid.clone().with_resource(nick); - let status = vec![MucStatus::SelfPresence, MucStatus::AssignedNick]; - let items = vec![MucItem::new(Affiliation::Owner, Role::Moderator)]; - let self_presence = Presence::new(PresenceType::None) - .with_from(participant) - .with_to(realjid.clone()) - .with_payloads(vec![MucUser { status, items }.into()]); - send_stanza(component, self_presence).await?; - - // Send subject - debug!("Sending subject!"); - let mut subject = Message::new(Some(Jid::Full(realjid))); - subject.from = Some(Jid::Bare(self.jid.clone())); - subject.subjects.insert(String::from("en"), Subject(String::from("Hanabi"))); - subject.type_ = MessageType::Groupchat; - send_stanza(component, subject).await?; - } - - Ok(()) - } -} - -#[derive(Debug, Clone)] -struct Occupant { - jid: BareJid, - nick: Nick, - sessions: Vec, -} - -impl Occupant { - fn new(fulljid: FullJid) -> Occupant { - Occupant { - jid: BareJid::from(fulljid.clone()), - nick: fulljid.resource.clone(), - sessions: vec![fulljid], - } - } - - fn add_session(&mut self, fulljid: FullJid) -> Result<(), Error> { - if BareJid::from(fulljid.clone()) != self.jid { - return Err(Error::MismatchJids(Jid::from(fulljid.clone()))); - } - - Ok(()) - } -} - -impl IntoIterator for Occupant { - type Item = FullJid; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.sessions.into_iter() - } -} - -impl Occupant { - fn iter(&self) -> std::slice::Iter<'_, FullJid> { - self.sessions.iter() - } - - fn iter_mut(&mut self) -> std::slice::IterMut<'_, FullJid> { - self.sessions.iter_mut() - } -} - -static mut ROOMS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); - async fn handle_iq_disco(component: &mut Component, iq: Iq, payload: Element) { match DiscoInfoQuery::try_from(payload) { Ok(DiscoInfoQuery { node }) if node.is_none() => { @@ -303,20 +166,3 @@ async fn main() { } } } - -#[cfg(test)] -mod tests { - use super::*; - - use std::str::FromStr; - - use xmpp_parsers::{BareJid, FullJid, Jid}; - - #[test] - fn occupant_ignore_dup_session() { - let fulljid = FullJid::from_str("foo@bar/meh").unwrap(); - let mut occupant = Occupant::new(fulljid.clone()); - occupant.add_session(fulljid.clone()); - assert_eq!(occupant.iter().count(), 1); - } -} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..3d02a68 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,182 @@ +// 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 . + +use crate::error::Error; + +use std::collections::HashMap; +use std::iter::IntoIterator; +use std::sync::{LazyLock, Mutex}; + +use log::debug; +use tokio_xmpp::Component; +use xmpp_parsers::{ + message::{Message, MessageType, Subject}, + muc::{ + user::{Affiliation, Item as MucItem, Role, Status as MucStatus}, + MucUser, + }, + presence::{Presence, Type as PresenceType}, + BareJid, Element, FullJid, Jid, +}; + +pub(crate) async fn send_stanza>( + component: &mut Component, + elem: E, +) -> Result<(), Error> { + let elem: Element = elem.into(); + debug!("SEND: {}", String::from(&elem)); + component.send_stanza(elem).await?; + Ok(()) +} + +pub(crate) type Nick = String; + +#[derive(Debug)] +pub(crate) struct Room { + jid: BareJid, + occupants: HashMap, +} + +impl Room { + pub(crate) fn new(jid: BareJid) -> Self { + Room { + jid, + occupants: HashMap::new(), + } + } + + pub(crate) async fn add_session( + &mut self, + component: &mut Component, + realjid: FullJid, + nick: Nick, + ) -> Result<(), Error> { + let bare = BareJid::from(realjid.clone()); + if let Some(occupant) = self.occupants.get_mut(&bare) { + occupant.add_session(realjid)?; + } else { + debug!("{} is joining {}", realjid, self.jid); + + // Ensure nick isn't already assigned + let _ = self.occupants.iter().try_for_each(|(_, occupant)| { + let nick = nick.clone(); + if occupant.nick == nick { + return Err(Error::NickAlreadyAssigned(nick)); + } + Ok(()) + })?; + + // Send occupants + debug!("Sending occupants for {}", realjid); + let presence = Presence::new(PresenceType::None).with_to(realjid.clone()); + for (_, occupant) in self.occupants.iter() { + for session in occupant.iter() { + let presence = presence.clone().with_from(session.clone()); + send_stanza(component, presence).await?; + } + } + + // Add into occupants + let _ = self + .occupants + .insert(bare.clone(), Occupant::new(realjid.clone())); + + // Self-presence + debug!("Sending self-presence for {}", realjid); + let participant: FullJid = self.jid.clone().with_resource(nick); + let status = vec![MucStatus::SelfPresence, MucStatus::AssignedNick]; + let items = vec![MucItem::new(Affiliation::Owner, Role::Moderator)]; + let self_presence = Presence::new(PresenceType::None) + .with_from(participant) + .with_to(realjid.clone()) + .with_payloads(vec![MucUser { status, items }.into()]); + send_stanza(component, self_presence).await?; + + // Send subject + debug!("Sending subject!"); + let mut subject = Message::new(Some(Jid::Full(realjid))); + subject.from = Some(Jid::Bare(self.jid.clone())); + subject + .subjects + .insert(String::from("en"), Subject(String::from("Hanabi"))); + subject.type_ = MessageType::Groupchat; + send_stanza(component, subject).await?; + } + + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Occupant { + jid: BareJid, + nick: Nick, + sessions: Vec, +} + +impl Occupant { + fn new(fulljid: FullJid) -> Occupant { + Occupant { + jid: BareJid::from(fulljid.clone()), + nick: fulljid.resource.clone(), + sessions: vec![fulljid], + } + } + + pub(crate) fn add_session(&mut self, fulljid: FullJid) -> Result<(), Error> { + if BareJid::from(fulljid.clone()) != self.jid { + return Err(Error::MismatchJids(Jid::from(fulljid.clone()))); + } + + Ok(()) + } +} + +impl IntoIterator for Occupant { + type Item = FullJid; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.sessions.into_iter() + } +} + +impl Occupant { + fn iter(&self) -> std::slice::Iter<'_, FullJid> { + self.sessions.iter() + } + + fn iter_mut(&mut self) -> std::slice::IterMut<'_, FullJid> { + self.sessions.iter_mut() + } +} + +pub(crate) static mut ROOMS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + use xmpp_parsers::FullJid; + + #[test] + fn occupant_ignore_dup_session() { + let fulljid = FullJid::from_str("foo@bar/meh").unwrap(); + let mut occupant = Occupant::new(fulljid.clone()); + occupant.add_session(fulljid.clone()).unwrap(); + assert_eq!(occupant.iter().count(), 1); + } +}