2022-08-16 16:08:33 +05:30
import ' dart:io ' ;
import ' dart:convert ' ;
2022-09-04 18:34:25 +05:30
import ' dart:math ' ;
2022-10-30 11:46:51 +05:30
import ' package:bip340/bip340.dart ' ;
2022-08-16 16:08:33 +05:30
import ' package:intl/intl.dart ' ;
2022-09-15 00:49:13 +05:30
import ' package:nostr_console/tree_ds.dart ' ;
2022-12-29 02:20:58 +05:30
import ' package:nostr_console/user.dart ' ;
2022-11-28 12:09:37 +05:30
import ' package:nostr_console/utils.dart ' ;
2022-08-23 04:36:31 +05:30
import ' package:translator/translator.dart ' ;
2022-08-24 22:07:03 +05:30
import ' package:crypto/crypto.dart ' ;
import ' package:nostr_console/settings.dart ' ;
2022-09-01 21:55:51 +05:30
import " dart:typed_data " ;
import ' dart:convert ' as convert ;
import " package:pointycastle/export.dart " ;
2022-09-02 01:14:23 +05:30
import ' package:kepler/kepler.dart ' ;
2022-11-15 07:55:35 +05:30
import ' package:http/http.dart ' as http ;
2024-04-06 21:23:17 +05:30
import ' package:nostr_console/nip_019.dart ' ;
2022-11-15 07:55:35 +05:30
2022-09-10 20:56:14 +05:30
String getStrInColor ( String s , String commentColor ) = > stdout . supportsAnsiEscapes ? " $ commentColor $ s $ gColorEndMarker " : s ;
2022-10-30 00:27:11 +05:30
void printInColor ( String s , String commentColor ) = > stdout . supportsAnsiEscapes ? stdout . write ( " $ commentColor $ s $ gColorEndMarker " ) : stdout . write ( s ) ;
2022-11-02 20:29:34 +05:30
void printWarning ( String s ) = > stdout . supportsAnsiEscapes ? stdout . write ( " $ gWarningColor $ s $ gColorEndMarker \n " ) : stdout . write ( " $ s \n " ) ;
2022-10-30 00:27:11 +05:30
2022-08-29 04:59:28 +05:30
// translate
2022-09-02 01:57:11 +05:30
GoogleTranslator ? translator ; // initialized in main when argument given
2022-11-26 21:03:08 +05:30
const int gNumTranslateDays = 1 ; // translate for this number of days
2022-08-29 04:59:28 +05:30
bool gTranslate = false ; // translate flag
2022-12-22 18:42:54 +05:30
int numEventsTranslated = 0 ;
2022-08-29 04:59:28 +05:30
2022-12-22 18:42:54 +05:30
List < String > nip08PlaceHolders = [ " #[0] " , " #[1] " , " #[2] " , " #[3] " , " #[4] " , " #[5] " , " #[6] " , " #[7] " , " #[8] " , " #[9] " , " #[10] " , " #[11] " , " #[12] " ] ;
2022-11-22 01:58:36 +05:30
2022-12-28 11:18:06 +05:30
2022-09-01 15:53:17 +05:30
// 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.
2022-08-29 04:59:28 +05:30
class UserNameInfo {
2022-08-31 22:15:17 +05:30
int ? createdAt ;
2022-12-29 21:22:23 +05:30
String ? name , about , picture , lud06 , lud16 , display_name , website ;
2022-08-31 22:15:17 +05:30
int ? createdAtKind3 ;
Event ? latestContactEvent ;
2022-11-15 07:55:35 +05:30
bool nip05Verified ;
2022-11-26 01:04:12 +05:30
String ? nip05Id ;
2024-03-31 17:14:01 +05:30
UserNameInfo ( this . createdAt , this . name , this . about , this . picture , this . lud06 , this . lud16 , this . display_name , this . website , this . nip05Id , this . latestContactEvent , [ this . createdAtKind3 , this . nip05Verified = false ] ) ;
2022-08-29 04:59:28 +05:30
}
/ *
* global user names from kind 0 events , mapped from public key to a 3 element array of [ name , about , picture ]
* JSON object { name: < username > , about: < string > , picture: < url , string > }
* only has info from latest kind 0 event
* /
Map < String , UserNameInfo > gKindONames = { } ;
2022-11-24 18:20:31 +05:30
// global reactions entry. Map of form <id of event reacted to, List of Reactors>
// reach Reactor is a list of 2-elements ( first is pubkey of reactor event, second is comment)
2022-12-26 12:12:50 +05:30
// each eventID -> multiple [ pubkey, comment ]
2022-08-29 04:59:28 +05:30
Map < String , List < List < String > > > gReactions = { } ;
2022-12-26 12:12:50 +05:30
// for the given eventID returns the pubkeys of reactors
Set < String > getReactorPubkeys ( String eventId ) {
Set < String > reactorIds = { } ;
List < List < String > > ? reactions = gReactions [ eventId ] ;
if ( reactions ! = null ) {
2024-03-31 17:14:01 +05:30
for ( var reaction in reactions ) { reactorIds . add ( reaction [ 0 ] ) ; }
2022-12-26 12:12:50 +05:30
}
return reactorIds ;
}
2022-08-22 04:06:18 +05:30
// global contact list of each user, including of the logged in user.
// maps from pubkey of a user, to the latest contact list of that user, which is the latest kind 3 message
// is updated as kind 3 events are received
Map < String , List < Contact > > gContactLists = { } ;
2022-11-09 23:30:18 +05:30
bool verifyEvent ( dynamic json ) {
2022-11-19 21:17:39 +05:30
return true ;
2022-11-09 23:30:18 +05:30
2022-11-19 21:17:39 +05:30
gSpecificDebug = 0 ;
if ( gSpecificDebug > 0 ) print ( " ---- \n In verify event: " ) ;
String createdAt = json [ ' created_at ' ] . toString ( ) ;
2022-11-09 23:30:18 +05:30
2022-11-19 21:17:39 +05:30
String strTags = getStrTagsFromJson ( json [ ' tags ' ] ) ;
2022-11-09 23:30:18 +05:30
//print("strTags = $strTags");
String id = json [ ' id ' ] ;
String eventPubkey = json [ ' pubkey ' ] ;
String strKind = json [ ' kind ' ] . toString ( ) ;
String content = json [ ' content ' ] ;
2022-11-19 21:17:39 +05:30
content = unEscapeChars ( content ) ;
String eventSig = json [ ' sig ' ] ;
2022-11-09 23:30:18 +05:30
2022-11-19 21:17:39 +05:30
if ( false ) {
String calculatedId = getShaId ( eventPubkey , createdAt . toString ( ) , strKind , strTags , content ) ;
bool verified = true ; //verify( eventPubkey, calculatedId, eventSig);
if ( ! verified & & ! eventPubkey . startsWith ( " 00 " ) ) {
if ( gSpecificDebug > 0 ) printWarning ( " \n wrong sig event \n event sig = $ eventSig \n event id = $ id \n calculated id = $ calculatedId " ) ;
if ( gSpecificDebug > 0 ) print ( " Event: kind = $ strKind \n " ) ;
//getShaId(eventPubkey, createdAt.toString(), strKind, strTags, content);
//print("$json");
//throw Exception();
} else {
if ( gSpecificDebug > 0 ) printInColor ( " \n verified correct sig for event id $ id \n " , gCommentColor ) ;
}
2022-11-09 23:30:18 +05:30
}
2022-11-19 21:17:39 +05:30
2022-11-09 23:30:18 +05:30
return true ;
}
2022-08-16 16:08:33 +05:30
class EventData {
2022-08-19 21:22:16 +05:30
String id ;
String pubkey ;
int createdAt ;
int kind ;
String content ;
2022-11-11 12:57:08 +05:30
List < List < String > > eTags ; // e tags
2022-12-30 09:43:43 +05:30
List < String > pTags ; // list of p tags
2022-08-16 16:08:33 +05:30
List < List < String > > tags ;
2022-08-19 21:22:16 +05:30
bool isNotification ; // whether its to be highlighted using highlight color
2022-08-23 04:36:31 +05:30
String evaluatedContent ; // content which has mentions expanded, and which has been translated
2022-12-26 12:12:50 +05:30
Set < String > newLikes ; // used for notifications, are colored as notifications and then reset ; set of pubkeys that are new likers
2022-08-16 16:08:33 +05:30
List < Contact > contactList = [ ] ; // used for kind:3 events, which is contact list event
2022-08-31 20:07:38 +05:30
bool isHidden ; // hidden by sending a reaction kind 7 event to this event, by the logged in user
2022-09-01 05:28:43 +05:30
bool isDeleted ; // deleted by kind 5 event
2022-11-11 12:57:08 +05:30
EventData ( this . id , this . pubkey , this . createdAt , this . kind , this . content ,
this . eTags , this . pTags , this . contactList , this . tags , this . newLikes ,
{
this . isNotification = false , this . evaluatedContent = " " , this . isHidden = false , this . isDeleted = false
} ) ;
2022-09-24 13:51:14 +05:30
// returns the immediate kind 1 parent
2022-09-16 22:28:42 +05:30
String getParent ( Map < String , Tree > allEventsMap ) {
2022-11-11 12:57:08 +05:30
2022-09-03 21:18:50 +05:30
if ( eTags . isNotEmpty ) {
2022-11-11 12:57:08 +05:30
2022-11-11 14:21:50 +05:30
int numRoot = 0 , numReply = 0 ;
2022-11-27 14:18:35 +05:30
// first go over all tags and find out at least one reply and root tag, and count their numbers
2022-11-11 14:21:50 +05:30
String rootId = " " , replyId = " " ;
2022-11-17 01:52:57 +05:30
for ( int i = 0 ; i < eTags . length ; i + + ) {
2022-11-11 14:21:50 +05:30
String eventId = eTags [ i ] [ 0 ] ;
if ( eTags [ i ] . length > = 3 ) {
if ( eTags [ i ] [ 2 ] . toLowerCase ( ) = = " root " ) {
numRoot + + ;
rootId = eventId ;
} else {
if ( eTags [ i ] [ 2 ] . toLowerCase ( ) = = " reply " ) {
numReply + + ;
replyId = eventId ;
}
}
}
}
2022-11-27 14:18:35 +05:30
// then depending on the numbers and values ( of root and replyto) return the parent
2024-03-31 17:14:01 +05:30
if ( replyId . isNotEmpty ) {
2022-11-11 14:21:50 +05:30
if ( numReply = = 1 ) {
return replyId ;
} else {
2022-11-17 01:52:57 +05:30
// if there are multiply reply's we can't tell which is which, so we return the one at top
2024-03-31 17:14:01 +05:30
if ( replyId . isNotEmpty ) {
2022-11-17 01:52:57 +05:30
return replyId ;
2022-11-11 14:21:50 +05:30
} else {
2022-12-05 20:48:52 +05:30
// this is case when there is no reply id . should not actually happen given if conditions
2024-03-31 17:14:01 +05:30
if ( rootId . isNotEmpty ) {
2022-11-17 01:52:57 +05:30
return rootId ;
2022-11-11 14:21:50 +05:30
}
}
}
} else {
2024-03-31 17:14:01 +05:30
if ( rootId . isNotEmpty ) {
2022-11-11 14:21:50 +05:30
//printWarning("returning root id. no reply id found.");
return rootId ;
}
}
2022-11-27 14:18:35 +05:30
// but if reply/root tags don't work, then try to look for parent tag with the deprecated logic from NIP-10
2024-03-09 10:47:02 +05:30
//if( gDebug > 0) log.info("using deprecated logic of nip10 for event id : $id");
2022-11-22 01:58:36 +05:30
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
2022-11-25 20:33:08 +05:30
String placeholder = nip08PlaceHolders . length > i ? nip08PlaceHolders [ i ] : " INVALIDPLACEHOLDER_SHOULDNOTEXIST " ;
2022-11-22 01:58:36 +05:30
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 ;
2022-09-16 22:28:42 +05:30
}
}
}
2022-11-11 12:57:08 +05:30
2022-08-16 16:08:33 +05:30
}
return " " ;
}
2022-12-05 10:12:16 +05:30
List < String > ? getTTags ( ) {
2024-03-31 17:14:01 +05:30
List < String > ? tTags ;
2022-12-05 10:12:16 +05:30
for ( int i = 0 ; i < tags . length ; i + + ) {
List < String > tag = tags [ i ] ;
if ( tag . length < 2 ) {
continue ;
}
if ( tag [ 0 ] = = ' t ' ) {
2024-03-31 17:14:01 +05:30
tTags ? ? = [ ] ;
2022-12-05 10:12:16 +05:30
tTags . add ( tag [ 1 ] ) ;
}
}
return tTags ;
}
2022-12-04 18:42:07 +05:30
// returns valueof location tag if present. returns null if that tag is not present.
String ? getSpecificTag ( String tagName ) {
for ( int i = 0 ; i < tags . length ; i + + ) {
List < String > tag = tags [ i ] ;
if ( tag . length < 2 ) {
continue ;
}
if ( tag [ 0 ] = = tagName ) {
// return the first value
return tag [ 1 ] ;
}
}
return null ;
}
2022-08-16 16:08:33 +05:30
factory EventData . fromJson ( dynamic json ) {
2022-09-10 04:02:49 +05:30
2022-08-16 16:08:33 +05:30
List < Contact > contactList = [ ] ;
2022-11-11 12:57:08 +05:30
List < List < String > > eTagsRead = [ ] ;
List < String > pTagsRead = [ ] ;
2022-08-16 16:08:33 +05:30
List < List < String > > tagsRead = [ ] ;
var jsonTags = json [ ' tags ' ] ;
var numTags = jsonTags . length ;
2022-08-19 05:19:47 +05:30
2022-11-09 23:30:18 +05:30
//print("\n----\nIn fromJson\n");
String sig = json [ ' sig ' ] ;
if ( sig . length = = 128 ) {
//print("found sig == 128 bytes");
//if(json['id'] == "15dd45769dd0ccb9c4ca1c69fcd27011d53c4b95c8b7c786265bf7377bc7fdad") {
// printInColor("found 15dd45769dd0ccb9c4ca1c69fcd27011d53c4b95c8b7c786265bf7377bc7fdad sig ${json['sig']}", gCommentColor);
//}
try {
2022-11-19 21:17:39 +05:30
verifyEvent ( json ) ;
2022-11-09 23:30:18 +05:30
2024-03-31 17:14:01 +05:30
} on Exception {
2022-11-09 23:30:18 +05:30
//printWarning("verify gave exception $e");
throw Exception ( " in Event constructor: sig verify gave exception " ) ;
}
}
2022-08-16 16:08:33 +05:30
// NIP 02: if the event is a contact list type, then populate contactList
if ( json [ ' kind ' ] = = 3 ) {
for ( int i = 0 ; i < numTags ; i + + ) {
var tag = jsonTags [ i ] ;
2022-08-28 02:30:09 +05:30
if ( tag . length < 2 ) {
if ( gDebug > 0 ) print ( " In event fromjson: invalid p tag of size 1 " ) ;
continue ;
}
2022-08-16 16:08:33 +05:30
String server = defaultServerUrl ;
2022-08-28 02:30:09 +05:30
if ( tag . length > = 3 ) {
2022-08-16 16:08:33 +05:30
server = tag [ 2 ] . toString ( ) ;
2023-02-08 12:44:03 -08:00
if ( server = = ' wss://nostr.rocks ' | | server = = " wss://offchain.pub " ) {
2022-08-16 16:08:33 +05:30
server = defaultServerUrl ;
}
}
2022-08-28 02:30:09 +05:30
if ( tag [ 0 ] = = " p " & & tag [ 1 ] . length = = 64 ) {
Contact c = Contact ( tag [ 1 ] as String , server ) ;
contactList . add ( c ) ;
}
2022-08-16 16:08:33 +05:30
}
} else {
2022-09-01 05:28:43 +05:30
int eKind = json [ ' kind ' ] ;
2022-11-25 19:26:12 +05:30
if ( eKind = = 1 | | eKind = = 7 | | eKind = = 42 | | eKind = = 5 | | eKind = = 4 | | eKind = = 140 | | eKind = = 141 | | eKind = = 142 | | eKind = = gSecretMessageKind ) {
2022-08-16 16:08:33 +05:30
for ( int i = 0 ; i < numTags ; i + + ) {
var tag = jsonTags [ i ] ;
2022-09-12 11:58:48 +05:30
2022-08-16 16:08:33 +05:30
if ( tag . isEmpty ) {
continue ;
}
if ( tag [ 0 ] = = " e " ) {
2022-11-11 12:57:08 +05:30
List < String > listTag = [ ] ;
for ( int i = 1 ; i < tag . length ; i + + ) {
listTag . add ( tag [ i ] ) ;
}
eTagsRead . add ( listTag ) ;
2022-08-16 16:08:33 +05:30
} else {
if ( tag [ 0 ] = = " p " ) {
pTagsRead . add ( tag [ 1 ] ) ;
}
}
List < String > t = [ ] ;
t . add ( tag [ 0 ] ) ;
t . add ( tag [ 1 ] ) ;
tagsRead . add ( t ) ;
// TODO add other tags
}
}
}
2022-09-07 15:44:15 +05:30
if ( gDebug > 0 & & json [ ' id ' ] = = gCheckEventId ) {
2022-09-01 21:55:51 +05:30
print ( " \n ----------------------------------------Creating EventData with content: ${ json [ ' content ' ] } " ) ;
print ( " In Event fromJson: got message: $ gCheckEventId " ) ;
2022-08-16 16:08:33 +05:30
}
return EventData ( json [ ' id ' ] as String , json [ ' pubkey ' ] as String ,
json [ ' created_at ' ] as int , json [ ' kind ' ] as int ,
2022-08-21 00:11:50 +05:30
json [ ' content ' ] . trim ( ) as String ,
eTagsRead , pTagsRead ,
2022-08-20 03:12:32 +05:30
contactList , tagsRead ,
{ } ) ;
2022-08-16 16:08:33 +05:30
}
2022-11-22 01:58:36 +05:30
String expandMentions ( String content , Map < String , Tree > tempChildEventsMap ) {
2022-08-16 16:08:33 +05:30
if ( tags . isEmpty ) {
return content ;
}
2024-04-06 21:23:17 +05:30
// just check whether "nostr:" is in comment, because only that indicates a mention; if not we return
if ( ! content . contains ( " nostr: " ) ) {
2022-08-19 21:22:16 +05:30
return content ;
}
2024-04-20 11:10:08 +05:30
//print("------------------\nin expandMentions: content = $content \n");
2022-12-28 11:18:06 +05:30
String replaceMentions ( Match mentionTagMatch ) {
2024-04-20 11:10:08 +05:30
//print("in replaceMentions\n");
2022-12-28 11:18:06 +05:30
String ? mentionTag = mentionTagMatch . group ( 0 ) ;
if ( mentionTag ! = null ) {
2024-04-07 12:41:30 +05:30
//print("mentionTag = $mentionTag");
2024-04-06 21:23:17 +05:30
String strBechId = mentionTag . substring ( 6 , mentionTag . length ) ;
String tempType = strBechId . substring ( 0 , 4 ) ;
if ( tempType ! = " note " & & tempType ! = " npub " ) {
return " nostr: $ strBechId " ;
}
//print("Going to decode: $strBechId");
2024-04-07 12:41:30 +05:30
try {
Map < String , String > nsec = bech32Decode ( strBechId ) ;
String ? type = nsec [ " prefix " ] ; // type can be "note" or "npub"
String ? strHex = nsec [ " data " ] ; // this is 64 byte hex pubkey or note id
if ( strHex ! = null & & type ! = null ) {
String mentionedId = strHex ;
2024-04-20 11:10:08 +05:30
//print("strHex = $strHex type = $type");
2024-04-07 12:41:30 +05:30
if ( type = = " npub " ) {
if ( gKindONames . containsKey ( mentionedId ) ) {
String ? author = getOnlyAuthorName ( mentionedId ) ;
if ( author = = null ) {
return " nostr: $ strBechId " ;
} else {
return " @ $ author " ;
}
}
} else {
if ( type = = " note " ) {
2024-04-20 11:10:08 +05:30
EventData ? mentionedEventData = tempChildEventsMap [ mentionedId ] ? . event . eventData ;
if ( mentionedEventData ! = null ) {
//print("Found note");
String quotedAuthor = getAuthorName ( mentionedEventData . pubkey ) ;
2024-04-07 12:41:30 +05:30
String prefixId = mentionedId . substring ( 0 , 3 ) ;
2024-04-20 11:10:08 +05:30
String mentionedContent = mentionedEventData . content ;
if ( mentionedEventData . evaluatedContent ! = " " ) {
//print("found evaluated content");
mentionedContent = mentionedEventData . evaluatedContent ;
} else {
//print("didnt find evaluated content");
}
String quote = " <Quoted event id ' $ prefixId ' by $ quotedAuthor : \" $ mentionedContent \" > " ;
//print("evaluatedContent: ${mentionedEventData.evaluatedContent}\n");
2024-04-07 12:41:30 +05:30
return quote ;
}
2024-04-20 11:10:08 +05:30
} else {
//print("Could not find event!\n");
}
2024-04-06 21:23:17 +05:30
}
2024-04-07 12:41:30 +05:30
return " nostr: $ strBechId " ;
2024-04-06 21:23:17 +05:30
} else {
2024-04-07 12:41:30 +05:30
//print("Could not parse the given nsec/private key. Exiting.");
return mentionTag ;
2022-11-22 01:58:36 +05:30
}
2024-04-20 15:48:08 +05:30
} on Exception {
2024-04-07 12:41:30 +05:30
//print("====================Caught exctption.");
return " nostr: $ strBechId " ;
2022-08-16 16:08:33 +05:30
}
}
2024-04-06 21:23:17 +05:30
if ( gDebug > = 0 ) printWarning ( " In replaceMentions returning nothing " ) ;
2022-12-28 11:18:06 +05:30
return " " ;
2024-04-07 12:41:30 +05:30
} // end replaceMentions()
2022-12-28 11:18:06 +05:30
// replace the mentions, if any are found
2024-04-07 12:41:30 +05:30
// The Bech32 alphabet contains 32 characters, including lowercase letters a-z and the numbers 0-9, excluding the number 1 and the letters ‘ b’ , ‘ i’ , ‘ o’ to avoid reader confusion.
String mentionStr = " (nostr:(npub1|note1)[a0c-hj-np-z2-9]{58}) " ; // bech32
2024-04-06 21:23:17 +05:30
RegExp mentionRegExp = RegExp ( mentionStr , caseSensitive: false ) ;
2022-12-28 11:18:06 +05:30
content = content . replaceAllMapped ( mentionRegExp , replaceMentions ) ;
2022-08-16 16:08:33 +05:30
return content ;
}
2022-09-05 22:48:11 +05:30
// is called only once for each event received ( or read from file)
2022-12-24 11:22:57 +05:30
void translateAndExpandMentions ( Map < String , Tree > tempChildEventsMap ) {
2022-10-30 00:27:11 +05:30
if ( id = = gCheckEventId ) {
2022-11-25 19:26:12 +05:30
//printInColor("in translateAndExpandMentions: decoding $gCheckEventId\n", redColor);
2022-10-30 00:27:11 +05:30
}
2022-09-02 01:14:23 +05:30
if ( content = = " " | | evaluatedContent ! = " " ) {
2022-10-30 00:27:11 +05:30
if ( id = = gCheckEventId ) {
2022-11-25 19:26:12 +05:30
//printInColor("in translateAndExpandMentions: returning \n", redColor);
2022-10-30 00:27:11 +05:30
}
2022-09-01 21:55:51 +05:30
return ;
}
2022-08-27 19:58:18 +05:30
2022-09-01 21:55:51 +05:30
switch ( kind ) {
case 1 :
2022-09-04 02:27:14 +05:30
case 42 :
2022-11-22 01:58:36 +05:30
evaluatedContent = expandMentions ( content , tempChildEventsMap ) ;
2022-12-25 13:51:28 +05:30
if ( gShowLnInvoicesAsQr ) {
evaluatedContent = expandLNInvoices ( evaluatedContent ) ;
}
2022-09-02 01:57:11 +05:30
if ( translator ! = null & & gTranslate & & ! evaluatedContent . isEnglish ( ) ) {
2022-08-23 04:36:31 +05:30
if ( gDebug > 0 ) print ( " found that this comment is non-English: $ evaluatedContent " ) ;
2022-09-02 01:57:11 +05:30
// only translate for latest events
2022-08-24 03:36:08 +05:30
if ( DateTime . fromMillisecondsSinceEpoch ( createdAt * 1000 ) . compareTo ( DateTime . now ( ) . subtract ( Duration ( days: gNumTranslateDays ) ) ) > 0 ) {
2022-08-23 04:36:31 +05:30
if ( gDebug > 0 ) print ( " Sending google request: translating $ content " ) ;
2022-09-02 01:57:11 +05:30
if ( translator ! = null ) {
try {
2022-12-22 18:42:54 +05:30
numEventsTranslated + + ;
2022-09-02 01:57:11 +05:30
translator ? . translate ( content , to: ' en ' )
2022-10-07 03:16:58 +05:30
. then ( ( result ) = > { evaluatedContent = " $ evaluatedContent \n \n Translation: ${ result . toString ( ) } " , if ( gDebug > 0 ) print ( " Google translate returned successfully for one call. " ) } )
. onError ( ( error , stackTrace ) {
if ( gDebug > 0 ) print ( " Translate error = $ error \n for content = $ content \n " ) ;
return { } ;
}
2022-09-02 01:57:11 +05:30
) ;
} on Exception catch ( err ) {
if ( gDebug > = 0 ) print ( " Info: Error in trying to use google translate: $ err " ) ;
}
2022-08-23 04:36:31 +05:30
}
}
}
2022-09-01 21:55:51 +05:30
break ;
2022-11-25 19:26:12 +05:30
} // end switch
return ;
} // end translateAndExpandMentions
// is called only once for each event received ( or read from file)
2022-12-01 22:40:14 +05:30
String ? TranslateAndDecryptGroupInvite ( ) {
2022-11-25 19:26:12 +05:30
if ( content = = " " | | evaluatedContent ! = " " ) {
return null ;
}
switch ( kind ) {
case gSecretMessageKind:
if ( userPrivateKey = = " " ) { // cant process if private key not given
return null ;
}
2024-03-31 17:14:01 +05:30
if ( ! isValidDirectMessage ( this , acceptableKind: kind ) ) {
2022-11-25 19:26:12 +05:30
return null ;
}
String ? decrypted = decryptDirectMessage ( ) ;
if ( decrypted ! = null ) {
evaluatedContent = decrypted ;
}
return id ;
} // end switch
return null ;
2022-12-01 22:40:14 +05:30
} // end TranslateAndDecryptGroupInvite
2022-11-25 19:26:12 +05:30
// 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 ) {
2022-09-01 21:55:51 +05:30
case 4 :
2022-09-03 04:55:30 +05:30
if ( userPrivateKey = = " " ) { // cant process if private key not given
break ;
}
2022-09-05 17:14:34 +05:30
//if( pubkey == userPublicKey ) break; // crashes right now otherwise
2022-09-05 22:48:11 +05:30
if ( ! isValidDirectMessage ( this ) ) {
2022-09-01 21:55:51 +05:30
break ;
}
2022-10-30 00:27:11 +05:30
if ( id = = gCheckEventId ) {
printInColor ( " in translateAndExpandMensitons: gonna decrypt \n " , redColor ) ;
}
2022-11-25 19:26:12 +05:30
//log.info("decrypting a message of kind 4");
2022-10-30 00:27:11 +05:30
String ? decrypted = decryptDirectMessage ( ) ;
if ( decrypted ! = null ) {
evaluatedContent = decrypted ;
2022-11-22 01:58:36 +05:30
evaluatedContent = expandMentions ( evaluatedContent , tempChildEventsMap ) ;
2022-10-30 00:27:11 +05:30
}
2022-11-25 19:26:12 +05:30
//print("evaluatedContent: $evaluatedContent");
2022-10-30 00:27:11 +05:30
break ;
2022-10-30 11:46:51 +05:30
} // end switch
} // end translateAndExpandMentions
// is called only once for each event received ( or read from file)
2022-12-01 22:40:14 +05:30
void translateAndDecrypt14x ( Set < String > secretMessageIds , List < Channel > encryptedChannels , Map < String , Tree > tempChildEventsMap ) {
2022-10-30 11:46:51 +05:30
if ( id = = gCheckEventId ) {
2022-11-25 19:26:12 +05:30
//printInColor("in translateAndExpand14x: decoding ee810ea73072af056cceaa6d051b4fcce60739247f7bcc752e72fa5defb64f09\n", redColor);
2022-10-30 11:46:51 +05:30
}
if ( content = = " " | | evaluatedContent ! = " " ) {
if ( id = = gCheckEventId ) {
2022-11-25 19:26:12 +05:30
//printInColor("in translateAndExpand14x: returning \n", redColor);
2022-10-30 11:46:51 +05:30
}
return ;
}
2022-11-29 11:02:31 +05:30
if ( createdAt < getSecondsDaysAgo ( 3 ) ) {
2022-11-25 19:26:12 +05:30
//print("old 142. not decrypting");
2022-11-29 11:02:31 +05:30
//return;
2022-11-25 19:26:12 +05:30
}
2022-10-30 11:46:51 +05:30
switch ( kind ) {
2022-10-30 00:27:11 +05:30
case 142 :
2022-11-25 19:26:12 +05:30
//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 ) ;
2022-09-05 22:48:11 +05:30
if ( decrypted ! = null ) {
2022-11-25 19:26:12 +05:30
//printWarning("Successfully decrypted kind 142: $id");
2022-09-05 22:48:11 +05:30
evaluatedContent = decrypted ;
2022-11-25 19:26:12 +05:30
//print("in translateAndDecrypt14x: calling expandMentions");
2022-11-22 01:58:36 +05:30
evaluatedContent = expandMentions ( evaluatedContent , tempChildEventsMap ) ;
2022-11-25 19:26:12 +05:30
//print("content = $content");
//print(evaluatedContent);
2022-09-05 22:48:11 +05:30
}
2022-09-02 01:14:23 +05:30
break ;
2022-10-30 11:46:51 +05:30
default :
break ;
2022-10-30 00:27:11 +05:30
2022-09-01 21:55:51 +05:30
} // end switch
2022-10-30 11:46:51 +05:30
} // end translateAndExpand14x
2022-08-23 04:36:31 +05:30
2022-10-30 00:27:11 +05:30
String ? decryptDirectMessage ( ) {
2022-09-05 22:48:11 +05:30
int ivIndex = content . indexOf ( " ?iv= " ) ;
2022-11-18 19:57:59 +05:30
if ( ivIndex > 0 ) {
var iv = content . substring ( ivIndex + 4 , content . length ) ;
2024-03-31 17:14:01 +05:30
var encStr = content . substring ( 0 , ivIndex ) ;
2022-11-18 19:57:59 +05:30
String userKey = userPrivateKey ;
2024-03-31 17:14:01 +05:30
String otherUserPubKey = " 02 $ pubkey " ;
2022-11-18 19:57:59 +05:30
if ( pubkey = = userPublicKey ) { // if user themselve is the sender change public key used to decrypt
userKey = userPrivateKey ;
int numPtags = 0 ;
2024-03-31 17:14:01 +05:30
for ( var tag in tags ) {
2022-11-18 19:57:59 +05:30
if ( tag [ 0 ] = = " p " ) {
2024-03-31 17:14:01 +05:30
otherUserPubKey = " 02 ${ tag [ 1 ] } " ;
2022-11-18 19:57:59 +05:30
numPtags + + ;
}
2024-03-31 17:14:01 +05:30
}
2022-11-18 19:57:59 +05:30
// if there are more than one p tags, we don't know who its for
if ( numPtags ! = 1 ) {
if ( gDebug > = 0 ) printInColor ( " in translateAndExpand: got event $ id with number of p tags != one : $ numPtags . not decrypting " , redColor ) ;
return null ;
2022-09-05 22:48:11 +05:30
}
2022-11-18 19:57:59 +05:30
}
2022-09-12 11:33:55 +05:30
2024-03-31 17:14:01 +05:30
var decrypted = myPrivateDecrypt ( userKey , otherUserPubKey , encStr , iv ) ; // use bob's privatekey and alic's publickey means bob can read message from alic
2022-11-18 19:57:59 +05:30
return decrypted ;
} else {
if ( gDebug > 0 ) print ( " Invalid content for dm, could not get ivIndex: $ content " ) ;
return null ;
}
2022-09-05 22:48:11 +05:30
}
2022-09-02 01:14:23 +05:30
2022-10-30 00:27:11 +05:30
Channel ? getChannelForMessage ( List < Channel > ? listChannel , String messageId ) {
if ( listChannel = = null ) {
return null ;
}
for ( int i = 0 ; i < listChannel . length ; i + + ) {
if ( listChannel [ i ] . messageIds . contains ( messageId ) ) {
return listChannel [ i ] ;
}
}
return null ;
}
2022-12-01 22:40:14 +05:30
String ? decryptEncryptedChannelMessage ( Set < String > secretMessageIds , Map < String , Tree > tempChildEventsMap ) {
2022-10-30 11:46:51 +05:30
2022-10-30 13:24:13 +05:30
if ( id = = " 865c9352de11a3959c06fce5350c5a1b9fa0475d3234078a1bb45d152b370f0b " ) { // known issue
2022-11-25 19:26:12 +05:30
return null ;
2022-10-30 11:46:51 +05:30
}
2022-10-30 00:27:11 +05:30
int ivIndex = content . indexOf ( " ?iv= " ) ;
2022-11-22 01:58:36 +05:30
if ( ivIndex = = - 1 ) {
2022-11-25 19:26:12 +05:30
return null ;
2022-11-22 01:58:36 +05:30
}
2022-10-30 00:27:11 +05:30
var iv = content . substring ( ivIndex + 4 , content . length ) ;
2024-03-31 17:14:01 +05:30
var encStr = content . substring ( 0 , ivIndex ) ;
2022-10-30 00:27:11 +05:30
2022-12-04 18:42:07 +05:30
String channelId = getChannelIdForKind4x ( ) ;
2022-10-30 00:27:11 +05:30
List < String > keys = [ ] ;
2022-11-25 19:26:12 +05:30
keys = getEncryptedChannelKeys ( secretMessageIds , tempChildEventsMap , channelId ) ;
2022-10-30 00:27:11 +05:30
if ( keys . length ! = 2 ) {
2022-12-02 12:28:02 +05:30
//printWarning("\nCould not get keys for event id: $id and channelId: $channelId\n");
//print("keys = $keys\n\n");
2022-11-25 19:26:12 +05:30
return null ;
2022-08-24 03:36:08 +05:30
}
2022-09-14 23:57:38 +05:30
2022-10-30 00:27:11 +05:30
String priKey = keys [ 0 ] ;
2024-03-31 17:14:01 +05:30
String pubKey = " 02 ${ keys [ 1 ] } " ;
2022-10-30 00:27:11 +05:30
2024-03-31 17:14:01 +05:30
var decrypted = myPrivateDecrypt ( priKey , pubKey , encStr , iv ) ; // use bob's privatekey and alic's publickey means bob can read message from alic
2022-10-30 00:27:11 +05:30
return decrypted ;
}
// only applicable for kind 42/142 event; returns the channel 40/140 id of which the event is part of
2022-12-04 18:42:07 +05:30
String getChannelIdForKind4x ( ) {
2022-10-30 00:27:11 +05:30
if ( kind ! = 42 & & kind ! = 142 & & kind ! = 141 ) {
return " " ;
}
2022-11-27 17:49:51 +05:30
2022-09-14 23:57:38 +05:30
// get first e tag, which should be the channel of which this is part of
2022-11-27 17:49:51 +05:30
for ( int i = 0 ; i < eTags . length ; i + + ) {
List tag = eTags [ i ] ;
2024-03-31 17:14:01 +05:30
if ( tag . isNotEmpty ) {
2022-11-27 17:49:51 +05:30
return tag [ 0 ] ;
2022-09-14 23:57:38 +05:30
}
}
return ' ' ;
2022-08-24 03:36:08 +05:30
}
2022-12-05 10:12:16 +05:30
String getChannelIdForTTagRoom ( String tagValue ) {
2024-03-31 17:14:01 +05:30
return " $ tagValue #t " ;
2022-12-05 10:12:16 +05:30
}
2022-12-04 18:42:07 +05:30
// only applicable for kind 42/142 event; returns the channel 40/140 id of which the event is part of
2022-12-04 22:47:27 +05:30
String getChannelIdForLocationRooms ( ) {
2022-12-04 18:42:07 +05:30
String ? location = getSpecificTag ( " location " ) ;
if ( kind = = 1 & & location ! = null & & location ! = " " ) {
2022-12-04 22:47:27 +05:30
return location + gLocationTagIdSuffix ;
2022-12-04 18:42:07 +05:30
}
return ' ' ;
}
2022-08-22 04:44:29 +05:30
// prints event data in the format that allows it to be shown in tree form by the Tree class
2022-12-01 22:40:14 +05:30
void printEventData ( int depth , bool topPost , Map < String , Tree > ? tempChildEventsMap , Set < String > ? secretMessageIds , List < Channel > ? encryptedChannels ) {
2022-09-03 21:18:50 +05:30
if ( ! ( kind = = 1 | | kind = = 4 | | kind = = 42 ) ) {
return ; // only print kind 1 and 42 and 4
2022-08-25 18:33:29 +05:30
}
2022-11-25 19:26:12 +05:30
// will only do decryption if its not been decrypted yet by looking at 'evaluatedContent'
2024-04-06 21:23:17 +05:30
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 ) ;
}
2022-11-25 19:26:12 +05:30
}
}
2022-12-26 12:38:31 +05:30
int n = gEventLenPrinted ; // is 6
2022-08-16 16:08:33 +05:30
String maxN ( String v ) = > v . length > n ? v . substring ( 0 , n ) : v . substring ( 0 , v . length ) ;
2022-09-03 21:18:50 +05:30
2022-12-31 03:32:44 +05:30
String name = getAuthorName ( pubkey , maxDisplayLen: gNameLengthInPost ) ;
2022-09-01 05:28:43 +05:30
String strDate = getPrintableDate ( createdAt ) ;
2022-09-03 21:18:50 +05:30
String tempEvaluatedContent = evaluatedContent ;
String tempContent = content ;
if ( isHidden ) {
name = " <hidden> " ;
strDate = " <hidden> " ;
tempEvaluatedContent = tempContent = " <You have hidden this post> " ;
}
// delete supercedes hidden
if ( isDeleted ) {
name = " <deleted> " ;
strDate = " <deleted> " ;
tempEvaluatedContent = tempContent = content ; // content would be changed so show that
}
2022-08-16 16:08:33 +05:30
if ( createdAt = = 0 ) {
2022-09-04 19:01:39 +05:30
print ( " debug: createdAt == 0 for event $ id $ content " ) ;
2022-08-16 16:08:33 +05:30
}
2022-09-03 23:05:55 +05:30
String commentColor = " " ;
2022-08-17 22:34:20 +05:30
if ( isNotification ) {
2022-09-03 23:05:55 +05:30
commentColor = gNotificationColor ;
2022-08-17 22:34:20 +05:30
isNotification = false ;
} else {
2022-09-03 23:05:55 +05:30
commentColor = gCommentColor ;
2022-08-17 22:34:20 +05:30
}
2022-09-10 19:33:20 +05:30
2022-09-10 20:20:01 +05:30
int tempEffectiveLen = name . length < gNameLengthInPost ? name . length: gNameLengthInPost ;
name = name . substring ( 0 , tempEffectiveLen ) ;
2022-09-10 19:33:20 +05:30
2022-09-10 20:20:01 +05:30
int effectiveNameFieldLen = gNameLengthInPost + 3 ; // get this before name is mangled by color
2022-09-11 00:14:59 +05:30
String nameColor = getNameColor ( pubkey ) ;
2022-09-10 20:20:01 +05:30
2022-09-13 21:14:21 +05:30
// pad name to left
2022-09-10 20:20:01 +05:30
name = name . padLeft ( gNameLengthInPost ) ;
name = name . substring ( 0 , gNameLengthInPost ) ;
name = getStrInColor ( name , nameColor ) ;
2022-09-10 19:33:20 +05:30
2022-09-09 03:31:23 +05:30
String strToPrint = " " ;
if ( ! topPost ) {
2022-09-14 06:58:45 +05:30
strToPrint + = " \n " ;
2022-09-11 19:12:23 +05:30
strToPrint + = getDepthSpaces ( depth ) ;
2022-09-13 21:14:21 +05:30
strToPrint + = " " ; // in place of block for top posts
2022-09-11 19:12:23 +05:30
} else {
2022-09-13 03:14:30 +05:30
strToPrint + = getDepthSpaces ( depth ) ;
strToPrint + = " █ " ;
2022-09-09 03:31:23 +05:30
}
2024-03-31 17:14:01 +05:30
strToPrint + = " $ name : " ;
2022-09-09 03:31:23 +05:30
const int typicalxLen = " |id: 82b5 , 12:04 AM Sep 19 " . length + 5 ; // not sure where 5 comes from
2022-12-28 14:55:59 +05:30
List < dynamic > reactionString = getReactionStr ( depth ) ;
//print("\n|${reactionString[0]}|\n ${ reactionString[1]}\n }");
String idDateLikes = " |id: ${ maxN ( id ) } , $ strDate ${ reactionString [ 0 ] } " ;
2022-09-09 03:31:23 +05:30
idDateLikes = idDateLikes . padRight ( typicalxLen ) ;
2022-09-14 18:31:21 +05:30
2022-09-09 03:31:23 +05:30
String temp = tempEvaluatedContent = = " " ? tempContent: tempEvaluatedContent ;
2022-09-14 18:31:21 +05:30
String contentShifted = makeParagraphAtDepth ( temp , gSpacesPerDepth * depth + effectiveNameFieldLen ) ;
2022-11-12 23:34:00 +05:30
2022-09-14 18:31:21 +05:30
int maxLineLen = gTextWidth - gSpacesPerDepth * depth - effectiveNameFieldLen ;
int lastLineLen = contentShifted . length ;
int i = 0 ;
contentShifted = contentShifted . trim ( ) ;
// find the effective length of the last line of the content
for ( i = contentShifted . length - 1 ; i > = 0 ; i - - ) {
if ( contentShifted [ i ] = = " \n " ) {
break ;
}
2022-09-09 03:31:23 +05:30
}
2022-09-14 18:31:21 +05:30
if ( i > = 0 & & contentShifted [ i ] = = " \n " ) {
lastLineLen = contentShifted . length - i ;
2022-09-09 03:31:23 +05:30
}
2022-09-12 01:36:14 +05:30
2022-09-14 18:31:21 +05:30
// effective len of last line is used to calcluate where the idDateLikes str is affixed at the end
int effectiveLastLineLen = lastLineLen - gSpacesPerDepth * depth - effectiveNameFieldLen - gNumLeftMarginSpaces ;
2024-03-31 17:14:01 +05:30
if ( contentShifted . length < = maxLineLen ) {
2022-09-14 18:31:21 +05:30
effectiveLastLineLen = contentShifted . length ;
2024-03-31 17:14:01 +05:30
}
2022-09-14 18:31:21 +05:30
2022-12-28 14:55:59 +05:30
// needed to use this because the color padding in notifications reactions will mess up the length calculation in the actual reaction string
int colorStrLen = reactionString [ 0 ] . length - reactionString [ 1 ] ;
2022-09-14 18:31:21 +05:30
// now actually find where the likesDates string goes
if ( ( gSpacesPerDepth * depth + effectiveNameFieldLen + effectiveLastLineLen + idDateLikes . length ) < = gTextWidth ) {
2022-12-28 14:55:59 +05:30
idDateLikes = idDateLikes . padLeft ( ( gTextWidth ) + colorStrLen - ( gSpacesPerDepth * depth + effectiveNameFieldLen + effectiveLastLineLen ) ) ;
2022-09-14 18:31:21 +05:30
} else {
2024-03-31 17:14:01 +05:30
idDateLikes = " \n ${ idDateLikes . padLeft ( gNumLeftMarginSpaces + gTextWidth + colorStrLen ) } " ;
2022-09-14 18:31:21 +05:30
}
2022-09-09 03:31:23 +05:30
2022-09-14 18:31:21 +05:30
// print content and the dateslikes string
2024-03-31 17:14:01 +05:30
strToPrint + = getStrInColor ( " $ contentShifted $ idDateLikes \n " , commentColor ) ;
2022-09-03 23:05:55 +05:30
stdout . write ( strToPrint ) ;
2022-08-16 16:08:33 +05:30
}
2022-12-01 22:40:14 +05:30
String getAsLine ( var tempChildEventsMap , Set < String > ? secretMessageIds , List < Channel > ? encryptedChannels , { int len = 20 } ) {
2022-11-25 19:26:12 +05:30
// will only do decryption if its not been decrypted yet by looking at 'evaluatedContent'
2024-03-31 17:14:01 +05:30
if ( kind = = 4 ) {
2022-11-25 19:26:12 +05:30
translateAndDecryptKind4 ( tempChildEventsMap ) ;
2024-03-31 17:14:01 +05:30
} else if ( [ 1 , 42 ] . contains ( kind ) ) {
2022-11-25 19:26:12 +05:30
translateAndExpandMentions ( tempChildEventsMap ) ;
} else if ( [ 142 ] . contains ( kind ) ) {
if ( tempChildEventsMap ! = null & & secretMessageIds ! = null & & encryptedChannels ! = null ) {
translateAndDecrypt14x ( secretMessageIds , encryptedChannels , tempChildEventsMap ) ;
}
}
2022-09-04 02:27:14 +05:30
String contentToPrint = evaluatedContent . isEmpty ? content: evaluatedContent ;
if ( len = = 0 | | len > contentToPrint . length ) {
2022-11-13 20:54:27 +05:30
//len = contentToPrint.length;
2022-08-26 03:57:50 +05:30
}
2022-11-25 19:26:12 +05:30
2022-09-05 17:14:34 +05:30
contentToPrint = contentToPrint . replaceAll ( " \n " , " " ) ;
2022-09-05 22:48:11 +05:30
contentToPrint = contentToPrint . replaceAll ( " \r " , " " ) ;
2022-11-13 20:54:27 +05:30
contentToPrint = contentToPrint . replaceAll ( " \t " , " " ) ;
contentToPrint = contentToPrint . padRight ( len ) . substring ( 0 , len ) ;
//contentToPrint = contentToPrint.padRight(len);
2022-12-31 03:32:44 +05:30
String strToPrint = ' $ contentToPrint - ${ getAuthorName ( pubkey , maxDisplayLen: gNameLengthInPost ) . padLeft ( 12 ) } ' ;
2022-09-06 03:40:34 +05:30
2022-11-13 20:54:27 +05:30
String paddedStrToPrint = strToPrint ;
2022-09-06 03:59:42 +05:30
if ( isNotification ) {
paddedStrToPrint = " $ gNotificationColor $ paddedStrToPrint $ gColorEndMarker " ;
isNotification = false ;
}
2022-11-25 19:26:12 +05:30
//print("returning $paddedStrToPrint");
2022-09-06 03:40:34 +05:30
return paddedStrToPrint ;
2022-08-26 03:57:50 +05:30
}
2022-11-25 19:26:12 +05:30
2022-12-06 13:46:21 +05:30
String getStrForChannel ( int depth , Map < String , Tree > tempChildEventsMap , Set < String > ? secretMessageIds , List < Channel > ? encryptedChannels ) {
2022-11-25 19:26:12 +05:30
// 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'
2024-03-31 17:14:01 +05:30
if ( kind = = 4 ) {
2022-11-25 19:26:12 +05:30
translateAndDecryptKind4 ( tempChildEventsMap ) ;
2024-03-31 17:14:01 +05:30
} else if ( [ 1 , 42 ] . contains ( kind ) ) {
2022-11-25 19:26:12 +05:30
translateAndExpandMentions ( tempChildEventsMap ) ;
} else if ( [ 142 ] . contains ( kind ) ) {
2024-03-31 17:14:01 +05:30
if ( secretMessageIds ! = null & & encryptedChannels ! = null ) {
2022-11-25 19:26:12 +05:30
//print('decrypting 14x in getStrForChannel');
translateAndDecrypt14x ( secretMessageIds , encryptedChannels , tempChildEventsMap ) ;
}
}
2022-09-04 00:15:33 +05:30
String strToPrint = " " ;
2022-12-31 03:32:44 +05:30
String name = getAuthorName ( pubkey , maxDisplayLen: gNameLengthInPost ) ;
2022-09-04 00:15:33 +05:30
String strDate = getPrintableDate ( createdAt ) ;
String tempEvaluatedContent = evaluatedContent ;
2022-09-04 02:27:14 +05:30
String tempContent = evaluatedContent . isEmpty ? content: evaluatedContent ;
2022-09-04 00:15:33 +05:30
if ( isHidden ) {
2022-09-04 19:01:39 +05:30
name = strDate = " <hidden> " ;
2022-09-04 00:15:33 +05:30
tempEvaluatedContent = tempContent = " <You have hidden this post> " ;
}
// delete supercedes hidden
if ( isDeleted ) {
2022-09-04 19:01:39 +05:30
name = strDate = " <deleted> " ;
2022-09-04 00:15:33 +05:30
tempEvaluatedContent = tempContent = content ; // content would be changed so show that
}
2024-03-31 17:14:01 +05:30
if ( tempEvaluatedContent = = " " ) {
2022-09-15 00:49:13 +05:30
tempEvaluatedContent = tempContent ;
2024-03-31 17:14:01 +05:30
}
2022-09-15 00:49:13 +05:30
2022-09-09 03:31:23 +05:30
const int nameWidthDepth = 16 ~ / gSpacesPerDepth ; // how wide name will be in depth spaces
2022-09-25 02:42:54 +05:30
const int timeWidthDepth = 18 ~ / gSpacesPerDepth ;
2022-09-06 00:20:55 +05:30
int nameWidth = gSpacesPerDepth * nameWidthDepth ;
2022-09-10 20:56:14 +05:30
// get name in color and pad it too
2022-09-06 00:20:55 +05:30
String nameToPrint = name . padLeft ( nameWidth ) . substring ( 0 , nameWidth ) ;
2022-09-11 00:14:59 +05:30
nameToPrint = getStrInColor ( nameToPrint , getNameColor ( pubkey ) ) ;
2022-09-10 20:56:14 +05:30
2022-09-06 00:20:55 +05:30
String dateToPrint = strDate . padLeft ( gSpacesPerDepth * timeWidthDepth ) . substring ( 0 , gSpacesPerDepth * timeWidthDepth ) ;
2022-09-10 22:30:56 +05:30
2022-09-06 00:20:55 +05:30
// depth above + ( depth numberof spaces = 1) + (depth of time = 2) + (depth of name = 3)
2022-09-15 02:19:30 +05:30
int contentDepth = depth + 1 + timeWidthDepth + nameWidthDepth ;
int magicNumberDepth6 = 2 ; // magic number for gSpacesPerDepth == 6
int finalContentDepthInSpaces = gSpacesPerDepth * contentDepth + magicNumberDepth6 ;
int contentPlacementColumn = finalContentDepthInSpaces + gNumLeftMarginSpaces ;
String contentShifted = makeParagraphAtDepth ( tempEvaluatedContent , finalContentDepthInSpaces ) ;
2022-12-06 13:46:21 +05:30
Event ? replyToEvent = getReplyToChannelEvent ( tempChildEventsMap ) ;
2022-09-15 02:19:30 +05:30
String strReplyTo = " " ;
if ( replyToEvent ! = null ) {
//print("in getStrForChannel: got replyTo id = ${replyToEvent.eventData.id}");
2022-11-22 01:58:36 +05:30
if ( replyToEvent . eventData . kind = = 1 | | replyToEvent . eventData . kind = = 42 | | replyToEvent . eventData . kind = = 142 ) { // make sure its a kind 1 or 40 message
2022-09-15 02:19:30 +05:30
if ( replyToEvent . eventData . id ! = id ) { // basic self test
2022-11-28 11:17:31 +05:30
// quote only a part of the reply if its too long. add ellipsis if requried.
String replyToPrint = " " ;
if ( replyToEvent . eventData . evaluatedContent . length < = gReplyLengthPrinted ) {
replyToPrint = replyToEvent . eventData . evaluatedContent ;
} else {
2024-03-31 17:14:01 +05:30
replyToPrint = " ${ replyToEvent . eventData . evaluatedContent . substring ( 0 , gReplyLengthPrinted ) } ... " ;
2022-11-28 11:17:31 +05:30
}
strReplyTo = ' In reply to:" ${ getAuthorName ( replyToEvent . eventData . pubkey ) } : $ replyToPrint " ' ;
2022-09-15 02:19:30 +05:30
strReplyTo = makeParagraphAtDepth ( strReplyTo , finalContentDepthInSpaces + 6 ) ; // one extra for content
// add reply to string to end of the content. How it will show:
2024-03-31 17:14:01 +05:30
contentShifted + = ( " \n ${ getNumSpaces ( contentPlacementColumn + gSpacesPerDepth ) } $ strReplyTo " ) ;
2022-09-15 02:19:30 +05:30
}
}
2022-11-22 01:58:36 +05:30
} else {
//printWarning("no reply to event for event id $id");
2022-09-15 02:19:30 +05:30
}
2022-11-08 13:31:00 +05:30
String msgId = id . substring ( 0 , 3 ) . padLeft ( gSpacesPerDepth ~ / 2 ) . padRight ( gSpacesPerDepth ) ;
2022-09-06 01:11:34 +05:30
if ( isNotification ) {
2024-03-31 17:14:01 +05:30
strToPrint = " $ gNotificationColor ${ getDepthSpaces ( depth - 1 ) } $ msgId $ dateToPrint $ nameToPrint : $ gNotificationColor $ contentShifted $ gColorEndMarker " ;
2022-09-06 01:11:34 +05:30
isNotification = false ;
2022-09-10 22:30:56 +05:30
} else {
2024-03-31 17:14:01 +05:30
strToPrint = " ${ getDepthSpaces ( depth - 1 ) } $ msgId $ dateToPrint $ nameToPrint : $ contentShifted " ;
2022-09-06 01:11:34 +05:30
}
2022-09-04 00:15:33 +05:30
return strToPrint ;
}
2022-08-22 04:44:29 +05:30
// looks up global map of reactions, if this event has any reactions, and then prints the reactions
// in appropriate color( in case one is a notification, which is stored in member variable)
2022-12-28 14:55:59 +05:30
// returns the string and its length in a dynamic list
List < dynamic > getReactionStr ( int depth ) {
2022-09-03 23:05:55 +05:30
String reactorNames = " " ;
2022-12-28 14:55:59 +05:30
int len = 0 ;
2022-09-03 23:05:55 +05:30
if ( isHidden | | isDeleted ) {
2022-12-28 14:55:59 +05:30
return [ " " , 0 ] ;
2022-09-03 23:05:55 +05:30
}
2022-08-19 19:47:00 +05:30
if ( gReactions . containsKey ( id ) ) {
2022-09-09 03:31:23 +05:30
reactorNames = " Likes: " ;
2022-12-28 14:55:59 +05:30
len = reactorNames . length ;
2022-08-19 19:47:00 +05:30
int numReactions = gReactions [ id ] ? . length ? ? 0 ;
List < List < String > > reactors = gReactions [ id ] ? ? [ ] ;
2022-09-03 21:18:50 +05:30
bool firstEntry = true ;
2022-08-19 19:47:00 +05:30
for ( int i = 0 ; i < numReactions ; i + + ) {
2022-09-03 21:18:50 +05:30
String comma = ( firstEntry ) ? " " : " , " ;
2022-12-28 14:55:59 +05:30
String authorName = " " ;
2022-09-03 21:18:50 +05:30
2022-08-19 19:47:00 +05:30
String reactorId = reactors [ i ] [ 0 ] ;
2022-09-03 21:18:50 +05:30
if ( newLikes . contains ( reactorId ) & & reactors [ i ] [ 1 ] = = " + " ) {
// this is a notifications, print it and then later empty newLikes
2022-12-28 14:55:59 +05:30
authorName = getAuthorName ( reactorId ) ;
reactorNames + = comma + gNotificationColor + authorName + gColorEndMarker + gCommentColor ; // restart with comment color because this is part of ongoing print
len + = 2 + authorName . length ;
2022-09-03 21:18:50 +05:30
firstEntry = false ;
2022-08-20 03:12:32 +05:30
} else {
2022-09-03 21:18:50 +05:30
// this is normal printing of the reaction. only print for + for now
2024-03-31 17:14:01 +05:30
if ( reactors [ i ] [ 1 ] = = " + " ) {
2022-12-28 14:55:59 +05:30
authorName = getAuthorName ( reactorId ) ;
2024-03-31 17:14:01 +05:30
}
2022-12-28 14:55:59 +05:30
reactorNames + = comma + authorName ;
len + = ( 2 + authorName . length ) ;
2022-09-03 21:18:50 +05:30
firstEntry = false ;
2022-08-20 03:12:32 +05:30
}
2022-12-28 14:55:59 +05:30
2022-09-03 21:18:50 +05:30
} // end for
2022-12-29 10:24:26 +05:30
// if at least one entry as colored notification was made
if ( firstEntry = = false ) {
reactorNames + = gColorEndMarker ;
}
2022-08-20 03:12:32 +05:30
newLikes . clear ( ) ;
2022-09-09 03:31:23 +05:30
reactorNames + = " " ;
2022-08-19 19:47:00 +05:30
}
2022-12-28 14:55:59 +05:30
return [ reactorNames , len ] ;
2022-08-19 19:47:00 +05:30
}
2022-09-15 00:49:13 +05:30
2022-11-22 01:58:36 +05:30
// returns the last e tag as reply to event for kind 42 and 142 events
2022-12-06 13:46:21 +05:30
Event ? getReplyToChannelEvent ( Map < String , Tree > tempChildEventsMap ) {
2024-03-31 17:14:01 +05:30
switch ( kind ) {
2022-12-06 13:46:21 +05:30
case 42 :
case 142 :
for ( int i = tags . length - 1 ; i > = 0 ; i - - ) {
List tag = tags [ i ] ;
if ( tag [ 0 ] = = ' e ' ) {
String replyToEventId = tag [ 1 ] ;
2024-03-31 17:14:01 +05:30
Event ? eventInReplyTo = ( gStore ? . allChildEventsMap [ replyToEventId ] ? . event ) ;
2022-12-06 13:46:21 +05:30
if ( eventInReplyTo ! = null ) {
// add 1 cause 42 can reply to or tag kind 1, and we'll show that kind 1
if ( [ 1 , 42 , 142 ] . contains ( eventInReplyTo . eventData . kind ) ) {
return eventInReplyTo ;
}
2022-09-15 00:49:13 +05:30
}
}
}
2022-12-06 13:46:21 +05:30
break ;
case 1 :
String replyToId = getParent ( tempChildEventsMap ) ;
return tempChildEventsMap [ replyToId ] ? . event ;
} // end of switch
2022-09-15 00:49:13 +05:30
return null ;
2022-12-06 13:46:21 +05:30
} // end getReplyToChannelEvent()
2022-08-16 16:08:33 +05:30
}
2022-08-22 04:44:29 +05:30
// This is mostly a placeholder for EventData. TODO combine both?
2022-08-16 16:08:33 +05:30
class Event {
String event ;
String id ;
EventData eventData ;
String originalJson ;
List < String > seenOnRelays ;
2022-09-03 03:29:36 +05:30
bool readFromFile ;
2024-04-21 09:38:43 +05:30
bool userRelevant ; // is made true if the event has been printed for the user ( and its relevant to user, which will later be saved in file)
2022-08-16 16:08:33 +05:30
2024-04-21 09:38:43 +05:30
Event ( this . event , this . id , this . eventData , this . seenOnRelays , this . originalJson , [ this . readFromFile = false , this . userRelevant = false ] ) ;
2022-08-16 16:08:33 +05:30
2022-08-28 05:33:01 +05:30
@ override
bool operator = = ( other ) {
return ( other is Event ) & & eventData . id = = other . eventData . id ;
}
2022-09-03 03:29:36 +05:30
factory Event . fromJson ( String d , String relay , [ bool fromFile = false ] ) {
2022-08-21 00:11:50 +05:30
try {
2022-12-22 06:58:02 +05:30
if ( d . length > gMaxEventLenthAccepted ) {
//throw Exception("Event json is larger than max len");
}
2022-08-21 00:11:50 +05:30
dynamic json = jsonDecode ( d ) ;
2022-11-09 23:30:18 +05:30
2022-08-21 00:11:50 +05:30
if ( json . length < 3 ) {
String e = " " ;
e = json . length > 1 ? json [ 0 ] : " " ;
2022-11-09 23:30:18 +05:30
if ( gDebug > 0 ) {
print ( " Could not create event. json.length = ${ json . length } string d= $ d $ e " ) ;
2022-09-06 00:54:11 +05:30
}
2022-11-09 23:30:18 +05:30
throw Exception ( " Event json has less than 3 elements " ) ;
2022-08-21 00:11:50 +05:30
}
2022-11-09 23:30:18 +05:30
2022-09-04 14:51:04 +05:30
EventData newEventData = EventData . fromJson ( json [ 2 ] ) ;
2024-03-31 17:14:01 +05:30
if ( ! fromFile ) {
2022-09-04 14:51:04 +05:30
newEventData . isNotification = true ;
2024-03-31 17:14:01 +05:30
}
2022-09-04 14:51:04 +05:30
return Event ( json [ 0 ] as String , json [ 1 ] as String , newEventData , [ relay ] , d , fromFile ) ;
2022-08-21 00:11:50 +05:30
} on Exception catch ( e ) {
2022-12-02 12:00:12 +05:30
if ( gDebug > 0 ) {
print ( " Could not create event. $ e \n problem str: $ d \n " ) ;
2022-08-30 14:58:27 +05:30
}
2024-03-31 17:14:01 +05:30
rethrow ;
2022-08-16 16:08:33 +05:30
}
}
2022-09-09 03:31:23 +05:30
void printEvent ( int depth , bool topPost ) {
2022-11-25 19:26:12 +05:30
eventData . printEventData ( depth , topPost , null , null , null ) ;
2022-09-06 00:54:11 +05:30
//stdout.write("\n$originalJson --------------------------------\n\n");
2022-08-16 16:08:33 +05:30
}
@ override
String toString ( ) {
return ' $ eventData Seen on: ${ seenOnRelays [ 0 ] } \n ' ;
}
}
2022-08-22 04:06:18 +05:30
// for the user userPubkey, returns the relay of its contact contactPubkey
String getRelayOfUser ( String userPubkey , String contactPubkey ) {
if ( gDebug > 0 ) print ( " In getRelayOfUser: Searching relay for contact $ contactPubkey " ) ;
String relay = " " ;
if ( userPubkey = = " " | | contactPubkey = = " " ) {
return " " ;
}
if ( gContactLists . containsKey ( userPubkey ) ) {
List < Contact > ? contacts = gContactLists [ userPubkey ] ;
if ( contacts ! = null ) {
for ( int i = 0 ; i < contacts . length ; i + + ) {
2022-12-26 15:28:24 +05:30
if ( contacts [ i ] . contactPubkey = = contactPubkey ) {
2022-08-22 04:06:18 +05:30
relay = contacts [ i ] . relay ;
return relay ;
}
}
}
}
// if not found return empty string
return relay ;
2022-08-22 09:05:23 +05:30
}
2022-12-25 01:24:17 +05:30
// https://codewithandrea.com/articles/flutter-exception-handling-try-catch-result-type/
2022-11-15 07:55:35 +05:30
Future < http . Response > fetchNip05Info ( String nip05Url ) {
2022-12-25 01:36:41 +05:30
http . Response resp404 = http . Response . bytes ( [ ] , 404 ) ;
2022-12-25 01:24:17 +05:30
try {
return http . get ( Uri . parse ( nip05Url ) ) ;
} catch ( ex ) {
2022-12-25 01:36:41 +05:30
return Future . value ( resp404 ) ;
2022-12-25 01:24:17 +05:30
}
2022-11-15 07:55:35 +05:30
}
2022-08-27 23:55:01 +05:30
// If given event is kind 0 event, then populates gKindONames with that info
2022-08-29 04:59:28 +05:30
// returns true if entry was created or modified, false otherwise
bool processKind0Event ( Event e ) {
2022-08-27 23:55:01 +05:30
if ( e . eventData . kind ! = 0 ) {
2022-08-29 04:59:28 +05:30
return false ;
2022-08-27 23:55:01 +05:30
}
String content = e . eventData . content ;
if ( content . isEmpty ) {
2022-08-29 04:59:28 +05:30
return false ;
2022-08-27 23:55:01 +05:30
}
2022-08-29 04:59:28 +05:30
String name = " " ;
String about = " " ;
String picture = " " ;
2022-12-26 23:19:22 +05:30
String lud06 = " " ;
2022-12-27 00:26:04 +05:30
String lud16 = " " ;
2024-03-31 17:14:01 +05:30
String displayName = " " ;
2022-12-29 21:22:23 +05:30
String website = " " ;
2022-11-15 07:55:35 +05:30
String nip05 = " " ;
2022-08-27 23:55:01 +05:30
try {
dynamic json = jsonDecode ( content ) ;
2022-11-16 10:51:08 +05:30
name = json [ " name " ] ? ? " " ;
about = json [ " about " ] ? ? " " ;
picture = json [ " picture " ] ? ? " " ;
2022-12-26 23:19:22 +05:30
lud06 = json [ " lud06 " ] ? ? " " ;
2022-12-27 00:26:04 +05:30
lud16 = json [ " lud16 " ] ? ? " " ;
2024-03-31 17:14:01 +05:30
displayName = json [ " display_name " ] ? ? " " ;
2022-12-29 21:22:23 +05:30
website = json [ " website " ] ? ? " " ;
2022-11-16 10:51:08 +05:30
nip05 = json [ ' nip05 ' ] ? ? " " ;
//String twitterId = json['twitter']??"";
//String githubId = json['github']??"";
2022-08-27 23:55:01 +05:30
} catch ( ex ) {
2022-11-16 10:51:08 +05:30
if ( gDebug > 0 ) print ( " Error in processKind0Event: $ ex for pubkey: ${ e . eventData . pubkey } " ) ;
2022-08-27 23:55:01 +05:30
}
2022-08-29 04:59:28 +05:30
bool newEntry = false , entryModified = false ;
if ( ! gKindONames . containsKey ( e . eventData . pubkey ) ) {
2024-03-31 17:14:01 +05:30
gKindONames [ e . eventData . pubkey ] = UserNameInfo ( e . eventData . createdAt , name , about , picture , lud06 , lud16 , displayName , website , nip05 , null ) ;
newEntry = true ;
2022-08-29 04:59:28 +05:30
} else {
int oldTime = gKindONames [ e . eventData . pubkey ] ? . createdAt ? ? 0 ;
if ( oldTime < e . eventData . createdAt ) {
2024-03-31 17:14:01 +05:30
gKindONames [ e . eventData . pubkey ] = UserNameInfo ( e . eventData . createdAt , name , about , picture , lud06 , lud16 , displayName , website , nip05 , null ) ;
2022-11-20 18:46:27 +05:30
entryModified = true ;
2022-08-27 23:55:01 +05:30
}
}
2022-08-29 04:59:28 +05:30
if ( gDebug > 0 ) {
2022-11-30 14:02:22 +05:30
print ( " At end of processKind0Events: for pubkey ${ e . eventData . pubkey } for name = $ name ${ newEntry ? " added entry " : ( entryModified ? " modified entry " : " No change done " ) } " ) ;
2022-08-29 04:59:28 +05:30
}
2022-11-15 07:55:35 +05:30
bool localDebug = false ; //e.eventData.pubkey == "9ec7a778167afb1d30c4833de9322da0c08ba71a69e1911d5578d3144bb56437"? true: false;
if ( newEntry | | entryModified ) {
2024-03-31 17:14:01 +05:30
if ( nip05 . isNotEmpty ) {
2022-11-15 07:55:35 +05:30
List < String > urlSplit = nip05 . split ( " @ " ) ;
if ( urlSplit . length = = 2 ) {
2024-03-31 17:14:01 +05:30
String urlNip05 = " ${ urlSplit [ 1 ] } /.well-known/nostr.json?name= ${ urlSplit [ 0 ] } " ;
2022-11-15 07:55:35 +05:30
if ( ! urlNip05 . startsWith ( " http " ) ) {
2024-03-31 17:14:01 +05:30
urlNip05 = " http:// $ urlNip05 " ;
2022-11-15 07:55:35 +05:30
}
2022-11-20 18:46:27 +05:30
2022-11-15 07:55:35 +05:30
fetchNip05Info ( urlNip05 )
. then ( ( httpResponse ) {
2022-11-20 13:18:45 +05:30
if ( localDebug ) print ( " ----- \n nip future for $ urlNip05 returned body ${ httpResponse . body } " ) ;
2022-11-15 07:55:35 +05:30
var namesInResponse ;
try {
dynamic json = jsonDecode ( httpResponse . body ) ;
namesInResponse = json [ " names " ] ;
if ( namesInResponse . length > 0 ) {
2022-11-16 10:51:08 +05:30
for ( var returntedName in namesInResponse . keys ) {
if ( returntedName = = urlSplit [ 0 ] & & namesInResponse [ returntedName ] = = e . eventData . pubkey ) {
2022-11-20 18:46:27 +05:30
int oldTime = 0 ;
2022-11-16 10:51:08 +05:30
if ( ! gKindONames . containsKey ( e . eventData . pubkey ) ) {
2022-11-20 18:46:27 +05:30
//printWarning("in response handing. creating user info");
2024-03-31 17:14:01 +05:30
gKindONames [ e . eventData . pubkey ] = UserNameInfo ( e . eventData . createdAt , name , about , picture , lud06 , lud16 , displayName , website , null , null ) ;
2022-11-20 18:46:27 +05:30
} else {
oldTime = gKindONames [ e . eventData . pubkey ] ? . createdAt ? ? 0 ;
//print("in response handing. user info exists with old time = $oldTime and this event time = ${e.eventData.createdAt}");
2022-11-16 10:51:08 +05:30
}
2022-11-20 18:46:27 +05:30
if ( oldTime < = e . eventData . createdAt ) {
gKindONames [ e . eventData . pubkey ] ? . nip05Verified = true ;
gKindONames [ e . eventData . pubkey ] ? . nip05Id = nip05 ;
}
2022-11-15 07:55:35 +05:30
return ;
}
}
} else {
//print("names = 0");
}
} catch ( ex ) {
}
} )
. catchError ( ( e ) { if ( gDebug > 0 ) print ( ' in fetch nip caught error $ e for url $ urlNip05 ' ) ; } ) ;
}
}
}
2022-08-29 04:59:28 +05:30
return newEntry | | entryModified ;
2022-08-27 23:55:01 +05:30
}
2022-08-31 22:15:17 +05:30
// If given event is kind 3 event, then populates gKindONames with contact info
// returns true if entry was created or modified, false otherwise
bool processKind3Event ( Event newContactEvent ) {
if ( newContactEvent . eventData . kind ! = 3 ) {
return false ;
}
bool newEntry = false , entryModified = false ;
if ( ! gKindONames . containsKey ( newContactEvent . eventData . pubkey ) ) {
2022-12-29 21:22:23 +05:30
gKindONames [ newContactEvent . eventData . pubkey ] = UserNameInfo ( null , null , null , null , null , null , null , null , null , newContactEvent , newContactEvent . eventData . createdAt ) ;
2024-03-31 17:14:01 +05:30
newEntry = true ;
2022-08-31 22:15:17 +05:30
} else {
// if entry already exists, then check its old time and update only if we have a newer entry now
int oldTime = gKindONames [ newContactEvent . eventData . pubkey ] ? . createdAtKind3 ? ? 0 ;
if ( oldTime < newContactEvent . eventData . createdAt ) {
2024-03-31 17:14:01 +05:30
int ? createdAt = gKindONames [ newContactEvent . eventData . pubkey ] ? . createdAt ;
2022-11-26 01:04:12 +05:30
String ? name = gKindONames [ newContactEvent . eventData . pubkey ] ? . name ,
about = gKindONames [ newContactEvent . eventData . pubkey ] ? . about ,
picture = gKindONames [ newContactEvent . eventData . pubkey ] ? . picture ,
2022-12-26 23:19:22 +05:30
lud06 = gKindONames [ newContactEvent . eventData . pubkey ] ? . lud06 ,
2022-12-27 00:26:04 +05:30
lud16 = gKindONames [ newContactEvent . eventData . pubkey ] ? . lud16 ,
2024-03-31 17:14:01 +05:30
displayName = gKindONames [ newContactEvent . eventData . pubkey ] ? . display_name ,
2022-12-29 21:22:23 +05:30
website = gKindONames [ newContactEvent . eventData . pubkey ] ? . website ,
2022-11-26 01:04:12 +05:30
nip05id = gKindONames [ newContactEvent . eventData . pubkey ] ? . nip05Id ? ? " " ;
2022-08-31 22:15:17 +05:30
2024-03-31 17:14:01 +05:30
gKindONames [ newContactEvent . eventData . pubkey ] = UserNameInfo ( createdAt , name , about , picture , lud06 , lud16 , displayName , website , nip05id , newContactEvent , newContactEvent . eventData . createdAt ) ;
entryModified = true ;
2022-08-31 22:15:17 +05:30
}
}
if ( gDebug > 0 ) {
print ( " At end of processKind3Events: ${ newEntry ? " added entry " : ( entryModified ? " modified entry " : " No change done " ) } " ) ;
}
return newEntry | | entryModified ;
}
2022-11-15 07:55:35 +05:30
String getNip05Name ( String pubkey ) {
String nip05name = " " ;
if ( gKindONames [ pubkey ] ? . name = = null | | gKindONames [ pubkey ] ? . name ? . length = = 0 ) {
nip05name = " " ;
}
else {
String name = gKindONames [ pubkey ] ? . name ? ? " " ;
if ( gKindONames [ pubkey ] ? . nip05Verified ? ? false ) {
nip05name = " $ name (nip05: ${ gKindONames [ pubkey ] ? . nip05Id ? ? " " } ) " ;
} else {
nip05name = name ;
}
}
return nip05name ;
}
2022-08-27 23:55:01 +05:30
// returns name by looking up global list gKindONames, which is populated by kind 0 events
2022-12-29 02:20:58 +05:30
String getAuthorName ( String pubkey , { int maxDisplayLen = gMaxInteger , int pubkeyLenShown = 5 } ) {
2024-03-31 17:14:01 +05:30
if ( gFollowList . isEmpty ) {
2022-12-29 02:20:58 +05:30
gFollowList = getFollows ( userPublicKey ) ;
}
bool isFollow = gFollowList . contains ( pubkey ) & & ( pubkey ! = userPublicKey ) ;
2022-12-24 22:08:01 +05:30
String maxLen ( String pubkey ) = > pubkey . length > pubkeyLenShown ? pubkey . substring ( 0 , pubkeyLenShown ) : pubkey . substring ( 0 , pubkey . length ) ;
2022-09-09 20:44:54 +05:30
String name = " " ;
2024-03-31 17:14:01 +05:30
if ( gKindONames [ pubkey ] ? . name = = null | | gKindONames [ pubkey ] ? . name ? . length = = 0 ) {
name = maxLen ( pubkey ) ;
} else {
2022-12-24 22:08:01 +05:30
name = ( gKindONames [ pubkey ] ? . name ) ? ? maxLen ( pubkey ) ;
2022-11-15 07:55:35 +05:30
}
2022-12-24 22:08:01 +05:30
2022-12-29 02:20:58 +05:30
// then add valid check mark in default follows
if ( isFollow ) {
if ( name . length > = maxDisplayLen ) {
name = name . substring ( 0 , maxDisplayLen - 1 ) + gValidCheckMark ;
} else {
name = name + gValidCheckMark ;
}
} else {
2024-04-07 12:41:30 +05:30
// remove this tick from other names to avoid 'tick hack'
2022-12-28 01:00:34 +05:30
name = name . replaceAll ( gValidCheckMark , " " ) ;
2024-04-07 12:41:30 +05:30
}
return name ;
}
// returns only name if it is available. Returns null if name is not available.
String ? getOnlyAuthorName ( String pubkey , { int maxDisplayLen = gMaxInteger , int pubkeyLenShown = 5 } ) {
if ( gFollowList . isEmpty ) {
gFollowList = getFollows ( userPublicKey ) ;
}
2022-12-24 22:08:01 +05:30
2024-04-07 12:41:30 +05:30
bool isFollow = gFollowList . contains ( pubkey ) & & ( pubkey ! = userPublicKey ) ;
String maxLen ( String pubkey ) = > pubkey . length > pubkeyLenShown ? pubkey . substring ( 0 , pubkeyLenShown ) : pubkey . substring ( 0 , pubkey . length ) ;
String name = " " ;
var temp = gKindONames [ pubkey ] ;
if ( temp = = null ) {
return null ;
} else {
var temp2 = temp . name ;
if ( temp2 = = null ) {
return null ;
} else {
name = temp2 ;
}
}
// then add valid check mark in default follows
if ( isFollow ) {
if ( name . length > = maxDisplayLen ) {
name = name . substring ( 0 , maxDisplayLen - 1 ) + gValidCheckMark ;
} else {
name = name + gValidCheckMark ;
}
} else {
// remove this tick from other names to avoid 'tick hack'
name = name . replaceAll ( gValidCheckMark , " " ) ;
2022-12-24 22:08:01 +05:30
}
2022-12-29 02:20:58 +05:30
2022-08-27 23:55:01 +05:30
return name ;
}
2024-04-07 12:41:30 +05:30
2022-08-24 22:07:03 +05:30
// returns full public key(s) for the given username( which can be first few letters of pubkey, or the user name)
2022-09-10 19:33:20 +05:30
Set < String > getPublicKeyFromName ( String inquiredName ) {
2024-03-31 17:14:01 +05:30
if ( inquiredName . isEmpty ) {
2022-09-10 19:33:20 +05:30
return { } ;
}
2022-08-22 09:05:23 +05:30
Set < String > pubkeys = { } ;
2022-09-10 19:33:20 +05:30
gKindONames . forEach ( ( pubkey , userInfo ) {
2022-08-24 22:07:03 +05:30
// check both the user name, and the pubkey to search for the user
2022-09-10 19:33:20 +05:30
// check username
if ( userInfo . name ! = null ) {
int minNameLen = min ( inquiredName . length , ( userInfo . name ? . length ) ? ? 0 ) ;
if ( inquiredName . toLowerCase ( ) = = userInfo . name ? . substring ( 0 , minNameLen ) . toLowerCase ( ) ) {
pubkeys . add ( pubkey ) ;
}
2022-08-22 09:05:23 +05:30
}
2022-09-10 19:33:20 +05:30
// check public key
2022-11-18 19:57:59 +05:30
if ( inquiredName . length > = 2 & & inquiredName . length < = pubkey . length ) {
2022-09-10 19:33:20 +05:30
if ( pubkey . substring ( 0 , inquiredName . length ) = = inquiredName ) {
pubkeys . add ( pubkey ) ;
2022-08-25 18:33:29 +05:30
}
2022-08-22 09:05:23 +05:30
}
} ) ;
return pubkeys ;
2022-08-23 16:47:24 +05:30
}
2022-08-26 12:41:30 +05:30
2022-12-24 13:12:16 +05:30
// returns the seconds since epoch N days ago
2022-08-28 06:38:38 +05:30
int getSecondsDaysAgo ( int N ) {
return DateTime . now ( ) . subtract ( Duration ( days: N ) ) . millisecondsSinceEpoch ~ / 1000 ;
}
2022-12-24 13:12:16 +05:30
// returns the seconds since epoch S seconds ago
int getTimeSecondsAgo ( int S ) {
return DateTime . now ( ) . subtract ( Duration ( seconds: S ) ) . millisecondsSinceEpoch ~ / 1000 ;
}
2022-11-29 19:32:28 +05:30
// will write d tabs worth of space ( where tab width is in settings)
2022-09-01 15:53:17 +05:30
void printDepth ( int d ) {
for ( int i = 0 ; i < gSpacesPerDepth * d + gNumLeftMarginSpaces ; i + + ) {
stdout . write ( " " ) ;
}
}
2022-09-06 00:20:55 +05:30
void printCenteredHeadline ( displayName ) {
int numDashes = 10 ; // num of dashes on each side
int startText = gNumLeftMarginSpaces + ( gTextWidth - ( displayName . length + 2 * numDashes ) ) ~ / 2 ;
2024-03-31 17:14:01 +05:30
if ( startText < 0 ) {
2022-09-06 00:20:55 +05:30
startText = 0 ;
2024-03-31 17:14:01 +05:30
}
2022-09-06 00:20:55 +05:30
String str = getNumSpaces ( startText ) + getNumDashes ( numDashes ) + displayName + getNumDashes ( numDashes ) ;
print ( str ) ;
}
2022-09-03 23:05:55 +05:30
String getDepthSpaces ( int d ) {
String str = " " ;
for ( int i = 0 ; i < gSpacesPerDepth * d + gNumLeftMarginSpaces ; i + + ) {
str + = " " ;
}
return str ;
}
2022-09-10 22:30:56 +05:30
// make a paragraph of s that starts at numSpaces ( from screen left), and does not extend beyond gTextWidth+gNumLeftMarginSpaces. break it, or add
// a newline if it goes beyond gTextWidth + gNumLeftMarginSpaces
2022-09-11 17:17:25 +05:30
String makeParagraphAtDepth ( String s , int depthInSpaces ) {
2022-11-12 23:34:00 +05:30
List < List < int > > urlRanges = getUrlRanges ( s ) ;
2022-09-01 15:53:17 +05:30
String newString = " " ;
2022-09-11 17:17:25 +05:30
String spacesString = getNumSpaces ( depthInSpaces + gNumLeftMarginSpaces ) ;
int lenPerLine = gTextWidth - depthInSpaces ;
2022-09-12 01:36:14 +05:30
//print("In makeParagraphAtDepth: gNumLeftMarginSpaces = $gNumLeftMarginSpaces depthInSPaces = $depthInSpaces LenPerLine = $lenPerLine gTextWidth = $gTextWidth ");
2022-09-11 17:17:25 +05:30
for ( int startIndex = 0 ; startIndex < s . length ; ) {
2022-11-12 23:34:00 +05:30
List listCulledLine = getLineWithMaxLen ( s , startIndex , lenPerLine , spacesString , urlRanges ) ;
2022-09-11 17:17:25 +05:30
String line = listCulledLine [ 0 ] ;
int lenReturned = listCulledLine [ 1 ] as int ;
2024-03-31 17:14:01 +05:30
if ( line . isEmpty | | lenReturned = = 0 ) break ;
2022-09-12 01:36:14 +05:30
2022-09-11 17:17:25 +05:30
newString + = line ;
startIndex + = lenReturned ;
2022-09-01 15:53:17 +05:30
}
2022-09-11 17:17:25 +05:30
2022-09-01 15:53:17 +05:30
return newString ;
}
2022-09-11 17:17:25 +05:30
// returns from string[startIndex:] the first len number of chars. no newline is added.
2022-11-12 23:34:00 +05:30
List getLineWithMaxLen ( String s , int startIndex , int lenPerLine , String spacesString , List < List < int > > urlRanges ) {
2022-09-11 17:17:25 +05:30
2024-03-31 17:14:01 +05:30
if ( startIndex > = s . length ) {
2022-09-11 17:17:25 +05:30
return [ " " , 0 ] ;
2024-03-31 17:14:01 +05:30
}
2022-09-11 17:17:25 +05:30
2022-11-12 23:34:00 +05:30
String line = " " ; // is returned
2022-09-11 17:17:25 +05:30
// if length required is greater than the length of string remaing, return whatever remains
int numCharsInLine = 0 ;
int i = startIndex ;
2022-11-12 23:34:00 +05:30
// i indexes over the input line ( which is the whole comment)
2022-09-12 01:36:14 +05:30
for ( ; i < startIndex + lenPerLine & & i < s . length ; i + + ) {
2022-09-11 17:17:25 +05:30
line + = s [ i ] ;
numCharsInLine + + ;
if ( s [ i ] = = " \n " ) {
i + + ;
numCharsInLine = 0 ;
line + = spacesString ;
break ;
}
}
2022-11-12 23:34:00 +05:30
int urlEnd = 0 ;
if ( ( urlEnd = isInRange ( i , urlRanges ) ) ! = 0 ) {
line = line + s . substring ( i , urlEnd ) ;
i = urlEnd ;
} else {
2022-09-12 01:36:14 +05:30
2022-11-12 23:34:00 +05:30
if ( numCharsInLine > lenPerLine | | ( ( numCharsInLine = = lenPerLine ) & & ( s . length > startIndex + numCharsInLine ) ) ) {
bool lineBroken = false ;
2022-09-12 01:36:14 +05:30
2022-11-12 23:34:00 +05:30
// line is broken only if the returned line is the longest it can be, and
// if its length is greater than the gMaxLenBrokenWord constant
if ( line . length > = lenPerLine & & line . length > gMaxLenUnbrokenWord ) {
int i = line . length - 1 ;
2022-09-12 01:36:14 +05:30
2022-11-12 23:34:00 +05:30
// find a whitespace character
2024-03-31 17:14:01 +05:30
for ( ; i > 0 & & ! isWordSeparater ( line [ i ] ) ; i - - ) {
{ }
}
2022-11-12 23:34:00 +05:30
// for ended
2022-09-12 01:36:14 +05:30
2022-11-12 23:34:00 +05:30
if ( line . length - i < gMaxLenUnbrokenWord ) {
2022-09-12 01:36:14 +05:30
2022-11-12 23:34:00 +05:30
// break the line here if its a word separator
if ( isWordSeparater ( line [ i ] ) ) {
int newLineStart = i + 1 ;
2024-03-31 17:14:01 +05:30
if ( line [ i ] ! = ' ' ) {
2022-11-12 23:34:00 +05:30
newLineStart = i ;
2024-03-31 17:14:01 +05:30
}
line = " ${ line . substring ( 0 , i ) } \n $ spacesString ${ line . substring ( newLineStart , line . length ) } " ;
2022-11-12 23:34:00 +05:30
lineBroken = true ;
}
2022-09-12 01:36:14 +05:30
}
2022-11-12 23:34:00 +05:30
2022-09-12 01:36:14 +05:30
}
2022-11-12 23:34:00 +05:30
if ( ! lineBroken ) {
if ( s . length > i ) {
line + = " \n " ;
line + = spacesString ;
}
2022-09-12 01:36:14 +05:30
}
}
2022-09-11 17:17:25 +05:30
}
return [ line , i - startIndex ] ;
}
2022-09-01 15:53:17 +05:30
// The contact only stores id and relay of contact. The actual name is stored in a global variable/map
2022-12-24 20:11:26 -05:00
class Contact implements Comparable < Contact > {
2022-12-26 15:28:24 +05:30
String contactPubkey , relay ;
Contact ( this . contactPubkey , this . relay ) ;
2022-09-01 15:53:17 +05:30
@ override
String toString ( ) {
2022-12-26 15:28:24 +05:30
return ' id: $ contactPubkey ( ${ getAuthorName ( contactPubkey ) } ) relay: $ relay ' ;
2022-09-01 15:53:17 +05:30
}
2022-12-24 20:11:26 -05:00
@ override
int compareTo ( Contact other ) {
2022-12-26 15:28:24 +05:30
return getAuthorName ( contactPubkey ) . compareTo ( getAuthorName ( other . contactPubkey ) ) ;
2022-12-24 20:11:26 -05:00
}
2022-09-01 15:53:17 +05:30
}
2022-11-09 23:30:18 +05:30
String getShaId ( String pubkey , String createdAt , String kind , String strTags , String content ) {
2022-09-01 15:53:17 +05:30
String buf = ' [0," $ pubkey ", $ createdAt , $ kind ,[ $ strTags ]," $ content "] ' ;
2022-11-19 21:17:39 +05:30
if ( gSpecificDebug > 0 ) print ( " in getShaId for buf: | $ buf | " ) ;
2022-09-01 15:53:17 +05:30
var bufInBytes = utf8 . encode ( buf ) ;
var value = sha256 . convert ( bufInBytes ) ;
return value . toString ( ) ;
}
// get printable date from seconds since epoch
String getPrintableDate ( int createdAt ) {
final df1 = DateFormat ( ' hh:mm a ' ) ;
final df2 = DateFormat ( DateFormat . ABBR_MONTH_DAY ) ;
String strDate = df1 . format ( DateTime . fromMillisecondsSinceEpoch ( createdAt * 1000 ) ) ;
strDate + = " ${ df2 . format ( DateTime . fromMillisecondsSinceEpoch ( createdAt * 1000 ) ) } " ;
return strDate ;
}
2022-09-01 21:55:51 +05:30
2022-09-05 22:48:11 +05:30
/ *
2022-12-01 21:25:40 +05:30
* Returns true if this is a valid direct message to just this user . Direct message = kind 4 AND 104
2022-09-05 22:48:11 +05:30
* /
2022-12-01 21:25:40 +05:30
bool isValidDirectMessage ( EventData directMessageData , { int acceptableKind = 4 } ) {
2022-11-25 19:26:12 +05:30
2022-12-01 21:25:40 +05:30
if ( acceptableKind ! = directMessageData . kind ) {
return false ;
2022-11-25 19:26:12 +05:30
}
2022-12-01 21:25:40 +05:30
bool validUserMessage = false ;
List < String > allPtags = [ ] ;
2024-03-31 17:14:01 +05:30
for ( var tag in directMessageData . tags ) {
2022-11-25 19:26:12 +05:30
if ( tag . length < 2 ) {
2024-03-31 17:14:01 +05:30
continue ;
2022-11-25 19:26:12 +05:30
}
2022-09-05 22:48:11 +05:30
if ( tag [ 0 ] = = " p " & & tag [ 1 ] . length = = 64 ) { // basic length sanity test
allPtags . add ( tag [ 1 ] ) ;
2022-09-01 21:55:51 +05:30
}
2024-03-31 17:14:01 +05:30
}
2022-09-01 21:55:51 +05:30
2024-03-31 17:14:01 +05:30
if ( gDebug > = 0 & & gCheckEventId = = directMessageData . id ) print ( " In isvalid direct message: ptags len: ${ allPtags . length } , ptags = $ allPtags " ) ;
2022-11-25 19:26:12 +05:30
2022-09-05 22:48:11 +05:30
if ( directMessageData . pubkey = = userPublicKey & & allPtags . length = = 1 ) {
2022-11-18 20:15:38 +05:30
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
}
2022-09-05 22:48:11 +05:30
} else {
2022-11-25 19:26:12 +05:30
if ( gCheckEventId = = directMessageData . id ) print ( " in else case 3 " ) ;
2022-09-05 22:48:11 +05:30
if ( directMessageData . pubkey ! = userPublicKey ) {
2022-11-25 19:26:12 +05:30
if ( gDebug > 0 & & gCheckEventId = = directMessageData . id ) print ( " in if 5 allpags 1st = ${ allPtags [ 0 ] } userPUblic key = $ userPublicKey " ) ;
2022-09-05 22:48:11 +05:30
if ( allPtags . length = = 1 & & allPtags [ 0 ] = = userPublicKey ) {
2022-11-25 19:26:12 +05:30
2022-09-05 22:48:11 +05:30
validUserMessage = true ; // case where this user is recipeint
}
}
}
return validUserMessage ;
2022-09-01 21:55:51 +05:30
}
2022-10-30 00:27:11 +05:30
String getRandomPrivKey ( ) {
FortunaRandom fr = FortunaRandom ( ) ;
2024-03-31 17:14:01 +05:30
final sGen = Random . secure ( ) ;
2022-10-30 00:27:11 +05:30
fr . seed ( KeyParameter (
2024-03-31 17:14:01 +05:30
Uint8List . fromList ( List . generate ( 32 , ( _ ) = > sGen . nextInt ( 255 ) ) ) ) ) ;
2022-10-30 00:27:11 +05:30
BigInt randomNumber = fr . nextBigInteger ( 256 ) ;
String strKey = randomNumber . toRadixString ( 16 ) ;
2022-10-30 11:46:51 +05:30
if ( strKey . length < 64 ) {
int numZeros = 64 - strKey . length ;
for ( int i = 0 ; i < numZeros ; i + + ) {
2024-03-31 17:14:01 +05:30
strKey = " 0 $ strKey " ;
2022-10-30 11:46:51 +05:30
}
}
2022-10-30 00:27:11 +05:30
return strKey ;
}
2022-09-05 22:48:11 +05:30
// pointy castle source https://github.com/PointyCastle/pointycastle/blob/master/tutorials/aes-cbc.md
// https://github.com/bcgit/pc-dart/blob/master/tutorials/aes-cbc.md
// 3 https://github.com/Dhuliang/flutter-bsv/blob/42a2d92ec6bb9ee3231878ffe684e1b7940c7d49/lib/src/aescbc.dart
2022-09-02 01:14:23 +05:30
/// Decrypt data using self private key
2022-09-03 03:29:36 +05:30
String myPrivateDecrypt ( String privateString ,
String publicString ,
String b64encoded ,
[ String b64IV = " " ] ) {
2022-09-07 03:53:15 +05:30
2022-09-03 03:29:36 +05:30
Uint8List encdData = convert . base64 . decode ( b64encoded ) ;
final rawData = myPrivateDecryptRaw ( privateString , publicString , encdData , b64IV ) ;
2022-09-05 22:48:11 +05:30
return convert . Utf8Decoder ( ) . convert ( rawData . toList ( ) ) ;
2022-09-03 03:29:36 +05:30
}
2022-09-01 21:55:51 +05:30
2022-09-07 03:53:15 +05:30
Map < String , List < List < int > > > gMapByteSecret = { } ;
2022-09-03 03:29:36 +05:30
Uint8List myPrivateDecryptRaw ( String privateString ,
String publicString ,
Uint8List cipherText ,
[ String b64IV = " " ] ) {
2022-12-26 16:06:50 +05:30
try {
List < List < int > > byteSecret = [ ] ;
if ( gMapByteSecret . containsKey ( publicString ) ) {
2022-09-07 03:53:15 +05:30
byteSecret = gMapByteSecret [ publicString ] ? ? [ ] ;
2022-12-26 16:06:50 +05:30
}
2022-09-03 03:29:36 +05:30
2022-12-26 16:06:50 +05:30
if ( byteSecret . isEmpty ) {
2024-03-31 17:14:01 +05:30
byteSecret = Kepler . byteSecret ( privateString , publicString ) ;
2022-12-26 16:06:50 +05:30
gMapByteSecret [ publicString ] = byteSecret ;
}
2022-09-07 03:53:15 +05:30
2022-12-26 16:06:50 +05:30
final secretIV = byteSecret ;
final key = Uint8List . fromList ( secretIV [ 0 ] ) ;
final iv = b64IV . length > 6
? convert . base64 . decode ( b64IV )
: Uint8List . fromList ( secretIV [ 1 ] ) ;
2022-09-03 03:29:36 +05:30
2024-03-31 17:14:01 +05:30
CipherParameters params = PaddedBlockCipherParameters (
ParametersWithIV ( KeyParameter ( key ) , iv ) , null ) ;
2022-09-03 03:29:36 +05:30
2024-03-31 17:14:01 +05:30
PaddedBlockCipherImpl cipherImpl = PaddedBlockCipherImpl (
PKCS7Padding ( ) , CBCBlockCipher ( AESEngine ( ) ) ) ;
2022-09-07 03:53:15 +05:30
2022-12-26 16:06:50 +05:30
cipherImpl . init ( false ,
params as PaddedBlockCipherParameters < CipherParameters ? ,
CipherParameters ? > ) ;
final Uint8List finalPlainText = Uint8List ( cipherText . length ) ; // allocate space
2022-09-03 03:29:36 +05:30
2022-12-26 16:06:50 +05:30
var offset = 0 ;
while ( offset < cipherText . length - 16 ) {
offset + = cipherImpl . processBlock ( cipherText , offset , finalPlainText , offset ) ;
}
2022-09-02 01:14:23 +05:30
2022-12-26 16:06:50 +05:30
//remove padding
offset + = cipherImpl . doFinal ( cipherText , offset , finalPlainText , offset ) ;
return finalPlainText . sublist ( 0 , offset ) ;
} catch ( e ) {
if ( gDebug > = 0 ) print ( " Decryption error = $ e " ) ;
return Uint8List ( 0 ) ;
2022-09-02 01:14:23 +05:30
}
2022-09-03 03:29:36 +05:30
}
2022-12-26 16:06:50 +05:30
// Encrypt data using self private key in nostr format ( with trailing ?iv=)
2022-09-05 22:48:11 +05:30
String myEncrypt ( String privateString ,
String publicString ,
String plainText ) {
Uint8List uintInputText = convert . Utf8Encoder ( ) . convert ( plainText ) ;
final encryptedString = myEncryptRaw ( privateString , publicString , uintInputText ) ;
return encryptedString ;
}
2022-09-05 17:14:34 +05:30
2022-09-05 22:48:11 +05:30
String myEncryptRaw ( String privateString ,
String publicString ,
Uint8List uintInputText ) {
final secretIV = Kepler . byteSecret ( privateString , publicString ) ;
final key = Uint8List . fromList ( secretIV [ 0 ] ) ;
// generate iv https://stackoverflow.com/questions/63630661/aes-engine-not-initialised-with-pointycastle-securerandom
FortunaRandom fr = FortunaRandom ( ) ;
2024-03-31 17:14:01 +05:30
final sGen = Random . secure ( ) ;
2022-09-05 22:48:11 +05:30
fr . seed ( KeyParameter (
2024-03-31 17:14:01 +05:30
Uint8List . fromList ( List . generate ( 32 , ( _ ) = > sGen . nextInt ( 255 ) ) ) ) ) ;
2022-12-26 16:06:50 +05:30
final iv = fr . nextBytes ( 16 ) ;
2022-09-05 22:48:11 +05:30
2024-03-31 17:14:01 +05:30
CipherParameters params = PaddedBlockCipherParameters (
ParametersWithIV ( KeyParameter ( key ) , iv ) , null ) ;
2022-09-05 17:14:34 +05:30
2024-03-31 17:14:01 +05:30
PaddedBlockCipherImpl cipherImpl = PaddedBlockCipherImpl (
PKCS7Padding ( ) , CBCBlockCipher ( AESEngine ( ) ) ) ;
2022-09-05 17:14:34 +05:30
2022-09-05 22:48:11 +05:30
cipherImpl . init ( true , // means to encrypt
params as PaddedBlockCipherParameters < CipherParameters ? ,
CipherParameters ? > ) ;
2022-12-26 16:06:50 +05:30
// allocate space
final Uint8List outputEncodedText = Uint8List ( uintInputText . length + 16 ) ;
2022-09-05 17:14:34 +05:30
2022-09-05 22:48:11 +05:30
var offset = 0 ;
while ( offset < uintInputText . length - 16 ) {
offset + = cipherImpl . processBlock ( uintInputText , offset , outputEncodedText , offset ) ;
}
//add padding
offset + = cipherImpl . doFinal ( uintInputText , offset , outputEncodedText , offset ) ;
final Uint8List finalEncodedText = outputEncodedText . sublist ( 0 , offset ) ;
2024-03-31 17:14:01 +05:30
String stringIv = convert . base64 . encode ( iv ) ;
2022-09-05 22:48:11 +05:30
String outputPlainText = convert . base64 . encode ( finalEncodedText ) ;
2024-03-31 17:14:01 +05:30
outputPlainText = " $ outputPlainText ?iv= $ stringIv " ;
2022-09-05 22:48:11 +05:30
return outputPlainText ;
}
2022-09-05 17:14:34 +05:30
2024-03-31 17:14:01 +05:30
/// Read events from file. a flag is set for such events, so that when writing events back, the ones read from file aren't added, and only
/// new events from relays are written to file.
2022-09-03 03:29:36 +05:30
Set < Event > readEventsFromFile ( String filename ) {
Set < Event > events = { } ;
final File file = File ( filename ) ;
// sync read
try {
List < String > lines = file . readAsLinesSync ( ) ;
for ( int i = 0 ; i < lines . length ; i + + ) {
Event e = Event . fromJson ( lines [ i ] , " " , true ) ;
events . add ( e ) ;
}
} on Exception catch ( e ) {
if ( gDebug > 0 ) print ( " Could not open file. error = $ e " ) ;
2022-09-01 21:55:51 +05:30
}
2022-09-03 03:29:36 +05:30
if ( gDebug > 0 ) print ( " In readEventsFromFile: returning ${ events . length } total events " ) ;
return events ;
}
2022-10-30 00:27:11 +05:30
2022-12-01 22:40:14 +05:30
List < String > getEncryptedChannelKeys ( Set < String > inviteMessageIds , Map < String , Tree > tempChildEventsMap , String channelId ) {
2022-10-30 11:46:51 +05:30
Event ? e = tempChildEventsMap [ channelId ] ? . event ;
2022-10-30 00:27:11 +05:30
if ( e ! = null ) {
2022-11-25 19:26:12 +05:30
2022-12-01 22:40:14 +05:30
for ( String inviteMessageid in inviteMessageIds ) {
Event ? messageEvent = tempChildEventsMap [ inviteMessageid ] ? . event ;
2022-11-25 19:26:12 +05:30
if ( messageEvent ! = null ) {
String evaluatedContent = messageEvent . eventData . evaluatedContent ;
if ( evaluatedContent . startsWith ( " App Encrypted Channels: " ) ) {
if ( evaluatedContent . contains ( channelId ) & & evaluatedContent . length = = 288 ) {
String priKey = evaluatedContent . substring ( 159 , 159 + 64 ) ;
String pubKey = evaluatedContent . substring ( 224 , 224 + 64 ) ;
if ( priKey . length = = 64 & & pubKey . length = = 64 ) {
return [ priKey , pubKey ] ;
2022-10-30 00:27:11 +05:30
}
}
}
2022-11-25 19:26:12 +05:30
} else {
print ( " could not get message event " ) ;
2022-10-30 00:27:11 +05:30
}
}
}
return [ ] ;
}
2022-10-30 11:46:51 +05:30
String myGetPublicKey ( String prikey ) {
String pubkey = getPublicKey ( prikey ) ;
if ( pubkey . length < 64 ) {
int numZeros = 64 - pubkey . length ;
for ( int i = 0 ; i < numZeros ; i + + ) {
2024-03-31 17:14:01 +05:30
pubkey = " 0 $ pubkey " ;
2022-10-30 11:46:51 +05:30
}
}
return pubkey ;
}