Support custom emoji in the message input field.

This commit is contained in:
John Preston 2022-06-29 18:56:59 +04:00
parent cb32c3957b
commit 2499955496
17 changed files with 192 additions and 173 deletions

View file

@ -628,6 +628,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity;
messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity;
messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity;
messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity;
messageEntityCustomEmoji#d4a00ed5 offset:int length:int stickerset:InputStickerSet document_id:long = MessageEntity;
inputChannelEmpty#ee8c1e86 = InputChannel; inputChannelEmpty#ee8c1e86 = InputChannel;
inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel;
@ -742,7 +743,7 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
messages.featuredStickers#84c02310 hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers; messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
messages.recentStickers#88d37c56 hash:long packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers; messages.recentStickers#88d37c56 hash:long packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
@ -1792,9 +1793,8 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
payments.getBankCardData#2e79d779 number:string = payments.BankCardData; payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice;
payments.assignAppStoreTransaction#fec13c6 flags:# restore:flags.0?true transaction_id:string receipt:bytes = Updates; payments.assignAppStoreTransaction#d5ccfd0 flags:# restore:flags.0?true receipt:bytes = Updates;
payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates; payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates;
payments.restorePlayMarketReceipt#d164e36a receipt:bytes = Updates;
payments.canPurchasePremium#aa6a90c8 = Bool; payments.canPurchasePremium#aa6a90c8 = Bool;
payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates; payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates;
@ -1853,4 +1853,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
// LAYER 143 // LAYER 144

View file

