Client sessions are not persisted in main config file any more.
This commit is contained in:
parent
c8c0f6a400
commit
90a863911e
7 changed files with 100 additions and 51 deletions
|
@ -9,6 +9,7 @@ tokio = "1.19.2"
|
||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
serde = "1.0.144"
|
serde = "1.0.144"
|
||||||
serde_yaml = "0.9.10"
|
serde_yaml = "0.9.10"
|
||||||
|
serde_with = "2.0.0"
|
||||||
|
|
||||||
postbus = "0.2.0"
|
postbus = "0.2.0"
|
||||||
mailparse = "0.13.8"
|
mailparse = "0.13.8"
|
||||||
|
|
|
@ -6,7 +6,7 @@ Have a look at the simple and the more advanced configuration file, it should be
|
||||||
|
|
||||||
The (absolute) path to your configuration file must be supplied as command line argument when starting the program.
|
The (absolute) path to your configuration file must be supplied as command line argument when starting the program.
|
||||||
|
|
||||||
Config files may be changed by the program, e.g. to save access tokens. However, they are rotated so you always have access to the previous 9 versions of the file.
|
A directory for client sessions will be created in the directory your config file is in. However, the files in there are rotated so you always have access to the previous 9 versions of the file.
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
Just invite the bot accounts to the rooms they should post in, and enter the room IDs of those rooms in the config file. They will join when the first email arrives.
|
Just invite the bot accounts to the rooms they should post in, and enter the room IDs of those rooms in the config file. They will join when the first email arrives.
|
||||||
|
|
|
@ -20,7 +20,7 @@ clients:
|
||||||
mappings:
|
mappings:
|
||||||
- to: "warning@mydomain.com"
|
- to: "warning@mydomain.com"
|
||||||
sender_mxid: "@secondbot:example.com"
|
sender_mxid: "@secondbot:example.com"
|
||||||
room_id: "!idofWARNINGroomyoucreatedbefore:example.com"
|
room: "!idofWARNINGroomyoucreatedbefore:example.com"
|
||||||
- to: "critical@mydomain.com"
|
- to: "critical@mydomain.com"
|
||||||
sender_mxid: "@firstbot:example.com"
|
sender_mxid: "@firstbot:example.com"
|
||||||
room_id: "#IMPORTANTroomyoucreatedbefore:example.com"
|
room: "#IMPORTANTroomyoucreatedbefore:example.com"
|
|
@ -1,6 +1,6 @@
|
||||||
# This config file will make matrixmailer start an SMTP server on localhost:25
|
# This config file will make matrixmailer start an SMTP server on localhost:25
|
||||||
# and forward any mail sent to the email address "yourbot@example.com" that has been received
|
# and forward any mail sent to the email address "yourbot@example.com" that has been received
|
||||||
# to the room specified in room_id, using the matrix account specified.
|
# to the room specified in room, using the matrix account specified.
|
||||||
# All other mail will be received, but ignored.
|
# All other mail will be received, but ignored.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -8,4 +8,4 @@ clients:
|
||||||
- mxid: "@yourbot:example.com"
|
- mxid: "@yourbot:example.com"
|
||||||
password: "p@ssw0rd"
|
password: "p@ssw0rd"
|
||||||
mappings:
|
mappings:
|
||||||
- room_id: "!idoftheroomyoucreatedbefore:example.com"
|
- room: "!idoftheroomyoucreatedbefore:example.com"
|
|
@ -3,6 +3,15 @@ use std::{fs, path::Path};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
// Pulled from configuration file
|
||||||
|
pub main: MainConfig,
|
||||||
|
// Pulled from auto-created yaml files
|
||||||
|
pub client_sessions: Vec<ClientSession>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde_with::skip_serializing_none]
|
||||||
|
pub struct MainConfig {
|
||||||
pub bind_address: Option<String>,
|
pub bind_address: Option<String>,
|
||||||
pub bind_port: Option<String>,
|
pub bind_port: Option<String>,
|
||||||
pub client_storage_path: Option<String>,
|
pub client_storage_path: Option<String>,
|
||||||
|
@ -15,21 +24,26 @@ pub struct Config {
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
pub mxid: String,
|
pub mxid: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub device_id: Option<String>,
|
|
||||||
pub access_token: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct ClientSession {
|
||||||
|
pub mxid: String,
|
||||||
|
pub device_id: String,
|
||||||
|
pub access_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct Mapping {
|
pub struct Mapping {
|
||||||
pub from: Option<String>,
|
pub from: Option<String>,
|
||||||
pub to: Option<String>,
|
pub to: Option<String>,
|
||||||
pub mxid_sender: Option<String>,
|
pub mxid_sender: Option<String>,
|
||||||
pub room_id: String,
|
pub room: String,
|
||||||
pub room_alias: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(file_path: &str) -> Config {
|
pub fn load_config(file_path: &str) -> Config {
|
||||||
let yaml_str = fs::read_to_string(file_path).expect("Error while reading config file");
|
let yaml_config = fs::read_to_string(file_path).expect("Error while reading config file");
|
||||||
let mut cfg: Config = serde_yaml::from_str(&yaml_str).expect("Config file not in expected format");
|
let mut cfg: MainConfig = serde_yaml::from_str(&yaml_config).expect("Config file not in expected format");
|
||||||
|
|
||||||
// Set default values if necessary
|
// Set default values if necessary
|
||||||
if cfg.bind_address.is_none() {
|
if cfg.bind_address.is_none() {
|
||||||
|
@ -43,21 +57,30 @@ pub fn load_config(file_path: &str) -> Config {
|
||||||
cfg.client_storage_path = Some(path.to_str().unwrap().to_string());
|
cfg.client_storage_path = Some(path.to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg
|
let sessions: Vec<ClientSession> = match fs::read_to_string(&format!("{}.sessions/sessions.yaml", file_path)) {
|
||||||
|
Ok(string) => serde_yaml::from_str(&string).expect("Sessions file not in expected format"),
|
||||||
|
Err(e) => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
Config {main: cfg, client_sessions: sessions}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_config(config: &Config, old_config: &Config, file_path: &str) {
|
pub fn write_client_sessions(new: &Vec<ClientSession>, old: &Vec<ClientSession>, config_file_path: &str) {
|
||||||
let new_yaml_str = serde_yaml::to_string(config).expect("Failed to serialize new config.");
|
let new_yaml_str = serde_yaml::to_string(new).expect("Failed to serialize new config.");
|
||||||
let old_yaml_str = serde_yaml::to_string(old_config).expect("Failed to serialize old config.");
|
let old_yaml_str = serde_yaml::to_string(old).expect("Failed to serialize old config.");
|
||||||
if new_yaml_str != old_yaml_str {
|
if new_yaml_str != old_yaml_str {
|
||||||
rotate_configs(file_path);
|
rotate_client_sessions(config_file_path);
|
||||||
fs::write(file_path, &new_yaml_str).expect("Error writing new config to file");
|
fs::write(&format!("{}.sessions/sessions.yaml", config_file_path), &new_yaml_str).expect("Error writing new config to file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate_configs(base_path: &str) {
|
fn rotate_client_sessions(base_path: &str) {
|
||||||
|
match std::fs::create_dir(format!("{}.sessions", base_path)) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_e) => (),
|
||||||
|
};
|
||||||
for i in 9..2 {
|
for i in 9..2 {
|
||||||
std::fs::copy(&format!("{}.{}", base_path, i - 1), &format!("{}.{}", base_path, i)).expect("");
|
std::fs::copy(&format!("{}.sessions/sessions.yaml.{}", base_path, i - 1), &format!("{}.{}", base_path, i)).expect("");
|
||||||
}
|
}
|
||||||
std::fs::copy(base_path, &format!("{}.1", base_path)).expect("");
|
std::fs::copy(base_path, &format!("{}.1", base_path)).expect("");
|
||||||
}
|
}
|
|
@ -18,13 +18,13 @@ async fn main() {
|
||||||
let mut config = crate::config::load_config(&args[1]);
|
let mut config = crate::config::load_config(&args[1]);
|
||||||
let immutable_config = config.clone();
|
let immutable_config = config.clone();
|
||||||
let clients = matrix::get_clients(&mut config).await;
|
let clients = matrix::get_clients(&mut config).await;
|
||||||
crate::config::write_config(&config, &immutable_config, &args[1]);
|
crate::config::write_client_sessions(&config.client_sessions, &immutable_config.client_sessions, &args[1]);
|
||||||
|
|
||||||
let service = SmtpService::create(format!("{}:{}", config.bind_address.unwrap(), config.bind_port.unwrap()).parse().unwrap(), "matrixmailer".into(),
|
let service = SmtpService::create(format!("{}:{}", config.main.bind_address.unwrap(), config.main.bind_port.unwrap()).parse().unwrap(), "matrixmailer".into(),
|
||||||
Arc::new(ToMatrixConverter {
|
Arc::new(ToMatrixConverter {
|
||||||
allowed_receivers: config.allowed_receivers,
|
allowed_receivers: config.main.allowed_receivers,
|
||||||
matrix_clients: clients,
|
matrix_clients: clients,
|
||||||
mappings: config.mappings,
|
mappings: config.main.mappings,
|
||||||
}));
|
}));
|
||||||
service.listen().await;
|
service.listen().await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use matrix_sdk::{Client, ruma::{UserId, RoomId, events::{room::message::RoomMessageEventContent}}, Session, room::Room, config::SyncSettings};
|
use matrix_sdk::{Client, ruma::{UserId, RoomId, events::{room::message::RoomMessageEventContent}}, Session, room::Room, config::SyncSettings};
|
||||||
use ruma::OwnedRoomAliasId;
|
use ruma::OwnedRoomAliasId;
|
||||||
use postbus::command::Mailbox;
|
use postbus::command::Mailbox;
|
||||||
use crate::config::{Mapping, Config};
|
use crate::config::{Mapping, Config, ClientSession};
|
||||||
|
|
||||||
pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
let mut clients: Vec<Client> = vec![];
|
let mut clients: Vec<Client> = vec![];
|
||||||
|
|
||||||
let mut room_aliases_resolved = false;
|
let mut room_aliases_resolved = false;
|
||||||
// Try to build a matrix_sdk::Client object for each configured client
|
// Try to build a matrix_sdk::Client object for each configured client
|
||||||
for conf in &mut config.clients {
|
for conf in &mut config.main.clients {
|
||||||
let user_id = match UserId::parse(&conf.mxid) {
|
let user_id = match UserId::parse(&conf.mxid) {
|
||||||
Ok(id) => id,
|
Ok(id) => id,
|
||||||
Err(_e) => continue,
|
Err(_e) => continue,
|
||||||
|
@ -18,13 +18,11 @@ pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
Err(_e) => continue,
|
Err(_e) => continue,
|
||||||
};
|
};
|
||||||
// Attempt to restore a previous login
|
// Attempt to restore a previous login
|
||||||
if let Some(token) = &conf.access_token {
|
if let Some(ClientSession {access_token, device_id, ..}) = get_session(&config.client_sessions, &conf.mxid) {
|
||||||
if let Some(dev_id) = &conf.device_id {
|
match client.restore_login(Session { access_token, user_id: (&user_id).to_owned(), device_id: device_id.into()}).await {
|
||||||
match client.restore_login(Session { access_token: token.to_string(), user_id: (&user_id).to_owned(), device_id: dev_id.as_str().into()}).await {
|
Ok(_) => (),
|
||||||
Ok(_) => (),
|
Err(_e) => continue,
|
||||||
Err(_e) => continue,
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// If login restoration did not work, log in using the password
|
// If login restoration did not work, log in using the password
|
||||||
if !client.logged_in().await {
|
if !client.logged_in().await {
|
||||||
|
@ -33,7 +31,7 @@ pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
_ => match client.login(
|
_ => match client.login(
|
||||||
user_id,
|
user_id,
|
||||||
&conf.password,
|
&conf.password,
|
||||||
(&conf).device_id.as_deref(),
|
None,
|
||||||
Some("matrixmailer")
|
Some("matrixmailer")
|
||||||
).await {
|
).await {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
|
@ -43,15 +41,14 @@ pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
}
|
}
|
||||||
// If client is logged in, update the config to persist the session
|
// If client is logged in, update the config to persist the session
|
||||||
if let Some(session) = client.session().await {
|
if let Some(session) = client.session().await {
|
||||||
conf.access_token = Some(session.access_token);
|
write_session(&mut config.client_sessions, session);
|
||||||
conf.device_id = Some(session.device_id.to_string());
|
|
||||||
}
|
}
|
||||||
// If room aliases are supplied as room_id in the config file, resolve them and store the pairs properly
|
// If room aliases are supplied as "room", resolve them and replace them with roomID's
|
||||||
if !room_aliases_resolved && client.logged_in().await {
|
if !room_aliases_resolved && client.logged_in().await {
|
||||||
for mapping in &mut config.mappings {
|
for mapping in &mut config.main.mappings {
|
||||||
if mapping.room_id.as_str().chars().nth(0).unwrap() == '#' || mapping.room_id.len() == 0 {
|
if mapping.room.as_str().chars().nth(0).unwrap() == '#' {
|
||||||
// TODO: Clean this up once matrix-rust-sdk 0.6.0 is available
|
// TODO: Clean this up once matrix-rust-sdk 0.6.0 is available
|
||||||
let alias: OwnedRoomAliasId = match OwnedRoomAliasId::try_from(mapping.room_id.as_str()) {
|
let alias: OwnedRoomAliasId = match OwnedRoomAliasId::try_from(mapping.room.as_str()) {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(_e) => continue,
|
Err(_e) => continue,
|
||||||
};
|
};
|
||||||
|
@ -59,8 +56,7 @@ pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
match client.send(resolve_request, None).await {
|
match client.send(resolve_request, None).await {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let matrix_sdk::ruma::api::client::alias::get_alias::v3::Response{room_id, ..} = result;
|
let matrix_sdk::ruma::api::client::alias::get_alias::v3::Response{room_id, ..} = result;
|
||||||
mapping.room_alias = Some(mapping.room_id.clone());
|
mapping.room = room_id.to_string();
|
||||||
mapping.room_id = room_id.to_string();
|
|
||||||
},
|
},
|
||||||
Err(_e) => (),
|
Err(_e) => (),
|
||||||
};
|
};
|
||||||
|
@ -75,15 +71,6 @@ pub async fn get_clients(config: &mut Config) -> Vec<Client> {
|
||||||
clients
|
clients
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn client_from_mxid(mxid: &str, clients: &Vec<Client>) -> Option<Client> {
|
|
||||||
for client in clients {
|
|
||||||
if client.user_id().await.unwrap().as_str() == mxid {
|
|
||||||
return Some(client.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send(from_mail: &Option<Mailbox>, recipient: &Mailbox, plain_content: &str, html_content: &str, clients: &Vec<Client>, mappings: &Vec<Mapping>) {
|
pub async fn send(from_mail: &Option<Mailbox>, recipient: &Mailbox, plain_content: &str, html_content: &str, clients: &Vec<Client>, mappings: &Vec<Mapping>) {
|
||||||
for mapping in mappings {
|
for mapping in mappings {
|
||||||
let mut applies = true;
|
let mut applies = true;
|
||||||
|
@ -112,7 +99,7 @@ pub async fn send(from_mail: &Option<Mailbox>, recipient: &Mailbox, plain_conten
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if let Some(c) = client_from_mxid(&sender_mxid, clients).await {
|
if let Some(c) = client_from_mxid(&sender_mxid, clients).await {
|
||||||
if let Ok(roomid) = RoomId::parse(&mapping.room_id) {
|
if let Ok(roomid) = RoomId::parse(&mapping.room) {
|
||||||
match c.sync_once(SyncSettings::default()).await {
|
match c.sync_once(SyncSettings::default()).await {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_e) => (),
|
Err(_e) => (),
|
||||||
|
@ -162,4 +149,42 @@ pub async fn send(from_mail: &Option<Mailbox>, recipient: &Mailbox, plain_conten
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn client_from_mxid(mxid: &str, clients: &Vec<Client>) -> Option<Client> {
|
||||||
|
for client in clients {
|
||||||
|
if client.user_id().await.unwrap().as_str() == mxid {
|
||||||
|
return Some(client.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_session(sessions: &Vec<ClientSession>, mxid: &str) -> Option<ClientSession> {
|
||||||
|
for s in sessions {
|
||||||
|
if s.mxid == mxid {
|
||||||
|
return Some(s.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn write_session(all_sessions: &mut Vec<ClientSession>, new: Session) {
|
||||||
|
match get_session(all_sessions, &new.user_id.to_string()) {
|
||||||
|
// If session had already been saved, update it.
|
||||||
|
Some(s) => for s in all_sessions {
|
||||||
|
if s.mxid == new.user_id.to_string() {
|
||||||
|
s.access_token = new.access_token;
|
||||||
|
s.device_id = new.device_id.to_string();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// If session did not exist before, add it to the list
|
||||||
|
None => all_sessions.push(ClientSession {
|
||||||
|
mxid: new.user_id.to_string(),
|
||||||
|
access_token: new.access_token,
|
||||||
|
device_id: new.device_id.to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue