xmpp/pubsub/
mod.rs

1// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use crate::{
8    Agent, Event, RoomNick,
9    jid::{BareJid, Jid},
10    minidom::Element,
11    muc::room::{JoinRoomSettings, LeaveRoomSettings},
12    parsers::{
13        bookmarks2, ns,
14        pubsub::{self, pubsub::PubSub},
15    },
16};
17
18use std::str::FromStr;
19
20#[cfg(feature = "avatars")]
21pub(crate) mod avatar;
22
23pub(crate) async fn handle_event(
24    #[cfg_attr(not(feature = "avatars"), allow(unused_variables))] from: &Jid,
25    elem: Element,
26    #[cfg_attr(not(feature = "avatars"), allow(unused_variables))] agent: &mut Agent,
27) -> Vec<Event> {
28    // We allow the useless mut warning for no-default-features,
29    // since for now only avatars pushes events here.
30    #[allow(unused_mut)]
31    let mut events = Vec::new();
32
33    let event = pubsub::Event::try_from(elem);
34    trace!("PubSub event: {:#?}", event);
35    match event {
36        Ok(pubsub::Event {
37            payload:
38                pubsub::event::Payload::Items {
39                    node,
40                    published,
41                    retracted,
42                },
43        }) => {
44            match node.0 {
45                #[cfg(feature = "avatars")]
46                ref node if node == ns::AVATAR_METADATA => {
47                    // TODO: Also handle retracted!
48                    let new_events =
49                        avatar::handle_metadata_pubsub_event(&from, agent, published).await;
50                    events.extend(new_events);
51                }
52                ref node if node == ns::BOOKMARKS2 => {
53                    let config = agent.get_config().await;
54                    // TODO: Check that our bare JID is the sender.
55                    if let [item] = &published[..] {
56                        let jid = BareJid::from_str(&item.id.clone().unwrap().0).unwrap();
57                        let payload = item.payload.clone().unwrap();
58                        match bookmarks2::Conference::try_from(payload) {
59                            Ok(conference) => {
60                                if conference.autojoin {
61                                    if !agent.rooms_joined.contains_key(&jid) {
62                                        agent
63                                            .join_room(JoinRoomSettings {
64                                                room: jid,
65                                                nick: conference.nick.map(RoomNick::new),
66                                                password: conference.password,
67                                                status: None,
68                                            })
69                                            .await;
70                                    } else {
71                                        if config.bookmarks_autojoin {
72                                            // So maybe another client of ours left the room... let's leave it too
73                                            agent.leave_room(LeaveRoomSettings::new(jid)).await;
74                                        }
75                                    }
76                                }
77                            }
78                            Err(err) => println!("not bookmark: {}", err),
79                        }
80                    } else if let [item] = &retracted[..] {
81                        if config.bookmarks_autojoin {
82                            let jid = BareJid::from_str(&item.0).unwrap();
83
84                            agent.leave_room(LeaveRoomSettings::new(jid)).await;
85                        }
86                    } else {
87                        error!("No published or retracted item in pubsub event!");
88                    }
89                }
90                ref node => unimplemented!("node {}", node),
91            }
92        }
93        Ok(pubsub::Event {
94            payload: pubsub::event::Payload::Purge { node },
95        }) => match node.0 {
96            ref node if node == ns::BOOKMARKS2 => {
97                warn!("The bookmarks2 PEP node was deleted!");
98            }
99            ref node => unimplemented!("node {}", node),
100        },
101        Err(e) => {
102            error!("Error parsing PubSub event: {}", e);
103        }
104        _ => unimplemented!("PubSub event: {:#?}", event),
105    }
106    events
107}
108
109pub(crate) async fn handle_iq_result(
110    #[cfg_attr(not(feature = "avatars"), allow(unused_variables))] from: &Jid,
111    elem: Element,
112    agent: &mut Agent,
113) -> impl IntoIterator<Item = Event> {
114    // We allow the useless mut warning for no-default-features,
115    // since for now only avatars pushes events here.
116    #[allow(unused_mut)]
117    let mut events = Vec::new();
118
119    let pubsub = PubSub::try_from(elem).unwrap();
120    trace!("PubSub: {:#?}", pubsub);
121    if let PubSub::Items(items) = pubsub {
122        match items.node.0.clone() {
123            #[cfg(feature = "avatars")]
124            ref node if node == ns::AVATAR_DATA => {
125                let new_events = avatar::handle_data_pubsub_iq(&from, &items);
126                events.extend(new_events);
127            }
128            ref node if node == ns::BOOKMARKS2 => {
129                let config = agent.get_config().await;
130                // Keep track of the new added/removed rooms in the bookmarks2 list.
131                // The rooms we joined which are no longer in the list should be left ASAP.
132                let mut new_room_list: Vec<BareJid> = Vec::new();
133
134                for item in items.items {
135                    let jid = BareJid::from_str(&item.id.clone().unwrap().0).unwrap();
136                    let payload = item.payload.clone().unwrap();
137                    match bookmarks2::Conference::try_from(payload) {
138                        Ok(conference) => {
139                            // This room was either marked for join or leave, but it was still in the bookmarks.
140                            // Keep track in new_room_list.
141                            new_room_list.push(jid.clone());
142
143                            if conference.autojoin {
144                                if !agent.rooms_joined.contains_key(&jid) {
145                                    agent
146                                        .join_room(JoinRoomSettings {
147                                            room: jid,
148                                            nick: conference.nick.map(RoomNick::new),
149                                            password: conference.password,
150                                            status: None,
151                                        })
152                                        .await;
153                                }
154                            } else {
155                                if config.bookmarks_autojoin {
156                                    // Leave the room that is no longer autojoin
157                                    agent.leave_room(LeaveRoomSettings::new(jid)).await;
158                                }
159                            }
160                        }
161                        Err(err) => {
162                            warn!("Wrong payload type in bookmarks2 item: {}", err);
163                        }
164                    }
165                }
166
167                if config.bookmarks_autojoin {
168                    // Now we leave the rooms that are no longer in the bookmarks
169                    let mut rooms_to_leave: Vec<BareJid> = Vec::new();
170                    for (room, _nick) in &agent.rooms_joined {
171                        if !new_room_list.contains(&room) {
172                            rooms_to_leave.push(room.clone());
173                        }
174                    }
175
176                    for room in rooms_to_leave {
177                        agent.leave_room(LeaveRoomSettings::new(room)).await;
178                    }
179                }
180            }
181            _ => unimplemented!(),
182        }
183    }
184    events
185}