local geoip, remove maxmind dependency

This commit is contained in:
Minecon724 2024-01-21 19:08:54 +00:00
parent 3abb437c5b
commit 31afa3b10f
20 changed files with 437 additions and 164 deletions

View file

@ -1,3 +1,8 @@
Current:
- weather forecast https://openweathermap.org/forecast5
- multiple conditions
- on join event
Milestone: yesterday Milestone: yesterday
- fix bugs - fix bugs
@ -8,6 +13,7 @@ Milestone: 0.5.1
Milestone: 0.6.0 Milestone: 0.6.0
- account for real sun movement, not just time - account for real sun movement, not just time
- release / debug separate versions
Milestone: future Milestone: future
- weather simulator (weather is clientside rn and doesnt have things such as lightning or other effects) - weather simulator (weather is clientside rn and doesnt have things such as lightning or other effects)

14
pom.xml
View file

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>pl.minecon724</groupId> <groupId>pl.minecon724</groupId>
<artifactId>realweather</artifactId> <artifactId>realweather</artifactId>
<version>0.5.0.1</version> <version>0.5.1-DEV</version>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
@ -24,15 +24,9 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.json</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>json</artifactId> <artifactId>gson</artifactId>
<version>20231013</version> <version>2.10.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>4.2.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View file

@ -1,35 +1,46 @@
package pl.minecon724.realweather; package pl.minecon724.realweather;
import com.maxmind.geoip2.WebServiceClient; import java.io.IOException;
import java.util.logging.Logger;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.realtime.RealTimeCommander; import pl.minecon724.realweather.realtime.RealTimeCommander;
import pl.minecon724.realweather.weather.WeatherCommander; import pl.minecon724.realweather.weather.WeatherCommander;
import pl.minecon724.realweather.weather.exceptions.DisabledException; import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
public class RW extends JavaPlugin { public class RealWeatherPlugin extends JavaPlugin {
FileConfiguration config;
WebServiceClient client = null; private final Logger logger = getLogger();
private FileConfiguration config;
@Override @Override
public void onEnable() { public void onEnable() {
long start = System.currentTimeMillis();
saveDefaultConfig(); saveDefaultConfig();
config = getConfig(); config = getConfig();
SubLogger.init( SubLogger.init(
getLogger(), logger,
config.getBoolean("logging", false) config.getBoolean("logging", false)
); );
WorldMap.init( ConfigurationSection mapConfigurationSection = config.getConfigurationSection("map");
config.getConfigurationSection("map")
); try {
WorldMap.init(
mapConfigurationSection,
getDataFolder()
);
} catch (IOException e) {
logger.severe("Unable to initialize WorldMap:");
e.printStackTrace();
getServer().getPluginManager().disablePlugin(this);
}
WeatherCommander weatherCommander = new WeatherCommander(this); WeatherCommander weatherCommander = new WeatherCommander(this);
try { try {
@ -37,9 +48,10 @@ public class RW extends JavaPlugin {
config.getConfigurationSection("weather") config.getConfigurationSection("weather")
); );
weatherCommander.start(); weatherCommander.start();
} catch (DisabledException e) { } catch (ModuleDisabledException e) {
getLogger().info("Weather module disabled"); logger.info("Weather is disabled by user");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.severe("Couldn't initialize weather provider:");
e.printStackTrace(); e.printStackTrace();
getServer().getPluginManager().disablePlugin(this); getServer().getPluginManager().disablePlugin(this);
} }
@ -50,11 +62,9 @@ public class RW extends JavaPlugin {
config.getConfigurationSection("time") config.getConfigurationSection("time")
); );
realTimeCommander.start(); realTimeCommander.start();
} catch (DisabledException e) { } catch (ModuleDisabledException e) {
getLogger().info("Time module disabled"); logger.info("Time is disabled by user");
} }
long end = System.currentTimeMillis();
this.getLogger().info( String.format( this.getName() + " enabled! (%s ms)", Long.toString( end-start ) ) );
} }
} }

View file

