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

This commit is contained in:
Ilya Laktyushin 2021-01-22 15:55:15 +03:00
commit 0c093d48ff
42 changed files with 4226 additions and 3546 deletions

View file

@ -5918,3 +5918,5 @@ Sorry for the inconvenience.";
"Channel.ErrorAdminsTooMuch" = "Sorry, too many administrators in this channel.";
"Conversation.AddMembers" = "Add Members";
"Conversation.ImportedMessageHint" = "The messages was imported from another app. We can't guarantee it's real.";

View file

@ -0,0 +1,18 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatHistoryImportTasks",
module_name = "ChatHistoryImportTasks",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,6 @@
import Foundation
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit

View file

@ -20,6 +20,7 @@ swift_library(
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
],
visibility = [
"//visibility:public",

View file

@ -22,6 +22,7 @@ public final class ChatImportActivityScreen: ViewController {
private let animationNode: AnimatedStickerNode
private let radialStatus: RadialStatusNode
private let radialCheck: RadialStatusNode
private let radialStatusBackground: ASImageNode
private let radialStatusText: ImmediateTextNode
private let progressText: ImmediateTextNode
@ -46,6 +47,7 @@ public final class ChatImportActivityScreen: ViewController {
self.animationNode = AnimatedStickerNode()
self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear)
self.radialCheck = RadialStatusNode(backgroundNodeColor: .clear)
self.radialStatusBackground = ASImageNode()
self.radialStatusBackground.isUserInteractionEnabled = false
self.radialStatusBackground.displaysAsynchronously = false
@ -90,6 +92,7 @@ public final class ChatImportActivityScreen: ViewController {
self.addSubnode(self.animationNode)
self.addSubnode(self.radialStatusBackground)
self.addSubnode(self.radialStatus)
self.addSubnode(self.radialCheck)
self.addSubnode(self.radialStatusText)
self.addSubnode(self.progressText)
self.addSubnode(self.statusText)
@ -159,6 +162,8 @@ public final class ChatImportActivityScreen: ViewController {
self.animationNode.updateLayout(size: iconSize)
self.radialStatus.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - radialStatusSize.width) / 2.0), y: hideIcon ? contentOriginY : (contentOriginY + iconSize.height + maxIconStatusSpacing)), size: radialStatusSize)
let checkSize: CGFloat = 130.0
self.radialCheck.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - checkSize) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - checkSize) / 2.0)), size: CGSize(width: checkSize, height: checkSize))
self.radialStatusBackground.frame = self.radialStatus.frame
self.radialStatusText.frame = CGRect(origin: CGPoint(x: self.radialStatus.frame.minX + floor((self.radialStatus.frame.width - radialStatusTextSize.width) / 2.0), y: self.radialStatus.frame.minY + floor((self.radialStatus.frame.height - radialStatusTextSize.height) / 2.0)), size: radialStatusTextSize)
@ -187,6 +192,18 @@ public final class ChatImportActivityScreen: ViewController {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate)
self.radialStatus.transitionToState(.progress(color: self.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: self.totalProgress, cancelEnabled: false), animated: animated, synchronous: true, completion: {})
if isDone {
self.radialCheck.transitionToState(.progress(color: .clear, lineWidth: 6.0, value: self.totalProgress, cancelEnabled: false), animated: false, synchronous: true, completion: {})
self.radialCheck.transitionToState(.check(self.presentationData.theme.list.itemAccentColor), animated: animated, synchronous: true, completion: {})
let transition: ContainedViewLayoutTransition
if animated {
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
transition.updateAlpha(node: self.radialStatusText, alpha: 0.0)
}
}
}
}
@ -201,9 +218,11 @@ public final class ChatImportActivityScreen: ViewController {
private let peerId: PeerId
private let archive: Archive
private let mainEntry: TempBoxFile
private let mainEntrySize: Int
private let otherEntries: [(Entry, String, ChatHistoryImport.MediaType)]
private let totalBytes: Int
private var pendingEntries = Set<String>()
private var pendingEntries: [String: (Int, Float)] = [:]
private let disposable = MetaDisposable()
@ -222,7 +241,21 @@ public final class ChatImportActivityScreen: ViewController {
self.mainEntry = mainEntry
self.otherEntries = otherEntries
self.pendingEntries = Set(otherEntries.map { $0.1 })
if let size = fileSize(self.mainEntry.path) {
self.mainEntrySize = size
} else {
self.mainEntrySize = 0
}
for (entry, fileName, _) in otherEntries {
self.pendingEntries[fileName] = (entry.uncompressedSize, 0.0)
}
var totalBytes: Int = self.mainEntrySize
for entry in self.otherEntries {
totalBytes += entry.0.uncompressedSize
}
self.totalBytes = totalBytes
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -253,14 +286,7 @@ public final class ChatImportActivityScreen: ViewController {
}
override public func loadDisplayNode() {
var totalBytes: Int = 0
if let size = fileSize(self.mainEntry.path) {
totalBytes += size
}
for entry in self.otherEntries {
totalBytes += entry.0.uncompressedSize
}
self.displayNode = Node(controller: self, context: self.context, totalBytes: totalBytes)
self.displayNode = Node(controller: self, context: self.context, totalBytes: self.totalBytes)
self.displayNodeDidLoad()
}
@ -298,8 +324,8 @@ public final class ChatImportActivityScreen: ViewController {
return .generic
}
}
|> mapToSignal { session -> Signal<String, ImportError> in
var importSignal: Signal<String, ImportError> = .single("")
|> mapToSignal { session -> Signal<(String, Float), ImportError> in
var importSignal: Signal<(String, Float), ImportError> = .single(("", 0.0))
for (entry, fileName, mediaType) in otherEntries {
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
@ -315,14 +341,14 @@ public final class ChatImportActivityScreen: ViewController {
return EmptyDisposable
}
let uploadedMedia = unpackedFile
|> mapToSignal { tempFile -> Signal<String, ImportError> in
|> mapToSignal { tempFile -> Signal<(String, Float), ImportError> in
return ChatHistoryImport.uploadMedia(account: context.account, session: session, file: tempFile, fileName: fileName, type: mediaType)
|> mapError { _ -> ImportError in
return .generic
}
|> map { _ -> String in
|> map { progress -> (String, Float) in
return (fileName, progress)
}
|> then(.single(fileName))
}
importSignal = importSignal
@ -334,19 +360,28 @@ public final class ChatImportActivityScreen: ViewController {
|> mapError { _ -> ImportError in
return .generic
}
|> map { _ -> String in
|> map { _ -> (String, Float) in
})
return importSignal
}
|> deliverOnMainQueue).start(next: { [weak self] fileName in
|> deliverOnMainQueue).start(next: { [weak self] (fileName, progress) in
guard let strongSelf = self else {
return
}
strongSelf.pendingEntries.remove(fileName)
if let (fileSize, _) = strongSelf.pendingEntries[fileName] {
strongSelf.pendingEntries[fileName] = (fileSize, progress)
}
var totalDoneBytes = strongSelf.mainEntrySize
for (_, sizeAndProgress) in strongSelf.pendingEntries {
totalDoneBytes += Int(Float(sizeAndProgress.0) * sizeAndProgress.1)
}
var totalProgress: CGFloat = 1.0
if !strongSelf.otherEntries.isEmpty {
totalProgress = CGFloat(strongSelf.otherEntries.count - strongSelf.pendingEntries.count) / CGFloat(strongSelf.otherEntries.count)
totalProgress = CGFloat(totalDoneBytes) / CGFloat(strongSelf.totalBytes)
}
strongSelf.controllerNode.updateProgress(totalProgress: totalProgress, isDone: false, animated: true)
}, error: { [weak self] _ in

View file

@ -106,6 +106,373 @@ static void generate_public_key(unsigned char key[32], id<EncryptionProvider> pr
}
}
typedef enum {
HelloGenerationCommandInvalid = 0,
HelloGenerationCommandString = 1,
HelloGenerationCommandZero = 2,
HelloGenerationCommandRandom = 3,
HelloGenerationCommandDomain = 4,
HelloGenerationCommandGrease = 5,
HelloGenerationCommandKey = 6,
HelloGenerationCommandPushLengthPosition = 7,
HelloGenerationCommandPopLengthPosition = 8
} HelloGenerationCommand;
typedef struct {
int position;
} HelloParseState;
static HelloGenerationCommand parseCommand(NSString *string, HelloParseState *state) {
if (state->position + 1 >= string.length) {
return HelloGenerationCommandInvalid;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == 'S') {
return HelloGenerationCommandString;
} else if (c == 'Z') {
return HelloGenerationCommandZero;
} else if (c == 'R') {
return HelloGenerationCommandRandom;
} else if (c == 'D') {
return HelloGenerationCommandDomain;
} else if (c == 'G') {
return HelloGenerationCommandGrease;
} else if (c == 'K') {
return HelloGenerationCommandKey;
} else if (c == '[') {
return HelloGenerationCommandPushLengthPosition;
} else if (c == ']') {
return HelloGenerationCommandPopLengthPosition;
} else {
return HelloGenerationCommandInvalid;
}
}
static bool parseSpace(NSString *string, HelloParseState *state) {
if (state->position + 1 >= string.length) {
return false;
}
bool hadSpace = false;
while (true) {
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == ' ') {
hadSpace = true;
} else {
if (hadSpace) {
return true;
} else {
return false;
}
}
}
return true;
}
static bool parseEndlineOrEnd(NSString *string, HelloParseState *state) {
if (state->position == string.length) {
return true;
} else if (state->position + 1 >= string.length) {
return false;
} else {
unichar c = [string characterAtIndex:state->position];
state->position += 1;
return c == '\n';
}
}
static bool parseHexByte(unichar c, uint8_t *output) {
if (c >= '0' && c <= '9') {
*output = (uint8_t)(c - '0');
} else if (c >= 'a' && c <= 'f') {
*output = (uint8_t)(c - 'a');
} else if (c >= 'A' && c <= 'F') {
*output = (uint8_t)(c - 'A');
} else {
return false;
}
return true;
}
static NSData *parseHexStringArgument(NSString *string, HelloParseState *state) {
if (state->position >= string.length) {
return nil;
}
NSMutableData *data = [[NSMutableData alloc] init];
while (true) {
if (state->position == string.length) {
return data;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == '\\') {
if (state->position >= string.length) {
return nil;
}
c = [string characterAtIndex:state->position];
state->position += 1;
if (c == 'x') {
if (state->position >= string.length) {
return nil;
}
unichar d1 = [string characterAtIndex:state->position];
state->position += 1;
if (state->position >= string.length) {
return nil;
}
unichar d0 = [string characterAtIndex:state->position];
state->position += 1;
uint8_t c1 = 0;
if (!parseHexByte(d1, &c1)) {
return nil;
}
uint8_t c0 = 0;
if (!parseHexByte(d0, &c0)) {
return nil;
}
uint8_t byteValue = (c1 << 4) | c0;
[data appendBytes:&byteValue length:1];
} else {
return nil;
}
} else if (c == '\n') {
return data;
} else {
return nil;
}
}
return nil;
}
static bool parseIntArgument(NSString *string, HelloParseState *state, int *output) {
if (state->position >= string.length) {
return false;
}
int value = 0;
while (true) {
if (state->position == string.length) {
*output = value;
return true;
}
unichar c = [string characterAtIndex:state->position];
state->position += 1;
if (c == '\n') {
*output = value;
return true;
} else if (c >= '0' && c <= '9') {
value *= 10;
value += c;
} else {
return false;
}
}
return false;
}
static NSData *executeGenerationCode(id<EncryptionProvider> provider, NSData *domain) {
NSString *code = @"S \"\\x16\\x03\\x01\\x02\\x00\\x01\\x00\\x01\\xfc\\x03\\x03\\n"
"Z 32"
"S \"\\x20\"\n"
"R 32\n"
"S \"\\x00\\x36\"\n"
"G 0\n"
"S \"\\x13\\x01\\x13\\x02\\x13\\x03\\xc0\\x2c\\xc0\\x2b\\xcc\\xa9\\xc0\\x30\\xc0\\x2f\\xcc\\xa8\\xc0\\x24\\xc0\\x23\\xc0\\x0a\\xc0\\x09\\xc0\\x28\\xc0\\x27\\xc0\\x14\\xc0\\x13\\x00\\x9d\\x00\\x9c\\x00\\x3d\\x00\\x3c\\x00\\x35\\x00\\x2f\\xc0\\x08\\xc0\\x12\\x00\\x0a\\x01\\x00\\x01\\x7d\"\n"
"G 2\n"
"S \"\\x00\\x00\\x00\\x00\"\n"
"[\n"
"[\n"
"S \"\\x00\"\n"
"[\n"
"D\n"
"]\n"
"]\n"
"]\n"
"S \"\\x00\\x17\\x00\\x00\\xff\\x01\\x00\\x01\\x00\\x00\\x0a\\x00\\x0c\\x00\\x0a\"\n"
"G 4\n"
"S \"\\x00\\x1d\\x00\\x17\\x00\\x18\\x00\\x19\\x00\\x0b\\x00\\x02\\x01\\x00\\x00\\x10\\x00\\x0e\\x00\\x0c\\x02\\x68\\x32\\x08\\x68\\x74\\x74\\x70\\x2f\\x31\\x2e\\x31\\x00\\x05\\x00\\x05\\x01\\x00\\x00\\x00\\x00\\x00\\x0d\\x00\\x18\\x00\\x16\\x04\\x03\\x08\\x04\\x04\\x01\\x05\\x03\\x02\\x03\\x08\\x05\\x08\\x05\\x05\\x01\\x08\\x06\\x06\\x01\\x02\\x01\\x00\\x12\\x00\\x00\\x00\\x33\\x00\\x2b\\x00\\x29\"\n"
"G 4\n"
"S \"\\x00\\x01\\x00\\x00\\x1d\\x00\\x20\"\n"
"K\n"
"S \"\\x00\\x2d\\x00\\x02\\x01\\x01\\x00\\x2b\\x00\\x0b\\x0a\"\n"
"G 6\n"
"S \"\\x03\\x04\\x03\\x03\\x03\\x02\\x03\\x01\"\n"
"G 3\n"
"S \"\\x00\\x01\\x00\\x00\\x15\"";
int greaseCount = 8;
NSMutableData *greaseData = [[NSMutableData alloc] initWithLength:greaseCount];
uint8_t *greaseBytes = (uint8_t *)greaseData.mutableBytes;
int result;
result = SecRandomCopyBytes(nil, greaseData.length, greaseData.mutableBytes);
for (int i = 0; i < greaseData.length; i++) {
uint8_t c = greaseBytes[i];
c = (c & 0xf0) | 0x0a;
greaseBytes[i] = c;
}
for (int i = 1; i < greaseData.length; i += 2) {
if (greaseBytes[i] == greaseBytes[i - 1]) {
greaseBytes[i] &= 0x10;
}
}
NSMutableData *resultData = [[NSMutableData alloc] init];
NSMutableArray<NSNumber *> *lengthStack = [[NSMutableArray alloc] init];
HelloParseState state;
state.position = 0;
while (true) {
if (state.position >= code.length) {
break;
} else {
HelloGenerationCommand command = parseCommand(code, &state);
switch (command) {
case HelloGenerationCommandString: {
if (!parseSpace(code, &state)) {
return nil;
}
NSData *data = parseHexStringArgument(code, &state);
if (data == nil) {
return nil;
}
[resultData appendData:data];
break;
}
case HelloGenerationCommandZero: {
if (!parseSpace(code, &state)) {
return false;
}
int zeroLength = 0;
if (!parseIntArgument(code, &state, &zeroLength)) {
return nil;
}
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:zeroLength];
[resultData appendData:zeroData];
break;
}
case HelloGenerationCommandRandom: {
if (!parseSpace(code, &state)) {
return nil;
}
int randomLength = 0;
if (!parseIntArgument(code, &state, &randomLength)) {
return nil;
}
NSMutableData *randomData = [[NSMutableData alloc] initWithLength:randomLength];
int randomResult = SecRandomCopyBytes(kSecRandomDefault, randomLength, randomData.mutableBytes);
if (randomResult != errSecSuccess) {
return nil;
}
[resultData appendData:randomData];
break;
}
case HelloGenerationCommandDomain: {
[resultData appendData:domain];
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
break;
}
case HelloGenerationCommandGrease: {
if (!parseSpace(code, &state)) {
return nil;
}
int greaseIndex = 0;
if (!parseIntArgument(code, &state, &greaseIndex)) {
return nil;
}
if (greaseIndex < 0 || greaseIndex >= greaseCount) {
return nil;
}
[resultData appendBytes:&greaseBytes[greaseIndex] length:1];
[resultData appendBytes:&greaseBytes[greaseIndex] length:1];
break;
}
case HelloGenerationCommandKey: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
NSMutableData *key = [[NSMutableData alloc] initWithLength:32];
generate_public_key(key.mutableBytes, provider);
[resultData appendData:key];
break;
}
case HelloGenerationCommandPushLengthPosition: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
[lengthStack addObject:@(resultData.length)];
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:2];
[resultData appendData:zeroData];
break;
}
case HelloGenerationCommandPopLengthPosition: {
if (!parseEndlineOrEnd(code, &state)) {
return nil;
}
if (lengthStack.count == 0) {
return nil;
}
int position = [lengthStack[lengthStack.count - 1] intValue];
uint16_t calculatedLength = resultData.length - 2 - position;
((uint8_t *)resultData.mutableBytes)[position] = ((uint8_t *)&calculatedLength)[1];
((uint8_t *)resultData.mutableBytes)[position + 1] = ((uint8_t *)&calculatedLength)[0];
[lengthStack removeLastObject];
break;
}
case HelloGenerationCommandInvalid: {
return nil;
}
default: {
return nil;
}
}
}
}
int paddingLengthPosition = (int)resultData.length;
[lengthStack addObject:@(resultData.length)];
NSMutableData *zeroData = [[NSMutableData alloc] initWithLength:2];
[resultData appendData:zeroData];
while (resultData.length < 517) {
uint8_t zero = 0;
[resultData appendBytes:&zero length:1];
}
uint16_t calculatedLength = resultData.length - 2 - paddingLengthPosition;
((uint8_t *)resultData.mutableBytes)[paddingLengthPosition] = ((uint8_t *)&calculatedLength)[1];
((uint8_t *)resultData.mutableBytes)[paddingLengthPosition + 1] = ((uint8_t *)&calculatedLength)[0];
return resultData;
}
@interface MTTcpConnectionData : NSObject
@property (nonatomic, strong, readonly) NSString *ip;

View file

@ -11,8 +11,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
dict[-213431562] = { return Api.ChatFull.parse_chatFull($0) }
dict[2055070967] = { return Api.ChatFull.parse_channelFull($0) }
dict[-213431562] = { return Api.ChatFull.parse_chatFull($0) }
dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) }
dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) }
dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) }
@ -660,7 +660,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
dict[-905587442] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsAndroidPay($0) }
dict[-1966921727] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsGooglePay($0) }
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }

View file

@ -2278,31 +2278,11 @@ public extension Api {
}
public enum ChatFull: TypeConstructorDescription {
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?)
case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?)
case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
if boxed {
buffer.appendInt32(-213431562)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
break
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
if boxed {
buffer.appendInt32(2055070967)
@ -2340,71 +2320,38 @@ public extension Api {
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 21) != 0 {call!.serialize(buffer, true)}
break
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
if boxed {
buffer.appendInt32(-213431562)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false)
serializeString(about, buffer: buffer, boxed: false)
participants.serialize(buffer, true)
if Int(flags) & Int(1 << 2) != 0 {chatPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 13) != 0 {exportedInvite!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(botInfo!.count))
for item in botInfo! {
item.serialize(buffer, true)
}}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 12) != 0 {call!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call)])
case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call):
return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call)])
case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call):
return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call)])
}
}
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
}
else {
return nil
}
}
public static func parse_channelFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
@ -2511,6 +2458,59 @@ public extension Api {
return nil
}
}
public static func parse_chatFull(_ reader: BufferReader) -> ChatFull? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.ChatParticipants?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.ChatParticipants
}
var _5: Api.Photo?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _6: Api.PeerNotifySettings?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
}
var _7: Api.ExportedChatInvite?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.ExportedChatInvite
} }
var _8: [Api.BotInfo]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotInfo.self)
} }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_10 = reader.readInt32() }
var _11: Api.InputGroupCall?
if Int(_1!) & Int(1 << 12) != 0 {if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.InputGroupCall
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 13) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 11) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11)
}
else {
return nil
}
}
}
public enum PollResults: TypeConstructorDescription {
@ -19217,7 +19217,7 @@ public extension Api {
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsAndroidPay(paymentToken: Api.DataJSON, googleTransactionId: String)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -19241,12 +19241,11 @@ public extension Api {
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-905587442)
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
serializeString(googleTransactionId, buffer: buffer, boxed: false)
break
}
}
@ -19259,8 +19258,8 @@ public extension Api {
return ("inputPaymentCredentials", [("flags", flags), ("data", data)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData)])
case .inputPaymentCredentialsAndroidPay(let paymentToken, let googleTransactionId):
return ("inputPaymentCredentialsAndroidPay", [("paymentToken", paymentToken), ("googleTransactionId", googleTransactionId)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken)])
}
}
@ -19307,17 +19306,14 @@ public extension Api {
return nil
}
}
public static func parse_inputPaymentCredentialsAndroidPay(_ reader: BufferReader) -> InputPaymentCredentials? {
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsAndroidPay(paymentToken: _1!, googleTransactionId: _2!)
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil

View file

@ -3874,6 +3874,41 @@ public extension Api {
})
}
public static func deleteChatUser(flags: Int32, chatId: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-986430054)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(chatId, buffer: buffer, boxed: false)
userId.serialize(buffer, true)
return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", flags), ("chatId", chatId), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) {
let buffer = Buffer()
buffer.appendInt32(1785900140)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetDate", offsetDate), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in
let reader = BufferReader(buffer)
var result: Api.messages.ExportedChatInvites?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites
}
return result
})
}
public static func exportChatInvite(flags: Int32, peer: Api.InputPeer, expireDate: Int32?, usageLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.ExportedChatInvite>) {
let buffer = Buffer()
buffer.appendInt32(347716823)
@ -3909,43 +3944,6 @@ public extension Api {
})
}
public static func getChatInviteImporters(peer: Api.InputPeer, link: String, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ChatInviteImporters>) {
let buffer = Buffer()
buffer.appendInt32(654013065)
peer.serialize(buffer, true)
serializeString(link, buffer: buffer, boxed: false)
serializeInt32(offsetDate, buffer: buffer, boxed: false)
offsetUser.serialize(buffer, true)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in
let reader = BufferReader(buffer)
var result: Api.messages.ChatInviteImporters?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters
}
return result
})
}
public static func getExportedChatInvites(flags: Int32, peer: Api.InputPeer, adminId: Api.InputUser?, offsetDate: Int32?, offsetLink: String?, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ExportedChatInvites>) {
let buffer = Buffer()
buffer.appendInt32(1785900140)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {adminId!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(offsetDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeString(offsetLink!, buffer: buffer, boxed: false)}
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getExportedChatInvites", parameters: [("flags", flags), ("peer", peer), ("adminId", adminId), ("offsetDate", offsetDate), ("offsetLink", offsetLink), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvites? in
let reader = BufferReader(buffer)
var result: Api.messages.ExportedChatInvites?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ExportedChatInvites
}
return result
})
}
public static func deleteRevokedExportedChatInvites(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(1375999075)
@ -3975,6 +3973,24 @@ public extension Api {
})
}
public static func getChatInviteImporters(peer: Api.InputPeer, link: String, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.ChatInviteImporters>) {
let buffer = Buffer()
buffer.appendInt32(654013065)
peer.serialize(buffer, true)
serializeString(link, buffer: buffer, boxed: false)
serializeInt32(offsetDate, buffer: buffer, boxed: false)
offsetUser.serialize(buffer, true)
serializeInt32(limit, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in
let reader = BufferReader(buffer)
var result: Api.messages.ChatInviteImporters?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.ChatInviteImporters
}
return result
})
}
public static func discardEncryption(flags: Int32, chatId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-208425312)
@ -4004,22 +4020,6 @@ public extension Api {
})
}
public static func deleteChatUser(flags: Int32, chatId: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-986430054)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(chatId, buffer: buffer, boxed: false)
userId.serialize(buffer, true)
return (FunctionDescription(name: "messages.deleteChatUser", parameters: [("flags", flags), ("chatId", chatId), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func deletePhoneCallHistory(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
let buffer = Buffer()
buffer.appendInt32(1828657989)
@ -4034,20 +4034,6 @@ public extension Api {
})
}
public static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImportParsed>) {
let buffer = Buffer()
buffer.appendInt32(1140726259)
serializeString(importHead, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", importHead)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in
let reader = BufferReader(buffer)
var result: Api.messages.HistoryImportParsed?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed
}
return result
})
}
public static func initHistoryImport(peer: Api.InputPeer, file: Api.InputFile, mediaCount: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImport>) {
let buffer = Buffer()
buffer.appendInt32(873008187)
@ -4095,6 +4081,20 @@ public extension Api {
return result
})
}
public static func checkHistoryImport(importHead: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HistoryImportParsed>) {
let buffer = Buffer()
buffer.appendInt32(1140726259)
serializeString(importHead, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.checkHistoryImport", parameters: [("importHead", importHead)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.HistoryImportParsed? in
let reader = BufferReader(buffer)
var result: Api.messages.HistoryImportParsed?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.HistoryImportParsed
}
return result
})
}
}
public struct channels {
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {

View file

@ -419,7 +419,7 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
}
} else {
if let peer = transaction.getPeer(peerId) {
if let _ = peer as? TelegramChannel {
if let channel = peer as? TelegramChannel, channel.participationStatus != .member {
if let notificationSettings = transaction.getPeerNotificationSettings(peerId) {
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
Logger.shared.log("State", "Peer \(peerId) (\(peer.debugDisplayTitle) has no stored inclusion, using synthesized one")
@ -447,7 +447,8 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
}
}
return AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
var state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
return state
}
func initialStateWithUpdateGroups(postbox: Postbox, groups: [UpdateGroup]) -> Signal<AccountMutableState, NoError> {

View file

@ -16,6 +16,36 @@ public enum ChatHistoryImport {
case generic
}
//messages.historyImportParsed flags:# pm:flags.0?true group:flags.1?true title:flags.1?string = messages.HistoryImportParsed;
public enum ParsedInfo {
case privateChat(title: String?)
case group(title: String?)
}
public enum GetInfoError {
case generic
case parseError
}
public static func getInfo(account: Account, header: String) -> Signal<ParsedInfo, GetInfoError> {
return account.network.request(Api.functions.messages.checkHistoryImport(importHead: header))
|> mapError { _ -> GetInfoError in
return .generic
}
|> mapToSignal { result -> Signal<ParsedInfo, GetInfoError> in
switch result {
case let .historyImportParsed(flags, title):
if (flags & (1 << 0)) != 0 {
return .single(.privateChat(title: title))
} else if (flags & (1 << 1)) != 0 {
return .single(.group(title: title))
} else {
return .fail(.parseError)
}
}
}
}
public static func initSession(account: Account, peerId: PeerId, file: TempBoxFile, mediaCount: Int32) -> Signal<Session, InitImportError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> InitImportError in
@ -63,12 +93,12 @@ public enum ChatHistoryImport {
case generic
}
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, type: MediaType) -> Signal<Never, UploadMediaError> {
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, type: MediaType) -> Signal<Float, UploadMediaError> {
return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false)
|> mapError { _ -> UploadMediaError in
return .generic
}
|> mapToSignal { result -> Signal<Never, UploadMediaError> in
|> mapToSignal { result -> Signal<Float, UploadMediaError> in
let inputMedia: Api.InputMedia
switch result {
case let .inputFile(inputFile):
@ -91,8 +121,8 @@ public enum ChatHistoryImport {
}
inputMedia = .inputMediaUploadedDocument(flags: 0, file: inputFile, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil)
}
case .progress:
return .complete()
case let .progress(value):
return .single(value)
case .inputSecretFile:
return .fail(.generic)
}
@ -100,8 +130,8 @@ public enum ChatHistoryImport {
|> mapError { _ -> UploadMediaError in
return .generic
}
|> mapToSignal { result -> Signal<Never, UploadMediaError> in
return .complete()
|> mapToSignal { result -> Signal<Float, UploadMediaError> in
return .single(1.0)
}
}
}

View file

@ -104,6 +104,8 @@ public extension Message {
if let peer = self.peers[sourceReference.messageId.peerId] {
return peer
}
} else if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
return author
}
return self.author
}

View file

@ -215,6 +215,7 @@ swift_library(
"//Telegram:GeneratedSources",
"//third-party/ZIPFoundation:ZIPFoundation",
"//submodules/ChatImportUI:ChatImportUI",
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
"//submodules/DatePickerNode:DatePickerNode",
],
visibility = [

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ic_imported.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -371,6 +371,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
private var isEmbeddedTitleContentHidden = false
private let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>
private weak var currentImportMessageTooltip: UndoOverlayController?
public override var customData: Any? {
return self.chatLocation
@ -2208,6 +2210,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.present(MessageReactionListController(context: strongSelf.context, messageId: message.id, initialReactions: initialReactions), in: .window(.root))
}
})
}, displayImportedMessageTooltip: { [weak self] _ in
guard let strongSelf = self else {
return
}
if let _ = strongSelf.currentImportMessageTooltip {
} else {
let controller = UndoOverlayController(presentationData: strongSelf.presentationData, content: .importedMessage(text: strongSelf.presentationData.strings.Conversation_ImportedMessageHint), elevatedLayout: false, action: { _ in return false })
strongSelf.currentImportMessageTooltip = controller
strongSelf.present(controller, in: .current)
}
}, displaySwipeToReplyHint: { [weak self] in
if let strongSelf = self, let validLayout = strongSelf.validLayout, min(validLayout.size.width, validLayout.size.height) > 320.0 {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .swipeToReply(title: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintTitle, text: strongSelf.presentationData.strings.Conversation_SwipeToReplyHintText), elevatedLayout: false, action: { _ in return false }), in: .current)

