diff --git a/bindings/Objective-C/MEGAChatMessage.h b/bindings/Objective-C/MEGAChatMessage.h index d267ece5a..eb8cf5b6e 100644 --- a/bindings/Objective-C/MEGAChatMessage.h +++ b/bindings/Objective-C/MEGAChatMessage.h @@ -67,6 +67,7 @@ typedef NS_ENUM(NSInteger, MEGAChatMessageEndCallReason) { @property (readonly, nonatomic) NSInteger messageIndex; @property (readonly, nonatomic) uint64_t userHandle; @property (readonly, nonatomic) MEGAChatMessageType type; +@property (readonly, nonatomic) BOOL hasConfirmedReactions; @property (readonly, nonatomic) NSDate *timestamp; @property (readonly, nonatomic) NSString *content; @property (readonly, nonatomic, getter=isEdited) BOOL edited; diff --git a/bindings/Objective-C/MEGAChatMessage.mm b/bindings/Objective-C/MEGAChatMessage.mm index 559ee17dd..07e8a0d2f 100644 --- a/bindings/Objective-C/MEGAChatMessage.mm +++ b/bindings/Objective-C/MEGAChatMessage.mm @@ -88,6 +88,10 @@ - (MEGAChatMessageType)type { return (MEGAChatMessageType) (self.megaChatMessage ? self.megaChatMessage->getType() : 0); } +- (BOOL)hasConfirmedReactions { + return self.megaChatMessage ? self.megaChatMessage->hasConfirmedReactions() : NO; +} + - (NSDate *)timestamp { return self.megaChatMessage ? [[NSDate alloc] initWithTimeIntervalSince1970:self.megaChatMessage->getTimestamp()] : nil; } diff --git a/bindings/Objective-C/MEGAChatRoomDelegate.h b/bindings/Objective-C/MEGAChatRoomDelegate.h index f4e047368..5df5ce124 100644 --- a/bindings/Objective-C/MEGAChatRoomDelegate.h +++ b/bindings/Objective-C/MEGAChatRoomDelegate.h @@ -13,5 +13,6 @@ - (void)onMessageReceived:(MEGAChatSdk *)api message:(MEGAChatMessage *)message; - (void)onMessageUpdate:(MEGAChatSdk *)api message:(MEGAChatMessage *)message; - (void)onHistoryReloaded:(MEGAChatSdk *)api chat:(MEGAChatRoom *)chat; +- (void)onReactionUpdate:(MEGAChatSdk *)api messageId:(uint64_t)messageId reaction:(NSString *)reaction count:(NSInteger)count; @end diff --git a/bindings/Objective-C/MEGAChatSdk.h b/bindings/Objective-C/MEGAChatSdk.h index 356406aac..87a05b066 100644 --- a/bindings/Objective-C/MEGAChatSdk.h +++ b/bindings/Objective-C/MEGAChatSdk.h @@ -278,6 +278,12 @@ typedef NS_ENUM (NSInteger, MEGAChatConnection) { - (MEGAChatMessage *)lastChatMessageSeenForChat:(uint64_t)chatId; - (void)removeUnsentMessageForChat:(uint64_t)chatId rowId:(uint64_t)rowId; +- (void)addReactionForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction; +- (void)deleteReactionForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction; +- (NSInteger)messageReactionCountForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction; +- (MEGAStringList *)messageReactionsForChat:(uint64_t)chatId messageId:(uint64_t)messageId; +- (MEGAHandleList *)reactionUsersForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction; + - (void)sendTypingNotificationForChat:(uint64_t)chatId; - (void)sendStopTypingNotificationForChat:(uint64_t)chatId; - (void)saveCurrentState; diff --git a/bindings/Objective-C/MEGAChatSdk.mm b/bindings/Objective-C/MEGAChatSdk.mm index cf1406fed..10f8f6c82 100644 --- a/bindings/Objective-C/MEGAChatSdk.mm +++ b/bindings/Objective-C/MEGAChatSdk.mm @@ -921,6 +921,26 @@ - (void)removeUnsentMessageForChat:(uint64_t)chatId rowId:(uint64_t)rowId { self.megaChatApi->removeUnsentMessage(chatId, rowId); } +- (void)addReactionForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction { + self.megaChatApi->addReaction(chatId, messageId, reaction ? [reaction UTF8String] : NULL); +} + +- (void)deleteReactionForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction { + self.megaChatApi->delReaction(chatId, messageId, reaction ? [reaction UTF8String] : NULL); +} + +- (NSInteger)messageReactionCountForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction { + return self.megaChatApi->getMessageReactionCount(chatId, messageId, reaction.UTF8String); +} + +- (MEGAStringList *)messageReactionsForChat:(uint64_t)chatId messageId:(uint64_t)messageId { + return self.megaChatApi ? [MEGAStringList.alloc initWithMegaStringList:self.megaChatApi->getMessageReactions(chatId, messageId) cMemoryOwn:YES] : nil; +} + +- (MEGAHandleList *)reactionUsersForChat:(uint64_t)chatId messageId:(uint64_t)messageId reaction:(NSString *)reaction { + return self.megaChatApi ? [MEGAHandleList.alloc initWithMegaHandleList:self.megaChatApi->getReactionUsers(chatId, messageId, reaction.UTF8String) cMemoryOwn:YES] : nil; +} + - (void)sendTypingNotificationForChat:(uint64_t)chatId { self.megaChatApi->sendTypingNotification(chatId); } diff --git a/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.h b/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.h index fc03a4f81..6b776708e 100644 --- a/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.h +++ b/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.h @@ -14,6 +14,7 @@ class DelegateMEGAChatRoomListener : public megachat::MegaChatRoomListener { void onMessageReceived(megachat::MegaChatApi *api, megachat::MegaChatMessage *message); void onMessageUpdate(megachat::MegaChatApi *api, megachat::MegaChatMessage *message); void onHistoryReloaded(megachat::MegaChatApi *api, megachat::MegaChatRoom *chat); + void onReactionUpdate(megachat::MegaChatApi *api, megachat::MegaChatHandle msgid, const char *reaction, int count); private: __weak MEGAChatSdk *megaChatSDK; diff --git a/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.mm b/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.mm index eaf4e4068..b115bce77 100644 --- a/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.mm +++ b/bindings/Objective-C/Private/DelegateMEGAChatRoomListener.mm @@ -69,3 +69,14 @@ }); } } + +void DelegateMEGAChatRoomListener::onReactionUpdate(MegaChatApi *api, MegaChatHandle msgid, const char *reaction, int count) { + if (listener != nil && [listener respondsToSelector:@selector(onReactionUpdate:messageId:reaction:count:)]) { + MEGAChatSdk *tempMegaChatSDK = this->megaChatSDK; + id tempListener = this->listener; + NSString *str = [NSString stringWithUTF8String:reaction]; + dispatch_async(dispatch_get_main_queue(), ^{ + [tempListener onReactionUpdate:tempMegaChatSDK messageId:msgid reaction:str count:count]; + }); + } +} diff --git a/bindings/java/nz/mega/sdk/DelegateMegaChatRoomListener.java b/bindings/java/nz/mega/sdk/DelegateMegaChatRoomListener.java index 01948e062..bf9b22015 100644 --- a/bindings/java/nz/mega/sdk/DelegateMegaChatRoomListener.java +++ b/bindings/java/nz/mega/sdk/DelegateMegaChatRoomListener.java @@ -113,4 +113,14 @@ public void run() { }); } } + + @Override + public void onReactionUpdate(MegaChatApi api, long msgid, String reaction, int count){ + if (listener != null) { + megaChatApi.runCallback((Runnable) () -> { + if (listener != null) + listener.onReactionUpdate(megaChatApi, msgid, reaction, count); + }); + } + } } diff --git a/bindings/java/nz/mega/sdk/MegaChatRoomListenerInterface.java b/bindings/java/nz/mega/sdk/MegaChatRoomListenerInterface.java index 8a5563d25..5c50723d6 100644 --- a/bindings/java/nz/mega/sdk/MegaChatRoomListenerInterface.java +++ b/bindings/java/nz/mega/sdk/MegaChatRoomListenerInterface.java @@ -22,4 +22,5 @@ public interface MegaChatRoomListenerInterface { public void onMessageReceived(MegaChatApiJava api, MegaChatMessage msg); public void onMessageUpdate(MegaChatApiJava api, MegaChatMessage msg); public void onHistoryReloaded(MegaChatApiJava api, MegaChatRoom chat); + public void onReactionUpdate(MegaChatApiJava api, long msgid, String reaction, int count); } diff --git a/contrib/cmake/CMakeLists.txt b/contrib/cmake/CMakeLists.txt index 8fcfcac25..0dcf26c90 100644 --- a/contrib/cmake/CMakeLists.txt +++ b/contrib/cmake/CMakeLists.txt @@ -1,9 +1,15 @@ # At this stage, this file is intended for building MEGAChat library directly on windows with Visual Studio (just for chat so far), and/or exploring the chat API with example app MEGAclc # Build chat and the SDK with CURL and OPENSSL -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) project(karere) +# Qt settings +if (USE_QT) + set (MEGA_QT_REQUIRED_COMPONENTS Core Gui) + set (MEGA_QT_LINK_LIBRARIES Qt5::Core Qt5::Gui) +endif() + if (WIN32) if("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") SET(build_64_bit 1) diff --git a/examples/megaclc/megaclc.cpp b/examples/megaclc/megaclc.cpp index 978ec8cf5..b10a0f83c 100644 --- a/examples/megaclc/megaclc.cpp +++ b/examples/megaclc/megaclc.cpp @@ -203,6 +203,23 @@ class OneShotRequestListener : public m::MegaRequestListener } }; +class OneShotTransferListener : public m::MegaTransferListener +{ +public: + std::function onTransferFinishFunc; + + explicit OneShotTransferListener(std::function f = {}) + :onTransferFinishFunc(f) + { + } + + void onTransferFinish(m::MegaApi* api, m::MegaTransfer *request, m::MegaError* e) override + { + if (onTransferFinishFunc) onTransferFinishFunc(api, request, e); + delete this; // one-shot is done so auto-delete + } +}; + class OneShotChatRequestListener : public c::MegaChatRequestListener { public: @@ -3459,7 +3476,18 @@ void exec_renamenode(ac::ACState& s) { g_megaApi->renameNode(node.get(), s.words[2].s.c_str(), new OneShotRequestListener([](m::MegaApi*, m::MegaRequest *, m::MegaError* e) { - check_err("renamenode", e); + check_err("renamenode", e, ReportResult); + })); + } +} + +void exec_startupload(ac::ACState& s) +{ + if (auto node = GetNodeByPath(s.words[2].s)) + { + g_megaApi->startUpload(s.words[1].s.c_str(), node.get(), new OneShotTransferListener([](m::MegaApi*, m::MegaTransfer*, m::MegaError* e) + { + check_err("startUpload", e, ReportResult); })); } } @@ -3890,6 +3918,7 @@ ac::ACN autocompleteSyntax() p->Add(exec_getnodebypath, sequence(text("getnodebypath"), param("remotepath"))); p->Add(exec_ls, sequence(text("ls"), repeat(either(flag("-recursive"), flag("-handles"), flag("-ctime"), flag("-mtime"), flag("-size"), flag("-versions"), sequence(flag("-order"), param("order")), sequence(flag("-refilter"), param("regex")))), param("path"))); p->Add(exec_renamenode, sequence(text("renamenode"), param("remotepath"), param("newname"))); + p->Add(exec_startupload, sequence(text("startupload"), param("localpath"), param("remotepath"))); p->Add(exec_pushreceived, sequence(text("pushreceived"), opt(flag("-beep")), opt(param("chatid")))); p->Add(exec_getcloudstorageused, sequence(text("getcloudstorageused"))); diff --git a/examples/qtmegachatapi/MegaChatApplication.cpp b/examples/qtmegachatapi/MegaChatApplication.cpp index c465d5613..ae1cd3f5b 100644 --- a/examples/qtmegachatapi/MegaChatApplication.cpp +++ b/examples/qtmegachatapi/MegaChatApplication.cpp @@ -1088,6 +1088,17 @@ void MegaChatApplication::onRequestFinish(MegaChatApi *, MegaChatRequest *reques } } } + else if (error == MegaChatError::ERROR_NOENT && request->getFlag() ) + { + if (request->getNumber() == -1) + { + QMessageBox::warning(nullptr, tr("Add reaction"), tr("This message reached the maximum limit of 50 reactions")); + } + else if (request->getNumber() == 1) + { + QMessageBox::warning(nullptr, tr("Add reaction"), tr("Our own user has reached the maximum limit of 24 reactions")); + } + } break; } diff --git a/examples/qtmegachatapi/chatMessage.cpp b/examples/qtmegachatapi/chatMessage.cpp index ed6a08c46..c00f6a6c2 100644 --- a/examples/qtmegachatapi/chatMessage.cpp +++ b/examples/qtmegachatapi/chatMessage.cpp @@ -21,8 +21,16 @@ ChatMessage::ChatMessage(ChatWindow *parent, megachat::MegaChatApi *mChatApi, me mMessage = msg; setAuthor(NULL); setTimestamp(mMessage->getTimestamp()); - megachat::MegaChatRoom *chatRoom = megaChatApi->getChatRoom(chatId); + mReactWidget = new QWidget(); + ui->mReactions->setWidget(mReactWidget); + mReactLayout = new QHBoxLayout(); + mReactWidget->setLayout(mReactLayout); + mReactWidget->setStyleSheet("text-align:left"); + mReactWidget->setSizePolicy(QSizePolicy ::Expanding , QSizePolicy ::Expanding); + mReactLayout->setAlignment(Qt::AlignLeft); + ui->mReactions->setFrameShape(QFrame::NoFrame); + megachat::MegaChatRoom *chatRoom = megaChatApi->getChatRoom(chatId); assert (chatRoom); if (chatRoom->isGroup() && mMessage->getStatus() == megachat::MegaChatMessage::STATUS_DELIVERED) { @@ -41,7 +49,7 @@ ChatMessage::ChatMessage(ChatWindow *parent, megachat::MegaChatApi *mChatApi, me { int count = megaChatApi->getMessageReactionCount(mChatId, mMessage->getMsgId(), reactions->get(i)); Reaction *reaction = new Reaction(this, reactions->get(i), count); - ui->mReactions->layout()->addWidget(reaction); // takes ownership + mReactWidget->layout()->addWidget(reaction); // takes ownership } connect(ui->mMsgDisplay, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onMessageCtxMenu(const QPoint&))); @@ -52,6 +60,8 @@ ChatMessage::ChatMessage(ChatWindow *parent, megachat::MegaChatApi *mChatApi, me ChatMessage::~ChatMessage() { clearReactions(); + mReactLayout->deleteLater(); + mReactWidget->deleteLater(); delete mMessage; delete ui; } @@ -59,9 +69,9 @@ ChatMessage::~ChatMessage() const Reaction *ChatMessage::getLocalReaction(const char *reactionStr) const { assert(reactionStr); - for (int i = 0; i < ui->mReactions->layout()->count(); i++) + for (int i = 0; i < mReactWidget->layout()->count(); i++) { - QLayoutItem *item = ui->mReactions->layout()->itemAt(i); + QLayoutItem *item = mReactWidget->layout()->itemAt(i); Reaction *reaction = static_cast(item->widget()); if (reaction->getReactionString() == reactionStr) { @@ -75,10 +85,10 @@ void ChatMessage::updateReaction(const char *reactionStr, int count) { assert(reactionStr); bool found = false; - int size = ui->mReactions->layout()->count(); + int size = mReactWidget->layout()->count(); for (int i = 0; i < size; i++) { - QLayoutItem *item = ui->mReactions->layout()->itemAt(i); + QLayoutItem *item = mReactWidget->layout()->itemAt(i); Reaction *reaction = static_cast(item->widget()); if (reaction->getReactionString() == reactionStr) { @@ -86,7 +96,7 @@ void ChatMessage::updateReaction(const char *reactionStr, int count) if (count == 0) { item->widget()->deleteLater(); - delete ui->mReactions->layout()->takeAt(i); + delete mReactWidget->layout()->takeAt(i); } else { @@ -99,7 +109,7 @@ void ChatMessage::updateReaction(const char *reactionStr, int count) if (!found && count) { Reaction *reaction = new Reaction(this, reactionStr, count); - ui->mReactions->layout()->addWidget(reaction); + mReactWidget->layout()->addWidget(reaction); } } @@ -273,7 +283,7 @@ ChatWindow *ChatMessage::getChatWindow() const void ChatMessage::clearReactions() { QLayoutItem *item; - while ((item = ui->mReactions->layout()->takeAt(0))) + while ((item = mReactWidget->layout()->takeAt(0))) { item->widget()->deleteLater(); delete item; @@ -579,6 +589,9 @@ void ChatMessage::onMessageCtxMenu(const QPoint& point) { QMenu *menu = ui->mMsgDisplay->createStandardContextMenu(point); + auto actReactCount = menu->addAction(tr("Get reactions count")); + connect(actReactCount, &QAction::triggered, this, [=](){onReactCount();}); + QMenu *addReactMenu = menu->addMenu("React to this message"); for (int i = 0; i < utf8reactionsList.size(); i++) { @@ -641,6 +654,15 @@ void ChatMessage::onMessageEditAction() startEditingMsgWidget(); } +void ChatMessage::onReactCount() +{ + mega::unique_ptr<::mega::MegaStringList> reactions(mChatWindow->mMegaChatApi->getMessageReactions(mChatId, mMessage->getMsgId())); + QMessageBox msg; + msg.setIcon(QMessageBox::Information); + msg.setText(std::to_string(reactions->size()).c_str()); + msg.exec(); +} + void ChatMessage::onManageReaction(bool del, const char *reactionStr) { QString reaction = reactionStr diff --git a/examples/qtmegachatapi/chatMessage.h b/examples/qtmegachatapi/chatMessage.h index 6260c0c9f..f4f13fa71 100644 --- a/examples/qtmegachatapi/chatMessage.h +++ b/examples/qtmegachatapi/chatMessage.h @@ -28,6 +28,8 @@ class ChatMessage: public QWidget megachat::MegaChatMessage *mMessage = NULL; megachat::MegaChatApi *megaChatApi; QListWidgetItem *mListWidgetItem; + QWidget *mReactWidget; + QHBoxLayout *mReactLayout; ChatWindow *mChatWindow; friend class ChatWindow; @@ -66,6 +68,7 @@ class ChatMessage: public QWidget void onMessageCtxMenu(const QPoint& point); void onMessageDelAction(); void onMessageEditAction(); + void onReactCount(); void onManageReaction(bool del, const char *reactionStr = nullptr); void onMessageRemoveLinkAction(); void onNodeDownloadOrImport(mega::MegaNode *node, bool import); diff --git a/examples/qtmegachatapi/chatMessageWidget.ui b/examples/qtmegachatapi/chatMessageWidget.ui index 2b86ea67a..0ef636c43 100644 --- a/examples/qtmegachatapi/chatMessageWidget.ui +++ b/examples/qtmegachatapi/chatMessageWidget.ui @@ -7,7 +7,7 @@ 0 0 256 - 103 + 170 @@ -25,7 +25,7 @@ - + 0 @@ -293,36 +293,34 @@ border-radius:12; <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Abyssinica SIL Regular'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Helvetica Neue DeskInterface'; font-size:13pt;"><br /></p></body></html> - - + + + + true + 40 40 - - - 2 - - - 0 - - - 0 - - - 0 - - - 0 - - + + + 16777215 + 16777215 + + + + QAbstractScrollArea::AdjustToContents + + + true + diff --git a/examples/qtmegachatapi/chatWindow.ui b/examples/qtmegachatapi/chatWindow.ui index 9ea1176f4..3422927d7 100644 --- a/examples/qtmegachatapi/chatWindow.ui +++ b/examples/qtmegachatapi/chatWindow.ui @@ -6,8 +6,8 @@ 0 0 - 436 - 368 + 636 + 568 @@ -219,7 +219,7 @@ - + 6 @@ -313,76 +313,76 @@ border-radius: 6px; - - - - - 0 - 0 - - - - - 20 - 20 - - - - - 40 - 20 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16 - false - - - - false - - - false - - - border-radius:8px; + + + + + 0 + 0 + + + + + 20 + 20 + + + + + 40 + 20 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16 + false + + + + false + + + false + + + border-radius:8px; background-color:#ffae35; color: white; - - - 100 - - - Qt::PlainText - - - false - - - Qt::AlignCenter - - - 2 - - - Qt::NoTextInteraction - - - + + + 100 + + + Qt::PlainText + + + false + + + Qt::AlignCenter + + + 2 + + + Qt::NoTextInteraction + + + @@ -686,7 +686,7 @@ border-image: url(':/groupchat-members-hover.svg') <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Abyssinica SIL Regular'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'.Helvetica Neue DeskInterface'; font-size:13pt;"><br /></p></body></html> diff --git a/examples/qtmegachatapi/uiSettings.cpp b/examples/qtmegachatapi/uiSettings.cpp index 21515669c..84b138384 100644 --- a/examples/qtmegachatapi/uiSettings.cpp +++ b/examples/qtmegachatapi/uiSettings.cpp @@ -32,5 +32,50 @@ QStringList utf8reactionsList = { QString::fromUtf8("\xF0\x9F\x98\x86"), QString::fromUtf8("\xF0\x9F\x98\x87"), QString::fromUtf8("\xF0\x9F\x98\x88"), - QString::fromUtf8("\xF0\x9F\x98\x89") + QString::fromUtf8("\xF0\x9F\x98\x89"), + QString::fromUtf8("\xF0\x9F\x98\x90"), + QString::fromUtf8("\xF0\x9F\x98\x91"), + QString::fromUtf8("\xF0\x9F\x98\x92"), + QString::fromUtf8("\xF0\x9F\x98\x93"), + QString::fromUtf8("\xF0\x9F\x98\x94"), + QString::fromUtf8("\xF0\x9F\x98\x95"), + QString::fromUtf8("\xF0\x9F\x98\x96"), + QString::fromUtf8("\xF0\x9F\x98\x97"), + QString::fromUtf8("\xF0\x9F\x98\x98"), + QString::fromUtf8("\xF0\x9F\x98\x99"), + QString::fromUtf8("\xF0\x9F\x98\xA0"), + QString::fromUtf8("\xF0\x9F\x98\xA1"), + QString::fromUtf8("\xF0\x9F\x98\xA2"), + QString::fromUtf8("\xF0\x9F\x98\xA3"), + QString::fromUtf8("\xF0\x9F\x98\xA4"), + QString::fromUtf8("\xF0\x9F\x98\xA5"), + QString::fromUtf8("\xF0\x9F\x98\xA6"), + QString::fromUtf8("\xF0\x9F\x98\xA7"), + QString::fromUtf8("\xF0\x9F\x98\xA8"), + QString::fromUtf8("\xF0\x9F\x98\xA9"), + QString::fromUtf8("\xF0\x9F\x98\xB0"), + QString::fromUtf8("\xF0\x9F\x98\xB1"), + QString::fromUtf8("\xF0\x9F\x98\xB2"), + QString::fromUtf8("\xF0\x9F\x98\xB3"), + QString::fromUtf8("\xF0\x9F\x98\xB4"), + QString::fromUtf8("\xF0\x9F\x98\xB5"), + QString::fromUtf8("\xF0\x9F\x98\xB6"), + QString::fromUtf8("\xF0\x9F\x98\xB7"), + QString::fromUtf8("\xF0\x9F\x98\xB8"), + QString::fromUtf8("\xF0\x9F\x98\xB9"), + QString::fromUtf8("\xF0\x9F\x99\x8F"), + QString::fromUtf8("\xF0\x9F\x91\x8F"), + QString::fromUtf8("\xF0\x9F\xA4\xB2"), + QString::fromUtf8("\xF0\x9F\x91\x8C"), + QString::fromUtf8("\xF0\x9F\xA4\x9F"), + QString::fromUtf8("\x31\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x32\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x33\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x34\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x35\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x36\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x37\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x38\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\x39\xEF\xB8\x8F\xE2\x83\xA3"), + QString::fromUtf8("\xF0\x9F\x94\x9F"), }; diff --git a/src/chatd.cpp b/src/chatd.cpp index d22d06a4c..2de6a1770 100644 --- a/src/chatd.cpp +++ b/src/chatd.cpp @@ -1945,7 +1945,7 @@ Idx Chat::getHistoryFromDb(unsigned count) for (auto msg: messages) { // Load msg reactions from cache - std::multimap reactions; + std::vector> reactions; CALL_DB(getReactions, msg->id(), reactions); for (auto& reaction : reactions) { @@ -3591,6 +3591,10 @@ Message* Chat::msgModify(Message& msg, const char* newdata, size_t newlen, void* else //our last text msg is not valid anymore, find another one { findAndNotifyLastTextMsg(); + if (!mLastTextMsg.isValid() && mHaveAllHistory) + { + CHATID_LOG_DEBUG("msgModify: lastTextMessage not found, no text message in whole history"); + } } } @@ -4484,6 +4488,10 @@ void Chat::onMsgUpdatedAfterDecrypt(time_t updateTs, bool richLinkRemoved, Messa else //our last text msg is not valid anymore, find another one { findAndNotifyLastTextMsg(); + if (!mLastTextMsg.isValid() && mHaveAllHistory) + { + CHATID_LOG_DEBUG("onMsgUpdatedAfterDecrypt: lastTextMessage not found, no text message in whole history"); + } } } } @@ -5562,6 +5570,10 @@ void Chat::onJoinComplete() { CHATID_LOG_DEBUG("onJoinComplete: Haven't received a text message during join, getting last text message on-demand"); findAndNotifyLastTextMsg(); + if (!mLastTextMsg.isValid() && mHaveAllHistory) + { + CHATID_LOG_DEBUG("onJoinComplete: lastTextMessage not found, no text message in whole history"); + } } } @@ -5713,7 +5725,6 @@ bool Chat::findLastTextMsg() } if (mHaveAllHistory) { - CHATID_LOG_DEBUG("lastTextMessage: No text message in whole history"); assert(!mLastTextMsg.isValid()); return true; } diff --git a/src/chatd.h b/src/chatd.h index 239b76e13..305eceee0 100644 --- a/src/chatd.h +++ b/src/chatd.h @@ -1767,7 +1767,7 @@ class DbInterface virtual void addPendingReaction(karere::Id msgId, const std::string &reaction, const std::string &encReaction, uint8_t status) = 0; virtual void delReaction(karere::Id msgId, karere::Id userId, const std::string &reaction) = 0; virtual void delPendingReaction(karere::Id msgId, const std::string &reaction) = 0; - virtual void getReactions(karere::Id msgId, std::multimap& reactions) const = 0; + virtual void getReactions(karere::Id msgId,std::vector> &reactions) const = 0; virtual void getPendingReactions(std::vector& reactions) const = 0; virtual bool hasPendingReactions() = 0; diff --git a/src/chatdDb.h b/src/chatdDb.h index b81593efe..b86910d5e 100644 --- a/src/chatdDb.h +++ b/src/chatdDb.h @@ -645,24 +645,24 @@ class ChatdSqliteDb: public chatd::DbInterface mChat.chatId(), msgId, reaction); } - void getReactions(karere::Id msgId, std::multimap& reactions) const override + void getReactions(karere::Id msgId, std::vector> &reactions) const override { - SqliteStmt stmt(mDb, "select reaction, userid from chat_reactions where chatid = ? and msgid = ?"); + SqliteStmt stmt(mDb, "select _rowid_, reaction, userid from chat_reactions where chatid = ? and msgid = ? ORDER BY `_rowid_` ASC"); stmt << mChat.chatId(); stmt << msgId; while (stmt.step()) { - reactions.insert(std::pair(stmt.stringCol(0), karere::Id(stmt.uint64Col(1)))); + reactions.emplace_back(std::pair(stmt.stringCol(1), karere::Id(stmt.uint64Col(2)))); } } void getPendingReactions(std::vector& reactions) const override { - SqliteStmt stmt(mDb, "select reaction, encReaction, msgid, status from chat_pending_reactions where chatid = ?"); + SqliteStmt stmt(mDb, "select _rowid_, reaction, encReaction, msgid, status from chat_pending_reactions where chatid = ? ORDER BY `_rowid_` ASC"); stmt << mChat.chatId(); while (stmt.step()) { - reactions.emplace_back(chatd::Chat::PendingReaction(stmt.stringCol(0), stmt.stringCol(1), karere::Id (stmt.uint64Col(2)), stmt.uint64Col(3))); + reactions.emplace_back(chatd::Chat::PendingReaction(stmt.stringCol(1), stmt.stringCol(2), karere::Id (stmt.uint64Col(3)), stmt.uint64Col(4))); } } diff --git a/src/chatdMsg.h b/src/chatdMsg.h index 75ad9fcba..7610cbdc9 100644 --- a/src/chatdMsg.h +++ b/src/chatdMsg.h @@ -541,6 +541,9 @@ class Message: public Buffer // even if the library adds support to the new type (unless the message is reloaded from server) }; + static const int maxMessageReactions = 50; + static const int maxOwnReactions = 24; + /** @brief Info recorder in a management message. * When a message is a management message, _and_ it needs to carry additional * info besides the standard fields (such as sender), the additional data @@ -850,6 +853,47 @@ class Message: public Buffer return std::string(buf()+2, dataSize()-2); } + /** @brief Check if reactions restrictions for this message have been reached. + - returns -1, if this message reached the maximum limit of maxMessageReactions reactions, and + we want to add a reaction that haven't beed added yet + - returns 1, if our own user has reached the maximum limit of maxOwnReactions reactions + - returns 0, if we can add the reaction + **/ + int allowReact(karere::Id myHandle, const char *reaction) const + { + bool foundReaction = false; + int ownReacts = 0; + for (auto &it : mReactions) + { + if (!it.mReaction.compare(reaction)) + { + foundReaction = true; + } + + for (auto &user: it.mUsers) + { + if (user == myHandle) + { + ownReacts++; + break; + } + } + + if (ownReacts >= maxOwnReactions) + { + return 1; + } + } + + if (mReactions.size() >= maxMessageReactions && !foundReaction) + { + // Add +1 to existing reaction is allowed, if we haven't reached our own limit (maxOwnReactions) + return -1; + } + + return 0; + } + /** @brief Returns a vector with all the reactions of the message **/ const std::vector getReactions() const { @@ -931,7 +975,7 @@ class Message: public Buffer { r = &mReactions.at(reactIndex); } - else // not found, add + else // not found, add reaction at last position, to preserve the order in which reactions were received { mReactions.emplace_back(reaction); r = &mReactions.back(); diff --git a/src/megachatapi_impl.cpp b/src/megachatapi_impl.cpp index 5a242191f..d73e1ce1d 100644 --- a/src/megachatapi_impl.cpp +++ b/src/megachatapi_impl.cpp @@ -2022,6 +2022,16 @@ void MegaChatApiImpl::sendPendingRequests() bool hasReacted = msg.hasReacted(reaction, mClient->myHandle()); if (request->getFlag()) { +// Uncomment this block upon release of reaction limitations +// // check if max number of reactions has been reached +// int res = msg.allowReact(mClient->myHandle(), reaction); +// if (res != 0) +// { +// request->setNumber(res); +// errorCode = MegaChatError::ERROR_NOENT; +// break; +// } + if ((hasReacted && pendingStatus != OP_DELREACTION) || (!hasReacted && pendingStatus == OP_ADDREACTION)) { @@ -2076,6 +2086,7 @@ void MegaChatApiImpl::sendPendingRequests() fireOnChatRequestFinish(request, megaChatError); break; } +#ifndef KARERE_DISABLE_WEBRTC case MegaChatRequest::TYPE_ENABLE_AUDIO_LEVEL_MONITOR: { handle chatid = request->getChatHandle(); @@ -2086,7 +2097,6 @@ void MegaChatApiImpl::sendPendingRequests() errorCode = MegaChatError::ERROR_ARGS; break; } - MegaChatCallHandler *handler = findChatCallHandler(chatid); if (!handler) { @@ -2108,6 +2118,7 @@ void MegaChatApiImpl::sendPendingRequests() fireOnChatRequestFinish(request, megaChatError); break; } +#endif default: { errorCode = MegaChatError::ERROR_UNKNOWN; @@ -4724,39 +4735,47 @@ MegaStringList* MegaChatApiImpl::getMessageReactions(MegaChatHandle chatid, Mega return new MegaStringListPrivate(); } - // Insert confirmed reactions - std::map auxReactMap; - const std::vector &reactions = msg->getReactions(); - for (auto &auxReact : reactions) - { - auxReactMap[auxReact.mReaction] = auxReact.mUsers.size(); - } + vector reactList; + const std::vector &confirmedReactions = msg->getReactions(); + const Chat::PendingReactions& pendingReactions = (findChatRoom(chatid))->chat().getPendingReactions(); - // Update confirmed reactions with pending reactions - ChatRoom *chatroom = findChatRoom(chatid); - auto pendingReactions = chatroom->chat().getPendingReactions(); - for (auto &auxReact : pendingReactions) + // iterate through confirmed reactions list + for (auto &auxReact : confirmedReactions) { - if (auxReact.mMsgId == msgid) - { - (auxReact.mStatus == OP_ADDREACTION) - ? auxReactMap[auxReact.mReactionString]++ - : auxReactMap[auxReact.mReactionString]--; + int reactUsers = static_cast(auxReact.mUsers.size()); + for (auto &pendingReact : pendingReactions) + { + if (pendingReact.mMsgId == msgid + && !pendingReact.mReactionString.compare(auxReact.mReaction)) + { + // increment or decrement reactUsers, for the confirmed reaction we are checking + (pendingReact.mStatus == OP_ADDREACTION) + ? reactUsers++ + : reactUsers--; - if (auxReactMap[auxReact.mReactionString] <= 0) - { - auxReactMap.erase(auxReact.mReactionString); - } - } + // a confirmed reaction only can have one pending reaction + break; + } + } + + if (reactUsers > 0) + { + reactList.emplace_back(MegaApi::strdup(auxReact.mReaction.c_str())); + } } - vector reactArray; - for (auto &auxReact: auxReactMap) + // add pending reactions that are not on confirmed list + for (auto &pendingReact : pendingReactions) { - reactArray.push_back(MegaApi::strdup(auxReact.first.c_str())); + if (pendingReact.mMsgId == msgid + && !msg->getReactionCount(pendingReact.mReactionString) + && pendingReact.mStatus == OP_ADDREACTION) + { + reactList.emplace_back (MegaApi::strdup(pendingReact.mReactionString.c_str())); + } } - return new MegaStringListPrivate(reactArray.data(), static_cast(reactArray.size())); + return new MegaStringListPrivate(reactList.data(), static_cast(reactList.size())); } MegaHandleList* MegaChatApiImpl::getReactionUsers(MegaChatHandle chatid, MegaChatHandle msgid, const char *reaction) @@ -4769,7 +4788,8 @@ MegaHandleList* MegaChatApiImpl::getReactionUsers(MegaChatHandle chatid, MegaCha SdkMutexGuard g(sdkMutex); Message *msg = findMessage(chatid, msgid); - if (!msg) + ChatRoom *chatroom = findChatRoom(chatid); + if (!msg || !chatroom) { API_LOG_ERROR("Chatroom or message not found"); return userList; @@ -4777,6 +4797,7 @@ MegaHandleList* MegaChatApiImpl::getReactionUsers(MegaChatHandle chatid, MegaCha bool reacted = false; string reactionStr(reaction); + int pendingReactionStatus = chatroom->chat().getPendingReactionStatus(reactionStr, msgid); const std::vector &users = msg->getReactionUsers(reactionStr); for (auto user: users) { @@ -4786,18 +4807,21 @@ MegaHandleList* MegaChatApiImpl::getReactionUsers(MegaChatHandle chatid, MegaCha } else { - reacted = true; + if (pendingReactionStatus != OP_DELREACTION) + { + // if we have reacted and there's no a pending DELREACTION + reacted = true; + userList->addMegaHandle(mClient->myHandle()); + } } } - ChatRoom *chatroom = findChatRoom(chatid); - int pendingReactionStatus = chatroom->chat().getPendingReactionStatus(reactionStr, msgid); - if ((reacted && pendingReactionStatus != OP_DELREACTION) - || (!reacted && pendingReactionStatus == OP_ADDREACTION)) + if (!reacted && pendingReactionStatus == OP_ADDREACTION) { - // Own user only must be added to userlist after check pending reactions + // if we don't have reacted and there's a pending ADDREACTION userList->addMegaHandle(mClient->myHandle()); } + return userList; } @@ -5719,7 +5743,7 @@ MegaChatCallPrivate::MegaChatCallPrivate(const rtcModule::ICall& call) MegaChatCallPrivate::MegaChatCallPrivate(Id chatid, Id callid, uint32_t duration) { - status = CALL_STATUS_USER_NO_PRESENT; + status = CALL_STATUS_INITIAL; this->chatid = chatid; this->callid = callid; // localAVFlags are invalid until state change to rtcModule::ICall::KStateHasLocalStream @@ -6020,6 +6044,11 @@ void MegaChatCallPrivate::setStatus(int status) void MegaChatCallPrivate::setLocalAudioVideoFlags(AvFlags localAVFlags) { + if (this->localAVFlags == localAVFlags) + { + return; + } + this->localAVFlags = localAVFlags; changed |= MegaChatCall::CHANGE_TYPE_LOCAL_AVFLAGS; } @@ -8477,8 +8506,18 @@ void MegaChatCallHandler::setCall(rtcModule::ICall *call) } else { - chatCall->setStatus(call->state()); + if (chatCall->getStatus() != call->state()) // Notify state only if it has changed + { + API_LOG_INFO("Call state changed. ChatId: %s, callid: %s, state: %s --> %s", + karere::Id(chatCall->getChatid()).toString().c_str(), + karere::Id(chatCall->getId()).toString().c_str(), + rtcModule::ICall::stateToStr(chatCall->getStatus()), + rtcModule::ICall::stateToStr(call->state())); + chatCall->setStatus(call->state()); + } + chatCall->setLocalAudioVideoFlags(call->sentFlags()); + megaChatApi->fireOnChatCallUpdate(chatCall); assert(chatCall->getId() == call->id()); } } @@ -8486,6 +8525,11 @@ void MegaChatCallHandler::setCall(rtcModule::ICall *call) void MegaChatCallHandler::onStateChange(uint8_t newState) { assert(chatCall); + if (chatCall->getStatus() == newState) // Avoid notify same state + { + return; + } + if (chatCall) { API_LOG_INFO("Call state changed. ChatId: %s, callid: %s, state: %s --> %s", diff --git a/src/net/libwebsocketsIO.cpp b/src/net/libwebsocketsIO.cpp index dc0819e40..1807e5f34 100644 --- a/src/net/libwebsocketsIO.cpp +++ b/src/net/libwebsocketsIO.cpp @@ -282,6 +282,7 @@ void LibwebsocketsClient::resetOutputBuffer() #define EVP_PKEY_get0_RSA(_pkey_) ((_pkey_)->pkey.rsa) #endif +#ifndef WIN32 const BIGNUM *RSA_get0_n(const RSA *rsa) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) @@ -314,6 +315,7 @@ const BIGNUM *RSA_get0_d(const RSA *rsa) return result; #endif } +#endif static bool check_public_key(X509_STORE_CTX* ctx) { diff --git a/src/strongvelope/strongvelope.cpp b/src/strongvelope/strongvelope.cpp index 302cb5b5e..6cc27562c 100644 --- a/src/strongvelope/strongvelope.cpp +++ b/src/strongvelope/strongvelope.cpp @@ -599,7 +599,7 @@ ProtocolHandler::reactionEncrypt(const Message &msg, const std::string &reaction { std::shared_ptr data; promise::Promise> symPms; - if (isPublicChat()) + if (msg.keyid == 0) // msg was ciphered with EKR off (public mode) { assert (mUnifiedKeyDecrypted.succeeded()); data = mUnifiedKey; @@ -652,7 +652,7 @@ promise::Promise> ProtocolHandler::reactionDecrypt(const karere::Id &msgid, const karere::Id &userid, const KeyId &keyid, const std::string &reaction) { promise::Promise> symPms; - if (isPublicChat()) + if (keyid == 0) // message posted in EKR off (public mode) { symPms = mUnifiedKeyDecrypted; }