Add Homeserver and MXID resolve functions; Make login use MXID in the command line instead of stdin

This commit is contained in:
0xf8 2023-04-12 16:18:38 -04:00
parent 931236b607
commit 69d08d4dfb
Signed by: 0xf8
GPG Key ID: 446580D758689584
4 changed files with 99 additions and 55 deletions

3
Cargo.lock generated
View File

@ -2067,13 +2067,14 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]] [[package]]
name = "scam-police" name = "scam-police"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dirs", "dirs",
"matrix-sdk", "matrix-sdk",
"once_cell", "once_cell",
"rand 0.8.5", "rand 0.8.5",
"reqwest",
"rpassword", "rpassword",
"serde", "serde",
"serde_json", "serde_json",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "scam-police" name = "scam-police"
version = "0.2.0" version = "0.3.0"
edition = "2021" edition = "2021"
authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ] authors = [ "@0xf8:projectsegfau.lt", "@jjj333:pain.agency" ]
@ -12,6 +12,7 @@ dirs = "5.0.0"
matrix-sdk = "0.6.2" matrix-sdk = "0.6.2"
once_cell = "1.17.1" once_cell = "1.17.1"
rand = "0.8.5" rand = "0.8.5"
reqwest = "0.11.16"
rpassword = "7.2.0" rpassword = "7.2.0"
serde = "1.0.160" serde = "1.0.160"
serde_json = "1.0.95" serde_json = "1.0.95"

View File

