no message

This commit is contained in:
Peter 2016-11-15 19:02:08 +03:00
parent a129ca9595
commit efe13e4130
26 changed files with 3759 additions and 627 deletions

View File

@ -8,6 +8,9 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
D003702B1DA42586004308D3 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003702A1DA42586004308D3 /* PhoneNumber.swift */; }; D003702B1DA42586004308D3 /* PhoneNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = D003702A1DA42586004308D3 /* PhoneNumber.swift */; };
D01AC91D1DD5DA5E00E8160F /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; };
D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; };
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */; };
D021E0DF1DB539FC00C6B04F /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; }; D021E0DF1DB539FC00C6B04F /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; };
D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; }; D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; };
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; };
@ -212,6 +215,10 @@
D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */; }; D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */; };
D0DF0C931D81AD09008AEB01 /* MessageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */; }; D0DF0C931D81AD09008AEB01 /* MessageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */; };
D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */; }; D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */; };
D0F7AB2C1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */; };
D0F7AB2D1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */; };
D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */; };
D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -226,6 +233,9 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = "<group>"; }; D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = "<group>"; };
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMessageActionCallback.swift; sourceTree = "<group>"; };
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestEditMessage.swift; sourceTree = "<group>"; };
D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplyUpdateMessage.swift; sourceTree = "<group>"; };
D021E0DE1DB539FC00C6B04F /* StickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPack.swift; sourceTree = "<group>"; }; D021E0DE1DB539FC00C6B04F /* StickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPack.swift; sourceTree = "<group>"; };
D021E0E11DB5401A00C6B04F /* StickerManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManagement.swift; sourceTree = "<group>"; }; D021E0E11DB5401A00C6B04F /* StickerManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManagement.swift; sourceTree = "<group>"; };
D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = "<group>"; }; D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = "<group>"; };
@ -358,6 +368,8 @@
D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRepresentationsUtils.swift; sourceTree = "<group>"; }; D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRepresentationsUtils.swift; sourceTree = "<group>"; };
D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageUtils.swift; sourceTree = "<group>"; }; D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageUtils.swift; sourceTree = "<group>"; };
D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerParticipants.swift; sourceTree = "<group>"; }; D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerParticipants.swift; sourceTree = "<group>"; };
D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = "<group>"; };
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -517,6 +529,8 @@
D03B0CE11D62249B00955575 /* InlineBotMessageAttribute.swift */, D03B0CE11D62249B00955575 /* InlineBotMessageAttribute.swift */,
D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */, D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */,
D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */, D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */,
D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */,
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */,
); );
name = Attributes; name = Attributes;
sourceTree = "<group>"; sourceTree = "<group>";
@ -549,6 +563,7 @@
D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */, D03B0D071D62255C00955575 /* UpdatesApiUtils.swift */,
D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */, D09BB6B31DB02C2B00A905C0 /* PendingMessageManager.swift */,
D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */, D09BB6B51DB0428000A905C0 /* PendingMessageUploadedContent.swift */,
D01AC9221DD5E9A200E8160F /* ApplyUpdateMessage.swift */,
); );
name = State; name = State;
sourceTree = "<group>"; sourceTree = "<group>";
@ -613,6 +628,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D03B0D711D631ABA00955575 /* SearchMessages.swift */, D03B0D711D631ABA00955575 /* SearchMessages.swift */,
D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */,
D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */,
); );
name = Messages; name = Messages;
sourceTree = "<group>"; sourceTree = "<group>";
@ -902,6 +919,7 @@
D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */, D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */,
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */, D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */,
D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */, D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */,
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */,
D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */, D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */,
D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */, D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */,
D03B0D6B1D631A9D00955575 /* Phonebook.swift in Sources */, D03B0D6B1D631A9D00955575 /* Phonebook.swift in Sources */,
@ -911,6 +929,8 @@
D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */, D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */,
D03B0D5F1D631A6900955575 /* Serialization.swift in Sources */, D03B0D5F1D631A6900955575 /* Serialization.swift in Sources */,
D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */, D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */,
D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */,
D01AC91D1DD5DA5E00E8160F /* RequestMessageActionCallback.swift in Sources */,
D09BB6B61DB0428000A905C0 /* PendingMessageUploadedContent.swift in Sources */, D09BB6B61DB0428000A905C0 /* PendingMessageUploadedContent.swift in Sources */,
D0B843B71DA7FF30005F29E1 /* NBMetadataCoreMapper.m in Sources */, D0B843B71DA7FF30005F29E1 /* NBMetadataCoreMapper.m in Sources */,
D0AB0B921D65E9FA002C78E7 /* ManagedServiceViews.swift in Sources */, D0AB0B921D65E9FA002C78E7 /* ManagedServiceViews.swift in Sources */,
@ -936,6 +956,7 @@
D03B0D671D631A8B00955575 /* AccountViewTracker.swift in Sources */, D03B0D671D631A8B00955575 /* AccountViewTracker.swift in Sources */,
D0B843BB1DA7FF30005F29E1 /* NBMetadataCoreTestMapper.m in Sources */, D0B843BB1DA7FF30005F29E1 /* NBMetadataCoreTestMapper.m in Sources */,
D03B0D101D62255C00955575 /* UpdatesApiUtils.swift in Sources */, D03B0D101D62255C00955575 /* UpdatesApiUtils.swift in Sources */,
D0F7AB2C1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */,
D03B0CBF1D62234A00955575 /* Log.swift in Sources */, D03B0CBF1D62234A00955575 /* Log.swift in Sources */,
D0B843B51DA7FF30005F29E1 /* NBMetadataCore.m in Sources */, D0B843B51DA7FF30005F29E1 /* NBMetadataCore.m in Sources */,
D03B0CD61D62245300955575 /* TelegramUser.swift in Sources */, D03B0CD61D62245300955575 /* TelegramUser.swift in Sources */,
@ -943,6 +964,7 @@
D03B0CE41D62249F00955575 /* TextEntitiesMessageAttribute.swift in Sources */, D03B0CE41D62249F00955575 /* TextEntitiesMessageAttribute.swift in Sources */,
D03B0CD31D62244300955575 /* Namespaces.swift in Sources */, D03B0CD31D62244300955575 /* Namespaces.swift in Sources */,
D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */, D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */,
D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */,
D0B843971DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift in Sources */, D0B843971DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift in Sources */,
D09BB6B41DB02C2B00A905C0 /* PendingMessageManager.swift in Sources */, D09BB6B41DB02C2B00A905C0 /* PendingMessageManager.swift in Sources */,
D0B843851DA6EDC4005F29E1 /* CachedChannelData.swift in Sources */, D0B843851DA6EDC4005F29E1 /* CachedChannelData.swift in Sources */,
@ -1040,6 +1062,7 @@
D0B418B91D7E05AD004562A4 /* SearchMessages.swift in Sources */, D0B418B91D7E05AD004562A4 /* SearchMessages.swift in Sources */,
D0B8443F1DAB91EF005F29E1 /* UpdateGroup.swift in Sources */, D0B8443F1DAB91EF005F29E1 /* UpdateGroup.swift in Sources */,
D03C53731DAD5CA9004C17B3 /* CachedGroupData.swift in Sources */, D03C53731DAD5CA9004C17B3 /* CachedGroupData.swift in Sources */,
D0F7AB2D1DCE889D009AD9A1 /* EditedMessageAttribute.swift in Sources */,
D0B844121DAB91CD005F29E1 /* Log.swift in Sources */, D0B844121DAB91CD005F29E1 /* Log.swift in Sources */,
D0B8444D1DAB9206005F29E1 /* JoinChannel.swift in Sources */, D0B8444D1DAB9206005F29E1 /* JoinChannel.swift in Sources */,
D03C53721DAD5CA9004C17B3 /* CachedUserData.swift in Sources */, D03C53721DAD5CA9004C17B3 /* CachedUserData.swift in Sources */,
@ -1047,6 +1070,7 @@
D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */, D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */,
D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */, D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */,
D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */, D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */,
D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */,
D073CE6D1DCBCF17007511FD /* InlineBotMessageAttribute.swift in Sources */, D073CE6D1DCBCF17007511FD /* InlineBotMessageAttribute.swift in Sources */,
D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */, D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */,
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */, D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
@ -1175,7 +1199,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0; SWIFT_VERSION = 3.0.1;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
}; };
name = Hockeyapp; name = Hockeyapp;
@ -1318,7 +1342,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0; SWIFT_VERSION = 3.0.1;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
}; };
name = Debug; name = Debug;
@ -1352,7 +1376,7 @@
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0; SWIFT_VERSION = 3.0.1;
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include"; USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
}; };
name = Release; name = Release;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0800" LastUpgradeVersion = "0820"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0800" LastUpgradeVersion = "0820"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -240,6 +240,8 @@ private var declaredEncodables: Void = {
declareEncodable(LocalFileReferenceMediaResource.self, f: { LocalFileReferenceMediaResource(decoder: $0) }) declareEncodable(LocalFileReferenceMediaResource.self, f: { LocalFileReferenceMediaResource(decoder: $0) })
declareEncodable(OutgoingMessageInfoAttribute.self, f: { OutgoingMessageInfoAttribute(decoder: $0) }) declareEncodable(OutgoingMessageInfoAttribute.self, f: { OutgoingMessageInfoAttribute(decoder: $0) })
declareEncodable(ForwardSourceInfoAttribute.self, f: { ForwardSourceInfoAttribute(decoder: $0) }) declareEncodable(ForwardSourceInfoAttribute.self, f: { ForwardSourceInfoAttribute(decoder: $0) })
declareEncodable(EditedMessageAttribute.self, f: { EditedMessageAttribute(decoder: $0) })
declareEncodable(ReplyMarkupMessageAttribute.self, f: { ReplyMarkupMessageAttribute(decoder: $0) })
return return
}() }()
@ -359,7 +361,7 @@ public class Account {
public let graphicsThreadPool = ThreadPool(threadCount: 3, threadPriority: 0.1) public let graphicsThreadPool = ThreadPool(threadCount: 3, threadPriority: 0.1)
//let imageCache: ImageCache = ImageCache(maxResidentSize: 5 * 1024 * 1024) //let imageCache: ImageCache = ImageCache(maxResidentSize: 5 * 1024 * 1024)
public var applicationSpecificData: Any? public var applicationContext: Any?
public let settings: AccountSettings = defaultAccountSettings() public let settings: AccountSettings = defaultAccountSettings()
@ -518,4 +520,10 @@ public func setupAccount(_ account: Account, fetchCachedResourceRepresentation:
account.managedContactsDisposable.set(manageContacts(network: account.network, postbox: account.postbox).start()) account.managedContactsDisposable.set(manageContacts(network: account.network, postbox: account.postbox).start())
account.managedStickerPacksDisposable.set(manageStickerPacks(network: account.network, postbox: account.postbox).start()) account.managedStickerPacksDisposable.set(manageStickerPacks(network: account.network, postbox: account.postbox).start())
/*account.network.request(Api.functions.help.getScheme(version: 0)).start(next: { result in
if case let .scheme(text, _, _, _) = result {
print("\(text)")
}
})*/
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
#else
import Postbox
import SwiftSignalKit
#endif
private func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) {
if let fromImage = from as? TelegramMediaImage, let toImage = to as? TelegramMediaImage {
if let fromLargestRepresentation = largestImageRepresentation(fromImage.representations), let toLargestRepresentation = largestImageRepresentation(toImage.representations) {
postbox.mediaBox.moveResourceData(from: fromLargestRepresentation.resource.id, to: toLargestRepresentation.resource.id)
}
} else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile {
postbox.mediaBox.moveResourceData(from: fromFile.resource.id, to: toFile.resource.id)
}
}
func applyUpdateMessage(postbox: Postbox, stateManager: StateManager, message: Message, result: Api.Updates) -> Signal<Void, NoError> {
let messageId = result.rawMessageIds.first
let apiMessage = result.messages.first
return postbox.modify { modifier -> Void in
var updatedTimestamp: Int32?
if let apiMessage = apiMessage {
switch apiMessage {
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _):
updatedTimestamp = date
case .messageEmpty:
break
case let .messageService(_, _, _, _, _, date, _):
updatedTimestamp = date
}
} else {
switch result {
case let .updateShortSentMessage(_, _, _, _, date, _, _):
updatedTimestamp = date
default:
break
}
}
modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId
if let messageId = messageId {
updatedId = MessageId(peerId: currentMessage.id.peerId, namespace: Namespaces.Message.Cloud, id: messageId)
} else {
updatedId = currentMessage.id
}
let media: [Media]
let attributes: [MessageAttribute]
let text: String
if let apiMessage = apiMessage, let updatedMessage = StoreMessage(apiMessage: apiMessage) {
media = updatedMessage.media
attributes = updatedMessage.attributes
text = updatedMessage.text
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities) = result {
let (_, mediaValue) = textAndMediaFromApiMedia(apiMedia)
if let mediaValue = mediaValue {
media = [mediaValue]
} else {
media = []
}
var updatedAttributes: [MessageAttribute] = currentMessage.attributes
if let entities = entities, !entities.isEmpty {
for i in 0 ..< updatedAttributes.count {
if updatedAttributes[i] is TextEntitiesMessageAttribute {
updatedAttributes.remove(at: i)
break
}
}
updatedAttributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)))
}
attributes = updatedAttributes
text = currentMessage.text
} else {
media = currentMessage.media
attributes = currentMessage.attributes
text = currentMessage.text
}
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)
}
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
}
return StoreMessage(id: updatedId, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)
})
if let updatedTimestamp = updatedTimestamp {
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, timestamp: updatedTimestamp)
}
stateManager.addUpdates(result)
}
}