View file

@ -103,6 +103,7 @@ public final class ChatControllerInteraction {
let performTextSelectionAction: (UInt32, NSAttributedString, TextSelectionAction) -> Void
let updateMessageLike: (MessageId, Bool) -> Void
let openMessageReactions: (MessageId) -> Void
let displayImportedMessageTooltip: (ASDisplayNode) -> Void
let displaySwipeToReplyHint: () -> Void
let dismissReplyMarkupMessage: (Message) -> Void
let openMessagePollResults: (MessageId, Data) -> Void
@ -192,6 +193,7 @@ public final class ChatControllerInteraction {
performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void,
updateMessageLike: @escaping (MessageId, Bool) -> Void,
openMessageReactions: @escaping (MessageId) -> Void,
displayImportedMessageTooltip: @escaping (ASDisplayNode) -> Void,
displaySwipeToReplyHint: @escaping () -> Void,
dismissReplyMarkupMessage: @escaping (Message) -> Void,
openMessagePollResults: @escaping (MessageId, Data) -> Void,
@ -271,6 +273,7 @@ public final class ChatControllerInteraction {
self.performTextSelectionAction = performTextSelectionAction
self.updateMessageLike = updateMessageLike
self.openMessageReactions = openMessageReactions
self.displayImportedMessageTooltip = displayImportedMessageTooltip
self.displaySwipeToReplyHint = displaySwipeToReplyHint
self.dismissReplyMarkupMessage = dismissReplyMarkupMessage
self.openMessagePollResults = openMessagePollResults
@ -320,6 +323,7 @@ public final class ChatControllerInteraction {
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -1168,6 +1168,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
actionButtonsNode.removeFromSupernode()
strongSelf.actionButtonsNode = nil
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
}

View file

@ -52,9 +52,15 @@ final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
return false
}
if let forwardInfo = self.forwardInfo, let otherForwardInfo = other.forwardInfo {
if forwardInfo.flags.contains(.isImported) == forwardInfo.flags.contains(.isImported) {
if forwardInfo.authorSignature != otherForwardInfo.authorSignature {
return false
if forwardInfo.flags.contains(.isImported) && forwardInfo.flags.contains(.isImported) == forwardInfo.flags.contains(.isImported) {
if let authorSignature = forwardInfo.authorSignature, let otherAuthorSignature = otherForwardInfo.authorSignature {
if authorSignature != otherAuthorSignature {
return false
}
} else if let authorId = forwardInfo.author?.id, let otherAuthorId = other.forwardInfo?.author?.id {
if authorId != otherAuthorId {
return false
}
}
} else {
return false
@ -74,7 +80,9 @@ final class ChatMessageAvatarAccessoryItem: ListViewAccessoryItem {
let node = ChatMessageAvatarAccessoryItemNode()
node.frame = CGRect(origin: CGPoint(), size: CGSize(width: 38.0, height: 38.0))
if let forwardInfo = self.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let authorSignature = forwardInfo.authorSignature, !authorSignature.isEmpty {
if let author = forwardInfo.author {
node.setPeer(context: self.context, theme: self.context.sharedContext.currentPresentationData.with({ $0 }).theme, synchronousLoad: synchronous, peer: author, authorOfMessage: self.messageReference, emptyColor: self.emptyColor, controllerInteraction: self.controllerInteraction)
} else if let authorSignature = forwardInfo.authorSignature, !authorSignature.isEmpty {
let components = authorSignature.components(separatedBy: " ")
if !components.isEmpty, !components[0].hasPrefix("+") {
var letters: [String] = []

View file

@ -56,10 +56,16 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
var isFile = false
inner: for media in message.media {
if let _ = media as? TelegramMediaImage {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
messageWithCaptionToAdd = (message, itemAttributes)
}
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else if let file = media as? TelegramMediaFile {
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
if isVideo {
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
messageWithCaptionToAdd = (message, itemAttributes)
}
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
} else {
var neighborSpacing: ChatMessageBubbleRelativePosition.NeighbourSpacing = .default
@ -1036,6 +1042,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
effectiveAuthor = source
displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), let author = forwardInfo.author {
ignoreForward = true
effectiveAuthor = author
displayAuthorInfo = !mergedTop.merged && incoming
} else if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature {
ignoreForward = true
effectiveAuthor = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: Int32(clamping: authorSignature.persistentHashValue)), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: UserInfoFlags())

View file

@ -319,6 +319,17 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
} else {
strongSelf.avatarNode.setCustomLetters(customLetters)
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
})

View file

@ -167,8 +167,24 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
private var theme: ChatPresentationThemeData?
private var layoutSize: CGSize?
private var tapGestureRecognizer: UITapGestureRecognizer?
var openReactions: (() -> Void)?
var openReplies: (() -> Void)?
var pressed: (() -> Void)? {
didSet {
if self.pressed != nil {
if self.tapGestureRecognizer == nil {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.tapGestureRecognizer = tapGestureRecognizer
self.view.addGestureRecognizer(tapGestureRecognizer)
}
} else if let tapGestureRecognizer = self.tapGestureRecognizer{
self.tapGestureRecognizer = nil
self.view.removeGestureRecognizer(tapGestureRecognizer)
}
}
}
override init() {
self.dateNode = TextNode()
@ -180,6 +196,12 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
self.addSubnode(self.dateNode)
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.pressed?()
}
}
func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize, _ reactions: [MessageReaction], _ replies: Int, _ isPinned: Bool) -> (CGSize, (Bool) -> Void) {
let dateLayout = TextNode.asyncLayout(self.dateNode)
@ -868,6 +890,11 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
return reactionButtonNode.view
}
}
if self.pressed != nil {
if self.bounds.contains(point) {
return self.view
}
}
return nil
}
}

View file

@ -51,6 +51,12 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
let _ = item.controllerInteraction.requestMessageUpdate(item.message.id)
}
}
self.interactiveFileNode.displayImportedTooltip = { [weak self] sourceNode in
if let strongSelf = self, let item = strongSelf.item {
let _ = item.controllerInteraction.displayImportedMessageTooltip(sourceNode)
}
}
}
required init?(coder aDecoder: NSCoder) {

View file

@ -84,6 +84,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
var toggleSelection: (Bool) -> Void = { _ in }
var activateLocalContent: () -> Void = { }
var requestUpdateLayout: (Bool) -> Void = { _ in }
var displayImportedTooltip: (ASDisplayNode) -> Void = { _ in }
private var context: AccountContext?
private var message: Message?
@ -729,6 +730,17 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
strongSelf.updateStatus(animated: isAnimated)
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
strongSelf.displayImportedTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
})

