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
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);
String strTags = node.getTagStr(channelId, exename);
String strTags = node.getTagStrForChannel(channel, exename);
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
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
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);
String strTags = node.getTagStrForChannel(channel, replyTo, exename);
String strTags = node.getTagStrForChannelReply(channel, replyTo, exename);
int createdAt = DateTime.now().millisecondsSinceEpoch ~/1000;
String id = getShaId(userPublicKey, createdAt.toString(), replyKind, strTags, messageToSend);
@ -812,7 +812,7 @@ Future<void> channelMenuUI(Store node) async {
if( channel != null) {
//print("sending reply |$actualMessage|");
await sendPublicChannelReply(node, channel, replyTo, actualMessage, "42");
await sendChannelReply(node, channel, replyTo, actualMessage, "42");
pageNum = 1; // reset it
}
}
@ -820,8 +820,12 @@ Future<void> channelMenuUI(Store node) async {
} else {
// send message to the given room
//print("sending message |$messageToSend|");
await sendPublicChannelMessage(node, fullChannelId, messageToSend, "42");
pageNum = 1; // reset it
Channel? channel = node.getChannelFromId(node.channels, fullChannelId);
if( channel != null) {
await sendChannelMessage(node, channel, messageToSend, "42");
pageNum = 1; // reset it
}
}
}
@ -1079,17 +1083,40 @@ Future<void> encryptedChannelMenuUI(Store node) async {
if( messageToSend.startsWith('/remove ')) {
// TODO finish
continue;
//continue;
}
// send message to the given room
String encryptedMessageToSend = encryptChannelMessage(node, fullChannelId, messageToSend);
if( encryptedMessageToSend != "") {
await sendPublicChannelMessage(node, fullChannelId, encryptedMessageToSend, "142");
pageNum = 1; // reset it
Channel? channel = node.getChannelFromId(node.encryptedChannels, fullChannelId);
if( messageToSend.length >= 7 && messageToSend.substring(0, 7).compareTo("/reply ") == 0) {
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
}
}
}
} else {
printWarning("\nCould not encrypt and send message. Do confirm that you have access to this encrypted channel");
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.");
}
}
}
}
}

View File