View File

@ -46,7 +46,7 @@ public final class CachedUserData: CachedPeerData {
extension CachedUserData { extension CachedUserData {
convenience init(apiUserFull: Api.UserFull) { convenience init(apiUserFull: Api.UserFull) {
switch apiUserFull { switch apiUserFull {
case let .userFull(_, _, about, _, _, _, apiBotInfo): case let .userFull(_, _, about, _, _, _, apiBotInfo, commonChatsCount):
let botInfo: BotInfo? let botInfo: BotInfo?
if let apiBotInfo = apiBotInfo { if let apiBotInfo = apiBotInfo {
botInfo = BotInfo(apiBotInfo: apiBotInfo) botInfo = BotInfo(apiBotInfo: apiBotInfo)

View File

@ -139,7 +139,7 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource {
} }
var apiInputLocation: Api.InputFileLocation { var apiInputLocation: Api.InputFileLocation {
return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash) return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, version: 0)
} }
public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int) { public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int) {

View File

@ -0,0 +1,22 @@
import Foundation
#if os(macOS)
import PostboxMac
#else
import Postbox
#endif
public class EditedMessageAttribute: MessageAttribute {
public let date: Int32
init(date: Int32) {
self.date = date
}
required public init(decoder: Decoder) {
self.date = decoder.decodeInt32ForKey("d")
}
public func encode(_ encoder: Encoder) {
encoder.encodeInt32(self.date, forKey: "d")
}
}

View File

@ -44,16 +44,6 @@ private final class PendingMessageRequestDependencyTag: NetworkRequestDependency
} }
} }
private func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) {
if let fromImage = from as? TelegramMediaImage, let toImage = to as? TelegramMediaImage {
if let fromLargestRepresentation = largestImageRepresentation(fromImage.representations), let toLargestRepresentation = largestImageRepresentation(toImage.representations) {
postbox.mediaBox.moveResourceData(from: fromLargestRepresentation.resource.id, to: toLargestRepresentation.resource.id)
}
} else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile {
postbox.mediaBox.moveResourceData(from: fromFile.resource.id, to: toFile.resource.id)
}
}
public final class PendingMessageManager { public final class PendingMessageManager {
private let network: Network private let network: Network
private let postbox: Postbox private let postbox: Postbox
@ -307,83 +297,7 @@ public final class PendingMessageManager {
let messageId = result.rawMessageIds.first let messageId = result.rawMessageIds.first
let apiMessage = result.messages.first let apiMessage = result.messages.first
return postbox.modify { modifier -> Void in return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) |> afterDisposed { [weak self] in
var updatedTimestamp: Int32?
if let apiMessage = apiMessage {
switch apiMessage {
case let .message(_, _, _, _, _, _, _, date, _, _, _, _, _, _):
updatedTimestamp = date
case .messageEmpty:
break
case let .messageService(_, _, _, _, _, date, _):
updatedTimestamp = date
}
} else {
switch result {
case let .updateShortSentMessage(_, _, _, _, date, _, _):
updatedTimestamp = date
default:
break
}
}
modifier.updateMessage(message.id, update: { currentMessage in
let updatedId: MessageId
if let messageId = messageId {
updatedId = MessageId(peerId: currentMessage.id.peerId, namespace: Namespaces.Message.Cloud, id: messageId)
} else {
updatedId = currentMessage.id
}
let media: [Media]
let attributes: [MessageAttribute]
let text: String
if let apiMessage = apiMessage, let updatedMessage = StoreMessage(apiMessage: apiMessage) {
media = updatedMessage.media
attributes = updatedMessage.attributes
text = updatedMessage.text
} else if case let .updateShortSentMessage(_, _, _, _, _, apiMedia, entities) = result {
let (_, mediaValue) = textAndMediaFromApiMedia(apiMedia)
if let mediaValue = mediaValue {
media = [mediaValue]
} else {
media = []
}
var updatedAttributes: [MessageAttribute] = currentMessage.attributes
if let entities = entities, !entities.isEmpty {
for i in 0 ..< updatedAttributes.count {
if updatedAttributes[i] is TextEntitiesMessageAttribute {
updatedAttributes.remove(at: i)
break
}
}
updatedAttributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)))
}
attributes = updatedAttributes
text = currentMessage.text
} else {
media = currentMessage.media
attributes = currentMessage.attributes
text = currentMessage.text
}
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)
}
if let fromMedia = currentMessage.media.first, let toMedia = media.first {
applyMediaResourceChanges(from: fromMedia, to: toMedia, postbox: postbox)
}
return StoreMessage(id: updatedId, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: currentMessage.tags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media)
})
if let updatedTimestamp = updatedTimestamp {
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, timestamp: updatedTimestamp)
}
} |> afterDisposed { [weak self] in
stateManager.addUpdates(result)
if let strongSelf = self { if let strongSelf = self {
strongSelf.queue.async { strongSelf.queue.async {
if let context = strongSelf.peerSummaryContexts[message.id.peerId] { if let context = strongSelf.peerSummaryContexts[message.id.peerId] {

View File

@ -56,7 +56,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, image
case let .progress(progress): case let .progress(progress):
return .progress(progress) return .progress(progress)
case let .inputFile(file): case let .inputFile(file):
return .content(message, .media(Api.InputMedia.inputMediaUploadedPhoto(file: file, caption: message.text))) return .content(message, .media(Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: file, caption: message.text, stickers: nil)))
} }
} }
} else { } else {
@ -75,7 +75,9 @@ private func inputDocumentAttributesFromFile(_ file: TelegramMediaFile) -> [Api.
case let .ImageSize(size): case let .ImageSize(size):
attributes.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) attributes.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
case let .Sticker(displayText): case let .Sticker(displayText):
attributes.append(.documentAttributeSticker(alt: displayText, stickerset: .inputStickerSetEmpty)) attributes.append(.documentAttributeSticker(flags: 0, alt: displayText, stickerset: .inputStickerSetEmpty, maskCoords: nil))
case .HasLinkedStickers:
attributes.append(.documentAttributeHasStickers)
case let .Video(duration, size): case let .Video(duration, size):
attributes.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height))) attributes.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
case let .Audio(isVoice, duration, title, performer, waveform): case let .Audio(isVoice, duration, title, performer, waveform):
@ -95,9 +97,6 @@ private func inputDocumentAttributesFromFile(_ file: TelegramMediaFile) -> [Api.
waveformBuffer = Buffer(data: waveform.makeData()) waveformBuffer = Buffer(data: waveform.makeData())
} }
attributes.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer)) attributes.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
break
case .Unknown:
break
} }
} }
return attributes return attributes
@ -110,7 +109,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, file:
case let .progress(progress): case let .progress(progress):
return .progress(progress) return .progress(progress)
case let .inputFile(inputFile): case let .inputFile(inputFile):
return .content(message, .media(Api.InputMedia.inputMediaUploadedDocument(file: inputFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFile(file), caption: message.text))) return .content(message, .media(Api.InputMedia.inputMediaUploadedDocument(flags: 0, file: inputFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFile(file), caption: message.text, stickers: nil)))
} }
} }
} }

