mirror of
https://github.com/vishalxl/nostr_console.git
synced 2025-03-29 11:11:43 +01:00
used unique names for subscription strings, used set of events rather than list, and called multiple relays for third call in main
This commit is contained in:
parent
b87cb0346b
commit
01b5b82228
@ -146,7 +146,7 @@ Future<void> main(List<String> arguments) async {
|
||||
if( gEventsFilename != "") {
|
||||
print("\n");
|
||||
stdout.write('Reading events from ${whetherDefault}file.......');
|
||||
List<Event> eventsFromFile = readEventsFromFile(gEventsFilename);
|
||||
Set<Event> eventsFromFile = readEventsFromFile(gEventsFilename);
|
||||
setRelaysIntialEvents(eventsFromFile);
|
||||
eventsFromFile.forEach((element) { element.eventData.kind == 1? numFileEvents++: numFileEvents;});
|
||||
print("read $numFileEvents posts from file $gEventsFilename");
|
||||
@ -155,7 +155,7 @@ Future<void> main(List<String> arguments) async {
|
||||
stdout.write('Sending request and waiting for events...');
|
||||
sendRequest(gListRelayUrls, argResults[requestArg]);
|
||||
Future.delayed(const Duration(milliseconds: numWaitSeconds * 2), () {
|
||||
List<Event> receivedEvents = getRecievedEvents();
|
||||
Set<Event> receivedEvents = getRecievedEvents();
|
||||
stdout.write("received ${receivedEvents.length - numFileEvents} events\n");
|
||||
|
||||
// remove bots
|
||||
@ -220,7 +220,7 @@ Future<void> main(List<String> arguments) async {
|
||||
|
||||
// get mentioned ptags, and then get the events for those users
|
||||
List<String> pTags = getpTags(getRecievedEvents(), 300);
|
||||
getMultiUserEvents(defaultServerUrl, pTags, 5000);
|
||||
getMultiUserEvents(gListRelayUrls, pTags, 5000);
|
||||
|
||||
stdout.write('Waiting for rest of posts to come in.....');
|
||||
Future.delayed(const Duration(milliseconds: numWaitSeconds * 2), () {
|
||||
|
@ -12,11 +12,11 @@ Future<void> processNotifications(Tree node) async {
|
||||
const int waitMilliSeconds = 400;
|
||||
Future.delayed(const Duration(milliseconds: waitMilliSeconds), () {
|
||||
|
||||
List<String> newEventsId = node.insertEvents(getRecievedEvents());
|
||||
Set<String> newEventIdsSet = node.insertEvents(getRecievedEvents());
|
||||
String nameToDisplay = userPrivateKey.length == 64?
|
||||
"$gCommentColor${getAuthorName(userPublicKey)}$colorEndMarker":
|
||||
"${gWarningColor}You are not signed in$colorEndMarker but are using public key $userPublicKey";
|
||||
node.printNotifications(newEventsId, nameToDisplay);
|
||||
node.printNotifications(newEventIdsSet, nameToDisplay);
|
||||
clearEvents();
|
||||
});
|
||||
|
||||
|
@ -416,6 +416,11 @@ class Event {
|
||||
|
||||
Event(this.event, this.id, this.eventData, this.seenOnRelays, this.originalJson);
|
||||
|
||||
@override
|
||||
bool operator ==( other) {
|
||||
return (other is Event) && eventData.id == other.eventData.id;
|
||||
}
|
||||
|
||||
factory Event.fromJson(String d, String relay) {
|
||||
try {
|
||||
dynamic json = jsonDecode(d);
|
||||
@ -487,11 +492,11 @@ class HistogramEntry {
|
||||
}
|
||||
|
||||
// return the numMostFrequent number of most frequent p tags ( user pubkeys) in the given events
|
||||
List<String> getpTags(List<Event> events, int numMostFrequent) {
|
||||
List<String> getpTags(Set<Event> events, int numMostFrequent) {
|
||||
List<HistogramEntry> listHistogram = [];
|
||||
Map<String, int> histogramMap = {};
|
||||
for(int i = 0; i < events.length; i++) {
|
||||
addToHistogram(histogramMap, events[i].eventData.pTags);
|
||||
for(var event in events) {
|
||||
addToHistogram(histogramMap, event.eventData.pTags);
|
||||
}
|
||||
|
||||
histogramMap.forEach((key, value) {listHistogram.add(HistogramEntry(key, value));/* print("added to list of histogramEntry $key $value"); */});
|
||||
@ -505,8 +510,8 @@ List<String> getpTags(List<Event> events, int numMostFrequent) {
|
||||
return ptags;
|
||||
}
|
||||
|
||||
List<Event> readEventsFromFile(String filename) {
|
||||
List<Event> events = [];
|
||||
Set<Event> readEventsFromFile(String filename) {
|
||||
Set<Event> events = {};
|
||||
final File file = File(filename);
|
||||
|
||||
// sync read
|
||||
@ -524,24 +529,19 @@ List<Event> readEventsFromFile(String filename) {
|
||||
}
|
||||
|
||||
// From the list of events provided, lookup the lastst contact information for the given user/pubkey
|
||||
Event? getContactEvent(List<Event> events, String pubkey) {
|
||||
Event? getContactEvent(Set<Event> events, String pubkey) {
|
||||
|
||||
// get the latest kind 3 event for the user, which lists his 'follows' list
|
||||
int latestContactsTime = 0, latestContactIndex = -1;
|
||||
for( int i = 0; i < events.length; i++) {
|
||||
var e = events[i];
|
||||
Event? latestContactEvent = null;
|
||||
int latestContactsTime = 0;
|
||||
for( var e in events) {
|
||||
if( e.eventData.pubkey == pubkey && e.eventData.kind == 3 && latestContactsTime < e.eventData.createdAt) {
|
||||
latestContactIndex = i;
|
||||
latestContactsTime = e.eventData.createdAt;
|
||||
latestContactEvent = e;
|
||||
}
|
||||
}
|
||||
|
||||
// if contact list was found, get user's feed, and keep the contact list for later use
|
||||
if (latestContactIndex != -1) {
|
||||
return events[latestContactIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
return latestContactEvent;
|
||||
}
|
||||
|
||||
// for the user userPubkey, returns the relay of its contact contactPubkey
|
||||
|
@ -10,7 +10,8 @@ class Relay {
|
||||
IOWebSocketChannel socket;
|
||||
List<String> users; // is used so that duplicate requests aren't sent for same user for this same relay
|
||||
int numReceived;
|
||||
Relay(this.url, this.socket, this.users, this.numReceived);
|
||||
int numRequestsSent;
|
||||
Relay(this.url, this.socket, this.users, this.numReceived, this.numRequestsSent);
|
||||
|
||||
void printInfo() {
|
||||
//print("In Relay: printInfo");
|
||||
@ -23,7 +24,7 @@ class Relay {
|
||||
*/
|
||||
class Relays {
|
||||
Map<String, Relay> relays;
|
||||
List<Event> rEvents = []; // current events received. can be used by others. Is cleared after consumption
|
||||
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);
|
||||
|
||||
@ -40,10 +41,10 @@ class Relays {
|
||||
IOWebSocketChannel fws = IOWebSocketChannel.connect(relayUrl);
|
||||
print('In Relay.relay: connecting to relay $relayUrl');
|
||||
Map<String, Relay> mapRelay = {};
|
||||
Relay relayObject = Relay( relayUrl, fws, [], 0);
|
||||
Relay relayObject = Relay( relayUrl, fws, [], 0, 0);
|
||||
mapRelay[relayUrl] = relayObject;
|
||||
|
||||
return Relays(mapRelay, [], {});
|
||||
return Relays(mapRelay, {}, {});
|
||||
}
|
||||
|
||||
/*
|
||||
@ -58,7 +59,10 @@ class Relays {
|
||||
}
|
||||
}
|
||||
|
||||
String subscriptionId = "single_user" + (relays[relayUrl]?.numRequestsSent??"").toString();
|
||||
|
||||
if( relays.containsKey(relayUrl)) {
|
||||
|
||||
List<String>? users = relays[relayUrl]?.users;
|
||||
if( users != null) {
|
||||
// following is too restrictive casuse changed sinceWhen is not considered. TODO improve it
|
||||
@ -70,7 +74,8 @@ class Relays {
|
||||
users.add(publicKey);
|
||||
}
|
||||
}
|
||||
String request = getUserRequest(publicKey, numEventsToGet, sinceWhen);
|
||||
|
||||
String request = getUserRequest(subscriptionId, publicKey, numEventsToGet, sinceWhen);
|
||||
sendRequest(relayUrl, request);
|
||||
}
|
||||
|
||||
@ -98,7 +103,9 @@ class Relays {
|
||||
}
|
||||
} // if relay exists and has a user list
|
||||
|
||||
String request = getMultiUserRequest(reqKeys, numEventsToGet);
|
||||
String subscriptionId = "multiple_user" + (relays[relayUrl]?.numRequestsSent??"").toString();
|
||||
|
||||
String request = getMultiUserRequest( subscriptionId, reqKeys, numEventsToGet);
|
||||
sendRequest(relayUrl, request);
|
||||
}
|
||||
|
||||
@ -116,13 +123,14 @@ class Relays {
|
||||
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);
|
||||
Relay newRelay = Relay(relay, fws2, [], 0, 1);
|
||||
relays[relay] = newRelay;
|
||||
fws = fws2;
|
||||
fws2.stream.listen(
|
||||
@ -135,7 +143,7 @@ class Relays {
|
||||
}
|
||||
String id = json[2]['id'] as String;
|
||||
if( uniqueIdsRecieved.contains(id)) {
|
||||
if( gDebug > 0) print("In relay: received duplicate event id : $id");
|
||||
//if( gDebug > 0) print("In relay: received duplicate event id : $id");
|
||||
return;
|
||||
} else {
|
||||
uniqueIdsRecieved.add(id);
|
||||
@ -166,6 +174,7 @@ class Relays {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(gDebug > 0) print('\nSending request: \n$request\n to $relay\n\n');
|
||||
fws?.sink.add(request);
|
||||
}
|
||||
@ -186,20 +195,20 @@ class Relays {
|
||||
}
|
||||
}
|
||||
|
||||
Relays relays = Relays({}, [], {});
|
||||
Relays relays = Relays({}, {}, {});
|
||||
|
||||
String getUserRequest(String publicKey, int numUserEvents, int sinceWhen) {
|
||||
String getUserRequest(String subscriptionId, String publicKey, int numUserEvents, int sinceWhen) {
|
||||
String strTime = "";
|
||||
if( sinceWhen != 0) {
|
||||
strTime = ', "since": ${sinceWhen.toString()}';
|
||||
}
|
||||
var strSubscription1 = '["REQ","single_user",{ "authors": ["';
|
||||
var strSubscription1 = '["REQ","$subscriptionId",{ "authors": ["';
|
||||
var strSubscription2 ='"], "limit": $numUserEvents $strTime } ]';
|
||||
return strSubscription1 + publicKey + strSubscription2;
|
||||
}
|
||||
|
||||
String getMultiUserRequest(List<String> publicKeys, int numUserEvents) {
|
||||
var strSubscription1 = '["REQ","multiple_user",{ "authors": [';
|
||||
String getMultiUserRequest(String subscriptionId, List<String> publicKeys, int numUserEvents) {
|
||||
var strSubscription1 = '["REQ","$subscriptionId",{ "authors": [';
|
||||
var strSubscription2 ='], "limit": $numUserEvents } ]';
|
||||
String s = "";
|
||||
|
||||
@ -249,17 +258,20 @@ void getUserEvents(List<String> serverUrls, publicKey, numUserEvents, sinceWhen)
|
||||
});
|
||||
}
|
||||
|
||||
void getMultiUserEvents(serverUrl, List<String> publicKeys, numUserEvents) {
|
||||
void getMultiUserEvents(List<String> serverUrls, List<String> publicKeys, numUserEvents) {
|
||||
if( gDebug > 0) print("Sending multi user request for ${publicKeys.length} users");
|
||||
const int numMaxUserRequests = 15;
|
||||
for( int i = 0; i < publicKeys.length; i+= numMaxUserRequests) {
|
||||
int getUserRequests = numMaxUserRequests;
|
||||
if( publicKeys.length - i <= numMaxUserRequests) {
|
||||
getUserRequests = publicKeys.length - i;
|
||||
|
||||
for(var serverUrl in serverUrls) {
|
||||
for( int i = 0; i < publicKeys.length; i+= numMaxUserRequests) {
|
||||
int getUserRequests = numMaxUserRequests;
|
||||
if( publicKeys.length - i <= numMaxUserRequests) {
|
||||
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);
|
||||
}
|
||||
//print(" sending request form $i to ${i + getUserRequests} ");
|
||||
List<String> partialList = publicKeys.sublist(i, i + getUserRequests);
|
||||
relays.getMultiUserEvents(serverUrl, partialList, numUserEvents);
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,16 +281,16 @@ void sendRequest(List<String> serverUrls, request) {
|
||||
}
|
||||
}
|
||||
|
||||
List<Event> getRecievedEvents() {
|
||||
Set<Event> getRecievedEvents() {
|
||||
return relays.rEvents;
|
||||
}
|
||||
|
||||
void clearEvents() {
|
||||
relays.rEvents = [];
|
||||
relays.rEvents.clear();
|
||||
if( gDebug > 0) print("clearEvents(): returning");
|
||||
}
|
||||
|
||||
void setRelaysIntialEvents(eventsFromFile) {
|
||||
void setRelaysIntialEvents(Set<Event> eventsFromFile) {
|
||||
relays.rEvents = eventsFromFile;
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,8 @@ List<String> gBots = [ "3b57518d02e6acfd5eb7198530b2e351e5a52278fb2499d14b66db2
|
||||
|
||||
const String gDefaultEventsFilename = "all_nostr_events.txt";
|
||||
String gEventsFilename = ""; // is set in arguments, and if set, then file is read from and written to
|
||||
bool gDontWriteOldEvents = true;
|
||||
const int gPurgeBeforeDays = 100;
|
||||
|
||||
const String gUsage = """$exename version $version
|
||||
The nostr console client built using dart.
|
||||
|
@ -22,7 +22,7 @@ class Tree {
|
||||
|
||||
// @method create top level Tree from events.
|
||||
// first create a map. then process each element in the map by adding it to its parent ( if its a child tree)
|
||||
factory Tree.fromEvents(List<Event> events) {
|
||||
factory Tree.fromEvents(Set<Event> events) {
|
||||
if( events.isEmpty) {
|
||||
return Tree(Event("","",EventData("non","", 0, 0, "", [], [], [], [[]], {}), [""], "[json]"), [], {}, [], false, {});
|
||||
}
|
||||
@ -163,12 +163,12 @@ class Tree {
|
||||
/*
|
||||
* @insertEvents inserts the given new events into the tree, and returns the id the ones actually inserted so that they can be printed as notifications
|
||||
*/
|
||||
List<String> insertEvents(List<Event> newEvents) {
|
||||
Set<String> insertEvents(Set<Event> newEventsSetToProcess) {
|
||||
|
||||
List<String> newEventsId = [];
|
||||
Set<String> newEventIdsSet = {};
|
||||
|
||||
// add the event to the Tree
|
||||
newEvents.forEach((newEvent) {
|
||||
newEventsSetToProcess.forEach((newEvent) {
|
||||
// don't process if the event is already present in the map
|
||||
// this condition also excludes any duplicate events sent as newEvents
|
||||
if( allChildEventsMap.containsKey(newEvent.eventData.id)) {
|
||||
@ -180,7 +180,7 @@ class Tree {
|
||||
String reactedTo = processReaction(newEvent);
|
||||
|
||||
if( reactedTo != "") {
|
||||
newEventsId.add(newEvent.eventData.id); // add here to process/give notification about this new reaction
|
||||
//newEventsId.add(newEvent.eventData.id); // add here to process/give notification about this new reaction
|
||||
if(gDebug > 0) print("In insertEvents: got a new reaction by: ${newEvent.eventData.id} to $reactedTo");
|
||||
} else {
|
||||
if(gDebug > 0) print("In insertEvents: For new reaction ${newEvent.eventData.id} could not find reactedTo or reaction was already present by this reactor");
|
||||
@ -203,11 +203,11 @@ class Tree {
|
||||
if( gDebug > 0) print("In insertEvents: adding event to main children map");
|
||||
|
||||
allChildEventsMap[newEvent.eventData.id] = Tree(newEvent, [], {}, [], false, {});
|
||||
newEventsId.add(newEvent.eventData.id);
|
||||
newEventIdsSet.add(newEvent.eventData.id);
|
||||
});
|
||||
|
||||
// now go over the newly inserted event, and add its to the tree. only for kind 1 events
|
||||
newEventsId.forEach((newId) {
|
||||
newEventIdsSet.forEach((newId) {
|
||||
Tree? newTree = allChildEventsMap[newId]; // this should return true because we just inserted this event in the allEvents in block above
|
||||
if( newTree != null) {
|
||||
|
||||
@ -258,9 +258,9 @@ class Tree {
|
||||
}
|
||||
});
|
||||
|
||||
if(gDebug > 0) print("In insertEvents: Found new ${newEventsId.length} events. ");
|
||||
if(gDebug > 0) print("In end of insertEvents: Returning ${newEventIdsSet.length} new notification-type event: $newEventIdsSet ");
|
||||
|
||||
return newEventsId;
|
||||
return newEventIdsSet;
|
||||
}
|
||||
|
||||
|
||||
@ -268,21 +268,19 @@ class Tree {
|
||||
* @printNotifications Add the given events to the Tree, and print the events as notifications
|
||||
* It should be ensured that these are only kind 1 events
|
||||
*/
|
||||
void printNotifications(List<String> newEventsId, String userName) {
|
||||
// remove duplicates
|
||||
Set temp = {};
|
||||
newEventsId.retainWhere((event) => temp.add(newEventsId));
|
||||
|
||||
void printNotifications(Set<String> newEventIdsSet, String userName) {
|
||||
if( gDebug > 0) print("Info: in printNotifications: num new evetns = ${newEventIdsSet.length}");
|
||||
|
||||
String strToWrite = "Notifications: ";
|
||||
int countNotificationEvents = 0;
|
||||
for( int i =0 ; i < newEventsId.length; i++) {
|
||||
int k = (allChildEventsMap[newEventsId[i]]?.e.eventData.kind??-1);
|
||||
for( var newEventId in newEventIdsSet) {
|
||||
int k = (allChildEventsMap[newEventId]?.e.eventData.kind??-1);
|
||||
if( k == 7 || k == 1 || k == 42 || k == 40) {
|
||||
countNotificationEvents++;
|
||||
}
|
||||
|
||||
if( allChildEventsMap.containsKey(newEventsId[i])) {
|
||||
if( gDebug > 0) print( "id = ${ (allChildEventsMap[newEventsId[i]]?.e.eventData.id??-1)}");
|
||||
if( allChildEventsMap.containsKey(newEventId)) {
|
||||
if( gDebug > 0) print( "id = ${ (allChildEventsMap[newEventId]?.e.eventData.id??-1)}");
|
||||
} else {
|
||||
if( gDebug > 0) print( "Info: could not find event id in map."); // this wont later be processed
|
||||
}
|
||||
@ -290,7 +288,7 @@ class Tree {
|
||||
}
|
||||
// TODO don't print notifications for events that are too old
|
||||
|
||||
if(gDebug > 0) print("Info: In printNotifications: newEventsId = $newEventsId count17 = $countNotificationEvents");
|
||||
if(gDebug > 0) print("Info: In printNotifications: newEventsId = $newEventIdsSet count17 = $countNotificationEvents");
|
||||
|
||||
if( countNotificationEvents == 0) {
|
||||
strToWrite += "No new replies/posts.\n";
|
||||
@ -300,14 +298,14 @@ class Tree {
|
||||
return;
|
||||
}
|
||||
// TODO call count() less
|
||||
strToWrite += "Number of new replies/posts = ${newEventsId.length}\n";
|
||||
strToWrite += "Number of new replies/posts = ${newEventIdsSet.length}\n";
|
||||
stdout.write("${getNumDashes(strToWrite.length -1 )}\n$strToWrite");
|
||||
stdout.write("Total posts : ${count()}\n");
|
||||
stdout.write("Signed in as : $userName\n");
|
||||
stdout.write("\nHere are the threads with new replies or new likes: \n\n");
|
||||
|
||||
List<Tree> topTrees = []; // collect all top tress to display in this list. only unique tress will be displayed
|
||||
newEventsId.forEach((eventID) {
|
||||
List<Tree> topNotificationTree = []; // collect all top tress to display in this list. only unique tress will be displayed
|
||||
newEventIdsSet.forEach((eventID) {
|
||||
|
||||
Tree ?t = allChildEventsMap[eventID];
|
||||
if( t == null) {
|
||||
@ -319,7 +317,7 @@ class Tree {
|
||||
case 1:
|
||||
t.e.eventData.isNotification = true;
|
||||
Tree topTree = getTopTree(t);
|
||||
topTrees.add(topTree);
|
||||
topNotificationTree.add(topTree);
|
||||
break;
|
||||
case 7:
|
||||
Event event = t.e;
|
||||
@ -333,7 +331,7 @@ class Tree {
|
||||
if( reactedToTree != null) {
|
||||
reactedToTree.e.eventData.newLikes.add( reactorId);
|
||||
Tree topTree = getTopTree(reactedToTree);
|
||||
topTrees.add(topTree);
|
||||
topNotificationTree.add(topTree);
|
||||
} else {
|
||||
if(gDebug > 0) print("Could not find reactedTo tree");
|
||||
}
|
||||
@ -350,9 +348,12 @@ class Tree {
|
||||
|
||||
// remove duplicate top trees
|
||||
Set ids = {};
|
||||
topTrees.retainWhere((t) => ids.add(t.e.eventData.id));
|
||||
topNotificationTree.retainWhere((t) => ids.add(t.e.eventData.id));
|
||||
|
||||
topTrees.forEach( (t) { t.printTree(0, 0, selectAll); });
|
||||
topNotificationTree.forEach( (t) {
|
||||
t.printTree(0, 0, selectAll);
|
||||
print("\n");
|
||||
});
|
||||
print("\n");
|
||||
}
|
||||
|
||||
@ -521,6 +522,13 @@ class Tree {
|
||||
for( var k in allChildEventsMap.keys) {
|
||||
Tree? t = allChildEventsMap[k];
|
||||
if( t != null) {
|
||||
// only write if its not too old
|
||||
if( gDontWriteOldEvents) {
|
||||
if( t.e.eventData.createdAt < (DateTime.now().subtract(Duration(days: gPurgeBeforeDays)).millisecondsSinceEpoch ~/ 1000)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String line = "${t.e.originalJson}\n";
|
||||
nLinesStr += line;
|
||||
eventCounter++;
|
||||
@ -1006,7 +1014,7 @@ String processReaction(Event event) {
|
||||
}
|
||||
|
||||
// will go over the list of events, and update the global gReactions appropriately
|
||||
void processReactions(List<Event> events) {
|
||||
void processReactions(Set<Event> events) {
|
||||
for (Event event in events) {
|
||||
processReaction(event);
|
||||
}
|
||||
@ -1016,7 +1024,7 @@ void processReactions(List<Event> events) {
|
||||
/*
|
||||
* @function getTree Creates a Tree out of these received List of events.
|
||||
*/
|
||||
Future<Tree> getTree(List<Event> events) async {
|
||||
Future<Tree> getTree(Set<Event> events) async {
|
||||
if( events.isEmpty) {
|
||||
print("Warning: In printEventsAsTree: events length = 0");
|
||||
return Tree(Event("","",EventData("non","", 0, 0, "", [], [], [], [[]], {}), [""], "[json]"), [], {}, [], true, {});
|
||||
|
Loading…
x
Reference in New Issue
Block a user