Home telegram - peerChannelMemberCategoriesContextsManager
Post
Cancel

telegram - peerChannelMemberCategoriesContextsManager

PeerChannelMemberCategoriesContextsManager

struct TelegramChatAdminRightsFlags:OptionSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public var rawValue: Int32

public static let canChangeInfo = TelegramChatAdminRightsFlags(rawValue: 1 << 0)
public static let canPostMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 1)
public static let canEditMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 2)
public static let canDeleteMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 3)
public static let canBanUsers = TelegramChatAdminRightsFlags(rawValue: 1 << 4)
public static let canInviteUsers = TelegramChatAdminRightsFlags(rawValue: 1 << 5)
public static let canPinMessages = TelegramChatAdminRightsFlags(rawValue: 1 << 7)
public static let canAddAdmins = TelegramChatAdminRightsFlags(rawValue: 1 << 9)
public static let canBeAnonymous = TelegramChatAdminRightsFlags(rawValue: 1 << 10)
public static let canManageCalls = TelegramChatAdminRightsFlags(rawValue: 1 << 11)

public static var all: TelegramChatAdminRightsFlags {
    return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canBeAnonymous, .canManageCalls]
}
public static var allChannel: TelegramChatAdminRightsFlags {
    return [.canChangeInfo, .canPostMessages, .canEditMessages, .canDeleteMessages, .canBanUsers, .canInviteUsers, .canPinMessages, .canAddAdmins, .canManageCalls]
}

public static var groupSpecific: TelegramChatAdminRightsFlags = [
    .canChangeInfo,
    .canDeleteMessages,
    .canBanUsers,
    .canInviteUsers,
    .canPinMessages,
    .canManageCalls,
    .canBeAnonymous,
    .canAddAdmins
]
public static var broadcastSpecific: TelegramChatAdminRightsFlags = [
    .canChangeInfo,
    .canPostMessages,
    .canEditMessages,
    .canDeleteMessages,
    .canManageCalls,
    .canInviteUsers,
    .canAddAdmins
]

public var count: Int {
    var result = 0
    var index = 0
    while index < 31 {
        let currentValue = self.rawValue >> Int32(index)
        index += 1
        if currentValue == 0 {
            break
        }
        
        if (currentValue & 1) != 0 {
            result += 1
        }
    }
    return result
}

struct TelegramChatAdminRights:PostboxCoding

1
public let rights: TelegramChatAdminRightsFlags

struct ChannelParticipantAdminInfo

1
2
3
public let rights: TelegramChatAdminRights
public let promotedBy: PeerId
public let canBeEditedByAccountPeer: Bool

struct TelegramChatBannedRightsFlags:OptionSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public var rawValue: Int32

public static let banReadMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 0)
public static let banSendMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 1)
public static let banSendMedia = TelegramChatBannedRightsFlags(rawValue: 1 << 2)
public static let banSendStickers = TelegramChatBannedRightsFlags(rawValue: 1 << 3)
public static let banSendGifs = TelegramChatBannedRightsFlags(rawValue: 1 << 4)
public static let banSendGames = TelegramChatBannedRightsFlags(rawValue: 1 << 5)
public static let banSendInline = TelegramChatBannedRightsFlags(rawValue: 1 << 6)
public static let banEmbedLinks = TelegramChatBannedRightsFlags(rawValue: 1 << 7)
public static let banSendPolls = TelegramChatBannedRightsFlags(rawValue: 1 << 8)
public static let banChangeInfo = TelegramChatBannedRightsFlags(rawValue: 1 << 10)
public static let banAddMembers = TelegramChatBannedRightsFlags(rawValue: 1 << 15)
public static let banPinMessages = TelegramChatBannedRightsFlags(rawValue: 1 << 17)

struct TelegramChatBannedRights

1
2
public let flags: TelegramChatBannedRightsFlags
public let untilDate: Int32

struct ChannelParticipantBannedInfo

1
2
3
4
public let rights: TelegramChatBannedRights
public let restrictedBy: PeerId
public let timestamp: Int32
public let isMember: Bool

enum ChannelParticipant

1
2
case creator(id: PeerId, adminInfo: ChannelParticipantAdminInfo?, rank: String?)
case member(id: PeerId, invitedAt: Int32, adminInfo: ChannelParticipantAdminInfo?, banInfo: ChannelParticipantBannedInfo?, rank: String?)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public extension ChannelParticipant {
    var adminInfo: ChannelParticipantAdminInfo? {
        switch self {
            case .creator:
                return nil
            case let .member(_, _, adminInfo, _, _):
                return adminInfo
        }
    }
    
    var banInfo: ChannelParticipantBannedInfo? {
        switch self {
            case .creator:
                return nil
            case let .member(_, _, _, banInfo, _):
                return banInfo
        }
    }
    
    func canBeBannedBy(peerId: PeerId) -> Bool {
        switch self {
            case .creator:
                return false
            case let .member(_, _, adminInfo, _, _):
                if let adminInfo = adminInfo {
                    if adminInfo.promotedBy != peerId {
                        return false
                    }
                }
        }
        return true
    }
}

struct RenderedChannelParticipant

1
2
3
4
public let participant: ChannelParticipant
public let peer: Peer
public let peers: [PeerId: Peer]
public let presences: [PeerId: PeerPresence]

enum ChannelMemberListLoadingState

1
2
case loading(initial: Bool)
case ready(hasMore: Bool)

struct ChannelMemberListState

1
2
public let list: [RenderedChannelParticipant]
public let loadingState: ChannelMemberListLoadingState

protocol ChannelMemberCategoryListContext

1
2
3
4
5
6
var listStateValue: ChannelMemberListState { get }
var listState: Signal<ChannelMemberListState, NoError> { get }
func loadMore()
func reset(_ force: Bool)
func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)])
func forceUpdateHead()

class PeerChannelMemberContextWithSubscribers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
let context: ChannelMemberCategoryListContext
private let emptyTimeout: Double
private let subscribers = Bag<(ChannelMemberListState) -> Void>()
private let disposable = MetaDisposable()
private let becameEmpty: () -> Void

private var emptyTimer: SwiftSignalKit.Timer?

init(context: ChannelMemberCategoryListContext, emptyTimeout: Double, becameEmpty: @escaping () -> Void) {
    self.context = context
    self.emptyTimeout = emptyTimeout
    self.becameEmpty = becameEmpty
    self.disposable.set((context.listState
    |> deliverOnMainQueue).start(next: { [weak self] value in
        if let strongSelf = self {
            for f in strongSelf.subscribers.copyItems() {
                f(value)
            }
        }
    }))
}

private func resetAndBeginEmptyTimer() {
    self.context.reset(false)
    self.emptyTimer?.invalidate()
    let emptyTimer = SwiftSignalKit.Timer(timeout: self.emptyTimeout, repeat: false, completion: { [weak self] in
        if let strongSelf = self {
            if strongSelf.subscribers.isEmpty {// became empty when subscribers are empty
                strongSelf.becameEmpty()
            }
        }
    }, queue: Queue.mainQueue())
    self.emptyTimer = emptyTimer
    emptyTimer.start()
}

