Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2019-08-24 06:31:03 +03:00
commit 9b7bbc5ae7
37 changed files with 4116 additions and 642 deletions

298
NotificationService/Api.h Normal file
View File

@ -0,0 +1,298 @@
#import <Foundation/Foundation.h>
/*
* 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

1979
NotificationService/Api.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {
public static func parseVector<T>(_ 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 {
}
}

View File

@ -0,0 +1,7 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
id _Nullable parseAttachment(NSData * _Nonnull data);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,30 @@
#import "Attachments.h"
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
#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];
}

View File

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
#import "StoredAccountInfos.h"
#import "Api.h"
#import <BuildConfig/BuildConfig.h>
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

View File

@ -0,0 +1,195 @@
#import "FetchImage.h"
#import <MTProtoKitDynamic/MTProtoKitDynamic.h>
#import "Serialization.h"
@interface InMemoryKeychain : NSObject <MTKeychain> {
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];
};
}

View File

@ -25,7 +25,7 @@
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
<string>NotificationService</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#import <UserNotifications/UserNotifications.h>
NS_ASSUME_NONNULL_BEGIN
@interface NotificationService : UNNotificationServiceExtension
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,359 @@
#import "NotificationService.h"
#import <UIKit/UIKit.h>
#import <BuildConfig/BuildConfig.h>
#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

View File

@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>
#import <MTProtoKitDynamic/MTProtoKitDynamic.h>
NS_ASSUME_NONNULL_BEGIN
@interface Serialization : NSObject <MTSerialization>
@end
NS_ASSUME_NONNULL_END

View File

@ -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

View File

@ -0,0 +1,67 @@
#import <Foundation/Foundation.h>
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<AccountDatacenterAddress *> *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<NSNumber *, AccountDatacenterInfo *> *datacenters;
@property (nonatomic, strong, readonly) AccountNotificationKey *notificationKey;
@end
@interface StoredAccountInfos : NSObject
@property (nonatomic, strong, readonly) AccountProxyConnection * _Nullable proxy;
@property (nonatomic, strong, readonly) NSArray<StoredAccountInfo *> *accounts;
+ (StoredAccountInfos * _Nullable)loadFromPath:(NSString *)path;
@end
NSDictionary * _Nullable decryptedNotificationPayload(NSArray<StoredAccountInfo *> *accounts, NSData *data, int *selectedAccountIndex);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,412 @@
#import "StoredAccountInfos.h"
#import <MtProtoKitDynamic/MtProtoKitDynamic.h>
#import <CommonCrypto/CommonDigest.h>
@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<AccountDatacenterAddress *> *)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<AccountDatacenterAddress *> *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<NSNumber *, AccountDatacenterInfo *> *)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<NSNumber *, AccountDatacenterInfo *> *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<StoredAccountInfo *> *)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<StoredAccountInfo *> *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<StoredAccountInfo *> *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;
}

View File

@ -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 = "<group>"; };
D0B8445A1DACF507005F29E1 /* HockeySDKResources.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = HockeySDKResources.bundle; path = "third-party/HockeySDKResources.bundle"; sourceTree = "<group>"; };
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 = "<group>"; };
D0BAAA1723100B7A00AFC473 /* Api.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Api.h; sourceTree = "<group>"; };
D0BAAA1923100C2700AFC473 /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = "<group>"; };
D0BAAA1A23100C2700AFC473 /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = "<group>"; };
D0BAAA1C2310117200AFC473 /* StoredAccountInfos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StoredAccountInfos.h; sourceTree = "<group>"; };
D0BAAA1D2310117200AFC473 /* StoredAccountInfos.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StoredAccountInfos.m; sourceTree = "<group>"; };
D0BAAA1F231026BC00AFC473 /* Attachments.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Attachments.h; sourceTree = "<group>"; };
D0BAAA20231026BC00AFC473 /* Attachments.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Attachments.m; sourceTree = "<group>"; };
D0BAAA222310302300AFC473 /* FetchImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FetchImage.h; sourceTree = "<group>"; };
D0BAAA232310302300AFC473 /* FetchImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FetchImage.m; sourceTree = "<group>"; };
D0BAAA252310326200AFC473 /* Serialization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Serialization.h; sourceTree = "<group>"; };
D0BAAA262310326200AFC473 /* Serialization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Serialization.m; sourceTree = "<group>"; };
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;
};

View File

@ -57,7 +57,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugAppStoreLLC"
buildConfiguration = "DebugHockeyapp"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"

View File

@ -408,32 +408,50 @@ final class MessageHistoryTable: Table {
return globallyUniqueIdToMessageId
}
func removeMessages(_ messageIds: [MessageId], 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 removeMessages(_ messageIds: [MessageId], 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) {
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]) {

View File

@ -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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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: &currentUnsentOperations, 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]]) {

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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?

View File

@ -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)
})
}

View File

@ -167,7 +167,9 @@ public func clearAuthorHistory(account: Account, peerId: PeerId, memberId: PeerI
|> `catch` { success -> Signal<Void, NoError> 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()

View File

@ -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 {

View File

@ -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()
}
}

View File

@ -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
)

View File

@ -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

View File

@ -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 = "<group>"; };
D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = "<group>"; };
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyUsedHashtags.swift; sourceTree = "<group>"; };
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessRemovedMedia.swift; sourceTree = "<group>"; };
D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelParticipants.swift; sourceTree = "<group>"; };
D0BC386D1E3FDAB70044D6FE /* CreateGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroup.swift; sourceTree = "<group>"; };
D0BC386F1E40853E0044D6FE /* UpdatePeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatePeers.swift; sourceTree = "<group>"; };
@ -1518,6 +1521,7 @@
D03B0D121D62257600955575 /* Resources */,
D0DFD5DE1FCDBCFD0039B3B1 /* CachedSentMediaReferences.swift */,
D0879BC722F85A3E00C4D6B3 /* ImageRepresentationWithReference.swift */,
D0BAAA13230FDB4100AFC473 /* ProcessRemovedMedia.swift */,
);
name = Media;
sourceTree = "<group>";
@ -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 */,

