PoC confcall invite, emoji.

This commit is contained in:
John Preston 2025-03-28 22:13:53 +05:00
parent dfe6ad3a32
commit 4401ea388c
11 changed files with 238 additions and 42 deletions

View file

@ -124,9 +124,23 @@ uint64 ComputeEmojiIndex(bytes::const_span bytes) {
} // namespace
std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
if (!call->isKeyShaForFingerprintReady()) {
return {};
}
return ComputeEmojiFingerprint(call->getKeyShaForFingerprint());
}
std::vector<EmojiPtr> ComputeEmojiFingerprint(
bytes::const_span fingerprint) {
auto result = std::vector<EmojiPtr>();
constexpr auto EmojiCount = (base::array_size(Offsets) - 1);
for (auto index = 0; index != EmojiCount; ++index) {
constexpr auto kPartSize = 8;
for (auto partOffset = 0
; partOffset != fingerprint.size()
; partOffset += kPartSize) {
auto value = ComputeEmojiIndex(
fingerprint.subspan(partOffset, kPartSize));
auto index = value % EmojiCount;
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(
@ -134,22 +148,7 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
}
if (call->isKeyShaForFingerprintReady()) {
auto sha256 = call->getKeyShaForFingerprint();
constexpr auto kPartSize = 8;
for (auto partOffset = 0; partOffset != sha256.size(); partOffset += kPartSize) {
auto value = ComputeEmojiIndex(gsl::make_span(sha256).subspan(partOffset, kPartSize));
auto index = value % EmojiCount;
auto offset = Offsets[index];
auto size = Offsets[index + 1] - offset;
auto string = QString::fromRawData(
reinterpret_cast<const QChar*>(Data + offset),
size);
auto emoji = Ui::Emoji::Find(string);
Assert(emoji != nullptr);
result.push_back(emoji);
}
result.push_back(emoji);
}
return result;
}

View file

@ -19,6 +19,8 @@ class Call;
[[nodiscard]] std::vector<EmojiPtr> ComputeEmojiFingerprint(
not_null<Call*> call);
[[nodiscard]] std::vector<EmojiPtr> ComputeEmojiFingerprint(
bytes::const_span fingerprint);
[[nodiscard]] object_ptr<Ui::RpWidget> CreateFingerprintAndSignalBars(
not_null<QWidget*> parent,

View file

@ -1112,6 +1112,10 @@ bool GroupCall::rtmp() const {
return _rtmp;
}
bool GroupCall::conference() const {
return _conferenceCall != nullptr;
}
bool GroupCall::listenersHidden() const {
return _listenersHidden;
}
@ -1152,6 +1156,12 @@ rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
return _realChanges.events();
}
rpl::producer<QByteArray> GroupCall::emojiHashValue() const {
Expects(_e2e != nullptr);
return _e2e->emojiHashValue();
}
void GroupCall::start(TimeId scheduleDate, bool rtmp) {
using Flag = MTPphone_CreateGroupCall::Flag;
_createRequestId = _api.request(MTPphone_CreateGroupCall(
@ -1610,8 +1620,13 @@ void GroupCall::requestSubchainBlocks(int subchain, int height) {
MTP_int(kShortPollChainBlocksPerRequest)
)).done([=](const MTPUpdates &result) {
auto &state = _subchains[subchain];
_peer->session().api().applyUpdates(result);
state.requestId = 0;
state.inShortPoll = true;
_peer->session().api().applyUpdates(result);
state.inShortPoll = false;
for (const auto &data : base::take(state.pending)) {
applySubChainUpdate(subchain, data.blocks, data.next);
}
_e2e->subchainBlocksRequestFinished(subchain);
}).fail([=](const MTP::Error &error) {
auto &state = _subchains[subchain];
@ -2221,11 +2236,26 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallChainBlocks &data) {
return;
}
auto &entry = _subchains[subchain];
const auto inpoll = entry.requestId != 0;
const auto &blocks = data.vblocks().v;
const auto next = data.vnext_offset().v;
auto now = next - int(data.vblocks().v.size());
for (const auto &block : data.vblocks().v) {
_e2e->apply(subchain, now++, { block.v }, inpoll);
if (entry.requestId) {
Assert(!entry.inShortPoll);
entry.pending.push_back({ blocks, next });
} else {
applySubChainUpdate(subchain, blocks, next);
}
}
void GroupCall::applySubChainUpdate(
int subchain,
const QVector<MTPbytes> &blocks,
int next) {
Expects(subchain >= 0 && subchain < kSubChainsCount);
auto &entry = _subchains[subchain];
auto now = next - int(blocks.size());
for (const auto &block : blocks) {
_e2e->apply(subchain, now++, { block.v }, entry.inShortPoll);
}
}
@ -3667,6 +3697,22 @@ std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
}
const auto owner = &_peer->owner();
if (_conferenceCall) {
for (const auto &user : users) {
_api.request(MTPphone_InviteConferenceCallParticipant(
inputCall(),
user->inputUser
)).send();
}
auto result = std::variant<int, not_null<UserData*>>(0);
if (users.size() != 1) {
result = int(users.size());
} else {
result = users.front();
}
return result;
}
auto count = 0;
auto slice = QVector<MTPInputUser>();
auto result = std::variant<int, not_null<UserData*>>(0);

View file

@ -245,6 +245,7 @@ public:
}
[[nodiscard]] bool scheduleStartSubscribed() const;
[[nodiscard]] bool rtmp() const;
[[nodiscard]] bool conference() const;
[[nodiscard]] bool listenersHidden() const;
[[nodiscard]] bool emptyRtmp() const;
[[nodiscard]] rpl::producer<bool> emptyRtmpValue() const;
@ -256,6 +257,7 @@ public:
[[nodiscard]] Data::GroupCall *lookupReal() const;
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
[[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const;
void start(TimeId scheduleDate, bool rtmp);
void hangup();
@ -479,8 +481,14 @@ private:
ssrc = updatedSsrc;
}
};
struct SubChainPending {
QVector<MTPbytes> blocks;
int next = 0;
};
struct SubChainState {
std::vector<SubChainPending> pending;
mtpRequestId requestId = 0;
bool inShortPoll = false;
};
friend inline constexpr bool is_flag_type(SendUpdateType) {
@ -515,6 +523,10 @@ private:
void handleUpdate(const MTPDupdateGroupCall &data);
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
void handleUpdate(const MTPDupdateGroupCallChainBlocks &data);
void applySubChainUpdate(
int subchain,
const QVector<MTPbytes> &blocks,
int next);
bool tryCreateController();
void destroyController();
bool tryCreateScreencast();

View file

@ -56,6 +56,43 @@ namespace {
return result;
}
class ConfInviteController final : public ContactsBoxController {
public:
ConfInviteController(
not_null<Main::Session*> session,
base::flat_set<not_null<UserData*>> alreadyIn,
Fn<void(not_null<PeerData*>)> choose)
: ContactsBoxController(session)
, _alreadyIn(std::move(alreadyIn))
, _choose(std::move(choose)) {
}
protected:
std::unique_ptr<PeerListRow> createRow(
not_null<UserData*> user) override {
if (user->isSelf()
|| user->isBot()
|| user->isServiceUser()
|| user->isInaccessible()) {
return nullptr;
}
auto result = ContactsBoxController::createRow(user);
if (_alreadyIn.contains(user)) {
result->setDisabledState(PeerListRow::State::DisabledChecked);
}
return result;
}
void rowClicked(not_null<PeerListRow*> row) override {
_choose(row->peer());
}
private:
const base::flat_set<not_null<UserData*>> _alreadyIn;
const Fn<void(not_null<PeerData*>)> _choose;
};
} // namespace
InviteController::InviteController(
@ -173,6 +210,7 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
return nullptr;
}
const auto peer = call->peer();
const auto weak = base::make_weak(call);
auto alreadyIn = peer->owner().invitedToCallUsers(real->id());
for (const auto &participant : real->participants()) {
if (const auto user = participant.peer->asUser()) {
@ -180,6 +218,39 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
}
}
alreadyIn.emplace(peer->session().user());
if (call->conference()) {
const auto close = std::make_shared<Fn<void()>>();
const auto invite = [=](not_null<PeerData*> peer) {
Expects(peer->isUser());
const auto call = weak.get();
if (!call) {
return;
}
const auto user = peer->asUser();
call->inviteUsers({ user });
showToast(tr::lng_group_call_invite_done_user(
tr::now,
lt_user,
Ui::Text::Bold(user->firstName),
Ui::Text::WithEntities));
(*close)();
};
auto controller = std::make_unique<ConfInviteController>(
&real->session(),
alreadyIn,
invite);
controller->setStyleOverrides(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
auto initBox = [=](not_null<PeerListBox*> box) {
box->setTitle(tr::lng_group_call_invite_title());
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
*close = [=] { box->closeBox(); };
};
return Box<PeerListBox>(std::move(controller), initBox);
}
auto controller = std::make_unique<InviteController>(peer, alreadyIn);
controller->setStyleOverrides(
&st::groupCallInviteMembersList,
@ -194,7 +265,6 @@ object_ptr<Ui::BoxContent> PrepareInviteBox(
&st::groupCallInviteMembersList,
&st::groupCallMultiSelect);
const auto weak = base::make_weak(call);
const auto invite = [=](const std::vector<not_null<UserData*>> &users) {
const auto call = weak.get();
if (!call) {

View file

@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_invite_controller.h"
#include "calls/group/ui/calls_group_scheduled_labels.h"
#include "calls/group/ui/desktop_capture_choose_source.h"
#include "calls/calls_emoji_fingerprint.h"
#include "ui/platform/ui_platform_window_title.h"
#include "ui/platform/ui_platform_utility.h"
#include "ui/controls/call_mute_button.h"
@ -76,6 +77,19 @@ constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
[[nodiscard]] QString ComposeTitle(const QByteArray &hash) {
auto result = tr::lng_confcall_join_title(tr::now);
if (hash.size() >= 32) {
const auto fp = bytes::make_span(hash).subspan(0, 32);
const auto emoji = Calls::ComputeEmojiFingerprint(fp);
result += QString::fromUtf8(" \xc2\xb7 ");
for (const auto &single : emoji) {
result += single->text();
}
}
return result;
}
class Show final : public Main::SessionShow {
public:
explicit Show(not_null<Panel*> panel);
@ -367,7 +381,13 @@ void Panel::initWindow() {
window()->setAttribute(Qt::WA_NoSystemBackground);
window()->setTitleStyle(st::groupCallTitle);
subscribeToPeerChanges();
if (_call->conference()) {
titleText() | rpl::start_with_next([=](const QString &text) {
window()->setTitle(text);
}, lifetime());
} else {
subscribeToPeerChanges();
}
base::install_event_filter(window().get(), [=](not_null<QEvent*> e) {
if (e->type() == QEvent::Close && handleClose()) {
@ -2445,19 +2465,26 @@ void Panel::updateMembersGeometry() {
}
}
rpl::producer<QString> Panel::titleText() {
if (_call->conference()) {
return _call->emojiHashValue() | rpl::map(ComposeTitle);
}
return rpl::combine(
Info::Profile::NameValue(_peer),
rpl::single(
QString()
) | rpl::then(_call->real(
) | rpl::map([=](not_null<Data::GroupCall*> real) {
return real->titleValue();
}) | rpl::flatten_latest())
) | rpl::map([=](const QString &name, const QString &title) {
return title.isEmpty() ? name : title;
});
}
void Panel::refreshTitle() {
if (!_title) {
auto text = rpl::combine(
Info::Profile::NameValue(_peer),
rpl::single(
QString()
) | rpl::then(_call->real(
) | rpl::map([=](not_null<Data::GroupCall*> real) {
return real->titleValue();
}) | rpl::flatten_latest())
) | rpl::map([=](const QString &name, const QString &title) {
return title.isEmpty() ? name : title;
}) | rpl::after_next([=] {
auto text = titleText() | rpl::after_next([=] {
refreshTitleGeometry();
});
_title.create(

View file

@ -155,6 +155,7 @@ private:
void setupMembers();
void setupVideo(not_null<Viewport*> viewport);
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
[[nodiscard]] rpl::producer<QString> titleText();
bool handleClose();
void startScheduledNow();

View file

@ -52,6 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mainwindow.h"
#include "main/main_session.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
#include "calls/calls_instance.h"
#include "spellcheck/spellcheck_types.h"
#include "storage/localstorage.h"
@ -1225,6 +1226,12 @@ void History::applyServiceChanges(
topic->setHidden(mtpIsTrue(*hidden));
}
}
}, [&](const MTPDmessageActionConferenceCall &data) {
if (!data.is_active() && !data.is_missed()) {
if (const auto window = session().tryResolveWindow()) {
window->resolveConferenceCall(qs(data.vslug()), item->id);
}
}
}, [](const auto &) {
});
}

View file

@ -5634,6 +5634,22 @@ void HistoryItem::setServiceMessageByAction(const MTPmessageAction &action) {
: duration
? tr::lng_action_confcall_finished(tr::now)
: tr::lng_action_confcall_invitation(tr::now);
if (duration) {
result.text.text += " (" + QString::number(duration) + " seconds)";
}
const auto id = this->id;
const auto slug = qs(action.vslug());
setCustomServiceLink(std::make_shared<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
const auto weak = my.sessionWindow;
if (const auto strong = weak.get()) {
strong->resolveConferenceCall(slug, id);
}
}));
return result;
};

View file

@ -840,12 +840,16 @@ void SessionNavigation::resolveCollectible(
}).send();
}
void SessionNavigation::resolveConferenceCall(const QString &slug) {
if (_conferenceCallSlug == slug) {
void SessionNavigation::resolveConferenceCall(
const QString &slug,
MsgId inviteMsgId) {
if (_conferenceCallSlug == slug
&& _conferenceCallInviteMsgId == inviteMsgId) {
return;
}
_api.request(base::take(_conferenceCallRequestId)).cancel();
_conferenceCallSlug = slug;
_conferenceCallInviteMsgId = inviteMsgId;
const auto limit = 5;
_conferenceCallRequestId = _api.request(MTPphone_GetGroupCall(
@ -864,15 +868,26 @@ void SessionNavigation::resolveConferenceCall(const QString &slug) {
false, // rtmp
true); // conference
call->processFullCall(result);
const auto confirmed = std::make_shared<bool>();
const auto join = [=] {
Core::App().calls().startOrJoinConferenceCall(
uiShow(),
{ .call = call, .linkSlug = slug });
*confirmed = true;
Core::App().calls().startOrJoinConferenceCall(uiShow(), {
.call = call,
.linkSlug = slug,
.joinMessageId = inviteMsgId,
});
};
uiShow()->show(Box(
const auto box = uiShow()->show(Box(
Calls::Group::ConferenceCallJoinConfirm,
call,
join));
box->boxClosing() | rpl::start_with_next([=] {
if (inviteMsgId && !*confirmed) {
_api.request(MTPphone_DeclineConferenceCallInvite(
MTP_int(inviteMsgId)
)).send();
}
}, box->lifetime());
});
}).fail([=] {
_conferenceCallRequestId = 0;

View file

@ -264,7 +264,7 @@ public:
PeerId ownerId,
const QString &entity,
Fn<void(QString)> fail = nullptr);
void resolveConferenceCall(const QString &slug);
void resolveConferenceCall(const QString &slug, MsgId inviteMsgId = 0);
base::weak_ptr<Ui::Toast::Instance> showToast(
Ui::Toast::Config &&config);
@ -331,6 +331,7 @@ private:
mtpRequestId _collectibleRequestId = 0;
QString _conferenceCallSlug;
MsgId _conferenceCallInviteMsgId;
mtpRequestId _conferenceCallRequestId = 0;
};