func subscribe(requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> Disposable {
    let wasEmpty = self.subscribers.isEmpty
  // listen
    let index = self.subscribers.add(updated)
  // update with the latest value immediately
    updated(self.context.listStateValue)
    if wasEmpty {
        self.emptyTimer?.invalidate()
        if requestUpdate {
          // force context update
            self.context.forceUpdateHead()
        }
    }
    return ActionDisposable { [weak self] in
        Queue.mainQueue().async {
            if let strongSelf = self {
                strongSelf.subscribers.remove(index)
                if strongSelf.subscribers.isEmpty {
                  // reset begin empty timer
                    strongSelf.resetAndBeginEmptyTimer()
                }
            }
        }
    }
}

enum PeerChannelMemberContextKey

1
2
3
4
5
6
7
8
9
case recent
case recentSearch(String)
case mentions(threadId: MessageId?, query: String?)
case admins(String?)
case contacts(String?)
case bots(String?)
case restrictedAndBanned(String?)
case restricted(String?)
case banned(String?)

struct PeerChannelMemberCategoryControl

1
fileprivate let key: PeerChannelMemberContextKey

class PeerChannelMemberCategoriesContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
private let engine: TelegramEngine
private let postbox: Postbox
private let network: Network
private let accountPeerId: PeerId
private let peerId: PeerId
private var becameEmpty: (Bool) -> Void

private var contexts: [PeerChannelMemberContextKey: PeerChannelMemberContextWithSubscribers] = [:]

// reset context key
func reset(_ key: PeerChannelMemberContextKey) {
    for (contextKey, context) in contexts {
        if contextKey == key {
            context.context.reset(true)
            context.context.loadMore()
        }
    }
}

func getContext(key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) {
    assert(Queue.mainQueue().isCurrent())
    if let current = self.contexts[key] {
        return (current.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
    }
    let context: ChannelMemberCategoryListContext
    let emptyTimeout: Double
    switch key {
        case .admins(nil), .banned(nil), .recentSearch(nil), .restricted(nil), .restrictedAndBanned(nil), .recent, .contacts:
            emptyTimeout = defaultEmptyTimeout
        default:
            emptyTimeout = 0.0
    }
    switch key {
        case .recent, .recentSearch, .admins, .contacts, .bots, .mentions:
            let mappedCategory: ChannelMemberListCategory
            switch key {
                case .recent:
                    mappedCategory = .recent
                case let .recentSearch(query):
                    mappedCategory = .recentSearch(query)
                case let .admins(query):
                    mappedCategory = .admins(query)
                case let .contacts(query):
                    mappedCategory = .contacts(query)
                case let .bots(query):
                    mappedCategory = .bots(query)
                case let .mentions(threadId, query):
                    mappedCategory = .mentions(threadId, query)
                default:
                    mappedCategory = .recent
            }
            context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: mappedCategory)
        case let .restrictedAndBanned(query):
            context = ChannelMemberMultiCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, categories: [.restricted(query), .banned(query)])
        case let .restricted(query):
            context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .restricted(query))
        case let .banned(query):
            context = ChannelMemberSingleCategoryListContext(engine: self.engine, postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, peerId: self.peerId, category: .banned(query))
    }
    let contextWithSubscribers = PeerChannelMemberContextWithSubscribers(context: context, emptyTimeout: emptyTimeout, becameEmpty: { [weak self] in
        assert(Queue.mainQueue().isCurrent())
        if let strongSelf = self {
            strongSelf.contexts.removeValue(forKey: key)
        }
    })
    self.contexts[key] = contextWithSubscribers
    return (contextWithSubscribers.subscribe(requestUpdate: requestUpdate, updated: updated), PeerChannelMemberCategoryControl(key: key))
}

func loadMore(_ control: PeerChannelMemberCategoryControl) {
    assert(Queue.mainQueue().isCurrent())
    if let context = self.contexts[control.key] {
        context.context.loadMore()
    }
}

func replayUpdates(_ updates: [(ChannelParticipant?, RenderedChannelParticipant?, Bool?)]) {
    for (_, context) in self.contexts {
        context.context.replayUpdates(updates)
    }
}

class PeerChannelMembersOnlineContext

1
2
3
4
5
6
7
8
let subscribers = Bag<(Int32) -> Void>()
let disposable: Disposable
var value: Int32?
var emptyTimer: SwiftSignalKit.Timer?

init(disposable: Disposable) {
    self.disposable = disposable
}

class ProfileDataPreloadContext

1
2
3
4
5
6
7
8
let subscribers = Bag<() -> Void>()

let disposable: Disposable
var emptyTimer: SwiftSignalKit.Timer?

init(disposable: Disposable) {
    self.disposable = disposable
}

class ProfileDataPhotoPreloadContext

1
2
3
4
5
let subscribers = Bag<(Any?) -> Void>()

let disposable: Disposable
var value: Any?
var emptyTimer: SwiftSignalKit.Timer?

class PeerChannelMemberCategoriesContextsManagerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
fileprivate var contexts: [PeerId: PeerChannelMemberCategoriesContext] = [:]
fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:]
fileprivate var profileDataPreloadContexts: [PeerId: ProfileDataPreloadContext] = [:]
fileprivate var profileDataPhotoPreloadContexts: [PeerId: ProfileDataPhotoPreloadContext] = [:]

func getContext(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable,
PeerChannelMemberCategoryControl) {
    if let current = self.contexts[peerId] {
        return current.getContext(key: key, requestUpdate: requestUpdate, updated: updated)
    } else {
        var becameEmptyImpl: ((Bool) -> Void)?
        let context = PeerChannelMemberCategoriesContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, becameEmpty: { value in
            becameEmptyImpl?(value)
        })
        becameEmptyImpl = { [weak self, weak context] value in
            assert(Queue.mainQueue().isCurrent())
            if let strongSelf = self {
                if let current = strongSelf.contexts[peerId], current === context {
                    strongSelf.contexts.removeValue(forKey: peerId)
                }
            }
        }
        self.contexts[peerId] = context
        return context.getContext(key: key, requestUpdate: requestUpdate, updated: updated)
    }
}

// recent online
func recentOnline(account: Account, accountPeerId: PeerId, peerId: PeerId, updated: @escaping (Int32) -> Void) -> Disposable {
    let context: PeerChannelMembersOnlineContext
    if let current = self.onlineContexts[peerId] {
        context = current
    } else {
        let disposable = MetaDisposable()
        context = PeerChannelMembersOnlineContext(disposable: disposable)
        self.onlineContexts[peerId] = context

        let signal = (
          // request online members
            TelegramEngine(account: account).peers.chatOnlineMembers(peerId: peerId)
            |> then(
                .complete()
                |> delay(30.0, queue: .mainQueue())
            )
        ) |> restart

        disposable.set(signal.start(next: { [weak context] value in
            guard let context = context else {
                return
            }
            context.value = value
            for f in context.subscribers.copyItems() {
                f(value)
            }
        }))
    }

    if let emptyTimer = context.emptyTimer {
        emptyTimer.invalidate()
        context.emptyTimer = nil
    }

    let index = context.subscribers.add({ next in
        updated(next)
    })
    updated(context.value ?? 0)

    return ActionDisposable { [weak self, weak context] in
        Queue.mainQueue().async {
            guard let strongSelf = self else {
                return
            }
            if let current = strongSelf.onlineContexts[peerId], let context = context, current === context {
                current.subscribers.remove(index)
                if current.subscribers.isEmpty {
                    if current.emptyTimer == nil {
                      // check subscriber empty after 60.0 seconds after disposed
                        let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
                            if let current = strongSelf.onlineContexts[peerId], let context = context, current === context {
                                if current.subscribers.isEmpty {
                                    strongSelf.onlineContexts.removeValue(forKey: peerId)
                                    current.disposable.dispose()
                                }
                            }
                        }, queue: Queue.mainQueue())
                        current.emptyTimer = timer
                        timer.start()
                    }
                }
            }
        }
    }
}

// load more
func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl) {
    if let context = self.contexts[peerId] {
        context.loadMore(control)
    }
}

// profile data
func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Disposable {
    let context: ProfileDataPreloadContext
    if let current = self.profileDataPreloadContexts[peerId] {
        context = current
    } else {
        let disposable = DisposableSet()
        context = ProfileDataPreloadContext(disposable: disposable)
        self.profileDataPreloadContexts[peerId] = context

        if let customData = customData {
            disposable.add(customData.start())
        }

        /*disposable.set(signal.start(next: { [weak context] value in
            guard let context = context else {
                return
            }
            context.value = value
            for f in context.subscribers.copyItems() {
                f(value)
            }
        }))*/
    }

    if let emptyTimer = context.emptyTimer {
        emptyTimer.invalidate()
        context.emptyTimer = nil
    }

    let index = context.subscribers.add({
    })
    //updated(context.value ?? 0)

    return ActionDisposable { [weak self, weak context] in
        Queue.mainQueue().async {
            guard let strongSelf = self else {
                return
            }
            if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
                current.subscribers.remove(index)
                if current.subscribers.isEmpty {
                    if current.emptyTimer == nil {
                        let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
                            if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
                                if current.subscribers.isEmpty {
                                    strongSelf.profileDataPreloadContexts.removeValue(forKey: peerId)
                                    current.disposable.dispose()
                                }
                            }
                        }, queue: Queue.mainQueue())
                        current.emptyTimer = timer
                        timer.start()
                    }
                }
            }
        }
    }
}

