diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 7412592163..47beb369b8 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -170,6 +170,10 @@ set(ayugram_files ayu/features/streamer_mode/streamer_mode.h ayu/features/messageshot/message_shot.cpp ayu/features/messageshot/message_shot.h + ayu/features/forward/ayu_forward.cpp + ayu/features/forward/ayu_forward.h + ayu/features/forward/ayu_sync.cpp + ayu/features/forward/ayu_sync.h ayu/data/messages_storage.cpp ayu/data/messages_storage.h ayu/data/entities.h diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index cf03883baa..fb1368e991 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -3338,6 +3338,20 @@ void ApiWrap::forwardMessages( sendFlags |= SendFlag::f_top_msg_id; } + const auto fullAyuForward = AyuForward::isFullAyuForwardNeeded(draft.items.front()->from(), draft.items.front()->history()); + if (fullAyuForward) { + crl::async([=] { + AyuForward::forwardMessages(peer, draft.items, _session, history, action, false); + }); + return; + } + const auto ayuIntelligentForwardNeeded = AyuForward::isAyuForwardNeeded(draft.items); + if (ayuIntelligentForwardNeeded) { + crl::async([=] { + AyuForward::intelligentForward(peer, draft.items, _session, history, action); + }); + return; + } auto forwardFrom = draft.items.front()->history()->peer; auto ids = QVector(); auto randomIds = QVector(); @@ -3766,7 +3780,11 @@ void ApiWrap::sendMessage(MessageToSend &&message) { ? replyTo->topicRootId() : Data::ForumTopic::kGeneralId; const auto topic = peer->forumTopicFor(topicRootId); - if (!(topic ? Data::CanSendTexts(topic) : Data::CanSendTexts(peer)) + if (!(topic + ? Data::CanSendTexts(topic) + : Data::CanSendTexts(peer) || AyuForward::isForwarding(peer->id) + ) + || Api::SendDice(message)) { return; } diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 529114b0a1..efdd6afae6 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" #include "data/stickers/data_stickers_set.h" #include "data/data_messages.h" +#include "ayu/features/forward/ayu_forward.h" class TaskQueue; struct MessageGroupId; diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp new file mode 100644 index 0000000000..97899a6f37 --- /dev/null +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp @@ -0,0 +1,309 @@ + +#include "ayu_forward.h" + +#include +#include +#include +#include +#include + +#include "apiwrap.h" +#include "ayu_sync.h" +#include "base/unixtime.h" +#include "core/application.h" +#include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "data/data_document.h" +#include "data/data_photo.h" +#include "data/data_session.h" +#include "data/data_user.h" +#include "storage/file_download.h" +#include "storage/localimageloader.h" +#include "storage/storage_account.h" +#include "storage/storage_media_prepare.h" +#include "ui/chat/attach/attach_prepare.h" +#include "ui/text/text_utilities.h" + +namespace AyuForward { + std::unordered_map forwardStates; + + bool isForwarding(const PeerId &id) { + const auto state = forwardStates.find(id); + return id.value + && state != forwardStates.end() + && state->second.state != ForwardState::State::Finished + && state->second.currentChunk < state->second.totalChunks + && !state->second.stopRequested + && state->second.totalChunks + && state->second.totalMessages; + } + + QString stateName(const PeerId &id) { + const auto fwState = forwardStates.find(id); + + if (fwState == forwardStates.end()) { + return QString(); + } + const auto state = fwState->second; + + QString messagesString = tr::ayu_AyuForwardStatusChunkCount(tr::now, + lt_count1, + QString::number(state.sentMessages), + lt_count2, + QString::number(state.totalMessages) + ); + QString chunkString = tr::ayu_AyuForwardStatusChunkCount(tr::now, + lt_count1, + QString::number(state.currentChunk + 1), + lt_count2, + QString::number(state.totalChunks) + ); + + const auto partString = state.totalChunks <= 1 ? messagesString : (messagesString + " • " + chunkString); + + QString status; + if (state.state == ForwardState::State::Preparing) { + status = tr::ayu_AyuForwardStatusPreparing(tr::now); + } else if (state.state == ForwardState::State::Downloading) { + status = tr::ayu_AyuForwardStatusLoadingMedia(tr::now); + } else if (state.state == ForwardState::State::Sending) { + status = tr::ayu_AyuForwardStatusForwarding(tr::now); + } else { // ForwardState::State::Finished + status = tr::ayu_AyuForwardStatusFinished(tr::now); + } + + + return status.append("\n").append(partString);// + "\n" + partString; + } + + void ForwardState::updateBottomBar(not_null session, const PeerData *peer, const State &st) { + state = st; + forwardStates[peer->id] = *this; + session->changes().peerUpdated(session->data().peer(peer->id), Data::PeerUpdate::Flag::Rights); + } + + + static Ui::PreparedList prepareMedia(not_null session, const std::vector> &items, int &i) { + const auto prepare = [&] (not_null media){ + auto prepared = Ui::PreparedFile(AyuSync::filePath(session, media)); + Storage::PrepareDetails(prepared, st::sendMediaPreviewSize, 1280); + return prepared; + }; + + Ui::PreparedList list; + const auto startItem = items[i]; + const auto media = startItem->media(); + const auto groupId = startItem->groupId(); + + list.files.emplace_back(prepare(media)); + + if (!groupId.value) { + return list; + } + + for (int k = i + 1; k < items.size(); ++k) { + const auto nextItem = items[k]; + if (nextItem->groupId() != groupId) { + break; + } + + if (const auto nextMedia = nextItem->media()) { + list.files.emplace_back(prepare(nextMedia)); + i = k; + } + } + + return list; + } + + void sendMedia( + not_null session, + Ui::PreparedList &&list, + not_null primaryMedia, + Api::MessageToSend &&message, + uint64 newGroupId) { + + + if (const auto document = primaryMedia->document()) { + if (document->isGifv() || document->sticker()) { + AyuSync::sendGifOrStickSync(session, message, document); + return; + } + } + + const auto mediaType = [&] { + if (const auto document = primaryMedia->document()) { + if (document->isVoiceMessage()) { + return SendMediaType::Audio; + } else if (document->isVideoMessage()) { + return SendMediaType::Round; + } else { + return SendMediaType::File; + } + } + return SendMediaType::Photo; + }(); + + + auto group = std::make_shared(); + group->groupId = newGroupId; + + AyuSync::sendDocumentSync( + session, + std::move(list), + mediaType, + std::move(message.textWithTags), + group, + message.action); + } + + + bool isAyuForwardNeeded(const std::vector> &items) { + for (const auto &item : items) { + if (isAyuForwardNeeded(item)) { + return true; + } + } + return false; + } + + bool isAyuForwardNeeded(not_null item) { + if (item->isDeleted() || item->isAyuNoForwards() || item->ttlDestroyAt()) { + return true; + } + return false; + } + + bool isFullAyuForwardNeeded(not_null peer, not_null history) { + return peer->isAyuNoForwards() || history->peer->isAyuNoForwards(); + } + + struct ForwardChunk { + bool isAyuForwardNeeded; + std::vector> items; + }; + + void intelligentForward(const PeerData* peer, const std::vector> &items, not_null session, not_null history, const Api::SendAction &action) { + history->setForwardDraft(action.replyTo.topicRootId, {}); + + auto chunks = std::vector(); + + auto currentArray = std::vector>(); + auto currentChunk = ForwardChunk({ + .isAyuForwardNeeded = isAyuForwardNeeded(items[0]), + .items = currentArray + }); + + for (const auto &item : items) { + if (isAyuForwardNeeded(item) != currentChunk.isAyuForwardNeeded) { + currentChunk.items = currentArray; + chunks.push_back(currentChunk); + + currentArray = std::vector>(); + currentChunk = ForwardChunk({ + .isAyuForwardNeeded = isAyuForwardNeeded(item), + .items = currentArray + }); + } + currentArray.push_back(item); + } + + currentChunk.items = currentArray; + chunks.push_back(currentChunk); + + auto state = std::make_shared(chunks.size()); + forwardStates[peer->id] = *state; + + for (const auto &chunk : chunks) { + if (chunk.isAyuForwardNeeded) { + forwardMessages(peer, chunk.items, session, history, action, true); + } else { + state->totalMessages = chunk.items.size(); + state->sentMessages = 0; + state->updateBottomBar(session, peer, ForwardState::State::Sending); + + AyuSync::forwardMessagesSync(session, chunk.items, action); + + state->sentMessages = state->totalMessages; + state->updateBottomBar(session, peer, ForwardState::State::Finished); + } + + state->currentChunk++; + } + state->updateBottomBar(session, peer, ForwardState::State::Finished); + + } + + void forwardMessages(const PeerData *peer, std::vector > items, + not_null session, + not_null history, const Api::SendAction &action, bool forwardState) { + history->setForwardDraft(action.replyTo.topicRootId, {}); + + ForwardState state; + if (forwardState) { + state = forwardStates[peer->id]; + } else { + state = ForwardState(1); + } + + forwardStates[peer->id] = state; + + std::unordered_map groupIds; + + + std::vector > toBeDownloaded; + + for (const auto item: items) { + if (item->media()) { + toBeDownloaded.push_back(item); + } + if (item->groupId()) { + const auto currentId = groupIds.find(item->groupId().value); + if (currentId == groupIds.end()) { + groupIds[item->groupId().value] = base::RandomValue(); + } + } + } + if (toBeDownloaded.size()) { + state.updateBottomBar(session, peer, ForwardState::State::Downloading); + AyuSync::loadDocuments(session, toBeDownloaded); + } + + state.totalMessages = items.size(); + state.sentMessages = 0; + state.updateBottomBar(session, peer, ForwardState::State::Sending); + + for (int i = 0; i < items.size(); i++) { + const auto item = items[i]; + + + if (state.stopRequested) { + state.updateBottomBar(session, peer, ForwardState::State::Finished); + return; + } + + auto message = Api::MessageToSend(Api::SendAction(session->data().history(peer->id))); + + message.textWithTags.text = item->originalText().text; + message.textWithTags.tags = TextUtilities::ConvertEntitiesToTextTags(item->originalText().entities); + + const auto groupId = item->groupId().value; + const auto newGroupId = groupIds.contains(groupId) ? groupIds.find(groupId)->second : 0; + + + if (const auto media = item->media()) { + if (const auto poll = media->poll()) { + // need to implement extract of the text + continue; + } + sendMedia(session, prepareMedia(session, items, i), media, std::move(message), newGroupId); + } else { + AyuSync::sendMessageSync(session, message); + } + state.sentMessages += 1; + } + state.updateBottomBar(session, peer, ForwardState::State::Finished); + } + +} // namespace AyuFeatures::AyuForward diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h new file mode 100644 index 0000000000..2f9537671c --- /dev/null +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.h @@ -0,0 +1,44 @@ + +#pragma once + + +#include
+ +#include "history/history.h" + + +namespace AyuForward { +bool isForwarding(const PeerId &id); +QString stateName(const PeerId &id); + +class ForwardState { +public: + enum class State { + Preparing, + Downloading, + Sending, + Finished + }; + + void updateBottomBar(not_null session, const PeerData *peer, const State &st); + + int totalChunks; + int currentChunk; + int totalMessages; + int sentMessages; + + State state = State::Preparing; + bool stopRequested = false; + +}; + + +bool isAyuForwardNeeded(const std::vector> &items); +bool isAyuForwardNeeded(not_null item); +bool isFullAyuForwardNeeded(not_null peer, not_null history); +void intelligentForward(const PeerData* peer, + const std::vector> &item, not_null session, not_null history, const Api::SendAction &action); +void forwardMessages(const PeerData* peer, std::vector> item, not_null session, not_null history, const Api::SendAction &action, bool forwardState); + + +} \ No newline at end of file diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp new file mode 100644 index 0000000000..cf2ccacd8e --- /dev/null +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp @@ -0,0 +1,287 @@ + +#include "ayu_sync.h" + +#include "api/api_sending.h" +#include "apiwrap.h" +#include "core/application.h" +#include "data/data_changes.h" +#include "data/data_document.h" +#include "data/data_photo.h" +#include "data/data_photo_media.h" +#include "data/data_session.h" +#include "history/history_item.h" +#include "main/main_session.h" +#include "storage/file_download_mtproto.h" + + +class TimedCountDownLatch +{ +public: + explicit TimedCountDownLatch(int count) : count_(count) {} + + void countDown() { + std::unique_lock lock(mutex_); + if (count_ > 0) { + count_--; + } + if (count_ == 0) { + cv_.notify_all(); + } + } + + bool await(std::chrono::milliseconds timeout) { + std::unique_lock lock(mutex_); + if (count_ == 0) { + return true; + } + return cv_.wait_for(lock, timeout, [this] { return count_ == 0; }); + } + +private: + std::mutex mutex_; + std::condition_variable cv_; + int count_; +}; + +namespace AyuSync { + +QString pathForSave(not_null session) { + const auto path = Core::App().settings().downloadPath(); + if (path.isEmpty()) { + return File::DefaultDownloadPath(session); + } + if (path == FileDialog::Tmp()) { + return session->local().tempDirectory(); + } + return path; +} + + +QString filePath(not_null session, const Data::Media *media) { + if (const auto document = media->document()) { + if (!document->filename().isEmpty()) { + return pathForSave(session) + media->document()->filename(); + } + if (const auto name = document->filepath(true); !name.isEmpty()) { + return name; + } + if (document->isVoiceMessage()) { + return pathForSave(session) + "audio_" + QString::number(document->getDC()) + "_" + + QString::number(document->id) + ".ogg"; + } + if (document->isVideoMessage()) { + return pathForSave(session) + "round_" + QString::number(document->getDC()) + "_" + + QString::number(document->id) + ".mp4"; + } + } else if (const auto photo = media->photo()) { + return pathForSave(session) + QString::number(photo->getDC()) + "_" + QString::number(photo->id) + ".jpg"; + } + + return QString(); +} +qint64 fileSize(not_null item) { + if (const auto path = filePath(&item->history()->session(), item->media()); !path.isEmpty()) { + QFile file(path); + if (file.exists()) { + return file.size(); + } + } + return 0; +} + +void loadDocuments(not_null session, const std::vector> &items) { + for (const auto &item : items) { + if (const auto data = item->media()->document()) { + + if (data->isGifv()) { + // no need to download it + continue; + } + + const auto size = fileSize(item); + + if (size == data->size) { + continue; + } + if (size && size < data->size) { + // in case there some unfinished file + QFile file(filePath(session, item->media())); + file.remove(); + } + + + loadDocumentSync(session, data, item); + + } else if (auto photo = item->media()->photo()) { + if (fileSize(item) == photo->imageByteSize(Data::PhotoSize::Large)) { + continue; + } + + loadPhotoSync(session, std::pair(photo, item->fullId())); + } + } +} +void loadDocumentSync(not_null session, DocumentData *data, not_null item) { + + auto latch = std::make_shared(1); + auto lifetime = std::make_shared(); + + data->save(Data::FileOriginMessage(item->fullId()), filePath(session, item->media())); + + session->downloaderTaskFinished() + | rpl::filter([&]{ + return item->media()->document()->status == FileDownloadFailed || fileSize(item) == data->size; + }) | rpl::start_with_next([&]() mutable{ + latch->countDown(); + base::take(lifetime)->destroy(); + }, *lifetime); + + latch->await(std::chrono::minutes(5)); +} + +void forwardMessagesSync(not_null session, + const std::vector> &items, + const ApiWrap::SendAction &action) { + auto latch = std::make_shared(1); + + crl::on_main([=, &latch]{ + session->api().forwardMessages(Data::ResolvedForwardDraft(items), action, [&]{ + latch->countDown(); + }); + }); + + + latch->await(std::chrono::minutes(1)); +} + + +void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photo) { + const auto folderPath = pathForSave(session); + const auto downloadPath = folderPath.isEmpty() ? Core::App().settings().downloadPath() : folderPath; + + const auto path = downloadPath.isEmpty() + ? File::DefaultDownloadPath(session) + : downloadPath == FileDialog::Tmp() + ? session->local().tempDirectory() + : downloadPath; + if (path.isEmpty()) { + return; + } + if (!QDir().mkpath(path)) { + return; + } + + const auto view = photo.first->createMediaView(); + if (!view) { + return; + } + view->wanted(Data::PhotoSize::Large, photo.second); + + const auto finalCheck = [=]{ + return !photo.first->loading(); + }; + + const auto saveToFiles = [=]{ + QDir directory(path); + const auto dir = directory.absolutePath(); + const auto nameBase = dir.endsWith('/') ? dir : dir + '/'; + const auto fullPath = nameBase + QString::number(photo.first->getDC()) + "_" + QString::number(photo.first->id) + ".jpg"; + view->saveToFile(fullPath); + }; + + auto latch = std::make_shared(1); + auto lifetime = std::make_shared(); + + if (finalCheck()) { + saveToFiles(); + } else { + session->downloaderTaskFinished() | rpl::filter([&]{ + return finalCheck(); + }) | rpl::start_with_next([&]() mutable{ + saveToFiles(); + latch->countDown(); + base::take(lifetime)->destroy(); + },*lifetime); + } + + latch->await(std::chrono::minutes(5)); +} + +void sendMessageSync(not_null session, Api::MessageToSend &message) { + crl::on_main([=, &message] { + // we cannot send events to objects + // owned by a different thread + // because sendMessage updates UI too + + session->api().sendMessage(std::move(message)); + }); + + + waitForMsgSync(session, message.action); +} + +void waitForMsgSync(not_null session, const Api::SendAction &action) { + auto latch = std::make_shared(1); + auto lifetime = std::make_shared(); + + + session->data().itemIdChanged() + | rpl::filter([&](const Data::Session::IdChange &update){ + return action.history->peer->id == update.newId.peer; + }) | rpl::start_with_next([&]{ + latch->countDown(); + base::take(lifetime)->destroy(); + },*lifetime); + + latch->await(std::chrono::minutes(2)); +} + +void sendDocumentSync(not_null session, + Ui::PreparedList &&list, + SendMediaType type, + TextWithTags &&caption, + const std::shared_ptr &album, + const Api::SendAction &action) { + + const auto size = list.files.size(); + auto latch = std::make_shared(size); + auto lifetime = std::make_shared(); + + crl::on_main([=, list = std::move(list), caption = std::move(caption)]() mutable{ + session->api().sendFiles(std::move(list), type, std::move(caption), album, action); + }); + + + // probably need to handle + // session->uploader().photoFailed() + // and + // session->uploader().documentFailed() + // too + + rpl::merge( + session->uploader().documentReady(), + session->uploader().photoReady() + ) | rpl::filter([&](const Storage::UploadedMedia &docOrPhoto){ + return docOrPhoto.fullId.peer == action.history->peer->id; + }) | rpl::start_with_next([&]{ + latch->countDown(); + }, *lifetime); + + latch->await(std::chrono::minutes(5 * size)); + base::take(lifetime)->destroy(); +} + +void sendGifOrStickSync(not_null session, + Api::MessageToSend &message, + not_null document) { + auto &action = message.action; + crl::on_main([&] { + Api::SendExistingDocument(std::move(message), document, std::nullopt); + }); + + + waitForMsgSync(session, action); +} + + +} // namespace AyuSync diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h new file mode 100644 index 0000000000..3a6e14816a --- /dev/null +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h @@ -0,0 +1,51 @@ +#pragma once +#include "data/data_media_types.h" +#include "data/data_session.h" +#include +#include +#include +#include + +#include "apiwrap.h" +#include "base/unixtime.h" +#include "data/data_document.h" +#include "data/data_photo.h" +#include "data/data_session.h" +#include "storage/file_download_mtproto.h" +#include "storage/file_upload.h" +#include "api/api_peer_photo.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "data/data_channel.h" +#include "data/data_chat.h" +#include "storage/localimageloader.h" +#include "storage/storage_media_prepare.h" +#include "ui/chat/attach/attach_prepare.h" +#include "storage/file_download.h" +#include "storage/storage_account.h" + + +namespace AyuSync { + +QString pathForSave(not_null session); +QString filePath(not_null session, const Data::Media *media); +void loadDocuments(not_null session, const std::vector > &items); +bool isMediaDownloadable(Data::Media *media); +void sendMessageSync(not_null session, Api::MessageToSend &message); + +void sendDocumentSync(not_null session, + Ui::PreparedList &&list, + SendMediaType type, + TextWithTags &&caption, + const std::shared_ptr &album, + const Api::SendAction &action); + +void sendGifOrStickSync(not_null session, + Api::MessageToSend &message, + not_null document); +void waitForMsgSync(not_null session, const Api::SendAction &action); +void loadPhotoSync(not_null session, const std::pair, FullMsgId> &photos); +void loadDocumentSync(not_null session, DocumentData *data, not_null item); +void forwardMessagesSync(not_null session, const std::vector > &items, const ApiWrap::SendAction &action); + +} diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 8761065915..2fef35be56 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -619,7 +619,9 @@ bool ChannelData::canAddAdmins() const { return amCreator() || (adminRights() & AdminRight::AddAdmins); } - +bool ChannelData::isAyuNoForwards() const { + return flags() & Flag::AyuNoForwards; +} bool ChannelData::allowsForwarding() const { return !(flags() & Flag::NoForwards); } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index b4f99b5243..7cc5c39e0a 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -74,6 +74,8 @@ enum class ChannelDataFlag : uint64 { StargiftsAvailable = (1ULL << 36), PaidMessagesAvailable = (1ULL << 37), AutoTranslation = (1ULL << 38), + + AyuNoForwards = (1ULL << 60), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; @@ -365,6 +367,7 @@ public: // Like in ChatData. [[nodiscard]] bool allowsForwarding() const; + [[nodiscard]] bool isAyuNoForwards() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; diff --git a/Telegram/SourceFiles/data/data_chat.cpp b/Telegram/SourceFiles/data/data_chat.cpp index fc33c01708..63859240a2 100644 --- a/Telegram/SourceFiles/data/data_chat.cpp +++ b/Telegram/SourceFiles/data/data_chat.cpp @@ -63,6 +63,9 @@ ChatAdminRightsInfo ChatData::defaultAdminRights(not_null user) { | Flag::ManageCall | (isCreator ? Flag::AddAdmins : Flag(0))); } +bool ChatData::isAyuNoForwards() const { + return flags() & Flag::AyuNoForwards; +} bool ChatData::allowsForwarding() const { return !(flags() & Flag::NoForwards); diff --git a/Telegram/SourceFiles/data/data_chat.h b/Telegram/SourceFiles/data/data_chat.h index 5a285aeeb5..15114fc098 100644 --- a/Telegram/SourceFiles/data/data_chat.h +++ b/Telegram/SourceFiles/data/data_chat.h @@ -23,6 +23,8 @@ enum class ChatDataFlag { CallNotEmpty = (1 << 6), CanSetUsername = (1 << 7), NoForwards = (1 << 8), + + AyuNoForwards = (1 << 20), }; inline constexpr bool is_flag_type(ChatDataFlag) { return true; }; using ChatDataFlags = base::flags; @@ -99,6 +101,7 @@ public: // Like in ChannelData. [[nodiscard]] bool allowsForwarding() const; + [[nodiscard]] bool isAyuNoForwards() const; [[nodiscard]] bool canEditInformation() const; [[nodiscard]] bool canEditPermissions() const; [[nodiscard]] bool canEditUsername() const; diff --git a/Telegram/SourceFiles/data/data_chat_participant_status.cpp b/Telegram/SourceFiles/data/data_chat_participant_status.cpp index 81b3330972..4bce0f1e63 100644 --- a/Telegram/SourceFiles/data/data_chat_participant_status.cpp +++ b/Telegram/SourceFiles/data/data_chat_participant_status.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_chat_participant_status.h" +#include "ayu/features/forward/ayu_forward.h" #include "base/unixtime.h" #include "boxes/peers/edit_peer_permissions_box.h" #include "chat_helpers/compose/compose_show.h" @@ -118,6 +119,9 @@ bool CanSendAnyOf( not_null peer, ChatRestrictions rights, bool forbidInForums) { + if (AyuForward::isForwarding(peer->id)) { + return false; + } if (peer->session().frozen() && !peer->isFreezeAppealChat()) { return false; @@ -180,6 +184,11 @@ bool CanSendAnyOf( SendError RestrictionError( not_null peer, ChatRestriction restriction) { + if (AyuForward::isForwarding(peer->id)) { + return SendError({ + .text = AyuForward::stateName(peer->id) + }); + } using Flag = ChatRestriction; if (peer->session().frozen() && !peer->isFreezeAppealChat()) { diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 40a29139b0..fca05dc9c3 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -1385,6 +1385,17 @@ Data::ForumTopic *PeerData::forumTopicFor(MsgId rootId) const { } return nullptr; } +bool PeerData::isAyuNoForwards() const { + if (const auto user = asUser()) { + return false; + } else if (const auto channel = asChannel()) { + return channel->isAyuNoForwards(); + } else if (const auto chat = asChat()) { + return chat->isAyuNoForwards(); + } + return true; +} + bool PeerData::allowsForwarding() const { if (isUser()) { diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index b0563c42d5..5615c378a6 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -264,6 +264,7 @@ public: return _notify; } + [[nodiscard]] bool isAyuNoForwards() const; [[nodiscard]] bool allowsForwarding() const; [[nodiscard]] Data::RestrictionCheckResult amRestricted( ChatRestriction right) const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 4d32e35e57..f91507e36f 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -860,7 +860,8 @@ not_null Session::processChat(const MTPChat &data) { && chat->groupCall()->fullCount() > 0)) ? Flag::CallNotEmpty : Flag()) - | (data.is_noforwards() ? Flag::NoForwards : Flag()); + | (data.is_noforwards() ? Flag::NoForwards : Flag()) + | (data.is_ayuNoforwards() ? Flag::AyuNoForwards : Flag()); chat->setFlags((chat->flags() & ~flagsMask) | flagsSet); chat->count = data.vparticipants_count().v; @@ -1017,7 +1018,8 @@ not_null Session::processChat(const MTPChat &data) { && data.is_stories_hidden()) ? Flag::StoriesHidden : Flag()) - | (data.is_autotranslation() ? Flag::AutoTranslation : Flag()); + | (data.is_autotranslation() ? Flag::AutoTranslation : Flag()) + | (data.is_ayuNoforwards() ? Flag::AyuNoForwards : Flag()); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setBotVerifyDetailsIcon( data.vbot_verification_icon().value_or_empty()); diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 9f0562e1b7..0a154a7e2c 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -351,6 +351,9 @@ enum class MessageFlag : uint64 { ReactionsAllowed = (1ULL << 50), HideDisplayDate = (1ULL << 51), + + + AyuNoForwards = (1ULL << 60), }; inline constexpr bool is_flag_type(MessageFlag) { return true; } using MessageFlags = base::flags; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index c6d4474a41..255b1bc059 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2776,7 +2776,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto itemId = item->fullId(); const auto blockSender = item->history()->peer->isRepliesChat(); if (isUponSelected != -2) { - if (item->allowsForward() && !item->isDeleted()) { + if (item->allowsForward()) { _menu->addAction(tr::lng_context_forward_msg(tr::now), [=] { forwardItem(itemId); }, &st::menuIconForward); @@ -3018,7 +3018,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { }, &st::menuIconSelect); } else if (item && ((isUponSelected != -2 && (canForward || canDelete)) || item->isRegular())) { if (isUponSelected != -2) { - if (canForward && !item->isDeleted()) { + if (canForward) { _menu->addAction(tr::lng_context_forward_msg(tr::now), [=] { forwardAsGroup(itemId); }, &st::menuIconForward); @@ -3945,7 +3945,7 @@ auto HistoryInner::getSelectionState() const if (selected.first->canDelete()) { ++result.canDeleteCount; } - if (selected.first->allowsForward() && !selected.first->isDeleted()) { + if (selected.first->allowsForward()) { ++result.canForwardCount; } } else if (selected.second.from != selected.second.to) { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 99250b6046..c8a84d953c 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1766,6 +1766,9 @@ bool HistoryItem::isScheduled() const { bool HistoryItem::isSponsored() const { return _flags & MessageFlag::Sponsored; } +bool HistoryItem::isAyuNoForwards() const { + return _flags & MessageFlag::AyuNoForwards; +} bool HistoryItem::skipNotification() const { if (isSilent() && (_flags & MessageFlag::IsContactSignUp)) { diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 17e635a32d..63822da59e 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -207,6 +207,9 @@ public: [[nodiscard]] bool isFromScheduled() const; [[nodiscard]] bool isScheduled() const; [[nodiscard]] bool isSponsored() const; + + [[nodiscard]] bool isAyuNoForwards() const; + [[nodiscard]] bool skipNotification() const; [[nodiscard]] bool isUserpicSuggestion() const; [[nodiscard]] BusinessShortcutId shortcutId() const; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 050dee1005..be30175144 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -767,6 +767,7 @@ MessageFlags FlagsFromMTP( | ((flags & MTP::f_views) ? Flag::HasViews : Flag()) // AyuGram: removed // | ((flags & MTP::f_noforwards) ? Flag::NoForwards : Flag()) + | (flags & MTP::f_noforwards ? Flag::AyuNoForwards : Flag()) | ((flags & MTP::f_invert_media) ? Flag::InvertMedia : Flag()) | ((flags & MTP::f_video_processing_pending) ? Flag::EstimatedDate diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index efe1beea85..2655837dc1 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -413,10 +413,6 @@ bool AddForwardMessageAction( const ContextMenuRequest &request, not_null list) { const auto item = request.item; - if (item && item->isDeleted()) { - return false; - } - if (!request.selectedItems.empty()) { return false; } else if (!item || !item->allowsForward()) { @@ -641,7 +637,7 @@ bool AddReplyToMessageAction( ? Data::CanSendAnything(topic) : Data::CanSendAnything(peer); const auto canReply = canSendReply || item->allowsForward(); - if (!canReply || item->isDeleted()) { + if (!canReply) { return false; } diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index db2d5f8161..94d949ee25 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -1358,7 +1358,7 @@ bool ListWidget::addToSelection( return false; } iterator->second.canDelete = item->canDelete(); - iterator->second.canForward = item->allowsForward() && !item->isDeleted(); + iterator->second.canForward = item->allowsForward(); iterator->second.canSendNow = item->allowsSendNow(); iterator->second.canReschedule = item->allowsReschedule(); return true;