feat: v0.3.7 - Weather with location name and PLZ support
Show city name via reverse geocoding in weather response. Allow specifying a German postal code with plz:XXXXX argument. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
40f8d17a9f
commit
fb4425958f
|
|
@ -1,5 +1,14 @@
|
|||
# Changelog
|
||||
|
||||
## [0.3.7] - 2026-02-16
|
||||
### Added
|
||||
- Weather-Befehl zeigt Ortsnamen via Reverse-Geocoding (Nominatim)
|
||||
- Optionales Argument `plz:XXXXX` fuer Wetter nach deutscher Postleitzahl
|
||||
- Geocoding-Methoden `_geocode_plz()` und `_reverse_geocode()` im Bot
|
||||
|
||||
### Changed
|
||||
- Help-Text zeigt `plz:XXXXX` Option beim Weather-Befehl
|
||||
|
||||
## [0.3.6] - 2026-02-15
|
||||
### Added
|
||||
- Node-Einstellungen Seite (`/settings`) zeigt Geraet, LoRa, Channels, Position, Power, Bluetooth/Netzwerk
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
version: "0.3.6"
|
||||
version: "0.3.7"
|
||||
|
||||
bot:
|
||||
name: "MeshDD-Bot"
|
||||
|
|
|
|||
|
|
@ -280,13 +280,19 @@ class MeshBot:
|
|||
f"{prefix}info - Bot-Info\n"
|
||||
f"{prefix}stats - Statistiken\n"
|
||||
f"{prefix}uptime - Laufzeit\n"
|
||||
f"{prefix}weather - Wetter\n"
|
||||
f"{prefix}weather [plz:XXXXX] - Wetter\n"
|
||||
f"{prefix}mesh - Mesh-Netzwerk\n"
|
||||
f"{prefix}help - Diese Hilfe"
|
||||
)
|
||||
|
||||
elif cmd == f"{prefix}weather":
|
||||
response = await self._get_weather(from_id)
|
||||
args = text.split()[1:]
|
||||
plz = None
|
||||
for arg in args:
|
||||
if arg.lower().startswith("plz:"):
|
||||
plz = arg[4:]
|
||||
break
|
||||
response = await self._get_weather(from_id, plz=plz)
|
||||
|
||||
elif cmd == f"{prefix}stats":
|
||||
stats = await self.db.get_stats()
|
||||
|
|
@ -415,14 +421,23 @@ class MeshBot:
|
|||
|
||||
return "\n\n".join(parts)
|
||||
|
||||
async def _get_weather(self, from_id: str) -> str:
|
||||
async def _get_weather(self, from_id: str, plz: str | None = None) -> str:
|
||||
lat, lon = None, None
|
||||
|
||||
if plz:
|
||||
coords = await self._geocode_plz(plz)
|
||||
if coords:
|
||||
lat, lon = coords
|
||||
else:
|
||||
return f"❌ PLZ {plz} nicht gefunden."
|
||||
|
||||
if lat is None:
|
||||
node = await self.db.get_node(from_id)
|
||||
fallback = False
|
||||
if node and node.get("lat") and node.get("lon"):
|
||||
lat, lon = node["lat"], node["lon"]
|
||||
else:
|
||||
lat, lon = 51.0504, 13.7373 # Dresden Zentrum
|
||||
fallback = True
|
||||
|
||||
url = (
|
||||
f"https://api.open-meteo.com/v1/forecast?"
|
||||
f"latitude={lat}&longitude={lon}"
|
||||
|
|
@ -439,9 +454,10 @@ class MeshBot:
|
|||
code = current.get("weather_code", 0)
|
||||
weather_icon = self._weather_code_to_icon(code)
|
||||
|
||||
location = " (Dresden)" if fallback else ""
|
||||
location = await self._reverse_geocode(lat, lon)
|
||||
loc_str = f" ({location})" if location else ""
|
||||
return (
|
||||
f"{weather_icon} Wetter{location}:\n"
|
||||
f"{weather_icon} Wetter{loc_str}:\n"
|
||||
f"Temp: {temp}°C\n"
|
||||
f"Feuchte: {humidity}%\n"
|
||||
f"Wind: {wind} km/h"
|
||||
|
|
@ -450,6 +466,41 @@ class MeshBot:
|
|||
logger.exception("Error fetching weather")
|
||||
return "❌ Wetterdaten konnten nicht abgerufen werden."
|
||||
|
||||
async def _geocode_plz(self, plz: str) -> tuple[float, float] | None:
|
||||
url = (
|
||||
f"https://nominatim.openstreetmap.org/search?"
|
||||
f"postalcode={plz}&country=DE&format=json&limit=1&accept-language=de"
|
||||
)
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "MeshDD-Bot/1.0"})
|
||||
data = await loop.run_in_executor(None, self._fetch_request, req)
|
||||
if data and len(data) > 0:
|
||||
return float(data[0]["lat"]), float(data[0]["lon"])
|
||||
except Exception:
|
||||
logger.debug("Geocode PLZ failed for %s", plz)
|
||||
return None
|
||||
|
||||
async def _reverse_geocode(self, lat: float, lon: float) -> str:
|
||||
url = (
|
||||
f"https://nominatim.openstreetmap.org/reverse?"
|
||||
f"lat={lat}&lon={lon}&format=json&zoom=10&accept-language=de"
|
||||
)
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "MeshDD-Bot/1.0"})
|
||||
data = await loop.run_in_executor(None, self._fetch_request, req)
|
||||
addr = data.get("address", {})
|
||||
return addr.get("city") or addr.get("town") or addr.get("village") or addr.get("municipality") or ""
|
||||
except Exception:
|
||||
logger.debug("Reverse geocode failed for %s,%s", lat, lon)
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def _fetch_request(req: urllib.request.Request) -> dict:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
|
||||
@staticmethod
|
||||
def _fetch_url(url: str) -> dict:
|
||||
with urllib.request.urlopen(url, timeout=10) as resp:
|
||||
|
|
|
|||
Loading…
Reference in a new issue