#include "stdafx.h" #define POLLING_ERRORS_LIMIT 5 void CSteamProto::ParsePollData(const JSONNode &data) { for (const JSONNode &item : data) { json_string steamId = item["steamid_from"].as_string(); time_t timestamp = _wtol(item["utc_timestamp"].as_mstring()); MCONTACT hContact = NULL; if (!IsMe(steamId.c_str()) && !(hContact = GetContact(steamId.c_str()))) // probably this is info about random player playing on same server, so we ignore it continue; json_string type = item["type"].as_string(); if (type == "my_saytext" || type =="my_emote") { json_string text = item["text"].as_string(); PROTORECVEVENT recv = { 0 }; recv.timestamp = timestamp; recv.szMessage = (char*)text.c_str(); recv.flags = PREF_SENT; RecvMsg(hContact, &recv); } else if (type == "saytext" || type =="emote") { json_string text = item["text"].as_string(); PROTORECVEVENT recv = { 0 }; recv.timestamp = timestamp; recv.szMessage = (char*)text.c_str(); ProtoChainRecvMsg(hContact, &recv); CallService(MS_PROTO_CONTACTISTYPING, hContact, (LPARAM)PROTOTYPE_CONTACTTYPING_OFF); m_typingTimestamps[steamId] = 0; } else if (type == "typing") { auto it = m_typingTimestamps.find(steamId); if (it != m_typingTimestamps.end()) { if ((timestamp - it->second) < STEAM_TYPING_TIME) continue; } CallService(MS_PROTO_CONTACTISTYPING, hContact, (LPARAM)STEAM_TYPING_TIME); m_typingTimestamps[steamId] = timestamp; } else if (type == "personastate") { if (!IsMe(steamId.c_str())) { // there no sense to change own status JSONNode node = item["persona_state"]; if (!node.isnull()) { int status = SteamToMirandaStatus((PersonaState)node.as_int()); SetContactStatus(hContact, status); } } int statusFlags = item["status_flags"].as_int(); if ((statusFlags & PersonaStatusFlag::PlayerName) == PersonaStatusFlag::PlayerName) { CMStringW nick = item["persona_name"].as_mstring(); if (!nick.IsEmpty()) setWString(hContact, "Nick", nick); } } else if (type == "personarelationship") { int state = item["persona_state"].as_int(); switch (state) { case PersonaRelationshipAction::Remove: hContact = GetContact(steamId.c_str()); if (hContact) ContactIsRemoved(hContact); break; case PersonaRelationshipAction::Ignore: hContact = GetContact(steamId.c_str()); if (hContact) ContactIsBlocked(hContact); break; case PersonaRelationshipAction::AuthRequest: hContact = GetContact(steamId.c_str()); if (hContact) ContactIsAskingAuth(hContact); else { // load info about this user from server ptrA token(getStringA("TokenSecret")); PushRequest( new GetUserSummariesRequest(token, steamId.c_str()), &CSteamProto::OnAuthRequested); } break; case PersonaRelationshipAction::AuthRequested: hContact = GetContact(steamId.c_str()); if (hContact) ContactIsFriend(hContact); default: continue; } } else if (type == "leftconversation") { if (!getBool("ShowChatEvents", true)) continue; BYTE bEventType = STEAM_DB_EVENT_CHATSTATES_GONE; DBEVENTINFO dbei = {}; dbei.pBlob = &bEventType; dbei.cbBlob = 1; dbei.eventType = EVENTTYPE_STEAM_CHATSTATES; dbei.flags = DBEF_READ; dbei.timestamp = now(); dbei.szModule = m_szModuleName; db_event_add(hContact, &dbei); } else { debugLogA(__FUNCTION__ ": Unknown event type \"%s\"", type.c_str()); continue; } } /*if (!steamIds.empty()) { steamIds.pop_back(); ptrA token(getStringA("TokenSecret")); PushRequest( new GetUserSummariesRequest(token, steamIds.c_str()), &CSteamProto::OnGotUserSummaries); }*/ } struct PollParam { int errors; int errorsLimit; }; void CSteamProto::OnGotPoll(const HttpResponse &response, void *arg) { PollParam *param = (PollParam*)arg; if (!response.IsSuccess()) { param->errors++; return; } JSONNode root = JSONNode::parse(response.Content); if (root.isnull()) { param->errors++; return; } json_string error = root["error"].as_string(); if (error == "Timeout") // Do nothing as this is not necessarily an error return; if (error == "OK") { // Remember last message timestamp time_t timestamp = _wtoi64(root["utc_timestamp"].as_mstring()); if (timestamp > getDword("LastMessageTS", 0)) setDword("LastMessageTS", timestamp); long messageId = root["messagelast"].as_int(); setDword("MessageID", messageId); JSONNode data = root["messages"].as_array(); ParsePollData(data); // Reset error counter only when we've got OK param->errors = 0; // m_pollingConnection = response->nlc; return; } if (error == "Not Logged On") { // 'else' below will handle this error, we don't need this particular check right now // need to relogin debugLogA(__FUNCTION__ ": Not Logged On"); // try to reconnect only when we're actually online (during normal logout we will still got this error anyway, but in that case our status is already offline) if (IsOnline()) { ptrA token(getStringA("TokenSecret")); SendRequest( new LogonRequest(token), &CSteamProto::OnReLogin); } return; } // something wrong debugLogA(__FUNCTION__ ": %s (%d)", error.c_str(), response.GetStatusCode()); // token has expired if (response.GetStatusCode() == HTTP_CODE_UNAUTHORIZED) delSetting("TokenSecret"); // too low timeout? int timeout = root["sectimeout"].as_int(); if (timeout < STEAM_API_TIMEOUT) debugLogA(__FUNCTION__ ": Timeout is too low (%d)", timeout); // let it jump out of further processing param->errors = param->errorsLimit; } void CSteamProto::PollingThread(void*) { debugLogA(__FUNCTION__ ": entering"); ptrA token(getStringA("TokenSecret")); PollParam param; param.errors = 0; param.errorsLimit = getByte("PollingErrorsLimit", POLLING_ERRORS_LIMIT); while (IsOnline() && param.errors < param.errorsLimit) { // request->nlc = m_pollingConnection; ptrA umqId(getStringA("UMQID")); UINT32 messageId = getDword("MessageID", 0); SendRequest( new PollRequest(token, umqId, messageId, IdleSeconds()), &CSteamProto::OnGotPoll, (void*)¶m); } if (IsOnline()) { debugLogA(__FUNCTION__ ": unexpected termination; switching protocol to offline"); SetStatus(ID_STATUS_OFFLINE); } m_hPollingThread = nullptr; debugLogA(__FUNCTION__ ": leaving"); }