From bd5f3034484d530b02d3f45110230f07fc6ee724 Mon Sep 17 00:00:00 2001 From: ImBenji Date: Tue, 19 Aug 2025 21:22:24 +0100 Subject: [PATCH] Enhance geolocation data retrieval by implementing multiple API sources with error handling --- lib/core/utils.dart | 215 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 8 deletions(-) diff --git a/lib/core/utils.dart b/lib/core/utils.dart index 28fcde7..32bb8a4 100644 --- a/lib/core/utils.dart +++ b/lib/core/utils.dart @@ -127,16 +127,215 @@ class GeolocationData { } } -// Get the geolocation data of the device using its IP address. Future getGeolocationData() async { - // Get the IP address of the server. https://ipapi.co/json/ - var response = await http.get(Uri.parse('https://ipapi.co/json/')); - if (response.statusCode == 200) { - var data = jsonDecode(response.body); - return GeolocationData.fromJson(data); - } else { - throw Exception('Failed to get geolocation data'); + // List of API configurations with their respective mappers + final apiConfigs = [ + _ApiConfig( + url: 'https://ipapi.co/json/', + mapper: _mapIpapiCoResponse, + name: 'ipapi.co', + ), + _ApiConfig( + url: 'http://ip-api.com/json/', + mapper: _mapIpApiResponse, + name: 'ip-api.com', + ), + _ApiConfig( + url: 'https://freeipapi.com/api/json/', + mapper: _mapFreeIpApiResponse, + name: 'freeipapi.com', + ), + _ApiConfig( + url: 'http://ipwhois.app/json/', + mapper: _mapIpWhoisResponse, + name: 'ipwhois.app', + ), + ]; + + Exception? lastException; + + for (final config in apiConfigs) { + try { + print('Trying ${config.name}...'); + + final response = await http.get( + Uri.parse(config.url), + headers: {'User-Agent': 'Dart/3.0'}, + ).timeout(const Duration(seconds: 10)); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + + // Check if the response indicates success (for APIs that include status) + if (data is Map) { + final status = data['status']?.toString().toLowerCase(); + if (status != null && status != 'success' && status != 'ok') { + throw Exception('API returned error status: $status'); + } + } + + final geolocationData = config.mapper(data); + print('Successfully got data from ${config.name}'); + return geolocationData; + } else { + throw Exception('HTTP ${response.statusCode}: ${response.reasonPhrase}'); + } + } catch (e) { + print('Failed to get data from ${config.name}: $e'); + lastException = Exception('${config.name}: $e'); + + // Add delay before trying next API to avoid hitting rate limits + if (config != apiConfigs.last) { + await Future.delayed(const Duration(milliseconds: 500)); + } + } } + + throw lastException ?? Exception('All geolocation APIs failed'); +} + +class _ApiConfig { + final String url; + final GeolocationData Function(Map) mapper; + final String name; + + _ApiConfig({ + required this.url, + required this.mapper, + required this.name, + }); +} + +// Mapper for ipapi.co (primary API - has most complete data) +GeolocationData _mapIpapiCoResponse(Map json) { + return GeolocationData( + ip: json['ip'] ?? '', + network: json['network'] ?? '', + version: json['version'] ?? 'IPv4', + city: json['city'] ?? '', + region: json['region'] ?? '', + regionCode: json['region_code'] ?? '', + country: json['country'] ?? '', + countryName: json['country_name'] ?? '', + countryCode: json['country_code'] ?? '', + countryCodeIso3: json['country_code_iso3'] ?? '', + countryCapital: json['country_capital'] ?? '', + countryTld: json['country_tld'] ?? '', + continentCode: json['continent_code'] ?? '', + inEu: json['in_eu'] ?? false, + postal: json['postal'] ?? '', + latitude: (json['latitude'] ?? 0.0).toDouble(), + longitude: (json['longitude'] ?? 0.0).toDouble(), + timezone: json['timezone'] ?? '', + utcOffset: json['utc_offset'] ?? '', + countryCallingCode: json['country_calling_code'] ?? '', + currency: json['currency'] ?? '', + currencyName: json['currency_name'] ?? '', + languages: json['languages'] ?? '', + countryArea: (json['country_area'] ?? 0.0).toDouble(), + countryPopulation: json['country_population'] ?? 0, + asn: json['asn'] ?? '', + org: json['org'] ?? '', + ); +} + +// Mapper for ip-api.com +GeolocationData _mapIpApiResponse(Map json) { + return GeolocationData( + ip: json['query'] ?? '', + network: '', // Not provided by ip-api.com + version: json['query']?.toString().contains(':') == true ? 'IPv6' : 'IPv4', + city: json['city'] ?? '', + region: json['regionName'] ?? '', + regionCode: json['region'] ?? '', + country: json['country'] ?? '', + countryName: json['country'] ?? '', + countryCode: json['countryCode'] ?? '', + countryCodeIso3: '', // Not provided by ip-api.com + countryCapital: '', // Not provided by ip-api.com + countryTld: '', // Not provided by ip-api.com + continentCode: '', // Not provided by ip-api.com + inEu: false, // Not provided by ip-api.com + postal: json['zip'] ?? '', + latitude: (json['lat'] ?? 0.0).toDouble(), + longitude: (json['lon'] ?? 0.0).toDouble(), + timezone: json['timezone'] ?? '', + utcOffset: '', // Not provided by ip-api.com + countryCallingCode: '', // Not provided by ip-api.com + currency: '', // Not provided by ip-api.com + currencyName: '', // Not provided by ip-api.com + languages: '', // Not provided by ip-api.com + countryArea: 0.0, // Not provided by ip-api.com + countryPopulation: 0, // Not provided by ip-api.com + asn: json['as'] ?? '', + org: json['org'] ?? json['isp'] ?? '', + ); +} + +// Mapper for freeipapi.com +GeolocationData _mapFreeIpApiResponse(Map json) { + return GeolocationData( + ip: json['ipAddress'] ?? '', + network: '', // Not provided by freeipapi.com + version: json['ipVersion'] ?? 'IPv4', + city: json['cityName'] ?? '', + region: json['regionName'] ?? '', + regionCode: '', // Not provided by freeipapi.com + country: json['countryName'] ?? '', + countryName: json['countryName'] ?? '', + countryCode: json['countryCode'] ?? '', + countryCodeIso3: '', // Not provided by freeipapi.com + countryCapital: '', // Not provided by freeipapi.com + countryTld: '', // Not provided by freeipapi.com + continentCode: '', // Not provided by freeipapi.com + inEu: false, // Not provided by freeipapi.com + postal: json['zipCode'] ?? '', + latitude: (json['latitude'] ?? 0.0).toDouble(), + longitude: (json['longitude'] ?? 0.0).toDouble(), + timezone: json['timeZone'] ?? '', + utcOffset: '', // Not provided by freeipapi.com + countryCallingCode: '', // Not provided by freeipapi.com + currency: '', // Not provided by freeipapi.com + currencyName: '', // Not provided by freeipapi.com + languages: '', // Not provided by freeipapi.com + countryArea: 0.0, // Not provided by freeipapi.com + countryPopulation: 0, // Not provided by freeipapi.com + asn: '', // Not provided by freeipapi.com + org: json['isProxy'] == true ? 'Proxy Service' : '', + ); +} + +// Mapper for ipwhois.app +GeolocationData _mapIpWhoisResponse(Map json) { + return GeolocationData( + ip: json['ip'] ?? '', + network: '', // Not provided by ipwhois.app + version: json['type'] ?? 'IPv4', + city: json['city'] ?? '', + region: json['region'] ?? '', + regionCode: json['region_code'] ?? '', + country: json['country'] ?? '', + countryName: json['country'] ?? '', + countryCode: json['country_code'] ?? '', + countryCodeIso3: '', // Not provided by ipwhois.app + countryCapital: json['country_capital'] ?? '', + countryTld: '', // Not provided by ipwhois.app + continentCode: json['continent_code'] ?? '', + inEu: json['country_eu'] ?? false, + postal: '', // Not provided by ipwhois.app + latitude: (json['latitude'] ?? 0.0).toDouble(), + longitude: (json['longitude'] ?? 0.0).toDouble(), + timezone: json['timezone_name'] ?? '', + utcOffset: json['timezone_gmt'] ?? '', + countryCallingCode: json['country_phone'] ?? '', + currency: json['currency'] ?? '', + currencyName: json['currency_name'] ?? '', + languages: '', // Not provided by ipwhois.app + countryArea: 0.0, // Not provided by ipwhois.app + countryPopulation: 0, // Not provided by ipwhois.app + asn: json['asn'] ?? '', + org: json['org'] ?? json['isp'] ?? '', + ); } /// Check if Wireguard is installed on the system.