Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
83 lines
2.6 KiB
Dart
83 lines
2.6 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
class IpLookupDetails {
|
|
final String city;
|
|
final String country;
|
|
final String isp;
|
|
final String network;
|
|
final bool mobile;
|
|
final bool proxy;
|
|
final String timezone;
|
|
|
|
const IpLookupDetails({
|
|
required this.city,
|
|
required this.country,
|
|
required this.isp,
|
|
required this.network,
|
|
required this.mobile,
|
|
required this.proxy,
|
|
required this.timezone,
|
|
});
|
|
|
|
factory IpLookupDetails.fromIpWhoIs(Map<String, dynamic> data) {
|
|
if (data['success'] != true) {
|
|
throw FormatException(data['message']?.toString() ?? 'IP lookup failed');
|
|
}
|
|
final connection = data['connection'] is Map
|
|
? (data['connection'] as Map).cast<String, dynamic>()
|
|
: const <String, dynamic>{};
|
|
final security = data['security'] is Map
|
|
? (data['security'] as Map).cast<String, dynamic>()
|
|
: const <String, dynamic>{};
|
|
final timezoneData = data['timezone'] is Map
|
|
? (data['timezone'] as Map).cast<String, dynamic>()
|
|
: const <String, dynamic>{};
|
|
final asn = connection['asn'];
|
|
final organization = connection['org']?.toString() ?? '';
|
|
final network = [
|
|
if (asn != null) 'AS$asn',
|
|
if (organization.isNotEmpty) organization,
|
|
].join(' ');
|
|
return IpLookupDetails(
|
|
city: data['city']?.toString() ?? '',
|
|
country: data['country']?.toString() ?? '',
|
|
isp: connection['isp']?.toString() ?? organization,
|
|
network: network,
|
|
mobile: security['mobile'] == true,
|
|
proxy: security['proxy'] == true,
|
|
timezone: timezoneData['id']?.toString() ?? '',
|
|
);
|
|
}
|
|
}
|
|
|
|
abstract class IpLookupService {
|
|
static const String providerName = 'ipwho.is';
|
|
static const Duration _timeout = Duration(seconds: 8);
|
|
|
|
static Future<IpLookupDetails> lookup(String ip) async {
|
|
final uri = Uri.https(providerName, '/$ip');
|
|
final client = HttpClient()..connectionTimeout = _timeout;
|
|
try {
|
|
final request = await client.getUrl(uri);
|
|
request.headers.set(HttpHeaders.userAgentHeader, 'QlyraIpLookup');
|
|
final response = await request.close().timeout(_timeout);
|
|
if (response.statusCode != HttpStatus.ok) {
|
|
await response.drain<void>();
|
|
throw HttpException('HTTP ${response.statusCode}', uri: uri);
|
|
}
|
|
final body = await response
|
|
.transform(const Utf8Decoder())
|
|
.join()
|
|
.timeout(_timeout);
|
|
final decoded = jsonDecode(body);
|
|
if (decoded is! Map) {
|
|
throw const FormatException('Invalid IP lookup response');
|
|
}
|
|
return IpLookupDetails.fromIpWhoIs(decoded.cast<String, dynamic>());
|
|
} finally {
|
|
client.close(force: true);
|
|
}
|
|
}
|
|
}
|