View file

@ -469,6 +469,17 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let telegramFile = updatedFile, previousAutomaticDownload != automaticDownload, automaticDownload {
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: item.context, message: item.message, file: telegramFile, userInitiated: false).start())
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
}

View file

@ -131,7 +131,11 @@ private func messagesShouldBeMerged(accountPeerId: PeerId, _ lhs: Message, _ rhs
lhsEffectiveTimestamp = lhsForwardInfo.date
rhsEffectiveTimestamp = rhsForwardInfo.date
sameAuthor = lhsForwardInfo.authorSignature == rhsForwardInfo.authorSignature
if let lhsAuthorId = lhsForwardInfo.author?.id, let rhsAuthorId = rhsForwardInfo.author?.id {
sameAuthor = lhsAuthorId == rhsAuthorId
} else if let lhsAuthorSignature = lhsForwardInfo.authorSignature, let rhsAuthorSignature = rhsForwardInfo.authorSignature {
sameAuthor = lhsAuthorSignature == rhsAuthorSignature
}
}
if lhs.id.peerId.isRepliesOrSavedMessages(accountPeerId: accountPeerId) {

View file

@ -426,6 +426,17 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.pinNode.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floor((imageFrame.size.width - pinSize.width) / 2.0), y: imageFrame.minY + floor(imageFrame.size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize)
pinApply()
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
})

