Add support for multiple users selection in bots peer requests

This commit is contained in:
Ilya Laktyushin 2023-12-21 04:04:17 +04:00
parent c3f15c4914
commit 6795603c4e
26 changed files with 247 additions and 124 deletions

View File

@ -8719,6 +8719,7 @@ Sorry for the inconvenience.";
"CreateGroup.PublicLinkInfo" = "You can use **a-z**, **0-9** and underscores. Minimum length is **5** characters.";
"Notification.RequestedPeer" = "You shared %1$@ with %2$@.";
"Notification.RequestedPeerMultiple" = "You shared %1$@ with %2$@.";
"Conversation.ViewInChannel" = "View in Channel";
@ -10829,3 +10830,8 @@ Sorry for the inconvenience.";
"Channel.Info.BoostLevelPlusBadge" = "Level %@+";
"Channel.Info.AppearanceItem" = "Appearance";
"RequestPeer.SelectUsers" = "Choose Users";
"RequestPeer.SelectUsers.SearchPlaceholder" = "Search";
"RequestPeer.ReachedMaximum_1" = "You can select up to %@ user.";
"RequestPeer.ReachedMaximum_any" = "You can select up to %@ users.";

View File

@ -70,6 +70,7 @@ public enum ContactMultiselectionControllerMode {
case channelCreation
case chatSelection(ChatSelection)
case premiumGifting
case requestedUsersSelection
}
public enum ContactListFilter {

View File

@ -404,6 +404,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[42402760] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmoji($0) }
dict[215889721] = { return Api.InputStickerSet.parse_inputStickerSetAnimatedEmojiAnimations($0) }
dict[-427863538] = { return Api.InputStickerSet.parse_inputStickerSetDice($0) }
dict[1232373075] = { return Api.InputStickerSet.parse_inputStickerSetEmojiChannelDefaultStatuses($0) }
dict[701560302] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultStatuses($0) }
dict[1153562857] = { return Api.InputStickerSet.parse_inputStickerSetEmojiDefaultTopicIcons($0) }
dict[80008398] = { return Api.InputStickerSet.parse_inputStickerSetEmojiGenericAnimations($0) }
@ -447,7 +448,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[901503851] = { return Api.KeyboardButton.parse_keyboardButtonCallback($0) }
dict[1358175439] = { return Api.KeyboardButton.parse_keyboardButtonGame($0) }
dict[-59151553] = { return Api.KeyboardButton.parse_keyboardButtonRequestGeoLocation($0) }
dict[218842764] = { return Api.KeyboardButton.parse_keyboardButtonRequestPeer($0) }
dict[1406648280] = { return Api.KeyboardButton.parse_keyboardButtonRequestPeer($0) }
dict[-1318425559] = { return Api.KeyboardButton.parse_keyboardButtonRequestPhone($0) }
dict[-1144565411] = { return Api.KeyboardButton.parse_keyboardButtonRequestPoll($0) }
dict[-1598009252] = { return Api.KeyboardButton.parse_keyboardButtonSimpleWebView($0) }
@ -503,7 +504,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1892568281] = { return Api.MessageAction.parse_messageActionPaymentSentMe($0) }
dict[-2132731265] = { return Api.MessageAction.parse_messageActionPhoneCall($0) }
dict[-1799538451] = { return Api.MessageAction.parse_messageActionPinMessage($0) }
dict[-25742243] = { return Api.MessageAction.parse_messageActionRequestedPeer($0) }
dict[827428507] = { return Api.MessageAction.parse_messageActionRequestedPeer($0) }
dict[1200788123] = { return Api.MessageAction.parse_messageActionScreenshotTaken($0) }
dict[-648257196] = { return Api.MessageAction.parse_messageActionSecureValuesSent($0) }
dict[455635795] = { return Api.MessageAction.parse_messageActionSecureValuesSentMe($0) }
@ -1262,7 +1263,7 @@ public extension Api {
return parser(reader)
}
else {
telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found")
telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found")
return nil
}
}

