Allow sending scheduled messages.

This commit is contained in:
John Preston 2019-08-12 17:33:36 +01:00
parent caef7dde24
commit 03cdddfe18
18 changed files with 336 additions and 180 deletions

View File

@ -40,7 +40,9 @@ void SendExistingMedia(
message.action.generateLocal = true;
api->sendAction(message.action);
const auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId());
const auto newId = FullMsgId(
peerToChannel(peer->id),
session->data().nextLocalMessageId());
const auto randomId = rand_value<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
@ -84,6 +86,13 @@ void SendExistingMedia(
const auto replyTo = message.action.replyTo;
const auto captionText = caption.text;
if (message.action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
session->data().registerMessageRandomId(randomId, newId);
history->addNewLocalMessage(
@ -92,7 +101,7 @@ void SendExistingMedia(
clientFlags,
0,
replyTo,
base::unixtime::now(),
HistoryItem::NewMessageDate(message.action.options.scheduled),
messageFromId,
messagePostAuthor,
media,
@ -111,7 +120,7 @@ void SendExistingMedia(
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(0) // schedule_date
MTP_int(message.action.options.scheduled)
)).done([=](const MTPUpdates &result) {
api->applyUpdates(result, randomId);
}).fail([=](const RPCError &error) {

View File

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_media_types.h"
#include "data/data_sparse_ids.h"
#include "data/data_search_controller.h"
#include "data/data_scheduled_messages.h"
#include "data/data_channel_admins.h"
#include "data/data_session.h"
#include "data/data_channel.h"
@ -4427,6 +4428,12 @@ void ApiWrap::forwardMessages(
if (silentPost) {
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
}
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
sendFlags |= MTPmessages_ForwardMessages::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
auto forwardFrom = items.front()->history()->peer;
auto currentGroupId = items.front()->groupId();
@ -4448,7 +4455,7 @@ void ApiWrap::forwardMessages(
MTP_vector<MTPint>(ids),
MTP_vector<MTPlong>(randomIds),
peer->input,
MTP_int(0) // schedule_date
MTP_int(action.options.scheduled)
)).done([=, callback = std::move(successCallback)](
const MTPUpdates &updates) {
applyUpdates(updates);
@ -4480,7 +4487,7 @@ void ApiWrap::forwardMessages(
if (const auto message = item->toHistoryMessage()) {
const auto newId = FullMsgId(
peerToChannel(peer->id),
clientMsgId());
session().data().nextLocalMessageId());
const auto self = _session->user();
const auto messageFromId = channelPost
? UserId(0)
@ -4492,7 +4499,7 @@ void ApiWrap::forwardMessages(
newId.msg,
flags,
clientFlags,
base::unixtime::now(),
HistoryItem::NewMessageDate(action.options.scheduled),
messageFromId,
messagePostAuthor,
message);
@ -4554,7 +4561,9 @@ void ApiWrap::sendSharedContact(
const auto history = action.history;
const auto peer = history->peer;
const auto newId = FullMsgId(history->channelId(), clientMsgId());
const auto newId = FullMsgId(
history->channelId(),
session().data().nextLocalMessageId());
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
@ -4571,6 +4580,11 @@ void ApiWrap::sendSharedContact(
} else {
flags |= MTPDmessage::Flag::f_from_id;
}
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
const auto messageFromId = channelPost ? 0 : _session->userId();
const auto messagePostAuthor = channelPost
? App::peerName(_session->user())
@ -4586,7 +4600,7 @@ void ApiWrap::sendSharedContact(
MTPMessageFwdHeader(),
MTPint(),
MTP_int(action.replyTo),
MTP_int(base::unixtime::now()),
MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)),
MTP_string(),
MTP_messageMediaContact(
MTP_string(phone),
@ -4907,7 +4921,9 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
HistoryItem *lastMessage = nullptr;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId());
auto newId = FullMsgId(
peerToChannel(peer->id),
session().data().nextLocalMessageId());
auto randomId = rand_value<uint64>();
TextUtilities::Trim(sending);
@ -4963,6 +4979,12 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
auto messagePostAuthor = channelPost
? App::peerName(_session->user())
: QString();
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
sendFlags |= MTPmessages_SendMessage::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
lastMessage = history->addNewMessage(
MTP_message(
MTP_flags(flags),
@ -4972,7 +4994,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTPMessageFwdHeader(),
MTPint(),
MTP_int(action.replyTo),
MTP_int(base::unixtime::now()),
MTP_int(
HistoryItem::NewMessageDate(action.options.scheduled)),
msgText,
media,
MTPReplyMarkup(),
@ -4993,7 +5016,7 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(0) // schedule_date
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
@ -5053,7 +5076,9 @@ void ApiWrap::sendInlineResult(
const auto history = action.history;
const auto peer = history->peer;
const auto newId = FullMsgId(peerToChannel(peer->id), clientMsgId());
const auto newId = FullMsgId(
peerToChannel(peer->id),
session().data().nextLocalMessageId());
const auto randomId = rand_value<uint64>();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
@ -5081,14 +5106,17 @@ void ApiWrap::sendInlineResult(
if (bot) {
flags |= MTPDmessage::Flag::f_via_bot_id;
}
if (action.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
sendFlags |= MTPmessages_SendInlineBotResult::Flag::f_schedule_date;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
auto messageFromId = channelPost ? 0 : _session->userId();
auto messagePostAuthor = channelPost
const auto messageFromId = channelPost ? 0 : _session->userId();
const auto messagePostAuthor = channelPost
? App::peerName(_session->user())
: QString();
MTPint messageDate = MTP_int(base::unixtime::now());
UserId messageViaBotId = bot ? peerToUser(bot->id) : 0;
MsgId messageId = newId.msg;
_session->data().registerMessageRandomId(randomId, newId);
@ -5096,10 +5124,10 @@ void ApiWrap::sendInlineResult(
history,
flags,
clientFlags,
messageId,
newId.msg,
messageFromId,
messageDate,
messageViaBotId,
MTP_int(HistoryItem::NewMessageDate(action.options.scheduled)),
bot ? peerToUser(bot->id) : 0,
action.replyTo,
messagePostAuthor);
@ -5113,7 +5141,7 @@ void ApiWrap::sendInlineResult(
MTP_long(randomId),
MTP_long(data->getQueryId()),
MTP_string(data->getId()),
MTP_int(0) // schedule_date
MTP_int(action.options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result, randomId);
history->clearSentDraftText(QString());
@ -5236,6 +5264,9 @@ void ApiWrap::sendMediaWithRandomId(
: MTPmessages_SendMedia::Flag(0))
| (!sentEntities.v.isEmpty()
? MTPmessages_SendMedia::Flag::f_entities
: MTPmessages_SendMedia::Flag(0))
| (options.scheduled
? MTPmessages_SendMedia::Flag::f_schedule_date
: MTPmessages_SendMedia::Flag(0));
const auto peer = history->peer;
@ -5249,7 +5280,7 @@ void ApiWrap::sendMediaWithRandomId(
MTP_long(randomId),
MTPReplyMarkup(),
sentEntities,
MTP_int(0) // schedule_date
MTP_int(options.scheduled)
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).fail([=](const RPCError &error) {
@ -5330,6 +5361,9 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
: MTPmessages_SendMultiMedia::Flag(0))
| (album->options.silent
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0))
| (album->options.scheduled
? MTPmessages_SendMultiMedia::Flag::f_schedule_date
: MTPmessages_SendMultiMedia::Flag(0));
const auto peer = history->peer;
history->sendRequestId = request(MTPmessages_SendMultiMedia(
@ -5337,7 +5371,7 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias),
MTP_int(0) // schedule_date
MTP_int(album->options.scheduled)
)).done([=](const MTPUpdates &result) {
_sendingAlbums.remove(groupId);
applyUpdates(result);
@ -5360,10 +5394,7 @@ FileLoadTo ApiWrap::fileLoadTaskOptions(const SendAction &action) const {
if (_session->data().notifySilentPosts(peer)) {
options.silent = true;
}
return FileLoadTo(
peer->id,
action.options,
action.replyTo);
return FileLoadTo(peer->id, action.options, action.replyTo);
}
void ApiWrap::requestSupportContact(FnMut<void(const MTPUser &)> callback) {
@ -5387,7 +5418,9 @@ void ApiWrap::uploadPeerPhoto(not_null<PeerData*> peer, QImage &&image) {
peer = peer->migrateToOrMe();
const auto ready = PreparePeerPhoto(peer->id, std::move(image));
const auto fakeId = FullMsgId(peerToChannel(peer->id), clientMsgId());
const auto fakeId = FullMsgId(
peerToChannel(peer->id),
session().data().nextLocalMessageId());
const auto already = ranges::find(
_peerPhotoUploads,
peer,
@ -5817,6 +5850,9 @@ void ApiWrap::createPoll(
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
if (action.options.scheduled) {
sendFlags |= MTPmessages_SendMedia::Flag::f_schedule_date;
}
const auto replyTo = action.replyTo;
history->sendRequestId = request(MTPmessages_SendMedia(
@ -5828,7 +5864,7 @@ void ApiWrap::createPoll(
MTP_long(rand_value<uint64>()),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>(),
MTP_int(0) // schedule_date
MTP_int(action.options.scheduled)
)).done([=, done = std::move(done)](const MTPUpdates &result) mutable {
applyUpdates(result);
done();

View File

@ -771,8 +771,14 @@ void DeleteMessagesBox::deleteAndClear() {
if (const auto item = _session->data().message(itemId)) {
const auto history = item->history();
if (item->isScheduled()) {
scheduledIdsByPeer[history->peer].push_back(MTP_int(
_session->data().scheduledMessages().lookupId(item)));
const auto wasOnServer = !item->isSending()
&& !item->hasFailed();
if (wasOnServer) {
scheduledIdsByPeer[history->peer].push_back(MTP_int(
_session->data().scheduledMessages().lookupId(item)));
} else {
_session->data().scheduledMessages().removeSending(item);
}
continue;
}
const auto wasOnServer = IsServerMsgId(item->id);

View File

@ -137,6 +137,49 @@ void ScheduledMessages::apply(
_updates.fire_copy(history);
}
void ScheduledMessages::apply(
const MTPDupdateMessageID &update,
not_null<HistoryItem*> local) {
const auto id = update.vid().v;
const auto i = _data.find(local->history());
Assert(i != end(_data));
auto &list = i->second;
const auto j = list.itemById.find(id);
if (j != end(list.itemById)) {
local->destroy();
} else {
Assert(!list.itemById.contains(local->id));
Assert(!list.idByItem.contains(local));
local->setRealId(local->history()->nextNonHistoryEntryId());
list.idByItem.emplace(local, id);
list.itemById.emplace(id, local);
}
}
void ScheduledMessages::appendSending(not_null<HistoryItem*> item) {
Expects(item->isSending());
Expects(item->isScheduled());
const auto history = item->history();
auto &list = _data[history];
list.items.emplace_back(item);
sort(list);
_updates.fire_copy(history);
}
void ScheduledMessages::removeSending(not_null<HistoryItem*> item) {
Expects(item->isSending() || item->hasFailed());
Expects(item->isScheduled());
const auto history = item->history();
auto &list = _data[history];
Assert(!list.itemById.contains(item->id));
Assert(!list.idByItem.contains(item));
list.items.erase(
ranges::remove(list.items, item.get(), &OwnedItem::get),
end(list.items));
}
rpl::producer<> ScheduledMessages::updates(not_null<History*> history) {
request(history);
@ -175,10 +218,12 @@ void ScheduledMessages::request(not_null<History*> history) {
if (request.requestId) {
return;
}
const auto i = _data.find(history);
const auto hash = (i != end(_data)) ? countListHash(i->second) : 0;
request.requestId = _session->api().request(
MTPmessages_GetScheduledHistory(
history->peer->input,
MTP_int(request.hash))
MTP_int(hash))
).done([=](const MTPmessages_Messages &result) {
parse(history, result);
}).fail([=](const RPCError &error) {
@ -197,14 +242,33 @@ void ScheduledMessages::parse(
}, [&](const auto &data) {
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
const auto &messages = data.vmessages().v;
if (messages.isEmpty()) {
if (element != end(_data)) {
_data.erase(element);
element = end(_data);
_updates.fire_copy(history);
}
return;
}
element = _data.emplace(history, List()).first;
auto received = base::flat_set<not_null<HistoryItem*>>();
auto &list = element->second;
for (const auto &message : messages) {
append(history, list, message);
if (const auto item = append(history, list, message)) {
received.emplace(item);
}
}
auto clear = base::flat_set<not_null<HistoryItem*>>();
for (const auto &owned : list.items) {
const auto item = owned.get();
if (!item->isSending() && !received.contains(item)) {
clear.emplace(item);
}
}
for (const auto item : clear) {
item->destroy();
}
if (!list.items.empty()) {
sort(list);
@ -214,24 +278,21 @@ void ScheduledMessages::parse(
}
_updates.fire_copy(history);
});
request.hash = (element != end(_data))
? countListHash(element->second)
: 0;
if (!request.requestId && !request.hash) {
if (!request.requestId) {
_requests.remove(history);
}
}
void ScheduledMessages::append(
HistoryItem *ScheduledMessages::append(
not_null<History*> history,
List &list,
const MTPMessage &message) {
const auto id = message.match([&](const auto &data) {
return data.vid().v;
});
if (list.itemById.find(id) != end(list.itemById)) {
return;
const auto i = list.itemById.find(id);
if (i != end(list.itemById)) {
return i->second;
}
const auto item = _session->data().addNewMessage(
@ -240,11 +301,12 @@ void ScheduledMessages::append(
NewMessageType::Existing);
if (!item || item->history() != history) {
LOG(("API Error: Bad data received in scheduled messages."));
return;
return nullptr;
}
list.items.emplace_back(item);
list.itemById.emplace(id, item);
list.idByItem.emplace(item, id);
return item;
}
void ScheduledMessages::sort(List &list) {
@ -277,7 +339,12 @@ int32 ScheduledMessages::countListHash(const List &list) const {
using namespace Api;
auto hash = HashInit();
for (const auto &item : list.items | ranges::view::reverse) {
auto &&serverside = ranges::view::all(
list.items
) | ranges::view::filter([](const OwnedItem &item) {
return !item->isSending() && !item->hasFailed();
}) | ranges::view::reverse;
for (const auto &item : serverside) {
const auto j = list.idByItem.find(item.get());
HashUpdate(hash, j->second);
if (const auto edited = item->Get<HistoryMessageEdited>()) {

View File

@ -32,6 +32,12 @@ public:
void apply(const MTPDupdateNewScheduledMessage &update);
void apply(const MTPDupdateDeleteScheduledMessages &update);
void apply(
const MTPDupdateMessageID &update,
not_null<HistoryItem*> local);
void appendSending(not_null<HistoryItem*> item);
void removeSending(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<> updates(not_null<History*> history);
[[nodiscard]] Data::MessagesSlice list(not_null<History*> history);
@ -44,7 +50,6 @@ private:
base::flat_map<not_null<HistoryItem*>, MsgId> idByItem;
};
struct Request {
int32 hash = 0;
mtpRequestId requestId = 0;
};
@ -52,7 +57,7 @@ private:
void parse(
not_null<History*> history,
const MTPmessages_Messages &list);
void append(
HistoryItem *append(
not_null<History*> history,
List &list,
const MTPMessage &message);

View File

@ -1844,6 +1844,12 @@ void Session::destroyMessage(not_null<HistoryItem*> item) {
list->erase(item->id);
}
MsgId Session::nextLocalMessageId() {
Expects(_localMessageIdCounter < EndClientMsgId);
return _localMessageIdCounter++;
}
HistoryItem *Session::message(ChannelId channelId, MsgId itemId) const {
if (!itemId) {
return nullptr;
@ -3627,13 +3633,14 @@ void Session::insertCheckedServiceNotification(
const auto flags = MTPDmessage::Flag::f_entities
| MTPDmessage::Flag::f_from_id
| MTPDmessage::Flag::f_media;
const auto clientFlags = MTPDmessage_ClientFlag::f_clientside_unread;
const auto clientFlags = MTPDmessage_ClientFlag::f_clientside_unread
| MTPDmessage_ClientFlag::f_local_history_entry;
auto sending = TextWithEntities(), left = message;
while (TextUtilities::CutPart(sending, left, MaxMessageSize)) {
addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(clientMsgId()),
MTP_int(nextLocalMessageId()),
MTP_int(peerToUser(PeerData::kServiceNotificationsId)),
MTP_peerUser(MTP_int(_session->userId())),
MTPMessageFwdHeader(),

View File

@ -387,6 +387,7 @@ public:
ChannelId channelId,
const QVector<MTPint> &data);
[[nodiscard]] MsgId nextLocalMessageId();
[[nodiscard]] HistoryItem *message(
ChannelId channelId,
MsgId itemId) const;
@ -877,6 +878,7 @@ private:
Dialogs::IndexedList _contactsList;
Dialogs::IndexedList _contactsNoChatsList;
MsgId _localMessageIdCounter = StartClientMsgId;
Messages _messages;
std::map<ChannelId, Messages> _channelMessages;
std::map<

View File

@ -415,12 +415,6 @@ inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) {
return !(a == b);
}
inline MsgId clientMsgId() {
static MsgId CurrentClientMsgId = StartClientMsgId;
Assert(CurrentClientMsgId < EndClientMsgId);
return CurrentClientMsgId++;
}
struct MessageCursor {
MessageCursor() = default;
MessageCursor(int position, int anchor, int scroll)

View File

@ -16,6 +16,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_drafts.h"
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_channel_admins.h"
#include "data/data_scheduled_messages.h"
#include "data/data_folder.h"
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "apiwrap.h"
#include "mainwidget.h"
@ -29,12 +36,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/storage_shared_media.h"
//#include "storage/storage_feed_messages.h" // #feed
#include "support/support_helper.h"
#include "data/data_channel_admins.h"
#include "data/data_folder.h"
#include "data/data_photo.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_user.h"
#include "ui/image/image.h"
#include "ui/text_options.h"
#include "core/crash_reports.h"
@ -622,51 +623,50 @@ HistoryItem *History::addNewMessage(
const MTPMessage &msg,
MTPDmessage_ClientFlags clientFlags,
NewMessageType type) {
if (type == NewMessageType::Existing) {
return addToHistory(msg, clientFlags);
}
if (!loadedAtBottom() || peer->migrateTo()) {
if (const auto item = addToHistory(msg, clientFlags)) {
setLastMessage(item);
if (type == NewMessageType::Unread) {
newItemAdded(item);
}
return item;
}
const auto detachExistingItem = (type == NewMessageType::Unread);
const auto item = createItem(msg, clientFlags, detachExistingItem);
if (!item) {
return nullptr;
}
return addNewToLastBlock(msg, clientFlags, type);
}
HistoryItem *History::addNewToLastBlock(
const MTPMessage &msg,
MTPDmessage_ClientFlags clientFlags,
NewMessageType type) {
Expects(type != NewMessageType::Existing);
const auto detachExistingItem = (type != NewMessageType::Last);
const auto item = createItem(msg, clientFlags, detachExistingItem);
if (!item || item->mainView()) {
if (type == NewMessageType::Existing || item->mainView()) {
return item;
}
const auto newUnreadMessage = (type == NewMessageType::Unread);
if (newUnreadMessage) {
const auto unread = (type == NewMessageType::Unread);
if (unread && item->isHistoryEntry()) {
applyMessageChanges(item, msg);
}
const auto result = addNewItem(item, newUnreadMessage);
checkForLoadedAtTop(result);
if (type == NewMessageType::Last) {
// When we add just one last item, like we do while loading dialogs,
// we want to remove a single added grouped media, otherwise it will
// jump once we open the message history (first we show only that
// media, then we load the rest of the group and show the group).
//
// That way when we open the message history we show nothing until a
// whole history part is loaded, it certainly will contain the group.
removeOrphanMediaGroupPart();
return addNewItem(item, unread);
}
not_null<HistoryItem*> History::addNewItem(
not_null<HistoryItem*> item,
bool unread) {
if (item->isScheduled()) {
session().data().scheduledMessages().appendSending(item);
return item;
} else if (!item->isHistoryEntry()) {
return item;
}
return result;
if (!loadedAtBottom() || peer->migrateTo()) {
setLastMessage(item);
if (unread) {
newItemAdded(item);
}
} else {
addNewToBack(item, unread);
checkForLoadedAtTop(item);
if (!unread) {
// When we add just one last item, like we do while loading dialogs,
// we want to remove a single added grouped media, otherwise it will
// jump once we open the message history (first we show only that
// media, then we load the rest of the group and show the group).
//
// That way when we open the message history we show nothing until a
// whole history part is loaded, it certainly will contain the group.
removeOrphanMediaGroupPart();
}
}
return item;
}
void History::checkForLoadedAtTop(not_null<HistoryItem*> added) {
@ -896,7 +896,7 @@ void History::addUnreadMentionsSlice(const MTPmessages_Messages &result) {
Notify::peerUpdatedDelayed(peer, Notify::PeerUpdate::Flag::UnreadMentionsChanged);
}
not_null<HistoryItem*> History::addNewItem(
not_null<HistoryItem*> History::addNewToBack(
not_null<HistoryItem*> item,
bool unread) {
Expects(!isBuildingFrontBlock());
@ -2866,7 +2866,7 @@ void History::insertLocalMessage(not_null<HistoryItem*> item) {
Expects(item->mainView() == nullptr);
if (isEmpty()) {
addNewItem(item, false);
addNewToBack(item, false);
return;
}

View File

@ -387,11 +387,6 @@ private:
// helper method for countScrollState(int top)
void countScrollTopItem(int top);
HistoryItem *addNewToLastBlock(
const MTPMessage &msg,
MTPDmessage_ClientFlags clientFlags,
NewMessageType type);
// this method just removes a block from the blocks list
// when the last item from this block was detached and
// calls the required previousItemChanged()
@ -401,6 +396,9 @@ private:
not_null<HistoryItem*> addNewItem(
not_null<HistoryItem*> item,
bool unread);
not_null<HistoryItem*> addNewToBack(
not_null<HistoryItem*> item,
bool unread);
not_null<HistoryItem*> addNewInTheMiddle(
not_null<HistoryItem*> item,
int blockIndex,

View File

@ -187,6 +187,11 @@ TimeId HistoryItem::date() const {
return _date;
}
TimeId HistoryItem::NewMessageDate(TimeId scheduled) {
const auto now = base::unixtime::now();
return scheduled ? std::max(scheduled, now + 60) : now;
}
void HistoryItem::finishEdition(int oldKeyboardTop) {
_history->owner().requestItemViewRefresh(this);
invalidateChatListEntry();

View File

@ -86,7 +86,8 @@ public:
UserData *getMessageBot() const;
[[nodiscard]] bool isHistoryEntry() const {
return (id < ServerMaxMsgId);
return IsServerMsgId(id)
|| (_clientFlags & MTPDmessage_ClientFlag::f_local_history_entry);
}
[[nodiscard]] bool isFromScheduled() const {
return isHistoryEntry()
@ -190,13 +191,13 @@ public:
return _clientFlags & MTPDmessage_ClientFlag::f_failed;
}
void sendFailed();
virtual int viewsCount() const {
[[nodiscard]] virtual int viewsCount() const {
return hasViews() ? 1 : -1;
}
virtual bool needCheck() const;
[[nodiscard]] virtual bool needCheck() const;
virtual bool serviceMsg() const {
[[nodiscard]] virtual bool serviceMsg() const {
return false;
}
virtual void applyEdition(const MTPDmessage &message) {
@ -218,14 +219,14 @@ public:
virtual void addToUnreadMentions(UnreadMentionType type);
virtual void eraseFromUnreadMentions() {
}
virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0;
[[nodiscard]] virtual Storage::SharedMediaTypesMask sharedMediaTypes() const = 0;
void indexAsNewItem();
virtual QString notificationHeader() const {
[[nodiscard]] virtual QString notificationHeader() const {
return QString();
}
virtual QString notificationText() const;
[[nodiscard]] virtual QString notificationText() const;
enum class DrawInDialog {
Normal,
@ -234,15 +235,15 @@ public:
// Returns text with link-start and link-end commands for service-color highlighting.
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
virtual QString inDialogsText(DrawInDialog way) const;
virtual QString inReplyText() const {
[[nodiscard]] virtual QString inDialogsText(DrawInDialog way) const;
[[nodiscard]] virtual QString inReplyText() const {
return inDialogsText(DrawInDialog::WithoutSender);
}
virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
virtual TextWithEntities originalText() const {
[[nodiscard]] virtual Ui::Text::IsolatedEmoji isolatedEmoji() const;
[[nodiscard]] virtual TextWithEntities originalText() const {
return TextWithEntities();
}
virtual TextForMimeData clipboardText() const {
[[nodiscard]] virtual TextForMimeData clipboardText() const {
return TextForMimeData();
}
@ -259,81 +260,83 @@ public:
const HistoryItem *&cacheFor,
Ui::Text::String &cache) const;
bool emptyText() const {
[[nodiscard]] bool emptyText() const {
return _text.isEmpty();
}
bool isPinned() const;
bool canPin() const;
bool canStopPoll() const;
virtual bool allowsSendNow() const;
virtual bool allowsForward() const;
virtual bool allowsEdit(TimeId now) const;
bool canDelete() const;
bool canDeleteForEveryone(TimeId now) const;
bool suggestReport() const;
bool suggestBanReport() const;
bool suggestDeleteAllReport() const;
[[nodiscard]] bool isPinned() const;
[[nodiscard]] bool canPin() const;
[[nodiscard]] bool canStopPoll() const;
[[nodiscard]] virtual bool allowsSendNow() const;
[[nodiscard]] virtual bool allowsForward() const;
[[nodiscard]] virtual bool allowsEdit(TimeId now) const;
[[nodiscard]] bool canDelete() const;
[[nodiscard]] bool canDeleteForEveryone(TimeId now) const;
[[nodiscard]] bool suggestReport() const;
[[nodiscard]] bool suggestBanReport() const;
[[nodiscard]] bool suggestDeleteAllReport() const;
bool hasDirectLink() const;
[[nodiscard]] bool hasDirectLink() const;
MsgId id;
ChannelId channelId() const;
FullMsgId fullId() const {
[[nodiscard]] ChannelId channelId() const;
[[nodiscard]] FullMsgId fullId() const {
return FullMsgId(channelId(), id);
}
Data::MessagePosition position() const;
TimeId date() const;
[[nodiscard]] Data::MessagePosition position() const;
[[nodiscard]] TimeId date() const;
Data::Media *media() const {
[[nodiscard]] static TimeId NewMessageDate(TimeId scheduled);
[[nodiscard]] Data::Media *media() const {
return _media.get();
}
virtual void setText(const TextWithEntities &textWithEntities) {
}
virtual bool textHasLinks() const {
[[nodiscard]] virtual bool textHasLinks() const {
return false;
}
virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize
[[nodiscard]] virtual HistoryMessage *toHistoryMessage() { // dynamic_cast optimize
return nullptr;
}
virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
[[nodiscard]] virtual const HistoryMessage *toHistoryMessage() const { // dynamic_cast optimize
return nullptr;
}
MsgId replyToId() const;
[[nodiscard]] MsgId replyToId() const;
not_null<PeerData*> author() const;
[[nodiscard]] not_null<PeerData*> author() const;
TimeId dateOriginal() const;
PeerData *senderOriginal() const;
const HiddenSenderInfo *hiddenForwardedInfo() const;
not_null<PeerData*> fromOriginal() const;
QString authorOriginal() const;
MsgId idOriginal() const;
[[nodiscard]] TimeId dateOriginal() const;
[[nodiscard]] PeerData *senderOriginal() const;
[[nodiscard]] const HiddenSenderInfo *hiddenForwardedInfo() const;
[[nodiscard]] not_null<PeerData*> fromOriginal() const;
[[nodiscard]] QString authorOriginal() const;
[[nodiscard]] MsgId idOriginal() const;
bool isEmpty() const;
[[nodiscard]] bool isEmpty() const;
MessageGroupId groupId() const;
[[nodiscard]] MessageGroupId groupId() const;
const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
[[nodiscard]] const HistoryMessageReplyMarkup *inlineReplyMarkup() const {
return const_cast<HistoryItem*>(this)->inlineReplyMarkup();
}
const ReplyKeyboard *inlineReplyKeyboard() const {
[[nodiscard]] const ReplyKeyboard *inlineReplyKeyboard() const {
return const_cast<HistoryItem*>(this)->inlineReplyKeyboard();
}
HistoryMessageReplyMarkup *inlineReplyMarkup();
ReplyKeyboard *inlineReplyKeyboard();
[[nodiscard]] HistoryMessageReplyMarkup *inlineReplyMarkup();
[[nodiscard]] ReplyKeyboard *inlineReplyKeyboard();
[[nodiscard]] ChannelData *discussionPostOriginalSender() const;
[[nodiscard]] bool isDiscussionPost() const;
[[nodiscard]] PeerData *displayFrom() const;
virtual std::unique_ptr<HistoryView::Element> createView(
[[nodiscard]] virtual std::unique_ptr<HistoryView::Element> createView(
not_null<HistoryView::ElementDelegate*> delegate) = 0;
virtual ~HistoryItem();
MsgId id;
protected:
HistoryItem(
not_null<History*> history,

View File

@ -799,8 +799,8 @@ not_null<HistoryService*> GenerateJoinedMessage(
MTPDmessage::Flags flags) {
return new HistoryService(
history,
MTPDmessage_ClientFlags(),
clientMsgId(),
MTPDmessage_ClientFlag::f_local_history_entry,
history->owner().nextLocalMessageId(),
inviteDate,
GenerateJoinedText(history, inviter),
flags);

View File

@ -634,12 +634,19 @@ HistoryWidget::HistoryWidget(
) | rpl::filter([=](const Api::SendAction &action) {
return (action.history == _history);
}) | rpl::start_with_next([=](const Api::SendAction &action) {
fastShowAtEnd(action.history);
const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
action.history->channelId(),
action.replyTo));
if (cancelReply(lastKeyboardUsed) && !action.clearDraft) {
onCloudDraftSave();
if (action.options.scheduled) {
crl::on_main(this, [=, history = action.history]{
controller->showSection(
HistoryView::ScheduledMemento(action.history));
});
} else {
fastShowAtEnd(action.history);
const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
action.history->channelId(),
action.replyTo));
if (cancelReply(lastKeyboardUsed) && !action.clearDraft) {
onCloudDraftSave();
}
}
if (action.options.handleSupportSwitch) {
handleSupportSwitch(action.history);
@ -2953,7 +2960,7 @@ void HistoryWidget::sendSilent() {
void HistoryWidget::sendScheduled() {
auto options = Api::SendOptions();
options.scheduled = INT_MAX;
options.scheduled = base::unixtime::now() + 86400;
send(options);
}
@ -4400,7 +4407,8 @@ void HistoryWidget::sendFileConfirmed(
channelId,
file->to.replyTo));
const auto newId = oldId.value_or(FullMsgId(channelId, clientMsgId()));
const auto newId = oldId.value_or(
FullMsgId(channelId, session().data().nextLocalMessageId()));
auto groupId = file->album ? file->album->groupId : uint64(0);
if (file->album) {
const auto proj = [](const SendingAlbum::Item &item) {
@ -4423,6 +4431,7 @@ void HistoryWidget::sendFileConfirmed(
const auto peer = history->peer;
auto action = Api::SendAction(history);
action.options = file->to.options;
action.clearDraft = false;
action.replyTo = file->to.replyTo;
action.generateLocal = true;
@ -4469,6 +4478,12 @@ void HistoryWidget::sendFileConfirmed(
if (groupId) {
flags |= MTPDmessage::Flag::f_grouped_id;
}
if (file->to.options.scheduled) {
flags |= MTPDmessage::Flag::f_from_scheduled;
} else {
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
}
const auto messageFromId = channelPost ? 0 : session().userId();
const auto messagePostAuthor = channelPost
? App::peerName(session().user())
@ -4489,7 +4504,7 @@ void HistoryWidget::sendFileConfirmed(
MTPMessageFwdHeader(),
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(base::unixtime::now()),
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
photo,
MTPReplyMarkup(),
@ -4525,7 +4540,7 @@ void HistoryWidget::sendFileConfirmed(
MTPMessageFwdHeader(),
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(base::unixtime::now()),
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
document,
MTPReplyMarkup(),
@ -4564,7 +4579,8 @@ void HistoryWidget::sendFileConfirmed(
MTPMessageFwdHeader(),
MTPint(),
MTP_int(file->to.replyTo),
MTP_int(base::unixtime::now()),
MTP_int(
HistoryItem::NewMessageDate(file->to.options.scheduled)),
MTP_string(caption.text),
document,
MTPReplyMarkup(),

View File

@ -3899,19 +3899,25 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
const auto &d = update.c_updateMessageID();
const auto randomId = d.vrandom_id().v;
if (const auto id = session().data().messageIdByRandomId(randomId)) {
const auto channel = id.channel;
const auto newId = d.vid().v;
if (const auto local = session().data().message(id)) {
const auto existing = session().data().message(channel, newId);
if (existing && !local->mainView()) {
const auto history = local->history();
local->destroy();
history->requestChatListMessage();
if (local->isScheduled()) {
session().data().scheduledMessages().apply(d, local);
} else {
if (existing) {
existing->destroy();
const auto channel = id.channel;
const auto existing = session().data().message(
channel,
newId);
if (existing && !local->mainView()) {
const auto history = local->history();
local->destroy();
history->requestChatListMessage();
} else {
if (existing) {
existing->destroy();
}
local->setRealId(d.vid().v);
}
local->setRealId(d.vid().v);
}
}
session().data().unregisterMessageRandomId(randomId);

View File

@ -66,8 +66,8 @@ enum class MTPDmessage_ClientFlag : uint32 {
// message has no media and only a several emoji text
f_isolated_emoji = (1U << 21),
// update this when adding new client side flags
MIN_FIELD = (1U << 21),
// message is local message existing in the message history
f_local_history_entry = (1U << 20),
};
inline constexpr bool is_flag_type(MTPDmessage_ClientFlag) { return true; }
using MTPDmessage_ClientFlags = base::flags<MTPDmessage_ClientFlag>;

View File

@ -1520,7 +1520,9 @@ void FormController::uploadEncryptedFile(
prepared->setFileData(prepared->content);
prepared->filemd5 = file.uploadData->md5checksum;
file.uploadData->fullId = FullMsgId(0, clientMsgId());
file.uploadData->fullId = FullMsgId(
0,
Auth().data().nextLocalMessageId());
Auth().uploader().upload(file.uploadData->fullId, std::move(prepared));
}

View File

@ -429,7 +429,7 @@ void ChatBackground::checkUploadWallPaper() {
const auto ready = PrepareWallPaper(_original);
const auto documentId = ready.id;
_wallPaperUploadId = FullMsgId(0, clientMsgId());
_wallPaperUploadId = FullMsgId(0, _session->data().nextLocalMessageId());
_session->uploader().uploadMedia(_wallPaperUploadId, ready);
if (_wallPaperUploadLifetime) {
return;