pass_it_on/endpoints/
matrix.rs1mod common;
23mod notify;
24pub(crate) mod verify;
25
26use crate::endpoints::matrix::common::{login, print_client_debug, ClientInfo, PersistentSession};
27use crate::endpoints::matrix::notify::{process_rooms, send_messages};
28use crate::endpoints::{Endpoint, EndpointConfig};
29use crate::notifications::{Key, ValidatedNotification};
30use crate::Error;
31use async_trait::async_trait;
32use tracing::{error, info};
33use serde::Deserialize;
34use std::any::Any;
35use std::collections::{HashMap, HashSet};
36use std::path::PathBuf;
37use tokio::sync::broadcast::Receiver;
38use tokio::sync::watch;
39
40#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone)]
42pub(crate) struct MatrixConfigFile {
43 home_server: String,
44 username: String,
45 password: String,
46 session_store_path: String,
47 recovery_passphrase: String,
48 room: Vec<MatrixRoomConfigFile>,
49}
50
51#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone)]
52pub(crate) struct MatrixRoomConfigFile {
53 room: String,
54 notifications: Vec<String>,
55}
56
57#[derive(Debug, Clone)]
59pub struct MatrixEndpoint {
60 home_server: String,
61 username: String,
62 password: String,
63 session_store_path: PathBuf,
64 recovery_passphrase: String,
65 rooms: Vec<MatrixRoom>,
66}
67
68#[derive(Debug, Clone)]
70pub struct MatrixRoom {
71 room: String,
72 notifications: HashSet<String>,
73}
74
75impl MatrixConfigFile {
76 fn rooms(&self) -> HashMap<String, HashSet<String>> {
77 let mut room_map: HashMap<String, HashSet<String>> = HashMap::new();
78 for room in &self.room {
79 match room_map.get(room.room.as_str()) {
80 None => room_map.insert(room.room.to_string(), room.notifications()),
81 Some(notifications) => {
82 let new_notifications = room.notifications();
83 let union: HashSet<_> = new_notifications.union(notifications).collect();
84 let union: HashSet<_> = union.into_iter().map(|s| s.to_string()).collect();
85 room_map.insert(room.room.to_string(), union)
86 }
87 };
88 }
89 room_map
90 }
91}
92
93impl MatrixRoomConfigFile {
94 fn notifications(&self) -> HashSet<String> {
95 let notifications: HashSet<_> = self.notifications.clone().into_iter().collect();
96 notifications
97 }
98}
99
100#[typetag::deserialize(name = "matrix")]
101impl EndpointConfig for MatrixConfigFile {
102 fn to_endpoint(&self) -> Result<Box<dyn Endpoint + Send>, Error> {
103 Ok(Box::new(MatrixEndpoint::try_from(self)?))
104 }
105}
106
107impl MatrixEndpoint {
108 pub fn new<S: AsRef<str>>(
110 home_server: S,
111 username: S,
112 password: S,
113 session_store_path: S,
114 recovery_passphrase: S,
115 rooms: Vec<MatrixRoom>,
116 ) -> Self {
117 let home_server = home_server.as_ref().into();
118 let username = username.as_ref().into();
119 let password = password.as_ref().into();
120 let session_store_path = PathBuf::from(session_store_path.as_ref());
121 let recovery_passphrase = recovery_passphrase.as_ref().into();
122 Self { home_server, username, password, session_store_path, recovery_passphrase, rooms }
123 }
124
125 pub fn home_server(&self) -> &str {
127 &self.home_server
128 }
129
130 pub fn username(&self) -> &str {
132 &self.username
133 }
134
135 pub fn password(&self) -> &str {
137 &self.password
138 }
139
140 pub fn session_store_path(&self) -> &PathBuf {
142 &self.session_store_path
143 }
144
145 pub fn recovery_passphrase(&self) -> &str {
147 &self.recovery_passphrase
148 }
149
150 pub fn rooms(&self) -> &[MatrixRoom] {
152 &self.rooms
153 }
154}
155
156impl TryFrom<&MatrixConfigFile> for MatrixEndpoint {
157 type Error = Error;
158
159 fn try_from(value: &MatrixConfigFile) -> Result<Self, Self::Error> {
160 if value.home_server.is_empty() {
161 return Err(Error::InvalidEndpointConfiguration("Matrix configuration home_server is blank".to_string()));
162 }
163
164 if value.username.is_empty() {
165 return Err(Error::InvalidEndpointConfiguration("Matrix configuration username is blank".to_string()));
166 }
167
168 if value.room.is_empty() {
169 return Err(Error::InvalidEndpointConfiguration("Matrix configuration has no rooms setup".to_string()));
170 }
171
172 let rooms = {
173 let mut rooms: Vec<_> = Vec::new();
174 for (room, notifications) in value.rooms() {
175 rooms.push(MatrixRoom::new(room, notifications));
176 }
177 rooms
178 };
179
180 Ok(MatrixEndpoint::new(
181 value.home_server.as_str(),
182 value.username.as_str(),
183 value.password.as_str(),
184 value.session_store_path.as_str(),
185 value.recovery_passphrase.as_str(),
186 rooms,
187 ))
188 }
189}
190
191impl MatrixRoom {
192 pub fn new(room: String, notifications: HashSet<String>) -> Self {
194 Self { room, notifications }
195 }
196
197 pub fn room(&self) -> &str {
199 &self.room
200 }
201
202 pub fn notifications(&self) -> &HashSet<String> {
204 &self.notifications
205 }
206}
207
208#[async_trait]
209impl Endpoint for MatrixEndpoint {
210 async fn notify(
211 &self,
212 endpoint_rx: Receiver<ValidatedNotification>,
213 shutdown: watch::Receiver<bool>,
214 ) -> Result<(), Error> {
215 let client_info = ClientInfo::try_from(self)?;
217 info!(
218
219 "Setting up Endpoint: Matrix -> User {} on {}",
220 client_info.username(),
221 client_info.homeserver()
222 );
223 let client = login(client_info.clone()).await?;
224
225 print_client_debug(&client).await;
226 let room_list = process_rooms(&client, self.rooms()).await;
227
228 tokio::spawn(async move {
230 let sync_token = send_messages(endpoint_rx, shutdown.clone(), room_list, &client).await;
231 let persist =
232 PersistentSession::new(&client_info, &client.matrix_auth().session().unwrap(), Some(sync_token));
233 if let Err(error) = persist.save_session() {
234 error!("{}", error)
235 }
236 });
237
238 Ok(())
239 }
240
241 fn generate_keys(&self, hash_key: &Key) -> HashMap<String, HashSet<Key>> {
242 let mut keys: HashMap<String, HashSet<Key>> = HashMap::new();
243
244 for room in self.rooms() {
245 let mut room_keys = HashSet::new();
246 for notification_name in room.notifications() {
247 room_keys.insert(Key::generate(notification_name, hash_key));
248 }
249 keys.insert(room.room().to_string(), room_keys);
250 }
251 keys
252 }
253
254 fn as_any(&self) -> &dyn Any {
255 self
256 }
257}