#include "stdafx.h" CSteamProto::CSteamProto(const char* protoName, const wchar_t* userName) : PROTO(protoName, userName), m_requestQueue(1), hAuthProcess(1), hMessageProcess(1) { CreateProtoService(PS_CREATEACCMGRUI, &CSteamProto::OnAccountManagerInit); m_idleTS = 0; m_lastMessageTS = 0; isLoginAgain = false; m_hPollingThread = nullptr; m_hRequestsQueueEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // default group m_defaultGroup = getWStringA("DefaultGroup"); if (m_defaultGroup == nullptr) m_defaultGroup = mir_wstrdup(L"Steam"); // icons wchar_t filePath[MAX_PATH]; GetModuleFileName(g_plugin.getInst(), filePath, MAX_PATH); wchar_t sectionName[100]; mir_snwprintf(sectionName, L"%s/%s", LPGENW("Protocols"), _A2W(MODULE)); char settingName[100]; mir_snprintf(settingName, "%s_%s", MODULE, "main"); SKINICONDESC sid = {}; sid.flags = SIDF_ALL_UNICODE; sid.defaultFile.w = filePath; sid.pszName = settingName; sid.section.w = sectionName; sid.description.w = LPGENW("Protocol icon"); sid.iDefaultIndex = -IDI_STEAM; IcoLib_AddIcon(&sid); mir_snprintf(settingName, "%s_%s", MODULE, "gaming"); sid.description.w = LPGENW("Gaming icon"); sid.iDefaultIndex = -IDI_GAMING; IcoLib_AddIcon(&sid); // temporary DB settings db_set_resident(m_szModuleName, "XStatusId"); db_set_resident(m_szModuleName, "XStatusName"); db_set_resident(m_szModuleName, "XStatusMsg"); db_set_resident(m_szModuleName, "IdleTS"); db_set_resident(m_szModuleName, "GameID"); db_set_resident(m_szModuleName, "ServerIP"); db_set_resident(m_szModuleName, "ServerID"); SetAllContactStatuses(ID_STATUS_OFFLINE); // avatar API CreateProtoService(PS_GETAVATARINFO, &CSteamProto::GetAvatarInfo); CreateProtoService(PS_GETAVATARCAPS, &CSteamProto::GetAvatarCaps); CreateProtoService(PS_GETMYAVATAR, &CSteamProto::GetMyAvatar); // custom status API CreateProtoService(PS_GETCUSTOMSTATUSEX, &CSteamProto::OnGetXStatusEx); CreateProtoService(PS_GETCUSTOMSTATUSICON, &CSteamProto::OnGetXStatusIcon); CreateProtoService(PS_GETADVANCEDSTATUSICON, &CSteamProto::OnRequestAdvStatusIconIdx); // menus CreateProtoService(PS_MENU_REQAUTH, &CSteamProto::AuthRequestCommand); CreateProtoService(PS_MENU_REVOKEAUTH, &CSteamProto::AuthRevokeCommand); // custom db events API CreateProtoService(STEAM_DB_GETEVENTTEXT_CHATSTATES, &CSteamProto::OnGetEventTextChatStates); // netlib support wchar_t name[128]; mir_snwprintf(name, TranslateT("%s connection"), m_tszUserName); NETLIBUSER nlu = {}; nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; nlu.szDescriptiveName.w = name; nlu.szSettingsModule = m_szModuleName; m_hNetlibUser = Netlib_RegisterUser(&nlu); debugLogA(__FUNCTION__":Setting protocol / module name to '%s'", m_szModuleName); } CSteamProto::~CSteamProto() { if (m_hNetlibUser) { Netlib_CloseHandle(m_hNetlibUser); m_hNetlibUser = nullptr; } if (m_hRequestsQueueEvent) { CloseHandle(m_hRequestsQueueEvent); m_hRequestsQueueEvent = nullptr; } } MCONTACT CSteamProto::AddToList(int, PROTOSEARCHRESULT *psr) { _T2A steamId(psr->id.w); MCONTACT hContact = AddContact(steamId, psr->nick.w, true); if (psr->cbSize == sizeof(STEAM_SEARCH_RESULT)) { STEAM_SEARCH_RESULT *ssr = (STEAM_SEARCH_RESULT*)psr; UpdateContactDetails(hContact, *ssr->data); } return hContact; } int CSteamProto::Authorize(MEVENT hDbEvent) { if (IsOnline() && hDbEvent) { MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); if (hContact == INVALID_CONTACT_ID) return 1; ptrA token(getStringA("TokenSecret")); ptrA sessionId(getStringA("SessionID")); ptrA steamId(getStringA("SteamID")); char *who = getStringA(hContact, "SteamID"); PushRequest( new ApprovePendingRequest(token, sessionId, steamId, who), &CSteamProto::OnPendingApproved, who); return 0; } return 1; } int CSteamProto::AuthRecv(MCONTACT hContact, PROTORECVEVENT *pre) { // remember to not create this event again, unless authorization status changes again setByte(hContact, "AuthAsked", 1); return Proto_AuthRecv(m_szModuleName, pre); } int CSteamProto::AuthDeny(MEVENT hDbEvent, const wchar_t*) { if (IsOnline() && hDbEvent) { MCONTACT hContact = GetContactFromAuthEvent(hDbEvent); if (hContact == INVALID_CONTACT_ID) return 1; ptrA token(getStringA("TokenSecret")); ptrA sessionId(getStringA("SessionID")); ptrA steamId(getStringA("SteamID")); char *who = getStringA(hContact, "SteamID"); PushRequest( new IgnorePendingRequest(token, sessionId, steamId, who), &CSteamProto::OnPendingIgnoreded, who); return 0; } return 1; } int CSteamProto::AuthRequest(MCONTACT hContact, const wchar_t*) { if (IsOnline() && hContact) { UINT hAuth = InterlockedIncrement(&hAuthProcess); SendAuthParam *param = (SendAuthParam*)mir_calloc(sizeof(SendAuthParam)); param->hContact = hContact; param->hAuth = (HANDLE)hAuth; ptrA token(getStringA("TokenSecret")); ptrA sessionId(getStringA("SessionID")); ptrA steamId(getStringA("SteamID")); ptrA who(getStringA(hContact, "SteamID")); PushRequest( new AddFriendRequest(token, sessionId, steamId, who), &CSteamProto::OnFriendAdded, param); return hAuth; } return 1; } INT_PTR CSteamProto::GetCaps(int type, MCONTACT) { switch (type) { case PFLAGNUM_1: return PF1_IM | PF1_BASICSEARCH | PF1_SEARCHBYNAME | PF1_AUTHREQ | PF1_SERVERCLIST | PF1_ADDSEARCHRES | PF1_MODEMSGRECV; case PFLAGNUM_2: return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_OUTTOLUNCH | PF2_FREECHAT; case PFLAGNUM_4: return PF4_AVATARS | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_FORCEAUTH | PF4_FORCEADDED | PF4_SUPPORTIDLE | PF4_SUPPORTTYPING;// | PF4_IMSENDOFFLINE; case PFLAGNUM_5: return PF2_HEAVYDND | PF2_OUTTOLUNCH | PF2_FREECHAT; case PFLAG_UNIQUEIDTEXT: return (INT_PTR)Translate("SteamID"); default: return 0; } } HANDLE CSteamProto::SearchBasic(const wchar_t* id) { if (!this->IsOnline()) return nullptr; ptrA token(getStringA("TokenSecret")); ptrA steamId(mir_u2a(id)); PushRequest( new GetUserSummariesRequest(token, steamId), &CSteamProto::OnSearchResults, (HANDLE)STEAM_SEARCH_BYID); return (HANDLE)STEAM_SEARCH_BYID; } HANDLE CSteamProto::SearchByName(const wchar_t* nick, const wchar_t* firstName, const wchar_t* lastName) { if (!IsOnline()) return nullptr; // Combine all fields to single text wchar_t keywordsT[200]; mir_snwprintf(keywordsT, L"%s %s %s", nick, firstName, lastName); ptrA token(getStringA("TokenSecret")); ptrA keywords(mir_utf8encodeW(rtrimw(keywordsT))); PushRequest( new SearchRequest(token, keywords), &CSteamProto::OnSearchByNameStarted, (HANDLE)STEAM_SEARCH_BYNAME); return (HANDLE)STEAM_SEARCH_BYNAME; } int CSteamProto::SendMsg(MCONTACT hContact, int, const char *message) { if (!IsOnline()) { ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, nullptr, (LPARAM)Translate("You cannot send messages when you are offline.")); return 0; } return OnSendMessage(hContact, message); } int CSteamProto::SetStatus(int new_status) { // Routing statuses not supported by Steam switch (new_status) { case ID_STATUS_OFFLINE: case ID_STATUS_AWAY: case ID_STATUS_NA: break; case ID_STATUS_DND: case ID_STATUS_OCCUPIED: case ID_STATUS_ONTHEPHONE: case ID_STATUS_OUTTOLUNCH: new_status = ID_STATUS_NA; break; default: new_status = ID_STATUS_ONLINE; break; } int old_status; { mir_cslock lock(m_setStatusLock); if (new_status == m_iDesiredStatus) return 0; debugLogA(__FUNCTION__ ": changing status from %i to %i", m_iStatus, new_status); old_status = m_iStatus; m_iDesiredStatus = new_status; } if (new_status == ID_STATUS_OFFLINE) { // Reset relogin flag isLoginAgain = false; m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; if (!Miranda_IsTerminated()) SetAllContactStatuses(ID_STATUS_OFFLINE); Logout(); } else if (old_status == ID_STATUS_OFFLINE) { // Load last message timestamp for correct loading of messages history m_lastMessageTS = getDword("LastMessageTS", 0); m_iStatus = ID_STATUS_CONNECTING; m_isTerminated = false; m_hRequestQueueThread = ForkThreadEx(&CSteamProto::RequestQueueThread, nullptr, nullptr); Login(); } else m_iStatus = new_status; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus); return 0; } void CSteamProto::GetAwayMsgThread(void *arg) { // Maybe not needed, but better to be sure that this won't happen faster than core handling return value of GetAwayMsg() Sleep(50); MCONTACT hContact = (UINT_PTR)arg; CMStringW message(db_get_wsa(hContact, "CList", "StatusMsg")); // if contact has no status message, get xstatus message if (message.IsEmpty()) { ptrW xStatusName(getWStringA(hContact, "XStatusName")); ptrW xStatusMsg(getWStringA(hContact, "XStatusMsg")); if (xStatusName) message.AppendFormat(L"%s: %s", xStatusName, xStatusMsg); else message.Append(xStatusMsg); } ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)message.c_str()); } HANDLE CSteamProto::GetAwayMsg(MCONTACT hContact) { ForkThread(&CSteamProto::GetAwayMsgThread, (void*)hContact); return (HANDLE)1; } void CSteamProto::OnContactDeleted(MCONTACT hContact) { // remove only authorized contacts if (!getByte(hContact, "Auth", 0)) { ptrA token(getStringA("TokenSecret")); ptrA sessionId(getStringA("SessionID")); ptrA steamId(getStringA("SteamID")); char *who = getStringA(hContact, "SteamID"); PushRequest(new RemoveFriendRequest(token, sessionId, steamId, who), &CSteamProto::OnFriendRemoved, (void*)who); } }