@ -8,15 +8,61 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/stickers/data_stickers_set.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
namespace Api { namespace Api {
namespace { namespace {
using namespace TextUtilities; using namespace TextUtilities;
[[nodiscard]] QString CustomEmojiEntityData(
not_null<Main::Session*> session,
const MTPDmessageEntityCustomEmoji &data) {
return Data::SerializeCustomEmojiId({
.selfId = session->userId().bare,
.id = data.vdocument_id().v,
.set = Data::FromInputSet(data.vstickerset()),
});
}
[[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity(
not_null<Main::Session*> session,
MTPint offset,
MTPint length,
const QString &data) {
const auto parsed = Data::ParseCustomEmojiData(data);
if (!parsed.id || parsed.selfId != session->userId().bare) {
return {};
}
return MTP_messageEntityCustomEmoji(
offset,
length,
Data::InputStickerSet(parsed.set),
MTP_long(parsed.id));
}
[[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity(
not_null<Main::Session*> session,
MTPint offset,
MTPint length,
const QString &data) {
const auto parsed = MentionNameDataToFields(data);
if (!parsed.userId || parsed.selfId != session->userId().bare) {
return {};
}
return MTP_inputMessageEntityMentionName(
offset,
length,
(parsed.userId == parsed.selfId
? MTP_inputUserSelf()
: MTP_inputUser(
MTP_long(parsed.userId),
MTP_long(parsed.accessHash))));
}
} // namespace } // namespace
EntitiesInText EntitiesFromMTP( EntitiesInText EntitiesFromMTP(
@ -34,34 +80,35 @@ EntitiesInText EntitiesFromMTP(
case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCashtag: { auto &d = entity.c_messageEntityCashtag(); result.push_back({ EntityType::Cashtag, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityPhone: break; // Skipping phones. case mtpc_messageEntityPhone: break; // Skipping phones.
case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityMention: { auto &d = entity.c_messageEntityMention(); result.push_back({ EntityType::Mention, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityMentionName: { case mtpc_messageEntityMentionName: if (session) {
const auto &d = entity.c_messageEntityMentionName(); const auto &d = entity.c_messageEntityMentionName();
const auto userId = UserId(d.vuser_id()); const auto userId = UserId(d.vuser_id());
const auto data = [&] { const auto user = session->data().userLoaded(userId);
if (session) { const auto data = MentionNameDataFromFields({
if (const auto user = session->data().userLoaded(userId)) { .selfId = session->userId().bare,
return MentionNameDataFromFields({ .userId = userId.bare,
userId.bare, .accessHash = user ? user->accessHash() : 0,
user->accessHash() });
});
}
}
return MentionNameDataFromFields(userId.bare);
}();
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data }); result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
} break; } break;
case mtpc_inputMessageEntityMentionName: { case mtpc_inputMessageEntityMentionName: if (session) {
const auto &d = entity.c_inputMessageEntityMentionName(); const auto &d = entity.c_inputMessageEntityMentionName();
const auto data = [&] { const auto data = d.vuser_id().match([&](
if (session && d.vuser_id().type() == mtpc_inputUserSelf) { const MTPDinputUserSelf &) {
return MentionNameDataFromFields(session->userId().bare); return MentionNameDataFromFields({
} else if (d.vuser_id().type() == mtpc_inputUser) { .selfId = session->userId().bare,
auto &user = d.vuser_id().c_inputUser(); .userId = session->userId().bare,
const auto userId = UserId(user.vuser_id()); .accessHash = session->user()->accessHash(),
return MentionNameDataFromFields({ userId.bare, user.vaccess_hash().v }); });
} }, [&](const MTPDinputUser &data) {
return MentionNameDataFromFields({
.selfId = session->userId().bare,
.userId = UserId(data.vuser_id()).bare,
.accessHash = data.vaccess_hash().v,
});
}, [&](const auto &) {
return QString(); return QString();
}(); });
if (!data.isEmpty()) { if (!data.isEmpty()) {
result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data }); result.push_back({ EntityType::MentionName, d.voffset().v, d.vlength().v, data });
} }
@ -73,9 +120,12 @@ EntitiesInText EntitiesFromMTP(
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset().v, d.vlength().v }); } break;
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break; case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset().v, d.vlength().v, qs(d.vlanguage()) }); } break;
case mtpc_messageEntityBankCard: break; // Skipping cards. case mtpc_messageEntityBankCard: break; // Skipping cards. // #TODO entities
case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break; case mtpc_messageEntitySpoiler: { auto &d = entity.c_messageEntitySpoiler(); result.push_back({ EntityType::Spoiler, d.voffset().v, d.vlength().v }); } break;
// #TODO entities case mtpc_messageEntityCustomEmoji: if (session) {
const auto &d = entity.c_messageEntityCustomEmoji();
result.push_back({ EntityType::CustomEmoji, d.voffset().v, d.vlength().v, CustomEmojiEntityData(session, d) });
} break;
} }
} }
} }
@ -100,7 +150,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
&& entity.type() != EntityType::Pre && entity.type() != EntityType::Pre
&& entity.type() != EntityType::Spoiler && entity.type() != EntityType::Spoiler
&& entity.type() != EntityType::MentionName && entity.type() != EntityType::MentionName
&& entity.type() != EntityType::CustomUrl) { && entity.type() != EntityType::CustomUrl
&& entity.type() != EntityType::CustomEmoji) {
continue; continue;
} }
@ -114,17 +165,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break; case EntityType::Cashtag: v.push_back(MTP_messageEntityCashtag(offset, length)); break;
case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break; case EntityType::Mention: v.push_back(MTP_messageEntityMention(offset, length)); break;
case EntityType::MentionName: { case EntityType::MentionName: {
auto inputUser = [&](const QString &data) -> MTPInputUser { if (const auto valid = MentionNameEntity(session, offset, length, entity.data())) {
auto fields = MentionNameDataToFields(data); v.push_back(*valid);
if (session && fields.userId == session->userId().bare) {
return MTP_inputUserSelf();
} else if (fields.userId) {
return MTP_inputUser(MTP_long(fields.userId), MTP_long(fields.accessHash));
}
return MTP_inputUserEmpty();
}(entity.data());
if (inputUser.type() != mtpc_inputUserEmpty) {
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
} }
} break; } break;
case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break; case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
@ -135,6 +177,11 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break; case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break; case EntityType::Spoiler: v.push_back(MTP_messageEntitySpoiler(offset, length)); break;
case EntityType::CustomEmoji: {
if (const auto valid = CustomEmojiEntity(session, offset, length, entity.data())) {
v.push_back(*valid);
}
} break;
} }
} }
return MTP_vector<MTPMessageEntity>(std::move(v)); return MTP_vector<MTPMessageEntity>(std::move(v));

