Files

1456 lines
48 KiB
Plaintext

#import <LegacyComponents/TGStringUtils.h>
#import "LegacyComponentsInternal.h"
#import <CommonCrypto/CommonDigest.h>
#import <LegacyComponents/TGLocalization.h>
#import <LegacyComponents/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
{ @"&quot;", 34 },
{ @"&amp;", 38 },
{ @"&apos;", 39 },
{ @"&lt;", 60 },
{ @"&gt;", 62 },
// A.2.1. Latin-1 characters
{ @"&nbsp;", 160 },
{ @"&iexcl;", 161 },
{ @"&cent;", 162 },
{ @"&pound;", 163 },
{ @"&curren;", 164 },
{ @"&yen;", 165 },
{ @"&brvbar;", 166 },
{ @"&sect;", 167 },
{ @"&uml;", 168 },
{ @"&copy;", 169 },
{ @"&ordf;", 170 },
{ @"&laquo;", 171 },
{ @"&not;", 172 },
{ @"&shy;", 173 },
{ @"&reg;", 174 },
{ @"&macr;", 175 },
{ @"&deg;", 176 },
{ @"&plusmn;", 177 },
{ @"&sup2;", 178 },
{ @"&sup3;", 179 },
{ @"&acute;", 180 },
{ @"&micro;", 181 },
{ @"&para;", 182 },
{ @"&middot;", 183 },
{ @"&cedil;", 184 },
{ @"&sup1;", 185 },
{ @"&ordm;", 186 },
{ @"&raquo;", 187 },
{ @"&frac14;", 188 },
{ @"&frac12;", 189 },
{ @"&frac34;", 190 },
{ @"&iquest;", 191 },
{ @"&Agrave;", 192 },
{ @"&Aacute;", 193 },
{ @"&Acirc;", 194 },
{ @"&Atilde;", 195 },
{ @"&Auml;", 196 },
{ @"&Aring;", 197 },
{ @"&AElig;", 198 },
{ @"&Ccedil;", 199 },
{ @"&Egrave;", 200 },
{ @"&Eacute;", 201 },
{ @"&Ecirc;", 202 },
{ @"&Euml;", 203 },
{ @"&Igrave;", 204 },
{ @"&Iacute;", 205 },
{ @"&Icirc;", 206 },
{ @"&Iuml;", 207 },
{ @"&ETH;", 208 },
{ @"&Ntilde;", 209 },
{ @"&Ograve;", 210 },
{ @"&Oacute;", 211 },
{ @"&Ocirc;", 212 },
{ @"&Otilde;", 213 },
{ @"&Ouml;", 214 },
{ @"&times;", 215 },
{ @"&Oslash;", 216 },
{ @"&Ugrave;", 217 },
{ @"&Uacute;", 218 },
{ @"&Ucirc;", 219 },
{ @"&Uuml;", 220 },
{ @"&Yacute;", 221 },
{ @"&THORN;", 222 },
{ @"&szlig;", 223 },
{ @"&agrave;", 224 },
{ @"&aacute;", 225 },
{ @"&acirc;", 226 },
{ @"&atilde;", 227 },
{ @"&auml;", 228 },
{ @"&aring;", 229 },
{ @"&aelig;", 230 },
{ @"&ccedil;", 231 },
{ @"&egrave;", 232 },
{ @"&eacute;", 233 },
{ @"&ecirc;", 234 },
{ @"&euml;", 235 },
{ @"&igrave;", 236 },
{ @"&iacute;", 237 },
{ @"&icirc;", 238 },
{ @"&iuml;", 239 },
{ @"&eth;", 240 },
{ @"&ntilde;", 241 },
{ @"&ograve;", 242 },
{ @"&oacute;", 243 },
{ @"&ocirc;", 244 },
{ @"&otilde;", 245 },
{ @"&ouml;", 246 },
{ @"&divide;", 247 },
{ @"&oslash;", 248 },
{ @"&ugrave;", 249 },
{ @"&uacute;", 250 },
{ @"&ucirc;", 251 },
{ @"&uuml;", 252 },
{ @"&yacute;", 253 },
{ @"&thorn;", 254 },
{ @"&yuml;", 255 },
// A.2.2. Special characters cont'd
{ @"&OElig;", 338 },
{ @"&oelig;", 339 },
{ @"&Scaron;", 352 },
{ @"&scaron;", 353 },
{ @"&Yuml;", 376 },
// A.2.3. Symbols
{ @"&fnof;", 402 },
// A.2.2. Special characters cont'd
{ @"&circ;", 710 },
{ @"&tilde;", 732 },
// A.2.3. Symbols cont'd
{ @"&Alpha;", 913 },
{ @"&Beta;", 914 },
{ @"&Gamma;", 915 },
{ @"&Delta;", 916 },
{ @"&Epsilon;", 917 },
{ @"&Zeta;", 918 },
{ @"&Eta;", 919 },
{ @"&Theta;", 920 },
{ @"&Iota;", 921 },
{ @"&Kappa;", 922 },
{ @"&Lambda;", 923 },
{ @"&Mu;", 924 },
{ @"&Nu;", 925 },
{ @"&Xi;", 926 },
{ @"&Omicron;", 927 },
{ @"&Pi;", 928 },
{ @"&Rho;", 929 },
{ @"&Sigma;", 931 },
{ @"&Tau;", 932 },
{ @"&Upsilon;", 933 },
{ @"&Phi;", 934 },
{ @"&Chi;", 935 },
{ @"&Psi;", 936 },
{ @"&Omega;", 937 },
{ @"&alpha;", 945 },
{ @"&beta;", 946 },
{ @"&gamma;", 947 },
{ @"&delta;", 948 },
{ @"&epsilon;", 949 },
{ @"&zeta;", 950 },
{ @"&eta;", 951 },
{ @"&theta;", 952 },
{ @"&iota;", 953 },
{ @"&kappa;", 954 },
{ @"&lambda;", 955 },
{ @"&mu;", 956 },
{ @"&nu;", 957 },
{ @"&xi;", 958 },
{ @"&omicron;", 959 },
{ @"&pi;", 960 },
{ @"&rho;", 961 },
{ @"&sigmaf;", 962 },
{ @"&sigma;", 963 },
{ @"&tau;", 964 },
{ @"&upsilon;", 965 },
{ @"&phi;", 966 },
{ @"&chi;", 967 },
{ @"&psi;", 968 },
{ @"&omega;", 969 },
{ @"&thetasym;", 977 },
{ @"&upsih;", 978 },
{ @"&piv;", 982 },
// A.2.2. Special characters cont'd
{ @"&ensp;", 8194 },
{ @"&emsp;", 8195 },
{ @"&thinsp;", 8201 },
{ @"&zwnj;", 8204 },
{ @"&zwj;", 8205 },
{ @"&lrm;", 8206 },
{ @"&rlm;", 8207 },
{ @"&ndash;", 8211 },
{ @"&mdash;", 8212 },
{ @"&lsquo;", 8216 },
{ @"&rsquo;", 8217 },
{ @"&sbquo;", 8218 },
{ @"&ldquo;", 8220 },
{ @"&rdquo;", 8221 },
{ @"&bdquo;", 8222 },
{ @"&dagger;", 8224 },
{ @"&Dagger;", 8225 },
// A.2.3. Symbols cont'd
{ @"&bull;", 8226 },
{ @"&hellip;", 8230 },
// A.2.2. Special characters cont'd
{ @"&permil;", 8240 },
// A.2.3. Symbols cont'd
{ @"&prime;", 8242 },
{ @"&Prime;", 8243 },
// A.2.2. Special characters cont'd
{ @"&lsaquo;", 8249 },
{ @"&rsaquo;", 8250 },
// A.2.3. Symbols cont'd
{ @"&oline;", 8254 },
{ @"&frasl;", 8260 },
// A.2.2. Special characters cont'd
{ @"&euro;", 8364 },
// A.2.3. Symbols cont'd
{ @"&image;", 8465 },
{ @"&weierp;", 8472 },
{ @"&real;", 8476 },
{ @"&trade;", 8482 },
{ @"&alefsym;", 8501 },
{ @"&larr;", 8592 },
{ @"&uarr;", 8593 },
{ @"&rarr;", 8594 },
{ @"&darr;", 8595 },
{ @"&harr;", 8596 },
{ @"&crarr;", 8629 },
{ @"&lArr;", 8656 },
{ @"&uArr;", 8657 },
{ @"&rArr;", 8658 },
{ @"&dArr;", 8659 },
{ @"&hArr;", 8660 },
{ @"&forall;", 8704 },
{ @"&part;", 8706 },
{ @"&exist;", 8707 },
{ @"&empty;", 8709 },
{ @"&nabla;", 8711 },
{ @"&isin;", 8712 },
{ @"&notin;", 8713 },
{ @"&ni;", 8715 },
{ @"&prod;", 8719 },
{ @"&sum;", 8721 },
{ @"&minus;", 8722 },
{ @"&lowast;", 8727 },
{ @"&radic;", 8730 },
{ @"&prop;", 8733 },
{ @"&infin;", 8734 },
{ @"&ang;", 8736 },
{ @"&and;", 8743 },
{ @"&or;", 8744 },
{ @"&cap;", 8745 },
{ @"&cup;", 8746 },
{ @"&int;", 8747 },
{ @"&there4;", 8756 },
{ @"&sim;", 8764 },
{ @"&cong;", 8773 },
{ @"&asymp;", 8776 },
{ @"&ne;", 8800 },
{ @"&equiv;", 8801 },
{ @"&le;", 8804 },
{ @"&ge;", 8805 },
{ @"&sub;", 8834 },
{ @"&sup;", 8835 },
{ @"&nsub;", 8836 },
{ @"&sube;", 8838 },
{ @"&supe;", 8839 },
{ @"&oplus;", 8853 },
{ @"&otimes;", 8855 },
{ @"&perp;", 8869 },
{ @"&sdot;", 8901 },
{ @"&lceil;", 8968 },
{ @"&rceil;", 8969 },
{ @"&lfloor;", 8970 },
{ @"&rfloor;", 8971 },
{ @"&lang;", 9001 },
{ @"&rang;", 9002 },
{ @"&loz;", 9674 },
{ @"&spades;", 9824 },
{ @"&clubs;", 9827 },
{ @"&hearts;", 9829 },
{ @"&diams;", 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 (&lt;) and less than 11 (&thetasym;)
if (length > 3 && length < 11)
{
if ([escapeString characterAtIndex:1] == '#')
{
unichar char2 = [escapeString characterAtIndex:2];
if (char2 == 'x' || char2 == 'X') {
// Hex escape squences &#xa3;
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 &#123;
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:@"<br/>" 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];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CC_MD5(ptr, (CC_LONG)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], md5Buffer);
#pragma clang diagnostic pop
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];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CC_MD5(data.bytes, (CC_LONG)data.length, md5Buffer);
#pragma clang diagnostic pop
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 <stdlib.h>
#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;
#pragma clang diagnostic push
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 180500
#pragma clang diagnostic ignored "-Wvla-cxx-extension"
#endif
char cleanString[length];
#pragma clang diagnostic pop
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