// profile photos
func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>, updated: @escaping (Any?) -> Void) -> Disposable {
    let context: ProfileDataPhotoPreloadContext
    if let current = self.profileDataPhotoPreloadContexts[peerId] {
        context = current
    } else {
        let disposable = MetaDisposable()
        context = ProfileDataPhotoPreloadContext(disposable: disposable)
        self.profileDataPhotoPreloadContexts[peerId] = context

        disposable.set(fetch.start(next: { [weak context] value in
            guard let context = context else {
                return
            }
            context.value = value
            for f in context.subscribers.copyItems() {
                f(value)
            }
        }))
    }

    if let emptyTimer = context.emptyTimer {
        emptyTimer.invalidate()
        context.emptyTimer = nil
    }

    let index = context.subscribers.add({ next in
        updated(next)
    })
    updated(context.value)

    return ActionDisposable { [weak self, weak context] in
        Queue.mainQueue().async {
            guard let strongSelf = self else {
                return
            }
            if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
                current.subscribers.remove(index)
                if current.subscribers.isEmpty {
                    if current.emptyTimer == nil {
                        let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
                            if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
                                if current.subscribers.isEmpty {
                                    strongSelf.profileDataPhotoPreloadContexts.removeValue(forKey: peerId)
                                    current.disposable.dispose()
                                }
                            }
                        }, queue: Queue.mainQueue())
                        current.emptyTimer = timer
                        timer.start()
                    }
                }
            }
        }
    }
}

class PeerChannelMemberCategoriesContextsManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
private let impl: QueueLocalObject<PeerChannelMemberCategoriesContextsManagerImpl>

private let removedChannelMembersPipe = ValuePipe<[(PeerId, PeerId)]>()
public var removedChannelMembers: Signal<[(PeerId, PeerId)], NoError> {
    return self.removedChannelMembersPipe.signal()
}

public init() {
    self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
        return PeerChannelMemberCategoriesContextsManagerImpl()
    })
}

public func loadMore(peerId: PeerId, control: PeerChannelMemberCategoryControl?) {
    if let control = control {
        self.impl.with { impl in
            impl.loadMore(peerId: peerId, control: control)
        }
    }
}

// get context
private func getContext(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    assert(Queue.mainQueue().isCurrent())
    let (disposable, control) = self.impl.syncWith({ impl in
        return impl.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
    })
    return (disposable, control)
}

// externally added
public func externallyAdded(peerId: PeerId, participant: RenderedChannelParticipant) {
    self.impl.with { impl in
        for (contextPeerId, context) in impl.contexts {
            if contextPeerId == peerId {
                context.replayUpdates([(nil, participant, nil)])
            }
        }
    }
}

// externally removed
public func externallyRemoved(peerId: PeerId, memberId: PeerId) {
    self.impl.with { impl in
        for (contextPeerId, context) in impl.contexts {
            if contextPeerId == peerId {
                context.replayUpdates([(.member(id: memberId, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), nil, nil)])
            }
        }
    }
}

// recent
public func recent(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    let key: PeerChannelMemberContextKey
    if let searchQuery = searchQuery {
        key = .recentSearch(searchQuery)
    } else {
        key = .recent
    }
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
}

// mentions
public func mentions(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadMessageId: MessageId?, searchQuery: String? = nil, requestUpdate: Bool = true, updated: @escaping
(ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    let key: PeerChannelMemberContextKey = .mentions(threadId: threadMessageId, query: searchQuery)
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: key, requestUpdate: requestUpdate, updated: updated)
}

// admins
public func admins(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable,
PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .admins(searchQuery), requestUpdate: true, updated: updated)
}

// contacts
public func contacts(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .contacts(searchQuery), requestUpdate: true, updated: updated)
}

// bots
public func bots(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .bots(searchQuery), requestUpdate: true, updated: updated)
}

// restricted
public func restricted(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restricted(searchQuery), requestUpdate: true, updated: updated)
}

// banned
public func banned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .banned(searchQuery), requestUpdate: true, updated: updated)
}

// restricted and banned
public func restrictedAndBanned(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, searchQuery: String? = nil, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl?) {
    return self.getContext(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, key: .restrictedAndBanned(searchQuery), requestUpdate: true, updated: updated)
}

// update member banned rights
public func updateMemberBannedRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, bannedRights: TelegramChatBannedRights?) -> Signal<Void, NoError> {
    return engine.peers.updateChannelMemberBannedRights(peerId: peerId, memberId: memberId, rights: bannedRights)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] (previous, updated, isMember) in
        if let strongSelf = self {
            strongSelf.impl.with { impl in
                for (contextPeerId, context) in impl.contexts {
                    if peerId == contextPeerId {
                        context.replayUpdates([(previous, updated, isMember)])
                    }
                }
            }
            if !isMember {
                strongSelf.removedChannelMembersPipe.putNext([(peerId, memberId)])
            }
        }
    }
    |> mapToSignal { _ -> Signal<Void, NoError> in
        return .complete()
    }
}

// update member admin rights
public func updateMemberAdminRights(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, adminRights: TelegramChatAdminRights?, rank: String?) -> Signal<Void, UpdateChannelAdminRightsError> {
    return engine.peers.updateChannelAdminRights(peerId: peerId, adminId: memberId, rights: adminRights, rank: rank)
    |> map(Optional.init)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] result in
        if let strongSelf = self, let (previous, updated) = result {
            strongSelf.impl.with { impl in
                for (contextPeerId, context) in impl.contexts {
                    if peerId == contextPeerId {
                        context.replayUpdates([(previous, updated, nil)])
                    }
                }
            }
        }
    }
    |> mapToSignal { _ -> Signal<Void, UpdateChannelAdminRightsError> in
        return .complete()
    }
}

// transfer ownership to memberId
public func transferOwnership(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, password: String) -> Signal<Void, ChannelOwnershipTransferError> {
    return engine.peers.updateChannelOwnership(channelId: peerId, memberId: memberId, password: password)
    |> map(Optional.init)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] results in
        if let strongSelf = self, let results = results {
            strongSelf.impl.with { impl in
                for (contextPeerId, context) in impl.contexts {
                    if peerId == contextPeerId {
                        context.replayUpdates(results.map { ($0.0, $0.1, nil) })
                    }
                }
            }
        }
    }
    |> mapToSignal { _ -> Signal<Void, ChannelOwnershipTransferError> in
        return .complete()
    }
}

// join channel 
public func join(engine: TelegramEngine, peerId: PeerId, hash: String?) -> Signal<Never, JoinChannelError> {
  // join channel or import chat invite
    return engine.peers.joinChannel(peerId: peerId, hash: hash)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] result in
        if let strongSelf = self, let updated = result {
            strongSelf.impl.with { impl in
                for (contextPeerId, context) in impl.contexts {
                    if peerId == contextPeerId {
                        context.replayUpdates([(nil, updated, nil)])
                    }
                }
            }
        }
    }
    |> ignoreValues
}

// add member
public func addMember(engine: TelegramEngine, peerId: PeerId, memberId: PeerId) -> Signal<Never, AddChannelMemberError> {
    return engine.peers.addChannelMember(peerId: peerId, memberId: memberId)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] result in
        if let strongSelf = self {
            let (previous, updated) = result
            strongSelf.impl.with { impl in
                for (contextPeerId, context) in impl.contexts {
                    if peerId == contextPeerId {
                        context.replayUpdates([(previous, updated, nil)])
                    }
                }
            }
        }
    }
    |> ignoreValues
}

// add members
public func addMembers(engine: TelegramEngine, peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> {
    let signals: [Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError>] = memberIds.map({ memberId in
        return engine.peers.addChannelMember(peerId: peerId, memberId: memberId)
        |> map(Optional.init)
        |> `catch` { error -> Signal<(ChannelParticipant?, RenderedChannelParticipant)?, AddChannelMemberError> in
            return .fail(error)
        }
    })
    return combineLatest(signals)
    |> deliverOnMainQueue
    |> beforeNext { [weak self] results in
        if let strongSelf = self {
            strongSelf.impl.with { impl in
                for result in results {
                    if let (previous, updated) = result {
                        for (contextPeerId, context) in impl.contexts {
                            if peerId == contextPeerId {
                                context.replayUpdates([(previous, updated, nil)])
                            }
                        }
                    }
                }
            }
        }
    }
    |> mapToSignal { _ -> Signal<Void, AddChannelMemberError> in
        return .complete()
    }
}

// recent online
public func recentOnline(account: Account, accountPeerId: PeerId, peerId: PeerId) -> Signal<Int32, NoError> {
    return Signal { [weak self] subscriber in
        guard let strongSelf = self else {
            subscriber.putNext(0)
            subscriber.putCompletion()
            return EmptyDisposable
        }
        let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
            return impl.recentOnline(account: account, accountPeerId: accountPeerId, peerId: peerId, updated: { value in
                subscriber.putNext(value)
            })
        })
        return disposable
    }
    |> runOn(Queue.mainQueue())
}

// recent-online small
public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<Int32, NoError> {
    return Signal { [weak self] subscriber in
        var previousIds: Set<PeerId>?
        let statusesDisposable = MetaDisposable()
        let disposableAndControl = self?.recent(engine: engine, postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId, updated: { state in
            var idList: [PeerId] = []
            for item in state.list {
                idList.append(item.peer.id)
                if idList.count >= 200 {
                    break
                }
            }
            let updatedIds = Set(idList)
            if previousIds != updatedIds {
                previousIds = updatedIds
                let key: PostboxViewKey = .peerPresences(peerIds: updatedIds)
                statusesDisposable.set((postbox.combinedView(keys: [key])
                |> map { view -> Int32 in
                    var count: Int32 = 0
                    let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
                    if let presences = (view.views[key] as? PeerPresencesView)?.presences {
                        for (_, presence) in presences {
                            if let presence = presence as? TelegramUserPresence {
                                let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp))
                                switch relativeStatus {
                                case .online:
                                    count += 1
                                default:
                                    break
                                }
                            }
                        }
                    }
                    return count
                }
                |> distinctUntilChanged
                |> deliverOnMainQueue).start(next: { count in
                    subscriber.putNext(count)
                }))
            }
        })
        return ActionDisposable {
            disposableAndControl?.0.dispose()
            statusesDisposable.dispose()
        }
    }
    |> runOn(Queue.mainQueue())
}

// profile data
public func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Signal<Never, NoError> {
    return Signal { [weak self] subscriber in
        guard let strongSelf = self else {
            subscriber.putCompletion()
            return EmptyDisposable
        }
        let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
            return impl.profileData(postbox: postbox, network: network, peerId: peerId, customData: customData)
        })
        return disposable
    }
    |> runOn(Queue.mainQueue())
}

// profile photos
public func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>) -> Signal<Any?, NoError> {
    return Signal { [weak self] subscriber in
        guard let strongSelf = self else {
            subscriber.putNext(0)
            subscriber.putCompletion()
            return EmptyDisposable
        }
        let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
            return impl.profilePhotos(postbox: postbox, network: network, peerId: peerId, fetch: fetch, updated: { value in
                subscriber.putNext(value)
            })
        })
        return disposable
    }
    |> runOn(Queue.mainQueue())
}
This post is licensed under CC BY 4.0 by the author.