@ -4,19 +4,35 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class SubLogger { public class SubLogger {
// TODO TODO too many static
private static Logger LOGGER; private static Logger LOGGER;
private static boolean ENABLED; private static boolean ENABLED;
private String name; private String name;
/**
* Initialize the SubLogger
* @param logger parent logger, usually JavaPlugin#getLogger()
* @param enabled is logging enabled
*/
static void init(Logger logger, boolean enabled) { static void init(Logger logger, boolean enabled) {
LOGGER = logger; LOGGER = logger;
ENABLED = enabled; ENABLED = enabled;
} }
/**
* Instantiate a SubLogger instance
* @param name name, it will be prefixing messages
*/
public SubLogger(String name) { public SubLogger(String name) {
this.name = name; this.name = name;
} }
/**
* Log a message
* @param level
* @param format message, formatted like {@link String#format(String, Object...)}
* @param args args for formatting
*/
public void log(Level level, String format, Object... args) { public void log(Level level, String format, Object... args) {
if (!ENABLED) return; if (!ENABLED) return;
@ -27,7 +43,45 @@ public class SubLogger {
LOGGER.log(level, String.format("[%s] " + format, combinedArgs)); LOGGER.log(level, String.format("[%s] " + format, combinedArgs));
} }
/**
* Log an info message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void info(String format, Object... args) { public void info(String format, Object... args) {
this.log(Level.INFO, format, args); this.log(Level.INFO, format, args);
} }
public void info(String message) {
this.log(Level.INFO, message, new Object[0]);
}
/**
* Log a severe message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void severe(String format, Object... args) {
this.log(Level.SEVERE, format, args);
}
public void severe(String message) {
this.log(Level.SEVERE, message, new Object[0]);
}
/**
* Log a warning message
* see {@link SubLogger#log(Level, String, Object...)}
* @param format message
* @param args args
*/
public void warning(String format, Object... args) {
this.log(Level.WARNING, format, args);
}
public void warning(String message) {
this.log(Level.WARNING, message, new Object[0]);
}
} }

View file

@ -0,0 +1,51 @@
package pl.minecon724.realweather.geoip;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import pl.minecon724.realweather.SubLogger;
public class DatabaseDownloader {
private SubLogger subLogger = new SubLogger("download");
private URL downloadUrl;
public DatabaseDownloader(URL downloadUrl) {
this.downloadUrl = downloadUrl;
}
// TODO verify
public void download(File file, boolean ipv6) throws IOException {
URL url = new URL(downloadUrl, ipv6 ? "ipv6.geo.gz" : "ipv4.geo.gz");
URLConnection connection = url.openConnection();
connection.connect();
InputStream inputStream = connection.getInputStream();
ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream);
file.getParentFile().mkdirs();
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
FileChannel fileChannel = fileOutputStream.getChannel();
long size = connection.getHeaderFieldLong("Content-Length", 0);
long position = 0;
while (position < size) {
position += fileChannel.transferFrom(readableByteChannel, position, 1048576);
subLogger.info("%d%%", (int)(1.0 * position / size * 100));
}
fileChannel.close();
fileOutputStream.close();
readableByteChannel.close();
inputStream.close(); // ok
}
}

View file

@ -0,0 +1,96 @@
package pl.minecon724.realweather.geoip;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.zip.GZIPInputStream;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Shorts;
import pl.minecon724.realweather.map.Coordinates;
public class GeoIPDatabase {
private byte formatVersion;
private long timestamp;
public HashMap<Integer, Coordinates> entries = new HashMap<>();
public void read(File file) throws IOException, FileNotFoundException {
if (!file.exists()) {
throw new FileNotFoundException();
}
if (timestamp != 0) {
throw new IOException("GeoIPDatabase can only be read once");
}
FileInputStream fileInputStream = new FileInputStream(file);
GZIPInputStream gzipInputStream = new GZIPInputStream(fileInputStream);
formatVersion = (byte) gzipInputStream.read();
byte[] bytes = gzipInputStream.readNBytes(2);
timestamp = recoverTime(bytes);
byte[] address;
Coordinates coordinates;
while (true) { // TODO true?
System.out.println(gzipInputStream.available());
address = gzipInputStream.readNBytes(4);
if (address.length == 0)
break;
coordinates = recoverCoordinates(gzipInputStream.readNBytes(6));
entries.put(IPUtils.toInt(address), coordinates);
}
gzipInputStream.close();
fileInputStream.close();
}
public String getTimestamp() {
return new SimpleDateFormat("dd.MM.yyyy HH:mm:ss")
.format(new Date(timestamp * 1000));
}
public byte getFormatVersion() {
return formatVersion;
}
/**
* 2 bytes to 4 bytes wow magic
* @param bytes 2 bytes
* @return
*/
@SuppressWarnings("null") // TODO better way of this?
private long recoverTime(byte[] bytes) {
long timestamp = 1704067200; // first second of 2024
timestamp += Shorts.fromByteArray(bytes) * 60 * 10;
return timestamp;
}
/**
* Encoded to Coordinates
* @param bytes 3 bytes
* @return decoded Coordinates
*/
private Coordinates recoverCoordinates(byte[] bytes) {
int skewedLatitude = Ints.fromBytes(
(byte)0, bytes[0], bytes[1], bytes[2]
);
int skewedLongitude = Ints.fromBytes(
(byte)0, bytes[3], bytes[4], bytes[5]
);
double latitude = (skewedLatitude - 900000) / 10000.0;
double longitude = (skewedLongitude - 1800000) / 10000.0;
return new Coordinates(latitude, longitude);
}
}