View File

@ -325,6 +325,7 @@ public extension Api {
case inputStickerSetAnimatedEmoji
case inputStickerSetAnimatedEmojiAnimations
case inputStickerSetDice(emoticon: String)
case inputStickerSetEmojiChannelDefaultStatuses
case inputStickerSetEmojiDefaultStatuses
case inputStickerSetEmojiDefaultTopicIcons
case inputStickerSetEmojiGenericAnimations
@ -352,6 +353,12 @@ public extension Api {
buffer.appendInt32(-427863538)
}
serializeString(emoticon, buffer: buffer, boxed: false)
break
case .inputStickerSetEmojiChannelDefaultStatuses:
if boxed {
buffer.appendInt32(1232373075)
}
break
case .inputStickerSetEmojiDefaultStatuses:
if boxed {
@ -407,6 +414,8 @@ public extension Api {
return ("inputStickerSetAnimatedEmojiAnimations", [])
case .inputStickerSetDice(let emoticon):
return ("inputStickerSetDice", [("emoticon", emoticon as Any)])
case .inputStickerSetEmojiChannelDefaultStatuses:
return ("inputStickerSetEmojiChannelDefaultStatuses", [])
case .inputStickerSetEmojiDefaultStatuses:
return ("inputStickerSetEmojiDefaultStatuses", [])
case .inputStickerSetEmojiDefaultTopicIcons:
@ -441,6 +450,9 @@ public extension Api {
return nil
}
}
public static func parse_inputStickerSetEmojiChannelDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiChannelDefaultStatuses
}
public static func parse_inputStickerSetEmojiDefaultStatuses(_ reader: BufferReader) -> InputStickerSet? {
return Api.InputStickerSet.inputStickerSetEmojiDefaultStatuses
}

View File

@ -429,7 +429,7 @@ public extension Api {
case keyboardButtonCallback(flags: Int32, text: String, data: Buffer)
case keyboardButtonGame(text: String)
case keyboardButtonRequestGeoLocation(text: String)
case keyboardButtonRequestPeer(text: String, buttonId: Int32, peerType: Api.RequestPeerType)
case keyboardButtonRequestPeer(text: String, buttonId: Int32, peerType: Api.RequestPeerType, maxQuantity: Int32)
case keyboardButtonRequestPhone(text: String)
case keyboardButtonRequestPoll(flags: Int32, quiz: Api.Bool?, text: String)
case keyboardButtonSimpleWebView(text: String, url: String)
@ -490,13 +490,14 @@ public extension Api {
}
serializeString(text, buffer: buffer, boxed: false)
break
case .keyboardButtonRequestPeer(let text, let buttonId, let peerType):
case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity):
if boxed {
buffer.appendInt32(218842764)
buffer.appendInt32(1406648280)
}
serializeString(text, buffer: buffer, boxed: false)
serializeInt32(buttonId, buffer: buffer, boxed: false)
peerType.serialize(buffer, true)
serializeInt32(maxQuantity, buffer: buffer, boxed: false)
break
case .keyboardButtonRequestPhone(let text):
if boxed {
@ -582,8 +583,8 @@ public extension Api {
return ("keyboardButtonGame", [("text", text as Any)])
case .keyboardButtonRequestGeoLocation(let text):
return ("keyboardButtonRequestGeoLocation", [("text", text as Any)])
case .keyboardButtonRequestPeer(let text, let buttonId, let peerType):
return ("keyboardButtonRequestPeer", [("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any)])
case .keyboardButtonRequestPeer(let text, let buttonId, let peerType, let maxQuantity):
return ("keyboardButtonRequestPeer", [("text", text as Any), ("buttonId", buttonId as Any), ("peerType", peerType as Any), ("maxQuantity", maxQuantity as Any)])
case .keyboardButtonRequestPhone(let text):
return ("keyboardButtonRequestPhone", [("text", text as Any)])
case .keyboardButtonRequestPoll(let flags, let quiz, let text):
@ -714,11 +715,14 @@ public extension Api {
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.RequestPeerType
}
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.KeyboardButton.keyboardButtonRequestPeer(text: _1!, buttonId: _2!, peerType: _3!, maxQuantity: _4!)
}
else {
return nil

View File

@ -683,7 +683,7 @@ public extension Api {
case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge)
case messageActionPhoneCall(flags: Int32, callId: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?)
case messageActionPinMessage
case messageActionRequestedPeer(buttonId: Int32, peer: Api.Peer)
case messageActionRequestedPeer(buttonId: Int32, peers: [Api.Peer])
case messageActionScreenshotTaken
case messageActionSecureValuesSent(types: [Api.SecureValueType])
case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted)
@ -920,12 +920,16 @@ public extension Api {
}
break
case .messageActionRequestedPeer(let buttonId, let peer):
case .messageActionRequestedPeer(let buttonId, let peers):
if boxed {
buffer.appendInt32(-25742243)
buffer.appendInt32(827428507)
}
serializeInt32(buttonId, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(peers.count))
for item in peers {
item.serialize(buffer, true)
}
break
case .messageActionScreenshotTaken:
if boxed {
@ -1076,8 +1080,8 @@ public extension Api {
return ("messageActionPhoneCall", [("flags", flags as Any), ("callId", callId as Any), ("reason", reason as Any), ("duration", duration as Any)])
case .messageActionPinMessage:
return ("messageActionPinMessage", [])
case .messageActionRequestedPeer(let buttonId, let peer):
return ("messageActionRequestedPeer", [("buttonId", buttonId as Any), ("peer", peer as Any)])
case .messageActionRequestedPeer(let buttonId, let peers):
return ("messageActionRequestedPeer", [("buttonId", buttonId as Any), ("peers", peers as Any)])
case .messageActionScreenshotTaken:
return ("messageActionScreenshotTaken", [])
case .messageActionSecureValuesSent(let types):
@ -1505,14 +1509,14 @@ public extension Api {
public static func parse_messageActionRequestedPeer(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
var _2: [Api.Peer]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peer: _2!)
return Api.MessageAction.messageActionRequestedPeer(buttonId: _1!, peers: _2!)
}
else {
return nil

View File

@ -328,6 +328,21 @@ public extension Api.functions.account {
})
}
}
public extension Api.functions.account {
static func getChannelDefaultEmojiStatuses(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.EmojiStatuses>) {
let buffer = Buffer()
buffer.appendInt32(1999087573)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "account.getChannelDefaultEmojiStatuses", parameters: [("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.EmojiStatuses? in
let reader = BufferReader(buffer)
var result: Api.account.EmojiStatuses?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.account.EmojiStatuses
}
return result
})
}
}
public extension Api.functions.account {
static func getChannelRestrictedStatusEmojis(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.EmojiList>) {
let buffer = Buffer()
@ -6750,14 +6765,18 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.messages {
static func sendBotRequestedPeer(peer: Api.InputPeer, msgId: Int32, buttonId: Int32, requestedPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func sendBotRequestedPeer(peer: Api.InputPeer, msgId: Int32, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-29831141)
buffer.appendInt32(-1850552224)
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
serializeInt32(buttonId, buffer: buffer, boxed: false)
requestedPeer.serialize(buffer, true)
return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("requestedPeer", String(describing: requestedPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(requestedPeers.count))
for item in requestedPeers {
item.serialize(buffer, true)
}
return (FunctionDescription(name: "messages.sendBotRequestedPeer", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId)), ("buttonId", String(describing: buttonId)), ("requestedPeers", String(describing: requestedPeers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {

View File

@ -63,7 +63,7 @@ extension ReplyMarkupButton {
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: false))
case let .keyboardButtonSimpleWebView(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .openWebView(url: url, simple: true))
case let .keyboardButtonRequestPeer(text, buttonId, peerType):
case let .keyboardButtonRequestPeer(text, buttonId, peerType, maxQuantity):
let mappedPeerType: ReplyMarkupButtonRequestPeerType
switch peerType {
case let .requestPeerTypeUser(_, bot, premium):
@ -88,7 +88,7 @@ extension ReplyMarkupButton {
botAdminRights: botAdminRights.flatMap(TelegramChatAdminRights.init(apiAdminRights:))
))
}
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId))
self.init(title: text, titleWhenForwarded: nil, action: .requestPeer(peerType: mappedPeerType, buttonId: buttonId, maxQuantity: maxQuantity))
}
}
}

View File

@ -242,8 +242,8 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
for id in userIds {
result.append(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id)))
}
case let .messageActionRequestedPeer(_, peer):
result.append(peer.peerId)
case let .messageActionRequestedPeer(_, peers):
result.append(contentsOf: peers.map(\.peerId))
case let .messageActionGiftCode(_, boostPeer, _, _, _, _, _, _):
if let boostPeer = boostPeer {
result.append(boostPeer.peerId)

View File

@ -121,8 +121,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .topicEdited(components: components))
case let .messageActionSuggestProfilePhoto(photo):
return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo)))
case let .messageActionRequestedPeer(buttonId, peer):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerId: peer.peerId))
case let .messageActionRequestedPeer(buttonId, peers):
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerIds: peers.map { $0.peerId }))
case let .messageActionSetChatWallPaper(flags, wallpaper):
if (flags & (1 << 0)) != 0 {
return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))

