mirror of
https://github.com/home-assistant/core.git
synced 2025-06-07 14:47:12 +00:00
Use whoami for location lookup (#50934)
This commit is contained in:
parent
0cbcb9e0d6
commit
7ff14b47a8
@ -25,7 +25,14 @@ from homeassistant.util import location
|
|||||||
from homeassistant.util.json import load_json, save_json
|
from homeassistant.util.json import load_json, save_json
|
||||||
|
|
||||||
from .config_flow import PlayStation4FlowHandler # noqa: F401
|
from .config_flow import PlayStation4FlowHandler # noqa: F401
|
||||||
from .const import ATTR_MEDIA_IMAGE_URL, COMMANDS, DOMAIN, GAMES_FILE, PS4_DATA
|
from .const import (
|
||||||
|
ATTR_MEDIA_IMAGE_URL,
|
||||||
|
COMMANDS,
|
||||||
|
COUNTRYCODE_NAMES,
|
||||||
|
DOMAIN,
|
||||||
|
GAMES_FILE,
|
||||||
|
PS4_DATA,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -91,7 +98,7 @@ async def async_migrate_entry(hass, entry):
|
|||||||
hass.helpers.aiohttp_client.async_get_clientsession()
|
hass.helpers.aiohttp_client.async_get_clientsession()
|
||||||
)
|
)
|
||||||
if loc:
|
if loc:
|
||||||
country = loc.country_name
|
country = COUNTRYCODE_NAMES.get(loc.country_code)
|
||||||
if country in COUNTRIES:
|
if country in COUNTRIES:
|
||||||
for device in data["devices"]:
|
for device in data["devices"]:
|
||||||
device[CONF_REGION] = country
|
device[CONF_REGION] = country
|
||||||
|
@ -17,7 +17,13 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.util import location
|
from homeassistant.util import location
|
||||||
|
|
||||||
from .const import CONFIG_ENTRY_VERSION, DEFAULT_ALIAS, DEFAULT_NAME, DOMAIN
|
from .const import (
|
||||||
|
CONFIG_ENTRY_VERSION,
|
||||||
|
COUNTRYCODE_NAMES,
|
||||||
|
DEFAULT_ALIAS,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
CONF_MODE = "Config Mode"
|
CONF_MODE = "Config Mode"
|
||||||
CONF_AUTO = "Auto Discover"
|
CONF_AUTO = "Auto Discover"
|
||||||
@ -178,7 +184,7 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
self.hass.helpers.aiohttp_client.async_get_clientsession()
|
self.hass.helpers.aiohttp_client.async_get_clientsession()
|
||||||
)
|
)
|
||||||
if self.location:
|
if self.location:
|
||||||
country = self.location.country_name
|
country = COUNTRYCODE_NAMES.get(self.location.country_code)
|
||||||
if country in COUNTRIES:
|
if country in COUNTRIES:
|
||||||
default_region = country
|
default_region = country
|
||||||
|
|
||||||
|
@ -12,3 +12,71 @@ COMMANDS = ("up", "down", "right", "left", "enter", "back", "option", "ps", "ps_
|
|||||||
|
|
||||||
# Deprecated used for logger/backwards compatibility from 0.89
|
# Deprecated used for logger/backwards compatibility from 0.89
|
||||||
REGIONS = ["R1", "R2", "R3", "R4", "R5"]
|
REGIONS = ["R1", "R2", "R3", "R4", "R5"]
|
||||||
|
|
||||||
|
COUNTRYCODE_NAMES = {
|
||||||
|
"AE": "United Arab Emirates",
|
||||||
|
"AR": "Argentina",
|
||||||
|
"AT": "Austria",
|
||||||
|
"AU": "Australia",
|
||||||
|
"BE": "Belgium",
|
||||||
|
"BG": "Bulgaria",
|
||||||
|
"BH": "Bahrain",
|
||||||
|
"BR": "Brazil",
|
||||||
|
"CA": "Canada",
|
||||||
|
"CH": "Switzerland",
|
||||||
|
"CL": "Chile",
|
||||||
|
"CO": "Columbia",
|
||||||
|
"CR": "Costa Rica",
|
||||||
|
"CY": "Cyprus",
|
||||||
|
"CZ": "Czech Republic",
|
||||||
|
"DE": "Germany",
|
||||||
|
"DK": "Denmark",
|
||||||
|
"EC": "Ecuador",
|
||||||
|
"ES": "Spain",
|
||||||
|
"FI": "Finland",
|
||||||
|
"FR": "France",
|
||||||
|
"GB": "United Kingdom",
|
||||||
|
"GR": "Greece",
|
||||||
|
"GT": "Guatemala",
|
||||||
|
"HK": "Hong Kong",
|
||||||
|
"HN": "Honduras",
|
||||||
|
"HR": "Croatia",
|
||||||
|
"HU": "Hungary",
|
||||||
|
"ID": "Indonesia",
|
||||||
|
"IE": "Ireland",
|
||||||
|
"IL": "Israel",
|
||||||
|
"IN": "India",
|
||||||
|
"IS": "Iceland",
|
||||||
|
"IT": "Italy",
|
||||||
|
"JP": "Japan",
|
||||||
|
"KW": "Kuwait",
|
||||||
|
"LB": "Lebanon",
|
||||||
|
"LU": "Luxembourg",
|
||||||
|
"MT": "Malta",
|
||||||
|
"MX": "Mexico",
|
||||||
|
"MY": "Maylasia",
|
||||||
|
"NI": "Nicaragua",
|
||||||
|
"NL": "Nederland",
|
||||||
|
"NO": "Norway",
|
||||||
|
"NZ": "New Zealand",
|
||||||
|
"OM": "Oman",
|
||||||
|
"PA": "Panama",
|
||||||
|
"PE": "Peru",
|
||||||
|
"PL": "Poland",
|
||||||
|
"PT": "Portugal",
|
||||||
|
"QA": "Qatar",
|
||||||
|
"RO": "Romania",
|
||||||
|
"RU": "Russia",
|
||||||
|
"SA": "Saudi Arabia",
|
||||||
|
"SE": "Sweden",
|
||||||
|
"SG": "Singapore",
|
||||||
|
"SI": "Slovenia",
|
||||||
|
"SK": "Slovakia",
|
||||||
|
"SV": "El Salvador",
|
||||||
|
"TH": "Thailand",
|
||||||
|
"TR": "Turkey",
|
||||||
|
"TW": "Taiwan",
|
||||||
|
"UA": "Ukraine",
|
||||||
|
"US": "United States",
|
||||||
|
"ZA": "South Africa",
|
||||||
|
}
|
||||||
|
@ -12,9 +12,7 @@ from typing import Any
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
ELEVATION_URL = "https://api.open-elevation.com/api/v1/lookup"
|
WHOAMI_URL = "https://whoami.home-assistant.io/v1"
|
||||||
IP_API = "http://ip-api.com/json"
|
|
||||||
IPAPI = "https://ipapi.co/json/"
|
|
||||||
|
|
||||||
# Constants from https://github.com/maurycyp/vincenty
|
# Constants from https://github.com/maurycyp/vincenty
|
||||||
# Earth ellipsoid according to WGS 84
|
# Earth ellipsoid according to WGS 84
|
||||||
@ -34,7 +32,6 @@ LocationInfo = collections.namedtuple(
|
|||||||
[
|
[
|
||||||
"ip",
|
"ip",
|
||||||
"country_code",
|
"country_code",
|
||||||
"country_name",
|
|
||||||
"region_code",
|
"region_code",
|
||||||
"region_name",
|
"region_name",
|
||||||
"city",
|
"city",
|
||||||
@ -51,10 +48,7 @@ async def async_detect_location_info(
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> LocationInfo | None:
|
) -> LocationInfo | None:
|
||||||
"""Detect location information."""
|
"""Detect location information."""
|
||||||
data = await _get_ipapi(session)
|
data = await _get_whoami(session)
|
||||||
|
|
||||||
if data is None:
|
|
||||||
data = await _get_ip_api(session)
|
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
@ -164,10 +158,10 @@ def vincenty(
|
|||||||
return round(s, 6)
|
return round(s, 6)
|
||||||
|
|
||||||
|
|
||||||
async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
async def _get_whoami(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
||||||
"""Query ipapi.co for location data."""
|
"""Query whoami.home-assistant.io for location data."""
|
||||||
try:
|
try:
|
||||||
resp = await session.get(IPAPI, timeout=5)
|
resp = await session.get(WHOAMI_URL, timeout=30)
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
except (aiohttp.ClientError, asyncio.TimeoutError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -176,44 +170,14 @@ async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
|||||||
except (aiohttp.ClientError, ValueError):
|
except (aiohttp.ClientError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ipapi allows 30k free requests/month. Some users exhaust those.
|
|
||||||
if raw_info.get("latitude") == "Sign up to access":
|
|
||||||
return None
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"ip": raw_info.get("ip"),
|
"ip": raw_info.get("ip"),
|
||||||
"country_code": raw_info.get("country"),
|
"country_code": raw_info.get("country"),
|
||||||
"country_name": raw_info.get("country_name"),
|
|
||||||
"region_code": raw_info.get("region_code"),
|
"region_code": raw_info.get("region_code"),
|
||||||
"region_name": raw_info.get("region"),
|
"region_name": raw_info.get("region"),
|
||||||
"city": raw_info.get("city"),
|
"city": raw_info.get("city"),
|
||||||
"zip_code": raw_info.get("postal"),
|
"zip_code": raw_info.get("postal_code"),
|
||||||
"time_zone": raw_info.get("timezone"),
|
"time_zone": raw_info.get("timezone"),
|
||||||
"latitude": raw_info.get("latitude"),
|
"latitude": float(raw_info.get("latitude")),
|
||||||
"longitude": raw_info.get("longitude"),
|
"longitude": float(raw_info.get("longitude")),
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_ip_api(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
|
||||||
"""Query ip-api.com for location data."""
|
|
||||||
try:
|
|
||||||
resp = await session.get(IP_API, timeout=5)
|
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
raw_info = await resp.json()
|
|
||||||
except (aiohttp.ClientError, ValueError):
|
|
||||||
return None
|
|
||||||
return {
|
|
||||||
"ip": raw_info.get("query"),
|
|
||||||
"country_code": raw_info.get("countryCode"),
|
|
||||||
"country_name": raw_info.get("country"),
|
|
||||||
"region_code": raw_info.get("region"),
|
|
||||||
"region_name": raw_info.get("regionName"),
|
|
||||||
"city": raw_info.get("city"),
|
|
||||||
"zip_code": raw_info.get("zip"),
|
|
||||||
"time_zone": raw_info.get("timezone"),
|
|
||||||
"latitude": raw_info.get("lat"),
|
|
||||||
"longitude": raw_info.get("lon"),
|
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,6 @@ async def test_detect_config_fail(hass, client):
|
|||||||
return_value=location.LocationInfo(
|
return_value=location.LocationInfo(
|
||||||
ip=None,
|
ip=None,
|
||||||
country_code=None,
|
country_code=None,
|
||||||
country_name=None,
|
|
||||||
region_code=None,
|
region_code=None,
|
||||||
region_name=None,
|
region_name=None,
|
||||||
city=None,
|
city=None,
|
||||||
|
@ -64,7 +64,6 @@ MOCK_MANUAL = {"Config Mode": "Manual Entry", CONF_IP_ADDRESS: MOCK_HOST}
|
|||||||
MOCK_LOCATION = location.LocationInfo(
|
MOCK_LOCATION = location.LocationInfo(
|
||||||
"0.0.0.0",
|
"0.0.0.0",
|
||||||
"US",
|
"US",
|
||||||
"United States",
|
|
||||||
"CA",
|
"CA",
|
||||||
"California",
|
"California",
|
||||||
"San Diego",
|
"San Diego",
|
||||||
|
@ -56,7 +56,6 @@ MOCK_CONFIG = MockConfigEntry(domain=DOMAIN, data=MOCK_DATA, entry_id=MOCK_ENTRY
|
|||||||
MOCK_LOCATION = location.LocationInfo(
|
MOCK_LOCATION = location.LocationInfo(
|
||||||
"0.0.0.0",
|
"0.0.0.0",
|
||||||
"US",
|
"US",
|
||||||
"United States",
|
|
||||||
"CA",
|
"CA",
|
||||||
"California",
|
"California",
|
||||||
"San Diego",
|
"San Diego",
|
||||||
|
16
tests/fixtures/ip-api.com.json
vendored
16
tests/fixtures/ip-api.com.json
vendored
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"as": "AS20001 Time Warner Cable Internet LLC",
|
|
||||||
"city": "San Diego",
|
|
||||||
"country": "United States",
|
|
||||||
"countryCode": "US",
|
|
||||||
"isp": "Time Warner Cable",
|
|
||||||
"lat": 32.8594,
|
|
||||||
"lon": -117.2073,
|
|
||||||
"org": "Time Warner Cable",
|
|
||||||
"query": "1.2.3.4",
|
|
||||||
"region": "CA",
|
|
||||||
"regionName": "California",
|
|
||||||
"status": "success",
|
|
||||||
"timezone": "America\/Los_Angeles",
|
|
||||||
"zip": "92122"
|
|
||||||
}
|
|
20
tests/fixtures/ipapi.co.json
vendored
20
tests/fixtures/ipapi.co.json
vendored
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"ip": "1.2.3.4",
|
|
||||||
"city": "Bern",
|
|
||||||
"region": "Bern",
|
|
||||||
"region_code": "BE",
|
|
||||||
"country": "CH",
|
|
||||||
"country_name": "Switzerland",
|
|
||||||
"continent_code": "EU",
|
|
||||||
"in_eu": false,
|
|
||||||
"postal": "3000",
|
|
||||||
"latitude": 46.9480278,
|
|
||||||
"longitude": 7.4490812,
|
|
||||||
"timezone": "Europe/Zurich",
|
|
||||||
"utc_offset": "+0100",
|
|
||||||
"country_calling_code": "+41",
|
|
||||||
"currency": "CHF",
|
|
||||||
"languages": "de-CH,fr-CH,it-CH,rm",
|
|
||||||
"asn": "AS6830",
|
|
||||||
"org": "Liberty Global B.V."
|
|
||||||
}
|
|
14
tests/fixtures/whoami.json
vendored
Normal file
14
tests/fixtures/whoami.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"ip": "1.2.3.4",
|
||||||
|
"city": "Gotham",
|
||||||
|
"continent": "Earth",
|
||||||
|
"country": "XX",
|
||||||
|
"latitude": "12.34567",
|
||||||
|
"longitude": "12.34567",
|
||||||
|
"postal_code": "12345",
|
||||||
|
"region_code": "00",
|
||||||
|
"region": "Gotham",
|
||||||
|
"timezone": "Earth/Gotham",
|
||||||
|
"iso_time": "2021-05-12T11:29:15.752Z",
|
||||||
|
"timestamp": 1620818956
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
"""Test Home Assistant location util methods."""
|
"""Test Home Assistant location util methods."""
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import pytest
|
import pytest
|
||||||
@ -72,76 +72,25 @@ def test_get_miles():
|
|||||||
assert round(miles, 2) == DISTANCE_MILES
|
assert round(miles, 2) == DISTANCE_MILES
|
||||||
|
|
||||||
|
|
||||||
async def test_detect_location_info_ipapi(aioclient_mock, session):
|
async def test_detect_location_info_whoami(aioclient_mock, session):
|
||||||
"""Test detect location info using ipapi.co."""
|
"""Test detect location info using whoami.home-assistant.io."""
|
||||||
aioclient_mock.get(location_util.IPAPI, text=load_fixture("ipapi.co.json"))
|
aioclient_mock.get(location_util.WHOAMI_URL, text=load_fixture("whoami.json"))
|
||||||
|
|
||||||
info = await location_util.async_detect_location_info(session, _test_real=True)
|
info = await location_util.async_detect_location_info(session, _test_real=True)
|
||||||
|
|
||||||
assert info is not None
|
assert info is not None
|
||||||
assert info.ip == "1.2.3.4"
|
assert info.ip == "1.2.3.4"
|
||||||
assert info.country_code == "CH"
|
assert info.country_code == "XX"
|
||||||
assert info.country_name == "Switzerland"
|
assert info.region_code == "00"
|
||||||
assert info.region_code == "BE"
|
assert info.city == "Gotham"
|
||||||
assert info.region_name == "Bern"
|
assert info.zip_code == "12345"
|
||||||
assert info.city == "Bern"
|
assert info.time_zone == "Earth/Gotham"
|
||||||
assert info.zip_code == "3000"
|
assert info.latitude == 12.34567
|
||||||
assert info.time_zone == "Europe/Zurich"
|
assert info.longitude == 12.34567
|
||||||
assert info.latitude == 46.9480278
|
|
||||||
assert info.longitude == 7.4490812
|
|
||||||
assert info.use_metric
|
assert info.use_metric
|
||||||
|
|
||||||
|
|
||||||
async def test_detect_location_info_ipapi_exhaust(aioclient_mock, session):
|
async def test_whoami_query_raises(raising_session):
|
||||||
"""Test detect location info using ipapi.co."""
|
"""Test whoami query when the request to API fails."""
|
||||||
aioclient_mock.get(location_util.IPAPI, json={"latitude": "Sign up to access"})
|
info = await location_util._get_whoami(raising_session)
|
||||||
aioclient_mock.get(location_util.IP_API, text=load_fixture("ip-api.com.json"))
|
|
||||||
|
|
||||||
info = await location_util.async_detect_location_info(session, _test_real=True)
|
|
||||||
|
|
||||||
assert info is not None
|
|
||||||
# ip_api result because ipapi got skipped
|
|
||||||
assert info.country_code == "US"
|
|
||||||
assert len(aioclient_mock.mock_calls) == 2
|
|
||||||
|
|
||||||
|
|
||||||
async def test_detect_location_info_ip_api(aioclient_mock, session):
|
|
||||||
"""Test detect location info using ip-api.com."""
|
|
||||||
aioclient_mock.get(location_util.IP_API, text=load_fixture("ip-api.com.json"))
|
|
||||||
|
|
||||||
with patch("homeassistant.util.location._get_ipapi", return_value=None):
|
|
||||||
info = await location_util.async_detect_location_info(session, _test_real=True)
|
|
||||||
|
|
||||||
assert info is not None
|
|
||||||
assert info.ip == "1.2.3.4"
|
|
||||||
assert info.country_code == "US"
|
|
||||||
assert info.country_name == "United States"
|
|
||||||
assert info.region_code == "CA"
|
|
||||||
assert info.region_name == "California"
|
|
||||||
assert info.city == "San Diego"
|
|
||||||
assert info.zip_code == "92122"
|
|
||||||
assert info.time_zone == "America/Los_Angeles"
|
|
||||||
assert info.latitude == 32.8594
|
|
||||||
assert info.longitude == -117.2073
|
|
||||||
assert not info.use_metric
|
|
||||||
|
|
||||||
|
|
||||||
async def test_detect_location_info_both_queries_fail(session):
|
|
||||||
"""Ensure we return None if both queries fail."""
|
|
||||||
with patch("homeassistant.util.location._get_ipapi", return_value=None), patch(
|
|
||||||
"homeassistant.util.location._get_ip_api", return_value=None
|
|
||||||
):
|
|
||||||
info = await location_util.async_detect_location_info(session, _test_real=True)
|
|
||||||
assert info is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_freegeoip_query_raises(raising_session):
|
|
||||||
"""Test ipapi.co query when the request to API fails."""
|
|
||||||
info = await location_util._get_ipapi(raising_session)
|
|
||||||
assert info is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_ip_api_query_raises(raising_session):
|
|
||||||
"""Test ip api query when the request to API fails."""
|
|
||||||
info = await location_util._get_ip_api(raising_session)
|
|
||||||
assert info is None
|
assert info is None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user