352 lines
11 KiB
Dart
352 lines
11 KiB
Dart
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:http/http.dart' as http;
|
|
|
|
class GeolocationData {
|
|
final String ip;
|
|
final String network;
|
|
final String version;
|
|
final String city;
|
|
final String region;
|
|
final String regionCode;
|
|
final String country;
|
|
final String countryName;
|
|
final String countryCode;
|
|
final String countryCodeIso3;
|
|
final String countryCapital;
|
|
final String countryTld;
|
|
final String continentCode;
|
|
final bool inEu;
|
|
final String postal;
|
|
final double latitude;
|
|
final double longitude;
|
|
final String timezone;
|
|
final String utcOffset;
|
|
final String countryCallingCode;
|
|
final String currency;
|
|
final String currencyName;
|
|
final String languages;
|
|
final double countryArea;
|
|
final int countryPopulation;
|
|
final String asn;
|
|
final String org;
|
|
|
|
GeolocationData({
|
|
required this.ip,
|
|
required this.network,
|
|
required this.version,
|
|
required this.city,
|
|
required this.region,
|
|
required this.regionCode,
|
|
required this.country,
|
|
required this.countryName,
|
|
required this.countryCode,
|
|
required this.countryCodeIso3,
|
|
required this.countryCapital,
|
|
required this.countryTld,
|
|
required this.continentCode,
|
|
required this.inEu,
|
|
required this.postal,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
required this.timezone,
|
|
required this.utcOffset,
|
|
required this.countryCallingCode,
|
|
required this.currency,
|
|
required this.currencyName,
|
|
required this.languages,
|
|
required this.countryArea,
|
|
required this.countryPopulation,
|
|
required this.asn,
|
|
required this.org,
|
|
});
|
|
|
|
factory GeolocationData.fromJson(Map<String, dynamic> json) {
|
|
return GeolocationData(
|
|
ip: json['ip'],
|
|
network: json['network'],
|
|
version: json['version'],
|
|
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'],
|
|
postal: json['postal'],
|
|
latitude: json['latitude'].toDouble(),
|
|
longitude: json['longitude'].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'].toDouble(),
|
|
countryPopulation: json['country_population'],
|
|
asn: json['asn'],
|
|
org: json['org'],
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'ip': ip,
|
|
'network': network,
|
|
'version': version,
|
|
'city': city,
|
|
'region': region,
|
|
'region_code': regionCode,
|
|
'country': country,
|
|
'country_name': countryName,
|
|
'country_code': countryCode,
|
|
'country_code_iso3': countryCodeIso3,
|
|
'country_capital': countryCapital,
|
|
'country_tld': countryTld,
|
|
'continent_code': continentCode,
|
|
'in_eu': inEu,
|
|
'postal': postal,
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'timezone': timezone,
|
|
'utc_offset': utcOffset,
|
|
'country_calling_code': countryCallingCode,
|
|
'currency': currency,
|
|
'currency_name': currencyName,
|
|
'languages': languages,
|
|
'country_area': countryArea,
|
|
'country_population': countryPopulation,
|
|
'asn': asn,
|
|
'org': org,
|
|
};
|
|
}
|
|
}
|
|
|
|
Future<GeolocationData> getGeolocationData() async {
|
|
// 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<String, dynamic>) {
|
|
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<String, dynamic>) 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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.
|
|
Future<bool> isWireguardInstalled() async {
|
|
try {
|
|
var result = await Process.run('wg', ['--version']);
|
|
return result.exitCode == 0;
|
|
} catch (e) {
|
|
print('Error checking WireGuard installation: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool get kIsRunningInDocker => File('/.dockerenv').existsSync(); |