mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-08-06 11:52:58 +00:00
Merge tag 'v7.0.4' into dev
This commit is contained in:
@@ -8,24 +8,209 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "api/api_polls.h"
|
||||
|
||||
#include "api/api_common.h"
|
||||
#include "api/api_statistics_data_deserialize.h"
|
||||
#include "api/api_text_entities.h"
|
||||
#include "api/api_updates.h"
|
||||
#include "apiwrap.h"
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
#include "base/call_delayed.h"
|
||||
#include "base/qt/qt_key_modifiers.h"
|
||||
#include "base/random.h"
|
||||
#include "data/business/data_shortcut_messages.h"
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_statistics_chart.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_helpers.h" // ShouldSendSilent
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "styles/style_polls.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "window/window_session_controller.h"
|
||||
|
||||
// AyuGram includes
|
||||
#include "ayu/ayu_settings.h"
|
||||
#include "ayu/utils/telegram_helpers.h"
|
||||
namespace {
|
||||
|
||||
constexpr auto kVoteRestrictionToastDuration = 5 * crl::time(1000);
|
||||
|
||||
const auto kSubscribersOnlyVoteErrorPatterns = std::array{
|
||||
u"POLL_SUBSCRIBERS_ONLY"_q,
|
||||
u"POLL_MEMBER_RESTRICTED"_q,
|
||||
u"VOTE_SUBSCRIBERS_ONLY"_q,
|
||||
u"SUBSCRIBERS_ONLY"_q,
|
||||
u"SUBSCRIBER_REQUIRED"_q,
|
||||
u"SUBSCRIBER_ONLY"_q,
|
||||
};
|
||||
|
||||
const auto kSubscribersJoinedTooRecentlyVoteErrorPatterns = std::array{
|
||||
u"POLL_SUBSCRIBERS_TOO_RECENT"_q,
|
||||
u"VOTE_SUBSCRIBERS_TOO_RECENT"_q,
|
||||
u"SUBSCRIBERS_TOO_RECENT"_q,
|
||||
u"SUBSCRIBER_TOO_RECENT"_q,
|
||||
u"JOINED_TOO_RECENTLY"_q,
|
||||
u"24_HOURS"_q,
|
||||
};
|
||||
|
||||
const auto kCountriesVoteErrorPatterns = std::array{
|
||||
u"POLL_COUNTRIES_ISO2"_q,
|
||||
u"VOTE_COUNTRIES_ISO2"_q,
|
||||
u"COUNTRIES_ISO2"_q,
|
||||
u"COUNTRY_RESTRICTED"_q,
|
||||
u"COUNTRY_ISO2"_q,
|
||||
};
|
||||
|
||||
template <size_t Size>
|
||||
[[nodiscard]] bool MatchesErrorPattern(
|
||||
const QString &type,
|
||||
const std::array<QString, Size> &patterns) {
|
||||
for (const auto &pattern : patterns) {
|
||||
if (!pattern.isEmpty()
|
||||
&& type.contains(pattern, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] PollData::VoteRestriction ParseVoteRestrictionError(
|
||||
const QString &type) {
|
||||
if (MatchesErrorPattern(
|
||||
type,
|
||||
kSubscribersJoinedTooRecentlyVoteErrorPatterns)) {
|
||||
return PollData::VoteRestriction::SubscribersJoinedTooRecently;
|
||||
} else if (MatchesErrorPattern(
|
||||
type,
|
||||
kSubscribersOnlyVoteErrorPatterns)) {
|
||||
return PollData::VoteRestriction::SubscribersOnly;
|
||||
} else if (MatchesErrorPattern(
|
||||
type,
|
||||
kCountriesVoteErrorPatterns)) {
|
||||
return PollData::VoteRestriction::Countries;
|
||||
}
|
||||
return PollData::VoteRestriction::None;
|
||||
}
|
||||
|
||||
void ShowVoteRestrictionToast(
|
||||
not_null<PeerData*> peer,
|
||||
not_null<const PollData*> poll,
|
||||
PollData::VoteRestriction restriction) {
|
||||
if (restriction == PollData::VoteRestriction::None) {
|
||||
return;
|
||||
}
|
||||
auto text = PollVoteRestrictionText(restriction, peer, poll);
|
||||
if (text.text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (const auto window = peer->session().tryResolveWindow(peer)) {
|
||||
window->showToast({
|
||||
.text = std::move(text),
|
||||
.iconLottie = u"ban"_q,
|
||||
.iconLottieSize = st::pollToastIconSize,
|
||||
.duration = kVoteRestrictionToastDuration,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
[[nodiscard]] Data::StatisticalGraph GenerateMockupPollStats(
|
||||
const PollData &poll) {
|
||||
auto chart = Data::StatisticalChart();
|
||||
const auto colorKeys = std::array<QString, 10>{
|
||||
u"BLUE"_q,
|
||||
u"GREEN"_q,
|
||||
u"RED"_q,
|
||||
u"GOLDEN"_q,
|
||||
u"LIGHTBLUE"_q,
|
||||
u"LIGHTGREEN"_q,
|
||||
u"ORANGE"_q,
|
||||
u"INDIGO"_q,
|
||||
u"PURPLE"_q,
|
||||
u"CYAN"_q,
|
||||
};
|
||||
|
||||
constexpr auto kPoints = 14;
|
||||
constexpr auto kOneDay = float64(24 * 60 * 60 * 1000);
|
||||
constexpr auto kStart = float64(1704067200000);
|
||||
chart.x.reserve(kPoints);
|
||||
for (auto i = 0; i != kPoints; ++i) {
|
||||
chart.x.push_back(kStart + i * kOneDay);
|
||||
}
|
||||
chart.timeStep = kOneDay;
|
||||
|
||||
auto lineId = 0;
|
||||
chart.lines.reserve(poll.answers.size());
|
||||
for (const auto &answer : poll.answers) {
|
||||
auto line = Data::StatisticalChart::Line();
|
||||
line.id = ++lineId;
|
||||
line.idString = u"answer_%1"_q.arg(line.id);
|
||||
line.name = answer.text.text.trimmed();
|
||||
if (line.name.isEmpty()) {
|
||||
line.name = QString("#%1").arg(line.id);
|
||||
}
|
||||
line.colorKey = colorKeys[(line.id - 1) % int(colorKeys.size())];
|
||||
line.y.reserve(kPoints);
|
||||
|
||||
auto seed = int64(13 * line.id + 17);
|
||||
for (const auto byte : answer.option) {
|
||||
seed += uchar(byte);
|
||||
}
|
||||
const auto base = std::max(int64(answer.votes), int64(1));
|
||||
for (auto i = 0; i != kPoints; ++i) {
|
||||
const auto wave = int64(
|
||||
((i + line.id) % 5) * ((i + 2 * line.id) % 4));
|
||||
const auto trend = int64((i * (line.id + 1)) / 3);
|
||||
const auto noise = int64((seed + i * 7 + line.id * 11) % 6);
|
||||
const auto value = std::max(
|
||||
base + wave + trend + noise - 2,
|
||||
int64(1));
|
||||
line.y.push_back(value);
|
||||
line.maxValue = std::max(line.maxValue, value);
|
||||
line.minValue = std::min(line.minValue, value);
|
||||
}
|
||||
chart.lines.push_back(std::move(line));
|
||||
}
|
||||
if (chart.lines.empty()) {
|
||||
auto line = Data::StatisticalChart::Line();
|
||||
line.id = 1;
|
||||
line.idString = u"votes"_q;
|
||||
line.name = tr::lng_notification_reactions_poll_votes(tr::now);
|
||||
line.colorKey = u"BLUE"_q;
|
||||
line.y.reserve(kPoints);
|
||||
|
||||
const auto base = std::max(int64(poll.totalVoters), int64(1));
|
||||
for (auto i = 0; i != kPoints; ++i) {
|
||||
const auto value = std::max(
|
||||
base + i * 2 + ((i * 5) % 7),
|
||||
int64(1));
|
||||
line.y.push_back(value);
|
||||
line.maxValue = std::max(line.maxValue, value);
|
||||
line.minValue = std::min(line.minValue, value);
|
||||
}
|
||||
chart.lines.push_back(std::move(line));
|
||||
}
|
||||
|
||||
chart.defaultZoomXIndex = {
|
||||
.min = std::max(0, kPoints - 8),
|
||||
.max = kPoints - 1,
|
||||
};
|
||||
chart.measure();
|
||||
if (chart.maxValue == chart.minValue) {
|
||||
if (chart.minValue) {
|
||||
chart.minValue = 0;
|
||||
} else {
|
||||
chart.maxValue = 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
.chart = std::move(chart),
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Api {
|
||||
|
||||
@@ -40,6 +225,7 @@ void Polls::create(
|
||||
SendAction action,
|
||||
Fn<void()> done,
|
||||
Fn<void(bool fileReferenceExpired)> fail) {
|
||||
StripEphemeralReply(_session, action.replyTo);
|
||||
_session->api().sendAction(action);
|
||||
|
||||
const auto history = action.history;
|
||||
@@ -156,6 +342,7 @@ void Polls::sendVotes(
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto peer = item->history()->peer;
|
||||
|
||||
const auto showSending = poll && !options.empty();
|
||||
const auto hideSending = [=] {
|
||||
@@ -169,6 +356,12 @@ void Polls::sendVotes(
|
||||
if (showSending) {
|
||||
poll->sendingVotes = options;
|
||||
_session->data().requestItemRepaint(item);
|
||||
} else if (poll && options.empty() && poll->voted()) {
|
||||
for (auto &answer : poll->answers) {
|
||||
answer.chosen = false;
|
||||
}
|
||||
++poll->version;
|
||||
_session->data().notifyPollUpdateDelayed(poll);
|
||||
}
|
||||
|
||||
auto prepared = QVector<MTPbytes>();
|
||||
@@ -178,22 +371,39 @@ void Polls::sendVotes(
|
||||
ranges::back_inserter(prepared),
|
||||
[](const QByteArray &option) { return MTP_bytes(option); });
|
||||
const auto requestId = _api.request(MTPmessages_SendVote(
|
||||
item->history()->peer->input(),
|
||||
peer->input(),
|
||||
MTP_int(item->id),
|
||||
MTP_vector<MTPbytes>(prepared)
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
if (poll) {
|
||||
if (poll->voteRestriction() != PollData::VoteRestriction::None) {
|
||||
poll->setVoteRestriction(PollData::VoteRestriction::None);
|
||||
_session->data().notifyPollUpdateDelayed(poll);
|
||||
}
|
||||
}
|
||||
_session->updates().applyUpdates(result);
|
||||
|
||||
const auto &ghost = AyuSettings::ghost(_session);
|
||||
if (!ghost.sendReadMessages() && ghost.markReadAfterAction() && item)
|
||||
{
|
||||
if (!ghost.sendReadMessages()
|
||||
&& ghost.markReadAfterAction()
|
||||
&& item) {
|
||||
readHistory(item);
|
||||
}
|
||||
}).fail([=] {
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollVotesRequestIds.erase(itemId);
|
||||
hideSending();
|
||||
if (poll) {
|
||||
const auto restriction = ParseVoteRestrictionError(error.type());
|
||||
if (restriction != PollData::VoteRestriction::None) {
|
||||
poll->setVoteRestriction(restriction);
|
||||
_session->data().notifyPollUpdateDelayed(poll);
|
||||
if (const auto item = _session->data().message(itemId)) {
|
||||
_session->data().requestItemResize(item);
|
||||
}
|
||||
ShowVoteRestrictionToast(peer, poll, restriction);
|
||||
}
|
||||
}
|
||||
}).send();
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
@@ -285,7 +495,8 @@ void Polls::close(not_null<HistoryItem*> item) {
|
||||
MTPVector<MTPMessageEntity>(),
|
||||
MTP_int(0), // schedule_date
|
||||
MTP_int(0), // schedule_repeat_period
|
||||
MTPint() // quick_reply_shortcut_id
|
||||
MTPint(), // quick_reply_shortcut_id
|
||||
MTPInputRichMessage()
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollCloseRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
@@ -316,4 +527,65 @@ void Polls::reloadResults(not_null<HistoryItem*> item) {
|
||||
_pollReloadRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::requestStats(
|
||||
FullMsgId itemId,
|
||||
Fn<void(Data::StatisticalGraph)> done,
|
||||
Fn<void(QString)> fail) {
|
||||
const auto item = _session->data().message(itemId);
|
||||
const auto media = item ? item->media() : nullptr;
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
if (!item || !item->isRegular() || !poll) {
|
||||
if (fail) {
|
||||
fail(QString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
if (base::IsCtrlPressed()) {
|
||||
auto callback = std::move(done);
|
||||
if (callback) {
|
||||
constexpr auto kMockupStatsDelay = 2 * crl::time(1000);
|
||||
auto graph = GenerateMockupPollStats(*poll);
|
||||
base::call_delayed(kMockupStatsDelay, _session, [=]() mutable {
|
||||
callback(std::move(graph));
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
const auto requestGraph = [=](const QString &token) {
|
||||
_api.request(MTPstats_LoadAsyncGraph(
|
||||
MTP_flags(MTPstats_LoadAsyncGraph::Flag(0)),
|
||||
MTP_string(token),
|
||||
MTP_long(0)
|
||||
)).done([=](const MTPStatsGraph &result) {
|
||||
if (done) {
|
||||
done(Api::StatisticalGraphFromTL(result));
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (fail) {
|
||||
fail(error.type());
|
||||
}
|
||||
}).send();
|
||||
};
|
||||
_api.request(MTPstats_GetPollStats(
|
||||
MTP_flags(MTPstats_GetPollStats::Flags(0)),
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id)
|
||||
)).done([=](const MTPstats_PollStats &result) {
|
||||
auto graph = Api::StatisticalGraphFromTL(result.data().vvotes_graph());
|
||||
if (graph.chart || !graph.error.isEmpty() || graph.zoomToken.isEmpty()) {
|
||||
if (done) {
|
||||
done(std::move(graph));
|
||||
}
|
||||
} else {
|
||||
requestGraph(graph.zoomToken);
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
if (fail) {
|
||||
fail(error.type());
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
} // namespace Api
|
||||
|
||||
Reference in New Issue
Block a user