File indexing completed on 2024-05-12 16:25:49

0001 /*
0002 
0003  * SPDX-FileCopyrightText: 2016 Riccardo Iaconelli <riccardo@kde.org>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  *
0007  */
0008 
0009 #include <QDataStream>
0010 #include <QFile>
0011 #include <QJsonDocument>
0012 #include <QModelIndex>
0013 
0014 #include "emoticons/emojimanager.h"
0015 #include "loadrecenthistorymanager.h"
0016 #include "messagesmodel.h"
0017 #include "rocketchataccount.h"
0018 #include "room.h"
0019 #include "ruqola_debug.h"
0020 #include "textconverter.h"
0021 #include "utils.h"
0022 
0023 #include <KLocalizedString>
0024 
0025 MessagesModel::MessagesModel(const QString &roomID, RocketChatAccount *account, Room *room, QObject *parent)
0026     : QAbstractListModel(parent)
0027     , mRoomId(roomID)
0028     , mRocketChatAccount(account)
0029     , mRoom(room)
0030     , mLoadRecentHistoryManager(new LoadRecentHistoryManager)
0031 {
0032     qCDebug(RUQOLA_LOG) << "Creating message Model";
0033     if (mRoom) {
0034         connect(mRoom, &Room::rolesChanged, this, &MessagesModel::refresh);
0035         connect(mRoom, &Room::ignoredUsersChanged, this, &MessagesModel::refresh);
0036         connect(mRoom, &Room::highlightsWordChanged, this, &MessagesModel::refresh);
0037     }
0038 }
0039 
0040 MessagesModel::~MessagesModel() = default;
0041 
0042 void MessagesModel::activate()
0043 {
0044     if (mRocketChatAccount) {
0045         connect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessagesModel::slotFileDownloaded);
0046     }
0047 }
0048 
0049 void MessagesModel::deactivate()
0050 {
0051     if (mRocketChatAccount) {
0052         disconnect(mRocketChatAccount, &RocketChatAccount::fileDownloaded, this, &MessagesModel::slotFileDownloaded);
0053     }
0054 }
0055 
0056 Message MessagesModel::findLastMessageBefore(const QString &messageId, const std::function<bool(const Message &)> &predicate) const
0057 {
0058     auto it = findMessage(messageId); // if it == end, we'll start from there
0059     auto rit = QVector<Message>::const_reverse_iterator(it); // this points to *it-1 already
0060     rit = std::find_if(rit, mAllMessages.rend(), predicate);
0061     return rit == mAllMessages.rend() ? Message() : *rit;
0062 }
0063 
0064 Message MessagesModel::findNextMessageAfter(const QString &messageId, const std::function<bool(const Message &)> &predicate) const
0065 {
0066     auto it = findMessage(messageId);
0067     if (it == mAllMessages.end()) {
0068         return Message(); // no wrap around, otherwise Alt+Key_Up would edit the oldest msg right away
0069     } else {
0070         ++it;
0071     }
0072     it = std::find_if(it, mAllMessages.end(), predicate);
0073     return it == mAllMessages.end() ? Message() : *it;
0074 }
0075 
0076 Message MessagesModel::findMessageById(const QString &messageId) const
0077 {
0078     auto it = findMessage(messageId);
0079     return it == mAllMessages.end() ? Message() : *it;
0080 }
0081 
0082 QModelIndex MessagesModel::indexForMessage(const QString &messageId) const
0083 {
0084     auto it = findMessage(messageId);
0085     if (it == mAllMessages.end()) {
0086         return {};
0087     }
0088     const QModelIndex idx = createIndex(std::distance(mAllMessages.begin(), it), 0);
0089     return idx;
0090 }
0091 
0092 QString MessagesModel::messageIdFromIndex(int rowIndex)
0093 {
0094     if (rowIndex >= 0 && rowIndex < mAllMessages.count()) {
0095         return mAllMessages.at(rowIndex).messageId();
0096     }
0097     return {};
0098 }
0099 
0100 void MessagesModel::refresh()
0101 {
0102     beginResetModel();
0103     endResetModel();
0104 }
0105 
0106 qint64 MessagesModel::lastTimestamp() const
0107 {
0108     if (!mAllMessages.isEmpty()) {
0109         // qCDebug(RUQOLA_LOG) << "returning timestamp" << mAllMessages.last().timeStamp();
0110         return mAllMessages.first().timeStamp();
0111     } else {
0112         return 0;
0113     }
0114 }
0115 
0116 int MessagesModel::rowCount(const QModelIndex &parent) const
0117 {
0118     Q_UNUSED(parent)
0119     return mAllMessages.size();
0120 }
0121 
0122 static bool compareTimeStamps(const Message &lhs, const Message &rhs)
0123 {
0124     return lhs.timeStamp() < rhs.timeStamp();
0125 }
0126 
0127 void MessagesModel::addMessage(const Message &message)
0128 {
0129     auto it = std::upper_bound(mAllMessages.begin(), mAllMessages.end(), message, compareTimeStamps);
0130 
0131     auto emitChanged = [this](int rowNumber, const QVector<int> &roles = QVector<int>()) {
0132         const QModelIndex index = createIndex(rowNumber, 0);
0133         Q_EMIT dataChanged(index, index, roles);
0134     };
0135 
0136     // When we have 1 element.
0137     if (mAllMessages.count() == 1 && (*mAllMessages.begin()).messageId() == message.messageId()) {
0138         (*mAllMessages.begin()) = message;
0139         qCDebug(RUQOLA_LOG) << "Update first message";
0140         emitChanged(0);
0141     } else if (((it) != mAllMessages.begin() && (*(it - 1)).messageId() == message.messageId())) {
0142         qCDebug(RUQOLA_LOG) << "Update message";
0143         if (message.pendingMessage()) {
0144             // If we already have a message and we must add pending message it's that server
0145             // send quickly new message => replace not it by a pending message
0146             return;
0147         }
0148         (*(it - 1)) = message;
0149         emitChanged(std::distance(mAllMessages.begin(), it - 1), {OriginalMessageOrAttachmentDescription});
0150     } else {
0151         const int pos = it - mAllMessages.begin();
0152         beginInsertRows(QModelIndex(), pos, pos);
0153         mAllMessages.insert(it, message);
0154         endInsertRows();
0155     }
0156 }
0157 
0158 void MessagesModel::addMessages(const QVector<Message> &messages, bool insertListMessages)
0159 {
0160     if (messages.isEmpty()) {
0161         return;
0162     }
0163     if (mAllMessages.isEmpty()) {
0164         beginInsertRows(QModelIndex(), 0, messages.count() - 1);
0165         mAllMessages = messages;
0166         std::sort(mAllMessages.begin(), mAllMessages.end(), compareTimeStamps);
0167         endInsertRows();
0168     } else if (insertListMessages) {
0169         beginResetModel();
0170         mAllMessages += messages;
0171         std::sort(mAllMessages.begin(), mAllMessages.end(), compareTimeStamps);
0172         endResetModel();
0173     } else {
0174         // TODO optimize this case as well?
0175         for (const Message &message : messages) {
0176             addMessage(message);
0177         }
0178     }
0179 }
0180 
0181 QVariant MessagesModel::data(const QModelIndex &index, int role) const
0182 {
0183     if (!index.isValid()) {
0184         qCWarning(RUQOLA_LOG) << "ERROR: invalid index";
0185         return {};
0186     }
0187     const int idx = index.row();
0188     const Message &message = mAllMessages.at(idx);
0189 
0190     switch (role) {
0191     case MessagesModel::MessagePointer:
0192         return QVariant::fromValue(&message);
0193     case MessagesModel::Username:
0194         return message.username();
0195     case Qt::AccessibleTextRole:
0196     case MessagesModel::OriginalMessage:
0197         return message.text();
0198     case MessagesModel::DateTimeUtc:
0199         return QDateTime::fromMSecsSinceEpoch(message.timeStamp(), Qt::UTC).toString(Qt::ISODateWithMs);
0200     case MessagesModel::MessageConvertedText:
0201         return convertedText(message, mSearchText);
0202     case MessagesModel::Timestamp:
0203         return message.displayTime();
0204     case MessagesModel::UserId:
0205         return message.userId();
0206     case MessagesModel::SystemMessageType:
0207         return message.systemMessageType();
0208     case MessagesModel::MessageId:
0209         return message.messageId();
0210     case MessagesModel::Alias:
0211         return message.alias();
0212     case MessagesModel::MessageType:
0213         return message.messageType();
0214     case MessagesModel::Avatar:
0215         return message.avatar();
0216     case MessagesModel::EditedAt:
0217         return message.editedAt();
0218     case MessagesModel::EditedByUserName:
0219         return message.editedByUsername();
0220     case MessagesModel::EditedToolTip:
0221         return i18n("Edited at %1 by %2", message.editedDisplayTime(), message.editedByUsername());
0222     case MessagesModel::Attachments: {
0223         QVariantList lst;
0224         lst.reserve(message.attachments().count());
0225         const auto attaches = message.attachments();
0226         for (const MessageAttachment &att : attaches) {
0227             lst.append(QVariant::fromValue(att));
0228         }
0229         return lst;
0230     }
0231     case MessagesModel::Urls: {
0232         QVariantList lst;
0233         lst.reserve(message.urls().count());
0234         const auto urls = message.urls();
0235         for (const MessageUrl &url : urls) {
0236             lst.append(QVariant::fromValue(url));
0237         }
0238         return lst;
0239     }
0240     case MessagesModel::Date: {
0241         const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp());
0242         return currentDate.date().toString();
0243     }
0244     case MessagesModel::DateDiffersFromPrevious:
0245         if (idx > 0) {
0246             const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp(), Qt::UTC);
0247             const Message &previousMessage = mAllMessages.at(idx - 1);
0248             const QDateTime previousDate = QDateTime::fromMSecsSinceEpoch(previousMessage.timeStamp(), Qt::UTC);
0249             return currentDate.date() != previousDate.date();
0250         }
0251         return true; // show date at the top
0252     case MessagesModel::CanEditMessage:
0253         return mRocketChatAccount->isMessageEditable(message); // && mRoom && mRoom->hasPermission(QStringLiteral("edit-message"));
0254     case MessagesModel::CanDeleteMessage:
0255         return mRocketChatAccount->isMessageDeletable(message);
0256     case MessagesModel::Starred:
0257         return message.isStarred();
0258     case MessagesModel::UsernameUrl: {
0259         const QString username = message.username();
0260         if (username.isEmpty()) {
0261             return {};
0262         }
0263         return QStringLiteral("<a href=\'ruqola:/user/%1\'>@%1</a>").arg(message.username());
0264     }
0265     case MessagesModel::Roles: {
0266         const QString str = roomRoles(message.userId()).join(QLatin1Char(','));
0267         return str;
0268     }
0269     case MessagesModel::Reactions: {
0270         QVariantList lst;
0271         const auto reactions = message.reactions().reactions();
0272         lst.reserve(reactions.count());
0273         for (const Reaction &react : reactions) {
0274             // Convert reactions
0275             lst.append(QVariant::fromValue(react));
0276         }
0277         return lst;
0278     }
0279     case MessagesModel::Ignored:
0280         return mRoom && mRoom->userIsIgnored(message.userId());
0281     case MessagesModel::DirectChannels:
0282         return mRoom && (mRoom->channelType() == Room::RoomType::Direct);
0283     case MessagesModel::Pinned:
0284         return message.messagePinned().pinned();
0285     case MessagesModel::DiscussionCount:
0286         return message.discussionCount();
0287     case MessagesModel::DiscussionRoomId:
0288         return message.discussionRoomId();
0289     case MessagesModel::DiscussionLastMessage:
0290         return message.discussionLastMessage();
0291     case MessagesModel::ThreadCount:
0292         return message.threadCount();
0293     case MessagesModel::ThreadLastMessage:
0294         return message.threadLastMessage();
0295     case MessagesModel::ThreadMessageId:
0296         return message.threadMessageId();
0297     case MessagesModel::ThreadMessagePreview:
0298         return threadMessagePreview(message.threadMessageId());
0299     case MessagesModel::ThreadMessageFollowed:
0300         return threadMessageFollowed(message.threadMessageId());
0301     case MessagesModel::ThreadMessage: {
0302         const Message tm = threadMessage(message.threadMessageId());
0303         return QVariant::fromValue(tm);
0304     }
0305     case MessagesModel::Groupable:
0306         return message.groupable();
0307     case MessagesModel::ShowTranslatedMessage:
0308         return message.showTranslatedMessage();
0309     case MessagesModel::DisplayAttachment:
0310         return {}; // Unused.
0311     case MessagesModel::DisplayUrlPreview:
0312         return {}; // Unused.
0313     case MessagesModel::DisplayLastSeenMessage:
0314         if (idx > 0) {
0315             if (mRoom) {
0316                 const QDateTime currentDate = QDateTime::fromMSecsSinceEpoch(message.timeStamp(), Qt::UTC);
0317                 const QDateTime lastSeenDate = QDateTime::fromMSecsSinceEpoch(mRoom->lastSeenAt(), Qt::UTC);
0318                 // qDebug() << " lastSeeDate" << lastSeeDate;
0319                 if (currentDate > lastSeenDate) {
0320                     const Message &previousMessage = mAllMessages.at(idx - 1);
0321                     const QDateTime previewMessageDate = QDateTime::fromMSecsSinceEpoch(previousMessage.timeStamp(), Qt::UTC);
0322                     const bool result = (previewMessageDate <= lastSeenDate);
0323                     return result;
0324                 }
0325             }
0326         }
0327         return false;
0328     case MessagesModel::Emoji:
0329         return message.emoji();
0330     case MessagesModel::AvatarInfo:
0331         return QVariant::fromValue(message.avatarInfo());
0332     case MessagesModel::PendingMessage:
0333         return message.pendingMessage();
0334     case MessagesModel::ShowIgnoredMessage:
0335         return message.showIgnoredMessage();
0336     case MessagesModel::MessageInEditMode:
0337         return message.isEditingMode();
0338     case MessagesModel::HoverHighLight:
0339         return message.hoverHighlight();
0340     case MessagesModel::LocalTranslation:
0341         return message.localTranslation();
0342     case MessagesModel::OriginalMessageOrAttachmentDescription:
0343         return message.originalMessageOrAttachmentDescription();
0344     case MessagesModel::GoToMessageBackgroundColor:
0345         return message.goToMessageBackgroundColor();
0346     }
0347 
0348     return {};
0349 }
0350 
0351 QString MessagesModel::convertedText(const Message &message, const QString &searchedText) const
0352 {
0353     if (message.messageType() == Message::System) {
0354         return message.systemMessageText();
0355         //    } else if (message.messageType() == Message::VideoConference) {
0356         //        return QStringLiteral("");
0357     } else {
0358         QStringList highlightWords;
0359         if (mRoom) {
0360             if (mRoom->channelType() != Room::RoomType::Direct) { // We can't ignore message but we can block user in direct message
0361                 if (mRoom->userIsIgnored(message.userId()) && !message.showIgnoredMessage()) {
0362                     return QString(QStringLiteral("<i>") + i18n("Ignored Message") + QStringLiteral("</i>"));
0363                 }
0364             }
0365             highlightWords = mRoom->highlightsWord();
0366         }
0367         const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString();
0368         const QStringList highlightWordsLst = mRocketChatAccount ? mRocketChatAccount->highlightWords() : highlightWords;
0369         return convertMessageText(message, userName, highlightWordsLst, searchedText);
0370     }
0371 }
0372 
0373 bool MessagesModel::setData(const QModelIndex &index, const QVariant &value, int role)
0374 {
0375     if (!index.isValid()) {
0376         qCWarning(RUQOLA_LOG) << "ERROR: invalid index";
0377         return false;
0378     }
0379     const int idx = index.row();
0380     Message &message = mAllMessages[idx];
0381 
0382     switch (role) {
0383     case MessagesModel::DisplayAttachment: {
0384         const auto visibility = value.value<AttachmentAndUrlPreviewVisibility>();
0385         auto attachments = message.attachments();
0386         for (int i = 0, total = attachments.count(); i < total; ++i) {
0387             const MessageAttachment att = attachments.at(i);
0388             if (att.attachmentId() == visibility.ElementId) {
0389                 MessageAttachment changeAttachment = attachments.takeAt(i);
0390                 changeAttachment.setShowAttachment(visibility.show);
0391                 attachments.insert(i, changeAttachment);
0392                 break;
0393             }
0394         }
0395         message.setAttachments(attachments);
0396         Q_EMIT dataChanged(index, index, {MessagesModel::DisplayAttachment});
0397         return true;
0398     }
0399     case MessagesModel::DisplayUrlPreview: {
0400         const auto visibility = value.value<AttachmentAndUrlPreviewVisibility>();
0401         auto urls = message.urls();
0402         for (int i = 0, total = urls.count(); i < total; ++i) {
0403             const MessageUrl att = urls.at(i);
0404             if (att.urlId() == visibility.ElementId) {
0405                 MessageUrl changeUrlPreview = urls.takeAt(i);
0406                 changeUrlPreview.setShowPreview(visibility.show);
0407                 urls.insert(i, changeUrlPreview);
0408                 break;
0409             }
0410         }
0411         message.setUrls(urls);
0412         Q_EMIT dataChanged(index, index, {MessagesModel::DisplayUrlPreview});
0413         return true;
0414     }
0415     case MessagesModel::ShowTranslatedMessage:
0416         message.setShowTranslatedMessage(value.toBool());
0417         Q_EMIT dataChanged(index, index, {MessagesModel::ShowTranslatedMessage});
0418         return true;
0419     case MessagesModel::ShowIgnoredMessage:
0420         message.setShowIgnoredMessage(value.toBool());
0421         Q_EMIT dataChanged(index, index, {MessagesModel::ShowIgnoredMessage});
0422         return true;
0423     case MessagesModel::MessageInEditMode:
0424         message.setIsEditingMode(value.toBool());
0425         Q_EMIT dataChanged(index, index, {MessagesModel::MessageInEditMode});
0426         return true;
0427     case MessagesModel::HoverHighLight:
0428         message.setHoverHighlight(value.toBool());
0429         Q_EMIT dataChanged(index, index, {MessagesModel::HoverHighLight});
0430         return true;
0431     case MessagesModel::LocalTranslation:
0432         message.setLocalTranslation(value.toString());
0433         Q_EMIT dataChanged(index, index, {MessagesModel::LocalTranslation});
0434         return true;
0435     case MessagesModel::GoToMessageBackgroundColor:
0436         message.setGoToMessageBackgroundColor(value.value<QColor>());
0437         Q_EMIT dataChanged(index, index, {MessagesModel::GoToMessageBackgroundColor});
0438         return true;
0439     }
0440     return false;
0441 }
0442 
0443 QStringList MessagesModel::roomRoles(const QString &userId) const
0444 {
0445     if (mRoom) {
0446         return mRoom->rolesForUserId(userId);
0447     }
0448     return {};
0449 }
0450 
0451 QString MessagesModel::convertMessageText(const Message &message, const QString &userName, const QStringList &highlightWords, const QString &searchedText) const
0452 {
0453     QString messageStr = message.text();
0454     EmojiManager *emojiManager = nullptr;
0455     MessageCache *messageCache = nullptr;
0456     int maximumRecursiveQuotedText = -1;
0457     if (mRocketChatAccount) {
0458         emojiManager = mRocketChatAccount->emojiManager();
0459         messageCache = mRocketChatAccount->messageCache();
0460         maximumRecursiveQuotedText = mRocketChatAccount->ruqolaServerConfig()->messageQuoteChainLimit();
0461         if (mRocketChatAccount->hasAutotranslateSupport()) {
0462             if (message.showTranslatedMessage()) {
0463                 if (mRoom && mRoom->autoTranslate() && !mRoom->autoTranslateLanguage().isEmpty()) {
0464                     const QString messageTranslation = message.messageTranslation().translatedStringFromLanguage(mRoom->autoTranslateLanguage());
0465                     if (!messageTranslation.isEmpty()) {
0466                         messageStr = messageTranslation;
0467                     } else if (!message.localTranslation().isEmpty()) {
0468                         messageStr = message.localTranslation();
0469                     }
0470                 }
0471             }
0472         } else if (message.showTranslatedMessage() && !message.localTranslation().isEmpty()) {
0473             messageStr = message.localTranslation();
0474         }
0475     }
0476 
0477     QString needUpdateMessageId;
0478     const TextConverter::ConvertMessageTextSettings settings(messageStr,
0479                                                              userName,
0480                                                              mAllMessages,
0481                                                              highlightWords,
0482                                                              emojiManager,
0483                                                              messageCache,
0484                                                              message.mentions(),
0485                                                              message.channels(),
0486                                                              searchedText,
0487                                                              maximumRecursiveQuotedText);
0488 
0489     int recursiveIndex = 0;
0490     return TextConverter::convertMessageText(settings, needUpdateMessageId, recursiveIndex);
0491 }
0492 
0493 void MessagesModel::setRoomId(const QString &roomId)
0494 {
0495     mRoomId = roomId;
0496 }
0497 
0498 bool MessagesModel::isEmpty() const
0499 {
0500     return mAllMessages.isEmpty();
0501 }
0502 
0503 void MessagesModel::clear()
0504 {
0505     mSearchText.clear();
0506     if (rowCount() != 0) {
0507         beginResetModel();
0508         mAllMessages.clear();
0509         endResetModel();
0510     }
0511 }
0512 
0513 void MessagesModel::changeShowOriginalMessage(const QString &messageId, bool showOriginal)
0514 {
0515     Q_UNUSED(showOriginal)
0516     auto it = findMessage(messageId);
0517     if (it != mAllMessages.end()) {
0518         // TODO implement it
0519     }
0520 }
0521 
0522 void MessagesModel::slotFileDownloaded(const QString &filePath, const QUrl &cacheImageUrl)
0523 {
0524     auto matchesFilePath = [&](const QVector<MessageAttachment> &msgAttachments) {
0525         return std::find_if(msgAttachments.begin(),
0526                             msgAttachments.end(),
0527                             [&](const MessageAttachment &attach) {
0528                                 // Transform link() the way RocketChatCache::downloadFile does it
0529                                 return mRocketChatAccount->urlForLink(attach.imageUrlPreview()).path() == filePath;
0530                             })
0531             != msgAttachments.end();
0532     };
0533     auto it = std::find_if(mAllMessages.begin(), mAllMessages.end(), [&](const Message &msg) {
0534         if (!msg.attachments().isEmpty()) {
0535             return matchesFilePath(msg.attachments());
0536         }
0537         auto *emojiManager = mRocketChatAccount->emojiManager();
0538         const auto reactions = msg.reactions().reactions();
0539         for (const Reaction &reaction : reactions) {
0540             const QString fileName = emojiManager->customEmojiFileName(reaction.reactionName());
0541             if (!fileName.isEmpty() && mRocketChatAccount->urlForLink(fileName).path() == filePath) {
0542                 return true;
0543             }
0544         }
0545         const Utils::AvatarInfo info = msg.avatarInfo();
0546         const QUrl iconUrlStr = QUrl(mRocketChatAccount->avatarUrl(info));
0547         if (!iconUrlStr.isEmpty()) {
0548             if (iconUrlStr == cacheImageUrl) {
0549                 return true;
0550             }
0551         }
0552         return false;
0553     });
0554     if (it != mAllMessages.end()) {
0555         const QModelIndex idx = createIndex(std::distance(mAllMessages.begin(), it), 0);
0556         Q_EMIT dataChanged(idx, idx);
0557     } else {
0558         qCWarning(RUQOLA_LOG) << "Attachment not found:" << filePath;
0559     }
0560 }
0561 
0562 void MessagesModel::deleteMessage(const QString &messageId)
0563 {
0564     auto it = findMessage(messageId);
0565     if (it != mAllMessages.end()) {
0566         const int i = std::distance(mAllMessages.begin(), it);
0567         beginRemoveRows(QModelIndex(), i, i);
0568         mAllMessages.erase(it);
0569         endRemoveRows();
0570     }
0571 }
0572 
0573 qint64 MessagesModel::generateNewStartTimeStamp(qint64 lastTimeStamp)
0574 {
0575     return mLoadRecentHistoryManager->generateNewStartTimeStamp(lastTimeStamp);
0576 }
0577 
0578 Message MessagesModel::threadMessage(const QString &threadMessageId) const
0579 {
0580     if (!threadMessageId.isEmpty()) {
0581         auto it = findMessage(threadMessageId);
0582         if (it != mAllMessages.cend()) {
0583             return (*it);
0584         } else {
0585             qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one
0586         }
0587     }
0588     return Message{};
0589 }
0590 
0591 QString MessagesModel::threadMessagePreview(const QString &threadMessageId) const
0592 {
0593     if (!threadMessageId.isEmpty()) {
0594         auto it = findMessage(threadMessageId);
0595         if (it != mAllMessages.cend()) {
0596             const QString userName = mRocketChatAccount ? mRocketChatAccount->userName() : QString();
0597             QString str = convertMessageText((*it), userName, mRocketChatAccount ? mRocketChatAccount->highlightWords() : QStringList(), QString());
0598             if (str.length() > 80) {
0599                 str = str.left(80) + QStringLiteral("...");
0600             }
0601             return str;
0602         } else {
0603             qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one
0604         }
0605     }
0606     return {};
0607 }
0608 
0609 bool MessagesModel::threadMessageFollowed(const QString &threadMessageId) const
0610 {
0611     if (!threadMessageId.isEmpty()) {
0612         auto it = findMessage(threadMessageId);
0613         if (it != mAllMessages.cend()) {
0614             const QString userId = mRocketChatAccount ? mRocketChatAccount->userId() : QString();
0615             if (!userId.isEmpty()) {
0616                 return (*it).replies().contains(userId);
0617             }
0618         } else {
0619             qCDebug(RUQOLA_LOG) << "Thread message" << threadMessageId << "not found"; // could be a very old one
0620         }
0621     }
0622     return false;
0623 }
0624 
0625 QVector<Message>::iterator MessagesModel::findMessage(const QString &messageId)
0626 {
0627     return std::find_if(mAllMessages.begin(), mAllMessages.end(), [&](const Message &msg) {
0628         return msg.messageId() == messageId;
0629     });
0630 }
0631 
0632 QVector<Message>::const_iterator MessagesModel::findMessage(const QString &messageId) const
0633 {
0634     return std::find_if(mAllMessages.cbegin(), mAllMessages.cend(), [&](const Message &msg) {
0635         return msg.messageId() == messageId;
0636     });
0637 }
0638 
0639 QString MessagesModel::roomId() const
0640 {
0641     return mRoomId;
0642 }
0643 
0644 QString MessagesModel::searchText() const
0645 {
0646     return mSearchText;
0647 }
0648 
0649 void MessagesModel::setSearchText(const QString &searchText)
0650 {
0651     mSearchText = searchText;
0652 }
0653 
0654 #include "moc_messagesmodel.cpp"