1
0
mirror of https://github.com/danog/Telegram.git synced 2024-12-04 10:27:46 +01:00
Telegram/legacy/TelegraphKit/TGMessage.mm
2016-02-25 01:03:51 +01:00

906 lines
30 KiB
Plaintext

#import "TGMessage.h"
#import "PSKeyValueCoder.h"
#import "PSKeyValueEncoder.h"
#import "PSKeyValueDecoder.h"
#import "TGTextCheckingResult.h"
#include <tr1/unordered_map>
static std::tr1::unordered_map<int, id<TGMediaAttachmentParser> > mediaAttachmentParsers;
typedef enum {
TGMessageFlagBroadcast = 1,
TGMessageFlagLayerMask = 2 | 4 | 8 | 16 | 32,
TGMessageFlagContainsMention = 64,
TGMessageFlagForceReply = (1 << 7),
TGMessageFlagLayerMaskExtended = 0xff << 8,
TGMessageFlagSilent = (1 << 16)
} TGMessageFlags;
@interface TGMessage ()
@property (nonatomic) bool hasNoCheckingResults;
@end
@implementation TGMessage
- (instancetype)initWithKeyValueCoder:(PSKeyValueCoder *)coder
{
TGMessage *object = [[TGMessage alloc] init];
object->_mid = [coder decodeInt32ForCKey:"i"];
[coder decodeBytesForCKey:"sk" value:object->_sortKey.key length:8 + 1 + 4 + 4];
object->_pts = [coder decodeInt32ForCKey:"pts"];
object->_unread = [coder decodeInt32ForCKey:"unr"] != 0;
object->_outgoing = [coder decodeInt32ForCKey:"out"] != 0;
object->_deliveryState = (TGMessageDeliveryState)[coder decodeInt32ForCKey:"ds"];
object->_fromUid = [coder decodeInt64ForCKey:"fi"];
object->_toUid = [coder decodeInt64ForCKey:"ti"];
object->_cid = [coder decodeInt64ForCKey:"ci"];
object->_text = [coder decodeStringForCKey:"t"];
object->_date = [coder decodeInt32ForCKey:"d"];
object.mediaAttachments = [TGMessage parseMediaAttachments:[coder decodeDataCorCKey:"md"]];
object->_realDate = [coder decodeInt32ForCKey:"rd"];
object->_randomId = [coder decodeInt64ForCKey:"ri"];
object->_messageLifetime = [coder decodeInt32ForCKey:"lt"];
object->_flags = [coder decodeInt64ForCKey:"f"];
object->_seqIn = [coder decodeInt32ForCKey:"sqi"];
object->_seqOut = [coder decodeInt32ForCKey:"sqo"];
object->_contentProperties = [TGMessage parseContentProperties:[coder decodeDataCorCKey:"cpr"]];
return object;
}
- (void)encodeWithKeyValueCoder:(PSKeyValueCoder *)coder
{
[coder encodeInt32:_mid forCKey:"i"];
[coder encodeBytes:_sortKey.key length:8 + 1 + 4 + 4 forCKey:"sk"];
[coder encodeInt32:_pts forCKey:"pts"];
[coder encodeInt32:_unread ? 1 : 0 forCKey:"unr"];
[coder encodeInt32:_outgoing ? 1 : 0 forCKey:"out"];
[coder encodeInt32:_deliveryState forCKey:"ds"];
[coder encodeInt64:_fromUid forCKey:"fi"];
[coder encodeInt64:_toUid forCKey:"ti"];
[coder encodeInt64:_cid forCKey:"ci"];
[coder encodeString:_text forCKey:"t"];
[coder encodeInt32:(int32_t)_date forCKey:"d"];
[coder encodeData:[self serializeMediaAttachments:true] forCKey:"md"];
[coder encodeInt32:(int32_t)_realDate forCKey:"rd"];
[coder encodeInt64:_randomId forCKey:"ri"];
[coder encodeInt32:_messageLifetime forCKey:"lt"];
[coder encodeInt64:_flags forCKey:"f"];
[coder encodeInt32:_seqIn forCKey:"sqi"];
[coder encodeInt32:_seqOut forCKey:"sqo"];
[coder encodeData:[self serializeContentProperties] forCKey:"cpr"];
}
- (id)copyWithZone:(NSZone *)__unused zone
{
TGMessage *copyMessage = [[TGMessage alloc] init];
copyMessage->_mid = _mid;
copyMessage->_sortKey = _sortKey;
copyMessage->_pts = _pts;
copyMessage->_unread = _unread;
copyMessage->_outgoing = _outgoing;
copyMessage->_deliveryState = _deliveryState;
copyMessage->_fromUid = _fromUid;
copyMessage->_toUid = _toUid;
copyMessage->_cid = _cid;
copyMessage->_text = _text;
copyMessage->_date = _date;
copyMessage->_mediaAttachments = [[NSArray alloc] initWithArray:_mediaAttachments];
copyMessage->_realDate = _realDate;
copyMessage->_randomId = _randomId;
copyMessage->_actionInfo = _actionInfo;
copyMessage->_textCheckingResults = _textCheckingResults;
copyMessage->_messageLifetime = _messageLifetime;
copyMessage->_flags = _flags;
copyMessage->_seqIn = _seqIn;
copyMessage->_seqOut = _seqOut;
copyMessage->_contentProperties = [[NSDictionary alloc] initWithDictionary:_contentProperties];
copyMessage->_hideReplyMarkup = _hideReplyMarkup;
copyMessage->_hole = _hole;
copyMessage->_group = _group;
return copyMessage;
}
- (TGMessageTransparentSortKey)transparentSortKey
{
return TGMessageTransparentSortKeyMake(TGMessageSortKeyPeerId(_sortKey), TGMessageSortKeyTimestamp(_sortKey), TGMessageSortKeyMid(_sortKey), TGMessageSortKeySpace(_sortKey));
}
- (void)setIsBroadcast:(bool)isBroadcast
{
if (isBroadcast)
_flags |= TGMessageFlagBroadcast;
else
_flags &= ~TGMessageFlagBroadcast;
}
- (bool)isBroadcast
{
return _flags & TGMessageFlagBroadcast;
}
- (void)setForceReply:(bool)forceReply
{
if (forceReply)
_flags |= TGMessageFlagForceReply;
else
_flags &= TGMessageFlagForceReply;
}
- (bool)forceReply
{
return _flags & TGMessageFlagForceReply;
}
- (void)setIsSilent:(bool)isSilent {
if (isSilent) {
_flags |= TGMessageFlagSilent;
} else {
_flags &= ~TGMessageFlagSilent;
}
}
- (bool)isSilent {
return _flags & TGMessageFlagSilent;
}
- (void)setLayer:(NSUInteger)layer
{
int32_t layerLow = (int32_t)MIN((int32_t)layer, 31);
int32_t layerHigh = (int32_t)((int32_t)layer - layerLow);
_flags = (_flags & ~TGMessageFlagLayerMask) | ((layerLow & (1 | 2 | 4 | 8 | 16)) << 1);
_flags = (_flags & ~TGMessageFlagLayerMaskExtended) | ((layerHigh & 0xff) << 8);
}
- (NSUInteger)layer
{
NSUInteger value = [TGMessage layerFromFlags:_flags];
if (value < 1)
value = 1;
return value;
}
- (void)setContainsMention:(bool)containsMention
{
if (containsMention)
_flags |= TGMessageFlagContainsMention;
else
_flags &= (~TGMessageFlagContainsMention);
}
- (bool)containsMention
{
return _flags & TGMessageFlagContainsMention;
}
+ (NSUInteger)layerFromFlags:(int64_t)flags
{
int32_t layerLow = (int32_t)((flags & TGMessageFlagLayerMask) >> 1);
int32_t layerHigh = (int32_t)((flags & TGMessageFlagLayerMaskExtended) >> 8);
int32_t value = layerLow + layerHigh;
if (value < 1)
value = 1;
return value;
}
- (int64_t)forwardPeerId
{
for (TGMediaAttachment *attachment in _mediaAttachments)
{
if (attachment.type == TGForwardedMessageMediaAttachmentType)
{
TGForwardedMessageMediaAttachment *forwardedMessageAttachment = (TGForwardedMessageMediaAttachment *)attachment;
return forwardedMessageAttachment.forwardPeerId;
}
}
return 0;
}
- (TGMessageViewCountContentProperty *)viewCount {
return _contentProperties[@"viewCount"];
}
- (void)setViewCount:(TGMessageViewCountContentProperty *)viewCount {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithDictionary:_contentProperties];
if (viewCount != nil) {
dict[@"viewCount"] = viewCount;
} else {
[dict removeObjectForKey:@"viewCount"];
}
_contentProperties = dict;
}
- (void)setText:(NSString *)text
{
_text = text;
_textCheckingResults = nil;
_hasNoCheckingResults = false;
}
- (bool)local
{
return _mid >= TGMessageLocalMidBaseline;
}
+ (NSArray *)textCheckingResultsForText:(NSString *)text highlightMentionsAndTags:(bool)highlightMentionsAndTags highlightCommands:(bool)highlightCommands
{
bool containsSomething = false;
int length = (int)text.length;
int digitsInRow = 0;
int schemeSequence = 0;
int dotSequence = 0;
unichar lastChar = 0;
SEL sel = @selector(characterAtIndex:);
unichar (*characterAtIndexImp)(id, SEL, NSUInteger) = (unichar (*)(id, SEL, NSUInteger))[text methodForSelector:sel];
for (int i = 0; i < length; i++)
{
unichar c = characterAtIndexImp(text, sel, i);
if (highlightMentionsAndTags && (c == '@' || c == '#'))
{
containsSomething = true;
break;
}
if (c >= '0' && c <= '9')
{
digitsInRow++;
if (digitsInRow >= 6)
{
containsSomething = true;
break;
}
schemeSequence = 0;
dotSequence = 0;
}
else if (!(c != ' ' && digitsInRow > 0))
digitsInRow = 0;
if (c == ':')
{
if (schemeSequence == 0)
schemeSequence = 1;
else
schemeSequence = 0;
}
else if (c == '/')
{
if (highlightCommands)
{
containsSomething = true;
break;
}
if (schemeSequence == 2)
{
containsSomething = true;
break;
}
if (schemeSequence == 1)
schemeSequence++;
else
schemeSequence = 0;
}
else if (c == '.')
{
if (dotSequence == 0 && lastChar != ' ')
dotSequence++;
else
dotSequence = 0;
}
else if (c != ' ' && lastChar == '.' && dotSequence == 1)
{
containsSomething = true;
break;
}
else
{
dotSequence = 0;
}
lastChar = c;
}
if (containsSomething)
{
NSError *error = nil;
static NSDataDetector *dataDetector = nil;
if (dataDetector == nil)
dataDetector = [NSDataDetector dataDetectorWithTypes:(int)(NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber) error:&error];
NSMutableArray *results = [[NSMutableArray alloc] init];
[dataDetector enumerateMatchesInString:text options:0 range:NSMakeRange(0, text.length) usingBlock:^(NSTextCheckingResult *match, __unused NSMatchingFlags flags, __unused BOOL *stop)
{
NSTextCheckingType type = [match resultType];
if (type == NSTextCheckingTypeLink || type == NSTextCheckingTypePhoneNumber)
{
[results addObject:match];
}
}];
static NSCharacterSet *characterSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
characterSet = [NSCharacterSet alphanumericCharacterSet];
});
if (containsSomething && (highlightMentionsAndTags || highlightCommands))
{
int mentionStart = -1;
int hashtagStart = -1;
int commandStart = -1;
unichar previous = 0;
for (int i = 0; i < length; i++)
{
unichar c = characterAtIndexImp(text, sel, i);
if (highlightMentionsAndTags && commandStart == -1)
{
if (mentionStart != -1)
{
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'))
{
if (i > mentionStart + 1)
{
NSRange range = NSMakeRange(mentionStart + 1, i - mentionStart - 1);
NSRange mentionRange = NSMakeRange(range.location - 1, range.length + 1);
unichar mentionStartChar = [text characterAtIndex:mentionRange.location + 1];
if (!(mentionRange.length <= 1 || (mentionStartChar >= '0' && mentionStartChar <= '9')))
{
[results addObject:[[TGTextCheckingResult alloc] initWithRange:mentionRange type:TGTextCheckingResultTypeMention contents:[text substringWithRange:range]]];
}
}
mentionStart = -1;
}
}
else if (hashtagStart != -1)
{
if (c == ' ' || (![characterSet characterIsMember:c] && c != '_'))
{
if (i > hashtagStart + 1)
{
NSRange range = NSMakeRange(hashtagStart + 1, i - hashtagStart - 1);
NSRange hashtagRange = NSMakeRange(range.location - 1, range.length + 1);
[results addObject:[[TGTextCheckingResult alloc] initWithRange:hashtagRange type:TGTextCheckingResultTypeHashtag contents:[text substringWithRange:range]]];
}
hashtagStart = -1;
}
}
if (c == '@')
{
mentionStart = i;
}
else if (c == '#')
{
hashtagStart = i;
}
}
if (highlightCommands && mentionStart == -1 && hashtagStart == -1)
{
if (commandStart != -1 && ![characterSet characterIsMember:c] && c != '@' && c != '_')
{
if (i - commandStart > 1)
{
NSRange range = NSMakeRange(commandStart, i - commandStart);
[results addObject:[[TGTextCheckingResult alloc] initWithRange:range type:TGTextCheckingResultTypeCommand contents:[text substringWithRange:range]]];
}
commandStart = -1;
}
else if (c == '/' && (previous == 0 || previous == ' ' || previous == '\n' || previous == '\t'))
{
commandStart = i;
}
}
previous = c;
}
if (mentionStart != -1 && mentionStart + 1 < length - 1)
{
NSRange range = NSMakeRange(mentionStart + 1, length - mentionStart - 1);
NSRange mentionRange = NSMakeRange(range.location - 1, range.length + 1);
unichar mentionStartChar = [text characterAtIndex:mentionRange.location + 1];
if (!(mentionRange.length <= 2 || (mentionStartChar >= '0' && mentionStartChar <= '9')))
{
[results addObject:[[TGTextCheckingResult alloc] initWithRange:mentionRange type:TGTextCheckingResultTypeMention contents:[text substringWithRange:range]]];
}
}
if (hashtagStart != -1 && hashtagStart + 1 < length - 1)
{
NSRange range = NSMakeRange(hashtagStart + 1, length - hashtagStart - 1);
NSRange hashtagRange = NSMakeRange(range.location - 1, range.length + 1);
[results addObject:[[TGTextCheckingResult alloc] initWithRange:hashtagRange type:TGTextCheckingResultTypeHashtag contents:[text substringWithRange:range]]];
}
if (commandStart != -1 && commandStart + 1 < length)
{
NSRange range = NSMakeRange(commandStart, length - commandStart);
[results addObject:[[TGTextCheckingResult alloc] initWithRange:range type:TGTextCheckingResultTypeCommand contents:[text substringWithRange:range]]];
}
}
return results;
}
return nil;
}
+ (NSArray *)entitiesForMarkedUpText:(NSString *)text resultingText:(__autoreleasing NSString **)resultingText {
NSMutableArray *entities = [[NSMutableArray alloc] init];
NSMutableString *cleanText = [[NSMutableString alloc] initWithString:text];
#ifdef DEBUG
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];
NSRange range = NSMakeRange(startRange.location, endRange.location - startRange.location);
[entities addObject:[[TGMessageEntityBold alloc] initWithRange:range]];
}
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];
NSRange range = NSMakeRange(startRange.location, endRange.location - startRange.location);
[entities addObject:[[TGMessageEntityItalic alloc] initWithRange:range]];
}
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];
NSRange range = NSMakeRange(startRange.location, endRange.location - startRange.location);
[entities addObject:[[TGMessageEntityPre alloc] initWithRange:range]];
}
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];
NSRange range = NSMakeRange(startRange.location, endRange.location - startRange.location);
[entities addObject:[[TGMessageEntityTextUrl alloc] initWithRange:range url:@"http://google.com"]];
}
#endif
if (resultingText != NULL) {
*resultingText = cleanText;
}
return entities.count == 0 ? nil : entities;
}
- (NSArray *)textCheckingResults
{
if (_textCheckingResults != nil) {
return _textCheckingResults;
}
if (_mediaAttachments.count != 0) {
for (TGMediaAttachment *attachment in _mediaAttachments) {
if (attachment.type == TGMessageEntitiesAttachmentType) {
NSMutableArray *textCheckingResults = [[NSMutableArray alloc] init];
for (TGMessageEntity *entity in ((TGMessageEntitiesAttachment *)attachment).entities) {
if (entity.range.location + entity.range.length > _text.length) {
continue;
}
if ([entity isKindOfClass:[TGMessageEntityBold class]]) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeBold contents:@""]];
} else if ([entity isKindOfClass:[TGMessageEntityBotCommand class]]) {
if (entity.range.length > 1) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeCommand contents:[_text substringWithRange:NSMakeRange(entity.range.location, entity.range.length)]]];
}
} else if ([entity isKindOfClass:[TGMessageEntityCode class]]) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeCode contents:@""]];
} else if ([entity isKindOfClass:[TGMessageEntityEmail class]]) {
NSString *email = [_text substringWithRange:entity.range];
[textCheckingResults addObject:[NSTextCheckingResult linkCheckingResultWithRange:entity.range URL:[NSURL URLWithString:[@"mailto:" stringByAppendingString:email]]]];
} else if ([entity isKindOfClass:[TGMessageEntityHashtag class]]) {
if (entity.range.length > 1) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeHashtag contents:[_text substringWithRange:NSMakeRange(entity.range.location + 1, entity.range.length - 1)]]];
}
} else if ([entity isKindOfClass:[TGMessageEntityItalic class]]) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeItalic contents:@""]];
} else if ([entity isKindOfClass:[TGMessageEntityMention class]]) {
if (entity.range.length > 1) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeMention contents:[_text substringWithRange:NSMakeRange(entity.range.location + 1, entity.range.length - 1)]]];
}
} else if ([entity isKindOfClass:[TGMessageEntityPre class]]) {
[textCheckingResults addObject:[[TGTextCheckingResult alloc] initWithRange:entity.range type:TGTextCheckingResultTypeCode contents:@""]];
} else if ([entity isKindOfClass:[TGMessageEntityTextUrl class]]) {
[textCheckingResults addObject:[NSTextCheckingResult linkCheckingResultWithRange:entity.range URL:[NSURL URLWithString:((TGMessageEntityTextUrl *)entity).url]]];
} else if ([entity isKindOfClass:[TGMessageEntityUrl class]]) {
NSString *link = [_text substringWithRange:entity.range];
NSURL *url = [NSURL URLWithString:link];
if (url == nil) {
url = [NSURL URLWithString:[link stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
[textCheckingResults addObject:[NSTextCheckingResult linkCheckingResultWithRange:entity.range URL:url]];
}
}
_textCheckingResults = textCheckingResults;
return textCheckingResults;
}
}
}
if (_text.length < 2 || _text.length > 1024 * 20)
return nil;
if (_textCheckingResults == nil && !_hasNoCheckingResults)
{
_textCheckingResults = [TGMessage textCheckingResultsForText:_text highlightMentionsAndTags:true highlightCommands:true];
_hasNoCheckingResults = _textCheckingResults == nil;
}
return _textCheckingResults;
}
- (void)setReplyMarkup:(TGBotReplyMarkup *)replyMarkup
{
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:_mediaAttachments];
NSUInteger index = 0;
for (TGMediaAttachment *attachment in array)
{
if (attachment.type == TGReplyMarkupAttachmentType)
{
[array removeObjectAtIndex:index];
break;
}
index++;
}
TGReplyMarkupAttachment *attachment = [[TGReplyMarkupAttachment alloc] init];
attachment.replyMarkup = replyMarkup;
[array addObject:attachment];
_mediaAttachments = array;
}
- (TGBotReplyMarkup *)replyMarkup
{
for (TGMediaAttachment *attachment in _mediaAttachments)
{
if (attachment.type == TGReplyMarkupAttachmentType)
{
return ((TGReplyMarkupAttachment *)attachment).replyMarkup;
}
}
return nil;
}
- (void)setEntities:(NSArray *)entities
{
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:_mediaAttachments];
NSUInteger index = 0;
for (TGMediaAttachment *attachment in array)
{
if (attachment.type == TGMessageEntitiesAttachmentType)
{
[array removeObjectAtIndex:index];
break;
}
index++;
}
TGMessageEntitiesAttachment *attachment = [[TGMessageEntitiesAttachment alloc] init];
attachment.entities = entities;
[array addObject:attachment];
_mediaAttachments = array;
}
- (NSArray *)entities
{
for (TGMediaAttachment *attachment in _mediaAttachments)
{
if (attachment.type == TGMessageEntitiesAttachmentType)
{
return ((TGMessageEntitiesAttachment *)attachment).entities;
}
}
return nil;
}
+ (void)registerMediaAttachmentParser:(int)type parser:(id<TGMediaAttachmentParser>)parser
{
mediaAttachmentParsers.insert(std::pair<int, id<TGMediaAttachmentParser> >(type, parser));
}
- (NSData *)serializeMediaAttachments:(bool)includeMeta
{
if (_mediaAttachments == nil || _mediaAttachments.count == 0)
return [NSData data];
NSMutableData *data = [[NSMutableData alloc] init];
int count = 0;
NSRange countRange = NSMakeRange(data.length, 4);
[data appendBytes:&count length:4];
for (TGMediaAttachment *attachment in _mediaAttachments)
{
if (!includeMeta && attachment.isMeta)
continue;
int type = attachment.type;
[data appendBytes:&type length:4];
[attachment serialize:data];
count++;
}
[data replaceBytesInRange:countRange withBytes:&count];
return data;
}
+ (NSData *)serializeMediaAttachments:(bool)includeMeta attachments:(NSArray *)attachments
{
if (attachments == nil || attachments.count == 0)
return [NSData data];
NSMutableData *data = [[NSMutableData alloc] init];
int count = 0;
NSRange countRange = NSMakeRange(data.length, 4);
[data appendBytes:&count length:4];
for (TGMediaAttachment *attachment in attachments)
{
if (!includeMeta && attachment.isMeta)
continue;
int type = attachment.type;
[data appendBytes:&type length:4];
[attachment serialize:data];
count++;
}
[data replaceBytesInRange:countRange withBytes:&count];
return data;
}
+ (NSData *)serializeAttachment:(TGMediaAttachment *)attachment
{
if (attachment == nil)
return [NSData data];
NSMutableData *data = [[NSMutableData alloc] init];
int count = 1;
[data appendBytes:&count length:4];
int type = attachment.type;
[data appendBytes:&type length:4];
[attachment serialize:data];
return data;
}
- (void)setMediaAttachments:(NSArray *)mediaAttachments
{
for (TGMediaAttachment *attachment in mediaAttachments)
{
if (attachment.type == TGActionMediaAttachmentType) {
_actionInfo = (TGActionMediaAttachment *)attachment;
}
}
_mediaAttachments = mediaAttachments;
}
+ (NSArray *)parseMediaAttachments:(NSData *)data
{
if (data == nil || data.length == 0)
return [NSArray array];
NSInputStream *is = [[NSInputStream alloc] initWithData:data];
[is open];
int count = 0;
[is read:(uint8_t *)&count maxLength:4];
NSMutableArray *attachments = [[NSMutableArray alloc] initWithCapacity:count];
for (int i = 0; i < count; i++)
{
int type = 0;
[is read:(uint8_t *)&type maxLength:4];
std::tr1::unordered_map<int, id<TGMediaAttachmentParser> >::iterator it = mediaAttachmentParsers.find(type);
if (it == mediaAttachmentParsers.end())
{
TGLog(@"***** Unknown media attachment type %d", type);
return [NSArray array];
}
TGMediaAttachment *attachment = [it->second parseMediaAttachment:is];
if (attachment != nil)
{
[attachments addObject:attachment];
}
}
[is close];
return [NSArray arrayWithArray:attachments];
}
- (NSData *)serializeContentProperties
{
if (_contentProperties.count == 0)
return nil;
PSKeyValueEncoder *encoder = [[PSKeyValueEncoder alloc] init];
[_contentProperties enumerateKeysAndObjectsUsingBlock:^(NSString *key, id<PSCoding> value, __unused BOOL *stop)
{
[encoder encodeObject:value forKey:key];
}];
return encoder.data;
}
+ (NSData *)serializeContentProperties:(NSDictionary *)contentProperties
{
if (contentProperties.count == 0)
return nil;
PSKeyValueEncoder *encoder = [[PSKeyValueEncoder alloc] init];
[contentProperties enumerateKeysAndObjectsUsingBlock:^(NSString *key, id<PSCoding> value, __unused BOOL *stop)
{
[encoder encodeObject:value forKey:key];
}];
return encoder.data;
}
+ (NSDictionary *)parseContentProperties:(NSData *)data
{
if (data.length == 0)
return nil;
PSKeyValueDecoder *decoder = [[PSKeyValueDecoder alloc] initWithData:data];
return [decoder decodeObjectsByKeys];
}
@end
@interface TGMediaId ()
{
int _cachedHash;
}
@end
@implementation TGMediaId
- (id)initWithType:(uint8_t)type itemId:(int64_t)itemId
{
self = [super init];
if (self != nil)
{
_type = type;
_itemId = itemId;
}
return self;
}
- (id)copyWithZone:(NSZone *)__unused zone
{
TGMediaId *copyMediaId = [[TGMediaId alloc] initWithType:_type itemId:_itemId];
return copyMediaId;
}
- (NSUInteger)hash
{
if (_cachedHash == 0)
_cachedHash = (int)(((_itemId >> 32) ^ _itemId & 0xffffffff) + (int)_type);
return _cachedHash;
}
- (BOOL)isEqual:(id)anObject
{
if (![anObject isKindOfClass:[TGMediaId class]])
return false;
TGMediaId *other = (TGMediaId *)anObject;
return other.itemId == _itemId && other.type == _type;
}
@end