mirror of
https://github.com/vishalxl/nostr_console.git
synced 2025-06-03 03:30:58 +02:00
added support for reading and writing events from/to a file with command line argument. Display and other improvements: printed user logged in for information.
This commit is contained in:
parent
0d09e43717
commit
d4cc9fbcae
21
README.md
21
README.md
@ -19,6 +19,8 @@ usage: dart run bin/nostr_console.dart [OPTIONS]
|
|||||||
--days <N as num> The latest number of days for which events are shown. Default is 1. Same as -d
|
--days <N as num> The latest number of days for which events are shown. Default is 1. Same as -d
|
||||||
--request <REQ string> This request is sent verbatim to the default relay. It can be used to recieve all events
|
--request <REQ string> This request is sent verbatim to the default relay. It can be used to recieve all events
|
||||||
from a relay. If not provided, then events for default or given user are shown. Same as -q
|
from a relay. If not provided, then events for default or given user are shown. Same as -q
|
||||||
|
--file <filename> Read from given file, if it is present, and at the end of the program execution, write
|
||||||
|
to it all the events (including the ones read, and any new received). Same as -f
|
||||||
UI Options
|
UI Options
|
||||||
--align <left> When "left" is given as option to this argument, then the text is aligned to left. By default
|
--align <left> When "left" is given as option to this argument, then the text is aligned to left. By default
|
||||||
the posts or text is aligned to the center of the terminal. Same as -a
|
the posts or text is aligned to the center of the terminal. Same as -a
|
||||||
@ -33,20 +35,27 @@ usage: dart run bin/nostr_console.dart [OPTIONS]
|
|||||||
To get ALL the latest messages for last 3 days (on linux which allows backtick execution):
|
To get ALL the latest messages for last 3 days (on linux which allows backtick execution):
|
||||||
|
|
||||||
```
|
```
|
||||||
dart run bin/nostr_console.dart --request=`echo "[\"REQ\",\"l\",{\"since\":$(date -d '-3 day' +%s)}]"`
|
nostr_console.exe --request=`echo "[\"REQ\",\"l\",{\"since\":$(date -d '-3 day' +%s)}]"`
|
||||||
```
|
```
|
||||||
|
|
||||||
To get the latest messages for user with private key K ( that is also used to sign posted/sent messages):
|
To get the latest messages for user with private key K ( that is also used to sign posted/sent messages):
|
||||||
|
|
||||||
```
|
```
|
||||||
dart run bin/nostr_console.dart --prikey=K
|
nostr_console.exe --prikey=K
|
||||||
```
|
```
|
||||||
|
|
||||||
To get the latest messages for user with private key K for last 4 days ( default is 1) from relay R:
|
To get the latest messages for user with private key K for last 4 days ( default is 1) from relay R:
|
||||||
|
|
||||||
```
|
```
|
||||||
dart run bin/nostr_console.dart --prikey=K --relay=R --days=4
|
nostr_console.exe --prikey=K --relay=R --days=4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To write events to a file ( and later read from it too), for any given private key K:
|
||||||
|
|
||||||
|
```
|
||||||
|
nostr_console.exe --file=eventsFile.txt --prikey=K
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# Screenshots
|
# Screenshots
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ const String helpArg = "help";
|
|||||||
const String alignArg = "align"; // can be "left"
|
const String alignArg = "align"; // can be "left"
|
||||||
const String widthArg = "width";
|
const String widthArg = "width";
|
||||||
const String maxDepthArg = "maxdepth";
|
const String maxDepthArg = "maxdepth";
|
||||||
|
const String eventFileArg = "file";
|
||||||
|
|
||||||
void printUsage() {
|
void printUsage() {
|
||||||
String usage = """$exename version $version
|
String usage = """$exename version $version
|
||||||
@ -34,6 +35,8 @@ usage: $exename [OPTIONS]
|
|||||||
--days <N as num> The latest number of days for which events are shown. Default is 1. Same as -d
|
--days <N as num> The latest number of days for which events are shown. Default is 1. Same as -d
|
||||||
--request <REQ string> This request is sent verbatim to the default relay. It can be used to recieve all events
|
--request <REQ string> This request is sent verbatim to the default relay. It can be used to recieve all events
|
||||||
from a relay. If not provided, then events for default or given user are shown. Same as -q
|
from a relay. If not provided, then events for default or given user are shown. Same as -q
|
||||||
|
--file <filename> Read from given file, if it is present, and at the end of the program execution, write
|
||||||
|
to it all the events (including the ones read, and any new received). Same as -f
|
||||||
UI Options
|
UI Options
|
||||||
--align <left> When "left" is given as option to this argument, then the text is aligned to left. By default
|
--align <left> When "left" is given as option to this argument, then the text is aligned to left. By default
|
||||||
the posts or text is aligned to the center of the terminal. Same as -a
|
the posts or text is aligned to the center of the terminal. Same as -a
|
||||||
@ -52,7 +55,8 @@ Future<void> main(List<String> arguments) async {
|
|||||||
final parser = ArgParser()..addOption(requestArg, abbr: 'q') ..addOption(pubkeyArg, abbr:"p")..addOption(prikeyArg, abbr:"k")
|
final parser = ArgParser()..addOption(requestArg, abbr: 'q') ..addOption(pubkeyArg, abbr:"p")..addOption(prikeyArg, abbr:"k")
|
||||||
..addOption(lastdaysArg, abbr:"d") ..addOption(relayArg, abbr:"r")
|
..addOption(lastdaysArg, abbr:"d") ..addOption(relayArg, abbr:"r")
|
||||||
..addFlag(helpArg, abbr:"h", defaultsTo: false)..addOption(alignArg, abbr:"a")
|
..addFlag(helpArg, abbr:"h", defaultsTo: false)..addOption(alignArg, abbr:"a")
|
||||||
..addOption(widthArg, abbr:"w")..addOption(maxDepthArg, abbr:"m");
|
..addOption(widthArg, abbr:"w")..addOption(maxDepthArg, abbr:"m")
|
||||||
|
..addOption(eventFileArg, abbr:"f");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ArgResults argResults = parser.parse(arguments);
|
ArgResults argResults = parser.parse(arguments);
|
||||||
@ -131,6 +135,14 @@ Future<void> main(List<String> arguments) async {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( argResults[eventFileArg] != null) {
|
||||||
|
gEventsFilename = argResults[eventFileArg];
|
||||||
|
if( gEventsFilename != "") {
|
||||||
|
print("Going to use file to read from and store events: $gEventsFilename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} on FormatException catch (e) {
|
} on FormatException catch (e) {
|
||||||
print(e.message);
|
print(e.message);
|
||||||
return;
|
return;
|
||||||
@ -139,26 +151,37 @@ Future<void> main(List<String> arguments) async {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int numFileEvents = 0, numUserEvents = 0, numFeedEvents = 0, numOtherEvents = 0;
|
||||||
|
if( gEventsFilename != "") {
|
||||||
|
stdout.write('Reading events from the given file.......');
|
||||||
|
List<Event> eventsFromFile = readEventsFromFile(gEventsFilename);
|
||||||
|
|
||||||
|
setRelaysIntialEvents(eventsFromFile);
|
||||||
|
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numFileEvents++: numFileEvents;});
|
||||||
|
print("read $numFileEvents posts from file \"$gEventsFilename\"");
|
||||||
|
//numFileEvents = getRecievedEvents().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// the default in case no arguments are given is:
|
// the default in case no arguments are given is:
|
||||||
// get a user's events, then from its type 3 event, gets events of its follows,
|
// get a user's events, then from its type 3 event, gets events of its follows,
|
||||||
// then get the events of user-id's mentioned in p-tags of received events
|
// then get the events of user-id's mentioned in p-tags of received events
|
||||||
// then display them all
|
// then display them all
|
||||||
getUserEvents(defaultServerUrl, userPublicKey, 1000, 0);
|
getUserEvents(defaultServerUrl, userPublicKey, 1000, 0);
|
||||||
|
|
||||||
int numUserEvents = 0, numFeedEvents = 0, numOtherEvents = 0;
|
|
||||||
|
|
||||||
const int numWaitSeconds = 2500;
|
const int numWaitSeconds = 2500;
|
||||||
stdout.write('Waiting for user events to come in.....');
|
stdout.write('Waiting for user posts to come in.....');
|
||||||
Future.delayed(const Duration(milliseconds: numWaitSeconds), () {
|
Future.delayed(const Duration(milliseconds: numWaitSeconds), () {
|
||||||
// count user events
|
// count user events
|
||||||
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numUserEvents++: numUserEvents;});
|
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numUserEvents++: numUserEvents;});
|
||||||
stdout.write("...received ${getRecievedEvents().length} events made by the user\n");
|
numUserEvents -= numFileEvents;
|
||||||
|
stdout.write("...received $numUserEvents posts made by the user\n");
|
||||||
|
|
||||||
// get the latest kind 3 event for the user, which lists his 'follows' list
|
// get the latest kind 3 event for the user, which lists his 'follows' list
|
||||||
int latestContactsTime = 0, latestContactIndex = -1;
|
int latestContactsTime = 0, latestContactIndex = -1;
|
||||||
for( int i = 0; i < getRecievedEvents().length; i++) {
|
for( int i = 0; i < getRecievedEvents().length; i++) {
|
||||||
var e = getRecievedEvents()[i];
|
var e = getRecievedEvents()[i];
|
||||||
if( e.eventData.kind == 3 && latestContactsTime < e.eventData.createdAt) {
|
if( e.eventData.pubkey == userPublicKey && e.eventData.kind == 3 && latestContactsTime < e.eventData.createdAt) {
|
||||||
latestContactIndex = i;
|
latestContactIndex = i;
|
||||||
latestContactsTime = e.eventData.createdAt;
|
latestContactsTime = e.eventData.createdAt;
|
||||||
}
|
}
|
||||||
@ -170,24 +193,24 @@ Future<void> main(List<String> arguments) async {
|
|||||||
contactList = getContactFeed(getRecievedEvents()[latestContactIndex].eventData.contactList, 300);
|
contactList = getContactFeed(getRecievedEvents()[latestContactIndex].eventData.contactList, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write('Waiting for feed to come in...............');
|
stdout.write('Waiting for feed to come in..............');
|
||||||
Future.delayed(const Duration(milliseconds: numWaitSeconds * 1), () {
|
Future.delayed(const Duration(milliseconds: numWaitSeconds * 1), () {
|
||||||
|
|
||||||
// count feed events
|
// count feed events
|
||||||
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numFeedEvents++: numFeedEvents;});
|
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numFeedEvents++: numFeedEvents;});
|
||||||
numFeedEvents = numFeedEvents - numUserEvents;
|
numFeedEvents = numFeedEvents - numUserEvents - numFileEvents;
|
||||||
stdout.write("received $numFeedEvents events from the follows\n");
|
stdout.write("received $numFeedEvents posts from the follows\n");
|
||||||
|
|
||||||
// get mentioned ptags, and then get the events for those users
|
// get mentioned ptags, and then get the events for those users
|
||||||
List<String> pTags = getpTags(getRecievedEvents());
|
List<String> pTags = getpTags(getRecievedEvents());
|
||||||
getMultiUserEvents(defaultServerUrl, pTags, 300);
|
getMultiUserEvents(defaultServerUrl, pTags, 300);
|
||||||
|
|
||||||
stdout.write('Waiting for rest of events to come in.....');
|
stdout.write('Waiting for rest of posts to come in.....');
|
||||||
Future.delayed(const Duration(milliseconds: numWaitSeconds * 2), () {
|
Future.delayed(const Duration(milliseconds: numWaitSeconds * 2), () {
|
||||||
// count other events
|
// count other events
|
||||||
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numOtherEvents++: numOtherEvents;});
|
getRecievedEvents().forEach((element) { element.eventData.kind == 1? numOtherEvents++: numOtherEvents;});
|
||||||
numOtherEvents = numOtherEvents - numFeedEvents - numUserEvents;
|
numOtherEvents = numOtherEvents - numFeedEvents - numUserEvents - numFileEvents;
|
||||||
stdout.write("received $numOtherEvents other events\n");
|
stdout.write("received $numOtherEvents other posts\n");
|
||||||
|
|
||||||
// get all events in Tree form
|
// get all events in Tree form
|
||||||
Tree node = getTree(getRecievedEvents());
|
Tree node = getTree(getRecievedEvents());
|
||||||
|
@ -100,10 +100,8 @@ Future<void> mainMenuUi(Tree node, var contactList) async {
|
|||||||
// align the text again in case the window size has been changed
|
// align the text again in case the window size has been changed
|
||||||
if( gAlignment == "center") {
|
if( gAlignment == "center") {
|
||||||
try {
|
try {
|
||||||
// can be computed only after textWidth has been found
|
|
||||||
gNumLeftMarginSpaces = (stdout.terminalColumns - gTextWidth )~/2;
|
gNumLeftMarginSpaces = (stdout.terminalColumns - gTextWidth )~/2;
|
||||||
} on StdoutException catch (e) {
|
} on StdoutException catch (e) {
|
||||||
//print("Cannot find terminal size. Left aligning by default.");
|
|
||||||
gNumLeftMarginSpaces = 0;
|
gNumLeftMarginSpaces = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,7 +112,7 @@ Future<void> mainMenuUi(Tree node, var contactList) async {
|
|||||||
Future.delayed(const Duration(milliseconds: waitMilliSeconds), () {
|
Future.delayed(const Duration(milliseconds: waitMilliSeconds), () {
|
||||||
|
|
||||||
List<String> newEventsId = node.insertEvents(getRecievedEvents());
|
List<String> newEventsId = node.insertEvents(getRecievedEvents());
|
||||||
node.printNotifications(newEventsId);
|
node.printNotifications(newEventsId, getAuthorName(userPublicKey));
|
||||||
clearEvents();
|
clearEvents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,7 +181,9 @@ Future<void> mainMenuUi(Tree node, var contactList) async {
|
|||||||
print("\nFinished fetching feed for user $userPublicKey ($authorName), whose contact list has ${contactList.length} profiles.\n ");
|
print("\nFinished fetching feed for user $userPublicKey ($authorName), whose contact list has ${contactList.length} profiles.\n ");
|
||||||
contactList.forEach((x) => stdout.write("${getAuthorName(x)}, "));
|
contactList.forEach((x) => stdout.write("${getAuthorName(x)}, "));
|
||||||
stdout.write("\n");
|
stdout.write("\n");
|
||||||
//await node.writeEventsToFile("nostrConsoleEventsStore.txt");
|
if( gEventsFilename != "") {
|
||||||
|
await node.writeEventsToFile(gEventsFilename);
|
||||||
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
} // end while
|
} // end while
|
||||||
|
@ -18,6 +18,7 @@ int gTextWidth = gDefaultTextWidth; // is changed by --width option
|
|||||||
const int gSpacesPerDepth = 8; // constant
|
const int gSpacesPerDepth = 8; // constant
|
||||||
int gNumLeftMarginSpaces = 0; // this number is modified in main
|
int gNumLeftMarginSpaces = 0; // this number is modified in main
|
||||||
String gAlignment = "center"; // is modified in main if --align argument is given
|
String gAlignment = "center"; // is modified in main if --align argument is given
|
||||||
|
const int gapBetweenTopTrees = 1;
|
||||||
|
|
||||||
// after depth of maxDepthAllowed the thread is re-aligned to left by leftShiftThreadBy
|
// after depth of maxDepthAllowed the thread is re-aligned to left by leftShiftThreadBy
|
||||||
const int gMinimumDepthAllowed = 2;
|
const int gMinimumDepthAllowed = 2;
|
||||||
@ -55,6 +56,9 @@ List<String> gBots = [ "3b57518d02e6acfd5eb7198530b2e351e5a52278fb2499d14b66db2
|
|||||||
"f4161c88558700d23af18d8a6386eb7d7fed769048e1297811dcc34e86858fb2" // bitcoin_bot
|
"f4161c88558700d23af18d8a6386eb7d7fed769048e1297811dcc34e86858fb2" // bitcoin_bot
|
||||||
];
|
];
|
||||||
|
|
||||||
|
//const String gDefaultEventsFilename = "events_store_nostr.txt";
|
||||||
|
String gEventsFilename = ""; // is set in arguments, and if set, then file is read from and written to
|
||||||
|
|
||||||
int gDebug = 0;
|
int gDebug = 0;
|
||||||
|
|
||||||
void printDepth(int d) {
|
void printDepth(int d) {
|
||||||
@ -194,7 +198,8 @@ class EventData {
|
|||||||
|
|
||||||
return EventData(json['id'] as String, json['pubkey'] as String,
|
return EventData(json['id'] as String, json['pubkey'] as String,
|
||||||
json['created_at'] as int, json['kind'] as int,
|
json['created_at'] as int, json['kind'] as int,
|
||||||
json['content'] as String, eTagsRead, pTagsRead,
|
json['content'].trim() as String,
|
||||||
|
eTagsRead, pTagsRead,
|
||||||
contactList, tagsRead,
|
contactList, tagsRead,
|
||||||
{});
|
{});
|
||||||
}
|
}
|
||||||
@ -317,13 +322,17 @@ class Event {
|
|||||||
Event(this.event, this.id, this.eventData, this.seenOnRelays, this.originalJson);
|
Event(this.event, this.id, this.eventData, this.seenOnRelays, this.originalJson);
|
||||||
|
|
||||||
factory Event.fromJson(String d, String relay) {
|
factory Event.fromJson(String d, String relay) {
|
||||||
dynamic json = jsonDecode(d);
|
try {
|
||||||
if( json.length < 3) {
|
dynamic json = jsonDecode(d);
|
||||||
String e = "";
|
if( json.length < 3) {
|
||||||
e = json.length > 1? json[0]: "";
|
String e = "";
|
||||||
return Event(e,"",EventData("non","", 0, 0, "", [], [], [], [[]], {}), [relay], "[json]");
|
e = json.length > 1? json[0]: "";
|
||||||
|
return Event(e,"",EventData("non","", 0, 0, "", [], [], [], [[]], {}), [relay], "[json]");
|
||||||
|
}
|
||||||
|
return Event(json[0] as String, json[1] as String, EventData.fromJson(json[2]), [relay], d );
|
||||||
|
} on Exception catch(e) {
|
||||||
|
return Event("","",EventData("non","", 0, 0, "", [], [], [], [[]], {}), [relay], "[json]");
|
||||||
}
|
}
|
||||||
return Event(json[0] as String, json[1] as String, EventData.fromJson(json[2]), [relay], d );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void printEvent(int depth) {
|
void printEvent(int depth) {
|
||||||
@ -402,3 +411,34 @@ void printUserInfo(List<Event> events, String pub) {
|
|||||||
}
|
}
|
||||||
print("Number of user events for user ${getAuthorName(pub)} : $numUserEvents");
|
print("Number of user events for user ${getAuthorName(pub)} : $numUserEvents");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Event> readEventsFromFile(String filename) {
|
||||||
|
|
||||||
|
List<Event> events = [];
|
||||||
|
final File file = File(filename);
|
||||||
|
|
||||||
|
// sync
|
||||||
|
try {
|
||||||
|
List<String> lines = file.readAsLinesSync();
|
||||||
|
for( int i = 0; i < lines.length; i++ ) {
|
||||||
|
Event e = Event.fromJson(lines[i], "");
|
||||||
|
events.add(e);
|
||||||
|
}
|
||||||
|
} on Exception catch(err) {
|
||||||
|
print("Cannot open file $gEventsFilename");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
String strEvent = file.readAsStringSync();
|
||||||
|
//print(strEvent);
|
||||||
|
|
||||||
|
}
|
||||||
|
on Exception catch(e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
@ -267,3 +267,9 @@ List<Event> getRecievedEvents() {
|
|||||||
void clearEvents() {
|
void clearEvents() {
|
||||||
relays.rEvents = [];
|
relays.rEvents = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setRelaysIntialEvents(eventsFromFile) {
|
||||||
|
relays.rEvents = eventsFromFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
147
lib/tree_ds.dart
147
lib/tree_ds.dart
@ -8,6 +8,8 @@ class Tree {
|
|||||||
List<String> eventsWithoutParent;
|
List<String> eventsWithoutParent;
|
||||||
Tree(this.e, this.children, this.allChildEventsMap, this.eventsWithoutParent);
|
Tree(this.e, this.children, this.allChildEventsMap, this.eventsWithoutParent);
|
||||||
|
|
||||||
|
static const List<int> typesInEventMap = [0, 1, 3, 7]; // 0 meta, 1 post, 3 follows list, 7 reactions
|
||||||
|
|
||||||
// @method create top level Tree from events.
|
// @method create top level Tree from events.
|
||||||
// first create a map. then process each element in the map by adding it to its parent ( if its a child tree)
|
// first create a map. then process each element in the map by adding it to its parent ( if its a child tree)
|
||||||
factory Tree.fromEvents(List<Event> events) {
|
factory Tree.fromEvents(List<Event> events) {
|
||||||
@ -17,7 +19,12 @@ class Tree {
|
|||||||
|
|
||||||
// create a map from list of events, key is eventId and value is event itself
|
// create a map from list of events, key is eventId and value is event itself
|
||||||
Map<String, Tree> allChildEventsMap = {};
|
Map<String, Tree> allChildEventsMap = {};
|
||||||
events.forEach((event) { allChildEventsMap[event.eventData.id] = Tree(event, [], {}, []); });
|
events.forEach((event) {
|
||||||
|
// only add in map those kinds that are supported or supposed to be added ( 0 1 3 7)
|
||||||
|
if( typesInEventMap.contains(event.eventData.kind)) {
|
||||||
|
allChildEventsMap[event.eventData.id] = Tree(event, [], {}, []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// this will become the children of the main top node. These are events without parents, which are printed at top.
|
// this will become the children of the main top node. These are events without parents, which are printed at top.
|
||||||
List<Tree> topLevelTrees = [];
|
List<Tree> topLevelTrees = [];
|
||||||
@ -25,6 +32,7 @@ class Tree {
|
|||||||
List<String> tempWithoutParent = [];
|
List<String> tempWithoutParent = [];
|
||||||
allChildEventsMap.forEach((key, value) {
|
allChildEventsMap.forEach((key, value) {
|
||||||
|
|
||||||
|
// only posts areadded to this tree structure
|
||||||
if( value.e.eventData.kind != 1) {
|
if( value.e.eventData.kind != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -34,16 +42,20 @@ class Tree {
|
|||||||
//stdout.write("added to parent a child\n");
|
//stdout.write("added to parent a child\n");
|
||||||
String id = key;
|
String id = key;
|
||||||
String parentId = value.e.eventData.getParent();
|
String parentId = value.e.eventData.getParent();
|
||||||
if( allChildEventsMap[parentId]?.e.eventData.kind != 1) { // since parent can only be a kind 1 event
|
if( allChildEventsMap.containsKey(parentId)) {
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(allChildEventsMap.containsKey( parentId)) {
|
if(allChildEventsMap.containsKey( parentId)) {
|
||||||
allChildEventsMap[parentId]?.addChildNode(value); // in this if condition this will get called
|
if( allChildEventsMap[parentId]?.e.eventData.kind != 1) { // since parent can only be a kind 1 event
|
||||||
|
print("In fromEvents: got an event whose parent is not a type 1 post: $id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
allChildEventsMap[parentId]?.addChildNode(value); // in this if condition this will get called
|
||||||
} else {
|
} else {
|
||||||
// in case where the parent of the new event is not in the pool of all events,
|
// in case where the parent of the new event is not in the pool of all events,
|
||||||
// then we create a dummy event and put it at top ( or make this a top event?) TODO handle so that this can be replied to, and is fetched
|
// then we create a dummy event and put it at top ( or make this a top event?) TODO handle so that this can be replied to, and is fetched
|
||||||
Tree dummyTopNode = Tree(Event("","",EventData("Unk" ,gDummyAccountPubkey, value.e.eventData.createdAt , 0, "Unknown parent event", [], [], [], [[]], {}), [""], "[json]"), [], {}, []);
|
Tree dummyTopNode = Tree(Event("","",EventData("Unk" ,gDummyAccountPubkey, value.e.eventData.createdAt , 1, "Unknown parent event", [], [], [], [[]], {}), [""], "[json]"), [], {}, []);
|
||||||
dummyTopNode.addChildNode(value);
|
dummyTopNode.addChildNode(value);
|
||||||
tempWithoutParent.add(value.e.eventData.id);
|
tempWithoutParent.add(value.e.eventData.id);
|
||||||
|
|
||||||
@ -56,13 +68,11 @@ class Tree {
|
|||||||
|
|
||||||
// add parent trees as top level child trees of this tree
|
// add parent trees as top level child trees of this tree
|
||||||
for( var value in allChildEventsMap.values) {
|
for( var value in allChildEventsMap.values) {
|
||||||
if( value.e.eventData.kind == 1 && value.e.eventData.eTagsRest.isEmpty) { // if its a parent
|
if( value.e.eventData.kind == 1 && value.e.eventData.eTagsRest.isEmpty) { // only posts which are parents
|
||||||
topLevelTrees.add(value);
|
topLevelTrees.add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add tempWithoutParent to topLevelTrees too
|
|
||||||
|
|
||||||
if(gDebug != 0) print("number of events without parent in fromEvents = ${tempWithoutParent.length}");
|
if(gDebug != 0) print("number of events without parent in fromEvents = ${tempWithoutParent.length}");
|
||||||
return Tree( events[0], topLevelTrees, allChildEventsMap, tempWithoutParent); // TODO remove events[0]
|
return Tree( events[0], topLevelTrees, allChildEventsMap, tempWithoutParent); // TODO remove events[0]
|
||||||
} // end fromEvents()
|
} // end fromEvents()
|
||||||
@ -78,7 +88,7 @@ class Tree {
|
|||||||
newEvents.forEach((newEvent) {
|
newEvents.forEach((newEvent) {
|
||||||
// don't process if the event is already present in the map
|
// don't process if the event is already present in the map
|
||||||
// this condition also excludes any duplicate events sent as newEvents
|
// this condition also excludes any duplicate events sent as newEvents
|
||||||
if( allChildEventsMap[newEvent.eventData.id] != null) {
|
if( allChildEventsMap.containsKey(newEvent.eventData.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,30 +97,28 @@ class Tree {
|
|||||||
String reactedTo = processReaction(newEvent);
|
String reactedTo = processReaction(newEvent);
|
||||||
|
|
||||||
if( reactedTo != "") {
|
if( reactedTo != "") {
|
||||||
newEventsId.add(newEvent.eventData.id);
|
newEventsId.add(newEvent.eventData.id); // add here to process/give notification about this new reaction
|
||||||
if(gDebug > 0) print("got a new reaction by: ${newEvent.eventData.id} to $reactedTo");
|
if(gDebug > 0) print("got a new reaction by: ${newEvent.eventData.id} to $reactedTo");
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// only kind 1 events are added to map, return otherwise
|
|
||||||
if( newEvent.eventData.kind != 1 && newEvent.eventData.kind != 7) {
|
// only kind 0, 1, 3, 7 events are added to map, return otherwise
|
||||||
|
if( !typesInEventMap.contains(newEvent.eventData.kind) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
allChildEventsMap[newEvent.eventData.id] = Tree(newEvent, [], {}, []);
|
allChildEventsMap[newEvent.eventData.id] = Tree(newEvent, [], {}, []);
|
||||||
newEventsId.add(newEvent.eventData.id);
|
newEventsId.add(newEvent.eventData.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// now go over the newly inserted event, and add its to the tree. only for kind 1 events
|
||||||
//print("In insertEvents num eventsId: ${newEventsId.length}");
|
|
||||||
// now go over the newly inserted event, and then find its parent, or if its a top tree
|
|
||||||
newEventsId.forEach((newId) {
|
newEventsId.forEach((newId) {
|
||||||
Tree? newTree = allChildEventsMap[newId]; // this should return true because we just inserted this event in the allEvents in block above
|
Tree? newTree = allChildEventsMap[newId]; // this should return true because we just inserted this event in the allEvents in block above
|
||||||
// in case the event is already present in the current collection of events (main Tree)
|
// in case the event is already present in the current collection of events (main Tree)
|
||||||
if( newTree != null) {
|
if( newTree != null) {
|
||||||
// now kind 7 events are also returned and are not added to the tree structure itself
|
// only kind 1 events are added to the overall tree structure
|
||||||
if( newTree.e.eventData.kind == 7) {
|
if( newTree.e.eventData.kind != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +140,8 @@ class Tree {
|
|||||||
int printTree(int depth, bool onlyPrintChildren, var newerThan) {
|
int printTree(int depth, bool onlyPrintChildren, var newerThan) {
|
||||||
|
|
||||||
if( e.eventData.kind != 1) {
|
if( e.eventData.kind != 1) {
|
||||||
|
print("Warning: In print tree found non kind 1 event");
|
||||||
|
//e.printEvent(depth);
|
||||||
return 0; // for kind 7 event or any other
|
return 0; // for kind 7 event or any other
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,8 +168,9 @@ class Tree {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
stdout.write("\n");
|
stdout.write("\n");
|
||||||
printDepth(depth+1);
|
for( int i = 0; i < gapBetweenTopTrees; i++ ) {
|
||||||
stdout.write("\n\n\n");
|
stdout.write("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the thread becomes too 'deep' then reset its depth, so that its
|
// if the thread becomes too 'deep' then reset its depth, so that its
|
||||||
@ -193,24 +204,27 @@ class Tree {
|
|||||||
* @printNotifications Add the given events to the Tree, and print the events as notifications
|
* @printNotifications Add the given events to the Tree, and print the events as notifications
|
||||||
* It should be ensured that these are only kind 1 events
|
* It should be ensured that these are only kind 1 events
|
||||||
*/
|
*/
|
||||||
void printNotifications(List<String> newEventsId) {
|
void printNotifications(List<String> newEventsId, String userName) {
|
||||||
|
// remove duplicates
|
||||||
// remove duplicate
|
|
||||||
Set temp = {};
|
Set temp = {};
|
||||||
newEventsId.retainWhere((event) => temp.add(newEventsId));
|
newEventsId.retainWhere((event) => temp.add(newEventsId));
|
||||||
|
|
||||||
stdout.write("\n---------------------------------------\nNotifications: ");
|
String strToWrite = "Notifications: ";
|
||||||
if( newEventsId.isEmpty) {
|
if( newEventsId.isEmpty) {
|
||||||
stdout.write("No new replies/posts.\nTotal posts: ${count()}\n\n");
|
strToWrite += "No new replies/posts.\n";
|
||||||
|
stdout.write("${getNumDashes(strToWrite.length - 1)}\n$strToWrite");
|
||||||
|
stdout.write("Total posts : ${count()}\n");
|
||||||
|
stdout.write("Signed in as : $userName\n\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO call count() less
|
// TODO call count() less
|
||||||
stdout.write("Number of new replies/posts = ${newEventsId.length}\nTotal posts: ${count()}\n");
|
strToWrite += "Number of new replies/posts = ${newEventsId.length}\n";
|
||||||
|
stdout.write("${getNumDashes(strToWrite.length -1 )}\n$strToWrite");
|
||||||
|
stdout.write("Total posts : ${count()}\n");
|
||||||
|
stdout.write("Signed in as : $userName\n");
|
||||||
stdout.write("\nHere are the threads with new replies or new likes: \n\n");
|
stdout.write("\nHere are the threads with new replies or new likes: \n\n");
|
||||||
|
|
||||||
|
|
||||||
List<Tree> topTrees = []; // collect all top tress to display in this list. only unique tress will be displayed
|
List<Tree> topTrees = []; // collect all top tress to display in this list. only unique tress will be displayed
|
||||||
|
|
||||||
newEventsId.forEach((eventID) {
|
newEventsId.forEach((eventID) {
|
||||||
// ignore if not in Tree. Should ideally not happen. TODO write warning otherwise
|
// ignore if not in Tree. Should ideally not happen. TODO write warning otherwise
|
||||||
if( allChildEventsMap[eventID] == null) {
|
if( allChildEventsMap[eventID] == null) {
|
||||||
@ -219,39 +233,40 @@ class Tree {
|
|||||||
|
|
||||||
Tree ?t = allChildEventsMap[eventID];
|
Tree ?t = allChildEventsMap[eventID];
|
||||||
if( t != null) {
|
if( t != null) {
|
||||||
if( t.e.eventData.kind == 1 ) {
|
switch(t.e.eventData.kind) {
|
||||||
t.e.eventData.isNotification = true;
|
case 1:
|
||||||
Tree topTree = getTopTree(t);
|
t.e.eventData.isNotification = true;
|
||||||
topTrees.add(topTree);
|
Tree topTree = getTopTree(t);
|
||||||
} else {
|
topTrees.add(topTree);
|
||||||
//t.e.eventData.newLikes = ;
|
break;
|
||||||
Event event = t.e;
|
case 7:
|
||||||
if(gDebug >= 0) ("Got notification of type 7");
|
Event event = t.e;
|
||||||
String reactorId = event.eventData.pubkey;
|
if(gDebug >= 0) ("Got notification of type 7");
|
||||||
String comment = event.eventData.content;
|
String reactorId = event.eventData.pubkey;
|
||||||
int lastEIndex = event.eventData.eTagsRest.length - 1;
|
int lastEIndex = event.eventData.eTagsRest.length - 1;
|
||||||
String reactedTo = event.eventData.eTagsRest[lastEIndex];
|
String reactedTo = event.eventData.eTagsRest[lastEIndex];
|
||||||
Event? reactedToEvent = allChildEventsMap[reactedTo]?.e;
|
Event? reactedToEvent = allChildEventsMap[reactedTo]?.e;
|
||||||
if( reactedToEvent != null) {
|
if( reactedToEvent != null) {
|
||||||
Tree? reactedToTree = allChildEventsMap[reactedTo];
|
Tree? reactedToTree = allChildEventsMap[reactedTo];
|
||||||
if( reactedToTree != null) {
|
if( reactedToTree != null) {
|
||||||
reactedToTree.e.eventData.newLikes.add( reactorId);
|
reactedToTree.e.eventData.newLikes.add( reactorId);
|
||||||
Tree topTree = getTopTree(reactedToTree);
|
Tree topTree = getTopTree(reactedToTree);
|
||||||
topTrees.add(topTree);
|
topTrees.add(topTree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove identidal entries
|
// remove duplicate top trees
|
||||||
// remove duplicate events
|
|
||||||
Set ids = {};
|
Set ids = {};
|
||||||
topTrees.retainWhere((t) => ids.add(t.e.eventData.id));
|
topTrees.retainWhere((t) => ids.add(t.e.eventData.id));
|
||||||
|
|
||||||
topTrees.forEach( (t) { t.printTree(0, false, 0); });
|
topTrees.forEach( (t) { t.printTree(0, false, 0); });
|
||||||
print("\n");
|
print("\n");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the tree's events to file as one event's json per line
|
// Write the tree's events to file as one event's json per line
|
||||||
@ -259,17 +274,24 @@ class Tree {
|
|||||||
//print("opening $filename to write to");
|
//print("opening $filename to write to");
|
||||||
try {
|
try {
|
||||||
final File file = File(filename);
|
final File file = File(filename);
|
||||||
|
|
||||||
|
// empty the file
|
||||||
|
await file.writeAsString("", mode: FileMode.writeOnly).then( (file) => file);
|
||||||
int eventCounter = 0;
|
int eventCounter = 0;
|
||||||
String nLinesStr = "";
|
String nLinesStr = "";
|
||||||
|
int countPosts = 0;
|
||||||
|
|
||||||
const int numLinesTogether = 200;
|
const int numLinesTogether = 100; // number of lines to write in one write call
|
||||||
int linesWritten = 0;
|
int linesWritten = 0;
|
||||||
for( var k in allChildEventsMap.keys) {
|
for( var k in allChildEventsMap.keys) {
|
||||||
Tree? t = allChildEventsMap[k];
|
Tree? t = allChildEventsMap[k];
|
||||||
if( t != null) {
|
if( t != null) {
|
||||||
String line = "${t.e.originalJson}\n";
|
String line = "${t.e.originalJson}\n";
|
||||||
nLinesStr += line;
|
nLinesStr += line;
|
||||||
eventCounter++;
|
eventCounter++;
|
||||||
|
if( t.e.eventData.kind == 1) {
|
||||||
|
countPosts++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( eventCounter % numLinesTogether == 0) {
|
if( eventCounter % numLinesTogether == 0) {
|
||||||
@ -284,10 +306,12 @@ class Tree {
|
|||||||
nLinesStr = "";
|
nLinesStr = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
int len = await file.length();
|
//int len = await file.length();
|
||||||
} on Exception catch (e) {
|
print("\n\nWrote total $eventCounter events to file \"$gEventsFilename\" of which ${countPosts + 1} are posts.") ; // TODO remove extra 1
|
||||||
|
} on Exception catch (err) {
|
||||||
print("Could not open file $filename.");
|
print("Could not open file $filename.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,11 +343,6 @@ class Tree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( latestEventId.isEmpty) {
|
|
||||||
// search for it in the dummy event id's
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//print("latestEventId = $latestEventId");
|
//print("latestEventId = $latestEventId");
|
||||||
if( latestEventId.isNotEmpty) {
|
if( latestEventId.isNotEmpty) {
|
||||||
@ -438,8 +457,8 @@ Tree getTree(List<Event> events) {
|
|||||||
//print("Got a reaction for $reactedTo. Total number of reactions = ${gReactions[reactedTo]?.length}");
|
//print("Got a reaction for $reactedTo. Total number of reactions = ${gReactions[reactedTo]?.length}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all events other than kind 1, 7 and 3 ( posts)
|
// remove all events other than kind 0, 1, 3 and 7
|
||||||
events.removeWhere( (item) => item.eventData.kind != 1 && item.eventData.kind != 7 && item.eventData.kind != 3);
|
events.removeWhere( (item) => !Tree.typesInEventMap.contains(item.eventData.kind));
|
||||||
|
|
||||||
// remove bot events
|
// remove bot events
|
||||||
events.removeWhere( (item) => gBots.contains(item.eventData.pubkey));
|
events.removeWhere( (item) => gBots.contains(item.eventData.pubkey));
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: nostr_console
|
name: nostr_console
|
||||||
description: A nostr client built for terminal/console.
|
description: A nostr client built for terminal/console.
|
||||||
version: 0.0.3
|
version: 0.0.4
|
||||||
homepage: https://github.com/vishalxl/nostr_console
|
homepage: https://github.com/vishalxl/nostr_console
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user