Merge tag 'v7.0.4' into dev

This commit is contained in:
AlexeyZavar
2026-07-23 06:48:00 +03:00
1335 changed files with 217894 additions and 24157 deletions
+333 -82
View File
@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/components/sponsored_messages.h"
#include "data/stickers/data_stickers.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_ai_compose_tones.h"
#include "data/data_bot_app.h"
#include "data/data_changes.h"
#include "data/data_group_call.h"
@@ -59,10 +60,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_download_manager.h"
#include "data/data_web_page.h"
#include "iv/iv_rich_page.h"
#include "data/data_game.h"
#include "data/data_poll.h"
#include "data/data_replies_list.h"
#include "data/data_chat_filters.h"
#include "dialogs/dialogs_entry.h"
#include "dialogs/dialogs_row.h"
#include "base/options.h"
#include "data/data_send_action.h"
#include "data/data_message_reactions.h"
#include "data/data_emoji_statuses.h"
@@ -97,6 +102,7 @@ namespace Data {
namespace {
constexpr auto kNextForUpgradeGiftTimeout = 5 * crl::time(1000);
constexpr auto kMaxServiceNotificationMessageSize = 4096;
using ViewElement = HistoryView::Element;
@@ -249,6 +255,7 @@ Session::Session(not_null<Main::Session*> session)
, _pollsClosingTimer([=] { checkPollsClosings(); })
, _watchForOfflineTimer([=] { checkLocalUsersWentOffline(); })
, _groups(this)
, _aiComposeTones(std::make_unique<AiComposeTones>(session))
, _chatsFilters(std::make_unique<ChatFilters>(this))
, _cloudThemes(std::make_unique<CloudThemes>(session))
, _sendActionManager(std::make_unique<SendActionManager>())
@@ -288,6 +295,12 @@ Session::Session(not_null<Main::Session*> session)
notifyUnreadBadgeChanged();
}, _lifetime);
base::options::lookup<bool>(
Dialogs::kOptionDialogsUnreadOnTop
).changes() | rpl::on_next([=] {
refreshChatListUnreadOnTop();
}, _lifetime);
_chatsFilters->changed(
) | rpl::on_next([=] {
const auto enabled = _chatsFilters->has();
@@ -407,6 +420,8 @@ void Session::subscribeForTopicRepliesLists() {
}
void Session::clear() {
_sessionDataAboutToBeCleared.fire({});
// Optimization: clear notifications before destroying items.
Core::App().notifications().clearFromSession(_session);
@@ -442,12 +457,21 @@ void Session::clear() {
_shortcutMessages = nullptr;
_session->scheduledMessages().clear();
_session->sponsoredMessages().clear();
// Items are gone now, so HistoryMessageReply::resolvedStory raw
// pointers no longer linger. Tear stories down here so their
// shared_ptr<GroupCall> drops while Main::Session::_data still
// holds a live pointer to us; otherwise the GroupCall destructor
// would run inside ~Data::Session and find data() returning a
// null reference (the parent unique_ptr resets its stored pointer
// before invoking the deleter).
_stories->clear();
_dependentMessages.clear();
base::take(_messages);
base::take(_nonChannelMessages);
_messageByRandomId.clear();
_sentMessagesData.clear();
cSetRecentInlineBots(RecentInlineBots());
cSetRecentStickers(RecentStickerPack());
HistoryView::Element::ClearGlobal();
_contactsNoChatsList.clear();
@@ -602,6 +626,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
info->hasMainApp = data.is_bot_has_main_app();
info->userCreatesTopics = data.is_bot_forum_can_manage_topics();
info->canManageBots = data.is_bot_can_manage_bots();
info->supportsGuestChat = data.is_bot_guestchat();
info->supportsGuard = data.is_bot_guard();
}
}
@@ -671,6 +697,8 @@ not_null<UserData*> Session::processUser(const MTPUser &data) {
result->setFlags((result->flags() & ~flagsMask) | flagsSet);
result->setBotVerifyDetailsIcon(
data.vbot_verification_icon().value_or_empty());
result->setLinkedCommunityId(
data.vlinked_community_id().value_or_empty());
if (!minimal) {
if (storiesState) {
result->setStoriesState(storiesState->hasVideoStream
@@ -836,6 +864,10 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
return peer(peerFromChannel(data.vid().v));
}, [&](const MTPDchannelForbidden &data) {
return peer(peerFromChannel(data.vid().v));
}, [&](const MTPDcommunity &data) {
return peer(peerFromChannel(data.vid().v));
}, [&](const MTPDcommunityForbidden &data) {
return peer(peerFromChannel(data.vid().v));
});
auto minimal = false;
@@ -1100,6 +1132,8 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
applyMonoforumLinkedId(
channel,
data.vlinked_monoforum_id().value_or_empty());
channel->setLinkedCommunityId(
data.vlinked_community_id().value_or_empty());
if (wasInChannel != channel->amIn()) {
flags |= UpdateFlag::ChannelAmIn;
@@ -1164,6 +1198,84 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
|| canAddMembers != channel->canAddMembers()) {
flags |= UpdateFlag::Rights;
}
}, [&](const MTPDcommunity &data) {
const auto channel = result->asChannel();
minimal = data.is_min();
if (minimal && !result->isLoaded()) {
LOG(("API Warning: not loaded minimal community applied."));
}
if (const auto accessHash = data.vaccess_hash()) {
if (!minimal || !channel->accessHash()) {
channel->setAccessHash(accessHash->v);
}
}
if (const auto rights = data.vdefault_banned_rights()) {
channel->setDefaultRestrictions(
ChatRestrictionsInfo(*rights).flags);
} else {
channel->setDefaultRestrictions(ChatRestrictions());
}
if (!minimal) {
const auto wasLoaded = result->isLoaded();
const auto hadRights = channel->hasAdminRights();
if (const auto rights = data.vadmin_rights()) {
channel->setAdminRights(ChatAdminRightsInfo(*rights).flags);
} else if (channel->hasAdminRights()) {
channel->setAdminRights(ChatAdminRights());
}
channel->date = data.vdate().v;
if (wasLoaded
&& !hadRights
&& channel->hasAdminRights()
&& !data.is_creator()) {
_communityAdminPromotions.fire(
not_null<ChannelData*>(channel));
}
}
channel->setName(qs(data.vtitle()), QString());
channel->setPhoto(data.vphoto());
using Flag = ChannelDataFlag;
const auto collapsed = data.is_collapsed_in_dialogs();
const auto flagsMask = Flag::Community
| Flag::Forbidden
| (!minimal
? (Flag::Left | Flag::Creator | Flag::CommunityCollapsed)
: Flag());
const auto flagsSet = Flag::Community
| (!minimal
? ((data.is_left() ? Flag::Left : Flag())
| (data.is_creator() ? Flag::Creator : Flag())
| (collapsed ? Flag::CommunityCollapsed : Flag()))
: Flag());
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
}, [&](const MTPDcommunityForbidden &data) {
const auto channel = result->asChannel();
using Flag = ChannelDataFlag;
const auto flagsMask = Flag::Community
| Flag::CommunityCollapsed
| Flag::Forbidden;
const auto flagsSet = Flag::Community | Flag::Forbidden;
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
if (channel->hasAdminRights()) {
channel->setAdminRights(ChatAdminRights());
}
if (channel->hasRestrictions()) {
channel->setRestrictions(ChatRestrictionsInfo());
}
channel->setName(qs(data.vtitle()), QString());
if (const auto accessHash = data.vaccess_hash()) {
channel->setAccessHash(accessHash->v);
}
channel->setPhoto(MTP_chatPhotoEmpty());
channel->date = 0;
channel->setMembersCount(0);
}, [](const MTPDchatEmpty &) {
});
@@ -1832,16 +1944,19 @@ void Session::documentLoadFail(
void Session::photoLoadProgress(not_null<PhotoData*> photo) {
requestPhotoViewRepaint(photo);
_photoLoadProgress.fire_copy(photo);
}
void Session::photoLoadDone(not_null<PhotoData*> photo) {
notifyPhotoLayoutChanged(photo);
_photoLoadProgress.fire_copy(photo);
}
void Session::photoLoadFail(
not_null<PhotoData*> photo,
bool started) {
notifyPhotoLayoutChanged(photo);
_photoLoadProgress.fire_copy(photo);
}
void Session::markMediaRead(not_null<const DocumentData*> document) {
@@ -2106,6 +2221,14 @@ rpl::producer<not_null<HistoryItem*>> Session::itemDataChanges() const {
return _itemDataChanges.events();
}
void Session::notifyReactionsRemoved(ReactionsRemoved update) {
_reactionsRemoved.fire(std::move(update));
}
rpl::producer<ReactionsRemoved> Session::reactionsRemoved() const {
return _reactionsRemoved.events();
}
void Session::requestItemTextRefresh(not_null<HistoryItem*> item) {
const auto call = [&](not_null<HistoryItem*> item) {
enumerateItemViews(item, [&](not_null<ViewElement*> view) {
@@ -2189,6 +2312,30 @@ rpl::producer<not_null<const HistoryItem*>> Session::itemRemoved(
});
}
rpl::producer<> Session::sessionDataAboutToBeCleared() const {
return _sessionDataAboutToBeCleared.events();
}
void Session::notifyItemsAboutToBeDestroyed(
const std::vector<not_null<HistoryItem*>> &items) {
_itemsAboutToBeDestroyed.fire_copy(items);
}
auto Session::itemsAboutToBeDestroyed() const
-> rpl::producer<std::vector<not_null<HistoryItem*>>> {
return _itemsAboutToBeDestroyed.events();
}
void Session::notifyViewAboutToBeRemoved(
not_null<const ViewElement*> view,
ViewRemovalReason reason) {
_viewAboutToBeRemoved.fire({ view, reason });
}
rpl::producer<ViewRemoval> Session::viewAboutToBeRemoved() const {
return _viewAboutToBeRemoved.events();
}
void Session::notifyViewRemoved(not_null<const ViewElement*> view) {
_viewRemoved.fire_copy(view);
}
@@ -2490,6 +2637,11 @@ void Session::applyPinnedChats(
if (folder) {
LOG(("API Error: Nested folders detected."));
}
}, [&](const MTPDdialogPeerCommunity &data) {
const auto channelId = ChannelId(data.vcommunity_id().v);
if (const auto channel = channelLoaded(channelId)) {
this->history(channel)->clearFolder();
}
});
}
chatsList(folder)->pinned()->applyList(this, list);
@@ -2553,6 +2705,22 @@ void Session::applyDialog(
setPinnedFromEntryList(folder, data.is_pinned());
}
void Session::applyDialog(
Data::Folder *requestFolder,
const MTPDdialogCommunity &data) {
const auto channelId = ChannelId(data.vcommunity_id().v);
const auto channel = channelLoaded(channelId);
if (!channel || !channel->isCommunity()) {
return;
}
const auto history = this->history(channel);
notifySettings().apply(
peerFromChannel(channelId),
data.vnotify_settings());
channel->ensuredCommunityInfo()->ensureRowInChatList();
setPinnedFromEntryList(history, data.is_pinned());
}
bool Session::pinnedCanPin(not_null<Dialogs::Entry*> entry) const {
if ([[maybe_unused]] const auto sublist = entry->asSublist()) {
if (sublist->parentChat()) {
@@ -2888,27 +3056,27 @@ void Session::unregisterMessageTTL(
}
void Session::checkTTLs() {
const auto &settings = AyuSettings::getInstance();
_ttlCheckTimer.cancel();
const auto now = base::unixtime::now();
if (settings.saveDeletedMessages()) {
auto toBeRemoved = ranges::views::take_while(
_ttlMessages,
[now](const auto &pair) {
return pair.first <= now;
}) | ranges::views::transform([](const auto &pair) {
return pair.second;
}) | ranges::views::join;
auto itemsToProcess = toBeRemoved | ranges::to_vector;
for (const auto &item : itemsToProcess) {
processMessageDelete(item);
auto expired = std::vector<not_null<HistoryItem*>>();
for (const auto &[when, items] : _ttlMessages) {
if (when > now) {
break;
}
} else {
while (!_ttlMessages.empty() && _ttlMessages.begin()->first <= now) {
_ttlMessages.begin()->second.front()->destroy();
expired.insert(expired.end(), items.begin(), items.end());
}
auto toDestroy = std::vector<not_null<HistoryItem*>>();
for (const auto &item : expired) {
if (isMessageSavable(item)) {
processMessageDelete(item);
} else {
toDestroy.push_back(item);
}
}
if (!toDestroy.empty()) {
notifyItemsAboutToBeDestroyed(toDestroy);
for (const auto &item : toDestroy) {
item->destroy();
}
}
scheduleNextTTLs();
@@ -2966,41 +3134,60 @@ void Session::processMessagesDeleted(
return;
}
auto toDestroy = std::vector<not_null<HistoryItem*>>();
auto historiesToCheck = base::flat_set<not_null<History*>>();
for (const auto &messageId : data) {
const auto i = list ? list->find(messageId.v) : Messages::iterator();
if (list && i != list->end()) {
const auto history = i->second->history();
processMessageDelete(i->second);
if (!history->chatListMessageKnown()) {
historiesToCheck.emplace(history);
const auto item = i->second;
const auto history = item->history();
if (isMessageSavable(item)) {
processMessageDelete(item);
} else {
toDestroy.push_back(item);
}
historiesToCheck.emplace(history);
} else if (affected) {
affected->unknownMessageDeleted(messageId.v);
}
}
if (!toDestroy.empty()) {
notifyItemsAboutToBeDestroyed(toDestroy);
for (const auto &item : toDestroy) {
item->destroy();
}
}
for (const auto &history : historiesToCheck) {
history->requestChatListMessage();
if (!history->chatListMessageKnown()) {
history->requestChatListMessage();
}
}
}
void Session::processNonChannelMessagesDeleted(const QVector<MTPint> &data) {
auto toDestroy = std::vector<not_null<HistoryItem*>>();
auto historiesToCheck = base::flat_set<not_null<History*>>();
for (const auto &messageId : data) {
if (const auto item = nonChannelMessage(messageId.v)) {
const auto history = item->history();
processMessageDelete(item);
if (!history->chatListMessageKnown()) {
historiesToCheck.emplace(history);
if (isMessageSavable(item)) {
processMessageDelete(item);
} else {
toDestroy.push_back(item);
}
historiesToCheck.emplace(history);
}
}
if (!toDestroy.empty()) {
notifyItemsAboutToBeDestroyed(toDestroy);
for (const auto &item : toDestroy) {
item->destroy();
}
}
for (const auto &history : historiesToCheck) {
history->requestChatListMessage();
if (!history->chatListMessageKnown()) {
history->requestChatListMessage();
}
}
}
@@ -3093,6 +3280,34 @@ HistoryItem *Session::message(FullMsgId itemId) const {
return message(itemId.peer, itemId.msg);
}
void Session::removeReactionsFromParticipant(
not_null<PeerData*> peer,
MsgId msgId,
not_null<PeerData*> participant,
const ReactionId &reaction,
MsgId originMsgId) {
if (msgId) {
if (const auto item = message(peer, msgId)) {
item->removeReactionsFromParticipant(participant, reaction);
}
} else if (const auto list = messagesList(peer->id)) {
for (const auto &entry : *list) {
const auto knownReaction = (originMsgId
&& (entry.second->id == originMsgId))
? reaction
: ReactionId();
entry.second->removeReactionsFromParticipant(
participant,
knownReaction);
}
}
notifyReactionsRemoved({
.peer = peer,
.msgId = msgId,
.participant = participant,
});
}
HistoryItem *Session::nonChannelMessage(MsgId itemId) const {
if (!IsServerMsgId(itemId)) {
return nullptr;
@@ -3176,14 +3391,13 @@ HistoryItem *Session::addNewMessage(
return nullptr;
}
if (data.type() == mtpc_message) {
if (type == NewMessageType::Unread
&& data.type() == mtpc_message) {
if (const auto h = historyLoaded(peerId)) {
if (const auto streamed = h->streamedDraftsIfExists()) {
if (const auto adopted = streamed->adoptIncoming(
data.c_message())) {
if (type == NewMessageType::Unread) {
CheckForSwitchInlineButton(adopted);
}
CheckForSwitchInlineButton(adopted);
return adopted;
}
}
@@ -3950,6 +4164,7 @@ not_null<WebPageData*> Session::processWebpage(
nullptr,
nullptr,
0,
0,
QString(),
false,
false,
@@ -3962,7 +4177,9 @@ not_null<WebPageData*> Session::processWebpage(
not_null<WebPageData*> Session::webpage(
WebPageId id,
const QString &siteName,
const TextWithEntities &content) {
const TextWithEntities &content,
PhotoData *photo,
DocumentData *document) {
return webpage(
id,
WebPageType::Article,
@@ -3971,8 +4188,8 @@ not_null<WebPageData*> Session::webpage(
siteName,
QString(),
content,
nullptr,
nullptr,
photo,
document,
WebPageCollage(),
nullptr,
nullptr,
@@ -4020,6 +4237,7 @@ not_null<WebPageData*> Session::webpage(
std::move(stickerSet),
std::move(uniqueGift),
nullptr,
0,
duration,
author,
hasLargeMedia,
@@ -4072,6 +4290,8 @@ void Session::webpageApplyFields(
return (DocumentData*)nullptr;
}, [](const MTPDwebPageAttributeStarGiftAuction &) {
return (DocumentData*)nullptr;
}, [](const MTPDwebPageAttributeAiComposeTone &) {
return (DocumentData*)nullptr;
});
if (result) {
return result;
@@ -4147,6 +4367,21 @@ void Session::webpageApplyFields(
return nullptr;
};
const auto lookupComposeToneEmojiId = [&]() -> DocumentId {
if (const auto attributes = data.vattributes()) {
for (const auto &attribute : attributes->v) {
const auto result = attribute.match([&](
const MTPDwebPageAttributeAiComposeTone &data) {
return DocumentId(data.vemoji_id().v);
}, [](const auto &) { return DocumentId(0); });
if (result) {
return result;
}
}
}
return 0;
};
auto story = (Data::Story*)nullptr;
auto storyId = FullStoryId();
if (const auto attributes = data.vattributes()) {
@@ -4177,55 +4412,25 @@ void Session::webpageApplyFields(
}, [](const auto &) {});
}
}
if (const auto page = data.vcached_page()) {
for (const auto &photo : page->data().vphotos().v) {
processPhoto(photo);
}
for (const auto &document : page->data().vdocuments().v) {
processDocument(document);
}
const auto process = [&](
const MTPPageBlock &block,
const auto &self) -> void {
block.match([&](const MTPDpageBlockChannel &data) {
processChat(data.vchannel());
}, [&](const MTPDpageBlockCover &data) {
self(data.vcover(), self);
}, [&](const MTPDpageBlockEmbedPost &data) {
for (const auto &block : data.vblocks().v) {
self(block, self);
}
}, [&](const MTPDpageBlockCollage &data) {
for (const auto &block : data.vitems().v) {
self(block, self);
}
}, [&](const MTPDpageBlockSlideshow &data) {
for (const auto &block : data.vitems().v) {
self(block, self);
}
}, [&](const MTPDpageBlockDetails &data) {
for (const auto &block : data.vblocks().v) {
self(block, self);
}
}, [](const auto &) {});
};
for (const auto &block : page->data().vblocks().v) {
process(block, process);
}
}
const auto type = story ? WebPageType::Story : ParseWebPageType(data);
auto iv = (data.vcached_page() && !IgnoreIv(type))
? std::make_unique<Iv::Data>(data, *data.vcached_page())
const auto cachedPage = data.vcached_page();
const auto ivPhoto = photo ? processPhoto(*photo).get() : nullptr;
const auto ivDocument = document ? processDocument(*document).get() : nullptr;
const auto richPage = (cachedPage && !IgnoreIv(type))
? Iv::ParseRichPage(_session, data)
: nullptr;
auto iv = richPage
? std::make_unique<Iv::Data>(data, richPage)
: nullptr;
const auto resolvedPhoto = story
? story->photo()
: photo
? processPhoto(*photo).get()
? ivPhoto
: nullptr;
const auto resolvedDocument = story
? story->document()
: document
? processDocument(*document).get()
? ivDocument
: lookupThemeDocument();
const auto photoIsVideoCover = data.is_video_cover_photo()
|| (resolvedDocument
@@ -4248,6 +4453,7 @@ void Session::webpageApplyFields(
lookupStickerSet(),
lookupUniqueGift(),
lookupAuction(),
lookupComposeToneEmojiId(),
data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
@@ -4271,6 +4477,7 @@ void Session::webpageApplyFields(
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
std::unique_ptr<WebPageAuction> auction,
DocumentId composeToneEmojiId,
int duration,
const QString &author,
bool hasLargeMedia,
@@ -4292,6 +4499,7 @@ void Session::webpageApplyFields(
std::move(stickerSet),
std::move(uniqueGift),
std::move(auction),
composeToneEmojiId,
duration,
author,
hasLargeMedia,
@@ -5102,6 +5310,11 @@ rpl::producer<not_null<ChannelData*>> Session::channelDifferenceTooLong() const
return _channelDifferenceTooLong.events();
}
auto Session::communityAdminPromotions() const
-> rpl::producer<not_null<ChannelData*>> {
return _communityAdminPromotions.events();
}
void Session::registerItemView(not_null<ViewElement*> view) {
_views[view->data()].push_back(view);
}
@@ -5172,6 +5385,13 @@ not_null<Dialogs::MainList*> Session::chatsListFor(
return topic->forum()->topicsList();
} else if (const auto sublist = entry->asSublist()) {
return sublist->parent()->chatsList();
} else if (const auto history = entry->asHistory()) {
if (const auto info = history->communityListInfo()
; info
&& info->collapsedInChatLists()
&& info->channel() != history->peer) {
return info->chatsList();
}
}
return chatsList(entry->folder());
}
@@ -5260,6 +5480,22 @@ void Session::refreshChatListEntry(Dialogs::Key key) {
}
}
void Session::refreshChatListUnreadOnTop() {
auto entries = std::vector<not_null<Dialogs::Entry*>>();
const auto collect = [&](not_null<Dialogs::MainList*> list) {
for (const auto &row : list->indexed()->all()) {
entries.push_back(row->entry());
}
};
collect(&_chatsList);
if (const auto folder = folderLoaded(Data::Folder::kId)) {
collect(folder->chatsList());
}
for (const auto &entry : entries) {
entry->updateChatListSortPosition();
}
}
void Session::removeChatListEntry(Dialogs::Key key) {
using namespace Dialogs;
@@ -5343,7 +5579,8 @@ void Session::serviceNotification(
MTPPeerColor(), // profile_color
MTPint(), // bot_active_users
MTPlong(), // bot_verification_icon
MTPlong())); // send_paid_messages_stars
MTPlong(), // send_paid_messages_stars
MTPlong())); // linked_community_id
}
const auto history = this->history(PeerData::kServiceNotificationsId);
const auto insert = [=] {
@@ -5370,7 +5607,10 @@ void Session::insertCheckedServiceNotification(
const auto localFlags = MessageFlag::ClientSideUnread
| MessageFlag::Local;
auto sending = TextWithEntities(), left = message;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
while (TextUtilities::CutPart(
sending,
left,
kMaxServiceNotificationMessageSize)) {
const auto id = nextLocalMessageId();
addNewMessage(
id,
@@ -5385,6 +5625,7 @@ void Session::insertCheckedServiceNotification(
MTPMessageFwdHeader(),
MTPlong(), // via_bot_id
MTPlong(), // via_business_bot_id
MTPPeer(), // guestchat_via_from
MTPMessageReplyHeader(),
MTP_int(date),
MTP_string(sending.text),
@@ -5407,7 +5648,8 @@ void Session::insertCheckedServiceNotification(
MTPlong(), // paid_message_stars
MTPSuggestedPost(),
MTPint(), // schedule_repeat_period
MTPstring()), // summary_from_language
MTPstring(), // summary_from_language
MTPRichMessage()),
localFlags,
NewMessageType::Unread);
}
@@ -5527,6 +5769,15 @@ auto Session::webViewResultSent() const -> rpl::producer<WebViewResultSent> {
return _webViewResultSent.events();
}
void Session::joinChatWebViewDecision(JoinChatWebViewDecision &&decision) {
return _joinChatWebViewDecision.fire(std::move(decision));
}
auto Session::joinChatWebViewDecision() const
-> rpl::producer<JoinChatWebViewDecision> {
return _joinChatWebViewDecision.events();
}
rpl::producer<not_null<PeerData*>> Session::peerDecorationsUpdated() const {
return _peerDecorationsUpdated.events();
}