mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Fix Fujitsu fglair authentication error and other issues (#125439)
* Use correct app credentials when europe is checked * Rework to add china as well * Use our own package since the maintainer of the original package is not responding * Revert to using rewardone's package * Import app credentials where needed instead of __init__ * Rework region selector * Bump config entry minor and add migration * Address comments
This commit is contained in:
parent
ac93570476
commit
e2f1c60981
@ -5,14 +5,14 @@ from __future__ import annotations
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from ayla_iot_unofficial import new_ayla_api
|
from ayla_iot_unofficial import new_ayla_api
|
||||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_ID, FGLAIR_APP_SECRET
|
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import API_TIMEOUT, CONF_EUROPE
|
from .const import API_TIMEOUT, CONF_EUROPE, CONF_REGION, REGION_DEFAULT, REGION_EU
|
||||||
from .coordinator import FGLairCoordinator
|
from .coordinator import FGLairCoordinator
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||||
@ -22,12 +22,13 @@ type FGLairConfigEntry = ConfigEntry[FGLairCoordinator]
|
|||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> bool:
|
||||||
"""Set up Fujitsu HVAC (based on Ayla IOT) from a config entry."""
|
"""Set up Fujitsu HVAC (based on Ayla IOT) from a config entry."""
|
||||||
|
app_id, app_secret = FGLAIR_APP_CREDENTIALS[entry.data[CONF_REGION]]
|
||||||
api = new_ayla_api(
|
api = new_ayla_api(
|
||||||
entry.data[CONF_USERNAME],
|
entry.data[CONF_USERNAME],
|
||||||
entry.data[CONF_PASSWORD],
|
entry.data[CONF_PASSWORD],
|
||||||
FGLAIR_APP_ID,
|
app_id,
|
||||||
FGLAIR_APP_SECRET,
|
app_secret,
|
||||||
europe=entry.data[CONF_EUROPE],
|
europe=entry.data[CONF_REGION] == REGION_EU,
|
||||||
websession=aiohttp_client.async_get_clientsession(hass),
|
websession=aiohttp_client.async_get_clientsession(hass),
|
||||||
timeout=API_TIMEOUT,
|
timeout=API_TIMEOUT,
|
||||||
)
|
)
|
||||||
@ -48,3 +49,24 @@ async def async_unload_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> b
|
|||||||
await entry.runtime_data.api.async_sign_out()
|
await entry.runtime_data.api.async_sign_out()
|
||||||
|
|
||||||
return unload_ok
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, entry: FGLairConfigEntry) -> bool:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
if entry.version > 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if entry.version == 1:
|
||||||
|
new_data = {**entry.data}
|
||||||
|
if entry.minor_version < 2:
|
||||||
|
is_europe = new_data.get(CONF_EUROPE, False)
|
||||||
|
if is_europe:
|
||||||
|
new_data[CONF_REGION] = REGION_EU
|
||||||
|
else:
|
||||||
|
new_data[CONF_REGION] = REGION_DEFAULT
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry, data=new_data, minor_version=2, version=1
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
@ -5,14 +5,15 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ayla_iot_unofficial import AylaAuthError, new_ayla_api
|
from ayla_iot_unofficial import AylaAuthError, new_ayla_api
|
||||||
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_ID, FGLAIR_APP_SECRET
|
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
|
||||||
|
|
||||||
from .const import API_TIMEOUT, CONF_EUROPE, DOMAIN
|
from .const import API_TIMEOUT, CONF_REGION, DOMAIN, REGION_DEFAULT, REGION_EU
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -21,7 +22,12 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required(CONF_USERNAME): str,
|
vol.Required(CONF_USERNAME): str,
|
||||||
vol.Required(CONF_PASSWORD): str,
|
vol.Required(CONF_PASSWORD): str,
|
||||||
vol.Required(CONF_EUROPE): bool,
|
vol.Required(CONF_REGION, default=REGION_DEFAULT): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=[region.lower() for region in FGLAIR_APP_CREDENTIALS],
|
||||||
|
translation_key=CONF_REGION,
|
||||||
|
)
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
||||||
@ -34,18 +40,20 @@ STEP_REAUTH_DATA_SCHEMA = vol.Schema(
|
|||||||
class FGLairConfigFlow(ConfigFlow, domain=DOMAIN):
|
class FGLairConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Fujitsu HVAC (based on Ayla IOT)."""
|
"""Handle a config flow for Fujitsu HVAC (based on Ayla IOT)."""
|
||||||
|
|
||||||
|
MINOR_VERSION = 2
|
||||||
_reauth_entry: ConfigEntry | None = None
|
_reauth_entry: ConfigEntry | None = None
|
||||||
|
|
||||||
async def _async_validate_credentials(
|
async def _async_validate_credentials(
|
||||||
self, user_input: dict[str, Any]
|
self, user_input: dict[str, Any]
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
app_id, app_secret = FGLAIR_APP_CREDENTIALS[user_input[CONF_REGION]]
|
||||||
api = new_ayla_api(
|
api = new_ayla_api(
|
||||||
user_input[CONF_USERNAME],
|
user_input[CONF_USERNAME],
|
||||||
user_input[CONF_PASSWORD],
|
user_input[CONF_PASSWORD],
|
||||||
FGLAIR_APP_ID,
|
app_id,
|
||||||
FGLAIR_APP_SECRET,
|
app_secret,
|
||||||
europe=user_input[CONF_EUROPE],
|
europe=user_input[CONF_REGION] == REGION_EU,
|
||||||
websession=aiohttp_client.async_get_clientsession(self.hass),
|
websession=aiohttp_client.async_get_clientsession(self.hass),
|
||||||
timeout=API_TIMEOUT,
|
timeout=API_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
@ -7,4 +7,7 @@ API_REFRESH = timedelta(minutes=5)
|
|||||||
|
|
||||||
DOMAIN = "fujitsu_fglair"
|
DOMAIN = "fujitsu_fglair"
|
||||||
|
|
||||||
|
CONF_REGION = "region"
|
||||||
CONF_EUROPE = "is_europe"
|
CONF_EUROPE = "is_europe"
|
||||||
|
REGION_EU = "EU"
|
||||||
|
REGION_DEFAULT = "default"
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["ayla-iot-unofficial==1.3.1"]
|
"requirements": ["ayla-iot-unofficial==1.4.1"]
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"title": "Enter your FGLair credentials",
|
"title": "Enter your FGLair credentials",
|
||||||
"data": {
|
"data": {
|
||||||
"is_europe": "Use european servers",
|
"region": "Region",
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"is_europe": "Allows the user to choose whether to use european servers or not since the API uses different endoint URLs for european vs non-european users"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
@ -29,5 +26,14 @@
|
|||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"region": {
|
||||||
|
"options": {
|
||||||
|
"default": "Other",
|
||||||
|
"eu": "Europe",
|
||||||
|
"cn": "China"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -532,7 +532,7 @@ autarco==3.0.0
|
|||||||
axis==62
|
axis==62
|
||||||
|
|
||||||
# homeassistant.components.fujitsu_fglair
|
# homeassistant.components.fujitsu_fglair
|
||||||
ayla-iot-unofficial==1.3.1
|
ayla-iot-unofficial==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
|
@ -481,7 +481,7 @@ autarco==3.0.0
|
|||||||
axis==62
|
axis==62
|
||||||
|
|
||||||
# homeassistant.components.fujitsu_fglair
|
# homeassistant.components.fujitsu_fglair
|
||||||
ayla-iot-unofficial==1.3.1
|
ayla-iot-unofficial==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
|
@ -7,7 +7,11 @@ from ayla_iot_unofficial import AylaApi
|
|||||||
from ayla_iot_unofficial.fujitsu_hvac import FanSpeed, FujitsuHVAC, OpMode, SwingMode
|
from ayla_iot_unofficial.fujitsu_hvac import FanSpeed, FujitsuHVAC, OpMode, SwingMode
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fujitsu_fglair.const import CONF_EUROPE, DOMAIN
|
from homeassistant.components.fujitsu_fglair.const import (
|
||||||
|
CONF_REGION,
|
||||||
|
DOMAIN,
|
||||||
|
REGION_DEFAULT,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -57,15 +61,19 @@ def mock_ayla_api(mock_devices: list[AsyncMock]) -> Generator[AsyncMock]:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_config_entry() -> MockConfigEntry:
|
def mock_config_entry(request: pytest.FixtureRequest) -> MockConfigEntry:
|
||||||
"""Return a regular config entry."""
|
"""Return a regular config entry."""
|
||||||
|
region = REGION_DEFAULT
|
||||||
|
if hasattr(request, "param"):
|
||||||
|
region = request.param
|
||||||
|
|
||||||
return MockConfigEntry(
|
return MockConfigEntry(
|
||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
unique_id=TEST_USERNAME,
|
unique_id=TEST_USERNAME,
|
||||||
data={
|
data={
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_EUROPE: False,
|
CONF_REGION: region,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,11 @@ from unittest.mock import AsyncMock
|
|||||||
from ayla_iot_unofficial import AylaAuthError
|
from ayla_iot_unofficial import AylaAuthError
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fujitsu_fglair.const import CONF_EUROPE, DOMAIN
|
from homeassistant.components.fujitsu_fglair.const import (
|
||||||
|
CONF_REGION,
|
||||||
|
DOMAIN,
|
||||||
|
REGION_DEFAULT,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -28,7 +32,7 @@ async def _initial_step(hass: HomeAssistant) -> FlowResult:
|
|||||||
{
|
{
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_EUROPE: False,
|
CONF_REGION: REGION_DEFAULT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +49,7 @@ async def test_full_flow(
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_EUROPE: False,
|
CONF_REGION: REGION_DEFAULT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -94,7 +98,7 @@ async def test_form_exceptions(
|
|||||||
{
|
{
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_EUROPE: False,
|
CONF_REGION: REGION_DEFAULT,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -103,7 +107,7 @@ async def test_form_exceptions(
|
|||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_EUROPE: False,
|
CONF_REGION: REGION_DEFAULT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,17 +1,33 @@
|
|||||||
"""Test the initialization of fujitsu_fglair entities."""
|
"""Test the initialization of fujitsu_fglair entities."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from ayla_iot_unofficial import AylaAuthError
|
from ayla_iot_unofficial import AylaAuthError
|
||||||
|
from ayla_iot_unofficial.fujitsu_consts import FGLAIR_APP_CREDENTIALS
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.fujitsu_fglair.const import API_REFRESH, DOMAIN
|
from homeassistant.components.fujitsu_fglair.const import (
|
||||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
API_REFRESH,
|
||||||
|
API_TIMEOUT,
|
||||||
|
CONF_EUROPE,
|
||||||
|
CONF_REGION,
|
||||||
|
DOMAIN,
|
||||||
|
REGION_DEFAULT,
|
||||||
|
REGION_EU,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_USERNAME,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import aiohttp_client, entity_registry as er
|
||||||
|
|
||||||
from . import entity_id, setup_integration
|
from . import entity_id, setup_integration
|
||||||
|
from .conftest import TEST_PASSWORD, TEST_USERNAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
@ -35,6 +51,63 @@ async def test_auth_failure(
|
|||||||
assert hass.states.get(entity_id(mock_devices[1])).state == STATE_UNAVAILABLE
|
assert hass.states.get(entity_id(mock_devices[1])).state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mock_config_entry", FGLAIR_APP_CREDENTIALS.keys(), indirect=True
|
||||||
|
)
|
||||||
|
async def test_auth_regions(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_ayla_api: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_devices: list[AsyncMock],
|
||||||
|
) -> None:
|
||||||
|
"""Test that we use the correct credentials if europe is selected."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.fujitsu_fglair.new_ayla_api", return_value=AsyncMock()
|
||||||
|
) as new_ayla_api_patch:
|
||||||
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
new_ayla_api_patch.assert_called_once_with(
|
||||||
|
TEST_USERNAME,
|
||||||
|
TEST_PASSWORD,
|
||||||
|
FGLAIR_APP_CREDENTIALS[mock_config_entry.data[CONF_REGION]][0],
|
||||||
|
FGLAIR_APP_CREDENTIALS[mock_config_entry.data[CONF_REGION]][1],
|
||||||
|
europe=mock_config_entry.data[CONF_REGION] == "EU",
|
||||||
|
websession=aiohttp_client.async_get_clientsession(hass),
|
||||||
|
timeout=API_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("is_europe", [True, False])
|
||||||
|
async def test_migrate_entry_v11_v12(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_ayla_api: AsyncMock,
|
||||||
|
is_europe: bool,
|
||||||
|
mock_devices: list[AsyncMock],
|
||||||
|
) -> None:
|
||||||
|
"""Test migration from schema 1.1 to 1.2."""
|
||||||
|
v11_config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=TEST_USERNAME,
|
||||||
|
data={
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_EUROPE: is_europe,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await setup_integration(hass, v11_config_entry)
|
||||||
|
updated_entry = hass.config_entries.async_get_entry(v11_config_entry.entry_id)
|
||||||
|
|
||||||
|
assert updated_entry.state is ConfigEntryState.LOADED
|
||||||
|
assert updated_entry.version == 1
|
||||||
|
assert updated_entry.minor_version == 2
|
||||||
|
if is_europe:
|
||||||
|
assert updated_entry.data[CONF_REGION] is REGION_EU
|
||||||
|
else:
|
||||||
|
assert updated_entry.data[CONF_REGION] is REGION_DEFAULT
|
||||||
|
|
||||||
|
|
||||||
async def test_device_auth_failure(
|
async def test_device_auth_failure(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user