2016-09-27 14:20:49 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
2018-01-03 10:23:14 +00:00
|
|
|
the official desktop application for the Telegram messaging service.
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2018-01-03 10:23:14 +00:00
|
|
|
For license and copyright information please follow this link:
|
|
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
2016-09-27 14:20:49 +00:00
|
|
|
*/
|
|
|
|
#include "history/history_item.h"
|
|
|
|
|
2017-04-13 08:27:10 +00:00
|
|
|
#include "lang/lang_keys.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
#include "mainwidget.h"
|
2018-01-11 19:33:26 +00:00
|
|
|
#include "layout.h"
|
|
|
|
#include "history/view/history_view_element.h"
|
2018-01-10 13:13:33 +00:00
|
|
|
#include "history/view/history_view_service_message.h"
|
2017-12-18 15:44:50 +00:00
|
|
|
#include "history/history_item_components.h"
|
2017-06-20 19:48:53 +00:00
|
|
|
#include "history/history_media_types.h"
|
2017-12-13 18:10:48 +00:00
|
|
|
#include "history/history_media_grouped.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "history/history_service.h"
|
2017-06-22 15:11:41 +00:00
|
|
|
#include "history/history_message.h"
|
2018-01-13 12:45:11 +00:00
|
|
|
#include "history/history.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
#include "media/media_clip_reader.h"
|
|
|
|
#include "styles/style_dialogs.h"
|
2016-11-16 10:44:06 +00:00
|
|
|
#include "styles/style_history.h"
|
2016-11-16 16:04:25 +00:00
|
|
|
#include "ui/effects/ripple_animation.h"
|
2017-12-28 13:06:06 +00:00
|
|
|
#include "ui/text_options.h"
|
2017-03-04 10:23:56 +00:00
|
|
|
#include "storage/file_upload.h"
|
2017-09-04 11:40:02 +00:00
|
|
|
#include "storage/storage_facade.h"
|
|
|
|
#include "storage/storage_shared_media.h"
|
2018-01-21 09:05:30 +00:00
|
|
|
#include "storage/storage_feed_messages.h"
|
2017-02-23 09:32:28 +00:00
|
|
|
#include "auth_session.h"
|
2017-12-25 14:17:00 +00:00
|
|
|
#include "apiwrap.h"
|
2017-05-18 20:18:59 +00:00
|
|
|
#include "media/media_audio.h"
|
2017-03-10 17:25:43 +00:00
|
|
|
#include "messenger.h"
|
2017-10-03 13:05:58 +00:00
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "window/window_controller.h"
|
2018-01-02 13:44:12 +00:00
|
|
|
#include "core/crash_reports.h"
|
2018-01-04 10:22:53 +00:00
|
|
|
#include "data/data_session.h"
|
2018-01-09 17:08:31 +00:00
|
|
|
#include "data/data_messages.h"
|
2018-01-14 16:02:25 +00:00
|
|
|
#include "data/data_media_types.h"
|
2018-01-05 15:57:18 +00:00
|
|
|
#include "data/data_feed.h"
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
not_null<HistoryItem*> CreateUnsupportedMessage(
|
|
|
|
not_null<History*> history,
|
|
|
|
MsgId msgId,
|
|
|
|
MTPDmessage::Flags flags,
|
|
|
|
MsgId replyTo,
|
|
|
|
UserId viaBotId,
|
|
|
|
QDateTime date,
|
|
|
|
UserId from) {
|
|
|
|
const auto siteLink = qsl("https://desktop.telegram.org");
|
|
|
|
auto text = TextWithEntities{
|
|
|
|
lng_message_unsupported(lt_link, siteLink)
|
|
|
|
};
|
|
|
|
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
|
|
|
|
text.entities.push_front(
|
|
|
|
EntityInText(EntityInTextItalic, 0, text.text.size()));
|
|
|
|
flags &= ~MTPDmessage::Flag::f_post_author;
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryMessage(
|
2018-01-14 16:02:25 +00:00
|
|
|
history,
|
|
|
|
msgId,
|
|
|
|
flags,
|
|
|
|
replyTo,
|
|
|
|
viaBotId,
|
|
|
|
date,
|
|
|
|
from,
|
|
|
|
QString(),
|
|
|
|
text);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-08-25 15:17:46 +00:00
|
|
|
HistoryItem::HistoryItem(
|
|
|
|
not_null<History*> history,
|
|
|
|
MsgId id,
|
|
|
|
MTPDmessage::Flags flags,
|
|
|
|
QDateTime date,
|
2018-01-13 12:45:11 +00:00
|
|
|
UserId from)
|
|
|
|
: id(id)
|
2017-08-25 15:17:46 +00:00
|
|
|
, date(date)
|
2016-09-27 14:20:49 +00:00
|
|
|
, _history(history)
|
2017-06-21 08:53:14 +00:00
|
|
|
, _from(from ? App::user(from) : history->peer)
|
2018-01-13 12:45:11 +00:00
|
|
|
, _flags(flags) {
|
2016-09-27 14:20:49 +00:00
|
|
|
App::historyRegItem(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HistoryItem::finishEdition(int oldKeyboardTop) {
|
2018-01-13 12:45:11 +00:00
|
|
|
Auth().data().requestItemViewRefresh(this);
|
2017-12-16 09:27:45 +00:00
|
|
|
invalidateChatsListEntry();
|
2018-01-18 13:59:22 +00:00
|
|
|
if (const auto group = Auth().data().groups().find(this)) {
|
|
|
|
const auto leader = group->items.back();
|
|
|
|
if (leader != this) {
|
|
|
|
Auth().data().requestItemViewRefresh(leader);
|
|
|
|
leader->invalidateChatsListEntry();
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
//if (oldKeyboardTop >= 0) { // #TODO edit bot message
|
|
|
|
// if (auto keyboard = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
// keyboard->oldTop = oldKeyboardTop;
|
|
|
|
// }
|
|
|
|
//}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
App::historyUpdateDependent(this);
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
void HistoryItem::setGroupId(MessageGroupId groupId) {
|
2018-01-17 16:21:01 +00:00
|
|
|
Expects(!_groupId);
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
_groupId = groupId;
|
|
|
|
Auth().data().groups().registerMessage(this);
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
HistoryMessageReplyMarkup *HistoryItem::inlineReplyMarkup() {
|
|
|
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
|
|
|
return markup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
ReplyKeyboard *HistoryItem::inlineReplyKeyboard() {
|
|
|
|
if (const auto markup = inlineReplyMarkup()) {
|
|
|
|
return markup->inlineKeyboard.get();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2017-12-16 09:27:45 +00:00
|
|
|
void HistoryItem::invalidateChatsListEntry() {
|
2018-01-22 17:39:20 +00:00
|
|
|
if (const auto main = App::main()) {
|
2018-01-05 15:57:18 +00:00
|
|
|
// #TODO feeds search results
|
2018-01-22 17:39:20 +00:00
|
|
|
main->repaintDialogRow(history(), id);
|
2017-12-16 09:27:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// invalidate cache for drawInDialog
|
|
|
|
if (history()->textCachedFor == this) {
|
|
|
|
history()->textCachedFor = nullptr;
|
|
|
|
}
|
2018-01-05 15:57:18 +00:00
|
|
|
if (const auto feed = history()->peer->feed()) {
|
|
|
|
if (feed->textCachedFor == this) {
|
|
|
|
feed->textCachedFor = nullptr;
|
|
|
|
feed->updateChatListEntry();
|
|
|
|
}
|
|
|
|
}
|
2017-12-16 09:27:45 +00:00
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
void HistoryItem::finishEditionToEmpty() {
|
|
|
|
finishEdition(-1);
|
2018-01-31 17:10:29 +00:00
|
|
|
_history->itemVanished(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-11-20 19:54:05 +00:00
|
|
|
bool HistoryItem::isMediaUnread() const {
|
|
|
|
if (!mentionsMe() && _history->peer->isChannel()) {
|
|
|
|
auto now = ::date(unixtime());
|
|
|
|
auto passed = date.secsTo(now);
|
|
|
|
if (passed >= Global::ChannelsReadMediaPeriod()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return _flags & MTPDmessage::Flag::f_media_unread;
|
|
|
|
}
|
|
|
|
|
2017-08-25 12:48:10 +00:00
|
|
|
void HistoryItem::markMediaRead() {
|
|
|
|
_flags &= ~MTPDmessage::Flag::f_media_unread;
|
|
|
|
|
|
|
|
if (mentionsMe()) {
|
|
|
|
history()->updateChatListEntry();
|
|
|
|
history()->eraseFromUnreadMentions(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
bool HistoryItem::definesReplyKeyboard() const {
|
|
|
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
if (markup->flags & MTPDreplyKeyboardMarkup_ClientFlag::f_inline) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// optimization: don't create markup component for the case
|
|
|
|
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
|
|
|
return (_flags & MTPDmessage::Flag::f_reply_markup);
|
|
|
|
}
|
|
|
|
|
|
|
|
MTPDreplyKeyboardMarkup::Flags HistoryItem::replyKeyboardFlags() const {
|
|
|
|
Expects(definesReplyKeyboard());
|
|
|
|
|
|
|
|
if (const auto markup = Get<HistoryMessageReplyMarkup>()) {
|
|
|
|
return markup->flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
// optimization: don't create markup component for the case
|
|
|
|
// MTPDreplyKeyboardHide with flags = 0, assume it has f_zero flag
|
|
|
|
return MTPDreplyKeyboardMarkup_ClientFlag::f_zero | 0;
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
void HistoryItem::addLogEntryOriginal(
|
|
|
|
WebPageId localId,
|
|
|
|
const QString &label,
|
|
|
|
const TextWithEntities &content) {
|
2017-06-18 13:08:49 +00:00
|
|
|
Expects(isLogEntry());
|
2018-01-14 16:02:25 +00:00
|
|
|
|
2017-06-18 13:08:49 +00:00
|
|
|
AddComponents(HistoryMessageLogEntryOriginal::Bit());
|
2018-01-17 16:21:01 +00:00
|
|
|
Get<HistoryMessageLogEntryOriginal>()->page = Auth().data().webpage(
|
2018-01-14 16:02:25 +00:00
|
|
|
localId,
|
|
|
|
label,
|
|
|
|
content);
|
2017-06-18 13:08:49 +00:00
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
UserData *HistoryItem::viaBot() const {
|
|
|
|
if (const auto via = Get<HistoryMessageVia>()) {
|
|
|
|
return via->bot;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
UserData *HistoryItem::getMessageBot() const {
|
|
|
|
if (const auto bot = viaBot()) {
|
|
|
|
return bot;
|
|
|
|
}
|
|
|
|
auto bot = from()->asUser();
|
|
|
|
if (!bot) {
|
|
|
|
bot = history()->peer->asUser();
|
|
|
|
}
|
|
|
|
return (bot && bot->botInfo) ? bot : nullptr;
|
|
|
|
};
|
|
|
|
|
2017-06-18 13:08:49 +00:00
|
|
|
void HistoryItem::destroy() {
|
2018-01-05 15:57:18 +00:00
|
|
|
const auto history = this->history();
|
2017-06-18 13:08:49 +00:00
|
|
|
if (isLogEntry()) {
|
2018-01-11 13:07:29 +00:00
|
|
|
Assert(!mainView());
|
2017-06-18 13:08:49 +00:00
|
|
|
} else {
|
2018-01-31 17:10:29 +00:00
|
|
|
// All this must be done for all items manually in History::clear()!
|
2017-12-08 18:27:28 +00:00
|
|
|
eraseFromUnreadMentions();
|
2017-09-04 11:40:02 +00:00
|
|
|
if (IsServerMsgId(id)) {
|
2017-12-25 14:17:00 +00:00
|
|
|
if (const auto types = sharedMediaTypes()) {
|
2017-09-04 11:40:02 +00:00
|
|
|
Auth().storage().remove(Storage::SharedMediaRemoveOne(
|
2018-01-05 15:57:18 +00:00
|
|
|
history->peer->id,
|
2017-09-04 11:40:02 +00:00
|
|
|
types,
|
|
|
|
id));
|
|
|
|
}
|
2017-12-25 14:17:00 +00:00
|
|
|
} else {
|
|
|
|
Auth().api().cancelLocalItem(this);
|
2017-09-04 11:40:02 +00:00
|
|
|
}
|
2018-01-31 17:10:29 +00:00
|
|
|
_history->itemRemoved(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
|
2018-01-18 13:59:22 +00:00
|
|
|
void HistoryItem::refreshMainView() {
|
|
|
|
if (const auto view = mainView()) {
|
|
|
|
Auth().data().notifyHistoryChangeDelayed(_history);
|
|
|
|
view->refreshInBlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-11 13:07:29 +00:00
|
|
|
void HistoryItem::removeMainView() {
|
|
|
|
if (const auto view = mainView()) {
|
2018-01-18 09:53:49 +00:00
|
|
|
Auth().data().notifyHistoryChangeDelayed(_history);
|
2018-01-11 13:07:29 +00:00
|
|
|
view->removeFromBlock();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-11 13:07:29 +00:00
|
|
|
void HistoryItem::clearMainView() {
|
2018-01-10 13:13:33 +00:00
|
|
|
_mainView = nullptr;
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void HistoryItem::addToUnreadMentions(UnreadMentionType type) {
|
|
|
|
}
|
|
|
|
|
2017-12-21 12:11:33 +00:00
|
|
|
void HistoryItem::indexAsNewItem() {
|
|
|
|
if (IsServerMsgId(id)) {
|
2018-01-01 20:23:18 +00:00
|
|
|
CrashReports::SetAnnotation("addToUnreadMentions", QString::number(id));
|
2017-12-21 12:11:33 +00:00
|
|
|
addToUnreadMentions(UnreadMentionType::New);
|
2018-01-01 20:23:18 +00:00
|
|
|
CrashReports::ClearAnnotation("addToUnreadMentions");
|
2017-12-21 12:11:33 +00:00
|
|
|
if (const auto types = sharedMediaTypes()) {
|
|
|
|
Auth().storage().add(Storage::SharedMediaAddNew(
|
|
|
|
history()->peer->id,
|
|
|
|
types,
|
|
|
|
id));
|
|
|
|
}
|
2018-01-21 09:05:30 +00:00
|
|
|
if (const auto channel = history()->peer->asChannel()) {
|
|
|
|
if (const auto feed = channel->feed()) {
|
|
|
|
Auth().storage().add(Storage::FeedMessagesAddNew(
|
|
|
|
feed->id(),
|
|
|
|
position()));
|
|
|
|
}
|
|
|
|
}
|
2017-12-21 12:11:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
void HistoryItem::setRealId(MsgId newId) {
|
|
|
|
Expects(!IsServerMsgId(id));
|
2016-11-18 16:27:47 +00:00
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
App::historyUnregItem(this);
|
|
|
|
const auto oldId = std::exchange(id, newId);
|
|
|
|
App::historyRegItem(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
|
|
|
|
// We don't need to call Notify::replyMarkupUpdated(this) and update keyboard
|
|
|
|
// in history widget, because it can't exist for an outgoing message.
|
|
|
|
// Only inline keyboards can be in outgoing messages.
|
2018-01-14 16:02:25 +00:00
|
|
|
if (const auto markup = inlineReplyMarkup()) {
|
2016-09-27 14:20:49 +00:00
|
|
|
if (markup->inlineKeyboard) {
|
|
|
|
markup->inlineKeyboard->updateMessageId();
|
|
|
|
}
|
|
|
|
}
|
2017-06-24 10:32:30 +00:00
|
|
|
|
2018-01-17 18:20:55 +00:00
|
|
|
Auth().data().notifyItemIdChange({ this, oldId });
|
2018-01-18 11:46:45 +00:00
|
|
|
Auth().data().requestItemRepaint(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
2017-11-21 13:23:56 +00:00
|
|
|
bool HistoryItem::isPinned() const {
|
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
|
|
|
return (channel->pinnedMessageId() == id);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-30 13:58:45 +00:00
|
|
|
bool HistoryItem::canPin() const {
|
2017-11-21 13:23:56 +00:00
|
|
|
if (id < 0 || !toHistoryMessage()) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-11-21 13:23:56 +00:00
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return channel->canPinMessages();
|
|
|
|
}
|
|
|
|
return false;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
bool HistoryItem::allowsForward() const {
|
2017-04-27 21:17:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
bool HistoryItem::allowsEdit(const QDateTime &now) const {
|
2016-09-27 14:20:49 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-04-30 13:58:45 +00:00
|
|
|
bool HistoryItem::canDelete() const {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (isLogEntry() || (!IsServerMsgId(id) && serviceMsg())) {
|
2017-06-29 09:32:19 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
auto channel = _history->peer->asChannel();
|
|
|
|
if (!channel) {
|
|
|
|
return !(_flags & MTPDmessage_ClientFlag::f_is_group_migrate);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id == 1) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
if (channel->canDeleteMessages()) {
|
2017-04-30 13:58:45 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
if (out() && toHistoryMessage()) {
|
|
|
|
return isPost() ? channel->canPublish() : true;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
return false;
|
2017-04-30 13:58:45 +00:00
|
|
|
}
|
|
|
|
|
2016-12-31 15:19:22 +00:00
|
|
|
bool HistoryItem::canDeleteForEveryone(const QDateTime &cur) const {
|
2017-06-04 11:09:29 +00:00
|
|
|
auto messageToMyself = _history->peer->isSelf();
|
2016-12-31 15:19:22 +00:00
|
|
|
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
|
2017-04-30 13:58:45 +00:00
|
|
|
if (id < 0 || messageToMyself || messageTooOld || isPost()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (history()->peer->isChannel()) {
|
|
|
|
return false;
|
2017-11-30 11:06:30 +00:00
|
|
|
} else if (auto user = history()->peer->asUser()) {
|
|
|
|
// Bots receive all messages and there is no sense in revoking them.
|
|
|
|
// See https://github.com/telegramdesktop/tdesktop/issues/3818
|
|
|
|
if (user->botInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-04-02 10:25:54 +00:00
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
if (!toHistoryMessage()) {
|
|
|
|
return false;
|
2016-12-31 15:19:22 +00:00
|
|
|
}
|
2018-01-14 16:02:25 +00:00
|
|
|
if (const auto media = this->media()) {
|
|
|
|
if (!media->allowsRevoke()) {
|
2017-04-27 21:17:00 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2017-04-30 13:58:45 +00:00
|
|
|
if (!out()) {
|
|
|
|
if (auto chat = history()->peer->asChat()) {
|
|
|
|
if (!chat->amCreator() && (!chat->amAdmin() || !chat->adminsEnabled())) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2016-12-31 15:19:22 +00:00
|
|
|
}
|
|
|
|
|
2017-06-04 11:09:29 +00:00
|
|
|
bool HistoryItem::suggestBanReport() const {
|
|
|
|
auto channel = history()->peer->asChannel();
|
2017-06-09 16:12:02 +00:00
|
|
|
auto fromUser = from()->asUser();
|
|
|
|
if (!channel || !fromUser || !channel->canRestrictUser(fromUser)) {
|
2017-06-04 11:09:29 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:12:02 +00:00
|
|
|
return !isPost() && !out() && toHistoryMessage();
|
2017-06-04 11:09:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool HistoryItem::suggestDeleteAllReport() const {
|
|
|
|
auto channel = history()->peer->asChannel();
|
|
|
|
if (!channel || !channel->canDeleteMessages()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return !isPost() && !out() && from()->isUser() && toHistoryMessage();
|
|
|
|
}
|
|
|
|
|
2017-07-11 17:21:24 +00:00
|
|
|
bool HistoryItem::hasDirectLink() const {
|
2018-01-28 15:08:34 +00:00
|
|
|
if (!IsServerMsgId(id)) {
|
2017-07-11 17:21:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (auto channel = _history->peer->asChannel()) {
|
|
|
|
return channel->isPublic();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-10 17:25:43 +00:00
|
|
|
QString HistoryItem::directLink() const {
|
2017-04-30 13:31:11 +00:00
|
|
|
if (hasDirectLink()) {
|
2017-07-11 17:21:24 +00:00
|
|
|
auto channel = _history->peer->asChannel();
|
2017-08-17 09:06:26 +00:00
|
|
|
Assert(channel != nullptr);
|
2017-07-11 17:21:24 +00:00
|
|
|
auto query = channel->username + '/' + QString::number(id);
|
|
|
|
if (!channel->isMegagroup()) {
|
2018-01-14 16:02:25 +00:00
|
|
|
if (const auto media = this->media()) {
|
|
|
|
if (const auto document = media->document()) {
|
2017-12-10 10:26:58 +00:00
|
|
|
if (document->isVideoMessage()) {
|
2017-07-11 17:21:24 +00:00
|
|
|
return qsl("https://telesco.pe/") + query;
|
|
|
|
}
|
2017-04-30 13:31:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Messenger::Instance().createInternalLinkFull(query);
|
|
|
|
}
|
|
|
|
return QString();
|
2017-03-10 17:25:43 +00:00
|
|
|
}
|
2017-04-30 13:31:11 +00:00
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
ChannelId HistoryItem::channelId() const {
|
|
|
|
return _history->channelId();
|
|
|
|
}
|
|
|
|
|
2018-01-09 17:08:31 +00:00
|
|
|
Data::MessagePosition HistoryItem::position() const {
|
|
|
|
return Data::MessagePosition(toServerTime(date.toTime_t()).v, fullId());
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
MsgId HistoryItem::replyToId() const {
|
|
|
|
if (auto reply = Get<HistoryMessageReply>()) {
|
|
|
|
return reply->replyToId();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
not_null<PeerData*> HistoryItem::author() const {
|
|
|
|
return isPost() ? history()->peer : from();
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
QDateTime HistoryItem::dateOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalDate;
|
|
|
|
}
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
not_null<PeerData*> HistoryItem::senderOriginal() const {
|
2017-12-18 15:44:50 +00:00
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalSender;
|
|
|
|
}
|
|
|
|
const auto peer = history()->peer;
|
|
|
|
return (peer->isChannel() && !peer->isMegagroup()) ? peer : from();
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
not_null<PeerData*> HistoryItem::fromOriginal() const {
|
2017-12-18 15:44:50 +00:00
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
if (const auto user = forwarded->originalSender->asUser()) {
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return from();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString HistoryItem::authorOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalAuthor;
|
|
|
|
} else if (const auto msgsigned = Get<HistoryMessageSigned>()) {
|
|
|
|
return msgsigned->author;
|
|
|
|
}
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
MsgId HistoryItem::idOriginal() const {
|
|
|
|
if (const auto forwarded = Get<HistoryMessageForwarded>()) {
|
|
|
|
return forwarded->originalId;
|
|
|
|
}
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
bool HistoryItem::needCheck() const {
|
|
|
|
return out() || (id < 0 && history()->peer->isSelf());
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
bool HistoryItem::unread() const {
|
|
|
|
// Messages from myself are always read.
|
|
|
|
if (history()->peer->isSelf()) return false;
|
|
|
|
|
|
|
|
if (out()) {
|
|
|
|
// Outgoing messages in converted chats are always read.
|
2017-04-02 10:25:54 +00:00
|
|
|
if (history()->peer->migrateTo()) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
|
2018-01-31 17:10:29 +00:00
|
|
|
if (IsServerMsgId(id)) {
|
|
|
|
if (!history()->isServerSideUnread(this)) {
|
2017-04-02 10:25:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
2018-01-31 17:10:29 +00:00
|
|
|
if (const auto user = history()->peer->asUser()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (user->botInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
} else if (auto channel = history()->peer->asChannel()) {
|
2017-04-02 10:25:54 +00:00
|
|
|
if (!channel->isMegagroup()) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-01-31 17:10:29 +00:00
|
|
|
if (IsServerMsgId(id)) {
|
|
|
|
if (!history()->isServerSideUnread(this)) {
|
2017-04-02 10:25:54 +00:00
|
|
|
return false;
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return (_flags & MTPDmessage_ClientFlag::f_clientside_unread);
|
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
MessageGroupId HistoryItem::groupId() const {
|
2018-01-14 16:02:25 +00:00
|
|
|
return _groupId;
|
2017-12-13 18:10:48 +00:00
|
|
|
}
|
|
|
|
|
2017-12-18 15:44:50 +00:00
|
|
|
bool HistoryItem::isEmpty() const {
|
|
|
|
return _text.isEmpty()
|
|
|
|
&& !_media
|
|
|
|
&& !Has<HistoryMessageLogEntryOriginal>();
|
|
|
|
}
|
|
|
|
|
2016-09-27 14:20:49 +00:00
|
|
|
QString HistoryItem::notificationText() const {
|
|
|
|
auto getText = [this]() {
|
2018-01-18 13:59:22 +00:00
|
|
|
if (_media) {
|
|
|
|
return _media->notificationText();
|
|
|
|
} else if (!emptyText()) {
|
|
|
|
return _text.originalText();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2018-01-18 13:59:22 +00:00
|
|
|
return QString();
|
2016-09-27 14:20:49 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
auto result = getText();
|
2017-04-02 10:25:54 +00:00
|
|
|
if (result.size() > 0xFF) {
|
|
|
|
result = result.mid(0, 0xFF) + qsl("...");
|
|
|
|
}
|
2016-09-27 14:20:49 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-09-05 17:21:56 +00:00
|
|
|
QString HistoryItem::inDialogsText(DrawInDialog way) const {
|
2016-09-27 14:20:49 +00:00
|
|
|
auto getText = [this]() {
|
2018-01-18 13:59:22 +00:00
|
|
|
if (_media) {
|
|
|
|
return _media->chatsListText();
|
|
|
|
} else if (!emptyText()) {
|
|
|
|
return TextUtilities::Clean(_text.originalText());
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2018-01-18 13:59:22 +00:00
|
|
|
return QString();
|
2016-09-27 14:20:49 +00:00
|
|
|
};
|
2017-12-05 16:38:13 +00:00
|
|
|
const auto plainText = getText();
|
|
|
|
const auto sender = [&]() -> PeerData* {
|
|
|
|
if (isPost() || isEmpty() || (way == DrawInDialog::WithoutSender)) {
|
|
|
|
return nullptr;
|
|
|
|
} else if (!_history->peer->isUser() || out()) {
|
|
|
|
return author();
|
2018-01-14 16:02:25 +00:00
|
|
|
} else if (_history->peer->isSelf() && !Has<HistoryMessageForwarded>()) {
|
2017-12-05 16:38:13 +00:00
|
|
|
return senderOriginal();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}();
|
|
|
|
if (sender) {
|
|
|
|
auto fromText = sender->isSelf() ? lang(lng_from_you) : sender->shortName();
|
2017-07-06 11:37:42 +00:00
|
|
|
auto fromWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, TextUtilities::Clean(fromText)));
|
2016-09-27 14:20:49 +00:00
|
|
|
return lng_dialogs_text_with_from(lt_from_part, fromWrapped, lt_message, plainText);
|
|
|
|
}
|
|
|
|
return plainText;
|
|
|
|
}
|
|
|
|
|
2017-09-05 17:21:56 +00:00
|
|
|
void HistoryItem::drawInDialog(
|
|
|
|
Painter &p,
|
|
|
|
const QRect &r,
|
|
|
|
bool active,
|
|
|
|
bool selected,
|
|
|
|
DrawInDialog way,
|
|
|
|
const HistoryItem *&cacheFor,
|
|
|
|
Text &cache) const {
|
2016-09-27 14:20:49 +00:00
|
|
|
if (cacheFor != this) {
|
|
|
|
cacheFor = this;
|
2017-12-28 13:06:06 +00:00
|
|
|
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
if (r.width()) {
|
2016-12-23 13:21:01 +00:00
|
|
|
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
|
2016-09-27 14:20:49 +00:00
|
|
|
p.setFont(st::dialogsTextFont);
|
2016-11-05 08:36:24 +00:00
|
|
|
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
2016-09-27 14:20:49 +00:00
|
|
|
cache.drawElided(p, r.left(), r.top(), r.width(), r.height() / st::dialogsTextFont->height);
|
2016-12-23 13:21:01 +00:00
|
|
|
p.restoreTextPalette();
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryItem::~HistoryItem() {
|
2018-01-21 09:05:30 +00:00
|
|
|
Auth().data().notifyItemRemoved(this);
|
2016-09-27 14:20:49 +00:00
|
|
|
App::historyUnregItem(this);
|
2017-08-04 14:54:32 +00:00
|
|
|
if (id < 0 && !App::quitting()) {
|
|
|
|
Auth().uploader().cancel(fullId());
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-26 15:40:11 +00:00
|
|
|
ClickHandlerPtr goToMessageClickHandler(
|
|
|
|
not_null<HistoryItem*> item,
|
|
|
|
FullMsgId returnToId) {
|
|
|
|
return goToMessageClickHandler(
|
|
|
|
item->history()->peer,
|
|
|
|
item->id,
|
|
|
|
returnToId);
|
|
|
|
}
|
|
|
|
|
2018-01-13 12:45:11 +00:00
|
|
|
ClickHandlerPtr goToMessageClickHandler(
|
|
|
|
not_null<PeerData*> peer,
|
2018-01-26 15:40:11 +00:00
|
|
|
MsgId msgId,
|
|
|
|
FullMsgId returnToId) {
|
2018-01-13 12:45:11 +00:00
|
|
|
return std::make_shared<LambdaClickHandler>([=] {
|
2018-01-26 15:40:11 +00:00
|
|
|
if (const auto main = App::main()) {
|
|
|
|
if (const auto returnTo = App::histItemById(returnToId)) {
|
|
|
|
if (returnTo->history()->peer == peer) {
|
|
|
|
main->pushReplyReturn(returnTo);
|
|
|
|
}
|
2016-11-16 10:44:06 +00:00
|
|
|
}
|
2017-10-03 13:05:58 +00:00
|
|
|
App::wnd()->controller()->showPeerHistory(
|
|
|
|
peer,
|
|
|
|
Window::SectionShow::Way::Forward,
|
|
|
|
msgId);
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2016-11-16 10:44:06 +00:00
|
|
|
});
|
2016-09-27 14:20:49 +00:00
|
|
|
}
|
2018-01-13 12:45:11 +00:00
|
|
|
|
2018-01-14 16:02:25 +00:00
|
|
|
not_null<HistoryItem*> HistoryItem::Create(
|
|
|
|
not_null<History*> history,
|
|
|
|
const MTPMessage &message) {
|
|
|
|
switch (message.type()) {
|
|
|
|
case mtpc_messageEmpty: {
|
|
|
|
const auto &data = message.c_messageEmpty();
|
|
|
|
const auto text = HistoryService::PreparedText {
|
|
|
|
lang(lng_message_empty)
|
|
|
|
};
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryService(history, data.vid.v, ::date(), text);
|
2018-01-14 16:02:25 +00:00
|
|
|
} break;
|
|
|
|
|
|
|
|
case mtpc_message: {
|
|
|
|
const auto &data = message.c_message();
|
|
|
|
enum class MediaCheckResult {
|
|
|
|
Good,
|
|
|
|
Unsupported,
|
|
|
|
Empty,
|
|
|
|
HasTimeToLive,
|
|
|
|
};
|
|
|
|
auto badMedia = MediaCheckResult::Good;
|
|
|
|
const auto &media = data.vmedia;
|
|
|
|
if (data.has_media()) switch (media.type()) {
|
|
|
|
case mtpc_messageMediaEmpty:
|
|
|
|
case mtpc_messageMediaContact: break;
|
|
|
|
case mtpc_messageMediaGeo:
|
|
|
|
switch (media.c_messageMediaGeo().vgeo.type()) {
|
|
|
|
case mtpc_geoPoint: break;
|
|
|
|
case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaVenue:
|
|
|
|
switch (media.c_messageMediaVenue().vgeo.type()) {
|
|
|
|
case mtpc_geoPoint: break;
|
|
|
|
case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaGeoLive:
|
|
|
|
switch (media.c_messageMediaGeoLive().vgeo.type()) {
|
|
|
|
case mtpc_geoPoint: break;
|
|
|
|
case mtpc_geoPointEmpty: badMedia = MediaCheckResult::Empty; break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaPhoto: {
|
|
|
|
auto &photo = media.c_messageMediaPhoto();
|
|
|
|
if (photo.has_ttl_seconds()) {
|
|
|
|
badMedia = MediaCheckResult::HasTimeToLive;
|
|
|
|
} else if (!photo.has_photo()) {
|
|
|
|
badMedia = MediaCheckResult::Empty;
|
|
|
|
} else {
|
|
|
|
switch (photo.vphoto.type()) {
|
|
|
|
case mtpc_photo: break;
|
|
|
|
case mtpc_photoEmpty: badMedia = MediaCheckResult::Empty; break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case mtpc_messageMediaDocument: {
|
|
|
|
auto &document = media.c_messageMediaDocument();
|
|
|
|
if (document.has_ttl_seconds()) {
|
|
|
|
badMedia = MediaCheckResult::HasTimeToLive;
|
|
|
|
} else if (!document.has_document()) {
|
|
|
|
badMedia = MediaCheckResult::Empty;
|
|
|
|
} else {
|
|
|
|
switch (document.vdocument.type()) {
|
|
|
|
case mtpc_document: break;
|
|
|
|
case mtpc_documentEmpty: badMedia = MediaCheckResult::Empty; break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case mtpc_messageMediaWebPage:
|
|
|
|
switch (media.c_messageMediaWebPage().vwebpage.type()) {
|
|
|
|
case mtpc_webPage:
|
|
|
|
case mtpc_webPageEmpty:
|
|
|
|
case mtpc_webPagePending: break;
|
|
|
|
case mtpc_webPageNotModified:
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaGame:
|
|
|
|
switch (media.c_messageMediaGame().vgame.type()) {
|
|
|
|
case mtpc_game: break;
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaInvoice:
|
|
|
|
break;
|
|
|
|
case mtpc_messageMediaUnsupported:
|
|
|
|
default: badMedia = MediaCheckResult::Unsupported; break;
|
|
|
|
}
|
|
|
|
if (badMedia == MediaCheckResult::Unsupported) {
|
|
|
|
return CreateUnsupportedMessage(
|
|
|
|
history,
|
|
|
|
data.vid.v,
|
|
|
|
data.vflags.v,
|
|
|
|
data.vreply_to_msg_id.v,
|
|
|
|
data.vvia_bot_id.v,
|
|
|
|
::date(data.vdate),
|
|
|
|
data.vfrom_id.v);
|
|
|
|
} else if (badMedia == MediaCheckResult::Empty) {
|
|
|
|
const auto text = HistoryService::PreparedText {
|
|
|
|
lang(lng_message_empty)
|
|
|
|
};
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryService(
|
2018-01-14 16:02:25 +00:00
|
|
|
history,
|
|
|
|
data.vid.v,
|
|
|
|
::date(data.vdate),
|
|
|
|
text,
|
|
|
|
data.vflags.v,
|
|
|
|
data.has_from_id() ? data.vfrom_id.v : UserId(0));
|
|
|
|
} else if (badMedia == MediaCheckResult::HasTimeToLive) {
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryService(history, data);
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryMessage(history, data);
|
2018-01-14 16:02:25 +00:00
|
|
|
} break;
|
|
|
|
|
|
|
|
case mtpc_messageService: {
|
|
|
|
auto &data = message.c_messageService();
|
|
|
|
if (data.vaction.type() == mtpc_messageActionPhoneCall) {
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryMessage(history, data);
|
2018-01-14 16:02:25 +00:00
|
|
|
}
|
2018-01-18 20:02:50 +00:00
|
|
|
return new HistoryService(history, data);
|
2018-01-14 16:02:25 +00:00
|
|
|
} break;
|
|
|
|
}
|
|
|
|
|
|
|
|
Unexpected("Type in HistoryItem::Create().");
|
|
|
|
}
|