mirror of
https://github.com/vishalxl/nostr_console.git
synced 2025-09-26 16:46:15 +02:00
Added test cases. improved incoming processing of new events, so that doublets for same top thread don't happen
This commit is contained in:
@@ -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() {
|
||||
|
@@ -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
78
lib/del.json
Normal 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
|
||||
}
|
@@ -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
|
||||
*/
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
180
lib/tree_ds.dart
180
lib/tree_ds.dart
@@ -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) {
|
||||
|
@@ -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);
|
||||
});
|
||||
|
Reference in New Issue
Block a user