From 7deb5bfdf22bbc811dff90a31be6ae5b54b04665 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 28 Mar 2025 00:41:05 +0500 Subject: [PATCH] Update API scheme, something works now. --- Telegram/Resources/langs/lang.strings | 5 + Telegram/SourceFiles/calls/calls.style | 31 ++- Telegram/SourceFiles/calls/calls_instance.cpp | 9 +- .../calls/group/calls_group_call.cpp | 241 +++++++++++------- .../calls/group/calls_group_call.h | 7 + .../calls/group/calls_group_common.cpp | 100 ++++++++ .../calls/group/calls_group_common.h | 11 + Telegram/SourceFiles/data/data_channel.cpp | 3 +- Telegram/SourceFiles/data/data_chat.cpp | 3 +- Telegram/SourceFiles/data/data_group_call.cpp | 14 +- Telegram/SourceFiles/data/data_group_call.h | 24 +- .../bot/starref/info_bot_starref_common.cpp | 15 +- .../bot/starref/info_bot_starref_common.h | 8 + Telegram/SourceFiles/mtproto/scheme/api.tl | 2 +- Telegram/SourceFiles/tde2e/tde2e_api.cpp | 92 +++++-- Telegram/SourceFiles/tde2e/tde2e_api.h | 9 +- .../SourceFiles/window/window_main_menu.cpp | 44 ++-- .../window/window_session_controller.cpp | 3 +- 18 files changed, 464 insertions(+), 157 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c908b48a04..b35a5259d5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4913,6 +4913,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_confcall_join_button" = "Join Group Call"; "lng_confcall_create_link" = "Create Call Link"; "lng_confcall_create_link_description" = "You can create a link that will allow your friends on Telegram to join the call."; +"lng_confcall_link_title" = "Call Link"; +"lng_confcall_link_about" = "Anyone on Telegram can join your call by following the link below."; +"lng_confcall_link_or" = "or"; +"lng_confcall_link_join" = "Be the first to join the call and add people from there. {link}"; +"lng_confcall_link_join_link" = "Open call {arrow}"; "lng_no_mic_permission" = "Telegram needs microphone access so that you can make calls and record voice messages."; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index a28a18169e..3a7c926bd7 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -1483,4 +1483,33 @@ groupCallCalendarColors: CalendarColors { titleTextColor: groupCallMembersFg; } -// + +confcallLinkButton: RoundButton(defaultActiveButton) { + height: 42px; + textTop: 12px; + style: semiboldTextStyle; +} +confcallLinkBox: Box(defaultBox) { + buttonPadding: margins(22px, 11px, 22px, 64px); + buttonHeight: 42px; + button: confcallLinkButton; + shadowIgnoreTopSkip: true; +} +confcallLinkCopyButton: RoundButton(confcallLinkButton) { + icon: icon {{ "info/edit/links_copy", activeButtonFg }}; + iconOver: icon {{ "info/edit/links_copy", activeButtonFgOver }}; + iconPosition: point(-1px, 5px); +} +confcallLinkShareButton: RoundButton(confcallLinkCopyButton) { + icon: icon {{ "info/edit/links_share", activeButtonFg }}; + iconOver: icon {{ "info/edit/links_share", activeButtonFgOver }}; +} +confcallLinkHeaderIconPadding: margins(0px, 32px, 0px, 10px); +confcallLinkTitlePadding: margins(0px, 0px, 0px, 12px); +confcallLinkCenteredText: FlatLabel(defaultFlatLabel) { + align: align(top); + minWidth: 40px; +} +confcallLinkFooterOr: FlatLabel(confcallLinkCenteredText) { + textFg: windowSubTextFg; +} diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index a7e7c102ab..6c8298f4cb 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -690,7 +690,14 @@ void Instance::handleGroupCallUpdate( }, [](const auto &) -> CallId { Unexpected("Type in Instance::handleGroupCallUpdate."); }); - if (const auto existing = session->data().groupCall(callId)) { + if (update.type() == mtpc_updateGroupCallChainBlocks) { + const auto existing = session->data().groupCall(callId); + if (existing + && _currentGroupCall + && _currentGroupCall->lookupReal() == existing) { + _currentGroupCall->handleUpdate(update); + } + } else if (const auto existing = session->data().groupCall(callId)) { existing->enqueueUpdate(update); } else { applyGroupCallUpdateChecked(session, update); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 3ab9bfb314..066fdddcf6 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1405,101 +1405,162 @@ void GroupCall::rejoin(not_null as) { const auto weak = base::make_weak(&_instanceGuard); _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) { crl::on_main(weak, [=, payload = std::move(payload)] { - if (state() != State::Joining) { - _joinState.finish(); - checkNextJoinAction(); - return; - } - const auto ssrc = payload.audioSsrc; + _joinState.payload = { + .ssrc = payload.audioSsrc, + .json = QByteArray::fromStdString(payload.json), + }; LOG(("Call Info: Join payload received, joining with ssrc: %1." - ).arg(ssrc)); - - const auto json = QByteArray::fromStdString(payload.json); - const auto wasMuteState = muted(); - const auto wasVideoStopped = !isSharingCamera(); - using Flag = MTPphone_JoinGroupCall::Flag; - const auto flags = (wasMuteState != MuteState::Active - ? Flag::f_muted - : Flag(0)) - | (_joinHash.isEmpty() - ? Flag(0) - : Flag::f_invite_hash) - | (wasVideoStopped - ? Flag::f_video_stopped - : Flag(0)) - | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) - | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); - _api.request(MTPphone_JoinGroupCall( - MTP_flags(flags), - (_conferenceLinkSlug.isEmpty() - ? inputCall() - : MTP_inputGroupCallSlug( - MTP_string(_conferenceLinkSlug))), - joinAs()->input, - MTP_string(_joinHash), - (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), - (_e2e - ? MTP_bytes( - _e2e->lastBlock0().value_or(TdE2E::Block()).data) - : MTPbytes()), - MTP_int(_conferenceJoinMessageId.bare), - MTP_dataJSON(MTP_bytes(json)) - )).done([=]( - const MTPUpdates &updates, - const MTP::Response &response) { - _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); - _serverTimeMsGotAt = crl::now(); - - _joinState.finish(ssrc); - _mySsrcs.emplace(ssrc); - - setState((_instanceState.current() - == InstanceState::Disconnected) - ? State::Connecting - : State::Joined); - applyMeInCallLocally(); - maybeSendMutedUpdate(wasMuteState); - _peer->session().api().applyUpdates(updates); - applyQueuedSelfUpdates(); - checkFirstTimeJoined(); - _screenJoinState.nextActionPending = true; - checkNextJoinAction(); - if (wasVideoStopped == isSharingCamera()) { - sendSelfUpdate(SendUpdateType::CameraStopped); - } - if (isCameraPaused()) { - sendSelfUpdate(SendUpdateType::CameraPaused); - } - sendPendingSelfUpdates(); - if (!_reloadedStaleCall - && _state.current() != State::Joining) { - if (const auto real = lookupReal()) { - _reloadedStaleCall = true; - real->reloadIfStale(); - } - } - requestSubchainBlocks(0, 0); - requestSubchainBlocks(1, 0); - }).fail([=](const MTP::Error &error) { - _joinState.finish(); - - const auto type = error.type(); - LOG(("Call Error: Could not join, error: %1").arg(type)); - - if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { - rejoin(); - return; - } - - hangup(); - Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) - ? tr::lng_group_not_accessible(tr::now) - : Lang::Hard::ServerError()); - }).send(); + ).arg(_joinState.payload.ssrc)); + sendJoinRequest(); }); }); } +void GroupCall::sendJoinRequest() { + if (state() != State::Joining) { + _joinState.finish(); + checkNextJoinAction(); + return; + } + auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray(); + if (_e2e && joinBlock.isEmpty()) { + _joinState.finish(); + LOG(("Call Error: Could not generate join block.")); + hangup(); + Ui::Toast::Show(u"Could not generate join block."_q); + return; + } + const auto wasMuteState = muted(); + const auto wasVideoStopped = !isSharingCamera(); + using Flag = MTPphone_JoinGroupCall::Flag; + const auto flags = (wasMuteState != MuteState::Active + ? Flag::f_muted + : Flag(0)) + | (_joinHash.isEmpty() + ? Flag(0) + : Flag::f_invite_hash) + | (wasVideoStopped + ? Flag::f_video_stopped + : Flag(0)) + | (_conferenceJoinMessageId ? Flag::f_invite_msg_id : Flag()) + | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); + _api.request(MTPphone_JoinGroupCall( + MTP_flags(flags), + (_conferenceLinkSlug.isEmpty() + ? inputCall() + : MTP_inputGroupCallSlug( + MTP_string(_conferenceLinkSlug))), + joinAs()->input, + MTP_string(_joinHash), + (_e2e ? TdE2E::PublicKeyToMTP(_e2e->myKey()) : MTPint256()), + MTP_bytes(joinBlock), + MTP_int(_conferenceJoinMessageId.bare), + MTP_dataJSON(MTP_bytes(_joinState.payload.json)) + )).done([=]( + const MTPUpdates &updates, + const MTP::Response &response) { + _serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); + _serverTimeMsGotAt = crl::now(); + + _joinState.finish(_joinState.payload.ssrc); + _mySsrcs.emplace(_joinState.ssrc); + + setState((_instanceState.current() + == InstanceState::Disconnected) + ? State::Connecting + : State::Joined); + applyMeInCallLocally(); + maybeSendMutedUpdate(wasMuteState); + _peer->session().api().applyUpdates(updates); + applyQueuedSelfUpdates(); + checkFirstTimeJoined(); + _screenJoinState.nextActionPending = true; + checkNextJoinAction(); + if (wasVideoStopped == isSharingCamera()) { + sendSelfUpdate(SendUpdateType::CameraStopped); + } + if (isCameraPaused()) { + sendSelfUpdate(SendUpdateType::CameraPaused); + } + sendPendingSelfUpdates(); + if (!_reloadedStaleCall + && _state.current() != State::Joining) { + if (const auto real = lookupReal()) { + _reloadedStaleCall = true; + real->reloadIfStale(); + } + } + if (_e2e) { + _e2e->joined(); + } + }).fail([=](const MTP::Error &error) { + const auto type = error.type(); + if (_e2e) { + if (type == u"CONF_WRITE_CHAIN_INVALID"_q) { + refreshLastBlockAndJoin(); + return; + } + } + _joinState.finish(); + + LOG(("Call Error: Could not join, error: %1").arg(type)); + + if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") { + rejoin(); + return; + } + + hangup(); + Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q) + ? tr::lng_group_not_accessible(tr::now) + : Lang::Hard::ServerError()); + }).send(); +} + +void GroupCall::refreshLastBlockAndJoin() { + Expects(_e2e != nullptr); + + if (state() != State::Joining) { + _joinState.finish(); + checkNextJoinAction(); + return; + } + _api.request(MTPphone_GetGroupCallChainBlocks( + inputCall(), + MTP_int(0), + MTP_int(-1), + MTP_int(1) + )).done([=](const MTPUpdates &result) { + if (result.type() != mtpc_updates) { + _joinState.finish(); + LOG(("Call Error: Bad result in GroupCallChainBlocks.")); + hangup(); + Ui::Toast::Show(u"Bad Updates in GroupCallChainBlocks."_q); + return; + } + _e2e->refreshLastBlock0({}); + const auto &data = result.c_updates(); + for (const auto &update : data.vupdates().v) { + if (update.type() != mtpc_updateGroupCallChainBlocks) { + continue; + } + const auto &data = update.c_updateGroupCallChainBlocks(); + const auto &blocks = data.vblocks().v; + if (!blocks.isEmpty()) { + _e2e->refreshLastBlock0(TdE2E::Block{ blocks.back().v }); + break; + } + } + sendJoinRequest(); + }).fail([=](const MTP::Error &error) { + _joinState.finish(); + const auto &type = error.type(); + LOG(("Call Error: Could not get last block, error: %1").arg(type)); + hangup(); + Ui::Toast::Show(error.type()); + }).send(); +} + void GroupCall::requestSubchainBlocks(int subchain, int height) { Expects(subchain >= 0 && subchain < kSubChainsCount); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index e839eeaff7..067351761f 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -466,9 +466,14 @@ private: Joining, Leaving, }; + struct JoinPayload { + uint32 ssrc = 0; + QByteArray json; + }; struct JoinState { uint32 ssrc = 0; JoinAction action = JoinAction::None; + JoinPayload payload; bool nextActionPending = false; void finish(uint32 updatedSsrc = 0) { @@ -540,6 +545,8 @@ private: void rejoinPresentation(); void leavePresentation(); void checkNextJoinAction(); + void sendJoinRequest(); + void refreshLastBlockAndJoin(); void requestSubchainBlocks(int subchain, int height); void audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data); diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.cpp b/Telegram/SourceFiles/calls/group/calls_group_common.cpp index 20223c9ebf..106fa229d8 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_common.cpp @@ -8,12 +8,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/group/calls_group_common.h" #include "base/platform/base_platform_info.h" +#include "boxes/share_box.h" +#include "data/data_group_call.h" +#include "info/bot/starref/info_bot_starref_common.h" +#include "ui/boxes/boost_box.h" +#include "ui/widgets/buttons.h" #include "ui/widgets/labels.h" #include "ui/layers/generic_box.h" #include "ui/text/text_utilities.h" +#include "ui/vertical_list.h" #include "lang/lang_keys.h" +#include "window/window_session_controller.h" #include "styles/style_layers.h" #include "styles/style_calls.h" +#include "styles/style_chat.h" + +#include +#include namespace Calls::Group { @@ -74,4 +85,93 @@ void ConferenceCallJoinConfirm( }); } +void ShowConferenceCallLinkBox( + not_null controller, + std::shared_ptr call, + const QString &link, + bool initial) { + controller->show(Box([=](not_null box) { + box->setStyle(st::confcallLinkBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + box->addTopButton(st::boxTitleClose, [=] { + box->closeBox(); + }); + + box->addRow( + Info::BotStarRef::CreateLinkHeaderIcon(box, &call->session()), + st::boxRowPadding + st::confcallLinkHeaderIconPadding); + box->addRow( + object_ptr>( + box, + object_ptr( + box, + tr::lng_confcall_link_title(), + st::boxTitle)), + st::boxRowPadding + st::confcallLinkTitlePadding); + box->addRow( + object_ptr( + box, + tr::lng_confcall_link_about(), + st::confcallLinkCenteredText), + st::boxRowPadding + )->setTryMakeSimilarLines(true); + + Ui::AddSkip(box->verticalLayout(), st::defaultVerticalListSkip * 2); + const auto preview = box->addRow( + Info::BotStarRef::MakeLinkLabel(box, link)); + Ui::AddSkip(box->verticalLayout()); + + const auto copyCallback = [=] { + QApplication::clipboard()->setText(link); + box->uiShow()->showToast(tr::lng_username_copied(tr::now)); + }; + const auto shareCallback = [=] { + FastShareLink(controller, link); + }; + preview->setClickedCallback(copyCallback); + [[maybe_unused]] const auto copy = box->addButton( + tr::lng_group_invite_copy(), + copyCallback, + st::confcallLinkCopyButton); + [[maybe_unused]] const auto share = box->addButton( + tr::lng_group_invite_share(), + shareCallback, + st::confcallLinkShareButton); + + const auto sep = Ui::CreateChild( + copy->parentWidget(), + tr::lng_confcall_link_or(), + st::confcallLinkFooterOr); + const auto footer = Ui::CreateChild( + copy->parentWidget(), + tr::lng_confcall_link_join( + lt_link, + tr::lng_confcall_link_join_link( + lt_arrow, + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), + [](QString v) { return Ui::Text::Link(v); }), + Ui::Text::WithEntities), + st::confcallLinkCenteredText); + footer->setTryMakeSimilarLines(true); + copy->geometryValue() | rpl::start_with_next([=](QRect geometry) { + const auto width = st::boxWideWidth + - st::boxRowPadding.left() + - st::boxRowPadding.right(); + footer->resizeToWidth(width); + const auto &st = box->getDelegate()->style(); + const auto top = geometry.y() + geometry.height(); + const auto available = st.buttonPadding.bottom(); + const auto footerHeight = sep->height() + footer->height(); + const auto skip = (available - footerHeight) / 2; + sep->move( + st::boxRowPadding.left() + (width - sep->width()) / 2, + top + skip); + footer->moveToLeft( + st::boxRowPadding.left(), + top + skip + sep->height()); + }, footer->lifetime()); + })); +} + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index ea18eadd3a..f2716f16bf 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -16,6 +16,7 @@ class GroupCall; } // namespace Data namespace Ui { +class Show; class GenericBox; } // namespace Ui @@ -23,6 +24,10 @@ namespace TdE2E { class Call; } // namespace TdE2E +namespace Window { +class SessionController; +} // namespace Window + namespace Calls::Group { constexpr auto kDefaultVolume = 10000; @@ -113,4 +118,10 @@ void ConferenceCallJoinConfirm( std::shared_ptr call, Fn join); +void ShowConferenceCallLinkBox( + not_null controller, + std::shared_ptr call, + const QString &link, + bool initial = false); + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index d1a99f90b3..0793099c70 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -1001,7 +1001,8 @@ void ChannelData::setGroupCall( data.vid().v, data.vaccess_hash().v, scheduleDate, - rtmp); + rtmp, + false); // conference owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index faea166b1d..8bd3a8cac9 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -234,7 +234,8 @@ void ChatData::setGroupCall( data.vid().v, data.vaccess_hash().v, scheduleDate, - rtmp); + rtmp, + false); // conference owner().registerGroupCall(_call.get()); session().changes().peerUpdated(this, UpdateFlag::GroupCall); addFlags(Flag::CallActive); diff --git a/Telegram/SourceFiles/data/data_group_call.cpp b/Telegram/SourceFiles/data/data_group_call.cpp index fc8f9fc260..6a1663a5ee 100644 --- a/Telegram/SourceFiles/data/data_group_call.cpp +++ b/Telegram/SourceFiles/data/data_group_call.cpp @@ -62,7 +62,8 @@ GroupCall::GroupCall( CallId id, uint64 accessHash, TimeId scheduleDate, - bool rtmp) + bool rtmp, + bool conference) : _id(id) , _accessHash(accessHash) , _peer(peer) @@ -70,15 +71,26 @@ GroupCall::GroupCall( , _speakingByActiveFinishTimer([=] { checkFinishSpeakingByActive(); }) , _scheduleDate(scheduleDate) , _rtmp(rtmp) +, _conference(conference) , _listenersHidden(rtmp) { + if (_conference) { + session().data().registerGroupCall(this); + } } GroupCall::~GroupCall() { + if (_conference) { + session().data().unregisterGroupCall(this); + } api().request(_unknownParticipantPeersRequestId).cancel(); api().request(_participantsRequestId).cancel(); api().request(_reloadRequestId).cancel(); } +Main::Session &GroupCall::session() const { + return _peer->session(); +} + CallId GroupCall::id() const { return _id; } diff --git a/Telegram/SourceFiles/data/data_group_call.h b/Telegram/SourceFiles/data/data_group_call.h index 506c9019c9..104ee83b21 100644 --- a/Telegram/SourceFiles/data/data_group_call.h +++ b/Telegram/SourceFiles/data/data_group_call.h @@ -17,6 +17,10 @@ namespace Calls { struct ParticipantVideoParams; } // namespace Calls +namespace Main { +class Session; +} // namespace Main + namespace TdE2E { struct ParticipantState; struct UserId; @@ -64,9 +68,12 @@ public: CallId id, uint64 accessHash, TimeId scheduleDate, - bool rtmp); + bool rtmp, + bool conference); ~GroupCall(); + [[nodiscard]] Main::Session &session() const; + [[nodiscard]] CallId id() const; [[nodiscard]] bool loaded() const; [[nodiscard]] bool rtmp() const; @@ -247,13 +254,14 @@ private: rpl::event_stream> _participantSpeaking; rpl::event_stream<> _participantsReloaded; - bool _joinMuted = false; - bool _canChangeJoinMuted = true; - bool _allParticipantsLoaded = false; - bool _joinedToTop = false; - bool _applyingQueuedUpdates = false; - bool _rtmp = false; - bool _listenersHidden = false; + bool _joinMuted : 1 = false; + bool _canChangeJoinMuted : 1 = true; + bool _allParticipantsLoaded : 1 = false; + bool _joinedToTop : 1 = false; + bool _applyingQueuedUpdates : 1 = false; + bool _rtmp : 1 = false; + bool _conference : 1 = false; + bool _listenersHidden : 1 = false; }; diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp index a088bc9585..50149d8a63 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.cpp @@ -73,7 +73,7 @@ void ConnectStarRef( [[nodiscard]] object_ptr CreateLinkIcon( not_null parent, - not_null bot, + not_null session, int users) { auto result = object_ptr(parent); const auto raw = result.data(); @@ -92,7 +92,7 @@ void ConnectStarRef( const auto inner = QSize(innerSide, innerSide); const auto state = raw->lifetime().make_state(State{ .icon = ChatHelpers::GenerateLocalTgsSticker( - &bot->session(), + session, u"starref_link"_q), }); state->icon->overrideEmojiUsesTextColor(true); @@ -419,7 +419,7 @@ object_ptr MakeLinkLabel( p.drawText( QRect(skip, margins.top(), available, font->height), style::al_top, - font->elided(link, available)); + font->elided(text, available)); }, raw->lifetime()); return result; @@ -441,7 +441,7 @@ object_ptr StarRefLinkBox( }); box->addRow( - CreateLinkIcon(box, bot, row.state.users), + CreateLinkIcon(box, &bot->session(), row.state.users), st::boxRowPadding + st::starrefJoinUserpicsPadding); box->addRow( object_ptr>( @@ -1058,4 +1058,11 @@ ConnectedBots Parse( return result; } +object_ptr CreateLinkHeaderIcon( + not_null parent, + not_null session, + int usersCount) { + return CreateLinkIcon(parent, session, usersCount); +} + } // namespace Info::BotStarRef \ No newline at end of file diff --git a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h index 02b8bf78e9..46e643241d 100644 --- a/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h +++ b/Telegram/SourceFiles/info/bot/starref/info_bot_starref_common.h @@ -105,4 +105,12 @@ void FinishProgram( not_null session, const MTPpayments_ConnectedStarRefBots &bots); +[[nodiscard]] object_ptr MakeLinkLabel( + not_null parent, + const QString &link); +[[nodiscard]] object_ptr CreateLinkHeaderIcon( + not_null parent, + not_null session, + int usersCount = 0); + } // namespace Info::BotStarRef diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 71bf5182ad..5857a15726 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -2606,7 +2606,7 @@ phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; -phone.createConferenceCall#dae2632f public_key:int256 zero_block:bytes = phone.GroupCall; +phone.createConferenceCall#fbcefee6 random_id:int = phone.GroupCall; phone.deleteConferenceCallParticipant#7b8cc2a3 call:InputGroupCall peer:InputPeer block:bytes = Updates; phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; phone.inviteConferenceCallParticipant#3e9cf7ee call:InputGroupCall user_id:InputUser = Updates; diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.cpp b/Telegram/SourceFiles/tde2e/tde2e_api.cpp index 190247a8bc..bd3940b39c 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.cpp +++ b/Telegram/SourceFiles/tde2e/tde2e_api.cpp @@ -57,7 +57,15 @@ PublicKey Call::myKey() const { return _myKey; } -Block Call::makeZeroBlock() const { +void Call::refreshLastBlock0(std::optional block) { + _lastBlock0 = std::move(block); +} + +Block Call::makeJoinBlock() { + if (failed()) { + return {}; + } + const auto publicKeyView = std::string_view{ reinterpret_cast(&_myKey), sizeof(_myKey), @@ -66,22 +74,48 @@ Block Call::makeZeroBlock() const { Assert(publicKeyId.is_ok()); const auto myKeyId = std::int64_t(_myKeyId.v); - const auto result = tde2e_api::call_create_zero_block(myKeyId, { - .height = 0, - .participants = { { - .user_id = std::int64_t(_myUserId.v), - .public_key_id = publicKeyId.value(), - .permissions = kPermissionAdd | kPermissionRemove, - } }, - }); - Assert(result.is_ok()); + const auto myParticipant = tde2e_api::CallParticipant{ + .user_id = std::int64_t(_myUserId.v), + .public_key_id = publicKeyId.value(), + .permissions = kPermissionAdd | kPermissionRemove, + }; + + const auto result = _lastBlock0 + ? tde2e_api::call_create_self_add_block( + myKeyId, + Slice(_lastBlock0->data), + myParticipant) + : tde2e_api::call_create_zero_block(myKeyId, { + .height = 0, + .participants = { myParticipant }, + }); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + return {}; + } return { .data = QByteArray::fromStdString(result.value()), }; } -void Call::create(const Block &last) { +void Call::joined() { + shortPoll(0); + if (_id.v) { + shortPoll(1); + } +} + +void Call::apply(const Block &last) { + if (_id.v) { + const auto result = tde2e_api::call_apply_block( + std::int64_t(_id.v), + Slice(last.data)); + if (!result.is_ok()) { + LOG_AND_FAIL(result.error(), CallFailure::Unknown); + } + return; + } const auto id = tde2e_api::call_create( std::int64_t(_myKeyId.v), Slice(last.data)); @@ -89,6 +123,7 @@ void Call::create(const Block &last) { LOG_AND_FAIL(id.error(), CallFailure::Unknown); return; } + _id = CallId{ uint64(id.value()) }; for (auto i = 0; i != kSubChainsCount; ++i) { auto &entry = _subchains[i]; @@ -98,7 +133,9 @@ void Call::create(const Block &last) { entry.shortPollTimer.setCallback([=] { shortPoll(i); }); - entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); + if (!entry.waitingTimer.isActive()) { + entry.shortPollTimer.callOnce(kShortPollChainBlocksTimeout); + } } } @@ -108,14 +145,12 @@ void Call::apply( const Block &block, bool fromShortPoll) { Expects(subchain >= 0 && subchain < kSubChainsCount); + Expects(_id.v != 0 || !fromShortPoll || !subchain); if (!subchain && index >= _lastBlock0Height) { _lastBlock0 = block; _lastBlock0Height = index; } - if (!subchain && !_id.v) { - create(block); - } if (failed()) { return; } @@ -123,22 +158,19 @@ void Call::apply( auto &entry = _subchains[subchain]; if (!fromShortPoll) { entry.lastUpdate = crl::now(); - if (index > entry.height + 1) { + if (index > entry.height || (!_id.v && subchain != 0)) { entry.waiting.emplace(index, block); checkWaitingBlocks(subchain); return; } } - const auto result = tde2e_api::call_apply_block( - std::int64_t(_id.v), - Slice(block.data)); - if (!result.is_ok()) { - LOG_AND_FAIL(result.error(), CallFailure::Unknown); + if (failed()) { return; + } else if (!_id.v || entry.height == index) { + apply(block); } - - entry.height = std::max(entry.height, index); + entry.height = index + 1; checkWaitingBlocks(subchain); } @@ -150,7 +182,10 @@ void Call::checkWaitingBlocks(int subchain, bool waited) { } auto &entry = _subchains[subchain]; - if (entry.shortPolling) { + if (!_id.v) { + entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); + return; + } else if (entry.shortPolling) { return; } auto &waiting = entry.waiting; @@ -186,6 +221,11 @@ void Call::shortPoll(int subchain) { auto &entry = _subchains[subchain]; entry.waitingTimer.cancel(); entry.shortPollTimer.cancel(); + if (subchain && !_id.v) { + // Not ready. + entry.waitingTimer.callOnce(kShortPollChainBlocksWaitFor); + return; + } entry.shortPolling = true; _subchainRequests.fire({ subchain, entry.height }); } @@ -217,10 +257,6 @@ rpl::producer Call::failures() const { return _failures.events(); } -const std::optional &Call::lastBlock0() const { - return _lastBlock0; -} - std::vector Call::encrypt(const std::vector &data) const { const auto result = tde2e_api::call_encrypt( std::int64_t(_id.v), diff --git a/Telegram/SourceFiles/tde2e/tde2e_api.h b/Telegram/SourceFiles/tde2e/tde2e_api.h index 5385a8681c..931702b0fb 100644 --- a/Telegram/SourceFiles/tde2e/tde2e_api.h +++ b/Telegram/SourceFiles/tde2e/tde2e_api.h @@ -55,10 +55,7 @@ public: [[nodiscard]] PublicKey myKey() const; - [[nodiscard]] Block makeZeroBlock() const; - - void create(const Block &last); - + void joined(); void apply( int subchain, int index, @@ -75,7 +72,8 @@ public: [[nodiscard]] std::optional failed() const; [[nodiscard]] rpl::producer failures() const; - [[nodiscard]] const std::optional &lastBlock0() const; + void refreshLastBlock0(std::optional block); + [[nodiscard]] Block makeJoinBlock(); [[nodiscard]] std::vector encrypt( const std::vector &data) const; @@ -94,6 +92,7 @@ private: int height = 0; }; + void apply(const Block &last); void fail(CallFailure reason); void checkWaitingBlocks(int subchain, bool waited = false); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 9dede3dcd5..3606e9cf87 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -10,9 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "apiwrap.h" #include "base/event_filter.h" #include "base/qt_signal_producer.h" +#include "base/random.h" #include "boxes/about_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/premium_preview_box.h" +#include "calls/group/calls_group_common.h" #include "calls/calls_box_controller.h" #include "calls/calls_instance.h" #include "core/application.h" @@ -121,18 +123,16 @@ constexpr auto kPlayStatusLimit = 2; (height - st::inviteViaLinkIcon.height()) / 2); }, icon->lifetime()); - const auto creating = result->lifetime().make_state(); + const auto creating = result->lifetime().make_state(); result->setClickedCallback([=] { if (*creating) { return; } - *creating = true; - auto e2e = std::make_shared( - TdE2E::MakeUserId(controller->session().user())); + *creating = base::RandomValue(); + const auto show = controller->uiShow(); const auto session = &controller->session(); session->api().request(MTPphone_CreateConferenceCall( - TdE2E::PublicKeyToMTP(e2e->myKey()), - MTP_bytes(e2e->makeZeroBlock().data) + MTP_int(*creating) )).done(crl::guard(controller, [=](const MTPphone_GroupCall &result) { result.data().vcall().match([&](const auto &data) { const auto call = std::make_shared( @@ -140,18 +140,32 @@ constexpr auto kPlayStatusLimit = 2; data.vid().v, data.vaccess_hash().v, TimeId(), // scheduleDate - false); // rtmp + false, // rtmp + true); // conference call->processFullCall(result); - Core::App().calls().startOrJoinConferenceCall( - controller->uiShow(), - { .call = call, .e2e = e2e }); + using Flag = MTPphone_ExportGroupCallInvite::Flag; + session->api().request(MTPphone_ExportGroupCallInvite( + MTP_flags(Flag::f_can_self_unmute), + MTP_inputGroupCall(data.vid(), data.vaccess_hash()) + )).done(crl::guard(controller, [=]( + const MTPphone_ExportedGroupCallInvite &result) { + const auto link = qs(result.data().vlink()); + Calls::Group::ShowConferenceCallLinkBox( + controller, + call, + link, + true); + if (const auto onstack = done) { + onstack(); + } + })).fail(crl::guard(controller, [=](const MTP::Error &error) { + show->showToast(error.type()); + *creating = 0; + })).send(); }); - if (const auto onstack = done) { - onstack(); - } })).fail(crl::guard(controller, [=](const MTP::Error &error) { - controller->uiShow()->showToast(error.type()); - *creating = false; + show->showToast(error.type()); + *creating = 0; })).send(); }); return result; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 514f4dd859..a493903ab3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -861,7 +861,8 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) { data.vid().v, data.vaccess_hash().v, TimeId(), // scheduleDate - false); // rtmp + false, // rtmp + true); // conference call->processFullCall(result); const auto join = [=] { Core::App().calls().startOrJoinConferenceCall(