View File

@ -70,6 +70,8 @@ extension StickerPackReference {
self = .iconStatusEmoji
case .inputStickerSetEmojiDefaultTopicIcons:
self = .iconTopicEmoji
case .inputStickerSetEmojiChannelDefaultStatuses:
return nil
}
}
}

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 168
return 169
}
public func parseMessage(_ data: Data!) -> Any! {

View File

@ -231,7 +231,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case setupPoll(isQuiz: Bool?)
case openUserProfile(peerId: PeerId)
case openWebView(url: String, simple: Bool)
case requestPeer(peerType: ReplyMarkupButtonRequestPeerType, buttonId: Int32)
case requestPeer(peerType: ReplyMarkupButtonRequestPeerType, buttonId: Int32, maxQuantity: Int32)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) {
@ -260,7 +260,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case 11:
self = .openWebView(url: decoder.decodeStringForKey("u", orElse: ""), simple: decoder.decodeInt32ForKey("s", orElse: 0) != 0)
case 12:
self = .requestPeer(peerType: decoder.decode(ReplyMarkupButtonRequestPeerType.self, forKey: "pt") ?? ReplyMarkupButtonRequestPeerType.user(ReplyMarkupButtonRequestPeerType.User(isBot: nil, isPremium: nil)), buttonId: decoder.decodeInt32ForKey("b", orElse: 0))
self = .requestPeer(peerType: decoder.decode(ReplyMarkupButtonRequestPeerType.self, forKey: "pt") ?? ReplyMarkupButtonRequestPeerType.user(ReplyMarkupButtonRequestPeerType.User(isBot: nil, isPremium: nil)), buttonId: decoder.decodeInt32ForKey("b", orElse: 0), maxQuantity: decoder.decodeInt32ForKey("q", orElse: 1))
default:
self = .text
}
@ -308,10 +308,11 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
encoder.encodeInt32(11, forKey: "v")
encoder.encodeString(url, forKey: "u")
encoder.encodeInt32(simple ? 1 : 0, forKey: "s")
case let .requestPeer(peerType, buttonId):
case let .requestPeer(peerType, buttonId, maxQuantity):
encoder.encodeInt32(12, forKey: "v")
encoder.encodeInt32(buttonId, forKey: "b")
encoder.encode(peerType, forKey: "pt")
encoder.encodeInt32(maxQuantity, forKey: "q")
}
}
}

View File

@ -119,7 +119,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case topicEdited(components: [ForumTopicEditComponent])
case suggestedProfilePhoto(image: TelegramMediaImage?)
case attachMenuBotAllowed
case requestedPeer(buttonId: Int32, peerId: PeerId)
case requestedPeer(buttonId: Int32, peerIds: [PeerId])
case setChatWallpaper(wallpaper: TelegramWallpaper, forBoth: Bool)
case setSameChatWallpaper(wallpaper: TelegramWallpaper)
case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?)
@ -205,7 +205,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 31:
self = .attachMenuBotAllowed
case 32:
self = .requestedPeer(buttonId: decoder.decodeInt32ForKey("b", orElse: 0), peerId: PeerId(decoder.decodeInt64ForKey("pi", orElse: 0)))
var peerIds = decoder.decodeInt64ArrayForKey("pis").map { PeerId($0) }
if peerIds.isEmpty {
peerIds = [PeerId(decoder.decodeInt64ForKey("pi", orElse: 0))]
}
self = .requestedPeer(buttonId: decoder.decodeInt32ForKey("b", orElse: 0), peerIds: peerIds)
case 33:
if let wallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wallpaper")?.value {
self = .setChatWallpaper(wallpaper: wallpaper, forBoth: decoder.decodeBoolForKey("both", orElse: false))
@ -385,10 +389,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
}
case .attachMenuBotAllowed:
encoder.encodeInt32(31, forKey: "_rawValue")
case let .requestedPeer(buttonId, peerId):
case let .requestedPeer(buttonId, peerIds):
encoder.encodeInt32(32, forKey: "_rawValue")
encoder.encodeInt32(buttonId, forKey: "b")
encoder.encodeInt64(peerId.toInt64(), forKey: "pi")
encoder.encodeInt64Array(peerIds.map { $0.toInt64() }, forKey: "pis")
case let .setChatWallpaper(wallpaper, forBoth):
encoder.encodeInt32(33, forKey: "_rawValue")
encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper")
@ -466,8 +470,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return [from, to]
case let .inviteToGroupPhoneCall(_, _, peerIds):
return peerIds
case let .requestedPeer(_, peerId):
return [peerId]
case let .requestedPeer(_, peerIds):
return peerIds
case let .giftCode(_, _, _, boostPeerId, _, _, _, _, _):
return boostPeerId.flatMap { [$0] } ?? []
default:

View File

@ -235,17 +235,17 @@ public enum SendBotRequestedPeerError {
case generic
}
func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId: MessageId, buttonId: Int32, requestedPeerId: PeerId) -> Signal<Void, SendBotRequestedPeerError> {
let signal = account.postbox.transaction { transaction -> Signal<Void, SendBotRequestedPeerError> in
if let peer = transaction.getPeer(peerId), let requestedPeer = transaction.getPeer(requestedPeerId) {
let inputPeer = apiInputPeer(peer)
let inputRequestedPeer = apiInputPeer(requestedPeer)
if let inputPeer = inputPeer, let inputRequestedPeer = inputRequestedPeer {
let signal = account.network.request(Api.functions.messages.sendBotRequestedPeer(peer: inputPeer, msgId: messageId.id, buttonId: buttonId, requestedPeer: inputRequestedPeer))
func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId: MessageId, buttonId: Int32, requestedPeerIds: [PeerId]) -> Signal<Void, SendBotRequestedPeerError> {
return account.postbox.transaction { transaction -> Signal<Void, SendBotRequestedPeerError> in
if let peer = transaction.getPeer(peerId) {
var inputRequestedPeers: [Api.InputPeer] = []
for requestedPeerId in requestedPeerIds {
if let requestedPeer = transaction.getPeer(requestedPeerId), let inputRequestedPeer = apiInputPeer(requestedPeer) {
inputRequestedPeers.append(inputRequestedPeer)
}
}
if let inputPeer = apiInputPeer(peer), !inputRequestedPeers.isEmpty {
let signal = account.network.request(Api.functions.messages.sendBotRequestedPeer(peer: inputPeer, msgId: messageId.id, buttonId: buttonId, requestedPeers: inputRequestedPeers))
|> mapError { error -> SendBotRequestedPeerError in
return .generic
}
@ -254,12 +254,9 @@ func _internal_sendBotRequestedPeer(account: Account, peerId: PeerId, messageId:
}
return signal
}
}
return .single(Void())
}
|> castError(SendBotRequestedPeerError.self)
return signal
|> switchToLatest
}

View File

@ -504,8 +504,8 @@ public extension TelegramEngine {
return _internal_addChannelMember(account: self.account, peerId: peerId, memberId: memberId)
}
public func sendBotRequestedPeer(messageId: MessageId, buttonId: Int32, requestedPeerId: PeerId) -> Signal<Void, SendBotRequestedPeerError> {
return _internal_sendBotRequestedPeer(account: self.account, peerId: messageId.peerId, messageId: messageId, buttonId: buttonId, requestedPeerId: requestedPeerId)
public func sendBotRequestedPeer(messageId: MessageId, buttonId: Int32, requestedPeerIds: [PeerId]) -> Signal<Void, SendBotRequestedPeerError> {
return _internal_sendBotRequestedPeer(account: self.account, peerId: messageId.peerId, messageId: messageId, buttonId: buttonId, requestedPeerIds: requestedPeerIds)
}
public func addChannelMembers(peerId: PeerId, memberIds: [PeerId]) -> Signal<Void, AddChannelMemberError> {

View File

@ -886,10 +886,19 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
case .attachMenuBotAllowed:
attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor)
case let .requestedPeer(_, peerId):
case let .requestedPeer(_, peerIds):
let botName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
let peerName = message.peers[peerId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
attributedString = addAttributesToStringWithRanges(strings.Notification_RequestedPeer(peerName, botName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId), (1, message.id.peerId)]))
var attributePeerIds: [(Int, EnginePeer.Id?)] = []
let resultTitleString: PresentationStrings.FormattedString
if peerIds.count == 1 {
attributePeerIds.append((0, peerIds.first))
attributePeerIds.append((1, message.id.peerId))
resultTitleString = strings.Notification_RequestedPeer(peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder), botName)
} else {
attributePeerIds.append((1, message.id.peerId))
resultTitleString = strings.Notification_RequestedPeerMultiple(peerDisplayTitles(peerIds, message.peers, strings: strings, nameDisplayOrder: nameDisplayOrder), botName)
}
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
case let .setChatWallpaper(_, forBoth):
if message.author?.id == accountPeerId {
if forBoth {

View File

@ -434,9 +434,9 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode {
})
case let .openWebView(url, simple):
self.controllerInteraction.openWebView(markupButton.title, url, simple, .generic)
case let .requestPeer(peerType, buttonId):
case let .requestPeer(peerType, buttonId, maxQuantity):
if let message = self.message {
self.controllerInteraction.openRequestedPeerSelection(message.id, peerType, buttonId)
self.controllerInteraction.openRequestedPeerSelection(message.id, peerType, buttonId, maxQuantity)
}
}
if dismissIfOnce {

View File

@ -565,7 +565,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openJoinLink: { _ in
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _ in
}, openRequestedPeerSelection: { _, _, _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {
}, displayGiveawayParticipationStatus: { _ in

View File

@ -225,7 +225,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
public let openJoinLink: (String) -> Void
public let openWebView: (String, String, Bool, ChatOpenWebViewSource) -> Void
public let activateAdAction: (EngineMessage.Id) -> Void
public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void
public let openRequestedPeerSelection: (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void
public let saveMediaToFiles: (EngineMessage.Id) -> Void
public let openNoAdsDemo: () -> Void
public let displayGiveawayParticipationStatus: (EngineMessage.Id) -> Void
@ -346,7 +346,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
openJoinLink: @escaping (String) -> Void,
openWebView: @escaping (String, String, Bool, ChatOpenWebViewSource) -> Void,
activateAdAction: @escaping (EngineMessage.Id) -> Void,
openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32) -> Void,
openRequestedPeerSelection: @escaping (EngineMessage.Id, ReplyMarkupButtonRequestPeerType, Int32, Int32) -> Void,
saveMediaToFiles: @escaping (EngineMessage.Id) -> Void,
openNoAdsDemo: @escaping () -> Void,
displayGiveawayParticipationStatus: @escaping (EngineMessage.Id) -> Void,

View File

@ -2996,7 +2996,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}, openJoinLink: { _ in
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _ in
}, openRequestedPeerSelection: { _, _, _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {
}, displayGiveawayParticipationStatus: { _ in

View File

@ -4174,17 +4174,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
}
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId in
}, openRequestedPeerSelection: { [weak self] messageId, peerType, buttonId, maxQuantity in
guard let self else {
return
}
let botName = self.presentationInterfaceState.renderedPeer?.peer.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
let context = self.context
let peerId = self.chatLocation.peerId
var createNewGroupImpl: (() -> Void)?
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [peerType], hasContactSelector: false, createNewGroup: {
createNewGroupImpl?()
}, hasCreation: true))
let presentConfirmation: (String, Bool, @escaping () -> Void) -> Void = { [weak self] peerName, isChannel, completion in
guard let strongSelf = self else {
@ -4242,18 +4238,64 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let controller = richTextAlertController(context: context, title: attributedTitle, text: attributedText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.RequestPeer_SelectionConfirmationSend, action: {
completion()
})])
strongSelf.present(controller, in: .window(.root))
}
if case .user = peerType, maxQuantity > 1 {
let presentationData = self.presentationData
var reachedLimitImpl: ((Int32) -> Void)?
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .requestedUsersSelection, options: [], isPeerEnabled: { peer in
if case let .user(user) = peer, user.botInfo == nil {
return true
} else {
return false
}
}, limit: maxQuantity, reachedLimit: { limit in
reachedLimitImpl?(limit)
}))
controller.navigationPresentation = .modal
reachedLimitImpl = { [weak controller] limit in
guard let controller else {
return
}
HapticFeedback().error()
controller.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.RequestPeer_ReachedMaximum(limit), timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
let _ = (controller.result
|> deliverOnMainQueue).startStandalone(next: { [weak controller] result in
guard let controller else {
return
}
var peerIds: [PeerId] = []
if case let .result(peerIdsValue, _) = result {
peerIds = peerIdsValue.compactMap({ peerId in
if case let .peer(peerId) = peerId {
return peerId
} else {
return nil
}
})
}
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: peerIds).startStandalone()
controller.dismiss()
})
self.push(controller)
} else {
var createNewGroupImpl: (() -> Void)?
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [peerType], hasContactSelector: false, createNewGroup: {
createNewGroupImpl?()
}, hasCreation: true))
controller.peerSelected = { [weak self, weak controller] peer, _ in
guard let strongSelf = self else {
return
}
if case .user = peerType {
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peer.id).startStandalone()
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peer.id]).startStandalone()
controller?.dismiss()
} else {
var isChannel = false
@ -4262,7 +4304,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let peerName = peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
presentConfirmation(peerName, isChannel, {
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peer.id).startStandalone()
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peer.id]).startStandalone()
controller?.dismiss()
})
}
@ -4277,7 +4319,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
complete()
})
}, completion: { peerId, dismiss in
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peerId).startStandalone()
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peerId]).startStandalone()
dismiss()
})
createGroupController.navigationPresentation = .modal
@ -4288,7 +4330,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
complete()
})
}, completion: { peerId, dismiss in
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerId: peerId).startStandalone()
let _ = context.engine.peers.sendBotRequestedPeer(messageId: messageId, buttonId: buttonId, requestedPeerIds: [peerId]).startStandalone()
dismiss()
})
createChannelController.navigationPresentation = .modal
@ -4296,6 +4338,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
self.push(controller)
}
}, saveMediaToFiles: { [weak self] messageId in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|> deliverOnMainQueue).startStandalone(next: { message in

View File

@ -202,12 +202,19 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
rightNavigationButton.isEnabled = true //count != 0 || self.params.alwaysEnabled
case .premiumGifting:
let maxCount: Int32 = 10
let maxCount: Int32 = self.limit ?? 10
var count = 0
if case let .contacts(contactsNode) = self.contactsNode.contentNode {
count = contactsNode.selectionState?.selectedPeerIndices.count ?? 0
}
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(count)/\(maxCount)")
case .requestedUsersSelection:
let maxCount: Int32 = self.limit ?? 10
var count = 0
if case let .contacts(contactsNode) = self.contactsNode.contentNode {
count = contactsNode.selectionState?.selectedPeerIndices.count ?? 0
}
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.RequestPeer_SelectUsers, counter: "\(count)/\(maxCount)")
case .channelCreation:
self.titleView.title = CounterContollerTitle(title: self.presentationData.strings.GroupInfo_AddParticipantTitle, counter: "")
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
@ -325,7 +332,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
switch strongSelf.mode {
case .groupCreation, .peerSelection, .chatSelection:
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled
case .channelCreation, .premiumGifting:
case .channelCreation, .premiumGifting, .requestedUsersSelection:
break
}
@ -334,8 +341,11 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
case .premiumGifting:
let maxCount: Int32 = 10
let maxCount: Int32 = strongSelf.limit ?? 10
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(updatedCount)/\(maxCount)")
case .requestedUsersSelection:
let maxCount: Int32 = strongSelf.limit ?? 10
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.RequestPeer_SelectUsers, counter: "\(updatedCount)/\(maxCount)")
case .peerSelection, .channelCreation, .chatSelection:
break
}
@ -407,7 +417,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
switch strongSelf.mode {
case .groupCreation, .peerSelection, .chatSelection:
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled
case .channelCreation, .premiumGifting:
case .channelCreation, .premiumGifting, .requestedUsersSelection:
break
}
switch strongSelf.mode {
@ -415,8 +425,11 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)")
case .premiumGifting:
let maxCount: Int32 = 10
let maxCount: Int32 = strongSelf.limit ?? 10
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(updatedCount)/\(maxCount)")
case .requestedUsersSelection:
let maxCount: Int32 = strongSelf.limit ?? 10
strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.RequestPeer_SelectUsers, counter: "\(updatedCount)/\(maxCount)")
case .peerSelection, .channelCreation, .chatSelection:
break
}

