mirror of
https://github.com/vishalxl/nostr_console.git
synced 2025-04-02 00:51:52 +02:00
408 lines
14 KiB
Dart
408 lines
14 KiB
Dart
import 'dart:io';
|
|
import 'dart:convert';
|
|
import 'package:nostr_console/event_ds.dart';
|
|
import 'package:nostr_console/settings.dart';
|
|
import 'package:web_socket_channel/io.dart';
|
|
|
|
class Relay {
|
|
String url;
|
|
IOWebSocketChannel socket;
|
|
List<String> users; // is used so that duplicate requests aren't sent for same user for this same relay
|
|
int numReceived;
|
|
int numRequestsSent;
|
|
Relay(this.url, this.socket, this.users, this.numReceived, this.numRequestsSent);
|
|
|
|
void printInfo() {
|
|
print("$url ${getNumSpaces(45 - url.length)} $numReceived ${users.length}");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @class Relays Contains connections to all relays.
|
|
*/
|
|
class Relays {
|
|
Map<String, Relay> relays;
|
|
Set<Event> rEvents = {}; // current events received. can be used by others. Is cleared after consumption
|
|
Set<String> uniqueIdsRecieved = {} ; // id of events received. only for internal usage, so that duplicate events are rejected
|
|
Relays(this.relays, this.rEvents, this.uniqueIdsRecieved);
|
|
|
|
void printInfo() {
|
|
printUnderlined("Server connection info");
|
|
print(" Server Url Num events received: Num users requested");
|
|
for( var key in relays.keys) {
|
|
|
|
relays[key]?.printInfo();
|
|
}
|
|
}
|
|
|
|
factory Relays.relay(String relayUrl) {
|
|
IOWebSocketChannel fws = IOWebSocketChannel.connect(relayUrl);
|
|
print('In Relay.relay: connecting to relay $relayUrl');
|
|
Map<String, Relay> mapRelay = {};
|
|
Relay relayObject = Relay( relayUrl, fws, [], 0, 0);
|
|
mapRelay[relayUrl] = relayObject;
|
|
|
|
return Relays(mapRelay, {}, {});
|
|
}
|
|
|
|
|
|
void getKindEvents(List<int> kind, String relayUrl, int limit, int sinceWhen) {
|
|
kind.toString();
|
|
String subscriptionId = "kind_" + kind.toString() + "_" + relayUrl.substring(6);
|
|
String request = getKindRequest(subscriptionId, kind, limit, sinceWhen);
|
|
|
|
sendRequest(relayUrl, request);
|
|
}
|
|
/*
|
|
* @connect Connect to given relay and get all events for the given publicKey and insert the
|
|
* received events in the given List<Event>
|
|
*/
|
|
void getUserEvents(String relayUrl, String publicKey, int limit, int sinceWhen) {
|
|
for(int i = 0; i < gBots.length; i++) { // ignore bots
|
|
if( publicKey == gBots[i]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
String subscriptionId = "single_user" + (relays[relayUrl]?.numRequestsSent??"").toString() + "_" + relayUrl.substring(6);
|
|
if( relays.containsKey(relayUrl)) {
|
|
List<String>? users = relays[relayUrl]?.users;
|
|
if( users != null) { // get a user only if it has not already been requested
|
|
// following is too restrictive casuse changed sinceWhen is not considered. TODO improve it
|
|
for(int i = 0; i < users.length; i++) {
|
|
if( users[i] == publicKey) {
|
|
return;
|
|
}
|
|
}
|
|
users.add(publicKey);
|
|
}
|
|
}
|
|
|
|
String request = getUserRequest(subscriptionId, publicKey, limit, sinceWhen);
|
|
sendRequest(relayUrl, request);
|
|
}
|
|
|
|
void getMentionEvents(String relayUrl, String publicKey, int limit, int sinceWhen) {
|
|
for(int i = 0; i < gBots.length; i++) { // ignore bots
|
|
if( publicKey == gBots[i]) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
String subscriptionId = "mention" + (relays[relayUrl]?.numRequestsSent??"").toString() + "_" + relayUrl.substring(6);
|
|
if( relays.containsKey(relayUrl)) {
|
|
List<String>? users = relays[relayUrl]?.users;
|
|
if( users != null) { // get a user only if it has not already been requested
|
|
// following is too restrictive casuse changed sinceWhen is not considered. TODO improve it
|
|
for(int i = 0; i < users.length; i++) {
|
|
if( users[i] == publicKey) {
|
|
return;
|
|
}
|
|
}
|
|
users.add(publicKey);
|
|
}
|
|
}
|
|
|
|
String request = getMentionRequest(subscriptionId, publicKey, limit, sinceWhen);
|
|
sendRequest(relayUrl, request);
|
|
}
|
|
|
|
|
|
/*
|
|
* @connect Connect to given relay and get all events for multiple users/publicKey and insert the
|
|
* received events in the given List<Event>
|
|
*/
|
|
void getMultiUserEvents(String relayUrl, List<String> publicKeys, int limit, int sinceWhen) {
|
|
|
|
List<String> reqKeys = [];
|
|
if( relays.containsKey(relayUrl)) {
|
|
List<String>? users = relays[relayUrl]?.users;
|
|
if( users != null) {
|
|
// following is too restrictive. TODO improve it
|
|
for(int i = 0; i < publicKeys.length; i++) {
|
|
if( users.any( (u) => u == publicKeys[i])) {
|
|
continue;
|
|
}
|
|
if( gBots.any( (bot) => bot == publicKeys[i] )) {
|
|
continue;
|
|
}
|
|
users.add(publicKeys[i]);
|
|
reqKeys.add(publicKeys[i]);
|
|
}
|
|
}
|
|
} // if relay exists and has a user list
|
|
|
|
String subscriptionId = "multiple_user" + (relays[relayUrl]?.numRequestsSent??"").toString() + "_" + relayUrl.substring(6);
|
|
|
|
String request = getMultiUserRequest( subscriptionId, reqKeys, limit, sinceWhen);
|
|
sendRequest(relayUrl, request);
|
|
}
|
|
|
|
/*
|
|
* Send the given string to the given relay. Is used to send both requests, and to send evnets.
|
|
*/
|
|
void sendRequest(String relay, String request) {
|
|
if(relay == "" ) {
|
|
if( gDebug != 0) print ("Invalid or empty relay given");
|
|
return;
|
|
}
|
|
|
|
if( gDebug > 0) print ("\nIn relay.sendRequest for relay $relay");
|
|
|
|
IOWebSocketChannel? fws;
|
|
if(relays.containsKey(relay)) {
|
|
|
|
fws = relays[relay]?.socket;
|
|
relays[relay]?.numRequestsSent++;
|
|
}
|
|
else {
|
|
if(gDebug !=0) print('connecting to $relay');
|
|
|
|
try {
|
|
IOWebSocketChannel fws2 = IOWebSocketChannel.connect(relay);
|
|
Relay newRelay = Relay(relay, fws2, [], 0, 1);
|
|
relays[relay] = newRelay;
|
|
fws = fws2;
|
|
fws2.stream.listen(
|
|
(d) {
|
|
Event e;
|
|
try {
|
|
dynamic json = jsonDecode(d);
|
|
if( json.length < 3) {
|
|
return;
|
|
}
|
|
newRelay.numReceived++;
|
|
|
|
String id = json[2]['id'] as String;
|
|
if( uniqueIdsRecieved.contains(id)) { // rEvents is often cleared, but uniqueIdsRecieved contains everything received til now
|
|
return;
|
|
}
|
|
|
|
e = Event.fromJson(d, relay);
|
|
|
|
if( gDebug > 0) log.info("In listener: Event size before adding: ${rEvents.length}");
|
|
if( rEvents.add(e) ) {
|
|
uniqueIdsRecieved.add(id);
|
|
String receivedSubscription = json[1];
|
|
if( gDebug > 3) e.eventData.printEventData(0, true);
|
|
if( gDebug > 2) print("");
|
|
|
|
if( gDebug > 1) log.info("In relay listener for relay url $relay: after adding element, rEvents Size = ${rEvents.length} numReceived = ${newRelay.numReceived} for subscription $receivedSubscription");
|
|
if( gDebug > 1) print("\n");
|
|
} else {
|
|
}
|
|
} on FormatException {
|
|
if( gDebug > 0) print( 'in Relay::sendRequset. FormatException in sendRequest for event');
|
|
return;
|
|
} catch(err) {
|
|
//dynamic json = jsonDecode(d);
|
|
if( gDebug > 0) print('---\nin Relay::sendRequset. exception = "$err" ; for relay $relay d = \n${d}');
|
|
return;
|
|
}
|
|
},
|
|
onError: (err) { printWarning("\nWarning: Error in creating connection to $relay. Kindly check your internet connection. Or maybe only this relay is down."); },
|
|
onDone: () { if( gDebug > 0) print('Info: In onDone'); }
|
|
);
|
|
} on WebSocketException {
|
|
print('WebSocketException exception for relay $relay');
|
|
return;
|
|
} on Exception catch(ex) {
|
|
printWarning("Invalid event\n");
|
|
}
|
|
|
|
catch(err) {
|
|
if( gDebug >= 0) printWarning('exception generic $err for relay $relay\n');
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if(gDebug > 0) log.info('Sending request: \n$request\n to $relay\n\n');
|
|
fws?.sink.add(request);
|
|
}
|
|
|
|
|
|
IOWebSocketChannel? getWS(String relay) {
|
|
return relays[relay]?.socket;
|
|
}
|
|
|
|
void printStatus() {
|
|
print("In Relays::printStatus. Number of relays = ${relays.length}");
|
|
relays.forEach((key, value) {
|
|
print("for relay: $key");
|
|
print("$value\n");
|
|
String? reason = value.socket.closeReason;
|
|
print( reason??"reason not found");
|
|
});
|
|
}
|
|
}
|
|
|
|
Relays relays = Relays({}, {}, {});
|
|
|
|
String getKindRequest(String subscriptionId, List<int> kind, int limit, int sinceWhen) {
|
|
String strTime = "";
|
|
if( sinceWhen != 0) {
|
|
strTime = ', "since":${sinceWhen.toString()}';
|
|
}
|
|
var strSubscription1 = '["REQ","$subscriptionId",{"kinds":[';
|
|
var strSubscription2 ='], "limit":$limit$strTime } ]';
|
|
|
|
String strKind = "";
|
|
for(int i = 0; i < kind.length; i++) {
|
|
String comma = ",";
|
|
if( i == kind.length-1) {
|
|
comma = "";
|
|
}
|
|
strKind = strKind + kind[i].toString() + comma;
|
|
}
|
|
String strRequest = strSubscription1 + strKind + strSubscription2;
|
|
//print(strRequest);
|
|
return strRequest;
|
|
}
|
|
|
|
String getUserRequest(String subscriptionId, String publicKey, int numUserEvents, int sinceWhen) {
|
|
String strTime = "";
|
|
if( sinceWhen != 0) {
|
|
strTime = ', "since": ${sinceWhen.toString()}';
|
|
}
|
|
var strSubscription1 = '["REQ","$subscriptionId",{ "authors": ["';
|
|
var strSubscription2 ='"], "limit": $numUserEvents $strTime } ]';
|
|
return strSubscription1 + publicKey.toLowerCase() + strSubscription2;
|
|
}
|
|
|
|
|
|
String getMentionRequest(String subscriptionId, String publicKey, int numUserEvents, int sinceWhen) {
|
|
String strTime = "";
|
|
if( sinceWhen != 0) {
|
|
strTime = ', "since": ${sinceWhen.toString()}';
|
|
}
|
|
var strSubscription1 = '["REQ","$subscriptionId",{ "#p": ["';
|
|
var strSubscription2 ='"], "limit": $numUserEvents $strTime } ]';
|
|
return strSubscription1 + publicKey.toLowerCase() + strSubscription2;
|
|
}
|
|
|
|
String getMultiUserRequest(String subscriptionId, List<String> publicKeys, int numUserEvents, int sinceWhen) {
|
|
String strTime = "";
|
|
if( sinceWhen != 0) {
|
|
strTime = ', "since": ${sinceWhen.toString()}';
|
|
}
|
|
|
|
var strSubscription1 = '["REQ","$subscriptionId",{ "authors": [';
|
|
var strSubscription2 ='], "limit": $numUserEvents $strTime } ]';
|
|
String s = "";
|
|
|
|
for(int i = 0; i < publicKeys.length; i++) {
|
|
s += "\"${publicKeys[i].toLowerCase()}\"";
|
|
if( i < publicKeys.length - 1) {
|
|
s += ",";
|
|
}
|
|
}
|
|
return strSubscription1 + s + strSubscription2;
|
|
}
|
|
|
|
void getContactFeed(List<String> relayUrls, Set<String> setContacts, int numEventsToGet, int sinceWhen) {
|
|
|
|
List<String> contacts = setContacts.toList();
|
|
//print("in getContactFeed: numEventsToGet = $numEventsToGet numContacts = ${setContacts.length} num contacts list = ${contacts.length}");
|
|
for( int i = 0; i < contacts.length; i += gMaxAuthorsInOneRequest) {
|
|
|
|
// for last iteration change upper limit
|
|
int upperLimit = (i + gMaxAuthorsInOneRequest) > contacts.length?
|
|
(contacts.length - i): gMaxAuthorsInOneRequest;
|
|
|
|
//print("upperLimit = $upperLimit i = $i");
|
|
List<String> groupContacts = [];
|
|
for( int j = 0; j < upperLimit; j++) {
|
|
groupContacts.add(contacts[i + j]);
|
|
}
|
|
|
|
//print( "i = $i upperLimit = $upperLimit") ;
|
|
relayUrls.forEach((relayUrl) {
|
|
relays.getMultiUserEvents(relayUrl, groupContacts, numEventsToGet, sinceWhen);
|
|
});
|
|
|
|
}
|
|
|
|
// return contact list for use by caller
|
|
return;
|
|
}
|
|
|
|
void getUserEvents(List<String> serverUrls, String publicKey, int numUserEvents, int sinceWhen) {
|
|
serverUrls.forEach((serverUrl) {
|
|
relays.getUserEvents(serverUrl, publicKey, numUserEvents, sinceWhen);
|
|
});
|
|
}
|
|
|
|
void getMentionEvents(List<String> serverUrls, String publicKey, int numUserEvents, int sinceWhen) {
|
|
serverUrls.forEach((serverUrl) {
|
|
relays.getMentionEvents(serverUrl, publicKey, numUserEvents, sinceWhen);
|
|
});
|
|
}
|
|
|
|
|
|
getKindEvents(List<int> kind, List<String> serverUrls, int limit, int sinceWhen) {
|
|
serverUrls.forEach((serverUrl) {
|
|
relays.getKindEvents(kind, serverUrl, limit, sinceWhen);
|
|
});
|
|
}
|
|
|
|
void getMultiUserEvents(List<String> serverUrls, List<String> publicKeys, int numUserEvents, int sinceWhen) {
|
|
if( gDebug > 0) print("Sending multi user request for ${publicKeys.length} users");
|
|
|
|
for(var serverUrl in serverUrls) {
|
|
for( int i = 0; i < publicKeys.length; i+= gMaxAuthorsInOneRequest) {
|
|
int getUserRequests = gMaxAuthorsInOneRequest;
|
|
if( publicKeys.length - i <= gMaxAuthorsInOneRequest) {
|
|
getUserRequests = publicKeys.length - i;
|
|
}
|
|
//print(" sending request form $i to ${i + getUserRequests} ");
|
|
List<String> partialList = publicKeys.sublist(i, i + getUserRequests);
|
|
relays.getMultiUserEvents(serverUrl, partialList, numUserEvents, sinceWhen);
|
|
}
|
|
}
|
|
}
|
|
|
|
// send request for specific events whose id's are passed as list eventIds
|
|
void sendEventsRequest(List<String> serverUrls, Set<String> eventIds) {
|
|
if( eventIds.length == 0)
|
|
return;
|
|
|
|
String eventIdsStr = "";
|
|
int i = 0;
|
|
|
|
eventIds.forEach((event) {
|
|
String comma = ",";
|
|
if( i == 0)
|
|
comma = "";
|
|
eventIdsStr = '$eventIdsStr$comma"${event}"';
|
|
i++;
|
|
});
|
|
|
|
String getEventRequest = '["REQ","event_${eventIds.length}",{"ids":[$eventIdsStr]}]';
|
|
if( gDebug > 0) log.info("sending $getEventRequest");
|
|
for(int i = 0; i < serverUrls.length; i++) {
|
|
relays.sendRequest(serverUrls[i], getEventRequest);
|
|
}
|
|
}
|
|
|
|
void sendRequest(List<String> serverUrls, request) {
|
|
for(int i = 0; i < serverUrls.length; i++) {
|
|
relays.sendRequest(serverUrls[i], request);
|
|
}
|
|
}
|
|
|
|
Set<Event> getRecievedEvents() {
|
|
return relays.rEvents;
|
|
}
|
|
|
|
void clearEvents() {
|
|
relays.rEvents.clear();
|
|
if( gDebug > 0) print("clearEvents(): returning");
|
|
}
|
|
|
|
void setRelaysIntialEvents(Set<Event> eventsFromFile) {
|
|
eventsFromFile.forEach((element) {relays.uniqueIdsRecieved.add(element.eventData.id);});
|
|
relays.rEvents = eventsFromFile;
|
|
}
|
|
|