Add filter for affected areas in NINA warnings (#97053)
* Add affected areas to warnings * Update config flow * Remove option from config_flow * Add regex check * Remove regex checkpull/98847/head
parent
b65e3ddc99
commit
d179f8b47d
|
@ -16,6 +16,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
|||
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
ALL_MATCH_REGEX,
|
||||
CONF_AREA_FILTER,
|
||||
CONF_FILTER_CORONA,
|
||||
CONF_HEADLINE_FILTER,
|
||||
CONF_REGIONS,
|
||||
|
@ -42,8 +44,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
new_data.pop(CONF_FILTER_CORONA, None)
|
||||
hass.config_entries.async_update_entry(entry, data=new_data)
|
||||
|
||||
if CONF_AREA_FILTER not in entry.data:
|
||||
new_data = {**entry.data, CONF_AREA_FILTER: ALL_MATCH_REGEX}
|
||||
hass.config_entries.async_update_entry(entry, data=new_data)
|
||||
|
||||
coordinator = NINADataUpdateCoordinator(
|
||||
hass, regions, entry.data[CONF_HEADLINE_FILTER]
|
||||
hass,
|
||||
regions,
|
||||
entry.data[CONF_HEADLINE_FILTER],
|
||||
entry.data[CONF_AREA_FILTER],
|
||||
)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
@ -77,6 +86,7 @@ class NinaWarningData:
|
|||
sender: str
|
||||
severity: str
|
||||
recommended_actions: str
|
||||
affected_areas: str
|
||||
sent: str
|
||||
start: str
|
||||
expires: str
|
||||
|
@ -89,12 +99,17 @@ class NINADataUpdateCoordinator(
|
|||
"""Class to manage fetching NINA data API."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, regions: dict[str, str], headline_filter: str
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
regions: dict[str, str],
|
||||
headline_filter: str,
|
||||
area_filter: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self._regions: dict[str, str] = regions
|
||||
self._nina: Nina = Nina(async_get_clientsession(hass))
|
||||
self.headline_filter: str = headline_filter
|
||||
self.area_filter: str = area_filter
|
||||
|
||||
for region in regions:
|
||||
self._nina.addRegion(region)
|
||||
|
@ -147,6 +162,21 @@ class NINADataUpdateCoordinator(
|
|||
if re.search(
|
||||
self.headline_filter, raw_warn.headline, flags=re.IGNORECASE
|
||||
):
|
||||
_LOGGER.debug(
|
||||
f"Ignore warning ({raw_warn.id}) by headline filter ({self.headline_filter}) with headline: {raw_warn.headline}"
|
||||
)
|
||||
continue
|
||||
|
||||
affected_areas_string: str = ", ".join(
|
||||
[str(area) for area in raw_warn.affected_areas]
|
||||
)
|
||||
|
||||
if not re.search(
|
||||
self.area_filter, affected_areas_string, flags=re.IGNORECASE
|
||||
):
|
||||
_LOGGER.debug(
|
||||
f"Ignore warning ({raw_warn.id}) by area filter ({self.area_filter}) with area: {affected_areas_string}"
|
||||
)
|
||||
continue
|
||||
|
||||
warning_data: NinaWarningData = NinaWarningData(
|
||||
|
@ -156,6 +186,7 @@ class NINADataUpdateCoordinator(
|
|||
raw_warn.sender,
|
||||
raw_warn.severity,
|
||||
" ".join([str(action) for action in raw_warn.recommended_actions]),
|
||||
affected_areas_string,
|
||||
raw_warn.sent or "",
|
||||
raw_warn.start or "",
|
||||
raw_warn.expires or "",
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|||
|
||||
from . import NINADataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_AFFECTED_AREAS,
|
||||
ATTR_DESCRIPTION,
|
||||
ATTR_EXPIRES,
|
||||
ATTR_HEADLINE,
|
||||
|
@ -73,7 +74,7 @@ class NINAMessage(CoordinatorEntity[NINADataUpdateCoordinator], BinarySensorEnti
|
|||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the sensor."""
|
||||
if not len(self.coordinator.data[self._region]) > self._warning_index:
|
||||
if len(self.coordinator.data[self._region]) <= self._warning_index:
|
||||
return False
|
||||
|
||||
data = self.coordinator.data[self._region][self._warning_index]
|
||||
|
@ -94,6 +95,7 @@ class NINAMessage(CoordinatorEntity[NINADataUpdateCoordinator], BinarySensorEnti
|
|||
ATTR_SENDER: data.sender,
|
||||
ATTR_SEVERITY: data.severity,
|
||||
ATTR_RECOMMENDED_ACTIONS: data.recommended_actions,
|
||||
ATTR_AFFECTED_AREAS: data.affected_areas,
|
||||
ATTR_ID: data.id,
|
||||
ATTR_SENT: data.sent,
|
||||
ATTR_START: data.start,
|
||||
|
|
|
@ -18,6 +18,7 @@ from homeassistant.helpers.entity_registry import (
|
|||
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
CONF_AREA_FILTER,
|
||||
CONF_HEADLINE_FILTER,
|
||||
CONF_MESSAGE_SLOTS,
|
||||
CONF_REGIONS,
|
||||
|
@ -263,6 +264,10 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||
CONF_HEADLINE_FILTER,
|
||||
default=self.data[CONF_HEADLINE_FILTER],
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_AREA_FILTER,
|
||||
default=self.data[CONF_AREA_FILTER],
|
||||
): cv.string,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
|
|
|
@ -12,17 +12,20 @@ SCAN_INTERVAL: timedelta = timedelta(minutes=5)
|
|||
DOMAIN: str = "nina"
|
||||
|
||||
NO_MATCH_REGEX: str = "/(?!)/"
|
||||
ALL_MATCH_REGEX: str = ".*"
|
||||
|
||||
CONF_REGIONS: str = "regions"
|
||||
CONF_MESSAGE_SLOTS: str = "slots"
|
||||
CONF_FILTER_CORONA: str = "corona_filter" # deprecated
|
||||
CONF_HEADLINE_FILTER: str = "headline_filter"
|
||||
CONF_AREA_FILTER: str = "area_filter"
|
||||
|
||||
ATTR_HEADLINE: str = "headline"
|
||||
ATTR_DESCRIPTION: str = "description"
|
||||
ATTR_SENDER: str = "sender"
|
||||
ATTR_SEVERITY: str = "severity"
|
||||
ATTR_RECOMMENDED_ACTIONS: str = "recommended_actions"
|
||||
ATTR_AFFECTED_AREAS: str = "affected_areas"
|
||||
ATTR_ID: str = "id"
|
||||
ATTR_SENT: str = "sent"
|
||||
ATTR_START: str = "start"
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
"_r_to_u": "[%key:component::nina::config::step::user::data::_r_to_u%]",
|
||||
"_v_to_z": "[%key:component::nina::config::step::user::data::_v_to_z%]",
|
||||
"slots": "[%key:component::nina::config::step::user::data::slots%]",
|
||||
"headline_filter": "[%key:component::nina::config::step::user::data::headline_filter%]"
|
||||
"headline_filter": "[%key:component::nina::config::step::user::data::headline_filter%]",
|
||||
"area_filter": "Whitelist regex to filter warnings based on affected areas"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@ from unittest.mock import patch
|
|||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.nina.const import (
|
||||
ATTR_AFFECTED_AREAS,
|
||||
ATTR_DESCRIPTION,
|
||||
ATTR_EXPIRES,
|
||||
ATTR_HEADLINE,
|
||||
|
@ -38,6 +39,13 @@ ENTRY_DATA_NO_CORONA: dict[str, Any] = {
|
|||
"regions": {"083350000000": "Aach, Stadt"},
|
||||
}
|
||||
|
||||
ENTRY_DATA_NO_AREA: dict[str, Any] = {
|
||||
"slots": 5,
|
||||
"corona_filter": False,
|
||||
"area_filter": ".*nagold.*",
|
||||
"regions": {"083350000000": "Aach, Stadt"},
|
||||
}
|
||||
|
||||
|
||||
async def test_sensors(hass: HomeAssistant) -> None:
|
||||
"""Test the creation and values of the NINA sensors."""
|
||||
|
@ -70,6 +78,10 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
|||
assert state_w1.attributes.get(ATTR_SENDER) == "Deutscher Wetterdienst"
|
||||
assert state_w1.attributes.get(ATTR_SEVERITY) == "Minor"
|
||||
assert state_w1.attributes.get(ATTR_RECOMMENDED_ACTIONS) == ""
|
||||
assert (
|
||||
state_w1.attributes.get(ATTR_AFFECTED_AREAS)
|
||||
== "Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere."
|
||||
)
|
||||
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-NW-BN-SE030-20201014-30-000"
|
||||
assert state_w1.attributes.get(ATTR_SENT) == "2021-10-11T05:20:00+01:00"
|
||||
assert state_w1.attributes.get(ATTR_START) == "2021-11-01T05:20:00+01:00"
|
||||
|
@ -87,6 +99,7 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
|||
assert state_w2.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w2.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w2.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w2.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w2.attributes.get(ATTR_ID) is None
|
||||
assert state_w2.attributes.get(ATTR_SENT) is None
|
||||
assert state_w2.attributes.get(ATTR_START) is None
|
||||
|
@ -104,6 +117,7 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
|||
assert state_w3.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w3.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w3.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w3.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w3.attributes.get(ATTR_ID) is None
|
||||
assert state_w3.attributes.get(ATTR_SENT) is None
|
||||
assert state_w3.attributes.get(ATTR_START) is None
|
||||
|
@ -121,6 +135,7 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
|||
assert state_w4.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w4.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w4.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w4.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w4.attributes.get(ATTR_ID) is None
|
||||
assert state_w4.attributes.get(ATTR_SENT) is None
|
||||
assert state_w4.attributes.get(ATTR_START) is None
|
||||
|
@ -138,6 +153,7 @@ async def test_sensors(hass: HomeAssistant) -> None:
|
|||
assert state_w5.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w5.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w5.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w5.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w5.attributes.get(ATTR_ID) is None
|
||||
assert state_w5.attributes.get(ATTR_SENT) is None
|
||||
assert state_w5.attributes.get(ATTR_START) is None
|
||||
|
@ -184,6 +200,10 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
state_w1.attributes.get(ATTR_RECOMMENDED_ACTIONS)
|
||||
== "Waschen sich regelmäßig und gründlich die Hände."
|
||||
)
|
||||
assert (
|
||||
state_w1.attributes.get(ATTR_AFFECTED_AREAS)
|
||||
== "Bundesland: Freie Hansestadt Bremen, Land Berlin, Land Hessen, Land Nordrhein-Westfalen, Land Brandenburg, Freistaat Bayern, Land Mecklenburg-Vorpommern, Land Rheinland-Pfalz, Freistaat Sachsen, Land Schleswig-Holstein, Freie und Hansestadt Hamburg, Freistaat Thüringen, Land Niedersachsen, Land Saarland, Land Sachsen-Anhalt, Land Baden-Württemberg"
|
||||
)
|
||||
assert state_w1.attributes.get(ATTR_ID) == "mow.DE-BW-S-SE018-20211102-18-001"
|
||||
assert state_w1.attributes.get(ATTR_SENT) == "2021-11-02T20:07:16+01:00"
|
||||
assert state_w1.attributes.get(ATTR_START) == ""
|
||||
|
@ -201,6 +221,10 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
state_w2.attributes.get(ATTR_DESCRIPTION)
|
||||
== "Es treten Sturmböen mit Geschwindigkeiten zwischen 70 km/h (20m/s, 38kn, Bft 8) und 85 km/h (24m/s, 47kn, Bft 9) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit schweren Sturmböen bis 90 km/h (25m/s, 48kn, Bft 10) gerechnet werden."
|
||||
)
|
||||
assert (
|
||||
state_w2.attributes.get(ATTR_AFFECTED_AREAS)
|
||||
== "Gemeinde Oberreichenbach, Gemeinde Neuweiler, Stadt Nagold, Stadt Neubulach, Gemeinde Schömberg, Gemeinde Simmersfeld, Gemeinde Simmozheim, Gemeinde Rohrdorf, Gemeinde Ostelsheim, Gemeinde Ebhausen, Gemeinde Egenhausen, Gemeinde Dobel, Stadt Bad Liebenzell, Stadt Solingen, Stadt Haiterbach, Stadt Bad Herrenalb, Gemeinde Höfen an der Enz, Gemeinde Gechingen, Gemeinde Enzklösterle, Gemeinde Gutach (Schwarzwaldbahn) und 3392 weitere."
|
||||
)
|
||||
assert state_w2.attributes.get(ATTR_SENDER) == "Deutscher Wetterdienst"
|
||||
assert state_w2.attributes.get(ATTR_SEVERITY) == "Minor"
|
||||
assert state_w2.attributes.get(ATTR_RECOMMENDED_ACTIONS) == ""
|
||||
|
@ -221,6 +245,7 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
assert state_w3.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w3.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w3.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w3.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w3.attributes.get(ATTR_ID) is None
|
||||
assert state_w3.attributes.get(ATTR_SENT) is None
|
||||
assert state_w3.attributes.get(ATTR_START) is None
|
||||
|
@ -238,6 +263,7 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
assert state_w4.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w4.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w4.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w4.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w4.attributes.get(ATTR_ID) is None
|
||||
assert state_w4.attributes.get(ATTR_SENT) is None
|
||||
assert state_w4.attributes.get(ATTR_START) is None
|
||||
|
@ -255,6 +281,7 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
assert state_w5.attributes.get(ATTR_SENDER) is None
|
||||
assert state_w5.attributes.get(ATTR_SEVERITY) is None
|
||||
assert state_w5.attributes.get(ATTR_RECOMMENDED_ACTIONS) is None
|
||||
assert state_w5.attributes.get(ATTR_AFFECTED_AREAS) is None
|
||||
assert state_w5.attributes.get(ATTR_ID) is None
|
||||
assert state_w5.attributes.get(ATTR_SENT) is None
|
||||
assert state_w5.attributes.get(ATTR_START) is None
|
||||
|
@ -262,3 +289,63 @@ async def test_sensors_without_corona_filter(hass: HomeAssistant) -> None:
|
|||
|
||||
assert entry_w5.unique_id == "083350000000-5"
|
||||
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
||||
|
||||
async def test_sensors_with_area_filter(hass: HomeAssistant) -> None:
|
||||
"""Test the creation and values of the NINA sensors with an area filter."""
|
||||
|
||||
with patch(
|
||||
"pynina.baseApi.BaseAPI._makeRequest",
|
||||
wraps=mocked_request_function,
|
||||
):
|
||||
conf_entry: MockConfigEntry = MockConfigEntry(
|
||||
domain=DOMAIN, title="NINA", data=ENTRY_DATA_NO_AREA
|
||||
)
|
||||
|
||||
entity_registry: er = er.async_get(hass)
|
||||
conf_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(conf_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert conf_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
state_w1 = hass.states.get("binary_sensor.warning_aach_stadt_1")
|
||||
entry_w1 = entity_registry.async_get("binary_sensor.warning_aach_stadt_1")
|
||||
|
||||
assert state_w1.state == STATE_ON
|
||||
|
||||
assert entry_w1.unique_id == "083350000000-1"
|
||||
assert state_w1.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
||||
state_w2 = hass.states.get("binary_sensor.warning_aach_stadt_2")
|
||||
entry_w2 = entity_registry.async_get("binary_sensor.warning_aach_stadt_2")
|
||||
|
||||
assert state_w2.state == STATE_OFF
|
||||
|
||||
assert entry_w2.unique_id == "083350000000-2"
|
||||
assert state_w2.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
||||
state_w3 = hass.states.get("binary_sensor.warning_aach_stadt_3")
|
||||
entry_w3 = entity_registry.async_get("binary_sensor.warning_aach_stadt_3")
|
||||
|
||||
assert state_w3.state == STATE_OFF
|
||||
|
||||
assert entry_w3.unique_id == "083350000000-3"
|
||||
assert state_w3.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
||||
state_w4 = hass.states.get("binary_sensor.warning_aach_stadt_4")
|
||||
entry_w4 = entity_registry.async_get("binary_sensor.warning_aach_stadt_4")
|
||||
|
||||
assert state_w4.state == STATE_OFF
|
||||
|
||||
assert entry_w4.unique_id == "083350000000-4"
|
||||
assert state_w4.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
||||
state_w5 = hass.states.get("binary_sensor.warning_aach_stadt_5")
|
||||
entry_w5 = entity_registry.async_get("binary_sensor.warning_aach_stadt_5")
|
||||
|
||||
assert state_w5.state == STATE_OFF
|
||||
|
||||
assert entry_w5.unique_id == "083350000000-5"
|
||||
assert state_w5.attributes.get("device_class") == BinarySensorDeviceClass.SAFETY
|
||||
|
|
|
@ -10,6 +10,7 @@ from pynina import ApiError
|
|||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.nina.const import (
|
||||
CONF_AREA_FILTER,
|
||||
CONF_HEADLINE_FILTER,
|
||||
CONF_MESSAGE_SLOTS,
|
||||
CONF_REGIONS,
|
||||
|
@ -38,6 +39,7 @@ DUMMY_DATA: dict[str, Any] = {
|
|||
CONST_REGION_R_TO_U: ["072320000000_0", "072320000000_1"],
|
||||
CONST_REGION_V_TO_Z: ["081270000000_0", "081270000000_1"],
|
||||
CONF_HEADLINE_FILTER: ".*corona.*",
|
||||
CONF_AREA_FILTER: ".*",
|
||||
}
|
||||
|
||||
DUMMY_RESPONSE_REGIONS: dict[str, Any] = json.loads(
|
||||
|
@ -146,6 +148,7 @@ async def test_options_flow_init(hass: HomeAssistant) -> None:
|
|||
title="NINA",
|
||||
data={
|
||||
CONF_HEADLINE_FILTER: deepcopy(DUMMY_DATA[CONF_HEADLINE_FILTER]),
|
||||
CONF_AREA_FILTER: deepcopy(DUMMY_DATA[CONF_AREA_FILTER]),
|
||||
CONF_MESSAGE_SLOTS: deepcopy(DUMMY_DATA[CONF_MESSAGE_SLOTS]),
|
||||
CONST_REGION_A_TO_D: deepcopy(DUMMY_DATA[CONST_REGION_A_TO_D]),
|
||||
CONF_REGIONS: {"095760000000": "Aach"},
|
||||
|
@ -184,6 +187,7 @@ async def test_options_flow_init(hass: HomeAssistant) -> None:
|
|||
|
||||
assert dict(config_entry.data) == {
|
||||
CONF_HEADLINE_FILTER: deepcopy(DUMMY_DATA[CONF_HEADLINE_FILTER]),
|
||||
CONF_AREA_FILTER: deepcopy(DUMMY_DATA[CONF_AREA_FILTER]),
|
||||
CONF_MESSAGE_SLOTS: deepcopy(DUMMY_DATA[CONF_MESSAGE_SLOTS]),
|
||||
CONST_REGION_A_TO_D: ["072350000000_1"],
|
||||
CONST_REGION_E_TO_H: [],
|
||||
|
|
|
@ -16,6 +16,7 @@ from tests.common import MockConfigEntry
|
|||
ENTRY_DATA: dict[str, Any] = {
|
||||
"slots": 5,
|
||||
"headline_filter": ".*corona.*",
|
||||
"area_filter": ".*",
|
||||
"regions": {"083350000000": "Aach, Stadt"},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue