diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6d528555b..bd6100058 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4258,6 +4258,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_request_peer_confirm_rights" = "This will also add {bot} to {chat} with the following rights: {rights}."; "lng_request_peer_confirm_send" = "Send"; "lng_request_user_title" = "Choose User"; +"lng_request_users_title" = "Choose Users"; "lng_request_user_premium_yes" = "The user should have a Premium subscription."; "lng_request_user_premium_no" = "The user shouldn't have a Premium subscription."; "lng_request_user_no" = "No such users"; diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 239d2d0ab..d49f1bbf7 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -415,12 +415,16 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { const auto peer = item->history()->peer; const auto itemId = item->id; const auto id = int32(button->buttonId); - const auto chosen = [=](not_null result) { + const auto chosen = [=](std::vector> result) { peer->session().api().request(MTPmessages_SendBotRequestedPeer( peer->input, MTP_int(itemId), MTP_int(id), - result->input + MTP_vector_from_range( + result + | ranges::views::transform([]( + not_null peer) { + return MTPInputPeer(peer->input); })) )).done([=](const MTPUpdates &result) { peer->session().api().applyUpdates(result); }).send(); diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp index 6ccc564fc..7d4d74453 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp @@ -40,11 +40,14 @@ public: not_null navigation, not_null bot, RequestPeerQuery query, - Fn)> callback); + Fn>)> callback); Main::Session &session() const override; void rowClicked(not_null row) override; + [[nodiscard]] rpl::producer selectedCountValue() const; + void submit(); + QString savedMessagesChatStatus() const override { return tr::lng_saved_forward_here(tr::now); } @@ -60,7 +63,9 @@ private: not_null _bot; RequestPeerQuery _query; base::flat_set> _commonGroups; - Fn)> _callback; + base::flat_set> _selected; + rpl::variable _selectedCount; + Fn>)> _callback; }; @@ -253,10 +258,10 @@ object_ptr CreatePeerByQueryBox( not_null navigation, not_null bot, RequestPeerQuery query, - Fn)> done) { + Fn>)> done) { const auto weak = std::make_shared>(); auto callback = [=](not_null peer) { - done(peer); + done({ peer }); if (const auto strong = weak->data()) { strong->closeBox(); } @@ -332,7 +337,7 @@ ChoosePeerBoxController::ChoosePeerBoxController( not_null navigation, not_null bot, RequestPeerQuery query, - Fn)> callback) + Fn>)> callback) : ChatsListBoxController(&navigation->session()) , _navigation(navigation) , _bot(bot) @@ -415,6 +420,8 @@ void ChoosePeerBoxController::prepareViewHook() { switch (_query.type) { case Type::User: return (_query.userIsBot == Restriction::Yes) ? tr::lng_request_bot_title() + : (_query.maxQuantity > 1) + ? tr::lng_request_users_title() : tr::lng_request_user_title(); case Type::Group: return tr::lng_request_group_title(); case Type::Broadcast: return tr::lng_request_channel_title(); @@ -425,10 +432,24 @@ void ChoosePeerBoxController::prepareViewHook() { } void ChoosePeerBoxController::rowClicked(not_null row) { + const auto limit = _query.maxQuantity; + const auto multiselect = (limit > 1); const auto peer = row->peer(); + if (multiselect) { + if (_selected.contains(peer) || _selected.size() < limit) { + delegate()->peerListSetRowChecked(row, !row->checked()); + if (row->checked()) { + _selected.emplace(peer); + } else { + _selected.remove(peer); + } + _selectedCount = int(_selected.size()); + } + return; + } const auto done = [callback = _callback, peer] { const auto onstack = callback; - onstack(peer); + onstack({ peer }); }; if (const auto user = peer->asUser()) { done(); @@ -438,6 +459,15 @@ void ChoosePeerBoxController::rowClicked(not_null row) { } } +rpl::producer ChoosePeerBoxController::selectedCountValue() const { + return _selectedCount.value(); +} + +void ChoosePeerBoxController::submit() { + const auto onstack = _callback; + onstack(ranges::to_vector(_selected)); +} + auto ChoosePeerBoxController::createRow(not_null history) -> std::unique_ptr { return FilterPeerByQuery(history->peer, _query, _commonGroups) @@ -474,7 +504,7 @@ void ShowChoosePeerBox( not_null navigation, not_null bot, RequestPeerQuery query, - Fn)> chosen) { + Fn>)> chosen) { const auto needCommonGroups = query.isBotParticipant && (query.type == RequestPeerQuery::Type::Group) && !query.myRights; @@ -488,22 +518,39 @@ void ShowChoosePeerBox( return; } const auto weak = std::make_shared>(); - auto initBox = [=](not_null box) { - box->addButton(tr::lng_cancel(), [box] { - box->closeBox(); - }); - }; - auto callback = [=, done = std::move(chosen)](not_null peer) { - done(peer); + auto callback = [=, done = std::move(chosen)]( + std::vector> peers) { + done(std::move(peers)); if (const auto strong = weak->data()) { strong->closeBox(); } }; + const auto limit = query.maxQuantity; + auto controller = std::make_unique( + navigation, + bot, + query, + std::move(callback)); + auto initBox = [=, ptr = controller.get()](not_null box) { + ptr->selectedCountValue() | rpl::start_with_next([=](int count) { + box->clearButtons(); + if (limit > 1) { + box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit))); + } + if (count > 0) { + box->addButton(tr::lng_intro_submit(), [=] { + ptr->submit(); + if (*weak) { + (*weak)->closeBox(); + } + }); + } + box->addButton(tr::lng_cancel(), [box] { + box->closeBox(); + }); + }, box->lifetime()); + }; *weak = navigation->parentController()->show(Box( - std::make_unique( - navigation, - bot, - query, - std::move(callback)), + std::move(controller), std::move(initBox))); } diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h index 2c9ab7a1d..982ec4784 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h @@ -21,4 +21,4 @@ void ShowChoosePeerBox( not_null navigation, not_null bot, RequestPeerQuery query, - Fn)> chosen); + Fn>)> chosen); diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index d92b99f94..7c9910e1e 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -1435,7 +1435,9 @@ ServiceAction ParseServiceAction( result.content = content; }, [&](const MTPDmessageActionRequestedPeer &data) { auto content = ActionRequestedPeer(); - content.peerId = ParsePeerId(data.vpeer()); + for (const auto &peer : data.vpeers().v) { + content.peers.push_back(ParsePeerId(peer)); + } content.buttonId = data.vbutton_id().v; result.content = content; }, [&](const MTPDmessageActionGiftCode &data) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 0dbb419bc..20bd15206 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -547,7 +547,7 @@ struct ActionGiftCode { }; struct ActionRequestedPeer { - PeerId peerId = 0; + std::vector peers; int buttonId = 0; }; diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index a830aceec..9a5f5b3d6 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -592,7 +592,11 @@ QByteArray SerializeMessage( pushActor(); pushAction("requested_peer"); push("button_id", data.buttonId); - push("peer_id", data.peerId.value); + auto values = std::vector(); + for (const auto &one : data.peers) { + values.push_back(Data::NumberToString(one.value)); + } + push("peers", SerializeArray(context, values)); }, [&](const ActionGiftCode &data) { pushAction("gift_code_prize"); push("gift_code", data.code); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 1b867ce6e..876757e2f 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -4476,18 +4476,45 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) { auto prepareRequestedPeer = [&]( const MTPDmessageActionRequestedPeer &action) { - const auto peerId = peerFromMTP(action.vpeer()); - const auto peer = history()->owner().peer(peerId); auto result = PreparedServiceText{}; + result.links.push_back(fromLink()); + + const auto &list = action.vpeers().v; + for (auto i = 0, count = int(list.size()); i != count; ++i) { + const auto id = peerFromMTP(list[i]); + + auto user = _history->owner().peer(id); + result.links.push_back(user->createOpenLink()); + + auto linkText = Ui::Text::Link(user->name(), 2 + i); + if (i == 0) { + result.text = linkText; + } else if (i + 1 == count) { + result.text = tr::lng_action_add_users_and_last( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } else { + result.text = tr::lng_action_add_users_and_one( + tr::now, + lt_accumulated, + result.text, + lt_user, + linkText, + Ui::Text::WithEntities); + } + } + result.text = tr::lng_action_shared_chat_with_bot( tr::now, lt_chat, - Ui::Text::Link(peer->name(), 1), + result.text, lt_bot, Ui::Text::Link(history()->peer->name(), 2), Ui::Text::WithEntities); - result.links.push_back(peer->createOpenLink()); - result.links.push_back(history()->peer->createOpenLink()); return result; }; diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.cpp b/Telegram/SourceFiles/history/history_item_reply_markup.cpp index c15381e92..05af27179 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.cpp +++ b/Telegram/SourceFiles/history/history_item_reply_markup.cpp @@ -37,10 +37,11 @@ namespace { } [[nodiscard]] RequestPeerQuery RequestPeerQueryFromTL( - const MTPRequestPeerType &query) { + const MTPDkeyboardButtonRequestPeer &query) { using Type = RequestPeerQuery::Type; using Restriction = RequestPeerQuery::Restriction; auto result = RequestPeerQuery(); + result.maxQuantity = query.vmax_quantity().v; const auto restriction = [](const MTPBool *value) { return !value ? Restriction::Any @@ -51,7 +52,7 @@ namespace { const auto rights = [](const MTPChatAdminRights *value) { return value ? ChatAdminRightsInfo(*value).flags : ChatAdminRights(); }; - query.match([&](const MTPDrequestPeerTypeUser &data) { + query.vpeer_type().match([&](const MTPDrequestPeerTypeUser &data) { result.type = Type::User; result.userIsBot = restriction(data.vbot()); result.userIsPremium = restriction(data.vpremium()); @@ -134,7 +135,7 @@ void HistoryMessageMarkupData::fillRows( }, [&](const MTPDkeyboardButtonRequestPhone &data) { row.emplace_back(Type::RequestPhone, qs(data.vtext())); }, [&](const MTPDkeyboardButtonRequestPeer &data) { - const auto query = RequestPeerQueryFromTL(data.vpeer_type()); + const auto query = RequestPeerQueryFromTL(data); row.emplace_back( Type::RequestPeer, qs(data.vtext()), diff --git a/Telegram/SourceFiles/history/history_item_reply_markup.h b/Telegram/SourceFiles/history/history_item_reply_markup.h index 828432113..5ed8dc4a3 100644 --- a/Telegram/SourceFiles/history/history_item_reply_markup.h +++ b/Telegram/SourceFiles/history/history_item_reply_markup.h @@ -45,6 +45,8 @@ struct RequestPeerQuery { Yes, No, }; + + int maxQuantity = 0; Type type = Type::User; Restriction userIsBot = Restriction::Any; Restriction userIsPremium = Restriction::Any; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 879e2ba0e..6f173d87b 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -170,7 +170,7 @@ messageActionGiftPremium#c83d6aec flags:# currency:string amount:long months:int messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; -messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; +messageActionRequestedPeer#31518e9b button_id:int peers:Vector = MessageAction; messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags.1?true wallpaper:WallPaper = MessageAction; messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long = MessageAction; messageActionGiveawayLaunch#332ba9ed = MessageAction; @@ -594,6 +594,7 @@ inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; +inputStickerSetEmojiChannelDefaultStatuses#49748553 = InputStickerSet; stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true text_color:flags.9?true channel_emoji_status:flags.10?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; @@ -619,7 +620,7 @@ inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = Keyboard keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonWebView#13767230 text:string url:string = KeyboardButton; keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton; -keyboardButtonRequestPeer#d0b468c text:string button_id:int peer_type:RequestPeerType = KeyboardButton; +keyboardButtonRequestPeer#53d7bfd8 text:string button_id:int peer_type:RequestPeerType max_quantity:int = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -1757,6 +1758,7 @@ account.deleteAutoSaveExceptions#53bc0020 = Bool; account.invalidateSignInCodes#ca8ae8ba codes:Vector = Bool; account.updateColor#7cefa15d flags:# for_profile:flags.1?true color:flags.2?int background_emoji_id:flags.0?long = Bool; account.getDefaultBackgroundEmojis#a60ab9ce hash:long = EmojiList; +account.getChannelDefaultEmojiStatuses#7727a7d5 hash:long = account.EmojiStatuses; account.getChannelRestrictedStatusEmojis#35a9e0d5 hash:long = EmojiList; users.getUsers#d91a548 id:Vector = Vector; @@ -1967,7 +1969,7 @@ messages.clearRecentReactions#9dfeefb4 = Bool; messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates; messages.setDefaultHistoryTTL#9eb51445 period:int = Bool; messages.getDefaultHistoryTTL#658b7188 = DefaultHistoryTTL; -messages.sendBotRequestedPeer#fe38d01b peer:InputPeer msg_id:int button_id:int requested_peer:InputPeer = Updates; +messages.sendBotRequestedPeer#91b2d060 peer:InputPeer msg_id:int button_id:int requested_peers:Vector = Updates; messages.getEmojiGroups#7488ce5b hash:int = messages.EmojiGroups; messages.getEmojiStatusGroups#2ecd56cd hash:int = messages.EmojiGroups; messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; @@ -2219,4 +2221,4 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 168 +// LAYER 169