package net.nulll.uso.iPAVGeoGrabber;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import net.nulll.uso.iPAddressViewer.LogItem;
import net.nulll.uso.iPAddressViewer.Msg;
import net.nulll.uso.iPAddressViewer.Uso;
import net.nulll.uso.iPAddressViewer.iPAddressViewer;
import org.bukkit.Bukkit;
public class GeoData {
String city = null;
String state = null;
String country = null;
GeoData(final LogItem logItem, final String addressString){
//GeoData(final LogItem logItem, final String address){//For testing specific addresses
//final String addressString = "8.8.8.8";//For testing specific addresses
//Create a list of data sources that haven't exceeded their limits
List<DataSource> availableSourceList = new ArrayList<DataSource>();
long time = new Date().getTime();
for(DataSource source : iPAVGeoGrabber.dataSources){
//If the data source's session has ended, reset it's query count and failures, then start a new session
if(source.getSessionTime() + source.getSessionDuration() < time){
source.setCurrentQueries(0);
source.setFailures(0);
source.updateSessionTime(time);
}
//If the source hasn't been queried too much within its current session, make it available on the source list
if(source.getCurrentQueries() < source.getMaxQueries() || source.getMaxQueries() < 0){
availableSourceList.add(source);
}
}
//Create an ordered list of data sources by using their failure count
//If a source is failing to give the geo data, it will fall down the list and allow other sources to be queried first instead
final List<DataSource> orderedSourceList = new ArrayList<DataSource>();
for(DataSource source : availableSourceList){
if(orderedSourceList.size()==0){orderedSourceList.add(source); continue;}
for(int i = 0; i < orderedSourceList.size(); i++){
if(source.getFailures() < orderedSourceList.get(i).getFailures()){
orderedSourceList.add(i, source);
break;
}else if(i==orderedSourceList.size()-1){
orderedSourceList.add(source);
break;
}
}
}
//Create an asynchronous task so that the main server thread isn't held up while the data is being acquired from the web source
Bukkit.getScheduler().runTaskAsynchronously(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
//Loop through all of the data sources. Hopefully the first source (which has the lowest failure count) will be successful
//If it isn't, the next source will be queried
for(final DataSource source : orderedSourceList){
//Catch any errors that occur while downloading from the current source and searching for geo data on it
//That way the search continues and the other sources can be queried too
try{
boolean validAddress = false;
//The website is about to be downloaded from, so the query count must be increased
//Downloading too much from one site in a certain time-frame (session) might make them blacklist your server address from asking them for info
source.setCurrentQueries(source.getCurrentQueries()+1);
//Schedule a task to save the data source statistics in the main server thread. Asynchronously saving can corrupt the data file.
Bukkit.getScheduler().runTask(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
//Use a saving utility that iPAV has in order to save iPAVGeoGrabber's data file
Uso.saveYaml(iPAVGeoGrabber.geoStats, iPAddressViewer.staticPlugin.getDataFolder() + "/geoSourceStats.ipav");
}});
//Build the website URL to access, open the connection, set the download fail time to 5 seconds, pretend we're a web browser, then start reading
URL lookupURL = new URL(source.getURLWithAddress(addressString));
URLConnection connection = lookupURL.openConnection();
connection.setReadTimeout(5000);
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 (.NET CLR 3.5.30729)");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line = in.readLine();
while(line != null){
//Check the source's yamlKey so that we know which website we're downloading from. That way we know where to get the data we want on their site
if(source.yamlKey.equals("ipTracker")){
if(line.toLowerCase().contains("country")){
validAddress = true;
}
if(line.contains("<th>Country:</th>")){
String[] contentArray = line.split("<|>");
for(int i = 0; i < contentArray.length; i++){
if(contentArray[i].contains("Country:")){
if(i+4 < contentArray.length){
country = contentArray[i+4].replace(" ", "").trim();
}
}else if(contentArray[i].contains("State:")){
if(i+4 < contentArray.length){
state = contentArray[i+4].trim();
}
}else if(contentArray[i].contains("City Location:")){
if(i+4 < contentArray.length){
city = contentArray[i+4].trim();
}
}
if(country != null && state != null && city != null){
break;
}
}
}
}else if(source.yamlKey.equals("whatismyipaddress")){
if(line.toLowerCase().contains("country")){
validAddress = true;
}
if(line.contains("<th>Country:</th>")){
String[] contentArray = line.split("<|>");
for(int i = 0; i < contentArray.length; i++){
if(contentArray[i].contains("Country:")){
if(i+4 < contentArray.length){
country = contentArray[i+4].replace(" ", "").trim();
break;
}
}
}
}
if(line.contains("<th>State/Region:</th>")){
String[] contentArray = line.split("<|>");
for(int i = 0; i < contentArray.length; i++){
if(contentArray[i].contains("State/Region:")){
if(i+4 < contentArray.length){
state = contentArray[i+4].trim();
break;
}
}
}
}
if(line.contains("<th>City:</th>")){
String[] contentArray = line.split("<|>");
for(int i = 0; i < contentArray.length; i++){
if(contentArray[i].contains("City:")){
if(i+4 < contentArray.length){
city = contentArray[i+4].trim();
break;
}
}
}
}
}else if(source.yamlKey.equals("ipinfo")){
if(line.toLowerCase().matches(" +\"country\": \"\\w{2,}\",\\n")){
validAddress = true;
}
if(line.startsWith(" \"city\": \"")){
city = line.replace(" \"city\": \"", "").replace("\",", "");
}else if(line.startsWith(" \"region\": \"")){
state = line.replace(" \"region\": \"", "").replace("\",", "");
}else if(line.startsWith(" \"country\": \"")){
country = line.replace(" \"country\": \"", "").replace("\",", "");
}
}else if(source.yamlKey.equals("hackertarget")){
if(line.toLowerCase().matches("country: \\w{2,}\\n")){
validAddress = true;
}
if(line.startsWith("City: ")){
city = line.replace("City: ", "");
}else if(line.startsWith("State: ")){
state = line.replace("State: ", "");
}else if(line.startsWith("Country: ")){
country = line.replace("Country: ", "");
}
}
//Check to see if all of the geo data has been collected. If so, stop reading the site data
if(country != null && state != null && city != null){
break;
}
//Read the next line of site data
line = in.readLine();
}
//Close the input stream that was opened earlier
in.close();
//If the geo data has been successfully acquired, format it a little and then forward it to iPAV
if(geoDataFound()){
//We're done downloading, now we want to go back into the main server thread so that anything iPAV does with the data is in-sync
Bukkit.getScheduler().runTask(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
//Some sources give a two-letter country code instead of the full country name, so we need to translate it using the hash map of country codes
if(country.length() <= 3 && iPAVGeoGrabber.countryCodeMap.get(country.toUpperCase()) != null){
country = iPAVGeoGrabber.countryCodeMap.get(country.toUpperCase());
}
if(city.toLowerCase().matches("unknown|n/a")){city = "?";}
if(state.toLowerCase().matches("unknown|n/a")){state = "?";}
//Update the log to have the new geo location data
logItem.applyGeoData(city, state, country);
//Send the updated log to iPAV so that it can save it and show it to admins
iPAddressViewer.forwardGeoData(logItem);
}});
//The data we wanted has been found and iPAV received it. We no longer want to query the other sources, so it's time to exit
return;
}else{
//If the page didn't have the data we wanted, add a failure for it.
//If the current data source is failing, other data sources will get priority over it
retrievalFailure(source, addressString);
if(validAddress){
//Record the failure for this data source. If it's failing now, other data sources will get priority over it
}else{
//For whatever reason, the data source didn't even look like it had the data we wanted.
//This might have been because of the address we tried to search for, so other addresses might be fine.
Bukkit.getScheduler().runTask(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
iPAddressViewer.forwardGeoData(logItem);
}});
return;
}
}
}catch(Exception e){
//There was an error while trying to read or download the website data
//Tell the server owner why geo data wasn't retrieved from the current source
e.printStackTrace();
//Record this failure for the data source
retrievalFailure(source, addressString);
}
}
//Even after searching though all of our data sources, we didn't get the geo location data
//iPAV still needs to receive the log back so that it can show admins the info about the user that is connecting
Bukkit.getScheduler().runTask(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
iPAddressViewer.forwardGeoData(logItem);
}});
}});
}
private void retrievalFailure(final DataSource source, final String address){
Bukkit.getScheduler().runTask(iPAddressViewer.staticPlugin, new Runnable(){public void run(){
source.setFailures(source.getFailures()+1);
Msg.msgC("&4[iPAVGeoGrabber] Unable to retrieve geo data from " + source.getURLWithAddress(address));
Uso.saveYaml(iPAVGeoGrabber.geoStats, iPAddressViewer.staticPlugin.getDataFolder() + "/geoSourceStats.ipav");
}});
}
boolean geoDataFound(){
//If we found at least one part of geo data for the address, then...
if((city != null && city.length() >= 2) || (state != null && state.length() >= 2) || (country != null && country.length() >= 2)){
//Make sure the other pieces of data won't cause errors later on for being invalid
if(city==null || city.equals("")){city = "?";}
if(state==null || state.equals("")){state = "?";}
if(country==null || country.equals("")){country = "?";}
return true;
}
return false;
}
}