diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 8c4c9892c..9c764943b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -975,6 +975,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_manage_history_visibility_hidden_about" = "New members won't see earlier messages."; "lng_manage_history_visibility_hidden_legacy" = "New members won't see more than 100 previous messages."; +"lng_manage_messages_ttl_title" = "Auto-delete messages"; +"lng_manage_messages_ttl_never" = "Never"; +"lng_manage_messages_ttl_after1" = "After 24 hours"; +"lng_manage_messages_ttl_after2" = "After 7 days"; +"lng_manage_messages_ttl_about" = "Turning on this setting will make auto-delete messages from this group after the selected period."; +"lng_manage_messages_ttl_about_channel" = "Turning on this setting will make auto-delete messages from this channel after the selected period."; + "lng_report_title" = "Report channel"; "lng_report_group_title" = "Report group"; "lng_report_bot_title" = "Report bot"; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index aa0a0e98e..e7d83e8a7 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -220,6 +220,7 @@ Session::Session(not_null session) session->serverConfig().pinnedDialogsCountMax.value()) , _contactsList(Dialogs::SortMode::Name) , _contactsNoChatsList(Dialogs::SortMode::Name) +, _ttlCheckTimer([=] { checkTTLs(); }) , _selfDestructTimer([=] { checkSelfDestructItems(); }) , _sendActionsAnimation([=](crl::time now) { return sendActionsAnimationCallback(now); @@ -1959,6 +1960,54 @@ void Session::registerMessage(not_null item) { list->emplace(itemId, item); } +void Session::registerMessageTTL(TimeId when, not_null item) { + Expects(when > 0); + + auto &list = _ttlMessages[when]; + list.emplace(item); + + const auto nearest = _ttlMessages.begin()->first; + if (nearest < when && _ttlCheckTimer.isActive()) { + return; + } + scheduleNextTTLs(); +} + +void Session::scheduleNextTTLs() { + if (_ttlMessages.empty()) { + return; + } + const auto nearest = _ttlMessages.begin()->first; + const auto now = base::unixtime::now(); + const auto timeout = (std::max(now, nearest) - now) * crl::time(1000); + _ttlCheckTimer.callOnce(timeout); +} + +void Session::unregisterMessageTTL( + TimeId when, + not_null item) { + Expects(when > 0); + + const auto i = _ttlMessages.find(when); + if (i == end(_ttlMessages)) { + return; + } + auto &list = i->second; + list.erase(item); + if (list.empty()) { + _ttlMessages.erase(i); + } +} + +void Session::checkTTLs() { + _ttlCheckTimer.cancel(); + const auto now = base::unixtime::now(); + while (!_ttlMessages.empty() && _ttlMessages.begin()->first <= now) { + _ttlMessages.begin()->second.front()->destroy(); + } + scheduleNextTTLs(); +} + void Session::processMessagesDeleted( ChannelId channelId, const QVector &data) { diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h index 4192aff98..6914c33d0 100644 --- a/Telegram/SourceFiles/data/data_session.h +++ b/Telegram/SourceFiles/data/data_session.h @@ -328,6 +328,9 @@ public: void registerMessage(not_null item); void unregisterMessage(not_null item); + void registerMessageTTL(TimeId when, not_null item); + void unregisterMessageTTL(TimeId when, not_null item); + // Returns true if item found and it is not detached. bool checkEntitiesAndViewsUpdate(const MTPDmessage &data); void updateEditedMessage(const MTPMessage &data); @@ -686,6 +689,9 @@ private: void checkSelfDestructItems(); + void scheduleNextTTLs(); + void checkTTLs(); + int computeUnreadBadge(const Dialogs::UnreadState &state) const; bool computeUnreadBadgeMuted(const Dialogs::UnreadState &state) const; @@ -857,6 +863,8 @@ private: std::map< not_null, base::flat_set>> _dependentMessages; + std::map>> _ttlMessages; + base::Timer _ttlCheckTimer; base::flat_map _messageByRandomId; base::flat_map _sentMessagesData; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 008cb72cd..de50cd2f2 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -79,6 +79,9 @@ public: [[nodiscard]] virtual bool notificationReady() const { return true; } + [[nodiscard]] virtual TimeId ttlDestroyAt() const { + return 0; + } [[nodiscard]] PeerData *specialNotificationPeer() const; [[nodiscard]] UserData *viaBot() const; diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 605483c67..36f557505 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -50,6 +50,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" #include "styles/style_window.h" +#include "base/call_delayed.h" // #TODO ttl +#include "base/unixtime.h" + #include #include @@ -523,6 +526,12 @@ HistoryMessage::HistoryMessage( setGroupId( MessageGroupId::FromRaw(history->peer->id, groupedId->v)); } + + if (const auto period = data.vttl_period()) { + if (period->v > 0) { + applyTTL(data.vdate().v + period->v); + } + } } HistoryMessage::HistoryMessage( @@ -1352,6 +1361,12 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) { clearReplies(); } + if (const auto period = message.vttl_period(); period && period->v > 0) { + applyTTL(message.vdate().v + period->v); + } else { + applyTTL(0); + } + finishEdition(keyboardTop); } @@ -1367,6 +1382,25 @@ void HistoryMessage::applyEdition(const MTPDmessageService &message) { } } +void HistoryMessage::applyTTL(TimeId destroyAt) { + const auto previousDestroyAt = std::exchange(_ttlDestroyAt, destroyAt); + if (previousDestroyAt) { + history()->owner().unregisterMessageTTL(previousDestroyAt, this); + } + if (!_ttlDestroyAt) { + return; + } else if (base::unixtime::now() >= _ttlDestroyAt) { + const auto session = &history()->session(); + crl::on_main(session, [session, id = fullId()]{ + if (const auto item = session->data().message(id)) { + item->destroy(); + } + }); + } else { + history()->owner().registerMessageTTL(_ttlDestroyAt, this); + } +} + void HistoryMessage::updateSentContent( const TextWithEntities &textWithEntities, const MTPMessageMedia *media) { @@ -1872,6 +1906,7 @@ std::unique_ptr HistoryMessage::createView( } HistoryMessage::~HistoryMessage() { + applyTTL(0); _media.reset(); clearSavedMedia(); if (auto reply = Get()) { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index 5bfc295d7..3f8ce8d41 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -188,6 +188,10 @@ public: return replyToId(); } + [[nodiscard]] TimeId ttlDestroyAt() const override { + return _ttlDestroyAt; + } + // dynamic_cast optimization. [[nodiscard]] HistoryMessage *toHistoryMessage() override { return this; @@ -240,6 +244,8 @@ private: const TextWithEntities &textWithEntities) const; void reapplyText(); + void applyTTL(TimeId destroyAt); + [[nodiscard]] bool checkRepliesPts(const MTPMessageReplies &data) const; QString _timeText; @@ -247,6 +253,8 @@ private: mutable int32 _fromNameVersion = 0; + TimeId _ttlDestroyAt = 0; + friend class HistoryView::Element; friend class HistoryView::Message;