Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: followers counter flow #463

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class EventCountRequestData with _$EventCountRequestData implements EventSeriali
const factory EventCountRequestData({
required List<RequestFilter> filters,
required EventCountRequestParams params,
List<String>? relays,
String? output,
}) = _EventCountRequestData;

Expand All @@ -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],
);
}
Expand All @@ -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,
...params.toTags(),
if (relays != null) ['relays', ...relays!],
],
);
}
}
Expand Down
18 changes: 0 additions & 18 deletions lib/app/features/nostr/providers/nostr_notifier.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,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/nostr/model/action_source.dart';
import 'package:ion/app/features/nostr/model/event_serializable.dart';
import 'package:ion/app/features/nostr/model/nostr_entity.dart';
Expand Down Expand Up @@ -142,22 +140,6 @@ class NostrNotifier extends _$NostrNotifier {
return entities.isNotEmpty ? entities.first as T : null;
}

Future<EventCountResultEntity> 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<EventMessage>()
.map(EventCountResultEntity.fromEventMessage)
.firstWhere(
(countResult) => countResult.data.requestEventId == requestEventMessage.id,
);
}

Future<EventMessage> sign(EventSerializable entityData) async {
final eventSigner = ref.read(currentUserNostrEventSignerProvider).valueOrNull;
final mainWallet = ref.read(mainWalletProvider).valueOrNull;
Expand Down
109 changes: 83 additions & 26 deletions lib/app/features/user/providers/followers_count_provider.c.dart
Original file line number Diff line number Diff line change
@@ -1,43 +1,100 @@
// 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/nostr/model/action_source.dart';
import 'package:ion/app/features/nostr/providers/nostr_cache.c.dart';
import 'package:ion/app/features/nostr/providers/nostr_notifier.c.dart';
import 'package:ion/app/features/nostr/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<int?> followersCount(Ref ref, String pubkey) async {
final followersCountEntity = ref.watch(
nostrCacheProvider.select(
cacheSelector<EventCountResultEntity>(
EventCountResultEntity.cacheKeyBuilder(
key: pubkey,
type: EventCountResultType.followers,
class FollowersCount extends _$FollowersCount {
@override
Future<int?> build(String pubkey) async {
final followersCountEntity = ref.watch(
nostrCacheProvider.select(
cacheSelector<EventCountResultEntity>(
EventCountResultEntity.cacheKeyBuilder(
key: pubkey,
type: EventCountResultType.followers,
),
),
),
),
);
);

if (followersCountEntity != null) {
return followersCountEntity.data.content as int;
}

final relay = await _getRandomUserRelay();

final requestEvent = await _buildRequestEvent(relayUrl: relay.url);

final subscriptionMessage = RequestMessage()
..addFilter(
RequestFilter(
kinds: const [EventCountResultEntity.kind],
e: [requestEvent.id],
limit: 1,
),
);

// We first subscribe to the count response
final subscription = relay.subscribe(subscriptionMessage);

// Then send the request event
await ref.watch(nostrNotifierProvider.notifier).sendEvent(
requestEvent,
actionSource: ActionSourceRelayUrl(relay.url),
cache: false,
);

if (followersCountEntity != null) {
return 0;
// Waiting for the response
final responseMessage =
await subscription.messages.firstWhere((message) => message is EventMessage);

// And unsubscribe
relay.unsubscribe(subscription.id);

//TODO::currently it fails on parsing
// 1. BE should add `b` tag to the event
// 2. The content is `{}`, so either request params are wrong or BE calculates it incorrectly
final eventCountResultEntity =
EventCountResultEntity.fromEventMessage(responseMessage as EventMessage);

ref.watch(nostrCacheProvider.notifier).cache(eventCountResultEntity);

return eventCountResultEntity.data.content as int?;
}

// TODO:uncomment when impl
// final followersCountRequest = EventCountRequestData(
// params: const EventCountRequestParams(group: 'p'),
// filters: [
// RequestFilter(kinds: const [FollowListEntity.kind], p: [pubkey]),
// ],
// );
Future<NostrRelay> _getRandomUserRelay() async {
final userRelays = await ref.watch(currentUserRelayProvider.future);
if (userRelays == null) {
throw UserRelaysNotFoundException();
}

// final response = await ref.read(nostrNotifierProvider.notifier).requestCount(
// followersCountRequest,
// actionSource: ActionSourceUser(pubkey),
// );
final relayUrl = userRelays.data.list.random.url;

return Random().nextInt(100);
return await ref.watch(relayProvider(relayUrl).future);
}

Future<EventMessage> _buildRequestEvent({required String relayUrl}) async {
final followersCountRequest = EventCountRequestData(
relays: [relayUrl],
params: const EventCountRequestParams(group: 'p'),
filters: [
RequestFilter(kinds: const [FollowListEntity.kind], p: [pubkey]),
],
);

return ref.watch(nostrNotifierProvider.notifier).sign(followersCountRequest);
}
}