View file

@ -308,6 +308,17 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
selectionNode.removeFromSupernode()
}
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
})

View file

@ -55,7 +55,16 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let titleFont = Font.medium(fontSize)
let textFont = Font.regular(fontSize)
let titleString = message.effectiveAuthor?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount
var titleString = message.effectiveAuthor?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
titleString = author.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)
} else if let authorSignature = forwardInfo.authorSignature {
titleString = authorSignature
}
}
let (textString, isMedia) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: message, strings: strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: context.account.peerId)
let placeholderColor: UIColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor

View file

@ -750,6 +750,17 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
actionButtonsNode.removeFromSupernode()
strongSelf.actionButtonsNode = nil
}
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.dateAndStatusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.dateAndStatusNode)
}
} else {
strongSelf.dateAndStatusNode.pressed = nil
}
}
})
}

View file

@ -277,8 +277,10 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let entities = entities {
attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont)
} else {
} else if !rawText.isEmpty {
attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor)
} else {
attributedText = NSAttributedString(string: " ", font: textFont, textColor: messageTheme.primaryTextColor)
}
var cutout: TextNodeCutout?
@ -339,6 +341,12 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom
}
if attributedText.string.isEmpty, var adjustedStatusFrameValue = adjustedStatusFrame {
adjustedStatusFrameValue.origin.y = 1.0
boundingSize.height = adjustedStatusFrameValue.maxY + 5.0
adjustedStatusFrame = adjustedStatusFrameValue
}
return (boundingSize, { [weak self] animation, _ in
if let strongSelf = self {
strongSelf.item = item
@ -407,6 +415,17 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
strongSelf.textAccessibilityOverlayNode.frame = textFrame
strongSelf.textAccessibilityOverlayNode.cachedLayout = textLayout
if let forwardInfo = item.message.forwardInfo, forwardInfo.flags.contains(.isImported) {
strongSelf.statusNode.pressed = {
guard let strongSelf = self else {
return
}
item.controllerInteraction.displayImportedMessageTooltip(strongSelf.statusNode)
}
} else {
strongSelf.statusNode.pressed = nil
}
}
})
})