View File

@ -0,0 +1,220 @@
import Foundation
#if os(macOS)
import PostboxMac
#else
import Postbox
#endif
/*
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton;
keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton;
keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton;
keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton;
keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
*/
/*
replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup;
replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup;
replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector<KeyboardButtonRow> = ReplyMarkup;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
*/
public enum ReplyMarkupButtonAction: Coding {
case text
case url(String)
case callback(MemoryBuffer)
case requestPhone
case requestMap
case switchInline(samePeer: Bool, query: String)
case openWebApp
public init(decoder: Decoder) {
switch decoder.decodeInt32ForKey("v") as Int32 {
case 0:
self = .text
case 1:
self = .url(decoder.decodeStringForKey("u"))
case 2:
self = .callback(decoder.decodeBytesForKey("d") ?? MemoryBuffer())
case 3:
self = .requestPhone
case 4:
self = .requestMap
case 5:
self = .switchInline(samePeer: decoder.decodeInt32ForKey("s") != 0, query: decoder.decodeStringForKey("q"))
case 6:
self = .openWebApp
default:
self = .text
}
}
public func encode(_ encoder: Encoder) {
switch self {
case .text:
encoder.encodeInt32(0, forKey: "v")
case let .url(url):
encoder.encodeInt32(1, forKey: "v")
encoder.encodeString(url, forKey: "u")
case let .callback(data):
encoder.encodeInt32(2, forKey: "v")
encoder.encodeBytes(data, forKey: "d")
case .requestPhone:
encoder.encodeInt32(3, forKey: "v")
case .requestMap:
encoder.encodeInt32(4, forKey: "v")
case let .switchInline(samePeer, query):
encoder.encodeInt32(5, forKey: "v")
encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s")
case .openWebApp:
encoder.encodeInt32(6, forKey: "v")
}
}
}
public struct ReplyMarkupButton: Coding {
public let title: String
public let action: ReplyMarkupButtonAction
init(title: String, action: ReplyMarkupButtonAction) {
self.title = title
self.action = action
}
public init(decoder: Decoder) {
self.title = decoder.decodeStringForKey(".t")
self.action = ReplyMarkupButtonAction(decoder: decoder)
}
public func encode(_ encoder: Encoder) {
encoder.encodeString(self.title, forKey: ".t")
self.action.encode(encoder)
}
}
public struct ReplyMarkupRow: Coding {
public let buttons: [ReplyMarkupButton]
init(buttons: [ReplyMarkupButton]) {
self.buttons = buttons
}
public init(decoder: Decoder) {
self.buttons = decoder.decodeObjectArrayWithDecoderForKey("b")
}
public func encode(_ encoder: Encoder) {
encoder.encodeObjectArray(self.buttons, forKey: "b")
}
}
public struct ReplyMarkupMessageFlags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let once = ReplyMarkupMessageFlags(rawValue: 1 << 0)
public static let personal = ReplyMarkupMessageFlags(rawValue: 1 << 1)
public static let setupReply = ReplyMarkupMessageFlags(rawValue: 1 << 2)
public static let inline = ReplyMarkupMessageFlags(rawValue: 1 << 3)
}
public class ReplyMarkupMessageAttribute: MessageAttribute {
public let rows: [ReplyMarkupRow]
public let flags: ReplyMarkupMessageFlags
init(rows: [ReplyMarkupRow], flags: ReplyMarkupMessageFlags) {
self.rows = rows
self.flags = flags
}
public required init(decoder: Decoder) {
self.rows = decoder.decodeObjectArrayWithDecoderForKey("r")
self.flags = ReplyMarkupMessageFlags(rawValue: decoder.decodeInt32ForKey("f"))
}
public func encode(_ encoder: Encoder) {
encoder.encodeObjectArray(self.rows, forKey: "r")
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
}
}
extension ReplyMarkupButton {
init(apiButton: Api.KeyboardButton) {
switch apiButton {
case let .keyboardButton(text):
self.init(title: text, action: .text)
case let .keyboardButtonCallback(text, data):
let memory = malloc(data.size)!
memcpy(memory, data.data, data.size)
let dataBuffer = MemoryBuffer(memory: memory, capacity: data.size, length: data.size, freeWhenDone: true)
self.init(title: text, action: .callback(dataBuffer))
case let .keyboardButtonRequestGeoLocation(text):
self.init(title: text, action: .requestMap)
case let .keyboardButtonRequestPhone(text):
self.init(title: text, action: .requestPhone)
case let .keyboardButtonSwitchInline(flags, text, query):
self.init(title: text, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query))
case let .keyboardButtonUrl(text, url):
self.init(title: text, action: .url(url))
case let .keyboardButtonGame(text):
self.init(title: text, action: .openWebApp)
}
}
}
extension ReplyMarkupRow {
init(apiRow: Api.KeyboardButtonRow) {
switch apiRow {
case let .keyboardButtonRow(buttons):
self.init(buttons: buttons.map { ReplyMarkupButton(apiButton: $0) })
}
}
}
extension ReplyMarkupMessageAttribute {
convenience init(apiMarkup: Api.ReplyMarkup) {
var rows: [ReplyMarkupRow] = []
var flags = ReplyMarkupMessageFlags()
switch apiMarkup {
case let .replyKeyboardMarkup(markupFlags, apiRows):
rows = apiRows.map { ReplyMarkupRow(apiRow: $0) }
if (markupFlags & (1 << 1)) != 0 {
flags.insert(.once)
}
if (markupFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
case let .replyInlineMarkup(apiRows):
rows = apiRows.map { ReplyMarkupRow(apiRow: $0) }
flags.insert(.inline)
case let .replyKeyboardForceReply(forceReplyFlags):
if (forceReplyFlags & (1 << 1)) != 0 {
flags.insert(.once)
}
if (forceReplyFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
case let .replyKeyboardHide(hideFlags):
if (hideFlags & (1 << 2)) != 0 {
flags.insert(.personal)
}
}
self.init(rows: rows, flags: flags)
}
}

View File

@ -0,0 +1,43 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public func requestEditMessage(account: Account, messageId: MessageId, text: String) -> Signal<Bool, NoError> {
return account.postbox.loadedPeerWithId(messageId.peerId)
|> take(1)
|> mapToSignal { peer in
if let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.editMessage(flags: (1 << 11), peer: inputPeer, id: messageId.id, message: text, replyMarkup: nil, entities: nil))
|> map { result -> Api.Updates? in
return result
}
|> `catch` { error -> Signal<Api.Updates?, MTRpcError> in
if error.errorDescription == "MESSAGE_NOT_MODIFIED" {
return .single(nil)
} else {
return .fail(error)
}
}
|> mapError { _ -> NoError in
return NoError()
}
|> mapToSignal { result -> Signal<Bool, NoError> in
if let result = result {
return .single(true)
account.stateManager.addUpdates(result)
} else {
return .single(false)
}
}
} else {
return .single(false)
}
}
}

