Added test cases. improved incoming processing of new events, so that doublets for same top thread don't happen

This commit is contained in:
Vishal
2025-08-25 21:27:43 +05:30
parent b308502334
commit 4d88a8015d
8 changed files with 713 additions and 190 deletions

View File

@@ -34,9 +34,10 @@ const String overWriteFlag = "overwrite";
const String locationArg = "location";
const String lnQrFlag = "lnqr";
const String configuredUser = "user";
const String nosaveArg = "nosave";
Future<void> main(List<String> arguments) async {
epochAppStartedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final parser = ArgParser()..addOption(requestArg) ..addOption(pubkeyArg, abbr:"p")..addOption(prikeyArg, abbr:"k")
..addOption(lastdaysArg, abbr:"d") ..addOption(relayArg, abbr:"r")
@@ -49,6 +50,7 @@ Future<void> main(List<String> arguments) async {
..addOption(colorArg, abbr:"c")
..addOption(difficultyArg, abbr:"y")
..addFlag(overWriteFlag, abbr:"e", defaultsTo: false)
..addOption(nosaveArg, abbr:"n")
..addOption(locationArg, abbr:"g")
..addFlag("debug")
..addFlag(lnQrFlag, abbr:"l", defaultsTo: false)
@@ -120,6 +122,12 @@ Future<void> main(List<String> arguments) async {
}
if( argResults[nosaveArg] != null) {
gDontSaveBeforeDays = int.parse(argResults[nosaveArg]);
print("Will not save events older than $gDontSaveBeforeDays days.");
}
if( argResults[translateArg]) {
gTranslate = true;
print("Going to translate comments in last $gNumTranslateDays days using Google translate service");
@@ -273,7 +281,7 @@ Future<void> main(List<String> arguments) async {
if( tempMaxDepth < gMinimumDepthAllowed || tempMaxDepth > gMaximumDepthAllowed) {
print("Maximum depth cannot be less than $gMinimumDepthAllowed and cannot be more than $gMaximumDepthAllowed. Going to use the default maximum depth, which is $gDefaultMaxDepth.");
} else {
maxDepthAllowed = tempMaxDepth;
gMaxDepthAllowed = tempMaxDepth;
print("Going to take threads to maximum depth of $gNumLastDays days");
}
}
@@ -416,12 +424,12 @@ Future<void> main(List<String> arguments) async {
stdout.write("...done\n");//received $numUserPosts new posts made by the user\n");
Set<String> userEvents = getOnlyUserEvents(initialEvents, userPublicKey);
//print('Total events fetched till now: ${initialEvents.length}. Total user events fetched: ${userEvents.length}');
print('Total events fetched till now: ${initialEvents.length}. Total user events fetched: ${userEvents.length}');
// get events from channels of user; gets public as well as encrypted channels
Set<String> userChannels = getUserChannels(initialEvents, userPublicKey);
//Set<String> userChannels = getUserChannels(initialEvents, userPublicKey);
//printSet(userChannels, "user channels: \n", "\n");
//getIdAndMentionEvents(gListRelayUrls1, userChannels, limitPerSubscription, 0, getSecondsDaysAgo(limitOthersEvents), "#e", "ids");
//getIdAndMentionEvents(gListRelayUrls, userChannels, limitPerSubscription, 0, getSecondsDaysAgo(limitOthersEvents), "#e", "ids");
getKindEvents([40, 41], gListRelayUrls, limitPerSubscription, getSecondsDaysAgo(limitSelfEvents));
getKindEvents([42], gListRelayUrls, 3 * limitPerSubscription, getSecondsDaysAgo(limitOthersEvents));
@@ -454,6 +462,8 @@ Future<void> main(List<String> arguments) async {
pTags = getpTags(initialEvents, gMaxPtagsToGet);
}
print("pTags = ${pTags.length}");
// get only limited number of contacts otherwise relays get less responsive
int maxContactsFetched = 700;
if( contacts.length > maxContactsFetched) {
@@ -466,7 +476,7 @@ Future<void> main(List<String> arguments) async {
// get meta events of all users fetched
getMultiUserEvents(gListRelayUrls, usersFetched, 10 * limitPerSubscription, getSecondsDaysAgo(limitSelfEvents*100), {0,3});
//print("fetched meta of ${usersFetched.length}");
print("fetched meta of ${usersFetched.length}");
void resetRelays() {

View File

@@ -377,16 +377,17 @@ void printProfile(Store node, String profilePubkey) {
}
}
print("\nName : $authorName ( $profilePubkey / $npubPubkey).");
print("About : $about");
print("Picture : $picture");
print("display_name: $displayName");
print("Website : $website");
print("Lud06 : $lud06");
print("Lud16 : $lud16");
print("Nip 05 : ${verified?"yes. $nip05Id":"no"}");
print("\nLast Updated: ${getPrintableDate(dateLastUpdated)}\n");
// print followers
List<String> followers = node.getFollowers(profilePubkey);
stdout.write("$pronoun have ${followers.length} followers: ");
followers.sort((a, b) => getAuthorName(a).compareTo(getAuthorName(b)));
for (var x in followers) {
stdout.write("${getAuthorName(x)}, ");
}
print("\n");
// get the latest kind 3 event for the user, which lists his 'follows' list
Event? profileContactEvent = getContactEvent(profilePubkey);
if (profileContactEvent != null ) {
@@ -432,14 +433,20 @@ void printProfile(Store node, String profilePubkey) {
}
}
// print followers
List<String> followers = node.getFollowers(profilePubkey);
stdout.write("$pronoun have ${followers.length} followers: ");
followers.sort((a, b) => getAuthorName(a).compareTo(getAuthorName(b)));
for (var x in followers) {
stdout.write("${getAuthorName(x)}, ");
}
print("");
print("-------------------------------------------------------------------------");
print("\nName : $authorName ( $profilePubkey / $npubPubkey).");
print("About : $about");
print("Picture : $picture");
print("display_name: $displayName");
print("Website : $website");
print("Lud06 : $lud06");
print("Lud16 : $lud16");
print("Nip 05 : ${verified?"yes. $nip05Id":"no"}");
print("\nKind 0 was Last Updated on: ${getPrintableDate(dateLastUpdated)}\n");
print("");
}
@@ -1355,6 +1362,226 @@ Future<void> PrivateMenuUI(Store node) async {
return;
}
Future<void> followUnfollowMenu(Store node) async {
bool continueMenu = true;
while(continueMenu) {
await processAnyIncomingEvents(node, true); // this takes 300 ms
String menuInfo = """You can follow or unfollow any given pubkey/User. You can also fetch events for some specific user, or fetch some specific event(s).""";
int option = showMenu([
'Follow a User', // 1
'Unfollow a User', // 2
'Fetch all events for User',// 3
'Fetch specific Event', // 4
'E(x)it to main menu'], // 5
"Follow/Unfollow Menu", // name of menu
menuInfo);
switch(option) {
case 1:
// in case the program was invoked with --pubkey, then user can't send kind 3 (contact list) messages
if( userPrivateKey == "") {
printWarning("Since no user private key has been supplied, new follow messages can't be sent. Invoke with --prikey");
break;
}
clearScreen();
stdout.write("Enter username or first few letters of user's public key( or full public key): ");
String? $tempUserName = stdin.readLineSync();
String userName = $tempUserName??"";
if( userName != "") {
Set<String> pubkey = getPublicKeyFromName(userName);
printPubkeyResult(pubkey);
if( pubkey.length > 1) {
printWarning("Got multiple users with the same name. Try again, and type a more unique name or id-prefix");
} else {
if (pubkey.isEmpty && userName.length != 64) {
printWarning("Could not find the user with that id or username. You can try again by providing the full 64 byte long hex public key.");
}
else {
if( pubkey.isEmpty) {
printWarning("Could not find the user with that id or username in internal store/list. However, since the given id is 64 bytes long, taking that as hex public key and adding them as contact.");
pubkey.add(userName);
}
String pk = pubkey.first;
// get this users latest contact list event ( kind 3 event)
Event? contactEvent = getContactEvent(userPublicKey);
if( contactEvent != null) {
Event newContactEvent = contactEvent;
bool alreadyContact = false;
for(int i = 0; i < newContactEvent.eventData.contactList.length; i++) {
if( newContactEvent.eventData.contactList[i].contactPubkey == pubkey.first) {
alreadyContact = true;
break;
}
}
if( !alreadyContact) {
print('Sending new contact event');
Contact newContact = Contact(pk, defaultServerUrl);
newContactEvent.eventData.contactList.add(newContact);
getUserEvents(gListRelayUrls, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newContactEvent);
} else {
print("The contact already exists in the contact list.");
}
} else {
// TODO fix the send event functions by streamlining them
if(confirmFirstContact()) {
print('Sending first contact event');
String newId = "", newPubkey = userPublicKey, newContent = "";
int newKind = 3;
List<List<String>> newEtags = [];
List<String> newPtags = [pk];
List<List<String>> newTags = [[]];
Set<String> newNewLikes = {};
int newCreatedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
List<Contact> newContactList = [ Contact(pk, defaultServerUrl) ];
EventData newEventData = EventData(newId, newPubkey, newCreatedAt, newKind, newContent, newEtags, newPtags, newContactList, newTags, newNewLikes,);
Event newEvent = Event( "EVENT", newId, newEventData, [], "");
getUserEvents(gListRelayUrls, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newEvent);
}
}
}
}
}
break;
case 2:
// in case the program was invoked with --pubkey, then user can't send kind 3 (contact list) messages
if( userPrivateKey == "") {
printWarning("Since no user private key has been supplied, new kind 3 messages can be sent, means you can't unfollow anyone. Invoke with --prikey");
break;
}
clearScreen();
stdout.write("Enter username or first few letters of user's public key who you want to unfollow ( or full public key): ");
String? $tempUserName = stdin.readLineSync();
String userName = $tempUserName??"";
if( userName != "") {
Set<String> pubkeysFromName = getPublicKeyFromName(userName);
Set<String> setFollows = getFollows(userPublicKey);
setFollows.addAll(pubkeysFromName);
Set<String> toUnfollow = {};
for ( String pubkey in pubkeysFromName) {
if( setFollows.contains(pubkey)) {
toUnfollow.add(pubkey);
}
}
printPubkeyResult(toUnfollow);
if( toUnfollow.length > 1) {
printWarning("Got multiple users with the same name. Try again, and type a more unique name or id-prefix");
} else {
if (toUnfollow.isEmpty && userName.length != 64) {
printWarning("Could not find the user with that id or username. You can try again by providing the full 64 byte long hex public key.");
}
else {
if( toUnfollow.isEmpty) {
printWarning("Could not find the user with that id or username in internal store/list. However, since the given id is 64 bytes long, taking that as hex public key and adding them as contact.");
}
// get this users latest contact list event ( kind 3 event)
Event? contactEvent = getContactEvent(userPublicKey);
if( contactEvent != null) {
Event newContactEvent = contactEvent;
bool alreadyContact = false;
for(int i = 0; i < newContactEvent.eventData.contactList.length; i++) {
if( newContactEvent.eventData.contactList[i].contactPubkey == toUnfollow.first) {
alreadyContact = true;
break;
}
}
if( !alreadyContact) {
print('Given user ${toUnfollow.first} is not in your contact list. You cannot unfollow them.');
} else {
print("Going to publish a new contact list without this given user.");
print("old contact list = ${newContactEvent.eventData.contactList.length}");
newContactEvent.eventData.contactList.removeWhere((element) => element.contactPubkey == toUnfollow.first);
print("new contact list = ${newContactEvent.eventData.contactList.length}");
sendEvent(node, newContactEvent);
}
} else {
print("Could not find your contact list, so no user can be unfollowed.");
}
}
}
}
break;
case 3:
clearScreen();
stdout.write("Enter username or first few letters of user's public key( or full public key): ");
String? $tempUserName = stdin.readLineSync();
String userName = $tempUserName??"";
if( userName != "") {
Set<String> pubkey = getPublicKeyFromName(userName);
printPubkeyResult(pubkey);
if( pubkey.length > 1) {
printWarning("Got multiple users with the same name. Try again, and type a more unique name or id-prefix");
} else {
if( pubkey.length == 1) {
getUserEvents(gListRelayUrls, pubkey.first, gLimitPerSubscription, getSecondsDaysAgo(4*gLimitFollowPosts));
} else {
//printWarning("Could not find any user with given name/id");
if( userName.length > 64) {
userName = userName.substring(0, 64);
}
for(var t in node.allChildEventsMap.values) {
EventData e = t.event.eventData;
if( e.pubkey.length < 64) {
continue;
}
// print(e.pubkey);
if( e.pubkey.substring(0, userName.length) == userName) {
print("matched ${e.pubkey}");
printPubkeyResult({e.pubkey});
getUserEvents(gListRelayUrls, e.pubkey, gLimitPerSubscription, getSecondsDaysAgo(4*gLimitFollowPosts));
break;
}
}
}
}
}
break;
case 4:
print("in case 4");
break;
case 5:
continueMenu = false;
break;
default:
break;
} // switch
} // while (continueMenu)
return;
}
Future<void> socialMenuUi(Store node) async {
clearScreen();
@@ -1373,18 +1600,18 @@ Future<void> socialMenuUi(Store node) async {
// the main menu
int option = showMenu([
'Your Feed', // 1
'Your Feed (follow+popular)', // 1
'Make a Post/Reply or Like', // 2
'Replies+ to you',// 3
'Your Posts', // 4
'Your Replies/Likes',//5
'Accounts you Follow', // 6
'Mutual Follows', // 7
'Search word or event id', // 8
'Follow new contact', // 9
'Show user profile', // 10
'Change # of hours printed', // 11
'E(x)it to main menu'], // 12
'Replies+ to you', // 3
'Your Posts', // 4
'Your Replies/Likes', // 5
'Accounts you Follow', // 6
'Mutual Follows', // 7
'Search word or event id', // 8
'Follow/Unfollow/Fetch Menu', // 9
'Show user profile', // 10
'Change # of hours printed', // 11
'E(x)it to main menu'], // 12
"Social Network Menu");
switch(option) {
@@ -1525,84 +1752,7 @@ Future<void> socialMenuUi(Store node) async {
break;
*/
case 9: // follow new contact
// in case the program was invoked with --pubkey, then user can't send messages
if( userPrivateKey == "") {
printWarning("Since no user private key has been supplied, posts/messages can't be sent. Invoke with --prikey");
break;
}
clearScreen();
stdout.write("Enter username or first few letters of user's public key( or full public key): ");
String? $tempUserName = stdin.readLineSync();
String userName = $tempUserName??"";
if( userName != "") {
Set<String> pubkey = getPublicKeyFromName(userName);
printPubkeyResult(pubkey);
if( pubkey.length > 1) {
if( pubkey.length > 1) {
printWarning("Got multiple users with the same name. Try again, and type a more unique name or id-prefix");
}
} else {
if (pubkey.isEmpty && userName.length != 64) {
printWarning("Could not find the user with that id or username. You can try again by providing the full 64 byte long hex public key.");
}
else {
if( pubkey.isEmpty) {
printWarning("Could not find the user with that id or username in internal store/list. However, since the given id is 64 bytes long, taking that as hex public key and adding them as contact.");
pubkey.add(userName);
}
String pk = pubkey.first;
// get this users latest contact list event ( kind 3 event)
Event? contactEvent = getContactEvent(userPublicKey);
if( contactEvent != null) {
Event newContactEvent = contactEvent;
bool alreadyContact = false;
for(int i = 0; i < newContactEvent.eventData.contactList.length; i++) {
if( newContactEvent.eventData.contactList[i].contactPubkey == pubkey.first) {
alreadyContact = true;
break;
}
}
if( !alreadyContact) {
print('Sending new contact event');
Contact newContact = Contact(pk, defaultServerUrl);
newContactEvent.eventData.contactList.add(newContact);
getUserEvents(gListRelayUrls, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newContactEvent);
} else {
print("The contact already exists in the contact list. Republishing the old contact list.");
getUserEvents(gListRelayUrls, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, contactEvent);
}
} else {
// TODO fix the send event functions by streamlining them
if(confirmFirstContact()) {
print('Sending first contact event');
String newId = "", newPubkey = userPublicKey, newContent = "";
int newKind = 3;
List<List<String>> newEtags = [];
List<String> newPtags = [pk];
List<List<String>> newTags = [[]];
Set<String> newNewLikes = {};
int newCreatedAt = DateTime.now().millisecondsSinceEpoch ~/ 1000;
List<Contact> newContactList = [ Contact(pk, defaultServerUrl) ];
EventData newEventData = EventData(newId, newPubkey, newCreatedAt, newKind, newContent, newEtags, newPtags, newContactList, newTags, newNewLikes,);
Event newEvent = Event( "EVENT", newId, newEventData, [], "");
getUserEvents(gListRelayUrls, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newEvent);
}
}
}
}
}
await followUnfollowMenu(node);
break;
@@ -1772,6 +1922,7 @@ Future<void> mainMenuUi(Store node) async {
if( gEventsFilename != "") {
await node.writeEventsToFile(gEventsFilename);
}
relays.printInfo();
exit(0);
} // end menu switch
} // end while

78
lib/del.json Normal file
View File

@@ -0,0 +1,78 @@
{
"id": "0133c16a68c92df01ea7250ade624688493d9fcbbfadf15f94640700ceb793d6",
"sig": "2ee3f19d633f5adebdcba05dfe5230a1ecc6a1be33609d3ff5b576fcc6dc51e438558ca4b6a8c247473cf5e403362ec0aa66d9a60768ca12ce765b53a9cf11ae",
"kind": 1,
"tags": [],
"pubkey": "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd",
"content": "gm Nostr",
"created_at": 1752504950
}
{
"id": "d296ba29223318e12a48b857ef1dfc97edc7a74af2979739e9aaa7343f6957d6",
"sig": "9e681df0988fb2304aca2c5a60a474441c3506af4501f9eefcc7b53d20b9d3cef6e8e4aa0557ddd083976738961a801855f6f12ccc2c2035f101e72212b72415",
"kind": 1,
"tags": [
[
"p",
"40b9c85fffeafc1cadf8c30a4e5c88660ff6e4971a0dc723d5ab674b5e61b451"
],
[
"client",
"nostr_console"
]
],
"pubkey": "3235036bd0957dfb27ccda02d452d7c763be40c91a1ac082ba6983b25238388c",
"content": "nostr:npub1gzuushllat7pet0ccv9yuhygvc8ldeyhrgxuwg744dn5khnpk3gs3ea5dstesting nostr console with mentions. for simple one word names without spaces. ",
"created_at": 1754389754
}
npub1gzuushllat7pet0ccv9yuhygvc8ldeyhrgxuwg744dn5khnpk3gs3ea5ds
{
"id": "a5344bc16e4ecd4cd1d2d6fc315216f00c4f3584e800cbaa8cb3f6772ae9a727",
"sig": "22840829d64327e827398e1915b781fe99fb7c4ebc957a21324941c2471304d5845039365386435f7512b14ffc51ff797bfa22929d40899573b878ed3319987d",
"kind": 1,
"tags": [
[
"p",
"3235036bd0957dfb27ccda02d452d7c763be40c91a1ac082ba6983b25238388c"
],
[
"p",
"290417c26b0fc78fe987a58c0edd76008977c60543dda873f248b60dbf89a11c"
],
[
"client",
"nostr_console"
]
],
"pubkey": "e00ce8ec993e1b5450a833ae0c164dc6f71990f7ec75589cd2c2388b7ee7e1c1",
"content": "testing mention nostr:npub1xg6sx67sj47lkf7vmgpdg5khca3musxfrgdvpq46dxpmy53c8zxqqy7kwr \n nostr:npub19yzp0sntplrcl6v85kxqahtkqzyh03s9g0w6suljfzmqm0uf5ywqwpjkda \n @nostr commander augustus \n ",
"created_at": 1754390145
}
egg
{
"id": "43d7bd9f443c7d9751550f1e1eafb4f0a5220c3d6edef8ed6f1741fb09570859",
"sig": "1e352832982460e5e3120e46ee948333e4680354858cfe2c655b30696ed9ba8e59ac4a38235ea6062ee562ae080441985af69b0de9b22a611f7ad8766161b077",
"kind": 1,
"tags": [
[
"imeta",
"url https://image.nostr.build/e70d185cd4e2f0ba5f3b9b41b4c743f49e759785874bf8da7d2f9de093e6a4d4.jpg",
"blurhash eIEpx:I-Kas.ai?[i_NYbtsqEkrrw5OTxtZ=I-$+aOXQnWInoN$+NG",
"dim 3024x4032"
],
[
"r",
"https://image.nostr.build/e70d185cd4e2f0ba5f3b9b41b4c743f49e759785874bf8da7d2f9de093e6a4d4.jpg"
]
],
"pubkey": "ddf03aca85ade039e6742d5bef3df352df199d0d31e22b9858e7eda85cb3bbbe",
"content": "Named my dog after my favourite distro. This is Archie\n\nhttps://image.nostr.build/e70d185cd4e2f0ba5f3b9b41b4c743f49e759785874bf8da7d2f9de093e6a4d4.jpg",
"created_at": 1754413437
}

View File

@@ -1206,15 +1206,17 @@ Future<http.Response> fetchNip05Info(String nip05Url) {
}
// If given event is kind 0 event, then populates gKindONames with that info
// returns true if entry was created or modified, false otherwise
bool processKind0Event(Event e) {
// returns 1 if entry was created, 2 if it was modified, 3 if it was an older kind 0 and a latest kind 0 is already there
// return -1 if its not a kind 0 event
// caller can discard the event if 2 and 3 are returned
int processKind0Event(Event e) {
if( e.eventData.kind != 0) {
return false;
return -1;
}
String content = e.eventData.content;
if( content.isEmpty) {
return false;
return 0;
}
String name = "";
@@ -1259,7 +1261,8 @@ bool processKind0Event(Event e) {
}
bool localDebug = false; //e.eventData.pubkey == "9ec7a778167afb1d30c4833de9322da0c08ba71a69e1911d5578d3144bb56437"? true: false;
// get NIP 05 info
if( newEntry || entryModified) {
if(nip05.isNotEmpty) {
List<String> urlSplit = nip05.split("@");
@@ -1309,7 +1312,14 @@ bool processKind0Event(Event e) {
}
}
return newEntry || entryModified;
if( newEntry) {
return 1;
}
if( entryModified) {
return 2;
}
return 3;
}
// If given event is kind 3 event, then populates gKindONames with contact info
@@ -1736,6 +1746,16 @@ String getPrintableDate(int createdAt) {
return strDate;
}
// get printable date from seconds since epoch
String getPrintableDateWithYear(int createdAt) {
final df1 = DateFormat('hh:mm a');
final df2 = DateFormat(DateFormat.ABBR_MONTH_DAY);
String strDate = df1.format(DateTime.fromMillisecondsSinceEpoch(createdAt*1000));
strDate += " ${df2.format(DateTime.fromMillisecondsSinceEpoch(createdAt*1000))}";
return strDate;
}
/*
* Returns true if this is a valid direct message to just this user. Direct message = kind 4 AND 104
*/

View File

@@ -154,7 +154,7 @@ class Relays {
return;
}
if( gDebug > 0) print ("\nIn relay.sendRequest for relay $relayUrl");
if( gDebug > 0 ) print ("\nIn relay.sendRequest for relay $relayUrl");
IOWebSocketChannel? fws;
if(relays.containsKey(relayUrl)) {
@@ -226,8 +226,9 @@ class Relays {
}
}
if(gDebug > 0) log.info('Sending request: \n$request\n to $relayUrl\n\n');
if(gDebug > 0 ) print('in sendRequest: Connected for \n$request\n to $relayUrl\n\n');
fws?.sink.add(request);
if(gDebug > 0 ) print('in sendRequest: Now sending request: \n$request\n to $relayUrl\n\n');
}
@@ -299,7 +300,7 @@ getKindEvents(List<int> kind, Set<String> serverUrls, int limit, int sinceWhen)
void getMultiUserEvents(Set<String> serverUrls, Set<String> setPublicKeys, int numUserEvents, int sinceWhen, [Set<int>? kind]) {
List<String> publicKeys = setPublicKeys.toList();
if( gDebug > 0) print("Sending multi user request for ${publicKeys.length} users");
if( gDebug > 0 || true) print("Sending multi user request for ${publicKeys.length} users");
for(var serverUrl in serverUrls) {
for( int i = 0; i < publicKeys.length; i+= gMaxAuthorsInOneRequest) {
@@ -309,6 +310,7 @@ void getMultiUserEvents(Set<String> serverUrls, Set<String> setPublicKeys, int n
}
List<String> partialList = publicKeys.sublist(i, i + getUserRequests);
relays.getMultiUserEvents(serverUrl, partialList, numUserEvents, sinceWhen, kind);
if( gDebug > 0 || true) print(" Sending multi user request for $getUserRequests users");
}
}
}
@@ -329,7 +331,7 @@ void sendEventsRequest(Set<String> serverUrls, Set<String> eventIds) {
}
}
void sendRequest(Set<String> serverUrls, request) {
void sendRequest(Set<String> serverUrls, String request) {
for (var url in serverUrls) {
relays.sendRequest(url, request);
}

View File

@@ -7,11 +7,14 @@ const String version = "0.3.8-beta";
int gDebug = 0;
int gSpecificDebug = 0;
const int SECONDS_PER_DAY = 84600;
int epochAppStartedAt = -1; // is set when application starts
final log = Logger('ExampleLogger');
// for debugging
String gCheckEventId = "xb9e1824fe65b10f7d06bd5f6dfe1ab3eda876d7243df5878ca0b9686d80c0840f";
String gCheckEventId = "_f3a267ecbb631012da618de620bc1fe265f6429f412359bf02330b437cf88e67";
int gMaxEventLenthAccepted = 80000; // max event size. events larger than this are rejected.
@@ -26,21 +29,28 @@ const int gNumRoomsShownByDefault = 20;
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 gDontSaveBeforeDays = 20; // dont save events older than this many days if gDontWriteOldEvents flag is true
const int gDeletePostsOlderThanDays = 20;
bool gOverWriteFile = false; // overwrite the file, and don't just append. Will write all events in memory.
// controlled by command line argument 'nosave'
// dont save events older than this many days. If its -1, then everything is saved.
int gDontSaveBeforeDays = -1;
// overwrite the file, and don't just append. Will write all events in memory.
bool gOverWriteFile = false;
const int gDontAddToStoreBeforeDays = 60; // events older than this are not added to the Store of all events
const int gLimitFollowPosts = 20; // when getting events, this is the since field (unless a fully formed request is given in command line)
const int gLimitFollowPosts = 50; // when getting events, this is the since field (unless a fully formed request is given in command line)
const int gLimitPerSubscription = 20000;
// don't show notifications for events that are older than 5 days and come when program is running
// applicable only for notifications and not for search results. Search results set a flag in EventData and don't use this variable
const int gDontHighlightEventsOlderThan = 4;
int gDefaultNumWaitSeconds = 12000; // is used in main()
const int gMaxAuthorsInOneRequest = 300; // number of author requests to send in one request
// Used in printTreeNotifications()
// don't show notifications for events that are older than 10 days and come when program is running
// applicable only for notifications and not for search results. Search results set a flag in EventData and don't use this variable
const int gDontHighlightEventsOlderThan = 10;
int gDefaultNumWaitSeconds = 120; // is used in main()
const int gMaxAuthorsInOneRequest = 100; // number of author requests to send in one request
const int gMaxPtagsToGet = 200; // maximum number of p tags that are taken from the comments of feed ( the top most, most frequent)
const int gSecsLatestLive = 2 * 3600; // the lastst seconds for which to get the latest event in main
@@ -50,10 +60,19 @@ int gHoursDefaultPrint = 6; // print latest given hours only
int numFileEvents = 0, numFilePosts = 0, numUserPosts = 0, numFeedPosts = 0, numOtherPosts = 0;
Set<String> aug18_2025 = {
"wss://relay.damus.io",
"wss://nostr-pub.wellorder.net",
"wss://nos.lol",
"wss://eden.nostr.land",
"wss://offchain.pub",
"wss://nostr.mom"
};
// edited on 29 sept 2024
String defaultServerUrl = "wss://relay.damus.io";
Set<String> gListRelayUrls = { defaultServerUrl,
Set<String> orignalUrls = { defaultServerUrl,
"wss://nostr.wine",
"wss://relay.nostr.info",
"wss://nos.lol",
@@ -61,6 +80,8 @@ Set<String> gListRelayUrls = { defaultServerUrl,
};
Set<String> gListRelayUrls = orignalUrls ; // aug18_2025;
// well known disposable test private key
const String gDefaultPublicKey = "";
String userPrivateKey = "";
@@ -143,11 +164,11 @@ String gAlignment = "center"; // is modified in main if --align
const int gapBetweenTopTrees = 1;
const int gNameLengthInPost = 12;
// after depth of maxDepthAllowed the thread is re-aligned to left by leftShiftThreadBy
// after depth of gMaxDepthAllowed the thread is re-aligned to left by leftShiftThreadBy
const int gMinimumDepthAllowed = 2;
const int gMaximumDepthAllowed = 12;
const int gDefaultMaxDepth = 5;
int maxDepthAllowed = gDefaultMaxDepth;
int gMaxDepthAllowed = gDefaultMaxDepth;
const int leftShiftThreadsBy = 3;
int gMaxLenUnbrokenWord = 8; // lines are broken if space is at end of line for this number of places

View File

@@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:io';
import 'dart:convert';
import 'package:nostr_console/event_ds.dart';
@@ -451,8 +452,8 @@ class Tree {
// if the thread becomes too 'deep' then reset its depth, so that its
// children will not be displayed too much on the right, but are shifted
// left by about <leftShiftThreadsBy> places
if( depth > maxDepthAllowed) {
depth = maxDepthAllowed - leftShiftThreadsBy;
if( depth > gMaxDepthAllowed) {
depth = gMaxDepthAllowed - leftShiftThreadsBy;
printDepth(depth+1);
stdout.write("${getNumDashes((leftShiftThreadsBy + 1) * gSpacesPerDepth - 1, "")}\n");
leftShifted = true;
@@ -588,7 +589,7 @@ class Tree {
// only used by writefile
bool treeSelectorUserMentioned(Set<String> pubkeys) {
for( int i = 0; i < event.eventData.pTags.length; i++ ) {
if( pubkeys.contains( event.eventData.pTags[i][0] )) {
if( pubkeys.contains( event.eventData.pTags[i] )) {
return true;
}
}
@@ -1418,14 +1419,14 @@ class Store {
for(int i = 0; i < topPosts.length; i++) {
Tree tree = topPosts[i];
if( tree.event.eventData.id == newEvent.eventData.id) {
if( tree.event.eventData.id == newEvent.eventData.id && tree.event.eventData.pubkey == gDummyAccountPubkey) {
// its a replacement.
if( gDebug >= 0 && newEvent.eventData.id == gCheckEventId) log.info("In processIncoming: Replaced old dummy event of id: ${newEvent.eventData.id}");
tree.event = newEvent;
allChildEventsMap[tree.event.eventData.id] = tree;
insertedInMap = true;
if(false) print(" replaced old ${tree.event.eventData.id}");
continue;
if(false) print(" In replaceAndInsertInMap: replaced old ${tree.event.eventData.id}");
break;
}
}
@@ -1452,14 +1453,17 @@ class Store {
// add the event to the main event store thats allChildEventsMap
for (var newEvent in newEventsToProcess) {
if(localDebug) print("processing incoming: ${newEvent.eventData.id}");
if(localDebug) print("In processIncomingEvent: processing incoming: ${newEvent.eventData.id}");
if( newEvent.eventData.kind == 1 && newEvent.eventData.content.compareTo("Hello Nostr! :)") == 0 && newEvent.eventData.id.substring(0,3).compareTo("000") == 0) {
continue; // spam prevention
}
if( allChildEventsMap.containsKey(newEvent.eventData.id)) {// don't process if the event is already present in the map
continue;
// don't process if the event is already present in the map and is not dummy
if( allChildEventsMap.containsKey(newEvent.eventData.id)) {
Tree? prev = allChildEventsMap[newEvent.eventData.id];
if( prev != null) {
if( prev.event.eventData.pubkey != gDummyAccountPubkey) {
if(localDebug) print("In processIncomingEvent: already exists ");
continue;
}
}
}
//ignore bots
@@ -1489,7 +1493,12 @@ class Store {
}
if( newEvent.eventData.kind == 0) {
processKind0Event(newEvent);
int retval = processKind0Event(newEvent);
if( retval == 2) {
// entry was modified, so delete old event
// TODO
}
continue;
}
// only kind 0, 1, 3, 4, 5( delete), 7, 40, 42, 140, 142 events are added to map-store, return otherwise
@@ -1514,7 +1523,7 @@ class Store {
if(localDebug) print(" adding to allChildEventsMap ${newEvent.eventData.id}");
allChildEventsMap[newEvent.eventData.id] = Tree(newEvent, [], this);
} else {
if(localDebug) print(" already in allChildEventsMap ${newEvent.eventData.id}");
if(localDebug) print(" inserted in replaceAndInsertInMap as allChildEventsMap ${newEvent.eventData.id}");
}
@@ -1533,6 +1542,7 @@ class Store {
switch(newTree.event.eventData.kind) {
case 1:
if(localDebug) print(" kind 1");
// only kind 1 events are added to the overall tree structure
String parentId = newTree.event.eventData.getParent(allChildEventsMap);
if( parentId == "") {
@@ -1559,6 +1569,12 @@ class Store {
if( allChildEventsMap.containsKey(parentId)) {
if(localDebug) print(" added to parent $parentId");
allChildEventsMap[parentId]?.children.add(newTree);
if(localDebug) print(" Got a child of $parentId. where new event id = ${newTree.event.eventData.id}");
if( allChildEventsMap[parentId]?.event.eventData.pubkey == gDummyAccountPubkey) {
if(localDebug) print(" parent is dummy");
}
topPosts.removeWhere((tree) => tree.event.eventData.id == newTree.event.eventData.id); // remove from top posts if it was there
} else {
@@ -1567,11 +1583,25 @@ class Store {
Event dummy = Event("","", EventData(parentId, gDummyAccountPubkey, newTree.event.eventData.createdAt, 1, "Missing event (not yet found on any relay)", [], [], [], [[]], {}), [""], "[json]");
Tree dummyTopNode = Tree.withoutStore(dummy, []);
dummyTopNode.children.add(newTree);
topPosts.add(dummyTopNode);
bool alreadyAdded = false;
for ( int i = 0; i < topPosts.length; i++) {
if( topPosts[i].event.eventData.id == newTree.event.eventData.id) {
topPosts[i] = dummyTopNode;
alreadyAdded = true;
break;
}
}
if( !alreadyAdded) {
topPosts.add(dummyTopNode);
}
allChildEventsMap.addAll({parentId: dummyTopNode});
// add it to list to fetch it from relays
if( parentId.length == 64) {
dummyEventIds.add(parentId);
if(localDebug) print(" parent added to dummyEventIds list.");
}
}
}
@@ -1643,8 +1673,8 @@ class Store {
int totalTreeSize = 0;
for (var element in topPosts) {totalTreeSize += element.count();}
if(gDebug > 0) print("In end of insertEvents: allChildEventsMap size = ${allChildEventsMap.length}; mainTree count = $totalTreeSize");
if(gDebug > 0) print("Returning ${newEventIdsSet.length} new notification-type events, which are ${newEventIdsSet.length < 10 ? newEventIdsSet: " <had more than 10 elements>"} ");
if(gDebug > 0 || localDebug) print("In end of insertEvents: allChildEventsMap size = ${allChildEventsMap.length}; mainTree count = $totalTreeSize");
if(gDebug > 0 || localDebug) print("Returning ${newEventIdsSet.length} new notification-type events, which are ${newEventIdsSet.length < 10 ? newEventIdsSet: " <had more than 10 elements>"} ");
return newEventIdsSet;
} // end processIncomingEvent()
@@ -1722,6 +1752,10 @@ class Store {
// remove duplicate top trees
Set ids = {};
topNotificationTree.retainWhere((t) => ids.add(t.event.eventData.id));
// remove entries older than given days
Set ids2 = {};
topNotificationTree.retainWhere((t) => ids2.add(t.event.eventData.createdAt > epochAppStartedAt - gDontHighlightEventsOlderThan * 84600));
Store.reCalculateMarkerStr();
@@ -1790,9 +1824,7 @@ class Store {
List<int> ret = [0,0,0];
//print("in printStoreTrees");
for( int i = 0; i < topPosts.length; i++) {
//print("i = $i");
// continue if this children isn't going to get printed anyway; selector is only called for top most tree
if( treeSelector(topPosts[i]) == false) {
continue;
@@ -1802,7 +1834,6 @@ class Store {
int newestChildTime = topPosts[i].getMostRecentTime(0);
DateTime dTime = DateTime.fromMillisecondsSinceEpoch(newestChildTime *1000);
if( dTime.compareTo(newerThan) < 0) {
//print("continue");
continue;
}
@@ -1829,8 +1860,6 @@ class Store {
print("\nTotal threads printed: ${ret[0]} for last $strTime.");
}
//print("in node print all: ret = $ret");
return ret;
}
@@ -2223,13 +2252,27 @@ class Store {
}
int oldEventCounter = 0;
// returns true if the given event should be saved in file
bool isRelevantForFileSave(Tree tree) {
bool isRelevantForFileSave(Tree tree, int cutoffTime) {
if( tree.event.userRelevant == true) {
return true;
}
// threads where the user and follows have involved themselves are returnes as true ( relevant)
// cutoff time is applicable only when this flag is set
if( gDontSaveBeforeDays != -1) {
// don't delete kind 0, which are meta data
if( tree.event.eventData.kind != 0 ) {
// delete other elements older than given value
if( tree.event.eventData.createdAt < cutoffTime) {
oldEventCounter++;
print("not saving old event # $oldEventCounter for event date ${getPrintableDate(tree.event.eventData.createdAt)}");
return false;
}
}
}
// threads where the user and follows have involved themselves are returned as true ( relevant)
if( tree.treeSelectorUserPostAndLike(gFollowList.union(gDefaultFollows).union({userPublicKey}), enableNotifications: false)
|| tree.treeSelectorDMtoFromUser({userPublicKey}, enableNotifications: false)
|| tree.treeSelectorUserReplies(gFollowList)
@@ -2255,20 +2298,35 @@ class Store {
gFollowList = getFollows(userPublicKey);
}
List<String> listEventIdsNotSaved = [];
if( gDebug > 0) print("opening $filename to write to.");
try {
final File file = File(filename);
if( gOverWriteFile) {
// if we are overwriting the file, then make it empty
await file.writeAsString("", mode: FileMode.write).then( (file) => file);
print("Created output file \"$filename\".");
} else {
print("Going to append to file \"$filename\".");
}
int eventCounter = 0;
String nLinesStr = "";
int countPosts = 0;
int cutoffTime = 0;
if( gDontSaveBeforeDays != -1) {
cutoffTime= epochAppStartedAt - gDontSaveBeforeDays * SECONDS_PER_DAY;
print("while saving, cutoff time: ${getPrintableDate(cutoffTime)}");
}
const int numLinesTogether = 100; // number of lines to write in one write call
int linesWritten = 0;
Map<int, int> mapNumEventsOfKind = {};
for( var tree in allChildEventsMap.values) {
if( tree.event.eventData.isDeleted // dont write those deleted
@@ -2283,7 +2341,18 @@ class Store {
}
}
if( !isRelevantForFileSave(tree)) {
if( !isRelevantForFileSave(tree, cutoffTime)) {
/*
print("not saving: ${tree.event.eventData.id}");
listEventIdsNotSaved.add(tree.event.eventData.id);
listEventIdsNotSaved.add(tree.event.originalJson);
listEventIdsNotSaved.add("------------------------------------------");
if( gFollowList.contains(tree.event.eventData.pubkey)) {
print(" Event is by a follow that's ${tree.event.eventData.pubkey}");
}
*/
continue;
}
@@ -2291,9 +2360,13 @@ class Store {
String line = "$temp\n";
nLinesStr += line;
eventCounter++;
if( tree.event.eventData.kind == 1) {
countPosts++;
}
mapNumEventsOfKind.update( tree.event.eventData.kind ,
(value) => ++value,
ifAbsent: () => 1
);
//if( temp.length < 10) print('len < 10');
if( eventCounter % numLinesTogether == 0) {
await file.writeAsString(nLinesStr, mode: FileMode.append).then( (file) => file);
@@ -2309,11 +2382,25 @@ class Store {
}
if(gDebug > 0) log.info("finished writing eventCounter = $eventCounter.");
print("Appended $eventCounter new events to file \"$gEventsFilename\" of which $countPosts are posts.");
print("Appended $eventCounter new events to file \"$gEventsFilename\". Their breakdown according to kind of event is as follows:\n\n Kind <Number of events>");
mapNumEventsOfKind.forEach((key, value) => print(" $key $value"));
print("");
} on Exception catch (e) {
print("Could not open file $filename.");
if( gDebug > 0) print("Could not open file: $e");
}
// create file where we note down events not saved, to help with debugging
if( gDebug > 0) {
print("create file where we note down events not saved.");
final File notSavedFile = File("not_saved.txt");
await notSavedFile.writeAsString("", mode: FileMode.write).then( (file) => file);
// write not saved event to file
await notSavedFile.writeAsString(listEventIdsNotSaved.join("\n"), mode: FileMode.append).then( (file) => file);
}
return;
}
@@ -2424,7 +2511,7 @@ class Store {
strTags += '["e","$latestEventId","$relay","reply"]';
}
print("Debug: Final tag string: |$strTags|\n");
//print("Debug: Final tag string: |$strTags|\n");
return strTags;
}
@@ -2815,6 +2902,27 @@ int ascendingTimeTree(Tree a, Tree b) {
return 1;
}
// sorter function that sorts according to participation by Web of Trust
// Mutual Follows 3 points
// Follows 2 points
// Well known 1
// people followed by 3+ follows: 1
int sortTreeWotScore(Tree a, Tree b) {
int aMostRecent = a.getMostRecentTime(0);
int bMostRecent = b.getMostRecentTime(0);
if(aMostRecent < bMostRecent) {
return -1;
} else {
if( aMostRecent == bMostRecent) {
return 0;
} else {
return 1;
}
}
}
// sorter function that looks at the latest event in the whole tree including the/its children
int sortTreeNewestReply(Tree a, Tree b) {
int aMostRecent = a.getMostRecentTime(0);
@@ -2876,9 +2984,15 @@ Store getTree(Set<Event> events) {
events.retainWhere((event) => ids.add(event.eventData.id));
// process kind 0 events about metadata
for (var event in events) {
/* for (var event in events) {
processKind0Event(event);
}
*/
ids.clear();
print("before kind 0 : ${events.length}");
events.retainWhere((event) => processKind0Event(event) != 3 );
print("after kind 0 : ${events.length}");
// process kind 3 events which is contact list. Update global info about the user (with meta data)
for (var event in events) {

View File

@@ -18,11 +18,12 @@ Tree exampleTree = Tree.withoutStore(exampleEvent, []);
//bool skipTest = true;
Relays relays = Relays({}, {}, {});
//Relays relays = Relays({}, {}, {});
void main() async {
relays = Relays({}, {}, {});
gListRelayUrls = {};
//gDebug = 1;
test('invalid_relay', () async {
@@ -43,6 +44,9 @@ void main() async {
//store.printStoreTrees(1, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all);
});
test('createNodeTree_ordered', () {
Event exampleEvent1 = Event.fromJson('["EVENT","latest",{"id":"167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de","pubkey":"137d948a0eee45e6cd113faaad934fcf17a97de2236c655b70650d4252daa9d3","created_at":1659722388,"kind":1,"tags":[],"content":"nostr is not federated is it? this is like a global feed of all nostr freaks?","sig":"6db0b287015d9529dfbacef91561cb4e32afd6968edd8454867b8482bde01452e17b6f3de69bffcb2d9deba2a52d3c9ff82e04f7b18eb32428daf7eab5fd27c5"}]', "");
@@ -60,6 +64,81 @@ void main() async {
test('createNodeTree_only2_1', () {
// ▄────────────
// █ 137d9: Non |id: 167063, 11:29 PM Aug 5 2
//
// 137d9: event2 reply 1 to top 1 |id: f3a267, 11:31 PM Aug 5 2
//
// 137d9: event3 reply 2 to reply 1 |id: dfc576, 11:32 PM Aug 5 1
//
// █
// ────────────▀
Event exampleEvent2 = Event.fromJson(
"""["EVENT","latest",{"id":"f3a267ecbb631012da618de620bc1fe265f6429f412359bf02330b437cf88e67","pubkey":"137d948a0eee45e6cd113faaad934fcf17a97de2236c655b70650d4252daa9d3",
"created_at":1659722463,
"kind":1,
"tags":[["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", "", "root"]],
"content":"event2 reply 1 to top 1",
"sig":"9f68031687214a24862226f291e3baadd956dc14ba9c5c552f8c881a40aacd34feda667ef4e4b09711cd43950eec2d272d5b11bd7636de5f457f38f31eaff398"}]""", "");
Event exampleEvent3 = Event.fromJson(
"""["EVENT","latest",{"id":"dfc5765da281c0ad99cb8693fc98c87f0f86ad56042a414f06f19d41c1315fc3","pubkey":"137d948a0eee45e6cd113faaad934fcf17a97de2236c655b70650d4252daa9d3",
"created_at":1659722537,"kind":1,
"tags":[
["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", "", "root"],
["e","f3a267ecbb631012da618de620bc1fe265f6429f412359bf02330b437cf88e67", "", "reply"]
],
"content":"event3 reply 2 to reply 1",
"sig":"d4fdc288e3cb95fc5ab46177fc0982d2aaa3b028eef6649f8200500da9c2e9a16c7a0462638afef7635bfea3094ec10901de759a48e362b60cb08f7e6585e02f"}]""", "");
Set<Event> setEmpty = { };
Store node = Store.fromEvents(setEmpty);
expect(node.topPosts.length, 0);
print(" createNodeTree_unordered_2212 : added none:");
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
print("calling processIncomingEvents with event3");
Set<Event> newEvents3 = {exampleEvent3};
node.processIncomingEvent(newEvents3);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].children.length, 1);
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
print("calling processIncomingEvents with event2");
Set<Event> newEvents2 = { exampleEvent2 };
node.processIncomingEvent(newEvents2);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].children.length, 1);
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
// repeat event
print("AGAIN calling processIncomingEvents with event2");
node.processIncomingEvent(newEvents2);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].children.length, 1);
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
});
test('createNodeTree_unordered_reactions', () {
@@ -95,7 +174,7 @@ void main() async {
"tags":[["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", ""]],
"sig":"9f68031687214a24862226f291e3baadd956dc14ba9c5c552f8c881a40aacd34feda667ef4e4b09711cd43950eec2d272d5b11bd7636de5f457f38f31eaff398"}]""", "");
Event exampleEvent3 = Event.fromJson(
Event reactionEvent3 = Event.fromJson(
"""["EVENT","latest",{"id":"dfc5765da281c0ad99cb8693fc98c87f0f86ad56042a414f06f19d41c1315fc3","pubkey":"137d948a0eee45e6cd113faaad934fcf17a97de2236c655b70650d4252daa9d3",
"created_at":1659722537,
"kind":7,
@@ -118,7 +197,7 @@ void main() async {
Set<Event> listEvents = { exampleEvent3};
Set<Event> listEvents = { reactionEvent3};
Store node = Store.fromEvents(listEvents);
@@ -145,13 +224,13 @@ void main() async {
// ▄────────────
// █ 137d9: top 1 |id: 167063, 11:29 PM Aug 5 2
// █ 137d9: event1 top 1 |id: 167063, 11:29 PM Aug 5 2
//
// 137d9: reply 1 to top 1 |id: f3a267, 11:31 PM Aug 5 2
// 137d9: event2 reply 1 to top 1 |id: f3a267, 11:31 PM Aug 5 2
//
// 137d9: reply 2 to reply 1 |id: dfc576, 11:32 PM Aug 5 1
// 137d9: event3 reply 2 to reply 1 |id: dfc576, 11:32 PM Aug 5 1
//
// 137d9: reply 2 to top 1 |id: afc576, 11:32 PM Aug 5 2
// 137d9: event4 reply 2 to top 1 |id: afc576, 11:32 PM Aug 5 2
// █
// ────────────▀
@@ -162,7 +241,7 @@ void main() async {
"created_at":1659722388,
"kind":1,
"tags":[],
"content":"top 1",
"content":"event1 top 1",
"sig":"6db0b287015d9529dfbacef91561cb4e32afd6968edd8454867b8482bde01452e17b6f3de69bffcb2d9deba2a52d3c9ff82e04f7b18eb32428daf7eab5fd27c5"}]"""
, "");
@@ -172,7 +251,7 @@ void main() async {
"created_at":1659722463,
"kind":1,
"tags":[["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", "", "root"]],
"content":"reply 1 to top 1",
"content":"event2 reply 1 to top 1",
"sig":"9f68031687214a24862226f291e3baadd956dc14ba9c5c552f8c881a40aacd34feda667ef4e4b09711cd43950eec2d272d5b11bd7636de5f457f38f31eaff398"}]""", "");
Event exampleEvent3 = Event.fromJson(
@@ -182,7 +261,7 @@ void main() async {
["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", "", "root"],
["e","f3a267ecbb631012da618de620bc1fe265f6429f412359bf02330b437cf88e67", "", "reply"]
],
"content":"reply 2 to reply 1",
"content":"event3 reply 2 to reply 1",
"sig":"d4fdc288e3cb95fc5ab46177fc0982d2aaa3b028eef6649f8200500da9c2e9a16c7a0462638afef7635bfea3094ec10901de759a48e362b60cb08f7e6585e02f"}]""", "");
@@ -192,26 +271,51 @@ void main() async {
"tags":[
["e","167063f491c41b7b8f79bc74f318e8a8b0a802bf8364b8bb7d19c887d59ec5de", "", "root"]
],
"content":"reply 2 to top 1",
"content":"event4 reply 2 to top 1",
"sig":"d4fdc288e3cb95fc5ab46177fc0982d2aaa3b028eef6649f8200500da9c2e9a16c7a0462638afef7635bfea3094ec10901de759a48e362b60cb08f7e6585e02f"}]""", "");
Set<Event> listEvents = { exampleEvent3};
Set<Event> setEvent3 = { exampleEvent3};
Store node = Store.fromEvents(listEvents);
Store node = Store.fromEvents(setEvent3);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].children.length, 1);
expect ( node.topPosts[0].children[0].children.length, 0);
//print(" createNodeTree_unordered_2212 : added only one, only one should get printed:");
//node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
Set<Event> newEvents = {exampleEvent2, exampleEvent1 , exampleEvent4};
node.processIncomingEvent(newEvents);
print(" createNodeTree_unordered_2212 : added only one, only one should get printed:");
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
print("calling processIncomingEvents with event4");
Set<Event> newEvents4 = {exampleEvent4};
node.processIncomingEvent(newEvents4);
expect(node.topPosts.length, 2);
expect ( node.topPosts[0].children.length, 1);
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
print("calling processIncomingEvents with event1");
Set<Event> newEvents1 = { exampleEvent1 };
node.processIncomingEvent(newEvents1);
expect(node.topPosts.length, 2);
expect ( node.topPosts[0].children.length, 1);
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
print("calling processIncomingEvents with 2");
Set<Event> newEvents2 = { exampleEvent2};
node.processIncomingEvent(newEvents2);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].event.eventData.pubkey, "137d948a0eee45e6cd113faaad934fcf17a97de2236c655b70650d4252daa9d3");
expect ( node.topPosts[0].children.length, 2);
//node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("node.count() = ${node.count()}");
});
@@ -280,15 +384,15 @@ void main() async {
expect ( node.topPosts[0].children.length, 1);
expect ( node.topPosts[0].children[0].children.length, 1);
//print("ended test createNodeTree_unordered1");
//node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
print("ended test createNodeTree_unordered1");
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
Set<Event> newEvents = {exampleEvent1 , exampleEvent4};
node.processIncomingEvent(newEvents);
expect(node.topPosts.length, 1);
expect ( node.topPosts[0].children.length, 2);
//node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
node.printStoreTrees(0, DateTime.now().subtract(Duration(days:2000)), selectorTrees_all); // will test for ~1000 days
});
@@ -573,6 +677,29 @@ String expectedResult =
});
test('getParent', () {
// "tags":[
// ["e","ed05e1ba26c9d3684a4f0f9443dbb45cccb4ba20f2b7b05a6ac1aee0a8324baf","","root"],
// ["e","ebb01cc8788dadfc4ce621999ec3b332ff712937bacc1be99a20bf01ed29d2b0","","reply"],
// ["p","20651ab8c2fb1febca56b80deba14630af452bdce64fe8f04a9f5f67e4a3c1cc"]
Event exampleEvent_31e_daniel = Event.fromJson('["EVENT","latest_live_all",{"content":"Hard to tell the difference these days, ngl.","created_at":1755366333,"id":"31e2ebe59b3fd691450055cbbae2545e41dd986032d564e49a56125da1ddea96","kind":1,"pubkey":"ee6ea13ab9fe5c4a68eaf9b1a34fe014a66b40117c50ee2a614f4cda959b6e74","sig":"7af6c0d86ffa4ccf96c9ffd9c7fe9d87ae77c7be062bbd05079e666129aafd6802023ebf6167291419b06a574e09167694c6fa45bfa585c61f22a122f74856a5","tags":[["e","ed05e1ba26c9d3684a4f0f9443dbb45cccb4ba20f2b7b05a6ac1aee0a8324baf","","root"],["e","ebb01cc8788dadfc4ce621999ec3b332ff712937bacc1be99a20bf01ed29d2b0","","reply"],["p","20651ab8c2fb1febca56b80deba14630af452bdce64fe8f04a9f5f67e4a3c1cc"]]}]', "");
Store store = Store.fromEvents({exampleEvent_31e_daniel});
//Tree tree = Tree.withoutStore(exampleEvent_31e_daniel,[]);
String parentId = exampleEvent_31e_daniel.eventData.getParent({});
print("parentId = $parentId");
expect(parentId, "ebb01cc8788dadfc4ce621999ec3b332ff712937bacc1be99a20bf01ed29d2b0");
});
Future.delayed(Duration(milliseconds: 2000), () {
exit(0);
});