View file

@ -442,6 +442,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -134,6 +134,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -127,6 +127,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -2038,6 +2038,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -76,7 +76,13 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
let message = messageView.message
var authorName = ""
var text = ""
if let author = message?.effectiveAuthor {
if let forwardInfo = message?.forwardInfo, forwardInfo.flags.contains(.isImported) {
if let author = forwardInfo.author {
authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
} else if let authorSignature = forwardInfo.authorSignature {
authorName = authorSignature
}
} else if let author = message?.effectiveAuthor {
authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
if let message = message {

View file

@ -381,6 +381,12 @@ public class ShareRootControllerImpl {
context.account.resetStateManagement()
}
/*if let application = UIApplication.value(forKeyPath: #keyPath(UIApplication.shared)) as? UIApplication {
let selector = NSSelectorFromString("openURL:")
let url = URL(string: "tg://open")!
application.perform(selector, with: url)
}*/
if let strongSelf = self, let inputItems = strongSelf.getExtensionContext()?.inputItems, inputItems.count == 1, let item = inputItems[0] as? NSExtensionItem, let attachments = item.attachments {
for attachment in attachments {
if attachment.hasItemConformingToTypeIdentifier(kUTTypeFileURL as String) {
@ -413,16 +419,6 @@ public class ShareRootControllerImpl {
let stickerRegex = try! NSRegularExpression(pattern: "[\\d]+-STICKER-.*?\\.webp")
let voiceRegex = try! NSRegularExpression(pattern: "[\\d]+-AUDIO-.*?\\.opus")
let groupVerificationRegexList = [
try! NSRegularExpression(pattern: "created this group"),
try! NSRegularExpression(pattern: "created group “(.*?)”"),
]
let groupCreationRegexList = [
try! NSRegularExpression(pattern: "created group “(.*?)”"),
try! NSRegularExpression(pattern: "] (.*?): Messages and calls are end-to-end encrypted")
]
var groupTitle: String?
var otherEntries: [(Entry, String, ChatHistoryImport.MediaType)] = []
var mainFile: TempBoxFile?
@ -435,29 +431,6 @@ public class ShareRootControllerImpl {
let tempFile = TempBox.shared.tempFile(fileName: entryPath)
if entryPath == "_chat.txt" {
let _ = try archive.extract(entry, to: URL(fileURLWithPath: tempFile.path))
if let fileContents = try? String(contentsOfFile: tempFile.path) {
let fullRange = NSRange(fileContents.startIndex ..< fileContents.endIndex, in: fileContents)
var isGroup = false
for regex in groupVerificationRegexList {
if let _ = regex.firstMatch(in: fileContents, options: [], range: fullRange) {
isGroup = true
break
}
}
if isGroup {
for regex in groupCreationRegexList {
if groupTitle != nil {
break
}
if let match = regex.firstMatch(in: fileContents, options: [], range: fullRange) {
let range = match.range(at: 1)
if let mappedRange = Range(range, in: fileContents) {
groupTitle = String(fileContents[mappedRange])
}
}
}
}
}
mainFile = tempFile
} else {
let entryFileName = (entryPath as NSString).lastPathComponent
@ -481,173 +454,207 @@ public class ShareRootControllerImpl {
}
} catch {
}
if let mainFile = mainFile {
if let groupTitle = groupTitle {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
var createNewGroupImpl: (() -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}, createNewGroup: {
createNewGroupImpl?()
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}
controller.peerSelected = { peer in
attemptSelectionImpl?(peer)
}
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) {
let mainFileHeader: String
if mainFileText.count < 1000 {
mainFileHeader = mainFileText
} else {
mainFileHeader = String(mainFileText[mainFileText.startIndex ..< mainFileText.index(mainFileText.startIndex, offsetBy: 1000)])
}
let _ = (ChatHistoryImport.getInfo(account: context.account, header: mainFileHeader)
|> deliverOnMainQueue).start(next: { parseInfo in
switch parseInfo {
case let .group(groupTitle):
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
var createNewGroupImpl: (() -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyGroups, .onlyManageable, .excludeDisabled, .doNotSearchMessages], hasContactSelector: false, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}, createNewGroup: {
createNewGroupImpl?()
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
}
attemptSelectionImpl = { peer in
var errorText: String?
if let channel = peer as? TelegramChannel {
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
} else {
errorText = "You need to be an admin of the group to import messages into it."
}
} else if let group = peer as? TelegramGroup {
switch group.role {
case .creator:
break
default:
errorText = "You need to be an admin of the group to import messages into it."
}
} else {
errorText = "You can't import history into this group."
}
if let errorText = errorText {
controller.peerSelected = { peer in
attemptSelectionImpl?(peer)
}
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
}
attemptSelectionImpl = { peer in
var errorText: String?
if let channel = peer as? TelegramChannel {
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
} else {
errorText = "You need to be an admin of the group to import messages into it."
}
} else if let group = peer as? TelegramGroup {
switch group.role {
case .creator:
break
default:
errorText = "You need to be an admin of the group to import messages into it."
}
} else {
errorText = "You can't import history into this group."
}
if let errorText = errorText {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})])
strongSelf.mainWindow?.present(controller, on: .root)
} else {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let text: String
if let groupTitle = groupTitle {
text = "Are you sure you want to import messages from **\(groupTitle)** into **\(peer.debugDisplayTitle)**?"
} else {
text = "Are you sure you want to import messages into **\(peer.debugDisplayTitle)**?"
}
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
beginWithPeer(peer.id)
})], parseMarkdown: true)
strongSelf.mainWindow?.present(controller, on: .root)
}
}
createNewGroupImpl = {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})])
strongSelf.mainWindow?.present(controller, on: .root)
} else {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: "Are you sure you want to import messages from **\(groupTitle)** into **\(peer.debugDisplayTitle)**?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
beginWithPeer(peer.id)
let resolvedGroupTitle: String
if let groupTitle = groupTitle {
resolvedGroupTitle = groupTitle
} else {
resolvedGroupTitle = "Group"
}
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Create Group and Import Messages", text: "Are you sure you want to create group **\(resolvedGroupTitle)** and import messages from another messaging app?", actions: [TextAlertAction(type: .defaultAction, title: "Create and Import", action: {
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: resolvedGroupTitle, description: nil, isForHistoryImport: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, NoError> in
return .single(nil)
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
if let strongSelf = self {
strongSelf.mainWindow?.present(controller, on: .root)
}
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
signal = signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).start(next: { peerId in
if let peerId = peerId {
beginWithPeer(peerId)
} else {
//TODO:localize
}
})
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})], parseMarkdown: true)
strongSelf.mainWindow?.present(controller, on: .root)
}
}
createNewGroupImpl = {
navigationController.viewControllers = [controller]
strongSelf.mainWindow?.present(navigationController, on: .root)
case let .privateChat(title):
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Create Group and Import Messages", text: "Are you sure you want to create group **\(groupTitle)** and import messages from another messaging app?", actions: [TextAlertAction(type: .defaultAction, title: "Create and Import", action: {
var signal: Signal<PeerId?, NoError> = createSupergroup(account: context.account, title: groupTitle, description: nil, isForHistoryImport: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<PeerId?, NoError> in
return .single(nil)
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
if let strongSelf = self {
strongSelf.mainWindow?.present(controller, on: .root)
}
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.15, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
signal = signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
let _ = (signal
|> deliverOnMainQueue).start(next: { peerId in
if let peerId = peerId {
beginWithPeer(peerId)
} else {
//TODO:localize
}
})
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})], parseMarkdown: true)
strongSelf.mainWindow?.present(controller, on: .root)
}
navigationController.viewControllers = [controller]
strongSelf.mainWindow?.present(navigationController, on: .root)
} else {
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}
controller.peerSelected = { peer in
attemptSelectionImpl?(peer)
}
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
let navigationController = NavigationController(mode: .single, theme: NavigationControllerTheme(presentationTheme: presentationData.theme))
//TODO:localize
var attemptSelectionImpl: ((Peer) -> Void)?
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeDisabled, .doNotSearchMessages], hasChatListSelector: false, hasContactSelector: true, hasGlobalSearch: false, title: "Import Chat", attemptSelection: { peer in
attemptSelectionImpl?(peer)
}, pretendPresentedInModal: true))
controller.customDismiss = {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
}
controller.peerSelected = { peer in
attemptSelectionImpl?(peer)
}
controller.navigationPresentation = .default
let beginWithPeer: (PeerId) -> Void = { peerId in
navigationController.view.endEditing(true)
navigationController.pushViewController(ChatImportActivityScreen(context: context, cancel: {
self?.getExtensionContext()?.completeRequest(returningItems: nil, completionHandler: nil)
}, peerId: peerId, archive: archive, mainEntry: mainFile, otherEntries: otherEntries))
}
attemptSelectionImpl = { [weak controller] peer in
controller?.inProgress = true
let _ = (ChatHistoryImport.checkPeerImport(account: context.account, peerId: peer.id)
|> deliverOnMainQueue).start(error: { error in
controller?.inProgress = false
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let errorText: String
switch error {
case .generic:
errorText = presentationData.strings.Login_UnknownError
case .userIsNotMutualContact:
errorText = "You can only import messages into private chats with users who added you in their contact list."
}
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})])
strongSelf.mainWindow?.present(controller, on: .root)
}, completed: {
controller?.inProgress = false
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let text: String
if let title = title {
text = "Are you sure you want to import messages from **\(title)** into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?"
} else {
text = "Are you sure you want to import messages into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?"
}
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
beginWithPeer(peer.id)
})], parseMarkdown: true)
strongSelf.mainWindow?.present(controller, on: .root)
})
}
navigationController.viewControllers = [controller]
strongSelf.mainWindow?.present(navigationController, on: .root)
}
attemptSelectionImpl = { [weak controller] peer in
controller?.inProgress = true
let _ = (ChatHistoryImport.checkPeerImport(account: context.account, peerId: peer.id)
|> deliverOnMainQueue).start(error: { error in
controller?.inProgress = false
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let errorText: String
switch error {
case .generic:
errorText = presentationData.strings.Login_UnknownError
case .userIsNotMutualContact:
errorText = "You can only import messages into private chats with users who added you in their contact list."
}
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})])
strongSelf.mainWindow?.present(controller, on: .root)
}, completed: {
controller?.inProgress = false
let presentationData = internalContext.sharedContext.currentPresentationData.with { $0 }
let controller = standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Import Messages", text: "Are you sure you want to import messages into the chat with **\(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))**?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}), TextAlertAction(type: .defaultAction, title: "Import", action: {
beginWithPeer(peer.id)
})], parseMarkdown: true)
strongSelf.mainWindow?.present(controller, on: .root)
})
}
navigationController.viewControllers = [controller]
strongSelf.mainWindow?.present(navigationController, on: .root)
}
}, error: { _ in
beginShare()
})
} else {
beginShare()
return

View file

@ -1245,6 +1245,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, performTextSelectionAction: { _, _, _ in
}, updateMessageLike: { _, _ in
}, openMessageReactions: { _ in
}, displayImportedMessageTooltip: { _ in
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in

View file

@ -26,6 +26,7 @@ public enum UndoOverlayContent {
case invitedToVoiceChat(context: AccountContext, peer: Peer, text: String)
case linkCopied(text: String)
case banned(text: String)
case importedMessage(text: String)
}
public enum UndoOverlayAction {

View file

@ -210,6 +210,18 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
case let .importedMessage(text):
self.avatarNode = nil
self.iconNode = ASImageNode()
self.iconNode?.displayWithoutProcessing = true
self.iconNode?.displaysAsynchronously = false
self.iconNode?.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/ImportedMessageTooltipIcon"), color: .white)
self.iconCheckNode = nil
self.animationNode = nil
self.animatedStickerNode = nil
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = false
self.originalRemainingSeconds = 5
case let .chatAddedToFolder(chatTitle, folderTitle):
self.avatarNode = nil
self.iconNode = nil
@ -495,7 +507,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned:
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage:
break
case .dice:
self.panelWrapperNode.clipsToBounds = true