mirror of
https://github.com/XiaoMi/ha_xiaomi_home.git
synced 2026-01-12 03:40:43 +08:00
Merge e5a5bb5777 into f4d591b4d3
This commit is contained in:
commit
b5ddf3f0e7
@ -45,13 +45,15 @@ off Xiaomi or its affiliates' products.
|
||||
|
||||
Light entities for Xiaomi Home.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Optional, List, Dict
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
@ -59,34 +61,31 @@ from homeassistant.components.light import (
|
||||
ATTR_EFFECT,
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
ColorMode
|
||||
)
|
||||
from homeassistant.util.color import (
|
||||
value_to_brightness,
|
||||
brightness_to_value
|
||||
ColorMode,
|
||||
)
|
||||
from homeassistant.util.color import value_to_brightness, brightness_to_value
|
||||
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.miot_device import MIoTDevice, MIoTEntityData, MIoTServiceEntity
|
||||
from .miot.const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][
|
||||
config_entry.entry_id]
|
||||
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for data in miot_device.entity_list.get('light', []):
|
||||
for data in miot_device.entity_list.get("light", []):
|
||||
new_entities.append(
|
||||
Light(miot_device=miot_device, entity_data=data))
|
||||
Light(miot_device=miot_device, entity_data=data, hass=hass))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
@ -94,6 +93,7 @@ async def async_setup_entry(
|
||||
|
||||
class Light(MIoTServiceEntity, LightEntity):
|
||||
"""Light entities for Xiaomi Home."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
_VALUE_RANGE_MODE_COUNT_MAX = 30
|
||||
_prop_on: Optional[MIoTSpecProperty]
|
||||
@ -105,16 +105,17 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
_brightness_scale: Optional[tuple[int, int]]
|
||||
_mode_map: Optional[dict[Any, Any]]
|
||||
|
||||
def __init__(
|
||||
self, miot_device: MIoTDevice, entity_data: MIoTEntityData
|
||||
) -> None:
|
||||
def __init__(self, miot_device: MIoTDevice, entity_data: MIoTEntityData,
|
||||
hass: HomeAssistant) -> None:
|
||||
"""Initialize the Light."""
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
super().__init__(miot_device=miot_device, entity_data=entity_data)
|
||||
self.hass = hass
|
||||
self._attr_color_mode = None
|
||||
self._attr_supported_color_modes = set()
|
||||
self._attr_supported_features = LightEntityFeature(0)
|
||||
if miot_device.did.startswith('group.'):
|
||||
self._attr_icon = 'mdi:lightbulb-group'
|
||||
self.miot_device = miot_device
|
||||
if miot_device.did.startswith("group."):
|
||||
self._attr_icon = "mdi:lightbulb-group"
|
||||
|
||||
self._prop_on = None
|
||||
self._prop_brightness = None
|
||||
@ -123,37 +124,38 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self._prop_mode = None
|
||||
self._brightness_scale = None
|
||||
self._mode_map = None
|
||||
self._command_send_mode_entity_id = None
|
||||
|
||||
# properties
|
||||
for prop in entity_data.props:
|
||||
# on
|
||||
if prop.name == 'on':
|
||||
if prop.name == "on":
|
||||
self._prop_on = prop
|
||||
# brightness
|
||||
if prop.name == 'brightness':
|
||||
if prop.name == "brightness":
|
||||
if prop.value_range:
|
||||
self._brightness_scale = (
|
||||
prop.value_range.min_, prop.value_range.max_)
|
||||
prop.value_range.min_,
|
||||
prop.value_range.max_,
|
||||
)
|
||||
self._prop_brightness = prop
|
||||
elif (
|
||||
self._mode_map is None
|
||||
and prop.value_list
|
||||
):
|
||||
elif self._mode_map is None and prop.value_list:
|
||||
# For value-list brightness
|
||||
self._mode_map = prop.value_list.to_map()
|
||||
self._attr_effect_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
self._prop_mode = prop
|
||||
else:
|
||||
_LOGGER.info(
|
||||
'invalid brightness format, %s', self.entity_id)
|
||||
_LOGGER.info("invalid brightness format, %s",
|
||||
self.entity_id)
|
||||
continue
|
||||
# color-temperature
|
||||
if prop.name == 'color-temperature':
|
||||
if prop.name == "color-temperature":
|
||||
if not prop.value_range:
|
||||
_LOGGER.info(
|
||||
'invalid color-temperature value_range format, %s',
|
||||
self.entity_id)
|
||||
"invalid color-temperature value_range format, %s",
|
||||
self.entity_id,
|
||||
)
|
||||
continue
|
||||
self._attr_min_color_temp_kelvin = prop.value_range.min_
|
||||
self._attr_max_color_temp_kelvin = prop.value_range.max_
|
||||
@ -161,40 +163,40 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
self._prop_color_temp = prop
|
||||
# color
|
||||
if prop.name == 'color':
|
||||
if prop.name == "color":
|
||||
self._attr_supported_color_modes.add(ColorMode.RGB)
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
self._prop_color = prop
|
||||
# mode
|
||||
if prop.name == 'mode':
|
||||
if prop.name == "mode":
|
||||
mode_list = None
|
||||
if prop.value_list:
|
||||
mode_list = prop.value_list.to_map()
|
||||
elif prop.value_range:
|
||||
mode_list = {}
|
||||
if (
|
||||
int((
|
||||
prop.value_range.max_
|
||||
- prop.value_range.min_
|
||||
) / prop.value_range.step)
|
||||
> self._VALUE_RANGE_MODE_COUNT_MAX
|
||||
):
|
||||
if (int((prop.value_range.max_ - prop.value_range.min_) /
|
||||
prop.value_range.step)
|
||||
> self._VALUE_RANGE_MODE_COUNT_MAX):
|
||||
_LOGGER.error(
|
||||
'too many mode values, %s, %s, %s',
|
||||
self.entity_id, prop.name, prop.value_range)
|
||||
"too many mode values, %s, %s, %s",
|
||||
self.entity_id,
|
||||
prop.name,
|
||||
prop.value_range,
|
||||
)
|
||||
else:
|
||||
for value in range(
|
||||
prop.value_range.min_,
|
||||
prop.value_range.max_,
|
||||
prop.value_range.step):
|
||||
mode_list[value] = f'mode {value}'
|
||||
prop.value_range.step,
|
||||
):
|
||||
mode_list[value] = f"mode {value}"
|
||||
if mode_list:
|
||||
self._mode_map = mode_list
|
||||
self._attr_effect_list = list(self._mode_map.values())
|
||||
self._attr_supported_features |= LightEntityFeature.EFFECT
|
||||
self._prop_mode = prop
|
||||
else:
|
||||
_LOGGER.info('invalid mode format, %s', self.entity_id)
|
||||
_LOGGER.info("invalid mode format, %s", self.entity_id)
|
||||
continue
|
||||
|
||||
if not self._attr_supported_color_modes:
|
||||
@ -241,9 +243,8 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
@property
|
||||
def effect(self) -> Optional[str]:
|
||||
"""Return the current mode."""
|
||||
return self.get_map_value(
|
||||
map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
return self.get_map_value(map_=self._mode_map,
|
||||
key=self.get_prop_value(prop=self._prop_mode))
|
||||
|
||||
async def async_turn_on(self, **kwargs) -> None:
|
||||
"""Turn the light on.
|
||||
@ -252,42 +253,176 @@ class Light(MIoTServiceEntity, LightEntity):
|
||||
"""
|
||||
# on
|
||||
# Dirty logic for lumi.gateway.mgl03 indicator light
|
||||
if self._prop_on:
|
||||
value_on = True if self._prop_on.format_ == bool else 1
|
||||
await self.set_property_async(
|
||||
prop=self._prop_on, value=value_on)
|
||||
# brightness
|
||||
# Determine whether the device sends the light-on properties in batches or one by one
|
||||
# Search entityid through unique_id to avoid the user modifying entityid and causing command_send_mode to not match
|
||||
if self._command_send_mode_entity_id is None:
|
||||
entity_registry = async_get_entity_registry(self.hass)
|
||||
device_id = list(
|
||||
self.miot_device.device_info.get("identifiers"))[0][1]
|
||||
self._command_send_mode_entity_id = entity_registry.async_get_entity_id(
|
||||
"select", DOMAIN, f"select.light_{device_id}_command_send_mode")
|
||||
if self._command_send_mode_entity_id is None:
|
||||
_LOGGER.error(
|
||||
"light command_send_mode not found, %s",
|
||||
self.entity_id,
|
||||
)
|
||||
return
|
||||
command_send_mode = self.hass.states.get(
|
||||
self._command_send_mode_entity_id)
|
||||
send_brightness_first = False
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(
|
||||
self._brightness_scale, kwargs[ATTR_BRIGHTNESS])
|
||||
await self.set_property_async(
|
||||
prop=self._prop_brightness, value=brightness,
|
||||
write_ha_state=False)
|
||||
# color-temperature
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_color_temp,
|
||||
value=kwargs[ATTR_COLOR_TEMP_KELVIN],
|
||||
write_ha_state=False)
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
# rgb color
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
r = kwargs[ATTR_RGB_COLOR][0]
|
||||
g = kwargs[ATTR_RGB_COLOR][1]
|
||||
b = kwargs[ATTR_RGB_COLOR][2]
|
||||
rgb = (r << 16) | (g << 8) | b
|
||||
await self.set_property_async(
|
||||
prop=self._prop_color, value=rgb,
|
||||
write_ha_state=False)
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
# mode
|
||||
if ATTR_EFFECT in kwargs:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(
|
||||
map_=self._mode_map, value=kwargs[ATTR_EFFECT]),
|
||||
write_ha_state=False)
|
||||
self.async_write_ha_state()
|
||||
brightness_new = kwargs[ATTR_BRIGHTNESS]
|
||||
brightness_old = self.brightness
|
||||
if brightness_old and brightness_new <= brightness_old:
|
||||
send_brightness_first = True
|
||||
if command_send_mode and command_send_mode.state == "Send Together":
|
||||
set_properties_list: List[Dict[str, Any]] = []
|
||||
# mode
|
||||
if ATTR_EFFECT in kwargs:
|
||||
set_properties_list.append({
|
||||
"prop":
|
||||
self._prop_mode,
|
||||
"value":
|
||||
self.get_map_key(map_=self._mode_map,
|
||||
value=kwargs[ATTR_EFFECT]),
|
||||
})
|
||||
# brightness
|
||||
if send_brightness_first and ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(self._brightness_scale,
|
||||
kwargs[ATTR_BRIGHTNESS])
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_brightness,
|
||||
"value": brightness
|
||||
})
|
||||
# color-temperature
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_color_temp,
|
||||
"value": kwargs[ATTR_COLOR_TEMP_KELVIN],
|
||||
})
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
# rgb color
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
r = kwargs[ATTR_RGB_COLOR][0]
|
||||
g = kwargs[ATTR_RGB_COLOR][1]
|
||||
b = kwargs[ATTR_RGB_COLOR][2]
|
||||
rgb = (r << 16) | (g << 8) | b
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_color,
|
||||
"value": rgb
|
||||
})
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
# brightness
|
||||
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(self._brightness_scale,
|
||||
kwargs[ATTR_BRIGHTNESS])
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_brightness,
|
||||
"value": brightness
|
||||
})
|
||||
|
||||
if self._prop_on:
|
||||
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_on,
|
||||
"value": value_on
|
||||
})
|
||||
await self.set_properties_async(set_properties_list)
|
||||
self.async_write_ha_state()
|
||||
elif command_send_mode and command_send_mode.state == "Send Turn On First":
|
||||
set_properties_list: List[Dict[str, Any]] = []
|
||||
if self._prop_on:
|
||||
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_on,
|
||||
"value": value_on
|
||||
})
|
||||
# mode
|
||||
if ATTR_EFFECT in kwargs:
|
||||
set_properties_list.append({
|
||||
"prop":
|
||||
self._prop_mode,
|
||||
"value":
|
||||
self.get_map_key(map_=self._mode_map,
|
||||
value=kwargs[ATTR_EFFECT]),
|
||||
})
|
||||
# brightness
|
||||
if send_brightness_first and ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(self._brightness_scale,
|
||||
kwargs[ATTR_BRIGHTNESS])
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_brightness,
|
||||
"value": brightness
|
||||
})
|
||||
# color-temperature
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_color_temp,
|
||||
"value": kwargs[ATTR_COLOR_TEMP_KELVIN],
|
||||
})
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
# rgb color
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
r = kwargs[ATTR_RGB_COLOR][0]
|
||||
g = kwargs[ATTR_RGB_COLOR][1]
|
||||
b = kwargs[ATTR_RGB_COLOR][2]
|
||||
rgb = (r << 16) | (g << 8) | b
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_color,
|
||||
"value": rgb
|
||||
})
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
# brightness
|
||||
if not send_brightness_first and ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(self._brightness_scale,
|
||||
kwargs[ATTR_BRIGHTNESS])
|
||||
set_properties_list.append({
|
||||
"prop": self._prop_brightness,
|
||||
"value": brightness
|
||||
})
|
||||
|
||||
await self.set_properties_async(set_properties_list)
|
||||
self.async_write_ha_state()
|
||||
|
||||
else:
|
||||
if self._prop_on:
|
||||
value_on = True if self._prop_on.format_ == bool else 1 # noqa: E721
|
||||
await self.set_property_async(prop=self._prop_on,
|
||||
value=value_on)
|
||||
# color-temperature
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_color_temp,
|
||||
value=kwargs[ATTR_COLOR_TEMP_KELVIN],
|
||||
write_ha_state=False,
|
||||
)
|
||||
self._attr_color_mode = ColorMode.COLOR_TEMP
|
||||
# rgb color
|
||||
if ATTR_RGB_COLOR in kwargs:
|
||||
r = kwargs[ATTR_RGB_COLOR][0]
|
||||
g = kwargs[ATTR_RGB_COLOR][1]
|
||||
b = kwargs[ATTR_RGB_COLOR][2]
|
||||
rgb = (r << 16) | (g << 8) | b
|
||||
await self.set_property_async(prop=self._prop_color,
|
||||
value=rgb,
|
||||
write_ha_state=False)
|
||||
self._attr_color_mode = ColorMode.RGB
|
||||
# brightness
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = brightness_to_value(self._brightness_scale,
|
||||
kwargs[ATTR_BRIGHTNESS])
|
||||
await self.set_property_async(prop=self._prop_brightness,
|
||||
value=brightness,
|
||||
write_ha_state=False)
|
||||
# mode
|
||||
if ATTR_EFFECT in kwargs:
|
||||
await self.set_property_async(
|
||||
prop=self._prop_mode,
|
||||
value=self.get_map_key(map_=self._mode_map,
|
||||
value=kwargs[ATTR_EFFECT]),
|
||||
write_ha_state=False,
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs) -> None:
|
||||
"""Turn the light off."""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -45,18 +45,24 @@ off Xiaomi or its affiliates' products.
|
||||
|
||||
Select entities for Xiaomi Home.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .miot.const import DOMAIN
|
||||
from .miot.miot_device import MIoTDevice, MIoTPropertyEntity
|
||||
from .miot.miot_spec import MIoTSpecProperty
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@ -64,17 +70,30 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a config entry."""
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]['devices'][
|
||||
device_list: list[MIoTDevice] = hass.data[DOMAIN]["devices"][
|
||||
config_entry.entry_id]
|
||||
|
||||
new_entities = []
|
||||
for miot_device in device_list:
|
||||
for prop in miot_device.prop_list.get('select', []):
|
||||
for prop in miot_device.prop_list.get("select", []):
|
||||
new_entities.append(Select(miot_device=miot_device, spec=prop))
|
||||
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
# create select for light
|
||||
new_light_select_entities = []
|
||||
for miot_device in device_list:
|
||||
# Add it to all devices with light entities, because some bathroom heaters and clothes drying racks also have lights.
|
||||
# if "device:light" in miot_device.spec_instance.urn:
|
||||
if miot_device.entity_list.get("light", []):
|
||||
device_id = list(miot_device.device_info.get("identifiers"))[0][1]
|
||||
new_light_select_entities.append(
|
||||
LightCommandSendMode(hass=hass, device_id=device_id))
|
||||
|
||||
if new_light_select_entities:
|
||||
async_add_entities(new_light_select_entities)
|
||||
|
||||
|
||||
class Select(MIoTPropertyEntity, SelectEntity):
|
||||
"""Select entities for Xiaomi Home."""
|
||||
@ -87,10 +106,47 @@ class Select(MIoTPropertyEntity, SelectEntity):
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self.set_property_async(
|
||||
value=self.get_vlist_value(description=option))
|
||||
await self.set_property_async(value=self.get_vlist_value(
|
||||
description=option))
|
||||
|
||||
@property
|
||||
def current_option(self) -> Optional[str]:
|
||||
"""Return the current selected option."""
|
||||
return self.get_vlist_description(value=self._value)
|
||||
|
||||
|
||||
class LightCommandSendMode(SelectEntity, RestoreEntity):
|
||||
"""To control whether to turn on the light, you need to send the light-on command first and
|
||||
then send other color temperatures and brightness or send them all at the same time.
|
||||
The default is to send one by one."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, device_id: str):
|
||||
super().__init__()
|
||||
self.hass = hass
|
||||
self._device_id = device_id
|
||||
self._attr_name = "Command Send Mode"
|
||||
self.entity_id = f"select.light_{device_id}_command_send_mode"
|
||||
self._attr_unique_id = self.entity_id
|
||||
|
||||
self._attr_options = [
|
||||
"Send One by One", "Send Turn On First", "Send Together"
|
||||
]
|
||||
|
||||
self._attr_device_info = {"identifiers": {(DOMAIN, device_id)}}
|
||||
self._attr_current_option = self._attr_options[0]
|
||||
self._attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
async def async_select_option(self, option: str):
|
||||
if option in self._attr_options:
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
await super().async_added_to_hass()
|
||||
if (last_state := await self.async_get_last_state()
|
||||
) and last_state.state in self._attr_options:
|
||||
self._attr_current_option = last_state.state
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
return self._attr_current_option
|
||||
|
||||
Loading…
Reference in New Issue
Block a user