/*
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"
void CVkProto::RetrievePollingInfo()
{
debugLogA("CVkProto::RetrievePollingInfo");
if (!IsOnline())
return;
Push(new AsyncHttpRequest(this, REQUEST_GET, "/method/messages.getLongPollServer.json", true, &CVkProto::OnReceivePollingInfo, AsyncHttpRequest::rpHigh)
<< INT_PARAM("use_ssl", 1)
<< INT_PARAM("lp_version", 3)
);
}
void CVkProto::OnReceivePollingInfo(MHttpResponse *reply, AsyncHttpRequest *pReq)
{
debugLogA("CVkProto::OnReceivePollingInfo %d", reply->resultCode);
if (reply->resultCode != 200)
return;
JSONNode jnRoot;
JSONNode jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
if (!jnResponse) {
if (!pReq->bNeedsRestart) {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread not start (getLongPollServer error)");
ClosePollingConnection();
ShutdownSession();
}
return;
}
char ts[32];
itoa(jnResponse["ts"].as_int(), ts, 10);
m_szPollingTs = mir_strdup(ts);
m_szPollingKey = mir_u2a(jnResponse["key"].as_mstring());
m_szPollingServer = mir_u2a(jnResponse["server"].as_mstring());
if (!m_hPollingThread) {
debugLogA("CVkProto::OnReceivePollingInfo m_hPollingThread is nullptr");
debugLogA("CVkProto::OnReceivePollingInfo m_pollingTs = '%s' m_pollingKey = '%s' m_pollingServer = '%s'",
m_szPollingTs ? m_szPollingTs.get() : "",
m_szPollingKey ? m_szPollingKey.get() : "",
m_szPollingServer ? m_szPollingServer.get() : "");
if (m_szPollingTs != nullptr && m_szPollingKey != nullptr && m_szPollingServer != nullptr) {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread starting...");
m_hPollingThread = ForkThreadEx(&CVkProto::PollingThread, nullptr, nullptr);
}
else {
debugLogA("CVkProto::OnReceivePollingInfo PollingThread not start");
ClosePollingConnection();
ShutdownSession();
return;
}
}
else
debugLogA("CVkProto::OnReceivePollingInfo m_hPollingThread is not nullptr");
}
void CVkProto::PollUpdates(const JSONNode &jnUpdates)
{
debugLogA("CVkProto::PollUpdates");
CMStringA szMids;
VKMessageID_t iMessageId;
int iFlags, iPlatform;
VKUserID_t iUserId;
MCONTACT hContact;
time_t tDateTime = 0;
CMStringW wszMsg;
for (auto &it : jnUpdates) {
const JSONNode &jnChild = it.as_array();
switch (jnChild[json_index_t(0)].as_int()) {
case VKPOLL_MSG_DELFLAGS:
if (jnChild.size() < 4)
break;
iMessageId = jnChild[1].as_int();
iFlags = jnChild[2].as_int();
iUserId = jnChild[3].as_int();
hContact = FindUser(iUserId);
if (hContact != 0 && (iFlags & VKFLAG_MSGDELETED)) {
if (!szMids.IsEmpty())
szMids.AppendChar(',');
szMids.AppendFormat("%d", iMessageId);
}
if (hContact != 0 && (iFlags & VKFLAG_MSGUNREAD) && !IsMessageExist(iMessageId, vkIN)) {
setDword(hContact, "LastMsgReadTime", time(0));
if (g_bMessageState)
CallService(MS_MESSAGESTATE_UPDATE, hContact, MRD_TYPE_READ);
else
SetSrmmReadStatus(hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
if (m_vkOptions.bSyncReadMessageStatusFromServer)
MarkDialogAsRead(hContact);
}
break;
case VKPOLL_MSG_ADDFLAGS:
if (jnChild.size() < 4)
break;
iMessageId = jnChild[1].as_int();
iFlags = jnChild[2].as_int();
iUserId = jnChild[3].as_int();
hContact = FindUser(iUserId);
if (hContact != 0 && (iFlags & VKFLAG_MSGDELETED) && IsMessageExist(iMessageId, vkALL) && GetMessageFromDb(iMessageId, tDateTime, wszMsg)) {
wchar_t ttime[64];
time_t tDeleteTime = time(0);
_locale_t locale = _create_locale(LC_ALL, "");
_wcsftime_l(ttime, _countof(ttime), TranslateT("%x at %X"), localtime(&tDeleteTime), locale);
_free_locale(locale);
wszMsg = SetBBCString(
CMStringW(FORMAT, TranslateT("This message has been deleted by sender in %s:\n"), ttime),
m_vkOptions.BBCForAttachments(), vkbbcB) +
wszMsg;
DB::EventInfo dbei;
if (iUserId == m_iMyUserId)
dbei.flags |= DBEF_SENT;
else if (m_vkOptions.bUserForceInvisibleOnActivity && time(0) - tDateTime < 60 * m_vkOptions.iInvisibleInterval)
SetInvisible(hContact);
char szMid[40];
_itoa(iMessageId, szMid, 10);
T2Utf pszMsg(wszMsg);
dbei.timestamp = tDateTime;
dbei.pBlob = pszMsg;
dbei.szId = szMid;
ProtoChainRecvMsg(hContact, dbei);
}
break;
case VKPOLL_MSG_EDITED:
iMessageId = jnChild[1].as_int();
if (!szMids.IsEmpty())
szMids.AppendChar(',');
szMids.AppendFormat("%d", iMessageId);
break;
case VKPOLL_MSG_ADDED: // new message
iMessageId = jnChild[1].as_int();
// skip outgoing messages sent from a client
iFlags = jnChild[2].as_int();
if (iFlags & VKFLAG_MSGOUTBOX && !(iFlags & VKFLAG_MSGCHAT) && !m_vkOptions.bSendVKLinksAsAttachments && IsMessageExist(iMessageId, vkOUT))
break;
if (!szMids.IsEmpty())
szMids.AppendChar(',');
szMids.AppendFormat("%d", iMessageId);
break;
case VKPOLL_READ_ALL_OUT:
iUserId = jnChild[1].as_int();
hContact = FindUser(iUserId);
if (hContact != 0) {
setDword(hContact, "LastMsgReadTime", time(0));
if (g_bMessageState)
CallService(MS_MESSAGESTATE_UPDATE, hContact, MRD_TYPE_READ);
else
SetSrmmReadStatus(hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
}
break;
case VKPOLL_READ_ALL_IN:
iUserId = jnChild[1].as_int();
hContact = FindUser(iUserId);
if (hContact != 0 && m_vkOptions.bSyncReadMessageStatusFromServer)
MarkDialogAsRead(hContact);
break;
case VKPOLL_USR_ONLINE:
iUserId = -jnChild[1].as_int();
if ((hContact = FindUser(iUserId)) != 0) {
setWord(hContact, "Status", ID_STATUS_ONLINE);
iPlatform = jnChild[2].as_int();
SetMirVer(hContact, iPlatform);
}
break;
case VKPOLL_USR_OFFLINE:
iUserId = -jnChild[1].as_int();
if ((hContact = FindUser(iUserId)) != 0) {
setWord(hContact, "Status", ID_STATUS_OFFLINE);
db_unset(hContact, m_szModuleName, "ListeningTo");
SetMirVer(hContact, -1);
}
break;
case VKPOLL_USR_UTN:
iUserId = jnChild[1].as_int();
hContact = FindUser(iUserId);
if (hContact != 0) {
ForkThread(&CVkProto::ContactTypingThread, (void *)hContact);
if (m_vkOptions.bUserForceInvisibleOnActivity)
SetInvisible(hContact);
}
break;
case VKPOLL_CHAT_UTN:
ForkThread(&CVkProto::ChatContactTypingThread, new CVKChatContactTypingParam(jnChild[2].as_int(), jnChild[1].as_int()));
break;
case VKPOLL_CHAT_CHANGED:
VKUserID_t iChatId = jnChild[1].as_int();
CVkChatInfo *cc = m_chats.find((CVkChatInfo*)&iChatId);
if (cc)
RetrieveChatInfo(cc);
break;
}
}
RetrieveMessagesByIds(szMids);
}
int CVkProto::PollServer()
{
debugLogA("CVkProto::PollServer");
if (!IsOnline()) {
debugLogA("CVkProto::PollServer is dead (not online)");
ClosePollingConnection();
ShutdownSession();
return 0;
}
debugLogA("CVkProto::PollServer (online)");
int iPollConnRetry = MAX_RETRIES;
CMStringA szReqUrl(FORMAT, "https://%s?act=a_check&key=%s&ts=%s&wait=25&access_token=%s&mode=%d&version=%d", m_szPollingServer, m_szPollingKey, m_szPollingTs, m_szAccessToken, 106, 2);
// see mode parametr description on https://vk.com/dev/using_longpoll (Russian version)
MHttpRequest req(REQUEST_GET);
req.m_szUrl = szReqUrl.GetBuffer();
req.flags = VK_NODUMPHEADERS | NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_SSL;
req.timeout = 30000;
req.nlc = m_hPollingConn;
time_t tLocalPoolThreadTimer;
{
mir_cslock lck(m_csPoolThreadTimer);
tLocalPoolThreadTimer = m_tPoolThreadTimer = time(0);
}
NLHR_PTR reply(0);
while ((reply = Netlib_HttpTransaction(m_hNetlibUser, &req)) == nullptr) {
{
mir_cslock lck(m_csPoolThreadTimer);
if (m_tPoolThreadTimer != tLocalPoolThreadTimer) {
debugLogA("CVkProto::PollServer is living dead => return");
return -2;
}
}
debugLogA("CVkProto::PollServer is dead");
ClosePollingConnection();
if (iPollConnRetry && !m_bTerminated) {
iPollConnRetry--;
debugLogA("CVkProto::PollServer restarting %d", MAX_RETRIES - iPollConnRetry);
Sleep(1000);
}
else {
debugLogA("CVkProto::PollServer => ShutdownSession");
ShutdownSession();
return 0;
}
}
int retVal = 0;
if (reply->resultCode == 200) {
JSONNode jnRoot = JSONNode::parse(reply->body);
const JSONNode &jnFailed = jnRoot["failed"];
if (jnFailed && jnFailed.as_int() > 1) {
RetrievePollingInfo();
retVal = -1;
debugLogA("Polling key expired, restarting polling thread");
}
else if (CheckJsonResult(nullptr, jnRoot)) {
char ts[32];
itoa(jnRoot["ts"].as_int(), ts, 10);
m_szPollingTs = mir_strdup(ts);
const JSONNode &jnUpdates = jnRoot["updates"];
if (jnUpdates)
PollUpdates(jnUpdates);
retVal = 1;
}
}
else if ((reply->resultCode >= 400 && reply->resultCode <= 417)
|| (reply->resultCode >= 500 && reply->resultCode <= 509)) {
debugLogA("CVkProto::PollServer is dead. Error code - %d", reply->resultCode);
ClosePollingConnection();
ShutdownSession();
return 0;
}
m_hPollingConn = reply->nlc;
debugLogA("CVkProto::PollServer return %d", retVal);
return retVal;
}
void CVkProto::PollingThread(void*)
{
debugLogA("CVkProto::PollingThread: entering");
while (!m_bTerminated) {
int iRetVal = PollServer();
if (iRetVal == -2)
return;
if (iRetVal == -1 || !m_hPollingThread)
break;
}
ClosePollingConnection();
debugLogA("CVkProto::PollingThread: leaving");
if (m_hPollingThread) {
CloseHandle(m_hPollingThread);
m_hPollingThread = nullptr;
}
}