use clap::{Parser, Subcommand};
use colored::*;
#[cfg(feature = "ddep")]
use mail_sitter::ddep;
use mail_sitter::email;
use mail_sitter::Config;
use std::error::Error;
use std::io::{self, BufRead};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(
name = "Mail Sitter",
author = "Ryan J. Kung",
version = "0.0.1",
about = "A email tool set"
)]
struct Args {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
Init {
#[arg(long)]
email: String,
#[arg(long)]
username: Option<String>,
#[arg(long)]
pwd: String,
#[arg(long)]
smtp: String,
#[arg(long, default_value_t = config_path_default())]
path: String,
},
#[cfg(feature = "ddep")]
Login {
#[arg(long)]
username: String,
#[arg(long, default_value_t = config_path_default())]
path: String,
},
Fetch {
#[arg(short, long, default_value_t = config_path_default())]
config: String,
},
#[cfg(feature = "ddep")]
Address {
#[arg(short, long, default_value_t = config_path_default())]
config: String,
},
}
fn config_path_default() -> String {
dirs::home_dir()
.unwrap()
.join(".mailsitter")
.join("config")
.to_string_lossy()
.to_string()
}
#[cfg(feature = "ddep")]
async fn login_ddep(u: String, config: email::EmailConfig) -> Result<(), Box<dyn Error>> {
let mut client = ddep::Client::new(u.clone(), None, None);
println!("{}", "Getting OTP...".cyan());
if let Err(_) = client.otp(None).await {
println!("{}", "DuckDuckGo thinks you are a bot.".red());
println!(
"{}",
"We need you to verify your identity by logging in once.".yellow()
);
println!("{}",
"Please click [here](https://duckduckgo.com/email/login) to log in to your email, and then return here."
.blue()
);
#[cfg(not(feature = "gui"))]
{
let _ = mail_sitter::utils::browser::open("https://duckduckgo.com/email/login");
println!("{}", "please press Enter to continue...".green());
let stdin = io::stdin();
let _ = stdin.lock().lines().next();
}
println!("{}", "Continuing...".cyan());
}
println!("{}", "Checking latest login email".cyan());
let emails = config
.fetch_until(
"NOT OR OR NOT FROM support@duck.com NOT BODY \"one-time passphrase\" SEEN",
30,
0.5,
)
.await?;
if !emails.is_empty() {
let msg = &emails.last().unwrap().body;
if let Some(otp) = ddep::get_otp_via_mail(msg) {
client.full_login(otp.as_str(), None).await?;
println!("{}", "Got token!".green());
let ddep_path = config_path_default();
let config: ddep::DdConfig = client.into();
config.save(&ddep_path)?;
} else {
println!("{} {:?}", "Failed to parse email,".red(), &msg);
}
}
Ok(())
}
async fn parse_cmd(cmd: Commands) -> Result<(), Box<dyn Error>> {
match cmd {
Commands::Init {
email,
pwd,
smtp,
path,
username,
..
} => {
let config = email::EmailConfig::new(email, pwd, smtp);
email::EmailConfig::save(&config, &path)?;
#[cfg(feature = "ddep")]
{
if let Some(u) = username {
login_ddep(u, config).await?;
}
}
println!("{}", "Configuration initialized successfully!".green());
}
#[cfg(feature = "ddep")]
Commands::Login { username, path } => {
let config: email::EmailConfig = email::EmailConfig::read(&path)?;
login_ddep(username, config).await?;
}
Commands::Fetch { config } => {
let config: email::EmailConfig = email::EmailConfig::read(&config)?;
println!("{}", "Fetching emails...".cyan());
config.fetch_email("NOT SEEN")?;
println!("{}", "Emails fetched successfully!".green());
}
#[cfg(feature = "ddep")]
Commands::Address { config } => {
if let Ok(cfg) = ddep::DdConfig::read(&config) {
let client: ddep::Client = cfg.into();
let addr = client.generate_alias().await?;
println!("{}", "Generated success!\n".green());
println!("{}", format!("{}@duck.com", addr).red());
} else {
println!(
"{}",
"Config of duckduckgo email protection not found!".red()
);
println!(
"{}",
"You can regist the services from https://duckduckgo.com/email/start".blue()
);
println!("{}", "Or set username with init command".blue());
#[cfg(not(feature = "gui"))]
{
println!("{}", "Open browser and visit site? [0]".yellow());
println!(
"{}",
"Setup username of duckduckgo email protection services? [1]".yellow()
);
let stdin = io::stdin();
let input = {
stdin
.lock()
.lines()
.next()
.unwrap_or_else(|| Ok(String::from("")))?
};
match input.trim().to_lowercase().as_str() {
"0" => {
mail_sitter::utils::browser::open(
"https://duckduckgo.com/email/start",
)?;
}
"1" => {
println!("{}", "Your username: \n\n".cyan());
let stdin = io::stdin();
let input = { stdin.lock().lines().next() };
if let Some(Ok(username)) = input {
let username = username.trim();
let email_cfg = email::EmailConfig::read(&config)?;
login_ddep(username.to_string(), email_cfg).await?;
}
}
_ => {
println!("{}", "Exit.".yellow());
}
}
}
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
colored::control::set_override(true);
#[cfg(feature = "gui")]
{
klask::run_derived::<Args, _>(klask::Settings::default(), |o| println!("{:#?}", o));
}
let args = Args::parse();
parse_cmd(args.command).await?;
Ok(())
}