mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
0c093d48ff
42 changed files with 4226 additions and 3546 deletions
|
|
@ -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.";
|
||||
|
|
|
|||
18
submodules/ChatHistoryImportTasks/BUILD
Normal file
18
submodules/ChatHistoryImportTasks/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ swift_library(
|
|||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -215,6 +215,7 @@ swift_library(
|
|||
"//Telegram:GeneratedSources",
|
||||
"//third-party/ZIPFoundation:ZIPFoundation",
|
||||
"//submodules/ChatImportUI:ChatImportUI",
|
||||
"//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks",
|
||||
"//submodules/DatePickerNode:DatePickerNode",
|
||||
],
|
||||
visibility = [
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_imported.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/ic_imported.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/ImportedMessageTooltipIcon.imageset/ic_imported.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] = []
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -2038,6 +2038,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1245,6 +1245,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}, performTextSelectionAction: { _, _, _ in
|
||||
}, updateMessageLike: { _, _ in
|
||||
}, openMessageReactions: { _ in
|
||||
}, displayImportedMessageTooltip: { _ in
|
||||
}, displaySwipeToReplyHint: {
|
||||
}, dismissReplyMarkupMessage: { _ in
|
||||
}, openMessagePollResults: { _, _ in
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue