/* Copyright (c) 2013-18 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(NETLIBHTTPREQUEST *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)"); m_pollingConn = nullptr; ShutdownSession(); } return; } char ts[32]; itoa(jnResponse["ts"].as_int(), ts, 10); m_pollingTs = mir_strdup(ts); m_pollingKey = mir_u2a(jnResponse["key"].as_mstring()); m_pollingServer = 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_pollingTs ? m_pollingTs : "", m_pollingKey ? m_pollingKey : "", m_pollingServer ? m_pollingServer : ""); if (m_pollingTs != nullptr && m_pollingKey != nullptr && m_pollingServer != nullptr) { debugLogA("CVkProto::OnReceivePollingInfo PollingThread starting..."); m_hPollingThread = ForkThreadEx(&CVkProto::PollingThread, nullptr, nullptr); } else { debugLogA("CVkProto::OnReceivePollingInfo PollingThread not start"); m_pollingConn = nullptr; ShutdownSession(); return; } } else debugLogA("CVkProto::OnReceivePollingInfo m_hPollingThread is not nullptr"); } void CVkProto::PollUpdates(const JSONNode &jnUpdates) { debugLogA("CVkProto::PollUpdates"); CMStringA mids; int msgid, uid, flags, platform; bool bNonEdited = true; MCONTACT hContact; 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; msgid = jnChild[1].as_int(); flags = jnChild[2].as_int(); uid = jnChild[3].as_int(); hContact = FindUser(uid); if (hContact != 0 && (flags & VKFLAG_MSGUNREAD) && !CheckMid(m_incIds, msgid)) { setDword(hContact, "LastMsgReadTime", time(0)); if (ServiceExists(MS_MESSAGESTATE_UPDATE)) { MessageReadData data(time(0), MRD_TYPE_READTIME); CallService(MS_MESSAGESTATE_UPDATE, hContact, (LPARAM)&data); } else SetSrmmReadStatus(hContact); if (m_vkOptions.bUserForceInvisibleOnActivity) SetInvisible(hContact); if (m_vkOptions.bSyncReadMessageStatusFromServer) MarkDialogAsRead(hContact); } break; case VKPOLL_MSG_EDITED: bNonEdited = false; case VKPOLL_MSG_ADDED: // new message msgid = jnChild[1].as_int(); // skip outgoing messages sent from a client flags = jnChild[2].as_int(); if (bNonEdited && (flags & VKFLAG_MSGOUTBOX && !(flags & VKFLAG_MSGCHAT) && !m_vkOptions.bSendVKLinksAsAttachments && CheckMid(m_sendIds, msgid))) break; if (!mids.IsEmpty()) mids.AppendChar(','); mids.AppendFormat("%d", msgid); if(!bNonEdited) m_editedIds.insert((HANDLE)msgid); bNonEdited = true; break; case VKPOLL_READ_ALL_OUT: uid = jnChild[1].as_int(); hContact = FindUser(uid); if (hContact != 0) { setDword(hContact, "LastMsgReadTime", time(0)); if (ServiceExists(MS_MESSAGESTATE_UPDATE)) { MessageReadData data(time(0), MRD_TYPE_READTIME); CallService(MS_MESSAGESTATE_UPDATE, hContact, (LPARAM)&data); } else SetSrmmReadStatus(hContact); if (m_vkOptions.bUserForceInvisibleOnActivity) SetInvisible(hContact); } break; case VKPOLL_READ_ALL_IN: uid = jnChild[1].as_int(); hContact = FindUser(uid); if (hContact != 0 && m_vkOptions.bSyncReadMessageStatusFromServer) MarkDialogAsRead(hContact); break; case VKPOLL_USR_ONLINE: uid = -jnChild[1].as_int(); if ((hContact = FindUser(uid)) != 0) { setWord(hContact, "Status", ID_STATUS_ONLINE); platform = jnChild[2].as_int(); SetMirVer(hContact, platform); } break; case VKPOLL_USR_OFFLINE: uid = -jnChild[1].as_int(); if ((hContact = FindUser(uid)) != 0) { setWord(hContact, "Status", ID_STATUS_OFFLINE); db_unset(hContact, m_szModuleName, "ListeningTo"); SetMirVer(hContact, -1); } break; case VKPOLL_USR_UTN: uid = jnChild[1].as_int(); hContact = FindUser(uid); 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: int chat_id = jnChild[1].as_int(); CVkChatInfo *cc = m_chats.find((CVkChatInfo*)&chat_id); if (cc) RetrieveChatInfo(cc); break; } } RetrieveMessagesByIds(mids); } int CVkProto::PollServer() { debugLogA("CVkProto::PollServer"); if (!IsOnline()) { debugLogA("CVkProto::PollServer is dead (not online)"); m_pollingConn = nullptr; ShutdownSession(); return 0; } debugLogA("CVkProto::PollServer (online)"); int iPollConnRetry = MAX_RETRIES; NETLIBHTTPREQUEST *reply; CMStringA szReqUrl(FORMAT, "https://%s?act=a_check&key=%s&ts=%s&wait=25&access_token=%s&mode=%d&version=%d", m_pollingServer, m_pollingKey, m_pollingTs, m_szAccessToken, 106, 2); // see mode parametr description on https://vk.com/dev/using_longpoll (Russian version) NETLIBHTTPREQUEST req = {}; req.cbSize = sizeof(req); req.requestType = REQUEST_GET; req.szUrl = szReqUrl.GetBuffer(); req.flags = VK_NODUMPHEADERS | NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_SSL; req.timeout = 30000; req.nlc = m_pollingConn; while ((reply = Netlib_HttpTransaction(m_hNetlibUser, &req)) == nullptr) { debugLogA("CVkProto::PollServer is dead"); m_pollingConn = nullptr; 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->pData); 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_pollingTs = 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); m_pollingConn = nullptr; Netlib_FreeHttpRequest(reply); ShutdownSession(); return 0; } m_pollingConn = reply->nlc; Netlib_FreeHttpRequest(reply); debugLogA("CVkProto::PollServer return %d", retVal); return retVal; } void CVkProto::PollingThread(void*) { debugLogA("CVkProto::PollingThread: entering"); while (!m_bTerminated) if (PollServer() == -1 || !m_hPollingThread) break; m_pollingConn = nullptr; debugLogA("CVkProto::PollingThread: leaving"); if (m_hPollingThread) { CloseHandle(m_hPollingThread); m_hPollingThread = nullptr; } }