used kind 104 to send shared secret for kind 14x

also internally improved logic that encrypted channels internal structures are created by first looking at kind 104  (secret kind id)

mentioned version 0.1.9-beta
This commit is contained in:
Vishal 2022-11-25 19:26:12 +05:30
parent 03317a1dfa
commit 32bc7d2bc0
8 changed files with 457 additions and 232 deletions

View File

@ -304,8 +304,13 @@ Future<void> main(List<String> arguments) async {
getMultiUserEvents(gListRelayUrls1, gDefaultFollows, limitPerSubscription, getSecondsDaysAgo(limitFollowPosts)); getMultiUserEvents(gListRelayUrls1, gDefaultFollows, limitPerSubscription, getSecondsDaysAgo(limitFollowPosts));
// get group and meta info events // get group and meta info events
getKindEvents([0, 3, 40, 41, 140, 141], gListRelayUrls1, 3 * limitPerSubscription, getSecondsDaysAgo(limitMetaInfoEvents)); // get all type 3 etc getKindEvents([0, 3, 40, 41], gListRelayUrls1, 3 * limitPerSubscription, getSecondsDaysAgo(limitMetaInfoEvents)); // get all type 3 etc
getKindEvents([42, 142], gListRelayUrls1, limitPerSubscription * 3, getSecondsDaysAgo( limitFollowPosts)); // 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) // TODO get all 40 events, and then get all #e for them ( responses to them)

View File

@ -132,12 +132,14 @@ Future<void> sendChannelReply(Store node, Channel channel, String replyTo, Strin
} }
// send DM // send DM
Future<void> sendDirectMessage(Store node, String otherPubkey, String messageToSend) async { Future<void> sendDirectMessage(Store node, String otherPubkey, String messageToSend, {String replyKind = "4"}) async {
//messageToSend = addEscapeChars(messageToSend); since this get encrypted , it does not need escaping //messageToSend = addEscapeChars(messageToSend); since this get encrypted , it does not need escaping
String otherPubkey02 = "02" + otherPubkey; String otherPubkey02 = "02" + otherPubkey;
String encryptedMessageToSend = myEncrypt(userPrivateKey, otherPubkey02, messageToSend); String encryptedMessageToSend = myEncrypt(userPrivateKey, otherPubkey02, messageToSend);
String replyKind = "4"; //print("in sendDirectMessage: replyKind = $replyKind");
//String replyKind = "4";
String strTags = '["p","$otherPubkey"]'; String strTags = '["p","$otherPubkey"]';
strTags += gWhetherToSendClientTag?',["client","nostr_console"]':''; strTags += gWhetherToSendClientTag?',["client","nostr_console"]':'';
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
@ -609,7 +611,7 @@ Future<void> channelMenuUI(Store node) async {
//await processNotifications(node); // this takes 300 ms //await processNotifications(node); // this takes 300 ms
if( !justShowedChannels) { if( !justShowedChannels) {
printInColor(" Public Channels ", gCommentColor); printInColor(" Public Channels ", gCommentColor);
node.printChannelsOverview(node.channels, 20, selectorShowAllRooms); node.printChannelsOverview(node.channels, 20, selectorShowAllRooms, node.allChildEventsMap, null);
justShowedChannels = true; justShowedChannels = true;
} }
@ -645,7 +647,7 @@ Future<void> channelMenuUI(Store node) async {
firstIteration = false; 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 == "") { if( fullChannelId == "") {
//print("Could not find the given channel."); //print("Could not find the given channel.");
showChannelOption = false; showChannelOption = false;
@ -719,7 +721,7 @@ Future<void> channelMenuUI(Store node) async {
case 2: case 2:
clearScreen(); clearScreen();
printInColor(" All Public Channels ", gCommentColor); 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; justShowedChannels = true;
break; break;
@ -777,7 +779,7 @@ Future<void> createEncryptedChannel(Store node) async {
String messageToSend = "App Encrypted Channels: inviting you to encrypted channel $newEncryptedChannelId encrypted using private public keys $channelPriKey $channelPubKey"; 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++) { for( int i = 0; i < participants.length; i++) {
// send message to all ( including self which is in that list) // 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 await processAnyIncomingEvents(node, false); // get latest event, this takes 300 ms
@ -788,7 +790,7 @@ Future<void> updateEncryptedChannel(Store node, String channelId,
String channelName, String channelAbout, String channelPic, String content, String tags, String channelName, String channelAbout, String channelPic, String content, String tags,
Set<String> participants, Set<String> newParticipants) async { Set<String> participants, Set<String> newParticipants) async {
List<String> keys = getEncryptedChannelKeys(node.directRooms, node.allChildEventsMap, channelId); List<String> keys = getEncryptedChannelKeys(node.encryptedGroupSecretIds, node.allChildEventsMap, channelId);
if( keys.length == 2) { if( keys.length == 2) {
String channelPriKey = keys[0], channelPubKey = keys[1]; String channelPriKey = keys[0], channelPubKey = keys[1];
@ -797,7 +799,7 @@ Future<void> updateEncryptedChannel(Store node, String channelId,
// send message to all new participants // send message to all new participants
newParticipants.forEach((participant) async { newParticipants.forEach((participant) async {
await sendDirectMessage(node, participant, messageToSend); await sendDirectMessage(node, participant, messageToSend, replyKind: gSecretMessageKind.toString());
}); });
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
@ -815,7 +817,7 @@ Future<void> updateEncryptedChannel(Store node, String channelId,
String encryptChannelMessage(Store node, String channelId, String messageToSend) { String encryptChannelMessage(Store node, String channelId, String messageToSend) {
String encryptedMessage = ''; String encryptedMessage = '';
List<String> keys = getEncryptedChannelKeys(node.directRooms, node.allChildEventsMap, channelId); List<String> keys = getEncryptedChannelKeys(node.encryptedGroupSecretIds, node.allChildEventsMap, channelId);
if( keys.length != 2) { if( keys.length != 2) {
return ''; return '';
} }
@ -888,7 +890,7 @@ Future<void> encryptedChannelMenuUI(Store node) async {
if( !justShowedChannels) { if( !justShowedChannels) {
printInColor(" Encrypted Channels ", gCommentColor); printInColor(" Encrypted Channels ", gCommentColor);
node.printChannelsOverview(node.encryptedChannels, 20, selectorShowAllRooms); node.printChannelsOverview(node.encryptedChannels, 20, selectorShowAllRooms, node.allChildEventsMap, node.encryptedGroupSecretIds);
justShowedChannels = true; justShowedChannels = true;
} }
@ -926,7 +928,7 @@ Future<void> encryptedChannelMenuUI(Store node) async {
firstIteration = false; 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 == "") { if( fullChannelId == "") {
//print("Could not find the given channel."); //print("Could not find the given channel.");
showChannelOption = false; showChannelOption = false;
@ -1007,7 +1009,7 @@ Future<void> encryptedChannelMenuUI(Store node) async {
case 2: case 2:
clearScreen(); clearScreen();
printInColor(" All Encrypted Channels ", gCommentColor); 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; justShowedChannels = true;
break; break;
@ -1036,7 +1038,7 @@ Future<void> PrivateMenuUI(Store node) async {
await processAnyIncomingEvents(node, true); // this takes 300 ms await processAnyIncomingEvents(node, true); // this takes 300 ms
printInColor(" Direct Messages", gCommentColor); 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. 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"""; 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"); print("\n");
bool showNotifications (ScrollableMessages room) => room.selectorNotifications(); bool showNotifications (ScrollableMessages room) => room.selectorNotifications();
int numDirectRoomsPrinted = node.printDirectRoomInfo(showNotifications); int numDirectRoomsPrinted = node.printDirectRoomInfo( showNotifications, node.allChildEventsMap);
if( numDirectRoomsPrinted > 0) if( numDirectRoomsPrinted > 0)
print("\n"); print("\n");

View File

@ -281,7 +281,7 @@ class EventData {
} }
} else { } else {
int eKind = json['kind']; 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++) { for( int i = 0; i < numTags; i++) {
var tag = jsonTags[i]; var tag = jsonTags[i];
@ -338,7 +338,7 @@ class EventData {
} }
// replace the patterns // replace the patterns
//print("in main body of expandmentions");
for(int i = 0; i < nip08PlaceHolders.length && i < tags.length; i++) { for(int i = 0; i < nip08PlaceHolders.length && i < tags.length; i++) {
int index = -1; int index = -1;
Pattern p = nip08PlaceHolders[i]; Pattern p = nip08PlaceHolders[i];
@ -364,19 +364,18 @@ class EventData {
} }
// is called only once for each event received ( or read from file) // is called only once for each event received ( or read from file)
void translateAndExpandMentions(List<DirectMessageRoom> directRooms, Map<String, Tree> tempChildEventsMap) { void translateAndExpandMentions(Map<String, Tree> tempChildEventsMap) {
if( id == gCheckEventId) { if( id == gCheckEventId) {
printInColor("in translateAndExpandMensitons: decoding $gCheckEventId\n", redColor); //printInColor("in translateAndExpandMentions: decoding $gCheckEventId\n", redColor);
} }
if (content == "" || evaluatedContent != "") { if (content == "" || evaluatedContent != "") {
if( id == gCheckEventId) { if( id == gCheckEventId) {
printInColor("in translateAndExpandMensitons: returning \n", redColor); //printInColor("in translateAndExpandMentions: returning \n", redColor);
} }
return; return;
} }
switch(kind) { switch(kind) {
case 1: case 1:
case 42: case 42:
@ -404,6 +403,64 @@ class EventData {
} }
break; break;
} // end switch
return;
} // end translateAndExpandMentions
// is called only once for each event received ( or read from file)
String? TranslateAndDecryptSecretMessage(Map<String, Tree> 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<String, Tree> 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: case 4:
if( userPrivateKey == ""){ // cant process if private key not given if( userPrivateKey == ""){ // cant process if private key not given
break; break;
@ -417,37 +474,63 @@ class EventData {
printInColor("in translateAndExpandMensitons: gonna decrypt \n", redColor); printInColor("in translateAndExpandMensitons: gonna decrypt \n", redColor);
} }
//log.info("decrypting a message of kind 4");
String? decrypted = decryptDirectMessage(); String? decrypted = decryptDirectMessage();
if( decrypted != null) { if( decrypted != null) {
evaluatedContent = decrypted; evaluatedContent = decrypted;
evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap); evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
} }
//print("evaluatedContent: $evaluatedContent");
break; break;
} // end switch } // end switch
} // end translateAndExpandMentions } // end translateAndExpandMentions
// is called only once for each event received ( or read from file) // is called only once for each event received ( or read from file)
void translateAndExpand14x(List<DirectMessageRoom> directRooms, List<Channel> encryptedChannels, Map<String, Tree> tempChildEventsMap) { void translateAndDecrypt14x(List<String> secretMessageIds, List<Channel> encryptedChannels, Map<String, Tree> tempChildEventsMap) {
if( id == gCheckEventId) { if( id == gCheckEventId) {
printInColor("in translateAndExpandMensitons: decoding ee810ea73072af056cceaa6d051b4fcce60739247f7bcc752e72fa5defb64f09\n", redColor); //printInColor("in translateAndExpand14x: decoding ee810ea73072af056cceaa6d051b4fcce60739247f7bcc752e72fa5defb64f09\n", redColor);
} }
if (content == "" || evaluatedContent != "") { if (content == "" || evaluatedContent != "") {
if( id == gCheckEventId) { if( id == gCheckEventId) {
printInColor("in translateAndExpandMensitons: returning \n", redColor); //printInColor("in translateAndExpand14x: returning \n", redColor);
} }
return; return;
} }
if( createdAt < getSecondsDaysAgo(1)) {
//print("old 142. not decrypting");
return;
}
switch(kind) { switch(kind) {
case 142: 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) { if( decrypted != null) {
//printWarning("Successfully decrypted kind 142: $id");
evaluatedContent = decrypted; evaluatedContent = decrypted;
//print("in translateAndDecrypt14x: calling expandMentions");
evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap); evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
//print("content = $content");
//print(evaluatedContent);
} }
break; break;
default: default:
@ -502,34 +585,21 @@ class EventData {
} }
String? decryptEncryptedChannelMessage(List<DirectMessageRoom> directRooms, List<Channel> encryptedChannels,Map<String, Tree> tempChildEventsMap) { String? decryptEncryptedChannelMessage(List<String> secretMessageIds, Map<String, Tree> 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;
}
if( id == "865c9352de11a3959c06fce5350c5a1b9fa0475d3234078a1bb45d152b370f0b") { // known issue if( id == "865c9352de11a3959c06fce5350c5a1b9fa0475d3234078a1bb45d152b370f0b") { // known issue
//print("\n\ngoing to decrypt b1ab66ac50f00f3c3bbc91e5b9e03fc8e79e3fdb9f6d5c9ae9777aa6ca3020a2"); //print("\n\ngoing to decrypt b1ab66ac50f00f3c3bbc91e5b9e03fc8e79e3fdb9f6d5c9ae9777aa6ca3020a2");
//print(channel.participants); //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"); //print("In decryptEncryptedChannelMessage: for event of kind 142 with event id = $id");
int ivIndex = content.indexOf("?iv="); int ivIndex = content.indexOf("?iv=");
if( ivIndex == -1) { if( ivIndex == -1) {
return ""; return null;
} }
var iv = content.substring( ivIndex + 4, content.length); var iv = content.substring( ivIndex + 4, content.length);
var enc_str = content.substring(0, ivIndex); var enc_str = content.substring(0, ivIndex);
@ -537,11 +607,11 @@ class EventData {
String channelId = getChannelIdForMessage(); String channelId = getChannelIdForMessage();
//print("In decryptEncryptedChannelMessage: got channel id $channelId"); //print("In decryptEncryptedChannelMessage: got channel id $channelId");
List<String> keys = []; List<String> keys = [];
keys = getEncryptedChannelKeys(directRooms, tempChildEventsMap, channelId); keys = getEncryptedChannelKeys(secretMessageIds, tempChildEventsMap, channelId);
if( keys.length != 2) { if( keys.length != 2) {
//print("Could not get keys for event id: $id and channelId: $channelId"); print("Could not get keys for event id: $id and channelId: $channelId");
return ""; return null;
} }
//print("\nevent id: $id"); //print("\nevent id: $id");
@ -551,6 +621,7 @@ class EventData {
String pubKey = "02" + keys[1]; 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 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; 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 // 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<String, Tree>? tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? encryptedChannels) {
if( !(kind == 1 || kind == 4 || kind == 42)) { if( !(kind == 1 || kind == 4 || kind == 42)) {
return; // only print kind 1 and 42 and 4 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; int n = 4;
String maxN(String v) => v.length > n? v.substring(0,n) : v.substring(0, v.length); 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); stdout.write(strToPrint);
} }
String getAsLine({int len = 20}) { String getAsLine(var tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? 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; String contentToPrint = evaluatedContent.isEmpty? content: evaluatedContent;
if( len == 0 || len > contentToPrint.length) { if( len == 0 || len > contentToPrint.length) {
//len = contentToPrint.length; //len = contentToPrint.length;
} }
contentToPrint = contentToPrint.replaceAll("\n", " "); contentToPrint = contentToPrint.replaceAll("\n", " ");
contentToPrint = contentToPrint.replaceAll("\r", " "); contentToPrint = contentToPrint.replaceAll("\r", " ");
contentToPrint = contentToPrint.replaceAll("\t", " "); contentToPrint = contentToPrint.replaceAll("\t", " ");
@ -691,10 +788,26 @@ class EventData {
paddedStrToPrint = "$gNotificationColor$paddedStrToPrint$gColorEndMarker"; paddedStrToPrint = "$gNotificationColor$paddedStrToPrint$gColorEndMarker";
isNotification = false; isNotification = false;
} }
//print("returning $paddedStrToPrint");
return paddedStrToPrint; return paddedStrToPrint;
} }
String getStrForChannel(int depth) {
String getStrForChannel(int depth, var tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? 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 strToPrint = "";
String name = getAuthorName(pubkey); String name = getAuthorName(pubkey);
String strDate = getPrintableDate(createdAt); String strDate = getPrintableDate(createdAt);
@ -859,7 +972,7 @@ class Event {
} }
void printEvent(int depth, bool topPost) { void printEvent(int depth, bool topPost) {
eventData.printEventData(depth, topPost); eventData.printEventData(depth, topPost, null, null, null);
//stdout.write("\n$originalJson --------------------------------\n\n"); //stdout.write("\n$originalJson --------------------------------\n\n");
} }
@ -1463,21 +1576,32 @@ bool isValidDirectMessage(EventData directMessageData) {
bool validUserMessage = false; bool validUserMessage = false;
List<String> allPtags = []; List<String> 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) { directMessageData.tags.forEach((tag) {
if( tag.length < 2 ) if( tag.length < 2 ) {
return; return;
}
if( tag[0] == "p" && tag[1].length == 64) { // basic length sanity test if( tag[0] == "p" && tag[1].length == 64) { // basic length sanity test
allPtags.add(tag[1]); 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( 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 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 validUserMessage = true; // case where this user is sender
} }
} else { } else {
if(gCheckEventId == directMessageData.id) print("in else case 3");
if ( directMessageData.pubkey != userPublicKey) { 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) { if( allPtags.length == 1 && allPtags[0] == userPublicKey) {
validUserMessage = true; // case where this user is recipeint validUserMessage = true; // case where this user is recipeint
} }
} }
@ -1656,40 +1780,36 @@ bool isValidPubkey(String pubkey) {
} }
List<String> getEncryptedChannelKeys(List<DirectMessageRoom> directRooms, Map<String, Tree> tempChildEventsMap, String channelId) { List<String> getEncryptedChannelKeys(List<String> secretMessageIds, Map<String, Tree> tempChildEventsMap, String channelId) {
Event? e = tempChildEventsMap[channelId]?.event; Event? e = tempChildEventsMap[channelId]?.event;
if( e != null) { if( e != null) {
//print("\n----------------\nIn getEncryptedChannelKeys for encrypted channel $channelId"); //print("\n----------------\nIn getEncryptedChannelKeys for encrypted channel $channelId");
String creatorPubKey = e.eventData.pubkey; 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) { for( int j = 0; j < secretMessageIds.length; j++) {
return [priKey, pubKey]; 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");
} }
} }
} }

View File

@ -184,7 +184,7 @@ class Relays {
if( rEvents.add(e) ) { if( rEvents.add(e) ) {
uniqueIdsRecieved.add(id); uniqueIdsRecieved.add(id);
String receivedSubscription = json[1]; 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 > 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"); 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");

View File

@ -3,7 +3,7 @@ import 'package:logging/logging.dart';
// name of executable // name of executable
const String exename = "nostr_console"; const String exename = "nostr_console";
const String version = "0.1.8-beta"; const String version = "0.1.9-beta";
int gDebug = 0; int gDebug = 0;
int gSpecificDebug = 0; int gSpecificDebug = 0;
@ -11,10 +11,13 @@ int gSpecificDebug = 0;
final log = Logger('ExampleLogger'); final log = Logger('ExampleLogger');
// for debugging // for debugging
String gCheckEventId = " e74e93fbc77af5275f29db688931f725813ab1385f16233bfa609078a8779dfa"; String gCheckEventId = "0082a613c9f4d43f796a427d15db74f10ac64212e28f465870ceaf099f488087";
int gDefaultNumWaitSeconds = 8000; // is used in main() int gDefaultNumWaitSeconds = 8000; // is used in main()
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// encrypted Group settings
const int gSecretMessageKind = 104;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// file related settings //////////////////////////////////////////////////////////////////////////////////////////////////////////////// file related settings
const String gDefaultEventsFilename = "all_nostr_events.txt"; const String gDefaultEventsFilename = "all_nostr_events.txt";
String gEventsFilename = ""; // is set in arguments, and if set, then file is read from and written to String gEventsFilename = ""; // is set in arguments, and if set, then file is read from and written to

View File

@ -240,7 +240,7 @@ class ScrollableMessages {
void printOnePage(Map<String, Tree> tempChildEventsMap, [int page = 1]) { void printOnePage(Map<String, Tree> tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? encryptedChannels, [int page = 1] ) {
if( page < 1) { if( page < 1) {
if( gDebug > 0) log.info("In ScrollableMessages::printOnepage got page = $page"); if( gDebug > 0) log.info("In ScrollableMessages::printOnepage got page = $page");
page = 1; page = 1;
@ -266,8 +266,8 @@ class ScrollableMessages {
String eId = messageIds[i]; String eId = messageIds[i];
Event? e = tempChildEventsMap[eId]?.event; Event? e = tempChildEventsMap[eId]?.event;
if( e!= null) { 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 //printWarning("in print one page");
print(e.eventData.getStrForChannel(0)); 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"); if( gDebug > 0) log.info("In printChannel got page = $page");
page = 1; 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 // 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 hasReaction = false;
bool childMatches = 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<List<String>>? reactions = gReactions[event.eventData.id]; List<List<String>>? reactions = gReactions[event.eventData.id];
if( reactions != null) { if( reactions != null) {
if( reactions.length > 0) { if( reactions.length > 0) {
// has reactions event.eventData.isNotification = true;
reactions.forEach((reaction) { return true;
// 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;
}
}
});
} }
} }
} }
if( event.eventData.pubkey == pk && children.length > 0) { if( event.eventData.pubkey == pubkey && children.length > 0) {
for( int i = 0; i < children.length; i++ ) { for( int i = 0; i < children.length; i++ ) {
children.forEach((child) { children.forEach((child) {
// if child is someone else then set notifications and flag, means there are replies to this event // 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++ ) { for( int i = 0; i < children.length; i++ ) {
if( children[i].treeSelectorRepliesAndLikes(pk)) { if( children[i].treeSelectorRepliesAndLikes(pubkey)) {
childMatches = true; childMatches = true;
} }
} }
@ -549,6 +540,7 @@ class Tree {
childMatches = true; childMatches = true;
} }
} }
if( event.eventData.id == gCheckEventId) printWarning("found the event $gCheckEventId");
if( event.eventData.content.toLowerCase().contains(word) || event.eventData.id == word ) { if( event.eventData.content.toLowerCase().contains(word) || event.eventData.id == word ) {
event.eventData.isNotification = true; event.eventData.isNotification = true;
@ -644,12 +636,14 @@ class Store {
List<Channel> encryptedChannels = []; List<Channel> encryptedChannels = [];
List<DirectMessageRoom> directRooms = []; List<DirectMessageRoom> directRooms = [];
List<String> encryptedGroupSecretIds; // event id's of gSecretMessageKind messages, which contain encrypted room secrets
static String startMarkerStr = "" ; static String startMarkerStr = "" ;
static String endMarkerStr = ""; static String endMarkerStr = "";
static const Set<int> typesInEventMap = {0, 1, 3, 4, 5, 7, 40, 42, 140, 141, 142}; // 0 meta, 1 post, 3 follows list, 7 reactions static const Set<int> 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) { allChildEventsMap.forEach((eventId, tree) {
if( tree.store == null) { if( tree.store == null) {
tree.setStore(this); tree.setStore(this);
@ -729,10 +723,84 @@ class Store {
} // end switch } // end switch
} }
static void handleEncryptedChannelEvents( List<DirectMessageRoom> directRooms, List<Channel> encryptedChannels, Map<String, Tree> tempChildEventsMap, Event ce) { static String? getEncryptedChannelIdFromSecretMessage( List<String> secretMessageIds, Map<String, Tree> 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<String> secretMessageIds, List<Channel> encryptedChannels, Map<String, Tree> tempChildEventsMap, Event eventSecretMessage) {
String? event140Id = getEncryptedChannelIdFromSecretMessage(secretMessageIds, tempChildEventsMap, eventSecretMessage);
Event? event140 = tempChildEventsMap[event140Id]?.event;
if( event140 != null) {
String eId = event140.eventData.id;
Set<String> 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<String> 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<String> secretMessageIds, List<Channel> encryptedChannels, Map<String, Tree> tempChildEventsMap, Event ce) {
String eId = ce.eventData.id; String eId = ce.eventData.id;
int eKind = ce.eventData.kind; 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) { switch(eKind) {
case 142: case 142:
{ {
@ -748,8 +816,8 @@ class Store {
channel.addMessageToRoom(eId, tempChildEventsMap); channel.addMessageToRoom(eId, tempChildEventsMap);
} else { } else {
Channel newChannel = Channel(channelId, "", "", "", [eId], {}, 0); //Channel newChannel = Channel(channelId, "", "", "", [eId], {}, 0);
encryptedChannels.add( newChannel); //encryptedChannels.add( newChannel);
} }
} }
} }
@ -791,7 +859,7 @@ class Store {
Event ?e = tempChildEventsMap[channel.messageIds[i]]?.event; Event ?e = tempChildEventsMap[channel.messageIds[i]]?.event;
if( e != null) { if( e != null) {
//print("num directRooms = ${directRooms.length}"); //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']; roomAbout = json['about'];
} }
List<String> emptyMessageList = []; List<String> 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"); //print("created encrypted room with id $chatRoomId and name $roomName");
encryptedChannels.add( room); //encryptedChannels.add( room);
} }
} on Exception catch(e) { } 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"); 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; break;
case 140:
{
Set<String> 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<String> 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: default:
break; break;
} // end switch } // end switch
} }
static void handleDirectMessages( List<DirectMessageRoom> directRooms, Map<String, Tree> tempChildEventsMap, Event ce) { // returns 1 if message was to the user; adds the secret message id to tempEncyrp... variable
static int handleSecretMessageKind(List<String> tempEncryptedSecretMessageIds, Map<String, Tree> 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<DirectMessageRoom> directRooms, Map<String, Tree> tempChildEventsMap, Event ce) {
String eId = ce.eventData.id; String eId = ce.eventData.id;
int eKind = ce.eventData.kind; int eKind = ce.eventData.kind;
int numMessagesDecrypted = 0;
if( ce.eventData.id == gCheckEventId) { if( ce.eventData.id == gCheckEventId) {
printInColor("in handleDirectmessge: $gCheckEventId", redColor); printInColor("in handleDirectmessge: $gCheckEventId", redColor);
} }
@ -876,7 +926,7 @@ class Store {
if( ce.eventData.id == gCheckEventId) { if( ce.eventData.id == gCheckEventId) {
printInColor("in handleDirectmessge: returning", redColor); printInColor("in handleDirectmessge: returning", redColor);
} }
return; return 0;
} }
switch(eKind) { switch(eKind) {
@ -905,7 +955,8 @@ class Store {
directRooms.add( newDirectRoom); 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}."); 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 { } else {
if( gDebug > 0) print("Could not get chat room id for event ${ce.eventData.id} sender pubkey = ${ce.eventData.pubkey}."); 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: default:
break; break;
} // end switch } // end switch
return numMessagesDecrypted;
} }
/***********************************************************************************************************************************/ /***********************************************************************************************************************************/
@ -923,7 +976,7 @@ class Store {
if( events.isEmpty) { if( events.isEmpty) {
List<DirectMessageRoom> temp = []; List<DirectMessageRoom> temp = [];
return Store( [], {}, [], [], [], temp); return Store( [], {}, [], [], [], temp, []);
} }
// create a map tempChildEventsMap from list of events, key is eventId and value is event itself // create a map tempChildEventsMap from list of events, key is eventId and value is event itself
@ -945,12 +998,16 @@ class Store {
List<Channel> encryptedChannels = []; List<Channel> encryptedChannels = [];
List<DirectMessageRoom> tempDirectRooms = []; List<DirectMessageRoom> tempDirectRooms = [];
Set<String> dummyEventIds = {}; Set<String> dummyEventIds = {};
List<String> tempEncryptedSecretMessageIds = [];
int numEventsNotPosts = 0; // just for debugging info int numEventsNotPosts = 0; // just for debugging info
int numKind40Events = 0; int numKind40Events = 0;
int numKind42Events = 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} "); 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) { tempChildEventsMap.forEach((newEventId, tree) {
int eKind = tree.event.eventData.kind; int eKind = tree.event.eventData.kind;
@ -961,10 +1018,18 @@ class Store {
if( eKind == 42 || eKind == 40) { if( eKind == 42 || eKind == 40) {
handleChannelEvents(channels, tempChildEventsMap, tree.event); handleChannelEvents(channels, tempChildEventsMap, tree.event);
return;
} }
if( eKind == 4) { 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 // 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); 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) { if( tree.event.eventData.id == gCheckEventId) {
print("In fromEvent: got evnet id $gCheckEventId"); print("In fromEvent: got evnet id $gCheckEventId");
} }
// find its parent and then add this element to that parent Tree // find its parent and then add this element to that parent Tree
String parentId = tree.event.eventData.getParent(tempChildEventsMap); String parentId = tree.event.eventData.getParent(tempChildEventsMap);
@ -1011,13 +1069,28 @@ class Store {
} }
if( tempChildEventsMap[parentId]?.event.eventData.kind != 1) { if( tempChildEventsMap[parentId]?.event.eventData.kind != 1) {
Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "<Parent is not of Kind 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, []); if(!dummyParentAlreadyExists) {
dummyTopNode.children.add(tree); Event dummy = Event("","", EventData(parentId,gDummyAccountPubkey, tree.event.eventData.createdAt, 1, "<Parent is not of Kind 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); 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) // dont add this dummy in dummyEventIds list ( cause that's used to fetch events not in store)
} else { } else {
tempChildEventsMap[parentId]?.children.add(tree); tempChildEventsMap[parentId]?.children.add(tree);
@ -1028,13 +1101,30 @@ class Store {
if( parentId.length == 64) { 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 // 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, []); // first check there isn't already a dummy in top trees
dummyTopNode.children.add(tree); bool dummyParentAlreadyExists = false;
tempWithoutParent.add(tree.event.eventData.id); for( int i = 0; i < topLevelTrees.length; i++) {
dummyEventIds.add(parentId); if( topLevelTrees[i].event.eventData.id == parentId) {
topLevelTrees.add(dummyTopNode); 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 { else {
if( gDebug > 0) { 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) { tempChildEventsMap.forEach((newEventId, tree) {
int eKind = tree.event.eventData.kind; int eKind = tree.event.eventData.kind;
if( eKind == 142 || eKind == 140 || eKind == 141) { if( eKind == 142 || eKind == 141) {
handleEncryptedChannelEvents(tempDirectRooms, encryptedChannels, tempChildEventsMap, tree.event); handleEncryptedChannelEvent(tempEncryptedSecretMessageIds, encryptedChannels, tempChildEventsMap, tree.event);
} }
}); });
@ -1073,18 +1177,17 @@ class Store {
//log.info("In fromEvents bfore calling SendEventsRequest for ${dummyEventIds.length} dummy evnets"); //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}"); 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 // get dummy events
sendEventsRequest(gListRelayUrls1, dummyEventIds); sendEventsRequest(gListRelayUrls1, dummyEventIds);
//log.info("In fromEvents After calling SendEventsRequest for ${dummyEventIds.length} dummy evnets ids: $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 // 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() } // 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 // 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) 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. // 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 // 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]; Tree tree = topPosts[i];
if( tree.event.eventData.id == newEvent.eventData.id) { if( tree.event.eventData.id == newEvent.eventData.id) {
// its a replacement. // 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.event = newEvent;
tree = topPosts.removeAt(i); //tree = topPosts.removeAt(i);
allChildEventsMap[tree.event.eventData.id] = tree; allChildEventsMap[tree.event.eventData.id] = tree;
if( newEvent.eventData.createdAt > getSecondsDaysAgo(gDontHighlightEventsOlderThan)) { if( newEvent.eventData.createdAt > getSecondsDaysAgo(gDontHighlightEventsOlderThan)) {
newEventIdsSet.add(newEvent.eventData.id); newEventIdsSet.add(newEvent.eventData.id);
@ -1243,36 +1346,26 @@ class Store {
} }
break; break;
case 140:
case 141: case 141:
//print("calling handleEncryptedChannelEvents for kind ${newTree.event.eventData.kind} from processIncoming");
handleEncryptedChannelEvents(directRooms, encryptedChannels, allChildEventsMap, newTree.event);
break;
case 142: case 142:
//print("calling handleEncryptedChannelEvents for kind ${newTree.event.eventData.kind} from processIncoming");
newTree.event.eventData.isNotification = true; // highlight it too in next printing handleEncryptedChannelEvent(encryptedGroupSecretIds, encryptedChannels, allChildEventsMap, newTree.event);
// 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<String> 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);
}
}
break; 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: default:
break; 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<Channel> channelstoPrint, int numRoomsOverview, fRoomSelector selector) { int printChannelsOverview(List<Channel> channelstoPrint, int numRoomsOverview, fRoomSelector selector, var tempChildEventsMap , List<String>? secretMessageIds) {
channelstoPrint.sort(scrollableCompareTo); channelstoPrint.sort(scrollableCompareTo);
int numChannelsActuallyPrinted = 0; int numChannelsActuallyPrinted = 0;
@ -1521,7 +1613,7 @@ class Store {
Event? e = allChildEventsMap[messageIds[i]]?.event; Event? e = allChildEventsMap[messageIds[i]]?.event;
if( e!= null) { if( e!= null) {
if( !(e.eventData.kind == 142 && e.eventData.content == e.eventData.evaluatedContent)) { 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 break; // print only one event, the latest one
} }
} }
@ -1536,13 +1628,13 @@ class Store {
return numChannelsActuallyPrinted; return numChannelsActuallyPrinted;
} }
void printChannel(Channel room, [int page = 1]) { void printChannel(Channel room, Map<String, Tree>? tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? encryptedChannels, [int page = 1]) {
if( page < 1) { if( page < 1) {
if( gDebug > 0) log.info("In printChannel got page = $page"); if( gDebug > 0) log.info("In printChannel got page = $page");
page = 1; page = 1;
} }
room.printOnePage(allChildEventsMap, page); room.printOnePage(allChildEventsMap, secretMessageIds, encryptedChannels, page);
} }
// prints some info about the encrypted channel // 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. // 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. // looks for channelId in id first, then in names.
String showChannel(List<Channel> listChannels, String channelId, [int page = 1]) { String showChannel(List<Channel> listChannels, String channelId, Map<String, Tree>? tempChildEventsMap, List<String>? secretMessageIds, List<Channel>? encryptedChannels, [int page = 1]) {
if( channelId.length > 64 ) { if( channelId.length > 64 ) {
return ""; return "";
} }
@ -1610,13 +1703,10 @@ class Store {
return ""; return "";
} }
printEncryptedChannelInfo(room); printEncryptedChannelInfo(room);
stdout.write("\n\n"); stdout.write("\n\n");
} }
printChannel(room, page); printChannel(room, tempChildEventsMap, secretMessageIds, encryptedChannels, page);
} }
return fullChannelId.first; return fullChannelId.first;
} else { } else {
@ -1638,7 +1728,7 @@ class Store {
/** /**
* @printDirectRoomInfo Print one line information about chat rooms * @printDirectRoomInfo Print one line information about chat rooms
*/ */
int printDirectRoomInfo(fRoomSelector roomSelector) { int printDirectRoomInfo(fRoomSelector roomSelector, var tempChildEventsMap) {
directRooms.sort(scrollableCompareTo); directRooms.sort(scrollableCompareTo);
int numNotificationRooms = 0; int numNotificationRooms = 0;
@ -1674,7 +1764,7 @@ class Store {
numRoomsActuallyPrinted++; numRoomsActuallyPrinted++;
Event? e = allChildEventsMap[messageIds[i]]?.event; Event? e = allChildEventsMap[messageIds[i]]?.event;
if( e!= null) { if( e!= null) {
String line = e.eventData.getAsLine(); String line = e.eventData.getAsLine(tempChildEventsMap, null, null);
stdout.write(line); stdout.write(line);
break; // print only one event, the latest one break; // print only one event, the latest one
} }
@ -2280,7 +2370,7 @@ Store getTree(Set<Event> events) {
if(gDebug > 0) log.info("Warning: In printEventsAsTree: events length = 0"); if(gDebug > 0) log.info("Warning: In printEventsAsTree: events length = 0");
List<DirectMessageRoom> temp =[]; List<DirectMessageRoom> temp =[];
return Store([], {}, [], [], [], temp); return Store([], {}, [], [], [], temp, []);
} }
// remove posts older than 20 days or so // remove posts older than 20 days or so
@ -2310,13 +2400,19 @@ Store getTree(Set<Event> events) {
if( gDebug > 0) log.info("Calling fromEvents for ${events.length} events."); if( gDebug > 0) log.info("Calling fromEvents for ${events.length} events.");
// create tree from 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); 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) // 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));; log.info('before calling expandmentions');
events.where((element) => element.eventData.kind == 142).forEach( (event) => event.eventData.translateAndExpand14x(node.directRooms, node.encryptedChannels, node.allChildEventsMap));; 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) log.info("expand mentions finished.");
if(gDebug > 0) print("total number of posts/replies in main tree = ${node.count()}"); if(gDebug > 0) print("total number of posts/replies in main tree = ${node.count()}");

View File

@ -1,11 +1,10 @@
name: nostr_console name: nostr_console
description: A multi-platform nostr client built for terminal/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 homepage: https://github.com/vishalxl/nostr_console
# Release 0.1.8-beta # Release 0.1.9-beta
# menu changed # used kind 104
# build
environment: environment:
sdk: '>=2.17.3 <3.0.0' sdk: '>=2.17.3 <3.0.0'

View File

@ -10,7 +10,7 @@ EventData exampleEdataChild = EventData("id2", "pubkey", 1111111, 1, "content ch
Event exampleEvent = Event('event', 'id3', exampleEdata, ['relay name'], "[json]"); Event exampleEvent = Event('event', 'id3', exampleEdata, ['relay name'], "[json]");
Event exampleEventChild = Event('event', 'id4', exampleEdataChild, ['relay name'], "[json]"); Event exampleEventChild = Event('event', 'id4', exampleEdataChild, ['relay name'], "[json]");
Store exampleStore = Store([], {}, [], [], [], []); Store exampleStore = Store([], {}, [], [], [], [], []);
Tree exampleTree = Tree.withoutStore(exampleEvent, []); Tree exampleTree = Tree.withoutStore(exampleEvent, []);
//bool skipTest = true; //bool skipTest = true;