diff --git a/src/main/java/dev/viper/weathertime/WeatherFetcher.java b/src/main/java/dev/viper/weathertime/WeatherFetcher.java index 797ade7..d4bdf5d 100644 --- a/src/main/java/dev/viper/weathertime/WeatherFetcher.java +++ b/src/main/java/dev/viper/weathertime/WeatherFetcher.java @@ -1,123 +1,158 @@ -package dev.viper.weathertime; - -import org.json.JSONObject; -import org.json.JSONArray; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.logging.Logger; - -public class WeatherFetcher { - - private final String apiKey; - private final String location; - private final String units; - private final Logger logger; - - public WeatherFetcher(String apiKey, String location, String units, Logger logger) { - this.apiKey = apiKey; - this.location = location; - this.units = units.toLowerCase(); - this.logger = logger; - if (!this.units.equals("metric") && !this.units.equals("imperial")) { - logger.warning("Ungültige Einheit für " + location + ": " + units + ". Verwende 'metric' als Fallback."); - } - } - - public WeatherData fetch() throws Exception { - String effectiveUnits = units.equals("metric") || units.equals("imperial") ? units : "metric"; - - String urlString = String.format( - "https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s", - location, apiKey, effectiveUnits); - - URL url = new URL(urlString); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - int status = connection.getResponseCode(); - if (status != 200) { - logger.warning("HTTP-Fehlercode: " + status + " für Ort: " + location); - throw new RuntimeException("HTTP-Fehlercode: " + status + " für Ort: " + location); - } - - BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); - StringBuilder content = new StringBuilder(); - String inputLine; - - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - connection.disconnect(); - - JSONObject json = new JSONObject(content.toString()); - long dt = json.getLong("dt"); - int timezoneShift; - try { - timezoneShift = json.getInt("timezone"); - } catch (Exception e) { - logger.warning("Fehler beim Abrufen der Zeitzone für Ort: " + location + ": " + e.getMessage()); - throw new RuntimeException("Fehler beim Abrufen der Zeitzone für Ort: " + location, e); - } - ZonedDateTime dateTime = Instant.ofEpochSecond(dt).atZone(ZoneId.of("UTC")).plusSeconds(timezoneShift); - String weatherMain = json.getJSONArray("weather").getJSONObject(0).getString("main"); - double temperature = json.getJSONObject("main").getDouble("temp"); - - if (effectiveUnits.equals("imperial") && (temperature < -50 || temperature > 150)) { - logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°F. API-Fehler?"); - } else if (effectiveUnits.equals("metric") && (temperature < -50 || temperature > 60)) { - logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°C. API-Fehler?"); - } - - return new WeatherData(dateTime, weatherMain, temperature); - } - - public JSONObject fetchForecast() throws Exception { - String effectiveUnits = units.equals("metric") || units.equals("imperial") ? units : "metric"; - - String urlString = String.format( - "https://api.openweathermap.org/data/2.5/forecast?q=%s&appid=%s&units=%s", - location, apiKey, effectiveUnits); - URL url = new URL(urlString); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - - int status = connection.getResponseCode(); - if (status != 200) { - logger.warning("HTTP-Fehlercode: " + status + " für Ort: " + location); - throw new RuntimeException("HTTP-Fehlercode: " + status + " für Ort: " + location); - } - - BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); - StringBuilder content = new StringBuilder(); - String inputLine; - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - } - in.close(); - connection.disconnect(); - - return new JSONObject(content.toString()); - } - - public static class WeatherData { - ZonedDateTime dateTime; - String weatherMain; - double temperature; - - public WeatherData(ZonedDateTime dateTime, String weatherMain, double temperature) { - this.dateTime = dateTime; - this.weatherMain = weatherMain; - this.temperature = temperature; - } - } -} \ No newline at end of file +package dev.viper.weathertime; + +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.logging.Logger; + +/** + * Holt Wetter und Zeitdaten asynchron von OpenWeatherMap. + * Liefert WeatherTimeData zurück, mit konsistentem Modell und Fehlerhandling. + * Erweiterung: Erfasst Luftfeuchtigkeit, Windgeschwindigkeit, Sonnenauf- und -untergang. + */ +public class WeatherFetcher { + + private final String apiKey; + private final String location; + private final String units; + private final Logger logger; + + public WeatherFetcher(String apiKey, String location, String units, Logger logger) { + this.apiKey = apiKey == null ? "" : apiKey.trim(); + this.location = location == null ? "Berlin,de" : location.trim(); + this.units = units == null ? "metric" : units.trim().toLowerCase(); + this.logger = logger; + if (!this.units.equals("metric") && !this.units.equals("imperial")) { + logger.warning("Ungültige Einheit für " + this.location + ": " + this.units + ". Verwende 'metric' als Fallback."); + } + } + + /** + * Holt die aktuellen Wetter- und Zeitdaten inklusive Zusatzinfos. + * Wirft RuntimeExceptions mit klaren Messages bei Fehlern. + */ + public WeatherTimeData fetchAsWeatherTimeData() throws Exception { + if (apiKey.isEmpty()) throw new RuntimeException("API-Key fehlt."); + String effectiveUnits = (units.equals("metric") || units.equals("imperial")) ? units : "metric"; + String urlString = String.format( + "https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s", + location, apiKey, effectiveUnits); + + try { + JSONObject json; + try (BufferedReader in = new BufferedReader(new InputStreamReader(openConnectionStream(urlString)))) { + StringBuilder content = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + json = new JSONObject(content.toString()); + } + + // Fehlerfall: Antwort-Code ungleich 200 + if (json.has("cod") && json.getInt("cod") != 200) { + String msg = json.has("message") ? json.getString("message") : "Unbekannter Fehler"; + logger.warning("API Error für " + location + ": " + msg); + throw new RuntimeException(msg); + } + + long dt = json.getLong("dt"); + int timezoneShift = json.optInt("timezone", 0); // Zeitzonen-Offset in Sekunden + + ZonedDateTime dateTime = Instant.ofEpochSecond(dt) + .atZone(ZoneId.of("UTC")) + .plusSeconds(timezoneShift); + + String weatherMain = json.getJSONArray("weather").getJSONObject(0).getString("main"); + double temperature = json.getJSONObject("main").getDouble("temp"); + + if (effectiveUnits.equals("imperial") && (temperature < -50 || temperature > 150)) { + logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°F. API-Fehler?"); + } else if (effectiveUnits.equals("metric") && (temperature < -50 || temperature > 60)) { + logger.warning("Unrealistische Temperatur für " + location + ": " + temperature + "°C. API-Fehler?"); + } + + // Temperatur immer in Celsius für WeatherTimeData speichern + double tempCelsius = effectiveUnits.equals("imperial") ? (temperature - 32) * 5 / 9 : temperature; + + // NEUE FELDER AUSLESEN + int humidity = json.getJSONObject("main").getInt("humidity"); + double windSpeed = json.getJSONObject("wind").optDouble("speed", 0.0); + + ZonedDateTime sunrise = Instant.ofEpochSecond(json.getJSONObject("sys").getLong("sunrise")) + .atZone(ZoneId.of("UTC")) + .plusSeconds(timezoneShift); + + ZonedDateTime sunset = Instant.ofEpochSecond(json.getJSONObject("sys").getLong("sunset")) + .atZone(ZoneId.of("UTC")) + .plusSeconds(timezoneShift); + + return new WeatherTimeData(dateTime, weatherMain, tempCelsius, + humidity, windSpeed, sunrise, sunset); + + } catch (RuntimeException ru) { + throw ru; + } catch (Exception ex) { + logger.warning("Fehler beim Abrufen von OpenWeatherMap: " + ex.getMessage()); + throw new RuntimeException("Verbindung oder API-Fehler: " + ex.getMessage(), ex); + } + } + + /** + * Holt das Wetter-Vorhersage-JSON (5 Tage, 3-Stunden-Takt). + * Muss im Command asynchron ausgewertet werden! + */ + public JSONObject fetchForecast() throws Exception { + if (apiKey.isEmpty()) throw new RuntimeException("API-Key fehlt."); + String effectiveUnits = (units.equals("metric") || units.equals("imperial")) ? units : "metric"; + String urlString = String.format( + "https://api.openweathermap.org/data/2.5/forecast?q=%s&appid=%s&units=%s", + location, apiKey, effectiveUnits); + + try (BufferedReader in = new BufferedReader(new InputStreamReader(openConnectionStream(urlString)))) { + StringBuilder content = new StringBuilder(); + String inputLine; + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + JSONObject json = new JSONObject(content.toString()); + if (json.has("cod") && (!"200".equals(json.get("cod").toString()))) { + String msg = json.has("message") ? json.getString("message") : "Unbekannter Fehler"; + logger.warning("API Error (Forecast) für " + location + ": " + msg); + throw new RuntimeException(msg); + } + return json; + } catch (Exception e) { + logger.warning("Fehler beim Abrufen der Wettervorhersage: " + e.getMessage()); + throw new RuntimeException("Fehler beim Abrufen der Wettervorhersage: " + e.getMessage(), e); + } + } + + /** Öffnet eine HTTP-GET-Verbindung und liefert den InputStream der Response zurück (Timeouts gesetzt). */ + private java.io.InputStream openConnectionStream(String urlString) throws Exception { + URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout(6000); + connection.setReadTimeout(6000); + + int status = connection.getResponseCode(); + if (status != 200) { + String err = "(keine Fehlerdetails)"; + try (BufferedReader errIn = new BufferedReader(new InputStreamReader(connection.getErrorStream()))) { + StringBuilder sb = new StringBuilder(); + String l; + while ((l = errIn.readLine()) != null) sb.append(l); + err = sb.toString(); + } catch (Exception suppress) {} + throw new RuntimeException("HTTP-Fehlercode: " + status + " – " + err); + } + return connection.getInputStream(); + } +}