View File

@ -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[..<range.lowerBound])
body = String(alert[range.upperBound...])
} else {
body = alert
}
} else if let aps = aps, let alert = aps["alert"] as? [AnyHashable: AnyObject] {
if let alertBody = alert["body"] as? String {
body = alertBody
if let alertTitle = alert["title"] as? String {
title = alertTitle
}
}
if let locKey = alert["loc-key"] as? String {
if locKey == "PHONE_CALL_REQUEST" {
isCall = true
} else if locKey == "GEO_LIVE_PENDING" {
isLocationPolling = true
} else if locKey == "MESSAGE_MUTED" {
isMutePolling = true
}
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
}
}
if let aps = aps, let address = aps["addr"] as? String, let datacenterId = aps["dc"] as? Int {
var host = address
var port: Int32 = 443
if let range = address.range(of: ":") {
host = String(address[address.startIndex ..< range.lowerBound])
if let portValue = Int(String(address[range.upperBound...])) {
port = Int32(portValue)
}
}
var secret: Data?
if let secretString = aps["sec"] as? String {
let data = dataWithHexString(secretString)
if data.count == 16 || data.count == 32 {
secret = data
}
}
configurationUpdate = (Int32(datacenterId), host, port, secret)
}
if let aps = aps, let sound = aps["sound"] as? String {
apnsSound = sound
}
if payload["call_id"] != nil {
isCall = true
}
if payload["announcement"] != nil {
isAnnouncement = true
}
if let body = body {
if isAnnouncement {
self.queuedAnnouncements.append(body)
self.maybeDequeueAnnouncements()
} else {
var peerId: PeerId?
var notificationRequestId: NotificationManagedNotificationRequestId?
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 msgId = payload["msg_id"] {
let msgIdValue = msgId as! NSString
if let peerId = peerId {
notificationRequestId = .messageId(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue)))
}
} else if let randomId = payload["random_id"] {
let randomIdValue = randomId as! NSString
var peerId: PeerId?
if let encryptionIdString = payload["encryption_id"] as? String, let encryptionId = Int32(encryptionIdString) {
peerId = PeerId(namespace: Namespaces.Peer.SecretChat, id: encryptionId)
}
notificationRequestId = .globallyUniqueId(randomIdValue.longLongValue, peerId)
} else {
isMutePolling = true
}
if let notificationRequestId = notificationRequestId {
self.queuedNotificationRequests.append((title, body, apnsSound, notificationRequestId))
self.maybeDequeueNotificationRequests()
} else if isMutePolling {
self.queuedMutePolling = true
self.maybeDequeueNotificationRequests()
}
}
} else if let _ = payload["max_id"] {
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 msgId = payload["max_id"] {
let msgIdValue = msgId as! NSString
if msgIdValue.intValue != 0 {
readMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(msgIdValue.intValue))
}
}
}
}
var addedWakeups = Set<QueuedWakeup>()
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<Void, NoError> 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<Void, NoError> 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)")
}

View File

@ -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) {

View File

@ -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()
}
}
}
})

View File

@ -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())
}
}
}))
}
}))
}
}

View File

@ -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()
}

View File

@ -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))

View File

@ -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 ?? "")

View File

@ -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 }