diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index b287dde0bd..37156c372c 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + C2366C831E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */; }; + C2366C841E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */; }; + C2366C861E4F403C0097CCFF /* UsernameAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C851E4F403C0097CCFF /* UsernameAvailability.swift */; }; + C2366C871E4F403C0097CCFF /* UsernameAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C851E4F403C0097CCFF /* UsernameAvailability.swift */; }; + C2366C891E4F40480097CCFF /* SupportPeerId.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C881E4F40480097CCFF /* SupportPeerId.swift */; }; + C2366C8A1E4F40480097CCFF /* SupportPeerId.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2366C881E4F40480097CCFF /* SupportPeerId.swift */; }; C2A315C01E2E776A00D89000 /* RequestStartBot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01749581E1092BC0057C89A /* RequestStartBot.swift */; }; D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0CFF1D62255C00955575 /* ChannelState.swift */; }; D001F3E91E128A1C007A8C60 /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; }; @@ -372,6 +378,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupReturnAndLeft.swift; sourceTree = ""; }; + C2366C851E4F403C0097CCFF /* UsernameAvailability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsernameAvailability.swift; sourceTree = ""; }; + C2366C881E4F40480097CCFF /* SupportPeerId.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SupportPeerId.swift; sourceTree = ""; }; D003702A1DA42586004308D3 /* PhoneNumber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneNumber.swift; sourceTree = ""; }; D00C7CCB1E3620C30080C3D5 /* CachedChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CachedChannelParticipants.swift; sourceTree = ""; }; D00C7CCE1E3628180080C3D5 /* UpdateCachedChannelParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateCachedChannelParticipants.swift; sourceTree = ""; }; @@ -997,6 +1006,9 @@ D0BC387A1E40D2880044D6FE /* TogglePeerChatPinned.swift */, D049EAEA1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift */, D050F2501E4A59C200988324 /* JoinLink.swift */, + C2366C821E4F3EAA0097CCFF /* GroupReturnAndLeft.swift */, + C2366C851E4F403C0097CCFF /* UsernameAvailability.swift */, + C2366C881E4F40480097CCFF /* SupportPeerId.swift */, ); name = Peers; sourceTree = ""; @@ -1225,6 +1237,7 @@ D073CE601DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift in Sources */, D03B0D6B1D631A9D00955575 /* Phonebook.swift in Sources */, D0AAD1A81E32602500D5B9DE /* AutoremoveTimeoutMessageAttribute.swift in Sources */, + C2366C831E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */, D03B0D3D1D6319E200955575 /* Fetch.swift in Sources */, D0DF0C931D81AD09008AEB01 /* MessageUtils.swift in Sources */, D03B0D681D631A8B00955575 /* RecentPeers.swift in Sources */, @@ -1271,6 +1284,7 @@ D0E35A101DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, D049EAD81E43DAD200A2CD3A /* ManagedRecentStickers.swift in Sources */, D0448C991E268F9A005A61A7 /* SecretApiLayer46.swift in Sources */, + C2366C891E4F40480097CCFF /* SupportPeerId.swift in Sources */, D003702B1DA42586004308D3 /* PhoneNumber.swift in Sources */, D03B0CF91D62250800955575 /* TelegramMediaMap.swift in Sources */, D0BC38791E40BAF20044D6FE /* SynchronizePinnedChatsOperation.swift in Sources */, @@ -1346,6 +1360,7 @@ D03B0CBB1D62233C00955575 /* MergeLists.swift in Sources */, D03B0CC11D62235000955575 /* StringFormat.swift in Sources */, D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */, + C2366C861E4F403C0097CCFF /* UsernameAvailability.swift in Sources */, D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */, D0FA8BA41E1FA341001E855B /* SecretChatKeychain.swift in Sources */, D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */, @@ -1396,6 +1411,7 @@ D00D97CB1E32917C00E5C2B6 /* PeerInputActivityManager.swift in Sources */, D0B844491DAB91FD005F29E1 /* ManagedChatListHoles.swift in Sources */, D03C53711DAD5CA9004C17B3 /* CachedGroupParticipants.swift in Sources */, + C2366C841E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */, D03C53671DAD5CA9004C17B3 /* ApiUtils.swift in Sources */, D0AAD1B91E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift in Sources */, D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */, @@ -1442,6 +1458,7 @@ D0448C8F1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */, D073CE6E1DCBCF17007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D001F3E81E128A1C007A8C60 /* ChannelState.swift in Sources */, + C2366C8A1E4F40480097CCFF /* SupportPeerId.swift in Sources */, D0B844451DAB91FD005F29E1 /* AccountViewTracker.swift in Sources */, D050F2601E4A5AD500988324 /* AutoremoveTimeoutMessageAttribute.swift in Sources */, D049EAD91E43DAD200A2CD3A /* ManagedRecentStickers.swift in Sources */, @@ -1517,6 +1534,7 @@ D0B844331DAB91E0005F29E1 /* NBPhoneNumber.m in Sources */, D001F3F51E128A1C007A8C60 /* PendingMessageManager.swift in Sources */, D001F3F61E128A1C007A8C60 /* PendingMessageUploadedContent.swift in Sources */, + C2366C871E4F403C0097CCFF /* UsernameAvailability.swift in Sources */, D02ABC7F1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */, D0FA8BA51E1FA341001E855B /* SecretChatKeychain.swift in Sources */, D0F7B1E71E045C87007EB8A5 /* JoinChannel.swift in Sources */, diff --git a/TelegramCore/GroupReturnAndLeft.swift b/TelegramCore/GroupReturnAndLeft.swift new file mode 100644 index 0000000000..dc27bf4352 --- /dev/null +++ b/TelegramCore/GroupReturnAndLeft.swift @@ -0,0 +1,46 @@ + +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + + +public func returnGroup(account: Account, peerId: PeerId) -> Signal { + return account.postbox.loadedPeerWithId(account.peerId) + |> take(1) + |> mapToSignal { peer -> Signal in + if let inputUser = apiInputUser(peer) { + return account.network.request(Api.functions.messages.addChatUser(chatId: peerId.id, userId: inputUser, fwdLimit: 50)) + |> retryRequest + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + } else { + return .complete() + } + } +} + +public func leftGroup(account: Account, peerId: PeerId) -> Signal { + return account.postbox.loadedPeerWithId(account.peerId) + |> take(1) + |> mapToSignal { peer -> Signal in + if let inputUser = apiInputUser(peer) { + return account.network.request(Api.functions.messages .deleteChatUser(chatId: peerId.id, userId: inputUser)) + |> retryRequest + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + } else { + return .complete() + } + } +} + diff --git a/TelegramCore/SupportPeerId.swift b/TelegramCore/SupportPeerId.swift new file mode 100644 index 0000000000..c495df4d3b --- /dev/null +++ b/TelegramCore/SupportPeerId.swift @@ -0,0 +1,34 @@ + +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + + +public func supportPeerId(account:Account) -> Signal { + return account.network.request(Api.functions.help.getSupport()) + |> map { Optional($0) } + |> `catch` { _ in + return Signal.single(nil) + } + |> mapToSignal { support -> Signal in + if let support = support { + switch support { + case let .support(phoneNumber: _, user: user): + let user = TelegramUser(user: user) + return account.postbox.modify { modifier -> PeerId in + updatePeers(modifier: modifier, peers: [user], update: { (previous, updated) -> Peer? in + return updated + }) + return user.id + } + } + } + return .single(nil) + } +} diff --git a/TelegramCore/UsernameAvailability.swift b/TelegramCore/UsernameAvailability.swift new file mode 100644 index 0000000000..ddb452fa48 --- /dev/null +++ b/TelegramCore/UsernameAvailability.swift @@ -0,0 +1,179 @@ + +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + + +public enum UsernameAvailabilityError { + case underscopeStart + case underscopeEnd + case digitStart + case invalid + case short + case alreadyTaken +} + +public enum UsernameAvailabilityState : Equatable { + case none(username: String?) + case success(username: String?) + case progress(username: String?) + case fail(username: String?, error: UsernameAvailabilityError) + + public var username:String? { + switch self { + case let .none(username:username): + return username + case let .success(username:username): + return username + case let .progress(username:username): + return username + case let .fail(fail): + return fail.username + } + } +} + + + +public func ==(lhs:UsernameAvailabilityState, rhs:UsernameAvailabilityState) -> Bool { + switch lhs { + case let .none(username:lhsName): + if case let .none(username:rhsName) = rhs, lhsName == rhsName { + return true + } + return false + case let .success(username:lhsName): + if case let .success(username:rhsName) = rhs, lhsName == rhsName { + return true + } + return false + case let .progress(username:lhsName): + if case let .progress(username:rhsName) = rhs, lhsName == rhsName { + return true + } + return false + case let .fail(lhsText): + if case let .fail(rhsText) = rhs, lhsText.error == rhsText.error && lhsText.username == rhsText.username { + return true + } + return false + } +} + + +public func usernameAvailability(account:Account, def:String?, current:String) -> Signal { + + return Signal { subscriber in + + let none = { () -> Disposable in + subscriber.putNext(.none(username: current)) + subscriber.putCompletion() + return EmptyDisposable + } + + let success = { () -> Disposable in + subscriber.putNext(.success(username: current)) + subscriber.putCompletion() + return EmptyDisposable + } + + let fail:(UsernameAvailabilityError)->Disposable = { (value) -> Disposable in + subscriber.putNext(.fail(username: current, error:value)) + subscriber.putCompletion() + return EmptyDisposable + } + + if def == current { + return success() + } + + for char in current.characters { + if char == "_" { + if char == current.characters.first { + return fail(.underscopeStart); + } else if char == current.characters.last { + return fail(current.characters.count < 5 ? .short : .underscopeEnd); + } + + } + if char == current.characters.first && char >= "0" && char <= "9" { + return fail(.digitStart); + } + if (!((char >= "a" && char <= "z") || (char >= "A" && char <= "Z") || (char >= "0" && char <= "9"))) { + return fail(.invalid); + } + } + + if current.characters.count < 5 { + if current.isEmpty { + return none() + } + return fail(.short) + } + + + subscriber.putNext(.progress(username: current)) + + let disposable:Disposable + + let req = account.network.request(Api.functions.account.checkUsername(username: current)) |> delay(0.3, queue: Queue.concurrentDefaultQueue()) |> map {result in + switch result { + case .boolFalse: + return .fail(username: current, error:.alreadyTaken) + case .boolTrue: + return .success(username: current) + } + } + |> `catch` { error -> Signal in + return Signal { subscriber in + subscriber.putNext(.fail(username: current, error:.invalid)) + subscriber.putCompletion() + return EmptyDisposable + } + } + |> retryRequest + + + disposable = req.start(next: { (status) in + subscriber.putNext(status) + }, completed:{ + subscriber.putCompletion() + }) + + return disposable + } +} + +public func updateUsername(account:Account, username:String) -> Signal { + + return account.network.request(Api.functions.account.updateUsername(username: username)) |> map { result in + return TelegramUser(user: result) + } + |> `catch` { error -> Signal in + return Signal { subscriber in + subscriber.putNext(nil) + subscriber.putCompletion() + return EmptyDisposable + } + } + |> retryRequest + |> mapToSignal({ (user) -> Signal in + if let user = user { + return account.postbox.modify { modifier -> Void in + updatePeers(modifier: modifier, peers: [user], update: { (previous, updated) -> Peer? in + return updated + }) + } |> map({true}) + } else { + return .single(false) + } + }) + + +}