use clap::{crate_version, App, Arg, ArgMatches};
use colored::Colorize;
use ipgeolocate::{Locator, Service};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use dns_lookup::lookup_host;
mod tools;
#[async_std::main]
async fn main() {
let matches = App::new("ipgeo")
.version(crate_version!())
.author("Grant Handy <grantshandy@gmail.com>")
.about("Finds IP Information")
.arg(
Arg::with_name("ADDRESS")
.help("What IP or DNS address to look up, if none are selected then your network IP address will be chosen")
.required(false)
.index(1)
)
.arg(
Arg::with_name("method")
.long("method")
.short("m")
.help("Choose Geolocation API, if not set it defaults to ipapi.")
.required(false)
.takes_value(true)
.value_name("SERVICE")
.possible_values(&["ipwhois", "ipapi", "ipapico", "freegeoip"])
)
.arg(
Arg::with_name("all")
.long("all")
.short("a")
.help("Print all available information")
.required(false)
.takes_value(false)
.conflicts_with("fields")
)
.arg(
Arg::with_name("silent")
.long("silent")
.short("s")
.help("Run without extra output")
.required(false)
.takes_value(false)
)
.arg(
Arg::with_name("horizontal")
.long("horizontal")
.help("Print fields horizontally.")
.required(false)
.takes_value(false)
)
.arg(
Arg::with_name("verbose")
.long("verbose")
.short("v")
.help("Run with verbose output")
.required(false)
.takes_value(false)
.conflicts_with("silent")
)
.arg(
Arg::with_name("fields")
.long("fields")
.short("f")
.help("Choose what fields to print about the IP address.")
.takes_value(true)
.value_name("FIELDS")
.required(false)
.multiple(true)
.possible_values(&["ip", "latitude", "longitude", "city", "region", "country", "timezone", "method", "dns"])
)
.get_matches();
if matches.is_present("horizontal") {
if !matches.is_present("fields") && !matches.is_present("all") {
eprintln!(
"{} The argument '{}' requires '{}' or '{}'\n",
"error:".red().bold(),
"--horizontal".yellow(),
"--fields".yellow(),
"--all".yellow()
);
eprintln!("For more information try {}", "--help".green());
std::process::exit(1);
};
};
let mut ip: String = match matches.value_of("ADDRESS") {
Some(value) => value.to_string(),
None => {
let i = tools::get_network_ip().await;
if matches.is_present("verbose") {
println!("no IP address set, using network IP address \"{}\"", i);
};
i
}
};
let address = ip.clone();
let mut is_dns = false;
if ip.parse::<Ipv4Addr>().is_ok() {
if matches.is_present("verbose") {
println!("detected IPv4 address")
};
} else if ip.parse::<Ipv6Addr>().is_ok() {
if matches.is_present("verbose") {
println!("detected IPv6 address")
};
} else {
if matches.is_present("verbose") {
println!(
"neither ipv4 or ipv6 IP address found, looking \"{}\" up as a DNS address",
ip
);
};
match lookup_host(&ip) {
Ok(data) => {
is_dns = true;
if matches.is_present("verbose") {
println!("DNS lookup for {} successful", ip);
};
for foo in data {
if foo.is_ipv4() | foo.is_ipv6() {
ip = foo.to_string();
continue;
};
}
}
Err(error) => {
eprintln!("can't find any information for \"{}\"", ip);
eprintln!("this probably means that the value for <ADDRESS> is not an IP address or DNS address");
eprintln!("DNS lookup error: {}", error);
std::process::exit(1);
}
};
};
let service: Service = match matches.value_of("method") {
Some(value) => tools::match_method(value),
None => Service::IpApi,
};
match Locator::get(&ip, service).await {
Ok(ip) => print_data(service, matches.clone(), ip, is_dns, &address),
Err(error) => eprintln!("error getting location data: {}", error),
};
}
fn print_data(service: Service, app: ArgMatches, ip: Locator, is_dns: bool, address: &str) {
if app.is_present("fields") {
match app.values_of("fields") {
Some(general_data) => {
for data_type in general_data {
let data_value = match data_type {
"dns" => {
if !is_dns {
tools::get_dns(ip.ip.clone().parse::<IpAddr>().unwrap())
} else {
address.to_string()
}
},
"city" => ip.city.clone(),
"country" => ip.country.clone(),
"ip" => ip.ip.clone(),
"latitude" => ip.latitude.clone(),
"longitude" => ip.longitude.clone(),
"region" => ip.region.clone(),
"timezone" => ip.timezone.clone(),
"method" => service.to_string().clone(),
&_ => String::from("NONE"),
};
print_field(&data_value, data_type, &app);
}
}
None => eprintln!("field interpretation error, unexpected!"),
};
} else {
if app.is_present("all") {
print_all_fields(&app, &ip, service, is_dns, address);
} else {
println!("{} - {} ({})", ip.ip, ip.city, ip.country);
};
};
if app.is_present("horizontal") {
print!("\n");
};
}
fn print_all_fields(app: &ArgMatches, ip: &Locator, service: Service, is_dns: bool, address: &str) {
let data_types = vec![
"ip",
"dns",
"city",
"region",
"country",
"latitude",
"longitude",
"timezone",
"method",
];
for data_type in data_types {
let data_value = match data_type {
"dns" => {
if !is_dns {
tools::get_dns(ip.ip.clone().parse::<IpAddr>().unwrap())
} else {
address.to_string()
}
},
"city" => ip.city.clone(),
"country" => ip.country.clone(),
"ip" => ip.ip.clone(),
"latitude" => ip.latitude.clone(),
"longitude" => ip.longitude.clone(),
"region" => ip.region.clone(),
"timezone" => ip.timezone.clone(),
"method" => service.to_string().clone(),
&_ => String::from("NONE"),
};
print_field(&data_value, data_type, &app);
}
}
fn print_field(data_value: &str, data_type: &str, app: &ArgMatches) {
if app.is_present("silent") {
if app.is_present("horizontal") {
print!("{} ", data_value);
} else {
println!("{}", data_value);
};
} else {
if app.is_present("horizontal") {
print!("{}: {}, ", data_type, data_value);
} else {
println!("{}: {}", data_type, data_value);
};
};
}