diff --git a/NotificationService/Api.h b/NotificationService/Api.h new file mode 100644 index 0000000000..dd47051a4a --- /dev/null +++ b/NotificationService/Api.h @@ -0,0 +1,298 @@ +#import + +/* + * Layer 1 + */ + +@class Api1_Photo; +@class Api1_Photo_photoEmpty; +@class Api1_Photo_photo; + +@class Api1_PhotoSize; +@class Api1_PhotoSize_photoSizeEmpty; +@class Api1_PhotoSize_photoSize; +@class Api1_PhotoSize_photoCachedSize; +@class Api1_PhotoSize_photoStrippedSize; + +@class Api1_FileLocation; +@class Api1_FileLocation_fileLocationToBeDeprecated; + +@class Api1_DocumentAttribute; +@class Api1_DocumentAttribute_documentAttributeImageSize; +@class Api1_DocumentAttribute_documentAttributeAnimated; +@class Api1_DocumentAttribute_documentAttributeSticker; +@class Api1_DocumentAttribute_documentAttributeVideo; +@class Api1_DocumentAttribute_documentAttributeAudio; +@class Api1_DocumentAttribute_documentAttributeFilename; +@class Api1_DocumentAttribute_documentAttributeHasStickers; + +@class Api1_InputStickerSet; +@class Api1_InputStickerSet_inputStickerSetEmpty; +@class Api1_InputStickerSet_inputStickerSetID; +@class Api1_InputStickerSet_inputStickerSetShortName; + +@class Api1_InputFileLocation; +@class Api1_InputFileLocation_inputPhotoFileLocation; +@class Api1_InputFileLocation_inputDocumentFileLocation; + +@class Api1_MaskCoords; +@class Api1_MaskCoords_maskCoords; + +@class Api1_Document; +@class Api1_Document_document; + + +@interface Api1__Environment : NSObject + ++ (NSData *)serializeObject:(id)object; ++ (id)parseObject:(NSData *)data; + +@end + +@interface Api1_FunctionContext : NSObject + +@property (nonatomic, strong, readonly) NSData *payload; +@property (nonatomic, copy, readonly) id (^responseParser)(NSData *); +@property (nonatomic, strong, readonly) id metadata; + +- (instancetype)initWithPayload:(NSData *)payload responseParser:(id (^)(NSData *))responseParser metadata:(id)metadata; + +@end + +/* + * Types 1 + */ + +@interface Api1_Photo : NSObject + +@property (nonatomic, strong, readonly) NSNumber * pid; + ++ (Api1_Photo_photoEmpty *)photoEmptyWithPid:(NSNumber *)pid; ++ (Api1_Photo_photo *)photoWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date sizes:(NSArray *)sizes dcId:(NSNumber *)dcId; + +@end + +@interface Api1_Photo_photoEmpty : Api1_Photo + +@end + +@interface Api1_Photo_photo : Api1_Photo + +@property (nonatomic, strong, readonly) NSNumber * flags; +@property (nonatomic, strong, readonly) NSNumber * accessHash; +@property (nonatomic, strong, readonly) NSData * fileReference; +@property (nonatomic, strong, readonly) NSNumber * date; +@property (nonatomic, strong, readonly) NSArray * sizes; +@property (nonatomic, strong, readonly) NSNumber * dcId; + +@end + + +@interface Api1_PhotoSize : NSObject + +@property (nonatomic, strong, readonly) NSString * type; + ++ (Api1_PhotoSize_photoSizeEmpty *)photoSizeEmptyWithType:(NSString *)type; ++ (Api1_PhotoSize_photoSize *)photoSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h size:(NSNumber *)size; ++ (Api1_PhotoSize_photoCachedSize *)photoCachedSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h bytes:(NSData *)bytes; ++ (Api1_PhotoSize_photoStrippedSize *)photoStrippedSizeWithType:(NSString *)type bytes:(NSData *)bytes; + +@end + +@interface Api1_PhotoSize_photoSizeEmpty : Api1_PhotoSize + +@end + +@interface Api1_PhotoSize_photoSize : Api1_PhotoSize + +@property (nonatomic, strong, readonly) Api1_FileLocation * location; +@property (nonatomic, strong, readonly) NSNumber * w; +@property (nonatomic, strong, readonly) NSNumber * h; +@property (nonatomic, strong, readonly) NSNumber * size; + +@end + +@interface Api1_PhotoSize_photoCachedSize : Api1_PhotoSize + +@property (nonatomic, strong, readonly) Api1_FileLocation * location; +@property (nonatomic, strong, readonly) NSNumber * w; +@property (nonatomic, strong, readonly) NSNumber * h; +@property (nonatomic, strong, readonly) NSData * bytes; + +@end + +@interface Api1_PhotoSize_photoStrippedSize : Api1_PhotoSize + +@property (nonatomic, strong, readonly) NSData * bytes; + +@end + + +@interface Api1_FileLocation : NSObject + +@property (nonatomic, strong, readonly) NSNumber * volumeId; +@property (nonatomic, strong, readonly) NSNumber * localId; + ++ (Api1_FileLocation_fileLocationToBeDeprecated *)fileLocationToBeDeprecatedWithVolumeId:(NSNumber *)volumeId localId:(NSNumber *)localId; + +@end + +@interface Api1_FileLocation_fileLocationToBeDeprecated : Api1_FileLocation + +@end + + +@interface Api1_DocumentAttribute : NSObject + ++ (Api1_DocumentAttribute_documentAttributeImageSize *)documentAttributeImageSizeWithW:(NSNumber *)w h:(NSNumber *)h; ++ (Api1_DocumentAttribute_documentAttributeAnimated *)documentAttributeAnimated; ++ (Api1_DocumentAttribute_documentAttributeSticker *)documentAttributeStickerWithFlags:(NSNumber *)flags alt:(NSString *)alt stickerset:(Api1_InputStickerSet *)stickerset maskCoords:(Api1_MaskCoords *)maskCoords; ++ (Api1_DocumentAttribute_documentAttributeVideo *)documentAttributeVideoWithFlags:(NSNumber *)flags duration:(NSNumber *)duration w:(NSNumber *)w h:(NSNumber *)h; ++ (Api1_DocumentAttribute_documentAttributeAudio *)documentAttributeAudioWithFlags:(NSNumber *)flags duration:(NSNumber *)duration title:(NSString *)title performer:(NSString *)performer waveform:(NSData *)waveform; ++ (Api1_DocumentAttribute_documentAttributeFilename *)documentAttributeFilenameWithFileName:(NSString *)fileName; ++ (Api1_DocumentAttribute_documentAttributeHasStickers *)documentAttributeHasStickers; + +@end + +@interface Api1_DocumentAttribute_documentAttributeImageSize : Api1_DocumentAttribute + +@property (nonatomic, strong, readonly) NSNumber * w; +@property (nonatomic, strong, readonly) NSNumber * h; + +@end + +@interface Api1_DocumentAttribute_documentAttributeAnimated : Api1_DocumentAttribute + +@end + +@interface Api1_DocumentAttribute_documentAttributeSticker : Api1_DocumentAttribute + +@property (nonatomic, strong, readonly) NSNumber * flags; +@property (nonatomic, strong, readonly) NSString * alt; +@property (nonatomic, strong, readonly) Api1_InputStickerSet * stickerset; +@property (nonatomic, strong, readonly) Api1_MaskCoords * maskCoords; + +@end + +@interface Api1_DocumentAttribute_documentAttributeVideo : Api1_DocumentAttribute + +@property (nonatomic, strong, readonly) NSNumber * flags; +@property (nonatomic, strong, readonly) NSNumber * duration; +@property (nonatomic, strong, readonly) NSNumber * w; +@property (nonatomic, strong, readonly) NSNumber * h; + +@end + +@interface Api1_DocumentAttribute_documentAttributeAudio : Api1_DocumentAttribute + +@property (nonatomic, strong, readonly) NSNumber * flags; +@property (nonatomic, strong, readonly) NSNumber * duration; +@property (nonatomic, strong, readonly) NSString * title; +@property (nonatomic, strong, readonly) NSString * performer; +@property (nonatomic, strong, readonly) NSData * waveform; + +@end + +@interface Api1_DocumentAttribute_documentAttributeFilename : Api1_DocumentAttribute + +@property (nonatomic, strong, readonly) NSString * fileName; + +@end + +@interface Api1_DocumentAttribute_documentAttributeHasStickers : Api1_DocumentAttribute + +@end + + +@interface Api1_InputStickerSet : NSObject + ++ (Api1_InputStickerSet_inputStickerSetEmpty *)inputStickerSetEmpty; ++ (Api1_InputStickerSet_inputStickerSetID *)inputStickerSetIDWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash; ++ (Api1_InputStickerSet_inputStickerSetShortName *)inputStickerSetShortNameWithShortName:(NSString *)shortName; + +@end + +@interface Api1_InputStickerSet_inputStickerSetEmpty : Api1_InputStickerSet + +@end + +@interface Api1_InputStickerSet_inputStickerSetID : Api1_InputStickerSet + +@property (nonatomic, strong, readonly) NSNumber * pid; +@property (nonatomic, strong, readonly) NSNumber * accessHash; + +@end + +@interface Api1_InputStickerSet_inputStickerSetShortName : Api1_InputStickerSet + +@property (nonatomic, strong, readonly) NSString * shortName; + +@end + + +@interface Api1_InputFileLocation : NSObject + +@property (nonatomic, strong, readonly) NSNumber * pid; +@property (nonatomic, strong, readonly) NSNumber * accessHash; +@property (nonatomic, strong, readonly) NSData * fileReference; +@property (nonatomic, strong, readonly) NSString * thumbSize; + ++ (Api1_InputFileLocation_inputPhotoFileLocation *)inputPhotoFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize; ++ (Api1_InputFileLocation_inputDocumentFileLocation *)inputDocumentFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize; + +@end + +@interface Api1_InputFileLocation_inputPhotoFileLocation : Api1_InputFileLocation + +@end + +@interface Api1_InputFileLocation_inputDocumentFileLocation : Api1_InputFileLocation + +@end + + +@interface Api1_MaskCoords : NSObject + +@property (nonatomic, strong, readonly) NSNumber * n; +@property (nonatomic, strong, readonly) NSNumber * x; +@property (nonatomic, strong, readonly) NSNumber * y; +@property (nonatomic, strong, readonly) NSNumber * zoom; + ++ (Api1_MaskCoords_maskCoords *)maskCoordsWithN:(NSNumber *)n x:(NSNumber *)x y:(NSNumber *)y zoom:(NSNumber *)zoom; + +@end + +@interface Api1_MaskCoords_maskCoords : Api1_MaskCoords + +@end + + +@interface Api1_Document : NSObject + +@property (nonatomic, strong, readonly) NSNumber * flags; +@property (nonatomic, strong, readonly) NSNumber * pid; +@property (nonatomic, strong, readonly) NSNumber * accessHash; +@property (nonatomic, strong, readonly) NSData * fileReference; +@property (nonatomic, strong, readonly) NSNumber * date; +@property (nonatomic, strong, readonly) NSString * mimeType; +@property (nonatomic, strong, readonly) NSNumber * size; +@property (nonatomic, strong, readonly) NSArray * thumbs; +@property (nonatomic, strong, readonly) NSNumber * dcId; +@property (nonatomic, strong, readonly) NSArray * attributes; + ++ (Api1_Document_document *)documentWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date mimeType:(NSString *)mimeType size:(NSNumber *)size thumbs:(NSArray *)thumbs dcId:(NSNumber *)dcId attributes:(NSArray *)attributes; + +@end + +@interface Api1_Document_document : Api1_Document + +@end + + +/* + * Functions 1 + */ + +@interface Api1: NSObject + +@end diff --git a/NotificationService/Api.m b/NotificationService/Api.m new file mode 100644 index 0000000000..e57d131ce8 --- /dev/null +++ b/NotificationService/Api.m @@ -0,0 +1,1979 @@ +#import "Api.h" +#import + +static const char *Api1__Serializer_Key = "Api1__Serializer"; + +@interface Api1__Number : NSNumber +{ + NSNumber *_value; +} + +@end + +@implementation Api1__Number + +- (instancetype)initWithNumber:(NSNumber *)number +{ + self = [super init]; + if (self != nil) + { + _value = number; + } + return self; +} + +- (char)charValue +{ + return [_value charValue]; +} + +- (unsigned char)unsignedCharValue +{ + return [_value unsignedCharValue]; +} + +- (short)shortValue +{ + return [_value shortValue]; +} + +- (unsigned short)unsignedShortValue +{ + return [_value unsignedShortValue]; +} + +- (int)intValue +{ + return [_value intValue]; +} + +- (unsigned int)unsignedIntValue +{ + return [_value unsignedIntValue]; +} + +- (long)longValue +{ + return [_value longValue]; +} + +- (unsigned long)unsignedLongValue +{ + return [_value unsignedLongValue]; +} + +- (long long)longLongValue +{ + return [_value longLongValue]; +} + +- (unsigned long long)unsignedLongLongValue +{ + return [_value unsignedLongLongValue]; +} + +- (float)floatValue +{ + return [_value floatValue]; +} + +- (double)doubleValue +{ + return [_value doubleValue]; +} + +- (BOOL)boolValue +{ + return [_value boolValue]; +} + +- (NSInteger)integerValue +{ + return [_value integerValue]; +} + +- (NSUInteger)unsignedIntegerValue +{ + return [_value unsignedIntegerValue]; +} + +- (NSString *)stringValue +{ + return [_value stringValue]; +} + +- (NSComparisonResult)compare:(NSNumber *)otherNumber +{ + return [_value compare:otherNumber]; +} + +- (BOOL)isEqualToNumber:(NSNumber *)number +{ + return [_value isEqualToNumber:number]; +} + +- (NSString *)descriptionWithLocale:(id)locale +{ + return [_value descriptionWithLocale:locale]; +} + +- (void)getValue:(void *)value +{ + [_value getValue:value]; +} + +- (const char *)objCType +{ + return [_value objCType]; +} + +- (NSUInteger)hash +{ + return [_value hash]; +} + +- (instancetype)copyWithZone:(NSZone *)__unused zone +{ + return self; +} + +@end + +@interface Api1__Serializer : NSObject + +@property (nonatomic) int32_t constructorSignature; +@property (nonatomic, copy) bool (^serializeBlock)(id object, NSMutableData *); + +@end + +@implementation Api1__Serializer + +- (instancetype)initWithConstructorSignature:(int32_t)constructorSignature serializeBlock:(bool (^)(id, NSMutableData *))serializeBlock +{ + self = [super init]; + if (self != nil) + { + self.constructorSignature = constructorSignature; + self.serializeBlock = serializeBlock; + } + return self; +} + ++ (id)addSerializerToObject:(id)object withConstructorSignature:(int32_t)constructorSignature serializeBlock:(bool (^)(id, NSMutableData *))serializeBlock +{ + if (object != nil) + objc_setAssociatedObject(object, Api1__Serializer_Key, [[Api1__Serializer alloc] initWithConstructorSignature:constructorSignature serializeBlock:serializeBlock], OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return object; +} + ++ (id)addSerializerToObject:(id)object serializer:(Api1__Serializer *)serializer +{ + if (object != nil) + objc_setAssociatedObject(object, Api1__Serializer_Key, serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + return object; +} + +@end + +@interface Api1__UnboxedTypeMetaInfo : NSObject + +@property (nonatomic, readonly) int32_t constructorSignature; + +@end + +@implementation Api1__UnboxedTypeMetaInfo + +- (instancetype)initWithConstructorSignature:(int32_t)constructorSignature +{ + self = [super init]; + if (self != nil) + { + _constructorSignature = constructorSignature; + } + return self; +} + +@end + +@interface Api1__PreferNSDataTypeMetaInfo : NSObject + +@end + +@implementation Api1__PreferNSDataTypeMetaInfo + ++ (instancetype)preferNSDataTypeMetaInfo +{ + static Api1__PreferNSDataTypeMetaInfo *instance = nil; + static dispatch_once_t t; + dispatch_once(&t, ^ + { + instance = [[Api1__PreferNSDataTypeMetaInfo alloc] init]; + }); + return instance; +} + +@end + +@interface Api1__BoxedTypeMetaInfo : NSObject + +@end + +@implementation Api1__BoxedTypeMetaInfo + ++ (instancetype)boxedTypeMetaInfo +{ + static Api1__BoxedTypeMetaInfo *instance = nil; + static dispatch_once_t t; + dispatch_once(&t, ^ + { + instance = [[Api1__BoxedTypeMetaInfo alloc] init]; + }); + return instance; +} + +@end + +@implementation Api1__Environment + ++ (id (^)(NSData *data, NSUInteger *offset, id metaInfo))parserByConstructorSignature:(int32_t)constructorSignature +{ + static NSMutableDictionary *parsers = nil; + static dispatch_once_t t; + dispatch_once(&t, ^ + { + parsers = [[NSMutableDictionary alloc] init]; + + parsers[@((int32_t)0xA8509BDA)] = [^id (NSData *data, NSUInteger *offset, __unused id metaInfo) + { + if (*offset + 4 > data.length) + return nil; + int32_t value = 0; + [data getBytes:(void *)&value range:NSMakeRange(*offset, 4)]; + *offset += 4; + return @(value); + } copy]; + + parsers[@((int32_t)0x22076CBA)] = [^id (NSData *data, NSUInteger *offset, __unused id metaInfo) + { + if (*offset + 8 > data.length) + return nil; + int64_t value = 0; + [data getBytes:(void *)&value range:NSMakeRange(*offset, 8)]; + *offset += 8; + return @(value); + } copy]; + + parsers[@((int32_t)0x2210C154)] = [^id (NSData *data, NSUInteger *offset, __unused id metaInfo) + { + if (*offset + 8 > data.length) + return nil; + double value = 0; + [data getBytes:(void *)&value range:NSMakeRange(*offset, 8)]; + *offset += 8; + return @(value); + } copy]; + + parsers[@((int32_t)0xB5286E24)] = [^id (NSData *data, NSUInteger *offset, __unused id metaInfo) + { + if (*offset + 1 > data.length) + return nil; + uint8_t tmp = 0; + [data getBytes:(void *)&tmp range:NSMakeRange(*offset, 1)]; + *offset += 1; + + int paddingBytes = 0; + + int32_t length = tmp; + if (length == 254) + { + length = 0; + if (*offset + 3 > data.length) + return nil; + [data getBytes:((uint8_t *)&length) + 1 range:NSMakeRange(*offset, 3)]; + *offset += 3; + length >>= 8; + + paddingBytes = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length; + } + else + paddingBytes = ((((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4)))) - (length + 1); + + bool isData = [metaInfo isKindOfClass:[Api1__PreferNSDataTypeMetaInfo class]]; + id object = nil; + + if (length > 0) + { + if (*offset + length > data.length) + return nil; + if (isData) + object = [[NSData alloc] initWithBytes:((uint8_t *)data.bytes) + *offset length:length]; + else + object = [[NSString alloc] initWithBytes:((uint8_t *)data.bytes) + *offset length:length encoding:NSUTF8StringEncoding]; + + *offset += length; + } + + *offset += paddingBytes; + + return object == nil ? (isData ? [NSData data] : @"") : object; + } copy]; + + parsers[@((int32_t)0x1cb5c415)] = [^id (NSData *data, NSUInteger *offset, id metaInfo) + { + if (*offset + 4 > data.length) + return nil; + + int32_t count = 0; + [data getBytes:(void *)&count range:NSMakeRange(*offset, 4)]; + *offset += 4; + + if (count < 0) + return nil; + + bool isBoxed = false; + int32_t unboxedConstructorSignature = 0; + if ([metaInfo isKindOfClass:[Api1__BoxedTypeMetaInfo class]]) + isBoxed = true; + else if ([metaInfo isKindOfClass:[Api1__UnboxedTypeMetaInfo class]]) + unboxedConstructorSignature = ((Api1__UnboxedTypeMetaInfo *)metaInfo).constructorSignature; + else + return nil; + + NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:(NSUInteger)count]; + for (int32_t i = 0; i < count; i++) + { + int32_t itemConstructorSignature = 0; + if (isBoxed) + { + if (*offset + 4 > data.length) + return nil; + [data getBytes:(void *)&itemConstructorSignature range:NSMakeRange(*offset, 4)]; + *offset += 4; + } + else + itemConstructorSignature = unboxedConstructorSignature; + id item = [Api1__Environment parseObject:data offset:offset implicitSignature:itemConstructorSignature metaInfo:nil]; + if (item == nil) + return nil; + + [array addObject:item]; + } + + return array; + } copy]; + + parsers[@((int32_t)0x2331b22d)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + return [Api1_Photo photoEmptyWithPid:pid]; + } copy]; + parsers[@((int32_t)0xd07504a5)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * flags = nil; + if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * accessHash = nil; + if ((accessHash = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSData * fileReference = nil; + if ((fileReference = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + NSNumber * date = nil; + if ((date = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSArray * sizes = nil; + int32_t sizes_signature = 0; [_data getBytes:(void *)&sizes_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((sizes = [Api1__Environment parseObject:_data offset:_offset implicitSignature:sizes_signature metaInfo:[Api1__BoxedTypeMetaInfo boxedTypeMetaInfo]]) == nil) + return nil; + NSNumber * dcId = nil; + if ((dcId = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + return [Api1_Photo photoWithFlags:flags pid:pid accessHash:accessHash fileReference:fileReference date:date sizes:sizes dcId:dcId]; + } copy]; + parsers[@((int32_t)0xe17e23c)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * type = nil; + if ((type = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + return [Api1_PhotoSize photoSizeEmptyWithType:type]; + } copy]; + parsers[@((int32_t)0x77bfb61b)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * type = nil; + if ((type = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + Api1_FileLocation * location = nil; + int32_t location_signature = 0; [_data getBytes:(void *)&location_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((location = [Api1__Environment parseObject:_data offset:_offset implicitSignature:location_signature metaInfo:nil]) == nil) + return nil; + NSNumber * w = nil; + if ((w = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * h = nil; + if ((h = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * size = nil; + if ((size = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + return [Api1_PhotoSize photoSizeWithType:type location:location w:w h:h size:size]; + } copy]; + parsers[@((int32_t)0xe9a734fa)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * type = nil; + if ((type = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + Api1_FileLocation * location = nil; + int32_t location_signature = 0; [_data getBytes:(void *)&location_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((location = [Api1__Environment parseObject:_data offset:_offset implicitSignature:location_signature metaInfo:nil]) == nil) + return nil; + NSNumber * w = nil; + if ((w = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * h = nil; + if ((h = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSData * bytes = nil; + if ((bytes = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + return [Api1_PhotoSize photoCachedSizeWithType:type location:location w:w h:h bytes:bytes]; + } copy]; + parsers[@((int32_t)0xe0b0bc2e)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * type = nil; + if ((type = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + NSData * bytes = nil; + if ((bytes = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + return [Api1_PhotoSize photoStrippedSizeWithType:type bytes:bytes]; + } copy]; + parsers[@((int32_t)0xbc7fc6cd)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * volumeId = nil; + if ((volumeId = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * localId = nil; + if ((localId = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + return [Api1_FileLocation fileLocationToBeDeprecatedWithVolumeId:volumeId localId:localId]; + } copy]; + parsers[@((int32_t)0x6c37c15c)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * w = nil; + if ((w = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * h = nil; + if ((h = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + return [Api1_DocumentAttribute documentAttributeImageSizeWithW:w h:h]; + } copy]; + parsers[@((int32_t)0x11b58939)] = [^id (__unused NSData *_data, __unused NSUInteger* _offset, __unused id metaInfo) + { + return [Api1_DocumentAttribute documentAttributeAnimated]; + } copy]; + parsers[@((int32_t)0x6319d612)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * flags = nil; + if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSString * alt = nil; + if ((alt = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + Api1_InputStickerSet * stickerset = nil; + int32_t stickerset_signature = 0; [_data getBytes:(void *)&stickerset_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((stickerset = [Api1__Environment parseObject:_data offset:_offset implicitSignature:stickerset_signature metaInfo:nil]) == nil) + return nil; + Api1_MaskCoords * maskCoords = nil; + if (flags != nil && ([flags intValue] & (1 << 0))) { + int32_t maskCoords_signature = 0; [_data getBytes:(void *)&maskCoords_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((maskCoords = [Api1__Environment parseObject:_data offset:_offset implicitSignature:maskCoords_signature metaInfo:nil]) == nil) + return nil; + } + return [Api1_DocumentAttribute documentAttributeStickerWithFlags:flags alt:alt stickerset:stickerset maskCoords:maskCoords]; + } copy]; + parsers[@((int32_t)0xef02ce6)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * flags = nil; + if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * duration = nil; + if ((duration = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * w = nil; + if ((w = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * h = nil; + if ((h = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + return [Api1_DocumentAttribute documentAttributeVideoWithFlags:flags duration:duration w:w h:h]; + } copy]; + parsers[@((int32_t)0x9852f9c6)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * flags = nil; + if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * duration = nil; + if ((duration = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSString * title = nil; + if (flags != nil && ([flags intValue] & (1 << 0))) { + if ((title = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + } + NSString * performer = nil; + if (flags != nil && ([flags intValue] & (1 << 1))) { + if ((performer = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + } + NSData * waveform = nil; + if (flags != nil && ([flags intValue] & (1 << 2))) { + if ((waveform = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + } + return [Api1_DocumentAttribute documentAttributeAudioWithFlags:flags duration:duration title:title performer:performer waveform:waveform]; + } copy]; + parsers[@((int32_t)0x15590068)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * fileName = nil; + if ((fileName = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + return [Api1_DocumentAttribute documentAttributeFilenameWithFileName:fileName]; + } copy]; + parsers[@((int32_t)0x9801d2f7)] = [^id (__unused NSData *_data, __unused NSUInteger* _offset, __unused id metaInfo) + { + return [Api1_DocumentAttribute documentAttributeHasStickers]; + } copy]; + parsers[@((int32_t)0xffb62b95)] = [^id (__unused NSData *_data, __unused NSUInteger* _offset, __unused id metaInfo) + { + return [Api1_InputStickerSet inputStickerSetEmpty]; + } copy]; + parsers[@((int32_t)0x9de7a269)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * accessHash = nil; + if ((accessHash = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + return [Api1_InputStickerSet inputStickerSetIDWithPid:pid accessHash:accessHash]; + } copy]; + parsers[@((int32_t)0x861cc8a0)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSString * shortName = nil; + if ((shortName = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + return [Api1_InputStickerSet inputStickerSetShortNameWithShortName:shortName]; + } copy]; + parsers[@((int32_t)0x40181ffe)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * accessHash = nil; + if ((accessHash = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSData * fileReference = nil; + if ((fileReference = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + NSString * thumbSize = nil; + if ((thumbSize = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + return [Api1_InputFileLocation inputPhotoFileLocationWithPid:pid accessHash:accessHash fileReference:fileReference thumbSize:thumbSize]; + } copy]; + parsers[@((int32_t)0xbad07584)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * accessHash = nil; + if ((accessHash = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSData * fileReference = nil; + if ((fileReference = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + NSString * thumbSize = nil; + if ((thumbSize = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + return [Api1_InputFileLocation inputDocumentFileLocationWithPid:pid accessHash:accessHash fileReference:fileReference thumbSize:thumbSize]; + } copy]; + parsers[@((int32_t)0xaed6dbb2)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * n = nil; + if ((n = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * x = nil; + if ((x = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x2210c154 metaInfo:nil]) == nil) + return nil; + NSNumber * y = nil; + if ((y = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x2210c154 metaInfo:nil]) == nil) + return nil; + NSNumber * zoom = nil; + if ((zoom = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x2210c154 metaInfo:nil]) == nil) + return nil; + return [Api1_MaskCoords maskCoordsWithN:n x:x y:y zoom:zoom]; + } copy]; + parsers[@((int32_t)0x9ba29cc1)] = [^id (NSData *_data, NSUInteger* _offset, __unused id metaInfo) + { + NSNumber * flags = nil; + if ((flags = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSNumber * pid = nil; + if ((pid = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSNumber * accessHash = nil; + if ((accessHash = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0x22076cba metaInfo:nil]) == nil) + return nil; + NSData * fileReference = nil; + if ((fileReference = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:[Api1__PreferNSDataTypeMetaInfo preferNSDataTypeMetaInfo]]) == nil) + return nil; + NSNumber * date = nil; + if ((date = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSString * mimeType = nil; + if ((mimeType = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xb5286e24 metaInfo:nil]) == nil) + return nil; + NSNumber * size = nil; + if ((size = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSArray * thumbs = nil; + if (flags != nil && ([flags intValue] & (1 << 0))) { + int32_t thumbs_signature = 0; [_data getBytes:(void *)&thumbs_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((thumbs = [Api1__Environment parseObject:_data offset:_offset implicitSignature:thumbs_signature metaInfo:[Api1__BoxedTypeMetaInfo boxedTypeMetaInfo]]) == nil) + return nil; + } + NSNumber * dcId = nil; + if ((dcId = [Api1__Environment parseObject:_data offset:_offset implicitSignature:(int32_t)0xa8509bda metaInfo:nil]) == nil) + return nil; + NSArray * attributes = nil; + int32_t attributes_signature = 0; [_data getBytes:(void *)&attributes_signature range:NSMakeRange(*_offset, 4)]; *_offset += 4; + if ((attributes = [Api1__Environment parseObject:_data offset:_offset implicitSignature:attributes_signature metaInfo:[Api1__BoxedTypeMetaInfo boxedTypeMetaInfo]]) == nil) + return nil; + return [Api1_Document documentWithFlags:flags pid:pid accessHash:accessHash fileReference:fileReference date:date mimeType:mimeType size:size thumbs:thumbs dcId:dcId attributes:attributes]; + } copy]; +}); + + return parsers[@(constructorSignature)]; +} + ++ (NSData *)serializeObject:(id)object +{ + NSMutableData *data = [[NSMutableData alloc] init]; + if ([self serializeObject:object data:data addSignature:true]) + return data; + return nil; +} + ++ (bool)serializeObject:(id)object data:(NSMutableData *)data addSignature:(bool)addSignature +{ + Api1__Serializer *serializer = objc_getAssociatedObject(object, Api1__Serializer_Key); + if (serializer == nil) + return false; + if (addSignature) + { + int32_t value = serializer.constructorSignature; + [data appendBytes:(void *)&value length:4]; + } + return serializer.serializeBlock(object, data); +} + ++ (id)parseObject:(NSData *)data +{ + if (data.length < 4) + return nil; + int32_t constructorSignature = 0; + [data getBytes:(void *)&constructorSignature length:4]; + NSUInteger offset = 4; + return [self parseObject:data offset:&offset implicitSignature:constructorSignature metaInfo:nil]; +} + ++ (id)parseObject:(NSData *)data offset:(NSUInteger *)offset implicitSignature:(int32_t)implicitSignature metaInfo:(id)metaInfo +{ + id (^parser)(NSData *data, NSUInteger *offset, id metaInfo) = [self parserByConstructorSignature:implicitSignature]; + if (parser) + return parser(data, offset, metaInfo); + return nil; +} + +@end + +@interface Api1_BuiltinSerializer_Int : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Int + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0xA8509BDA serializeBlock:^bool (NSNumber *object, NSMutableData *data) + { + int32_t value = (int32_t)[object intValue]; + [data appendBytes:(void *)&value length:4]; + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_Long : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Long + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0x22076CBA serializeBlock:^bool (NSNumber *object, NSMutableData *data) + { + int64_t value = (int64_t)[object longLongValue]; + [data appendBytes:(void *)&value length:8]; + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_Double : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Double + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0x2210C154 serializeBlock:^bool (NSNumber *object, NSMutableData *data) + { + double value = (double)[object doubleValue]; + [data appendBytes:(void *)&value length:8]; + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_String : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_String + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0xB5286E24 serializeBlock:^bool (NSString *object, NSMutableData *data) + { + NSData *value = [object dataUsingEncoding:NSUTF8StringEncoding]; + int32_t length = value.length; + int32_t padding = 0; + if (length >= 254) + { + uint8_t tmp = 254; + [data appendBytes:&tmp length:1]; + [data appendBytes:(void *)&length length:3]; + padding = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length; + } + else + { + [data appendBytes:(void *)&length length:1]; + padding = ((((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4)))) - (length + 1); + } + [data appendData:value]; + for (int i = 0; i < padding; i++) + { + uint8_t tmp = 0; + [data appendBytes:(void *)&tmp length:1]; + } + + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_Bytes : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Bytes + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0xB5286E24 serializeBlock:^bool (NSData *object, NSMutableData *data) + { + NSData *value = object; + int32_t length = (int32_t)value.length; + int32_t padding = 0; + if (length >= 254) + { + uint8_t tmp = 254; + [data appendBytes:&tmp length:1]; + [data appendBytes:(void *)&length length:3]; + padding = (((length % 4) == 0 ? length : (length + 4 - (length % 4)))) - length; + } + else + { + [data appendBytes:(void *)&length length:1]; + padding = ((((length + 1) % 4) == 0 ? (length + 1) : ((length + 1) + 4 - ((length + 1) % 4)))) - (length + 1); + } + [data appendData:value]; + for (int i = 0; i < padding; i++) + { + uint8_t tmp = 0; + [data appendBytes:(void *)&tmp length:1]; + } + + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_Int128 : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Int128 + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0x4BB5362B serializeBlock:^bool (NSData *object, NSMutableData *data) + { + if (object.length != 16) + return false; + [data appendData:object]; + return true; + }]; +} + +@end + +@interface Api1_BuiltinSerializer_Int256 : Api1__Serializer +@end + +@implementation Api1_BuiltinSerializer_Int256 + +- (instancetype)init +{ + return [super initWithConstructorSignature:(int32_t)0x0929C32F serializeBlock:^bool (NSData *object, NSMutableData *data) + { + if (object.length != 32) + return false; + [data appendData:object]; + return true; + }]; +} + +@end + + + +@implementation Api1_FunctionContext + +- (instancetype)initWithPayload:(NSData *)payload responseParser:(id (^)(NSData *))responseParser metadata:(id)metadata +{ + self = [super init]; + if (self != nil) + { + _payload = payload; + _responseParser = [responseParser copy]; + _metadata = metadata; + } + return self; +} + +@end + +@interface Api1_Photo () + +@property (nonatomic, strong) NSNumber * pid; + +@end + +@interface Api1_Photo_photoEmpty () + +@end + +@interface Api1_Photo_photo () + +@property (nonatomic, strong) NSNumber * flags; +@property (nonatomic, strong) NSNumber * accessHash; +@property (nonatomic, strong) NSData * fileReference; +@property (nonatomic, strong) NSNumber * date; +@property (nonatomic, strong) NSArray * sizes; +@property (nonatomic, strong) NSNumber * dcId; + +@end + +@implementation Api1_Photo + ++ (Api1_Photo_photoEmpty *)photoEmptyWithPid:(NSNumber *)pid +{ + Api1_Photo_photoEmpty *_object = [[Api1_Photo_photoEmpty alloc] init]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + return _object; +} + ++ (Api1_Photo_photo *)photoWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date sizes:(NSArray *)sizes dcId:(NSNumber *)dcId +{ + Api1_Photo_photo *_object = [[Api1_Photo_photo alloc] init]; + _object.flags = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:flags] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.accessHash = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:accessHash] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.fileReference = [Api1__Serializer addSerializerToObject:[fileReference copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + _object.date = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:date] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.sizes = +({ +NSMutableArray *sizes_copy = [[NSMutableArray alloc] initWithCapacity:sizes.count]; +for (id sizes_item in sizes) +{ + [sizes_copy addObject:sizes_item]; +} +id sizes_result = [Api1__Serializer addSerializerToObject:sizes_copy serializer:[[Api1__Serializer alloc] initWithConstructorSignature:(int32_t)0x1cb5c415 serializeBlock:^bool (NSArray *object, NSMutableData *data) +{ + int32_t count = (int32_t)object.count; + [data appendBytes:(void *)&count length:4]; + for (id item in object) + { + if (![Api1__Environment serializeObject:item data:data addSignature:true]) + return false; + } + return true; +}]]; sizes_result;}); + _object.dcId = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:dcId] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_Photo_photoEmpty + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x2331b22d serializeBlock:^bool (Api1_Photo_photoEmpty *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photoEmpty id:%@)", self.pid]; +} + +@end + +@implementation Api1_Photo_photo + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xd07504a5 serializeBlock:^bool (Api1_Photo_photo *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.flags data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.accessHash data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.fileReference data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.date data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.sizes data:data addSignature:true]) + return false; + if (![Api1__Environment serializeObject:object.dcId data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photo flags:%@ id:%@ access_hash:%@ file_reference:%d date:%@ sizes:%@ dc_id:%@)", self.flags, self.pid, self.accessHash, (int)[self.fileReference length], self.date, self.sizes, self.dcId]; +} + +@end + + + + +@interface Api1_PhotoSize () + +@property (nonatomic, strong) NSString * type; + +@end + +@interface Api1_PhotoSize_photoSizeEmpty () + +@end + +@interface Api1_PhotoSize_photoSize () + +@property (nonatomic, strong) Api1_FileLocation * location; +@property (nonatomic, strong) NSNumber * w; +@property (nonatomic, strong) NSNumber * h; +@property (nonatomic, strong) NSNumber * size; + +@end + +@interface Api1_PhotoSize_photoCachedSize () + +@property (nonatomic, strong) Api1_FileLocation * location; +@property (nonatomic, strong) NSNumber * w; +@property (nonatomic, strong) NSNumber * h; +@property (nonatomic, strong) NSData * bytes; + +@end + +@interface Api1_PhotoSize_photoStrippedSize () + +@property (nonatomic, strong) NSData * bytes; + +@end + +@implementation Api1_PhotoSize + ++ (Api1_PhotoSize_photoSizeEmpty *)photoSizeEmptyWithType:(NSString *)type +{ + Api1_PhotoSize_photoSizeEmpty *_object = [[Api1_PhotoSize_photoSizeEmpty alloc] init]; + _object.type = [Api1__Serializer addSerializerToObject:[type copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + return _object; +} + ++ (Api1_PhotoSize_photoSize *)photoSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h size:(NSNumber *)size +{ + Api1_PhotoSize_photoSize *_object = [[Api1_PhotoSize_photoSize alloc] init]; + _object.type = [Api1__Serializer addSerializerToObject:[type copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.location = location; + _object.w = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:w] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.h = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:h] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.size = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:size] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + return _object; +} + ++ (Api1_PhotoSize_photoCachedSize *)photoCachedSizeWithType:(NSString *)type location:(Api1_FileLocation *)location w:(NSNumber *)w h:(NSNumber *)h bytes:(NSData *)bytes +{ + Api1_PhotoSize_photoCachedSize *_object = [[Api1_PhotoSize_photoCachedSize alloc] init]; + _object.type = [Api1__Serializer addSerializerToObject:[type copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.location = location; + _object.w = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:w] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.h = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:h] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.bytes = [Api1__Serializer addSerializerToObject:[bytes copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + return _object; +} + ++ (Api1_PhotoSize_photoStrippedSize *)photoStrippedSizeWithType:(NSString *)type bytes:(NSData *)bytes +{ + Api1_PhotoSize_photoStrippedSize *_object = [[Api1_PhotoSize_photoStrippedSize alloc] init]; + _object.type = [Api1__Serializer addSerializerToObject:[type copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.bytes = [Api1__Serializer addSerializerToObject:[bytes copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_PhotoSize_photoSizeEmpty + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xe17e23c serializeBlock:^bool (Api1_PhotoSize_photoSizeEmpty *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.type data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photoSizeEmpty type:%d)", (int)[self.type length]]; +} + +@end + +@implementation Api1_PhotoSize_photoSize + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x77bfb61b serializeBlock:^bool (Api1_PhotoSize_photoSize *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.type data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.location data:data addSignature:true]) + return false; + if (![Api1__Environment serializeObject:object.w data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.h data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.size data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photoSize type:%d location:%@ w:%@ h:%@ size:%@)", (int)[self.type length], self.location, self.w, self.h, self.size]; +} + +@end + +@implementation Api1_PhotoSize_photoCachedSize + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xe9a734fa serializeBlock:^bool (Api1_PhotoSize_photoCachedSize *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.type data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.location data:data addSignature:true]) + return false; + if (![Api1__Environment serializeObject:object.w data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.h data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.bytes data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photoCachedSize type:%d location:%@ w:%@ h:%@ bytes:%d)", (int)[self.type length], self.location, self.w, self.h, (int)[self.bytes length]]; +} + +@end + +@implementation Api1_PhotoSize_photoStrippedSize + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xe0b0bc2e serializeBlock:^bool (Api1_PhotoSize_photoStrippedSize *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.type data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.bytes data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(photoStrippedSize type:%d bytes:%d)", (int)[self.type length], (int)[self.bytes length]]; +} + +@end + + + + +@interface Api1_FileLocation () + +@property (nonatomic, strong) NSNumber * volumeId; +@property (nonatomic, strong) NSNumber * localId; + +@end + +@interface Api1_FileLocation_fileLocationToBeDeprecated () + +@end + +@implementation Api1_FileLocation + ++ (Api1_FileLocation_fileLocationToBeDeprecated *)fileLocationToBeDeprecatedWithVolumeId:(NSNumber *)volumeId localId:(NSNumber *)localId +{ + Api1_FileLocation_fileLocationToBeDeprecated *_object = [[Api1_FileLocation_fileLocationToBeDeprecated alloc] init]; + _object.volumeId = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:volumeId] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.localId = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:localId] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_FileLocation_fileLocationToBeDeprecated + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xbc7fc6cd serializeBlock:^bool (Api1_FileLocation_fileLocationToBeDeprecated *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.volumeId data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.localId data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(fileLocationToBeDeprecated volume_id:%@ local_id:%@)", self.volumeId, self.localId]; +} + +@end + + + + +@interface Api1_DocumentAttribute () + +@end + +@interface Api1_DocumentAttribute_documentAttributeImageSize () + +@property (nonatomic, strong) NSNumber * w; +@property (nonatomic, strong) NSNumber * h; + +@end + +@interface Api1_DocumentAttribute_documentAttributeAnimated () + +@end + +@interface Api1_DocumentAttribute_documentAttributeSticker () + +@property (nonatomic, strong) NSNumber * flags; +@property (nonatomic, strong) NSString * alt; +@property (nonatomic, strong) Api1_InputStickerSet * stickerset; +@property (nonatomic, strong) Api1_MaskCoords * maskCoords; + +@end + +@interface Api1_DocumentAttribute_documentAttributeVideo () + +@property (nonatomic, strong) NSNumber * flags; +@property (nonatomic, strong) NSNumber * duration; +@property (nonatomic, strong) NSNumber * w; +@property (nonatomic, strong) NSNumber * h; + +@end + +@interface Api1_DocumentAttribute_documentAttributeAudio () + +@property (nonatomic, strong) NSNumber * flags; +@property (nonatomic, strong) NSNumber * duration; +@property (nonatomic, strong) NSString * title; +@property (nonatomic, strong) NSString * performer; +@property (nonatomic, strong) NSData * waveform; + +@end + +@interface Api1_DocumentAttribute_documentAttributeFilename () + +@property (nonatomic, strong) NSString * fileName; + +@end + +@interface Api1_DocumentAttribute_documentAttributeHasStickers () + +@end + +@implementation Api1_DocumentAttribute + ++ (Api1_DocumentAttribute_documentAttributeImageSize *)documentAttributeImageSizeWithW:(NSNumber *)w h:(NSNumber *)h +{ + Api1_DocumentAttribute_documentAttributeImageSize *_object = [[Api1_DocumentAttribute_documentAttributeImageSize alloc] init]; + _object.w = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:w] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.h = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:h] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeAnimated *)documentAttributeAnimated +{ + Api1_DocumentAttribute_documentAttributeAnimated *_object = [[Api1_DocumentAttribute_documentAttributeAnimated alloc] init]; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeSticker *)documentAttributeStickerWithFlags:(NSNumber *)flags alt:(NSString *)alt stickerset:(Api1_InputStickerSet *)stickerset maskCoords:(Api1_MaskCoords *)maskCoords +{ + Api1_DocumentAttribute_documentAttributeSticker *_object = [[Api1_DocumentAttribute_documentAttributeSticker alloc] init]; + _object.flags = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:flags] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.alt = [Api1__Serializer addSerializerToObject:[alt copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.stickerset = stickerset; + _object.maskCoords = maskCoords; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeVideo *)documentAttributeVideoWithFlags:(NSNumber *)flags duration:(NSNumber *)duration w:(NSNumber *)w h:(NSNumber *)h +{ + Api1_DocumentAttribute_documentAttributeVideo *_object = [[Api1_DocumentAttribute_documentAttributeVideo alloc] init]; + _object.flags = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:flags] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.duration = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:duration] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.w = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:w] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.h = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:h] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeAudio *)documentAttributeAudioWithFlags:(NSNumber *)flags duration:(NSNumber *)duration title:(NSString *)title performer:(NSString *)performer waveform:(NSData *)waveform +{ + Api1_DocumentAttribute_documentAttributeAudio *_object = [[Api1_DocumentAttribute_documentAttributeAudio alloc] init]; + _object.flags = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:flags] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.duration = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:duration] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.title = [Api1__Serializer addSerializerToObject:[title copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.performer = [Api1__Serializer addSerializerToObject:[performer copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.waveform = [Api1__Serializer addSerializerToObject:[waveform copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeFilename *)documentAttributeFilenameWithFileName:(NSString *)fileName +{ + Api1_DocumentAttribute_documentAttributeFilename *_object = [[Api1_DocumentAttribute_documentAttributeFilename alloc] init]; + _object.fileName = [Api1__Serializer addSerializerToObject:[fileName copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + return _object; +} + ++ (Api1_DocumentAttribute_documentAttributeHasStickers *)documentAttributeHasStickers +{ + Api1_DocumentAttribute_documentAttributeHasStickers *_object = [[Api1_DocumentAttribute_documentAttributeHasStickers alloc] init]; + return _object; +} + + +@end + +@implementation Api1_DocumentAttribute_documentAttributeImageSize + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x6c37c15c serializeBlock:^bool (Api1_DocumentAttribute_documentAttributeImageSize *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.w data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.h data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeImageSize w:%@ h:%@)", self.w, self.h]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeAnimated + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x11b58939 serializeBlock:^bool (__unused Api1_DocumentAttribute_documentAttributeAnimated *object, __unused NSMutableData *data) + { + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeAnimated)"]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeSticker + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x6319d612 serializeBlock:^bool (Api1_DocumentAttribute_documentAttributeSticker *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.flags data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.alt data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.stickerset data:data addSignature:true]) + return false; + if ([object.flags intValue] & (1 << 0)) { + if (![Api1__Environment serializeObject:object.maskCoords data:data addSignature:true]) + return false; + } + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeSticker flags:%@ alt:%d stickerset:%@ mask_coords:%@)", self.flags, (int)[self.alt length], self.stickerset, self.maskCoords]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeVideo + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xef02ce6 serializeBlock:^bool (Api1_DocumentAttribute_documentAttributeVideo *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.flags data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.duration data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.w data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.h data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeVideo flags:%@ duration:%@ w:%@ h:%@)", self.flags, self.duration, self.w, self.h]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeAudio + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x9852f9c6 serializeBlock:^bool (Api1_DocumentAttribute_documentAttributeAudio *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.flags data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.duration data:data addSignature:false]) + return false; + if ([object.flags intValue] & (1 << 0)) { + if (![Api1__Environment serializeObject:object.title data:data addSignature:false]) + return false; + } + if ([object.flags intValue] & (1 << 1)) { + if (![Api1__Environment serializeObject:object.performer data:data addSignature:false]) + return false; + } + if ([object.flags intValue] & (1 << 2)) { + if (![Api1__Environment serializeObject:object.waveform data:data addSignature:false]) + return false; + } + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeAudio flags:%@ duration:%@ title:%d performer:%d waveform:%d)", self.flags, self.duration, (int)[self.title length], (int)[self.performer length], (int)[self.waveform length]]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeFilename + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x15590068 serializeBlock:^bool (Api1_DocumentAttribute_documentAttributeFilename *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.fileName data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeFilename file_name:%d)", (int)[self.fileName length]]; +} + +@end + +@implementation Api1_DocumentAttribute_documentAttributeHasStickers + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x9801d2f7 serializeBlock:^bool (__unused Api1_DocumentAttribute_documentAttributeHasStickers *object, __unused NSMutableData *data) + { + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(documentAttributeHasStickers)"]; +} + +@end + + + + +@interface Api1_InputStickerSet () + +@end + +@interface Api1_InputStickerSet_inputStickerSetEmpty () + +@end + +@interface Api1_InputStickerSet_inputStickerSetID () + +@property (nonatomic, strong) NSNumber * pid; +@property (nonatomic, strong) NSNumber * accessHash; + +@end + +@interface Api1_InputStickerSet_inputStickerSetShortName () + +@property (nonatomic, strong) NSString * shortName; + +@end + +@implementation Api1_InputStickerSet + ++ (Api1_InputStickerSet_inputStickerSetEmpty *)inputStickerSetEmpty +{ + Api1_InputStickerSet_inputStickerSetEmpty *_object = [[Api1_InputStickerSet_inputStickerSetEmpty alloc] init]; + return _object; +} + ++ (Api1_InputStickerSet_inputStickerSetID *)inputStickerSetIDWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash +{ + Api1_InputStickerSet_inputStickerSetID *_object = [[Api1_InputStickerSet_inputStickerSetID alloc] init]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.accessHash = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:accessHash] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + return _object; +} + ++ (Api1_InputStickerSet_inputStickerSetShortName *)inputStickerSetShortNameWithShortName:(NSString *)shortName +{ + Api1_InputStickerSet_inputStickerSetShortName *_object = [[Api1_InputStickerSet_inputStickerSetShortName alloc] init]; + _object.shortName = [Api1__Serializer addSerializerToObject:[shortName copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_InputStickerSet_inputStickerSetEmpty + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xffb62b95 serializeBlock:^bool (__unused Api1_InputStickerSet_inputStickerSetEmpty *object, __unused NSMutableData *data) + { + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(inputStickerSetEmpty)"]; +} + +@end + +@implementation Api1_InputStickerSet_inputStickerSetID + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x9de7a269 serializeBlock:^bool (Api1_InputStickerSet_inputStickerSetID *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.accessHash data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(inputStickerSetID id:%@ access_hash:%@)", self.pid, self.accessHash]; +} + +@end + +@implementation Api1_InputStickerSet_inputStickerSetShortName + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x861cc8a0 serializeBlock:^bool (Api1_InputStickerSet_inputStickerSetShortName *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.shortName data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(inputStickerSetShortName short_name:%d)", (int)[self.shortName length]]; +} + +@end + + + + +@interface Api1_InputFileLocation () + +@property (nonatomic, strong) NSNumber * pid; +@property (nonatomic, strong) NSNumber * accessHash; +@property (nonatomic, strong) NSData * fileReference; +@property (nonatomic, strong) NSString * thumbSize; + +@end + +@interface Api1_InputFileLocation_inputPhotoFileLocation () + +@end + +@interface Api1_InputFileLocation_inputDocumentFileLocation () + +@end + +@implementation Api1_InputFileLocation + ++ (Api1_InputFileLocation_inputPhotoFileLocation *)inputPhotoFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize +{ + Api1_InputFileLocation_inputPhotoFileLocation *_object = [[Api1_InputFileLocation_inputPhotoFileLocation alloc] init]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.accessHash = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:accessHash] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.fileReference = [Api1__Serializer addSerializerToObject:[fileReference copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + _object.thumbSize = [Api1__Serializer addSerializerToObject:[thumbSize copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + return _object; +} + ++ (Api1_InputFileLocation_inputDocumentFileLocation *)inputDocumentFileLocationWithPid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference thumbSize:(NSString *)thumbSize +{ + Api1_InputFileLocation_inputDocumentFileLocation *_object = [[Api1_InputFileLocation_inputDocumentFileLocation alloc] init]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.accessHash = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:accessHash] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.fileReference = [Api1__Serializer addSerializerToObject:[fileReference copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + _object.thumbSize = [Api1__Serializer addSerializerToObject:[thumbSize copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_InputFileLocation_inputPhotoFileLocation + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x40181ffe serializeBlock:^bool (Api1_InputFileLocation_inputPhotoFileLocation *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.accessHash data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.fileReference data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.thumbSize data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(inputPhotoFileLocation id:%@ access_hash:%@ file_reference:%d thumb_size:%d)", self.pid, self.accessHash, (int)[self.fileReference length], (int)[self.thumbSize length]]; +} + +@end + +@implementation Api1_InputFileLocation_inputDocumentFileLocation + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xbad07584 serializeBlock:^bool (Api1_InputFileLocation_inputDocumentFileLocation *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.accessHash data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.fileReference data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.thumbSize data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(inputDocumentFileLocation id:%@ access_hash:%@ file_reference:%d thumb_size:%d)", self.pid, self.accessHash, (int)[self.fileReference length], (int)[self.thumbSize length]]; +} + +@end + + + + +@interface Api1_MaskCoords () + +@property (nonatomic, strong) NSNumber * n; +@property (nonatomic, strong) NSNumber * x; +@property (nonatomic, strong) NSNumber * y; +@property (nonatomic, strong) NSNumber * zoom; + +@end + +@interface Api1_MaskCoords_maskCoords () + +@end + +@implementation Api1_MaskCoords + ++ (Api1_MaskCoords_maskCoords *)maskCoordsWithN:(NSNumber *)n x:(NSNumber *)x y:(NSNumber *)y zoom:(NSNumber *)zoom +{ + Api1_MaskCoords_maskCoords *_object = [[Api1_MaskCoords_maskCoords alloc] init]; + _object.n = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:n] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.x = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:x] serializer:[[Api1_BuiltinSerializer_Double alloc] init]]; + _object.y = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:y] serializer:[[Api1_BuiltinSerializer_Double alloc] init]]; + _object.zoom = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:zoom] serializer:[[Api1_BuiltinSerializer_Double alloc] init]]; + return _object; +} + + +@end + +@implementation Api1_MaskCoords_maskCoords + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0xaed6dbb2 serializeBlock:^bool (Api1_MaskCoords_maskCoords *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.n data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.x data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.y data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.zoom data:data addSignature:false]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(maskCoords n:%@ x:%@ y:%@ zoom:%@)", self.n, self.x, self.y, self.zoom]; +} + +@end + + + + +@interface Api1_Document () + +@property (nonatomic, strong) NSNumber * flags; +@property (nonatomic, strong) NSNumber * pid; +@property (nonatomic, strong) NSNumber * accessHash; +@property (nonatomic, strong) NSData * fileReference; +@property (nonatomic, strong) NSNumber * date; +@property (nonatomic, strong) NSString * mimeType; +@property (nonatomic, strong) NSNumber * size; +@property (nonatomic, strong) NSArray * thumbs; +@property (nonatomic, strong) NSNumber * dcId; +@property (nonatomic, strong) NSArray * attributes; + +@end + +@interface Api1_Document_document () + +@end + +@implementation Api1_Document + ++ (Api1_Document_document *)documentWithFlags:(NSNumber *)flags pid:(NSNumber *)pid accessHash:(NSNumber *)accessHash fileReference:(NSData *)fileReference date:(NSNumber *)date mimeType:(NSString *)mimeType size:(NSNumber *)size thumbs:(NSArray *)thumbs dcId:(NSNumber *)dcId attributes:(NSArray *)attributes +{ + Api1_Document_document *_object = [[Api1_Document_document alloc] init]; + _object.flags = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:flags] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.pid = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:pid] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.accessHash = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:accessHash] serializer:[[Api1_BuiltinSerializer_Long alloc] init]]; + _object.fileReference = [Api1__Serializer addSerializerToObject:[fileReference copy] serializer:[[Api1_BuiltinSerializer_Bytes alloc] init]]; + _object.date = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:date] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.mimeType = [Api1__Serializer addSerializerToObject:[mimeType copy] serializer:[[Api1_BuiltinSerializer_String alloc] init]]; + _object.size = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:size] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.thumbs = +({ +NSMutableArray *thumbs_copy = [[NSMutableArray alloc] initWithCapacity:thumbs.count]; +for (id thumbs_item in thumbs) +{ + [thumbs_copy addObject:thumbs_item]; +} +id thumbs_result = [Api1__Serializer addSerializerToObject:thumbs_copy serializer:[[Api1__Serializer alloc] initWithConstructorSignature:(int32_t)0x1cb5c415 serializeBlock:^bool (NSArray *object, NSMutableData *data) +{ + int32_t count = (int32_t)object.count; + [data appendBytes:(void *)&count length:4]; + for (id item in object) + { + if (![Api1__Environment serializeObject:item data:data addSignature:true]) + return false; + } + return true; +}]]; thumbs_result;}); + _object.dcId = [Api1__Serializer addSerializerToObject:[[Api1__Number alloc] initWithNumber:dcId] serializer:[[Api1_BuiltinSerializer_Int alloc] init]]; + _object.attributes = +({ +NSMutableArray *attributes_copy = [[NSMutableArray alloc] initWithCapacity:attributes.count]; +for (id attributes_item in attributes) +{ + [attributes_copy addObject:attributes_item]; +} +id attributes_result = [Api1__Serializer addSerializerToObject:attributes_copy serializer:[[Api1__Serializer alloc] initWithConstructorSignature:(int32_t)0x1cb5c415 serializeBlock:^bool (NSArray *object, NSMutableData *data) +{ + int32_t count = (int32_t)object.count; + [data appendBytes:(void *)&count length:4]; + for (id item in object) + { + if (![Api1__Environment serializeObject:item data:data addSignature:true]) + return false; + } + return true; +}]]; attributes_result;}); + return _object; +} + + +@end + +@implementation Api1_Document_document + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + [Api1__Serializer addSerializerToObject:self withConstructorSignature:0x9ba29cc1 serializeBlock:^bool (Api1_Document_document *object, NSMutableData *data) + { + if (![Api1__Environment serializeObject:object.flags data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.pid data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.accessHash data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.fileReference data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.date data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.mimeType data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.size data:data addSignature:false]) + return false; + if ([object.flags intValue] & (1 << 0)) { + if (![Api1__Environment serializeObject:object.thumbs data:data addSignature:true]) + return false; + } + if (![Api1__Environment serializeObject:object.dcId data:data addSignature:false]) + return false; + if (![Api1__Environment serializeObject:object.attributes data:data addSignature:true]) + return false; + return true; + }]; + } + return self; +} + +- (NSString *)description +{ + return [[NSString alloc] initWithFormat:@"(document flags:%@ id:%@ access_hash:%@ file_reference:%d date:%@ mime_type:%d size:%@ thumbs:%@ dc_id:%@ attributes:%@)", self.flags, self.pid, self.accessHash, (int)[self.fileReference length], self.date, (int)[self.mimeType length], self.size, self.thumbs, self.dcId, self.attributes]; +} + +@end + + + + +@implementation Api1: NSObject + +@end diff --git a/NotificationService/Api0.swift b/NotificationService/Api0.swift index 0654582811..b7debd8831 100644 --- a/NotificationService/Api0.swift +++ b/NotificationService/Api0.swift @@ -29,8 +29,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { return dict }() -struct Api { - static func parse(_ buffer: Buffer) -> Any? { +public struct Api { + public static func parse(_ buffer: Buffer) -> Any? { let reader = BufferReader(buffer) if let signature = reader.readInt32() { return parse(reader, signature: signature) @@ -38,17 +38,17 @@ struct Api { return nil } - static func parse(_ reader: BufferReader, signature: Int32) -> Any? { + public static func parse(_ reader: BufferReader, signature: Int32) -> Any? { if let parser = parsers[signature] { return parser(reader) } else { - //Logger.shared.log("TL", "Type constructor \(String(signature, radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") return nil } } - static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { + public static func parseVector(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? { if let count = reader.readInt32() { var array = [T]() var i: Int32 = 0 @@ -83,7 +83,7 @@ struct Api { return nil } - static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { + public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) { switch object { case let _1 as Api.Photo: _1.serialize(buffer, boxed) @@ -107,12 +107,12 @@ struct Api { } } -extension Api { - enum Photo: TypeConstructorDescription { +public extension Api { + public enum Photo: TypeConstructorDescription { case photoEmpty(id: Int64) case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize], dcId: Int32) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .photoEmpty(let id): if boxed { @@ -139,7 +139,7 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .photoEmpty(let id): return ("photoEmpty", [("id", id)]) @@ -148,7 +148,7 @@ extension Api { } } - static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { + public static func parse_photoEmpty(_ reader: BufferReader) -> Photo? { var _1: Int64? _1 = reader.readInt64() let _c1 = _1 != nil @@ -159,7 +159,7 @@ extension Api { return nil } } - static func parse_photo(_ reader: BufferReader) -> Photo? { + public static func parse_photo(_ reader: BufferReader) -> Photo? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? @@ -192,13 +192,13 @@ extension Api { } } - enum PhotoSize: TypeConstructorDescription { + public enum PhotoSize: TypeConstructorDescription { case photoSizeEmpty(type: String) case photoSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, size: Int32) case photoCachedSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, bytes: Buffer) case photoStrippedSize(type: String, bytes: Buffer) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .photoSizeEmpty(let type): if boxed { @@ -236,7 +236,7 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .photoSizeEmpty(let type): return ("photoSizeEmpty", [("type", type)]) @@ -249,7 +249,7 @@ extension Api { } } - static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { + public static func parse_photoSizeEmpty(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil @@ -260,7 +260,7 @@ extension Api { return nil } } - static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { + public static func parse_photoSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) var _2: Api.FileLocation? @@ -285,7 +285,7 @@ extension Api { return nil } } - static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { + public static func parse_photoCachedSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) var _2: Api.FileLocation? @@ -310,7 +310,7 @@ extension Api { return nil } } - static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { + public static func parse_photoStrippedSize(_ reader: BufferReader) -> PhotoSize? { var _1: String? _1 = parseString(reader) var _2: Buffer? @@ -326,10 +326,10 @@ extension Api { } } - enum FileLocation: TypeConstructorDescription { + public enum FileLocation: TypeConstructorDescription { case fileLocationToBeDeprecated(volumeId: Int64, localId: Int32) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .fileLocationToBeDeprecated(let volumeId, let localId): if boxed { @@ -341,14 +341,14 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .fileLocationToBeDeprecated(let volumeId, let localId): return ("fileLocationToBeDeprecated", [("volumeId", volumeId), ("localId", localId)]) } } - static func parse_fileLocationToBeDeprecated(_ reader: BufferReader) -> FileLocation? { + public static func parse_fileLocationToBeDeprecated(_ reader: BufferReader) -> FileLocation? { var _1: Int64? _1 = reader.readInt64() var _2: Int32? @@ -364,7 +364,7 @@ extension Api { } } - enum DocumentAttribute: TypeConstructorDescription { + public enum DocumentAttribute: TypeConstructorDescription { case documentAttributeImageSize(w: Int32, h: Int32) case documentAttributeAnimated case documentAttributeSticker(flags: Int32, alt: String, stickerset: Api.InputStickerSet, maskCoords: Api.MaskCoords?) @@ -373,7 +373,7 @@ extension Api { case documentAttributeFilename(fileName: String) case documentAttributeHasStickers - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .documentAttributeImageSize(let w, let h): if boxed { @@ -431,7 +431,7 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .documentAttributeImageSize(let w, let h): return ("documentAttributeImageSize", [("w", w), ("h", h)]) @@ -450,7 +450,7 @@ extension Api { } } - static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeImageSize(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -464,10 +464,10 @@ extension Api { return nil } } - static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeAnimated(_ reader: BufferReader) -> DocumentAttribute? { return Api.DocumentAttribute.documentAttributeAnimated } - static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeSticker(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? _1 = reader.readInt32() var _2: String? @@ -491,7 +491,7 @@ extension Api { return nil } } - static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeVideo(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -511,7 +511,7 @@ extension Api { return nil } } - static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeAudio(_ reader: BufferReader) -> DocumentAttribute? { var _1: Int32? _1 = reader.readInt32() var _2: Int32? @@ -534,7 +534,7 @@ extension Api { return nil } } - static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeFilename(_ reader: BufferReader) -> DocumentAttribute? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil @@ -545,17 +545,17 @@ extension Api { return nil } } - static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? { + public static func parse_documentAttributeHasStickers(_ reader: BufferReader) -> DocumentAttribute? { return Api.DocumentAttribute.documentAttributeHasStickers } } - enum InputStickerSet: TypeConstructorDescription { + public enum InputStickerSet: TypeConstructorDescription { case inputStickerSetEmpty case inputStickerSetID(id: Int64, accessHash: Int64) case inputStickerSetShortName(shortName: String) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .inputStickerSetEmpty: if boxed { @@ -579,7 +579,7 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .inputStickerSetEmpty: return ("inputStickerSetEmpty", []) @@ -590,10 +590,10 @@ extension Api { } } - static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { + public static func parse_inputStickerSetEmpty(_ reader: BufferReader) -> InputStickerSet? { return Api.InputStickerSet.inputStickerSetEmpty } - static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { + public static func parse_inputStickerSetID(_ reader: BufferReader) -> InputStickerSet? { var _1: Int64? _1 = reader.readInt64() var _2: Int64? @@ -607,7 +607,7 @@ extension Api { return nil } } - static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { + public static func parse_inputStickerSetShortName(_ reader: BufferReader) -> InputStickerSet? { var _1: String? _1 = parseString(reader) let _c1 = _1 != nil @@ -620,11 +620,11 @@ extension Api { } } - enum InputFileLocation: TypeConstructorDescription { + public enum InputFileLocation: TypeConstructorDescription { case inputPhotoFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): if boxed { @@ -647,7 +647,7 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .inputPhotoFileLocation(let id, let accessHash, let fileReference, let thumbSize): return ("inputPhotoFileLocation", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("thumbSize", thumbSize)]) @@ -656,7 +656,7 @@ extension Api { } } - static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { + public static func parse_inputPhotoFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? _1 = reader.readInt64() var _2: Int64? @@ -676,7 +676,7 @@ extension Api { return nil } } - static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { + public static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? _1 = reader.readInt64() var _2: Int64? @@ -698,10 +698,10 @@ extension Api { } } - enum MaskCoords: TypeConstructorDescription { + public enum MaskCoords: TypeConstructorDescription { case maskCoords(n: Int32, x: Double, y: Double, zoom: Double) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .maskCoords(let n, let x, let y, let zoom): if boxed { @@ -715,14 +715,14 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .maskCoords(let n, let x, let y, let zoom): return ("maskCoords", [("n", n), ("x", x), ("y", y), ("zoom", zoom)]) } } - static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { + public static func parse_maskCoords(_ reader: BufferReader) -> MaskCoords? { var _1: Int32? _1 = reader.readInt32() var _2: Double? @@ -744,10 +744,10 @@ extension Api { } } - enum Document: TypeConstructorDescription { + public enum Document: TypeConstructorDescription { case document(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, mimeType: String, size: Int32, thumbs: [Api.PhotoSize]?, dcId: Int32, attributes: [Api.DocumentAttribute]) - func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .document(let flags, let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumbs, let dcId, let attributes): if boxed { @@ -775,14 +775,14 @@ extension Api { } } - func descriptionFields() -> (String, [(String, Any)]) { + public func descriptionFields() -> (String, [(String, Any)]) { switch self { case .document(let flags, let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumbs, let dcId, let attributes): return ("document", [("flags", flags), ("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("date", date), ("mimeType", mimeType), ("size", size), ("thumbs", thumbs), ("dcId", dcId), ("attributes", attributes)]) } } - static func parse_document(_ reader: BufferReader) -> Document? { + public static func parse_document(_ reader: BufferReader) -> Document? { var _1: Int32? _1 = reader.readInt32() var _2: Int64? @@ -827,8 +827,8 @@ extension Api { } } -extension Api { - struct functions { +public extension Api { + public struct functions { } } diff --git a/NotificationService/Attachments.h b/NotificationService/Attachments.h new file mode 100644 index 0000000000..a67240cfce --- /dev/null +++ b/NotificationService/Attachments.h @@ -0,0 +1,7 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +id _Nullable parseAttachment(NSData * _Nonnull data); + +NS_ASSUME_NONNULL_END diff --git a/NotificationService/Attachments.m b/NotificationService/Attachments.m new file mode 100644 index 0000000000..e0a86247ba --- /dev/null +++ b/NotificationService/Attachments.m @@ -0,0 +1,30 @@ +#import "Attachments.h" + +#import +#import "Api.h" + +id _Nullable parseAttachment(NSData * _Nonnull data) { + if (data.length < 4) { + return nil; + } + + MTInputStream *inputStream = [[MTInputStream alloc] initWithData:data]; + + int32_t signature = [inputStream readInt32]; + + NSData *dataToParse = nil; + if (signature == 0x3072cfa1) { + NSData *bytes = [inputStream readBytes]; + if (bytes != nil) { + dataToParse = [MTGzip decompress:bytes]; + } + } else { + dataToParse = data; + } + + if (dataToParse == nil) { + return nil; + } + + return [Api1__Environment parseObject:dataToParse]; +} diff --git a/NotificationService/FetchImage.h b/NotificationService/FetchImage.h new file mode 100644 index 0000000000..dd9b2fed90 --- /dev/null +++ b/NotificationService/FetchImage.h @@ -0,0 +1,11 @@ +#import + +#import "StoredAccountInfos.h" +#import "Api.h" +#import + +NS_ASSUME_NONNULL_BEGIN + +dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^_completion)(NSData * _Nullable)); + +NS_ASSUME_NONNULL_END diff --git a/NotificationService/FetchImage.m b/NotificationService/FetchImage.m new file mode 100644 index 0000000000..8a1e8921a5 --- /dev/null +++ b/NotificationService/FetchImage.m @@ -0,0 +1,195 @@ +#import "FetchImage.h" + +#import + +#import "Serialization.h" + +@interface InMemoryKeychain : NSObject { + NSMutableDictionary *_dict; +} + +@end + +@implementation InMemoryKeychain + +- (instancetype)init { + self = [super init]; + if (self != nil) { + _dict = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void)setObject:(id)object forKey:(NSString *)aKey group:(NSString *)group { + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object]; + _dict[[NSString stringWithFormat:@"%@:%@", group, aKey]] = data; +} + +- (id)objectForKey:(NSString *)aKey group:(NSString *)group { + NSData *data = _dict[[NSString stringWithFormat:@"%@:%@", group, aKey]]; + if (data != nil) { + return [NSKeyedUnarchiver unarchiveObjectWithData:data]; + } else { + return nil; + } +} + +- (void)removeObjectForKey:(NSString *)aKey group:(NSString *)group { + [_dict removeObjectForKey:[NSString stringWithFormat:@"%@:%@", group, aKey]]; +} + +- (void)dropGroup:(NSString *)group { +} + +@end + +static void MTLoggingFunction(NSString *string, va_list args) { + NSLogv(string, args); +} + +@interface ParsedFile : NSObject + +@property (nonatomic, strong, readonly) NSData * _Nullable data; + +@end + +@implementation ParsedFile + +- (instancetype)initWithData:(NSData * _Nullable)data { + self = [super init]; + if (self != nil) { + _data = data; + } + return self; +} + +@end + +dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _Nullable proxyConnection, StoredAccountInfo *account, Api1_InputFileLocation *inputFileLocation, int32_t datacenterId, void (^completion)(NSData * _Nullable)) { + MTLogSetEnabled(true); + MTLogSetLoggingFunction(&MTLoggingFunction); + + Serialization *serialization = [[Serialization alloc] init]; + + MTApiEnvironment *apiEnvironment = [[MTApiEnvironment alloc] init]; + + apiEnvironment.apiId = buildConfig.apiId; + apiEnvironment.langPack = @"ios"; + apiEnvironment.layer = @([serialization currentLayer]); + apiEnvironment.disableUpdates = true; + apiEnvironment = [apiEnvironment withUpdatedLangPackCode:@"en"]; + + if (proxyConnection != nil) { + apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]]; + } + + MTContext *context = [[MTContext alloc] initWithSerialization:serialization apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:false]; + + NSDictionary *seedAddressList = @{}; + + if (account.isTestingEnvironment) { + seedAddressList = @{ + @(1): @[@"149.154.175.10"], + @(2): @[@"149.154.167.40"] + }; + } else { + seedAddressList = @{ + @(1): @[@"149.154.175.50", @"2001:b28:f23d:f001::a"], + @(2): @[@"149.154.167.50", @"2001:67c:4e8:f002::a"], + @(3): @[@"149.154.175.100", @"2001:b28:f23d:f003::a"], + @(4): @[@"149.154.167.91", @"2001:67c:4e8:f004::a"], + @(5): @[@"149.154.171.5", @"2001:b28:f23f:f005::a"] + }; + } + + for (NSNumber *datacenterId in seedAddressList) { + NSMutableArray *addressList = [[NSMutableArray alloc] init]; + for (NSString *host in seedAddressList[datacenterId]) { + [addressList addObject:[[MTDatacenterAddress alloc] initWithIp:host port:443 preferForMedia:false restrictToTcp:false cdn:false preferForProxy:false secret:nil]]; + } + [context setSeedAddressSetForDatacenterWithId:[datacenterId intValue] seedAddressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:addressList]]; + } + + InMemoryKeychain *keychain = [[InMemoryKeychain alloc] init]; + context.keychain = keychain; + + [context performBatchUpdates:^{ + for (NSNumber *datacenterId in account.datacenters) { + AccountDatacenterInfo *info = account.datacenters[datacenterId]; + if (info.addressList.count != 0) { + NSMutableArray *list = [[NSMutableArray alloc] init]; + for (AccountDatacenterAddress *address in info.addressList) { + [list addObject:[[MTDatacenterAddress alloc] initWithIp:address.host port:address.port preferForMedia:address.isMedia restrictToTcp:false cdn:false preferForProxy:address.isProxy secret:address.secret]]; + } + [context updateAddressSetForDatacenterWithId:[datacenterId intValue] addressSet:[[MTDatacenterAddressSet alloc] initWithAddressList:list] forceUpdateSchemes:true]; + } + } + }]; + + for (NSNumber *datacenterId in account.datacenters) { + AccountDatacenterInfo *info = account.datacenters[datacenterId]; + [context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{} mainTempAuthKey:nil mediaTempAuthKey:nil]]; + } + + MTProto * mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil]; + mtProto.useTempAuthKeys = context.useTempAuthKeys; + mtProto.checkForProxyConnectionIssues = false; + + MTRequestMessageService *requestService = [[MTRequestMessageService alloc] initWithContext:context]; + [mtProto addMessageService:requestService]; + + MTRequest *request = [[MTRequest alloc] init]; + + MTOutputStream *outputStream = [[MTOutputStream alloc] init]; + [outputStream writeInt32:-475607115]; //upload.getFile + [outputStream writeData:[Api1__Environment serializeObject:inputFileLocation]]; + + [outputStream writeInt32:0]; + [outputStream writeInt32:32 * 1024]; + + [request setPayload:[outputStream currentBytes] metadata:@"getFile" shortMetadata:@"getFile" responseParser:^id(NSData *response) { + MTInputStream *inputStream = [[MTInputStream alloc] initWithData:response]; + int32_t signature = [inputStream readInt32]; + if (signature != 157948117) { + return [[ParsedFile alloc] initWithData:nil]; + } + [inputStream readInt32]; //type + [inputStream readInt32]; //mtime + + return [[ParsedFile alloc] initWithData:[inputStream readBytes]]; + }]; + + request.dependsOnPasswordEntry = false; + request.shouldContinueExecutionWithErrorContext = ^bool (__unused MTRequestErrorContext *errorContext) { + return true; + }; + + request.completed = ^(id boxedResponse, __unused NSTimeInterval completionTimestamp, MTRpcError *error) { + if (error != nil) { + if (completion) { + completion(nil); + } + } else { + if ([boxedResponse isKindOfClass:[ParsedFile class]]) { + if (completion) { + completion(((ParsedFile *)boxedResponse).data); + } + } else { + if (completion) { + completion(nil); + } + } + } + }; + + [requestService addRequest:request]; + [mtProto resume]; + + id internalId = request.internalId; + return ^{ + [requestService removeRequestByInternalId:internalId]; + [context performBatchUpdates:^{ + }]; + [mtProto stop]; + }; +} diff --git a/NotificationService/Info.plist b/NotificationService/Info.plist index 659f01ce86..14cecfaf70 100644 --- a/NotificationService/Info.plist +++ b/NotificationService/Info.plist @@ -25,7 +25,7 @@ NSExtensionPointIdentifier com.apple.usernotifications.service NSExtensionPrincipalClass - $(PRODUCT_MODULE_NAME).NotificationService + NotificationService diff --git a/NotificationService/NotificationService.h b/NotificationService/NotificationService.h new file mode 100644 index 0000000000..91142ad7de --- /dev/null +++ b/NotificationService/NotificationService.h @@ -0,0 +1,10 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NotificationService : UNNotificationServiceExtension + +@end + +NS_ASSUME_NONNULL_END diff --git a/NotificationService/NotificationService.m b/NotificationService/NotificationService.m new file mode 100644 index 0000000000..54ced01308 --- /dev/null +++ b/NotificationService/NotificationService.m @@ -0,0 +1,359 @@ +#import "NotificationService.h" + +#import +#import + +#import "StoredAccountInfos.h" +#import "Attachments.h" +#import "Api.h" +#import "FetchImage.h" + +static NSData * _Nullable parseBase64(NSString *string) { + string = [string stringByReplacingOccurrencesOfString:@"-" withString:@"+"]; + string = [string stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; + while (string.length % 4 != 0) { + string = [string stringByAppendingString:@"="]; + } + return [[NSData alloc] initWithBase64EncodedString:string options:0]; +} + +typedef enum { + PeerNamespaceCloudUser = 0, + PeerNamespaceCloudGroup = 1, + PeerNamespaceCloudChannel = 2, + PeerNamespaceSecretChat = 3 +} PeerNamespace; + +static int64_t makePeerId(int32_t namespace, int32_t value) { + return (((int64_t)(namespace)) << 32) | ((int64_t)((uint64_t)((uint32_t)value))); +} + +@interface NotificationService () { + NSString * _Nullable _rootPath; + NSString * _Nullable _baseAppBundleId; + void (^_contentHandler)(UNNotificationContent *); + UNMutableNotificationContent * _Nullable _bestAttemptContent; + void (^_cancelFetch)(void); +} + +@end + +@implementation NotificationService + +- (instancetype)init { + self = [super init]; + if (self != nil) { + NSString *appBundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + NSRange lastDotRange = [appBundleIdentifier rangeOfString:@"." options:NSBackwardsSearch]; + if (lastDotRange.location != NSNotFound) { + _baseAppBundleId = [appBundleIdentifier substringToIndex:lastDotRange.location]; + NSString *appGroupName = [@"group." stringByAppendingString:_baseAppBundleId]; + NSURL *appGroupUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupName]; + + if (appGroupUrl != nil) { + NSString *rootPath = [[appGroupUrl path] stringByAppendingPathComponent:@"telegram-data"]; + _rootPath = rootPath; + } else { + NSAssert(false, @"appGroupUrl == nil"); + } + } else { + NSAssert(false, @"Invalid bundle id"); + } + } + return self; +} + +- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { + if (_rootPath == nil) { + contentHandler(request.content); + return; + } + + _contentHandler = [contentHandler copy]; + _bestAttemptContent = (UNMutableNotificationContent *)[request.content mutableCopy]; + + NSString * _Nullable encryptedPayload = request.content.userInfo[@"p"]; + NSData * _Nullable encryptedData = nil; + if (encryptedPayload != nil && [encryptedPayload isKindOfClass:[NSString class]]) { + encryptedData = parseBase64(encryptedPayload); + } + + StoredAccountInfos * _Nullable accountInfos = [StoredAccountInfos loadFromPath:[_rootPath stringByAppendingPathComponent:@"accounts-shared-data"]]; + + int selectedAccountIndex = -1; + NSDictionary *decryptedPayload = decryptedNotificationPayload(accountInfos.accounts, encryptedData, &selectedAccountIndex); + + if (decryptedPayload != nil && selectedAccountIndex != -1) { + StoredAccountInfo *account = accountInfos.accounts[selectedAccountIndex]; + + NSMutableDictionary *userInfo = nil; + if (_bestAttemptContent.userInfo != nil) { + userInfo = [[NSMutableDictionary alloc] initWithDictionary:_bestAttemptContent.userInfo]; + } else { + userInfo = [[NSMutableDictionary alloc] init]; + } + userInfo[@"accountId"] = @(account.accountId); + + int64_t peerId = 0; + int32_t messageId = 0; + bool silent = false; + + NSString *messageIdString = decryptedPayload[@"msg_id"]; + if ([messageIdString isKindOfClass:[NSString class]]) { + userInfo[@"msg_id"] = messageIdString; + messageId = [messageIdString intValue]; + } + + NSString *fromIdString = decryptedPayload[@"from_id"]; + if ([fromIdString isKindOfClass:[NSString class]]) { + userInfo[@"from_id"] = fromIdString; + peerId = makePeerId(PeerNamespaceCloudUser, [fromIdString intValue]); + } + + NSString *chatIdString = decryptedPayload[@"chat_id"]; + if ([chatIdString isKindOfClass:[NSString class]]) { + userInfo[@"chat_id"] = chatIdString; + peerId = makePeerId(PeerNamespaceCloudGroup, [chatIdString intValue]); + } + + NSString *channelIdString = decryptedPayload[@"channel_id"]; + if ([channelIdString isKindOfClass:[NSString class]]) { + userInfo[@"channel_id"] = channelIdString; + peerId = makePeerId(PeerNamespaceCloudChannel, [channelIdString intValue]); + } + + NSString *silentString = decryptedPayload[@"silent"]; + if ([silentString isKindOfClass:[NSString class]]) { + silent = [silentString intValue] != 0; + } + + NSString *attachmentDataString = decryptedPayload[@"attachb64"]; + NSData *attachmentData = nil; + id parsedAttachment = nil; + if ([attachmentDataString isKindOfClass:[NSString class]]) { + attachmentData = parseBase64(attachmentDataString); + if (attachmentData != nil) { + parsedAttachment = parseAttachment(attachmentData); + } + } + + NSString *imagesPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"aps-data"]; + [[NSFileManager defaultManager] createDirectoryAtPath:imagesPath withIntermediateDirectories:true attributes:nil error:nil]; + NSString *accountBasePath = [_rootPath stringByAppendingPathComponent:[NSString stringWithFormat:@"account-%llud", account.accountId]]; + + NSString *mediaBoxPath = [accountBasePath stringByAppendingPathComponent:@"/postbox/media"]; + + NSString *tempImagePath = nil; + NSString *mediaBoxThumbnailImagePath = nil; + + int32_t fileDatacenterId = 0; + Api1_InputFileLocation *inputFileLocation = nil; + + NSString *fetchResourceId = nil; + bool isPng = false; + bool isExpandableMedia = false; + + if (parsedAttachment != nil) { + if ([parsedAttachment isKindOfClass:[Api1_Photo_photo class]]) { + Api1_Photo_photo *photo = parsedAttachment; + isExpandableMedia = true; + for (id size in photo.sizes) { + if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) { + Api1_PhotoSize_photoSize *sizeValue = size; + if ([sizeValue.type isEqualToString:@"m"]) { + inputFileLocation = [Api1_InputFileLocation inputPhotoFileLocationWithPid:photo.pid accessHash:photo.accessHash fileReference:photo.fileReference thumbSize:sizeValue.type]; + fileDatacenterId = [photo.dcId intValue]; + fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-photo-size-%@-%@-%@", photo.dcId, photo.pid, sizeValue.type]; + break; + } + } + } + } else if ([parsedAttachment isKindOfClass:[Api1_Document_document class]]) { + Api1_Document_document *document = parsedAttachment; + + bool isSticker = false; + for (id attribute in document.attributes) { + if ([attribute isKindOfClass:[Api1_DocumentAttribute_documentAttributeSticker class]]) { + isSticker = true; + } + } + bool isAnimatedSticker = [document.mimeType isEqualToString:@"application/x-tgsticker"]; + if (isSticker || isAnimatedSticker) { + isExpandableMedia = true; + } + for (id size in document.thumbs) { + if ([size isKindOfClass:[Api1_PhotoSize_photoSize class]]) { + Api1_PhotoSize_photoSize *photoSize = size; + if ((isSticker && [photoSize.type isEqualToString:@"s"]) || [photoSize.type isEqualToString:@"m"]) { + if (isSticker) { + isPng = true; + } + inputFileLocation = [Api1_InputFileLocation inputDocumentFileLocationWithPid:document.pid accessHash:document.accessHash fileReference:document.fileReference thumbSize:photoSize.type]; + fileDatacenterId = [document.dcId intValue]; + fetchResourceId = [NSString stringWithFormat:@"telegram-cloud-document-size-%@-%@-%@", document.dcId, document.pid, photoSize.type]; + break; + } + } + } + } + } + + if (fetchResourceId != nil) { + tempImagePath = [imagesPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", fetchResourceId, isPng ? @"png" : @"jpg"]]; + mediaBoxThumbnailImagePath = [mediaBoxPath stringByAppendingPathComponent:fetchResourceId]; + } + + NSDictionary *aps = decryptedPayload[@"aps"]; + if ([aps isKindOfClass:[NSDictionary class]]) { + id alert = aps[@"alert"]; + if ([alert isKindOfClass:[NSDictionary class]]) { + NSDictionary *alertDict = alert; + NSString *title = alertDict[@"title"]; + NSString *subtitle = alertDict[@"subtitle"]; + NSString *body = alertDict[@"body"]; + if (![title isKindOfClass:[NSString class]]) { + title = @""; + } + if (![subtitle isKindOfClass:[NSString class]]) { + subtitle = @""; + } + if (![body isKindOfClass:[NSString class]]) { + body = nil; + } + if (title.length != 0 && silent) { + title = [title stringByAppendingString:@" 🔕"]; + } + _bestAttemptContent.title = title; + _bestAttemptContent.subtitle = subtitle; + _bestAttemptContent.body = body; + } else if ([alert isKindOfClass:[NSString class]]) { + _bestAttemptContent.title = @""; + _bestAttemptContent.subtitle = @""; + _bestAttemptContent.body = alert; + } + + NSString *threadIdString = aps[@"thread-id"]; + if ([threadIdString isKindOfClass:[NSString class]]) { + _bestAttemptContent.threadIdentifier = threadIdString; + } + NSString *soundString = aps[@"sound"]; + if ([soundString isKindOfClass:[NSString class]]) { + _bestAttemptContent.sound = [UNNotificationSound soundNamed:soundString]; + } + NSString *categoryString = aps[@"category"]; + if ([categoryString isKindOfClass:[NSString class]]) { + _bestAttemptContent.categoryIdentifier = categoryString; + if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) { + userInfo[@"peerId"] = @(peerId); + userInfo[@"messageId.namespace"] = @(0); + userInfo[@"messageId.id"] = @(messageId); + + userInfo[@"media"] = [attachmentData base64EncodedStringWithOptions:0]; + + if (isExpandableMedia) { + if ([categoryString isEqualToString:@"r"]) { + _bestAttemptContent.categoryIdentifier = @"withReplyMedia"; + } else if ([categoryString isEqualToString:@"m"]) { + _bestAttemptContent.categoryIdentifier = @"withMuteMedia"; + } + } + } + } + + if (accountInfos.accounts.count > 1) { + if (_bestAttemptContent.title.length != 0 && account.peerName.length != 0) { + _bestAttemptContent.title = [NSString stringWithFormat:@"%@ → %@", _bestAttemptContent.title, account.peerName]; + } + } + } + + _bestAttemptContent.userInfo = userInfo; + + if (_cancelFetch) { + _cancelFetch(); + _cancelFetch = nil; + } + + if (mediaBoxThumbnailImagePath != nil && tempImagePath != nil && inputFileLocation != nil) { + NSData *data = [NSData dataWithContentsOfFile:mediaBoxThumbnailImagePath]; + if (data != nil) { + NSData *tempData = data; + if (isPng) { + /*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() { + tempData = imageData + }*/ + } + if ([tempData writeToFile:tempImagePath atomically:true]) { + UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil]; + if (attachment != nil) { + _bestAttemptContent.attachments = @[attachment]; + } + } + if (_contentHandler && _bestAttemptContent != nil) { + _contentHandler(_bestAttemptContent); + } + } else { + BuildConfig *buildConfig = [[BuildConfig alloc] initWithBaseAppBundleId:_baseAppBundleId]; + + __weak NotificationService *weakSelf = self; + _cancelFetch = fetchImage(buildConfig, accountInfos.proxy, account, inputFileLocation, fileDatacenterId, ^(NSData * _Nullable data) { + dispatch_async(dispatch_get_main_queue(), ^{ + __strong NotificationService *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + if (strongSelf->_cancelFetch) { + strongSelf->_cancelFetch(); + strongSelf->_cancelFetch = nil; + + if (data != nil) { + [data writeToFile:mediaBoxThumbnailImagePath atomically:true]; + NSData *tempData = data; + if (isPng) { + /*if let image = WebP.convert(fromWebP: data), let imageData = image.pngData() { + tempData = imageData + }*/ + } + if ([tempData writeToFile:tempImagePath atomically:true]) { + UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:tempImagePath] options:nil error:nil]; + if (attachment != nil) { + strongSelf->_bestAttemptContent.attachments = @[attachment]; + } + } + } + + if (strongSelf->_contentHandler && strongSelf->_bestAttemptContent != nil) { + strongSelf->_contentHandler(strongSelf->_bestAttemptContent); + } + } + }); + }); + } + } else { + if (_contentHandler && _bestAttemptContent != nil) { + _contentHandler(_bestAttemptContent); + } + } + } else { + if (_contentHandler && _bestAttemptContent != nil) { + _contentHandler(_bestAttemptContent); + } + } +} + +- (void)serviceExtensionTimeWillExpire { + if (_cancelFetch) { + _cancelFetch(); + _cancelFetch = nil; + } + + if (_contentHandler) { + if(_bestAttemptContent) { + _contentHandler(_bestAttemptContent); + _bestAttemptContent = nil; + } + _contentHandler = nil; + } +} + +@end diff --git a/NotificationService/Serialization.h b/NotificationService/Serialization.h new file mode 100644 index 0000000000..f08453b3f3 --- /dev/null +++ b/NotificationService/Serialization.h @@ -0,0 +1,10 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Serialization : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/NotificationService/Serialization.m b/NotificationService/Serialization.m new file mode 100644 index 0000000000..a10db76cba --- /dev/null +++ b/NotificationService/Serialization.m @@ -0,0 +1,35 @@ +#import "Serialization.h" + +@implementation Serialization + +- (NSUInteger)currentLayer { + return 105; +} + +- (id _Nullable)parseMessage:(NSData * _Nullable)data { + return nil; +} + +- (MTExportAuthorizationResponseParser _Nonnull)exportAuthorization:(int32_t)datacenterId data:(__autoreleasing NSData **)data { + return ^MTExportedAuthorizationData *(NSData *resultData) { + return nil; + }; +} + +- (NSData * _Nonnull)importAuthorization:(int32_t)authId bytes:(NSData * _Nonnull)bytes { + return [NSData data]; +} + +- (MTRequestDatacenterAddressListParser)requestDatacenterAddressWithData:(__autoreleasing NSData **)data { + return ^MTDatacenterAddressListData *(NSData *resultData) { + return nil; + }; +} + +- (MTRequestNoopParser)requestNoop:(__autoreleasing NSData **)data { + return ^id(NSData *resultData) { + return nil; + }; +} + +@end diff --git a/NotificationService/StoredAccountInfos.h b/NotificationService/StoredAccountInfos.h new file mode 100644 index 0000000000..a5fb775e47 --- /dev/null +++ b/NotificationService/StoredAccountInfos.h @@ -0,0 +1,67 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AccountNotificationKey: NSObject + +@property (nonatomic, strong, readonly) NSData *keyId; +@property (nonatomic, strong, readonly) NSData *data; + +@end + +@interface AccountDatacenterKey: NSObject + +@property (nonatomic, readonly) int64_t keyId; +@property (nonatomic, strong, readonly) NSData *data; + +@end + +@interface AccountDatacenterAddress: NSObject + +@property (nonatomic, strong, readonly) NSString *host; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, readonly) bool isMedia; +@property (nonatomic, strong, readonly) NSData * _Nullable secret; + +@end + +@interface AccountDatacenterInfo: NSObject + +@property (nonatomic, strong, readonly) AccountDatacenterKey *masterKey; +@property (nonatomic, strong, readonly) NSArray *addressList; + +@end + +@interface AccountProxyConnection: NSObject + +@property (nonatomic, strong, readonly) NSString *host; +@property (nonatomic, readonly) int32_t port; +@property (nonatomic, strong) NSString * _Nullable username; +@property (nonatomic, strong) NSString * _Nullable password; +@property (nonatomic, strong) NSData * _Nullable secret; + +@end + +@interface StoredAccountInfo : NSObject + +@property (nonatomic, readonly) int64_t accountId; +@property (nonatomic, readonly) int32_t primaryId; +@property (nonatomic, readonly) bool isTestingEnvironment; +@property (nonatomic, strong, readonly) NSString *peerName; +@property (nonatomic, strong, readonly) NSDictionary *datacenters; +@property (nonatomic, strong, readonly) AccountNotificationKey *notificationKey; + +@end + +@interface StoredAccountInfos : NSObject + +@property (nonatomic, strong, readonly) AccountProxyConnection * _Nullable proxy; +@property (nonatomic, strong, readonly) NSArray *accounts; + ++ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path; + +@end + +NSDictionary * _Nullable decryptedNotificationPayload(NSArray *accounts, NSData *data, int *selectedAccountIndex); + +NS_ASSUME_NONNULL_END diff --git a/NotificationService/StoredAccountInfos.m b/NotificationService/StoredAccountInfos.m new file mode 100644 index 0000000000..bdfeb05095 --- /dev/null +++ b/NotificationService/StoredAccountInfos.m @@ -0,0 +1,412 @@ +#import "StoredAccountInfos.h" + +#import +#import + +@implementation AccountNotificationKey + +- (instancetype)initWithKeyId:(NSData *)keyId data:(NSData *)data { + self = [super init]; + if (self != nil) { + _keyId = keyId; + _data = data; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSString *keyIdString = dict[@"id"]; + NSData *keyId = nil; + if ([keyIdString isKindOfClass:[NSString class]]) { + keyId = [[NSData alloc] initWithBase64EncodedString:keyIdString options:0]; + } + if (keyId == nil) { + return nil; + } + + NSString *dataString = dict[@"data"]; + NSData *data = nil; + if ([dataString isKindOfClass:[NSString class]]) { + data = [[NSData alloc] initWithBase64EncodedString:dataString options:0]; + } + if (data == nil) { + return nil; + } + + return [[AccountNotificationKey alloc] initWithKeyId:keyId data:data]; +} + +@end + +@implementation AccountDatacenterKey + +- (instancetype)initWithKeyId:(int64_t)keyId data:(NSData *)data { + self = [super init]; + if (self != nil) { + _keyId = keyId; + _data = data; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSNumber *keyIdNumber = dict[@"id"]; + if (![keyIdNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + int64_t keyId = [keyIdNumber longLongValue]; + + NSString *dataString = dict[@"data"]; + NSData *data = nil; + if ([dataString isKindOfClass:[NSString class]]) { + data = [[NSData alloc] initWithBase64EncodedString:dataString options:0]; + } + if (data == nil) { + return nil; + } + + return [[AccountDatacenterKey alloc] initWithKeyId:keyId data:data]; +} + +@end + +@implementation AccountDatacenterAddress + +- (instancetype)initWithHost:(NSString *)host port:(int32_t)port isMedia:(bool)isMedia secret:(NSData * _Nullable)secret { + self = [super init]; + if (self != nil) { + _host = host; + _port = port; + _isMedia = isMedia; + _secret = secret; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSString *hostString = dict[@"host"]; + if (![hostString isKindOfClass:[NSString class]]) { + return nil; + } + NSString *host = hostString; + + NSNumber *portNumber = dict[@"port"]; + if (![portNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + int32_t port = [portNumber intValue]; + + NSNumber *isMediaNumber = dict[@"isMedia"]; + if (![isMediaNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + bool isMedia = [isMediaNumber intValue] != 0; + + NSString *secretString = dict[@"secret"]; + NSData *secret = nil; + if ([secretString isKindOfClass:[NSString class]]) { + secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0]; + } + + return [[AccountDatacenterAddress alloc] initWithHost:host port:port isMedia:isMedia secret:secret]; +} + +@end + +@implementation AccountDatacenterInfo + +- (instancetype)initWithMasterKey:(AccountDatacenterKey *)masterKey addressList:(NSArray *)addressList { + self = [super init]; + if (self != nil) { + _masterKey = masterKey; + _addressList = addressList; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSDictionary *masterKeyDict = dict[@"masterKey"]; + if (![masterKeyDict isKindOfClass:[NSDictionary class]]) { + return nil; + } + AccountDatacenterKey *masterKey = [AccountDatacenterKey parse:masterKeyDict]; + if (masterKey == nil) { + return nil; + } + + NSArray *addressListArray = dict[@"addressList"]; + if (![addressListArray isKindOfClass:[NSArray class]]) { + return nil; + } + + NSMutableArray *addressList = [[NSMutableArray alloc] init]; + for (NSDictionary *addressListItem in addressListArray) { + if (![addressListItem isKindOfClass:[NSDictionary class]]) { + return nil; + } + AccountDatacenterAddress *address = [AccountDatacenterAddress parse:addressListItem]; + if (address == nil) { + return nil; + } + [addressList addObject:address]; + } + + return [[AccountDatacenterInfo alloc] initWithMasterKey:masterKey addressList:addressList]; +} + +@end + +@implementation AccountProxyConnection + +- (instancetype)initWithHost:(NSString *)host port:(int32_t)port username:(NSString * _Nullable)username password:(NSString * _Nullable)password secret:(NSData * _Nullable)secret { + self = [super init]; + if (self != nil) { + _host = host; + _port = port; + username = _username; + password = _password; + secret = _secret; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSString *hostString = dict[@"host"]; + if (![hostString isKindOfClass:[NSString class]]) { + return nil; + } + NSString *host = hostString; + + NSNumber *portNumber = dict[@"port"]; + if (![portNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + + int32_t port = [portNumber intValue]; + + NSString *usernameString = dict[@"username"]; + NSString *username = nil; + if ([usernameString isKindOfClass:[NSString class]]) { + username = usernameString; + } + + NSString *passwordString = dict[@"password"]; + NSString *password = nil; + if ([passwordString isKindOfClass:[NSString class]]) { + password = passwordString; + } + + NSString *secretString = dict[@"secret"]; + NSData *secret = nil; + if ([secretString isKindOfClass:[NSString class]]) { + secret = [[NSData alloc] initWithBase64EncodedString:secretString options:0]; + } + + return [[AccountProxyConnection alloc] initWithHost:host port:port username:username password:password secret:secret]; +} + +@end + +@implementation StoredAccountInfo + +- (instancetype)initWithAccountId:(int64_t)accountId primaryId:(int32_t)primaryId isTestingEnvironment:(bool)isTestingEnvironment peerName:(NSString *)peerName datacenters:(NSDictionary *)datacenters notificationKey:(AccountNotificationKey *)notificationKey { + self = [super init]; + if (self != nil) { + _accountId = accountId; + _primaryId = primaryId; + _isTestingEnvironment = isTestingEnvironment; + _peerName = peerName; + _datacenters = datacenters; + _notificationKey = notificationKey; + } + return self; +} + ++ (instancetype _Nullable)parse:(NSDictionary *)dict { + NSNumber *idNumber = dict[@"id"]; + if (![idNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + int64_t accountId = [idNumber longLongValue]; + + NSNumber *primaryIdNumber = dict[@"primaryId"]; + if (![primaryIdNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + + int32_t primaryId = [primaryIdNumber intValue]; + + NSNumber *isTestingEnvironmentNumber = dict[@"isTestingEnvironment"]; + if (![isTestingEnvironmentNumber isKindOfClass:[NSNumber class]]) { + return nil; + } + + bool isTestingEnvironment = [isTestingEnvironmentNumber intValue] != 0; + + NSString *peerNameString = dict[@"peerName"]; + if (![peerNameString isKindOfClass:[NSString class]]) { + return nil; + } + + NSString *peerName = peerNameString; + + NSArray *datacentersArray = dict[@"datacenters"]; + if (![datacentersArray isKindOfClass:[NSArray class]]) { + return nil; + } + + NSMutableDictionary *datacenters = [[NSMutableDictionary alloc] init]; + + for (NSInteger i = 0; i < datacentersArray.count; i += 2) { + NSNumber *datacenterKey = datacentersArray[i]; + NSDictionary *datacenterData = datacentersArray[i + 1]; + + if (![datacenterKey isKindOfClass:[NSNumber class]]) { + return nil; + } + if (![datacenterData isKindOfClass:[NSDictionary class]]) { + return nil; + } + AccountDatacenterInfo *parsedDatacenter = [AccountDatacenterInfo parse:datacenterData]; + if (parsedDatacenter != nil) { + datacenters[datacenterKey] = parsedDatacenter; + } + } + + NSDictionary *notificationKeyDict = dict[@"notificationKey"]; + if (![notificationKeyDict isKindOfClass:[NSDictionary class]]) { + return nil; + } + + AccountNotificationKey *notificationKey = [AccountNotificationKey parse:notificationKeyDict]; + if (notificationKey == nil) { + return nil; + } + + return [[StoredAccountInfo alloc] initWithAccountId:accountId primaryId:primaryId isTestingEnvironment:isTestingEnvironment peerName:peerName datacenters:datacenters notificationKey:notificationKey]; +} + +@end + +@implementation StoredAccountInfos + +- (instancetype)initWithProxy:(AccountProxyConnection * _Nullable)proxy accounts:(NSArray *)accounts { + self = [super init]; + if (self != nil) { + _proxy = proxy; + _accounts = accounts; + } + return self; +} + ++ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path { + NSData *data = [NSData dataWithContentsOfFile:path]; + if (data == nil) { + return nil; + } + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + if (![dict isKindOfClass:[NSDictionary class]]) { + return nil; + } + + AccountProxyConnection * _Nullable proxy = nil; + NSDictionary *proxyDict = dict[@"proxy"]; + if ([proxyDict isKindOfClass:[NSDictionary class]]) { + proxy = [AccountProxyConnection parse:proxyDict]; + } + + NSMutableArray *accounts = [[NSMutableArray alloc] init]; + + NSArray *accountsObject = dict[@"accounts"]; + if ([accountsObject isKindOfClass:[NSArray class]]) { + for (NSDictionary *object in accountsObject) { + if ([object isKindOfClass:[NSDictionary class]]) { + StoredAccountInfo *account = [StoredAccountInfo parse:object]; + if (account != nil) { + [accounts addObject:account]; + } + } + } + } + + return [[StoredAccountInfos alloc] initWithProxy:proxy accounts:accounts];; +} + +@end + +static NSData *sha256Digest(NSData *data) { + uint8_t digest[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(data.bytes, (CC_LONG)data.length, digest); + + return [[NSData alloc] initWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; +} + +static NSData *concatData(NSData *data1, NSData *data2) { + NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length]; + [data appendData:data1]; + [data appendData:data2]; + return data; +} + +static NSData *concatData3(NSData *data1, NSData *data2, NSData *data3) { + NSMutableData *data = [[NSMutableData alloc] initWithCapacity:data1.length + data2.length + data3.length]; + [data appendData:data1]; + [data appendData:data2]; + [data appendData:data3]; + return data; +} + +NSDictionary * _Nullable decryptedNotificationPayload(NSArray *accounts, NSData *data, int *selectedAccountIndex) { + if (data.length < 8 + 16) { + return nil; + } + + int accountIndex = -1; + for (StoredAccountInfo *account in accounts) { + accountIndex += 1; + + AccountNotificationKey *notificationKey = account.notificationKey; + if (![[data subdataWithRange:NSMakeRange(0, 8)] isEqualToData:notificationKey.keyId]) { + continue; + } + + int x = 8; + NSData *msgKey = [data subdataWithRange:NSMakeRange(8, 16)]; + NSData *rawData = [data subdataWithRange:NSMakeRange(8 + 16, data.length - (8 + 16))]; + + NSData *sha256_a = sha256Digest(concatData(msgKey, [notificationKey.data subdataWithRange:NSMakeRange(x, 36)])); + NSData *sha256_b = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(40 + x, 36)], msgKey)); + NSData *aesKey = concatData3([sha256_a subdataWithRange:NSMakeRange(0, 8)], [sha256_b subdataWithRange:NSMakeRange(8, 16)], [sha256_a subdataWithRange:NSMakeRange(24, 8)]); + NSData *aesIv = concatData3([sha256_b subdataWithRange:NSMakeRange(0, 8)], [sha256_a subdataWithRange:NSMakeRange(8, 16)], [sha256_b subdataWithRange:NSMakeRange(24, 8)]); + + NSData *decryptedData = MTAesDecrypt(rawData, aesKey, aesIv); + if (decryptedData.length <= 4) { + return nil; + } + + int32_t dataLength = 0; + [decryptedData getBytes:&dataLength range:NSMakeRange(0, 4)]; + + if (dataLength < 0 || dataLength > decryptedData.length - 4) { + return nil; + } + + NSData *checkMsgKeyLarge = sha256Digest(concatData([notificationKey.data subdataWithRange:NSMakeRange(88 + x, 32)], decryptedData)); + NSData *checkMsgKey = [checkMsgKeyLarge subdataWithRange:NSMakeRange(8, 16)]; + + if (![checkMsgKey isEqualToData:msgKey]) { + return nil; + } + + NSData *contentData = [decryptedData subdataWithRange:NSMakeRange(4, dataLength)]; + NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:contentData options:0 error:nil]; + if (![dict isKindOfClass:[NSDictionary class]]) { + return nil; + } + if (selectedAccountIndex != nil) { + *selectedAccountIndex = accountIndex; + } + return dict; + } + return nil; +} diff --git a/Telegram-iOS.xcodeproj/project.pbxproj b/Telegram-iOS.xcodeproj/project.pbxproj index 2f2ccce4f4..889e67f3ad 100644 --- a/Telegram-iOS.xcodeproj/project.pbxproj +++ b/Telegram-iOS.xcodeproj/project.pbxproj @@ -226,13 +226,10 @@ D008185622B579A1008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185522B579A1008A895F /* BuildConfig.framework */; }; D008185822B579AD008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185722B579AD008A895F /* BuildConfig.framework */; }; D00818A522B58CCB008A895F /* WatchCommonWatch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D00818A422B58CCB008A895F /* WatchCommonWatch.framework */; }; - D00818CF22B595DB008A895F /* LightweightAccountData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D00818CE22B595DB008A895F /* LightweightAccountData.framework */; }; D00859A91B28189D00EAF753 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D00859A81B28189D00EAF753 /* Images.xcassets */; }; D00859AC1B28189D00EAF753 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D00859AA1B28189D00EAF753 /* LaunchScreen.xib */; }; D00ED75A1FE94630001F38BD /* AppIntentVocabulary.plist in Resources */ = {isa = PBXBuildFile; fileRef = D00ED7581FE94630001F38BD /* AppIntentVocabulary.plist */; }; D00ED75D1FE95287001F38BD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D00ED75B1FE95287001F38BD /* InfoPlist.strings */; }; - D015E011225CCEB300CB9E8A /* ReadBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E010225CCEB300CB9E8A /* ReadBuffer.swift */; }; - D015E01F225CDF5100CB9E8A /* Api0.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015E01E225CDF5000CB9E8A /* Api0.swift */; }; D015E04D225D2D8F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; D015E050225D303F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; }; D015E051225D303F00CB9E8A /* WebP.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -302,7 +299,6 @@ D06706611D51185400DED3E3 /* TelegramCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D06706601D51185400DED3E3 /* TelegramCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D06706621D5118F500DED3E3 /* TelegramCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D06706601D51185400DED3E3 /* TelegramCore.framework */; }; D073E52021FF7CE900742DDD /* Crypto.m in Sources */ = {isa = PBXBuildFile; fileRef = D073E51F21FF7CE900742DDD /* Crypto.m */; }; - D073E52222003E1E00742DDD /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D073E52122003E1E00742DDD /* Data.swift */; }; D08410501FABDD54008FFE92 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08410511FABDD54008FFE92 /* MtProtoKitDynamic.framework */; }; D08611B21F5711080047111E /* HockeySDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D01A47541F4DBED700383CC1 /* HockeySDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D08984FE2118B3F100918162 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D08984FD2118B3F100918162 /* MtProtoKitDynamic.framework */; }; @@ -354,6 +350,12 @@ D0B4AF8F1EC122A700D51FF6 /* TelegramUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0400ED81D5B8F97007931CE /* TelegramUI.framework */; }; D0B4AF901EC122A700D51FF6 /* TelegramUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0400ED81D5B8F97007931CE /* TelegramUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D0B844601DACF561005F29E1 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D0B8445F1DACF561005F29E1 /* libc++.tbd */; }; + D0BAAA1823100B7A00AFC473 /* Api.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1623100B7A00AFC473 /* Api.m */; }; + D0BAAA1B23100C2700AFC473 /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1A23100C2700AFC473 /* NotificationService.m */; }; + D0BAAA1E2310117200AFC473 /* StoredAccountInfos.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */; }; + D0BAAA21231026BC00AFC473 /* Attachments.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA20231026BC00AFC473 /* Attachments.m */; }; + D0BAAA242310302300AFC473 /* FetchImage.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA232310302300AFC473 /* FetchImage.m */; }; + D0BAAA272310326200AFC473 /* Serialization.m in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA262310326200AFC473 /* Serialization.m */; }; D0C2DFF81CC4D1BA0044FF83 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C2DFF71CC4D1BA0044FF83 /* MobileCoreServices.framework */; }; D0CAD6A421C03BEB001E3055 /* FFMpeg.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAD6A321C03BEB001E3055 /* FFMpeg.framework */; }; D0CAD6A521C03BEB001E3055 /* FFMpeg.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAD6A321C03BEB001E3055 /* FFMpeg.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -389,7 +391,6 @@ D0D17E8A1CAAD66600C4750B /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0D17E891CAAD66600C4750B /* Accelerate.framework */; }; D0D268791D79A70A00C422DA /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D268781D79A70A00C422DA /* IntentHandler.swift */; }; D0D2688E1D79A70B00C422DA /* SiriIntents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0D268761D79A70A00C422DA /* SiriIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - D0E2CE642227F0680084E3DD /* ManagedFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E2CE632227F0680084E3DD /* ManagedFile.swift */; }; D0E8B8AD2044496C00605593 /* voip_connecting.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8A82044496B00605593 /* voip_connecting.mp3 */; }; D0E8B8AE2044496C00605593 /* voip_end.caf in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8A92044496C00605593 /* voip_end.caf */; }; D0E8B8AF2044496C00605593 /* voip_fail.caf in Resources */ = {isa = PBXBuildFile; fileRef = D0E8B8AA2044496C00605593 /* voip_fail.caf */; }; @@ -399,10 +400,6 @@ D0E8C2E02285EA6A009F26E8 /* BlackIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D0E8C2DF2285EA6A009F26E8 /* BlackIcon@3x.png */; }; D0ECCB7F1FE9C38500609802 /* Telegram_iOS_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ECCB7E1FE9C38500609802 /* Telegram_iOS_UITests.swift */; }; D0ECCB8A1FE9C4AC00609802 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ECCB891FE9C4AC00609802 /* SnapshotHelper.swift */; }; - D0ED633A21FF3EDF001D4648 /* AccountData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED633921FF3EDF001D4648 /* AccountData.swift */; }; - D0ED633D21FF4580001D4648 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0400EE41D5B912E007931CE /* NotificationService.swift */; }; - D0ED633F21FF46E4001D4648 /* ImageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED633E21FF46E4001D4648 /* ImageData.swift */; }; - D0ED634121FF4786001D4648 /* Serialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED634021FF4786001D4648 /* Serialization.swift */; }; D0F575132083B96B00F1C1E1 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0F575122083B96B00F1C1E1 /* CloudKit.framework */; }; D0FC1948201D2DA800FEDBB2 /* SFCompactRounded-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = D0FC1947201D2DA700FEDBB2 /* SFCompactRounded-Semibold.otf */; }; /* End PBXBuildFile section */ @@ -1060,6 +1057,18 @@ D0B844591DACF507005F29E1 /* HockeySDK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HockeySDK.framework; path = "third-party/HockeySDK.framework"; sourceTree = ""; }; D0B8445A1DACF507005F29E1 /* HockeySDKResources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = HockeySDKResources.bundle; path = "third-party/HockeySDKResources.bundle"; sourceTree = ""; }; D0B8445F1DACF561005F29E1 /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + D0BAAA1623100B7A00AFC473 /* Api.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Api.m; sourceTree = ""; }; + D0BAAA1723100B7A00AFC473 /* Api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Api.h; sourceTree = ""; }; + D0BAAA1923100C2700AFC473 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; + D0BAAA1A23100C2700AFC473 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; + D0BAAA1C2310117200AFC473 /* StoredAccountInfos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoredAccountInfos.h; sourceTree = ""; }; + D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoredAccountInfos.m; sourceTree = ""; }; + D0BAAA1F231026BC00AFC473 /* Attachments.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Attachments.h; sourceTree = ""; }; + D0BAAA20231026BC00AFC473 /* Attachments.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Attachments.m; sourceTree = ""; }; + D0BAAA222310302300AFC473 /* FetchImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FetchImage.h; sourceTree = ""; }; + D0BAAA232310302300AFC473 /* FetchImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FetchImage.m; sourceTree = ""; }; + D0BAAA252310326200AFC473 /* Serialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Serialization.h; sourceTree = ""; }; + D0BAAA262310326200AFC473 /* Serialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Serialization.m; sourceTree = ""; }; D0C2DFF51CC4D1B20044FF83 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; D0C2DFF71CC4D1BA0044FF83 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; D0C2DFF91CC4D1C90044FF83 /* QuickLook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLook.framework; path = System/Library/Frameworks/QuickLook.framework; sourceTree = SDKROOT; }; @@ -1148,7 +1157,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D00818CF22B595DB008A895F /* LightweightAccountData.framework in Frameworks */, D008185822B579AD008A895F /* BuildConfig.framework in Frameworks */, D015E04D225D2D8F00CB9E8A /* WebP.framework in Frameworks */, D0CCD61D222EFFB000EE1E08 /* MtProtoKitDynamic.framework in Frameworks */, @@ -2157,6 +2165,18 @@ D0400EE31D5B912E007931CE /* NotificationService */ = { isa = PBXGroup; children = ( + D0BAAA1723100B7A00AFC473 /* Api.h */, + D0BAAA1623100B7A00AFC473 /* Api.m */, + D0BAAA1923100C2700AFC473 /* NotificationService.h */, + D0BAAA1A23100C2700AFC473 /* NotificationService.m */, + D0BAAA1C2310117200AFC473 /* StoredAccountInfos.h */, + D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */, + D0BAAA1F231026BC00AFC473 /* Attachments.h */, + D0BAAA20231026BC00AFC473 /* Attachments.m */, + D0BAAA222310302300AFC473 /* FetchImage.h */, + D0BAAA232310302300AFC473 /* FetchImage.m */, + D0BAAA252310326200AFC473 /* Serialization.h */, + D0BAAA262310326200AFC473 /* Serialization.m */, D015E01E225CDF5000CB9E8A /* Api0.swift */, D000CAC221FB6E170011B15D /* NotificationService-AppStore.entitlements */, D000CAC321FB6E170011B15D /* NotificationService-AppStoreLLC.entitlements */, @@ -3175,15 +3195,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D073E52222003E1E00742DDD /* Data.swift in Sources */, - D0E2CE642227F0680084E3DD /* ManagedFile.swift in Sources */, - D0ED633D21FF4580001D4648 /* NotificationService.swift in Sources */, - D015E01F225CDF5100CB9E8A /* Api0.swift in Sources */, - D0ED633A21FF3EDF001D4648 /* AccountData.swift in Sources */, - D0ED634121FF4786001D4648 /* Serialization.swift in Sources */, + D0BAAA242310302300AFC473 /* FetchImage.m in Sources */, + D0BAAA1E2310117200AFC473 /* StoredAccountInfos.m in Sources */, + D0BAAA1823100B7A00AFC473 /* Api.m in Sources */, D073E52021FF7CE900742DDD /* Crypto.m in Sources */, - D0ED633F21FF46E4001D4648 /* ImageData.swift in Sources */, - D015E011225CCEB300CB9E8A /* ReadBuffer.swift in Sources */, + D0BAAA1B23100C2700AFC473 /* NotificationService.m in Sources */, + D0BAAA21231026BC00AFC473 /* Attachments.m in Sources */, + D0BAAA272310326200AFC473 /* Serialization.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Telegram-iOS.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme b/Telegram-iOS.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme index 837168a96b..85283e68b3 100644 --- a/Telegram-iOS.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme +++ b/Telegram-iOS.xcodeproj/xcshareddata/xcschemes/NotificationService.xcscheme @@ -57,7 +57,7 @@ Void) { for (peerId, messageIds) in self.messageIdsByPeerId(messageIds) { var operations: [MessageHistoryIndexOperation] = [] for id in messageIds { self.messageHistoryIndexTable.removeMessage(id, operations: &operations) } + for operation in operations { + if case let .Remove(index) = operation { + if let message = self.getMessage(index) { + for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) { + forEachMedia(media) + } + } + } + } self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations) } } - func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) { + func removeMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) { var operations: [MessageHistoryIndexOperation] = [] self.messageHistoryIndexTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operations: &operations) + for operation in operations { + if case let .Remove(index) = operation { + if let message = self.getMessage(index) { + for media in self.renderMessageMedia(referencedMedia: message.referencedMedia, embeddedMediaData: message.embeddedMediaData) { + forEachMedia(media) + } + } + } + } self.processIndexOperations(peerId, operations: operations, processedOperationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations) } - func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) { + func clearHistory(peerId: PeerId, namespaces: MessageIdNamespaces, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) { let indices = self.allMessageIndices(peerId: peerId).filter { namespaces.contains($0.id.namespace) } - self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations) + self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia) } - func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) { + func removeAllMessagesWithAuthor(peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation], forEachMedia: (Media) -> Void) { let indices = self.allIndicesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace) - self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations) + self.removeMessages(indices.map { $0.id }, operationsByPeerId: &operationsByPeerId, updatedMedia: &updatedMedia, unsentMessageOperations: &unsentMessageOperations, updatedPeerReadStateOperations: &updatedPeerReadStateOperations, globalTagsOperations: &globalTagsOperations, pendingActionsOperations: &pendingActionsOperations, updatedMessageActionsSummaries: &updatedMessageActionsSummaries, updatedMessageTagSummaries: &updatedMessageTagSummaries, invalidateMessageTagSummaries: &invalidateMessageTagSummaries, localTagsOperations: &localTagsOperations, forEachMedia: forEachMedia) } func updateMessage(_ id: MessageId, message: StoreMessage, operationsByPeerId: inout [PeerId: [MessageHistoryOperation]], updatedMedia: inout [MediaId: Media?], unsentMessageOperations: inout [IntermediateMessageHistoryUnsentOperation], updatedPeerReadStateOperations: inout [PeerId: PeerReadStateSynchronizationOperation?], globalTagsOperations: inout [GlobalMessageHistoryTagsOperation], pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32], updatedMessageTagSummaries: inout [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], invalidateMessageTagSummaries: inout [InvalidatedMessageHistoryTagsSummaryEntryOperation], localTagsOperations: inout [IntermediateMessageHistoryLocalTagsOperation]) { diff --git a/submodules/Postbox/Postbox/Postbox.swift b/submodules/Postbox/Postbox/Postbox.swift index 653c023d84..5cb5a0d31e 100644 --- a/submodules/Postbox/Postbox/Postbox.swift +++ b/submodules/Postbox/Postbox/Postbox.swift @@ -72,28 +72,28 @@ public final class Transaction { self.postbox?.replaceChatListHole(groupId: groupId, index: index, hole: hole) } - public func deleteMessages(_ messageIds: [MessageId]) { + public func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) { assert(!self.disposed) - self.postbox?.deleteMessages(messageIds) + self.postbox?.deleteMessages(messageIds, forEachMedia: forEachMedia) } - public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) { + public func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) { assert(!self.disposed) - self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId) + self.postbox?.deleteMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, forEachMedia: forEachMedia) } public func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace? = nil, _ f: (Message) -> Bool) { self.postbox?.withAllMessages(peerId: peerId, namespace: namespace, f) } - public func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces) { + public func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) { assert(!self.disposed) - self.postbox?.clearHistory(peerId, namespaces: namespaces) + self.postbox?.clearHistory(peerId, namespaces: namespaces, forEachMedia: forEachMedia) } - public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) { + public func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { assert(!self.disposed) - self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace) + self.postbox?.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: forEachMedia) } public func messageIdsForGlobalIds(_ ids: [Int32]) -> [MessageId] { @@ -105,11 +105,11 @@ public final class Transaction { } } - public func deleteMessagesWithGlobalIds(_ ids: [Int32]) { + public func deleteMessagesWithGlobalIds(_ ids: [Int32], forEachMedia: (Media) -> Void) { assert(!self.disposed) if let postbox = self.postbox { let messageIds = postbox.messageIdsForGlobalIds(ids) - postbox.deleteMessages(messageIds) + postbox.deleteMessages(messageIds, forEachMedia: forEachMedia) } } @@ -1499,12 +1499,12 @@ public final class Postbox { self.chatListTable.replaceHole(groupId: groupId, index: index, hole: hole, operations: &self.currentChatListOperations) } - fileprivate func deleteMessages(_ messageIds: [MessageId]) { - self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations) + fileprivate func deleteMessages(_ messageIds: [MessageId], forEachMedia: (Media) -> Void) { + self.messageHistoryTable.removeMessages(messageIds, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia) } - fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id) { - self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations) + fileprivate func deleteMessagesInRange(peerId: PeerId, namespace: MessageId.Namespace, minId: MessageId.Id, maxId: MessageId.Id, forEachMedia: (Media) -> Void) { + self.messageHistoryTable.removeMessagesInRange(peerId: peerId, namespace: namespace, minId: minId, maxId: maxId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia) } fileprivate func withAllMessages(peerId: PeerId, namespace: MessageId.Namespace?, _ f: (Message) -> Bool) { @@ -1519,15 +1519,15 @@ public final class Postbox { } } - fileprivate func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces) { - self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations) + fileprivate func clearHistory(_ peerId: PeerId, namespaces: MessageIdNamespaces, forEachMedia: (Media) -> Void) { + self.messageHistoryTable.clearHistory(peerId: peerId, namespaces: namespaces, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia) for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) where namespaces.contains(namespace) { self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations) } } - fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) { - self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations) + fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace, forEachMedia: (Media) -> Void) { + self.messageHistoryTable.removeAllMessagesWithAuthor(peerId: peerId, authorId: authorId, namespace: namespace, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: ¤tUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations, forEachMedia: forEachMedia) } fileprivate func resetIncomingReadStates(_ states: [PeerId: [MessageId.Namespace: PeerReadState]]) { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index f0753116f6..d86a58791b 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -86,7 +86,9 @@ public final class ReactionContextNode: ASDisplayNode { private let smallCircleNode: ASImageNode private let smallCircleShadowNode: ASImageNode + private let contentContainer: ASDisplayNode private var itemNodes: [ReactionNode] = [] + private let disclosureButton: HighlightTrackingButtonNode private var isExpanded: Bool = false private var highlightedReaction: String? @@ -139,6 +141,28 @@ public final class ReactionContextNode: ASDisplayNode { self.largeCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: largeCircleSize, shadowBlur: shadowBlur) self.smallCircleShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: smallCircleSize, shadowBlur: shadowBlur) + self.contentContainer = ASDisplayNode() + self.contentContainer.clipsToBounds = true + + self.disclosureButton = HighlightTrackingButtonNode() + self.disclosureButton.hitTestSlop = UIEdgeInsets(top: -6.0, left: -6.0, bottom: -6.0, right: -6.0) + let buttonImage = generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(theme.contextMenu.dimColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setStrokeColor(UIColor.clear.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setLineJoin(.round) + context.beginPath() + context.move(to: CGPoint(x: 8.0, y: size.height / 2.0 + 3.0)) + context.addLine(to: CGPoint(x: size.width / 2.0, y: 11.0)) + context.addLine(to: CGPoint(x: size.width - 8.0, y: size.height / 2.0 + 3.0)) + context.strokePath() + }) + self.disclosureButton.setImage(buttonImage, for: []) + super.init() self.addSubnode(self.smallCircleShadowNode) @@ -150,10 +174,23 @@ public final class ReactionContextNode: ASDisplayNode { self.backgroundContainerNode.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundContainerNode) + self.contentContainer.addSubnode(self.disclosureButton) + self.itemNodes = self.items.map { item in return ReactionNode(account: account, theme: theme, reaction: .reaction(value: item.value, text: item.text, path: item.path), maximizedReactionSize: 30.0 - 18.0, loadFirstFrame: true) } - self.itemNodes.forEach(self.addSubnode) + self.itemNodes.forEach(self.contentContainer.addSubnode) + + self.addSubnode(self.contentContainer) + + self.disclosureButton.addTarget(self, action: #selector(self.disclosurePressed), forControlEvents: .touchUpInside) + self.disclosureButton.highligthedChanged = { [weak self] highlighted in + if highlighted { + self?.disclosureButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.15, removeOnCompletion: false) + } else { + self?.disclosureButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.25) + } + } } override public func didLoad() { @@ -193,18 +230,27 @@ public final class ReactionContextNode: ASDisplayNode { let minimizedItemSize: CGFloat = 30.0 let maximizedItemSize: CGFloat = 30.0 - 18.0 let shadowBlur: CGFloat = 5.0 - let rowHeight: CGFloat = 52.0 + let verticalInset: CGFloat = 11.0 + let rowHeight: CGFloat = 30.0 + let rowSpacing: CGFloat = itemSpacing - let columnCount = min(7, self.items.count) + let columnCount = min(6, self.items.count) let contentWidth = CGFloat(columnCount) * minimizedItemSize + (CGFloat(columnCount) - 1.0) * itemSpacing + sideInset * 2.0 let rowCount = self.items.count / columnCount + (self.items.count % columnCount == 0 ? 0 : 1) - let contentHeight = rowHeight * CGFloat(rowCount) + + let expandedRowCount = self.isExpanded ? rowCount : 1 + + let contentHeight = verticalInset * 2.0 + rowHeight * CGFloat(expandedRowCount) + CGFloat(expandedRowCount - 1) * rowSpacing let (backgroundFrame, isLeftAligned) = self.calculateBackgroundFrame(containerSize: size, insets: insets, anchorRect: anchorRect, contentSize: CGSize(width: contentWidth, height: contentHeight)) + transition.updateFrame(node: self.contentContainer, frame: backgroundFrame) + for i in 0 ..< self.items.count { - let row = CGFloat(i / columnCount) - let column = CGFloat(i % columnCount) + let rowIndex = i / columnCount + let columnIndex = i % columnCount + let row = CGFloat(rowIndex) + let column = CGFloat(columnIndex) var reactionValue: String? switch self.itemNodes[i].reaction { @@ -224,18 +270,43 @@ public final class ReactionContextNode: ASDisplayNode { itemSize = updatedSize } - transition.updateFrame(node: self.itemNodes[i], frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: backgroundFrame.minY + row * rowHeight + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize)), beginWithCurrentState: true) + let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (minimizedItemSize + itemSpacing) - itemOffset, y: verticalInset + row * (rowHeight + rowSpacing) + floor((rowHeight - minimizedItemSize) / 2.0) - itemOffset), size: CGSize(width: itemSize, height: itemSize)) + transition.updateFrame(node: self.itemNodes[i], frame: itemFrame, beginWithCurrentState: true) self.itemNodes[i].updateLayout(size: CGSize(width: itemSize, height: itemSize), scale: itemSize / (maximizedItemSize + 18.0), transition: transition, displayText: false) self.itemNodes[i].updateIsAnimating(false, animated: false) - if row != 0 { + if rowIndex != 0 || columnIndex == columnCount - 1 { if self.isExpanded { - self.itemNodes[i].alpha = 1.0 + if self.itemNodes[i].alpha.isZero { + self.itemNodes[i].alpha = 1.0 + if transition.isAnimated { + let delayOffset: Double = 1.0 - Double(columnIndex) / Double(columnCount - 1) + self.itemNodes[i].layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4 + delayOffset * 0.32, initialVelocity: 0.0, damping: 95.0) + self.itemNodes[i].layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.05) + } + } } else { self.itemNodes[i].alpha = 0.0 } } else { self.itemNodes[i].alpha = 1.0 } + + if rowIndex == 0 && columnIndex == columnCount - 1 { + transition.updateFrame(node: self.disclosureButton, frame: itemFrame) + if self.isExpanded { + if self.disclosureButton.alpha.isEqual(to: 1.0) { + self.disclosureButton.alpha = 0.0 + if transition.isAnimated { + self.disclosureButton.layer.animateScale(from: 0.8, to: 0.1, duration: 0.2, removeOnCompletion: false) + self.disclosureButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.disclosureButton.layer.removeAnimation(forKey: "scale") + }) + } + } + } else { + self.disclosureButton.alpha = 1.0 + } + } } let isInOverflow = backgroundFrame.maxY > anchorRect.minY @@ -254,10 +325,10 @@ public final class ReactionContextNode: ASDisplayNode { let largeCircleFrame: CGRect let smallCircleFrame: CGRect if isLeftAligned { - largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 22.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) + largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.maxX + 16.0 - rowHeight + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.maxX - 3.0, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) } else { - largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 24.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) + largeCircleFrame = CGRect(origin: CGPoint(x: anchorRect.minX - 18.0 + floor((rowHeight - largeCircleSize) / 2.0), y: backgroundFrame.maxY - largeCircleSize / 2.0), size: CGSize(width: largeCircleSize, height: largeCircleSize)) smallCircleFrame = CGRect(origin: CGPoint(x: largeCircleFrame.minX + 3.0 - smallCircleSize, y: largeCircleFrame.maxY + 2.0), size: CGSize(width: smallCircleSize, height: smallCircleSize)) } @@ -289,15 +360,16 @@ public final class ReactionContextNode: ASDisplayNode { } public func animateOut(to targetAnchorRect: CGRect?, animatingOutToReaction: Bool) { + self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) for itemNode in self.itemNodes { - self.backgroundNode.layer.animateAlpha(from: self.backgroundNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.backgroundShadowNode.layer.animateAlpha(from: self.backgroundShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.largeCircleNode.layer.animateAlpha(from: self.largeCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.largeCircleShadowNode.layer.animateAlpha(from: self.largeCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.smallCircleNode.layer.animateAlpha(from: self.smallCircleNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.smallCircleShadowNode.layer.animateAlpha(from: self.smallCircleShadowNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) - itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) } + self.disclosureButton.layer.animateAlpha(from: self.disclosureButton.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) if let targetAnchorRect = targetAnchorRect, let (size, insets, anchorRect) = self.validLayout { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .immediate, animateInFromAnchorRect: nil, animateOutToAnchorRect: targetAnchorRect) @@ -383,8 +455,14 @@ public final class ReactionContextNode: ASDisplayNode { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let contentPoint = self.contentContainer.view.convert(point, from: self.view) + if !self.disclosureButton.alpha.isZero { + if let result = self.disclosureButton.hitTest(self.disclosureButton.view.convert(point, from: self.view), with: event) { + return result + } + } for itemNode in self.itemNodes { - if itemNode.frame.contains(point) { + if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) { return self.view } } @@ -401,13 +479,14 @@ public final class ReactionContextNode: ASDisplayNode { } public func reaction(at point: CGPoint) -> ReactionGestureItem? { + let contentPoint = self.contentContainer.view.convert(point, from: self.view) for itemNode in self.itemNodes { - if itemNode.frame.contains(point) { + if !itemNode.alpha.isZero && itemNode.frame.contains(contentPoint) { return itemNode.reaction } } for itemNode in self.itemNodes { - if itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(point) { + if !itemNode.alpha.isZero && itemNode.frame.insetBy(dx: -8.0, dy: -8.0).contains(contentPoint) { return itemNode.reaction } } @@ -420,4 +499,11 @@ public final class ReactionContextNode: ASDisplayNode { self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.18, curve: .easeInOut), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true) } } + + @objc private func disclosurePressed() { + self.isExpanded = true + if let (size, insets, anchorRect) = self.validLayout { + self.updateLayout(size: size, insets: insets, anchorRect: anchorRect, transition: .animated(duration: 0.3, curve: .spring), animateInFromAnchorRect: nil, animateOutToAnchorRect: nil, animateReactionHighlight: true) + } + } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 10e3d67288..77c5fa1c2c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -80,7 +80,7 @@ final class ReactionNode: ASDisplayNode { switch reaction { case let .reaction(value, _, path): switch value { - case "😒": + case "😔": intrinsicSize.width *= 1.7 intrinsicSize.height *= 1.7 self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) @@ -96,6 +96,34 @@ final class ReactionNode: ASDisplayNode { intrinsicSize.width *= 1.256 intrinsicSize.height *= 1.256 self.intrinsicOffset = CGPoint(x: 0.0, y: 0.05 * intrinsicSize.width) + case "🥳": + intrinsicSize.width *= 1.39 + intrinsicSize.height *= 1.39 + self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: -0.05 * intrinsicSize.width) + case "😭": + intrinsicSize.width *= 1.15 + intrinsicSize.height *= 1.15 + self.intrinsicOffset = CGPoint(x: 0.1 * intrinsicSize.width, y: 0.03 * intrinsicSize.width) + case "😒": + intrinsicSize.width *= 1.05 + intrinsicSize.height *= 1.05 + self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) + case "👌": + intrinsicSize.width *= 1.08 + intrinsicSize.height *= 1.08 + self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) + case "😐": + intrinsicSize.width *= 1.75 + intrinsicSize.height *= 1.75 + self.intrinsicOffset = CGPoint(x: 0.0, y: 0.01 * intrinsicSize.width) + case "💩": + intrinsicSize.width *= 1.1 + intrinsicSize.height *= 1.1 + self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) + case "😊": + intrinsicSize.width *= 1.2 + intrinsicSize.height *= 1.2 + self.intrinsicOffset = CGPoint(x: 0.0, y: -0.01 * intrinsicSize.width) default: self.intrinsicOffset = CGPoint(x: 0.0, y: 0.0) } @@ -116,6 +144,8 @@ final class ReactionNode: ASDisplayNode { super.init() + //self.backgroundColor = .gray + self.textBackgroundNode.addSubnode(self.textNode) self.addSubnode(self.textBackgroundNode) diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index 0f1d4a7e5b..8ff38ca2cb 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -477,7 +477,7 @@ public final class ShareController: ViewController { if let error = error { Queue.mainQueue().async { let _ = (account.postbox.transaction { transaction -> Peer? in - transaction.deleteMessages([id]) + deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: [id]) return transaction.getPeer(id.peerId) } |> deliverOnMainQueue).start(next: { peer in diff --git a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift index 9b79249143..4bfaf95d0b 100644 --- a/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/TelegramCore/AccountStateManagementUtils.swift @@ -2283,14 +2283,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP } } case let .DeleteMessagesWithGlobalIds(ids): - transaction.deleteMessagesWithGlobalIds(ids) + transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in + processRemovedMedia(mediaBox, media) + }) case let .DeleteMessages(ids): deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids) case let .UpdateMinAvailableMessage(id): if let message = transaction.getMessage(id) { updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: id.peerId, minTimestamp: message.timestamp, forceRootGroupIfNotExists: false) } - transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id) + transaction.deleteMessagesInRange(peerId: id.peerId, namespace: id.namespace, minId: 1, maxId: id.id, forEachMedia: { media in + processRemovedMedia(mediaBox, media) + }) case let .UpdatePeerChatInclusion(peerId, groupId, changedGroup): let currentInclusion = transaction.getPeerChatListInclusion(peerId) var currentPinningIndex: UInt16? diff --git a/submodules/TelegramCore/TelegramCore/DeleteMessages.swift b/submodules/TelegramCore/TelegramCore/DeleteMessages.swift index c7baa9356e..179ce204f9 100644 --- a/submodules/TelegramCore/TelegramCore/DeleteMessages.swift +++ b/submodules/TelegramCore/TelegramCore/DeleteMessages.swift @@ -26,7 +26,15 @@ public func deleteMessages(transaction: Transaction, mediaBox: MediaBox, ids: [M } } } - transaction.deleteMessages(ids) + transaction.deleteMessages(ids, forEachMedia: { media in + processRemovedMedia(mediaBox, media) + }) +} + +public func deleteAllMessagesWithAuthor(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) { + transaction.removeAllMessagesWithAuthor(peerId, authorId: authorId, namespace: namespace, forEachMedia: { media in + processRemovedMedia(mediaBox, media) + }) } public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: PeerId, namespaces: MessageIdNamespaces) { @@ -36,5 +44,7 @@ public func clearHistory(transaction: Transaction, mediaBox: MediaBox, peerId: P return true }) } - transaction.clearHistory(peerId, namespaces: namespaces) + transaction.clearHistory(peerId, namespaces: namespaces, forEachMedia: { media in + processRemovedMedia(mediaBox, media) + }) } diff --git a/submodules/TelegramCore/TelegramCore/DeleteMessagesInteractively.swift b/submodules/TelegramCore/TelegramCore/DeleteMessagesInteractively.swift index 57c7e4d65f..0b8992a424 100644 --- a/submodules/TelegramCore/TelegramCore/DeleteMessagesInteractively.swift +++ b/submodules/TelegramCore/TelegramCore/DeleteMessagesInteractively.swift @@ -167,7 +167,9 @@ public func clearAuthorHistory(account: Account, peerId: PeerId, memberId: PeerI |> `catch` { success -> Signal in if success { return account.postbox.transaction { transaction -> Void in - transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud) + transaction.removeAllMessagesWithAuthor(peerId, authorId: memberId, namespace: Namespaces.Message.Cloud, forEachMedia: { media in + processRemovedMedia(account.postbox.mediaBox, media) + }) } } else { return .complete() diff --git a/submodules/TelegramCore/TelegramCore/ImageRepresentationsUtils.swift b/submodules/TelegramCore/TelegramCore/ImageRepresentationsUtils.swift index 15729290e9..8dd0e2b071 100644 --- a/submodules/TelegramCore/TelegramCore/ImageRepresentationsUtils.swift +++ b/submodules/TelegramCore/TelegramCore/ImageRepresentationsUtils.swift @@ -1,10 +1,12 @@ #if os(macOS) import PostboxMac import TelegramApiMac + import MtProtoKitMac #else import Postbox import UIKit import TelegramApi + import MtProtoKitDynamic #endif public func smallestImageRepresentation(_ representations: [TelegramMediaImageRepresentation]) -> TelegramMediaImageRepresentation? { @@ -74,7 +76,18 @@ public func imageRepresentationLargerThan(_ representations: [TelegramMediaImage } public func parseMediaData(data: Data) -> Media? { - if let object = Api.parse(Buffer(data: data)) { + let buffer = BufferReader(Buffer(data: data)) + var parseBuffer: Buffer? + guard let signature = buffer.readInt32() else { + return nil + } + if signature == 0x3072cfa1 { + parseBuffer = parseBytes(buffer).flatMap({ $0.makeData() }).flatMap(MTGzip.decompress).flatMap(Buffer.init(data:)) + } else { + parseBuffer = Buffer(data: data) + } + + if let parseBuffer = parseBuffer, let object = Api.parse(parseBuffer) { if let photo = object as? Api.Photo { return telegramMediaImageFromApiPhoto(photo) } else if let document = object as? Api.Document { diff --git a/submodules/TelegramCore/TelegramCore/ProcessRemovedMedia.swift b/submodules/TelegramCore/TelegramCore/ProcessRemovedMedia.swift new file mode 100644 index 0000000000..fdeacf55fa --- /dev/null +++ b/submodules/TelegramCore/TelegramCore/ProcessRemovedMedia.swift @@ -0,0 +1,15 @@ +import Foundation +#if os(macOS) +import PostboxMac +#else +import Postbox +#endif + +func processRemovedMedia(_ mediaBox: MediaBox, _ media: Media) { + if let image = media as? TelegramMediaImage { + let _ = mediaBox.removeCachedResources(Set(image.representations.map({ WrappedMediaResourceId($0.resource.id) }))).start() + } else if let file = media as? TelegramMediaFile { + let _ = mediaBox.removeCachedResources(Set(file.previewRepresentations.map({ WrappedMediaResourceId($0.resource.id) }))).start() + let _ = mediaBox.removeCachedResources(Set([WrappedMediaResourceId(file.resource.id)])).start() + } +} diff --git a/submodules/TelegramCore/TelegramCore/ScheduledMessages.swift b/submodules/TelegramCore/TelegramCore/ScheduledMessages.swift index 23f66776b6..66eedfd5e7 100644 --- a/submodules/TelegramCore/TelegramCore/ScheduledMessages.swift +++ b/submodules/TelegramCore/TelegramCore/ScheduledMessages.swift @@ -127,7 +127,9 @@ func managedApplyPendingScheduledMessagesActions(postbox: Postbox, network: Netw }) |> then( postbox.transaction { transaction -> Void in - transaction.deleteMessages([entry.id]) + transaction.deleteMessages([entry.id], forEachMedia: { media in + processRemovedMedia(postbox.mediaBox, media) + }) } |> ignoreValues ) diff --git a/submodules/TelegramCore/TelegramCore/UpdateCachedPeerData.swift b/submodules/TelegramCore/TelegramCore/UpdateCachedPeerData.swift index 4fb5c5230c..7722753bdc 100644 --- a/submodules/TelegramCore/TelegramCore/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/TelegramCore/UpdateCachedPeerData.swift @@ -425,7 +425,9 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { - transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id) + transaction.deleteMessagesInRange(peerId: peerId, namespace: minAvailableMessageId.namespace, minId: 1, maxId: minAvailableMessageId.id, forEachMedia: { media in + processRemovedMedia(postbox.mediaBox, media) + }) } case .chatFull: break diff --git a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj index 3f4ff9c444..63bc08ccc6 100644 --- a/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj +++ b/submodules/TelegramCore/TelegramCore_Xcode.xcodeproj/project.pbxproj @@ -604,6 +604,8 @@ D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; }; D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; }; D0B85AC61F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */; }; + D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; }; + D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */; }; D0BB7C5A1E5C8074001527C3 /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; }; D0BC386E1E3FDAB70044D6FE /* CreateGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */; }; D0BC38701E40853E0044D6FE /* UpdatePeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */; }; @@ -1122,6 +1124,7 @@ D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangePeerNotificationSettings.swift; sourceTree = ""; }; D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = ""; }; D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyUsedHashtags.swift; sourceTree = ""; }; + D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessRemovedMedia.swift; sourceTree = ""; }; D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelParticipants.swift; sourceTree = ""; }; D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = ""; }; D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeers.swift; sourceTree = ""; }; @@ -1518,6 +1521,7 @@ D03B0D121D62257600955575 /* Resources */, D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */, D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */, + D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */, ); name = Media; sourceTree = ""; @@ -2492,6 +2496,7 @@ D041E3F81E535A88008C24B4 /* RemovePeerMember.swift in Sources */, D076F8892296D8E9004F895A /* ManageChannelDiscussionGroup.swift in Sources */, D0B417C11D7DCEEF004562A4 /* ApiGroupOrChannel.swift in Sources */, + D0BAAA14230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */, D041E3F51E535464008C24B4 /* AddPeerMember.swift in Sources */, D0AF32311FACEDEC0097362B /* CoreSettings.swift in Sources */, D054649A20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */, @@ -2883,6 +2888,7 @@ D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */, D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */, + D0BAAA15230FDB4100AFC473 /* ProcessRemovedMedia.swift in Sources */, D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */, D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */, D041E3F61E535464008C24B4 /* AddPeerMember.swift in Sources */, diff --git a/submodules/TelegramUI/TelegramUI/AppDelegate.swift b/submodules/TelegramUI/TelegramUI/AppDelegate.swift index 80e1b842af..faf983cda1 100644 --- a/submodules/TelegramUI/TelegramUI/AppDelegate.swift +++ b/submodules/TelegramUI/TelegramUI/AppDelegate.swift @@ -1229,6 +1229,9 @@ final class SharedApplicationContext { extendNow = true } } + #if DEBUG + extendNow = false + #endif sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0, extendNow: extendNow) }) @@ -1300,7 +1303,20 @@ final class SharedApplicationContext { } Logger.shared.log("App \(self.episodeId)", "remoteNotification: \(redactedPayload)") - completionHandler(UIBackgroundFetchResult.noData) + + if userInfo["p"] == nil { + return + } + + let _ = (self.sharedContextPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { sharedApplicationContext in + + sharedApplicationContext.wakeupManager.replaceCurrentExtensionWithExternalTime(completion: { + completionHandler(.newData) + }, timeout: 29.0) + sharedApplicationContext.notificationManager.addNotification(userInfo) + }) } func application(_ application: UIApplication, didReceive notification: UILocalNotification) { @@ -1325,261 +1341,11 @@ final class SharedApplicationContext { if case PKPushType.voIP = type { Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)") - sharedApplicationContext.notificationManager.addEncryptedNotification(payload.dictionaryPayload) + sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload) } }) } - /*private func processPushPayload(_ payload: [AnyHashable: Any], account: Account) { - let decryptedPayload: Signal<[AnyHashable: Any]?, NoError> - if let _ = payload["aps"] as? [AnyHashable: Any] { - decryptedPayload = .single(payload) - } else if var encryptedPayload = payload["p"] as? String { - encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") - encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/") - while encryptedPayload.count % 4 != 0 { - encryptedPayload.append("=") - } - if let data = Data(base64Encoded: encryptedPayload) { - decryptedPayload = decryptedNotificationPayload(account: account, data: data) - |> map { value -> [AnyHashable: Any]? in - if let value = value, let object = try? JSONSerialization.jsonObject(with: value, options: []) { - return object as? [AnyHashable: Any] - } - return nil - } - } else { - decryptedPayload = .single(nil) - } - } else { - decryptedPayload = .single(nil) - } - - let _ = (decryptedPayload - |> deliverOnMainQueue).start(next: { payload in - guard let payload = payload else { - return - } - - var redactedPayload = payload - if var aps = redactedPayload["aps"] as? [AnyHashable: Any] { - if Logger.shared.redactSensitiveData { - if aps["alert"] != nil { - aps["alert"] = "[[redacted]]" - } - if aps["body"] != nil { - aps["body"] = "[[redacted]]" - } - } - redactedPayload["aps"] = aps - } - Logger.shared.log("Apns \(self.episodeId)", "\(redactedPayload)") - - let aps = payload["aps"] as? [AnyHashable: Any] - - if UIApplication.shared.applicationState == .background { - var readMessageId: MessageId? - var isCall = false - var isAnnouncement = false - var isLocationPolling = false - var isMutePolling = false - var title: String = "" - var body: String? - var apnsSound: String? - var configurationUpdate: (Int32, String, Int32, Data?)? - if let aps = aps, let alert = aps["alert"] as? String { - if let range = alert.range(of: ": ") { - title = String(alert[..() - if isCall { - addedWakeups.insert(.call) - } - if isLocationPolling { - addedWakeups.insert(.backgroundLocation) - } - if !addedWakeups.isEmpty { - self.queuedWakeups.formUnion(addedWakeups) - self.maybeDequeueWakeups() - } - if let readMessageId = readMessageId { - self.clearNotificationsManager?.append(readMessageId) - self.clearNotificationsManager?.commitNow() - - let signal = self.context.get() - |> take(1) - |> mapToSignal { context -> Signal in - if let context = context { - return context.context.account.postbox.transaction (ignoreDisabled: true, { transaction -> Void in - transaction.applyIncomingReadMaxId(readMessageId) - }) - } else { - return .complete() - } - } - let _ = signal.start() - } - - if let (datacenterId, host, port, secret) = configurationUpdate { - let signal = self.context.get() - |> take(1) - |> mapToSignal { context -> Signal in - if let context = context { - context.context.account.network.mergeBackupDatacenterAddress(datacenterId: datacenterId, host: host, port: port, secret: secret) - } - return .complete() - } - let _ = signal.start() - } - } - }) - }*/ - public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) { Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)") } diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 98434a588e..ab65a9a81f 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -530,11 +530,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } let reactions: [(String, String, String)] = [ - ("😒", "Sad", "sad"), + ("😔", "Sad", "sad"), ("😳", "Surprised", "surprised"), ("😂", "Fun", "lol"), ("👍", "Like", "thumbsup"), ("❤", "Love", "heart"), + ("🥳", "Celebrate", "celebrate"), + ("😭", "Cry", "cry"), + ("😒", "Meh", "meh"), + ("👌", "OK", "ok"), + ("😐", "Poker", "poker"), + ("💩", "Poop", "poop"), + ("😊", "Smile", "smile") ] var reactionItems: [ReactionContextItem] = [] @@ -7045,8 +7052,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let strongSelf = self { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } }) if actions.contains(3) { + let mediaBox = strongSelf.context.account.postbox.mediaBox let _ = strongSelf.context.account.postbox.transaction({ transaction -> Void in - transaction.removeAllMessagesWithAuthor(peerId, authorId: author.id, namespace: Namespaces.Message.Cloud) + deleteAllMessagesWithAuthor(transaction: transaction, mediaBox: mediaBox, peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud) }).start() let _ = clearAuthorHistory(account: strongSelf.context.account, peerId: peerId, memberId: author.id).start() } else if actions.contains(0) { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift index 94451cd5bd..bf13645ce6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageDateAndStatusNode.swift @@ -9,6 +9,7 @@ import TelegramPresentationData import AccountContext private let dateFont = UIFont.italicSystemFont(ofSize: 11.0) +private let reactionCountFont = Font.semiboldItalic(11.0) private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) { if let _ = layer.animation(forKey: "clockFrameAnimation") { @@ -62,50 +63,10 @@ enum ChatMessageDateAndStatusType: Equatable { case ImageOutgoing(ChatMessageDateAndStatusOutgoingType) case FreeIncoming case FreeOutgoing(ChatMessageDateAndStatusOutgoingType) - - static func ==(lhs: ChatMessageDateAndStatusType, rhs: ChatMessageDateAndStatusType) -> Bool { - switch lhs { - case .BubbleIncoming: - if case .BubbleIncoming = rhs { - return true - } else { - return false - } - case let .BubbleOutgoing(type): - if case .BubbleOutgoing(type) = rhs { - return true - } else { - return false - } - case .ImageIncoming: - if case .ImageIncoming = rhs { - return true - } else { - return false - } - case let .ImageOutgoing(type): - if case .ImageOutgoing(type) = rhs { - return true - } else { - return false - } - case .FreeIncoming: - if case .FreeIncoming = rhs { - return true - } else { - return false - } - case let .FreeOutgoing(type): - if case .FreeOutgoing(type) = rhs { - return true - } else { - return false - } - } - } } -private let reactionSize: CGFloat = 18.0 +private let reactionSize: CGFloat = 19.0 +private let reactionFont = Font.regular(12.0) private final class StatusReactionNode: ASImageNode { let value: String @@ -120,7 +81,7 @@ private final class StatusReactionNode: ASImageNode { self.image = generateImage(CGSize(width: reactionSize, height: reactionSize), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) UIGraphicsPushContext(context) - let string = NSAttributedString(string: value, font: Font.regular(11.0), textColor: .black) + let string = NSAttributedString(string: value, font: reactionFont, textColor: .black) string.draw(at: CGPoint(x: 1.0, y: 3.0)) UIGraphicsPopContext() }) @@ -136,6 +97,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { private let dateNode: TextNode private var impressionIcon: ASImageNode? private var reactionNodes: [StatusReactionNode] = [] + private var reactionCountNode: TextNode? private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? @@ -166,6 +128,8 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { let currentType = self.type let currentTheme = self.theme + let makeReactionCountLayout = TextNode.asyncLayout(self.reactionCountNode) + return { context, presentationData, edited, impressionCount, dateText, type, constrainedSize, reactions in let dateColor: UIColor var backgroundImage: UIImage? @@ -396,9 +360,20 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { backgroundInsets = UIEdgeInsets(top: 2.0, left: 7.0, bottom: 2.0, right: 7.0) } + var reactionCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? + var reactionInset: CGFloat = 0.0 if !reactions.isEmpty { - reactionInset = 1.0 + CGFloat(reactions.count) * reactionSize + reactionInset = 5.0 + CGFloat(reactions.count) * reactionSize + + var count = 0 + for reaction in reactions { + count += Int(reaction.count) + } + + let layoutAndApply = makeReactionCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(count)", font: reactionCountFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) + reactionInset += layoutAndApply.0.size.width + 2.0 + reactionCountLayoutAndApply = layoutAndApply } leftInset += reactionInset @@ -543,7 +518,14 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } else { node = StatusReactionNode(value: reactions[i].value, count: Int(reactions[i].count)) if strongSelf.reactionNodes.count > i { - strongSelf.reactionNodes[i].removeFromSupernode() + let previousNode = strongSelf.reactionNodes[i] + if animated { + previousNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousNode] _ in + previousNode?.removeFromSupernode() + }) + } else { + previousNode.removeFromSupernode() + } strongSelf.reactionNodes[i] = node } else { strongSelf.reactionNodes.append(node) @@ -551,12 +533,44 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { } if node.supernode == nil { strongSelf.addSubnode(node) + if animated { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } } - node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + 1.0 + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize)) + node.frame = CGRect(origin: CGPoint(x: reactionOffset, y: backgroundInsets.top + offset - 3.0), size: CGSize(width: reactionSize, height: reactionSize)) reactionOffset += reactionSize } for _ in reactions.count ..< strongSelf.reactionNodes.count { - strongSelf.reactionNodes.removeLast().removeFromSupernode() + let node = strongSelf.reactionNodes.removeLast() + if animated { + node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak node] _ in + node?.removeFromSupernode() + }) + } else { + node.removeFromSupernode() + } + } + + if let (layout, apply) = reactionCountLayoutAndApply { + let node = apply() + if strongSelf.reactionCountNode !== node { + strongSelf.reactionCountNode?.removeFromSupernode() + strongSelf.addSubnode(node) + strongSelf.reactionCountNode = node + if animated { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } + node.frame = CGRect(origin: CGPoint(x: reactionOffset + 1, y: backgroundInsets.top + 1.0 + offset), size: layout.size) + } else if let reactionCountNode = strongSelf.reactionCountNode { + strongSelf.reactionCountNode = nil + if animated { + reactionCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak reactionCountNode] _ in + reactionCountNode?.removeFromSupernode() + }) + } else { + reactionCountNode.removeFromSupernode() + } } } }) diff --git a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift index 55a05c1cbc..05b3e48a3f 100644 --- a/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift +++ b/submodules/TelegramUI/TelegramUI/NotificationContentContext.swift @@ -151,161 +151,187 @@ public final class NotificationViewControllerImpl { return } - if let accountIdValue = notification.request.content.userInfo["accountId"] as? Int64, let peerIdValue = notification.request.content.userInfo["peerId"] as? Int64, let messageIdNamespace = notification.request.content.userInfo["messageId.namespace"] as? Int32, let messageIdId = notification.request.content.userInfo["messageId.id"] as? Int32, let mediaDataString = notification.request.content.userInfo["media"] as? String, let mediaData = Data(base64Encoded: mediaDataString), let media = parseMediaData(data: mediaData) { - let messageId = MessageId(peerId: PeerId(peerIdValue), namespace: messageIdNamespace, id: messageIdId) + guard let accountIdValue = notification.request.content.userInfo["accountId"] as? Int64 else { + return + } + + guard let peerIdValue = notification.request.content.userInfo["peerId"] as? Int64 else { + return + } + + guard let messageIdNamespace = notification.request.content.userInfo["messageId.namespace"] as? Int32 else { + return + } + + guard let messageIdId = notification.request.content.userInfo["messageId.id"] as? Int32 else { + return + } + + guard let mediaDataString = notification.request.content.userInfo["media"] as? String else { + return + } + + guard let mediaData = Data(base64Encoded: mediaDataString) else { + return + } + + guard let media = parseMediaData(data: mediaData) else { + return + } + + let messageId = MessageId(peerId: PeerId(peerIdValue), namespace: messageIdNamespace, id: messageIdId) + + if let image = media as? TelegramMediaImage, let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 120.0, height: 120.0)), let largestRepresentation = largestImageRepresentation(image.representations) { + let dimensions = largestRepresentation.dimensions + let fittedSize = dimensions.fitted(CGSize(width: view.bounds.width, height: 1000.0)) + view.frame = CGRect(origin: view.frame.origin, size: fittedSize) + self.setPreferredContentSize(fittedSize) - if let image = media as? TelegramMediaImage, let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 120.0, height: 120.0)), let largestRepresentation = largestImageRepresentation(image.representations) { - let dimensions = largestRepresentation.dimensions - let fittedSize = dimensions.fitted(CGSize(width: view.bounds.width, height: 1000.0)) - view.frame = CGRect(origin: view.frame.origin, size: fittedSize) - self.setPreferredContentSize(fittedSize) - - self.imageInfo = (false, dimensions) - self.updateImageLayout(boundingSize: view.bounds.size) - - let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media" - - if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true))) - |> map { $0.1 }) - return + self.imageInfo = (false, dimensions) + self.updateImageLayout(boundingSize: view.bounds.size) + + let mediaBoxPath = accountsPath + "/" + accountRecordIdPathName(AccountRecordId(rawValue: accountIdValue)) + "/postbox/media" + + if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(largestRepresentation.resource.id.uniqueId)"), options: .mappedRead) { + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(nil, data, true))) + |> map { $0.1 }) + return + } + + if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) { + self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false))) + |> map { $0.1 }) + } + + guard let sharedAccountContext = sharedAccountContext else { + return + } + + self.applyDisposable.set((sharedAccountContext.activeAccounts + |> map { _, accounts, _ -> Account? in + return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1 + } + |> filter { account in + return account != nil + } + |> take(1) + |> mapToSignal { account -> Signal<(Account, ImageMediaReference?), NoError> in + guard let account = account else { + return .complete() } - - if let data = try? Data(contentsOf: URL(fileURLWithPath: mediaBoxPath + "/\(thumbnailRepresentation.resource.id.uniqueId)"), options: .mappedRead) { - self.imageNode.setSignal(chatMessagePhotoInternal(photoData: .single(Tuple(data, nil, false))) - |> map { $0.1 }) - } - - guard let sharedAccountContext = sharedAccountContext else { - return - } - - self.applyDisposable.set((sharedAccountContext.activeAccounts - |> map { _, accounts, _ -> Account? in - return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1 - } - |> filter { account in - return account != nil - } - |> take(1) - |> mapToSignal { account -> Signal<(Account, ImageMediaReference?), NoError> in - guard let account = account else { - return .complete() - } - return account.postbox.messageAtId(messageId) - |> take(1) - |> map { message in - var imageReference: ImageMediaReference? - if let message = message { - for media in message.media { - if let image = media as? TelegramMediaImage { - imageReference = .message(message: MessageReference(message), media: image) - } - } - } else { - imageReference = .standalone(media: image) - } - return (account, imageReference) - } - } - |> deliverOnMainQueue).start(next: { [weak self] accountAndImage in - guard let strongSelf = self else { - return - } - if let imageReference = accountAndImage.1 { - strongSelf.imageNode.setSignal(chatMessagePhoto(postbox: accountAndImage.0.postbox, photoReference: imageReference)) - - accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) - strongSelf.fetchedDisposable.set(standaloneChatMessagePhotoInteractiveFetched(account: accountAndImage.0, photoReference: imageReference).start()) - } - })) - } else if let file = media as? TelegramMediaFile, let dimensions = file.dimensions { - guard let sharedAccountContext = sharedAccountContext else { - return - } - - let fittedSize = dimensions.fitted(CGSize(width: min(256.0, view.bounds.width), height: 256.0)) - view.frame = CGRect(origin: view.frame.origin, size: fittedSize) - self.setPreferredContentSize(fittedSize) - - self.imageInfo = (true, dimensions) - self.updateImageLayout(boundingSize: view.bounds.size) - - self.applyDisposable.set((sharedAccountContext.activeAccounts - |> map { _, accounts, _ -> Account? in - return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1 - } - |> filter { account in - return account != nil - } - |> take(1) - |> mapToSignal { account -> Signal<(Account, FileMediaReference?), NoError> in - guard let account = account else { - return .complete() - } - return account.postbox.messageAtId(messageId) + return account.postbox.messageAtId(messageId) |> take(1) |> map { message in - var fileReference: FileMediaReference? + var imageReference: ImageMediaReference? if let message = message { for media in message.media { - if let file = media as? TelegramMediaFile { - fileReference = .message(message: MessageReference(message), media: file) + if let image = media as? TelegramMediaImage { + imageReference = .message(message: MessageReference(message), media: image) } } } else { - fileReference = .standalone(media: file) + imageReference = .standalone(media: image) } - return (account, fileReference) + return (account, imageReference) + } + } + |> deliverOnMainQueue).start(next: { [weak self] accountAndImage in + guard let strongSelf = self else { + return + } + if let imageReference = accountAndImage.1 { + strongSelf.imageNode.setSignal(chatMessagePhoto(postbox: accountAndImage.0.postbox, photoReference: imageReference)) + + accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) + strongSelf.fetchedDisposable.set(standaloneChatMessagePhotoInteractiveFetched(account: accountAndImage.0, photoReference: imageReference).start()) + } + })) + } else if let file = media as? TelegramMediaFile, let dimensions = file.dimensions { + guard let sharedAccountContext = sharedAccountContext else { + return + } + + let fittedSize = dimensions.fitted(CGSize(width: min(256.0, view.bounds.width), height: 256.0)) + view.frame = CGRect(origin: view.frame.origin, size: fittedSize) + self.setPreferredContentSize(fittedSize) + + self.imageInfo = (true, dimensions) + self.updateImageLayout(boundingSize: view.bounds.size) + + self.applyDisposable.set((sharedAccountContext.activeAccounts + |> map { _, accounts, _ -> Account? in + return accounts.first(where: { $0.0 == AccountRecordId(rawValue: accountIdValue) })?.1 + } + |> filter { account in + return account != nil + } + |> take(1) + |> mapToSignal { account -> Signal<(Account, FileMediaReference?), NoError> in + guard let account = account else { + return .complete() + } + return account.postbox.messageAtId(messageId) + |> take(1) + |> map { message in + var fileReference: FileMediaReference? + if let message = message { + for media in message.media { + if let file = media as? TelegramMediaFile { + fileReference = .message(message: MessageReference(message), media: file) + } + } + } else { + fileReference = .standalone(media: file) + } + return (account, fileReference) + } + } + |> deliverOnMainQueue).start(next: { [weak self, weak view] accountAndImage in + guard let strongSelf = self else { + return + } + if let fileReference = accountAndImage.1 { + if file.isAnimatedSticker { + let animatedStickerNode: AnimatedStickerNode + if let current = strongSelf.animatedStickerNode { + animatedStickerNode = current + } else { + animatedStickerNode = AnimatedStickerNode() + strongSelf.animatedStickerNode = animatedStickerNode + animatedStickerNode.started = { + guard let strongSelf = self else { + return + } + strongSelf.imageNode.isHidden = true + } + if !strongSelf.imageNode.frame.width.isZero { + animatedStickerNode.frame = strongSelf.imageNode.frame + animatedStickerNode.updateLayout(size: strongSelf.imageNode.frame.size) + } + view?.addSubnode(animatedStickerNode) + } + let dimensions = fileReference.media.dimensions ?? CGSize(width: 512.0, height: 512.0) + let fittedDimensions = dimensions.aspectFitted(CGSize(width: 512.0, height: 512.0)) + strongSelf.imageNode.setSignal(chatMessageAnimatedSticker(postbox: accountAndImage.0.postbox, file: fileReference.media, small: false, size: fittedDimensions)) + animatedStickerNode.setup(account: accountAndImage.0, resource: .resource(fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) + animatedStickerNode.visibility = true + + accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) + strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start()) + } else if file.isSticker { + if let animatedStickerNode = strongSelf.animatedStickerNode { + animatedStickerNode.removeFromSupernode() + strongSelf.animatedStickerNode = nil + } + strongSelf.imageNode.isHidden = false + + strongSelf.imageNode.setSignal(chatMessageSticker(account: accountAndImage.0, file: file, small: false)) + + accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) + strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start()) } } - |> deliverOnMainQueue).start(next: { [weak self, weak view] accountAndImage in - guard let strongSelf = self else { - return - } - if let fileReference = accountAndImage.1 { - if file.isAnimatedSticker { - let animatedStickerNode: AnimatedStickerNode - if let current = strongSelf.animatedStickerNode { - animatedStickerNode = current - } else { - animatedStickerNode = AnimatedStickerNode() - strongSelf.animatedStickerNode = animatedStickerNode - animatedStickerNode.started = { - guard let strongSelf = self else { - return - } - strongSelf.imageNode.isHidden = true - } - if !strongSelf.imageNode.frame.width.isZero { - animatedStickerNode.frame = strongSelf.imageNode.frame - animatedStickerNode.updateLayout(size: strongSelf.imageNode.frame.size) - } - view?.addSubnode(animatedStickerNode) - } - let dimensions = fileReference.media.dimensions ?? CGSize(width: 512.0, height: 512.0) - let fittedDimensions = dimensions.aspectFitted(CGSize(width: 512.0, height: 512.0)) - strongSelf.imageNode.setSignal(chatMessageAnimatedSticker(postbox: accountAndImage.0.postbox, file: fileReference.media, small: false, size: fittedDimensions)) - animatedStickerNode.setup(account: accountAndImage.0, resource: .resource(fileReference.media.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct) - animatedStickerNode.visibility = true - - accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) - strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start()) - } else if file.isSticker { - if let animatedStickerNode = strongSelf.animatedStickerNode { - animatedStickerNode.removeFromSupernode() - strongSelf.animatedStickerNode = nil - } - strongSelf.imageNode.isHidden = false - - strongSelf.imageNode.setSignal(chatMessageSticker(account: accountAndImage.0, file: file, small: false)) - - accountAndImage.0.network.shouldExplicitelyKeepWorkerConnections.set(.single(true)) - strongSelf.fetchedDisposable.set(freeMediaFileInteractiveFetched(account: accountAndImage.0, fileReference: fileReference).start()) - } - } - })) - } + })) } } diff --git a/submodules/TelegramUI/TelegramUI/SharedNotificationManager.swift b/submodules/TelegramUI/TelegramUI/SharedNotificationManager.swift index 127075c8a5..e25fa61511 100644 --- a/submodules/TelegramUI/TelegramUI/SharedNotificationManager.swift +++ b/submodules/TelegramUI/TelegramUI/SharedNotificationManager.swift @@ -21,6 +21,14 @@ private final class PollStateContext { } } +private final class NotificationInfo { + let dict: [AnyHashable: Any] + + init(dict: [AnyHashable: Any]) { + self.dict = dict + } +} + public final class SharedNotificationManager { private let episodeId: UInt32 private let application: UIApplication @@ -35,7 +43,7 @@ public final class SharedNotificationManager { private var accountsAndKeys: [(Account, Bool, MasterNotificationKey)]? private var accountsAndKeysDisposable: Disposable? - private var encryptedNotifications: [[AnyHashable: Any]] = [] + private var notifications: [NotificationInfo] = [] private var pollStateContexts: [AccountRecordId: PollStateContext] = [:] @@ -149,8 +157,8 @@ public final class SharedNotificationManager { } } - func addEncryptedNotification(_ dict: [AnyHashable: Any]) { - self.encryptedNotifications.append(dict) + func addNotification(_ dict: [AnyHashable: Any]) { + self.notifications.append(NotificationInfo(dict: dict)) if self.accountsAndKeys != nil { self.process() @@ -162,8 +170,8 @@ public final class SharedNotificationManager { return } var decryptedNotifications: [(Account, Bool, [AnyHashable: Any])] = [] - for dict in self.encryptedNotifications { - if var encryptedPayload = dict["p"] as? String { + for notification in self.notifications { + if var encryptedPayload = notification.dict["p"] as? String { encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+") encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/") while encryptedPayload.count % 4 != 0 { @@ -181,7 +189,7 @@ public final class SharedNotificationManager { } } } - self.encryptedNotifications.removeAll() + self.notifications.removeAll() for (account, isCurrent, payload) in decryptedNotifications { var redactedPayload = payload @@ -206,7 +214,7 @@ public final class SharedNotificationManager { var isAnnouncement = false var isLocationPolling = false var notificationRequestId: NotificationManagedNotificationRequestId? - var isMutePolling = false + var shouldPollState = false var title: String = "" var body: String? var apnsSound: String? @@ -226,63 +234,37 @@ public final class SharedNotificationManager { title = alertTitle } } - if let locKey = alert["loc-key"] as? String { - if locKey == "SESSION_REVOKE" { - isForcedLogOut = true - } else if locKey == "PHONE_CALL_REQUEST" { - isCall = true - } else if locKey == "GEO_LIVE_PENDING" { - isLocationPolling = true - } else if locKey == "MESSAGE_MUTED" { - isMutePolling = true - } else if locKey == "MESSAGE_DELETED" { - var peerId: PeerId? - if let fromId = payload["from_id"] { - let fromIdValue = fromId as! NSString - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue)) - } else if let fromId = payload["chat_id"] { - let fromIdValue = fromId as! NSString - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue)) - } else if let fromId = payload["channel_id"] { - let fromIdValue = fromId as! NSString - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue)) - } - if let peerId = peerId { - if let messageIds = payload["messages"] as? String { - for messageId in messageIds.split(separator: ",") { - if let messageIdValue = Int32(messageId) { - messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue)) - } + } + if let locKey = payload["loc-key"] as? String { + if locKey == "SESSION_REVOKE" { + isForcedLogOut = true + } else if locKey == "PHONE_CALL_REQUEST" { + isCall = true + } else if locKey == "GEO_LIVE_PENDING" { + isLocationPolling = true + } else if locKey == "MESSAGE_MUTED" { + shouldPollState = true + } else if locKey == "MESSAGE_DELETED" { + var peerId: PeerId? + if let fromId = payload["from_id"] { + let fromIdValue = fromId as! NSString + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue)) + } else if let fromId = payload["chat_id"] { + let fromIdValue = fromId as! NSString + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: Int32(fromIdValue.intValue)) + } else if let fromId = payload["channel_id"] { + let fromIdValue = fromId as! NSString + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: Int32(fromIdValue.intValue)) + } + if let peerId = peerId { + if let messageIds = payload["messages"] as? String { + for messageId in messageIds.split(separator: ",") { + if let messageIdValue = Int32(messageId) { + messagesDeleted.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue)) } } } } - let string = NSLocalizedString(locKey, comment: "") - if !string.isEmpty { - if let locArgs = alert["loc-args"] as? [AnyObject] { - var args: [CVarArg] = [] - var failed = false - for arg in locArgs { - if let arg = arg as? CVarArg { - args.append(arg) - } else { - failed = true - break - } - } - if failed { - body = "\(string)" - } else { - body = String(format: string, arguments: args) - } - } else { - body = "\(string)" - } - } else { - body = nil - } - } else { - body = nil } } @@ -326,6 +308,8 @@ public final class SharedNotificationManager { } else { var peerId: PeerId? + shouldPollState = true + if let fromId = payload["from_id"] { let fromIdValue = fromId as! NSString peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: Int32(fromIdValue.intValue)) @@ -350,7 +334,7 @@ public final class SharedNotificationManager { } notificationRequestId = .globallyUniqueId(randomIdValue.longLongValue, peerId) } else { - isMutePolling = true + shouldPollState = true } } } else if let _ = payload["max_id"] { @@ -386,7 +370,7 @@ public final class SharedNotificationManager { return } - if notificationRequestId != nil || isMutePolling || isCall { + if notificationRequestId != nil || shouldPollState || isCall { if !self.inForeground || !isCurrent { self.beginPollingState(account: account) } @@ -411,7 +395,7 @@ public final class SharedNotificationManager { if !messagesDeleted.isEmpty { let _ = account.postbox.transaction(ignoreDisabled: true, { transaction -> Void in - transaction.deleteMessages(messagesDeleted) + deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: messagesDeleted) }).start() } diff --git a/submodules/TelegramUI/TelegramUI/SharedWakeupManager.swift b/submodules/TelegramUI/TelegramUI/SharedWakeupManager.swift index f4aa1a9a63..e6d31125b9 100644 --- a/submodules/TelegramUI/TelegramUI/SharedWakeupManager.swift +++ b/submodules/TelegramUI/TelegramUI/SharedWakeupManager.swift @@ -60,6 +60,8 @@ public final class SharedWakeupManager { private var hasActiveAudioSessionDisposable: Disposable? private var tasksDisposable: Disposable? private var currentTask: (UIBackgroundTaskIdentifier, Double, SwiftSignalKit.Timer)? + private var currentExternalCompletion: (() -> Void, SwiftSignalKit.Timer)? + private var currentExternalCompletionValidationTimer: SwiftSignalKit.Timer? private var managedPausedInBackgroundPlayer: Disposable? @@ -201,8 +203,51 @@ public final class SharedWakeupManager { } } + func replaceCurrentExtensionWithExternalTime(completion: @escaping () -> Void, timeout: Double) { + if let (currentCompletion, timer) = self.currentExternalCompletion { + currentCompletion() + timer.invalidate() + self.currentExternalCompletion = nil + } + let timer = SwiftSignalKit.Timer(timeout: timeout - 5.0, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.currentExternalCompletionValidationTimer?.invalidate() + strongSelf.currentExternalCompletionValidationTimer = nil + if let (completion, timer) = strongSelf.currentExternalCompletion { + strongSelf.currentExternalCompletion = nil + timer.invalidate() + completion() + } + strongSelf.checkTasks() + }, queue: Queue.mainQueue()) + self.currentExternalCompletion = (completion, timer) + timer.start() + + self.currentExternalCompletionValidationTimer?.invalidate() + let validationTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.currentExternalCompletionValidationTimer?.invalidate() + strongSelf.currentExternalCompletionValidationTimer = nil + strongSelf.checkTasks() + }, queue: Queue.mainQueue()) + self.currentExternalCompletionValidationTimer = validationTimer + validationTimer.start() + self.checkTasks() + } + func checkTasks() { + var hasTasksForBackgroundExtension = false if self.inForeground || self.hasActiveAudioSession { + if let (completion, timer) = self.currentExternalCompletion { + self.currentExternalCompletion = nil + completion() + timer.invalidate() + } + if let (taskId, _, timer) = self.currentTask { self.currentTask = nil timer.invalidate() @@ -210,13 +255,21 @@ public final class SharedWakeupManager { self.isInBackgroundExtension = false } } else { - var hasTasksForBackgroundExtension = false for (_, _, tasks) in self.accountsAndTasks { if !tasks.isEmpty { hasTasksForBackgroundExtension = true break } } + + if !hasTasksForBackgroundExtension && self.currentExternalCompletionValidationTimer == nil { + if let (completion, timer) = self.currentExternalCompletion { + self.currentExternalCompletion = nil + completion() + timer.invalidate() + } + } + if self.activeExplicitExtensionTimer != nil { hasTasksForBackgroundExtension = true } @@ -265,11 +318,11 @@ public final class SharedWakeupManager { self.isInBackgroundExtension = false } } - self.updateAccounts() + self.updateAccounts(hasTasks: hasTasksForBackgroundExtension) } - private func updateAccounts() { - if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || self.activeExplicitExtensionTimer != nil { + private func updateAccounts(hasTasks: Bool) { + if self.inForeground || self.hasActiveAudioSession || self.isInBackgroundExtension || (hasTasks && self.currentExternalCompletion != nil) || self.activeExplicitExtensionTimer != nil { for (account, primary, tasks) in self.accountsAndTasks { if (self.inForeground && primary) || !tasks.isEmpty || (self.activeExplicitExtensionTimer != nil && primary) { account.shouldBeServiceTaskMaster.set(.single(.always)) diff --git a/submodules/UrlEscaping/Sources/UrlEscaping.swift b/submodules/UrlEscaping/Sources/UrlEscaping.swift index a312d0e5df..9988ce64ef 100644 --- a/submodules/UrlEscaping/Sources/UrlEscaping.swift +++ b/submodules/UrlEscaping/Sources/UrlEscaping.swift @@ -1,6 +1,11 @@ import Foundation public func doesUrlMatchText(url: String, text: String) -> Bool { + for c in url { + if !c.isASCII { + return false + } + } if url == text { return true } @@ -32,7 +37,6 @@ public extension CharacterSet { } public func isValidUrl(_ url: String) -> Bool { - if let escapedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: escapedUrl), ["http", "https"].contains(url.scheme), let host = url.host, host.contains(".") && url.user == nil { let components = host.components(separatedBy: ".") let domain = (components.first ?? "") diff --git a/submodules/ffmpeg/FFMpeg/ffmpeg-4.1/libavformat/id3v2.c b/submodules/ffmpeg/FFMpeg/ffmpeg-4.1/libavformat/id3v2.c index f7de26a1d8..934bd52771 100644 --- a/submodules/ffmpeg/FFMpeg/ffmpeg-4.1/libavformat/id3v2.c +++ b/submodules/ffmpeg/FFMpeg/ffmpeg-4.1/libavformat/id3v2.c @@ -791,7 +791,7 @@ typedef struct ID3v2EMFunc { static const ID3v2EMFunc id3v2_extra_meta_funcs[] = { { "GEO", "GEOB", read_geobtag, free_geobtag }, - { "PIC", "APIC", read_apic, free_apic }, + //{ "PIC", "APIC", read_apic, free_apic }, { "CHAP","CHAP", read_chapter, free_chapter }, { "PRIV","PRIV", read_priv, free_priv }, { NULL }