View File

@ -0,0 +1,32 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public func requestMessageActionCallback(account: Account, messageId: MessageId, data: MemoryBuffer?) -> Signal<Void, NoError> {
return account.postbox.loadedPeerWithId(messageId.peerId)
|> take(1)
|> mapToSignal { peer in
if let inputPeer = apiInputPeer(peer) {
var flags: Int32 = 0
var dataBuffer: Buffer?
if let data = data {
flags != Int32(1 << 0)
dataBuffer = Buffer(data: data.makeData())
}
return account.network.request(Api.functions.messages.getBotCallbackAnswer(flags: flags, peer: inputPeer, msgId: messageId.id, data: dataBuffer))
|> retryRequest
|> map { result in
return Void()
}
} else {
return .complete()
}
}
}

View File

@ -28,7 +28,7 @@ private func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]
messagePeers[peer.id] = peer messagePeers[peer.id] = peer
} }
return Message(stableId: 0, id: id, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, forwardInfo: nil, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary()) return Message(stableId: 0, stableVersion: 0, id: id, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, forwardInfo: nil, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
} }
public func searchMessages(account: Account, query: String) -> Signal<[Message], NoError> { public func searchMessages(account: Account, query: String) -> Signal<[Message], NoError> {

View File

@ -15,7 +15,7 @@ private func uploadedMessageMedia(network: Network, postbox: Postbox, media: Med
|> mapToSignal { result -> Signal<Api.InputMedia?, NoError> in |> mapToSignal { result -> Signal<Api.InputMedia?, NoError> in
switch result { switch result {
case let .inputFile(file): case let .inputFile(file):
return .single(Api.InputMedia.inputMediaUploadedPhoto(file: file, caption: "")) return .single(Api.InputMedia.inputMediaUploadedPhoto(flags: 0, file: file, caption: "", stickers: nil))
default: default:
return .complete() return .complete()
} }

View File

@ -20,7 +20,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 53 return 60
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {
@ -56,7 +56,8 @@ public class Serialization: NSObject, MTSerialization {
return { response -> MTDatacenterAddressListData! in return { response -> MTDatacenterAddressListData! in
if let config = parse(Buffer(data: response)) { if let config = parse(Buffer(data: response)) {
switch config { switch config {
case let .config(_, _, _, _, dcOptions, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): //config flags:# date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int tmp_sessions:flags.0?int phonecalls_enabled:flags.1?true disabled_features:Vector<DisabledFeature> = Config;
case let .config(_, _, _, _, _, dcOptions, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
var addressList = [MTDatacenterAddress]() var addressList = [MTDatacenterAddress]()
for option in dcOptions { for option in dcOptions {
switch option { switch option {

View File

@ -37,6 +37,7 @@ private enum MutationOperation {
case AddMessages([StoreMessage], AddMessagesLocation) case AddMessages([StoreMessage], AddMessagesLocation)
case DeleteMessagesWithGlobalIds([Int32]) case DeleteMessagesWithGlobalIds([Int32])
case DeleteMessages([MessageId]) case DeleteMessages([MessageId])
case EditMessage(MessageId, StoreMessage)
case UpdateMedia(MediaId, Media?) case UpdateMedia(MediaId, Media?)
case ReadInbox(MessageId) case ReadInbox(MessageId)
case ReadOutbox(MessageId) case ReadOutbox(MessageId)
@ -111,6 +112,10 @@ private struct MutableState {
self.addOperation(.DeleteMessages(messageIds)) self.addOperation(.DeleteMessages(messageIds))
} }
mutating func editMessage(_ id: MessageId, message: StoreMessage) {
self.addOperation(.EditMessage(id, message))
}
mutating func updateMedia(_ id: MediaId, media: Media?) { mutating func updateMedia(_ id: MediaId, media: Media?) {
self.addOperation(.UpdateMedia(id, media)) self.addOperation(.UpdateMedia(id, media))
} }
@ -177,7 +182,7 @@ private struct MutableState {
mutating func addOperation(_ operation: MutationOperation) { mutating func addOperation(_ operation: MutationOperation) {
switch operation { switch operation {
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .UpdateMedia, .ReadInbox, .ReadOutbox, .ResetReadState, .MergePeerPresences: case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .ReadInbox, .ReadOutbox, .ResetReadState, .MergePeerPresences:
break break
case let .AddMessages(messages, _): case let .AddMessages(messages, _):
for message in messages { for message in messages {
@ -297,6 +302,9 @@ private func peerIdsFromDifference(_ difference: Api.updates.Difference) -> Set<
peerIds.insert(peerId) peerIds.insert(peerId)
} }
} }
case let .differenceTooLong(pts):
assertionFailure()
break
} }
return peerIds return peerIds
@ -339,6 +347,8 @@ private func associatedMessageIdsFromDifference(_ difference: Api.updates.Differ
} }
} }
} }
case .differenceTooLong:
break
} }
return messageIds return messageIds
@ -373,6 +383,8 @@ private func peersWithNewMessagesFromDifference(_ difference: Api.updates.Differ
peerIds.insert(messageId.peerId) peerIds.insert(messageId.peerId)
} }
} }
case .differenceTooLong:
break
} }
return peerIds return peerIds
@ -572,6 +584,9 @@ private func finalStateWithDifference(account: Account, state: MutableState, dif
case let .state(pts, qts, date, seq, _): case let .state(pts, qts, date, seq, _):
updatedState.updateState(AuthorizedAccountState.State(pts: pts, qts: qts, date: date, seq: seq)) updatedState.updateState(AuthorizedAccountState.State(pts: pts, qts: qts, date: date, seq: seq))
} }
case .differenceTooLong:
assertionFailure()
break
} }
updatedState.mergeChats(chats) updatedState.mergeChats(chats)
@ -693,8 +708,37 @@ private func finalStateWithUpdates(account: Account, state: MutableState, update
channelsToPoll.insert(peerId) channelsToPoll.insert(peerId)
} }
} }
case let .updateEditChannelMessage(apiMessage, pts, ptsCount):
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id {
let peerId = messageId.peerId
if let previousState = updatedState.channelStates[peerId] {
if previousState.pts >= pts {
//trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update")
} else if previousState.pts + ptsCount == pts {
updatedState.editMessage(messageId, message: message)
updatedState.updateChannelState(peerId, state: previousState.setPts(pts))
} else {
if !channelsToPoll.contains(peerId) {
trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole")
channelsToPoll.insert(peerId)
//updatedMissingUpdates = true
}
}
} else {
if !channelsToPoll.contains(peerId) {
//trace("State", what: "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) state unknown")
channelsToPoll.insert(peerId)
}
}
} else {
trace("State", what: "Invalid updateEditChannelMessage")
}
case let .updateDeleteMessages(messages, _, _): case let .updateDeleteMessages(messages, _, _):
updatedState.deleteMessagesWithGlobalIds(messages) updatedState.deleteMessagesWithGlobalIds(messages)
case let .updateEditMessage(apiMessage, _, _):
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id {
updatedState.editMessage(messageId, message: message)
}
case let .updateNewChannelMessage(message, pts, ptsCount): case let .updateNewChannelMessage(message, pts, ptsCount):
if let message = StoreMessage(apiMessage: message) { if let message = StoreMessage(apiMessage: message) {
if let previousState = updatedState.channelStates[message.id.peerId] { if let previousState = updatedState.channelStates[message.id.peerId] {
@ -928,7 +972,7 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Mut
private func pollChannel(_ account: Account, peer: Peer, state: MutableState) -> Signal<MutableState, NoError> { private func pollChannel(_ account: Account, peer: Peer, state: MutableState) -> Signal<MutableState, NoError> {
if let inputChannel = apiInputChannel(peer) { if let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.updates.getChannelDifference(channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: 20)) return account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: 20))
|> retryRequest |> retryRequest
|> map { difference -> MutableState in |> map { difference -> MutableState in
var updatedState = state var updatedState = state
@ -1097,7 +1141,7 @@ private func optimizedOperations(_ operations: [MutationOperation]) -> [Mutation
var currentAddMessages: OptimizeAddMessagesState? var currentAddMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings: case .AddHole, .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ResetReadState, .UpdatePeerNotificationSettings:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -1147,6 +1191,8 @@ private func replayFinalState(_ modifier: Modifier, finalState: MutableState) ->
modifier.deleteMessagesWithGlobalIds(ids) modifier.deleteMessagesWithGlobalIds(ids)
case let .DeleteMessages(ids): case let .DeleteMessages(ids):
modifier.deleteMessages(ids) modifier.deleteMessages(ids)
case let .EditMessage(id, message):
modifier.updateMessage(id, update: { _ in message })
case let .UpdateMedia(id, media): case let .UpdateMedia(id, media):
modifier.updateMedia(id, update: media) modifier.updateMedia(id, update: media)
case let .ReadInbox(messageId): case let .ReadInbox(messageId):
@ -1212,7 +1258,7 @@ private func pollDifference(_ account: Account) -> Signal<Void, NoError> {
|> take(1) |> take(1)
|> mapToSignal { state -> Signal<Void, NoError> in |> mapToSignal { state -> Signal<Void, NoError> in
if let authorizedState = (state as! AuthorizedAccountState).state { if let authorizedState = (state as! AuthorizedAccountState).state {
let request = account.network.request(Api.functions.updates.getDifference(pts: authorizedState.pts, date: authorizedState.date, qts: authorizedState.qts)) let request = account.network.request(Api.functions.updates.getDifference(flags: 0, pts: authorizedState.pts, ptsTotalLimit: nil, date: authorizedState.date, qts: authorizedState.qts))
|> retryRequest |> retryRequest
return request |> mapToSignal { difference -> Signal<Void, NoError> in return request |> mapToSignal { difference -> Signal<Void, NoError> in
return initialStateWithDifference(account, difference: difference) return initialStateWithDifference(account, difference: difference)

View File

@ -129,7 +129,7 @@ extension Api.Message {
} }
switch action { switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear: case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionHistoryClear, .messageActionGameScore:
break break
case let .messageActionChannelMigrateFrom(_, chatId): case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)) result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId))
@ -222,6 +222,8 @@ func textAndMediaFromApiMedia(_ media: Api.MessageMedia?) -> (String?, Media?) {
break break
case .messageMediaEmpty: case .messageMediaEmpty:
break break
case .messageMediaGame:
break
} }
} }
@ -261,10 +263,12 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
return result return result
} }
//message#c09be45f flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int = Message;
extension StoreMessage { extension StoreMessage {
convenience init?(apiMessage: Api.Message) { convenience init?(apiMessage: Api.Message) {
switch apiMessage { switch apiMessage {
case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, _, entities, views, _): case let .message(flags, id, fromId, toId, fwdFrom, viaBotId, replyToMsgId, date, message, media, replyMarkup, entities, views, editDate):
let peerId: PeerId let peerId: PeerId
var authorId: PeerId? var authorId: PeerId?
switch toId { switch toId {
@ -345,14 +349,25 @@ extension StoreMessage {
attributes.append(ViewCountMessageAttribute(count: Int(views))) attributes.append(ViewCountMessageAttribute(count: Int(views)))
} }
if let editDate = editDate {
attributes.append(EditedMessageAttribute(date: editDate))
}
if let entities = entities, !entities.isEmpty { if let entities = entities, !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))) attributes.append(TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)))
} }
if let replyMarkup = replyMarkup {
attributes.append(ReplyMarkupMessageAttribute(apiMarkup: replyMarkup))
}
var storeFlags = StoreMessageFlags() var storeFlags = StoreMessageFlags()
if (flags & 2) == 0 { if (flags & (1 << 1)) == 0 {
let _ = storeFlags.insert(.Incoming) let _ = storeFlags.insert(.Incoming)
} }
if (flags & (1 << 4)) != 0 {
let _ = storeFlags.insert(.Personal)
}
self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date, flags: storeFlags, tags: tagsForStoreMessage(medias), forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias) self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), timestamp: date, flags: storeFlags, tags: tagsForStoreMessage(medias), forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
case .messageEmpty: case .messageEmpty:

View File

@ -11,6 +11,7 @@ private let typeImageSize: Int32 = 2
private let typeAnimated: Int32 = 3 private let typeAnimated: Int32 = 3
private let typeVideo: Int32 = 4 private let typeVideo: Int32 = 4
private let typeAudio: Int32 = 5 private let typeAudio: Int32 = 5
private let typeHasLinkedStickers: Int32 = 6
public enum TelegramMediaFileAttribute: Coding { public enum TelegramMediaFileAttribute: Coding {
case FileName(fileName: String) case FileName(fileName: String)
@ -19,7 +20,7 @@ public enum TelegramMediaFileAttribute: Coding {
case Animated case Animated
case Video(duration: Int, size: CGSize) case Video(duration: Int, size: CGSize)
case Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: MemoryBuffer?) case Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: MemoryBuffer?)
case Unknown case HasLinkedStickers
public init(decoder: Decoder) { public init(decoder: Decoder) {
let type: Int32 = decoder.decodeInt32ForKey("t") let type: Int32 = decoder.decodeInt32ForKey("t")
@ -41,8 +42,10 @@ public enum TelegramMediaFileAttribute: Coding {
waveform = MemoryBuffer(copyOf: waveformBuffer) waveform = MemoryBuffer(copyOf: waveformBuffer)
} }
self = .Audio(isVoice: decoder.decodeInt32ForKey("iv") != 0, duration: Int(decoder.decodeInt32ForKey("du")), title: decoder.decodeStringForKey("ti"), performer: decoder.decodeStringForKey("pe"), waveform: waveform) self = .Audio(isVoice: decoder.decodeInt32ForKey("iv") != 0, duration: Int(decoder.decodeInt32ForKey("du")), title: decoder.decodeStringForKey("ti"), performer: decoder.decodeStringForKey("pe"), waveform: waveform)
case typeHasLinkedStickers:
self = .HasLinkedStickers
default: default:
self = .Unknown preconditionFailure()
} }
} }
@ -78,8 +81,8 @@ public enum TelegramMediaFileAttribute: Coding {
if let waveform = waveform { if let waveform = waveform {
encoder.encodeBytes(waveform, forKey: "wf") encoder.encodeBytes(waveform, forKey: "wf")
} }
case .Unknown: case .HasLinkedStickers:
break encoder.encodeInt32(typeHasLinkedStickers, forKey: "t")
} }
} }
} }
@ -219,8 +222,10 @@ public func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.Docu
switch attribute { switch attribute {
case let .documentAttributeFilename(fileName): case let .documentAttributeFilename(fileName):
result.append(.FileName(fileName: fileName)) result.append(.FileName(fileName: fileName))
case let .documentAttributeSticker(alt, _): case let .documentAttributeSticker(flags, alt, stickerSet, maskCoords):
result.append(.Sticker(displayText: alt)) result.append(.Sticker(displayText: alt))
case .documentAttributeHasStickers:
result.append(.HasLinkedStickers)
case let .documentAttributeImageSize(w, h): case let .documentAttributeImageSize(w, h):
result.append(.ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h)))) result.append(.ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h))))
case .documentAttributeAnimated: case .documentAttributeAnimated:
@ -243,7 +248,7 @@ public func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.Docu
public func telegramMediaFileFromApiDocument(_ document: Api.Document) -> TelegramMediaFile? { public func telegramMediaFileFromApiDocument(_ document: Api.Document) -> TelegramMediaFile? {
switch document { switch document {
case let .document(id, accessHash, _, mimeType, size, thumb, dcId, attributes): case let .document(id, accessHash, _, mimeType, size, thumb, dcId, _, attributes):
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: telegramMediaImageRepresentationsFromApiSizes([thumb]), mimeType: mimeType, size: Int(size), attributes: telegramMediaFileAttributesFromApiAttributes(attributes)) return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: telegramMediaImageRepresentationsFromApiSizes([thumb]), mimeType: mimeType, size: Int(size), attributes: telegramMediaFileAttributesFromApiAttributes(attributes))
case .documentEmpty: case .documentEmpty:
return nil return nil

View File

@ -136,7 +136,7 @@ public func telegramMediaImageRepresentationsFromApiSizes(_ sizes: [Api.PhotoSiz
public func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? { public func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? {
switch photo { switch photo {
case let .photo(id, accessHash, _, sizes): case let .photo(flags, id, accessHash, date, sizes):
return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: telegramMediaImageRepresentationsFromApiSizes(sizes)) return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: telegramMediaImageRepresentationsFromApiSizes(sizes))
case .photoEmpty: case .photoEmpty:
return nil return nil

View File

@ -214,9 +214,11 @@ public final class TelegramMediaWebpage: Media {
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? { func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? {
switch webpage { switch webpage {
case .webPageNotModified:
return nil
case let .webPagePending(id, date): case let .webPagePending(id, date):
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date)) return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date))
case let .webPage(_, id, url, displayUrl, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document): case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage):
var embedSize: CGSize? var embedSize: CGSize?
if let embedWidth = embedWidth, let embedHeight = embedHeight { if let embedWidth = embedWidth, let embedHeight = embedHeight {
embedSize = CGSize(width: CGFloat(embedWidth), height: CGFloat(embedHeight)) embedSize = CGSize(width: CGFloat(embedWidth), height: CGFloat(embedHeight))

View File

@ -16,7 +16,8 @@ func fetchAndUpdateCachedPeerData(peerId: PeerId, network: Network, postbox: Pos
|> mapToSignal { result -> Signal<Void, NoError> in |> mapToSignal { result -> Signal<Void, NoError> in
return postbox.modify { modifier -> Void in return postbox.modify { modifier -> Void in
switch result { switch result {
case let .userFull(_, _, _, _, _, notifySettings, _):
case let .userFull(_, _, _, _, _, notifySettings, _, commonChatCount):
modifier.updatePeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) modifier.updatePeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)])
} }
modifier.updatePeerCachedData(peerIds: [peerId], update: { peerId, _ in modifier.updatePeerCachedData(peerIds: [peerId], update: { peerId, _ in

View File

@ -129,6 +129,8 @@ extension Api.Update {
return (pts, ptsCount) return (pts, ptsCount)
case let .updateReadMessages(_, pts, ptsCount): case let .updateReadMessages(_, pts, ptsCount):
return (pts, ptsCount) return (pts, ptsCount)
case let .updateEditMessage(_, pts, ptsCount):
return (pts, ptsCount)
case let .updateReadMessagesContents(_, pts, ptsCount): case let .updateReadMessagesContents(_, pts, ptsCount):
return (pts, ptsCount) return (pts, ptsCount)
case let .updateRestoreMessages(_, pts): case let .updateRestoreMessages(_, pts):

View File

@ -140,6 +140,10 @@ extension Api.Update {
return message return message
case let .updateNewChannelMessage(message, _, _): case let .updateNewChannelMessage(message, _, _):
return message return message
case let .updateEditMessage(message, _, _):
return message
case let .updateEditChannelMessage(message, _, _):
return message
default: default:
return nil return nil
} }
@ -174,6 +178,10 @@ extension Api.Update {
return message.peerIds return message.peerIds
case let .updateNewMessage(message, _, _): case let .updateNewMessage(message, _, _):
return message.peerIds return message.peerIds
case let .updateEditMessage(message, _, _):
return message.peerIds
case let .updateEditChannelMessage(message, _, _):
return message.peerIds
//case let .updateReadChannelInbox(channelId, _): //case let .updateReadChannelInbox(channelId, _):
// return [PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)] // return [PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)]
case let .updateUserName(userId, _, _, _): case let .updateUserName(userId, _, _, _):

View File

@ -6,7 +6,7 @@ import Foundation
#endif #endif
public class ViewCountMessageAttribute: MessageAttribute { public class ViewCountMessageAttribute: MessageAttribute {
let count: Int public let count: Int
public var associatedMessageIds: [MessageId] = [] public var associatedMessageIds: [MessageId] = []