@ -24,6 +24,8 @@ GoogleTranslator? translator; // initialized in main when argument given
const int gNumTranslateDays = 2;// translate for this number of days
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
// kind 0 event and/or kind 3 event, both with their own time stamps.
class UserNameInfo {
@ -143,8 +145,6 @@ class EventData {
// returns the immediate kind 1 parent
String getParent(Map<String, Tree> allEventsMap) {
if( eTags.isNotEmpty) {
int numRoot = 0, numReply = 0;
@ -188,17 +188,26 @@ class EventData {
// 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");
for( int i = eTags.length - 1; i >= 0; i--) {
String eventId = eTags[i][0];
if( allEventsMap[eventId]?.event.eventData.kind == 1) {
String? parentId = allEventsMap[eventId]?.event.eventData.id;
if( parentId != null) {
return parentId;
for( int i = tags.length - 1; i >= 0; i--) {
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) {
String? parentId = allEventsMap[eventId]?.event.eventData.id;
if( parentId != null) {
return parentId;
}
} else {
// if first e tag ( from end, which is the immediate parent) does not exist in the store, then return that eventID still.
// Child comment would get a dummy parent, and called could then fetch that event
return eventId;
}
} else {
// if first e tag ( from end, which is the immediate parent) does not exist in the store, then return that eventID still.
// Child comment would get a dummy parent, and called could then fetch that event
return eventId;
}
}
@ -302,7 +311,7 @@ class EventData {
{});
}
String expandMentions(String content) {
String expandMentions(String content, Map<String, Tree> tempChildEventsMap) {
if( id == gCheckEventId) {
printInColor("in expandMentions: decoding $gCheckEventId\n", redColor);
}
@ -318,14 +327,25 @@ class EventData {
}
// 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;
Pattern p = placeHolders[i];
Pattern p = nip08PlaceHolders[i];
if( (index = content.indexOf(p)) != -1 ) {
String mentionedId = tags[i][1];
if( tags[i].length >= 2) {
String author = getAuthorName(tags[i][1]);
content = "${content.substring(0, index)}@$author${content.substring(index + 4)}";
if( gKindONames.containsKey(mentionedId)) {
String author = getAuthorName(mentionedId);
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) {
case 1:
case 42:
evaluatedContent = expandMentions(content);
evaluatedContent = expandMentions(content, tempChildEventsMap);
if( translator != null && gTranslate && !evaluatedContent.isEnglish()) {
if( gDebug > 0) print("found that this comment is non-English: $evaluatedContent");
@ -389,7 +409,7 @@ class EventData {
String? decrypted = decryptDirectMessage();
if( decrypted != null) {
evaluatedContent = decrypted;
evaluatedContent = expandMentions(evaluatedContent);
evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
}
break;
@ -416,7 +436,7 @@ class EventData {
String? decrypted = decryptEncryptedChannelMessage(directRooms, encryptedChannels, tempChildEventsMap);
if( decrypted != null) {
evaluatedContent = decrypted;
evaluatedContent = expandMentions(evaluatedContent);
evaluatedContent = expandMentions(evaluatedContent, tempChildEventsMap);
}
break;
default:
@ -497,6 +517,9 @@ class EventData {
//print("In decryptEncryptedChannelMessage: for event of kind 142 with event id = $id");
int ivIndex = content.indexOf("?iv=");
if( ivIndex == -1) {
return "";
}
var iv = content.substring( ivIndex + 4, content.length);
var enc_str = content.substring(0, ivIndex);
@ -703,15 +726,17 @@ class EventData {
String strReplyTo = "";
if( replyToEvent != null) {
//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
strReplyTo = 'In reply to:"${replyToEvent.eventData.content}"';
strReplyTo = 'In reply to:"${replyToEvent.eventData.evaluatedContent}"';
strReplyTo = makeParagraphAtDepth(strReplyTo, finalContentDepthInSpaces + 6); // one extra for content
// add reply to string to end of the content. How it will show:
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) ;
@ -761,7 +786,7 @@ class EventData {
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() {
for(int i = tags.length - 1; i >= 0; i--) {
List tag = tags[i];
@ -769,7 +794,7 @@ class EventData {
String replyToEventId = tag[1];
Event? eventInReplyTo = (gStore?.allChildEventsMap[replyToEventId]?.event)??null;
if( eventInReplyTo != null) {
if ( [1,42].contains( eventInReplyTo.eventData.kind)) {
if ( [1,42,142].contains( eventInReplyTo.eventData.kind)) {
return eventInReplyTo;
}
}

View File

@ -851,10 +851,11 @@ class Store {
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
String parentId = tree.event.eventData.getParent(tempChildEventsMap);
// find its parent and then add this element to that parent Tree
String parentId = tree.event.eventData.getParent(tempChildEventsMap);
if( parentId != "") {
if( tree.event.eventData.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
@ -919,7 +923,7 @@ class Store {
// add parent trees as top level child trees of this tree
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);
}
}
@ -1031,12 +1035,12 @@ class Store {
switch(newTree.event.eventData.kind) {
case 1:
// only kind 1 events are added to the overall tree structure
if( newTree.event.eventData.eTags.isEmpty) {
// if its a new parent event, then add it to the main top parents ( this.children)
String parentId = newTree.event.eventData.getParent(allChildEventsMap);
if( parentId == "") {
// if its a new parent event, then add it to the main top parents
topPosts.add(newTree);
} else {
// 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)) {
allChildEventsMap[parentId]?.children.add(newTree);
} else {
@ -1746,7 +1750,21 @@ class Store {
* Also adds 'client' tag with application name.
* @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;
clientName = (clientName == "")? "nostr_console": clientName; // in case its empty