/*
Copyright (c) 2013-24 Miranda NG team (https://miranda-ng.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "stdafx.h"
//////////////////////////////////////////////////////////////////////////////
int CVkProto::ForwardMsg(MCONTACT hContact, std::vector& vForvardEvents, const char* szMsg)
{
debugLogA("CVkProto::ForwardMsg");
if (!IsOnline() || !vForvardEvents.size())
return -1;
bool bIsChat = isChatRoom(hContact);
VKUserID_t iUserId = ReadVKUserID(hContact);
if (iUserId == VK_INVALID_USER || iUserId == VK_FEED_USER) {
ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, 0);
return 0;
}
CMStringA szBody;
int StickerId = 0;
ptrA pszRetMsg(GetStickerId(szMsg, StickerId));
if (StickerId) {
SendMsg(hContact, 0, CMStringA(FORMAT, "[sticker:%d]", StickerId));
szBody = pszRetMsg;
}
else
szBody = szMsg;
CMStringA szIds;
CMStringW wszForwardMessagesTxt;
int iForwardVKMessageCount = 0;
for (auto &mEvnt : vForvardEvents) {
if (iForwardVKMessageCount == VK_MAX_FORWARD_MESSAGES)
break;
iForwardVKMessageCount++;
DB::EventInfo dbei(mEvnt);
if (!dbei || dbei.eventType != EVENTTYPE_MESSAGE)
continue;
if (!Proto_IsProtoOnContact(dbei.hContact, m_szModuleName)) {
CMStringW wszContactName = (dbei.flags & DBEF_SENT) ? getWStringA(0, "Nick", TranslateT("Me")) : Clist_GetContactDisplayName(dbei.hContact);
wchar_t ttime[64];
time_t tTimestamp(dbei.timestamp);
_locale_t locale = _create_locale(LC_ALL, "");
_wcsftime_l(ttime, _countof(ttime), TranslateT("%x at %X"), localtime(&tTimestamp), locale);
_free_locale(locale);
wchar_t tcSplit = m_vkOptions.bSplitFormatFwdMsg ? '\n' : ' ';
wszForwardMessagesTxt.AppendFormat(L"%s %s%c%s %s:\n\n%s\n\n",
TranslateT("Message from"),
wszContactName.c_str(),
tcSplit,
TranslateT("at"),
ttime,
dbei.pBlob ? ptrW(mir_utf8decodeW((char*)dbei.pBlob)) : L""
);
} else if (mir_strlen(dbei.szId) > 0) {
if (!szIds.IsEmpty())
szIds.AppendChar(',');
szIds += dbei.szId;
}
}
ULONG uMsgId = ::InterlockedIncrement(&m_iMsgId);
AsyncHttpRequest* pReq = new AsyncHttpRequest(
this,
REQUEST_POST,
"/method/messages.send.json",
true,
&CVkProto::OnSendMessage,
AsyncHttpRequest::rpHigh
);
pReq
<< INT_PARAM(bIsChat ? "chat_id" : "peer_id", iUserId)
<< INT_PARAM("random_id", ((long)time(0)) * 100 + uMsgId % 100)
<< CHAR_PARAM("forward_messages", szIds);
pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded");
szBody += T2Utf(wszForwardMessagesTxt);
if (!IsEmpty(szBody)) {
pReq << CHAR_PARAM("message", szBody);
if (m_vkOptions.bSendVKLinksAsAttachments) {
CMStringA szAttachments = GetAttachmentsFromMessage(szBody);
if (!szAttachments.IsEmpty()) {
debugLogA("CVkProto::ForwardMsg Attachments = %s", szAttachments.c_str());
pReq << CHAR_PARAM("attachment", szAttachments);
}
}
}
pReq->pUserInfo = new CVkSendMsgParam(hContact, uMsgId);
Push(pReq);
if (!m_vkOptions.bServerDelivery)
ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)uMsgId);
if (vForvardEvents.size() > VK_MAX_FORWARD_MESSAGES) {
std::vector vNextForvardEvents(vForvardEvents.begin() + VK_MAX_FORWARD_MESSAGES, vForvardEvents.end());
ForwardMsg(hContact, vNextForvardEvents, "");
}
if (m_iStatus == ID_STATUS_INVISIBLE)
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/account.setOffline.json", true, &CVkProto::OnReceiveSmth));
return uMsgId;
}
int CVkProto::SendMsg(MCONTACT hContact, MEVENT hReplyEvent, const char *szMsg)
{
debugLogA("CVkProto::SendMsg hReplyEvent = %d", hReplyEvent);
if (!IsOnline())
return -1;
bool bIsChat = isChatRoom(hContact);
VKUserID_t iUserId = ReadVKUserID(hContact);
if (iUserId == VK_INVALID_USER || iUserId == VK_FEED_USER) {
ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, 0);
return 0;
}
int StickerId = 0;
ptrA pszRetMsg(GetStickerId(szMsg, StickerId));
ULONG uMsgId = ::InterlockedIncrement(&m_iMsgId);
AsyncHttpRequest *pReq = new AsyncHttpRequest(
this,
REQUEST_POST,
"/method/messages.send.json",
true,
&CVkProto::OnSendMessage,
AsyncHttpRequest::rpHigh
);
pReq
<< INT_PARAM(bIsChat ? "chat_id" : "peer_id", iUserId)
<< INT_PARAM("random_id", ((long)time(0)) * 100 + uMsgId % 100);
pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded");
if (hReplyEvent) {
DB::EventInfo dbei(hReplyEvent, false);
if (dbei && mir_strlen(dbei.szId) > 0)
pReq << CHAR_PARAM("reply_to", dbei.szId);
}
if (StickerId)
pReq << INT_PARAM("sticker_id", StickerId);
else {
pReq << CHAR_PARAM("message", szMsg);
if (m_vkOptions.bSendVKLinksAsAttachments) {
CMStringA szAttachments = GetAttachmentsFromMessage(szMsg);
if (!szAttachments.IsEmpty()) {
debugLogA("CVkProto::SendMsg Attachments = %s", szAttachments.c_str());
pReq << CHAR_PARAM("attachment", szAttachments);
}
}
}
pReq->pUserInfo = new CVkSendMsgParam(hContact, uMsgId);
Push(pReq);
if (!m_vkOptions.bServerDelivery)
ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)uMsgId);
if (!IsEmpty(pszRetMsg))
SendMsg(hContact, 0, pszRetMsg);
else if (m_iStatus == ID_STATUS_INVISIBLE)
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/account.setOffline.json", true, &CVkProto::OnReceiveSmth));
return uMsgId;
}
void CVkProto::OnSendMessage(MHttpResponse *reply, AsyncHttpRequest *pReq)
{
int iResult = ACKRESULT_FAILED;
if (pReq->pUserInfo == nullptr) {
debugLogA("CVkProto::OnSendMessage failed! (pUserInfo == nullptr)");
return;
}
CVkSendMsgParam *param = (CVkSendMsgParam *)pReq->pUserInfo;
debugLogA("CVkProto::OnSendMessage %d", reply->resultCode);
VKMessageID_t iMessageId = 0;
if (reply->resultCode == 200) {
JSONNode jnRoot;
const JSONNode &jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
if (jnResponse) {
iMessageId = jnResponse["message_id"].as_int();
debugLogA("CVkProto::OnSendMessage jnResponse %d", iMessageId ? iMessageId : jnResponse.as_int());
if (!iMessageId)
switch (jnResponse.type()) {
case JSON_NUMBER:
iMessageId = jnResponse.as_int();
break;
case JSON_STRING:
if (swscanf(jnResponse.as_mstring(), L"%u", &iMessageId) != 1)
iMessageId = 0;
break;
case JSON_ARRAY:
iMessageId = jnResponse.as_array()[json_index_t(0)].as_int();
break;
default:
iMessageId = 0;
}
if (iMessageId > ReadQSWord(param->hContact, "lastmsgid"))
WriteQSWord(param->hContact, "lastmsgid", iMessageId);
if (m_vkOptions.iMarkMessageReadOn >= MarkMsgReadOn::markOnReply)
MarkMessagesRead(param->hContact);
iResult = ACKRESULT_SUCCESS;
}
}
char szMid[40];
_ltoa(iMessageId, szMid, 10);
if (param->pFUP) {
ProtoBroadcastAck(param->hContact, ACKTYPE_FILE, iResult, (HANDLE)(param->pFUP));
if (!pReq->bNeedsRestart || m_bTerminated)
delete param->pFUP;
}
else if (m_vkOptions.bServerDelivery)
ProtoBroadcastAck(param->hContact, ACKTYPE_MESSAGE, iResult, (HANDLE)(param->iMsgID), (LPARAM)szMid);
MEVENT hDbEvent;
if ((iResult == ACKRESULT_SUCCESS) && (hDbEvent = db_event_getById(m_szModuleName, szMid)))
db_event_delivered(param->hContact, hDbEvent);
if (!pReq->bNeedsRestart || m_bTerminated) {
delete param;
pReq->pUserInfo = nullptr;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void CVkProto::OnMarkRead(MCONTACT hContact, MEVENT)
{
debugLogA("CVkProto::OnDbEventRead");
if (m_vkOptions.iMarkMessageReadOn == MarkMsgReadOn::markOnRead)
MarkMessagesRead(hContact);
}
INT_PTR CVkProto::SvcMarkMessagesAsRead(WPARAM hContact, LPARAM)
{
MarkDialogAsRead(hContact);
MarkMessagesRead(hContact);
return 0;
}
void CVkProto::MarkMessagesRead(const MCONTACT hContact)
{
debugLogA("CVkProto::MarkMessagesRead (hContact)");
if (!IsOnline() || !hContact)
return;
if (!IsEmpty(ptrW(db_get_wsa(hContact, m_szModuleName, "Deactivated"))))
return;
VKUserID_t iUserId = ReadVKUserID(hContact);
if (iUserId == VK_INVALID_USER || iUserId == VK_FEED_USER)
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/messages.markAsRead.json", true, &CVkProto::OnReceiveSmth, AsyncHttpRequest::rpLow)
<< INT_PARAM("mark_conversation_as_read", 1)
<< INT_PARAM("peer_id", isChatRoom(hContact) ? VK_CHAT_MIN + iUserId : iUserId));
}
void CVkProto::RetrieveMessagesByIds(const CMStringA &szMids)
{
debugLogA("CVkProto::RetrieveMessagesByIds");
if (!IsOnline() || szMids.IsEmpty())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/execute.RetrieveMessagesConversationByIds", true, &CVkProto::OnReceiveMessages, AsyncHttpRequest::rpHigh)
<< CHAR_PARAM("mids", szMids)
);
}
void CVkProto::RetrieveUnreadMessages()
{
debugLogA("CVkProto::RetrieveUnreadMessages");
if (!IsOnline())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/execute.RetrieveUnreadConversations", true, &CVkProto::OnReceiveDlgs, AsyncHttpRequest::rpHigh));
}
void CVkProto::OnReceiveMessages(MHttpResponse *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceiveMessages %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONNode jnRoot;
const JSONNode &jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
if (!jnResponse)
return;
if (!jnResponse["Msgs"])
return;
CMStringA szMids;
int iNumMessages = jnResponse["Msgs"]["count"].as_int();
const JSONNode &jnMsgs = jnResponse["Msgs"]["items"];
const JSONNode &jnFUsers = jnResponse["fwd_users"];
debugLogA("CVkProto::OnReceiveMessages numMessages = %d", iNumMessages);
if (jnResponse["conv"]) {
const JSONNode& jnConversation = jnResponse["conv"]["items"];
for (auto& jnItem : jnConversation) {
const JSONNode& jnPeer = jnItem["peer"];
if (!jnPeer)
break;
CMStringW wszPeerType(jnPeer["type"].as_mstring());
VKUserID_t iUserId = jnPeer["id"].as_int();
MCONTACT hContact = (wszPeerType == L"chat") ? FindChat(iUserId % VK_CHAT_MIN) : FindUser(iUserId, true);
WriteQSWord(hContact, "in_read", jnItem["in_read"].as_int());
WriteQSWord(hContact, "out_read", jnItem["out_read"].as_int());
if (m_vkOptions.iMarkMessageReadOn == MarkMsgReadOn::markOnReceive)
MarkMessagesRead(hContact);
const JSONNode& jnCanWrite = jnItem["can_write"];
if (jnCanWrite)
Contact::Readonly(hContact, !jnCanWrite["allowed"].as_bool());
}
}
for (auto& jnMsg : jnMsgs) {
if (!jnMsg) {
debugLogA("CVkProto::OnReceiveMessages pMsg == nullptr");
break;
}
VKMessageID_t iMessageId = jnMsg["id"].as_int();
CMStringW wszBody(jnMsg["text"].as_mstring());
time_t tDateTime = jnMsg["date"].as_int();
int isOut = jnMsg["out"].as_int();
VKUserID_t iUserId = jnMsg["peer_id"].as_int();
MCONTACT hContact = 0;
VKUserID_t iChatId = (GetVKPeerType(iUserId) == VKPeerType::vkPeerMUC) ? iUserId % VK_CHAT_MIN : 0;
if (iChatId == 0)
hContact = FindUser(iUserId, true);
char szMid[40], szReplyId[40] = "";
_ltoa(iMessageId, szMid, 10);
bool bUseServerReadFlag = m_vkOptions.bSyncReadMessageStatusFromServer ? true : !m_vkOptions.bMesAsUnread;
if (iChatId != 0) {
debugLogA("CVkProto::OnReceiveMessages chat_id != 0");
CMStringW wszActionChat = jnMsg["action"]["type"].as_mstring();
VKMessageID_t iActionMessageId = _wtol(jnMsg["action"]["member_id"].as_mstring());
if ((wszActionChat == L"chat_kick_user") && (iActionMessageId == m_iMyUserId))
KickFromChat(iChatId, iUserId, jnMsg, jnFUsers);
else {
MCONTACT chatContact = FindChat(iChatId);
if (chatContact && getBool(chatContact, "kicked", true))
db_unset(chatContact, m_szModuleName, "kicked");
AppendChatConversationMessage(iChatId, jnMsg, jnFUsers, false);
}
continue;
}
const JSONNode& jnFwdMessages = jnMsg["fwd_messages"];
if (jnFwdMessages && !jnFwdMessages.empty()) {
CMStringW wszFwdMessages = GetFwdMessages(jnFwdMessages, jnFUsers, m_vkOptions.BBCForAttachments());
if (!wszBody.IsEmpty())
wszFwdMessages = L"\n" + wszFwdMessages;
wszBody += wszFwdMessages;
}
const JSONNode& jnReplyMessages = jnMsg["reply_message"];
if (jnReplyMessages && !jnReplyMessages.empty())
if (m_vkOptions.bShowReplyInMessage) {
CMStringW wszReplyMessages = GetFwdMessages(jnReplyMessages, jnFUsers, m_vkOptions.BBCForAttachments());
if (!wszBody.IsEmpty())
wszReplyMessages = L"\n" + wszReplyMessages;
wszBody += wszReplyMessages;
}
else if (jnReplyMessages["id"])
_ltoa(jnReplyMessages["id"].as_int(), szReplyId, 10);
CMStringW wszBodyNoAttachments = wszBody;
CMStringW wszAttachmentDescr;
const JSONNode& jnAttachments = jnMsg["attachments"];
if (jnAttachments && !jnAttachments.empty()) {
wszAttachmentDescr = GetAttachmentDescr(jnAttachments, m_vkOptions.BBCForAttachments(), hContact, iMessageId);
if (wszAttachmentDescr == L"== FilterAudioMessages ==") {
if (hContact && (iMessageId > ReadQSWord(hContact, "lastmsgid", -1)))
WriteQSWord(hContact, "lastmsgid", iMessageId);
continue;
}
if (!wszBody.IsEmpty())
wszBody += L"\n";
wszBody += wszAttachmentDescr;
}
if (m_vkOptions.bAddMessageLinkToMesWAtt && ((jnAttachments && !jnAttachments.empty()) || (jnFwdMessages && !jnFwdMessages.empty()) || (jnReplyMessages && !jnReplyMessages.empty() && m_vkOptions.bShowReplyInMessage)))
wszBody += SetBBCString(TranslateT("Message link"), m_vkOptions.BBCForAttachments(), vkbbcUrl,
CMStringW(FORMAT, L"https://vk.com/im?sel=%d&msgid=%d", iUserId, iMessageId));
VKMessageID_t iReadMsg = ReadQSWord(hContact, "in_read", 0);
bool bIsRead = (iMessageId <= iReadMsg);
time_t tUpdateTime = (time_t)jnMsg["update_time"].as_int();
bool bEdited = (tUpdateTime != 0);
if (bEdited) {
wchar_t ttime[64];
_locale_t locale = _create_locale(LC_ALL, "");
_wcsftime_l(ttime, _countof(ttime), TranslateT("%x at %X"), localtime(&tUpdateTime), locale);
_free_locale(locale);
wszBody = SetBBCString(
CMStringW(FORMAT, TranslateT("Edited message (updated %s):\n"), ttime),
m_vkOptions.BBCForAttachments(), vkbbcB) +
wszBody;
if (m_vkOptions.bShowBeforeEditedPostVersion) {
CMStringW wszOldMsg;
if (GetMessageFromDb(iMessageId, tDateTime, wszOldMsg))
wszBody += SetBBCString(TranslateT("\nOriginal message:\n"), m_vkOptions.BBCForAttachments(), vkbbcB) +
wszOldMsg;
}
}
DB::EventInfo dbei;
if (bIsRead && bUseServerReadFlag)
dbei.flags |= DBEF_READ;
if (isOut)
dbei.flags |= DBEF_SENT;
else if (m_vkOptions.bUserForceInvisibleOnActivity && ((time(0) - tDateTime) < (60 * m_vkOptions.iInvisibleInterval)))
SetInvisible(hContact);
T2Utf pszBody(wszBody);
dbei.timestamp = bEdited ? tDateTime : (m_vkOptions.bUseLocalTime ? time(0) : tDateTime);
dbei.pBlob = pszBody;
if (!m_vkOptions.bShowReplyInMessage && szReplyId) {
dbei.szReplyId = szReplyId;
debugLogA("CVkProto::OnReceiveMessages szReplyId = %s", szReplyId);
}
debugLogA("CVkProto::OnReceiveMessages mid = %d, datetime = %d, isOut = %d, isRead = %d, iUserId = %d, Edited = %d", iMessageId, tDateTime, isOut, (int)bIsRead, iUserId, (int)bEdited);
if (!IsMessageExist(iMessageId, vkALL) || bEdited || szReplyId) {
debugLogA("CVkProto::OnReceiveMessages new or edited message");
dbei.szId = szMid;
ProtoChainRecvMsg(hContact, dbei);
if (iMessageId > ReadQSWord(hContact, "lastmsgid", -1))
WriteQSWord(hContact, "lastmsgid", iMessageId);
}
else if (m_vkOptions.bLoadSentAttachments && !wszAttachmentDescr.IsEmpty()) {
CMStringW wszOldMsg;
if (GetMessageFromDb(iMessageId, tDateTime, wszOldMsg) && (wszOldMsg == wszBody))
continue;
if (wszBodyNoAttachments != wszOldMsg)
continue;
debugLogA("CVkProto::OnReceiveMessages add attachments");
T2Utf pszAttach(wszAttachmentDescr);
dbei.timestamp = isOut ? time(0) : tDateTime;
dbei.pBlob = pszAttach;
dbei.szId = strcat(szMid, "_");
ProtoChainRecvMsg(hContact, dbei);
}
}
}
void CVkProto::OnReceiveDlgs(MHttpResponse *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceiveDlgs %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONNode jnRoot;
const JSONNode &jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
if (!jnResponse)
return;
const JSONNode &jnDialogs = jnResponse["dialogs"];
if (!jnDialogs)
return;
const JSONNode &jnDlgs = jnDialogs["items"];
if (!jnDlgs)
return;
OBJLIST lufUsers(20, NumericKeySortT);
const JSONNode &jnUsers = jnResponse["users"];
if (jnUsers)
for (auto &it : jnUsers) {
VKUserID_t iUserId = it["user_id"].as_int();
int iStatus = it["friend_status"].as_int();
// iStatus == 3 - user is friend
// iUserId < 0 - user is group
if (GetVKPeerType(iUserId) != VKPeerType::vkPeerUser || iStatus != 3 || lufUsers.find((VKUserID_t *) &iUserId))
continue;
lufUsers.insert(new VKUserID_t(iUserId));
}
const JSONNode &jnGroups = jnResponse["groups"];
if (jnGroups)
for (auto &it : jnGroups) {
VKUserID_t iUserId = it.as_int();
if (lufUsers.find((VKUserID_t*) &iUserId))
continue;
lufUsers.insert(new VKUserID_t(iUserId));
}
CMStringA szGroupIds;
for (auto& it : jnDlgs) {
if (!it)
break;
const JSONNode& jnConversation = it["conversation"];
const JSONNode& jnLastMessage = it["last_message"];
if (!jnConversation)
break;
int iUnreadCount = jnConversation["unread_count"].as_int();
const JSONNode& jnPeer = jnConversation["peer"];
if (!jnPeer)
break;
VKUserID_t iUserId = 0;
MCONTACT hContact(0);
CMStringW wszPeerType(jnPeer["type"].as_mstring());
if (wszPeerType == L"user" || wszPeerType == L"group") {
iUserId = jnPeer["id"].as_int();
VKUserID_t *pUserID = lufUsers.find((VKUserID_t*) &iUserId);
debugLogA("CVkProto::OnReceiveDlgs UserId = %d, iIndex = %p, numUnread = %d", iUserId, pUserID, iUnreadCount);
if (m_vkOptions.bLoadOnlyFriends && iUnreadCount == 0 && !pUserID)
continue;
hContact = FindUser(iUserId, true);
debugLogA("CVkProto::OnReceiveDlgs add UserId = %d", iUserId);
if (IsGroupUser(hContact))
szGroupIds.AppendFormat(szGroupIds.IsEmpty() ? "%d" : ",%d", -1 * iUserId);
WriteQSWord(hContact, "in_read", jnConversation["in_read"].as_int());
WriteQSWord(hContact, "out_read", jnConversation["out_read"].as_int());
}
if (wszPeerType == L"chat") {
VKUserID_t iChatId = jnPeer["local_id"].as_int();
debugLogA("CVkProto::OnReceiveDlgs chatid = %d", iChatId);
if (m_chats.find((CVkChatInfo*)&iChatId) == nullptr)
AppendConversationChat(iChatId, it);
hContact = FindChat(iChatId);
}
if (g_bMessageState) {
bool bIsOut = jnLastMessage["out"].as_bool();
bool bIsRead = (jnLastMessage["id"].as_int() <= jnConversation["in_read"].as_int());
if (bIsRead && bIsOut)
db_event_delivered(hContact, 0);
}
if (m_vkOptions.iSyncHistoryMetod) {
VKMessageID_t iMessageId = jnLastMessage["id"].as_int();
m_bNotifyForEndLoadingHistory = false;
if (ReadQSWord(hContact, "lastmsgid", -1) == -1 && iUnreadCount && !getBool(hContact, "ActiveHistoryTask")) {
setByte(hContact, "ActiveHistoryTask", 1);
GetServerHistory(hContact, 0, iUnreadCount, 0, 0, true);
}
else
GetHistoryDlg(hContact, iMessageId);
if (m_vkOptions.iMarkMessageReadOn == MarkMsgReadOn::markOnReceive && iUnreadCount)
MarkMessagesRead(hContact);
}
else if (iUnreadCount && !getBool(hContact, "ActiveHistoryTask")) {
m_bNotifyForEndLoadingHistory = false;
setByte(hContact, "ActiveHistoryTask", 1);
GetServerHistory(hContact, 0, iUnreadCount, 0, 0, true);
if (m_vkOptions.iMarkMessageReadOn == MarkMsgReadOn::markOnReceive)
MarkMessagesRead(hContact);
}
if (jnConversation["can_write"] && jnConversation["can_write"]["allowed"])
Contact::Readonly(hContact, !jnConversation["can_write"]["allowed"].as_bool());
}
lufUsers.destroy();
RetrieveUsersInfo();
RetrieveGroupInfo(szGroupIds);
}