diff --git a/lib/app/features/feed/data/models/entities/event_count_request_data.c.dart b/lib/app/features/feed/data/models/entities/event_count_request_data.c.dart index b9b9b8297..2ba05a5c9 100644 --- a/lib/app/features/feed/data/models/entities/event_count_request_data.c.dart +++ b/lib/app/features/feed/data/models/entities/event_count_request_data.c.dart @@ -57,7 +57,8 @@ class EventCountRequestEntity class EventCountRequestData with _$EventCountRequestData implements EventSerializable { const factory EventCountRequestData({ required List filters, - required EventCountRequestParams params, + EventCountRequestParams? params, + List? relays, String? output, }) = _EventCountRequestData; @@ -71,6 +72,7 @@ class EventCountRequestData with _$EventCountRequestData implements EventSeriali return EventCountRequestData( filters: filters, params: EventCountRequestParams.fromTags(tags[EventCountRequestParams.tagName] ?? []), + relays: tags['relays']?.first.skip(1).toList(), output: tags['output']?.first[1], ); } @@ -85,8 +87,12 @@ class EventCountRequestData with _$EventCountRequestData implements EventSeriali signer: signer, createdAt: createdAt, kind: EventCountRequestEntity.kind, - content: json.encode(filters.map((filter) => filter.toString()).toList()), - tags: [...tags, ...params.toTags()], + content: json.encode(filters), + tags: [ + ...tags, + if (params != null) ...params!.toTags(), + if (relays != null) ['relays', ...relays!], + ], ); } } diff --git a/lib/app/features/feed/data/models/entities/event_count_result_data.c.dart b/lib/app/features/feed/data/models/entities/event_count_result_data.c.dart index e134f17ec..7a5b7a539 100644 --- a/lib/app/features/feed/data/models/entities/event_count_result_data.c.dart +++ b/lib/app/features/feed/data/models/entities/event_count_result_data.c.dart @@ -41,7 +41,10 @@ class EventCountResultEntity const EventCountResultEntity._(); /// https://github.com/nostr-protocol/nips/blob/vending-machine/90.md - factory EventCountResultEntity.fromEventMessage(EventMessage eventMessage) { + factory EventCountResultEntity.fromEventMessage( + EventMessage eventMessage, { + String? key, + }) { if (eventMessage.kind != kind) { throw IncorrectEventKindException(eventId: eventMessage.id, kind: kind); } @@ -49,7 +52,7 @@ class EventCountResultEntity final data = EventCountResultData.fromEventMessage(eventMessage); final type = data.getType(); final summary = EventCountResultSummary( - key: data.getKey(type), + key: key ?? data.getKey(type), type: type, content: data.content, requestEventId: data.request.id, @@ -125,14 +128,14 @@ class EventCountResultData with _$EventCountResultData { EventCountResultType getType() { final EventCountRequestData(:filters, :params) = request.data; final filter = filters.first; - if (params.group == RelatedEventMarker.reply.toShortString() || - params.group == RelatedEventMarker.root.toShortString()) { + if (params?.group == RelatedEventMarker.reply.toShortString() || + params?.group == RelatedEventMarker.root.toShortString()) { return EventCountResultType.replies; } else if (filter.kinds != null && filter.kinds!.contains(RepostEntity.kind) && - params.group == RelatedEvent.tagName) { + params?.group == RelatedEvent.tagName) { return EventCountResultType.reposts; - } else if (params.group == QuotedEvent.tagName) { + } else if (params?.group == QuotedEvent.tagName) { return EventCountResultType.quotes; } else if (filter.kinds != null && filter.kinds!.contains(ReactionEntity.kind)) { return EventCountResultType.reactions; diff --git a/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart b/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart index f4812ddd3..ec70704ba 100644 --- a/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart +++ b/lib/app/features/ion_connect/providers/ion_connect_notifier.c.dart @@ -7,8 +7,6 @@ import 'package:ion/app/exceptions/exceptions.dart'; import 'package:ion/app/extensions/extensions.dart'; import 'package:ion/app/features/auth/providers/auth_provider.c.dart'; import 'package:ion/app/features/chat/providers/user_chat_relays_provider.c.dart'; -import 'package:ion/app/features/feed/data/models/entities/event_count_request_data.c.dart'; -import 'package:ion/app/features/feed/data/models/entities/event_count_result_data.c.dart'; import 'package:ion/app/features/ion_connect/ion_connect.dart' as ion; import 'package:ion/app/features/ion_connect/ion_connect.dart' hide requestEvents; import 'package:ion/app/features/ion_connect/model/action_source.dart'; @@ -196,22 +194,6 @@ class IonConnectNotifier extends _$IonConnectNotifier { return entities.isNotEmpty ? entities.first as T : null; } - Future requestCount( - EventCountRequestData requestData, { - ActionSource actionSource = const ActionSourceCurrentUser(), - }) async { - final requestEventMessage = await sign(requestData); - final relay = await _getRelay(actionSource); - relay.sendMessage(requestEventMessage); - return relay.messages - .where((message) => message is EventMessage && message.kind == EventCountResultEntity.kind) - .cast() - .map(EventCountResultEntity.fromEventMessage) - .firstWhere( - (countResult) => countResult.data.requestEventId == requestEventMessage.id, - ); - } - Future sign(EventSerializable entityData) async { final eventSigner = ref.read(currentUserIonConnectEventSignerProvider).valueOrNull; final mainWallet = ref.read(mainWalletProvider).valueOrNull; diff --git a/lib/app/features/user/providers/followers_count_provider.c.dart b/lib/app/features/user/providers/followers_count_provider.c.dart index 8f78fe65a..b1e91dd9e 100644 --- a/lib/app/features/user/providers/followers_count_provider.c.dart +++ b/lib/app/features/user/providers/followers_count_provider.c.dart @@ -1,43 +1,101 @@ // SPDX-License-Identifier: ice License 1.0 -import 'dart:math'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:ion/app/exceptions/exceptions.dart'; +import 'package:ion/app/extensions/extensions.dart'; +import 'package:ion/app/features/feed/data/models/entities/event_count_request_data.c.dart'; import 'package:ion/app/features/feed/data/models/entities/event_count_result_data.c.dart'; +import 'package:ion/app/features/ion_connect/model/action_source.dart'; import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart'; +import 'package:ion/app/features/ion_connect/providers/ion_connect_notifier.c.dart'; +import 'package:ion/app/features/ion_connect/providers/relays_provider.c.dart'; +import 'package:ion/app/features/user/model/follow_list.c.dart'; +import 'package:ion/app/features/user/providers/user_relays_manager.c.dart'; +import 'package:nostr_dart/nostr_dart.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'followers_count_provider.c.g.dart'; @Riverpod(keepAlive: true) -Future followersCount(Ref ref, String pubkey) async { - final followersCountEntity = ref.watch( - ionConnectCacheProvider.select( - cacheSelector( - EventCountResultEntity.cacheKeyBuilder( - key: pubkey, - type: EventCountResultType.followers, +class FollowersCount extends _$FollowersCount { + @override + Future build(String pubkey) async { + final followersCountEntity = ref.watch( + ionConnectCacheProvider.select( + cacheSelector( + EventCountResultEntity.cacheKeyBuilder( + key: pubkey, + type: EventCountResultType.followers, + ), ), ), - ), - ); + ); + + if (followersCountEntity != null) { + return followersCountEntity.data.content as int; + } - if (followersCountEntity != null) { - return 0; + return _fetchFollowersCount(pubkey); } - // TODO:uncomment when impl - // final followersCountRequest = EventCountRequestData( - // params: const EventCountRequestParams(group: 'p'), - // filters: [ - // RequestFilter(kinds: const [FollowListEntity.kind], p: [pubkey]), - // ], - // ); + Future _fetchFollowersCount(String pubkey) async { + final relay = await _getRandomUserRelay(); + + final requestEvent = await _buildRequestEvent(relayUrl: relay.url); + final subscriptionMessage = RequestMessage() + ..addFilter( + RequestFilter( + kinds: const [EventCountResultEntity.kind, 7400], + tags: { + '#p': [pubkey], + }, + ), + ); + + final subscription = relay.subscribe(subscriptionMessage); + EventMessage? responseMessage; + + try { + await ref.read(ionConnectNotifierProvider.notifier).sendEvent( + requestEvent, + actionSource: ActionSourceRelayUrl(relay.url), + cache: false, + ); - // final response = await ref.read(ionConnectNotifierProvider.notifier).requestCount( - // followersCountRequest, - // actionSource: ActionSourceUser(pubkey), - // ); + responseMessage = await subscription.messages + .firstWhere((message) => message is EventMessage) + .timeout(const Duration(seconds: 10)) as EventMessage; - return Random().nextInt(100); + final eventCountResultEntity = + EventCountResultEntity.fromEventMessage(responseMessage, key: pubkey); + ref.read(ionConnectCacheProvider.notifier).cache(eventCountResultEntity); + + return eventCountResultEntity.data.content as int; + } finally { + relay.unsubscribe(subscription.id); + } + } + + Future _getRandomUserRelay() async { + final userRelays = await ref.read(currentUserRelayProvider.future); + if (userRelays == null) { + throw UserRelaysNotFoundException(); + } + + final relayUrl = userRelays.data.list.random.url; + return await ref.read(relayProvider(relayUrl).future); + } + + Future _buildRequestEvent({required String relayUrl}) async { + final followersCountRequest = EventCountRequestData( + relays: [relayUrl], + filters: [ + RequestFilter( + kinds: const [FollowListEntity.kind], + authors: [pubkey], + ), + ], + ); + + return ref.read(ionConnectNotifierProvider.notifier).sign(followersCountRequest); + } }