Added support for kind 1 mentions of events (issue 30)

And '/reply id' for encrypted group chat.
This commit is contained in:
Vishal
2022-11-22 01:58:36 +05:30
parent 0eb0380bea
commit 3eb5aadb7a
3 changed files with 116 additions and 46 deletions

View File

@@ -87,10 +87,10 @@ Future<void> sendReplyPostLike(Store node, String replyToId, String replyKind, S
} }
// Sends a public channel message // Sends a public channel message
Future<void> sendPublicChannelMessage(Store node, String channelId, String messageToSend, String replyKind) async { Future<void> sendChannelMessage(Store node, Channel channel, String messageToSend, String replyKind) async {
messageToSend = addEscapeChars(messageToSend); messageToSend = addEscapeChars(messageToSend);
String strTags = node.getTagStr(channelId, exename); String strTags = node.getTagStrForChannel(channel, exename);
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
String id = getShaId(userPublicKey, createdAt.toString(), replyKind, strTags, messageToSend); String id = getShaId(userPublicKey, createdAt.toString(), replyKind, strTags, messageToSend);
@@ -109,11 +109,11 @@ Future<void> sendPublicChannelMessage(Store node, String channelId, String messa
} }
// Sends a public channel message // Sends a public channel message
Future<void> sendPublicChannelReply(Store node, Channel channel, String replyTo, String messageToSend, String replyKind) async { Future<void> sendChannelReply(Store node, Channel channel, String replyTo, String messageToSend, String replyKind) async {
messageToSend = addEscapeChars(messageToSend); messageToSend = addEscapeChars(messageToSend);
String strTags = node.getTagStrForChannel(channel, replyTo, exename); String strTags = node.getTagStrForChannelReply(channel, replyTo, exename);
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000; int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
String id = getShaId(userPublicKey, createdAt.toString(), replyKind, strTags, messageToSend); String id = getShaId(userPublicKey, createdAt.toString(), replyKind, strTags, messageToSend);
@@ -812,7 +812,7 @@ Future<void> channelMenuUI(Store node) async {
if( channel != null) { if( channel != null) {
//print("sending reply |$actualMessage|"); //print("sending reply |$actualMessage|");
await sendPublicChannelReply(node, channel, replyTo, actualMessage, "42"); await sendChannelReply(node, channel, replyTo, actualMessage, "42");
pageNum = 1; // reset it pageNum = 1; // reset it
} }
} }
@@ -820,8 +820,12 @@ Future<void> channelMenuUI(Store node) async {
} else { } else {
// send message to the given room // send message to the given room
//print("sending message |$messageToSend|"); //print("sending message |$messageToSend|");
await sendPublicChannelMessage(node, fullChannelId, messageToSend, "42"); Channel? channel = node.getChannelFromId(node.channels, fullChannelId);
if( channel != null) {
await sendChannelMessage(node, channel, messageToSend, "42");
pageNum = 1; // reset it pageNum = 1; // reset it
}
} }
} }
@@ -1079,19 +1083,42 @@ Future<void> encryptedChannelMenuUI(Store node) async {
if( messageToSend.startsWith('/remove ')) { if( messageToSend.startsWith('/remove ')) {
// TODO finish // TODO finish
continue; //continue;
} }
// send message to the given room // send message to the given room
String encryptedMessageToSend = encryptChannelMessage(node, fullChannelId, messageToSend); Channel? channel = node.getChannelFromId(node.encryptedChannels, fullChannelId);
if( encryptedMessageToSend != "") { if( messageToSend.length >= 7 && messageToSend.substring(0, 7).compareTo("/reply ") == 0) {
await sendPublicChannelMessage(node, fullChannelId, encryptedMessageToSend, "142"); List<String> tokens = messageToSend.split(' ');
if( tokens.length >= 3) {
String replyTo = tokens[1];
String actualMessage = messageToSend.substring(7);
if( messageToSend.indexOf(tokens[1]) + tokens[1].length < messageToSend.length)
actualMessage = messageToSend.substring( messageToSend.indexOf(tokens[1]) + tokens[1].length + 1);
if( channel != null) {
String encryptedMessageToSend = encryptChannelMessage(node, fullChannelId, actualMessage);
if( encryptChannelMessage != "") {
await sendChannelReply(node, channel, replyTo, encryptedMessageToSend, "142");
pageNum = 1; // reset it pageNum = 1; // reset it
} else {
printWarning("\nCould not encrypt and send message. Do confirm that you have access to this encrypted channel");
} }
} }
} }
} else {
if( channel != null) {
String encryptedMessageToSend = encryptChannelMessage(node, fullChannelId, messageToSend);
if( encryptChannelMessage != "") {
await sendChannelMessage(node, channel, encryptedMessageToSend, "142");
pageNum = 1; // reset it
}
} else {
printWarning("\nCould not get send message because could not get channel id.");
}
}
}
}
} }
} else { } else {
print("Refreshing..."); print("Refreshing...");

View File

@@ -24,6 +24,8 @@ GoogleTranslator? translator; // initialized in main when argument given
const int gNumTranslateDays = 2;// translate for this number of days const int gNumTranslateDays = 2;// translate for this number of days
bool gTranslate = false; // translate flag bool gTranslate = false; // translate flag
List<String> nip08PlaceHolders = ["#[0]", "#[1]", "#[2]", "#[3]", "#[4]", "#[5]", "#[6]", "#[7]", "#[8]", "#[9]" ];
// Structure to store kind 0 event meta data, and kind 3 meta data for each user. Will have info from latest // Structure to store kind 0 event meta data, and kind 3 meta data for each user. Will have info from latest
// kind 0 event and/or kind 3 event, both with their own time stamps. // kind 0 event and/or kind 3 event, both with their own time stamps.
class UserNameInfo { class UserNameInfo {
@@ -143,8 +145,6 @@ class EventData {
// returns the immediate kind 1 parent // returns the immediate kind 1 parent
String getParent(Map<String, Tree> allEventsMap) { String getParent(Map<String, Tree> allEventsMap) {
if( eTags.isNotEmpty) { if( eTags.isNotEmpty) {
int numRoot = 0, numReply = 0; int numRoot = 0, numReply = 0;
@@ -188,8 +188,16 @@ class EventData {
// if reply/root tags don't work, then try to look for parent tag with the deprecated logic from NIP-10 // if reply/root tags don't work, then try to look for parent tag with the deprecated logic from NIP-10
if( gDebug > 0) log.info("using deprecated logic of nip10 for event id : $id"); if( gDebug > 0) log.info("using deprecated logic of nip10 for event id : $id");
for( int i = eTags.length - 1; i >= 0; i--) { for( int i = tags.length - 1; i >= 0; i--) {
String eventId = eTags[i][0]; if( tags[i][0] == "e") {
String eventId = tags[i][1];
// ignore this e tag if its mentioned in the body of the event
String placeholder = nip08PlaceHolders[i];
if( content.contains(placeholder)) {
continue;
}
if( allEventsMap[eventId]?.event.eventData.kind == 1) { if( allEventsMap[eventId]?.event.eventData.kind == 1) {
String? parentId = allEventsMap[eventId]?.event.eventData.id; String? parentId = allEventsMap[eventId]?.event.eventData.id;
if( parentId != null) { if( parentId != null) {
@@ -201,6 +209,7 @@ class EventData {
return eventId; return eventId;
} }
} }
}
} }
return ""; return "";
@@ -302,7 +311,7 @@ class EventData {
{}); {});
} }
String expandMentions(String content) { String expandMentions(String content, Map<String, Tree> tempChildEventsMap) {
if( id == gCheckEventId) { if( id == gCheckEventId) {
printInColor("in expandMentions: decoding $gCheckEventId\n", redColor); printInColor("in expandMentions: decoding $gCheckEventId\n", redColor);
} }
@@ -318,14 +327,25 @@ class EventData {
} }
// replace the patterns // replace the patterns
List<String> placeHolders = ["#[0]", "#[1]", "#[2]", "#[3]", "#[4]", "#[5]", "#[6]", "#[7]" ];
for(int i = 0; i < placeHolders.length && i < tags.length; i++) { for(int i = 0; i < nip08PlaceHolders.length && i < tags.length; i++) {
int index = -1; int index = -1;
Pattern p = placeHolders[i]; Pattern p = nip08PlaceHolders[i];
if( (index = content.indexOf(p)) != -1 ) { if( (index = content.indexOf(p)) != -1 ) {
String mentionedId = tags[i][1];
if( tags[i].length >= 2) { if( tags[i].length >= 2) {
String author = getAuthorName(tags[i][1]); if( gKindONames.containsKey(mentionedId)) {
String author = getAuthorName(mentionedId);
content = "${content.substring(0, index)}@$author${content.substring(index + 4)}"; content = "${content.substring(0, index)}@$author${content.substring(index + 4)}";
} else {
EventData? eventData = tempChildEventsMap[mentionedId]?.event.eventData??null;
if( eventData != null) {
String quotedAuthor = getAuthorName(eventData.pubkey);
String prefixId = mentionedId.substring(0, 3);
String quote = "<Quoted event id '$prefixId' by $quotedAuthor: \"${eventData.evaluatedContent}\">";
content = "${content.substring(0, index)}$quote${content.substring(index + 4)}";
}
}
} }
} }
} }
@@ -349,7 +369,7 @@ class EventData {
switch(kind) { switch(kind) {
case 1: case 1:
case 42: case 42:
evaluatedContent = expandMentions(content); evaluatedContent = expandMentions(content, tempChildEventsMap);
if( translator != null && gTranslate && !evaluatedContent.isEnglish()) { if( translator != null && gTranslate && !evaluatedContent.isEnglish()) {
if( gDebug > 0) print("found that this comment is non-English: $evaluatedContent"); if( gDebug > 0) print("found that this comment is non-English: $evaluatedContent");
@@ -389,7 +409,7 @@ class EventData {
String? decrypted = decryptDirectMessage(); String? decrypted = decryptDirectMessage();
if( decrypted != null) { if( decrypted != null) {
evaluatedContent = decrypted; evaluatedContent = decrypted;
evaluatedContent = expandMentions(evaluatedContent); evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
} }
break; break;
@@ -416,7 +436,7 @@ class EventData {
String? decrypted = decryptEncryptedChannelMessage(directRooms, encryptedChannels, tempChildEventsMap); String? decrypted = decryptEncryptedChannelMessage(directRooms, encryptedChannels, tempChildEventsMap);
if( decrypted != null) { if( decrypted != null) {
evaluatedContent = decrypted; evaluatedContent = decrypted;
evaluatedContent = expandMentions(evaluatedContent); evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
} }
break; break;
default: default:
@@ -497,6 +517,9 @@ class EventData {
//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) {
return "";
}
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);
@@ -703,15 +726,17 @@ class EventData {
String strReplyTo = ""; String strReplyTo = "";
if( replyToEvent != null) { if( replyToEvent != null) {
//print("in getStrForChannel: got replyTo id = ${replyToEvent.eventData.id}"); //print("in getStrForChannel: got replyTo id = ${replyToEvent.eventData.id}");
if( replyToEvent.eventData.kind == 1 || replyToEvent.eventData.kind == 42) { // make sure its a kind 1 or 40 message if( replyToEvent.eventData.kind == 1 || replyToEvent.eventData.kind == 42 || replyToEvent.eventData.kind == 142) { // make sure its a kind 1 or 40 message
if( replyToEvent.eventData.id != id) { // basic self test if( replyToEvent.eventData.id != id) { // basic self test
strReplyTo = 'In reply to:"${replyToEvent.eventData.content}"'; strReplyTo = 'In reply to:"${replyToEvent.eventData.evaluatedContent}"';
strReplyTo = makeParagraphAtDepth(strReplyTo, finalContentDepthInSpaces + 6); // one extra for content strReplyTo = makeParagraphAtDepth(strReplyTo, finalContentDepthInSpaces + 6); // one extra for content
// add reply to string to end of the content. How it will show: // add reply to string to end of the content. How it will show:
contentShifted += ( "\n" + getNumSpaces( contentPlacementColumn + gSpacesPerDepth) + strReplyTo); contentShifted += ( "\n" + getNumSpaces( contentPlacementColumn + gSpacesPerDepth) + strReplyTo);
} }
} }
} else {
//printWarning("no reply to event for event id $id");
} }
String msgId = id.substring(0, 3).padLeft(gSpacesPerDepth~/2).padRight(gSpacesPerDepth) ; String msgId = id.substring(0, 3).padLeft(gSpacesPerDepth~/2).padRight(gSpacesPerDepth) ;
@@ -761,7 +786,7 @@ class EventData {
return reactorNames; return reactorNames;
} }
// returns the last e tag as reply to event for kind 42 events // returns the last e tag as reply to event for kind 42 and 142 events
Event? getReplyToEvent() { Event? getReplyToEvent() {
for(int i = tags.length - 1; i >= 0; i--) { for(int i = tags.length - 1; i >= 0; i--) {
List tag = tags[i]; List tag = tags[i];
@@ -769,7 +794,7 @@ class EventData {
String replyToEventId = tag[1]; String replyToEventId = tag[1];
Event? eventInReplyTo = (gStore?.allChildEventsMap[replyToEventId]?.event)??null; Event? eventInReplyTo = (gStore?.allChildEventsMap[replyToEventId]?.event)??null;
if( eventInReplyTo != null) { if( eventInReplyTo != null) {
if ( [1,42].contains( eventInReplyTo.eventData.kind)) { if ( [1,42,142].contains( eventInReplyTo.eventData.kind)) {
return eventInReplyTo; return eventInReplyTo;
} }
} }

View File

@@ -851,11 +851,12 @@ class Store {
print("In fromEvent: got evnet id $gCheckEventId"); print("In fromEvent: got evnet id $gCheckEventId");
} }
if(tree.event.eventData.eTags.isNotEmpty ) {
// is not a parent, 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);
if( parentId != "") {
if( tree.event.eventData.id == gCheckEventId) { if( tree.event.eventData.id == gCheckEventId) {
if(gDebug >= 0) print("In Tree FromEvents: e tag not empty. its parent id = $parentId for id: $gCheckEventId"); if(gDebug >= 0) print("In Tree FromEvents: e tag not empty. its parent id = $parentId for id: $gCheckEventId");
} }
@@ -900,6 +901,9 @@ class Store {
} }
} }
} else {
// is not a parent, has no parent tag. then make it its own top tree, which will be done later in this function
} }
}); // going over tempChildEventsMap and adding children to their parent's .children list }); // going over tempChildEventsMap and adding children to their parent's .children list
@@ -919,7 +923,7 @@ class Store {
// add parent trees as top level child trees of this tree // add parent trees as top level child trees of this tree
for( var tree in tempChildEventsMap.values) { for( var tree in tempChildEventsMap.values) {
if( tree.event.eventData.kind == 1 && tree.event.eventData.eTags.isEmpty) { // only posts which are parents if( tree.event.eventData.kind == 1 && tree.event.eventData.getParent(tempChildEventsMap) == "") { // only posts which are parents
topLevelTrees.add(tree); topLevelTrees.add(tree);
} }
} }
@@ -1031,12 +1035,12 @@ class Store {
switch(newTree.event.eventData.kind) { switch(newTree.event.eventData.kind) {
case 1: case 1:
// only kind 1 events are added to the overall tree structure // only kind 1 events are added to the overall tree structure
if( newTree.event.eventData.eTags.isEmpty) { String parentId = newTree.event.eventData.getParent(allChildEventsMap);
// if its a new parent event, then add it to the main top parents ( this.children) if( parentId == "") {
// if its a new parent event, then add it to the main top parents
topPosts.add(newTree); topPosts.add(newTree);
} else { } else {
// if it has a parent , then add the newTree as the parent's child // if it has a parent , then add the newTree as the parent's child
String parentId = newTree.event.eventData.getParent(allChildEventsMap);
if( allChildEventsMap.containsKey(parentId)) { if( allChildEventsMap.containsKey(parentId)) {
allChildEventsMap[parentId]?.children.add(newTree); allChildEventsMap[parentId]?.children.add(newTree);
} else { } else {
@@ -1746,7 +1750,21 @@ class Store {
* Also adds 'client' tag with application name. * Also adds 'client' tag with application name.
* @parameter replyToId First few letters of an event id for which reply is being made * @parameter replyToId First few letters of an event id for which reply is being made
*/ */
String getTagStrForChannel(Channel channel, String replyToId, String clientName, [bool addAllP = false]) { String getTagStrForChannel(Channel channel, String clientName, [bool addAllP = false]) {
String channelId = channel.channelId;
clientName = (clientName == "")? "nostr_console": clientName; // in case its empty
String strTags = "";
strTags += '["e","$channelId"],';
strTags += '["client","$clientName"]' ;
return strTags;
}
/*
* @getTagsFromEvent Searches for all events, and creates a json of e-tag type which can be sent with event
* Also adds 'client' tag with application name.
* @parameter replyToId First few letters of an event id for which reply is being made
*/
String getTagStrForChannelReply(Channel channel, String replyToId, String clientName, [bool addAllP = false]) {
String channelId = channel.channelId; String channelId = channel.channelId;
clientName = (clientName == "")? "nostr_console": clientName; // in case its empty clientName = (clientName == "")? "nostr_console": clientName; // in case its empty