import 'dart:io'; import 'package:qr/qr.dart'; enum enumRoomType { kind4, kind40, kind140, RoomLocationTag, RoomTTag} int gMinLnInvoiceLength = 20; // TODO put real value int gMaxStrLenForQrCode = 600; // in bytes, maximum acceptable length of string that is converted to qr code. for lnbc1 invoices String getPostKindFrom(enumRoomType eType) { switch (eType) { case enumRoomType.kind4: return "4"; case enumRoomType.kind40: return "42"; case enumRoomType.kind140: return "142"; case enumRoomType.RoomLocationTag: return "1"; case enumRoomType.RoomTTag: return "1"; } } Set? getTagsFromContent(String content) { Set? tags; String regexp1 = '(#[a-zA-Z0-9_-]+ )|(#[a-zA-Z0-9_-]+)\$'; RegExp httpRegExp = RegExp(regexp1); for( var match in httpRegExp.allMatches(content) ) { tags ??= {}; tags.add( content.substring(match.start + 1, match.end).trim() ); } return tags; } class HistogramEntry { String str; int count; HistogramEntry(this.str, this.count); static int histogramSorter(HistogramEntry a, HistogramEntry b) { if( a.count < b.count ) { return 1; } if( a.count == b.count ) { return 0; } else { return -1; } } } Future myWait(int ms) async { Future foo1() async { await Future.delayed(Duration(milliseconds: ms)); return; } await foo1(); } bool isNumeric(String s) { return double.tryParse(s) != null; } bool isWordSeparater(String s) { if( s.length != 1) { return false; } return s[0] == ' ' || s[0] == '\n' || s[0] == '\r' || s[0] == '\t' || s[0] == ',' || s[0] == '.' || s[0] == '-' || s[0] == '('|| s[0] == ')'; } bool isWhitespace(String s) { if( s.length != 1) { return false; } return s[0] == ' ' || s[0] == '\n' || s[0] == '\r' || s[0] == '\t'; } extension StringX on String { int isChannelPageNumber(int max) { if(length < 2 || this[0] != '/') { return 0; } String rest = substring(1); //print("rest = $rest"); int? n = int.tryParse(rest); if( n != null) { if( n < max) { return n; } } return 0; } isEnglish( ) { // since smaller words can be smileys they should not be translated if( length < 10) { return true; } if( !isLatinAlphabet()) { return false; } if (isRomanceLanguage()) { return false; } return true; } isPortugese() { false; // https://1000mostcommonwords.com/1000-most-common-portuguese-words/ } bool isRomanceLanguage() { // https://www.thoughtco.com/most-common-french-words-1372759 Set frenchWords = {"oui", "je", "le", "un", "de", "merci", "une", "ce", "pas"}; // "et" is in 'et al' Set spanishWords = {"y", "se", "el", "uso", "que", "te", "los", "va", "ser", "si", "por", "lo", "es", "era", "un", "o"}; Set portugeseWords = {"como", "seu", "que", "ele", "foi", "eles", "tem", "este", "por", "quente", "vai", "ter", "mas", "ou", "teve", "fora", "é", "te", "mais"}; Set romanceWords = frenchWords.union(spanishWords).union(portugeseWords); for( String word in romanceWords) { if( toLowerCase().contains(" $word ")) { return true; } } return false; } isLatinAlphabet({caseSensitive = false}) { int countLatinletters = 0; for (int i = 0; i < length; i++) { final target = caseSensitive ? this[i] : this[i].toLowerCase(); if ( (target.codeUnitAt(0) > 96 && target.codeUnitAt(0) < 123) || ( isNumeric(target) ) || isWhitespace(target)) { countLatinletters++; } } if( countLatinletters < ( 40.0/100 ) * length ) { return false; } else { return true; } } } bool isValidHexPubkey(String pubkey) { if( pubkey.length == 64) { return true; } return false; } String myPadRight(String str, int width) { String newStr = ""; if( str.length < width) { newStr = str.padRight(width); } else { newStr = str.substring(0, width); } return newStr; } // returns tags as string that can be used to calculate event has. called from EventData constructor String getStrTagsFromJson(dynamic json) { String str = ""; int i = 0; for( dynamic tag in json ) { if( i != 0) { str += ","; } str += "["; int j = 0; for(dynamic element in tag) { if( j != 0) { str += ","; } str += "\"${element.toString()}\""; j++; } str += "]"; i++; } return str; } String addEscapeChars(String str) { String temp = ""; //temp = temp.replaceAll("\\", "\\\\"); temp = str.replaceAll("\"", "\\\""); return temp.replaceAll("\n", "\\n"); } String unEscapeChars(String str) { String temp = str.replaceAll("\"", "\\\""); temp = temp.replaceAll("\n", "\\n"); return temp; } void printUnderlined(String x) { stdout.write("$x\n${getNumDashes(x.length)}\n");} String getNumSpaces(int num) { String s = ""; for( int i = 0; i < num; i++) { s += " "; } return s; } String getNumDashes(int num, [String dashType = "-"]) { String s = ""; for( int i = 0; i < num; i++) { s += dashType; } return s; } List> getUrlRanges(String s) { List> urlRanges = []; String regexp1 = "http[s]*://[a-zA-Z0-9]+([.a-zA-Z0-9/_\\-\\#\\+=\\&\\?]*)"; RegExp httpRegExp = RegExp(regexp1); for( var match in httpRegExp.allMatches(s) ) { List entry = [match.start, match.end]; urlRanges.add(entry); } return urlRanges; } // returns true if n is in any of the ranges given in list int isInRange( int n, List> ranges ) { for( int i = 0; i < ranges.length; i++) { if( n >= ranges[i][0] && n < ranges[i][1]) { return ranges[i][1]; } } return 0; } // https://jpgraph.net/download/manuals/chunkhtml/ch27.html // both go from 1 to 20 inclusive. index is type. List qrMaxDataBits = [152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888]; List qrModules = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97]; // return type and module as entries in a list List? getTypeAndModule(String str) { if( qrMaxDataBits.length != qrModules.length) { return null; } // 5 for padding which it seems to need, otherwise it gives error like 'QrInputTooLongException: Input too long. 2212 > 2192' for a str which is exactly 2192 int strLen = str.length + 5; for( int i = 0; i < qrModules.length; i++) { if( strLen * 8 <= qrMaxDataBits[i]) { return [i+1, qrModules[i]]; } } return null; } bool sanityChecked(String lnInvoice) { if( lnInvoice.length < gMinLnInvoiceLength) { return false; } if( lnInvoice.substring(0, 4).toLowerCase() != "lnbc") { return false; } return true; } String expandLNInvoices(String content) { String regexp1 = '(lnbc[a-zA-Z0-9]+)'; RegExp httpRegExp = RegExp(regexp1); for( var match in httpRegExp.allMatches(content.toLowerCase()) ) { String lnInvoice = content.substring(match.start, match.end); if( !sanityChecked(lnInvoice)) { continue; } if( lnInvoice.length > gMaxStrLenForQrCode) { continue; } String qrStr = ""; List? typeAndModule = getTypeAndModule(lnInvoice); if( typeAndModule == null) { continue; } qrStr = getPubkeyAsQrString(lnInvoice, typeAndModule[0], typeAndModule[1], ""); content = "${content.substring(0, match.start)}:-\n\n$qrStr\n\n${content.substring(match.end)}"; } return content; } // https://www.sproutqr.com/blog/qr-code-types // https://jpgraph.net/download/manuals/chunkhtml/ch27.html // default 4 and 33 work for pubkey String getPubkeyAsQrString(String str, [int typeNumber = 4, moduleCount = 33, String leftPadding = " "]) { String output = ""; final qrCode = QrCode(typeNumber, QrErrorCorrectLevel.L) ..addData(str); final qrImage = QrImage(qrCode); assert( qrImage.moduleCount == moduleCount); var x = 0; for (x = 0; x < qrImage.moduleCount -1 ; x += 2) { output += leftPadding; for (var y = 0; y < qrImage.moduleCount ; y++) { bool topDark = qrImage.isDark(y, x); bool bottomDark = qrImage.isDark(y, x + 1); if (topDark && bottomDark) { output += "█"; } else if (topDark ) { output += "▀"; } else if ( bottomDark) { output += "▄"; } else if( !topDark && !bottomDark) { output += " "; } } output += "\n"; } if( qrImage.moduleCount %2 == 1) { output += leftPadding; for (var y = 0; y < qrImage.moduleCount ; y++) { bool dark = qrImage.isDark(y, x); if (dark ) { output += "▀"; } else { output += " "; } } output += "\n"; } return output; } void clearScreen() { print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); } // returns a string entered by the user String getStringFromUser(String prompt, [String defaultValue=""] ) { String str = ""; stdout.write(prompt); str = (stdin.readLineSync())??""; if( str.isEmpty) { str = defaultValue; } return str; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// relay related functions // returns list in form ( if 3 sized list) // "pubkey1","pubkey2","pubkey3" String getCommaSeparatedQuotedStrs(Set publicKeys) { String s = ""; int i = 0; for(String pubkey in publicKeys) { s += "\"${pubkey.toLowerCase()}\""; if( i < publicKeys.length - 1) { s += ","; } i++; } return s; } String getCommaSeparatedInts(Set? kind) { if( kind == null) { return ""; } if( kind.isEmpty) { return ""; } String strKind = ""; int i = 0; for (var k in kind) { String comma = ","; if( i == kind.length-1) { comma = ""; } strKind = strKind + k.toString() + comma; i++; } return strKind; } String getKindRequest(String subscriptionId, List kind, int limit, int sinceWhen) { String strTime = ""; if( sinceWhen != 0) { strTime = ', "since":${sinceWhen.toString()}'; } var strSubscription1 = '["REQ","$subscriptionId",{"kinds":['; var strSubscription2 ='], "limit":$limit$strTime } ]'; String strKind = getCommaSeparatedInts(kind.toSet()); String strRequest = strSubscription1 + strKind + strSubscription2; return strRequest; } String getUserRequest(String subscriptionId, String publicKey, int numUserEvents, int sinceWhen, [Set? kind]) { Set kind = {}; kind = kind; String strKind = getCommaSeparatedInts(kind); String strKindSection = ""; if( strKind.isNotEmpty) { strKindSection = '"kinds":[$strKind],'; } String strTime = ""; if( sinceWhen != 0) { strTime = ', "since": ${sinceWhen.toString()}'; } var strSubscription1 = '["REQ","$subscriptionId",{ "authors": ["'; var strSubscription2 ='"],$strKindSection"limit": $numUserEvents $strTime } ]'; String request = strSubscription1 + publicKey.toLowerCase() + strSubscription2; return request; } String getMentionRequest(String subscriptionId, Set ids, int numUserEvents, int sinceWhen, String tagToGet) { String strTime = ""; if( sinceWhen != 0) { strTime = ', "since": ${sinceWhen.toString()}'; } var strSubscription1 = '["REQ","$subscriptionId",{ "$tagToGet": ['; var strSubscription2 ='], "limit": $numUserEvents $strTime } ]'; return strSubscription1 + getCommaSeparatedQuotedStrs(ids) + strSubscription2; } String getIdAndMentionRequest(String subscriptionId, Set ids, int numUserEvents, int idSinceWhen, int mentionSinceWhen, String tagToGet, String idString) { String idStrTime = "", mentionStrTime = ""; if( idSinceWhen != 0) { idStrTime = ', "since": ${idSinceWhen.toString()}'; } if( mentionSinceWhen != 0) { mentionStrTime = ', "since": ${mentionSinceWhen.toString()}'; } var strSubscription1 = '["REQ","$subscriptionId",{ "$tagToGet": ['; var strSubscription2 ='], "limit": $numUserEvents $idStrTime } ]'; String req = '["REQ","$subscriptionId",{ "$tagToGet": [${getCommaSeparatedQuotedStrs(ids)}], "limit": $numUserEvents $mentionStrTime},{"$idString":[${getCommaSeparatedQuotedStrs(ids)}]$idStrTime}]'; return req; } String getMultiUserRequest(String subscriptionId, Set publicKeys, int numUserEvents, int sinceWhen, [Set? kind]) { String strTime = ""; if( sinceWhen != 0) { strTime = ', "since": ${sinceWhen.toString()}'; } String strKind = getCommaSeparatedInts(kind); String strKindSection = ""; if( strKind.isNotEmpty) { strKindSection = '"kinds":[$strKind],'; } var strSubscription1 = '["REQ","$subscriptionId",{ "authors": ['; var strSubscription2 ='],$strKindSection"limit": $numUserEvents $strTime } ]'; String s = ""; s = getCommaSeparatedQuotedStrs(publicKeys); String request = strSubscription1 + s + strSubscription2; return request; } // ends with a newline void printSet( Set toPrint, [ String prefix = "", String separator = ""]) { stdout.write(prefix); int i = 0; for (var element in toPrint) { if( i != 0) { stdout.write(separator); } stdout.write(element); i++; } stdout.write("\n"); }