View File

@ -108,6 +108,11 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
proceedImpl?()
})
case .requestedUsersSelection:
placeholder = self.presentationData.strings.RequestPeer_SelectUsers_SearchPlaceholder
self.footerPanelNode = FooterPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: {
proceedImpl?()
})
default:
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
self.footerPanelNode = nil
@ -149,6 +154,8 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var displayTopPeers = false
if case .premiumGifting = mode {
displayTopPeers = true
} else if case .requestedUsersSelection = mode {
displayTopPeers = true
}
self.contentNode = .contacts(ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, selectionState: ContactListNodeGroupSelectionState()))
}
@ -233,7 +240,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
searchGroups = true
searchChannels = true
globalSearch = false
case .premiumGifting:
case .premiumGifting, .requestedUsersSelection:
searchChatList = true
}
let searchResultsNode = ContactListNode(context: context, presentation: .single(.search(signal: searchText.get(), searchChatList: searchChatList, searchDeviceContacts: false, searchGroups: searchGroups, searchChannels: searchChannels, globalSearch: globalSearch)), filters: filters, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true)

View File

@ -166,7 +166,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, openJoinLink: { _ in
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _ in
}, openRequestedPeerSelection: { _, _, _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {
}, displayGiveawayParticipationStatus: { _ in

View File

@ -1670,7 +1670,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, openJoinLink: { _ in
}, openWebView: { _, _, _, _ in
}, activateAdAction: { _ in
}, openRequestedPeerSelection: { _, _, _ in
}, openRequestedPeerSelection: { _, _, _, _ in
}, saveMediaToFiles: { _ in
}, openNoAdsDemo: {
}, displayGiveawayParticipationStatus: { _ in