diff --git a/Telegram/SourceFiles/ayu/ayu_settings.cpp b/Telegram/SourceFiles/ayu/ayu_settings.cpp index f0d357674a..2280020ce8 100644 --- a/Telegram/SourceFiles/ayu/ayu_settings.cpp +++ b/Telegram/SourceFiles/ayu/ayu_settings.cpp @@ -1051,9 +1051,9 @@ void AyuSettings::setSingleCornerRadius(bool val) { } void to_json(nlohmann::json &j, const AyuSettings &s) { - std::map ghostAccounts; + auto ghostAccounts = nlohmann::json::object(); for (const auto &[key, value] : s._ghostAccounts) { - ghostAccounts[std::to_string(key)] = std::move(*value); + ghostAccounts[std::to_string(key)] = *value; } j = nlohmann::json{ diff --git a/Telegram/SourceFiles/ayu/data/ayu_database.cpp b/Telegram/SourceFiles/ayu/data/ayu_database.cpp index 211b3b728d..18d9f540b4 100644 --- a/Telegram/SourceFiles/ayu/data/ayu_database.cpp +++ b/Telegram/SourceFiles/ayu/data/ayu_database.cpp @@ -248,6 +248,10 @@ void addEditedMessage(const EditedMessage &message) { storage.insert(message); storage.commit(); } catch (std::exception &ex) { + try { + storage.rollback(); + } catch (...) { + } LOG(("Failed to save edited message for some reason: %1").arg(ex.what())); } } @@ -289,6 +293,10 @@ void addDeletedMessage(const DeletedMessage &message) { storage.insert(message); storage.commit(); } catch (std::exception &ex) { + try { + storage.rollback(); + } catch (...) { + } LOG(("Failed to save edited message for some reason: %1").arg(ex.what())); } } @@ -442,7 +450,10 @@ void addRegexFilter(const RegexFilter &filter) { storage.replace(filter); // we're using replace as we set std::vector as primary key storage.commit(); } catch (std::exception &ex) { - storage.rollback(); + try { + storage.rollback(); + } catch (...) { + } LOG(("Failed to save regex filter for some reason: %1").arg(ex.what())); } } @@ -453,6 +464,10 @@ void addRegexExclusion(const RegexFilterGlobalExclusion &exclusion) { storage.insert(exclusion); storage.commit(); } catch (std::exception &ex) { + try { + storage.rollback(); + } catch (...) { + } LOG(("Failed to save regex filter exclusion for some reason: %1").arg(ex.what())); } } diff --git a/Telegram/SourceFiles/ayu/data/entities.h b/Telegram/SourceFiles/ayu/data/entities.h index 9608898599..0e0e9b7e70 100644 --- a/Telegram/SourceFiles/ayu/data/entities.h +++ b/Telegram/SourceFiles/ayu/data/entities.h @@ -8,7 +8,7 @@ #include -#define ID long long +using ID = long long; class AyuMessageBase { diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.cpp b/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.cpp index cb4e5a197b..64b2cf38f9 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.cpp +++ b/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.cpp @@ -15,12 +15,16 @@ #include "history/history_item.h" #include "rpl/event_stream.h" +#include +#include #include -static std::mutex mutex; - namespace FiltersCacheController { +std::mutex rebuildMutex; +std::mutex cacheMutex; +std::mutex filteredMessagesMutex; + rpl::event_stream<> filtersUpdateStream; void fireUpdate() { @@ -31,21 +35,17 @@ rpl::producer<> updates() { return filtersUpdateStream.events(); } -std::optional> sharedPatterns; -std::optional>> patternsByDialogId; +std::shared_ptr cache; -std::optional>> exclusionsByDialogId; - -std::unordered_map, std::optional>> filteredMessages; - -void rebuildCache() { - std::lock_guard lock(mutex); +std::unordered_map> filteredMessages; +std::unordered_set dialogsWithHiddenBlockedMessages; // purely for show / hide filtered messages +std::shared_ptr buildCache() { const auto filters = AyuDatabase::getAllRegexFilters(); const auto exclusions = AyuDatabase::getAllFiltersExclusions(); std::vector shared; - std::unordered_map> byDialogId; + std::unordered_map> byDialogId; for (const auto &filter : filters) { if (!filter.enabled || filter.text.empty()) { @@ -56,25 +56,47 @@ void rebuildCache() { if (filter.caseInsensitive) flags |= UREGEX_CASE_INSENSITIVE; auto status = U_ZERO_ERROR; - auto pattern = RegexPattern::compile(UnicodeString::fromUTF8(filter.text), flags, status); + auto pattern = icu::RegexPattern::compile(icu::UnicodeString::fromUTF8(filter.text), flags, status); if (!pattern) { continue; } if (filter.dialogId.has_value()) { - byDialogId[filter.dialogId.value()].push_back({std::shared_ptr(pattern), filter.reversed}); + byDialogId[filter.dialogId.value()].push_back({ + std::shared_ptr(pattern), + filter.reversed, + }); } else { - shared.push_back({filter.id, {std::shared_ptr(pattern), filter.reversed}}); + shared.push_back({ + filter.id, + { + std::shared_ptr(pattern), + filter.reversed, + }, + }); } } auto exclByDialogId = buildExclusions(exclusions, shared); + auto result = std::make_shared(); + result->sharedPatterns = std::move(shared); + result->patternsByDialogId = std::move(byDialogId); + result->exclusionsByDialogId = std::move(exclByDialogId); - sharedPatterns = shared; - patternsByDialogId = byDialogId; - exclusionsByDialogId = exclByDialogId; - filteredMessages.clear(); + return result; +} + +void rebuildCache() { + std::lock_guard rebuildLock(rebuildMutex); + auto next = buildCache(); + { + std::lock_guard cacheLock(cacheMutex); + std::lock_guard filteredLock(filteredMessagesMutex); + cache = std::move(next); + filteredMessages.clear(); + dialogsWithHiddenBlockedMessages.clear(); + } } std::unordered_map> buildExclusions( @@ -95,8 +117,32 @@ std::unordered_map return exclusionsByDialogId; } +std::shared_ptr snapshot() { + { + std::lock_guard lock(cacheMutex); + if (cache) { + return cache; + } + } + + std::lock_guard rebuildLock(rebuildMutex); + { + std::lock_guard lock(cacheMutex); + if (cache) { + return cache; + } + } + + auto next = buildCache(); + std::lock_guard lock(cacheMutex); + if (!cache) { + cache = std::move(next); + } + return cache; +} + std::optional isFiltered(not_null item) { - std::lock_guard lock(mutex); + std::lock_guard lock(filteredMessagesMutex); auto dialogIt = filteredMessages.find(item->history()->peer->id.value); if (dialogIt == filteredMessages.end()) { @@ -112,21 +158,38 @@ std::optional isFiltered(not_null item) { } bool hasFilteredMessages(not_null peer) { - std::lock_guard lock(mutex); + std::lock_guard lock(filteredMessagesMutex); + if (dialogsWithHiddenBlockedMessages.contains(peer->id.value)) { + return true; + } const auto dialogIt = filteredMessages.find(peer->id.value); if (dialogIt == filteredMessages.end()) { return false; } for (const auto &entry : dialogIt->second) { - if (entry.second == true) { + if (entry.second) { return true; } } return false; } -void putFiltered(not_null item, const Data::Group *group, bool res) { - std::lock_guard lock(mutex); +void putHiddenBlockedMessage(not_null item) { + std::lock_guard lock(filteredMessagesMutex); + dialogsWithHiddenBlockedMessages.insert(item->history()->peer->id.value); +} + +void putFiltered( + not_null item, + const Data::Group *group, + bool res, + const std::shared_ptr &matchedCache) { + std::lock_guard cacheLock(cacheMutex); + if (cache != matchedCache) { + return; + } + + std::lock_guard filteredLock(filteredMessagesMutex); filteredMessages[item->history()->peer->id.value][item->id.bare] = res; if (group && res) { for (const auto& groupItem : group->items) { @@ -151,7 +214,7 @@ void invalidateSingle(not_null item) { } void invalidate(not_null item) { - std::lock_guard lock(mutex); + std::lock_guard lock(filteredMessagesMutex); if (const auto group = item->history()->owner().groups().find(item)) { for (const auto& groupItem : group->items) { invalidateSingle(groupItem); @@ -161,36 +224,4 @@ void invalidate(not_null item) { } } -std::optional> getPatternsByDialogId(uint64 dialogId) { - if (!patternsByDialogId.has_value()) { - rebuildCache(); - } - const auto it = patternsByDialogId.value().find(dialogId); - if (it == patternsByDialogId.value().end()) { - return std::nullopt; - } - - return it->second; -} - -std::optional> getExclusionsByDialogId(long long dialogId) { - std::lock_guard lock(mutex); - - if (!exclusionsByDialogId.has_value()) { - rebuildCache(); - } - const auto it = exclusionsByDialogId.value().find(dialogId); - if (it == exclusionsByDialogId.value().end()) { - return std::nullopt; - } - return it->second; -} - -const std::vector &getSharedPatterns() { - if (!sharedPatterns.has_value()) { - rebuildCache(); - } - return sharedPatterns.value(); -} - } diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.h b/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.h index 0ce1d13f0e..ca71cc3cb6 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.h +++ b/Telegram/SourceFiles/ayu/features/filters/filters_cache_controller.h @@ -10,31 +10,47 @@ #include "ayu/features/filters/filters_controller.h" #include "rpl/producer.h" +#include +#include +#include + namespace Data { struct Group; } -using namespace FiltersController; - namespace FiltersCacheController { +using HashablePattern = FiltersController::HashablePattern; +using PatternHasher = FiltersController::PatternHasher; +using ReversiblePattern = FiltersController::ReversiblePattern; + void fireUpdate(); [[nodiscard]] rpl::producer<> updates(); void rebuildCache(); +struct Cache +{ + std::vector sharedPatterns; + std::unordered_map> patternsByDialogId; + std::unordered_map> exclusionsByDialogId; +}; + +[[nodiscard]] std::shared_ptr snapshot(); + std::unordered_map> buildExclusions( const std::vector &exclusions, const std::vector &shared); std::optional isFiltered(not_null item); bool hasFilteredMessages(not_null peer); -void putFiltered(not_null item, const Data::Group *group, bool res); +void putHiddenBlockedMessage(not_null item); +void putFiltered( + not_null item, + const Data::Group *group, + bool res, + const std::shared_ptr &matchedCache); void invalidate(not_null item); -std::optional> getPatternsByDialogId(uint64 dialogId); -std::optional> getExclusionsByDialogId(long long dialogId); -const std::vector &getSharedPatterns(); - } diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_controller.cpp b/Telegram/SourceFiles/ayu/features/filters/filters_controller.cpp index 7f7a2e008d..ab19c76e5f 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_controller.cpp +++ b/Telegram/SourceFiles/ayu/features/filters/filters_controller.cpp @@ -20,6 +20,7 @@ #include "history/history_item_components.h" #include "unicode/regex.h" +#include #include namespace FiltersController { @@ -35,23 +36,27 @@ bool filterBlocked(const not_null item) { return false; } -std::optional isFiltered(const QString &str, uint64 dialogId) { +std::optional isFiltered( + const QString &str, + long long dialogId, + const std::shared_ptr &cache) { if (str.isEmpty()) { return std::nullopt; } - const auto icuStr = UnicodeString(reinterpret_cast(str.constData()), str.length()); + const auto icuStr = icu::UnicodeString(reinterpret_cast(str.constData()), str.length()); const auto matches = [&](const ReversiblePattern &pattern) { UErrorCode status = U_ZERO_ERROR; - auto match = pattern.pattern->matcher(icuStr, status)->find(); - if (U_FAILURE(status)) { + const auto matcher = std::unique_ptr(pattern.pattern->matcher(icuStr, status)); + if (U_FAILURE(status) || !matcher) { LOG(("FILTER FAILED: %1").arg(u_errorName(status))); return false; } + const auto match = matcher->find(); const auto reversed = pattern.reversed; if ((!reversed && match) || (reversed && !match)) { @@ -60,20 +65,18 @@ std::optional isFiltered(const QString &str, uint64 dialogId) { return false; }; - const auto &dialogPatterns = FiltersCacheController::getPatternsByDialogId(dialogId); - if (dialogPatterns.has_value() && !dialogPatterns.value().empty()) { - for (const auto &pattern : dialogPatterns.value()) { + if (const auto i = cache->patternsByDialogId.find(dialogId); i != cache->patternsByDialogId.end()) { + for (const auto &pattern : i->second) { if (matches(pattern)) { return true; } } } - const auto &exclusions = FiltersCacheController::getExclusionsByDialogId(dialogId); - const auto &sharedPatterns = FiltersCacheController::getSharedPatterns(); - if (!sharedPatterns.empty()) { - for (const auto &pattern : sharedPatterns) { - if (exclusions.has_value() && exclusions.value().contains(pattern)) { + const auto exclusions = cache->exclusionsByDialogId.find(dialogId); + if (!cache->sharedPatterns.empty()) { + for (const auto &pattern : cache->sharedPatterns) { + if (exclusions != cache->exclusionsByDialogId.end() && exclusions->second.contains(pattern)) { continue; } if (matches(pattern.pattern)) { @@ -92,29 +95,44 @@ bool isEnabled(not_null peer) { bool isBlocked(const not_null item) { const auto &settings = AyuSettings::getInstance(); + auto shadowBanMatched = false; const auto blocked = [&]() -> bool { - if (item->from()->isUser() && - item->from()->asUser()->isBlocked()) { + const auto isShadowBanned = [&](PeerData *peer) { + return peer + && (peer->isUser() || peer->isBroadcast()) + && settings.isShadowBanned(getDialogIdFromPeer(peer)); + }; + + if (isShadowBanned(item->from()) + && item->from()->id != item->history()->peer->id) { + shadowBanMatched = true; + return true; + } + + if (item->from()->isUser() + && item->from()->asUser()->isBlocked()) { // don't hide messages if it's a dialog with blocked user return item->from()->asUser()->id != item->history()->peer->id; } if (const auto forwarded = item->Get()) { - if (forwarded->originalSender && - forwarded->originalSender->isUser() && - forwarded->originalSender->asUser()->isBlocked()) { - return true; + if (const auto originalSender = forwarded->originalSender) { + const auto originalShadowBanned = isShadowBanned(originalSender); + if (originalShadowBanned + || (originalSender->isUser() + && originalSender->asUser()->isBlocked())) { + shadowBanMatched = originalShadowBanned; + return true; + } } } return false; }(); - return settings.filtersEnabled() && - ( - ((item->from()->isUser() || item->from()->isBroadcast()) && settings.isShadowBanned(getDialogIdFromPeer(item->from()))) || - (settings.hideFromBlocked() && blocked) - ); + return settings.filtersEnabled() + && (shadowBanMatched || settings.hideFromBlocked()) + && blocked; } bool isBlocked(const not_null peer) { @@ -140,7 +158,10 @@ bool filtered(const not_null item) { return false; } - if (filterBlocked(item)) return true; + if (filterBlocked(item)) { + FiltersCacheController::putHiddenBlockedMessage(item); + return true; + } if (!isEnabled(item->history()->peer)) return false; @@ -149,13 +170,17 @@ bool filtered(const not_null item) { return cached.value(); } const auto group = item->history()->owner().groups().find(item); - const auto res = isFiltered(FilterUtils::extractAllText(item, group), getDialogIdFromPeer(item->history()->peer)); + const auto cache = FiltersCacheController::snapshot(); + const auto res = isFiltered( + FilterUtils::extractAllText(item, group), + getDialogIdFromPeer(item->history()->peer), + cache); // sometimes item has empty text. // so we cache result only if // processed item is filterable if (res.has_value()) { - FiltersCacheController::putFiltered(item, group, res.value()); + FiltersCacheController::putFiltered(item, group, res.value(), cache); return res.value(); } return false; diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_controller.h b/Telegram/SourceFiles/ayu/features/filters/filters_controller.h index 09fadc3d18..098a8ecc3f 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_controller.h +++ b/Telegram/SourceFiles/ayu/features/filters/filters_controller.h @@ -8,15 +8,14 @@ #include "unicode/regex.h" +#include #include #include #include -using namespace icu_78; - namespace FiltersController { -bool isEnabled(PeerData *peer); +bool isEnabled(not_null peer); bool isBlocked(not_null item); bool isBlocked(not_null peer); bool filtered(not_null historyItem); @@ -27,7 +26,7 @@ void invalidate(not_null item); struct ReversiblePattern { - std::shared_ptr pattern; + std::shared_ptr pattern; bool reversed; }; diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_utils.cpp b/Telegram/SourceFiles/ayu/features/filters/filters_utils.cpp index bf237028b1..3e40b04baa 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_utils.cpp +++ b/Telegram/SourceFiles/ayu/features/filters/filters_utils.cpp @@ -6,27 +6,41 @@ // Copyright @Radolyn, 2026 #include "ayu/features/filters/filters_utils.h" +#include "apiwrap.h" #include "lang_auto.h" #include "ayu/ayu_settings.h" #include "ayu/data/ayu_database.h" #include "ayu/features/filters/filters_cache_controller.h" #include "ayu/utils/telegram_helpers.h" +#include "base/qthelp_url.h" +#include "boxes/abstract_box.h" +#include "core/local_url_handlers.h" +#include "data/data_channel.h" +#include "data/data_chat.h" #include "data/data_document.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "data/data_user.h" #include "history/history.h" #include "history/history_item.h" +#include "lang/lang_text_entity.h" #include "main/main_account.h" #include "main/main_domain.h" #include "main/main_session.h" +#include "ui/boxes/confirm_box.h" #include "ui/toast/toast.h" +#include +#include +#include +#include #include #include #include #include #include #include +#include #include #include #include @@ -34,6 +48,247 @@ constexpr auto BACKUP_VERSION = 2; +enum class PeerResolveHintType { + Username, + Invite, +}; + +struct PeerResolveHint { + PeerResolveHintType type = PeerResolveHintType::Username; + QString value; +}; + +bool HasChanges(const ApplyChanges &changes) { + return !changes.newFilters.empty() + || !changes.removeFiltersById.empty() + || !changes.filtersOverrides.empty() + || !changes.newExclusions.empty() + || !changes.removeExclusions.empty() + || !changes.peersToBeResolved.empty(); +} + +void AppendChangeSummary( + TextWithEntities &summary, + TextWithEntities &&line) { + if (!summary.text.isEmpty()) { + summary.append('\n'); + } + summary.append(std::move(line)); +} + +TextWithEntities ChangeSummaryText(const ApplyChanges &changes) { + auto result = tr::marked(); + + if (!changes.newFilters.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetNewFilters( + tr::now, + lt_count, + int(changes.newFilters.size()), + tr::rich)); + } + if (!changes.removeFiltersById.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetRemovedFilters( + tr::now, + lt_count, + int(changes.removeFiltersById.size()), + tr::rich)); + } + if (!changes.filtersOverrides.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetUpdatedFilters( + tr::now, + lt_count, + int(changes.filtersOverrides.size()), + tr::rich)); + } + if (!changes.newExclusions.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetNewExclusions( + tr::now, + lt_count, + int(changes.newExclusions.size()), + tr::rich)); + } + if (!changes.removeExclusions.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetRemovedExclusions( + tr::now, + lt_count, + int(changes.removeExclusions.size()), + tr::rich)); + } + if (!changes.peersToBeResolved.empty()) { + AppendChangeSummary( + result, + tr::ayu_FiltersSheetDialogsToResolve( + tr::now, + lt_count, + int(changes.peersToBeResolved.size()), + tr::rich)); + } + + return result; +} + +std::optional ParsePeerResolveHint(QString value) { + value = value.trimmed(); + if (value.isEmpty()) { + return std::nullopt; + } + + const auto converted = Core::TryConvertUrlToLocal(value); + const auto local = converted.isEmpty() ? value : converted; + const auto delimiter = local.indexOf('?'); + const auto params = (delimiter > 0) + ? qthelp::url_parse_params( + local.mid(delimiter + 1), + qthelp::UrlParamNameTransform::ToLower) + : QMap(); + + if (local.startsWith(u"tg://join"_q, Qt::CaseInsensitive)) { + const auto invite = params.value(u"invite"_q); + if (!invite.isEmpty()) { + return PeerResolveHint{ + .type = PeerResolveHintType::Invite, + .value = invite, + }; + } + } else if (local.startsWith(u"tg://resolve"_q, Qt::CaseInsensitive)) { + const auto domain = params.value(u"domain"_q); + if (!domain.isEmpty()) { + return PeerResolveHint{ + .type = PeerResolveHintType::Username, + .value = domain, + }; + } + } + + if (value.startsWith('@')) { + value = value.mid(1); + } + if (value.startsWith('+')) { + return PeerResolveHint{ + .type = PeerResolveHintType::Invite, + .value = value.mid(1), + }; + } + if (value.startsWith("joinchat/"_q, Qt::CaseInsensitive)) { + return PeerResolveHint{ + .type = PeerResolveHintType::Invite, + .value = value.mid(9), + }; + } + + const auto slash = value.indexOf('/'); + if (slash >= 0) { + value = value.left(slash); + } + if (value.isEmpty()) { + return std::nullopt; + } + return PeerResolveHint{ + .type = PeerResolveHintType::Username, + .value = value, + }; +} + +std::vector ParseFilterId(QString id) { + id.remove('-'); + if (id.size() != 32) { + return {}; + } + + const auto bytes = QByteArray::fromHex(id.toUtf8()); + if (bytes.size() != 16) { + return {}; + } + return std::vector(bytes.constData(), bytes.constData() + bytes.size()); +} + +PeerData *LoadedPeerFromDialogId(not_null data, ID dialogId) { + if (!dialogId) { + return nullptr; + } + const auto bare = (dialogId < 0) ? -dialogId : dialogId; + if (dialogId > 0) { + return data->userLoaded(UserId(bare)); + } + if (const auto channel = data->channelLoaded(ChannelId(bare))) { + return channel; + } + return data->chatLoaded(ChatId(bare)); +} + +void ResolveFilterBackupPeers(const std::vector &peerHints) { + auto session = currentSession(); + auto hintsCopy = peerHints; + + crl::async([=] + { + for (const auto &entry : hintsCopy) { + const auto hint = ParsePeerResolveHint(entry); + if (!hint || hint->value.isEmpty()) { + continue; + } + auto latch = std::make_shared(1); + auto floodWait = std::make_shared(false); + + auto onFail = [=](const MTP::Error &error) + { + if (MTP::IsFloodError(error.type())) { + floodWait->store(true); + } + latch->countDown(); + }; + + crl::on_main([=] + { + if (hint->type == PeerResolveHintType::Username) { + session->api().request(MTPcontacts_ResolveUsername( + MTP_flags(0), + MTP_string(hint->value), + MTP_string() + )).done([=](const MTPcontacts_ResolvedPeer &result) + { + const auto &data = result.data(); + session->data().processUsers(data.vusers()); + session->data().processChats(data.vchats()); + latch->countDown(); + }).fail(onFail).send(); + } else { + session->api().checkChatInvite( + hint->value, + [=](const MTPChatInvite &invite) + { + invite.match([=](const MTPDchatInvite &data) + { + }, [=](const MTPDchatInviteAlready &data) + { + session->data().processChat(data.vchat()); + }, [=](const MTPDchatInvitePeek &data) + { + session->data().processChat(data.vchat()); + }); + latch->countDown(); + }, + onFail); + } + }); + latch->await(std::chrono::seconds(20)); + if (floodWait->load()) { + std::this_thread::sleep_for(std::chrono::seconds(20)); + } + } + }); +} + void FilterUtils::importFromLink(const QString &link) { if (link.isEmpty()) { Ui::Toast::Show(tr::ayu_FiltersToastFailFetch(tr::now)); @@ -41,15 +296,21 @@ void FilterUtils::importFromLink(const QString &link) { } const auto request = QNetworkRequest(QUrl(link)); - _reply = _manager->get(request); + const auto reply = _manager->get(request); + const auto failed = std::make_shared(false); connect( - _reply, + reply, &QNetworkReply::finished, this, [=] { - const auto responseData = _reply->readAll(); + if (*failed) { + reply->deleteLater(); + return; + } + + const auto responseData = reply->readAll(); const auto jsonString = QString::fromUtf8(responseData); @@ -57,25 +318,26 @@ void FilterUtils::importFromLink(const QString &link) { LOG(("FilterUtils: Invalid response.")); Ui::Toast::Show(tr::ayu_FiltersToastFailImport(tr::now)); - _reply->deleteLater(); + reply->deleteLater(); return; } - if (!handleResponse(jsonString.toUtf8())) { - LOG(("FilterUtils: Error handling response.")); - } - _reply->deleteLater(); + handleResponse(jsonString.toUtf8()); + reply->deleteLater(); }); connect( - _reply, + reply, &QNetworkReply::errorOccurred, this, [=](QNetworkReply::NetworkError e) { + if (*failed) { + return; + } + *failed = true; gotFailure(e); - - _reply->deleteLater(); + reply->deleteLater(); }); } @@ -102,17 +364,17 @@ void FilterUtils::publishFilters() { QNetworkRequest request(QUrl("https://dpaste.com/api/v2/")); - _reply = _manager->post(request, multiPart); - multiPart->setParent(_reply); + const auto reply = _manager->post(request, multiPart); + multiPart->setParent(reply); connect( - _reply, + reply, &QNetworkReply::finished, this, [=] { - const auto error = _reply->error(); - const auto location = _reply->header(QNetworkRequest::LocationHeader); + const auto error = reply->error(); + const auto location = reply->header(QNetworkRequest::LocationHeader); if (error == QNetworkReply::NoError && location.isValid()) { auto url = location.toString(); @@ -121,15 +383,15 @@ void FilterUtils::publishFilters() { Ui::Toast::Show(tr::lng_stickers_copied(tr::now)); } else { - LOG(("Failed to publish filters to dpaste, error: %1").arg(_reply->errorString())); + LOG(("Failed to publish filters to dpaste, error: %1").arg(reply->errorString())); Ui::Toast::Show(tr::ayu_FiltersToastFailPublish(tr::now)); } - _reply->deleteLater(); + reply->deleteLater(); }); } -bool FilterUtils::importFromJson(const QByteArray &json) { +void FilterUtils::importFromJson(const QByteArray &json) { auto error = QJsonParseError{0, QJsonParseError::NoError}; const auto document = QJsonDocument::fromJson(json, &error); @@ -137,33 +399,37 @@ bool FilterUtils::importFromJson(const QByteArray &json) { Ui::Toast::Show(tr::ayu_FiltersToastFailImport(tr::now)); LOG(("FilterUtils: Failed to parse JSON, error: %1" ).arg(error.errorString())); - return false; + return; } if (!document.isObject()) { Ui::Toast::Show(tr::ayu_FiltersToastFailImport(tr::now)); LOG(("FilterUtils: not an object received in JSON")); - return false; + return; } const auto changes = prepareChanges(document.object()); - if (changes == ApplyChanges{}) { + if (!HasChanges(changes)) { Ui::Toast::Show(tr::ayu_FiltersToastFailNoChanges(tr::now)); LOG(("FilterUtils: received empty changes")); - return false; + return; } - const auto any = !changes.newFilters.empty() || !changes.removeFiltersById.empty() || !changes.filtersOverrides. - empty() || !changes.newExclusions.empty() || !changes.removeExclusions.empty() || !changes.peersToBeResolved. - empty(); - - if (!any) { - Ui::Toast::Show(tr::ayu_FiltersToastFailNoChanges(tr::now)); - return false; - } - - applyChanges(changes); - - return true; + auto box = Ui::MakeConfirmBox({ + .text = ChangeSummaryText(changes), + .confirmed = [=](Fn close) { + close(); + try { + applyChanges(changes); + Ui::Toast::Show(tr::ayu_FiltersToastSuccess(tr::now)); + } catch (...) { + LOG(("FilterUtils: Failed to apply import changes")); + Ui::Toast::Show(tr::ayu_FiltersToastFailImport(tr::now)); + } + }, + .confirmText = tr::ayu_FiltersMenuImport(), + .title = tr::ayu_FiltersSheetTitle(), + }); + Ui::show(std::move(box)); } struct BackupExclusion @@ -248,7 +514,7 @@ QString FilterUtils::exportFilters() { if (!item.dialogId.has_value()) { continue; } - if (const auto peer = session->data().peer(peerFromChat(abs(item.dialogId.value())))) { + if (const auto peer = LoadedPeerFromDialogId(&session->data(), item.dialogId.value())) { if (!peer->username().isEmpty()) { QString key = QString::number(item.dialogId.value()); peers[key] = peer->username(); @@ -372,10 +638,14 @@ int typeOfMessage(const HistoryItem *item) { } QString extractSingle(const not_null item) { - QString text(item->originalText().text); - if (!item->originalText().entities.empty()) { - for (const auto &entity : item->originalText().entities) { - if (entity.type() == EntityType::Url || entity.type() == EntityType::CustomUrl) { + const auto original = item->originalText(); + QString text(original.text); + if (!original.entities.empty()) { + for (const auto &entity : original.entities) { + if (entity.type() == EntityType::Url) { + text.append("\n"); + text.append(original.text.mid(entity.offset(), entity.length())); + } else if (entity.type() == EntityType::CustomUrl) { text.append("\n"); text.append(entity.data()); } @@ -414,12 +684,12 @@ QString FilterUtils::extractAllText(const not_null item, const Dat return text; } -bool FilterUtils::handleResponse(const QByteArray &response) { +void FilterUtils::handleResponse(const QByteArray &response) { try { - return importFromJson(response); + importFromJson(response); } catch (...) { LOG(("FilterUtils: Failed to apply response")); - return false; + Ui::Toast::Show(tr::ayu_FiltersToastFailImport(tr::now)); } } @@ -442,9 +712,9 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { std::vector filtersOverrides; std::map, RegexFilter> newFilters; std::vector newExclusions; - std::vector removeFiltersById; + std::vector> removeFiltersById; std::vector removeExclusions; - std::map peersToBeResolved; + std::vector peersToBeResolved; if (const auto &filters = root.value("filters").toArray(); !filters.isEmpty()) { @@ -461,11 +731,10 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { } regex.enabled = filter.value("enabled").toBool(); - auto idString = filter.value("id").toString(); - idString.remove('-'); - - auto idBytes = QByteArray::fromHex(idString.toUtf8()); - regex.id = std::vector(idBytes.constData(), idBytes.constData() + idBytes.size()); + regex.id = ParseFilterId(filter.value("id").toString()); + if (regex.id.empty()) { + continue; + } regex.reversed = filter.value("reversed").toBool(); regex.text = filter.value("text").toString().toStdString(); @@ -479,7 +748,7 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { if (it != existingFilters.end()) { const RegexFilter &existing = *it; if (existing != regex) { - filtersOverrides.push_back(existing); + filtersOverrides.push_back(std::move(regex)); } } else { newFilters[regex.id] = std::move(regex); @@ -495,14 +764,10 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { regex.dialogId = exclusion.value("dialogId").toVariant().toLongLong(); - auto filterIdString = exclusion.value("filterId").toString(); - filterIdString.remove('-'); - - auto filterIdBytes = QByteArray::fromHex(filterIdString.toUtf8()); - regex.filterId = std::vector( - filterIdBytes.constData(), - filterIdBytes.constData() + filterIdBytes.size() - ); + regex.filterId = ParseFilterId(exclusion.value("filterId").toString()); + if (regex.filterId.empty()) { + continue; + } auto it = std::ranges::find_if( existingExclusions, @@ -521,10 +786,10 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { if (const auto removeFiltersByIdJson = root.value("removeFiltersById").toArray(); !removeFiltersByIdJson. isEmpty()) { for (const auto &filterRef : removeFiltersByIdJson) { - const auto filter = filterRef.toString(); - - const auto byteArray = filter.toUtf8(); - const auto filterId = std::vector(byteArray.constData(), byteArray.constData() + byteArray.size()); + const auto filterId = ParseFilterId(filterRef.toString()); + if (filterId.empty()) { + continue; + } const auto exists = std::ranges::any_of( existingFilters, @@ -533,7 +798,7 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { return f.id == filterId; }); if (exists) { - removeFiltersById.push_back(filter); + removeFiltersById.push_back(filterId); } } } @@ -541,23 +806,24 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { if (const auto removeExclusionsJson = root.value("removeExclusions").toArray(); !removeExclusionsJson.isEmpty()) { for (const auto &exclusionRef : removeExclusionsJson) { const auto exclusionObj = exclusionRef.toObject(); - const auto filterIdStr = exclusionObj.value("filterId").toString(); const qint64 dialogId = exclusionObj.value("dialogId").toVariant().toLongLong(); - const auto byteArray = filterIdStr.toUtf8(); - const auto filterIdVec = std::vector(byteArray.constData(), byteArray.constData() + byteArray.size()); + const auto filterId = ParseFilterId(exclusionObj.value("filterId").toString()); + if (filterId.empty()) { + continue; + } const bool exists = std::ranges::any_of( existingExclusions, [&](const RegexFilterGlobalExclusion &x) { - return x.filterId == filterIdVec && x.dialogId == dialogId; + return x.filterId == filterId && x.dialogId == dialogId; }); if (exists) { RegexFilterGlobalExclusion regex; regex.dialogId = dialogId; - regex.filterId = filterIdVec; + regex.filterId = filterId; removeExclusions.push_back(regex); } } @@ -567,24 +833,22 @@ ApplyChanges FilterUtils::prepareChanges(const QJsonObject &root) { for (const auto &dialogIdStr : peersJson.keys()) { bool parsed; const auto dialogId = dialogIdStr.toLongLong(&parsed); - if (!parsed) { - continue; - } - // almost everytime fails PeerData *peerMaybe = nullptr; - for (const auto &[index, account] : Core::App().domain().accounts()) { - if (const auto session = account->maybeSession()) { - if (const auto peer = session->data().peer(peerFromChat(abs(dialogId)))) { - peerMaybe = peer; - break; + if (parsed) { + for (const auto &[index, account] : Core::App().domain().accounts()) { + if (const auto session = account->maybeSession()) { + if (const auto peer = LoadedPeerFromDialogId(&session->data(), dialogId)) { + peerMaybe = peer; + break; + } } } } if (!peerMaybe) { - const auto username = peersJson.value(dialogIdStr).toString(); - peersToBeResolved[dialogId] = username; + const auto resolverHint = peersJson.value(dialogIdStr).toString(); + peersToBeResolved.push_back(resolverHint); } } } @@ -611,9 +875,8 @@ void FilterUtils::applyChanges(const ApplyChanges &changes) { if (!changes.removeFiltersById.empty()) { for (const auto &id : changes.removeFiltersById) { - const auto byteArray = id.toUtf8(); - const auto filterId = std::vector(byteArray.constData(), byteArray.constData() + byteArray.size()); - AyuDatabase::deleteExclusionsByFilterId(filterId); + AyuDatabase::deleteExclusionsByFilterId(id); + AyuDatabase::deleteFilter(id); } } @@ -636,7 +899,7 @@ void FilterUtils::applyChanges(const ApplyChanges &changes) { } if (!changes.peersToBeResolved.empty()) { - resolveAllChats(changes.peersToBeResolved); + ResolveFilterBackupPeers(changes.peersToBeResolved); } FiltersCacheController::rebuildCache(); diff --git a/Telegram/SourceFiles/ayu/features/filters/filters_utils.h b/Telegram/SourceFiles/ayu/features/filters/filters_utils.h index 84624d6403..ed650765e5 100644 --- a/Telegram/SourceFiles/ayu/features/filters/filters_utils.h +++ b/Telegram/SourceFiles/ayu/features/filters/filters_utils.h @@ -17,14 +17,14 @@ struct ApplyChanges { std::vector newFilters; - std::vector removeFiltersById; + std::vector> removeFiltersById; std::vector filtersOverrides; std::vector newExclusions; std::vector removeExclusions; - std::map peersToBeResolved; + std::vector peersToBeResolved; bool operator==(const ApplyChanges &) const = default; }; @@ -45,7 +45,7 @@ public: FilterUtils &operator=(FilterUtils &&) = delete; void importFromLink(const QString &link); - bool importFromJson(const QByteArray &json); + void importFromJson(const QByteArray &json); void publishFilters(); static QString exportFilters(); @@ -57,14 +57,11 @@ private: : _manager(std::make_unique()) { } - bool handleResponse(const QByteArray &response); + void handleResponse(const QByteArray &response); void gotFailure(const QNetworkReply::NetworkError &error); ApplyChanges prepareChanges(const QJsonObject &response); void applyChanges(const ApplyChanges &changes); - QTimer *_timer = nullptr; - std::unique_ptr _manager = nullptr; - QNetworkReply *_reply = nullptr; }; diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp index 298fe28d98..5258d44367 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_forward.cpp @@ -156,7 +156,7 @@ void sendMedia( Api::MessageToSend &&message, bool sendImagesAsPhotos) { if (const auto document = primaryMedia->document(); document && document->sticker()) { - AyuSync::sendStickerSync(session, message, document); + AyuSync::sendStickerSync(session, std::move(message), document); return; } @@ -245,12 +245,17 @@ void intelligentForward( const Api::SendAction &action, const Data::ResolvedForwardDraft &draft) { const auto history = action.history; - crl::on_main([&] + const auto topicRootId = action.replyTo.topicRootId; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + crl::on_main([=] { - history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {}); + history->setForwardDraft(topicRootId, monoforumPeerId, {}); }); const auto items = draft.items; + if (items.empty()) { + return; + } const auto peer = history->peer; auto chunks = std::vector(); @@ -312,9 +317,11 @@ void forwardMessages( const auto history = action.history; const auto peer = history->peer; - crl::on_main([&] + const auto topicRootId = action.replyTo.topicRootId; + const auto monoforumPeerId = action.replyTo.monoforumPeerId; + crl::on_main([=] { - history->setForwardDraft(action.replyTo.topicRootId, action.replyTo.monoforumPeerId, {}); + history->setForwardDraft(topicRootId, monoforumPeerId, {}); }); std::shared_ptr state; @@ -378,10 +385,10 @@ void forwardMessages( } if (!mediaDownloadable(item->media())) { - AyuSync::sendMessageSync(session, message); + AyuSync::sendMessageSync(session, std::move(message)); } else if (const auto media = item->media()) { if (media->poll()) { - AyuSync::sendMessageSync(session, message); + AyuSync::sendMessageSync(session, std::move(message)); continue; } diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp index 4b86440641..3a8e8d1429 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.cpp @@ -226,8 +226,9 @@ void loadPhotoSync(not_null session, const std::pair session, Api::MessageToSend &message) { - crl::on_main([=, &message] +void sendMessageSync(not_null session, Api::MessageToSend &&message) { + const auto action = message.action; + crl::on_main([=, message = std::move(message)]() mutable { // we cannot send events to objects // owned by a different thread @@ -237,7 +238,7 @@ void sendMessageSync(not_null session, Api::MessageToSend &messa }); - waitForMsgSync(session, message.action); + waitForMsgSync(session, action); } void waitForMsgSync(not_null session, const Api::SendAction &action) { @@ -286,10 +287,10 @@ void sendDocumentSync(not_null session, } void sendStickerSync(not_null session, - Api::MessageToSend &message, + Api::MessageToSend &&message, not_null document) { - auto &action = message.action; - crl::on_main([&] + const auto action = message.action; + crl::on_main([=, message = std::move(message)]() mutable { Api::SendExistingDocument(std::move(message), document, std::nullopt); }); diff --git a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h index f48db508eb..d0b564cb02 100644 --- a/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h +++ b/Telegram/SourceFiles/ayu/features/forward/ayu_sync.h @@ -23,7 +23,7 @@ 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 sendMessageSync(not_null session, Api::MessageToSend &&message); void sendDocumentSync(not_null session, Ui::PreparedGroup &group, @@ -32,7 +32,7 @@ void sendDocumentSync(not_null session, const Api::SendAction &action); void sendStickerSync(not_null session, - Api::MessageToSend &message, + 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); diff --git a/Telegram/SourceFiles/ayu/features/message_shot/message_shot.cpp b/Telegram/SourceFiles/ayu/features/message_shot/message_shot.cpp index 787da4c48d..77b5fd9087 100644 --- a/Telegram/SourceFiles/ayu/features/message_shot/message_shot.cpp +++ b/Telegram/SourceFiles/ayu/features/message_shot/message_shot.cpp @@ -464,12 +464,16 @@ void ShowMessageShotBox( not_null controller, const MessageIdsList &ids, Fn clearSelected) { - const auto messages = ranges::views::all(ids) - | ranges::views::transform([=](const auto item) - { - return resolveMessage(item); - }) - | ranges::to_vector; + auto messages = std::vector>(); + messages.reserve(ids.size()); + for (const auto item : ids) { + if (const auto message = resolveMessage(item)) { + messages.push_back(message); + } + } + if (messages.empty()) { + return; + } const AyuFeatures::MessageShot::ShotConfig config = { controller, @@ -502,7 +506,7 @@ void WrapperImpl( } ShowMessageShotBox( - [=](const auto item) { return gsl::not_null(session->data().message(item)); }, + [=](const auto item) { return session->data().message(item); }, controller, items, std::move(clearSelected)); diff --git a/Telegram/SourceFiles/ayu/ui/boxes/import_filters_box.cpp b/Telegram/SourceFiles/ayu/ui/boxes/import_filters_box.cpp index 7283624885..027820e8a8 100644 --- a/Telegram/SourceFiles/ayu/ui/boxes/import_filters_box.cpp +++ b/Telegram/SourceFiles/ayu/ui/boxes/import_filters_box.cpp @@ -44,8 +44,10 @@ void FillImportFiltersBox(not_null box, bool import) { Ui::InputField *importURLField = nullptr; Ui::SlideWrap *importURLWrap = nullptr; + const auto clipboardText = QGuiApplication::clipboard()->text().trimmed(); + const auto clipboardHasUrl = import && clipboardText.startsWith("http"); - const auto intoURL = std::make_shared>(false); + const auto intoURL = std::make_shared>(clipboardHasUrl); const auto addOption = [&](bool value, const QString &text) { inner->add( @@ -58,9 +60,6 @@ void FillImportFiltersBox(not_null box, bool import) { st::settingsSendTypePadding); if (import && value) { - const auto clipboardText = QGuiApplication::clipboard()->text().trimmed(); - const auto prefill = clipboardText.startsWith("http") ? clipboardText : QString(); - importURLWrap = inner->add( object_ptr>( inner, @@ -73,10 +72,12 @@ void FillImportFiltersBox(not_null box, bool import) { container, st::defaultInputField, rpl::single(QString("URL")), - prefill + clipboardHasUrl ? clipboardText : QString() ) ); - importURLWrap->hide(anim::type::instant); + if (!clipboardHasUrl) { + importURLWrap->hide(anim::type::instant); + } } }; addOption(false, import ? tr::ayu_FiltersImportClipboard(tr::now) : tr::ayu_FiltersExportClipboard(tr::now)); diff --git a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp index b712364359..8fceff22c4 100644 --- a/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp +++ b/Telegram/SourceFiles/ayu/ui/context_menu/context_menu.cpp @@ -234,7 +234,7 @@ void AddAyuGramActions(PeerData *peerData, return; } - const auto topic = peerData->isForum() ? thread->asTopic() : nullptr; + const auto topic = peerData->isForum() && thread ? thread->asTopic() : nullptr; const auto topicId = topic ? topic->rootId().bare : 0; addCallback(Window::PeerMenuCallback::Args{ @@ -274,11 +274,12 @@ void AddAyuGramActions(PeerData *peerData, tr::ayu_ViewDeletedMenuText(tr::now), [=] { - sessionController->session().tryResolveWindow() - ->showSection(std::make_shared( + if (const auto window = sessionController->session().tryResolveWindow()) { + window->showSection(std::make_shared( peerData, nullptr, topicId)); + } }, &st::menuIconArchive); if (showFilters || filteredToggleShown.value_or(false)) addAction({ .isSeparator = true }); @@ -478,9 +479,10 @@ void AddHistoryAction(not_null menu, HistoryItem *item) { tr::ayu_EditsHistoryMenuText(tr::now), [=] { - item->history()->session().tryResolveWindow() - ->showSection( + if (const auto window = item->history()->session().tryResolveWindow()) { + window->showSection( std::make_shared(item->history()->peer, item, 0)); + } }, &st::ayuEditsHistoryIcon); } diff --git a/Telegram/SourceFiles/ayu/ui/settings/filters/per_dialog_filter.cpp b/Telegram/SourceFiles/ayu/ui/settings/filters/per_dialog_filter.cpp index de24220fc4..422b590312 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/filters/per_dialog_filter.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/filters/per_dialog_filter.cpp @@ -74,9 +74,7 @@ void PerDialogFiltersListController::prepareShadowBan() { const auto &shadowBanned = settings.shadowBanIds(); for (const auto id : shadowBanned) { - auto row = std::make_unique(id); - - delegate()->peerListAppendRow(reinterpret_cast&&>(row)); + delegate()->peerListAppendRow(std::make_unique(id)); } } @@ -118,7 +116,7 @@ void PerDialogFiltersListController::prepare() { row->setCustomStatus(status, false); - delegate()->peerListAppendRow(reinterpret_cast&&>(row)); + delegate()->peerListAppendRow(std::move(row)); } // sortByName(); diff --git a/Telegram/SourceFiles/ayu/ui/settings/settings_filters.cpp b/Telegram/SourceFiles/ayu/ui/settings/settings_filters.cpp index 4bc6e02bbf..f01b2eec03 100644 --- a/Telegram/SourceFiles/ayu/ui/settings/settings_filters.cpp +++ b/Telegram/SourceFiles/ayu/ui/settings/settings_filters.cpp @@ -22,7 +22,9 @@ #include "inline_bots/bot_attach_web_view.h" #include "settings/settings_builder.h" #include "settings/settings_common.h" +#include "styles/style_ayu_icons.h" #include "styles/style_boxes.h" +#include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "ui/vertical_list.h" @@ -233,9 +235,9 @@ void AyuFilters::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) { &st::menuIconUnarchive); } addAction({ .isSeparator = true }); - addAction( - tr::ayu_FiltersMenuClear(tr::now), - [=] { + addAction({ + .text = tr::ayu_FiltersMenuClear(tr::now), + .handler = [=] { auto callback = [=](Fn &&close) { AyuDatabase::deleteAllFilters(); AyuDatabase::deleteAllExclusions(); @@ -246,11 +248,14 @@ void AyuFilters::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) { auto box = Ui::MakeConfirmBox({ .text = tr::ayu_FiltersClearPopupText(), .confirmed = callback, - .confirmText = tr::ayu_FiltersClearPopupActionText() + .confirmText = tr::ayu_FiltersClearPopupActionText(), + .confirmStyle = &st::attentionBoxButton, }); Ui::show(std::move(box)); }, - &st::menuIconClear); + .icon = &st::menuIconClearAttention, + .isAttention = true, + }); } AyuFilters::AyuFilters( diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp index 949fd08a7f..d95ee87391 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.cpp @@ -53,6 +53,7 @@ #include "ui/toast/toast.h" #include "window/window_controller.h" +#include #include #include #include @@ -1223,49 +1224,6 @@ void applyLocalPremiumEmoji(TextWithEntities &text) { } } -void resolveAllChats(const std::map &peers) { - auto session = currentSession(); - - crl::async([=, &session] - { - while (!peers.empty()) { - for (const auto &[id, username] : peers) { - auto latch = std::make_shared(1); - - auto onSuccess = [=, &latch](const MTPChatInvite &invite) - { - invite.match([=](const MTPDchatInvite &data) - { - }, - [=](const MTPDchatInviteAlready &data) - { - if (const auto chat = session->data().processChat(data.vchat())) { - if (const auto channel = chat->asChannel()) { - channel->clearInvitePeek(); - } - } - }, - [=](const MTPDchatInvitePeek &data) - { - }); - - latch->countDown(); - }; - auto onFail = [=, &latch](const MTP::Error &error) - { - if (MTP::IsFloodError(error.type())) { - std::this_thread::sleep_for(std::chrono::seconds(20)); - } - latch->countDown(); - }; - - session->api().checkChatInvite(username, onSuccess, onFail); - latch->await(std::chrono::seconds(20)); - } - } - }); -} - not_null currentSession() { return &Core::App().domain().active().session(); } diff --git a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h index 7fbfee443a..021679a640 100644 --- a/Telegram/SourceFiles/ayu/utils/telegram_helpers.h +++ b/Telegram/SourceFiles/ayu/utils/telegram_helpers.h @@ -106,7 +106,6 @@ bool mediaDownloadable(const Data::Media* media); TextWithEntities reverseLocalPremiumEmoji(const TextWithEntities &text, not_null history, bool isForQuote = false); void applyLocalPremiumEmoji(TextWithEntities &text); -void resolveAllChats(const std::map &peers); not_null currentSession(); PeerData* getPeerFromDialogId(ID id); diff --git a/Telegram/SourceFiles/ayu/utils/windows_utils.cpp b/Telegram/SourceFiles/ayu/utils/windows_utils.cpp index 21c5e69c16..dd95ad31ab 100644 --- a/Telegram/SourceFiles/ayu/utils/windows_utils.cpp +++ b/Telegram/SourceFiles/ayu/utils/windows_utils.cpp @@ -31,13 +31,11 @@ void processIcon(QString shortcut, QString iconPath) { if (SUCCEEDED(hr)) { hr = pShellLink->QueryInterface(IID_IPersistFile, (void**) &pPersistFile); if (SUCCEEDED(hr)) { - WCHAR wszShortcutPath[MAX_PATH]; - shortcut.toWCharArray(wszShortcutPath); - wszShortcutPath[shortcut.length()] = '\0'; + const auto shortcutPath = shortcut.toStdWString(); - if (SUCCEEDED(pPersistFile->Load(wszShortcutPath, STGM_READWRITE))) { + if (SUCCEEDED(pPersistFile->Load(shortcutPath.c_str(), STGM_READWRITE))) { pShellLink->SetIconLocation(iconPath.toStdWString().c_str(), 0); - pPersistFile->Save(wszShortcutPath, TRUE); + pPersistFile->Save(shortcutPath.c_str(), TRUE); } pPersistFile->Release(); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 1e3beb8551..1fcaa29ae4 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -92,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include // AyuGram includes +#include "ayu/features/filters/filters_cache_controller.h" #include "ayu/utils/telegram_helpers.h" @@ -500,6 +501,23 @@ ListWidget::ListWidget( } }, lifetime()); + rpl::merge( + _session->changes().peerUpdates( + Data::PeerUpdate::Flag::IsBlocked + ) | rpl::to_empty, + FiltersCacheController::updates() + ) | rpl::on_next([=] { + crl::on_main(this, [=] { + if (_viewsCapacity.empty()) { + for (const auto &view : _items) { + view->setPendingResize(); + } + const auto old = _slice; + refreshRows(old); + } + }); + }, lifetime()); + _session->downloaderTaskFinished( ) | rpl::on_next([=] { update(); diff --git a/Telegram/SourceFiles/window/window_main_menu.cpp b/Telegram/SourceFiles/window/window_main_menu.cpp index 10215b7dfd..61524ae690 100644 --- a/Telegram/SourceFiles/window/window_main_menu.cpp +++ b/Telegram/SourceFiles/window/window_main_menu.cpp @@ -791,11 +791,10 @@ void MainMenu::setupMenu() { MarkAsReadChatList(chats); // slight delay for forums to send packets - dispatchToMainThread([=]() mutable - { + dispatchToMainThread(crl::guard(controller, [=] { auto &ghost = AyuSettings::ghost(&controller->session()); ghost.setSendReadMessages(prev); - }, 200); + }), 200); close(); };