summaryrefslogtreecommitdiff
path: root/protocols/ICQ-WIM/src/proto.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/ICQ-WIM/src/proto.cpp')
-rw-r--r--protocols/ICQ-WIM/src/proto.cpp479
1 files changed, 479 insertions, 0 deletions
diff --git a/protocols/ICQ-WIM/src/proto.cpp b/protocols/ICQ-WIM/src/proto.cpp
new file mode 100644
index 0000000000..86fb3257fb
--- /dev/null
+++ b/protocols/ICQ-WIM/src/proto.cpp
@@ -0,0 +1,479 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede
+// Copyright © 2001-2002 Jon Keating, Richard Hughes
+// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater
+// Copyright © 2004-2010 Joe Kucera, George Hazan
+// Copyright © 2012-2019 Miranda NG team
+//
+// 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; either version 2
+// of the License, or (at your option) any later version.
+//
+// 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, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Protocol Interface Implementation
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+#include "m_icolib.h"
+
+#pragma warning(disable:4355)
+
+CIcqProto::CIcqProto(const char* aProtoName, const wchar_t* aUserName) :
+ PROTO<CIcqProto>(aProtoName, aUserName),
+ m_arHttpQueue(10),
+ m_arOwnIds(1),
+ m_arCache(20, NumericKeySortT),
+ arMarkReadQueue(10, NumericKeySortT),
+ m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
+ m_dwUin(this, DB_KEY_UIN, 0),
+ m_szPassword(this, "Password"),
+ m_bUseFriendly(this, "UseFriendly", 1),
+ m_bHideGroupchats(this, "HideChats", 1)
+{
+ // services
+ CreateProtoService(PS_CREATEACCMGRUI, &CIcqProto::CreateAccMgrUI);
+ CreateProtoService(PS_GETAVATARINFO, &CIcqProto::GetAvatarInfo);
+ CreateProtoService(PS_GETMYAVATAR, &CIcqProto::GetAvatar);
+ CreateProtoService(PS_GETAVATARCAPS, &CIcqProto::GetAvatarCaps);
+ CreateProtoService(PS_SETMYAVATAR, &CIcqProto::SetAvatar);
+
+ // events
+ HookProtoEvent(ME_CLIST_GROUPCHANGE, &CIcqProto::OnGroupChange);
+ HookProtoEvent(ME_DB_EVENT_MARKED_READ, &CIcqProto::OnDbEventRead);
+ HookProtoEvent(ME_GC_EVENT, &CIcqProto::GroupchatEventHook);
+ HookProtoEvent(ME_GC_BUILDMENU, &CIcqProto::GroupchatMenuHook);
+ HookProtoEvent(ME_OPT_INITIALISE, &CIcqProto::OnOptionsInit);
+
+ // netlib handle
+ CMStringW descr(FORMAT, TranslateT("%s server connection"), m_tszUserName);
+
+ NETLIBUSER nlu = {};
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szDescriptiveName.w = descr.GetBuffer();
+ m_hNetlibUser = Netlib_RegisterUser(&nlu);
+
+ m_hWorkerThread = ForkThreadEx(&CIcqProto::ServerThread, nullptr, nullptr);
+
+ InitContactCache();
+}
+
+CIcqProto::~CIcqProto()
+{
+ ::CloseHandle(m_evRequestsQueue);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// OnModulesLoadedEx - performs hook registration
+
+void CIcqProto::OnModulesLoaded()
+{
+ GCREGISTER gcr = {};
+ gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ Chat_Register(&gcr);
+}
+
+void CIcqProto::OnShutdown()
+{
+ m_bTerminated = true;
+}
+
+void CIcqProto::OnContactDeleted(MCONTACT hContact)
+{
+ CMStringA szId(GetUserId(hContact));
+ if (!isChatRoom(hContact))
+ m_arCache.remove(FindContactByUIN(atoi(szId)));
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeBuddy");
+ pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("buddy", szId)
+ << CHAR_PARAM("r", pReq->m_reqId) << INT_PARAM("allGroups", 1);
+ pReq->flags |= NLHRF_NODUMPSEND;
+ Push(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnBuildProtoMenu()
+{
+ CMenuItem mi(&g_plugin);
+ mi.root = Menu_GetProtocolRoot(this);
+ mi.flags = CMIF_UNMOVABLE;
+
+ // "Bookmarks..."
+ mi.pszService = "/UploadGroups";
+ CreateProtoService(mi.pszService, &CIcqProto::UploadGroups);
+ mi.name.a = LPGEN("Synchronize server groups");
+ mi.position = 200001;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_GROUP);
+ m_hUploadGroups = Menu_AddProtoMenuItem(&mi, m_szModuleName);
+
+ Menu_ShowItem(m_hUploadGroups, false);
+}
+
+INT_PTR CIcqProto::UploadGroups(WPARAM, LPARAM)
+{
+ for (auto &it : AccContacts()) {
+ if (isChatRoom(it))
+ continue;
+
+ CMStringW wszIcqGroup(getMStringW(it, "IcqGroup")), wszMirGroup(db_get_wsm(it, "CList", "Group"));
+ if (wszMirGroup.IsEmpty())
+ wszMirGroup = L"General";
+ if (wszIcqGroup != wszMirGroup)
+ MoveContactToGroup(it, wszIcqGroup, wszMirGroup);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::MarkReadTimerProc(HWND hwnd, UINT, UINT_PTR id, DWORD)
+{
+ CIcqProto *ppro = (CIcqProto*)id;
+
+ mir_cslock lck(ppro->csMarkReadQueue);
+ while (ppro->arMarkReadQueue.getCount()) {
+ IcqCacheItem *pUser = ppro->arMarkReadQueue[0];
+
+ auto *pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER);
+ JSONNode request, params; params.set_name("params");
+ params << CHAR_PARAM("sn", ppro->GetUserId(pUser->m_hContact)) << INT64_PARAM("lastRead", ppro->getId(pUser->m_hContact, DB_KEY_LASTMSGID));
+ request << CHAR_PARAM("method", "setDlgStateWim") << CHAR_PARAM("reqId", pReq->m_reqId)
+ << CHAR_PARAM("authToken", ppro->m_szRToken) << INT_PARAM("clientId", ppro->m_iRClientId) << params;
+ pReq->m_szParam = ptrW(json_write(&request));
+ ppro->Push(pReq);
+
+ ppro->arMarkReadQueue.remove(0);
+ }
+ KillTimer(hwnd, id);
+}
+
+int CIcqProto::OnDbEventRead(WPARAM, LPARAM hDbEvent)
+{
+ MCONTACT hContact = db_event_getContact(hDbEvent);
+ if (!hContact)
+ return 0;
+
+ // filter out only events of my protocol
+ const char *szProto = GetContactProto(hContact);
+ if (mir_strcmp(szProto, m_szModuleName))
+ return 0;
+
+ if (m_bOnline) {
+ SetTimer(g_hwndHeartbeat, UINT_PTR(this), 200, &CIcqProto::MarkReadTimerProc);
+
+ IcqCacheItem *pCache = FindContactByUIN(getDword(hContact, DB_KEY_UIN));
+ if (pCache) {
+ mir_cslock lck(csMarkReadQueue);
+ if (arMarkReadQueue.indexOf(pCache) == -1)
+ arMarkReadQueue.insert(pCache);
+ }
+ }
+ return 0;
+}
+
+int CIcqProto::OnGroupChange(WPARAM hContact, LPARAM lParam)
+{
+ if (!m_bOnline)
+ return 0;
+
+ CLISTGROUPCHANGE *pParam = (CLISTGROUPCHANGE*)lParam;
+ if (hContact == 0) { // whole group is changed
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/");
+ pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("r", pReq->m_reqId);
+ if (pParam->pszOldName == nullptr) {
+ pReq->m_szUrl += "addGroup";
+ pReq << WCHAR_PARAM("group", pParam->pszNewName);
+ }
+ else if (pParam->pszNewName == nullptr) {
+ pReq->m_szUrl += "removeGroup";
+ pReq << WCHAR_PARAM("group", pParam->pszOldName);
+ }
+ else {
+ pReq->m_szUrl += "renameGroup";
+ pReq << WCHAR_PARAM("oldGroup", pParam->pszOldName) << WCHAR_PARAM("newGroup", pParam->pszNewName);
+ }
+ Push(pReq);
+ }
+ else MoveContactToGroup(hContact, getMStringW(hContact, "IcqGroup"), pParam->pszNewName);
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_AddToList - adds a contact to the contact list
+
+MCONTACT CIcqProto::AddToList(int, PROTOSEARCHRESULT *psr)
+{
+ if (mir_wstrlen(psr->id.w) == 0)
+ return 0;
+
+ DWORD dwUin = _wtol(psr->id.w);
+
+ MCONTACT hContact = CreateContact(dwUin, true);
+ if (psr->nick.w)
+ setWString(hContact, "Nick", psr->nick.w);
+ if (psr->firstName.w)
+ setWString(hContact, "FirstName", psr->firstName.w);
+ if (psr->lastName.w)
+ setWString(hContact, "LastName", psr->lastName.w);
+
+ return hContact;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSS_AUTHREQUEST
+
+int CIcqProto::AuthRequest(MCONTACT hContact, const wchar_t* szMessage)
+{
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/buddylist/addBuddy", &CIcqProto::OnAddBuddy);
+ pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("r", pReq->m_reqId) << WCHAR_PARAM("authorizationMsg", szMessage)
+ << CHAR_PARAM("buddy", GetUserId(hContact)) << CHAR_PARAM("group", "General") << INT_PARAM("preAuthorized", 1);
+ pReq->flags |= NLHRF_NODUMPSEND;
+ pReq->hContact = hContact;
+ Push(pReq);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileAllow - starts a file transfer
+
+HANDLE CIcqProto::FileAllow(MCONTACT, HANDLE, const wchar_t*)
+{
+ return nullptr; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileCancel - cancels a file transfer
+
+int CIcqProto::FileCancel(MCONTACT, HANDLE)
+{
+ return 1; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileDeny - denies a file transfer
+
+int CIcqProto::FileDeny(MCONTACT, HANDLE, const wchar_t*)
+{
+ return 1; // Invalid contact
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileResume - processes file renaming etc
+
+int CIcqProto::FileResume(HANDLE, int*, const wchar_t**)
+{
+ return 1; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetCaps - return protocol capabilities bits
+
+INT_PTR CIcqProto::GetCaps(int type, MCONTACT)
+{
+ INT_PTR nReturn = 0;
+
+ switch (type) {
+ case PFLAGNUM_1:
+ nReturn = PF1_IM | PF1_URL | PF1_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | /*PF1_SEARCHBYNAME | TODO */
+ PF1_VISLIST | PF1_INVISLIST | PF1_MODEMSG | PF1_FILE | PF1_CONTACT | PF1_SERVERCLIST;
+ break;
+
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_INVISIBLE;
+
+ case PFLAGNUM_3:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_INVISIBLE;
+
+ case PFLAGNUM_4:
+ nReturn = PF4_FORCEAUTH | PF4_SUPPORTIDLE | PF4_IMSENDOFFLINE | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID;
+ break;
+
+ case PFLAG_UNIQUEIDTEXT:
+ return (INT_PTR)Translate("ICQ number or phone");
+ }
+
+ return nReturn;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetInfo - retrieves a contact info
+
+int CIcqProto::GetInfo(MCONTACT hContact, int)
+{
+ RetrieveUserInfo(hContact);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SearchBasic - searches the contact by UID
+
+HANDLE CIcqProto::SearchBasic(const wchar_t *pszSearch)
+{
+ if (!m_bOnline)
+ return nullptr;
+
+ auto *pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER, &CIcqProto::OnSearchResults);
+
+ JSONNode request, params; params.set_name("params");
+ params << WCHAR_PARAM("keyword", pszSearch);
+ request << CHAR_PARAM("method", "search") << CHAR_PARAM("reqId", pReq->m_reqId) << CHAR_PARAM("authToken", m_szRToken)
+ << INT_PARAM("clientId", m_iRClientId) << params;
+ pReq->m_szParam = ptrW(json_write(&request));
+ Push(pReq);
+ return pReq;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendFile - sends a file
+
+HANDLE CIcqProto::SendFile(MCONTACT, const wchar_t*, wchar_t**)
+{
+ return nullptr; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SendMessage - sends a message
+
+int CIcqProto::SendMsg(MCONTACT hContact, int, const char *pszSrc)
+{
+ CMStringA szUserid(GetUserId(hContact));
+ if (szUserid.IsEmpty())
+ return 0;
+
+ int id = InterlockedIncrement(&m_msgId);
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/im/sendIM", &CIcqProto::OnSendMessage);
+
+ auto *pOwn = new IcqOwnMessage(hContact, id, pReq->m_reqId);
+ pReq->pUserInfo = pOwn;
+ m_arOwnIds.insert(pOwn);
+
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("f", "json") << CHAR_PARAM("k", ICQ_APP_ID)
+ << CHAR_PARAM("mentions", "") << CHAR_PARAM("message", pszSrc) << CHAR_PARAM("offlineIM", "true") << CHAR_PARAM("r", pReq->m_reqId)
+ << CHAR_PARAM("t", szUserid) << INT_PARAM("ts", time(0));
+ Push(pReq);
+ return id;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendUrl
+
+int CIcqProto::SendUrl(MCONTACT, int, const char*)
+{
+ return 1; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetStatus - sets the protocol status
+
+int CIcqProto::SetStatus(int iNewStatus)
+{
+ debugLogA("CIcqProto::SetStatus iNewStatus = %d, m_iStatus = %d, m_iDesiredStatus = %d m_hWorkerThread = %p", iNewStatus, m_iStatus, m_iDesiredStatus, m_hWorkerThread);
+
+ if (iNewStatus == m_iStatus)
+ return 0;
+
+ m_iDesiredStatus = iNewStatus;
+ int iOldStatus = m_iStatus;
+
+ // go offline
+ if (iNewStatus == ID_STATUS_OFFLINE) {
+ if (m_bOnline) {
+ SetServerStatus(ID_STATUS_OFFLINE);
+ ShutdownSession();
+ }
+ m_iStatus = m_iDesiredStatus;
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+ }
+ // not logged in? come on
+ else if (!IsStatusConnecting(m_iStatus)) {
+ m_iStatus = ID_STATUS_CONNECTING;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+
+ if (m_dwUin == 0) {
+ debugLogA("Thread ended, UIN/password are not configured");
+ ConnectionFailed(LOGINERR_BADUSERID);
+ return 0;
+ }
+
+ if (!getByte("PhoneReg") && mir_wstrlen(m_szPassword) == 0) {
+ debugLogA("Thread ended, password is not configured");
+ ConnectionFailed(LOGINERR_BADUSERID);
+ return 0;
+ }
+
+ CheckPassword();
+ }
+ else if (m_bOnline) {
+ debugLogA("setting server online status to %d", iNewStatus);
+ SetServerStatus(iNewStatus);
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_GetAwayMsg - returns a contact's away message
+
+HANDLE CIcqProto::GetAwayMsg(MCONTACT)
+{
+ return nullptr; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSR_AWAYMSG - processes received status mode message
+
+int CIcqProto::RecvAwayMsg(MCONTACT, int, PROTORECVEVENT*)
+{
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetAwayMsg - sets the away status message
+
+int CIcqProto::SetAwayMsg(int, const wchar_t*)
+{
+ return 0; // Success
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// PS_UserIsTyping - sends a UTN notification
+
+int CIcqProto::UserIsTyping(MCONTACT hContact, int type)
+{
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/im/setTyping");
+ pReq->flags |= NLHRF_NODUMPSEND;
+ pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("f", "json")
+ << CHAR_PARAM("t", GetUserId(hContact)) << CHAR_PARAM("r", pReq->m_reqId)
+ << CHAR_PARAM("typingStatus", (type == PROTOTYPE_SELFTYPING_ON) ? "typing" : "typed");
+ Push(pReq);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetApparentMode - sets the visibility status
+
+int CIcqProto::SetApparentMode(MCONTACT, int)
+{
+ return 1; // Failure
+}