Skip to content

Commit

Permalink
new csv features
Browse files Browse the repository at this point in the history
  • Loading branch information
boringthegod committed Feb 20, 2024
1 parent d06a31b commit 84b40f5
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 30 deletions.
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
[package]
name = "azure_email_checker"
version = "0.1.0"
version = "0.1.1"
edition = "2021"

[[bin]]
name = "azure_email_checker"
path = "src/main.rs"

[dependencies]
clap = { version = "4.5.1", features = ["derive"] }
reqwest = { version = "0.11", features = ["json"] }
regex = "1"
tokio = { version = "1", features = ["full"] }
futures = "0.3"
colored = "2.0"
colored = "2.0"
csv = "1.3.0"
103 changes: 75 additions & 28 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use clap::Parser;
use regex::Regex;
use reqwest::Client;
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufReader, AsyncBufReadExt};
use tokio::io::{BufReader, AsyncBufReadExt};
use std::path::Path;
use std::sync::Arc;
use futures::future::join_all;
Expand All @@ -12,7 +12,7 @@ use clap::CommandFactory;

/// Validates email addresses with Azure / Office 365 without submitting connection attempts.
#[derive(Parser, Debug)]
#[command(name = "AzureEmailChecker", version = "1.0", author = "boring", about = "Checks whether an email is valid or not on Microsoft / Azure")]
#[command(name = "AzureEmailChecker", version = "1.1", author = "boring", about = "Checks whether an email is valid or not on Microsoft / Azure")]
struct Args {
/// Email address to be validated.
#[arg(short, long)]
Expand All @@ -29,11 +29,17 @@ struct Args {
/// Enables 'VALID' and 'INVALID' results to be displayed in the terminal.
#[arg(short, long, action = clap::ArgAction::SetTrue)]
verbose: bool,

/// Output CSV file for valid addresses
#[arg(short = 'c', long = "csv")]
csv: Option<String>,
}

#[tokio::main]
async fn main() {
let args = Args::parse();
let is_single_check = args.email.is_some();
let email_clone = args.email.clone();
if args.email.is_none() && args.file.is_none() {
Args::command().print_help().expect("Failed to print help");
println!();
Expand All @@ -42,7 +48,6 @@ async fn main() {
let client = Arc::new(Client::new());
let regex_valid = Regex::new(r#""IfExistsResult":0"#).unwrap();
let regex_invalid = Regex::new(r#""IfExistsResult":1"#).unwrap();
let valid_emails_count = Arc::new(AtomicUsize::new(0));
let total_emails = Arc::new(AtomicUsize::new(0));

let mut tasks = vec![];
Expand All @@ -54,71 +59,88 @@ async fn main() {
email,
regex_valid.clone(),
regex_invalid.clone(),
args.output.clone(),
args.verbose,
valid_emails_count.clone(),
is_single_check,
)));
} else if let Some(file_path) = args.file {
let lines = read_lines(&file_path).await.expect("Failed to read lines");
total_emails.fetch_add(lines.len(), Ordering::SeqCst);
for email in lines {
tasks.push(tokio::spawn(check_email(
client.clone(),
email,
email,
regex_valid.clone(),
regex_invalid.clone(),
args.output.clone(),
args.verbose,
valid_emails_count.clone(),
is_single_check,
)));
}
}

join_all(tasks).await;
let results = join_all(tasks).await;

let valid_emails: Vec<String> = results.into_iter()
.filter_map(|task_result| {
if let Ok(Ok(Some(email))) = task_result {
Some(email)
} else {
None
}
})
.collect();

if let Some(csv_path) = args.csv {
write_to_csv(&csv_path, &valid_emails).await.expect("Failed to write to CSV");
}

let valid_emails_count = valid_emails.len();

println!("Validation completed: {} valid emails out of {} processed. Results saved to '{}'", valid_emails_count.load(Ordering::SeqCst), total_emails.load(Ordering::SeqCst), args.output.unwrap_or_else(|| "No file specified".to_string()));
if let Some(output_path) = args.output.as_ref() {
write_to_text(output_path, &valid_emails).await.expect("Failed to write to text file");
}


if is_single_check {
if let Some(email) = email_clone {
println!("Checking completed for: {}", email);
}
} else {
println!("Validation completed: {} valid emails out of {} processed. Results saved to '{}'", valid_emails_count, total_emails.load(Ordering::SeqCst), args.output.unwrap_or_else(|| "No file specified".to_string()));
}
}

async fn check_email(
client: Arc<Client>,
email: String,
regex_valid: Regex,
regex_invalid: Regex,
output_path: Option<String>,
verbose: bool,
valid_emails_count: Arc<AtomicUsize>,
) {
is_single_check: bool,
) -> Result<Option<String>, Box<dyn std::error::Error + Send>> {
let url = "https://login.microsoftonline.com/common/GetCredentialType";
let body = format!(r#"{{"Username":"{}"}}"#, email);
let res = client.post(url)
.header("Content-Type", "application/json")
.body(body)
.send()
.await
.expect("Failed to send request")
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?
.text()
.await
.expect("Failed to read response text");
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;

if regex_invalid.is_match(&res) {
if verbose {
if verbose || is_single_check {
println!("{} - {}", email, "INVALID".red());
}
Ok(None)
} else if regex_valid.is_match(&res) {
if verbose {
if verbose || is_single_check {
println!("{} - {}", email, "VALID".green());
}
valid_emails_count.fetch_add(1, Ordering::SeqCst);
if let Some(path) = &output_path {
let mut file = tokio::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.await
.expect("Failed to open output file");
let content = format!("{}\n", email);
file.write_all(content.as_bytes()).await.expect("Failed to write to file");
}
Ok(Some(email))
} else {
Ok(None)
}
}

Expand All @@ -134,3 +156,28 @@ async fn read_lines<P: AsRef<Path>>(filename: P) -> Result<Vec<String>, std::io:

Ok(lines)
}

async fn write_to_csv(path: &str, emails: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let mut wtr = csv::Writer::from_path(path)?;

for (index, email) in emails.iter().enumerate() {
wtr.write_record(&[&index.to_string(), email])?;
}

wtr.flush()?;
Ok(())
}

async fn write_to_text(path: &str, emails: &[String]) -> Result<(), Box<dyn std::error::Error>> {
use tokio::io::{AsyncWriteExt, BufWriter};
let file = File::create(path).await?;
let mut writer = BufWriter::new(file);

for email in emails {
writer.write_all(email.as_bytes()).await?;
writer.write_all(b"\n").await?;
}

writer.flush().await?;
Ok(())
}

0 comments on commit 84b40f5

Please sign in to comment.