mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-14 21:27:07 +02:00
Track factcheck text and create media.
This commit is contained in:
parent
5ee2bca616
commit
b299881bf8
13 changed files with 332 additions and 11 deletions
136
Telegram/SourceFiles/data/components/factchecks.cpp
Normal file
136
Telegram/SourceFiles/data/components/factchecks.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/components/factchecks.h"
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "base/random.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/view/media/history_view_web_page.h"
|
||||
#include "history/view/history_view_message.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kRequestDelay = crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
Factchecks::Factchecks(not_null<Main::Session*> session)
|
||||
: _session(session)
|
||||
, _requestTimer([=] { request(); }) {
|
||||
}
|
||||
|
||||
void Factchecks::requestFor(not_null<HistoryItem*> item) {
|
||||
subscribeIfNotYet();
|
||||
|
||||
if (const auto factcheck = item->Get<HistoryMessageFactcheck>()) {
|
||||
factcheck->requested = true;
|
||||
}
|
||||
if (!_requestTimer.isActive()) {
|
||||
_requestTimer.callOnce(kRequestDelay);
|
||||
}
|
||||
const auto changed = !_pending.empty()
|
||||
&& (_pending.front()->history() != item->history());
|
||||
const auto added = _pending.emplace(item).second;
|
||||
if (changed) {
|
||||
request();
|
||||
} else if (added && _pending.size() == 1) {
|
||||
_requestTimer.callOnce(kRequestDelay);
|
||||
}
|
||||
}
|
||||
|
||||
void Factchecks::subscribeIfNotYet() {
|
||||
if (_subscribed) {
|
||||
return;
|
||||
}
|
||||
_subscribed = true;
|
||||
|
||||
_session->data().itemRemoved(
|
||||
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
_pending.remove(item);
|
||||
const auto i = ranges::find(_requested, item.get());
|
||||
if (i != end(_requested)) {
|
||||
*i = nullptr;
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Factchecks::request() {
|
||||
_requestTimer.cancel();
|
||||
|
||||
if (!_requested.empty() || _pending.empty()) {
|
||||
return;
|
||||
}
|
||||
_session->api().request(base::take(_requestId)).cancel();
|
||||
|
||||
auto ids = QVector<MTPint>();
|
||||
ids.reserve(_pending.size());
|
||||
const auto history = _pending.front()->history();
|
||||
for (auto i = begin(_pending); i != end(_pending);) {
|
||||
const auto &item = *i;
|
||||
if (item->history() == history) {
|
||||
_requested.push_back(item);
|
||||
ids.push_back(MTP_int(item->id.bare));
|
||||
i = _pending.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
_requestId = _session->api().request(MTPmessages_GetFactCheck(
|
||||
history->peer->input,
|
||||
MTP_vector<MTPint>(std::move(ids))
|
||||
)).done([=](const MTPVector<MTPFactCheck> &result) {
|
||||
_requestId = 0;
|
||||
const auto &list = result.v;
|
||||
auto index = 0;
|
||||
for (const auto &item : base::take(_requested)) {
|
||||
if (!item) {
|
||||
} else if (index >= list.size()) {
|
||||
item->setFactcheck({});
|
||||
} else {
|
||||
item->setFactcheck(FromMTP(item, &list[index]));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (!_pending.empty()) {
|
||||
request();
|
||||
}
|
||||
}).fail([=] {
|
||||
_requestId = 0;
|
||||
for (const auto &item : base::take(_requested)) {
|
||||
if (item) {
|
||||
item->setFactcheck({});
|
||||
}
|
||||
}
|
||||
if (!_pending.empty()) {
|
||||
request();
|
||||
}
|
||||
}).send();
|
||||
}
|
||||
|
||||
std::unique_ptr<HistoryView::WebPage> Factchecks::makeMedia(
|
||||
not_null<HistoryView::Message*> view,
|
||||
not_null<HistoryMessageFactcheck*> factcheck) {
|
||||
if (!factcheck->page) {
|
||||
factcheck->page = view->history()->owner().webpage(
|
||||
base::RandomValue<WebPageId>(),
|
||||
tr::lng_factcheck_title(tr::now),
|
||||
factcheck->data.text);
|
||||
}
|
||||
return std::make_unique<HistoryView::WebPage>(
|
||||
view,
|
||||
factcheck->page,
|
||||
MediaWebPageFlags());
|
||||
}
|
||||
|
||||
} // namespace Data
|
51
Telegram/SourceFiles/data/components/factchecks.h
Normal file
51
Telegram/SourceFiles/data/components/factchecks.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
|
||||
class HistoryItem;
|
||||
struct HistoryMessageFactcheck;
|
||||
|
||||
namespace HistoryView {
|
||||
class Message;
|
||||
class WebPage;
|
||||
} // namespace HistoryView
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Factchecks final {
|
||||
public:
|
||||
explicit Factchecks(not_null<Main::Session*> session);
|
||||
|
||||
void requestFor(not_null<HistoryItem*> item);
|
||||
[[nodiscard]] std::unique_ptr<HistoryView::WebPage> makeMedia(
|
||||
not_null<HistoryView::Message*> view,
|
||||
not_null<HistoryMessageFactcheck*> factcheck);
|
||||
|
||||
private:
|
||||
void subscribeIfNotYet();
|
||||
void request();
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
base::Timer _requestTimer;
|
||||
base::flat_set<not_null<HistoryItem*>> _pending;
|
||||
std::vector<HistoryItem*> _requested;
|
||||
mtpRequestId _requestId = 0;
|
||||
bool _subscribed = false;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
|
@ -4250,29 +4250,27 @@ void Session::notifyPollUpdateDelayed(not_null<PollData*> poll) {
|
|||
}
|
||||
|
||||
void Session::sendWebPageGamePollNotifications() {
|
||||
auto resize = std::vector<not_null<ViewElement*>>();
|
||||
for (const auto &page : base::take(_webpagesUpdated)) {
|
||||
_webpageUpdates.fire_copy(page);
|
||||
const auto i = _webpageViews.find(page);
|
||||
if (i != _webpageViews.end()) {
|
||||
for (const auto &view : i->second) {
|
||||
requestViewResize(view);
|
||||
}
|
||||
if (const auto i = _webpageViews.find(page)
|
||||
; i != _webpageViews.end()) {
|
||||
resize.insert(end(resize), begin(i->second), end(i->second));
|
||||
}
|
||||
}
|
||||
for (const auto &game : base::take(_gamesUpdated)) {
|
||||
if (const auto i = _gameViews.find(game); i != _gameViews.end()) {
|
||||
for (const auto &view : i->second) {
|
||||
requestViewResize(view);
|
||||
}
|
||||
resize.insert(end(resize), begin(i->second), end(i->second));
|
||||
}
|
||||
}
|
||||
for (const auto &poll : base::take(_pollsUpdated)) {
|
||||
if (const auto i = _pollViews.find(poll); i != _pollViews.end()) {
|
||||
for (const auto &view : i->second) {
|
||||
requestViewResize(view);
|
||||
}
|
||||
resize.insert(end(resize), begin(i->second), end(i->second));
|
||||
}
|
||||
}
|
||||
for (const auto &view : resize) {
|
||||
requestViewResize(view);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<not_null<WebPageData*>> Session::webPageUpdates() const {
|
||||
|
|
|
@ -1306,6 +1306,8 @@ void InnerWidget::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
|||
&& !link
|
||||
&& (view->hasVisibleText()
|
||||
|| mediaHasTextForCopy
|
||||
|| (item->Has<HistoryMessageFactcheck>()
|
||||
&& !item->Get<HistoryMessageFactcheck>()->data.text.empty())
|
||||
|| item->Has<HistoryMessageLogEntryOriginal>())) {
|
||||
_menu->addAction(tr::lng_context_copy_text(tr::now), [=] {
|
||||
copyContextText(itemId);
|
||||
|
|
|
@ -415,6 +415,11 @@ HistoryItem::HistoryItem(
|
|||
}
|
||||
setReactions(data.vreactions());
|
||||
applyTTL(data);
|
||||
|
||||
if (const auto check = FromMTP(this, data.vfactcheck())) {
|
||||
AddComponents(HistoryMessageFactcheck::Bit());
|
||||
Get<HistoryMessageFactcheck>()->data = check;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1494,6 +1499,33 @@ void HistoryItem::addLogEntryOriginal(
|
|||
content);
|
||||
}
|
||||
|
||||
void HistoryItem::setFactcheck(MessageFactcheck info) {
|
||||
if (!info) {
|
||||
if (Has<HistoryMessageFactcheck>()) {
|
||||
RemoveComponents(HistoryMessageFactcheck::Bit());
|
||||
history()->owner().requestItemResize(this);
|
||||
}
|
||||
} else {
|
||||
AddComponents(HistoryMessageFactcheck::Bit());
|
||||
const auto factcheck = Get<HistoryMessageFactcheck>();
|
||||
if (factcheck->data.hash == info.hash
|
||||
&& (info.needCheck || !factcheck->data.needCheck)) {
|
||||
return;
|
||||
} else if (factcheck->data.text != info.text
|
||||
|| factcheck->data.country != info.country
|
||||
|| factcheck->data.hash != info.hash) {
|
||||
factcheck->data = std::move(info);
|
||||
factcheck->requested = false;
|
||||
history()->owner().requestItemResize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryItem::hasUnrequestedFactcheck() const {
|
||||
const auto factcheck = Get<HistoryMessageFactcheck>();
|
||||
return factcheck && factcheck->data.needCheck && !factcheck->requested;
|
||||
}
|
||||
|
||||
PeerData *HistoryItem::specialNotificationPeer() const {
|
||||
return (mentionsMe() && !_history->peer->isUser())
|
||||
? from().get()
|
||||
|
@ -3143,6 +3175,8 @@ EffectId HistoryItem::effectId() const {
|
|||
bool HistoryItem::isEmpty() const {
|
||||
return _text.empty()
|
||||
&& !_media
|
||||
&& (!Has<HistoryMessageFactcheck>()
|
||||
|| Get<HistoryMessageFactcheck>()->data.text.empty())
|
||||
&& !Has<HistoryMessageLogEntryOriginal>();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,11 @@ struct HistoryMessageReplyMarkup;
|
|||
struct HistoryMessageTranslation;
|
||||
struct HistoryMessageForwarded;
|
||||
struct HistoryMessageSavedMediaData;
|
||||
struct HistoryMessageFactcheck;
|
||||
struct HistoryServiceDependentData;
|
||||
enum class HistorySelfDestructType;
|
||||
struct PreparedServiceText;
|
||||
struct MessageFactcheck;
|
||||
class ReplyKeyboard;
|
||||
struct LanguageId;
|
||||
|
||||
|
@ -204,6 +206,8 @@ public:
|
|||
WebPageId localId,
|
||||
const QString &label,
|
||||
const TextWithEntities &content);
|
||||
void setFactcheck(MessageFactcheck info);
|
||||
[[nodiscard]] bool hasUnrequestedFactcheck() const;
|
||||
|
||||
[[nodiscard]] not_null<Data::Thread*> notificationThread() const;
|
||||
[[nodiscard]] not_null<History*> history() const {
|
||||
|
|
|
@ -1062,6 +1062,31 @@ HistoryMessageLogEntryOriginal &HistoryMessageLogEntryOriginal::operator=(
|
|||
|
||||
HistoryMessageLogEntryOriginal::~HistoryMessageLogEntryOriginal() = default;
|
||||
|
||||
MessageFactcheck FromMTP(
|
||||
not_null<HistoryItem*> item,
|
||||
const tl::conditional<MTPFactCheck> &factcheck) {
|
||||
auto result = MessageFactcheck();
|
||||
if (!factcheck) {
|
||||
return result;
|
||||
}
|
||||
const auto &data = factcheck->data();
|
||||
if (const auto text = data.vtext()) {
|
||||
const auto &data = text->data();
|
||||
result.text = {
|
||||
qs(data.vtext()),
|
||||
Api::EntitiesFromMTP(
|
||||
&item->history()->session(),
|
||||
data.ventities().v),
|
||||
};
|
||||
}
|
||||
if (const auto country = data.vcountry()) {
|
||||
result.country = qs(country->v);
|
||||
}
|
||||
result.hash = data.vhash().v;
|
||||
result.needCheck = data.is_need_check();
|
||||
return result;
|
||||
}
|
||||
|
||||
HistoryDocumentCaptioned::HistoryDocumentCaptioned()
|
||||
: caption(st::msgFileMinWidth - st::msgPadding.left() - st::msgPadding.right()) {
|
||||
}
|
||||
|
|
|
@ -562,6 +562,31 @@ struct HistoryMessageLogEntryOriginal
|
|||
|
||||
};
|
||||
|
||||
struct MessageFactcheck {
|
||||
TextWithEntities text;
|
||||
QString country;
|
||||
uint64 hash = 0;
|
||||
bool needCheck = false;
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return text.empty() && country.isEmpty() && !hash;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return !empty();
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] MessageFactcheck FromMTP(
|
||||
not_null<HistoryItem*> item,
|
||||
const tl::conditional<MTPFactCheck> &factcheck);
|
||||
|
||||
struct HistoryMessageFactcheck
|
||||
: public RuntimeComponent<HistoryMessageFactcheck, HistoryItem> {
|
||||
MessageFactcheck data;
|
||||
WebPageData *page = nullptr;
|
||||
bool requested = false;
|
||||
};
|
||||
|
||||
struct HistoryServiceData
|
||||
: public RuntimeComponent<HistoryServiceData, HistoryItem> {
|
||||
std::vector<ClickHandlerPtr> textLinks;
|
||||
|
|
|
@ -46,6 +46,12 @@ TextForMimeData HistoryItemText(not_null<HistoryItem*> item) {
|
|||
titleResult.append('\n').append(std::move(descriptionResult));
|
||||
return titleResult;
|
||||
}();
|
||||
auto factcheckResult = [&] {
|
||||
const auto factcheck = item->Get<HistoryMessageFactcheck>();
|
||||
return factcheck
|
||||
? TextForMimeData::Rich(base::duplicate(factcheck->data.text))
|
||||
: TextForMimeData();
|
||||
}();
|
||||
auto result = textResult;
|
||||
if (result.empty()) {
|
||||
result = std::move(mediaResult);
|
||||
|
@ -57,6 +63,11 @@ TextForMimeData HistoryItemText(not_null<HistoryItem*> item) {
|
|||
} else if (!logEntryOriginalResult.empty()) {
|
||||
result.append(u"\n\n"_q).append(std::move(logEntryOriginalResult));
|
||||
}
|
||||
if (result.empty()) {
|
||||
result = std::move(factcheckResult);
|
||||
} else if (!factcheckResult.empty()) {
|
||||
result.append(u"\n\n"_q).append(std::move(factcheckResult));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/round_rect.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/power_saving.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_user.h"
|
||||
|
@ -799,6 +800,24 @@ QSize Message::performCountOptimalSize() {
|
|||
RemoveComponents(Reply::Bit());
|
||||
}
|
||||
|
||||
const auto factcheck = item->Get<HistoryMessageFactcheck>();
|
||||
if (factcheck && !factcheck->data.text.empty()) {
|
||||
AddComponents(Factcheck::Bit());
|
||||
Get<Factcheck>()->page = history()->session().factchecks().makeMedia(
|
||||
this,
|
||||
factcheck);
|
||||
|
||||
auto copy = data()->originalText();
|
||||
if (!copy.text.contains("FACT CHECK")) {
|
||||
copy.append("\n\nFACT CHECK!!\n\n").append(factcheck->data.text);
|
||||
crl::on_main(this, [=] {
|
||||
data()->setText(std::move(copy));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
RemoveComponents(Factcheck::Bit());
|
||||
}
|
||||
|
||||
const auto markup = item->inlineReplyMarkup();
|
||||
const auto reactionsKey = [&] {
|
||||
return embedReactionsInBottomInfo()
|
||||
|
@ -1069,6 +1088,10 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
|||
const auto item = data();
|
||||
const auto media = this->media();
|
||||
|
||||
if (item->hasUnrequestedFactcheck()) {
|
||||
item->history()->session().factchecks().requestFor(item);
|
||||
}
|
||||
|
||||
const auto stm = context.messageStyle();
|
||||
const auto bubble = drawBubble();
|
||||
|
||||
|
|
|
@ -44,6 +44,11 @@ struct LogEntryOriginal
|
|||
std::unique_ptr<WebPage> page;
|
||||
};
|
||||
|
||||
struct Factcheck
|
||||
: public RuntimeComponent<Factcheck, Element> {
|
||||
std::unique_ptr<WebPage> page;
|
||||
};
|
||||
|
||||
struct PsaTooltipState : public RuntimeComponent<PsaTooltipState, Element> {
|
||||
QString type;
|
||||
mutable ClickHandlerPtr link;
|
||||
|
|
|
@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "storage/file_upload.h"
|
||||
#include "storage/storage_account.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "data/components/factchecks.h"
|
||||
#include "data/components/recent_peers.h"
|
||||
#include "data/components/scheduled_messages.h"
|
||||
#include "data/components/sponsored_messages.h"
|
||||
|
@ -105,6 +106,7 @@ Session::Session(
|
|||
, _scheduledMessages(std::make_unique<Data::ScheduledMessages>(this))
|
||||
, _sponsoredMessages(std::make_unique<Data::SponsoredMessages>(this))
|
||||
, _topPeers(std::make_unique<Data::TopPeers>(this))
|
||||
, _factchecks(std::make_unique<Data::Factchecks>(this))
|
||||
, _cachedReactionIconFactory(std::make_unique<ReactionIconFactory>())
|
||||
, _supportHelper(Support::Helper::Create(this))
|
||||
, _saveSettingsTimer([=] { saveSettings(); }) {
|
||||
|
|
|
@ -35,6 +35,7 @@ class RecentPeers;
|
|||
class ScheduledMessages;
|
||||
class SponsoredMessages;
|
||||
class TopPeers;
|
||||
class Factchecks;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView::Reactions {
|
||||
|
@ -127,6 +128,9 @@ public:
|
|||
[[nodiscard]] Data::TopPeers &topPeers() const {
|
||||
return *_topPeers;
|
||||
}
|
||||
[[nodiscard]] Data::Factchecks &factchecks() const {
|
||||
return *_factchecks;
|
||||
}
|
||||
[[nodiscard]] Api::Updates &updates() const {
|
||||
return *_updates;
|
||||
}
|
||||
|
@ -254,6 +258,7 @@ private:
|
|||
const std::unique_ptr<Data::ScheduledMessages> _scheduledMessages;
|
||||
const std::unique_ptr<Data::SponsoredMessages> _sponsoredMessages;
|
||||
const std::unique_ptr<Data::TopPeers> _topPeers;
|
||||
const std::unique_ptr<Data::Factchecks> _factchecks;
|
||||
|
||||
using ReactionIconFactory = HistoryView::Reactions::CachedIconFactory;
|
||||
const std::unique_ptr<ReactionIconFactory> _cachedReactionIconFactory;
|
||||
|
|
Loading…
Add table
Reference in a new issue