@ -85,6 +85,8 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args: Vec<String> = std::env::args().collect();
let data_dir = dirs::data_dir() let data_dir = dirs::data_dir()
.expect("no data_dir directory found") .expect("no data_dir directory found")
.join("scam_police"); .join("scam_police");
@ -92,8 +94,10 @@ async fn main() -> anyhow::Result<()> {
let (client, sync_token) = if session_file.exists() { let (client, sync_token) = if session_file.exists() {
matrix::restore_session(&session_file).await? matrix::restore_session(&session_file).await?
} else if args.len() > 1 {
(matrix::login(&data_dir, &session_file, args.get(1).unwrap().to_owned()).await?, None)
} else { } else {
(matrix::login(&data_dir, &session_file).await?, None) anyhow::bail!("No previous session found, please run {} MXID", args.get(1).unwrap());
}; };
let (client, sync_settings) = matrix::sync(client, sync_token) let (client, sync_settings) = matrix::sync(client, sync_token)

View File

@ -3,10 +3,12 @@ use matrix_sdk::{
}; };
use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Value, from_str};
use std::{ use std::{
io::{self, Write}, io::{self, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use reqwest::Client as http;
use tokio::fs; use tokio::fs;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -24,39 +26,19 @@ pub struct FullSession {
sync_token: Option<String>, sync_token: Option<String>,
} }
pub async fn restore_session(session_file: &Path) -> anyhow::Result<(Client, Option<String>)> {
let serialized_session = fs::read_to_string(session_file).await?;
let FullSession {
client_session,
user_session,
sync_token,
} = serde_json::from_str(&serialized_session)?;
let client = Client::builder() //
.homeserver_url(client_session.homeserver) // Matrix Login & Init
.sled_store(client_session.db_path, Some(&client_session.passphrase))? //
.build()
.await?;
println!("[*] Restoring session for {}", user_session.user_id); pub async fn login(data_dir: &Path, session_file: &Path, mxid: String) -> anyhow::Result<Client> {
println!("[*] No previous session, loggin in with mxid...");
client.restore_login(user_session).await?; let (user, hs) = resolve_mxid(mxid).await?;
let (client, client_session) = build_client(data_dir, hs).await?;
Ok((client, sync_token))
}
pub async fn login(data_dir: &Path, session_file: &Path) -> anyhow::Result<Client> {
println!("[*] No previous session found, logging in…");
let (client, client_session) = build_client(data_dir).await?;
loop { loop {
let mut user = String::new(); let password = rpassword::prompt_password("Password\n> ").unwrap();
io::stdout().flush().expect("Unable to write to stdout");
io::stdin()
.read_line(&mut user)
.expect("Unable to read user input");
let password = rpassword::prompt_password("Password> ").unwrap();
match client match client
.login_username(&user, &password) .login_username(&user, &password)
@ -87,8 +69,7 @@ pub async fn login(data_dir: &Path, session_file: &Path) -> anyhow::Result<Clien
Ok(client) Ok(client)
} }
/// Build a new client. pub async fn build_client(data_dir: &Path, hs: String) -> anyhow::Result<(Client, ClientSession)> {
pub async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSession)> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let db_subfolder: String = (&mut rng) let db_subfolder: String = (&mut rng)
@ -104,29 +85,18 @@ pub async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSess
.map(char::from) .map(char::from)
.collect(); .collect();
loop {
let mut homeserver = String::new();
print!("Homeserver> ");
io::stdout().flush().expect("Unable to write to stdout");
io::stdin()
.read_line(&mut homeserver)
.expect("Unable to read user input");
println!("[*] Checking homeserver…");
match Client::builder() match Client::builder()
.homeserver_url(&homeserver) .homeserver_url(&hs)
.sled_store(&db_path, Some(&passphrase)) .sled_store(&db_path, Some(&passphrase))?
.unwrap()
.build() .build()
.await .await
{ {
Ok(client) => { Ok(client) => {
println!("[*] Homeserver OK");
return Ok(( return Ok((
client, client,
ClientSession { ClientSession {
homeserver, homeserver: hs,
db_path, db_path,
passphrase, passphrase,
}, },
@ -136,8 +106,7 @@ pub async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSess
matrix_sdk::ClientBuildError::AutoDiscovery(_) matrix_sdk::ClientBuildError::AutoDiscovery(_)
| matrix_sdk::ClientBuildError::Url(_) | matrix_sdk::ClientBuildError::Url(_)
| matrix_sdk::ClientBuildError::Http(_) => { | matrix_sdk::ClientBuildError::Http(_) => {
println!("[!] Error checking the homeserver: {error}"); anyhow::bail!("[!] {error:?}");
println!("[!] Please try again\n");
} }
_ => { _ => {
return Err(error.into()); return Err(error.into());
@ -145,8 +114,56 @@ pub async fn build_client(data_dir: &Path) -> anyhow::Result<(Client, ClientSess
}, },
} }
} }
//
// Helper Functions
//
// Resolve mxid into user and hs
pub async fn resolve_mxid(mxid: String) -> anyhow::Result<(String, String)> {
if mxid.get(0..1).unwrap() != "@" || !mxid.contains(":") {
anyhow::bail!("Invalid mxid");
} }
let sep = mxid.find(":").unwrap();
let user = mxid.get(1..sep).unwrap().to_string();
let hs = resolve_homeserver(mxid.get((sep + 1)..).unwrap().to_string()).await?;
Ok((user, hs))
}
// Resolve homeserver
pub async fn resolve_homeserver(homeserver: String) -> anyhow::Result<String> {
let mut hs = homeserver;
if !hs.contains("://") {
hs = format!("https://{hs}");
}
if hs.chars().last().unwrap().to_string() == "/" {
hs.pop();
}
let ident = http::new().get(format!("{hs}/.well-known/matrix/client")).send().await;
match ident {
Ok(r) => {
let body = r.text().await?;
let json: Value = from_str(&body)?;
let discovered = json["m.homeserver"]["base_url"].as_str().unwrap();
Ok(discovered.to_string())
},
Err(e) => {
Err(e.into())
}
}
}
//
// Persistence
//
pub async fn sync<'a>( pub async fn sync<'a>(
client: Client, client: Client,
initial_sync_token: Option<String>, initial_sync_token: Option<String>,
@ -187,7 +204,7 @@ pub async fn persist_sync_token(sync_token: String) -> anyhow::Result<()> {
let session_file = data_dir.join("session"); let session_file = data_dir.join("session");
let serialized_session = fs::read_to_string(&session_file).await?; let serialized_session = fs::read_to_string(&session_file).await?;
let mut full_session: FullSession = serde_json::from_str(&serialized_session)?; let mut full_session: FullSession = from_str(&serialized_session)?;
full_session.sync_token = Some(sync_token); full_session.sync_token = Some(sync_token);
let serialized_session = serde_json::to_string(&full_session)?; let serialized_session = serde_json::to_string(&full_session)?;
@ -195,3 +212,24 @@ pub async fn persist_sync_token(sync_token: String) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub async fn restore_session(session_file: &Path) -> anyhow::Result<(Client, Option<String>)> {
let serialized_session = fs::read_to_string(session_file).await?;
let FullSession {
client_session,
user_session,
sync_token,
} = from_str(&serialized_session)?;
let client = Client::builder()
.homeserver_url(client_session.homeserver)
.sled_store(client_session.db_path, Some(&client_session.passphrase))?
.build()
.await?;
println!("[*] Restoring session for {}", user_session.user_id);
client.restore_login(user_session).await?;
Ok((client, sync_token))
}