Use whoami for location lookup (#50934)
This commit is contained in:
parent
0cbcb9e0d6
commit
7ff14b47a8
11 changed files with 121 additions and 152 deletions
|
@ -25,7 +25,14 @@ from homeassistant.util import location
|
|||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
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__)
|
||||
|
||||
|
@ -91,7 +98,7 @@ async def async_migrate_entry(hass, entry):
|
|||
hass.helpers.aiohttp_client.async_get_clientsession()
|
||||
)
|
||||
if loc:
|
||||
country = loc.country_name
|
||||
country = COUNTRYCODE_NAMES.get(loc.country_code)
|
||||
if country in COUNTRIES:
|
||||
for device in data["devices"]:
|
||||
device[CONF_REGION] = country
|
||||
|
|
|
@ -17,7 +17,13 @@ from homeassistant.const import (
|
|||
)
|
||||
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_AUTO = "Auto Discover"
|
||||
|
@ -178,7 +184,7 @@ class PlayStation4FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self.hass.helpers.aiohttp_client.async_get_clientsession()
|
||||
)
|
||||
if self.location:
|
||||
country = self.location.country_name
|
||||
country = COUNTRYCODE_NAMES.get(self.location.country_code)
|
||||
if country in COUNTRIES:
|
||||
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
|
||||
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
|
||||
|
||||
ELEVATION_URL = "https://api.open-elevation.com/api/v1/lookup"
|
||||
IP_API = "http://ip-api.com/json"
|
||||
IPAPI = "https://ipapi.co/json/"
|
||||
WHOAMI_URL = "https://whoami.home-assistant.io/v1"
|
||||
|
||||
# Constants from https://github.com/maurycyp/vincenty
|
||||
# Earth ellipsoid according to WGS 84
|
||||
|
@ -34,7 +32,6 @@ LocationInfo = collections.namedtuple(
|
|||
[
|
||||
"ip",
|
||||
"country_code",
|
||||
"country_name",
|
||||
"region_code",
|
||||
"region_name",
|
||||
"city",
|
||||
|
@ -51,10 +48,7 @@ async def async_detect_location_info(
|
|||
session: aiohttp.ClientSession,
|
||||
) -> LocationInfo | None:
|
||||
"""Detect location information."""
|
||||
data = await _get_ipapi(session)
|
||||
|
||||
if data is None:
|
||||
data = await _get_ip_api(session)
|
||||
data = await _get_whoami(session)
|
||||
|
||||
if data is None:
|
||||
return None
|
||||
|
@ -164,10 +158,10 @@ def vincenty(
|
|||
return round(s, 6)
|
||||
|
||||
|
||||
async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
||||
"""Query ipapi.co for location data."""
|
||||
async def _get_whoami(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
||||
"""Query whoami.home-assistant.io for location data."""
|
||||
try:
|
||||
resp = await session.get(IPAPI, timeout=5)
|
||||
resp = await session.get(WHOAMI_URL, timeout=30)
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
||||
return None
|
||||
|
||||
|
@ -176,44 +170,14 @@ async def _get_ipapi(session: aiohttp.ClientSession) -> dict[str, Any] | None:
|
|||
except (aiohttp.ClientError, ValueError):
|
||||
return None
|
||||
|
||||
# ipapi allows 30k free requests/month. Some users exhaust those.
|
||||
if raw_info.get("latitude") == "Sign up to access":
|
||||
return None
|
||||
|
||||
return {
|
||||
"ip": raw_info.get("ip"),
|
||||
"country_code": raw_info.get("country"),
|
||||
"country_name": raw_info.get("country_name"),
|
||||
"region_code": raw_info.get("region_code"),
|
||||
"region_name": raw_info.get("region"),
|
||||
"city": raw_info.get("city"),
|
||||
"zip_code": raw_info.get("postal"),
|
||||
"zip_code": raw_info.get("postal_code"),
|
||||
"time_zone": raw_info.get("timezone"),
|
||||
"latitude": raw_info.get("latitude"),
|
||||
"longitude": 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"),
|
||||
"latitude": float(raw_info.get("latitude")),
|
||||
"longitude": float(raw_info.get("longitude")),
|
||||
}
|
||||
|
|
|
@ -144,7 +144,6 @@ async def test_detect_config_fail(hass, client):
|
|||
return_value=location.LocationInfo(
|
||||
ip=None,
|
||||
country_code=None,
|
||||
country_name=None,
|
||||
region_code=None,
|
||||
region_name=None,
|
||||
city=None,
|
||||
|
|
|
@ -64,7 +64,6 @@ MOCK_MANUAL = {"Config Mode": "Manual Entry", CONF_IP_ADDRESS: MOCK_HOST}
|
|||
MOCK_LOCATION = location.LocationInfo(
|
||||
"0.0.0.0",
|
||||
"US",
|
||||
"United States",
|
||||
"CA",
|
||||
"California",
|
||||
"San Diego",
|
||||
|
|
|
@ -56,7 +56,6 @@ MOCK_CONFIG = MockConfigEntry(domain=DOMAIN, data=MOCK_DATA, entry_id=MOCK_ENTRY
|
|||
MOCK_LOCATION = location.LocationInfo(
|
||||
"0.0.0.0",
|
||||
"US",
|
||||
"United States",
|
||||
"CA",
|
||||
"California",
|
||||
"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."""
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import Mock
|
||||
|
||||
import aiohttp
|
||||
import pytest
|
||||
|
@ -72,76 +72,25 @@ def test_get_miles():
|
|||
assert round(miles, 2) == DISTANCE_MILES
|
||||
|
||||
|
||||
async def test_detect_location_info_ipapi(aioclient_mock, session):
|
||||
"""Test detect location info using ipapi.co."""
|
||||
aioclient_mock.get(location_util.IPAPI, text=load_fixture("ipapi.co.json"))
|
||||
async def test_detect_location_info_whoami(aioclient_mock, session):
|
||||
"""Test detect location info using whoami.home-assistant.io."""
|
||||
aioclient_mock.get(location_util.WHOAMI_URL, text=load_fixture("whoami.json"))
|
||||
|
||||
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 == "CH"
|
||||
assert info.country_name == "Switzerland"
|
||||
assert info.region_code == "BE"
|
||||
assert info.region_name == "Bern"
|
||||
assert info.city == "Bern"
|
||||
assert info.zip_code == "3000"
|
||||
assert info.time_zone == "Europe/Zurich"
|
||||
assert info.latitude == 46.9480278
|
||||
assert info.longitude == 7.4490812
|
||||
assert info.country_code == "XX"
|
||||
assert info.region_code == "00"
|
||||
assert info.city == "Gotham"
|
||||
assert info.zip_code == "12345"
|
||||
assert info.time_zone == "Earth/Gotham"
|
||||
assert info.latitude == 12.34567
|
||||
assert info.longitude == 12.34567
|
||||
assert info.use_metric
|
||||
|
||||
|
||||
async def test_detect_location_info_ipapi_exhaust(aioclient_mock, session):
|
||||
"""Test detect location info using ipapi.co."""
|
||||
aioclient_mock.get(location_util.IPAPI, json={"latitude": "Sign up to access"})
|
||||
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)
|
||||
async def test_whoami_query_raises(raising_session):
|
||||
"""Test whoami query when the request to API fails."""
|
||||
info = await location_util._get_whoami(raising_session)
|
||||
assert info is None
|
||||
|
|
Loading…
Add table
Reference in a new issue