chore: rework filters & fix crashes

This commit is contained in:
AlexeyZavar
2026-04-26 20:53:35 +03:00
parent d376e202c8
commit 10092ba792
22 changed files with 618 additions and 282 deletions
+2 -2
View File
@@ -1051,9 +1051,9 @@ void AyuSettings::setSingleCornerRadius(bool val) {
}
void to_json(nlohmann::json &j, const AyuSettings &s) {
std::map<std::string, GhostModeAccountSettings> 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{
+16 -1
View File
@@ -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<char> 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()));
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
#include <string>
#define ID long long
using ID = long long;
class AyuMessageBase
{
@@ -15,12 +15,16 @@
#include "history/history_item.h"
#include "rpl/event_stream.h"
#include <memory>
#include <mutex>
#include <unordered_set>
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<std::vector<HashablePattern>> sharedPatterns;
std::optional<std::unordered_map<long long, std::vector<ReversiblePattern>>> patternsByDialogId;
std::shared_ptr<const Cache> cache;
std::optional<std::unordered_map<long long, std::unordered_set<HashablePattern, PatternHasher>>> exclusionsByDialogId;
std::unordered_map<long long, std::unordered_map<std::optional<int>, std::optional<bool>>> filteredMessages;
void rebuildCache() {
std::lock_guard lock(mutex);
std::unordered_map<long long, std::unordered_map<int64, bool>> filteredMessages;
std::unordered_set<BareId> dialogsWithHiddenBlockedMessages; // purely for show / hide filtered messages
std::shared_ptr<const Cache> buildCache() {
const auto filters = AyuDatabase::getAllRegexFilters();
const auto exclusions = AyuDatabase::getAllFiltersExclusions();
std::vector<HashablePattern> shared;
std::unordered_map<long long, std::vector<ReversiblePattern>> byDialogId;
std::unordered_map<ID, std::vector<ReversiblePattern>> 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<RegexPattern>(pattern), filter.reversed});
byDialogId[filter.dialogId.value()].push_back({
std::shared_ptr<icu::RegexPattern>(pattern),
filter.reversed,
});
} else {
shared.push_back({filter.id, {std::shared_ptr<RegexPattern>(pattern), filter.reversed}});
shared.push_back({
filter.id,
{
std::shared_ptr<icu::RegexPattern>(pattern),
filter.reversed,
},
});
}
}
auto exclByDialogId = buildExclusions(exclusions, shared);
auto result = std::make_shared<Cache>();
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<long long, std::unordered_set<HashablePattern, PatternHasher>> buildExclusions(
@@ -95,8 +117,32 @@ std::unordered_map<long long, std::unordered_set<HashablePattern, PatternHasher>
return exclusionsByDialogId;
}
std::shared_ptr<const Cache> 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<bool> isFiltered(not_null<HistoryItem*> 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<bool> isFiltered(not_null<HistoryItem*> item) {
}
bool hasFilteredMessages(not_null<PeerData*> 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<HistoryItem*> item, const Data::Group *group, bool res) {
std::lock_guard lock(mutex);
void putHiddenBlockedMessage(not_null<HistoryItem*> item) {
std::lock_guard lock(filteredMessagesMutex);
dialogsWithHiddenBlockedMessages.insert(item->history()->peer->id.value);
}
void putFiltered(
not_null<HistoryItem*> item,
const Data::Group *group,
bool res,
const std::shared_ptr<const Cache> &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<HistoryItem*> item) {
}
void invalidate(not_null<HistoryItem*> 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<HistoryItem*> item) {
}
}
std::optional<std::vector<ReversiblePattern>> 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<const std::unordered_set<HashablePattern, PatternHasher>> 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<HashablePattern> &getSharedPatterns() {
if (!sharedPatterns.has_value()) {
rebuildCache();
}
return sharedPatterns.value();
}
}
@@ -10,31 +10,47 @@
#include "ayu/features/filters/filters_controller.h"
#include "rpl/producer.h"
#include <memory>
#include <unordered_map>
#include <unordered_set>
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<HashablePattern> sharedPatterns;
std::unordered_map<ID, std::vector<ReversiblePattern>> patternsByDialogId;
std::unordered_map<ID, std::unordered_set<HashablePattern, PatternHasher>> exclusionsByDialogId;
};
[[nodiscard]] std::shared_ptr<const Cache> snapshot();
std::unordered_map<long long, std::unordered_set<HashablePattern, PatternHasher>> buildExclusions(
const std::vector<RegexFilterGlobalExclusion> &exclusions,
const std::vector<HashablePattern> &shared);
std::optional<bool> isFiltered(not_null<HistoryItem*> item);
bool hasFilteredMessages(not_null<PeerData*> peer);
void putFiltered(not_null<HistoryItem*> item, const Data::Group *group, bool res);
void putHiddenBlockedMessage(not_null<HistoryItem*> item);
void putFiltered(
not_null<HistoryItem*> item,
const Data::Group *group,
bool res,
const std::shared_ptr<const Cache> &matchedCache);
void invalidate(not_null<HistoryItem*> item);
std::optional<std::vector<ReversiblePattern>> getPatternsByDialogId(uint64 dialogId);
std::optional<const std::unordered_set<HashablePattern, PatternHasher>> getExclusionsByDialogId(long long dialogId);
const std::vector<HashablePattern> &getSharedPatterns();
}
@@ -20,6 +20,7 @@
#include "history/history_item_components.h"
#include "unicode/regex.h"
#include <memory>
#include <unordered_set>
namespace FiltersController {
@@ -35,23 +36,27 @@ bool filterBlocked(const not_null<HistoryItem*> item) {
return false;
}
std::optional<bool> isFiltered(const QString &str, uint64 dialogId) {
std::optional<bool> isFiltered(
const QString &str,
long long dialogId,
const std::shared_ptr<const FiltersCacheController::Cache> &cache) {
if (str.isEmpty()) {
return std::nullopt;
}
const auto icuStr = UnicodeString(reinterpret_cast<const UChar*>(str.constData()), str.length());
const auto icuStr = icu::UnicodeString(reinterpret_cast<const UChar*>(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<icu::RegexMatcher>(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<bool> 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<PeerData*> peer) {
bool isBlocked(const not_null<HistoryItem*> 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<HistoryMessageForwarded>()) {
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<PeerData*> peer) {
@@ -140,7 +158,10 @@ bool filtered(const not_null<HistoryItem*> 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<HistoryItem*> 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;
@@ -8,15 +8,14 @@
#include "unicode/regex.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
using namespace icu_78;
namespace FiltersController {
bool isEnabled(PeerData *peer);
bool isEnabled(not_null<PeerData*> peer);
bool isBlocked(not_null<HistoryItem*> item);
bool isBlocked(not_null<PeerData*> peer);
bool filtered(not_null<HistoryItem*> historyItem);
@@ -27,7 +26,7 @@ void invalidate(not_null<HistoryItem*> item);
struct ReversiblePattern
{
std::shared_ptr<RegexPattern> pattern;
std::shared_ptr<icu::RegexPattern> pattern;
bool reversed;
};
@@ -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 <atomic>
#include <chrono>
#include <memory>
#include <optional>
#include <QByteArray>
#include <QClipboard>
#include <QGuiApplication>
#include <QJsonArray>
#include <qjsondocument.h>
#include <QString>
#include <thread>
#include <vector>
#include <QtNetwork/QHttpPart>
#include <QtNetwork/QNetworkAccessManager>
@@ -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<PeerResolveHint> 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<QString, QString>();
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<char> 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::Session*> 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<QString> &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<TimedCountDownLatch>(1);
auto floodWait = std::make_shared<std::atomic_bool>(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<bool>(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<void()> 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<HistoryItem*> 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<HistoryItem*> 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<RegexFilter> filtersOverrides;
std::map<std::vector<char>, RegexFilter> newFilters;
std::vector<RegexFilterGlobalExclusion> newExclusions;
std::vector<QString> removeFiltersById;
std::vector<std::vector<char>> removeFiltersById;
std::vector<RegexFilterGlobalExclusion> removeExclusions;
std::map<long long, QString> peersToBeResolved;
std::vector<QString> 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<char>(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<char>(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();
@@ -17,14 +17,14 @@
struct ApplyChanges
{
std::vector<RegexFilter> newFilters;
std::vector<QString> removeFiltersById;
std::vector<std::vector<char>> removeFiltersById;
std::vector<RegexFilter> filtersOverrides;
std::vector<RegexFilterGlobalExclusion> newExclusions;
std::vector<RegexFilterGlobalExclusion> removeExclusions;
std::map<long long, QString> peersToBeResolved;
std::vector<QString> 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<QNetworkAccessManager>()) {
}
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<QNetworkAccessManager> _manager = nullptr;
QNetworkReply *_reply = nullptr;
};
@@ -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<ForwardChunk>();
@@ -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<ForwardState> 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;
}
@@ -226,8 +226,9 @@ void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<Ph
}
}
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message) {
crl::on_main([=, &message]
void sendMessageSync(not_null<Main::Session*> 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<Main::Session*> session, Api::MessageToSend &messa
});
waitForMsgSync(session, message.action);
waitForMsgSync(session, action);
}
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action) {
@@ -286,10 +287,10 @@ void sendDocumentSync(not_null<Main::Session*> session,
}
void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message,
Api::MessageToSend &&message,
not_null<DocumentData*> 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);
});
@@ -23,7 +23,7 @@ QString pathForSave(not_null<Main::Session*> session);
QString filePath(not_null<Main::Session*> session, const Data::Media *media);
void loadDocuments(not_null<Main::Session*> session, const std::vector<not_null<HistoryItem*>> &items);
bool isMediaDownloadable(Data::Media *media);
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &message);
void sendMessageSync(not_null<Main::Session*> session, Api::MessageToSend &&message);
void sendDocumentSync(not_null<Main::Session*> session,
Ui::PreparedGroup &group,
@@ -32,7 +32,7 @@ void sendDocumentSync(not_null<Main::Session*> session,
const Api::SendAction &action);
void sendStickerSync(not_null<Main::Session*> session,
Api::MessageToSend &message,
Api::MessageToSend &&message,
not_null<DocumentData*> document);
void waitForMsgSync(not_null<Main::Session*> session, const Api::SendAction &action);
void loadPhotoSync(not_null<Main::Session*> session, const std::pair<not_null<PhotoData*>, FullMsgId> &photos);
@@ -464,12 +464,16 @@ void ShowMessageShotBox(
not_null<Window::SessionController*> controller,
const MessageIdsList &ids,
Fn<void()> clearSelected) {
const auto messages = ranges::views::all(ids)
| ranges::views::transform([=](const auto item)
{
return resolveMessage(item);
})
| ranges::to_vector;
auto messages = std::vector<not_null<HistoryItem*>>();
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));
@@ -44,8 +44,10 @@ void FillImportFiltersBox(not_null<Ui::GenericBox*> box, bool import) {
Ui::InputField *importURLField = nullptr;
Ui::SlideWrap<Ui::VerticalLayout> *importURLWrap = nullptr;
const auto clipboardText = QGuiApplication::clipboard()->text().trimmed();
const auto clipboardHasUrl = import && clipboardText.startsWith("http");
const auto intoURL = std::make_shared<RadioenumGroup<bool>>(false);
const auto intoURL = std::make_shared<RadioenumGroup<bool>>(clipboardHasUrl);
const auto addOption = [&](bool value, const QString &text)
{
inner->add(
@@ -58,9 +60,6 @@ void FillImportFiltersBox(not_null<Ui::GenericBox*> 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<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
@@ -73,10 +72,12 @@ void FillImportFiltersBox(not_null<Ui::GenericBox*> 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));
@@ -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<MessageHistory::SectionMemento>(
if (const auto window = sessionController->session().tryResolveWindow()) {
window->showSection(std::make_shared<MessageHistory::SectionMemento>(
peerData,
nullptr,
topicId));
}
},
&st::menuIconArchive);
if (showFilters || filteredToggleShown.value_or(false)) addAction({ .isSeparator = true });
@@ -478,9 +479,10 @@ void AddHistoryAction(not_null<Ui::PopupMenu*> 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<MessageHistory::SectionMemento>(item->history()->peer, item, 0));
}
},
&st::ayuEditsHistoryIcon);
}
@@ -74,9 +74,7 @@ void PerDialogFiltersListController::prepareShadowBan() {
const auto &shadowBanned = settings.shadowBanIds();
for (const auto id : shadowBanned) {
auto row = std::make_unique<PerDialogFiltersListRow>(id);
delegate()->peerListAppendRow(reinterpret_cast<std::unique_ptr<PeerListRow>&&>(row));
delegate()->peerListAppendRow(std::make_unique<PerDialogFiltersListRow>(id));
}
}
@@ -118,7 +116,7 @@ void PerDialogFiltersListController::prepare() {
row->setCustomStatus(status, false);
delegate()->peerListAppendRow(reinterpret_cast<std::unique_ptr<PeerListRow>&&>(row));
delegate()->peerListAppendRow(std::move(row));
}
// sortByName();
@@ -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<void()> &&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(
@@ -53,6 +53,7 @@
#include "ui/toast/toast.h"
#include "window/window_controller.h"
#include <atomic>
#include <functional>
#include <latch>
#include <QTimer>
@@ -1223,49 +1224,6 @@ void applyLocalPremiumEmoji(TextWithEntities &text) {
}
}
void resolveAllChats(const std::map<long long, QString> &peers) {
auto session = currentSession();
crl::async([=, &session]
{
while (!peers.empty()) {
for (const auto &[id, username] : peers) {
auto latch = std::make_shared<TimedCountDownLatch>(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<Main::Session*> currentSession() {
return &Core::App().domain().active().session();
}
@@ -106,7 +106,6 @@ bool mediaDownloadable(const Data::Media* media);
TextWithEntities reverseLocalPremiumEmoji(const TextWithEntities &text, not_null<History *> history, bool isForQuote = false);
void applyLocalPremiumEmoji(TextWithEntities &text);
void resolveAllChats(const std::map<long long, QString> &peers);
not_null<Main::Session *> currentSession();
PeerData* getPeerFromDialogId(ID id);
@@ -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();
@@ -92,6 +92,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QMimeData>
// 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();
@@ -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();
};