diff --git a/bin/nostr_console.dart b/bin/nostr_console.dart index 15fb2da..98b5b37 100644 --- a/bin/nostr_console.dart +++ b/bin/nostr_console.dart @@ -304,8 +304,13 @@ Future main(List arguments) async { getMultiUserEvents(gListRelayUrls1, gDefaultFollows, limitPerSubscription, getSecondsDaysAgo(limitFollowPosts)); // get group and meta info events - getKindEvents([0, 3, 40, 41, 140, 141], gListRelayUrls1, 3 * limitPerSubscription, getSecondsDaysAgo(limitMetaInfoEvents)); // get all type 3 etc - getKindEvents([42, 142], gListRelayUrls1, limitPerSubscription * 3, getSecondsDaysAgo( limitFollowPosts)); // get all type 3 etc + getKindEvents([0, 3, 40, 41], gListRelayUrls1, 3 * limitPerSubscription, getSecondsDaysAgo(limitMetaInfoEvents)); // get all type 3 etc + getKindEvents([140, 141], gListRelayUrls1, 3 * limitPerSubscription, getSecondsDaysAgo(1)); // get all type 3 etc + + getKindEvents([42], gListRelayUrls1, limitPerSubscription * 3, getSecondsDaysAgo( limitFollowPosts)); // get all type 3 etc + getKindEvents([142], gListRelayUrls1, limitPerSubscription * 3, getSecondsDaysAgo( 1)); // get all type 3 etc + + getKindEvents([gSecretMessageKind], gListRelayUrls1, limitPerSubscription * 3, getSecondsDaysAgo( 1)); // get all type 3 etc // TODO get all 40 events, and then get all #e for them ( responses to them) diff --git a/lib/console_ui.dart b/lib/console_ui.dart index 1420e33..3985abf 100644 --- a/lib/console_ui.dart +++ b/lib/console_ui.dart @@ -132,12 +132,14 @@ Future sendChannelReply(Store node, Channel channel, String replyTo, Strin } // send DM -Future sendDirectMessage(Store node, String otherPubkey, String messageToSend) async { +Future sendDirectMessage(Store node, String otherPubkey, String messageToSend, {String replyKind = "4"}) async { //messageToSend = addEscapeChars(messageToSend); since this get encrypted , it does not need escaping String otherPubkey02 = "02" + otherPubkey; String encryptedMessageToSend = myEncrypt(userPrivateKey, otherPubkey02, messageToSend); - String replyKind = "4"; + //print("in sendDirectMessage: replyKind = $replyKind"); + + //String replyKind = "4"; String strTags = '["p","$otherPubkey"]'; strTags += gWhetherToSendClientTag?',["client","nostr_console"]':''; int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; @@ -609,7 +611,7 @@ Future channelMenuUI(Store node) async { //await processNotifications(node); // this takes 300 ms if( !justShowedChannels) { printInColor(" Public Channels ", gCommentColor); - node.printChannelsOverview(node.channels, 20, selectorShowAllRooms); + node.printChannelsOverview(node.channels, 20, selectorShowAllRooms, node.allChildEventsMap, null); justShowedChannels = true; } @@ -645,7 +647,7 @@ Future channelMenuUI(Store node) async { firstIteration = false; } - String fullChannelId = node.showChannel(node.channels, channelId, pageNum); + String fullChannelId = node.showChannel(node.channels, channelId, null, null, null, pageNum); // direct channel does not need this, only encrypted channels needs them if( fullChannelId == "") { //print("Could not find the given channel."); showChannelOption = false; @@ -719,7 +721,7 @@ Future channelMenuUI(Store node) async { case 2: clearScreen(); printInColor(" All Public Channels ", gCommentColor); - node.printChannelsOverview(node.channels, node.channels.length, selectorShowAllRooms); + node.printChannelsOverview(node.channels, node.channels.length, selectorShowAllRooms, node.allChildEventsMap, null); justShowedChannels = true; break; @@ -777,7 +779,7 @@ Future createEncryptedChannel(Store node) async { String messageToSend = "App Encrypted Channels: inviting you to encrypted channel $newEncryptedChannelId encrypted using private public keys $channelPriKey $channelPubKey"; for( int i = 0; i < participants.length; i++) { // send message to all ( including self which is in that list) - await sendDirectMessage(node, participants[i], messageToSend); + await sendDirectMessage(node, participants[i], messageToSend, replyKind: gSecretMessageKind.toString()); } await processAnyIncomingEvents(node, false); // get latest event, this takes 300 ms @@ -788,7 +790,7 @@ Future updateEncryptedChannel(Store node, String channelId, String channelName, String channelAbout, String channelPic, String content, String tags, Set participants, Set newParticipants) async { - List keys = getEncryptedChannelKeys(node.directRooms, node.allChildEventsMap, channelId); + List keys = getEncryptedChannelKeys(node.encryptedGroupSecretIds, node.allChildEventsMap, channelId); if( keys.length == 2) { String channelPriKey = keys[0], channelPubKey = keys[1]; @@ -797,7 +799,7 @@ Future updateEncryptedChannel(Store node, String channelId, // send message to all new participants newParticipants.forEach((participant) async { - await sendDirectMessage(node, participant, messageToSend); + await sendDirectMessage(node, participant, messageToSend, replyKind: gSecretMessageKind.toString()); }); int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; @@ -815,7 +817,7 @@ Future updateEncryptedChannel(Store node, String channelId, String encryptChannelMessage(Store node, String channelId, String messageToSend) { String encryptedMessage = ''; - List keys = getEncryptedChannelKeys(node.directRooms, node.allChildEventsMap, channelId); + List keys = getEncryptedChannelKeys(node.encryptedGroupSecretIds, node.allChildEventsMap, channelId); if( keys.length != 2) { return ''; } @@ -888,7 +890,7 @@ Future encryptedChannelMenuUI(Store node) async { if( !justShowedChannels) { printInColor(" Encrypted Channels ", gCommentColor); - node.printChannelsOverview(node.encryptedChannels, 20, selectorShowAllRooms); + node.printChannelsOverview(node.encryptedChannels, 20, selectorShowAllRooms, node.allChildEventsMap, node.encryptedGroupSecretIds); justShowedChannels = true; } @@ -926,7 +928,7 @@ Future encryptedChannelMenuUI(Store node) async { firstIteration = false; } - String fullChannelId = node.showChannel(node.encryptedChannels, channelId, pageNum); + String fullChannelId = node.showChannel(node.encryptedChannels, channelId, node.allChildEventsMap, node.encryptedGroupSecretIds, node.encryptedChannels, pageNum); if( fullChannelId == "") { //print("Could not find the given channel."); showChannelOption = false; @@ -1007,7 +1009,7 @@ Future encryptedChannelMenuUI(Store node) async { case 2: clearScreen(); printInColor(" All Encrypted Channels ", gCommentColor); - node.printChannelsOverview(node.encryptedChannels, node.encryptedChannels.length, selectorShowAllRooms); + node.printChannelsOverview(node.encryptedChannels, node.encryptedChannels.length, selectorShowAllRooms, node.allChildEventsMap, node.encryptedGroupSecretIds); justShowedChannels = true; break; @@ -1036,7 +1038,7 @@ Future PrivateMenuUI(Store node) async { await processAnyIncomingEvents(node, true); // this takes 300 ms printInColor(" Direct Messages", gCommentColor); - node.printDirectRoomInfo(showAllRooms); + node.printDirectRoomInfo(showAllRooms, node.allChildEventsMap); String menuInfo = """Direct Message howto: To send a Direct Message to someone for the first time, enter their 64 byte hex pubkey. To enter or continue a conversation seen in overview, enter the first few letters of the other person's name or of their pubkey"""; @@ -1368,7 +1370,7 @@ void showInitialNotifications(Store node) { print("\n"); bool showNotifications (ScrollableMessages room) => room.selectorNotifications(); - int numDirectRoomsPrinted = node.printDirectRoomInfo(showNotifications); + int numDirectRoomsPrinted = node.printDirectRoomInfo( showNotifications, node.allChildEventsMap); if( numDirectRoomsPrinted > 0) print("\n"); diff --git a/lib/event_ds.dart b/lib/event_ds.dart index d92e763..406629d 100644 --- a/lib/event_ds.dart +++ b/lib/event_ds.dart @@ -281,7 +281,7 @@ class EventData { } } else { int eKind = json['kind']; - if ( eKind == 1 || eKind == 7 || eKind == 42 || eKind == 5 || eKind == 4 || eKind == 140 || eKind == 141 || eKind == 142) { + if ( eKind == 1 || eKind == 7 || eKind == 42 || eKind == 5 || eKind == 4 || eKind == 140 || eKind == 141 || eKind == 142 || eKind == gSecretMessageKind) { for( int i = 0; i < numTags; i++) { var tag = jsonTags[i]; @@ -338,7 +338,7 @@ class EventData { } // replace the patterns - + //print("in main body of expandmentions"); for(int i = 0; i < nip08PlaceHolders.length && i < tags.length; i++) { int index = -1; Pattern p = nip08PlaceHolders[i]; @@ -364,19 +364,18 @@ class EventData { } // is called only once for each event received ( or read from file) - void translateAndExpandMentions(List directRooms, Map tempChildEventsMap) { + void translateAndExpandMentions(Map tempChildEventsMap) { if( id == gCheckEventId) { - printInColor("in translateAndExpandMensitons: decoding $gCheckEventId\n", redColor); + //printInColor("in translateAndExpandMentions: decoding $gCheckEventId\n", redColor); } if (content == "" || evaluatedContent != "") { if( id == gCheckEventId) { - printInColor("in translateAndExpandMensitons: returning \n", redColor); + //printInColor("in translateAndExpandMentions: returning \n", redColor); } return; } - switch(kind) { case 1: case 42: @@ -404,6 +403,64 @@ class EventData { } break; + } // end switch + return; + } // end translateAndExpandMentions + + // is called only once for each event received ( or read from file) + String? TranslateAndDecryptSecretMessage(Map tempChildEventsMap) { + if( id == gCheckEventId) { + //printInColor("in TranslateAndDecryptSecretMessage: decoding $gCheckEventId\n", redColor); + } + + if (content == "" || evaluatedContent != "") { + if( id == gCheckEventId) { + //printInColor("in TranslateAndDecryptSecretMessage: returning \n", redColor); + } + return null; + } + + switch(kind) { + case gSecretMessageKind: + if( userPrivateKey == ""){ // cant process if private key not given + return null; + } + + if(!isValidDirectMessage(this)) { + return null; + } + + if( id == gCheckEventId) { + //printInColor("in translateAndExpandMensitons: gonna decrypt \n", redColor); + } + + //log.info("decrypting a secret message"); + + String? decrypted = decryptDirectMessage(); + if( decrypted != null) { + evaluatedContent = decrypted; + } + return id; + } // end switch + + return null; + } // end TranslateAndDecryptSecretMessage + + + // is called only once for each event received ( or read from file) + void translateAndDecryptKind4(Map tempChildEventsMap) { + if( id == gCheckEventId) { + printInColor("in translateAndDecryptKind4: decoding $gCheckEventId\n", redColor); + } + + if (content == "" || evaluatedContent != "") { + if( id == gCheckEventId) { + printInColor("in translateAndDecryptKind4: returning \n", redColor); + } + return; + } + + switch(kind) { case 4: if( userPrivateKey == ""){ // cant process if private key not given break; @@ -417,37 +474,63 @@ class EventData { printInColor("in translateAndExpandMensitons: gonna decrypt \n", redColor); } + //log.info("decrypting a message of kind 4"); + String? decrypted = decryptDirectMessage(); if( decrypted != null) { evaluatedContent = decrypted; evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap); } + //print("evaluatedContent: $evaluatedContent"); break; - - } // end switch } // end translateAndExpandMentions // is called only once for each event received ( or read from file) - void translateAndExpand14x(List directRooms, List encryptedChannels, Map tempChildEventsMap) { + void translateAndDecrypt14x(List secretMessageIds, List encryptedChannels, Map tempChildEventsMap) { if( id == gCheckEventId) { - printInColor("in translateAndExpandMensitons: decoding ee810ea73072af056cceaa6d051b4fcce60739247f7bcc752e72fa5defb64f09\n", redColor); + //printInColor("in translateAndExpand14x: decoding ee810ea73072af056cceaa6d051b4fcce60739247f7bcc752e72fa5defb64f09\n", redColor); } if (content == "" || evaluatedContent != "") { if( id == gCheckEventId) { - printInColor("in translateAndExpandMensitons: returning \n", redColor); + //printInColor("in translateAndExpand14x: returning \n", redColor); } return; } + if( createdAt < getSecondsDaysAgo(1)) { + //print("old 142. not decrypting"); + return; + } + + + switch(kind) { case 142: - String? decrypted = decryptEncryptedChannelMessage(directRooms, encryptedChannels, tempChildEventsMap); + //print("in translateAndDecrypt14x"); + Channel? channel = getChannelForMessage( encryptedChannels, id); + if( channel == null) { + break; + } + + if(!channel.participants.contains(userPublicKey)) { + break; + } + + if(!channel.participants.contains(pubkey)) { + break; + } + + String? decrypted = decryptEncryptedChannelMessage(secretMessageIds, tempChildEventsMap); if( decrypted != null) { + //printWarning("Successfully decrypted kind 142: $id"); evaluatedContent = decrypted; + //print("in translateAndDecrypt14x: calling expandMentions"); evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap); + //print("content = $content"); + //print(evaluatedContent); } break; default: @@ -502,34 +585,21 @@ class EventData { } - String? decryptEncryptedChannelMessage(List directRooms, List encryptedChannels,Map tempChildEventsMap) { - Channel? channel = getChannelForMessage( encryptedChannels, id); - if( channel == null) { - print("could not find channel"); - return null; - } - - if(!channel.participants.contains(userPublicKey)) { - return null; - } - - if(!channel.participants.contains(pubkey)) { - return null; - } + String? decryptEncryptedChannelMessage(List secretMessageIds, Map tempChildEventsMap) { if( id == "865c9352de11a3959c06fce5350c5a1b9fa0475d3234078a1bb45d152b370f0b") { // known issue //print("\n\ngoing to decrypt b1ab66ac50f00f3c3bbc91e5b9e03fc8e79e3fdb9f6d5c9ae9777aa6ca3020a2"); //print(channel.participants); - return ""; + return null; } - //print("Going to decrypt event id: $id"); + //printWarning("Going to decrypt 14x event id: $id"); //print("In decryptEncryptedChannelMessage: for event of kind 142 with event id = $id"); int ivIndex = content.indexOf("?iv="); if( ivIndex == -1) { - return ""; + return null; } var iv = content.substring( ivIndex + 4, content.length); var enc_str = content.substring(0, ivIndex); @@ -537,11 +607,11 @@ class EventData { String channelId = getChannelIdForMessage(); //print("In decryptEncryptedChannelMessage: got channel id $channelId"); List keys = []; - keys = getEncryptedChannelKeys(directRooms, tempChildEventsMap, channelId); + keys = getEncryptedChannelKeys(secretMessageIds, tempChildEventsMap, channelId); if( keys.length != 2) { - //print("Could not get keys for event id: $id and channelId: $channelId"); - return ""; + print("Could not get keys for event id: $id and channelId: $channelId"); + return null; } //print("\nevent id: $id"); @@ -551,6 +621,7 @@ class EventData { String pubKey = "02" + keys[1]; var decrypted = myPrivateDecrypt( priKey, pubKey, enc_str, iv); // use bob's privatekey and alic's publickey means bob can read message from alic + //print("decrypted = |$decrypted|"); return decrypted; } @@ -571,11 +642,24 @@ class EventData { } // prints event data in the format that allows it to be shown in tree form by the Tree class - void printEventData(int depth, bool topPost) { + void printEventData(int depth, bool topPost, Map? tempChildEventsMap, List? secretMessageIds, List? encryptedChannels) { if( !(kind == 1 || kind == 4 || kind == 42)) { return; // only print kind 1 and 42 and 4 } + // will only do decryption if its not been decrypted yet by looking at 'evaluatedContent' + if( tempChildEventsMap != null ) + if(kind == 4) + translateAndDecryptKind4( tempChildEventsMap); + else if ([1, 42].contains(kind)) { + translateAndExpandMentions(tempChildEventsMap); + } else if ([142].contains(kind)) { + if( secretMessageIds != null && encryptedChannels != null) { + translateAndDecrypt14x( secretMessageIds, encryptedChannels, tempChildEventsMap); + } + } + + int n = 4; String maxN(String v) => v.length > n? v.substring(0,n) : v.substring(0, v.length); @@ -672,12 +756,25 @@ class EventData { stdout.write(strToPrint); } - String getAsLine({int len = 20}) { + String getAsLine(var tempChildEventsMap, List? secretMessageIds, List? encryptedChannels, {int len = 20}) { + + // will only do decryption if its not been decrypted yet by looking at 'evaluatedContent' + if(kind == 4) + translateAndDecryptKind4( tempChildEventsMap); + else if ([1, 42].contains(kind)) { + translateAndExpandMentions(tempChildEventsMap); + } else if ([142].contains(kind)) { + if( tempChildEventsMap != null && secretMessageIds != null && encryptedChannels != null) { + translateAndDecrypt14x(secretMessageIds, encryptedChannels, tempChildEventsMap); + } + } + String contentToPrint = evaluatedContent.isEmpty? content: evaluatedContent; if( len == 0 || len > contentToPrint.length) { //len = contentToPrint.length; } - + + contentToPrint = contentToPrint.replaceAll("\n", " "); contentToPrint = contentToPrint.replaceAll("\r", " "); contentToPrint = contentToPrint.replaceAll("\t", " "); @@ -691,10 +788,26 @@ class EventData { paddedStrToPrint = "$gNotificationColor$paddedStrToPrint$gColorEndMarker"; isNotification = false; } + //print("returning $paddedStrToPrint"); return paddedStrToPrint; } - String getStrForChannel(int depth) { + + String getStrForChannel(int depth, var tempChildEventsMap, List? secretMessageIds, List? encryptedChannels) { + + // will only do decryption if its not been decrypted yet by looking at 'evaluatedContent' + // will only do decryption if its not been decrypted yet by looking at 'evaluatedContent' + if(kind == 4) + translateAndDecryptKind4( tempChildEventsMap); + else if ([1, 42].contains(kind)) { + translateAndExpandMentions(tempChildEventsMap); + } else if ([142].contains(kind)) { + if( tempChildEventsMap != null && secretMessageIds != null && encryptedChannels != null) { + //print('decrypting 14x in getStrForChannel'); + translateAndDecrypt14x(secretMessageIds, encryptedChannels, tempChildEventsMap); + } + } + String strToPrint = ""; String name = getAuthorName(pubkey); String strDate = getPrintableDate(createdAt); @@ -859,7 +972,7 @@ class Event { } void printEvent(int depth, bool topPost) { - eventData.printEventData(depth, topPost); + eventData.printEventData(depth, topPost, null, null, null); //stdout.write("\n$originalJson --------------------------------\n\n"); } @@ -1463,21 +1576,32 @@ bool isValidDirectMessage(EventData directMessageData) { bool validUserMessage = false; List allPtags = []; + + if( [4, gSecretMessageKind].contains( directMessageData.kind) ) { + if(gDebug > 0 && gCheckEventId == directMessageData.id) print("in isValidDirectMessage for kind $gSecretMessageKind. its id = ${directMessageData.id}"); + } + directMessageData.tags.forEach((tag) { - if( tag.length < 2 ) + if( tag.length < 2 ) { return; + } if( tag[0] == "p" && tag[1].length == 64) { // basic length sanity test allPtags.add(tag[1]); } }); + if(gDebug > 0 && gCheckEventId == directMessageData.id) print("In isvalid direct message: ptags len: ${allPtags.length}, ptags = ${allPtags}"); + if( directMessageData.pubkey == userPublicKey && allPtags.length == 1) { if( allPtags[0].substring(0, 32) != "0".padLeft(32, '0')) { // check that the message hasn't been sent to an invalid pubkey validUserMessage = true; // case where this user is sender } } else { + if(gCheckEventId == directMessageData.id) print("in else case 3"); if ( directMessageData.pubkey != userPublicKey) { + if(gDebug > 0 && gCheckEventId == directMessageData.id) print("in if 5 allpags 1st = ${allPtags[0]} userPUblic key = $userPublicKey"); if( allPtags.length == 1 && allPtags[0] == userPublicKey) { + validUserMessage = true; // case where this user is recipeint } } @@ -1656,40 +1780,36 @@ bool isValidPubkey(String pubkey) { } -List getEncryptedChannelKeys(List directRooms, Map tempChildEventsMap, String channelId) { +List getEncryptedChannelKeys(List secretMessageIds, Map tempChildEventsMap, String channelId) { Event? e = tempChildEventsMap[channelId]?.event; if( e != null) { //print("\n----------------\nIn getEncryptedChannelKeys for encrypted channel $channelId"); String creatorPubKey = e.eventData.pubkey; - for( int i = 0; i < directRooms.length; i++) { - DirectMessageRoom room = directRooms[i]; - if( room.otherPubkey == creatorPubKey) { - //print("got other pubkey $creatorPubKey"); - for( int j = 0; j < room.messageIds.length; j++) { - String messageId = room.messageIds[j]; - Event? messageEvent = tempChildEventsMap[messageId]?.event; - if( messageEvent != null) { - //print("got a message which is: ${messageEvent.eventData.evaluatedContent}"); - //print(messageEvent.eventData.getStrForChannel(0)); - String evaluatedContent = messageEvent.eventData.evaluatedContent; - if( evaluatedContent.startsWith("App Encrypted Channels:")) { - //print("got App"); - if( evaluatedContent.contains(channelId) && evaluatedContent.length == 288) { - //print("in getEncryptedChannelKeys: success: got password in pvt message: $evaluatedContent"); - //print("len of evaluated content: ${evaluatedContent.length} "); - String priKey = evaluatedContent.substring(159, 159 + 64); - String pubKey = evaluatedContent.substring(224, 224 + 64); - if( priKey.length == 64 && pubKey.length == 64) { - return [priKey, pubKey]; - } - } + for( int j = 0; j < secretMessageIds.length; j++) { + String messageId = secretMessageIds[j]; + + Event? messageEvent = tempChildEventsMap[messageId]?.event; + if( messageEvent != null) { + //print("got a message which is: ${messageEvent.eventData.evaluatedContent}"); + //print(messageEvent.eventData.getStrForChannel(0)); + String evaluatedContent = messageEvent.eventData.evaluatedContent; + if( evaluatedContent.startsWith("App Encrypted Channels:")) { + //print("got App"); + if( evaluatedContent.contains(channelId) && evaluatedContent.length == 288) { + //print("in getEncryptedChannelKeys: success: got password in pvt message: $evaluatedContent"); + //print("len of evaluated content: ${evaluatedContent.length} "); + String priKey = evaluatedContent.substring(159, 159 + 64); + String pubKey = evaluatedContent.substring(224, 224 + 64); + + if( priKey.length == 64 && pubKey.length == 64) { + return [priKey, pubKey]; } - } else { - print("could not get message event"); } } + } else { + print("could not get message event"); } } } diff --git a/lib/relays.dart b/lib/relays.dart index 1532e24..acaed90 100644 --- a/lib/relays.dart +++ b/lib/relays.dart @@ -184,7 +184,7 @@ class Relays { if( rEvents.add(e) ) { uniqueIdsRecieved.add(id); String receivedSubscription = json[1]; - if( gDebug > 3) e.eventData.printEventData(0, true); + if( gDebug > 3) e.eventData.printEventData(0, true, null, null, null); if( gDebug > 2) print(""); if( gDebug > 1) log.info("In relay listener for relay url $relay: after adding element, rEvents Size = ${rEvents.length} numReceived = ${newRelay.numReceived} for subscription $receivedSubscription"); diff --git a/lib/settings.dart b/lib/settings.dart index 168046e..d24b459 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -3,7 +3,7 @@ import 'package:logging/logging.dart'; // name of executable const String exename = "nostr_console"; -const String version = "0.1.8-beta"; +const String version = "0.1.9-beta"; int gDebug = 0; int gSpecificDebug = 0; @@ -11,10 +11,13 @@ int gSpecificDebug = 0; final log = Logger('ExampleLogger'); // for debugging -String gCheckEventId = " e74e93fbc77af5275f29db688931f725813ab1385f16233bfa609078a8779dfa"; +String gCheckEventId = "0082a613c9f4d43f796a427d15db74f10ac64212e28f465870ceaf099f488087"; int gDefaultNumWaitSeconds = 8000; // is used in main() +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// encrypted Group settings +const int gSecretMessageKind = 104; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// file related settings const String gDefaultEventsFilename = "all_nostr_events.txt"; String gEventsFilename = ""; // is set in arguments, and if set, then file is read from and written to diff --git a/lib/tree_ds.dart b/lib/tree_ds.dart index 4693e4f..45ba33b 100644 --- a/lib/tree_ds.dart +++ b/lib/tree_ds.dart @@ -240,7 +240,7 @@ class ScrollableMessages { - void printOnePage(Map tempChildEventsMap, [int page = 1]) { + void printOnePage(Map tempChildEventsMap, List? secretMessageIds, List? encryptedChannels, [int page = 1] ) { if( page < 1) { if( gDebug > 0) log.info("In ScrollableMessages::printOnepage got page = $page"); page = 1; @@ -266,8 +266,8 @@ class ScrollableMessages { String eId = messageIds[i]; Event? e = tempChildEventsMap[eId]?.event; if( e!= null) { - if( !(e.eventData.kind == 142 && (e.eventData.content == e.eventData.evaluatedContent))) // condition so that in encrypted channels non-encrypted messages aren't printed - print(e.eventData.getStrForChannel(0)); + //printWarning("in print one page"); + print(e.eventData.getStrForChannel(0, tempChildEventsMap, secretMessageIds, encryptedChannels)); } } @@ -373,7 +373,7 @@ class DirectMessageRoom extends ScrollableMessages{ if( gDebug > 0) log.info("In printChannel got page = $page"); page = 1; } - printOnePage(store.allChildEventsMap, page); + printOnePage(store.allChildEventsMap, null, null, page); } } @@ -454,40 +454,31 @@ class Tree { } // returns true if the treee or its children has a reply or like for the user with public key pk; and notification flags are set for such events - bool treeSelectorRepliesAndLikes(String pk) { + bool treeSelectorRepliesAndLikes(String pubkey) { bool hasReaction = false; bool childMatches = false; - if( event.eventData.pubkey == pk && gReactions.containsKey(event.eventData.id)) { + if( event.eventData.pubkey == pubkey && gReactions.containsKey(event.eventData.id)) { List>? reactions = gReactions[event.eventData.id]; if( reactions != null) { if( reactions.length > 0) { - // has reactions - reactions.forEach((reaction) { - // dont add notificatoin for self reaction - Event? reactorEvent = store?.allChildEventsMap[reaction[0]]?.event; - if( reactorEvent != null) { - if( reactorEvent.eventData.pubkey != pk){ // ignore self likes - event.eventData.newLikes.add(reaction[0]); - hasReaction = true; - } - } - }); + event.eventData.isNotification = true; + return true; } } } - if( event.eventData.pubkey == pk && children.length > 0) { + if( event.eventData.pubkey == pubkey && children.length > 0) { for( int i = 0; i < children.length; i++ ) { children.forEach((child) { // if child is someone else then set notifications and flag, means there are replies to this event - childMatches = child.event.eventData.isNotification = ((child.event.eventData.pubkey != pk)? true: false) ; + childMatches = child.event.eventData.isNotification = ((child.event.eventData.pubkey != pubkey)? true: false) ; }); } } for( int i = 0; i < children.length; i++ ) { - if( children[i].treeSelectorRepliesAndLikes(pk)) { + if( children[i].treeSelectorRepliesAndLikes(pubkey)) { childMatches = true; } } @@ -549,6 +540,7 @@ class Tree { childMatches = true; } } + if( event.eventData.id == gCheckEventId) printWarning("found the event $gCheckEventId"); if( event.eventData.content.toLowerCase().contains(word) || event.eventData.id == word ) { event.eventData.isNotification = true; @@ -644,12 +636,14 @@ class Store { List encryptedChannels = []; List directRooms = []; + List encryptedGroupSecretIds; // event id's of gSecretMessageKind messages, which contain encrypted room secrets + static String startMarkerStr = "" ; static String endMarkerStr = ""; - static const Set typesInEventMap = {0, 1, 3, 4, 5, 7, 40, 42, 140, 141, 142}; // 0 meta, 1 post, 3 follows list, 7 reactions + static const Set typesInEventMap = {0, 1, 3, 4, 5, 7, 40, 42, 140, 141, 142, gSecretMessageKind}; // 0 meta, 1 post, 3 follows list, 7 reactions - Store(this.topPosts, this.allChildEventsMap, this.eventsWithoutParent, this.channels, this.encryptedChannels, this.directRooms) { + Store(this.topPosts, this.allChildEventsMap, this.eventsWithoutParent, this.channels, this.encryptedChannels, this.directRooms, this.encryptedGroupSecretIds) { allChildEventsMap.forEach((eventId, tree) { if( tree.store == null) { tree.setStore(this); @@ -729,10 +723,84 @@ class Store { } // end switch } - static void handleEncryptedChannelEvents( List directRooms, List encryptedChannels, Map tempChildEventsMap, Event ce) { + static String? getEncryptedChannelIdFromSecretMessage( List secretMessageIds, Map tempChildEventsMap, Event eventSecretMessage) { + String evaluatedContent = eventSecretMessage.eventData.evaluatedContent; + + //print("In getEncryptedChannelIdFromSecretMessage: evaluatedContent length = ${evaluatedContent.length}\nevaluatedContent = ${evaluatedContent} "); + if( evaluatedContent.startsWith("App Encrypted Channels:")) { + //print("got App"); + if(evaluatedContent.length == 288) { + String channelId = evaluatedContent.substring(58, 58 + 64); + + if( channelId.length == 64) { + return channelId; + } + + } + } + + return null; + } + + static void createEncryptedRoomFromInvite( List secretMessageIds, List encryptedChannels, Map tempChildEventsMap, Event eventSecretMessage) { + + String? event140Id = getEncryptedChannelIdFromSecretMessage(secretMessageIds, tempChildEventsMap, eventSecretMessage); + Event? event140 = tempChildEventsMap[event140Id]?.event; + if( event140 != null) { + String eId = event140.eventData.id; + + Set participants = {}; + event140.eventData.pTags.forEach((element) { participants.add(element);}); + //print("In createEncryptedRoomFromInvite: processing new enc channel with participants = $participants"); + + String chatRoomId = eId; + try { + dynamic json = jsonDecode(event140.eventData.content); + Channel? channel = getChannel(encryptedChannels, chatRoomId); + if( channel != null) { + // if channel entry already exists, then update its participants info, and name info + if( channel.chatRoomName == "" && json.containsKey('name')) { + channel.chatRoomName = json['name']; + //print("renamed channel to ${channel.chatRoomName}"); + } + if( channel.lastUpdated == 0) { // == 0 only when it was created using a 142 msg. otherwise, don't update it if it was created using 141 + channel.participants = participants; + channel.lastUpdated = event140.eventData.createdAt; + } + channel.creatorPubkey = event140.eventData.pubkey; + + } else { + String roomName = "", roomAbout = ""; + if( json.containsKey('name') ) { + roomName = json['name']??""; + } + + if( json.containsKey('about')) { + roomAbout = json['about']; + } + List emptyMessageList = []; + Channel room = Channel(chatRoomId, roomName, roomAbout, "", emptyMessageList, participants, event140.eventData.createdAt, event140.eventData.pubkey); + //print("created encrypted room with id $chatRoomId and name $roomName"); + encryptedChannels.add( room); + } + } on Exception catch(e) { + if( gDebug > 0) print("In From Event. Event type 140. Json Decode error for event id ${event140.eventData.id}. error = $e"); + } + } // end if 140 + else { + printWarning("could not find event 140 from event $gSecretMessageKind ${eventSecretMessage.eventData.id}"); + } + + } + + static void handleEncryptedChannelEvent( List secretMessageIds, List encryptedChannels, Map tempChildEventsMap, Event ce) { String eId = ce.eventData.id; int eKind = ce.eventData.kind; + if( ce.eventData.createdAt < getSecondsDaysAgo(1)) { + return; // dont process old 142/141 messages cause they're different format + } + switch(eKind) { case 142: { @@ -748,8 +816,8 @@ class Store { channel.addMessageToRoom(eId, tempChildEventsMap); } else { - Channel newChannel = Channel(channelId, "", "", "", [eId], {}, 0); - encryptedChannels.add( newChannel); + //Channel newChannel = Channel(channelId, "", "", "", [eId], {}, 0); + //encryptedChannels.add( newChannel); } } } @@ -791,7 +859,7 @@ class Store { Event ?e = tempChildEventsMap[channel.messageIds[i]]?.event; if( e != null) { //print("num directRooms = ${directRooms.length}"); - e.eventData.translateAndExpand14x(directRooms, encryptedChannels, tempChildEventsMap); + e.eventData.translateAndDecrypt14x(secretMessageIds, encryptedChannels, tempChildEventsMap); } } } @@ -807,9 +875,9 @@ class Store { roomAbout = json['about']; } List emptyMessageList = []; - Channel room = Channel(chatRoomId, roomName, roomAbout, "", emptyMessageList, participants, ce.eventData.createdAt); + //Channel room = Channel(chatRoomId, roomName, roomAbout, "", emptyMessageList, participants, ce.eventData.createdAt); //print("created encrypted room with id $chatRoomId and name $roomName"); - encryptedChannels.add( room); + //encryptedChannels.add( room); } } on Exception catch(e) { if( gDebug > 0) print("In From Event. Event type 140. Json Decode error for event id ${ce.eventData.id}. error = $e"); @@ -817,57 +885,39 @@ class Store { } break; - case 140: - { - Set participants = {}; - ce.eventData.pTags.forEach((element) { participants.add(element);}); - //print("In handleEncryptedChannelEvents: processing new en channel with participants = $participants"); - - String chatRoomId = eId; - try { - dynamic json = jsonDecode(ce.eventData.content); - Channel? channel = getChannel(encryptedChannels, chatRoomId); - if( channel != null) { - // if channel entry already exists, then update its participants info, and name info - if( channel.chatRoomName == "" && json.containsKey('name')) { - channel.chatRoomName = json['name']; - //print("renamed channel to ${channel.chatRoomName}"); - } - if( channel.lastUpdated == 0) { // == 0 only when it was created using a 142 msg. otherwise, don't update it if it was created using 141 - channel.participants = participants; - channel.lastUpdated = ce.eventData.createdAt; - } - channel.creatorPubkey = ce.eventData.pubkey; - - } else { - String roomName = "", roomAbout = ""; - if( json.containsKey('name') ) { - roomName = json['name']??""; - } - - if( json.containsKey('about')) { - roomAbout = json['about']; - } - List emptyMessageList = []; - Channel room = Channel(chatRoomId, roomName, roomAbout, "", emptyMessageList, participants, ce.eventData.createdAt, ce.eventData.pubkey); - //print("created encrypted room with id $chatRoomId and name $roomName"); - encryptedChannels.add( room); - } - } on Exception catch(e) { - if( gDebug > 0) print("In From Event. Event type 140. Json Decode error for event id ${ce.eventData.id}. error = $e"); - } - } - break; default: break; } // end switch } - static void handleDirectMessages( List directRooms, Map tempChildEventsMap, Event ce) { + // returns 1 if message was to the user; adds the secret message id to tempEncyrp... variable + static int handleSecretMessageKind(List tempEncryptedSecretMessageIds, Map tempChildEventsMap, Event ce) { + int eKind = ce.eventData.kind; + + if( gSecretMessageKind != eKind || !isValidDirectMessage(ce.eventData)) { + return 0; + } + + int i = 0; + for(i = 0; i < tempEncryptedSecretMessageIds.length; i++) { + if ( ce.eventData.id == tempEncryptedSecretMessageIds[i]) { + return 0; + } + } + + tempEncryptedSecretMessageIds.add( ce.eventData.id); + + return 1; + + } + + static int handleDirectMessage( List directRooms, Map tempChildEventsMap, Event ce) { String eId = ce.eventData.id; int eKind = ce.eventData.kind; + int numMessagesDecrypted = 0; + if( ce.eventData.id == gCheckEventId) { printInColor("in handleDirectmessge: $gCheckEventId", redColor); } @@ -876,7 +926,7 @@ class Store { if( ce.eventData.id == gCheckEventId) { printInColor("in handleDirectmessge: returning", redColor); } - return; + return 0; } switch(eKind) { @@ -905,7 +955,8 @@ class Store { directRooms.add( newDirectRoom); if( ce.eventData.id == gCheckEventId && gDebug >= 0) print("Adding new message ${ce.eventData.id} to NEW direct room $directRoomId. sender pubkey = ${ce.eventData.pubkey}."); } - ce.eventData.translateAndExpandMentions(directRooms, tempChildEventsMap); + //ce.eventData.translateAndExpandMentions(directRooms, tempChildEventsMap); + if( ce.eventData.evaluatedContent.length > 0) numMessagesDecrypted++; } else { if( gDebug > 0) print("Could not get chat room id for event ${ce.eventData.id} sender pubkey = ${ce.eventData.pubkey}."); } @@ -914,6 +965,8 @@ class Store { default: break; } // end switch + + return numMessagesDecrypted; } /***********************************************************************************************************************************/ @@ -923,7 +976,7 @@ class Store { if( events.isEmpty) { List temp = []; - return Store( [], {}, [], [], [], temp); + return Store( [], {}, [], [], [], temp, []); } // create a map tempChildEventsMap from list of events, key is eventId and value is event itself @@ -945,12 +998,16 @@ class Store { List encryptedChannels = []; List tempDirectRooms = []; Set dummyEventIds = {}; + List tempEncryptedSecretMessageIds = []; int numEventsNotPosts = 0; // just for debugging info int numKind40Events = 0; int numKind42Events = 0; if( gDebug > 0) print("In Tree from Events: after adding all required events of type ${typesInEventMap} to tempChildEventsMap map, its size = ${tempChildEventsMap.length} "); + + log.info('in middle of fromEvents'); + int totoalDirectMessages = 0; tempChildEventsMap.forEach((newEventId, tree) { int eKind = tree.event.eventData.kind; @@ -961,10 +1018,18 @@ class Store { if( eKind == 42 || eKind == 40) { handleChannelEvents(channels, tempChildEventsMap, tree.event); + return; } if( eKind == 4) { - handleDirectMessages(tempDirectRooms, tempChildEventsMap, tree.event); + totoalDirectMessages += handleDirectMessage(tempDirectRooms, tempChildEventsMap, tree.event); + return; + } + + if( eKind == gSecretMessageKind) { + // add the event id to given structure if its a valid message + handleSecretMessageKind(tempEncryptedSecretMessageIds, tempChildEventsMap, tree.event); + return; } // if reacted to event is not in store, then add it to dummy list so it can be fetched @@ -984,17 +1049,10 @@ class Store { print(e); } - // only posts, of kind 1, are added to the main tree structure - if( eKind != 1) { - numEventsNotPosts++; - return; - } - if( tree.event.eventData.id == gCheckEventId) { print("In fromEvent: got evnet id $gCheckEventId"); } - // find its parent and then add this element to that parent Tree String parentId = tree.event.eventData.getParent(tempChildEventsMap); @@ -1011,13 +1069,28 @@ class Store { } if( tempChildEventsMap[parentId]?.event.eventData.kind != 1) { - Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "", [], [], [], [[]], {}), [""], "[json]"); + // first check there isn't already a dummy in top trees + bool dummyParentAlreadyExists = false; + for( int i = 0; i < topLevelTrees.length; i++) { + if( topLevelTrees[i].event.eventData.id == parentId) { + dummyParentAlreadyExists = true; + topLevelTrees[i].children.add(tree); + if( parentId == gCheckEventId) print("7f261931531d1e5c500236725c6cfaea89b7afbe424816d3bfd5d8dfb3ddcec7 already exists as top, as non-kind 1"); + break; + } + } - Tree dummyTopNode = Tree.withoutStore(dummy, []); - dummyTopNode.children.add(tree); + if(!dummyParentAlreadyExists) { + Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "", [], [], [], [[]], {}), [""], "[json]"); + + Tree dummyTopNode = Tree.withoutStore(dummy, []); + dummyTopNode.children.add(tree); + topLevelTrees.add(dummyTopNode); + if( parentId == gCheckEventId) print("7f261931531d1e5c500236725c6cfaea89b7afbe424816d3bfd5d8dfb3ddcec7 added as top, and it is not kind 1"); + } // else is handled in above for loop itself + tempWithoutParent.add(tree.event.eventData.id); - topLevelTrees.add(dummyTopNode); - + //printWarning("added ${tree.event.eventData.id} as a non kind 1 top tree"); // dont add this dummy in dummyEventIds list ( cause that's used to fetch events not in store) } else { tempChildEventsMap[parentId]?.children.add(tree); @@ -1028,13 +1101,30 @@ class Store { if( parentId.length == 64) { // add the dummy evnets to top level trees, so that their real children get printed too with them so no post is missed by reader - Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "Event not loaded", [], [], [], [[]], {}), [""], "[json]"); - Tree dummyTopNode = Tree.withoutStore(dummy, []); - dummyTopNode.children.add(tree); - tempWithoutParent.add(tree.event.eventData.id); - dummyEventIds.add(parentId); - topLevelTrees.add(dummyTopNode); + // first check there isn't already a dummy in top trees + bool dummyParentAlreadyExists = false; + for( int i = 0; i < topLevelTrees.length; i++) { + if( topLevelTrees[i].event.eventData.id == parentId) { + dummyParentAlreadyExists = true; + topLevelTrees[i].children.add(tree); + if( parentId == gCheckEventId) print("7f261931531d1e5c500236725c6cfaea89b7afbe424816d3bfd5d8dfb3ddcec7 already exists as top, as unknown event"); + break; + } + } + + if(!dummyParentAlreadyExists) { + // kind 1 is needed to enable search etc . the dummy pubkey distinguishes it as a dummy node + Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "Event not loaded", [], [], [], [[]], {}), [""], "[json]"); + + Tree dummyTopNode = Tree.withoutStore(dummy, []); + dummyTopNode.children.add(tree); + tempWithoutParent.add(tree.event.eventData.id); + dummyEventIds.add(parentId); + topLevelTrees.add(dummyTopNode); + if( parentId == gCheckEventId) print("7f261931531d1e5c500236725c6cfaea89b7afbe424816d3bfd5d8dfb3ddcec7 added as top, as unknown event"); + } + //printWarning("Added unknown event as top : ${parentId}"); } else { if( gDebug > 0) { @@ -1057,10 +1147,24 @@ class Store { } }); + + // tempEncrytedSecretMessageIds has been created above + // now create encrypted rooms + tempEncryptedSecretMessageIds.forEach((secretEventId) { + Event? secretEvent = tempChildEventsMap[secretEventId]?.event; + + if( secretEvent != null) { + secretEvent.eventData.TranslateAndDecryptSecretMessage(tempChildEventsMap); + //printWarning("created enc room"); + createEncryptedRoomFromInvite(tempEncryptedSecretMessageIds, encryptedChannels, tempChildEventsMap, secretEvent); + } + }); + + tempChildEventsMap.forEach((newEventId, tree) { int eKind = tree.event.eventData.kind; - if( eKind == 142 || eKind == 140 || eKind == 141) { - handleEncryptedChannelEvents(tempDirectRooms, encryptedChannels, tempChildEventsMap, tree.event); + if( eKind == 142 || eKind == 141) { + handleEncryptedChannelEvent(tempEncryptedSecretMessageIds, encryptedChannels, tempChildEventsMap, tree.event); } }); @@ -1073,18 +1177,17 @@ class Store { //log.info("In fromEvents bfore calling SendEventsRequest for ${dummyEventIds.length} dummy evnets"); - if(gDebug != 0) print("In Tree FromEvents: number of events in map which are not kind 1 = ${numEventsNotPosts}"); - if(gDebug != 0) print("In Tree FromEvents: number of events in map of kind 40 = ${numKind40Events}"); - if(gDebug != 0) print("In Tree FromEvents: number of events in map of kind 42 = ${numKind42Events}"); if(gDebug != 0) print("In Tree FromEvents: number of events without parent in fromEvents = ${tempWithoutParent.length}"); + log.info("total direct messages: $totoalDirectMessages"); + // get dummy events sendEventsRequest(gListRelayUrls1, dummyEventIds); //log.info("In fromEvents After calling SendEventsRequest for ${dummyEventIds.length} dummy evnets ids: $dummyEventIds"); // create a dummy top level tree and then create the main Tree object - return Store( topLevelTrees, tempChildEventsMap, tempWithoutParent, channels, encryptedChannels, tempDirectRooms); + return Store( topLevelTrees, tempChildEventsMap, tempWithoutParent, channels, encryptedChannels, tempDirectRooms, tempEncryptedSecretMessageIds); } // end fromEvents() /***********************************************************************************************************************************/ @@ -1142,7 +1245,7 @@ class Store { // expand mentions ( and translate if flag is set) and then add event to main event map; 142 events are expanded later if( newEvent.eventData.kind != 142) - newEvent.eventData.translateAndExpandMentions(directRooms, allChildEventsMap); // this also handles dm decryption for kind 4 messages, for kind 1 will do translation/expansion; + newEvent.eventData.translateAndExpandMentions( allChildEventsMap); // this also handles dm decryption for kind 4 messages, for kind 1 will do translation/expansion; // add them to the main store of the Tree object, but after checking that its not one of the dummy/missing events. // In that case, replace the older dummy event, and only then add it to store-map @@ -1151,9 +1254,9 @@ class Store { Tree tree = topPosts[i]; if( tree.event.eventData.id == newEvent.eventData.id) { // its a replacement. - if( gDebug > 0) log.info("In processIncoming: Replaced old dummy event of id: ${newEvent.eventData.id}"); + if( gDebug >= 0 && newEvent.eventData.id == gCheckEventId) log.info("In processIncoming: Replaced old dummy event of id: ${newEvent.eventData.id}"); tree.event = newEvent; - tree = topPosts.removeAt(i); + //tree = topPosts.removeAt(i); allChildEventsMap[tree.event.eventData.id] = tree; if( newEvent.eventData.createdAt > getSecondsDaysAgo(gDontHighlightEventsOlderThan)) { newEventIdsSet.add(newEvent.eventData.id); @@ -1243,36 +1346,26 @@ class Store { } break; - case 140: case 141: - //print("calling handleEncryptedChannelEvents for kind ${newTree.event.eventData.kind} from processIncoming"); - handleEncryptedChannelEvents(directRooms, encryptedChannels, allChildEventsMap, newTree.event); - break; - case 142: - - newTree.event.eventData.isNotification = true; // highlight it too in next printing - // add 142 chat message event id to its chat room - String channelId = newTree.event.eventData.getChannelIdForMessage(); - if( channelId != "") { - Channel? channel = getChannel(encryptedChannels, channelId); - if( channel != null) { - if( gDebug > 0) print("added event to encrypted chat room in insert event"); - channel.addMessageToRoom(newTree.event.eventData.id, allChildEventsMap); // adds in order - newTree.event.eventData.translateAndExpand14x(directRooms, encryptedChannels, allChildEventsMap); - break; - } else { - Set participants = {}; - newTree.event.eventData.pTags.forEach((element) {participants.add(element);}); - Channel newChannel = Channel(channelId, "", "", "", [], participants, 0); - newChannel.addMessageToRoom(newTree.event.eventData.id, allChildEventsMap); - encryptedChannels.add(newChannel); - newTree.event.eventData.translateAndExpand14x(directRooms, encryptedChannels, allChildEventsMap); - } - } - + //print("calling handleEncryptedChannelEvents for kind ${newTree.event.eventData.kind} from processIncoming"); + handleEncryptedChannelEvent(encryptedGroupSecretIds, encryptedChannels, allChildEventsMap, newTree.event); break; + case gSecretMessageKind: + if( isValidDirectMessage(newTree.event.eventData)) { + //printWarning("1. decrypting secret message with id: ${newTree.event.eventData.id}"); + String ? temp = newTree.event.eventData.TranslateAndDecryptSecretMessage( allChildEventsMap); + if( temp != null) { + //printWarning("added to the secretMesssageIds"); + createEncryptedRoomFromInvite(encryptedGroupSecretIds, encryptedChannels, allChildEventsMap, newTree.event); + } + } else { + //print("1. kind $gSecretMessageKind with id ${newTree.event.eventData.id} is not a valid direct message to user. "); + } + + + break; default: break; @@ -1479,10 +1572,9 @@ class Store { } /** - * @printAllChennelsInfo Print one line information about all channels, which are type 40 events ( class ChatRoom) + * @printAllChennelsInfo Print one line information about all channels, which are type 40 events ( class ChatRoom) and for 14x channels both; channelsToPrint is different for both */ - int printChannelsOverview(List channelstoPrint, int numRoomsOverview, fRoomSelector selector) { - + int printChannelsOverview(List channelstoPrint, int numRoomsOverview, fRoomSelector selector, var tempChildEventsMap , List? secretMessageIds) { channelstoPrint.sort(scrollableCompareTo); int numChannelsActuallyPrinted = 0; @@ -1521,7 +1613,7 @@ class Store { Event? e = allChildEventsMap[messageIds[i]]?.event; if( e!= null) { if( !(e.eventData.kind == 142 && e.eventData.content == e.eventData.evaluatedContent)) { - stdout.write("${e.eventData.getAsLine()}"); + stdout.write("${e.eventData.getAsLine(tempChildEventsMap, secretMessageIds, channelstoPrint)}"); break; // print only one event, the latest one } } @@ -1536,13 +1628,13 @@ class Store { return numChannelsActuallyPrinted; } - void printChannel(Channel room, [int page = 1]) { + void printChannel(Channel room, Map? tempChildEventsMap, List? secretMessageIds, List? encryptedChannels, [int page = 1]) { if( page < 1) { if( gDebug > 0) log.info("In printChannel got page = $page"); page = 1; } - room.printOnePage(allChildEventsMap, page); + room.printOnePage(allChildEventsMap, secretMessageIds, encryptedChannels, page); } // prints some info about the encrypted channel @@ -1569,9 +1661,10 @@ class Store { } + // works for both 4x and 14x channels // shows the given channelId, where channelId is prefix-id or channel name as mentioned in room.name. returns full id of channel. // looks for channelId in id first, then in names. - String showChannel(List listChannels, String channelId, [int page = 1]) { + String showChannel(List listChannels, String channelId, Map? tempChildEventsMap, List? secretMessageIds, List? encryptedChannels, [int page = 1]) { if( channelId.length > 64 ) { return ""; } @@ -1610,13 +1703,10 @@ class Store { return ""; } - printEncryptedChannelInfo(room); - - stdout.write("\n\n"); } - printChannel(room, page); + printChannel(room, tempChildEventsMap, secretMessageIds, encryptedChannels, page); } return fullChannelId.first; } else { @@ -1638,7 +1728,7 @@ class Store { /** * @printDirectRoomInfo Print one line information about chat rooms */ - int printDirectRoomInfo(fRoomSelector roomSelector) { + int printDirectRoomInfo(fRoomSelector roomSelector, var tempChildEventsMap) { directRooms.sort(scrollableCompareTo); int numNotificationRooms = 0; @@ -1674,7 +1764,7 @@ class Store { numRoomsActuallyPrinted++; Event? e = allChildEventsMap[messageIds[i]]?.event; if( e!= null) { - String line = e.eventData.getAsLine(); + String line = e.eventData.getAsLine(tempChildEventsMap, null, null); stdout.write(line); break; // print only one event, the latest one } @@ -2280,7 +2370,7 @@ Store getTree(Set events) { if(gDebug > 0) log.info("Warning: In printEventsAsTree: events length = 0"); List temp =[]; - return Store([], {}, [], [], [], temp); + return Store([], {}, [], [], [], temp, []); } // remove posts older than 20 days or so @@ -2310,13 +2400,19 @@ Store getTree(Set events) { if( gDebug > 0) log.info("Calling fromEvents for ${events.length} events."); // create tree from events - //log.info("Before calling fromEvents for ${events.length} events"); + log.info("Before calling fromEvents for ${events.length} events"); Store node = Store.fromEvents(events); - //log.info("After calling fromEvents with ${node.allChildEventsMap.length} events in its internal store"); + log.info("After calling fromEvents with ${node.allChildEventsMap.length} events in its internal store"); // translate and expand mentions for all ( both take 0.5 sec for 20k events) - events.where((element) => element.eventData.kind != 142).forEach( (event) => event.eventData.translateAndExpandMentions(node.directRooms, node.allChildEventsMap));; - events.where((element) => element.eventData.kind == 142).forEach( (event) => event.eventData.translateAndExpand14x(node.directRooms, node.encryptedChannels, node.allChildEventsMap));; + log.info('before calling expandmentions'); + events.where((element) => [1, 42, gSecretMessageKind].contains(element.eventData.kind)).forEach( (event) => event.eventData.translateAndExpandMentions( node.allChildEventsMap));; + + + + log.info('between calling expandmentions'); + events.where((element) => element.eventData.kind == 142).forEach( (event) => event.eventData.translateAndDecrypt14x(node.encryptedGroupSecretIds, node.encryptedChannels, node.allChildEventsMap));; + log.info('after calling expandmentions'); if( gDebug > 0) log.info("expand mentions finished."); if(gDebug > 0) print("total number of posts/replies in main tree = ${node.count()}"); diff --git a/pubspec.yaml b/pubspec.yaml index b11189d..7823c21 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,10 @@ name: nostr_console description: A multi-platform nostr client built for terminal/console. -version: 0.1.8-beta +version: 0.1.9-beta homepage: https://github.com/vishalxl/nostr_console -# Release 0.1.8-beta -# menu changed -# build +# Release 0.1.9-beta +# used kind 104 environment: sdk: '>=2.17.3 <3.0.0' diff --git a/test/nostr_console_test.dart b/test/nostr_console_test.dart index e712758..3b2defd 100644 --- a/test/nostr_console_test.dart +++ b/test/nostr_console_test.dart @@ -10,7 +10,7 @@ EventData exampleEdataChild = EventData("id2", "pubkey", 1111111, 1, "content ch Event exampleEvent = Event('event', 'id3', exampleEdata, ['relay name'], "[json]"); Event exampleEventChild = Event('event', 'id4', exampleEdataChild, ['relay name'], "[json]"); -Store exampleStore = Store([], {}, [], [], [], []); +Store exampleStore = Store([], {}, [], [], [], [], []); Tree exampleTree = Tree.withoutStore(exampleEvent, []); //bool skipTest = true;