added new social network menu

This commit is contained in:
Vishal 2022-11-24 18:20:31 +05:30
parent 2e2793675e
commit 8f7b7b03e2
5 changed files with 388 additions and 200 deletions

View File

@ -375,16 +375,14 @@ void printVerifiedAccounts(Store node) {
List<dynamic> listVerified = []; // num follows, pubkey, name, nip05id
printUnderlined("NIP 05 Verifid Users");
printUnderlined("NIP 05 Verified Users");
print("") ;
//print("A list of all NIP05 verified accounts is as follows: \n");
print("Username Num Followers pubkey Nip Id\n");
gKindONames.forEach((key, value) {
String pubkey = key;
if( value.nip05Verified) {
//print("${getAuthorName(pubkey).padRight(20)} ${pubkey} ${value.nip05Id}");
List<String> followers = node.getFollowers(pubkey);
listVerified.add([followers.length, pubkey, getAuthorName(pubkey), value.nip05Id]);
}
@ -469,46 +467,19 @@ Future<void> otherOptionsMenuUi(Store node) async {
await processAnyIncomingEvents(node); // this takes 300 ms
int option = showMenu([ 'Show user profile', // 1
'Search by client name', // 2
'Search word(s) or event id', // 3
'Display contact list', // 4
'Follow new contact', // 5
'Edit your profile', // 6
'Change number of days printed', // 7
'Delete event', // 8
'Application stats', // 9
'Help and About', // 10
'E(x)it to main menu'], // 11
int option = showMenu([
'Search by client name', // 1
'Edit your profile', // 2
'Delete event', // 3
'Application stats', // 4
'Help and About', // 5
'E(x)it to main menu'], // 6
"Other Options Menu"); // menu name
switch(option) {
case 1:
stdout.write("Type 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);
printPubkeys(pubkey);
if( pubkey.length > 1) {
if( pubkey.length > 1) {
printWarning("Got multiple users with the same name. Try again, and/or type a more unique name or their full public keys.");
}
} else {
if (pubkey.isEmpty ) {
printWarning("Could not find the user with that id or username.");
}
else {
printProfile(node, pubkey.first);
}
}
}
break;
case 2:
stdout.write("Enter nostr client name whose events you want to see: ");
String? $tempWords = stdin.readLineSync();
String clientName = $tempWords??"";
@ -518,106 +489,8 @@ Future<void> otherOptionsMenuUi(Store node) async {
}
break;
case 3: // search word or event id
stdout.write("Enter word(s) to search: ");
String? $tempWords = stdin.readLineSync();
String words = $tempWords??"";
if( words != "") {
bool onlyWords (Tree t) => t.treeSelectorHasWords(words.toLowerCase());
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), onlyWords); // search for last gNumLastDays only
} else printWarning("Blank word entered. Try again.");
break;
case 4: // display contact list
String authorName = getAuthorName(userPublicKey);
List<Contact>? contactList = gKindONames[userPublicKey]?.latestContactEvent?.eventData.contactList;
if( contactList != null) {
print("\nHere is the contact list for user $userPublicKey ($authorName), which has ${contactList.length} profiles in it:\n");
contactList.forEach((Contact contact) => stdout.write("${getAuthorName(contact.id)}, "));
print("");
}
break;
case 5: // 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;
}
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);
printPubkeys(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].id == pubkey.first) {
alreadyContact = true;
break;
}
}
if( !alreadyContact) {
print('Sending new contact event');
Contact newContact = Contact(pk, defaultServerUrl);
newContactEvent.eventData.contactList.add(newContact);
getUserEvents(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newContactEvent);
} else {
print("The contact already exists in the contact list. Republishing the old contact list.");
getUserEvents(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, contactEvent);
}
} else {
// TODO fix the send event functions by streamlining them
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(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newEvent);
}
}
}
}
break;
case 6: //edit your profile
case 2: //edit your profile
print("Your current name: ${getAuthorName(userPublicKey)}");
print("Your 'about me': ${gKindONames[userPublicKey]?.about}");
print("Your current profile picture: ${gKindONames[userPublicKey]?.picture}\n");
@ -639,25 +512,7 @@ Future<void> otherOptionsMenuUi(Store node) async {
await processAnyIncomingEvents(node, false); // get latest event, this takes 300 ms
break;
case 7: // change number of days printed
stdout.write("Enter number of days for which you want to see posts: ");
String? $tempNumDays = stdin.readLineSync();
String newNumDays = $tempNumDays??"";
try {
gNumLastDays = int.parse(newNumDays);
print("Changed number of days printed to $gNumLastDays");
} on FormatException catch (e) {
printWarning("Invalid input. Kindly try again.");
if( gDebug > 0) print(" ${e.message}");
continue;
} on Exception catch (e) {
printWarning("Invalid input. Kindly try again.");
if( gDebug > 0) print(" ${e}");
continue;
}
break;
case 8:
case 3:
stdout.write("Enter event id to delete: ");
String? $tempEventId = stdin.readLineSync();
String userInputId = $tempEventId??"";
@ -678,7 +533,7 @@ Future<void> otherOptionsMenuUi(Store node) async {
break;
case 9: // application info
case 4: // application info
print("\n\n");
printUnderlined("Application stats");
print("\n");
@ -702,14 +557,14 @@ Future<void> otherOptionsMenuUi(Store node) async {
printVerifiedAccounts(node);
break;
case 10:
case 5:
print(helpAndAbout);
break;
case 11:
case 6:
continueOtherMenu = false;
break;
break;
default:
break;
@ -1262,6 +1117,240 @@ Future<void> PrivateMenuUI(Store node) async {
return;
}
Future<void> socialMenuUi(Store node) async {
clearScreen();
//Show only notifications
showInitialNotifications(node);
bool socialMenuContinue = true;
while(socialMenuContinue) {
await processAnyIncomingEvents(node); // this takes 300 ms
// the main menu
int option = showMenu([
'All Posts', // 1
'Post/Reply/Like', // 2
'Your notifications',// 3
'Your Posts', // 4
'Your Replies/Likes',//5
'Friends Posts/Replies/Likes', // 6
'Search word(s) or event id', // 7
'Follow new contact', // 8
'Change number of days printed', // 9
'Show user profile', // 10
'E(x)it to main menu'], // 11
"Social Network Menu");
switch(option) {
case 1:
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_all);
break;
case 2:
// 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 \n");
break;
}
stdout.write("Type comment to post/reply (type '+' to send a like): ");
String? $contentVar = stdin.readLineSync();
String content = $contentVar??"";
if( content == "") {
clearScreen();
break;
}
stdout.write("\nType id of event to reply to (leave blank to make a new post; type x to cancel): ");
String? $replyToVar = stdin.readLineSync();
String replyToId = $replyToVar??"";
if( replyToId == "x") {
print("Cancelling post/reply.");
break;
}
String replyKind = "1";
if( content == "+") {
print("Sending a like to given post.");
replyKind = "7";
} else if( content == "!") {
print("Hiding the given post.");
replyKind = "7";
}
await sendReplyPostLike(node, replyToId, replyKind, content);
await processAnyIncomingEvents(node, false);
break;
case 3:
String temp = userPublicKey;
bool selectorTrees_userNotifications (Tree t) => t.treeSelectorRepliesAndLikes(temp);
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_userNotifications);
break;
case 4:
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_selfPosts);
break;
case 5:
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_userRepliesLikes);
break;
case 6:
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_followsPosts);
break;
case 7: // search word or event id
stdout.write("Enter word(s) to search: ");
String? $tempWords = stdin.readLineSync();
String words = $tempWords??"";
if( words != "") {
bool onlyWords (Tree t) => t.treeSelectorHasWords(words.toLowerCase());
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), onlyWords); // search for last gNumLastDays only
} else printWarning("Blank word entered. Try again.");
break;
/*
case 700: // display contact list
String authorName = getAuthorName(userPublicKey);
List<Contact>? contactList = gKindONames[userPublicKey]?.latestContactEvent?.eventData.contactList;
if( contactList != null) {
print("\nHere is the contact list for user $userPublicKey ($authorName), which has ${contactList.length} profiles in it:\n");
contactList.forEach((Contact contact) => stdout.write("${getAuthorName(contact.id)}, "));
print("");
}
break;
*/
case 8: // 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;
}
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);
printPubkeys(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].id == pubkey.first) {
alreadyContact = true;
break;
}
}
if( !alreadyContact) {
print('Sending new contact event');
Contact newContact = Contact(pk, defaultServerUrl);
newContactEvent.eventData.contactList.add(newContact);
getUserEvents(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newContactEvent);
} else {
print("The contact already exists in the contact list. Republishing the old contact list.");
getUserEvents(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, contactEvent);
}
} else {
// TODO fix the send event functions by streamlining them
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(gListRelayUrls1, pk, gLimitPerSubscription, getSecondsDaysAgo(gLimitFollowPosts));
sendEvent(node, newEvent);
}
}
}
}
break;
case 9: // change number of days printed
stdout.write("Enter number of days for which you want to see posts: ");
String? $tempNumDays = stdin.readLineSync();
String newNumDays = $tempNumDays??"";
try {
gNumLastDays = int.parse(newNumDays);
print("Changed number of days printed to $gNumLastDays");
} on FormatException catch (e) {
printWarning("Invalid input. Kindly try again.");
if( gDebug > 0) print(" ${e.message}");
continue;
} on Exception catch (e) {
printWarning("Invalid input. Kindly try again.");
if( gDebug > 0) print(" ${e}");
continue;
}
break;
case 10:
stdout.write("Type 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);
printPubkeys(pubkey);
if( pubkey.length > 1) {
if( pubkey.length > 1) {
printWarning("Got multiple users with the same name. Try again, and/or type a more unique name or their full public keys.");
}
} else {
if (pubkey.isEmpty ) {
printWarning("Could not find the user with that id or username.");
}
else {
printProfile(node, pubkey.first);
}
}
}
break;
case 11:
default:
socialMenuContinue = false;
} // end menu switch
} // end while
} // end mainMenuUi()
void showInitialNotifications(Store node) {
bool hasNotifications (Tree t) => t.treeSelectorNotifications();
@ -1288,8 +1377,8 @@ Future<void> mainMenuUi(Store node) async {
await processAnyIncomingEvents(node); // this takes 300 ms
// the main menu
int option = showMenu(['Display feed', // 1
'Post/Reply/Like', // 2
int option = showMenu(['Home Page', // 1
'Social Network', // 2
'Public Channels', // 3
'Encrypted Channels',// 4
'Private Messages', // 5
@ -1299,40 +1388,13 @@ Future<void> mainMenuUi(Store node) async {
switch(option) {
case 1:
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorShowAllTrees);
node.printTree(0, DateTime.now().subtract(Duration(days:gNumLastDays)), selectorTrees_all);
break;
case 2:
// 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 \n");
break;
}
stdout.write("Type comment to post/reply (type '+' to send a like): ");
String? $contentVar = stdin.readLineSync();
String content = $contentVar??"";
if( content == "") {
break;
}
stdout.write("\nType id of event to reply to (leave blank to make a new post; type x to cancel): ");
String? $replyToVar = stdin.readLineSync();
String replyToId = $replyToVar??"";
if( replyToId == "x") {
print("Cancelling post/reply.");
break;
}
String replyKind = "1";
if( content == "+") {
print("Sending a like to given post.");
replyKind = "7";
} else if( content == "!") {
print("Hiding the given post.");
replyKind = "7";
}
await sendReplyPostLike(node, replyToId, replyKind, content);
await processAnyIncomingEvents(node, false);
clearScreen();
await socialMenuUi(node);
clearScreen();
break;
case 3:

View File

@ -45,8 +45,8 @@ class UserNameInfo {
*/
Map<String, UserNameInfo> gKindONames = {};
// global reactions entry. Map of form <if of event reacted to, List of Reactors>
// reach Reactor is a list of 2-elements ( first is public id of reactor event, second is comment)
// global reactions entry. Map of form <id of event reacted to, List of Reactors>
// reach Reactor is a list of 2-elements ( first is pubkey of reactor event, second is comment)
Map< String, List<List<String>> > gReactions = {};
// global contact list of each user, including of the logged in user.
@ -1419,6 +1419,7 @@ class Contact {
String toString() {
return 'id: $id ( ${getAuthorName(id)}) relay: $relay';
}
}
String addEscapeChars(String str) {

View File

@ -62,7 +62,7 @@ String userPublicKey = gDefaultPublicKey;
// default follows; taken from nostr.io/stats
List<String> gDefaultFollows = [
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", //damus
"6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964" // dergigi
"6b0d4c8d9dc59e110d380b0429a02891f1341a0fa2ba1b1cf83a3db4d47e3964", // dergigi
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", // jb55
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", // fiatjaf
"2ef93f01cd2493e04235a6b87b10d3c4a74e2a7eb7c3caf168268f6af73314b5", // unclebobmarting
@ -83,7 +83,6 @@ List<String> gDefaultFollows = [
"f43c1f9bff677b8f27b602725ea0ad51af221344f69a6b352a74991a4479bac3", // manfromhighcastle
"80482e60178c2ce996da6d67577f56a2b2c47ccb1c84c81f2b7960637cb71b78", // Leo
"42a0825e980b9f97943d2501d99c3a3859d4e68cd6028c02afe58f96ba661a9d", // zerosequioso
"e8caa2028a7090ffa85f1afee67451b309ba2f9dee655ec8f7e0a02c29388180", // nostr_console test
"3235036bd0957dfb27ccda02d452d7c763be40c91a1ac082ba6983b25238388c"]; // vishalxl ];

View File

@ -9,10 +9,136 @@ typedef fRoomSelector = bool Function(ScrollableMessages room);
Store? gStore = null;
bool selectorShowAllTrees(Tree t) {
// only show in which user is involved
bool selectorTrees_selfPosts(Tree t) {
if( userPublicKey == t.event.eventData.pubkey) {
return true;
}
return false;
}
/*
// returns true of the user has received a like or response to this post
bool userHasNotification(String pubkey, Event e) {
if( e.eventData.pubkey == pubkey && gReactions.containsKey(e.eventData.id) ) {
List<List<String>>? temp = gReactions[e.eventData.id];
if( temp != null) {
if( temp.length > 0) {
return true;
}
}
}
return false;
}
// only show in which user is involved
bool selectorTrees_userNotifications(Tree t) {
if( userHasNotification(userPublicKey, t.event)) {
return true;
}
for( Tree child in t.children) {
if( selectorTrees_userNotifications(child)) {
return true;
}
}
return false;
}
*/
bool userInvolved(String pubkey, Event e) {
if( e.eventData.pubkey == pubkey) {
return true;
}
if( gReactions.containsKey(e.eventData.id)) {
List<List<String>>? reactors = gReactions[e.eventData.id]??null;
if( reactors != null) {
for( var reactor in reactors) {
//String reactorEventId = reactor[0];
String reactorPubkey = reactor[0];
if( reactorPubkey == pubkey) {
return true;
}
}
}
}
return false;
}
bool selectorTrees_all(Tree t) {
return true;
}
// only show in which user is involved
bool selectorTrees_userRepliesLikes(Tree t) {
if( userInvolved(userPublicKey, t.event)) {
return true;
}
for( Tree child in t.children) {
if( selectorTrees_userRepliesLikes(child)) {
return true;
}
}
return false;
}
bool followsInvolved(Event e, Event? contactEvent) {
if( contactEvent == null) {
return false;
}
// if its an event by any of the contact
if(contactEvent.eventData.contactList.any((contact) => e.eventData.pubkey == contact.id )) {
return true;
}
// check if any of the contact liked it
if( gReactions.containsKey(e.eventData.id)) {
List<List<String>>? reactors = gReactions[e.eventData.id]??null;
if( reactors != null) {
for( var reactor in reactors) {
//String reactorEventId = reactor[0];
String reactorPubkey = reactor[0];
if(contactEvent.eventData.contactList.any((contact) => reactorPubkey == contact.id )) {
return true;
}
}
}
}
return false;
}
// only show in which user is involved
bool selectorTrees_followsPosts(Tree t) {
Event? contactEvent = gKindONames[userPublicKey]?.latestContactEvent;
if( followsInvolved(t.event, contactEvent)) {
return true;
}
for( Tree child in t.children) {
if( selectorTrees_followsPosts(child)) {
return true;
}
}
return false;
}
bool selectorShowAllRooms(ScrollableMessages room) {
return true;
}

View File

@ -32,7 +32,7 @@ void main() {
tree.children.add(treeChild);
store.printTree(0, DateTime.now().subtract(Duration(days:1)), selectorShowAllTrees);
store.printTree(0, DateTime.now().subtract(Duration(days:1)), selectorTrees_all);
});
@ -59,7 +59,7 @@ void main() {
Set<Event> listEvents = { exampleEvent3, exampleEvent2, exampleEvent1};
Store node = Store.fromEvents(listEvents);
node.printTree(0, DateTime.now().subtract(Duration(days:1000)), selectorShowAllTrees); // will test for ~1000 days
node.printTree(0, DateTime.now().subtract(Duration(days:1000)), selectorTrees_all); // will test for ~1000 days
});
test('make_paragraph', () {