View file

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -48,13 +49,14 @@ using EditLinkSelection = Ui::InputField::EditLinkSelection;
constexpr auto kParseLinksTimeout = crl::time(1000); constexpr auto kParseLinksTimeout = crl::time(1000);
// For mention tags save and validate userId, ignore tags for different userId. // For mention / custom emoji tags save and validate selfId,
class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor { // ignore tags for different users.
class FieldTagMimeProcessor final {
public: public:
explicit FieldTagMimeProcessor( explicit FieldTagMimeProcessor(
not_null<Window::SessionController*> controller); not_null<Window::SessionController*> controller);
QString tagFromMimeTag(const QString &mimeTag) override; QString operator()(QStringView mimeTag);
private: private:
const not_null<Window::SessionController*> _controller; const not_null<Window::SessionController*> _controller;
@ -66,17 +68,23 @@ FieldTagMimeProcessor::FieldTagMimeProcessor(
: _controller(controller) { : _controller(controller) {
} }
QString FieldTagMimeProcessor::tagFromMimeTag(const QString &mimeTag) { QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
if (TextUtilities::IsMentionLink(mimeTag)) { const auto id = _controller->session().userId().bare;
const auto userId = _controller->session().userId(); auto all = TextUtilities::SplitTags(mimeTag);
auto match = QRegularExpression(":(\\d+)$").match(mimeTag); for (auto i = all.begin(); i != all.end();) {
if (!match.hasMatch() const auto tag = *i;
|| match.capturedView(1).toULongLong() != userId.bare) { if (TextUtilities::IsMentionLink(tag)
return QString(); && TextUtilities::MentionNameDataToFields(tag).selfId != id) {
i = all.erase(i);
} else if (Ui::InputField::IsCustomEmojiLink(tag)
&& Data::ParseCustomEmojiData(
Ui::InputField::CustomEmojiEntityData(tag)).selfId != id) {
i = all.erase(i);
} else {
++i;
} }
return mimeTag.mid(0, mimeTag.size() - match.capturedLength());
} }
return mimeTag; return TextUtilities::JoinTag(all);
} }
//bool ValidateUrl(const QString &value) { //bool ValidateUrl(const QString &value) {
@ -225,7 +233,9 @@ QString PrepareMentionTag(not_null<UserData*> user) {
return TextUtilities::kMentionTagStart return TextUtilities::kMentionTagStart
+ QString::number(user->id.value) + QString::number(user->id.value)
+ '.' + '.'
+ QString::number(user->accessHash()); + QString::number(user->accessHash())
+ ':'
+ QString::number(user->session().userId().bare);
} }
TextWithTags PrepareEditText(not_null<HistoryItem*> item) { TextWithTags PrepareEditText(not_null<HistoryItem*> item) {
@ -279,11 +289,19 @@ Fn<bool(
void InitMessageField( void InitMessageField(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<Ui::InputField*> field) { not_null<Ui::InputField*> field) {
field->setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding); field->setMinHeight(
st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight); field->setMaxHeight(st::historyComposeFieldMaxHeight);
field->setTagMimeProcessor( field->setTagMimeProcessor(FieldTagMimeProcessor(controller));
std::make_unique<FieldTagMimeProcessor>(controller)); field->setCustomEmojiFactory([=](QStringView data, Fn<void()> update) {
return controller->session().data().customEmojiManager().create(
data,
std::move(update));
}, [=] {
return controller->isGifPausedAtLeastFor(
Window::GifPauseReason::Any);
});
field->document()->setDocumentMargin(4.); field->document()->setDocumentMargin(4.);
field->setAdditionalMargin(style::ConvertScale(4) - 4); field->setAdditionalMargin(style::ConvertScale(4) - 4);

View file

@ -258,15 +258,6 @@ rpl::producer<> UiIntegration::forcePopupMenuHideRequests() {
return Core::App().passcodeLockChanges() | rpl::to_empty; return Core::App().passcodeLockChanges() | rpl::to_empty;
} }
QString UiIntegration::convertTagToMimeTag(const QString &tagId) {
if (TextUtilities::IsMentionLink(tagId)) {
if (const auto session = Core::App().activeAccount().maybeSession()) {
return tagId + ':' + QString::number(session->userId().bare);
}
}
return tagId;
}
const Ui::Emoji::One *UiIntegration::defaultEmojiVariant( const Ui::Emoji::One *UiIntegration::defaultEmojiVariant(
const Ui::Emoji::One *emoji) { const Ui::Emoji::One *emoji) {
if (!emoji || !emoji->hasVariants()) { if (!emoji || !emoji->hasVariants()) {

View file

@ -54,7 +54,6 @@ public:
const QString &url, const QString &url,
const QVariant &context) override; const QVariant &context) override;
rpl::producer<> forcePopupMenuHideRequests() override; rpl::producer<> forcePopupMenuHideRequests() override;
QString convertTagToMimeTag(const QString &tagId) override;
const Ui::Emoji::One *defaultEmojiVariant( const Ui::Emoji::One *defaultEmojiVariant(
const Ui::Emoji::One *emoji) override; const Ui::Emoji::One *emoji) override;
std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji( std::unique_ptr<Ui::Text::CustomEmoji> createCustomEmoji(

View file

@ -22,15 +22,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h" #include "apiwrap.h"
#include "data/stickers/data_stickers.h" #include "data/stickers/data_stickers.h"
#include "data/stickers/data_stickers_set.h"
namespace Data { namespace Data {
struct CustomEmojiId {
StickerSetIdentifier set;
uint64 id = 0;
};
namespace { namespace {
using SizeTag = CustomEmojiManager::SizeTag; using SizeTag = CustomEmojiManager::SizeTag;
@ -52,42 +45,6 @@ using SizeTag = CustomEmojiManager::SizeTag;
Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag."); Unexpected("SizeTag value in CustomEmojiManager-SizeFromTag.");
} }
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id) {
return QString::number(id.id)
+ '@'
+ QString::number(id.set.id)
+ ':'
+ QString::number(id.set.accessHash);
}
[[nodiscard]] QString SerializeCustomEmojiId(
not_null<DocumentData*> document) {
const auto sticker = document->sticker();
return SerializeCustomEmojiId({
sticker ? sticker->set : StickerSetIdentifier(),
document->id,
});
}
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data) {
const auto parts = data.split('@');
if (parts.size() != 2) {
return {};
}
const auto id = parts[0].toULongLong();
if (!id) {
return {};
}
const auto second = parts[1].split(':');
return {
.set = {
.id = second[0].toULongLong(),
.accessHash = second[1].toULongLong(),
},
.id = id
};
}
} // namespace } // namespace
class CustomEmojiLoader final class CustomEmojiLoader final
@ -392,7 +349,7 @@ CustomEmojiManager::CustomEmojiManager(not_null<Session*> owner)
CustomEmojiManager::~CustomEmojiManager() = default; CustomEmojiManager::~CustomEmojiManager() = default;
std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create( std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
const QString &data, QStringView data,
Fn<void()> update) { Fn<void()> update) {
const auto parsed = ParseCustomEmojiData(data); const auto parsed = ParseCustomEmojiData(data);
if (!parsed.id || !parsed.set.id) { if (!parsed.id || !parsed.set.id) {
@ -547,56 +504,46 @@ Session &CustomEmojiManager::owner() const {
return *_owner; return *_owner;
} }
void FillTestCustomEmoji( QString SerializeCustomEmojiId(const CustomEmojiId &id) {
not_null<Main::Session*> session, return QString::number(id.set.id)
TextWithEntities &text) { + '.'
auto &sets = session->data().stickers().sets(); + QString::number(id.set.accessHash)
auto recentIt = sets.find(Data::Stickers::CloudRecentSetId); + ':'
const auto pack = &session->emojiStickersPack(); + QString::number(id.selfId)
const auto begin = text.text.constData(), end = begin + text.text.size(); + '/'
for (auto ch = begin; ch != end;) { + QString::number(id.id);
auto length = 0; }
if (const auto emoji = Ui::Emoji::Find(ch, end, &length)) {
auto replace = (DocumentData*)nullptr; QString SerializeCustomEmojiId(not_null<DocumentData*> document) {
if (recentIt != sets.end()) { const auto sticker = document->sticker();
for (const auto document : recentIt->second->stickers) { return SerializeCustomEmojiId({
if (const auto sticker = document->sticker()) { .selfId = document->session().userId().bare,
if (Ui::Emoji::Find(sticker->alt) == emoji) { .id = document->id,
replace = document; .set = sticker ? sticker->set : StickerSetIdentifier(),
} });
} }
}
} CustomEmojiId ParseCustomEmojiData(QStringView data) {
if (const auto found = pack->stickerForEmoji(emoji)) { const auto components = data.split('.');
Assert(found.document->sticker() != nullptr); if (components.size() != 2) {
if (!replace && found.document->sticker()->set.id) { return {};
replace = found.document;
}
}
if (replace) {
text.entities.push_back({
EntityType::CustomEmoji,
(ch - begin),
length,
SerializeCustomEmojiId({
replace->sticker()->set,
replace->id,
}),
});
}
ch += length;
} else if (ch->isHighSurrogate()
&& (ch + 1 != end)
&& (ch + 1)->isLowSurrogate()) {
ch += 2;
} else {
++ch;
}
} }
ranges::stable_sort( const auto parts = components[1].split(':');
text.entities, if (parts.size() != 2) {
ranges::less(), return {};
&EntityInText::offset); }
const auto endings = parts[1].split('/');
if (endings.size() != 2) {
return {};
}
return {
.selfId = endings[0].toULongLong(),
.id = endings[1].toULongLong(),
.set = {
.id = components[0].toULongLong(),
.accessHash = parts[0].toULongLong(),
},
};
} }
} // namespace Data } // namespace Data

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/stickers/data_stickers_set.h"
#include "ui/text/custom_emoji_instance.h" #include "ui/text/custom_emoji_instance.h"
#include "base/timer.h" #include "base/timer.h"
#include "base/weak_ptr.h" #include "base/weak_ptr.h"
@ -20,9 +21,14 @@ class Session;
namespace Data { namespace Data {
class Session; class Session;
struct CustomEmojiId;
class CustomEmojiLoader; class CustomEmojiLoader;
struct CustomEmojiId {
uint64 selfId = 0;
uint64 id = 0;
StickerSetIdentifier set;
};
class CustomEmojiManager final : public base::has_weak_ptr { class CustomEmojiManager final : public base::has_weak_ptr {
public: public:
enum class SizeTag { enum class SizeTag {
@ -34,7 +40,7 @@ public:
~CustomEmojiManager(); ~CustomEmojiManager();
[[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create( [[nodiscard]] std::unique_ptr<Ui::Text::CustomEmoji> create(
const QString &data, QStringView data,
Fn<void()> update); Fn<void()> update);
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
@ -76,8 +82,9 @@ private:
}; };
void FillTestCustomEmoji( [[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id);
not_null<Main::Session*> session, [[nodiscard]] QString SerializeCustomEmojiId(
TextWithEntities &text); not_null<DocumentData*> document);
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data);
} // namespace Data } // namespace Data

View file

@ -155,7 +155,8 @@ std::vector<TextPart> ParseText(
[](const MTPDmessageEntityBlockquote&) { [](const MTPDmessageEntityBlockquote&) {
return Type::Blockquote; }, return Type::Blockquote; },
[](const MTPDmessageEntityBankCard&) { return Type::BankCard; }, [](const MTPDmessageEntityBankCard&) { return Type::BankCard; },
[](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; }); [](const MTPDmessageEntitySpoiler&) { return Type::Spoiler; },
[](const MTPDmessageEntityCustomEmoji&) { return Type::CustomEmoji; });
part.text = mid(start, length); part.text = mid(start, length);
part.additional = entity.match( part.additional = entity.match(
[](const MTPDmessageEntityPre &data) { [](const MTPDmessageEntityPre &data) {
@ -164,6 +165,8 @@ std::vector<TextPart> ParseText(
return ParseString(data.vurl()); return ParseString(data.vurl());
}, [](const MTPDmessageEntityMentionName &data) { }, [](const MTPDmessageEntityMentionName &data) {
return NumberToString(data.vuser_id().v); return NumberToString(data.vuser_id().v);
}, [](const MTPDmessageEntityCustomEmoji &data) {
return NumberToString(data.vdocument_id().v);
}, [](const auto &) { return Utf8String(); }); }, [](const auto &) { return Utf8String(); });
result.push_back(std::move(part)); result.push_back(std::move(part));

View file

@ -549,6 +549,7 @@ struct TextPart {
Blockquote, Blockquote,
BankCard, BankCard,
Spoiler, Spoiler,
CustomEmoji,
}; };
Type type = Type::Text; Type type = Type::Text;
Utf8String text; Utf8String text;

View file

@ -274,6 +274,9 @@ QByteArray FormatText(
"onclick=\"ShowSpoiler(this)\">" "onclick=\"ShowSpoiler(this)\">"
"<span aria-hidden=\"true\">" "<span aria-hidden=\"true\">"
+ text + "</span></span>"; + text + "</span></span>";
case Type::CustomEmoji: return SerializeString("{custom_emoji}")
+ text // TODO custom-emoji
+ SerializeString("{/custom_emoji}");
} }
Unexpected("Type in text entities serialization."); Unexpected("Type in text entities serialization.");
}) | ranges::to_vector); }) | ranges::to_vector);

View file

@ -181,17 +181,21 @@ QByteArray SerializeText(
case Type::Blockquote: return "blockquote"; case Type::Blockquote: return "blockquote";
case Type::BankCard: return "bank_card"; case Type::BankCard: return "bank_card";
case Type::Spoiler: return "spoiler"; case Type::Spoiler: return "spoiler";
case Type::CustomEmoji: return "custom_emoji";
} }
Unexpected("Type in SerializeText."); Unexpected("Type in SerializeText.");
}(); }();
const auto additionalName = (part.type == Type::MentionName) const auto additionalName = (part.type == Type::MentionName)
? "user_id" ? "user_id"
: (part.type == Type::CustomEmoji)
? "document_id"
: (part.type == Type::Pre) : (part.type == Type::Pre)
? "language" ? "language"
: (part.type == Type::TextUrl) : (part.type == Type::TextUrl)
? "href" ? "href"
: "none"; : "none";
const auto additionalValue = (part.type == Type::MentionName) const auto additionalValue = (part.type == Type::MentionName
|| part.type == Type::CustomEmoji)
? part.additional ? part.additional
: (part.type == Type::Pre || part.type == Type::TextUrl) : (part.type == Type::Pre || part.type == Type::TextUrl)
? SerializeString(part.additional) ? SerializeString(part.additional)

View file

@ -337,7 +337,6 @@ HistoryMessage::HistoryMessage(
&history->session(), &history->session(),
data.ventities().value_or_empty()) data.ventities().value_or_empty())
}; };
Data::FillTestCustomEmoji(&history->session(), textWithEntities);
setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities)); setText(_media ? textWithEntities : EnsureNonEmpty(textWithEntities));
if (const auto groupedId = data.vgrouped_id()) { if (const auto groupedId = data.vgrouped_id()) {
setGroupId( setGroupId(

View file

@ -25,7 +25,7 @@ struct LoadedPart {
class Loader { class Loader {
public: public:
static constexpr auto kPartSize = 128 * 1024; static constexpr auto kPartSize = int64(128 * 1024);
[[nodiscard]] virtual Storage::Cache::Key baseCacheKey() const = 0; [[nodiscard]] virtual Storage::Cache::Key baseCacheKey() const = 0;
[[nodiscard]] virtual int64 size() const = 0; [[nodiscard]] virtual int64 size() const = 0;

View file

@ -127,7 +127,7 @@ bytes::const_span ParseCachedMap(
if (size > maxSize) { if (size > maxSize) {
return {}; return {};
} }
for (auto offset = 0; offset < size; offset += kPartSize) { for (auto offset = int64(); offset < size; offset += kPartSize) {
const auto part = data.subspan( const auto part = data.subspan(
offset, offset,
std::min(kPartSize, size - offset)); std::min(kPartSize, size - offset));

View file

@ -107,7 +107,7 @@ void StreamedFileDownloader::requestPart() {
++_partsRequested; ++_partsRequested;
} }
QByteArray StreamedFileDownloader::readLoadedPart(int offset) { QByteArray StreamedFileDownloader::readLoadedPart(int64 offset) {
Expects(offset >= 0 && offset < _fullSize); Expects(offset >= 0 && offset < _fullSize);
Expects(!(offset % kPartSize)); Expects(!(offset % kPartSize));

View file

@ -45,7 +45,7 @@ public:
uint64 objId() const override; uint64 objId() const override;
Data::FileOrigin fileOrigin() const override; Data::FileOrigin fileOrigin() const override;
QByteArray readLoadedPart(int offset); QByteArray readLoadedPart(int64 offset);
private: private:
void startLoading() override; void startLoading() override;

@ -1 +1 @@
Subproject commit 32cf0968a14f7ef7756834f72500fc58fef50c6d Subproject commit 6bd7518109850d650a174b74e5582367555390da