563 lines
16 KiB
C++
563 lines
16 KiB
C++
/*
|
|
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 "api/api_sending.h"
|
|
|
|
#include "api/api_text_entities.h"
|
|
#include "base/openssl_help.h"
|
|
#include "base/unixtime.h"
|
|
#include "data/data_document.h"
|
|
#include "data/data_photo.h"
|
|
#include "data/data_channel.h" // ChannelData::addsSignature.
|
|
#include "data/data_user.h" // UserData::name
|
|
#include "data/data_session.h"
|
|
#include "data/data_file_origin.h"
|
|
#include "data/data_histories.h"
|
|
#include "data/data_changes.h"
|
|
#include "data/stickers/data_stickers.h"
|
|
#include "history/history.h"
|
|
#include "history/history_message.h" // NewMessageFlags.
|
|
#include "chat_helpers/message_field.h" // ConvertTextTagsToEntities.
|
|
#include "chat_helpers/stickers_dice_pack.h" // DicePacks::kDiceString.
|
|
#include "ui/text/text_entity.h" // TextWithEntities.
|
|
#include "ui/item_text_options.h" // Ui::ItemTextOptions.
|
|
#include "main/main_session.h"
|
|
#include "main/main_account.h"
|
|
#include "main/main_app_config.h"
|
|
#include "storage/localimageloader.h"
|
|
#include "storage/file_upload.h"
|
|
#include "mainwidget.h"
|
|
#include "apiwrap.h"
|
|
#include "app.h"
|
|
|
|
namespace Api {
|
|
namespace {
|
|
|
|
void InnerFillMessagePostFlags(
|
|
const Api::SendOptions &options,
|
|
not_null<PeerData*> peer,
|
|
MTPDmessage::Flags &flags) {
|
|
const auto anonymousPost = peer->amAnonymous();
|
|
if (!anonymousPost) {
|
|
flags |= MTPDmessage::Flag::f_from_id;
|
|
return;
|
|
} else if (peer->asMegagroup()) {
|
|
return;
|
|
}
|
|
flags |= MTPDmessage::Flag::f_post;
|
|
// Don't display views and author of a new post when it's scheduled.
|
|
if (options.scheduled) {
|
|
return;
|
|
}
|
|
flags |= MTPDmessage::Flag::f_views;
|
|
if (peer->asChannel()->addsSignature()) {
|
|
flags |= MTPDmessage::Flag::f_post_author;
|
|
}
|
|
}
|
|
|
|
template <typename MediaData>
|
|
void SendExistingMedia(
|
|
Api::MessageToSend &&message,
|
|
not_null<MediaData*> media,
|
|
Fn<MTPInputMedia()> inputMedia,
|
|
Data::FileOrigin origin) {
|
|
const auto history = message.action.history;
|
|
const auto peer = history->peer;
|
|
const auto session = &history->session();
|
|
const auto api = &session->api();
|
|
|
|
message.action.clearDraft = false;
|
|
message.action.generateLocal = true;
|
|
api->sendAction(message.action);
|
|
|
|
const auto newId = FullMsgId(
|
|
peerToChannel(peer->id),
|
|
session->data().nextLocalMessageId());
|
|
const auto randomId = openssl::RandomValue<uint64>();
|
|
|
|
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
|
|
auto clientFlags = NewMessageClientFlags();
|
|
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
|
if (message.action.replyTo) {
|
|
flags |= MTPDmessage::Flag::f_reply_to;
|
|
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
|
}
|
|
const auto anonymousPost = peer->amAnonymous();
|
|
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
|
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
|
if (silentPost) {
|
|
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
|
}
|
|
auto messageFromId = anonymousPost ? 0 : session->userPeerId();
|
|
auto messagePostAuthor = peer->isBroadcast() ? session->user()->name : QString();
|
|
|
|
auto caption = TextWithEntities{
|
|
message.textWithTags.text,
|
|
TextUtilities::ConvertTextTagsToEntities(message.textWithTags.tags)
|
|
};
|
|
TextUtilities::Trim(caption);
|
|
auto sentEntities = EntitiesToMTP(
|
|
session,
|
|
caption.entities,
|
|
ConvertOption::SkipLocal);
|
|
if (!sentEntities.v.isEmpty()) {
|
|
sendFlags |= MTPmessages_SendMedia::Flag::f_entities;
|
|
}
|
|
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(
|
|
newId.msg,
|
|
flags,
|
|
clientFlags,
|
|
0,
|
|
replyTo,
|
|
HistoryItem::NewMessageDate(message.action.options.scheduled),
|
|
messageFromId,
|
|
messagePostAuthor,
|
|
media,
|
|
caption,
|
|
MTPReplyMarkup());
|
|
|
|
auto performRequest = [=](const auto &repeatRequest) -> void {
|
|
auto &histories = history->owner().histories();
|
|
const auto requestType = Data::Histories::RequestType::Send;
|
|
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
|
const auto usedFileReference = media->fileReference();
|
|
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
|
MTP_flags(sendFlags),
|
|
peer->input,
|
|
MTP_int(replyTo),
|
|
inputMedia(),
|
|
MTP_string(captionText),
|
|
MTP_long(randomId),
|
|
MTPReplyMarkup(),
|
|
sentEntities,
|
|
MTP_int(message.action.options.scheduled)
|
|
)).done([=](const MTPUpdates &result) {
|
|
api->applyUpdates(result, randomId);
|
|
finish();
|
|
}).fail([=](const MTP::Error &error) {
|
|
if (error.code() == 400
|
|
&& error.type().startsWith(qstr("FILE_REFERENCE_"))) {
|
|
api->refreshFileReference(origin, [=](const auto &result) {
|
|
if (media->fileReference() != usedFileReference) {
|
|
repeatRequest(repeatRequest);
|
|
} else {
|
|
api->sendMessageFail(error, peer, randomId, newId);
|
|
}
|
|
});
|
|
} else {
|
|
api->sendMessageFail(error, peer, randomId, newId);
|
|
}
|
|
finish();
|
|
}).afterRequest(history->sendRequestId
|
|
).send();
|
|
return history->sendRequestId;
|
|
});
|
|
};
|
|
performRequest(performRequest);
|
|
|
|
api->finishForwarding(message.action);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void SendExistingDocument(
|
|
Api::MessageToSend &&message,
|
|
not_null<DocumentData*> document) {
|
|
const auto inputMedia = [=] {
|
|
return MTP_inputMediaDocument(
|
|
MTP_flags(0),
|
|
document->mtpInput(),
|
|
MTPint(), // ttl_seconds
|
|
MTPstring()); // query
|
|
};
|
|
SendExistingMedia(
|
|
std::move(message),
|
|
document,
|
|
inputMedia,
|
|
document->stickerOrGifOrigin());
|
|
|
|
if (document->sticker()) {
|
|
document->owner().stickers().incrementSticker(document);
|
|
}
|
|
}
|
|
|
|
void SendExistingPhoto(
|
|
Api::MessageToSend &&message,
|
|
not_null<PhotoData*> photo) {
|
|
const auto inputMedia = [=] {
|
|
return MTP_inputMediaPhoto(
|
|
MTP_flags(0),
|
|
photo->mtpInput(),
|
|
MTPint());
|
|
};
|
|
SendExistingMedia(
|
|
std::move(message),
|
|
photo,
|
|
inputMedia,
|
|
Data::FileOrigin());
|
|
}
|
|
|
|
bool SendDice(Api::MessageToSend &message) {
|
|
const auto full = message.textWithTags.text.midRef(0).trimmed();
|
|
auto length = 0;
|
|
if (!Ui::Emoji::Find(full.data(), full.data() + full.size(), &length)
|
|
|| length != full.size()) {
|
|
return false;
|
|
}
|
|
auto &account = message.action.history->session().account();
|
|
auto &config = account.appConfig();
|
|
static const auto hardcoded = std::vector<QString>{
|
|
Stickers::DicePacks::kDiceString,
|
|
Stickers::DicePacks::kDartString,
|
|
Stickers::DicePacks::kSlotString,
|
|
Stickers::DicePacks::kFballString,
|
|
Stickers::DicePacks::kFballString + QChar(0xFE0F),
|
|
Stickers::DicePacks::kBballString,
|
|
};
|
|
const auto list = config.get<std::vector<QString>>(
|
|
"emojies_send_dice",
|
|
hardcoded);
|
|
const auto emoji = full.toString();
|
|
if (!ranges::contains(list, emoji)) {
|
|
return false;
|
|
}
|
|
const auto history = message.action.history;
|
|
const auto peer = history->peer;
|
|
const auto session = &history->session();
|
|
const auto api = &session->api();
|
|
|
|
message.textWithTags = TextWithTags();
|
|
message.action.clearDraft = false;
|
|
message.action.generateLocal = true;
|
|
api->sendAction(message.action);
|
|
|
|
const auto newId = FullMsgId(
|
|
peerToChannel(peer->id),
|
|
session->data().nextLocalMessageId());
|
|
const auto randomId = openssl::RandomValue<uint64>();
|
|
|
|
auto &histories = history->owner().histories();
|
|
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
|
|
auto clientFlags = NewMessageClientFlags();
|
|
auto sendFlags = MTPmessages_SendMedia::Flags(0);
|
|
if (message.action.replyTo) {
|
|
flags |= MTPDmessage::Flag::f_reply_to;
|
|
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
|
|
}
|
|
const auto replyHeader = NewMessageReplyHeader(message.action);
|
|
const auto anonymousPost = peer->amAnonymous();
|
|
const auto silentPost = ShouldSendSilent(peer, message.action.options);
|
|
InnerFillMessagePostFlags(message.action.options, peer, flags);
|
|
if (silentPost) {
|
|
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
|
|
}
|
|
auto messageFromId = anonymousPost ? 0 : session->userPeerId();
|
|
auto messagePostAuthor = peer->isBroadcast() ? session->user()->name : QString();
|
|
const auto replyTo = message.action.replyTo;
|
|
|
|
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);
|
|
|
|
const auto views = 1;
|
|
const auto forwards = 0;
|
|
history->addNewMessage(
|
|
MTP_message(
|
|
MTP_flags(flags),
|
|
MTP_int(newId.msg),
|
|
peerToMTP(messageFromId),
|
|
peerToMTP(history->peer->id),
|
|
MTPMessageFwdHeader(),
|
|
MTPint(), // via_bot_id
|
|
replyHeader,
|
|
MTP_int(HistoryItem::NewMessageDate(
|
|
message.action.options.scheduled)),
|
|
MTP_string(),
|
|
MTP_messageMediaDice(MTP_int(0), MTP_string(emoji)),
|
|
MTPReplyMarkup(),
|
|
MTP_vector<MTPMessageEntity>(),
|
|
MTP_int(views),
|
|
MTP_int(forwards),
|
|
MTPMessageReplies(),
|
|
MTPint(), // edit_date
|
|
MTP_string(messagePostAuthor),
|
|
MTPlong(),
|
|
//MTPMessageReactions(),
|
|
MTPVector<MTPRestrictionReason>(),
|
|
MTPint()), // ttl_period
|
|
clientFlags,
|
|
NewMessageType::Unread);
|
|
|
|
const auto requestType = Data::Histories::RequestType::Send;
|
|
histories.sendRequest(history, requestType, [=](Fn<void()> finish) {
|
|
history->sendRequestId = api->request(MTPmessages_SendMedia(
|
|
MTP_flags(sendFlags),
|
|
peer->input,
|
|
MTP_int(replyTo),
|
|
MTP_inputMediaDice(MTP_string(emoji)),
|
|
MTP_string(),
|
|
MTP_long(randomId),
|
|
MTPReplyMarkup(),
|
|
MTP_vector<MTPMessageEntity>(),
|
|
MTP_int(message.action.options.scheduled)
|
|
)).done([=](const MTPUpdates &result) {
|
|
api->applyUpdates(result, randomId);
|
|
finish();
|
|
}).fail([=](const MTP::Error &error) {
|
|
api->sendMessageFail(error, peer, randomId, newId);
|
|
finish();
|
|
}).afterRequest(history->sendRequestId
|
|
).send();
|
|
return history->sendRequestId;
|
|
});
|
|
api->finishForwarding(message.action);
|
|
return true;
|
|
}
|
|
|
|
void FillMessagePostFlags(
|
|
const Api::SendAction &action,
|
|
not_null<PeerData*> peer,
|
|
MTPDmessage::Flags &flags) {
|
|
InnerFillMessagePostFlags(action.options, peer, flags);
|
|
}
|
|
|
|
void SendConfirmedFile(
|
|
not_null<Main::Session*> session,
|
|
const std::shared_ptr<FileLoadResult> &file,
|
|
const std::optional<FullMsgId> &oldId) {
|
|
const auto isEditing = oldId.has_value();
|
|
const auto channelId = peerToChannel(file->to.peer);
|
|
|
|
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) {
|
|
return item.taskId;
|
|
};
|
|
const auto it = ranges::find(file->album->items, file->taskId, proj);
|
|
Assert(it != file->album->items.end());
|
|
|
|
it->msgId = newId;
|
|
}
|
|
file->edit = isEditing;
|
|
session->uploader().upload(newId, file);
|
|
|
|
const auto itemToEdit = isEditing
|
|
? session->data().message(newId)
|
|
: nullptr;
|
|
|
|
const auto history = session->data().history(file->to.peer);
|
|
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;
|
|
session->api().sendAction(action);
|
|
|
|
auto caption = TextWithEntities{
|
|
file->caption.text,
|
|
TextUtilities::ConvertTextTagsToEntities(file->caption.tags)
|
|
};
|
|
const auto prepareFlags = Ui::ItemTextOptions(
|
|
history,
|
|
session->user()).flags;
|
|
TextUtilities::PrepareForSending(caption, prepareFlags);
|
|
TextUtilities::Trim(caption);
|
|
auto localEntities = Api::EntitiesToMTP(session, caption.entities);
|
|
|
|
if (itemToEdit) {
|
|
if (const auto id = itemToEdit->groupId()) {
|
|
groupId = id.value;
|
|
}
|
|
}
|
|
|
|
auto flags = (isEditing ? MTPDmessage::Flags() : NewMessageFlags(peer))
|
|
| MTPDmessage::Flag::f_entities
|
|
| MTPDmessage::Flag::f_media;
|
|
auto clientFlags = NewMessageClientFlags();
|
|
if (file->to.replyTo) {
|
|
flags |= MTPDmessage::Flag::f_reply_to;
|
|
}
|
|
const auto replyHeader = NewMessageReplyHeader(action);
|
|
const auto anonymousPost = peer->amAnonymous();
|
|
const auto silentPost = ShouldSendSilent(peer, file->to.options);
|
|
Api::FillMessagePostFlags(action, peer, flags);
|
|
if (silentPost) {
|
|
flags |= MTPDmessage::Flag::f_silent;
|
|
}
|
|
if (groupId) {
|
|
flags |= MTPDmessage::Flag::f_grouped_id;
|
|
}
|
|
if (file->to.options.scheduled) {
|
|
flags |= MTPDmessage::Flag::f_from_scheduled;
|
|
// Scheduled messages have no the 'edited' badge.
|
|
flags |= MTPDmessage::Flag::f_edit_hide;
|
|
} else {
|
|
clientFlags |= MTPDmessage_ClientFlag::f_local_history_entry;
|
|
}
|
|
|
|
const auto messageFromId = anonymousPost ? 0 : session->userPeerId();
|
|
const auto messagePostAuthor = peer->isBroadcast()
|
|
? session->user()->name
|
|
: QString();
|
|
|
|
const auto views = 1;
|
|
const auto forwards = 0;
|
|
if (file->type == SendMediaType::Photo) {
|
|
const auto photoFlags = MTPDmessageMediaPhoto::Flag::f_photo | 0;
|
|
const auto photo = MTP_messageMediaPhoto(
|
|
MTP_flags(photoFlags),
|
|
file->photo,
|
|
MTPint());
|
|
|
|
const auto mtpMessage = MTP_message(
|
|
MTP_flags(flags),
|
|
MTP_int(newId.msg),
|
|
peerToMTP(messageFromId),
|
|
peerToMTP(file->to.peer),
|
|
MTPMessageFwdHeader(),
|
|
MTPint(),
|
|
replyHeader,
|
|
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
|
|
MTP_string(caption.text),
|
|
photo,
|
|
MTPReplyMarkup(),
|
|
localEntities,
|
|
MTP_int(views),
|
|
MTP_int(forwards),
|
|
MTPMessageReplies(),
|
|
MTPint(), // edit_date
|
|
MTP_string(messagePostAuthor),
|
|
MTP_long(groupId),
|
|
//MTPMessageReactions(),
|
|
MTPVector<MTPRestrictionReason>(),
|
|
MTPint()); // ttl_period
|
|
|
|
if (itemToEdit) {
|
|
itemToEdit->savePreviousMedia();
|
|
itemToEdit->applyEdition(mtpMessage.c_message());
|
|
} else {
|
|
history->addNewMessage(
|
|
mtpMessage,
|
|
clientFlags,
|
|
NewMessageType::Unread);
|
|
}
|
|
} else if (file->type == SendMediaType::File) {
|
|
const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
|
|
const auto document = MTP_messageMediaDocument(
|
|
MTP_flags(documentFlags),
|
|
file->document,
|
|
MTPint());
|
|
|
|
const auto mtpMessage = MTP_message(
|
|
MTP_flags(flags),
|
|
MTP_int(newId.msg),
|
|
peerToMTP(messageFromId),
|
|
peerToMTP(file->to.peer),
|
|
MTPMessageFwdHeader(),
|
|
MTPint(),
|
|
replyHeader,
|
|
MTP_int(HistoryItem::NewMessageDate(file->to.options.scheduled)),
|
|
MTP_string(caption.text),
|
|
document,
|
|
MTPReplyMarkup(),
|
|
localEntities,
|
|
MTP_int(views),
|
|
MTP_int(forwards),
|
|
MTPMessageReplies(),
|
|
MTPint(), // edit_date
|
|
MTP_string(messagePostAuthor),
|
|
MTP_long(groupId),
|
|
//MTPMessageReactions(),
|
|
MTPVector<MTPRestrictionReason>(),
|
|
MTPint()); // ttl_period
|
|
|
|
if (itemToEdit) {
|
|
itemToEdit->savePreviousMedia();
|
|
itemToEdit->applyEdition(mtpMessage.c_message());
|
|
} else {
|
|
history->addNewMessage(
|
|
mtpMessage,
|
|
clientFlags,
|
|
NewMessageType::Unread);
|
|
}
|
|
} else if (file->type == SendMediaType::Audio) {
|
|
if (!peer->isChannel() || peer->isMegagroup()) {
|
|
flags |= MTPDmessage::Flag::f_media_unread;
|
|
}
|
|
const auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
|
|
const auto document = MTP_messageMediaDocument(
|
|
MTP_flags(documentFlags),
|
|
file->document,
|
|
MTPint());
|
|
history->addNewMessage(
|
|
MTP_message(
|
|
MTP_flags(flags),
|
|
MTP_int(newId.msg),
|
|
peerToMTP(messageFromId),
|
|
peerToMTP(file->to.peer),
|
|
MTPMessageFwdHeader(),
|
|
MTPint(),
|
|
replyHeader,
|
|
MTP_int(
|
|
HistoryItem::NewMessageDate(file->to.options.scheduled)),
|
|
MTP_string(caption.text),
|
|
document,
|
|
MTPReplyMarkup(),
|
|
localEntities,
|
|
MTP_int(views),
|
|
MTP_int(forwards),
|
|
MTPMessageReplies(),
|
|
MTPint(), // edit_date
|
|
MTP_string(messagePostAuthor),
|
|
MTP_long(groupId),
|
|
//MTPMessageReactions(),
|
|
MTPVector<MTPRestrictionReason>(),
|
|
MTPint()), // ttl_period
|
|
clientFlags,
|
|
NewMessageType::Unread);
|
|
// Voices can't be edited.
|
|
} else {
|
|
Unexpected("Type in sendFilesConfirmed.");
|
|
}
|
|
|
|
if (isEditing) {
|
|
return;
|
|
}
|
|
|
|
session->data().sendHistoryChangeNotifications();
|
|
if (!itemToEdit) {
|
|
session->changes().historyUpdated(
|
|
history,
|
|
(action.options.scheduled
|
|
? Data::HistoryUpdate::Flag::ScheduledSent
|
|
: Data::HistoryUpdate::Flag::MessageSent));
|
|
}
|
|
}
|
|
|
|
} // namespace Api
|