import 'dart:convert'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nostr/nostr_user.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/material.dart'; import 'package:nostr_tools/nostr_tools.dart'; class NostrProfileHandler { static final relayToDomainMap = { 'relay.snort.social': 'snort.social', }; static Nip05 _nip05 = Nip05(); static Future queryProfile(BuildContext context, String nip05Address) async { var profile = await _nip05.queryProfile(nip05Address); if (profile?.pubkey != null) { if (profile?.relays?.isNotEmpty == true) { return profile; } else { await _showErrorDialog(context, S.of(context).no_relays, S.of(context).no_relays_message); } } return null; } static Future processRelays( BuildContext context, ProfilePointer profile, String nip05Address) async { String userDomain = _extractDomain(nip05Address); const int metaData = 0; for (String relayUrl in profile.relays ?? []) { final relayDomain = _getDomainFromRelayUrl(relayUrl); final formattedRelayDomain = relayToDomainMap[relayDomain] ?? relayDomain; if (formattedRelayDomain == userDomain) { final userDomainData = await _fetchInfoFromRelay(relayUrl, profile.pubkey, [metaData]); if (userDomainData != null) { return userDomainData; } } } await _showErrorDialog(context, S.of(context).no_relays, S.of(context).no_relay_on_domain); String? chosenRelayUrl = await _showRelayChoiceDialog(context, profile.relays ?? []); if (chosenRelayUrl != null) { final userData = await _fetchInfoFromRelay(chosenRelayUrl, profile.pubkey, [metaData]); if (userData != null) { return userData; } } return null; } static Future _fetchInfoFromRelay( String relayUrl, String userPubKey, List kinds) async { try { final relay = RelayApi(relayUrl: relayUrl); final stream = await relay.connect(); relay.sub([ Filter( kinds: kinds, authors: [userPubKey], ) ]); await for (var message in stream) { if (message.type == 'EVENT') { final event = message.message as Event; final eventContent = json.decode(event.content) as Map; final userMetadata = UserMetadata.fromJson(eventContent); relay.close(); return userMetadata; } } relay.close(); return null; } catch (e) { printV('[!] Error with relay $relayUrl: $e'); return null; } } static Future _showErrorDialog( BuildContext context, String title, String errorMessage) async { if (context.mounted) { await showPopUp( context: context, builder: (BuildContext dialogContext) { return AlertWithOneAction( alertTitle: title, alertContent: errorMessage, buttonText: S.of(dialogContext).ok, buttonAction: () => Navigator.of(dialogContext).pop(), ); }, ); } } static String _extractDomain(String nip05Address) { var parts = nip05Address.split('@'); return parts.length == 2 ? parts[1] : ''; } static String _getDomainFromRelayUrl(String relayUrl) { try { var uri = Uri.parse(relayUrl); return uri.host; } catch (e) { printV('Error parsing URL: $e'); return ''; } } static Future _showRelayChoiceDialog(BuildContext context, List relays) async { String? selectedRelay; if (context.mounted) { await showPopUp( context: context, builder: (BuildContext dialogContext) { return Picker( selectedAtIndex: 0, title: S.of(dialogContext).choose_relay, items: relays, onItemSelected: (String relay) => selectedRelay = relay, ); }, ); } return selectedRelay; } }