View file

@ -0,0 +1,32 @@
package pl.minecon724.realweather.geoip;
import com.google.common.primitives.Ints;
public class IPUtils {
public static byte[] getSubnetStart(byte[] addressBytes, byte subnet) {
int address = toInt(addressBytes);
int mask = 0xFFFFFFFF << (32 - subnet);
return fromInt(address & mask);
}
@SuppressWarnings("null")
public static int toInt(byte[] address) {
return Ints.fromByteArray(address);
}
public static byte[] fromInt(int value) {
return Ints.toByteArray(value);
}
public static String toString(byte[] address) {
String s = "";
for (int i=0; i<4; i++) {
s += Integer.toString(address[i] & 0xFF);
if (i < 3) s += ".";
}
return s;
}
}

View file

@ -1,7 +1,5 @@
package pl.minecon724.realweather.map; package pl.minecon724.realweather.map;
import com.maxmind.geoip2.record.Location;
public class Coordinates { public class Coordinates {
public double latitude, longitude; public double latitude, longitude;
@ -38,11 +36,4 @@ public class Coordinates {
private static double wrapDouble(double min, double max, double val) { private static double wrapDouble(double min, double max, double val) {
return min + (val - min) % (max - min); return min + (val - min) % (max - min);
} }
public static Coordinates fromGeoIpLocation(Location location) {
return new Coordinates(
location.getLatitude(),
location.getLongitude()
);
}
} }

View file

@ -1,30 +1,48 @@
package pl.minecon724.realweather.map; package pl.minecon724.realweather.map;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import com.maxmind.geoip2.WebServiceClient;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.geoip.DatabaseDownloader;
import pl.minecon724.realweather.geoip.GeoIPDatabase;
import pl.minecon724.realweather.geoip.IPUtils;
import pl.minecon724.realweather.map.exceptions.GeoIPException; import pl.minecon724.realweather.map.exceptions.GeoIPException;
public class GeoLocator { public class GeoLocator {
private static GeoLocator INSTANCE = null; private static GeoLocator INSTANCE = null;
private WebServiceClient client; private SubLogger subLogger = new SubLogger("geolocator");
private Map<InetAddress, Coordinates> cache; private GeoIPDatabase database;
private HashMap<InetAddress, Coordinates> cache = new HashMap<>();
public static void init(int accountId, String apiKey) { public static void init(File databaseFile, String downloadUrl) throws IOException {
INSTANCE = new GeoLocator( INSTANCE = new GeoLocator(
new WebServiceClient.Builder(accountId, apiKey) new GeoIPDatabase());
.host("geolite.info").build());
INSTANCE.load(databaseFile, downloadUrl);
} }
public GeoLocator(WebServiceClient client) { public GeoLocator(GeoIPDatabase database) {
this.client = client; this.database = database;
this.cache = new HashMap<>(); }
public void load(File databaseFile, String downloadUrl) throws IOException {
subLogger.info("This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com");
try {
database.read(databaseFile);
} catch (FileNotFoundException e) {
new DatabaseDownloader(new URL(downloadUrl))
.download(databaseFile, false);
database.read(databaseFile);
}
subLogger.info("Database: %s", INSTANCE.database.getTimestamp());
} }
/** /**
@ -34,35 +52,37 @@ public class GeoLocator {
* @throws GeoIp2Exception * @throws GeoIp2Exception
* @throws IOException * @throws IOException
*/ */
public static Coordinates getCoordinates(InetAddress address) public static Coordinates getCoordinates(InetAddress inetAddress)
throws GeoIPException { throws GeoIPException {
GeoLocator instance = INSTANCE; Coordinates coordinates = INSTANCE.cache.get(inetAddress);
Coordinates coordinates = null;
coordinates = instance.lookup(address);
if (coordinates != null) if (coordinates != null)
return coordinates; return coordinates;
try { byte[] address = inetAddress.getAddress();
coordinates = Coordinates.fromGeoIpLocation( byte subnet = 32;
instance.client.city(address).getLocation()
while (coordinates == null) {
if (subnet == 0) {
INSTANCE.subLogger.info("Not found :(");
coordinates = new Coordinates(0, 0);
break;
}
int query = IPUtils.toInt(address);
coordinates = INSTANCE.database.entries.get(
query
); );
} catch (IOException | GeoIp2Exception e) {
throw new GeoIPException(e.getMessage()); INSTANCE.subLogger.info("trying %s/%d = %d", IPUtils.toString(address), subnet, query);
address = IPUtils.getSubnetStart(address, --subnet);
} }
instance.store(address, coordinates); INSTANCE.subLogger.info("Done, caching");
INSTANCE.cache.put(inetAddress, coordinates);
return coordinates; return coordinates;
} }
private Coordinates lookup(InetAddress address) {
return this.cache.get(address);
}
private void store(InetAddress address, Coordinates coordinates) {
this.cache.put(address, coordinates);
}
} }

View file

@ -1,5 +1,8 @@
package pl.minecon724.realweather.map; package pl.minecon724.realweather.map;
import java.io.File;
import java.io.IOException;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -24,8 +27,8 @@ public class WorldMap {
this.point = point; this.point = point;
} }
public static void init(ConfigurationSection config) public static void init(ConfigurationSection config, File dataFolder)
throws IllegalArgumentException { throws IOException {
Type type; Type type;
@ -45,8 +48,8 @@ public class WorldMap {
} else if (type == Type.PLAYER) { } else if (type == Type.PLAYER) {
GeoLocator.init( GeoLocator.init(
config.getInt("player.geolite2_accountId"), dataFolder.toPath().resolve("geoip/ipv4.geo.gz").toFile(),
config.getString("player.geolite2_api_key") config.getString("player.download_url", "https://inferior.network/geoip/")
); );
} else if (type == Type.GLOBE) { } else if (type == Type.GLOBE) {

View file

@ -14,11 +14,11 @@ import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent; import org.bukkit.event.world.WorldUnloadEvent;
import pl.minecon724.realweather.RW; import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.weather.exceptions.DisabledException; import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
public class RealTimeCommander implements Listener { public class RealTimeCommander implements Listener {
private RW plugin; private RealWeatherPlugin plugin;
private List<String> worldNames; private List<String> worldNames;
private double scale; private double scale;
@ -31,15 +31,15 @@ public class RealTimeCommander implements Listener {
private RealTimeTask task; private RealTimeTask task;
private PlayerTimeSyncTask playerTimeSyncTask; private PlayerTimeSyncTask playerTimeSyncTask;
public RealTimeCommander(RW plugin) { public RealTimeCommander(RealWeatherPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
public void init(ConfigurationSection config) public void init(ConfigurationSection config)
throws DisabledException { throws ModuleDisabledException {
if (!config.getBoolean("enabled")) if (!config.getBoolean("enabled"))
throw new DisabledException(); throw new ModuleDisabledException();
try { try {
timezone = ZoneId.of(config.getString("timezone")); timezone = ZoneId.of(config.getString("timezone"));

View file

@ -1,6 +1,5 @@
package pl.minecon724.realweather.weather; package pl.minecon724.realweather.weather;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -9,7 +8,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import pl.minecon724.realweather.RW; import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.SubLogger; import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.map.WorldMap;
@ -17,13 +16,14 @@ import pl.minecon724.realweather.map.WorldMap.Type;
import pl.minecon724.realweather.map.exceptions.GeoIPException; import pl.minecon724.realweather.map.exceptions.GeoIPException;
import pl.minecon724.realweather.weather.WeatherState.State; import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.events.WeatherSyncEvent; import pl.minecon724.realweather.weather.events.WeatherSyncEvent;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
import pl.minecon724.realweather.weather.provider.Provider; import pl.minecon724.realweather.weather.provider.Provider;
public class GetStateTask extends BukkitRunnable { public class GetStateTask extends BukkitRunnable {
private SubLogger subLogger = new SubLogger("weather updater"); private SubLogger subLogger = new SubLogger("weather updater");
private RW plugin; private RealWeatherPlugin plugin;
private Provider provider; private Provider provider;
private WorldMap worldMap; private WorldMap worldMap;
@ -32,7 +32,7 @@ public class GetStateTask extends BukkitRunnable {
private PluginManager pluginManager = Bukkit.getPluginManager(); private PluginManager pluginManager = Bukkit.getPluginManager();
public GetStateTask( public GetStateTask(
RW plugin, RealWeatherPlugin plugin,
Provider provider, Provider provider,
WorldMap worldMap WorldMap worldMap
) { ) {
@ -92,8 +92,9 @@ public class GetStateTask extends BukkitRunnable {
} }
} }
} }
} catch (IOException e) { } catch (WeatherProviderException e) {
subLogger.info("Error updating: %s", e.getMessage()); subLogger.info("Weather provider error");
e.printStackTrace();
} }
} }

View file

@ -26,7 +26,7 @@ public class WeatherChanger implements Listener {
@EventHandler @EventHandler
public void onWorldLoad(WorldLoadEvent event) { public void onWorldLoad(WorldLoadEvent event) {
World world = event.getWorld(); World world = event.getWorld();
subLogger.info("World %s is loading", world.getName()); subLogger.info("World %s has been loaded", world.getName());
if (worldNames.contains(world.getName())) { if (worldNames.contains(world.getName())) {
worlds.add(world); worlds.add(world);

View file

@ -1,20 +1,20 @@
package pl.minecon724.realweather.weather; package pl.minecon724.realweather.weather;
import java.io.IOException;
import java.util.List; import java.util.List;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import pl.minecon724.realweather.RW; import pl.minecon724.realweather.RealWeatherPlugin;
import pl.minecon724.realweather.SubLogger; import pl.minecon724.realweather.SubLogger;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.map.WorldMap; import pl.minecon724.realweather.map.WorldMap;
import pl.minecon724.realweather.weather.exceptions.DisabledException; import pl.minecon724.realweather.weather.exceptions.ModuleDisabledException;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
import pl.minecon724.realweather.weather.provider.Provider; import pl.minecon724.realweather.weather.provider.Provider;
public class WeatherCommander { public class WeatherCommander {
private WorldMap worldMap = WorldMap.getInstance(); private WorldMap worldMap = WorldMap.getInstance();
private RW plugin; private RealWeatherPlugin plugin;
private boolean enabled; private boolean enabled;
private List<String> worldNames; private List<String> worldNames;
@ -26,23 +26,23 @@ public class WeatherCommander {
private SubLogger subLogger = new SubLogger("weather"); private SubLogger subLogger = new SubLogger("weather");
public WeatherCommander(RW plugin) { public WeatherCommander(RealWeatherPlugin plugin) {
this.plugin = plugin; this.plugin = plugin;
} }
/** /**
* Initialize weather commander * Initialize weather commander
* @param config "weather" ConfigurationSection * @param config "weather" ConfigurationSection
* @throws DisabledException if disabled in config * @throws ModuleDisabledException if disabled in config
* @throws ProviderException if invalid provider config * @throws ProviderException if invalid provider config
*/ */
public void init(ConfigurationSection config) public void init(ConfigurationSection config)
throws DisabledException, IllegalArgumentException { throws ModuleDisabledException, IllegalArgumentException {
enabled = config.getBoolean("enabled"); enabled = config.getBoolean("enabled");
if (!enabled) if (!enabled)
throw new DisabledException(); throw new ModuleDisabledException();
worldNames = config.getStringList("worlds"); worldNames = config.getStringList("worlds");
@ -59,15 +59,15 @@ public class WeatherCommander {
try { try {
provider.request_state(new Coordinates(0, 0)); provider.request_state(new Coordinates(0, 0));
} catch (IOException e) { } catch (WeatherProviderException e) {
subLogger.info("Provider test failed, errors may occur", new Object[0]); subLogger.severe("Provider test failed, errors may occur");
e.printStackTrace(); e.printStackTrace();
} }
plugin.getServer().getPluginManager().registerEvents( plugin.getServer().getPluginManager().registerEvents(
new WeatherChanger(worldNames), plugin); new WeatherChanger(worldNames), plugin);
subLogger.info("done", new Object[0]); subLogger.info("done");
} }
public void start() { public void start() {

View file

@ -1,5 +1,5 @@
package pl.minecon724.realweather.weather.exceptions; package pl.minecon724.realweather.weather.exceptions;
public class DisabledException extends Exception { public class ModuleDisabledException extends Exception {
} }

View file

@ -0,0 +1,9 @@
package pl.minecon724.realweather.weather.exceptions;
public class WeatherProviderException extends Exception {
public WeatherProviderException(String message) {
super(message);
}
}

View file

@ -1,18 +1,19 @@
package pl.minecon724.realweather.weather.provider; package pl.minecon724.realweather.weather.provider;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import org.json.JSONException; import com.google.common.base.Charsets;
import org.json.JSONObject; import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.weather.WeatherState.*; import pl.minecon724.realweather.weather.WeatherState.*;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
public class OpenWeatherMapProvider implements Provider { public class OpenWeatherMapProvider implements Provider {
@ -24,6 +25,11 @@ public class OpenWeatherMapProvider implements Provider {
this.apiKey = apiKey; this.apiKey = apiKey;
} }
@Override
public String getName() {
return "OpenWeatherMap";
}
public void init() { public void init() {
try { try {
endpoint = new URL("https://api.openweathermap.org"); endpoint = new URL("https://api.openweathermap.org");
@ -32,34 +38,40 @@ public class OpenWeatherMapProvider implements Provider {
} }
} }
public State request_state(Coordinates coordinates) throws IOException { public State request_state(Coordinates coordinates) throws WeatherProviderException {
JSONObject json = new JSONObject(); JsonObject jsonObject;
try { try {
URL url = new URL( URL url = new URL(
endpoint + String.format("/data/2.5/weather?lat=%s&lon=%s&appid=%s", String.format("%s/data/2.5/weather?lat=%f&lon=%f&appid=%s",
Double.toString(coordinates.latitude), Double.toString(coordinates.longitude), apiKey endpoint, coordinates.latitude, coordinates.longitude, apiKey
)); ));
InputStream is = url.openStream(); InputStream is = url.openStream();
BufferedReader rd = new BufferedReader( new InputStreamReader(is, Charset.forName("UTF-8")) ); BufferedReader rd = new BufferedReader(
StringBuilder sb = new StringBuilder(); new InputStreamReader(is, Charsets.UTF_8));
int c;
while ((c = rd.read()) != -1) { JsonReader jsonReader = new JsonReader(rd);
sb.append((char) c); jsonObject = new Gson().fromJson(jsonReader, JsonObject.class);
}
is.close();
json = new JSONObject(sb.toString());
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Couldn't contact openweathermap"); e.printStackTrace();
throw new WeatherProviderException("Couldn't contact openweathermap");
} }
int stateId; int stateId;
try { try {
stateId = json.getJSONArray("weather") stateId = jsonObject.getAsJsonArray("weather")
.getJSONObject(0).getInt("id"); .get(0).getAsJsonObject()
} catch (JSONException e) { .get("id").getAsInt();
throw new IOException("Invalid data from openweathermap"); /*
* org.json comparison:
* stateId = json.getJSONArray("weather").getJSONObject(0).getInt("id");
* so is it truly worth it? yes see loading jsonobject from inputstream
*/
} catch (Exception e) {
e.printStackTrace();
throw new WeatherProviderException("Invalid data from openweathermap");
} }
// Here comes the mess // Here comes the mess
@ -67,60 +79,44 @@ public class OpenWeatherMapProvider implements Provider {
ConditionLevel level = ConditionLevel.LIGHT; ConditionLevel level = ConditionLevel.LIGHT;
if (stateId < 300) { if (stateId < 300) {
condition = Condition.THUNDER; condition = Condition.THUNDER;
switch (stateId) { switch (stateId % 10) {
case 200: case 0: // 200, 210, 230
case 210:
case 230:
level = ConditionLevel.LIGHT; level = ConditionLevel.LIGHT;
break; break;
case 201: case 1: // 201, 211, 221, 231
case 211:
case 221:
case 231:
level = ConditionLevel.MODERATE; level = ConditionLevel.MODERATE;
break; break;
case 202: case 2: // 202, 212, 232
case 212:
case 232:
level = ConditionLevel.HEAVY; level = ConditionLevel.HEAVY;
} }
} else if (stateId < 400) { } else if (stateId < 400) {
condition = Condition.DRIZZLE; condition = Condition.DRIZZLE;
switch (stateId) { switch (stateId % 10) {
case 300: case 0: // 300, 310
case 310:
level = ConditionLevel.LIGHT; level = ConditionLevel.LIGHT;
break; break;
case 301: case 1: // 301, 311, 321
case 311: case 3: // 313
case 313:
case 321:
level = ConditionLevel.MODERATE; level = ConditionLevel.MODERATE;
break; break;
case 302: case 2: // 302, 312
case 312: case 4: // 314
case 314:
level = ConditionLevel.HEAVY; level = ConditionLevel.HEAVY;
} }
} else if (stateId < 600) { } else if (stateId < 600) {
condition = Condition.RAIN; condition = Condition.RAIN;
switch (stateId) { switch (stateId % 10) {
case 500: case 0: // 500, 520
case 520:
level = ConditionLevel.LIGHT; level = ConditionLevel.LIGHT;
break; break;
case 501: case 1: // 501, 511, 521, 531
case 511:
case 521:
case 531:
level = ConditionLevel.MODERATE; level = ConditionLevel.MODERATE;
break; break;
case 502: case 2: // 502, 522
case 522:
level = ConditionLevel.HEAVY; level = ConditionLevel.HEAVY;
break; break;
case 503: case 3: // 503
case 504: case 4: // 504
level = ConditionLevel.EXTREME; level = ConditionLevel.EXTREME;
} }
} else if (stateId < 700) { } else if (stateId < 700) {
@ -165,7 +161,16 @@ public class OpenWeatherMapProvider implements Provider {
} }
@Override @Override
public String getName() { public State[] request_state(Coordinates[] coordinates) throws WeatherProviderException {
return "OpenWeatherMap"; // OpenWeatherMap doesnt support bulk requests
int length = coordinates.length;
State[] states = new State[length];
for (int i=0; i<length; i++) {
states[i] = request_state(coordinates[i]);
}
return states;
} }
} }

View file

@ -1,12 +1,13 @@
package pl.minecon724.realweather.weather.provider; package pl.minecon724.realweather.weather.provider;
import java.io.IOException;
import pl.minecon724.realweather.map.Coordinates; import pl.minecon724.realweather.map.Coordinates;
import pl.minecon724.realweather.weather.WeatherState; import pl.minecon724.realweather.weather.WeatherState.State;
import pl.minecon724.realweather.weather.exceptions.WeatherProviderException;
public interface Provider { public interface Provider {
public void init(); public void init();
public WeatherState.State request_state(Coordinates coordinates) throws IOException;
public String getName(); public String getName();
public State request_state(Coordinates coordinates) throws WeatherProviderException;
public State[] request_state(Coordinates[] coordinates) throws WeatherProviderException;
} }

View file

@ -25,9 +25,7 @@ map:
longitude: -89.485937 longitude: -89.485937
player: player:
# Get your own @ https://www.maxmind.com/en/geolite2/signup empty: for now
geolite2_accountId: 710438
geolite2_api_key: 'qLeseHp4QNQcqRGn'
globe: globe:
# Valid latitude range: -90 to 90 # Valid latitude range: -90 to 90

View file

@ -1,9 +1,11 @@
name: RealWeather name: RealWeather
author: Minecon724
version: ${project.version} version: ${project.version}
api-version: 1.16 api-version: 1.16
load: STARTUP load: STARTUP
author: Minecon724 main: pl.minecon724.realweather.RealWeatherPlugin
main: pl.minecon724.realweather.RW
libraries: libraries:
- org.json:json:20231013 - com.google.code.gson:gson:2.10.1
- com.maxmind.geoip2:geoip2:4.2.0