#import "TGStringUtils.h" #import "LegacyComponentsInternal.h" #import #import "TGLocalization.h" #import "TGPluralization.h" typedef struct { __unsafe_unretained NSString *escapeSequence; unichar uchar; } HTMLEscapeMap; // Taken from http://www.w3.org/TR/xhtml1/dtds.html#a_dtd_Special_characters // Ordered by uchar lowest to highest for bsearching static HTMLEscapeMap gAsciiHTMLEscapeMap[] = { // A.2.2. Special characters { @""", 34 }, { @"&", 38 }, { @"'", 39 }, { @"<", 60 }, { @">", 62 }, // A.2.1. Latin-1 characters { @" ", 160 }, { @"¡", 161 }, { @"¢", 162 }, { @"£", 163 }, { @"¤", 164 }, { @"¥", 165 }, { @"¦", 166 }, { @"§", 167 }, { @"¨", 168 }, { @"©", 169 }, { @"ª", 170 }, { @"«", 171 }, { @"¬", 172 }, { @"­", 173 }, { @"®", 174 }, { @"¯", 175 }, { @"°", 176 }, { @"±", 177 }, { @"²", 178 }, { @"³", 179 }, { @"´", 180 }, { @"µ", 181 }, { @"¶", 182 }, { @"·", 183 }, { @"¸", 184 }, { @"¹", 185 }, { @"º", 186 }, { @"»", 187 }, { @"¼", 188 }, { @"½", 189 }, { @"¾", 190 }, { @"¿", 191 }, { @"À", 192 }, { @"Á", 193 }, { @"Â", 194 }, { @"Ã", 195 }, { @"Ä", 196 }, { @"Å", 197 }, { @"Æ", 198 }, { @"Ç", 199 }, { @"È", 200 }, { @"É", 201 }, { @"Ê", 202 }, { @"Ë", 203 }, { @"Ì", 204 }, { @"Í", 205 }, { @"Î", 206 }, { @"Ï", 207 }, { @"Ð", 208 }, { @"Ñ", 209 }, { @"Ò", 210 }, { @"Ó", 211 }, { @"Ô", 212 }, { @"Õ", 213 }, { @"Ö", 214 }, { @"×", 215 }, { @"Ø", 216 }, { @"Ù", 217 }, { @"Ú", 218 }, { @"Û", 219 }, { @"Ü", 220 }, { @"Ý", 221 }, { @"Þ", 222 }, { @"ß", 223 }, { @"à", 224 }, { @"á", 225 }, { @"â", 226 }, { @"ã", 227 }, { @"ä", 228 }, { @"å", 229 }, { @"æ", 230 }, { @"ç", 231 }, { @"è", 232 }, { @"é", 233 }, { @"ê", 234 }, { @"ë", 235 }, { @"ì", 236 }, { @"í", 237 }, { @"î", 238 }, { @"ï", 239 }, { @"ð", 240 }, { @"ñ", 241 }, { @"ò", 242 }, { @"ó", 243 }, { @"ô", 244 }, { @"õ", 245 }, { @"ö", 246 }, { @"÷", 247 }, { @"ø", 248 }, { @"ù", 249 }, { @"ú", 250 }, { @"û", 251 }, { @"ü", 252 }, { @"ý", 253 }, { @"þ", 254 }, { @"ÿ", 255 }, // A.2.2. Special characters cont'd { @"Œ", 338 }, { @"œ", 339 }, { @"Š", 352 }, { @"š", 353 }, { @"Ÿ", 376 }, // A.2.3. Symbols { @"ƒ", 402 }, // A.2.2. Special characters cont'd { @"ˆ", 710 }, { @"˜", 732 }, // A.2.3. Symbols cont'd { @"Α", 913 }, { @"Β", 914 }, { @"Γ", 915 }, { @"Δ", 916 }, { @"Ε", 917 }, { @"Ζ", 918 }, { @"Η", 919 }, { @"Θ", 920 }, { @"Ι", 921 }, { @"Κ", 922 }, { @"Λ", 923 }, { @"Μ", 924 }, { @"Ν", 925 }, { @"Ξ", 926 }, { @"Ο", 927 }, { @"Π", 928 }, { @"Ρ", 929 }, { @"Σ", 931 }, { @"Τ", 932 }, { @"Υ", 933 }, { @"Φ", 934 }, { @"Χ", 935 }, { @"Ψ", 936 }, { @"Ω", 937 }, { @"α", 945 }, { @"β", 946 }, { @"γ", 947 }, { @"δ", 948 }, { @"ε", 949 }, { @"ζ", 950 }, { @"η", 951 }, { @"θ", 952 }, { @"ι", 953 }, { @"κ", 954 }, { @"λ", 955 }, { @"μ", 956 }, { @"ν", 957 }, { @"ξ", 958 }, { @"ο", 959 }, { @"π", 960 }, { @"ρ", 961 }, { @"ς", 962 }, { @"σ", 963 }, { @"τ", 964 }, { @"υ", 965 }, { @"φ", 966 }, { @"χ", 967 }, { @"ψ", 968 }, { @"ω", 969 }, { @"ϑ", 977 }, { @"ϒ", 978 }, { @"ϖ", 982 }, // A.2.2. Special characters cont'd { @" ", 8194 }, { @" ", 8195 }, { @" ", 8201 }, { @"‌", 8204 }, { @"‍", 8205 }, { @"‎", 8206 }, { @"‏", 8207 }, { @"–", 8211 }, { @"—", 8212 }, { @"‘", 8216 }, { @"’", 8217 }, { @"‚", 8218 }, { @"“", 8220 }, { @"”", 8221 }, { @"„", 8222 }, { @"†", 8224 }, { @"‡", 8225 }, // A.2.3. Symbols cont'd { @"•", 8226 }, { @"…", 8230 }, // A.2.2. Special characters cont'd { @"‰", 8240 }, // A.2.3. Symbols cont'd { @"′", 8242 }, { @"″", 8243 }, // A.2.2. Special characters cont'd { @"‹", 8249 }, { @"›", 8250 }, // A.2.3. Symbols cont'd { @"‾", 8254 }, { @"⁄", 8260 }, // A.2.2. Special characters cont'd { @"€", 8364 }, // A.2.3. Symbols cont'd { @"ℑ", 8465 }, { @"℘", 8472 }, { @"ℜ", 8476 }, { @"™", 8482 }, { @"ℵ", 8501 }, { @"←", 8592 }, { @"↑", 8593 }, { @"→", 8594 }, { @"↓", 8595 }, { @"↔", 8596 }, { @"↵", 8629 }, { @"⇐", 8656 }, { @"⇑", 8657 }, { @"⇒", 8658 }, { @"⇓", 8659 }, { @"⇔", 8660 }, { @"∀", 8704 }, { @"∂", 8706 }, { @"∃", 8707 }, { @"∅", 8709 }, { @"∇", 8711 }, { @"∈", 8712 }, { @"∉", 8713 }, { @"∋", 8715 }, { @"∏", 8719 }, { @"∑", 8721 }, { @"−", 8722 }, { @"∗", 8727 }, { @"√", 8730 }, { @"∝", 8733 }, { @"∞", 8734 }, { @"∠", 8736 }, { @"∧", 8743 }, { @"∨", 8744 }, { @"∩", 8745 }, { @"∪", 8746 }, { @"∫", 8747 }, { @"∴", 8756 }, { @"∼", 8764 }, { @"≅", 8773 }, { @"≈", 8776 }, { @"≠", 8800 }, { @"≡", 8801 }, { @"≤", 8804 }, { @"≥", 8805 }, { @"⊂", 8834 }, { @"⊃", 8835 }, { @"⊄", 8836 }, { @"⊆", 8838 }, { @"⊇", 8839 }, { @"⊕", 8853 }, { @"⊗", 8855 }, { @"⊥", 8869 }, { @"⋅", 8901 }, { @"⌈", 8968 }, { @"⌉", 8969 }, { @"⌊", 8970 }, { @"⌋", 8971 }, { @"⟨", 9001 }, { @"⟩", 9002 }, { @"◊", 9674 }, { @"♠", 9824 }, { @"♣", 9827 }, { @"♥", 9829 }, { @"♦", 9830 } }; @implementation TGStringUtils + (void)reset { } + (NSString *)stringByEscapingForURL:(NSString *)string { static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,.:;'\"`<>()[]{}/\\|~ "; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if (unescapedString == nil) unescapedString = string; return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)unescapedString, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); #pragma clang diagnostic pop } + (NSString *)stringByEscapingForActorURL:(NSString *)string { static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *unescapedString = [string stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; if (unescapedString == nil) unescapedString = string; return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)unescapedString, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); #pragma clang diagnostic pop } + (NSString *)stringByEncodingInBase64:(NSData *)data { NSUInteger length = [data length]; NSMutableData *mutableData = [[NSMutableData alloc] initWithLength:((length + 2) / 3) * 4]; uint8_t *input = (uint8_t *)[data bytes]; uint8_t *output = (uint8_t *)[mutableData mutableBytes]; for (NSUInteger i = 0; i < length; i += 3) { NSUInteger value = 0; for (NSUInteger j = i; j < (i + 3); j++) { value <<= 8; if (j < length) { value |= (0xFF & input[j]); } } static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; NSUInteger idx = (i / 3) * 4; output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; } return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; } + (NSString *)stringByUnescapingFromHTML:(NSString *)srcString { NSRange range = NSMakeRange(0, [srcString length]); NSRange subrange = [srcString rangeOfString:@"&" options:NSBackwardsSearch range:range]; NSRange tagSubrange = NSMakeRange(0, 0); if (subrange.length == 0) { tagSubrange = [srcString rangeOfString:@"<" options:NSBackwardsSearch range:range]; if (tagSubrange.length == 0) return srcString; } NSMutableString *finalString = [NSMutableString stringWithString:srcString]; if (subrange.length != 0) { do { NSRange semiColonRange = NSMakeRange(subrange.location, NSMaxRange(range) - subrange.location); semiColonRange = [srcString rangeOfString:@";" options:0 range:semiColonRange]; range = NSMakeRange(0, subrange.location); // if we don't find a semicolon in the range, we don't have a sequence if (semiColonRange.location == NSNotFound) { continue; } NSRange escapeRange = NSMakeRange(subrange.location, semiColonRange.location - subrange.location + 1); NSString *escapeString = [srcString substringWithRange:escapeRange]; NSUInteger length = [escapeString length]; // a squence must be longer than 3 (<) and less than 11 (ϑ) if (length > 3 && length < 11) { if ([escapeString characterAtIndex:1] == '#') { unichar char2 = [escapeString characterAtIndex:2]; if (char2 == 'x' || char2 == 'X') { // Hex escape squences £ NSString *hexSequence = [escapeString substringWithRange:NSMakeRange(3, length - 4)]; NSScanner *scanner = [NSScanner scannerWithString:hexSequence]; unsigned value; if ([scanner scanHexInt:&value] && value < USHRT_MAX && value > 0 && [scanner scanLocation] == length - 4) { unichar uchar = (unichar)value; NSString *charString = [NSString stringWithCharacters:&uchar length:1]; [finalString replaceCharactersInRange:escapeRange withString:charString]; } } else { // Decimal Sequences { NSString *numberSequence = [escapeString substringWithRange:NSMakeRange(2, length - 3)]; NSScanner *scanner = [NSScanner scannerWithString:numberSequence]; int value; if ([scanner scanInt:&value] && value < USHRT_MAX && value > 0 && [scanner scanLocation] == length - 3) { unichar uchar = (unichar)value; NSString *charString = [NSString stringWithCharacters:&uchar length:1]; [finalString replaceCharactersInRange:escapeRange withString:charString]; } } } else { for (unsigned i = 0; i < sizeof(gAsciiHTMLEscapeMap) / sizeof(HTMLEscapeMap); ++i) { if ([escapeString isEqualToString:gAsciiHTMLEscapeMap[i].escapeSequence]) { [finalString replaceCharactersInRange:escapeRange withString:[NSString stringWithCharacters:&gAsciiHTMLEscapeMap[i].uchar length:1]]; break; } } } } } while ((subrange = [srcString rangeOfString:@"&" options:NSBackwardsSearch range:range]).length != 0); } [finalString replaceOccurrencesOfString:@"
" withString:@"\n" options:NSLiteralSearch range:NSMakeRange(0, finalString.length)]; return finalString; } + (NSString *)stringWithLocalizedNumber:(NSInteger)number { return [self stringWithLocalizedNumberCharacters:[[NSString alloc] initWithFormat:@"%d", (int)number]]; } + (NSString *)stringWithLocalizedNumberCharacters:(NSString *)string { NSString *resultString = string; if (TGIsArabic()) { static NSString *arabicNumbers = @"٠١٢٣٤٥٦٧٨٩"; NSMutableString *mutableString = [[NSMutableString alloc] init]; for (int i = 0; i < (int)string.length; i++) { unichar c = [string characterAtIndex:i]; if (c >= '0' && c <= '9') [mutableString replaceCharactersInRange:NSMakeRange(mutableString.length, 0) withString:[arabicNumbers substringWithRange:NSMakeRange(c - '0', 1)]]; else [mutableString replaceCharactersInRange:NSMakeRange(mutableString.length, 0) withString:[string substringWithRange:NSMakeRange(i, 1)]]; } resultString = mutableString; } return resultString; } + (NSString *)md5:(NSString *)string { /*static const char *md5PropertyKey = "MD5Key"; NSString *result = objc_getAssociatedObject(string, md5PropertyKey); if (result != nil) return result;*/ const char *ptr = [string UTF8String]; unsigned char md5Buffer[16]; CC_MD5(ptr, (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], md5Buffer); NSString *output = [[NSString alloc] initWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5Buffer[0], md5Buffer[1], md5Buffer[2], md5Buffer[3], md5Buffer[4], md5Buffer[5], md5Buffer[6], md5Buffer[7], md5Buffer[8], md5Buffer[9], md5Buffer[10], md5Buffer[11], md5Buffer[12], md5Buffer[13], md5Buffer[14], md5Buffer[15]]; //objc_setAssociatedObject(string, md5PropertyKey, output, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return output; } + (NSString *)md5ForData:(NSData *)data { unsigned char md5Buffer[16]; CC_MD5(data.bytes, (CC_LONG)data.length, md5Buffer); NSString *output = [[NSString alloc] initWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", md5Buffer[0], md5Buffer[1], md5Buffer[2], md5Buffer[3], md5Buffer[4], md5Buffer[5], md5Buffer[6], md5Buffer[7], md5Buffer[8], md5Buffer[9], md5Buffer[10], md5Buffer[11], md5Buffer[12], md5Buffer[13], md5Buffer[14], md5Buffer[15]]; return output; } + (NSDictionary *)argumentDictionaryInUrlString:(NSString *)string { NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] init]; NSArray *urlComponents = [string componentsSeparatedByString:@"&"]; for (NSString *keyValuePair in urlComponents) { NSRange equalsSignRange = [keyValuePair rangeOfString:@"="]; if (equalsSignRange.location != NSNotFound) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *key = [keyValuePair substringToIndex:equalsSignRange.location]; NSString *value = [[[keyValuePair substringFromIndex:equalsSignRange.location + equalsSignRange.length] stringByReplacingOccurrencesOfString:@"+" withString:@" "] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; #pragma clang diagnostic pop [queryStringDictionary setObject:value forKey:key]; } } return queryStringDictionary; } + (bool)stringContainsEmoji:(NSString *)string { __block bool returnValue = NO; [string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) { const unichar hs = [substring characterAtIndex:0]; if (0xd800 <= hs && hs <= 0xdbff) { if (substring.length > 1) { const unichar ls = [substring characterAtIndex:1]; const int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000; if (0x1d000 <= uc && uc <= 0x1f77f) { returnValue = YES; } } } else if (substring.length > 1) { const unichar ls = [substring characterAtIndex:1]; if (ls == 0x20e3) { returnValue = YES; } } else { if (0x2100 <= hs && hs <= 0x27ff) { returnValue = YES; } else if (0x2B05 <= hs && hs <= 0x2b07) { returnValue = YES; } else if (0x2934 <= hs && hs <= 0x2935) { returnValue = YES; } else if (0x3297 <= hs && hs <= 0x3299) { returnValue = YES; } else if (hs == 0xa9 || hs == 0xae || hs == 0x303d || hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c || hs == 0x2b1b || hs == 0x2b50) { returnValue = YES; } } if (returnValue && stop != NULL) *stop = true; }]; return returnValue; } static bool isEmojiCharacter(NSString *singleChar) { const unichar high = [singleChar characterAtIndex:0]; if (0xd800 <= high && high <= 0xdbff && singleChar.length >= 2) { const unichar low = [singleChar characterAtIndex:1]; const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000; return (0x1d000 <= codepoint && codepoint <= 0x1f77f); } return (0x2100 <= high && high <= 0x27bf); } + (bool)stringContainsEmojiOnly:(NSString *)string length:(NSUInteger *)length { if (string.length == 0) return false; __block bool result = true; __block NSUInteger count = 0; [string enumerateSubstringsInRange:NSMakeRange(0, string.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) { if (!isEmojiCharacter(substring)) { result = false; *stop = true; } count++; }]; if (length != NULL) *length = count; return result; } + (NSString *)stringForMessageTimerSeconds:(NSUInteger)seconds { if (seconds < 60) { int number = (int)seconds; return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Seconds" count:(int32_t)number]; } else if (seconds < 60 * 60) { int number = (int)seconds / 60; return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Minutes" count:(int32_t)number]; } else if (seconds < 60 * 60 * 24) { int number = (int)seconds / (60 * 60); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Hours" count:(int32_t)number]; } else if (seconds < 60 * 60 * 24 * 7) { int number = (int)seconds / (60 * 60 * 24); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Days" count:(int32_t)number]; } else if (seconds < 60 * 60 * 24 * 7 * 4) { int number = (int)seconds / (60 * 60 * 24 * 7); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Weeks" count:(int32_t)number]; } else { int number = MAX(1, (int)ceilf((int)(seconds / (60 * 60 * 24 * 29)))); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.Months" count:(int32_t)number]; } return @""; } + (NSString *)stringForShortMessageTimerSeconds:(NSUInteger)seconds { if (seconds < 60) { int number = (int)seconds; return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.ShortSeconds" count:(int32_t)number]; } else if (seconds < 60 * 60) { int number = (int)seconds / 60; return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.ShortMinutes" count:(int32_t)number]; } else if (seconds < 60 * 60 * 24) { int number = (int)seconds / (60 * 60); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.ShortHours" count:(int32_t)number]; } else if (seconds < 60 * 60 * 24 * 7) { int number = (int)seconds / (60 * 60 * 24); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.ShortDays" count:(int32_t)number]; } else { int number = (int)seconds / (60 * 60 * 24 * 7); return [legacyEffectiveLocalization() getPluralized:@"MessageTimer.ShortWeeks" count:(int32_t)number]; } return @""; } + (NSArray *)stringComponentsForMessageTimerSeconds:(NSUInteger)seconds { NSString *first = @""; NSString *second = @""; if (seconds < 60) { int number = (int)seconds; NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Seconds_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } else if (seconds < 60 * 60) { int number = (int)seconds / 60; NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Minutes_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } else if (seconds < 60 * 60 * 24) { int number = (int)seconds / (60 * 60); NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Hours_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } else if (seconds < 60 * 60 * 24 * 7) { int number = (int)seconds / (60 * 60 * 24); NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Days_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } else if (seconds < 60 * 60 * 24 * 30) { int number = (int)seconds / (60 * 60 * 24 * 7); NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Weeks_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } else { int number = (int)ceilf((seconds / (60 * 60 * 24 * 30.5f))); NSString *format = TGLocalized([self integerValueFormat:@"MessageTimer.Months_" value:number]); NSRange range = [format rangeOfString:@"%@"]; if (range.location != NSNotFound) { first = [[NSString alloc] initWithFormat:@"%d", number]; second = [[format substringFromIndex:range.location + range.length] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } else { first = format; } } return @[first, second]; } + (NSString *)stringForCallDurationSeconds:(NSUInteger)seconds { if (seconds < 60) { int number = (int)seconds; return [legacyEffectiveLocalization() getPluralized:@"Call.Seconds" count:number]; } else { int number = (int)seconds / 60; return [legacyEffectiveLocalization() getPluralized:@"Call.Minutes" count:number]; } } + (NSString *)stringForShortCallDurationSeconds:(NSUInteger)seconds { if (seconds < 60) { int number = (int)seconds; return [legacyEffectiveLocalization() getPluralized:@"Call.ShortSeconds" count:(int32_t)number]; } else { int number = (int)seconds / 60; return [legacyEffectiveLocalization() getPluralized:@"Call.ShortMinutes" count:(int32_t)number]; } } + (NSString *)stringForUserCount:(NSUInteger)userCount { NSUInteger number = userCount; return [legacyEffectiveLocalization() getPluralized:@"UserCount" count:(int32_t)number]; } + (NSString *)stringForFileSize:(int64_t)size { NSString *format = @""; float floatSize = size; bool useFloat = false; if (floatSize < 1024) format = TGLocalized(@"FileSize.B"); else if (size < 1024 * 1024) { format = TGLocalized(@"FileSize.KB"); floatSize = size / 1024; } else if (size < 1024 * 1024 * 1024) { format = TGLocalized(@"FileSize.MB"); floatSize = size / (1024 * 1024); } else { format = TGLocalized(@"FileSize.GB"); floatSize = size / (1024.0f * 1024.0f * 1024.0f); useFloat = true; } if (useFloat) { return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%0.1f", floatSize]]; } else { return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", (int)floatSize]]; } } + (NSString *)stringForFileSize:(int64_t)size precision:(NSInteger)precision { NSString *string = @""; if (size < 1024) { string = [[NSString alloc] initWithFormat:TGLocalized(@"FileSize.B"), [[NSString alloc] initWithFormat:@"%d", (int)size]];} else if (size < 1024 * 1024) { string = [[NSString alloc] initWithFormat:TGLocalized(@"FileSize.KB"), [[NSString alloc] initWithFormat:@"%d", (int)(size / 1024)]]; } else { NSString *format = [NSString stringWithFormat:@"%%0.%df", (int)precision]; string = [[NSString alloc] initWithFormat:TGLocalized(@"FileSize.MB"), [[NSString alloc] initWithFormat:format, (CGFloat)(size / 1024.0f / 1024.0f)]]; } return string; } + (NSString *)integerValueFormat:(NSString *)prefix value:(NSInteger)value { TGLocalization *localization = legacyEffectiveLocalization(); NSString *form = @"any"; switch (TGPluralForm(localization.languageCodeHash, (int)value)) { case TGPluralFormZero: form = @"0"; break; case TGPluralFormOne: form = @"1"; break; case TGPluralFormTwo: form = @"2"; break; case TGPluralFormFew: form = @"3_10"; break; case TGPluralFormMany: form = @"many"; break; case TGPluralFormOther: form = @"any"; break; default: break; } NSString *result = [prefix stringByAppendingString:form]; if ([localization contains:result]) { return result; } else { return [prefix stringByAppendingString:@"any"]; } } + (NSString *)stringForMuteInterval:(int)value { value = MAX(1 * 60, value); if (value < 24 * 60 * 60) { value /= 60 * 60; NSString *format = TGLocalized([self integerValueFormat:@"MuteFor.Hours_" value:value]); return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", value]]; } else { value /= 24 * 60 * 60; NSString *format = TGLocalized([self integerValueFormat:@"MuteFor.Days_" value:value]); return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", value]]; } return @""; } + (NSString *)stringForRemainingMuteInterval:(int)value { value = MAX(1 * 60, value); if (value <= 1 * 60 * 60) { value = (int)roundf(value / 60.0f); NSString *format = TGLocalized([self integerValueFormat:@"MuteExpires.Minutes_" value:value]); return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", value]]; } else if (value <= 24 * 60 * 60) { value = (int)roundf(value / (60.0f * 60.0f)); NSString *format = TGLocalized([self integerValueFormat:@"MuteExpires.Hours_" value:value]); return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", value]]; } else { value = (int)roundf(value / (24.0f * 60.0f * 60.0f)); NSString *format = TGLocalized([self integerValueFormat:@"MuteExpires.Days_" value:value]); return [[NSString alloc] initWithFormat:format, [[NSString alloc] initWithFormat:@"%d", value]]; } return @""; } + (NSString *)stringForDeviceType { NSString *model = @"iPhone"; NSString *rawModel = [[[UIDevice currentDevice] model] lowercaseString]; if ([rawModel rangeOfString:@"ipod"].location != NSNotFound) model = @"iPod"; else if ([rawModel rangeOfString:@"ipad"].location != NSNotFound) model = @"iPad"; return model; } + (NSString *)stringForCurrency:(NSString *)__unused currency amount:(int64_t)__unused amount { return nil; } + (NSString *)stringForEmojiHashOfData:(NSData *)data count:(NSInteger)count positionExtractor:(int32_t (^)(uint8_t *, int32_t, int32_t))positionExtractor { if (data.length != 32) return @""; NSArray *emojis = @[ @"😉", @"😍", @"😛", @"😭", @"😱", @"😡", @"😎", @"😴", @"😵", @"😈", @"😬", @"😇", @"😏", @"👮", @"👷", @"💂", @"👶", @"👨", @"👩", @"👴", @"👵", @"😻", @"😽", @"🙀", @"👺", @"🙈", @"🙉", @"🙊", @"💀", @"👽", @"💩", @"🔥", @"💥", @"💤", @"👂", @"👀", @"👃", @"👅", @"👄", @"👍", @"👎", @"👌", @"👊", @"✌️", @"✋️", @"👐", @"👆", @"👇", @"👉", @"👈", @"🙏", @"👏", @"💪", @"🚶", @"🏃", @"💃", @"👫", @"👪", @"👬", @"👭", @"💅", @"🎩", @"👑", @"👒", @"👟", @"👞", @"👠", @"👕", @"👗", @"👖", @"👙", @"👜", @"👓", @"🎀", @"💄", @"💛", @"💙", @"💜", @"💚", @"💍", @"💎", @"🐶", @"🐺", @"🐱", @"🐭", @"🐹", @"🐰", @"🐸", @"🐯", @"🐨", @"🐻", @"🐷", @"🐮", @"🐗", @"🐴", @"🐑", @"🐘", @"🐼", @"🐧", @"🐥", @"🐔", @"🐍", @"🐢", @"🐛", @"🐝", @"🐜", @"🐞", @"🐌", @"🐙", @"🐚", @"🐟", @"🐬", @"🐋", @"🐐", @"🐊", @"🐫", @"🍀", @"🌹", @"🌻", @"🍁", @"🌾", @"🍄", @"🌵", @"🌴", @"🌳", @"🌞", @"🌚", @"🌙", @"🌎", @"🌋", @"⚡️", @"☔️", @"❄️", @"⛄️", @"🌀", @"🌈", @"🌊", @"🎓", @"🎆", @"🎃", @"👻", @"🎅", @"🎄", @"🎁", @"🎈", @"🔮", @"🎥", @"📷", @"💿", @"💻", @"☎️", @"📡", @"📺", @"📻", @"🔉", @"🔔", @"⏳", @"⏰", @"⌚️", @"🔒", @"🔑", @"🔎", @"💡", @"🔦", @"🔌", @"🔋", @"🚿", @"🚽", @"🔧", @"🔨", @"🚪", @"🚬", @"💣", @"🔫", @"🔪", @"💊", @"💉", @"💰", @"💵", @"💳", @"✉️", @"📫", @"📦", @"📅", @"📁", @"✂️", @"📌", @"📎", @"✒️", @"✏️", @"📐", @"📚", @"🔬", @"🔭", @"🎨", @"🎬", @"🎤", @"🎧", @"🎵", @"🎹", @"🎻", @"🎺", @"🎸", @"👾", @"🎮", @"🃏", @"🎲", @"🎯", @"🏈", @"🏀", @"⚽️", @"⚾️", @"🎾", @"🎱", @"🏉", @"🎳", @"🏁", @"🏇", @"🏆", @"🏊", @"🏄", @"☕️", @"🍼", @"🍺", @"🍷", @"🍴", @"🍕", @"🍔", @"🍟", @"🍗", @"🍱", @"🍚", @"🍜", @"🍡", @"🍳", @"🍞", @"🍩", @"🍦", @"🎂", @"🍰", @"🍪", @"🍫", @"🍭", @"🍯", @"🍎", @"🍏", @"🍊", @"🍋", @"🍒", @"🍇", @"🍉", @"🍓", @"🍑", @"🍌", @"🍐", @"🍍", @"🍆", @"🍅", @"🌽", @"🏡", @"🏥", @"🏦", @"⛪️", @"🏰", @"⛺️", @"🏭", @"🗻", @"🗽", @"🎠", @"🎡", @"⛲️", @"🎢", @"🚢", @"🚤", @"⚓️", @"🚀", @"✈️", @"🚁", @"🚂", @"🚋", @"🚎", @"🚌", @"🚙", @"🚗", @"🚕", @"🚛", @"🚨", @"🚔", @"🚒", @"🚑", @"🚲", @"🚠", @"🚜", @"🚦", @"⚠️", @"🚧", @"⛽️", @"🎰", @"🗿", @"🎪", @"🎭", @"🇯🇵", @"🇰🇷", @"🇩🇪", @"🇨🇳", @"🇺🇸", @"🇫🇷", @"🇪🇸", @"🇮🇹", @"🇷🇺", @"🇬🇧", @"1️⃣", @"2️⃣", @"3️⃣", @"4️⃣", @"5️⃣", @"6️⃣", @"7️⃣", @"8️⃣", @"9️⃣", @"0️⃣", @"🔟", @"❗️", @"❓", @"♥️", @"♦️", @"💯", @"🔗", @"🔱", @"🔴", @"🔵", @"🔶", @"🔷" ]; uint8_t bytes[32]; [data getBytes:bytes length:32]; NSString *result = @""; for (int32_t i = 0; i < count; i++) { int32_t position = positionExtractor(bytes, i, (int32_t)emojis.count); NSString *emoji = emojis[position]; result = [result stringByAppendingString:emoji]; } return result; } @end #if defined(_MSC_VER) #define FORCE_INLINE __forceinline #include #define ROTL32(x,y) _rotl(x,y) #define ROTL64(x,y) _rotl64(x,y) #define BIG_CONSTANT(x) (x) // Other compilers #else // defined(_MSC_VER) #define FORCE_INLINE __attribute__((always_inline)) static inline uint32_t rotl32 ( uint32_t x, int8_t r ) { return (x << r) | (x >> (32 - r)); } #define ROTL32(x,y) rotl32(x,y) #define ROTL64(x,y) rotl64(x,y) #define BIG_CONSTANT(x) (x##LLU) #endif // !defined(_MSC_VER) //----------------------------------------------------------------------------- // Block read - if your platform needs to do endian-swapping or can only // handle aligned reads, do the conversion here static FORCE_INLINE uint32_t getblock ( const uint32_t * p, int i ) { return p[i]; } //----------------------------------------------------------------------------- // Finalization mix - force all bits of a hash block to avalanche static FORCE_INLINE uint32_t fmix ( uint32_t h ) { h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; return h; } //---------- //----------------------------------------------------------------------------- static void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out ) { const uint8_t * data = (const uint8_t*)key; const int nblocks = len / 4; uint32_t h1 = seed; const uint32_t c1 = 0xcc9e2d51; const uint32_t c2 = 0x1b873593; //---------- // body const uint32_t * blocks = (const uint32_t *)(data + nblocks*4); for(int i = -nblocks; i; i++) { uint32_t k1 = getblock(blocks,i); k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; h1 = ROTL32(h1,13); h1 = h1*5+0xe6546b64; } //---------- // tail const uint8_t * tail = (const uint8_t*)(data + nblocks*4); uint32_t k1 = 0; switch(len & 3) { case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0]; k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; }; //---------- // finalization h1 ^= len; h1 = fmix(h1); *(uint32_t*)out = h1; } int32_t legacy_murMurHash32(NSString *string) { const char *utf8 = string.UTF8String; int32_t result = 0; MurmurHash3_x86_32((uint8_t *)utf8, (int)strlen(utf8), -137723950, &result); return result; } int32_t legacy_murMurHashBytes32(void *bytes, int length) { int32_t result = 0; MurmurHash3_x86_32(bytes, length, -137723950, &result); return result; } int32_t phoneMatchHash(NSString *phone) { int length = (int)phone.length; char cleanString[length]; int cleanLength = 0; for (int i = 0; i < length; i++) { unichar c = [phone characterAtIndex:i]; if (c >= '0' && c <= '9') cleanString[cleanLength++] = (char)c; } int32_t result = 0; if (cleanLength > 8) MurmurHash3_x86_32((uint8_t *)cleanString + (cleanLength - 8), 8, -137723950, &result); else MurmurHash3_x86_32((uint8_t *)cleanString, cleanLength, -137723950, &result); return result; } bool TGIsRTL() { static bool value = false; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { value = ([NSLocale characterDirectionForLanguage:[[NSLocale preferredLanguages] objectAtIndex:0]] == NSLocaleLanguageDirectionRightToLeft); }); if (!value && iosMajorVersion() >= 9) value = [UIView appearance].semanticContentAttribute == UISemanticContentAttributeForceRightToLeft; return value; } bool TGIsArabic() { static bool value = false; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0]; value = [language isEqualToString:@"ar"] || [language hasPrefix:@"ar-"]; }); return value; } bool TGIsKorean() { static bool value = false; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0]; value = [language isEqualToString:@"ko"] || [language hasPrefix:@"ko-"]; }); return value; } bool TGIsLocaleArabic() { static bool value = false; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { NSString *identifier = [[NSLocale currentLocale] localeIdentifier]; value = [identifier isEqualToString:@"ar"] || [identifier hasPrefix:@"ar-"]; }); return value; } @implementation NSString (Telegraph) - (int)lengthByComposedCharacterSequences { return [self lengthByComposedCharacterSequencesInRange:NSMakeRange(0, self.length)]; } - (int)lengthByComposedCharacterSequencesInRange:(NSRange)range { __block NSInteger length = 0; [self enumerateSubstringsInRange:range options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(__unused NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) { if (substring.length != 0) length++; //TGLegacyLog(@"substringRange %@, enclosingRange %@, length %d", NSStringFromRange(substringRange), NSStringFromRange(enclosingRange), length); }]; //TGLegacyLog(@"length %d", length); return (int)length; } - (bool)hasNonWhitespaceCharacters { NSInteger textLength = self.length; bool hasNonWhitespace = false; for (int i = 0; i < textLength; i++) { unichar c = [self characterAtIndex:i]; if (c != ' ' && c != '\n' && c != '\t' && c != NSAttachmentCharacter) { hasNonWhitespace = true; break; } } return hasNonWhitespace; } - (NSAttributedString *)attributedFormattedStringWithRegularFont:(UIFont *)regularFont boldFont:(UIFont *)boldFont lineSpacing:(CGFloat)lineSpacing paragraphSpacing:(CGFloat)paragraphSpacing alignment:(NSTextAlignment)alignment { NSMutableArray *boldRanges = [[NSMutableArray alloc] init]; NSMutableString *cleanText = [[NSMutableString alloc] initWithString:self]; while (true) { NSRange startRange = [cleanText rangeOfString:@"**"]; if (startRange.location == NSNotFound) break; [cleanText deleteCharactersInRange:startRange]; NSRange endRange = [cleanText rangeOfString:@"**"]; if (endRange.location == NSNotFound) break; [cleanText deleteCharactersInRange:endRange]; [boldRanges addObject:[NSValue valueWithRange:NSMakeRange(startRange.location, endRange.location - startRange.location)]]; } NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; style.lineSpacing = lineSpacing; style.lineBreakMode = NSLineBreakByWordWrapping; style.alignment = alignment; style.paragraphSpacing = paragraphSpacing; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:cleanText attributes:@ { }]; [attributedString addAttributes:@{NSParagraphStyleAttributeName: style, NSFontAttributeName: regularFont} range:NSMakeRange(0, attributedString.length)]; NSDictionary *boldAttributes = @{NSFontAttributeName: boldFont}; for (NSValue *nRange in boldRanges) { [attributedString addAttributes:boldAttributes range:[nRange rangeValue]]; } return attributedString; } - (NSString *)urlAnchorPart { NSURL *url = [[NSURL alloc] initWithString:self]; return [url fragment]; } static unsigned char strToChar (char a, char b) { char encoder[3] = {'\0','\0','\0'}; encoder[0] = a; encoder[1] = b; return (char)strtol(encoder,NULL,16); } - (NSData *)dataByDecodingHexString { const char *bytes = [self cStringUsingEncoding:NSUTF8StringEncoding]; NSUInteger length = strlen(bytes); unsigned char *r = (unsigned char *)malloc(length / 2); unsigned char *index = r; while ((*bytes) && (*(bytes + 1))) { *index = strToChar(*bytes, *(bytes +1)); index++; bytes+=2; } return [[NSData alloc] initWithBytesNoCopy:r length:length / 2 freeWhenDone:true]; } - (bool)containsSingleEmoji { if (self.length > 0 && self.length < 16) { NSArray *emojis = [self emojiArray:false]; return emojis.count == 1 && [emojis.firstObject isEqualToString:self]; } return false; } - (bool)isEmoji { static dispatch_once_t onceToken; static NSCharacterSet *variationSelectors; dispatch_once(&onceToken, ^ { variationSelectors = [NSCharacterSet characterSetWithRange:NSMakeRange(0xFE00, 16)]; }); if ([self rangeOfCharacterFromSet:variationSelectors].location != NSNotFound) return true; const unichar high = [self characterAtIndex:0]; if (0xd800 <= high && high <= 0xdbff) { if (self.length < 2) return false; const unichar low = [self characterAtIndex:1]; const int codepoint = ((high - 0xd800) * 0x400) + (low - 0xdc00) + 0x10000; return (0x1d000 <= codepoint && codepoint <= 0x1f77f) || (0x1F900 <= codepoint && codepoint <= 0x1f9ff); } else { return (0x2100 <= high && high <= 0x27BF); } } - (NSArray *)emojiArray:(bool)stripModifiers { __block NSMutableArray *emoji = [[NSMutableArray alloc] init]; [self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, __unused BOOL *stop) { if ([substring isEmoji]) { if (substring.length > 2 && stripModifiers) { for (int i = 1; i < substring.length - 1; i++) { NSString *test = [substring substringToIndex:i]; if ([test isEmoji]) { [emoji addObject:test]; break; } } } else { [emoji addObject:substring]; } } }]; return emoji; } @end @implementation NSData (Telegraph) + (NSData *)dataWithHexString:(NSString *)hex { char buf[3]; buf[2] = '\0'; NSAssert(0 == [hex length] % 2, @"Hex strings should have an even number of digits (%@)", hex); uint8_t *bytes = (uint8_t *)malloc(hex.length / 2); uint8_t *bp = bytes; for (CFIndex i = 0; i < [hex length]; i += 2) { buf[0] = [hex characterAtIndex:i]; buf[1] = [hex characterAtIndex:i+1]; char *b2 = NULL; *bp++ = strtol(buf, &b2, 16); NSAssert(b2 == buf + 2, @"String should be all hex digits: %@ (bad digit around %d)", hex, (int)i); } return [NSData dataWithBytesNoCopy:bytes length:[hex length]/2 freeWhenDone:YES]; } - (NSString *)stringByEncodingInHex { const unsigned char *dataBuffer = (const unsigned char *)[self bytes]; if (dataBuffer == NULL) return [NSString string]; NSUInteger dataLength = [self length]; NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; for (int i = 0; i < (int)dataLength; ++i) [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; return hexString; } - (NSString *)stringByEncodingInHexSeparatedByString:(NSString *)string { const unsigned char *dataBuffer = (const unsigned char *)[self bytes]; if (dataBuffer == NULL) return [NSString string]; NSUInteger dataLength = [self length]; NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; NSString *divider = string; for (int i = 0; i < (int)dataLength; ++i) { if (i == (int)dataLength - 1) divider = @""; else if (i == (int)dataLength / 2 - 1) divider = [divider stringByAppendingString:divider]; else divider = string; [hexString appendString:[NSString stringWithFormat:@"%02lx%@", (unsigned long)dataBuffer[i], divider]]; } return hexString; } @end