diff options
author | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
---|---|---|
committer | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
commit | 1979fd80424d16b2e489f9b57d01d9c7811d25a2 (patch) | |
tree | 960d42c5fe4a51f0fe2850bea91256e226bce221 /protocols/ICQ-WIM | |
parent | adfbbb217d4f4a05acf198755f219a5223d31c27 (diff) |
Update copyrights
Diffstat (limited to 'protocols/ICQ-WIM')
-rw-r--r-- | protocols/ICQ-WIM/src/groupchats.cpp | 584 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/http.cpp | 770 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/ignore.cpp | 164 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/main.cpp | 204 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/mra.cpp | 282 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/options.cpp | 788 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/poll.cpp | 818 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/proto.cpp | 1356 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/proto.h | 988 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/server.cpp | 2440 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/stdafx.cxx | 36 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/stdafx.h | 218 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/userinfo.cpp | 128 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/utils.cpp | 792 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/version.h | 2 |
15 files changed, 4785 insertions, 4785 deletions
diff --git a/protocols/ICQ-WIM/src/groupchats.cpp b/protocols/ICQ-WIM/src/groupchats.cpp index e92811baa1..807bec394a 100644 --- a/protocols/ICQ-WIM/src/groupchats.cpp +++ b/protocols/ICQ-WIM/src/groupchats.cpp @@ -1,292 +1,292 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -void CIcqProto::LoadChatInfo(SESSION_INFO *si) -{ - int memberCount = getDword(si->hContact, "MemberCount"); - for (int i = 0; i < memberCount; i++) { - char buf[100]; - mir_snprintf(buf, "m%d", i); - ptrW szSetting(getWStringA(si->hContact, buf)); - JSONNode *node = json_parse(T2Utf(szSetting)); - if (node == nullptr) - continue; - - CMStringW nick((*node)["nick"].as_mstring()); - CMStringW role((*node)["role"].as_mstring()); - CMStringW sn((*node)["sn"].as_mstring()); - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN }; - gce.dwFlags = GCEF_SILENT; - gce.pszID.w = si->ptszID; - gce.pszNick.w = nick; - gce.pszUID.w = sn; - gce.time = ::time(0); - gce.bIsMe = sn == m_szOwnId; - gce.pszStatus.w = TranslateW(role); - Chat_Event(&gce); - - json_delete(node); - } -} - -void CIcqProto::OnGetChatInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - SESSION_INFO *si = (SESSION_INFO*)pReq->pUserInfo; - - RobustReply root(pReply); - if (root.error() != 20000) - return; - - int n = 0; - char buf[100]; - const JSONNode &results = root.results(); - for (auto &it : results["members"]) { - mir_snprintf(buf, "m%d", n++); - - CMStringW friendly = it["friendly"].as_mstring(); - CMStringW role = it["role"].as_mstring(); - CMStringW sn = it["sn"].as_mstring(); - - JSONNode member; - member << WCHAR_PARAM("nick", friendly) << WCHAR_PARAM("role", role) << WCHAR_PARAM("sn", sn); - ptrW text(json_write(&member)); - setWString(si->hContact, buf, text); - } - - setDword(si->hContact, "MemberCount", n); - setId(si->hContact, "InfoVersion", _wtoi64(results["infoVersion"].as_mstring())); - setId(si->hContact, "MembersVersion", _wtoi64(results["membersVersion"].as_mstring())); - - LoadChatInfo(si); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Invitation dialog - -class CGroupchatInviteDlg : public CIcqDlgBase -{ - CCtrlClc m_clc; - SESSION_INFO *m_si; - - void FilterList(CCtrlClc*) - { - for (auto &hContact : Contacts()) { - char *proto = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(proto, m_proto->m_szModuleName) || m_proto->isChatRoom(hContact)) - if (HANDLE hItem = m_clc.FindContact(hContact)) - m_clc.DeleteItem(hItem); - } - } - - void ResetListOptions(CCtrlClc*) - { - m_clc.SetHideEmptyGroups(1); - m_clc.SetHideOfflineRoot(1); - } - -public: - CGroupchatInviteDlg(CIcqProto *ppro, SESSION_INFO *si) : - CIcqDlgBase(ppro, IDD_GROUPCHAT_INVITE), - m_si(si), - m_clc(this, IDC_CLIST) - { - m_clc.OnNewContact = - m_clc.OnListRebuilt = Callback(this, &CGroupchatInviteDlg::FilterList); - m_clc.OnOptionsChanged = Callback(this, &CGroupchatInviteDlg::ResetListOptions); - } - - bool OnInitDialog() override - { - SetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE, - GetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE) | CLS_SHOWHIDDEN | CLS_HIDEOFFLINE | CLS_CHECKBOXES | CLS_HIDEEMPTYGROUPS | CLS_USEGROUPS | CLS_GREYALTERNATE | CLS_GROUPCHECKBOXES); - m_clc.SendMsg(CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP | CLS_EX_TRACKSELECT, 0); - - ResetListOptions(&m_clc); - FilterList(&m_clc); - return true; - } - - bool OnApply() override - { - CMStringW szMembers; - for (auto &hContact : m_proto->AccContacts()) { - if (m_proto->isChatRoom(hContact)) - continue; - - if (HANDLE hItem = m_clc.FindContact(hContact)) { - if (m_clc.GetCheck(hItem)) { - if (!szMembers.IsEmpty()) - szMembers.AppendChar(','); - szMembers.Append(m_proto->GetUserId(hContact)); - } - } - } - - m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/mchat/AddChat") - << AIMSID(m_proto) << WCHAR_PARAM("chat_id", m_si->ptszID) << WCHAR_PARAM("members", szMembers)); - return true; - } -}; - -void CIcqProto::InviteUserToChat(SESSION_INFO *si) -{ - CGroupchatInviteDlg dlg(this, si); - if (si->pDlg) - dlg.SetParent(((CDlgBase*)si->pDlg)->GetHwnd()); - dlg.DoModal(); -} - -void CIcqProto::LeaveDestroyChat(SESSION_INFO *si) -{ - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/hideChat") - << AIMSID(this) << WCHAR_PARAM("buddy", si->ptszID) << INT64_PARAM("lastMsgId", getId(si->hContact, DB_KEY_LASTMSGID))); - - Chat_Terminate(si->pszModule, si->ptszID, true); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Group chats - -static gc_item sttLogListItems[] = -{ - { LPGENW("&Invite a user"), IDM_INVITE, MENU_ITEM }, - { nullptr, 0, MENU_SEPARATOR }, - { LPGENW("&Leave/destroy chat"), IDM_LEAVE, MENU_ITEM } -}; - -int CIcqProto::GroupchatMenuHook(WPARAM, LPARAM lParam) -{ - GCMENUITEMS *gcmi = (GCMENUITEMS*)lParam; - if (gcmi == nullptr) - return 0; - - if (mir_strcmpi(gcmi->pszModule, m_szModuleName)) - return 0; - - SESSION_INFO *si = g_chatApi.SM_FindSession(gcmi->pszID, gcmi->pszModule); - if (si == nullptr) - return 0; - - if (gcmi->Type == MENU_ON_LOG) - Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin); - - return 0; -} - -int CIcqProto::GroupchatEventHook(WPARAM, LPARAM lParam) -{ - GCHOOK *gch = (GCHOOK*)lParam; - if (gch == nullptr) - return 0; - - if (mir_strcmpi(gch->si->pszModule, m_szModuleName)) - return 0; - - SESSION_INFO *si = g_chatApi.SM_FindSession(gch->si->ptszID, gch->si->pszModule); - if (si == nullptr) - return 1; - - switch (gch->iType) { - case GC_USER_MESSAGE: - rtrimw(gch->ptszText); - if (!mir_wstrlen(gch->ptszText)) - break; - - if (m_bOnline) { - wchar_t *wszText = NEWWSTR_ALLOCA(gch->ptszText); - Chat_UnescapeTags(wszText); - SendMsg(si->hContact, 0, T2Utf(wszText)); - } - break; - - case GC_USER_PRIVMESS: - Chat_SendPrivateMessage(gch); - break; - - case GC_USER_LOGMENU: - Chat_ProcessLogMenu(si, gch->dwData); - break; - } - - return 1; -} - -void CIcqProto::Chat_ProcessLogMenu(SESSION_INFO *si, int iChoice) -{ - switch (iChoice) { - case IDM_INVITE: - InviteUserToChat(si); - break; - - case IDM_LEAVE: - LeaveDestroyChat(si); - break; - } -} - -void CIcqProto::Chat_SendPrivateMessage(GCHOOK *gch) -{ - MCONTACT hContact; - auto *pCache = FindContactByUIN(gch->ptszUID); - if (pCache == nullptr) { - hContact = CreateContact(gch->ptszUID, true); - setWString(hContact, "Nick", gch->ptszNick); - Contact::Hide(hContact); - db_set_dw(hContact, "Ignore", "Mask1", 0); - } - else hContact = pCache->m_hContact; - - CallService(MS_MSG_SENDMESSAGE, hContact, 0); -} - -void CIcqProto::ProcessGroupChat(const JSONNode &ev) -{ - for (auto &it : ev["mchats"]) { - CMStringW wszId(it["sender"].as_mstring()); - SESSION_INFO *si = g_chatApi.SM_FindSession(wszId, m_szModuleName); - if (si == nullptr) - continue; - - CMStringW method(it["method"].as_mstring()); - GCEVENT gce = { m_szModuleName, 0, (method == "add_members") ? GC_EVENT_JOIN : GC_EVENT_PART }; - gce.pszID.w = si->ptszID; - - int iStart = 0; - CMStringW members(it["members"].as_mstring()); - while (true) { - CMStringW member = members.Tokenize(L",", iStart); - if (member.IsEmpty()) - break; - - auto *pCache = FindContactByUIN(member); - if (pCache == nullptr) - continue; - - gce.pszNick.w = Clist_GetContactDisplayName(pCache->m_hContact); - gce.pszUID.w = member; - gce.time = ::time(0); - gce.bIsMe = member == m_szOwnId; - Chat_Event(&gce); - } - } -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+void CIcqProto::LoadChatInfo(SESSION_INFO *si)
+{
+ int memberCount = getDword(si->hContact, "MemberCount");
+ for (int i = 0; i < memberCount; i++) {
+ char buf[100];
+ mir_snprintf(buf, "m%d", i);
+ ptrW szSetting(getWStringA(si->hContact, buf));
+ JSONNode *node = json_parse(T2Utf(szSetting));
+ if (node == nullptr)
+ continue;
+
+ CMStringW nick((*node)["nick"].as_mstring());
+ CMStringW role((*node)["role"].as_mstring());
+ CMStringW sn((*node)["sn"].as_mstring());
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN };
+ gce.dwFlags = GCEF_SILENT;
+ gce.pszID.w = si->ptszID;
+ gce.pszNick.w = nick;
+ gce.pszUID.w = sn;
+ gce.time = ::time(0);
+ gce.bIsMe = sn == m_szOwnId;
+ gce.pszStatus.w = TranslateW(role);
+ Chat_Event(&gce);
+
+ json_delete(node);
+ }
+}
+
+void CIcqProto::OnGetChatInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ SESSION_INFO *si = (SESSION_INFO*)pReq->pUserInfo;
+
+ RobustReply root(pReply);
+ if (root.error() != 20000)
+ return;
+
+ int n = 0;
+ char buf[100];
+ const JSONNode &results = root.results();
+ for (auto &it : results["members"]) {
+ mir_snprintf(buf, "m%d", n++);
+
+ CMStringW friendly = it["friendly"].as_mstring();
+ CMStringW role = it["role"].as_mstring();
+ CMStringW sn = it["sn"].as_mstring();
+
+ JSONNode member;
+ member << WCHAR_PARAM("nick", friendly) << WCHAR_PARAM("role", role) << WCHAR_PARAM("sn", sn);
+ ptrW text(json_write(&member));
+ setWString(si->hContact, buf, text);
+ }
+
+ setDword(si->hContact, "MemberCount", n);
+ setId(si->hContact, "InfoVersion", _wtoi64(results["infoVersion"].as_mstring()));
+ setId(si->hContact, "MembersVersion", _wtoi64(results["membersVersion"].as_mstring()));
+
+ LoadChatInfo(si);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Invitation dialog
+
+class CGroupchatInviteDlg : public CIcqDlgBase
+{
+ CCtrlClc m_clc;
+ SESSION_INFO *m_si;
+
+ void FilterList(CCtrlClc*)
+ {
+ for (auto &hContact : Contacts()) {
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (mir_strcmp(proto, m_proto->m_szModuleName) || m_proto->isChatRoom(hContact))
+ if (HANDLE hItem = m_clc.FindContact(hContact))
+ m_clc.DeleteItem(hItem);
+ }
+ }
+
+ void ResetListOptions(CCtrlClc*)
+ {
+ m_clc.SetHideEmptyGroups(1);
+ m_clc.SetHideOfflineRoot(1);
+ }
+
+public:
+ CGroupchatInviteDlg(CIcqProto *ppro, SESSION_INFO *si) :
+ CIcqDlgBase(ppro, IDD_GROUPCHAT_INVITE),
+ m_si(si),
+ m_clc(this, IDC_CLIST)
+ {
+ m_clc.OnNewContact =
+ m_clc.OnListRebuilt = Callback(this, &CGroupchatInviteDlg::FilterList);
+ m_clc.OnOptionsChanged = Callback(this, &CGroupchatInviteDlg::ResetListOptions);
+ }
+
+ bool OnInitDialog() override
+ {
+ SetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE,
+ GetWindowLongPtr(m_clc.GetHwnd(), GWL_STYLE) | CLS_SHOWHIDDEN | CLS_HIDEOFFLINE | CLS_CHECKBOXES | CLS_HIDEEMPTYGROUPS | CLS_USEGROUPS | CLS_GREYALTERNATE | CLS_GROUPCHECKBOXES);
+ m_clc.SendMsg(CLM_SETEXSTYLE, CLS_EX_DISABLEDRAGDROP | CLS_EX_TRACKSELECT, 0);
+
+ ResetListOptions(&m_clc);
+ FilterList(&m_clc);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ CMStringW szMembers;
+ for (auto &hContact : m_proto->AccContacts()) {
+ if (m_proto->isChatRoom(hContact))
+ continue;
+
+ if (HANDLE hItem = m_clc.FindContact(hContact)) {
+ if (m_clc.GetCheck(hItem)) {
+ if (!szMembers.IsEmpty())
+ szMembers.AppendChar(',');
+ szMembers.Append(m_proto->GetUserId(hContact));
+ }
+ }
+ }
+
+ m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/mchat/AddChat")
+ << AIMSID(m_proto) << WCHAR_PARAM("chat_id", m_si->ptszID) << WCHAR_PARAM("members", szMembers));
+ return true;
+ }
+};
+
+void CIcqProto::InviteUserToChat(SESSION_INFO *si)
+{
+ CGroupchatInviteDlg dlg(this, si);
+ if (si->pDlg)
+ dlg.SetParent(((CDlgBase*)si->pDlg)->GetHwnd());
+ dlg.DoModal();
+}
+
+void CIcqProto::LeaveDestroyChat(SESSION_INFO *si)
+{
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/hideChat")
+ << AIMSID(this) << WCHAR_PARAM("buddy", si->ptszID) << INT64_PARAM("lastMsgId", getId(si->hContact, DB_KEY_LASTMSGID)));
+
+ Chat_Terminate(si->pszModule, si->ptszID, true);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Group chats
+
+static gc_item sttLogListItems[] =
+{
+ { LPGENW("&Invite a user"), IDM_INVITE, MENU_ITEM },
+ { nullptr, 0, MENU_SEPARATOR },
+ { LPGENW("&Leave/destroy chat"), IDM_LEAVE, MENU_ITEM }
+};
+
+int CIcqProto::GroupchatMenuHook(WPARAM, LPARAM lParam)
+{
+ GCMENUITEMS *gcmi = (GCMENUITEMS*)lParam;
+ if (gcmi == nullptr)
+ return 0;
+
+ if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
+ return 0;
+
+ SESSION_INFO *si = g_chatApi.SM_FindSession(gcmi->pszID, gcmi->pszModule);
+ if (si == nullptr)
+ return 0;
+
+ if (gcmi->Type == MENU_ON_LOG)
+ Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin);
+
+ return 0;
+}
+
+int CIcqProto::GroupchatEventHook(WPARAM, LPARAM lParam)
+{
+ GCHOOK *gch = (GCHOOK*)lParam;
+ if (gch == nullptr)
+ return 0;
+
+ if (mir_strcmpi(gch->si->pszModule, m_szModuleName))
+ return 0;
+
+ SESSION_INFO *si = g_chatApi.SM_FindSession(gch->si->ptszID, gch->si->pszModule);
+ if (si == nullptr)
+ return 1;
+
+ switch (gch->iType) {
+ case GC_USER_MESSAGE:
+ rtrimw(gch->ptszText);
+ if (!mir_wstrlen(gch->ptszText))
+ break;
+
+ if (m_bOnline) {
+ wchar_t *wszText = NEWWSTR_ALLOCA(gch->ptszText);
+ Chat_UnescapeTags(wszText);
+ SendMsg(si->hContact, 0, T2Utf(wszText));
+ }
+ break;
+
+ case GC_USER_PRIVMESS:
+ Chat_SendPrivateMessage(gch);
+ break;
+
+ case GC_USER_LOGMENU:
+ Chat_ProcessLogMenu(si, gch->dwData);
+ break;
+ }
+
+ return 1;
+}
+
+void CIcqProto::Chat_ProcessLogMenu(SESSION_INFO *si, int iChoice)
+{
+ switch (iChoice) {
+ case IDM_INVITE:
+ InviteUserToChat(si);
+ break;
+
+ case IDM_LEAVE:
+ LeaveDestroyChat(si);
+ break;
+ }
+}
+
+void CIcqProto::Chat_SendPrivateMessage(GCHOOK *gch)
+{
+ MCONTACT hContact;
+ auto *pCache = FindContactByUIN(gch->ptszUID);
+ if (pCache == nullptr) {
+ hContact = CreateContact(gch->ptszUID, true);
+ setWString(hContact, "Nick", gch->ptszNick);
+ Contact::Hide(hContact);
+ db_set_dw(hContact, "Ignore", "Mask1", 0);
+ }
+ else hContact = pCache->m_hContact;
+
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+}
+
+void CIcqProto::ProcessGroupChat(const JSONNode &ev)
+{
+ for (auto &it : ev["mchats"]) {
+ CMStringW wszId(it["sender"].as_mstring());
+ SESSION_INFO *si = g_chatApi.SM_FindSession(wszId, m_szModuleName);
+ if (si == nullptr)
+ continue;
+
+ CMStringW method(it["method"].as_mstring());
+ GCEVENT gce = { m_szModuleName, 0, (method == "add_members") ? GC_EVENT_JOIN : GC_EVENT_PART };
+ gce.pszID.w = si->ptszID;
+
+ int iStart = 0;
+ CMStringW members(it["members"].as_mstring());
+ while (true) {
+ CMStringW member = members.Tokenize(L",", iStart);
+ if (member.IsEmpty())
+ break;
+
+ auto *pCache = FindContactByUIN(member);
+ if (pCache == nullptr)
+ continue;
+
+ gce.pszNick.w = Clist_GetContactDisplayName(pCache->m_hContact);
+ gce.pszUID.w = member;
+ gce.time = ::time(0);
+ gce.bIsMe = member == m_szOwnId;
+ Chat_Event(&gce);
+ }
+ }
+}
diff --git a/protocols/ICQ-WIM/src/http.cpp b/protocols/ICQ-WIM/src/http.cpp index 0d15a71b9f..79910dad11 100644 --- a/protocols/ICQ-WIM/src/http.cpp +++ b/protocols/ICQ-WIM/src/http.cpp @@ -1,385 +1,385 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -#pragma comment(lib, "Rpcrt4.lib") - -void CIcqProto::DropQueue() -{ - mir_cslock lck(m_csHttpQueue); - - while (m_arHttpQueue.getCount()) { - auto *pReq = m_arHttpQueue[0]; - m_arHttpQueue.remove(0); - delete pReq; - } -} - -bool CIcqProto::IsQueueEmpty() -{ - mir_cslock lck(m_csHttpQueue); - return m_arHttpQueue.getCount() == 0; -} - -void __cdecl CIcqProto::ServerThread(void*) -{ - memset(&m_ConnPool, 0, sizeof(m_ConnPool)); - m_bTerminated = false; - - debugLogA("CIcqProto::WorkerThread: %s", "entering"); - - while (true) { - WaitForSingleObject(m_evRequestsQueue, 1000); - if (m_bTerminated) - break; - - while (true) { - bool bNeedSleep = false; - AsyncHttpRequest *pReq; - { - mir_cslock lck(m_csHttpQueue); - if (m_arHttpQueue.getCount() == 0) - break; - - pReq = m_arHttpQueue[0]; - m_arHttpQueue.remove(0); - bNeedSleep = (m_arHttpQueue.getCount() > 1); - } - if (m_bTerminated) - break; - - ExecuteRequest(pReq); - if (bNeedSleep) - Sleep(200); - } - - int ts = time(0); - for (auto &it : m_ConnPool) { - int idx = int(&it - m_ConnPool); - if (idx == CONN_FETCH) - continue; - - if (it.s && it.lastTs + it.timeout < ts) { - debugLogA("Socket #1 (%p) expired", idx, it.s); - Netlib_CloseHandle(it.s); - it.s = nullptr; - it.lastTs = 0; - } - } - } - - m_hWorkerThread = nullptr; - for (auto &it : m_ConnPool) { - if (it.s) - Netlib_CloseHandle(it.s); - it.s = nullptr; - it.lastTs = it.timeout = 0; - } - - debugLogA("CIcqProto::WorkerThread: %s", "leaving"); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -AsyncRapiRequest::AsyncRapiRequest(CIcqProto *ppro, const char *pszMethod, MTHttpRequestHandler pFunc) : - AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER, pFunc) -{ - params.set_name("params"); - - if (ppro->getByte(DB_KEY_PHONEREG)) { - m_szUrl.AppendChar('/'); - m_szUrl.Append(pszMethod); - - AddHeader("Content-Type", "application/json"); - request << CHAR_PARAM("aimsid", ppro->m_aimsid); - } - else request << CHAR_PARAM("method", pszMethod); -} - -void AsyncRapiRequest::OnPush() -{ - request << CHAR_PARAM("reqId", m_reqId) << params; - - m_szParam = ptrW(json_write(&request)); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -AsyncHttpRequest::AsyncHttpRequest(IcqConnection conn, int iType, const char *szUrl, MTHttpRequestHandler pFunc) : - m_conn(conn) -{ - flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_DUMPASTEXT; - requestType = iType; - m_szUrl = szUrl; - m_pFunc = pFunc; - timeout = 10000; - - GUID packetId; - UuidCreate(&packetId); - - RPC_CSTR szId; - UuidToStringA(&packetId, &szId); - strncpy_s(m_reqId, (char*)szId, _TRUNCATE); - RpcStringFreeA(&szId); - - if (iType == REQUEST_POST) { - AddHeader("Content-Type", "application/x-www-form-urlencoded"); - - dataLength = m_szParam.GetLength(); - pData = m_szParam.Detach(); - } -} - -void AsyncHttpRequest::ReplaceJsonParam(const JSONNode &n) -{ - auto *szNodeName = n.name(); - - JSONNode root = JSONNode::parse(m_szParam); - JSONNode& old = root.at(szNodeName); - if (old) - old = n; - else - root.push_back(n); - m_szParam = root.write().c_str(); - - replaceStr(pData, nullptr); - dataLength = 0; -} - -bool CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq) -{ - CMStringA str; - - pReq->szUrl = pReq->m_szUrl.GetBuffer(); - if (!pReq->m_szParam.IsEmpty()) { - if (pReq->requestType == REQUEST_GET) { - str.Format("%s?%s", pReq->m_szUrl.c_str(), pReq->m_szParam.c_str()); - pReq->szUrl = str.GetBuffer(); - } - else { - pReq->dataLength = pReq->m_szParam.GetLength(); - pReq->pData = mir_strdup(pReq->m_szParam); - } - } - - // replace credentials inside JSON body for pure RAPI requests - if (pReq->m_conn == CONN_RAPI && !mir_strcmp(pReq->szUrl, ICQ_ROBUST_SERVER) && !getByte(DB_KEY_PHONEREG)) { - CMStringA szAgent(FORMAT, "%S Mail.ru Windows ICQ (version 10.0.1999)", (wchar_t*)m_szOwnId); - pReq->AddHeader("User-Agent", szAgent); - pReq->AddHeader("Content-Type", "application/json"); - - if (m_szRToken.IsEmpty()) { - if (!RefreshRobustToken(pReq)) { - delete pReq; - return false; - } - } - - if (m_iRClientId) - pReq->ReplaceJsonParam(JSONNode("clientId", m_iRClientId)); - pReq->ReplaceJsonParam(JSONNode("authToken", m_szRToken)); - pReq->dataLength = pReq->m_szParam.GetLength(); - pReq->pData = mir_strdup(pReq->m_szParam); - } - - debugLogA("Executing request %s:\n%s", pReq->m_reqId, pReq->szUrl); - - if (pReq->m_conn != CONN_NONE) { - pReq->flags |= NLHRF_PERSISTENT; - pReq->nlc = m_ConnPool[pReq->m_conn].s; - m_ConnPool[pReq->m_conn].lastTs = time(0); - } - - bool bRet; - NLHR_PTR reply(Netlib_HttpTransaction(m_hNetlibUser, pReq)); - if (reply != nullptr) { - if (pReq->m_conn != CONN_NONE) { - auto &conn = m_ConnPool[pReq->m_conn]; - conn.s = reply->nlc; - conn.timeout = 0; - if (auto *pszHdr = Netlib_GetHeader(reply, "Keep-Alive")) { - int timeout; - if (1 == sscanf(pszHdr, "timeout=%d", &timeout)) - conn.timeout = timeout; - } - } - - if (pReq->m_conn == CONN_RAPI && reply->pData && strstr(reply->pData, "\"code\": 40201")) { - RobustReply r(reply); - if (r.error() == 40201) { // robust token expired - m_szRToken.Empty(); - - // if token refresh succeeded, replace it in the query and push request back - if (!RefreshRobustToken(pReq)) { - delete pReq; - return false; - } - - Push(pReq); - return true; - } - } - - if (pReq->m_pFunc != nullptr) - (this->*(pReq->m_pFunc))(reply, pReq); - - bRet = true; - } - else { - debugLogA("Request %s failed", pReq->m_reqId); - - if (IsStatusConnecting(m_iStatus)) - ConnectionFailed(LOGINERR_NONETWORK); - - if (pReq->m_conn != CONN_NONE) - m_ConnPool[pReq->m_conn].s = nullptr; - - bRet = false; - } - - delete pReq; - return bRet; -} - -void CIcqProto::Push(MHttpRequest *p) -{ - AsyncHttpRequest *pReq = (AsyncHttpRequest*)p; - - pReq->OnPush(); - { - mir_cslock lck(m_csHttpQueue); - m_arHttpQueue.insert(pReq); - } - - SetEvent(m_evRequestsQueue); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const AIMSID ¶m) -{ - pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", param.m_ppro->m_aimsid) << CHAR_PARAM("r", pReq->m_reqId); - #ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; - #endif - return pReq; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MHttpRequest* operator<<(MHttpRequest *pReq, const GROUP_PARAM ¶m) -{ - if (param.wszValue) { - CMStringW tmp(param.wszValue); - tmp.Replace(L"\\", L">"); - tmp.Replace(L"/", L">"); - pReq << WCHAR_PARAM(param.szName, tmp); - } - return pReq; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -JsonReply::JsonReply(NETLIBHTTPREQUEST *pReply) -{ - if (pReply == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = pReply->resultCode; - if (m_errorCode != 200) - return; - - m_root = json_parse(pReply->pData); - if (m_root == nullptr) { - m_errorCode = 500; - return; - } - - JSONNode &response = (*m_root)["response"]; - m_errorCode = response["statusCode"].as_int(); - m_requestId = response["requestId"].as_mstring(); - m_detailCode = response["statusDetailCode"].as_int(); - m_data = &response["data"]; -} - -JsonReply::~JsonReply() -{ - json_delete(m_root); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -FileReply::FileReply(NETLIBHTTPREQUEST *pReply) -{ - if (pReply == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = pReply->resultCode; - if (m_errorCode != 200) - return; - - m_root = json_parse(pReply->pData); - if (m_root == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = (*m_root)["status"].as_int(); - m_data = &(*m_root)["data"]; -} - -FileReply::~FileReply() -{ - json_delete(m_root); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -RobustReply::RobustReply(NETLIBHTTPREQUEST *pReply) -{ - if (pReply == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = pReply->resultCode; - if (m_errorCode != 200) - return; - - m_root = json_parse(pReply->pData); - if (m_root == nullptr) { - m_errorCode = 500; - return; - } - - m_errorCode = (*m_root)["status"]["code"].as_int(); - m_result = &(*m_root)["result"]; - m_results = &(*m_root)["results"]; -} - -RobustReply::~RobustReply() -{ - json_delete(m_root); -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+#pragma comment(lib, "Rpcrt4.lib")
+
+void CIcqProto::DropQueue()
+{
+ mir_cslock lck(m_csHttpQueue);
+
+ while (m_arHttpQueue.getCount()) {
+ auto *pReq = m_arHttpQueue[0];
+ m_arHttpQueue.remove(0);
+ delete pReq;
+ }
+}
+
+bool CIcqProto::IsQueueEmpty()
+{
+ mir_cslock lck(m_csHttpQueue);
+ return m_arHttpQueue.getCount() == 0;
+}
+
+void __cdecl CIcqProto::ServerThread(void*)
+{
+ memset(&m_ConnPool, 0, sizeof(m_ConnPool));
+ m_bTerminated = false;
+
+ debugLogA("CIcqProto::WorkerThread: %s", "entering");
+
+ while (true) {
+ WaitForSingleObject(m_evRequestsQueue, 1000);
+ if (m_bTerminated)
+ break;
+
+ while (true) {
+ bool bNeedSleep = false;
+ AsyncHttpRequest *pReq;
+ {
+ mir_cslock lck(m_csHttpQueue);
+ if (m_arHttpQueue.getCount() == 0)
+ break;
+
+ pReq = m_arHttpQueue[0];
+ m_arHttpQueue.remove(0);
+ bNeedSleep = (m_arHttpQueue.getCount() > 1);
+ }
+ if (m_bTerminated)
+ break;
+
+ ExecuteRequest(pReq);
+ if (bNeedSleep)
+ Sleep(200);
+ }
+
+ int ts = time(0);
+ for (auto &it : m_ConnPool) {
+ int idx = int(&it - m_ConnPool);
+ if (idx == CONN_FETCH)
+ continue;
+
+ if (it.s && it.lastTs + it.timeout < ts) {
+ debugLogA("Socket #1 (%p) expired", idx, it.s);
+ Netlib_CloseHandle(it.s);
+ it.s = nullptr;
+ it.lastTs = 0;
+ }
+ }
+ }
+
+ m_hWorkerThread = nullptr;
+ for (auto &it : m_ConnPool) {
+ if (it.s)
+ Netlib_CloseHandle(it.s);
+ it.s = nullptr;
+ it.lastTs = it.timeout = 0;
+ }
+
+ debugLogA("CIcqProto::WorkerThread: %s", "leaving");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncRapiRequest::AsyncRapiRequest(CIcqProto *ppro, const char *pszMethod, MTHttpRequestHandler pFunc) :
+ AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER, pFunc)
+{
+ params.set_name("params");
+
+ if (ppro->getByte(DB_KEY_PHONEREG)) {
+ m_szUrl.AppendChar('/');
+ m_szUrl.Append(pszMethod);
+
+ AddHeader("Content-Type", "application/json");
+ request << CHAR_PARAM("aimsid", ppro->m_aimsid);
+ }
+ else request << CHAR_PARAM("method", pszMethod);
+}
+
+void AsyncRapiRequest::OnPush()
+{
+ request << CHAR_PARAM("reqId", m_reqId) << params;
+
+ m_szParam = ptrW(json_write(&request));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest::AsyncHttpRequest(IcqConnection conn, int iType, const char *szUrl, MTHttpRequestHandler pFunc) :
+ m_conn(conn)
+{
+ flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_DUMPASTEXT;
+ requestType = iType;
+ m_szUrl = szUrl;
+ m_pFunc = pFunc;
+ timeout = 10000;
+
+ GUID packetId;
+ UuidCreate(&packetId);
+
+ RPC_CSTR szId;
+ UuidToStringA(&packetId, &szId);
+ strncpy_s(m_reqId, (char*)szId, _TRUNCATE);
+ RpcStringFreeA(&szId);
+
+ if (iType == REQUEST_POST) {
+ AddHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ dataLength = m_szParam.GetLength();
+ pData = m_szParam.Detach();
+ }
+}
+
+void AsyncHttpRequest::ReplaceJsonParam(const JSONNode &n)
+{
+ auto *szNodeName = n.name();
+
+ JSONNode root = JSONNode::parse(m_szParam);
+ JSONNode& old = root.at(szNodeName);
+ if (old)
+ old = n;
+ else
+ root.push_back(n);
+ m_szParam = root.write().c_str();
+
+ replaceStr(pData, nullptr);
+ dataLength = 0;
+}
+
+bool CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq)
+{
+ CMStringA str;
+
+ pReq->szUrl = pReq->m_szUrl.GetBuffer();
+ if (!pReq->m_szParam.IsEmpty()) {
+ if (pReq->requestType == REQUEST_GET) {
+ str.Format("%s?%s", pReq->m_szUrl.c_str(), pReq->m_szParam.c_str());
+ pReq->szUrl = str.GetBuffer();
+ }
+ else {
+ pReq->dataLength = pReq->m_szParam.GetLength();
+ pReq->pData = mir_strdup(pReq->m_szParam);
+ }
+ }
+
+ // replace credentials inside JSON body for pure RAPI requests
+ if (pReq->m_conn == CONN_RAPI && !mir_strcmp(pReq->szUrl, ICQ_ROBUST_SERVER) && !getByte(DB_KEY_PHONEREG)) {
+ CMStringA szAgent(FORMAT, "%S Mail.ru Windows ICQ (version 10.0.1999)", (wchar_t*)m_szOwnId);
+ pReq->AddHeader("User-Agent", szAgent);
+ pReq->AddHeader("Content-Type", "application/json");
+
+ if (m_szRToken.IsEmpty()) {
+ if (!RefreshRobustToken(pReq)) {
+ delete pReq;
+ return false;
+ }
+ }
+
+ if (m_iRClientId)
+ pReq->ReplaceJsonParam(JSONNode("clientId", m_iRClientId));
+ pReq->ReplaceJsonParam(JSONNode("authToken", m_szRToken));
+ pReq->dataLength = pReq->m_szParam.GetLength();
+ pReq->pData = mir_strdup(pReq->m_szParam);
+ }
+
+ debugLogA("Executing request %s:\n%s", pReq->m_reqId, pReq->szUrl);
+
+ if (pReq->m_conn != CONN_NONE) {
+ pReq->flags |= NLHRF_PERSISTENT;
+ pReq->nlc = m_ConnPool[pReq->m_conn].s;
+ m_ConnPool[pReq->m_conn].lastTs = time(0);
+ }
+
+ bool bRet;
+ NLHR_PTR reply(Netlib_HttpTransaction(m_hNetlibUser, pReq));
+ if (reply != nullptr) {
+ if (pReq->m_conn != CONN_NONE) {
+ auto &conn = m_ConnPool[pReq->m_conn];
+ conn.s = reply->nlc;
+ conn.timeout = 0;
+ if (auto *pszHdr = Netlib_GetHeader(reply, "Keep-Alive")) {
+ int timeout;
+ if (1 == sscanf(pszHdr, "timeout=%d", &timeout))
+ conn.timeout = timeout;
+ }
+ }
+
+ if (pReq->m_conn == CONN_RAPI && reply->pData && strstr(reply->pData, "\"code\": 40201")) {
+ RobustReply r(reply);
+ if (r.error() == 40201) { // robust token expired
+ m_szRToken.Empty();
+
+ // if token refresh succeeded, replace it in the query and push request back
+ if (!RefreshRobustToken(pReq)) {
+ delete pReq;
+ return false;
+ }
+
+ Push(pReq);
+ return true;
+ }
+ }
+
+ if (pReq->m_pFunc != nullptr)
+ (this->*(pReq->m_pFunc))(reply, pReq);
+
+ bRet = true;
+ }
+ else {
+ debugLogA("Request %s failed", pReq->m_reqId);
+
+ if (IsStatusConnecting(m_iStatus))
+ ConnectionFailed(LOGINERR_NONETWORK);
+
+ if (pReq->m_conn != CONN_NONE)
+ m_ConnPool[pReq->m_conn].s = nullptr;
+
+ bRet = false;
+ }
+
+ delete pReq;
+ return bRet;
+}
+
+void CIcqProto::Push(MHttpRequest *p)
+{
+ AsyncHttpRequest *pReq = (AsyncHttpRequest*)p;
+
+ pReq->OnPush();
+ {
+ mir_cslock lck(m_csHttpQueue);
+ m_arHttpQueue.insert(pReq);
+ }
+
+ SetEvent(m_evRequestsQueue);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const AIMSID ¶m)
+{
+ pReq << CHAR_PARAM("f", "json") << CHAR_PARAM("aimsid", param.m_ppro->m_aimsid) << CHAR_PARAM("r", pReq->m_reqId);
+ #ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+ #endif
+ return pReq;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MHttpRequest* operator<<(MHttpRequest *pReq, const GROUP_PARAM ¶m)
+{
+ if (param.wszValue) {
+ CMStringW tmp(param.wszValue);
+ tmp.Replace(L"\\", L">");
+ tmp.Replace(L"/", L">");
+ pReq << WCHAR_PARAM(param.szName, tmp);
+ }
+ return pReq;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+JsonReply::JsonReply(NETLIBHTTPREQUEST *pReply)
+{
+ if (pReply == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = pReply->resultCode;
+ if (m_errorCode != 200)
+ return;
+
+ m_root = json_parse(pReply->pData);
+ if (m_root == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ JSONNode &response = (*m_root)["response"];
+ m_errorCode = response["statusCode"].as_int();
+ m_requestId = response["requestId"].as_mstring();
+ m_detailCode = response["statusDetailCode"].as_int();
+ m_data = &response["data"];
+}
+
+JsonReply::~JsonReply()
+{
+ json_delete(m_root);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+FileReply::FileReply(NETLIBHTTPREQUEST *pReply)
+{
+ if (pReply == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = pReply->resultCode;
+ if (m_errorCode != 200)
+ return;
+
+ m_root = json_parse(pReply->pData);
+ if (m_root == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = (*m_root)["status"].as_int();
+ m_data = &(*m_root)["data"];
+}
+
+FileReply::~FileReply()
+{
+ json_delete(m_root);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+RobustReply::RobustReply(NETLIBHTTPREQUEST *pReply)
+{
+ if (pReply == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = pReply->resultCode;
+ if (m_errorCode != 200)
+ return;
+
+ m_root = json_parse(pReply->pData);
+ if (m_root == nullptr) {
+ m_errorCode = 500;
+ return;
+ }
+
+ m_errorCode = (*m_root)["status"]["code"].as_int();
+ m_result = &(*m_root)["result"];
+ m_results = &(*m_root)["results"];
+}
+
+RobustReply::~RobustReply()
+{
+ json_delete(m_root);
+}
diff --git a/protocols/ICQ-WIM/src/ignore.cpp b/protocols/ICQ-WIM/src/ignore.cpp index d2d6dfb36a..a2ce8c098f 100644 --- a/protocols/ICQ-WIM/src/ignore.cpp +++ b/protocols/ICQ-WIM/src/ignore.cpp @@ -1,82 +1,82 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- -// Server permissions - -#include "stdafx.h" - -void CIcqProto::GetPermitDeny() -{ - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/preference/getPermitDeny", &CIcqProto::OnGetPermitDeny) << AIMSID(this)); -} - -void CIcqProto::OnGetPermitDeny(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (root.error() == 200) - ProcessPermissions(root.data()); -} - -void CIcqProto::ProcessPermissions(const JSONNode &ev) -{ - { mir_cslock lck(m_csCache); - for (auto &it : m_arCache) - it->m_iApparentMode = 0; - } - - for (auto &it : ev["allows"]) { - auto *p = FindContactByUIN(it.as_mstring()); - if (p) - p->m_iApparentMode = ID_STATUS_ONLINE; - } - - m_bIgnoreListEmpty = true; - for (auto &it : ev["ignores"]) { - CMStringW wszId(it.as_mstring()); - auto *p = FindContactByUIN(wszId); - if (p == nullptr) { - CreateContact(wszId, false); - p = FindContactByUIN(wszId); - } - p->m_iApparentMode = ID_STATUS_OFFLINE; - Contact::Hide(p->m_hContact); - m_bIgnoreListEmpty = false; - } - - { mir_cslock lck(m_csCache); - for (auto &it : m_arCache) { - int oldMode = getDword(it->m_hContact, "ApparentMode"); - if (oldMode != it->m_iApparentMode) { - if (it->m_iApparentMode == 0) - delSetting(it->m_hContact, "ApparentMode"); - else - setDword(it->m_hContact, "ApparentMode", it->m_iApparentMode); - } - } - } -} - -void CIcqProto::SetPermitDeny(const CMStringW &userId, bool bAllow) -{ - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/preference/setPermitDeny") - << AIMSID(this) << WCHAR_PARAM((bAllow) ? "pdIgnoreRemove" : "pdIgnore", userId); - if (!m_bIgnoreListEmpty) - pReq << CHAR_PARAM("pdMode", "denySome"); - Push(pReq); -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+// Server permissions
+
+#include "stdafx.h"
+
+void CIcqProto::GetPermitDeny()
+{
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/preference/getPermitDeny", &CIcqProto::OnGetPermitDeny) << AIMSID(this));
+}
+
+void CIcqProto::OnGetPermitDeny(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ JsonReply root(pReply);
+ if (root.error() == 200)
+ ProcessPermissions(root.data());
+}
+
+void CIcqProto::ProcessPermissions(const JSONNode &ev)
+{
+ { mir_cslock lck(m_csCache);
+ for (auto &it : m_arCache)
+ it->m_iApparentMode = 0;
+ }
+
+ for (auto &it : ev["allows"]) {
+ auto *p = FindContactByUIN(it.as_mstring());
+ if (p)
+ p->m_iApparentMode = ID_STATUS_ONLINE;
+ }
+
+ m_bIgnoreListEmpty = true;
+ for (auto &it : ev["ignores"]) {
+ CMStringW wszId(it.as_mstring());
+ auto *p = FindContactByUIN(wszId);
+ if (p == nullptr) {
+ CreateContact(wszId, false);
+ p = FindContactByUIN(wszId);
+ }
+ p->m_iApparentMode = ID_STATUS_OFFLINE;
+ Contact::Hide(p->m_hContact);
+ m_bIgnoreListEmpty = false;
+ }
+
+ { mir_cslock lck(m_csCache);
+ for (auto &it : m_arCache) {
+ int oldMode = getDword(it->m_hContact, "ApparentMode");
+ if (oldMode != it->m_iApparentMode) {
+ if (it->m_iApparentMode == 0)
+ delSetting(it->m_hContact, "ApparentMode");
+ else
+ setDword(it->m_hContact, "ApparentMode", it->m_iApparentMode);
+ }
+ }
+ }
+}
+
+void CIcqProto::SetPermitDeny(const CMStringW &userId, bool bAllow)
+{
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/preference/setPermitDeny")
+ << AIMSID(this) << WCHAR_PARAM((bAllow) ? "pdIgnoreRemove" : "pdIgnore", userId);
+ if (!m_bIgnoreListEmpty)
+ pReq << CHAR_PARAM("pdMode", "denySome");
+ Push(pReq);
+}
diff --git a/protocols/ICQ-WIM/src/main.cpp b/protocols/ICQ-WIM/src/main.cpp index 68245fff9c..b445bc7b29 100644 --- a/protocols/ICQ-WIM/src/main.cpp +++ b/protocols/ICQ-WIM/src/main.cpp @@ -1,102 +1,102 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -bool g_bSecureIM, g_bMessageState; - -///////////////////////////////////////////////////////////////////////////////////////// - -static PLUGININFOEX pluginInfoEx = { - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - { 0xEFB2355B, 0x82B3, 0x4759, { 0xb7, 0xd8, 0x95, 0xf8, 0xe9, 0x50, 0x62, 0x91 } } // {EFB2355B-82B3-4759-B7D8-95F8E9506291} -}; - -CMPlugin g_plugin; - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CMPluginMra : public ACCPROTOPLUGIN<CIcqProto> -{ - CMPluginMra() : ACCPROTOPLUGIN<CIcqProto>("MRA", pluginInfoEx) - { - SetUniqueId(DB_KEY_ID); - } - - void Register() - { - m_hInst = g_plugin.getInst(); - RegisterProtocol(PROTOTYPE_PROTOCOL, g_plugin.fnInit, g_plugin.fnUninit); - } -} -static g_pluginMra; - -///////////////////////////////////////////////////////////////////////////////////////// - -CMPlugin::CMPlugin() : - ACCPROTOPLUGIN<CIcqProto>(MODULENAME, pluginInfoEx) -{ - SetUniqueId(DB_KEY_ID); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// - -static int ModuleLoad(WPARAM, LPARAM) -{ - g_bSecureIM = ServiceExists("SecureIM/IsContactSecured"); - g_bMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE); - return 0; -} - -static int OnModulesLoaded(WPARAM, LPARAM) -{ - ModuleLoad(0, 0); - return 0; -} - -static IconItem iconList[] = -{ - { LPGEN("E-mail"), "icq_email", IDI_INBOX }, - { LPGEN("E-mail notification"), "icq_email_notif", IDI_MAIL_NOTIFY } -}; - -int CMPlugin::Load() -{ - // register the second instance of this plugin as MRA - g_pluginMra.Register(); - - registerIcon("Protocols/ICQ", iconList, "ICQ"); - - HookEvent(ME_SYSTEM_MODULELOAD, ModuleLoad); - HookEvent(ME_SYSTEM_MODULEUNLOAD, ModuleLoad); - HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded); - return 0; -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+bool g_bSecureIM, g_bMessageState;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static PLUGININFOEX pluginInfoEx = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ { 0xEFB2355B, 0x82B3, 0x4759, { 0xb7, 0xd8, 0x95, 0xf8, 0xe9, 0x50, 0x62, 0x91 } } // {EFB2355B-82B3-4759-B7D8-95F8E9506291}
+};
+
+CMPlugin g_plugin;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct CMPluginMra : public ACCPROTOPLUGIN<CIcqProto>
+{
+ CMPluginMra() : ACCPROTOPLUGIN<CIcqProto>("MRA", pluginInfoEx)
+ {
+ SetUniqueId(DB_KEY_ID);
+ }
+
+ void Register()
+ {
+ m_hInst = g_plugin.getInst();
+ RegisterProtocol(PROTOTYPE_PROTOCOL, g_plugin.fnInit, g_plugin.fnUninit);
+ }
+}
+static g_pluginMra;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMPlugin::CMPlugin() :
+ ACCPROTOPLUGIN<CIcqProto>(MODULENAME, pluginInfoEx)
+{
+ SetUniqueId(DB_KEY_ID);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int ModuleLoad(WPARAM, LPARAM)
+{
+ g_bSecureIM = ServiceExists("SecureIM/IsContactSecured");
+ g_bMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE);
+ return 0;
+}
+
+static int OnModulesLoaded(WPARAM, LPARAM)
+{
+ ModuleLoad(0, 0);
+ return 0;
+}
+
+static IconItem iconList[] =
+{
+ { LPGEN("E-mail"), "icq_email", IDI_INBOX },
+ { LPGEN("E-mail notification"), "icq_email_notif", IDI_MAIL_NOTIFY }
+};
+
+int CMPlugin::Load()
+{
+ // register the second instance of this plugin as MRA
+ g_pluginMra.Register();
+
+ registerIcon("Protocols/ICQ", iconList, "ICQ");
+
+ HookEvent(ME_SYSTEM_MODULELOAD, ModuleLoad);
+ HookEvent(ME_SYSTEM_MODULEUNLOAD, ModuleLoad);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+ return 0;
+}
diff --git a/protocols/ICQ-WIM/src/mra.cpp b/protocols/ICQ-WIM/src/mra.cpp index f5dcaae595..0c1422ddab 100644 --- a/protocols/ICQ-WIM/src/mra.cpp +++ b/protocols/ICQ-WIM/src/mra.cpp @@ -1,141 +1,141 @@ -/* -Copyright (C) 2012-22 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 <http://www.gnu.org/licenses/>. -*/ - -#include "stdafx.h" - -void CIcqProto::SendMrimLogin(NETLIBHTTPREQUEST *pReply) -{ - if (pReply) { - for (int i=0; i < pReply->headersCount; i++) { - if (!mir_strcmpi(pReply->headers[i].szName, "Set-Cookie")) { - char *p = strchr(pReply->headers[i].szValue, ';'); - if (p) *p = 0; - if (!m_szMraCookie.IsEmpty()) - m_szMraCookie.Append("; "); - - m_szMraCookie.Append(pReply->headers[i].szValue); - } - } - } - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://icqapilogin.mail.ru/auth/mrimLogin", &CIcqProto::OnCheckMrimLogin); - pReq->AddHeader("User-Agent", NETLIB_USER_AGENT); - if (!m_szMraCookie.IsEmpty()) - pReq->AddHeader("Cookie", m_szMraCookie); - pReq << CHAR_PARAM("clientName", "webagent") << INT_PARAM("clientVersion", 711) << CHAR_PARAM("devId", MRA_APP_ID) - << CHAR_PARAM("r", "91640-1626423568") << CHAR_PARAM("f", "json"); -#ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; -#endif - Push(pReq); -} - -void CIcqProto::OnCheckMrimLogin(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - JsonReply root(pReply); - switch (root.error()) { - case 200: - case 302: - break; - - case 460: // no cookies at all, obtain them via MRA auth site - { - CMStringW uin(m_szOwnId); - - int iStart = 0; - CMStringW wszLogin = uin.Tokenize(L"@", iStart); - CMStringW wszDomain = uin.Tokenize(L"@", iStart); - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://auth.mail.ru/cgi-bin/auth?from=splash", &CIcqProto::OnCheckMraAuth); - pReq->AddHeader("User-Agent", NETLIB_USER_AGENT); - if (!m_szMraCookie.IsEmpty()) - pReq->AddHeader("Cookie", m_szMraCookie); - pReq << WCHAR_PARAM("Domain", wszDomain) << WCHAR_PARAM("Login", wszLogin) << CHAR_PARAM("Password", m_szPassword) - << INT_PARAM("new_auth_form", 1) << INT_PARAM("saveauth", 1); -#ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; -#endif - Push(pReq); - } - return; - - case 462: // bad cookies, refresh them - if (!m_bError462) { - m_bError462 = true; - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://auth.mail.ru/sdc?JSONP_call=jscb_tmp_c85825&from=https%3A%2F%2Ficqapilogin.mail.ru%2Fauth%2FmrimLogin", &CIcqProto::OnCheckMraAuthFinal); - pReq->flags |= NLHRF_REDIRECT; - pReq->AddHeader("Cookie", m_szMraCookie); - pReq->AddHeader("Referer", "https://webagent.mail.ru/"); - pReq->AddHeader("User-Agent", NETLIB_USER_AGENT); - Push(pReq); - return; - } - - m_bError462 = false; - __fallthrough; - - default: - ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error()); - return; - } - - JSONNode &data = root.data(); - m_szAToken = data["token"]["a"].as_mstring(); - mir_urlDecode(m_szAToken.GetBuffer()); - setString(DB_KEY_ATOKEN, m_szAToken); - - m_szSessionKey = data["sessionKey"].as_mstring(); - - CMStringW szUin = data["loginId"].as_mstring(); - if (szUin) - m_szOwnId = szUin; - - int srvTS = data["hostTime"].as_int(); - m_iTimeShift = (srvTS) ? time(0) - srvTS : 0; - - StartSession(); -} - -void CIcqProto::OnCheckMraAuth(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - JsonReply root(pReply); - switch (root.error()) { - case 200: - case 302: - m_szMraCookie.Empty(); - SendMrimLogin(pReply); - break; - - default: - ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error()); - } -} - -void CIcqProto::OnCheckMraAuthFinal(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - switch (pReply->resultCode) { - case 200: - case 302: - // accumulate sdcs cookie and resend request - SendMrimLogin(pReply); - break; - - default: - ConnectionFailed(LOGINERR_WRONGPROTOCOL, 500); - } -} +/*
+Copyright (C) 2012-23 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+void CIcqProto::SendMrimLogin(NETLIBHTTPREQUEST *pReply)
+{
+ if (pReply) {
+ for (int i=0; i < pReply->headersCount; i++) {
+ if (!mir_strcmpi(pReply->headers[i].szName, "Set-Cookie")) {
+ char *p = strchr(pReply->headers[i].szValue, ';');
+ if (p) *p = 0;
+ if (!m_szMraCookie.IsEmpty())
+ m_szMraCookie.Append("; ");
+
+ m_szMraCookie.Append(pReply->headers[i].szValue);
+ }
+ }
+ }
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://icqapilogin.mail.ru/auth/mrimLogin", &CIcqProto::OnCheckMrimLogin);
+ pReq->AddHeader("User-Agent", NETLIB_USER_AGENT);
+ if (!m_szMraCookie.IsEmpty())
+ pReq->AddHeader("Cookie", m_szMraCookie);
+ pReq << CHAR_PARAM("clientName", "webagent") << INT_PARAM("clientVersion", 711) << CHAR_PARAM("devId", MRA_APP_ID)
+ << CHAR_PARAM("r", "91640-1626423568") << CHAR_PARAM("f", "json");
+#ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+#endif
+ Push(pReq);
+}
+
+void CIcqProto::OnCheckMrimLogin(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *)
+{
+ JsonReply root(pReply);
+ switch (root.error()) {
+ case 200:
+ case 302:
+ break;
+
+ case 460: // no cookies at all, obtain them via MRA auth site
+ {
+ CMStringW uin(m_szOwnId);
+
+ int iStart = 0;
+ CMStringW wszLogin = uin.Tokenize(L"@", iStart);
+ CMStringW wszDomain = uin.Tokenize(L"@", iStart);
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://auth.mail.ru/cgi-bin/auth?from=splash", &CIcqProto::OnCheckMraAuth);
+ pReq->AddHeader("User-Agent", NETLIB_USER_AGENT);
+ if (!m_szMraCookie.IsEmpty())
+ pReq->AddHeader("Cookie", m_szMraCookie);
+ pReq << WCHAR_PARAM("Domain", wszDomain) << WCHAR_PARAM("Login", wszLogin) << CHAR_PARAM("Password", m_szPassword)
+ << INT_PARAM("new_auth_form", 1) << INT_PARAM("saveauth", 1);
+#ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+#endif
+ Push(pReq);
+ }
+ return;
+
+ case 462: // bad cookies, refresh them
+ if (!m_bError462) {
+ m_bError462 = true;
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, "https://auth.mail.ru/sdc?JSONP_call=jscb_tmp_c85825&from=https%3A%2F%2Ficqapilogin.mail.ru%2Fauth%2FmrimLogin", &CIcqProto::OnCheckMraAuthFinal);
+ pReq->flags |= NLHRF_REDIRECT;
+ pReq->AddHeader("Cookie", m_szMraCookie);
+ pReq->AddHeader("Referer", "https://webagent.mail.ru/");
+ pReq->AddHeader("User-Agent", NETLIB_USER_AGENT);
+ Push(pReq);
+ return;
+ }
+
+ m_bError462 = false;
+ __fallthrough;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error());
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_szAToken = data["token"]["a"].as_mstring();
+ mir_urlDecode(m_szAToken.GetBuffer());
+ setString(DB_KEY_ATOKEN, m_szAToken);
+
+ m_szSessionKey = data["sessionKey"].as_mstring();
+
+ CMStringW szUin = data["loginId"].as_mstring();
+ if (szUin)
+ m_szOwnId = szUin;
+
+ int srvTS = data["hostTime"].as_int();
+ m_iTimeShift = (srvTS) ? time(0) - srvTS : 0;
+
+ StartSession();
+}
+
+void CIcqProto::OnCheckMraAuth(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *)
+{
+ JsonReply root(pReply);
+ switch (root.error()) {
+ case 200:
+ case 302:
+ m_szMraCookie.Empty();
+ SendMrimLogin(pReply);
+ break;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error());
+ }
+}
+
+void CIcqProto::OnCheckMraAuthFinal(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *)
+{
+ switch (pReply->resultCode) {
+ case 200:
+ case 302:
+ // accumulate sdcs cookie and resend request
+ SendMrimLogin(pReply);
+ break;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL, 500);
+ }
+}
diff --git a/protocols/ICQ-WIM/src/options.cpp b/protocols/ICQ-WIM/src/options.cpp index 7a53219a3d..2de6641efc 100644 --- a/protocols/ICQ-WIM/src/options.cpp +++ b/protocols/ICQ-WIM/src/options.cpp @@ -1,394 +1,394 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -class CIcqEnterLoginDlg : public CIcqDlgBase -{ - CCtrlEdit edtPass; - CCtrlCheck chkSave; - -public: - CIcqEnterLoginDlg(CIcqProto *ppro) : - CIcqDlgBase(ppro, IDD_LOGINPW), - edtPass(this, IDC_PASSWORD), - chkSave(this, IDC_SAVEPASS) - { - } - - bool OnInitDialog() override - { - m_proto->m_bDlgActive = true; - chkSave.SetState(m_proto->getBool("RememberPass")); - Window_SetIcon_IcoLib(m_hwnd, m_proto->m_hProtoIcon); - return true; - } - - bool OnApply() override - { - m_proto->setByte("RememberPass", m_proto->m_bRememberPwd = chkSave.GetState()); - m_proto->m_szPassword = ptrA(edtPass.GetTextU()); - EndModal(true); - return true; - } - - void OnDestroy() override - { - m_proto->m_bDlgActive = false; - } -}; - -bool CIcqProto::RetrievePassword() -{ - // if we registered via phone (i.e., server holds the password), we don't need to enter it - if (getByte(DB_KEY_PHONEREG)) - return true; - - if (!m_szPassword.IsEmpty() && m_bRememberPwd) - return true; - - m_szPassword = getMStringA("Password"); - if (!m_szPassword.IsEmpty()) { - m_bRememberPwd = true; - return true; - } - - if (m_bDlgActive) - return false; - - return CIcqEnterLoginDlg(this).DoModal(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -struct CIcqRegistrationDlg : public CIcqDlgBase -{ - CMStringA szTrans, szMsisdn; - int iErrorCode = 0; - - CCtrlEdit edtPhone, edtCode; - CCtrlButton btnSendSms; - - CIcqRegistrationDlg(CIcqProto *ppro) : - CIcqDlgBase(ppro, IDD_REGISTER), - edtPhone(this, IDC_PHONE), - edtCode(this, IDC_CODE), - btnSendSms(this, IDC_SENDSMS) - { - btnSendSms.OnClick = Callback(this, &CIcqRegistrationDlg::onClick_SendSms); - edtPhone.OnChange = Callback(this, &CIcqRegistrationDlg::onChange_Phone); - } - - bool OnApply() override - { - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsreg/loginWithPhoneNumber.php", &CIcqProto::OnLoginViaPhone); - pReq << CHAR_PARAM("locale", "en") << CHAR_PARAM("msisdn", szMsisdn) << CHAR_PARAM("trans_id", szTrans) << CHAR_PARAM("k", m_proto->appId()) - << CHAR_PARAM("r", pReq->m_reqId) << CHAR_PARAM("f", "json") << WCHAR_PARAM("sms_code", ptrW(edtCode.GetText())) << INT_PARAM("create_account", 1); - pReq->pUserInfo = this; - - SetCursor(LoadCursor(0, IDC_WAIT)); - m_proto->ExecuteRequest(pReq); - SetCursor(LoadCursor(0, IDC_ARROW)); - - if (iErrorCode != 200) - return false; - - EndDialog(m_hwnd, 1); - return true; - } - - void onChange_Phone(CCtrlEdit*) - { - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsapi/fcgi-bin/smsphoneinfo", &CIcqProto::OnCheckPhone); - pReq << CHAR_PARAM("service", "icq_registration") << CHAR_PARAM("info", "typing_check,score,iso_country_code") << CHAR_PARAM("lang", "ru") - << WCHAR_PARAM("phone", ptrW(edtPhone.GetText())) << CHAR_PARAM("id", pReq->m_reqId); - pReq->pUserInfo = this; - m_proto->Push(pReq); - } - - void onClick_SendSms(CCtrlButton*) - { - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsreg/requestPhoneValidation.php", &CIcqProto::OnValidateSms); - pReq << CHAR_PARAM("locale", "en") << CHAR_PARAM("msisdn", szMsisdn) << CHAR_PARAM("r", pReq->m_reqId) - << CHAR_PARAM("smsFormatType", "human") << CHAR_PARAM("k", m_proto->appId()); - pReq->pUserInfo = this; - m_proto->Push(pReq); - } -}; - -void CIcqProto::OnCheckPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - if (pReply == nullptr || pReply->resultCode != 200) - return; - - CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo; - pDlg->btnSendSms.Disable(); - pDlg->edtCode.Disable(); - - JSONROOT root(pReply->pData); - CMStringW wszStatus((*root)["status"].as_mstring()); - if (wszStatus != L"OK") { - pDlg->edtCode.SetText((*root)["printable"].as_mstring()); - return; - } - - CMStringA szPhoneNumber((*root)["typing_check"]["modified_phone_number"].as_mstring()); - CMStringA szPrefix((*root)["modified_prefix"].as_mstring()); - - auto *pNew = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://www.icq.com/smsreg/normalizePhoneNumber.php", &CIcqProto::OnNormalizePhone); - pNew << CHAR_PARAM("countryCode", szPrefix) << CHAR_PARAM("phoneNumber", szPhoneNumber.c_str() + szPrefix.GetLength()) - << CHAR_PARAM("k", appId()) << CHAR_PARAM("r", pReq->m_reqId); - pNew->pUserInfo = pDlg; - Push(pNew); -} - -void CIcqProto::OnNormalizePhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo; - - JsonReply root(pReply); - pDlg->iErrorCode = root.error(); - if (root.error() != 200) - return; - - const JSONNode &data = root.data(); - pDlg->szMsisdn = data["msisdn"].as_mstring(); - pDlg->btnSendSms.Enable(); -} - -void CIcqProto::OnValidateSms(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - JsonReply root(pReply); - if (root.error() != 200) - return; - - CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo; - const JSONNode &data = root.data(); - pDlg->szTrans = data["trans_id"].as_mstring(); - - pDlg->edtCode.Enable(); - pDlg->edtCode.SetText(L""); -} - -void CIcqProto::OnLoginViaPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo; - - JsonReply root(pReply); - pDlg->iErrorCode = root.error(); - if (root.error() != 200) - return; - - const JSONNode &data = root.data(); - m_szAToken = data["token"]["a"].as_mstring(); - mir_urlDecode(m_szAToken.GetBuffer()); - setString(DB_KEY_ATOKEN, m_szAToken); - - int srvTS = data["hostTime"].as_int(); - m_iTimeShift = (srvTS) ? time(0) - srvTS : 0; - - m_szSessionKey = data["sessionKey"].as_mstring(); - setString(DB_KEY_SESSIONKEY, m_szSessionKey); - - m_szOwnId = data["loginId"].as_mstring(); - setByte(DB_KEY_PHONEREG, 1); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CIcqOptionsDlg : public CIcqDlgBase -{ - CCtrlEdit edtUin, edtPassword; - CCtrlCheck chkHideChats, chkTrayIcon, chkLaunchMailbox, chkShowErrorPopups; - CCtrlButton btnCreate; - CMStringW wszOldPass; - -public: - CIcqOptionsDlg(CIcqProto *ppro, int iDlgID, bool bFullDlg) : - CIcqDlgBase(ppro, iDlgID), - edtUin(this, IDC_UIN), - btnCreate(this, IDC_REGISTER), - edtPassword(this, IDC_PASSWORD), - chkTrayIcon(this, IDC_USETRAYICON), - chkHideChats(this, IDC_HIDECHATS), - chkLaunchMailbox(this, IDC_LAUNCH_MAILBOX), - chkShowErrorPopups(this, IDC_SHOWERRORPOPUPS) - { - btnCreate.OnClick = Callback(this, &CIcqOptionsDlg::onClick_Register); - - CreateLink(edtUin, ppro->m_szOwnId); - if (bFullDlg) { - CreateLink(chkHideChats, ppro->m_bHideGroupchats); - CreateLink(chkTrayIcon, ppro->m_bUseTrayIcon); - CreateLink(chkLaunchMailbox, ppro->m_bLaunchMailbox); - CreateLink(chkShowErrorPopups, ppro->m_bErrorPopups); - - chkTrayIcon.OnChange = Callback(this, &CIcqOptionsDlg::onChange_Tray); - } - } - - bool OnInitDialog() override - { - if (m_proto->m_isMra) - btnCreate.Hide(); - else - SetDlgItemText(m_hwnd, IDC_UIN_LABEL, TranslateT("UIN:")); - - wszOldPass = m_proto->getMStringW("Password"); - edtPassword.SetText(wszOldPass); - return true; - } - - bool OnApply() override - { - ptrW wszPass(edtPassword.GetText()); - if (wszPass) - m_proto->setWString("Password", wszPass); - else - m_proto->delSetting("Password"); - - if (wszOldPass != wszPass) { - m_proto->delSetting(DB_KEY_ATOKEN); - m_proto->delSetting(DB_KEY_SESSIONKEY); - m_proto->delSetting(DB_KEY_PHONEREG); - } - - if (mir_wstrlen(wszPass)) { - m_proto->m_szPassword = T2Utf(wszPass).get(); - m_proto->m_bRememberPwd = true; - } - else m_proto->m_bRememberPwd = m_proto->getByte("RememberPass"); - - return true; - } - - void onChange_Tray(CCtrlCheck*) - { - chkLaunchMailbox.Enable(chkTrayIcon.GetState()); - } - - void onClick_Register(CCtrlButton*) - { - CIcqRegistrationDlg dlg(m_proto); - dlg.SetParent(m_hwnd); - if (dlg.DoModal()) // force exit to avoid data corruption - PostMessage(m_hwndParent, WM_COMMAND, MAKELONG(IDCANCEL, BN_CLICKED), 0); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Advanced options - -class CIcqOptionsAdv : public CIcqDlgBase -{ - CCtrlEdit edtDiff1, edtDiff2; - CCtrlSpin spin1, spin2; - CCtrlCombo cmbStatus1, cmbStatus2; - -public: - CIcqOptionsAdv(CIcqProto *ppro) : - CIcqDlgBase(ppro, IDD_OPTIONS_ADV), - spin1(this, IDC_SPIN1, 32000), - spin2(this, IDC_SPIN2, 32000), - edtDiff1(this, IDC_DIFF1), - edtDiff2(this, IDC_DIFF2), - cmbStatus1(this, IDC_STATUS1), - cmbStatus2(this, IDC_STATUS2) - { - edtDiff1.OnChange = Callback(this, &CIcqOptionsAdv::onChange_Timeout1); - edtDiff2.OnChange = Callback(this, &CIcqOptionsAdv::onChange_Timeout2); - - CreateLink(spin1, ppro->m_iTimeDiff1); - CreateLink(spin2, ppro->m_iTimeDiff2); - } - - bool OnInitDialog() override - { - if (cmbStatus1.GetHwnd()) { - for (uint32_t iStatus = ID_STATUS_OFFLINE; iStatus <= ID_STATUS_MAX; iStatus++) { - int idx = cmbStatus1.AddString(Clist_GetStatusModeDescription(iStatus, 0)); - cmbStatus1.SetItemData(idx, iStatus); - if (iStatus == m_proto->m_iStatus1) - cmbStatus1.SetCurSel(idx); - - idx = cmbStatus2.AddString(Clist_GetStatusModeDescription(iStatus, 0)); - cmbStatus2.SetItemData(idx, iStatus); - if (iStatus == m_proto->m_iStatus2) - cmbStatus2.SetCurSel(idx); - } - } - - return true; - } - - bool OnApply() override - { - if (cmbStatus1.GetHwnd()) { - m_proto->m_iStatus1 = cmbStatus1.GetCurData(); - m_proto->m_iStatus2 = cmbStatus2.GetCurData(); - } - - return true; - } - - void onChange_Timeout1(CCtrlEdit*) - { - bool bEnabled = edtDiff1.GetInt() != 0; - spin2.Enable(bEnabled); - edtDiff2.Enable(bEnabled); - cmbStatus1.Enable(bEnabled); - cmbStatus2.Enable(bEnabled && edtDiff2.GetInt() != 0); - } - - void onChange_Timeout2(CCtrlEdit*) - { - bool bEnabled = edtDiff2.GetInt() != 0; - cmbStatus2.Enable(bEnabled); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Services - -INT_PTR CIcqProto::CreateAccMgrUI(WPARAM, LPARAM hwndParent) -{ - CIcqOptionsDlg *pDlg = new CIcqOptionsDlg(this, IDD_OPTIONS_ACCMGR, false); - pDlg->SetParent((HWND)hwndParent); - pDlg->Create(); - return (INT_PTR)pDlg->GetHwnd(); -} - -int CIcqProto::OnOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szTitle.w = m_tszUserName; - odp.flags = ODPF_UNICODE | ODPF_BOLDGROUPS; - odp.szGroup.w = LPGENW("Network"); - odp.position = 1; - - odp.szTab.w = LPGENW("General"); - odp.pDialog = new CIcqOptionsDlg(this, IDD_OPTIONS_FULL, true); - g_plugin.addOptions(wParam, &odp); - - odp.szTab.w = LPGENW("Advanced"); - odp.pDialog = new CIcqOptionsAdv(this); - g_plugin.addOptions(wParam, &odp); - return 0; -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+class CIcqEnterLoginDlg : public CIcqDlgBase
+{
+ CCtrlEdit edtPass;
+ CCtrlCheck chkSave;
+
+public:
+ CIcqEnterLoginDlg(CIcqProto *ppro) :
+ CIcqDlgBase(ppro, IDD_LOGINPW),
+ edtPass(this, IDC_PASSWORD),
+ chkSave(this, IDC_SAVEPASS)
+ {
+ }
+
+ bool OnInitDialog() override
+ {
+ m_proto->m_bDlgActive = true;
+ chkSave.SetState(m_proto->getBool("RememberPass"));
+ Window_SetIcon_IcoLib(m_hwnd, m_proto->m_hProtoIcon);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ m_proto->setByte("RememberPass", m_proto->m_bRememberPwd = chkSave.GetState());
+ m_proto->m_szPassword = ptrA(edtPass.GetTextU());
+ EndModal(true);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ m_proto->m_bDlgActive = false;
+ }
+};
+
+bool CIcqProto::RetrievePassword()
+{
+ // if we registered via phone (i.e., server holds the password), we don't need to enter it
+ if (getByte(DB_KEY_PHONEREG))
+ return true;
+
+ if (!m_szPassword.IsEmpty() && m_bRememberPwd)
+ return true;
+
+ m_szPassword = getMStringA("Password");
+ if (!m_szPassword.IsEmpty()) {
+ m_bRememberPwd = true;
+ return true;
+ }
+
+ if (m_bDlgActive)
+ return false;
+
+ return CIcqEnterLoginDlg(this).DoModal();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct CIcqRegistrationDlg : public CIcqDlgBase
+{
+ CMStringA szTrans, szMsisdn;
+ int iErrorCode = 0;
+
+ CCtrlEdit edtPhone, edtCode;
+ CCtrlButton btnSendSms;
+
+ CIcqRegistrationDlg(CIcqProto *ppro) :
+ CIcqDlgBase(ppro, IDD_REGISTER),
+ edtPhone(this, IDC_PHONE),
+ edtCode(this, IDC_CODE),
+ btnSendSms(this, IDC_SENDSMS)
+ {
+ btnSendSms.OnClick = Callback(this, &CIcqRegistrationDlg::onClick_SendSms);
+ edtPhone.OnChange = Callback(this, &CIcqRegistrationDlg::onChange_Phone);
+ }
+
+ bool OnApply() override
+ {
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsreg/loginWithPhoneNumber.php", &CIcqProto::OnLoginViaPhone);
+ pReq << CHAR_PARAM("locale", "en") << CHAR_PARAM("msisdn", szMsisdn) << CHAR_PARAM("trans_id", szTrans) << CHAR_PARAM("k", m_proto->appId())
+ << CHAR_PARAM("r", pReq->m_reqId) << CHAR_PARAM("f", "json") << WCHAR_PARAM("sms_code", ptrW(edtCode.GetText())) << INT_PARAM("create_account", 1);
+ pReq->pUserInfo = this;
+
+ SetCursor(LoadCursor(0, IDC_WAIT));
+ m_proto->ExecuteRequest(pReq);
+ SetCursor(LoadCursor(0, IDC_ARROW));
+
+ if (iErrorCode != 200)
+ return false;
+
+ EndDialog(m_hwnd, 1);
+ return true;
+ }
+
+ void onChange_Phone(CCtrlEdit*)
+ {
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsapi/fcgi-bin/smsphoneinfo", &CIcqProto::OnCheckPhone);
+ pReq << CHAR_PARAM("service", "icq_registration") << CHAR_PARAM("info", "typing_check,score,iso_country_code") << CHAR_PARAM("lang", "ru")
+ << WCHAR_PARAM("phone", ptrW(edtPhone.GetText())) << CHAR_PARAM("id", pReq->m_reqId);
+ pReq->pUserInfo = this;
+ m_proto->Push(pReq);
+ }
+
+ void onClick_SendSms(CCtrlButton*)
+ {
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://u.icq.net/api/v60/smsreg/requestPhoneValidation.php", &CIcqProto::OnValidateSms);
+ pReq << CHAR_PARAM("locale", "en") << CHAR_PARAM("msisdn", szMsisdn) << CHAR_PARAM("r", pReq->m_reqId)
+ << CHAR_PARAM("smsFormatType", "human") << CHAR_PARAM("k", m_proto->appId());
+ pReq->pUserInfo = this;
+ m_proto->Push(pReq);
+ }
+};
+
+void CIcqProto::OnCheckPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ if (pReply == nullptr || pReply->resultCode != 200)
+ return;
+
+ CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo;
+ pDlg->btnSendSms.Disable();
+ pDlg->edtCode.Disable();
+
+ JSONROOT root(pReply->pData);
+ CMStringW wszStatus((*root)["status"].as_mstring());
+ if (wszStatus != L"OK") {
+ pDlg->edtCode.SetText((*root)["printable"].as_mstring());
+ return;
+ }
+
+ CMStringA szPhoneNumber((*root)["typing_check"]["modified_phone_number"].as_mstring());
+ CMStringA szPrefix((*root)["modified_prefix"].as_mstring());
+
+ auto *pNew = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, "https://www.icq.com/smsreg/normalizePhoneNumber.php", &CIcqProto::OnNormalizePhone);
+ pNew << CHAR_PARAM("countryCode", szPrefix) << CHAR_PARAM("phoneNumber", szPhoneNumber.c_str() + szPrefix.GetLength())
+ << CHAR_PARAM("k", appId()) << CHAR_PARAM("r", pReq->m_reqId);
+ pNew->pUserInfo = pDlg;
+ Push(pNew);
+}
+
+void CIcqProto::OnNormalizePhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo;
+
+ JsonReply root(pReply);
+ pDlg->iErrorCode = root.error();
+ if (root.error() != 200)
+ return;
+
+ const JSONNode &data = root.data();
+ pDlg->szMsisdn = data["msisdn"].as_mstring();
+ pDlg->btnSendSms.Enable();
+}
+
+void CIcqProto::OnValidateSms(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ JsonReply root(pReply);
+ if (root.error() != 200)
+ return;
+
+ CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo;
+ const JSONNode &data = root.data();
+ pDlg->szTrans = data["trans_id"].as_mstring();
+
+ pDlg->edtCode.Enable();
+ pDlg->edtCode.SetText(L"");
+}
+
+void CIcqProto::OnLoginViaPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ CIcqRegistrationDlg *pDlg = (CIcqRegistrationDlg*)pReq->pUserInfo;
+
+ JsonReply root(pReply);
+ pDlg->iErrorCode = root.error();
+ if (root.error() != 200)
+ return;
+
+ const JSONNode &data = root.data();
+ m_szAToken = data["token"]["a"].as_mstring();
+ mir_urlDecode(m_szAToken.GetBuffer());
+ setString(DB_KEY_ATOKEN, m_szAToken);
+
+ int srvTS = data["hostTime"].as_int();
+ m_iTimeShift = (srvTS) ? time(0) - srvTS : 0;
+
+ m_szSessionKey = data["sessionKey"].as_mstring();
+ setString(DB_KEY_SESSIONKEY, m_szSessionKey);
+
+ m_szOwnId = data["loginId"].as_mstring();
+ setByte(DB_KEY_PHONEREG, 1);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CIcqOptionsDlg : public CIcqDlgBase
+{
+ CCtrlEdit edtUin, edtPassword;
+ CCtrlCheck chkHideChats, chkTrayIcon, chkLaunchMailbox, chkShowErrorPopups;
+ CCtrlButton btnCreate;
+ CMStringW wszOldPass;
+
+public:
+ CIcqOptionsDlg(CIcqProto *ppro, int iDlgID, bool bFullDlg) :
+ CIcqDlgBase(ppro, iDlgID),
+ edtUin(this, IDC_UIN),
+ btnCreate(this, IDC_REGISTER),
+ edtPassword(this, IDC_PASSWORD),
+ chkTrayIcon(this, IDC_USETRAYICON),
+ chkHideChats(this, IDC_HIDECHATS),
+ chkLaunchMailbox(this, IDC_LAUNCH_MAILBOX),
+ chkShowErrorPopups(this, IDC_SHOWERRORPOPUPS)
+ {
+ btnCreate.OnClick = Callback(this, &CIcqOptionsDlg::onClick_Register);
+
+ CreateLink(edtUin, ppro->m_szOwnId);
+ if (bFullDlg) {
+ CreateLink(chkHideChats, ppro->m_bHideGroupchats);
+ CreateLink(chkTrayIcon, ppro->m_bUseTrayIcon);
+ CreateLink(chkLaunchMailbox, ppro->m_bLaunchMailbox);
+ CreateLink(chkShowErrorPopups, ppro->m_bErrorPopups);
+
+ chkTrayIcon.OnChange = Callback(this, &CIcqOptionsDlg::onChange_Tray);
+ }
+ }
+
+ bool OnInitDialog() override
+ {
+ if (m_proto->m_isMra)
+ btnCreate.Hide();
+ else
+ SetDlgItemText(m_hwnd, IDC_UIN_LABEL, TranslateT("UIN:"));
+
+ wszOldPass = m_proto->getMStringW("Password");
+ edtPassword.SetText(wszOldPass);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ ptrW wszPass(edtPassword.GetText());
+ if (wszPass)
+ m_proto->setWString("Password", wszPass);
+ else
+ m_proto->delSetting("Password");
+
+ if (wszOldPass != wszPass) {
+ m_proto->delSetting(DB_KEY_ATOKEN);
+ m_proto->delSetting(DB_KEY_SESSIONKEY);
+ m_proto->delSetting(DB_KEY_PHONEREG);
+ }
+
+ if (mir_wstrlen(wszPass)) {
+ m_proto->m_szPassword = T2Utf(wszPass).get();
+ m_proto->m_bRememberPwd = true;
+ }
+ else m_proto->m_bRememberPwd = m_proto->getByte("RememberPass");
+
+ return true;
+ }
+
+ void onChange_Tray(CCtrlCheck*)
+ {
+ chkLaunchMailbox.Enable(chkTrayIcon.GetState());
+ }
+
+ void onClick_Register(CCtrlButton*)
+ {
+ CIcqRegistrationDlg dlg(m_proto);
+ dlg.SetParent(m_hwnd);
+ if (dlg.DoModal()) // force exit to avoid data corruption
+ PostMessage(m_hwndParent, WM_COMMAND, MAKELONG(IDCANCEL, BN_CLICKED), 0);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Advanced options
+
+class CIcqOptionsAdv : public CIcqDlgBase
+{
+ CCtrlEdit edtDiff1, edtDiff2;
+ CCtrlSpin spin1, spin2;
+ CCtrlCombo cmbStatus1, cmbStatus2;
+
+public:
+ CIcqOptionsAdv(CIcqProto *ppro) :
+ CIcqDlgBase(ppro, IDD_OPTIONS_ADV),
+ spin1(this, IDC_SPIN1, 32000),
+ spin2(this, IDC_SPIN2, 32000),
+ edtDiff1(this, IDC_DIFF1),
+ edtDiff2(this, IDC_DIFF2),
+ cmbStatus1(this, IDC_STATUS1),
+ cmbStatus2(this, IDC_STATUS2)
+ {
+ edtDiff1.OnChange = Callback(this, &CIcqOptionsAdv::onChange_Timeout1);
+ edtDiff2.OnChange = Callback(this, &CIcqOptionsAdv::onChange_Timeout2);
+
+ CreateLink(spin1, ppro->m_iTimeDiff1);
+ CreateLink(spin2, ppro->m_iTimeDiff2);
+ }
+
+ bool OnInitDialog() override
+ {
+ if (cmbStatus1.GetHwnd()) {
+ for (uint32_t iStatus = ID_STATUS_OFFLINE; iStatus <= ID_STATUS_MAX; iStatus++) {
+ int idx = cmbStatus1.AddString(Clist_GetStatusModeDescription(iStatus, 0));
+ cmbStatus1.SetItemData(idx, iStatus);
+ if (iStatus == m_proto->m_iStatus1)
+ cmbStatus1.SetCurSel(idx);
+
+ idx = cmbStatus2.AddString(Clist_GetStatusModeDescription(iStatus, 0));
+ cmbStatus2.SetItemData(idx, iStatus);
+ if (iStatus == m_proto->m_iStatus2)
+ cmbStatus2.SetCurSel(idx);
+ }
+ }
+
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ if (cmbStatus1.GetHwnd()) {
+ m_proto->m_iStatus1 = cmbStatus1.GetCurData();
+ m_proto->m_iStatus2 = cmbStatus2.GetCurData();
+ }
+
+ return true;
+ }
+
+ void onChange_Timeout1(CCtrlEdit*)
+ {
+ bool bEnabled = edtDiff1.GetInt() != 0;
+ spin2.Enable(bEnabled);
+ edtDiff2.Enable(bEnabled);
+ cmbStatus1.Enable(bEnabled);
+ cmbStatus2.Enable(bEnabled && edtDiff2.GetInt() != 0);
+ }
+
+ void onChange_Timeout2(CCtrlEdit*)
+ {
+ bool bEnabled = edtDiff2.GetInt() != 0;
+ cmbStatus2.Enable(bEnabled);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Services
+
+INT_PTR CIcqProto::CreateAccMgrUI(WPARAM, LPARAM hwndParent)
+{
+ CIcqOptionsDlg *pDlg = new CIcqOptionsDlg(this, IDD_OPTIONS_ACCMGR, false);
+ pDlg->SetParent((HWND)hwndParent);
+ pDlg->Create();
+ return (INT_PTR)pDlg->GetHwnd();
+}
+
+int CIcqProto::OnOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.szTitle.w = m_tszUserName;
+ odp.flags = ODPF_UNICODE | ODPF_BOLDGROUPS;
+ odp.szGroup.w = LPGENW("Network");
+ odp.position = 1;
+
+ odp.szTab.w = LPGENW("General");
+ odp.pDialog = new CIcqOptionsDlg(this, IDD_OPTIONS_FULL, true);
+ g_plugin.addOptions(wParam, &odp);
+
+ odp.szTab.w = LPGENW("Advanced");
+ odp.pDialog = new CIcqOptionsAdv(this);
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/protocols/ICQ-WIM/src/poll.cpp b/protocols/ICQ-WIM/src/poll.cpp index d7ca7298ad..6fca78728f 100644 --- a/protocols/ICQ-WIM/src/poll.cpp +++ b/protocols/ICQ-WIM/src/poll.cpp @@ -1,409 +1,409 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- -// Long-poll thread and its item handlers - -#include "stdafx.h" - -void CIcqProto::ProcessBuddyList(const JSONNode &ev) -{ - m_arGroups.destroy(); - - LIST<IcqGroup> tmpGroups(10); - bool bEnableMenu = false; - - for (auto &it : ev["groups"]) { - auto *pGroup = new IcqGroup(it["id"].as_int(), it["name"].as_mstring()); - debugLogA("new group: id=%d, level=%d, name=%S", pGroup->id, pGroup->level, pGroup->wszName.c_str()); - if (pGroup->level != 0) { - for (auto &p : tmpGroups.rev_iter()) { - if (p->level == pGroup->level-1) { - pGroup->wszName = p->wszName + L"\\" + pGroup->wszName; - debugLogA("Group name fixed as %S", pGroup->wszName.c_str()); - break; - } - } - } - tmpGroups.insert(pGroup); - m_arGroups.insert(pGroup); - - bool bCreated = false; - - for (auto &buddy : it["buddies"]) { - MCONTACT hContact = ParseBuddyInfo(buddy); - if (hContact == INVALID_CONTACT_ID) - continue; - - setWString(hContact, "IcqGroup", pGroup->wszName); - - if (!bCreated) { - Clist_GroupCreate(0, pGroup->wszName); - bCreated = true; - } - - ptrW mirGroup(Clist_GetGroup(hContact)); - if (mir_wstrcmp(mirGroup, pGroup->wszName)) - bEnableMenu = true; - - if (!mirGroup) - Clist_SetGroup(hContact, pGroup->wszName); - } - } - - if (bEnableMenu) - Menu_ShowItem(m_hUploadGroups, true); - - for (auto &it : m_arCache) - if (!it->m_bInList && !getBool(it->m_hContact, "IcqDeleted")) - Contact::RemoveFromList(it->m_hContact); -} - -void CIcqProto::ProcessDiff(const JSONNode &ev) -{ - for (auto &block : ev) { - CMStringW szType = block["type"].as_mstring(); - if (szType != "updated" && szType != "created" && szType != "deleted") - continue; - - for (auto &it : block["data"]) { - int grpId = it["id"].as_int(); - CMStringW wszName = it["name"].as_mstring(); - - auto *pGroup = m_arGroups.find((IcqGroup *)&grpId); - if (pGroup == nullptr) { - if (szType != "created") { - debugLogA("Group %d isn't found", grpId); - continue; - } - - pGroup = new IcqGroup(grpId, wszName); - m_arGroups.insert(pGroup); - } - else { - pGroup->wszSrvName = wszName; - pGroup->SetName(wszName); - } - - bool bCreated = false, bDeleted = (szType == "deleted"); - - for (auto &buddy : it["buddies"]) { - if (bDeleted) - continue; - - MCONTACT hContact = ParseBuddyInfo(buddy, true); - if (hContact == INVALID_CONTACT_ID) - continue; - - setWString(hContact, "IcqGroup", pGroup->wszName); - - if (!bCreated) { - Clist_GroupCreate(0, pGroup->wszName); - bCreated = true; - } - - ptrW wszGroup(Clist_GetGroup(hContact)); - if (!wszGroup) - Clist_SetGroup(hContact, pGroup->wszName); - } - - if (bDeleted) - m_arGroups.remove(pGroup); - } - - RefreshGroups(); - } -} - -void CIcqProto::ProcessEvent(const JSONNode &ev) -{ - const JSONNode &pData = ev["eventData"]; - CMStringW szType = ev["type"].as_mstring(); - if (szType == L"buddylist") - ProcessBuddyList(pData); - else if (szType == L"diff") - ProcessDiff(pData); - else if (szType == L"histDlgState") - ProcessHistData(pData); - else if (szType == L"imState") - ProcessImState(pData); - else if (szType == L"mchat") - ProcessGroupChat(pData); - else if (szType == L"myInfo") - ProcessMyInfo(pData); - else if (szType == L"notification") - ProcessNotification(pData); - else if (szType == L"permitDeny") - ProcessPermissions(pData); - else if (szType == L"presence") - ProcessPresence(pData); - else if (szType == L"sessionEnded") - ProcessSessionEnd(pData); - else if (szType == L"typing") - ProcessTyping(pData); -} - -void CIcqProto::ProcessHistData(const JSONNode &ev) -{ - MCONTACT hContact; - bool bVeryBeginning = m_bFirstBos; - - CMStringW wszId(ev["sn"].as_mstring()); - auto *pCache = FindContactByUIN(wszId); // might be NULL for groupchats - - if (IsChat(wszId)) { - SESSION_INFO *si = g_chatApi.SM_FindSession(wszId, m_szModuleName); - if (si == nullptr) - return; - - hContact = si->hContact; - - if (si->arUsers.getCount() == 0) { - __int64 srvInfoVer = _wtoi64(ev["mchatState"]["infoVersion"].as_mstring()); - __int64 srvMembersVer = _wtoi64(ev["mchatState"]["membersVersion"].as_mstring()); - if (srvInfoVer != getId(hContact, "InfoVersion") || srvMembersVer != getId(hContact, "MembersVersion")) { - auto *pReq = new AsyncRapiRequest(this, "getChatInfo", &CIcqProto::OnGetChatInfo); - pReq->params << WCHAR_PARAM("sn", wszId) << INT_PARAM("memberLimit", 100) << CHAR_PARAM("aimSid", m_aimsid); - pReq->pUserInfo = si; - Push(pReq); - } - else LoadChatInfo(si); - } - } - else { - hContact = CreateContact(wszId, true); - - // for temporary contacts that just gonna be created - if (pCache == nullptr) { - bVeryBeginning = true; - pCache = FindContactByUIN(wszId); - } - } - - // restore reading from the previous point, if we just installed Miranda - __int64 lastMsgId = getId(hContact, DB_KEY_LASTMSGID); - if (lastMsgId == 0) { - lastMsgId = _wtoi64(ev["yours"]["lastRead"].as_mstring()); - setId(hContact, DB_KEY_LASTMSGID, lastMsgId); - } - - __int64 patchVersion = _wtoi64(ev["patchVersion"].as_mstring()); - setId(hContact, DB_KEY_PATCHVER, patchVersion); - - __int64 srvLastId = _wtoi64(ev["lastMsgId"].as_mstring()); - - // we load history in the very beginning or if the previous message - if (bVeryBeginning) { - if (pCache) { - debugLogA("Setting cache = %lld for %d", srvLastId, hContact); - pCache->m_iProcessedMsgId = srvLastId; - } - - if (srvLastId > lastMsgId) { - debugLogA("We need to retrieve history for %S: %lld > %lld", wszId.c_str(), srvLastId, lastMsgId); - RetrieveUserHistory(hContact, lastMsgId, false); - } - } - else { - if (!(pCache && pCache->m_iProcessedMsgId >= srvLastId)) { - if (pCache) - debugLogA("Proceeding with cache for %d: %lld < %lld", hContact, pCache->m_iProcessedMsgId, srvLastId); - else - debugLogA("Proceeding with empty cache for %d", hContact); - - for (auto &it : ev["tail"]["messages"]) - ParseMessage(hContact, lastMsgId, it, false, true); - - setId(hContact, DB_KEY_LASTMSGID, lastMsgId); - if (pCache) { - pCache->m_iProcessedMsgId = lastMsgId; - debugLogA("Setting second cache = %lld for %d", srvLastId, hContact); - } - } - } - - // check remote read - if (g_bMessageState) { - __int64 srvRemoteRead = _wtoi64(ev["theirs"]["lastRead"].as_mstring()); - __int64 lastRemoteRead = getId(hContact, DB_KEY_REMOTEREAD); - if (srvRemoteRead > lastRemoteRead) { - setId(hContact, DB_KEY_REMOTEREAD, srvRemoteRead); - - if (g_bMessageState) - CallService(MS_MESSAGESTATE_UPDATE, hContact, MRD_TYPE_READ); - } - } -} - -void CIcqProto::ProcessImState(const JSONNode &ev) -{ - for (auto &it : ev["imStates"]) { - if (it["state"].as_mstring() != L"sent") - continue; - - CMStringA reqId(it["sendReqId"].as_mstring()); - CMStringA msgId(it["histMsgId"].as_mstring()); - MCONTACT hContact = CheckOwnMessage(reqId, msgId, false); - if (hContact) - CheckLastId(hContact, ev); - } -} - -void CIcqProto::ProcessMyInfo(const JSONNode &ev) -{ - if (auto &var = ev["friendly"]) - setWString("Nick", var.as_mstring()); - - if (auto &var = ev["attachedPhoneNumber"]) - setWString(DB_KEY_PHONE, var.as_mstring()); - - CheckAvatarChange(0, ev); -} - -void CIcqProto::ProcessNotification(const JSONNode &ev) -{ - for (auto &fld : ev["fields"]) { - const JSONNode &email = fld["mailbox.newMessage"]; - if (email) { - JSONROOT root(email.as_string().c_str()); - CMStringW wszFrom((*root)["from"].as_mstring()); - CMStringW wszSubj((*root)["subject"].as_mstring()); - m_unreadEmails = (*root)["unreadCount"].as_int(); - debugLogW(L"You received e-mail (%d) from <%s>: <%s>", m_unreadEmails, wszFrom.c_str(), wszSubj.c_str()); - - CMStringW wszMessage(FORMAT, TranslateT("You received e-mail from %s: %s"), wszFrom.c_str(), wszSubj.c_str()); - EmailNotification(wszMessage); - } - - const JSONNode &status = fld["mailbox.status"]; - if (status) { - int iOldCount = m_unreadEmails; - - JSONROOT root(status.as_string().c_str()); - m_szMailBox = (*root)["email"].as_mstring(); - m_unreadEmails = (*root)["unreadCount"].as_int(); - - // we've read/removed some messages from server - if (iOldCount > m_unreadEmails) { - g_clistApi.pfnRemoveEvent(0, ICQ_FAKE_EVENT_ID); - return; - } - - // we notify about initial mail count only during login - if (m_bFirstBos && m_unreadEmails > 0) { - CMStringW wszMessage(FORMAT, TranslateT("You have %d unread emails"), m_unreadEmails); - EmailNotification(wszMessage); - } - } - } -} - -void CIcqProto::ProcessPresence(const JSONNode &ev) -{ - CMStringW aimId = ev["aimId"].as_mstring(); - - IcqCacheItem *pCache = FindContactByUIN(aimId); - if (pCache == nullptr) - return; - - int iNewStatus = StatusFromPresence(ev, pCache->m_hContact); - if (iNewStatus == -1) - iNewStatus = ID_STATUS_OFFLINE; - - // major crutch dedicated to the official client behaviour to go offline - // when its window gets closed. we change the status of a contact to the - // first chosen one from options and initialize a timer - if (iNewStatus == ID_STATUS_OFFLINE) { - if (m_iTimeDiff1) { - iNewStatus = m_iStatus1; - pCache->m_timer1 = time(0); - } - } - // if a client returns back online, we clear timers not to play with statuses anymore - else pCache->m_timer1 = pCache->m_timer2 = 0; - - setWord(pCache->m_hContact, "Status", iNewStatus); - - Json2string(pCache->m_hContact, ev, "friendly", "Nick", true); - CheckAvatarChange(pCache->m_hContact, ev); -} - -void CIcqProto::ProcessSessionEnd(const JSONNode &/*ev*/) -{ - m_szRToken.Empty(); - m_iRClientId = 0; - delSetting(DB_KEY_RCLIENTID); - - ShutdownSession(); -} - -void CIcqProto::ProcessTyping(const JSONNode &ev) -{ - CMStringW aimId = ev["aimId"].as_mstring(); - CMStringW wszStatus = ev["typingStatus"].as_mstring(); - - IcqCacheItem *pCache = FindContactByUIN(aimId); - if (pCache) { - if (wszStatus == "typing") - CallService(MS_PROTO_CONTACTISTYPING, pCache->m_hContact, 60); - else - CallService(MS_PROTO_CONTACTISTYPING, pCache->m_hContact, PROTOTYPE_CONTACTTYPING_OFF); - } -} - -void CIcqProto::OnFetchEvents(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - if (root.error() != 200) { - ShutdownSession(); - return; - } - - JSONNode &data = root.data(); - m_fetchBaseURL = data["fetchBaseURL"].as_mstring(); - - for (auto &it : data["events"]) - ProcessEvent(it); -} - -void __cdecl CIcqProto::PollThread(void*) -{ - debugLogA("Polling thread started"); - m_bFirstBos = true; - - while (m_bOnline && !m_fetchBaseURL.IsEmpty()) { - CMStringA szUrl = m_fetchBaseURL; - if (m_bFirstBos) - szUrl.Append("&first=1"); - else - szUrl.Append("&timeout=25000"); - - auto *pReq = new AsyncHttpRequest(CONN_FETCH, REQUEST_GET, szUrl, &CIcqProto::OnFetchEvents); - if (!m_bFirstBos) - pReq->timeout = 62000; - - if (!ExecuteRequest(pReq)) { - ShutdownSession(); - break; - } - - m_bFirstBos = false; - } - - debugLogA("Polling thread ended"); -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+// Long-poll thread and its item handlers
+
+#include "stdafx.h"
+
+void CIcqProto::ProcessBuddyList(const JSONNode &ev)
+{
+ m_arGroups.destroy();
+
+ LIST<IcqGroup> tmpGroups(10);
+ bool bEnableMenu = false;
+
+ for (auto &it : ev["groups"]) {
+ auto *pGroup = new IcqGroup(it["id"].as_int(), it["name"].as_mstring());
+ debugLogA("new group: id=%d, level=%d, name=%S", pGroup->id, pGroup->level, pGroup->wszName.c_str());
+ if (pGroup->level != 0) {
+ for (auto &p : tmpGroups.rev_iter()) {
+ if (p->level == pGroup->level-1) {
+ pGroup->wszName = p->wszName + L"\\" + pGroup->wszName;
+ debugLogA("Group name fixed as %S", pGroup->wszName.c_str());
+ break;
+ }
+ }
+ }
+ tmpGroups.insert(pGroup);
+ m_arGroups.insert(pGroup);
+
+ bool bCreated = false;
+
+ for (auto &buddy : it["buddies"]) {
+ MCONTACT hContact = ParseBuddyInfo(buddy);
+ if (hContact == INVALID_CONTACT_ID)
+ continue;
+
+ setWString(hContact, "IcqGroup", pGroup->wszName);
+
+ if (!bCreated) {
+ Clist_GroupCreate(0, pGroup->wszName);
+ bCreated = true;
+ }
+
+ ptrW mirGroup(Clist_GetGroup(hContact));
+ if (mir_wstrcmp(mirGroup, pGroup->wszName))
+ bEnableMenu = true;
+
+ if (!mirGroup)
+ Clist_SetGroup(hContact, pGroup->wszName);
+ }
+ }
+
+ if (bEnableMenu)
+ Menu_ShowItem(m_hUploadGroups, true);
+
+ for (auto &it : m_arCache)
+ if (!it->m_bInList && !getBool(it->m_hContact, "IcqDeleted"))
+ Contact::RemoveFromList(it->m_hContact);
+}
+
+void CIcqProto::ProcessDiff(const JSONNode &ev)
+{
+ for (auto &block : ev) {
+ CMStringW szType = block["type"].as_mstring();
+ if (szType != "updated" && szType != "created" && szType != "deleted")
+ continue;
+
+ for (auto &it : block["data"]) {
+ int grpId = it["id"].as_int();
+ CMStringW wszName = it["name"].as_mstring();
+
+ auto *pGroup = m_arGroups.find((IcqGroup *)&grpId);
+ if (pGroup == nullptr) {
+ if (szType != "created") {
+ debugLogA("Group %d isn't found", grpId);
+ continue;
+ }
+
+ pGroup = new IcqGroup(grpId, wszName);
+ m_arGroups.insert(pGroup);
+ }
+ else {
+ pGroup->wszSrvName = wszName;
+ pGroup->SetName(wszName);
+ }
+
+ bool bCreated = false, bDeleted = (szType == "deleted");
+
+ for (auto &buddy : it["buddies"]) {
+ if (bDeleted)
+ continue;
+
+ MCONTACT hContact = ParseBuddyInfo(buddy, true);
+ if (hContact == INVALID_CONTACT_ID)
+ continue;
+
+ setWString(hContact, "IcqGroup", pGroup->wszName);
+
+ if (!bCreated) {
+ Clist_GroupCreate(0, pGroup->wszName);
+ bCreated = true;
+ }
+
+ ptrW wszGroup(Clist_GetGroup(hContact));
+ if (!wszGroup)
+ Clist_SetGroup(hContact, pGroup->wszName);
+ }
+
+ if (bDeleted)
+ m_arGroups.remove(pGroup);
+ }
+
+ RefreshGroups();
+ }
+}
+
+void CIcqProto::ProcessEvent(const JSONNode &ev)
+{
+ const JSONNode &pData = ev["eventData"];
+ CMStringW szType = ev["type"].as_mstring();
+ if (szType == L"buddylist")
+ ProcessBuddyList(pData);
+ else if (szType == L"diff")
+ ProcessDiff(pData);
+ else if (szType == L"histDlgState")
+ ProcessHistData(pData);
+ else if (szType == L"imState")
+ ProcessImState(pData);
+ else if (szType == L"mchat")
+ ProcessGroupChat(pData);
+ else if (szType == L"myInfo")
+ ProcessMyInfo(pData);
+ else if (szType == L"notification")
+ ProcessNotification(pData);
+ else if (szType == L"permitDeny")
+ ProcessPermissions(pData);
+ else if (szType == L"presence")
+ ProcessPresence(pData);
+ else if (szType == L"sessionEnded")
+ ProcessSessionEnd(pData);
+ else if (szType == L"typing")
+ ProcessTyping(pData);
+}
+
+void CIcqProto::ProcessHistData(const JSONNode &ev)
+{
+ MCONTACT hContact;
+ bool bVeryBeginning = m_bFirstBos;
+
+ CMStringW wszId(ev["sn"].as_mstring());
+ auto *pCache = FindContactByUIN(wszId); // might be NULL for groupchats
+
+ if (IsChat(wszId)) {
+ SESSION_INFO *si = g_chatApi.SM_FindSession(wszId, m_szModuleName);
+ if (si == nullptr)
+ return;
+
+ hContact = si->hContact;
+
+ if (si->arUsers.getCount() == 0) {
+ __int64 srvInfoVer = _wtoi64(ev["mchatState"]["infoVersion"].as_mstring());
+ __int64 srvMembersVer = _wtoi64(ev["mchatState"]["membersVersion"].as_mstring());
+ if (srvInfoVer != getId(hContact, "InfoVersion") || srvMembersVer != getId(hContact, "MembersVersion")) {
+ auto *pReq = new AsyncRapiRequest(this, "getChatInfo", &CIcqProto::OnGetChatInfo);
+ pReq->params << WCHAR_PARAM("sn", wszId) << INT_PARAM("memberLimit", 100) << CHAR_PARAM("aimSid", m_aimsid);
+ pReq->pUserInfo = si;
+ Push(pReq);
+ }
+ else LoadChatInfo(si);
+ }
+ }
+ else {
+ hContact = CreateContact(wszId, true);
+
+ // for temporary contacts that just gonna be created
+ if (pCache == nullptr) {
+ bVeryBeginning = true;
+ pCache = FindContactByUIN(wszId);
+ }
+ }
+
+ // restore reading from the previous point, if we just installed Miranda
+ __int64 lastMsgId = getId(hContact, DB_KEY_LASTMSGID);
+ if (lastMsgId == 0) {
+ lastMsgId = _wtoi64(ev["yours"]["lastRead"].as_mstring());
+ setId(hContact, DB_KEY_LASTMSGID, lastMsgId);
+ }
+
+ __int64 patchVersion = _wtoi64(ev["patchVersion"].as_mstring());
+ setId(hContact, DB_KEY_PATCHVER, patchVersion);
+
+ __int64 srvLastId = _wtoi64(ev["lastMsgId"].as_mstring());
+
+ // we load history in the very beginning or if the previous message
+ if (bVeryBeginning) {
+ if (pCache) {
+ debugLogA("Setting cache = %lld for %d", srvLastId, hContact);
+ pCache->m_iProcessedMsgId = srvLastId;
+ }
+
+ if (srvLastId > lastMsgId) {
+ debugLogA("We need to retrieve history for %S: %lld > %lld", wszId.c_str(), srvLastId, lastMsgId);
+ RetrieveUserHistory(hContact, lastMsgId, false);
+ }
+ }
+ else {
+ if (!(pCache && pCache->m_iProcessedMsgId >= srvLastId)) {
+ if (pCache)
+ debugLogA("Proceeding with cache for %d: %lld < %lld", hContact, pCache->m_iProcessedMsgId, srvLastId);
+ else
+ debugLogA("Proceeding with empty cache for %d", hContact);
+
+ for (auto &it : ev["tail"]["messages"])
+ ParseMessage(hContact, lastMsgId, it, false, true);
+
+ setId(hContact, DB_KEY_LASTMSGID, lastMsgId);
+ if (pCache) {
+ pCache->m_iProcessedMsgId = lastMsgId;
+ debugLogA("Setting second cache = %lld for %d", srvLastId, hContact);
+ }
+ }
+ }
+
+ // check remote read
+ if (g_bMessageState) {
+ __int64 srvRemoteRead = _wtoi64(ev["theirs"]["lastRead"].as_mstring());
+ __int64 lastRemoteRead = getId(hContact, DB_KEY_REMOTEREAD);
+ if (srvRemoteRead > lastRemoteRead) {
+ setId(hContact, DB_KEY_REMOTEREAD, srvRemoteRead);
+
+ if (g_bMessageState)
+ CallService(MS_MESSAGESTATE_UPDATE, hContact, MRD_TYPE_READ);
+ }
+ }
+}
+
+void CIcqProto::ProcessImState(const JSONNode &ev)
+{
+ for (auto &it : ev["imStates"]) {
+ if (it["state"].as_mstring() != L"sent")
+ continue;
+
+ CMStringA reqId(it["sendReqId"].as_mstring());
+ CMStringA msgId(it["histMsgId"].as_mstring());
+ MCONTACT hContact = CheckOwnMessage(reqId, msgId, false);
+ if (hContact)
+ CheckLastId(hContact, ev);
+ }
+}
+
+void CIcqProto::ProcessMyInfo(const JSONNode &ev)
+{
+ if (auto &var = ev["friendly"])
+ setWString("Nick", var.as_mstring());
+
+ if (auto &var = ev["attachedPhoneNumber"])
+ setWString(DB_KEY_PHONE, var.as_mstring());
+
+ CheckAvatarChange(0, ev);
+}
+
+void CIcqProto::ProcessNotification(const JSONNode &ev)
+{
+ for (auto &fld : ev["fields"]) {
+ const JSONNode &email = fld["mailbox.newMessage"];
+ if (email) {
+ JSONROOT root(email.as_string().c_str());
+ CMStringW wszFrom((*root)["from"].as_mstring());
+ CMStringW wszSubj((*root)["subject"].as_mstring());
+ m_unreadEmails = (*root)["unreadCount"].as_int();
+ debugLogW(L"You received e-mail (%d) from <%s>: <%s>", m_unreadEmails, wszFrom.c_str(), wszSubj.c_str());
+
+ CMStringW wszMessage(FORMAT, TranslateT("You received e-mail from %s: %s"), wszFrom.c_str(), wszSubj.c_str());
+ EmailNotification(wszMessage);
+ }
+
+ const JSONNode &status = fld["mailbox.status"];
+ if (status) {
+ int iOldCount = m_unreadEmails;
+
+ JSONROOT root(status.as_string().c_str());
+ m_szMailBox = (*root)["email"].as_mstring();
+ m_unreadEmails = (*root)["unreadCount"].as_int();
+
+ // we've read/removed some messages from server
+ if (iOldCount > m_unreadEmails) {
+ g_clistApi.pfnRemoveEvent(0, ICQ_FAKE_EVENT_ID);
+ return;
+ }
+
+ // we notify about initial mail count only during login
+ if (m_bFirstBos && m_unreadEmails > 0) {
+ CMStringW wszMessage(FORMAT, TranslateT("You have %d unread emails"), m_unreadEmails);
+ EmailNotification(wszMessage);
+ }
+ }
+ }
+}
+
+void CIcqProto::ProcessPresence(const JSONNode &ev)
+{
+ CMStringW aimId = ev["aimId"].as_mstring();
+
+ IcqCacheItem *pCache = FindContactByUIN(aimId);
+ if (pCache == nullptr)
+ return;
+
+ int iNewStatus = StatusFromPresence(ev, pCache->m_hContact);
+ if (iNewStatus == -1)
+ iNewStatus = ID_STATUS_OFFLINE;
+
+ // major crutch dedicated to the official client behaviour to go offline
+ // when its window gets closed. we change the status of a contact to the
+ // first chosen one from options and initialize a timer
+ if (iNewStatus == ID_STATUS_OFFLINE) {
+ if (m_iTimeDiff1) {
+ iNewStatus = m_iStatus1;
+ pCache->m_timer1 = time(0);
+ }
+ }
+ // if a client returns back online, we clear timers not to play with statuses anymore
+ else pCache->m_timer1 = pCache->m_timer2 = 0;
+
+ setWord(pCache->m_hContact, "Status", iNewStatus);
+
+ Json2string(pCache->m_hContact, ev, "friendly", "Nick", true);
+ CheckAvatarChange(pCache->m_hContact, ev);
+}
+
+void CIcqProto::ProcessSessionEnd(const JSONNode &/*ev*/)
+{
+ m_szRToken.Empty();
+ m_iRClientId = 0;
+ delSetting(DB_KEY_RCLIENTID);
+
+ ShutdownSession();
+}
+
+void CIcqProto::ProcessTyping(const JSONNode &ev)
+{
+ CMStringW aimId = ev["aimId"].as_mstring();
+ CMStringW wszStatus = ev["typingStatus"].as_mstring();
+
+ IcqCacheItem *pCache = FindContactByUIN(aimId);
+ if (pCache) {
+ if (wszStatus == "typing")
+ CallService(MS_PROTO_CONTACTISTYPING, pCache->m_hContact, 60);
+ else
+ CallService(MS_PROTO_CONTACTISTYPING, pCache->m_hContact, PROTOTYPE_CONTACTTYPING_OFF);
+ }
+}
+
+void CIcqProto::OnFetchEvents(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ JsonReply root(pReply);
+ if (root.error() != 200) {
+ ShutdownSession();
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_fetchBaseURL = data["fetchBaseURL"].as_mstring();
+
+ for (auto &it : data["events"])
+ ProcessEvent(it);
+}
+
+void __cdecl CIcqProto::PollThread(void*)
+{
+ debugLogA("Polling thread started");
+ m_bFirstBos = true;
+
+ while (m_bOnline && !m_fetchBaseURL.IsEmpty()) {
+ CMStringA szUrl = m_fetchBaseURL;
+ if (m_bFirstBos)
+ szUrl.Append("&first=1");
+ else
+ szUrl.Append("&timeout=25000");
+
+ auto *pReq = new AsyncHttpRequest(CONN_FETCH, REQUEST_GET, szUrl, &CIcqProto::OnFetchEvents);
+ if (!m_bFirstBos)
+ pReq->timeout = 62000;
+
+ if (!ExecuteRequest(pReq)) {
+ ShutdownSession();
+ break;
+ }
+
+ m_bFirstBos = false;
+ }
+
+ debugLogA("Polling thread ended");
+}
diff --git a/protocols/ICQ-WIM/src/proto.cpp b/protocols/ICQ-WIM/src/proto.cpp index 84839ad923..5a86c896d8 100644 --- a/protocols/ICQ-WIM/src/proto.cpp +++ b/protocols/ICQ-WIM/src/proto.cpp @@ -1,678 +1,678 @@ -// ---------------------------------------------------------------------------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-2022 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) - -static int CompareCache(const IcqCacheItem *p1, const IcqCacheItem *p2) -{ - return mir_wstrcmp(p1->m_aimid, p2->m_aimid); -} - -CIcqProto::CIcqProto(const char *aProtoName, const wchar_t *aUserName) : - PROTO<CIcqProto>(aProtoName, aUserName), - m_impl(*this), - m_arHttpQueue(10), - m_arOwnIds(1, PtrKeySortT), - m_arCache(20, &CompareCache), - m_arGroups(10, NumericKeySortT), - m_arMarkReadQueue(10, NumericKeySortT), - m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr)), - m_szOwnId(this, DB_KEY_ID), - m_iStatus1(this, "Status1", ID_STATUS_AWAY), - m_iStatus2(this, "Status2", ID_STATUS_NA), - m_iTimeDiff1(this, "TimeDiff1", 0), - m_iTimeDiff2(this, "TimeDiff2", 0), - m_bHideGroupchats(this, "HideChats", true), - m_bUseTrayIcon(this, "UseTrayIcon", false), - m_bErrorPopups(this, "ShowErrorPopups", true), - m_bLaunchMailbox(this, "LaunchMailbox", true) -{ - db_set_resident(m_szModuleName, DB_KEY_IDLE); - db_set_resident(m_szModuleName, DB_KEY_ONLINETS); - - m_isMra = !stricmp(Proto_GetAccount(m_szModuleName)->szProtoName, "MRA"); - - // services - CreateProtoService(PS_CREATEACCMGRUI, &CIcqProto::CreateAccMgrUI); - - CreateProtoService(PS_GETAVATARCAPS, &CIcqProto::GetAvatarCaps); - CreateProtoService(PS_GETAVATARINFO, &CIcqProto::GetAvatarInfo); - CreateProtoService(PS_GETMYAVATAR, &CIcqProto::GetAvatar); - CreateProtoService(PS_SETMYAVATAR, &CIcqProto::SetAvatar); - - CreateProtoService(PS_MENU_LOADHISTORY, &CIcqProto::OnMenuLoadHistory); - CreateProtoService(PS_GETUNREADEMAILCOUNT, &CIcqProto::GetEmailCount); - CreateProtoService(PS_GOTO_INBOX, &CIcqProto::GotoInbox); - - // 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); - - // group chats - GCREGISTER gcr = {}; - gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR; - gcr.ptszDispName = m_tszUserName; - gcr.pszModule = m_szModuleName; - Chat_Register(&gcr); - - // netlib handle - NETLIBUSER nlu = {}; - nlu.szSettingsModule = m_szModuleName; - nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; - nlu.szDescriptiveName.w = m_tszUserName; - m_hNetlibUser = Netlib_RegisterUser(&nlu); - - // this was previously an old ICQ account - ptrW wszUin(GetUIN(0)); - if (wszUin != nullptr) { - delSetting("UIN"); - - m_szOwnId = wszUin; - - for (auto &it : AccContacts()) - delSetting(it, "e-mail"); - } - // this was previously an old MRA account - else { - CMStringW wszEmail(getMStringW("e-mail")); - if (!wszEmail.IsEmpty()) { - m_szOwnId = wszEmail; - delSetting("e-mail"); - } - } - - m_hWorkerThread = ForkThreadEx(&CIcqProto::ServerThread, nullptr, nullptr); -} - -CIcqProto::~CIcqProto() -{ - ::CloseHandle(m_evRequestsQueue); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// OnModulesLoadedEx - performs hook registration - -void CIcqProto::OnModulesLoaded() -{ - InitContactCache(); - - HookProtoEvent(ME_USERINFO_INITIALISE, &CIcqProto::OnUserInfoInit); - - // load custom smilies - CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers\\*.png", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName); - SMADD_CONT cont = { 2, m_szModuleName, wszPath }; - CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont)); -} - -void CIcqProto::OnShutdown() -{ - m_bTerminated = true; -} - -void CIcqProto::OnContactAdded(MCONTACT hContact) -{ - CMStringW wszId(getMStringW(hContact, DB_KEY_ID)); - if (!wszId.IsEmpty() && !FindContactByUIN(wszId)) { - mir_cslock l(m_csCache); - m_arCache.insert(new IcqCacheItem(wszId, hContact)); - } -} - -void CIcqProto::OnContactDeleted(MCONTACT hContact) -{ - CMStringW szId(GetUserId(hContact)); - if (!isChatRoom(hContact)) - m_arCache.remove(FindContactByUIN(szId)); - - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeBuddy") - << AIMSID(this) << WCHAR_PARAM("buddy", szId) << INT_PARAM("allGroups", 1)); -} - -void CIcqProto::OnEventEdited(MCONTACT, MEVENT) -{ - -} - -INT_PTR CIcqProto::OnMenuLoadHistory(WPARAM hContact, LPARAM) -{ - delSetting(hContact, DB_KEY_LASTMSGID); - - RetrieveUserHistory(hContact, 1, true); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -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); - - mi.pszService = "/EditGroups"; - CreateProtoService(mi.pszService, &CIcqProto::EditGroups); - mi.name.a = LPGEN("Edit server groups"); - mi.position = 200002; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_GROUP); - Menu_AddProtoMenuItem(&mi, m_szModuleName); - - mi.pszService = "/EditProfile"; - CreateProtoService(mi.pszService, &CIcqProto::EditProfile); - mi.name.a = LPGEN("Edit my web profile"); - mi.position = 210001; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDAWEB); - Menu_AddProtoMenuItem(&mi, m_szModuleName); - - Menu_ShowItem(m_hUploadGroups, false); -} - -INT_PTR CIcqProto::UploadGroups(WPARAM, LPARAM) -{ - for (auto &it : AccContacts()) { - if (isChatRoom(it)) - continue; - - ptrW wszIcqGroup(getWStringA(it, "IcqGroup")); - if (wszIcqGroup == nullptr) - continue; - - ptrW wszMirGroup(Clist_GetGroup(it)); - if (!wszMirGroup) - wszMirGroup = mir_wstrdup(L"General"); - if (mir_wstrcmp(wszIcqGroup, wszMirGroup)) - MoveContactToGroup(it, wszIcqGroup, wszMirGroup); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CGroupEditDlg : public CIcqDlgBase -{ - CCtrlListView groups; - -public: - - static CGroupEditDlg *pDlg; - - CGroupEditDlg(CIcqProto *ppro) : - CIcqDlgBase(ppro, IDD_EDITGROUPS), - groups(this, IDC_GROUPS) - { - groups.OnBuildMenu = Callback(this, &CGroupEditDlg::onMenu); - } - - void RefreshGroups() - { - for (auto &it : m_proto->m_arGroups.rev_iter()) - groups.AddItem(it->wszName, 0, (LPARAM)it); - } - - bool OnInitDialog() override - { - pDlg = this; - groups.AddColumn(0, TranslateT("Name"), 300); - RefreshGroups(); - return true; - } - - void OnDestroy() override - { - pDlg = nullptr; - } - - void onMenu(void *) - { - int cur = groups.GetSelectionMark(); - if (cur == -1) - return; - - IcqGroup *pGroup = (IcqGroup *)groups.GetItemData(cur); - - HMENU hMenu = CreatePopupMenu(); - AppendMenu(hMenu, MF_STRING, 1, TranslateT("Rename")); - AppendMenu(hMenu, MF_STRING, 2, TranslateT("Delete")); - - POINT pt; - GetCursorPos(&pt); - int cmd = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr); - DestroyMenu(hMenu); - - if (cmd == 1) { // rename - ENTER_STRING es = {}; - es.szModuleName = m_proto->m_szModuleName; - es.caption = TranslateT("Enter new group name"); - if (!EnterString(&es)) - return; - - m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/renameGroup") - << AIMSID(m_proto) << WCHAR_PARAM("oldGroup", pGroup->wszSrvName) << GROUP_PARAM("newGroup", es.ptszResult)); - - mir_free(es.ptszResult); - } - else if (cmd == 2) { // delete - m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeGroup") - << AIMSID(m_proto) << WCHAR_PARAM("group", pGroup->wszSrvName)); - } - } -}; - -CGroupEditDlg *CGroupEditDlg::pDlg = nullptr; - -INT_PTR CIcqProto::EditGroups(WPARAM, LPARAM) -{ - (new CGroupEditDlg(this))->Show(); - return 0; -} - -INT_PTR CIcqProto::EditProfile(WPARAM, LPARAM) -{ - if (mir_wstrlen(m_szOwnId)) - Utils_OpenUrlW(CMStringW(FORMAT, L"https://icq.com/people/%s/edit/", (wchar_t*)m_szOwnId)); - return 0; -} - -void RefreshGroups(void) -{ - if (CGroupEditDlg::pDlg != nullptr) - CGroupEditDlg::pDlg->RefreshGroups(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CIcqProto::GetEmailCount(WPARAM, LPARAM) -{ - if (!m_bOnline) - return 0; - return m_unreadEmails; -} - -INT_PTR CIcqProto::GotoInbox(WPARAM, LPARAM) -{ - Utils_OpenUrl("https://e.mail.ru/messages/inbox"); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::SendMarkRead() -{ - mir_cslock lck(m_csMarkReadQueue); - while (m_arMarkReadQueue.getCount()) { - IcqCacheItem *pUser = m_arMarkReadQueue[0]; - - auto *pReq = new AsyncRapiRequest(this, "setDlgStateWim"); - pReq->params << WCHAR_PARAM("sn", GetUserId(pUser->m_hContact)) << INT64_PARAM("lastRead", getId(pUser->m_hContact, DB_KEY_LASTMSGID)); - Push(pReq); - - m_arMarkReadQueue.remove(0); - } -} - -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 = Proto_GetBaseAccountName(hContact); - if (mir_strcmp(szProto, m_szModuleName)) - return 0; - - MarkAsRead(hContact); - 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 - if (pParam->pszOldName == nullptr) { - for (auto &it : m_arGroups) - if (it->wszName == pParam->pszNewName) - return 0; - - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/addGroup") - << AIMSID(this) << GROUP_PARAM("group", pParam->pszNewName)); - } - else if (pParam->pszNewName == nullptr) { - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeGroup") - << AIMSID(this) << GROUP_PARAM("group", pParam->pszOldName)); - } - else { - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/renameGroup") - << AIMSID(this) << GROUP_PARAM("oldGroup", pParam->pszOldName) << GROUP_PARAM("newGroup", pParam->pszNewName)); - } - } - else MoveContactToGroup(hContact, ptrW(getWStringA(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; - - MCONTACT hContact = CreateContact(psr->id.w, 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; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// PSR_AUTH - -int CIcqProto::AuthRecv(MCONTACT, PROTORECVEVENT *pre) -{ - return Proto_AuthRecv(m_szModuleName, pre); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// PSS_AUTHREQUEST - -int CIcqProto::AuthRequest(MCONTACT hContact, const wchar_t* szMessage) -{ - ptrW wszGroup(Clist_GetGroup(hContact)); - if (!wszGroup) - wszGroup = mir_wstrdup(L"General"); - - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/buddylist/addBuddy", &CIcqProto::OnAddBuddy); - pReq << AIMSID(this) << WCHAR_PARAM("authorizationMsg", szMessage) << WCHAR_PARAM("buddy", GetUserId(hContact)) << WCHAR_PARAM("group", wszGroup) << INT_PARAM("preAuthorized", 1); - pReq->hContact = hContact; - Push(pReq); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// File operations - -HANDLE CIcqProto::FileAllow(MCONTACT, HANDLE hTransfer, const wchar_t *pwszSavePath) -{ - if (!m_bOnline) - return nullptr; - - auto *ft = (IcqFileTransfer *)hTransfer; - ft->m_wszFileName.Insert(0, pwszSavePath); - ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer(); - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, ft->m_szHost, &CIcqProto::OnFileRecv); - pReq->pUserInfo = ft; - pReq->AddHeader("Sec-Fetch-User", "?1"); - pReq->AddHeader("Sec-Fetch-Site", "cross-site"); - pReq->AddHeader("Sec-Fetch-Mode", "navigate"); - Push(pReq); - - return hTransfer; -} - -int CIcqProto::FileCancel(MCONTACT hContact, HANDLE hTransfer) -{ - ProtoBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, hTransfer); - - auto *ft = (IcqFileTransfer *)hTransfer; - if (ft->pfts.currentFileTime != 0) - ft->m_bCanceled = true; - else - delete ft; - return 0; -} - -int CIcqProto::FileResume(HANDLE hTransfer, int, const wchar_t *szFilename) -{ - auto *ft = (IcqFileTransfer *)hTransfer; - if (!m_bOnline || ft == nullptr) - return 1; - - if (szFilename != nullptr) { - ft->m_wszFileName = szFilename; - ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer(); - } - - ::SetEvent(ft->hWaitEvent); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// 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_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | /*PF1_SEARCHBYNAME | TODO */ - PF1_VISLIST | 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_5: - return PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_INVISIBLE; - - case PFLAGNUM_4: - nReturn = PF4_FORCEAUTH | PF4_SUPPORTIDLE | PF4_OFFLINEFILES | PF4_IMSENDOFFLINE | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID | PF4_READNOTIFY; - break; - - case PFLAG_UNIQUEIDTEXT: - return (INT_PTR)TranslateT("UIN/e-mail/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 AsyncRapiRequest(this, "search", &CIcqProto::OnSearchResults); - pReq->params << WCHAR_PARAM(*pszSearch == '+' ? "phonenum" : "keyword", pszSearch); - Push(pReq); - - return pReq; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// SendFile - sends a file - -HANDLE CIcqProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) -{ - // we can't send more than one file at a time - if (ppszFiles[1] != 0) - return nullptr; - - struct _stat statbuf; - if (_wstat(ppszFiles[0], &statbuf)) { - debugLogW(L"'%s' is an invalid filename", ppszFiles[0]); - return nullptr; - } - - int iFileId = _wopen(ppszFiles[0], _O_RDONLY | _O_BINARY, _S_IREAD); - if (iFileId < 0) - return nullptr; - - auto *pTransfer = new IcqFileTransfer(hContact, ppszFiles[0]); - pTransfer->pfts.totalFiles = 1; - pTransfer->pfts.currentFileSize = pTransfer->pfts.totalBytes = statbuf.st_size; - pTransfer->m_fileId = iFileId; - if (mir_wstrlen(szDescription)) - pTransfer->m_wszDescr = szDescription; - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, "https://files.icq.com/files/init", &CIcqProto::OnFileInit); - pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("f", "json") << WCHAR_PARAM("filename", pTransfer->m_wszShortName) - << CHAR_PARAM("k", appId()) << INT_PARAM("size", statbuf.st_size) << INT_PARAM("ts", TS()); - CalcHash(pReq); - pReq->pUserInfo = pTransfer; - Push(pReq); - - return pTransfer; // 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; - { - mir_cslock lck(m_csOwnIds); - m_arOwnIds.insert(pOwn); - } - - pReq << AIMSID(this) << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("mentions", "") - << CHAR_PARAM("message", pszSrc) << CHAR_PARAM("offlineIM", "true") << CHAR_PARAM("t", szUserid) << INT_PARAM("ts", TS()); - Push(pReq); - return id; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// 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); - - 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 (!m_bOnline && !IsStatusConnecting(m_iStatus)) { - m_iStatus = ID_STATUS_CONNECTING; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); - - if (mir_wstrlen(m_szOwnId) == 0) { - debugLogA("Thread ended, UIN/password are not configured"); - ConnectionFailed(LOGINERR_BADUSERID); - return 0; - } - - if (!RetrievePassword()) { - 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_UserIsTyping - sends a UTN notification - -int CIcqProto::UserIsTyping(MCONTACT hContact, int type) -{ - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/im/setTyping") - << AIMSID(this) << WCHAR_PARAM("t", GetUserId(hContact)) << CHAR_PARAM("typingStatus", (type == PROTOTYPE_SELFTYPING_ON) ? "typing" : "typed")); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// PS_SetApparentMode - sets the visibility status - -int CIcqProto::SetApparentMode(MCONTACT hContact, int iMode) -{ - int oldMode = getWord(hContact, "ApparentMode"); - if (oldMode != iMode) { - setWord(hContact, "ApparentMode", iMode); - SetPermitDeny(GetUserId(hContact), iMode != ID_STATUS_OFFLINE); - } - return 0; -} +// ---------------------------------------------------------------------------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-2023 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)
+
+static int CompareCache(const IcqCacheItem *p1, const IcqCacheItem *p2)
+{
+ return mir_wstrcmp(p1->m_aimid, p2->m_aimid);
+}
+
+CIcqProto::CIcqProto(const char *aProtoName, const wchar_t *aUserName) :
+ PROTO<CIcqProto>(aProtoName, aUserName),
+ m_impl(*this),
+ m_arHttpQueue(10),
+ m_arOwnIds(1, PtrKeySortT),
+ m_arCache(20, &CompareCache),
+ m_arGroups(10, NumericKeySortT),
+ m_arMarkReadQueue(10, NumericKeySortT),
+ m_evRequestsQueue(CreateEvent(nullptr, FALSE, FALSE, nullptr)),
+ m_szOwnId(this, DB_KEY_ID),
+ m_iStatus1(this, "Status1", ID_STATUS_AWAY),
+ m_iStatus2(this, "Status2", ID_STATUS_NA),
+ m_iTimeDiff1(this, "TimeDiff1", 0),
+ m_iTimeDiff2(this, "TimeDiff2", 0),
+ m_bHideGroupchats(this, "HideChats", true),
+ m_bUseTrayIcon(this, "UseTrayIcon", false),
+ m_bErrorPopups(this, "ShowErrorPopups", true),
+ m_bLaunchMailbox(this, "LaunchMailbox", true)
+{
+ db_set_resident(m_szModuleName, DB_KEY_IDLE);
+ db_set_resident(m_szModuleName, DB_KEY_ONLINETS);
+
+ m_isMra = !stricmp(Proto_GetAccount(m_szModuleName)->szProtoName, "MRA");
+
+ // services
+ CreateProtoService(PS_CREATEACCMGRUI, &CIcqProto::CreateAccMgrUI);
+
+ CreateProtoService(PS_GETAVATARCAPS, &CIcqProto::GetAvatarCaps);
+ CreateProtoService(PS_GETAVATARINFO, &CIcqProto::GetAvatarInfo);
+ CreateProtoService(PS_GETMYAVATAR, &CIcqProto::GetAvatar);
+ CreateProtoService(PS_SETMYAVATAR, &CIcqProto::SetAvatar);
+
+ CreateProtoService(PS_MENU_LOADHISTORY, &CIcqProto::OnMenuLoadHistory);
+ CreateProtoService(PS_GETUNREADEMAILCOUNT, &CIcqProto::GetEmailCount);
+ CreateProtoService(PS_GOTO_INBOX, &CIcqProto::GotoInbox);
+
+ // 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);
+
+ // group chats
+ GCREGISTER gcr = {};
+ gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ Chat_Register(&gcr);
+
+ // netlib handle
+ NETLIBUSER nlu = {};
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szDescriptiveName.w = m_tszUserName;
+ m_hNetlibUser = Netlib_RegisterUser(&nlu);
+
+ // this was previously an old ICQ account
+ ptrW wszUin(GetUIN(0));
+ if (wszUin != nullptr) {
+ delSetting("UIN");
+
+ m_szOwnId = wszUin;
+
+ for (auto &it : AccContacts())
+ delSetting(it, "e-mail");
+ }
+ // this was previously an old MRA account
+ else {
+ CMStringW wszEmail(getMStringW("e-mail"));
+ if (!wszEmail.IsEmpty()) {
+ m_szOwnId = wszEmail;
+ delSetting("e-mail");
+ }
+ }
+
+ m_hWorkerThread = ForkThreadEx(&CIcqProto::ServerThread, nullptr, nullptr);
+}
+
+CIcqProto::~CIcqProto()
+{
+ ::CloseHandle(m_evRequestsQueue);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// OnModulesLoadedEx - performs hook registration
+
+void CIcqProto::OnModulesLoaded()
+{
+ InitContactCache();
+
+ HookProtoEvent(ME_USERINFO_INITIALISE, &CIcqProto::OnUserInfoInit);
+
+ // load custom smilies
+ CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers\\*.png", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName);
+ SMADD_CONT cont = { 2, m_szModuleName, wszPath };
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
+}
+
+void CIcqProto::OnShutdown()
+{
+ m_bTerminated = true;
+}
+
+void CIcqProto::OnContactAdded(MCONTACT hContact)
+{
+ CMStringW wszId(getMStringW(hContact, DB_KEY_ID));
+ if (!wszId.IsEmpty() && !FindContactByUIN(wszId)) {
+ mir_cslock l(m_csCache);
+ m_arCache.insert(new IcqCacheItem(wszId, hContact));
+ }
+}
+
+void CIcqProto::OnContactDeleted(MCONTACT hContact)
+{
+ CMStringW szId(GetUserId(hContact));
+ if (!isChatRoom(hContact))
+ m_arCache.remove(FindContactByUIN(szId));
+
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeBuddy")
+ << AIMSID(this) << WCHAR_PARAM("buddy", szId) << INT_PARAM("allGroups", 1));
+}
+
+void CIcqProto::OnEventEdited(MCONTACT, MEVENT)
+{
+
+}
+
+INT_PTR CIcqProto::OnMenuLoadHistory(WPARAM hContact, LPARAM)
+{
+ delSetting(hContact, DB_KEY_LASTMSGID);
+
+ RetrieveUserHistory(hContact, 1, true);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+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);
+
+ mi.pszService = "/EditGroups";
+ CreateProtoService(mi.pszService, &CIcqProto::EditGroups);
+ mi.name.a = LPGEN("Edit server groups");
+ mi.position = 200002;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_GROUP);
+ Menu_AddProtoMenuItem(&mi, m_szModuleName);
+
+ mi.pszService = "/EditProfile";
+ CreateProtoService(mi.pszService, &CIcqProto::EditProfile);
+ mi.name.a = LPGEN("Edit my web profile");
+ mi.position = 210001;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDAWEB);
+ Menu_AddProtoMenuItem(&mi, m_szModuleName);
+
+ Menu_ShowItem(m_hUploadGroups, false);
+}
+
+INT_PTR CIcqProto::UploadGroups(WPARAM, LPARAM)
+{
+ for (auto &it : AccContacts()) {
+ if (isChatRoom(it))
+ continue;
+
+ ptrW wszIcqGroup(getWStringA(it, "IcqGroup"));
+ if (wszIcqGroup == nullptr)
+ continue;
+
+ ptrW wszMirGroup(Clist_GetGroup(it));
+ if (!wszMirGroup)
+ wszMirGroup = mir_wstrdup(L"General");
+ if (mir_wstrcmp(wszIcqGroup, wszMirGroup))
+ MoveContactToGroup(it, wszIcqGroup, wszMirGroup);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CGroupEditDlg : public CIcqDlgBase
+{
+ CCtrlListView groups;
+
+public:
+
+ static CGroupEditDlg *pDlg;
+
+ CGroupEditDlg(CIcqProto *ppro) :
+ CIcqDlgBase(ppro, IDD_EDITGROUPS),
+ groups(this, IDC_GROUPS)
+ {
+ groups.OnBuildMenu = Callback(this, &CGroupEditDlg::onMenu);
+ }
+
+ void RefreshGroups()
+ {
+ for (auto &it : m_proto->m_arGroups.rev_iter())
+ groups.AddItem(it->wszName, 0, (LPARAM)it);
+ }
+
+ bool OnInitDialog() override
+ {
+ pDlg = this;
+ groups.AddColumn(0, TranslateT("Name"), 300);
+ RefreshGroups();
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ pDlg = nullptr;
+ }
+
+ void onMenu(void *)
+ {
+ int cur = groups.GetSelectionMark();
+ if (cur == -1)
+ return;
+
+ IcqGroup *pGroup = (IcqGroup *)groups.GetItemData(cur);
+
+ HMENU hMenu = CreatePopupMenu();
+ AppendMenu(hMenu, MF_STRING, 1, TranslateT("Rename"));
+ AppendMenu(hMenu, MF_STRING, 2, TranslateT("Delete"));
+
+ POINT pt;
+ GetCursorPos(&pt);
+ int cmd = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr);
+ DestroyMenu(hMenu);
+
+ if (cmd == 1) { // rename
+ ENTER_STRING es = {};
+ es.szModuleName = m_proto->m_szModuleName;
+ es.caption = TranslateT("Enter new group name");
+ if (!EnterString(&es))
+ return;
+
+ m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/renameGroup")
+ << AIMSID(m_proto) << WCHAR_PARAM("oldGroup", pGroup->wszSrvName) << GROUP_PARAM("newGroup", es.ptszResult));
+
+ mir_free(es.ptszResult);
+ }
+ else if (cmd == 2) { // delete
+ m_proto->Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeGroup")
+ << AIMSID(m_proto) << WCHAR_PARAM("group", pGroup->wszSrvName));
+ }
+ }
+};
+
+CGroupEditDlg *CGroupEditDlg::pDlg = nullptr;
+
+INT_PTR CIcqProto::EditGroups(WPARAM, LPARAM)
+{
+ (new CGroupEditDlg(this))->Show();
+ return 0;
+}
+
+INT_PTR CIcqProto::EditProfile(WPARAM, LPARAM)
+{
+ if (mir_wstrlen(m_szOwnId))
+ Utils_OpenUrlW(CMStringW(FORMAT, L"https://icq.com/people/%s/edit/", (wchar_t*)m_szOwnId));
+ return 0;
+}
+
+void RefreshGroups(void)
+{
+ if (CGroupEditDlg::pDlg != nullptr)
+ CGroupEditDlg::pDlg->RefreshGroups();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR CIcqProto::GetEmailCount(WPARAM, LPARAM)
+{
+ if (!m_bOnline)
+ return 0;
+ return m_unreadEmails;
+}
+
+INT_PTR CIcqProto::GotoInbox(WPARAM, LPARAM)
+{
+ Utils_OpenUrl("https://e.mail.ru/messages/inbox");
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::SendMarkRead()
+{
+ mir_cslock lck(m_csMarkReadQueue);
+ while (m_arMarkReadQueue.getCount()) {
+ IcqCacheItem *pUser = m_arMarkReadQueue[0];
+
+ auto *pReq = new AsyncRapiRequest(this, "setDlgStateWim");
+ pReq->params << WCHAR_PARAM("sn", GetUserId(pUser->m_hContact)) << INT64_PARAM("lastRead", getId(pUser->m_hContact, DB_KEY_LASTMSGID));
+ Push(pReq);
+
+ m_arMarkReadQueue.remove(0);
+ }
+}
+
+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 = Proto_GetBaseAccountName(hContact);
+ if (mir_strcmp(szProto, m_szModuleName))
+ return 0;
+
+ MarkAsRead(hContact);
+ 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
+ if (pParam->pszOldName == nullptr) {
+ for (auto &it : m_arGroups)
+ if (it->wszName == pParam->pszNewName)
+ return 0;
+
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/addGroup")
+ << AIMSID(this) << GROUP_PARAM("group", pParam->pszNewName));
+ }
+ else if (pParam->pszNewName == nullptr) {
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/removeGroup")
+ << AIMSID(this) << GROUP_PARAM("group", pParam->pszOldName));
+ }
+ else {
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/renameGroup")
+ << AIMSID(this) << GROUP_PARAM("oldGroup", pParam->pszOldName) << GROUP_PARAM("newGroup", pParam->pszNewName));
+ }
+ }
+ else MoveContactToGroup(hContact, ptrW(getWStringA(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;
+
+ MCONTACT hContact = CreateContact(psr->id.w, 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSR_AUTH
+
+int CIcqProto::AuthRecv(MCONTACT, PROTORECVEVENT *pre)
+{
+ return Proto_AuthRecv(m_szModuleName, pre);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSS_AUTHREQUEST
+
+int CIcqProto::AuthRequest(MCONTACT hContact, const wchar_t* szMessage)
+{
+ ptrW wszGroup(Clist_GetGroup(hContact));
+ if (!wszGroup)
+ wszGroup = mir_wstrdup(L"General");
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/buddylist/addBuddy", &CIcqProto::OnAddBuddy);
+ pReq << AIMSID(this) << WCHAR_PARAM("authorizationMsg", szMessage) << WCHAR_PARAM("buddy", GetUserId(hContact)) << WCHAR_PARAM("group", wszGroup) << INT_PARAM("preAuthorized", 1);
+ pReq->hContact = hContact;
+ Push(pReq);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// File operations
+
+HANDLE CIcqProto::FileAllow(MCONTACT, HANDLE hTransfer, const wchar_t *pwszSavePath)
+{
+ if (!m_bOnline)
+ return nullptr;
+
+ auto *ft = (IcqFileTransfer *)hTransfer;
+ ft->m_wszFileName.Insert(0, pwszSavePath);
+ ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, ft->m_szHost, &CIcqProto::OnFileRecv);
+ pReq->pUserInfo = ft;
+ pReq->AddHeader("Sec-Fetch-User", "?1");
+ pReq->AddHeader("Sec-Fetch-Site", "cross-site");
+ pReq->AddHeader("Sec-Fetch-Mode", "navigate");
+ Push(pReq);
+
+ return hTransfer;
+}
+
+int CIcqProto::FileCancel(MCONTACT hContact, HANDLE hTransfer)
+{
+ ProtoBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, hTransfer);
+
+ auto *ft = (IcqFileTransfer *)hTransfer;
+ if (ft->pfts.currentFileTime != 0)
+ ft->m_bCanceled = true;
+ else
+ delete ft;
+ return 0;
+}
+
+int CIcqProto::FileResume(HANDLE hTransfer, int, const wchar_t *szFilename)
+{
+ auto *ft = (IcqFileTransfer *)hTransfer;
+ if (!m_bOnline || ft == nullptr)
+ return 1;
+
+ if (szFilename != nullptr) {
+ ft->m_wszFileName = szFilename;
+ ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
+ }
+
+ ::SetEvent(ft->hWaitEvent);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// 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_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | /*PF1_SEARCHBYNAME | TODO */
+ PF1_VISLIST | 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_5:
+ return PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_INVISIBLE;
+
+ case PFLAGNUM_4:
+ nReturn = PF4_FORCEAUTH | PF4_SUPPORTIDLE | PF4_OFFLINEFILES | PF4_IMSENDOFFLINE | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID | PF4_READNOTIFY;
+ break;
+
+ case PFLAG_UNIQUEIDTEXT:
+ return (INT_PTR)TranslateT("UIN/e-mail/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 AsyncRapiRequest(this, "search", &CIcqProto::OnSearchResults);
+ pReq->params << WCHAR_PARAM(*pszSearch == '+' ? "phonenum" : "keyword", pszSearch);
+ Push(pReq);
+
+ return pReq;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendFile - sends a file
+
+HANDLE CIcqProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles)
+{
+ // we can't send more than one file at a time
+ if (ppszFiles[1] != 0)
+ return nullptr;
+
+ struct _stat statbuf;
+ if (_wstat(ppszFiles[0], &statbuf)) {
+ debugLogW(L"'%s' is an invalid filename", ppszFiles[0]);
+ return nullptr;
+ }
+
+ int iFileId = _wopen(ppszFiles[0], _O_RDONLY | _O_BINARY, _S_IREAD);
+ if (iFileId < 0)
+ return nullptr;
+
+ auto *pTransfer = new IcqFileTransfer(hContact, ppszFiles[0]);
+ pTransfer->pfts.totalFiles = 1;
+ pTransfer->pfts.currentFileSize = pTransfer->pfts.totalBytes = statbuf.st_size;
+ pTransfer->m_fileId = iFileId;
+ if (mir_wstrlen(szDescription))
+ pTransfer->m_wszDescr = szDescription;
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, "https://files.icq.com/files/init", &CIcqProto::OnFileInit);
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("f", "json") << WCHAR_PARAM("filename", pTransfer->m_wszShortName)
+ << CHAR_PARAM("k", appId()) << INT_PARAM("size", statbuf.st_size) << INT_PARAM("ts", TS());
+ CalcHash(pReq);
+ pReq->pUserInfo = pTransfer;
+ Push(pReq);
+
+ return pTransfer; // 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;
+ {
+ mir_cslock lck(m_csOwnIds);
+ m_arOwnIds.insert(pOwn);
+ }
+
+ pReq << AIMSID(this) << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("mentions", "")
+ << CHAR_PARAM("message", pszSrc) << CHAR_PARAM("offlineIM", "true") << CHAR_PARAM("t", szUserid) << INT_PARAM("ts", TS());
+ Push(pReq);
+ return id;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// 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);
+
+ 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 (!m_bOnline && !IsStatusConnecting(m_iStatus)) {
+ m_iStatus = ID_STATUS_CONNECTING;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+
+ if (mir_wstrlen(m_szOwnId) == 0) {
+ debugLogA("Thread ended, UIN/password are not configured");
+ ConnectionFailed(LOGINERR_BADUSERID);
+ return 0;
+ }
+
+ if (!RetrievePassword()) {
+ 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_UserIsTyping - sends a UTN notification
+
+int CIcqProto::UserIsTyping(MCONTACT hContact, int type)
+{
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/im/setTyping")
+ << AIMSID(this) << WCHAR_PARAM("t", GetUserId(hContact)) << CHAR_PARAM("typingStatus", (type == PROTOTYPE_SELFTYPING_ON) ? "typing" : "typed"));
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetApparentMode - sets the visibility status
+
+int CIcqProto::SetApparentMode(MCONTACT hContact, int iMode)
+{
+ int oldMode = getWord(hContact, "ApparentMode");
+ if (oldMode != iMode) {
+ setWord(hContact, "ApparentMode", iMode);
+ SetPermitDeny(GetUserId(hContact), iMode != ID_STATUS_OFFLINE);
+ }
+ return 0;
+}
diff --git a/protocols/ICQ-WIM/src/proto.h b/protocols/ICQ-WIM/src/proto.h index b909a0d74a..20636cc4c6 100644 --- a/protocols/ICQ-WIM/src/proto.h +++ b/protocols/ICQ-WIM/src/proto.h @@ -1,494 +1,494 @@ -// ---------------------------------------------------------------------------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-2022 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 declarations -// ----------------------------------------------------------------------------- - -#ifndef _ICQ_PROTO_H_ -#define _ICQ_PROTO_H_ - -#include "m_system.h" -#include "m_protoint.h" - -#define MRA_APP_ID "ic1pzYNtEU6dDnEQ" -#define ICQ_APP_ID "ic1nmMjqg7Yu-0hL" -#define ICQ_API_SERVER "https://u.icq.net/wim" -#define ICQ_FILE_SERVER "https://u.icq.net/files/api/v1.1" -#define ICQ_FAKE_EVENT_ID 0xBABAEB -#define ICQ_ROBUST_SERVER "https://u.icq.net/rapi" - -#define PS_DUMMY "/DoNothing" -#define PS_GOTO_INBOX "/GotoInbox" - -#define WIM_CAP_VOIP_VOICE "094613504c7f11d18222444553540000" -#define WIM_CAP_VOIP_VIDEO "094613514c7f11d18222444553540000" -#define WIM_CAP_FILETRANSFER "094613434c7f11d18222444553540000" -#define WIM_CAP_UNIQ_REQ_ID "094613534c7f11d18222444553540000" -#define WIM_CAP_EMOJI "094613544c7f11d18222444553540000" -#define WIM_CAP_MENTIONS "0946135b4c7f11d18222444553540000" -#define WIM_CAP_MAIL_NOTIFICATIONS "094613594c7f11d18222444553540000" -#define WIM_CAP_INTRO_DLG_STATE "0946135a4c7f11d18222444553540000" - -#define NG_CAP_SECUREIM "4d69724e47536563757265494d000000" - -typedef CProtoDlgBase<CIcqProto> CIcqDlgBase; - -struct AIMSID -{ - AIMSID(CIcqProto *_ppro) : - m_ppro(_ppro) - {} - - CIcqProto *m_ppro; -}; - -enum ChatMenuItems -{ - IDM_INVITE = 10, IDM_LEAVE -}; - -struct IcqFileInfo -{ - IcqFileInfo(const std::string &pszUrl, const CMStringW &pwszDescr, uint32_t dwSize) : - szUrl(pszUrl.c_str()), - wszDescr(pwszDescr), - dwFileSize(dwSize) - {} - - CMStringA szUrl; - CMStringW wszDescr; - uint32_t dwFileSize; - bool bIsSticker = false; -}; - -struct IcqGroup -{ - IcqGroup(int _p1, const CMStringW &_p2) : - id(_p1), - wszSrvName(_p2) - { - SetName(_p2); - } - - int id; - int level; - CMStringW wszName, wszSrvName; - - void SetName(const CMStringW &str) - { - wszName = str; - level = wszName.SpanIncluding(L">").GetLength(); - if (level != 0) - wszName.Delete(0, level); - wszName.Replace(L">", L"\\"); - } -}; - -struct IcqCacheItem : public MZeroedObject -{ - IcqCacheItem(const CMStringW &wszId, MCONTACT _contact) : - m_aimid(wszId), - m_hContact(_contact) - {} - - CMStringW m_aimid; - MCONTACT m_hContact; - bool m_bInList; - __int64 m_iProcessedMsgId; - int m_iApparentMode; - time_t m_timer1, m_timer2; -}; - -struct IcqOwnMessage -{ - IcqOwnMessage(MCONTACT _hContact, int _msgid, const char *guid) - : m_hContact(_hContact), m_msgid(_msgid) - { - strncpy_s(m_guid, guid, _TRUNCATE); - } - - MCONTACT m_hContact; - int m_msgid; - char m_guid[50]; -}; - -struct IcqConn -{ - HNETLIBCONN s; - int lastTs, timeout; -}; - -struct IcqFileTransfer : public MZeroedObject -{ - // create an object for receiving - IcqFileTransfer(MCONTACT hContact, const char *pszUrl) : - m_szHost(pszUrl) - { - pfts.hContact = hContact; - pfts.totalFiles = 1; - pfts.flags = PFTS_UNICODE | PFTS_RECEIVING; - - ptrW pwszFileName(mir_utf8decodeW(pszUrl)); - if (pwszFileName == nullptr) - pwszFileName = mir_a2u(pszUrl); - - const wchar_t *p = wcsrchr(pwszFileName, '/'); - m_wszFileName = (p == nullptr) ? pwszFileName : p + 1; - m_wszShortName = m_wszFileName; - } - - // create an object for sending - IcqFileTransfer(MCONTACT hContact, const wchar_t *pwszFileName) : - m_wszFileName(pwszFileName) - { - pfts.flags = PFTS_UNICODE | PFTS_SENDING; - pfts.hContact = hContact; - pfts.szCurrentFile.w = m_wszFileName.GetBuffer(); - - const wchar_t *p = wcsrchr(pfts.szCurrentFile.w, '\\'); - if (pwszFileName != nullptr) - p++; - else - p = pfts.szCurrentFile.w; - m_wszShortName = p; - } - - ~IcqFileTransfer() - { - if (m_fileId >= 0) - _close(m_fileId); - } - - bool m_bCanceled = false, m_bStarted = false; - int m_fileId = -1; - CMStringA m_szHost; - CMStringW m_wszFileName, m_wszDescr; - const wchar_t *m_wszShortName; - PROTOFILETRANSFERSTATUS pfts; - HANDLE hWaitEvent; - - void FillHeaders(AsyncHttpRequest *pReq) - { - pReq->AddHeader("Content-Type", "application/octet-stream"); - pReq->AddHeader("Content-Disposition", CMStringA(FORMAT, "attachment; filename=\"%s\"", T2Utf(m_wszShortName).get())); - - uint32_t dwPortion = pfts.currentFileSize - pfts.currentFileProgress; - if (dwPortion > 1000000) - dwPortion = 1000000; - - pReq->AddHeader("Content-Range", CMStringA(FORMAT, "bytes %lld-%lld/%lld", pfts.currentFileProgress, pfts.currentFileProgress + dwPortion - 1, pfts.currentFileSize)); - pReq->AddHeader("Content-Length", CMStringA(FORMAT, "%d", dwPortion)); - - pReq->dataLength = dwPortion; - pReq->pData = (char*)mir_alloc(dwPortion); - _lseek(m_fileId, pfts.currentFileProgress, SEEK_SET); - _read(m_fileId, pReq->pData, dwPortion); - - pfts.currentFileProgress += dwPortion; - pfts.totalProgress += dwPortion; - } -}; - -class CIcqProto : public PROTO<CIcqProto> -{ - friend struct AsyncRapiRequest; - - class CIcqProtoImpl - { - friend class CIcqProto; - - CIcqProto &m_proto; - CTimer m_heartBeat, m_markRead; - - void OnHeartBeat(CTimer *) { - m_proto.CheckStatus(); - } - - void OnMarkRead(CTimer *pTimer) { - m_proto.SendMarkRead(); - pTimer->Stop(); - } - - CIcqProtoImpl(CIcqProto &pro) : - m_proto(pro), - m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)), - m_heartBeat(Miranda_GetSystemWindow(), UINT_PTR(this) + 1) - { - m_markRead.OnEvent = Callback(this, &CIcqProtoImpl::OnMarkRead); - m_heartBeat.OnEvent = Callback(this, &CIcqProtoImpl::OnHeartBeat); - } - } m_impl; - - friend struct CIcqRegistrationDlg; - friend class CGroupchatInviteDlg; - friend class CEditIgnoreListDlg; - friend class CIcqEnterLoginDlg; - friend class CIcqOptionsDlg; - friend class CGroupEditDlg; - - friend AsyncHttpRequest* operator <<(AsyncHttpRequest*, const AIMSID&); - - bool m_bOnline, m_bTerminated, m_bFirstBos, m_isMra, m_bError462; - int m_iTimeShift; - - MCONTACT CheckOwnMessage(const CMStringA &reqId, const CMStringA &msgId, bool bRemove); - void CheckPassword(void); - void ConnectionFailed(int iReason, int iErrorCode = 0); - void EmailNotification(const wchar_t *pwszText); - void GetPermitDeny(); - wchar_t* GetUIN(MCONTACT hContact); - void MarkAsRead(MCONTACT hContact); - void MoveContactToGroup(MCONTACT hContact, const wchar_t *pwszGroup, const wchar_t *pwszNewGroup); - bool RetrievePassword(); - void RetrieveUserHistory(MCONTACT, __int64 startMsgId, bool bCreateRead); - void RetrieveUserInfo(MCONTACT hContact); - void SendMrimLogin(NETLIBHTTPREQUEST *pReply); - void SetServerStatus(int iNewStatus); - void ShutdownSession(void); - void StartSession(void); - - void CheckAvatarChange(MCONTACT hContact, const JSONNode&); - IcqFileInfo* CheckFile(MCONTACT hContact, CMStringW &wszFileName, bool &bIsFile); - void CheckLastId(MCONTACT hContact, const JSONNode&); - void Json2int(MCONTACT, const JSONNode&, const char *szJson, const char *szSetting, bool bIsPartial); - void Json2string(MCONTACT, const JSONNode&, const char *szJson, const char *szSetting, bool bIsPartial); - MCONTACT ParseBuddyInfo(const JSONNode &buddy, MCONTACT hContact = INVALID_CONTACT_ID, bool bIsPartial = false); - void ParseMessage(MCONTACT hContact, __int64 &lastMsgId, const JSONNode &msg, bool bCreateRead, bool bLocalTime); - int StatusFromPresence(const JSONNode &presence, MCONTACT hContact); - - void OnLoggedIn(void); - void OnLoggedOut(void); - - mir_cs m_csMarkReadQueue; - LIST<IcqCacheItem> m_arMarkReadQueue; - void SendMarkRead(); - - __int64 getId(MCONTACT hContact, const char *szSetting); - void setId(MCONTACT hContact, const char *szSetting, __int64 iValue); - - void OnAddBuddy(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnAddClient(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnCheckMraAuth(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnCheckMraAuthFinal(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnCheckMrimLogin(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnCheckPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnFetchEvents(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnFileContinue(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnFileInit(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGenToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGetChatInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGetPermitDeny(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGetSticker(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGetUserHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnGetUserInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnLoginViaPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnNormalizePhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnSearchResults(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnSendMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnSessionEnd(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - void OnValidateSms(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq); - - void ProcessBuddyList(const JSONNode &pRoot); - void ProcessDiff(const JSONNode &pRoot); - void ProcessEvent(const JSONNode &pRoot); - void ProcessGroupChat(const JSONNode &pRoot); - void ProcessHistData(const JSONNode &pRoot); - void ProcessImState(const JSONNode &pRoot); - void ProcessMyInfo(const JSONNode &pRoot); - void ProcessNotification(const JSONNode &pRoot); - void ProcessPermissions(const JSONNode &pRoot); - void ProcessPresence(const JSONNode &pRoot); - void ProcessSessionEnd(const JSONNode &pRoot); - void ProcessTyping(const JSONNode &pRoot); - - IcqConn m_ConnPool[CONN_LAST]; - CMStringA m_szPassword; - CMStringA m_szSessionKey; - CMStringA m_szAToken; - CMStringA m_szRToken; - CMStringA m_fetchBaseURL; - CMStringA m_aimsid; - CMStringA m_szMraCookie; - LONG m_msgId = 1; - int m_iRClientId; - HGENMENU m_hUploadGroups; - - mir_cs m_csOwnIds; - OBJLIST<IcqOwnMessage> m_arOwnIds; - - OBJLIST<IcqGroup> m_arGroups; - - int m_unreadEmails = -1; - CMStringA m_szMailBox; - - bool m_bIgnoreListEmpty = true; - bool m_bRememberPwd; // store password in a database - bool m_bDlgActive; - - //////////////////////////////////////////////////////////////////////////////////////// - // group chats - - int __cdecl GroupchatEventHook(WPARAM, LPARAM); - int __cdecl GroupchatMenuHook(WPARAM, LPARAM); - - void Chat_ProcessLogMenu(SESSION_INFO *si, int); - void Chat_SendPrivateMessage(GCHOOK *gch); - - void InviteUserToChat(SESSION_INFO *si); - void LeaveDestroyChat(SESSION_INFO *si); - void LoadChatInfo(SESSION_INFO *si); - - //////////////////////////////////////////////////////////////////////////////////////// - // http queue - - mir_cs m_csHttpQueue; - HANDLE m_evRequestsQueue; - LIST<AsyncHttpRequest> m_arHttpQueue; - - void CalcHash(AsyncHttpRequest*); - void DropQueue(); - bool ExecuteRequest(AsyncHttpRequest*); - bool IsQueueEmpty(); - void Push(MHttpRequest*); - bool RefreshRobustToken(AsyncHttpRequest *pReq); - - //////////////////////////////////////////////////////////////////////////////////////// - // cache - - mir_cs m_csCache; - OBJLIST<IcqCacheItem> m_arCache; - - void InitContactCache(void); - IcqCacheItem* FindContactByUIN(const CMStringW &pwszId); - MCONTACT CreateContact(const CMStringW &pwszId, bool bTemporary); - - void GetAvatarFileName(MCONTACT hContact, wchar_t *pszDest, size_t cbLen); - - //////////////////////////////////////////////////////////////////////////////////////// - // threads - - HANDLE m_hWorkerThread; - void __cdecl ServerThread(void*); - void __cdecl PollThread(void*); - - //////////////////////////////////////////////////////////////////////////////////////// - // services - - INT_PTR __cdecl GetAvatar(WPARAM, LPARAM); - INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM); - INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); - INT_PTR __cdecl SetAvatar(WPARAM, LPARAM); - - INT_PTR __cdecl CreateAccMgrUI(WPARAM, LPARAM); - INT_PTR __cdecl EditGroups(WPARAM, LPARAM); - INT_PTR __cdecl EditProfile(WPARAM, LPARAM); - INT_PTR __cdecl GetEmailCount(WPARAM, LPARAM); - INT_PTR __cdecl GotoInbox(WPARAM, LPARAM); - INT_PTR __cdecl UploadGroups(WPARAM, LPARAM); - - INT_PTR __cdecl OnMenuLoadHistory(WPARAM, LPARAM); - - //////////////////////////////////////////////////////////////////////////////////////// - // events - - int __cdecl OnGroupChange(WPARAM, LPARAM); - int __cdecl OnDbEventRead(WPARAM, LPARAM); - int __cdecl OnOptionsInit(WPARAM, LPARAM); - int __cdecl OnUserInfoInit(WPARAM, LPARAM); - - //////////////////////////////////////////////////////////////////////////////////////// - // PROTO_INTERFACE - - MCONTACT AddToList( int flags, PROTOSEARCHRESULT *psr) override; - - int AuthRecv(MCONTACT, PROTORECVEVENT *pre) override; - int AuthRequest(MCONTACT hContact, const wchar_t *szMessage) override; - - INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; - int GetInfo(MCONTACT hContact, int infoType) override; - - HANDLE SearchBasic(const wchar_t *id) override; - - HANDLE FileAllow(MCONTACT hContact, HANDLE hTransfer, const wchar_t *szPath) override; - int FileCancel(MCONTACT hContact, HANDLE hTransfer) override; - int FileResume(HANDLE hTransfer, int action, const wchar_t *szFilename) override; - - HANDLE SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) override; - int SendMsg(MCONTACT hContact, int flags, const char *msg) override; - - int SetApparentMode(MCONTACT hContact, int mode) override; - int SetStatus(int iNewStatus) override; - - int UserIsTyping(MCONTACT hContact, int type) override; - - void OnBuildProtoMenu(void) override; - void OnContactAdded(MCONTACT) override; - void OnContactDeleted(MCONTACT) override; - void OnEventEdited(MCONTACT, MEVENT) override; - void OnModulesLoaded() override; - void OnShutdown() override; - -public: - CIcqProto(const char*, const wchar_t*); - ~CIcqProto(); - - CMOption<wchar_t*> m_szOwnId; // our own aim id - CMOption<uint8_t> m_bHideGroupchats; // don't pop up group chat windows on startup - CMOption<uint8_t> m_bUseTrayIcon; // use tray icon notifications - CMOption<uint8_t> m_bErrorPopups; // display popups with errors - CMOption<uint8_t> m_bLaunchMailbox; // launch browser to view email - CMOption<uint32_t> m_iTimeDiff1; // set this status to m_iStatus1 after this interval of secs - CMOption<uint32_t> m_iStatus1; - CMOption<uint32_t> m_iTimeDiff2; // set this status to m_iStatus2 after this interval of secs - CMOption<uint32_t> m_iStatus2; - - void CheckStatus(void); - CMStringW GetUserId(MCONTACT); - - __forceinline int TS() const - { return time(0) - m_iTimeShift; - } - - __forceinline const char *appId() const - { return (m_isMra) ? MRA_APP_ID : ICQ_APP_ID; - } - - void SetPermitDeny(const CMStringW &userId, bool bAllow); -}; - -struct CMPlugin : public ACCPROTOPLUGIN<CIcqProto> -{ - CMPlugin(); - - int Load() override; -}; - -#endif +// ---------------------------------------------------------------------------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-2023 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 declarations
+// -----------------------------------------------------------------------------
+
+#ifndef _ICQ_PROTO_H_
+#define _ICQ_PROTO_H_
+
+#include "m_system.h"
+#include "m_protoint.h"
+
+#define MRA_APP_ID "ic1pzYNtEU6dDnEQ"
+#define ICQ_APP_ID "ic1nmMjqg7Yu-0hL"
+#define ICQ_API_SERVER "https://u.icq.net/wim"
+#define ICQ_FILE_SERVER "https://u.icq.net/files/api/v1.1"
+#define ICQ_FAKE_EVENT_ID 0xBABAEB
+#define ICQ_ROBUST_SERVER "https://u.icq.net/rapi"
+
+#define PS_DUMMY "/DoNothing"
+#define PS_GOTO_INBOX "/GotoInbox"
+
+#define WIM_CAP_VOIP_VOICE "094613504c7f11d18222444553540000"
+#define WIM_CAP_VOIP_VIDEO "094613514c7f11d18222444553540000"
+#define WIM_CAP_FILETRANSFER "094613434c7f11d18222444553540000"
+#define WIM_CAP_UNIQ_REQ_ID "094613534c7f11d18222444553540000"
+#define WIM_CAP_EMOJI "094613544c7f11d18222444553540000"
+#define WIM_CAP_MENTIONS "0946135b4c7f11d18222444553540000"
+#define WIM_CAP_MAIL_NOTIFICATIONS "094613594c7f11d18222444553540000"
+#define WIM_CAP_INTRO_DLG_STATE "0946135a4c7f11d18222444553540000"
+
+#define NG_CAP_SECUREIM "4d69724e47536563757265494d000000"
+
+typedef CProtoDlgBase<CIcqProto> CIcqDlgBase;
+
+struct AIMSID
+{
+ AIMSID(CIcqProto *_ppro) :
+ m_ppro(_ppro)
+ {}
+
+ CIcqProto *m_ppro;
+};
+
+enum ChatMenuItems
+{
+ IDM_INVITE = 10, IDM_LEAVE
+};
+
+struct IcqFileInfo
+{
+ IcqFileInfo(const std::string &pszUrl, const CMStringW &pwszDescr, uint32_t dwSize) :
+ szUrl(pszUrl.c_str()),
+ wszDescr(pwszDescr),
+ dwFileSize(dwSize)
+ {}
+
+ CMStringA szUrl;
+ CMStringW wszDescr;
+ uint32_t dwFileSize;
+ bool bIsSticker = false;
+};
+
+struct IcqGroup
+{
+ IcqGroup(int _p1, const CMStringW &_p2) :
+ id(_p1),
+ wszSrvName(_p2)
+ {
+ SetName(_p2);
+ }
+
+ int id;
+ int level;
+ CMStringW wszName, wszSrvName;
+
+ void SetName(const CMStringW &str)
+ {
+ wszName = str;
+ level = wszName.SpanIncluding(L">").GetLength();
+ if (level != 0)
+ wszName.Delete(0, level);
+ wszName.Replace(L">", L"\\");
+ }
+};
+
+struct IcqCacheItem : public MZeroedObject
+{
+ IcqCacheItem(const CMStringW &wszId, MCONTACT _contact) :
+ m_aimid(wszId),
+ m_hContact(_contact)
+ {}
+
+ CMStringW m_aimid;
+ MCONTACT m_hContact;
+ bool m_bInList;
+ __int64 m_iProcessedMsgId;
+ int m_iApparentMode;
+ time_t m_timer1, m_timer2;
+};
+
+struct IcqOwnMessage
+{
+ IcqOwnMessage(MCONTACT _hContact, int _msgid, const char *guid)
+ : m_hContact(_hContact), m_msgid(_msgid)
+ {
+ strncpy_s(m_guid, guid, _TRUNCATE);
+ }
+
+ MCONTACT m_hContact;
+ int m_msgid;
+ char m_guid[50];
+};
+
+struct IcqConn
+{
+ HNETLIBCONN s;
+ int lastTs, timeout;
+};
+
+struct IcqFileTransfer : public MZeroedObject
+{
+ // create an object for receiving
+ IcqFileTransfer(MCONTACT hContact, const char *pszUrl) :
+ m_szHost(pszUrl)
+ {
+ pfts.hContact = hContact;
+ pfts.totalFiles = 1;
+ pfts.flags = PFTS_UNICODE | PFTS_RECEIVING;
+
+ ptrW pwszFileName(mir_utf8decodeW(pszUrl));
+ if (pwszFileName == nullptr)
+ pwszFileName = mir_a2u(pszUrl);
+
+ const wchar_t *p = wcsrchr(pwszFileName, '/');
+ m_wszFileName = (p == nullptr) ? pwszFileName : p + 1;
+ m_wszShortName = m_wszFileName;
+ }
+
+ // create an object for sending
+ IcqFileTransfer(MCONTACT hContact, const wchar_t *pwszFileName) :
+ m_wszFileName(pwszFileName)
+ {
+ pfts.flags = PFTS_UNICODE | PFTS_SENDING;
+ pfts.hContact = hContact;
+ pfts.szCurrentFile.w = m_wszFileName.GetBuffer();
+
+ const wchar_t *p = wcsrchr(pfts.szCurrentFile.w, '\\');
+ if (pwszFileName != nullptr)
+ p++;
+ else
+ p = pfts.szCurrentFile.w;
+ m_wszShortName = p;
+ }
+
+ ~IcqFileTransfer()
+ {
+ if (m_fileId >= 0)
+ _close(m_fileId);
+ }
+
+ bool m_bCanceled = false, m_bStarted = false;
+ int m_fileId = -1;
+ CMStringA m_szHost;
+ CMStringW m_wszFileName, m_wszDescr;
+ const wchar_t *m_wszShortName;
+ PROTOFILETRANSFERSTATUS pfts;
+ HANDLE hWaitEvent;
+
+ void FillHeaders(AsyncHttpRequest *pReq)
+ {
+ pReq->AddHeader("Content-Type", "application/octet-stream");
+ pReq->AddHeader("Content-Disposition", CMStringA(FORMAT, "attachment; filename=\"%s\"", T2Utf(m_wszShortName).get()));
+
+ uint32_t dwPortion = pfts.currentFileSize - pfts.currentFileProgress;
+ if (dwPortion > 1000000)
+ dwPortion = 1000000;
+
+ pReq->AddHeader("Content-Range", CMStringA(FORMAT, "bytes %lld-%lld/%lld", pfts.currentFileProgress, pfts.currentFileProgress + dwPortion - 1, pfts.currentFileSize));
+ pReq->AddHeader("Content-Length", CMStringA(FORMAT, "%d", dwPortion));
+
+ pReq->dataLength = dwPortion;
+ pReq->pData = (char*)mir_alloc(dwPortion);
+ _lseek(m_fileId, pfts.currentFileProgress, SEEK_SET);
+ _read(m_fileId, pReq->pData, dwPortion);
+
+ pfts.currentFileProgress += dwPortion;
+ pfts.totalProgress += dwPortion;
+ }
+};
+
+class CIcqProto : public PROTO<CIcqProto>
+{
+ friend struct AsyncRapiRequest;
+
+ class CIcqProtoImpl
+ {
+ friend class CIcqProto;
+
+ CIcqProto &m_proto;
+ CTimer m_heartBeat, m_markRead;
+
+ void OnHeartBeat(CTimer *) {
+ m_proto.CheckStatus();
+ }
+
+ void OnMarkRead(CTimer *pTimer) {
+ m_proto.SendMarkRead();
+ pTimer->Stop();
+ }
+
+ CIcqProtoImpl(CIcqProto &pro) :
+ m_proto(pro),
+ m_markRead(Miranda_GetSystemWindow(), UINT_PTR(this)),
+ m_heartBeat(Miranda_GetSystemWindow(), UINT_PTR(this) + 1)
+ {
+ m_markRead.OnEvent = Callback(this, &CIcqProtoImpl::OnMarkRead);
+ m_heartBeat.OnEvent = Callback(this, &CIcqProtoImpl::OnHeartBeat);
+ }
+ } m_impl;
+
+ friend struct CIcqRegistrationDlg;
+ friend class CGroupchatInviteDlg;
+ friend class CEditIgnoreListDlg;
+ friend class CIcqEnterLoginDlg;
+ friend class CIcqOptionsDlg;
+ friend class CGroupEditDlg;
+
+ friend AsyncHttpRequest* operator <<(AsyncHttpRequest*, const AIMSID&);
+
+ bool m_bOnline, m_bTerminated, m_bFirstBos, m_isMra, m_bError462;
+ int m_iTimeShift;
+
+ MCONTACT CheckOwnMessage(const CMStringA &reqId, const CMStringA &msgId, bool bRemove);
+ void CheckPassword(void);
+ void ConnectionFailed(int iReason, int iErrorCode = 0);
+ void EmailNotification(const wchar_t *pwszText);
+ void GetPermitDeny();
+ wchar_t* GetUIN(MCONTACT hContact);
+ void MarkAsRead(MCONTACT hContact);
+ void MoveContactToGroup(MCONTACT hContact, const wchar_t *pwszGroup, const wchar_t *pwszNewGroup);
+ bool RetrievePassword();
+ void RetrieveUserHistory(MCONTACT, __int64 startMsgId, bool bCreateRead);
+ void RetrieveUserInfo(MCONTACT hContact);
+ void SendMrimLogin(NETLIBHTTPREQUEST *pReply);
+ void SetServerStatus(int iNewStatus);
+ void ShutdownSession(void);
+ void StartSession(void);
+
+ void CheckAvatarChange(MCONTACT hContact, const JSONNode&);
+ IcqFileInfo* CheckFile(MCONTACT hContact, CMStringW &wszFileName, bool &bIsFile);
+ void CheckLastId(MCONTACT hContact, const JSONNode&);
+ void Json2int(MCONTACT, const JSONNode&, const char *szJson, const char *szSetting, bool bIsPartial);
+ void Json2string(MCONTACT, const JSONNode&, const char *szJson, const char *szSetting, bool bIsPartial);
+ MCONTACT ParseBuddyInfo(const JSONNode &buddy, MCONTACT hContact = INVALID_CONTACT_ID, bool bIsPartial = false);
+ void ParseMessage(MCONTACT hContact, __int64 &lastMsgId, const JSONNode &msg, bool bCreateRead, bool bLocalTime);
+ int StatusFromPresence(const JSONNode &presence, MCONTACT hContact);
+
+ void OnLoggedIn(void);
+ void OnLoggedOut(void);
+
+ mir_cs m_csMarkReadQueue;
+ LIST<IcqCacheItem> m_arMarkReadQueue;
+ void SendMarkRead();
+
+ __int64 getId(MCONTACT hContact, const char *szSetting);
+ void setId(MCONTACT hContact, const char *szSetting, __int64 iValue);
+
+ void OnAddBuddy(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnAddClient(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnCheckMraAuth(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnCheckMraAuthFinal(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnCheckMrimLogin(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnCheckPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnFetchEvents(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnFileContinue(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnFileInit(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGenToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGetChatInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGetPermitDeny(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGetSticker(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGetUserHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnGetUserInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnLoginViaPhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnNormalizePhone(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnSearchResults(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnSendMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnSessionEnd(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+ void OnValidateSms(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq);
+
+ void ProcessBuddyList(const JSONNode &pRoot);
+ void ProcessDiff(const JSONNode &pRoot);
+ void ProcessEvent(const JSONNode &pRoot);
+ void ProcessGroupChat(const JSONNode &pRoot);
+ void ProcessHistData(const JSONNode &pRoot);
+ void ProcessImState(const JSONNode &pRoot);
+ void ProcessMyInfo(const JSONNode &pRoot);
+ void ProcessNotification(const JSONNode &pRoot);
+ void ProcessPermissions(const JSONNode &pRoot);
+ void ProcessPresence(const JSONNode &pRoot);
+ void ProcessSessionEnd(const JSONNode &pRoot);
+ void ProcessTyping(const JSONNode &pRoot);
+
+ IcqConn m_ConnPool[CONN_LAST];
+ CMStringA m_szPassword;
+ CMStringA m_szSessionKey;
+ CMStringA m_szAToken;
+ CMStringA m_szRToken;
+ CMStringA m_fetchBaseURL;
+ CMStringA m_aimsid;
+ CMStringA m_szMraCookie;
+ LONG m_msgId = 1;
+ int m_iRClientId;
+ HGENMENU m_hUploadGroups;
+
+ mir_cs m_csOwnIds;
+ OBJLIST<IcqOwnMessage> m_arOwnIds;
+
+ OBJLIST<IcqGroup> m_arGroups;
+
+ int m_unreadEmails = -1;
+ CMStringA m_szMailBox;
+
+ bool m_bIgnoreListEmpty = true;
+ bool m_bRememberPwd; // store password in a database
+ bool m_bDlgActive;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // group chats
+
+ int __cdecl GroupchatEventHook(WPARAM, LPARAM);
+ int __cdecl GroupchatMenuHook(WPARAM, LPARAM);
+
+ void Chat_ProcessLogMenu(SESSION_INFO *si, int);
+ void Chat_SendPrivateMessage(GCHOOK *gch);
+
+ void InviteUserToChat(SESSION_INFO *si);
+ void LeaveDestroyChat(SESSION_INFO *si);
+ void LoadChatInfo(SESSION_INFO *si);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // http queue
+
+ mir_cs m_csHttpQueue;
+ HANDLE m_evRequestsQueue;
+ LIST<AsyncHttpRequest> m_arHttpQueue;
+
+ void CalcHash(AsyncHttpRequest*);
+ void DropQueue();
+ bool ExecuteRequest(AsyncHttpRequest*);
+ bool IsQueueEmpty();
+ void Push(MHttpRequest*);
+ bool RefreshRobustToken(AsyncHttpRequest *pReq);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // cache
+
+ mir_cs m_csCache;
+ OBJLIST<IcqCacheItem> m_arCache;
+
+ void InitContactCache(void);
+ IcqCacheItem* FindContactByUIN(const CMStringW &pwszId);
+ MCONTACT CreateContact(const CMStringW &pwszId, bool bTemporary);
+
+ void GetAvatarFileName(MCONTACT hContact, wchar_t *pszDest, size_t cbLen);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // threads
+
+ HANDLE m_hWorkerThread;
+ void __cdecl ServerThread(void*);
+ void __cdecl PollThread(void*);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // services
+
+ INT_PTR __cdecl GetAvatar(WPARAM, LPARAM);
+ INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM);
+ INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM);
+ INT_PTR __cdecl SetAvatar(WPARAM, LPARAM);
+
+ INT_PTR __cdecl CreateAccMgrUI(WPARAM, LPARAM);
+ INT_PTR __cdecl EditGroups(WPARAM, LPARAM);
+ INT_PTR __cdecl EditProfile(WPARAM, LPARAM);
+ INT_PTR __cdecl GetEmailCount(WPARAM, LPARAM);
+ INT_PTR __cdecl GotoInbox(WPARAM, LPARAM);
+ INT_PTR __cdecl UploadGroups(WPARAM, LPARAM);
+
+ INT_PTR __cdecl OnMenuLoadHistory(WPARAM, LPARAM);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // events
+
+ int __cdecl OnGroupChange(WPARAM, LPARAM);
+ int __cdecl OnDbEventRead(WPARAM, LPARAM);
+ int __cdecl OnOptionsInit(WPARAM, LPARAM);
+ int __cdecl OnUserInfoInit(WPARAM, LPARAM);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO_INTERFACE
+
+ MCONTACT AddToList( int flags, PROTOSEARCHRESULT *psr) override;
+
+ int AuthRecv(MCONTACT, PROTORECVEVENT *pre) override;
+ int AuthRequest(MCONTACT hContact, const wchar_t *szMessage) override;
+
+ INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override;
+ int GetInfo(MCONTACT hContact, int infoType) override;
+
+ HANDLE SearchBasic(const wchar_t *id) override;
+
+ HANDLE FileAllow(MCONTACT hContact, HANDLE hTransfer, const wchar_t *szPath) override;
+ int FileCancel(MCONTACT hContact, HANDLE hTransfer) override;
+ int FileResume(HANDLE hTransfer, int action, const wchar_t *szFilename) override;
+
+ HANDLE SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) override;
+ int SendMsg(MCONTACT hContact, int flags, const char *msg) override;
+
+ int SetApparentMode(MCONTACT hContact, int mode) override;
+ int SetStatus(int iNewStatus) override;
+
+ int UserIsTyping(MCONTACT hContact, int type) override;
+
+ void OnBuildProtoMenu(void) override;
+ void OnContactAdded(MCONTACT) override;
+ void OnContactDeleted(MCONTACT) override;
+ void OnEventEdited(MCONTACT, MEVENT) override;
+ void OnModulesLoaded() override;
+ void OnShutdown() override;
+
+public:
+ CIcqProto(const char*, const wchar_t*);
+ ~CIcqProto();
+
+ CMOption<wchar_t*> m_szOwnId; // our own aim id
+ CMOption<uint8_t> m_bHideGroupchats; // don't pop up group chat windows on startup
+ CMOption<uint8_t> m_bUseTrayIcon; // use tray icon notifications
+ CMOption<uint8_t> m_bErrorPopups; // display popups with errors
+ CMOption<uint8_t> m_bLaunchMailbox; // launch browser to view email
+ CMOption<uint32_t> m_iTimeDiff1; // set this status to m_iStatus1 after this interval of secs
+ CMOption<uint32_t> m_iStatus1;
+ CMOption<uint32_t> m_iTimeDiff2; // set this status to m_iStatus2 after this interval of secs
+ CMOption<uint32_t> m_iStatus2;
+
+ void CheckStatus(void);
+ CMStringW GetUserId(MCONTACT);
+
+ __forceinline int TS() const
+ { return time(0) - m_iTimeShift;
+ }
+
+ __forceinline const char *appId() const
+ { return (m_isMra) ? MRA_APP_ID : ICQ_APP_ID;
+ }
+
+ void SetPermitDeny(const CMStringW &userId, bool bAllow);
+};
+
+struct CMPlugin : public ACCPROTOPLUGIN<CIcqProto>
+{
+ CMPlugin();
+
+ int Load() override;
+};
+
+#endif
diff --git a/protocols/ICQ-WIM/src/server.cpp b/protocols/ICQ-WIM/src/server.cpp index 167aabfee9..114ad5ed99 100644 --- a/protocols/ICQ-WIM/src/server.cpp +++ b/protocols/ICQ-WIM/src/server.cpp @@ -1,1220 +1,1220 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -#pragma comment(lib, "libcrypto.lib") - -void CIcqProto::CheckAvatarChange(MCONTACT hContact, const JSONNode &ev) -{ - CMStringW wszIconId(ev["bigIconId"].as_mstring()); - if (wszIconId.IsEmpty()) - wszIconId = ev["iconId"].as_mstring(); - - if (!wszIconId.IsEmpty()) { - CMStringW oldIconID(getMStringW(hContact, "IconId")); - if (wszIconId == oldIconID) { - wchar_t wszFullName[MAX_PATH]; - GetAvatarFileName(hContact, wszFullName, _countof(wszFullName)); - if (_waccess(wszFullName, 0) == 0) - return; - } - - setWString(hContact, "IconId", wszIconId); - - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/expressions/get", &CIcqProto::OnReceiveAvatar); - pReq << CHAR_PARAM("f", "native") << WCHAR_PARAM("t", GetUserId(hContact)) << CHAR_PARAM("type", "bigBuddyIcon"); - pReq->hContact = hContact; - Push(pReq); - } - else delSetting(hContact, "IconId"); -} - -void CIcqProto::CheckLastId(MCONTACT hContact, const JSONNode &ev) -{ - __int64 msgId = _wtoi64(ev["histMsgId"].as_mstring()); - __int64 lastId = getId(hContact, DB_KEY_LASTMSGID); - if (msgId > lastId) - setId(hContact, DB_KEY_LASTMSGID, msgId); -} - -MCONTACT CIcqProto::CheckOwnMessage(const CMStringA &reqId, const CMStringA &msgId, bool bRemove) -{ - IcqOwnMessage *pOwn = nullptr; - { - mir_cslock lck(m_csOwnIds); - for (auto &it : m_arOwnIds) { - if (reqId == it->m_guid) { - pOwn = it; - break; - } - } - } - - if (pOwn == nullptr) - return 0; - - ProtoBroadcastAck(pOwn->m_hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)pOwn->m_msgid, (LPARAM)msgId.c_str()); - - MCONTACT ret = pOwn->m_hContact; - if (bRemove) { - mir_cslock lck(m_csOwnIds); - m_arOwnIds.remove(pOwn); - } - return ret; -} - -void CIcqProto::CheckPassword() -{ - char mirVer[100]; - Miranda_GetVersionText(mirVer, _countof(mirVer)); - - m_szAToken = getMStringA(DB_KEY_ATOKEN); - m_iRClientId = getDword(DB_KEY_RCLIENTID); - m_szSessionKey = getMStringA(DB_KEY_SESSIONKEY); - if (!m_szAToken.IsEmpty() && !m_szSessionKey.IsEmpty()) { - StartSession(); - return; - } - - if (m_isMra) { - m_bError462 = false; - SendMrimLogin(nullptr); - } - else { - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword); - pReq << CHAR_PARAM("clientName", "Miranda NG") << CHAR_PARAM("clientVersion", mirVer) << CHAR_PARAM("devId", appId()) - << CHAR_PARAM("f", "json") << CHAR_PARAM("tokenType", "longTerm") << WCHAR_PARAM("s", m_szOwnId) << CHAR_PARAM("pwd", m_szPassword); - #ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; - #endif - Push(pReq); - } -} - -IcqFileInfo* CIcqProto::CheckFile(MCONTACT hContact, CMStringW &wszText, bool &bIsFile) -{ - CMStringW wszUrl(wszText.Mid(26)); - int idx = wszUrl.Find(' '); - if (idx != -1) - wszUrl.Truncate(idx); - - bIsFile = false; - IcqFileInfo *pFileInfo = nullptr; - - // is it already downloaded sticker? - CMStringW wszLoadedPath(FORMAT, L"%s\\%S\\Stickers\\STK{%s}.png", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName, wszUrl.c_str()); - if (!_waccess(wszLoadedPath, 0)) { - pFileInfo = (IcqFileInfo *)this; - wszText.Format(L"STK{%s}", wszUrl.c_str()); - } - else { - // download file info - CMStringA szUrl(FORMAT, ICQ_FILE_SERVER "/info/%S/", wszUrl.c_str()); - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, szUrl, &CIcqProto::OnFileInfo); - pReq->hContact = hContact; - pReq->pUserInfo = &pFileInfo; - pReq << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("previews", "192,600,xlarge"); - if (!ExecuteRequest(pReq)) - return nullptr; - - // is it a sticker? - if (pFileInfo->bIsSticker) { - if (ServiceExists(MS_SMILEYADD_LOADCONTACTSMILEYS)) { - auto *pNew = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, pFileInfo->szUrl, &CIcqProto::OnGetSticker); - pNew->flags |= NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT; - pNew->pUserInfo = wszUrl.GetBuffer(); - pNew->AddHeader("Sec-Fetch-User", "?1"); - pNew->AddHeader("Sec-Fetch-Site", "cross-site"); - pNew->AddHeader("Sec-Fetch-Mode", "navigate"); - if (!ExecuteRequest(pNew)) - return nullptr; - - wszText.Format(L"STK{%s}", wszUrl.c_str()); - delete pFileInfo; - } - else wszText = TranslateT("SmileyAdd plugin required to support stickers"); - } - else bIsFile = true; - } - - return pFileInfo; -} - -void CIcqProto::CheckStatus() -{ - time_t now = time(0); - int diff1 = m_iTimeDiff1, diff2 = m_iTimeDiff2; - - for (auto &it : m_arCache) { - // this contact is really offline and is on the first timer - // if the first timer is expired, we clear it and look for the second status - if (diff1 && it->m_timer1 && now - it->m_timer1 > diff1) { - it->m_timer1 = 0; - - // if the second timer is set up, activate it - if (m_iTimeDiff2) { - setWord(it->m_hContact, "Status", m_iStatus2); - it->m_timer2 = now; - } - // if the second timer is not set, simply mark a contact as offline - else setWord(it->m_hContact, "Status", ID_STATUS_OFFLINE); - continue; - } - - // if the second timer is expired, set status to offline - if (diff2 && it->m_timer2 && now - it->m_timer2 > m_iTimeDiff2) { - it->m_timer2 = 0; - setWord(it->m_hContact, "Status", ID_STATUS_OFFLINE); - } - } -} - -void CIcqProto::ConnectionFailed(int iReason, int iErrorCode) -{ - debugLogA("ConnectionFailed -> reason %d", iReason); - - if (m_bErrorPopups) { - POPUPDATAW Popup = {}; - Popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true); - wcscpy_s(Popup.lpwzContactName, m_tszUserName); - switch (iReason) { - case LOGINERR_BADUSERID: - mir_snwprintf(Popup.lpwzText, TranslateT("You have not entered a login or password.\nConfigure this in Options -> Network -> ICQ and try again.")); - break; - case LOGINERR_WRONGPASSWORD: - mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nYour login or password was rejected (%d)."), iErrorCode); - break; - case LOGINERR_NONETWORK: - case LOGINERR_NOSERVER: - mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nThe server is temporarily unavailable (%d)."), iErrorCode); - break; - default: - mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nUnknown error during sign on: %d"), iErrorCode); - break; - } - PUAddPopupW(&Popup); - } - - ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, iReason); - ShutdownSession(); -} - -void CIcqProto::MoveContactToGroup(MCONTACT hContact, const wchar_t *pwszGroup, const wchar_t *pwszNewGroup) -{ - // otherwise we'll get a server error - if (!mir_wstrlen(pwszGroup)) - return; - - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/moveBuddy") << AIMSID(this) << WCHAR_PARAM("buddy", GetUserId(hContact)) - << GROUP_PARAM("group", pwszGroup); - if (mir_wstrlen(pwszNewGroup)) - pReq << GROUP_PARAM("newGroup", pwszNewGroup); - Push(pReq); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::OnLoggedIn() -{ - debugLogA("CIcqProto::OnLoggedIn"); - m_bOnline = true; - m_impl.m_heartBeat.Start(1000); - - for (auto &it : m_arCache) - it->m_timer1 = it->m_timer2 = 0; - - SetServerStatus(m_iDesiredStatus); - RetrieveUserInfo(0); - GetPermitDeny(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::OnLoggedOut() -{ - debugLogA("CIcqProto::OnLoggedOut"); - m_bOnline = false; - m_impl.m_heartBeat.Stop(); - - for (auto &it : m_arCache) - it->m_timer1 = it->m_timer2 = 0; - - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - - setAllContactStatuses(ID_STATUS_OFFLINE, false); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::MarkAsRead(MCONTACT hContact) -{ - if (!m_bOnline) - return; - - m_impl.m_markRead.Start(200); - - auto *pCache = FindContactByUIN(GetUserId(hContact)); - if (pCache) { - mir_cslock lck(m_csMarkReadQueue); - if (m_arMarkReadQueue.indexOf(pCache) == -1) - m_arMarkReadQueue.insert(pCache); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MCONTACT CIcqProto::ParseBuddyInfo(const JSONNode &buddy, MCONTACT hContact, bool bIsPartial) -{ - // user chat? - CMStringW wszId(buddy["aimId"].as_mstring()); - if (IsChat(wszId)) { - CMStringW wszChatId(buddy["aimId"].as_mstring()); - CMStringW wszChatName(buddy["friendly"].as_mstring()); - - auto *pContact = FindContactByUIN(wszId); - if (pContact && pContact->m_iApparentMode == ID_STATUS_OFFLINE) - return INVALID_CONTACT_ID; - - auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChatId, wszChatName); - if (si == nullptr) - return INVALID_CONTACT_ID; - - Chat_AddGroup(si, TranslateT("admin")); - Chat_AddGroup(si, TranslateT("member")); - Chat_Control(m_szModuleName, wszChatId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE); - Chat_Control(m_szModuleName, wszChatId, SESSION_ONLINE); - return si->hContact; - } - - bool bIgnored = !IsValidType(buddy); - if (hContact == INVALID_CONTACT_ID) { - if (bIgnored) - return INVALID_CONTACT_ID; - - hContact = CreateContact(wszId, false); - FindContactByUIN(wszId)->m_bInList = true; - } - else if (bIgnored) { - db_delete_contact(hContact); - return INVALID_CONTACT_ID; - } - - CMStringA szVer; - bool bVersionDetected = false, bSecureIM = false; - - for (auto &it : buddy["capabilities"]) { - CMStringW wszCap(it.as_mstring()); - if (wszCap.GetLength() != 32) - continue; - - uint8_t cap[16]; - hex2binW(wszCap, cap, sizeof(cap)); - if (!memcmp(cap, "MiNG", 4)) { // Miranda - int v[4]; - if (4 == swscanf(wszCap.c_str() + 16, L"%04x%04x%04x%04x", &v[0], &v[1], &v[2], &v[3])) { - szVer.Format("Miranda NG %d.%d.%d.%d (ICQ %d.%d.%d.%d)", v[0], v[1], v[2], v[3], cap[4], cap[5], cap[6], cap[7]); - setString(hContact, "MirVer", szVer); - bVersionDetected = true; - } - } - else if (wszCap == _A2W(NG_CAP_SECUREIM)) { - bSecureIM = bVersionDetected = true; - } - else if (!memcmp(cap, "Mod by Mikanoshi", 16)) { - szVer = "R&Q build by Mikanoshi"; - bVersionDetected = true; - } - else if (!memcmp(cap, "Mandarin IM", 11)) { - szVer = "Mandarin IM"; - bVersionDetected = true; - } - } - - if (bVersionDetected) { - if (bSecureIM) - szVer.Append(" + SecureIM"); - setString(hContact, "MirVer", szVer); - } - else delSetting(hContact, "MirVer"); - - const JSONNode &var = buddy["friendly"]; - if (var) - setWString(hContact, "Nick", var.as_mstring()); - - if (buddy["deleted"].as_bool()) { - setByte(hContact, "IcqDeleted", 1); - Contact::PutOnList(hContact); - } - - Json2string(hContact, buddy, "emailId", "Email", bIsPartial); - Json2string(hContact, buddy, "cellNumber", "Cellular", bIsPartial); - Json2string(hContact, buddy, "workNumber", "CompanyPhone", bIsPartial); - - // we shall not remove existing phone number anyhow - Json2string(hContact, buddy, "phoneNumber", DB_KEY_PHONE, true); - - Json2int(hContact, buddy, "official", "Official", bIsPartial); - Json2int(hContact, buddy, "onlineTime", DB_KEY_ONLINETS, bIsPartial); - Json2int(hContact, buddy, "idleTime", "IdleTS", bIsPartial); - Json2int(hContact, buddy, "memberSince", DB_KEY_MEMBERSINCE, bIsPartial); - - int iStatus = StatusFromPresence(buddy, hContact); - if (iStatus > 0) - setWord(hContact, "Status", iStatus); - - const JSONNode &profile = buddy["profile"]; - if (profile) { - Json2string(hContact, profile, "friendlyName", DB_KEY_ICQNICK, bIsPartial); - Json2string(hContact, profile, "firstName", "FirstName", bIsPartial); - Json2string(hContact, profile, "lastName", "LastName", bIsPartial); - Json2string(hContact, profile, "aboutMe", DB_KEY_ABOUT, bIsPartial); - - ptrW wszNick(getWStringA(hContact, "Nick")); - if (!wszNick) { - CMStringW srvNick = profile["friendlyName"].as_mstring(); - if (!srvNick.IsEmpty()) - setWString(hContact, "Nick", srvNick); - } - - time_t birthDate = profile["birthDate"].as_int(); - if (birthDate != 0) { - struct tm *timeinfo = localtime(&birthDate); - if (timeinfo != nullptr) { - setWord(hContact, "BirthDay", timeinfo->tm_mday); - setWord(hContact, "BirthMonth", timeinfo->tm_mon+1); - setWord(hContact, "BirthYear", timeinfo->tm_year+1900); - } - } - - CMStringW str = profile["gender"].as_mstring(); - if (!str.IsEmpty()) { - if (str == "male") - setByte(hContact, "Gender", 'M'); - else if (str == "female") - setByte(hContact, "Gender", 'F'); - } - - for (auto &it : profile["homeAddress"]) { - Json2string(hContact, it, "city", "City", bIsPartial); - Json2string(hContact, it, "state", "State", bIsPartial); - Json2string(hContact, it, "country", "Country", bIsPartial); - } - } - - CMStringW str = buddy["statusMsg"].as_mstring(); - if (str.IsEmpty()) - db_unset(hContact, "CList", "StatusMsg"); - else - db_set_ws(hContact, "CList", "StatusMsg", str); - - CheckAvatarChange(hContact, buddy); - return hContact; -} - -void CIcqProto::ParseMessage(MCONTACT hContact, __int64 &lastMsgId, const JSONNode &it, bool bCreateRead, bool bLocalTime) -{ - CMStringA szMsgId(it["msgId"].as_mstring()); - __int64 msgId = _atoi64(szMsgId); - if (msgId > lastMsgId) - lastMsgId = msgId; - - CMStringW wszText; - const JSONNode &sticker = it["sticker"]; - if (sticker) { - CMStringW wszUrl, wszSticker(sticker["id"].as_mstring()); - int iCollectionId, iStickerId; - if (2 == swscanf(wszSticker, L"ext:%d:sticker:%d", &iCollectionId, &iStickerId)) - wszUrl.Format(L"https://c.icq.com/store/stickers/%d/%d/medium", iCollectionId, iStickerId); - else - wszUrl = TranslateT("Unknown sticker"); - wszText.Format(L"%s\n%s", TranslateT("User sent a sticker:"), wszUrl.c_str()); - } - else { - wszText = it["text"].as_mstring(); - wszText.TrimRight(); - - // user added you - if (it["class"].as_mstring() == L"event" && it["eventTypeId"].as_mstring() == L"27:33000") { - if (bLocalTime) { - CMStringA id = getMStringA(hContact, DB_KEY_ID); - int pos = id.Find('@'); - CMStringA nick = (pos == -1) ? id : id.Left(pos); - DB::AUTH_BLOB blob(hContact, nick, nullptr, nullptr, id, nullptr); - - PROTORECVEVENT pre = {}; - pre.timestamp = (uint32_t)time(0); - pre.lParam = blob.size(); - pre.szMessage = blob; - ProtoChainRecv(hContact, PSR_AUTH, 0, (LPARAM)&pre); - } - return; - } - } - - int iMsgTime = (bLocalTime) ? time(0) : it["time"].as_int(); - bool bIsOutgoing = it["outgoing"].as_bool(), bIsFileTransfer = false; - IcqFileInfo *pFileInfo = nullptr; - - if (!bCreateRead && !bIsOutgoing && wszText.Left(26) == L"https://files.icq.net/get/") { - pFileInfo = CheckFile(hContact, wszText, bIsFileTransfer); - if (!pFileInfo) - return; - - for (auto &jt : it["parts"]) { - CMStringW wszDescr(jt["captionedContent"]["caption"].as_mstring()); - if (!wszDescr.IsEmpty()) - pFileInfo->wszDescr = wszDescr; - } - } - - if (isChatRoom(hContact)) { - CMStringA reqId(it["reqId"].as_mstring()); - CheckOwnMessage(reqId, szMsgId, true); - - CMStringW wszSender(it["chat"]["sender"].as_mstring()); - CMStringW wszChatId(getMStringW(hContact, "ChatRoomID")); - - if (bIsFileTransfer) { - wszText = pFileInfo->szUrl; - if (!pFileInfo->wszDescr) - wszText.AppendFormat(L"\r\n%s", pFileInfo->wszDescr.c_str()); - delete pFileInfo; - } - - GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE }; - gce.pszID.w = wszChatId; - gce.dwFlags = GCEF_ADDTOLOG; - gce.pszUID.w = wszSender; - gce.pszText.w = wszText; - gce.time = iMsgTime; - gce.bIsMe = wszSender == m_szOwnId; - Chat_Event(&gce); - return; - } - - // skip own messages, just set the server msgid - CMStringA reqId(it["reqId"].as_mstring()); - if (CheckOwnMessage(reqId, szMsgId, true)) { - debugLogA("Skipping our own message %s", szMsgId.c_str()); - return; - } - - // ignore duplicates - MEVENT hDbEvent = db_event_getById(m_szModuleName, szMsgId); - if (hDbEvent != 0) { - debugLogA("Message %s already exists", szMsgId.c_str()); - return; - } - - // convert a file info into Miranda's file transfer - if (bIsFileTransfer) { - auto *ft = new IcqFileTransfer(hContact, pFileInfo->szUrl); - ft->pfts.totalBytes = ft->pfts.currentFileSize = pFileInfo->dwFileSize; - ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer(); - - PROTORECVFILE pre = {}; - pre.dwFlags = PRFF_UNICODE; - pre.fileCount = 1; - pre.timestamp = iMsgTime; - pre.files.w = &ft->m_wszShortName; - pre.descr.w = pFileInfo->wszDescr; - pre.lParam = (LPARAM)ft; - ProtoChainRecvFile(hContact, &pre); - - delete pFileInfo; - return; - } - - // suppress notifications for already loaded/processed messages - __int64 storedLastId = getId(hContact, DB_KEY_LASTMSGID); - if (msgId <= storedLastId) { - debugLogA("Parsing old/processed message with id %lld < %lld, setting CR to true", msgId, storedLastId); - bCreateRead = true; - } - - debugLogA("Adding message %d:%lld (CR=%d)", hContact, msgId, bCreateRead); - - ptrA szUtf(mir_utf8encodeW(wszText)); - - PROTORECVEVENT pre = {}; - if (bIsOutgoing) pre.flags |= PREF_SENT; - if (bCreateRead) pre.flags |= PREF_CREATEREAD; - pre.szMsgId = szMsgId; - pre.timestamp = iMsgTime; - pre.szMessage = szUtf; - ProtoChainRecvMsg(hContact, &pre); -} - -bool CIcqProto::RefreshRobustToken(AsyncHttpRequest *pOrigReq) -{ - if (!m_szRToken.IsEmpty()) - return true; - - auto *pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER "/genToken", &CIcqProto::OnGenToken); - #ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; - #endif - - int ts = TS(); - pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("nonce", CMStringA(FORMAT, "%d-%d", ts, rand() % 10)) << INT_PARAM("ts", ts); - CalcHash(pReq); - - CMStringA szAgent(FORMAT, "%S Mail.ru Windows ICQ (version 10.0.1999)", (wchar_t*)m_szOwnId); - pReq->AddHeader("User-Agent", szAgent); - if (!ExecuteRequest(pReq)) { -LBL_Error: - (this->*(pOrigReq->m_pFunc))(nullptr, pOrigReq); - return false; - } - if (m_szRToken.IsEmpty()) - goto LBL_Error; - - // now add this token - bool bRet = false; - pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER "/addClient", &CIcqProto::OnAddClient); - #ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; - #endif - pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("f", "json") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", ts) - << CHAR_PARAM("client", "icq") << CHAR_PARAM("reqId", pReq->m_reqId) << CHAR_PARAM("authToken", m_szRToken); - pReq->pUserInfo = &bRet; - if (!ExecuteRequest(pReq)) - goto LBL_Error; - - return bRet; -} - -void CIcqProto::RetrieveUserInfo(MCONTACT hContact) -{ - auto *pReq = new AsyncRapiRequest(this, "getUserInfo", &CIcqProto::OnGetUserInfo); - pReq->params << WCHAR_PARAM("sn", GetUserId(hContact)); - pReq->hContact = hContact; - Push(pReq); -} - -void CIcqProto::RetrieveUserHistory(MCONTACT hContact, __int64 startMsgId, bool bCreateRead) -{ - if (startMsgId == 0) - startMsgId = -1; - - __int64 patchVer = getId(hContact, DB_KEY_PATCHVER); - if (patchVer == 0) - patchVer = 1; - - auto *pReq = new AsyncRapiRequest(this, "getHistory", &CIcqProto::OnGetUserHistory); - #ifndef _DEBUG - pReq->flags |= NLHRF_NODUMPSEND; - #endif - pReq->hContact = hContact; - pReq->pUserInfo = (bCreateRead) ? pReq : 0; - pReq->params << WCHAR_PARAM("sn", GetUserId(hContact)) << INT64_PARAM("fromMsgId", startMsgId) << INT_PARAM("count", 1000) - << SINT64_PARAM("patchVersion", patchVer) << CHAR_PARAM("language", "ru-ru"); - Push(pReq); -} - -void CIcqProto::SetServerStatus(int iStatus) -{ - const char *szStatus = "online"; - int invisible = 0; - - switch (iStatus) { - case ID_STATUS_OFFLINE: szStatus = "offline"; break; - case ID_STATUS_NA: szStatus = "occupied"; break; - case ID_STATUS_AWAY: - case ID_STATUS_DND: szStatus = "away"; break; - case ID_STATUS_INVISIBLE: - invisible = 1; - } - - Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/presence/setState") - << AIMSID(this) << CHAR_PARAM("view", szStatus) << INT_PARAM("invisible", invisible)); - - if (iStatus == ID_STATUS_OFFLINE && !getByte(DB_KEY_PHONEREG)) { - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, ICQ_API_SERVER "/aim/endSession", &CIcqProto::OnSessionEnd); - pReq << AIMSID(this) << INT_PARAM("invalidateToken", 1); - ExecuteRequest(pReq); - } - - int iOldStatus = m_iStatus; m_iStatus = iStatus; - ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus); -} - -void CIcqProto::ShutdownSession() -{ - if (m_bTerminated) - return; - - debugLogA("CIcqProto::ShutdownSession"); - - // shutdown all resources - DropQueue(); - - if (m_hWorkerThread) - SetEvent(m_evRequestsQueue); - - OnLoggedOut(); - - for (auto &it : m_ConnPool) { - if (it.s) { - Netlib_Shutdown(it.s); - it.s = nullptr; - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define EVENTS "myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps" -#define FIELDS "aimId,buddyIcon,bigBuddyIcon,iconId,bigIconId,largeIconId,displayId,friendly,offlineMsg,state,statusMsg,userType,phoneNumber,cellNumber,smsNumber,workNumber,otherNumber,capabilities,ssl,abPhoneNumber,moodIcon,lastName,abPhones,abContactName,lastseen,mute,livechat,official" - -void CIcqProto::StartSession() -{ - ptrA szDeviceId(getStringA("DeviceId")); - if (szDeviceId == nullptr) { - UUID deviceId; - UuidCreate(&deviceId); - RPC_CSTR szId; - UuidToStringA(&deviceId, &szId); - szDeviceId = mir_strdup((char*)szId); - setString("DeviceId", szDeviceId); - RpcStringFreeA(&szId); - } - - int ts = TS(); - CMStringA nonce(FORMAT, "%d-2", ts); - CMStringA caps(WIM_CAP_UNIQ_REQ_ID "," WIM_CAP_EMOJI "," WIM_CAP_MAIL_NOTIFICATIONS "," WIM_CAP_INTRO_DLG_STATE); - if (g_bSecureIM) { - caps.AppendChar(','); - caps.Append(NG_CAP_SECUREIM); - } - - MFileVersion v; - Miranda_GetFileVersion(&v); - caps.AppendFormat(",%02x%02x%02x%02x%02x%02x%02x%02x%04x%04x%04x%04x", 'M', 'i', 'N', 'G', - __MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM, v[0], v[1], v[2], v[3]); - - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/aim/startSession", &CIcqProto::OnStartSession); - pReq << CHAR_PARAM("a", m_szAToken) << INT_PARAM("activeTimeout", 180) << CHAR_PARAM("assertCaps", caps) - << INT_PARAM("buildNumber", __BUILD_NUM) << CHAR_PARAM("deviceId", szDeviceId) << CHAR_PARAM("events", EVENTS) - << CHAR_PARAM("f", "json") << CHAR_PARAM("imf", "plain") << CHAR_PARAM("inactiveView", "offline") - << CHAR_PARAM("includePresenceFields", FIELDS) << CHAR_PARAM("invisible", "false") - << CHAR_PARAM("k", appId()) << INT_PARAM("mobile", 0) << CHAR_PARAM("nonce", nonce) << CHAR_PARAM("r", pReq->m_reqId) - << INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online"); - - CalcHash(pReq); - Push(pReq); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::OnAddBuddy(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - JsonReply root(pReply); - if (root.error() != 200) - return; - - CMStringW wszId = getMStringW(pReq->hContact, DB_KEY_ID); - for (auto &it : root.data()["results"]) { - if (it["buddy"].as_mstring() != wszId) - continue; - - switch (int iResultCode = it["resultCode"].as_int()) { - case 0: // success - case 3: // already in contact list - break; - - default: - debugLogA("Contact %d failed to add: error %d", pReq->hContact, iResultCode); - - POPUPDATAW Popup = {}; - Popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR)); - wcsncpy_s(Popup.lpwzText, TranslateT("Buddy addition failed"), _TRUNCATE); - wcsncpy_s(Popup.lpwzContactName, Clist_GetContactDisplayName(pReq->hContact), _TRUNCATE); - Popup.iSeconds = 20; - PUAddPopupW(&Popup); - - // Contact::RemoveFromList(pReq->hContact); - } - - RetrieveUserInfo(pReq->hContact); - Contact::PutOnList(pReq->hContact); - } -} - -void CIcqProto::OnAddClient(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - bool *pRet = (bool*)pReq->pUserInfo; - - RobustReply reply(pReply); - if (reply.error() != 20000) { - *pRet = false; - return; - } - - const JSONNode &results = reply.results(); - m_iRClientId = results["clientId"].as_int(); - setDword(DB_KEY_RCLIENTID, m_iRClientId); - *pRet = true; -} - -void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - JsonReply root(pReply); - switch (root.error()) { - case 200: - break; - - case 330: - case 440: - ConnectionFailed(LOGINERR_WRONGPASSWORD, root.error()); - return; - - default: - ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error()); - return; - } - - JSONNode &data = root.data(); - m_szAToken = data["token"]["a"].as_mstring(); - mir_urlDecode(m_szAToken.GetBuffer()); - setString(DB_KEY_ATOKEN, m_szAToken); - - CMStringA szSessionSecret = data["sessionSecret"].as_mstring(); - CMStringA szPassTemp = m_szPassword; - - unsigned int len; - uint8_t hashOut[MIR_SHA256_HASH_SIZE]; - HMAC(EVP_sha256(), szPassTemp, szPassTemp.GetLength(), (uint8_t*)szSessionSecret.c_str(), szSessionSecret.GetLength(), hashOut, &len); - m_szSessionKey = ptrA(mir_base64_encode(hashOut, sizeof(hashOut))); - setString(DB_KEY_SESSIONKEY, m_szSessionKey); - - CMStringW szUin = data["loginId"].as_mstring(); - if (szUin) - m_szOwnId = szUin; - - int srvTS = data["hostTime"].as_int(); - m_iTimeShift = (srvTS) ? time(0) - srvTS : 0; - - StartSession(); -} - -void CIcqProto::OnFileContinue(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld) -{ - IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo; - if (pTransfer->m_bCanceled) { -LBL_Error: - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer); - delete pTransfer; - return; - } - - switch (pReply->resultCode) { - case 200: // final ok - case 206: // partial ok - break; - - default: - goto LBL_Error; - } - - // file transfer succeeded? - if (pTransfer->pfts.currentFileProgress == pTransfer->pfts.currentFileSize) { - FileReply root(pReply); - if (root.error() != 200) - goto LBL_Error; - - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, pTransfer); - - const JSONNode &data = root.data(); - CMStringW wszUrl(data["static_url"].as_mstring()); - - JSONNode bundle, contents; contents.set_name("captionedContent"); - contents << WCHAR_PARAM("caption", pTransfer->m_wszDescr) << WCHAR_PARAM("url", wszUrl); - bundle << CHAR_PARAM("mediaType", "text") << CHAR_PARAM("text", "") << contents; - CMStringW wszParts(FORMAT, L"[%s]", ptrW(json_write(&bundle)).get()); - - if (!pTransfer->m_wszDescr.IsEmpty()) - wszUrl += L" " + pTransfer->m_wszDescr; - - int id = InterlockedIncrement(&m_msgId); - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/im/sendIM", &CIcqProto::OnSendMessage); - - auto *pOwn = new IcqOwnMessage(pTransfer->pfts.hContact, id, pReq->m_reqId); - pReq->pUserInfo = pOwn; - { - mir_cslock lck(m_csOwnIds); - m_arOwnIds.insert(pOwn); - } - - pReq << AIMSID(this) << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("mentions", "") << WCHAR_PARAM("message", wszUrl) - << CHAR_PARAM("offlineIM", "true") << WCHAR_PARAM("parts", wszParts) << WCHAR_PARAM("t", GetUserId(pTransfer->pfts.hContact)) << INT_PARAM("ts", TS()); - Push(pReq); - - delete pTransfer; - return; - } - - // else send the next portion - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue); - pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", TS()); - CalcHash(pReq); - pReq->m_szUrl.AppendChar('?'); - pReq->m_szUrl += pReq->m_szParam; pReq->m_szParam.Empty(); - pReq->pUserInfo = pTransfer; - pTransfer->FillHeaders(pReq); - Push(pReq); - - pTransfer->pfts.currentFileTime = time(0); - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts); -} - -void CIcqProto::OnFileInit(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld) -{ - IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo; - if (pTransfer->m_bCanceled) { -LBL_Error: - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer); - delete pTransfer; - return; - } - - FileReply root(pReply); - if (root.error() != 200) - goto LBL_Error; - - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, pTransfer); - pTransfer->pfts.currentFileTime = time(0); - - const JSONNode &data = root.data(); - CMStringW wszHost(data["host"].as_mstring()); - CMStringW wszUrl(data["url"].as_mstring()); - pTransfer->m_szHost = L"https://" + wszHost + wszUrl; - - auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue); - pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", TS()); - CalcHash(pReq); - pReq->m_szUrl.AppendChar('?'); - pReq->m_szUrl += pReq->m_szParam; pReq->m_szParam.Empty(); - pReq->pUserInfo = pTransfer; - pTransfer->FillHeaders(pReq); - Push(pReq); - - ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Support for stickers - -void CIcqProto::OnGetSticker(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - if (pReply->resultCode != 200) { - debugLogA("Error getting sticker: %d", pReply->resultCode); - return; - } - - CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName); - CreateDirectoryTreeW(wszPath); - - CMStringW wszFileName(FORMAT, L"%s\\STK{%s}.png", wszPath.c_str(), pReq->pUserInfo); - FILE *out = _wfopen(wszFileName, L"wb"); - fwrite(pReply->pData, 1, pReply->dataLength, out); - fclose(out); - - SMADD_CONT cont = { 1, m_szModuleName, wszFileName }; - CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont)); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// File info request - -void CIcqProto::OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - IcqFileInfo **res = (IcqFileInfo **)pReq->pUserInfo; - *res = nullptr; - - RobustReply root(pReply); - if (root.error() != 200) - return; - - auto &pData = root.result(); - auto &pInfo = pData["info"] ; - std::string szUrl(pInfo["dlink"].as_string()); - if (szUrl.empty()) - return; - - MarkAsRead(pReq->hContact); - - bool bIsSticker; - CMStringW wszDescr(pInfo["file_name"].as_mstring()); - if (!mir_wstrncmp(wszDescr, L"dnld", 4)) { - bIsSticker = true; - - std::string szPreview = pData["previews"]["192"].as_string(); - if (!szPreview.empty()) - szUrl = szPreview; - } - else bIsSticker = false; - - mir_urlDecode(&*szUrl.begin()); - - *res = new IcqFileInfo(szUrl, wszDescr, pInfo["file_size"].as_int()); - res[0]->bIsSticker = bIsSticker; -} - -void CIcqProto::OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - auto *ft = (IcqFileTransfer*)pReq->pUserInfo; - - if (pReply->resultCode != 200) { -LBL_Error: - FileCancel(pReq->hContact, ft); - return; - } - - ft->hWaitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&ft->pfts)) - WaitForSingleObject(ft->hWaitEvent, INFINITE); - CloseHandle(ft->hWaitEvent); - - debugLogW(L"Saving to [%s]", ft->pfts.szCurrentFile.w); - int fileId = _wopen(ft->pfts.szCurrentFile.w, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); - if (fileId == -1) { - debugLogW(L"Cannot open [%s] for writing", ft->pfts.szCurrentFile.w); - goto LBL_Error; - } - - int result = _write(fileId, pReply->pData, pReply->dataLength); - _close(fileId); - if (result != pReply->dataLength) { - debugLogW(L"Error writing data into [%s]", ft->pfts.szCurrentFile.w); - goto LBL_Error; - } - - ft->pfts.totalProgress += pReply->dataLength; - ft->pfts.currentFileProgress += pReply->dataLength; - ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts); - - ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft); - delete ft; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::OnGenToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) -{ - RobustReply root(pReply); - if (root.error() != 20000) - return; - - auto &results = root.results(); - m_szRToken = results["authToken"].as_mstring(); -} - -void CIcqProto::OnGetUserHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - RobustReply root(pReply); - if (root.error() != 20000) - return; - - __int64 lastMsgId = getId(pReq->hContact, DB_KEY_LASTMSGID); - - int count = 0; - auto &results = root.results(); - for (auto &it : results["messages"]) { - ParseMessage(pReq->hContact, lastMsgId, it, pReq->pUserInfo != nullptr, false); - count++; - } - - setId(pReq->hContact, DB_KEY_LASTMSGID, lastMsgId); - - if (count >= 999) - RetrieveUserHistory(pReq->hContact, lastMsgId, pReq->pUserInfo != nullptr); -} - -void CIcqProto::OnGetUserInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - RobustReply root(pReply); - if (root.error() != 20000) { - ProtoBroadcastAck(pReq->hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, nullptr); - return; - } - - ParseBuddyInfo(root.results(), pReq->hContact, true); - - ProtoBroadcastAck(pReq->hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, nullptr); -} - -void CIcqProto::OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - JsonReply root(pReply); - switch (root.error()) { - case 200: - break; - - case 451: - // session forcibly closed from site - delSetting(DB_KEY_ATOKEN); - delSetting(DB_KEY_SESSIONKEY); - CheckPassword(); - return; - - case 401: - delSetting(DB_KEY_ATOKEN); - delSetting(DB_KEY_SESSIONKEY); - if (root.detail() == 1002) // session expired - CheckPassword(); - else - ConnectionFailed(LOGINERR_WRONGPASSWORD, root.error()); - return; - - case 400: - if (root.detail() == 1015 && m_iTimeShift == 0) { // wrong timestamp - JSONNode &data = root.data(); - int srvTS = data["ts"].as_int(); - m_iTimeShift = (srvTS) ? time(0) - srvTS : 0; - StartSession(); - return; - } - __fallthrough; - - default: - ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error()); - return; - } - - JSONNode &data = root.data(); - m_fetchBaseURL = data["fetchBaseURL"].as_mstring(); - m_aimsid = data["aimsid"].as_mstring(); - - ProcessMyInfo(data["myInfo"]); - - int srvTS = data["ts"].as_int(); - m_iTimeShift = (srvTS) ? time(0) - srvTS : 0; - - OnLoggedIn(); - - for (auto &it : data["events"]) - ProcessEvent(it); - - ForkThread(&CIcqProto::PollThread); -} - -void CIcqProto::OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - PROTO_AVATAR_INFORMATION ai = {}; - ai.hContact = pReq->hContact; - - if (pReply->resultCode != 200 || pReply->pData == nullptr) { -LBL_Error: - ProtoBroadcastAck(pReq->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai), 0); - return; - } - - const char *szContentType = Netlib_GetHeader(pReply, "Content-Type"); - if (szContentType == nullptr) - szContentType = "image/jpeg"; - - ai.format = ProtoGetAvatarFormatByMimeType(szContentType); - setByte(pReq->hContact, "AvatarType", ai.format); - GetAvatarFileName(pReq->hContact, ai.filename, _countof(ai.filename)); - - FILE *out = _wfopen(ai.filename, L"wb"); - if (out == nullptr) - goto LBL_Error; - - fwrite(pReply->pData, pReply->dataLength, 1, out); - fclose(out); - - if (pReq->hContact != 0) { - ProtoBroadcastAck(pReq->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai), 0); - debugLogW(L"Broadcast new avatar: %s", ai.filename); - } - else ReportSelfAvatarChanged(); -} - -void CIcqProto::OnSearchResults(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - RobustReply root(pReply); - if (root.error() != 20000) { - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)pReq, 0); - return; - } - - const JSONNode &results = root.results(); - - PROTOSEARCHRESULT psr = {}; - psr.cbSize = sizeof(psr); - psr.flags = PSR_UNICODE; - for (auto &it : results["persons"]) { - CMStringW wszId = it["sn"].as_mstring(); - if (wszId == m_szOwnId) - continue; - - CMStringW wszNick = it["friendly"].as_mstring(); - CMStringW wszFirst = it["firstName"].as_mstring(); - CMStringW wszLast = it["lastName"].as_mstring(); - - psr.id.w = wszId.GetBuffer(); - psr.nick.w = wszNick.GetBuffer(); - psr.firstName.w = wszFirst.GetBuffer(); - psr.lastName.w = wszLast.GetBuffer(); - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)pReq, LPARAM(&psr)); - } - - ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pReq); -} - -void CIcqProto::OnSendMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) -{ - IcqOwnMessage *ownMsg = (IcqOwnMessage*)pReq->pUserInfo; - - JsonReply root(pReply); - if (root.error() != 200) { - ProtoBroadcastAck(ownMsg->m_hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)ownMsg->m_msgid, 0); - - mir_cslock lck(m_csOwnIds); - m_arOwnIds.remove(ownMsg); - } - - if (g_bMessageState) - CallService(MS_MESSAGESTATE_UPDATE, ownMsg->m_hContact, MRD_TYPE_DELIVERED); - - const JSONNode &data = root.data(); - CMStringA reqId(root.requestId()); - CMStringA msgId(data["histMsgId"].as_mstring()); - CheckOwnMessage(reqId, msgId, false); - CheckLastId(ownMsg->m_hContact, data); -} - -void CIcqProto::OnSessionEnd(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *) -{ - JsonReply root(pReply); - if (root.error() == 200) { - m_szAToken.Empty(); - delSetting(DB_KEY_ATOKEN); - - m_szSessionKey.Empty(); - delSetting(DB_KEY_SESSIONKEY); - - ShutdownSession(); - } -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+#pragma comment(lib, "libcrypto.lib")
+
+void CIcqProto::CheckAvatarChange(MCONTACT hContact, const JSONNode &ev)
+{
+ CMStringW wszIconId(ev["bigIconId"].as_mstring());
+ if (wszIconId.IsEmpty())
+ wszIconId = ev["iconId"].as_mstring();
+
+ if (!wszIconId.IsEmpty()) {
+ CMStringW oldIconID(getMStringW(hContact, "IconId"));
+ if (wszIconId == oldIconID) {
+ wchar_t wszFullName[MAX_PATH];
+ GetAvatarFileName(hContact, wszFullName, _countof(wszFullName));
+ if (_waccess(wszFullName, 0) == 0)
+ return;
+ }
+
+ setWString(hContact, "IconId", wszIconId);
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/expressions/get", &CIcqProto::OnReceiveAvatar);
+ pReq << CHAR_PARAM("f", "native") << WCHAR_PARAM("t", GetUserId(hContact)) << CHAR_PARAM("type", "bigBuddyIcon");
+ pReq->hContact = hContact;
+ Push(pReq);
+ }
+ else delSetting(hContact, "IconId");
+}
+
+void CIcqProto::CheckLastId(MCONTACT hContact, const JSONNode &ev)
+{
+ __int64 msgId = _wtoi64(ev["histMsgId"].as_mstring());
+ __int64 lastId = getId(hContact, DB_KEY_LASTMSGID);
+ if (msgId > lastId)
+ setId(hContact, DB_KEY_LASTMSGID, msgId);
+}
+
+MCONTACT CIcqProto::CheckOwnMessage(const CMStringA &reqId, const CMStringA &msgId, bool bRemove)
+{
+ IcqOwnMessage *pOwn = nullptr;
+ {
+ mir_cslock lck(m_csOwnIds);
+ for (auto &it : m_arOwnIds) {
+ if (reqId == it->m_guid) {
+ pOwn = it;
+ break;
+ }
+ }
+ }
+
+ if (pOwn == nullptr)
+ return 0;
+
+ ProtoBroadcastAck(pOwn->m_hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)pOwn->m_msgid, (LPARAM)msgId.c_str());
+
+ MCONTACT ret = pOwn->m_hContact;
+ if (bRemove) {
+ mir_cslock lck(m_csOwnIds);
+ m_arOwnIds.remove(pOwn);
+ }
+ return ret;
+}
+
+void CIcqProto::CheckPassword()
+{
+ char mirVer[100];
+ Miranda_GetVersionText(mirVer, _countof(mirVer));
+
+ m_szAToken = getMStringA(DB_KEY_ATOKEN);
+ m_iRClientId = getDword(DB_KEY_RCLIENTID);
+ m_szSessionKey = getMStringA(DB_KEY_SESSIONKEY);
+ if (!m_szAToken.IsEmpty() && !m_szSessionKey.IsEmpty()) {
+ StartSession();
+ return;
+ }
+
+ if (m_isMra) {
+ m_bError462 = false;
+ SendMrimLogin(nullptr);
+ }
+ else {
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword);
+ pReq << CHAR_PARAM("clientName", "Miranda NG") << CHAR_PARAM("clientVersion", mirVer) << CHAR_PARAM("devId", appId())
+ << CHAR_PARAM("f", "json") << CHAR_PARAM("tokenType", "longTerm") << WCHAR_PARAM("s", m_szOwnId) << CHAR_PARAM("pwd", m_szPassword);
+ #ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+ #endif
+ Push(pReq);
+ }
+}
+
+IcqFileInfo* CIcqProto::CheckFile(MCONTACT hContact, CMStringW &wszText, bool &bIsFile)
+{
+ CMStringW wszUrl(wszText.Mid(26));
+ int idx = wszUrl.Find(' ');
+ if (idx != -1)
+ wszUrl.Truncate(idx);
+
+ bIsFile = false;
+ IcqFileInfo *pFileInfo = nullptr;
+
+ // is it already downloaded sticker?
+ CMStringW wszLoadedPath(FORMAT, L"%s\\%S\\Stickers\\STK{%s}.png", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName, wszUrl.c_str());
+ if (!_waccess(wszLoadedPath, 0)) {
+ pFileInfo = (IcqFileInfo *)this;
+ wszText.Format(L"STK{%s}", wszUrl.c_str());
+ }
+ else {
+ // download file info
+ CMStringA szUrl(FORMAT, ICQ_FILE_SERVER "/info/%S/", wszUrl.c_str());
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, szUrl, &CIcqProto::OnFileInfo);
+ pReq->hContact = hContact;
+ pReq->pUserInfo = &pFileInfo;
+ pReq << CHAR_PARAM("aimsid", m_aimsid) << CHAR_PARAM("previews", "192,600,xlarge");
+ if (!ExecuteRequest(pReq))
+ return nullptr;
+
+ // is it a sticker?
+ if (pFileInfo->bIsSticker) {
+ if (ServiceExists(MS_SMILEYADD_LOADCONTACTSMILEYS)) {
+ auto *pNew = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, pFileInfo->szUrl, &CIcqProto::OnGetSticker);
+ pNew->flags |= NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ pNew->pUserInfo = wszUrl.GetBuffer();
+ pNew->AddHeader("Sec-Fetch-User", "?1");
+ pNew->AddHeader("Sec-Fetch-Site", "cross-site");
+ pNew->AddHeader("Sec-Fetch-Mode", "navigate");
+ if (!ExecuteRequest(pNew))
+ return nullptr;
+
+ wszText.Format(L"STK{%s}", wszUrl.c_str());
+ delete pFileInfo;
+ }
+ else wszText = TranslateT("SmileyAdd plugin required to support stickers");
+ }
+ else bIsFile = true;
+ }
+
+ return pFileInfo;
+}
+
+void CIcqProto::CheckStatus()
+{
+ time_t now = time(0);
+ int diff1 = m_iTimeDiff1, diff2 = m_iTimeDiff2;
+
+ for (auto &it : m_arCache) {
+ // this contact is really offline and is on the first timer
+ // if the first timer is expired, we clear it and look for the second status
+ if (diff1 && it->m_timer1 && now - it->m_timer1 > diff1) {
+ it->m_timer1 = 0;
+
+ // if the second timer is set up, activate it
+ if (m_iTimeDiff2) {
+ setWord(it->m_hContact, "Status", m_iStatus2);
+ it->m_timer2 = now;
+ }
+ // if the second timer is not set, simply mark a contact as offline
+ else setWord(it->m_hContact, "Status", ID_STATUS_OFFLINE);
+ continue;
+ }
+
+ // if the second timer is expired, set status to offline
+ if (diff2 && it->m_timer2 && now - it->m_timer2 > m_iTimeDiff2) {
+ it->m_timer2 = 0;
+ setWord(it->m_hContact, "Status", ID_STATUS_OFFLINE);
+ }
+ }
+}
+
+void CIcqProto::ConnectionFailed(int iReason, int iErrorCode)
+{
+ debugLogA("ConnectionFailed -> reason %d", iReason);
+
+ if (m_bErrorPopups) {
+ POPUPDATAW Popup = {};
+ Popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true);
+ wcscpy_s(Popup.lpwzContactName, m_tszUserName);
+ switch (iReason) {
+ case LOGINERR_BADUSERID:
+ mir_snwprintf(Popup.lpwzText, TranslateT("You have not entered a login or password.\nConfigure this in Options -> Network -> ICQ and try again."));
+ break;
+ case LOGINERR_WRONGPASSWORD:
+ mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nYour login or password was rejected (%d)."), iErrorCode);
+ break;
+ case LOGINERR_NONETWORK:
+ case LOGINERR_NOSERVER:
+ mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nThe server is temporarily unavailable (%d)."), iErrorCode);
+ break;
+ default:
+ mir_snwprintf(Popup.lpwzText, TranslateT("Connection failed.\nUnknown error during sign on: %d"), iErrorCode);
+ break;
+ }
+ PUAddPopupW(&Popup);
+ }
+
+ ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, nullptr, iReason);
+ ShutdownSession();
+}
+
+void CIcqProto::MoveContactToGroup(MCONTACT hContact, const wchar_t *pwszGroup, const wchar_t *pwszNewGroup)
+{
+ // otherwise we'll get a server error
+ if (!mir_wstrlen(pwszGroup))
+ return;
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/buddylist/moveBuddy") << AIMSID(this) << WCHAR_PARAM("buddy", GetUserId(hContact))
+ << GROUP_PARAM("group", pwszGroup);
+ if (mir_wstrlen(pwszNewGroup))
+ pReq << GROUP_PARAM("newGroup", pwszNewGroup);
+ Push(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnLoggedIn()
+{
+ debugLogA("CIcqProto::OnLoggedIn");
+ m_bOnline = true;
+ m_impl.m_heartBeat.Start(1000);
+
+ for (auto &it : m_arCache)
+ it->m_timer1 = it->m_timer2 = 0;
+
+ SetServerStatus(m_iDesiredStatus);
+ RetrieveUserInfo(0);
+ GetPermitDeny();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnLoggedOut()
+{
+ debugLogA("CIcqProto::OnLoggedOut");
+ m_bOnline = false;
+ m_impl.m_heartBeat.Stop();
+
+ for (auto &it : m_arCache)
+ it->m_timer1 = it->m_timer2 = 0;
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::MarkAsRead(MCONTACT hContact)
+{
+ if (!m_bOnline)
+ return;
+
+ m_impl.m_markRead.Start(200);
+
+ auto *pCache = FindContactByUIN(GetUserId(hContact));
+ if (pCache) {
+ mir_cslock lck(m_csMarkReadQueue);
+ if (m_arMarkReadQueue.indexOf(pCache) == -1)
+ m_arMarkReadQueue.insert(pCache);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MCONTACT CIcqProto::ParseBuddyInfo(const JSONNode &buddy, MCONTACT hContact, bool bIsPartial)
+{
+ // user chat?
+ CMStringW wszId(buddy["aimId"].as_mstring());
+ if (IsChat(wszId)) {
+ CMStringW wszChatId(buddy["aimId"].as_mstring());
+ CMStringW wszChatName(buddy["friendly"].as_mstring());
+
+ auto *pContact = FindContactByUIN(wszId);
+ if (pContact && pContact->m_iApparentMode == ID_STATUS_OFFLINE)
+ return INVALID_CONTACT_ID;
+
+ auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, wszChatId, wszChatName);
+ if (si == nullptr)
+ return INVALID_CONTACT_ID;
+
+ Chat_AddGroup(si, TranslateT("admin"));
+ Chat_AddGroup(si, TranslateT("member"));
+ Chat_Control(m_szModuleName, wszChatId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
+ Chat_Control(m_szModuleName, wszChatId, SESSION_ONLINE);
+ return si->hContact;
+ }
+
+ bool bIgnored = !IsValidType(buddy);
+ if (hContact == INVALID_CONTACT_ID) {
+ if (bIgnored)
+ return INVALID_CONTACT_ID;
+
+ hContact = CreateContact(wszId, false);
+ FindContactByUIN(wszId)->m_bInList = true;
+ }
+ else if (bIgnored) {
+ db_delete_contact(hContact);
+ return INVALID_CONTACT_ID;
+ }
+
+ CMStringA szVer;
+ bool bVersionDetected = false, bSecureIM = false;
+
+ for (auto &it : buddy["capabilities"]) {
+ CMStringW wszCap(it.as_mstring());
+ if (wszCap.GetLength() != 32)
+ continue;
+
+ uint8_t cap[16];
+ hex2binW(wszCap, cap, sizeof(cap));
+ if (!memcmp(cap, "MiNG", 4)) { // Miranda
+ int v[4];
+ if (4 == swscanf(wszCap.c_str() + 16, L"%04x%04x%04x%04x", &v[0], &v[1], &v[2], &v[3])) {
+ szVer.Format("Miranda NG %d.%d.%d.%d (ICQ %d.%d.%d.%d)", v[0], v[1], v[2], v[3], cap[4], cap[5], cap[6], cap[7]);
+ setString(hContact, "MirVer", szVer);
+ bVersionDetected = true;
+ }
+ }
+ else if (wszCap == _A2W(NG_CAP_SECUREIM)) {
+ bSecureIM = bVersionDetected = true;
+ }
+ else if (!memcmp(cap, "Mod by Mikanoshi", 16)) {
+ szVer = "R&Q build by Mikanoshi";
+ bVersionDetected = true;
+ }
+ else if (!memcmp(cap, "Mandarin IM", 11)) {
+ szVer = "Mandarin IM";
+ bVersionDetected = true;
+ }
+ }
+
+ if (bVersionDetected) {
+ if (bSecureIM)
+ szVer.Append(" + SecureIM");
+ setString(hContact, "MirVer", szVer);
+ }
+ else delSetting(hContact, "MirVer");
+
+ const JSONNode &var = buddy["friendly"];
+ if (var)
+ setWString(hContact, "Nick", var.as_mstring());
+
+ if (buddy["deleted"].as_bool()) {
+ setByte(hContact, "IcqDeleted", 1);
+ Contact::PutOnList(hContact);
+ }
+
+ Json2string(hContact, buddy, "emailId", "Email", bIsPartial);
+ Json2string(hContact, buddy, "cellNumber", "Cellular", bIsPartial);
+ Json2string(hContact, buddy, "workNumber", "CompanyPhone", bIsPartial);
+
+ // we shall not remove existing phone number anyhow
+ Json2string(hContact, buddy, "phoneNumber", DB_KEY_PHONE, true);
+
+ Json2int(hContact, buddy, "official", "Official", bIsPartial);
+ Json2int(hContact, buddy, "onlineTime", DB_KEY_ONLINETS, bIsPartial);
+ Json2int(hContact, buddy, "idleTime", "IdleTS", bIsPartial);
+ Json2int(hContact, buddy, "memberSince", DB_KEY_MEMBERSINCE, bIsPartial);
+
+ int iStatus = StatusFromPresence(buddy, hContact);
+ if (iStatus > 0)
+ setWord(hContact, "Status", iStatus);
+
+ const JSONNode &profile = buddy["profile"];
+ if (profile) {
+ Json2string(hContact, profile, "friendlyName", DB_KEY_ICQNICK, bIsPartial);
+ Json2string(hContact, profile, "firstName", "FirstName", bIsPartial);
+ Json2string(hContact, profile, "lastName", "LastName", bIsPartial);
+ Json2string(hContact, profile, "aboutMe", DB_KEY_ABOUT, bIsPartial);
+
+ ptrW wszNick(getWStringA(hContact, "Nick"));
+ if (!wszNick) {
+ CMStringW srvNick = profile["friendlyName"].as_mstring();
+ if (!srvNick.IsEmpty())
+ setWString(hContact, "Nick", srvNick);
+ }
+
+ time_t birthDate = profile["birthDate"].as_int();
+ if (birthDate != 0) {
+ struct tm *timeinfo = localtime(&birthDate);
+ if (timeinfo != nullptr) {
+ setWord(hContact, "BirthDay", timeinfo->tm_mday);
+ setWord(hContact, "BirthMonth", timeinfo->tm_mon+1);
+ setWord(hContact, "BirthYear", timeinfo->tm_year+1900);
+ }
+ }
+
+ CMStringW str = profile["gender"].as_mstring();
+ if (!str.IsEmpty()) {
+ if (str == "male")
+ setByte(hContact, "Gender", 'M');
+ else if (str == "female")
+ setByte(hContact, "Gender", 'F');
+ }
+
+ for (auto &it : profile["homeAddress"]) {
+ Json2string(hContact, it, "city", "City", bIsPartial);
+ Json2string(hContact, it, "state", "State", bIsPartial);
+ Json2string(hContact, it, "country", "Country", bIsPartial);
+ }
+ }
+
+ CMStringW str = buddy["statusMsg"].as_mstring();
+ if (str.IsEmpty())
+ db_unset(hContact, "CList", "StatusMsg");
+ else
+ db_set_ws(hContact, "CList", "StatusMsg", str);
+
+ CheckAvatarChange(hContact, buddy);
+ return hContact;
+}
+
+void CIcqProto::ParseMessage(MCONTACT hContact, __int64 &lastMsgId, const JSONNode &it, bool bCreateRead, bool bLocalTime)
+{
+ CMStringA szMsgId(it["msgId"].as_mstring());
+ __int64 msgId = _atoi64(szMsgId);
+ if (msgId > lastMsgId)
+ lastMsgId = msgId;
+
+ CMStringW wszText;
+ const JSONNode &sticker = it["sticker"];
+ if (sticker) {
+ CMStringW wszUrl, wszSticker(sticker["id"].as_mstring());
+ int iCollectionId, iStickerId;
+ if (2 == swscanf(wszSticker, L"ext:%d:sticker:%d", &iCollectionId, &iStickerId))
+ wszUrl.Format(L"https://c.icq.com/store/stickers/%d/%d/medium", iCollectionId, iStickerId);
+ else
+ wszUrl = TranslateT("Unknown sticker");
+ wszText.Format(L"%s\n%s", TranslateT("User sent a sticker:"), wszUrl.c_str());
+ }
+ else {
+ wszText = it["text"].as_mstring();
+ wszText.TrimRight();
+
+ // user added you
+ if (it["class"].as_mstring() == L"event" && it["eventTypeId"].as_mstring() == L"27:33000") {
+ if (bLocalTime) {
+ CMStringA id = getMStringA(hContact, DB_KEY_ID);
+ int pos = id.Find('@');
+ CMStringA nick = (pos == -1) ? id : id.Left(pos);
+ DB::AUTH_BLOB blob(hContact, nick, nullptr, nullptr, id, nullptr);
+
+ PROTORECVEVENT pre = {};
+ pre.timestamp = (uint32_t)time(0);
+ pre.lParam = blob.size();
+ pre.szMessage = blob;
+ ProtoChainRecv(hContact, PSR_AUTH, 0, (LPARAM)&pre);
+ }
+ return;
+ }
+ }
+
+ int iMsgTime = (bLocalTime) ? time(0) : it["time"].as_int();
+ bool bIsOutgoing = it["outgoing"].as_bool(), bIsFileTransfer = false;
+ IcqFileInfo *pFileInfo = nullptr;
+
+ if (!bCreateRead && !bIsOutgoing && wszText.Left(26) == L"https://files.icq.net/get/") {
+ pFileInfo = CheckFile(hContact, wszText, bIsFileTransfer);
+ if (!pFileInfo)
+ return;
+
+ for (auto &jt : it["parts"]) {
+ CMStringW wszDescr(jt["captionedContent"]["caption"].as_mstring());
+ if (!wszDescr.IsEmpty())
+ pFileInfo->wszDescr = wszDescr;
+ }
+ }
+
+ if (isChatRoom(hContact)) {
+ CMStringA reqId(it["reqId"].as_mstring());
+ CheckOwnMessage(reqId, szMsgId, true);
+
+ CMStringW wszSender(it["chat"]["sender"].as_mstring());
+ CMStringW wszChatId(getMStringW(hContact, "ChatRoomID"));
+
+ if (bIsFileTransfer) {
+ wszText = pFileInfo->szUrl;
+ if (!pFileInfo->wszDescr)
+ wszText.AppendFormat(L"\r\n%s", pFileInfo->wszDescr.c_str());
+ delete pFileInfo;
+ }
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE };
+ gce.pszID.w = wszChatId;
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszUID.w = wszSender;
+ gce.pszText.w = wszText;
+ gce.time = iMsgTime;
+ gce.bIsMe = wszSender == m_szOwnId;
+ Chat_Event(&gce);
+ return;
+ }
+
+ // skip own messages, just set the server msgid
+ CMStringA reqId(it["reqId"].as_mstring());
+ if (CheckOwnMessage(reqId, szMsgId, true)) {
+ debugLogA("Skipping our own message %s", szMsgId.c_str());
+ return;
+ }
+
+ // ignore duplicates
+ MEVENT hDbEvent = db_event_getById(m_szModuleName, szMsgId);
+ if (hDbEvent != 0) {
+ debugLogA("Message %s already exists", szMsgId.c_str());
+ return;
+ }
+
+ // convert a file info into Miranda's file transfer
+ if (bIsFileTransfer) {
+ auto *ft = new IcqFileTransfer(hContact, pFileInfo->szUrl);
+ ft->pfts.totalBytes = ft->pfts.currentFileSize = pFileInfo->dwFileSize;
+ ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
+
+ PROTORECVFILE pre = {};
+ pre.dwFlags = PRFF_UNICODE;
+ pre.fileCount = 1;
+ pre.timestamp = iMsgTime;
+ pre.files.w = &ft->m_wszShortName;
+ pre.descr.w = pFileInfo->wszDescr;
+ pre.lParam = (LPARAM)ft;
+ ProtoChainRecvFile(hContact, &pre);
+
+ delete pFileInfo;
+ return;
+ }
+
+ // suppress notifications for already loaded/processed messages
+ __int64 storedLastId = getId(hContact, DB_KEY_LASTMSGID);
+ if (msgId <= storedLastId) {
+ debugLogA("Parsing old/processed message with id %lld < %lld, setting CR to true", msgId, storedLastId);
+ bCreateRead = true;
+ }
+
+ debugLogA("Adding message %d:%lld (CR=%d)", hContact, msgId, bCreateRead);
+
+ ptrA szUtf(mir_utf8encodeW(wszText));
+
+ PROTORECVEVENT pre = {};
+ if (bIsOutgoing) pre.flags |= PREF_SENT;
+ if (bCreateRead) pre.flags |= PREF_CREATEREAD;
+ pre.szMsgId = szMsgId;
+ pre.timestamp = iMsgTime;
+ pre.szMessage = szUtf;
+ ProtoChainRecvMsg(hContact, &pre);
+}
+
+bool CIcqProto::RefreshRobustToken(AsyncHttpRequest *pOrigReq)
+{
+ if (!m_szRToken.IsEmpty())
+ return true;
+
+ auto *pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER "/genToken", &CIcqProto::OnGenToken);
+ #ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+ #endif
+
+ int ts = TS();
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("nonce", CMStringA(FORMAT, "%d-%d", ts, rand() % 10)) << INT_PARAM("ts", ts);
+ CalcHash(pReq);
+
+ CMStringA szAgent(FORMAT, "%S Mail.ru Windows ICQ (version 10.0.1999)", (wchar_t*)m_szOwnId);
+ pReq->AddHeader("User-Agent", szAgent);
+ if (!ExecuteRequest(pReq)) {
+LBL_Error:
+ (this->*(pOrigReq->m_pFunc))(nullptr, pOrigReq);
+ return false;
+ }
+ if (m_szRToken.IsEmpty())
+ goto LBL_Error;
+
+ // now add this token
+ bool bRet = false;
+ pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER "/addClient", &CIcqProto::OnAddClient);
+ #ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+ #endif
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("f", "json") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", ts)
+ << CHAR_PARAM("client", "icq") << CHAR_PARAM("reqId", pReq->m_reqId) << CHAR_PARAM("authToken", m_szRToken);
+ pReq->pUserInfo = &bRet;
+ if (!ExecuteRequest(pReq))
+ goto LBL_Error;
+
+ return bRet;
+}
+
+void CIcqProto::RetrieveUserInfo(MCONTACT hContact)
+{
+ auto *pReq = new AsyncRapiRequest(this, "getUserInfo", &CIcqProto::OnGetUserInfo);
+ pReq->params << WCHAR_PARAM("sn", GetUserId(hContact));
+ pReq->hContact = hContact;
+ Push(pReq);
+}
+
+void CIcqProto::RetrieveUserHistory(MCONTACT hContact, __int64 startMsgId, bool bCreateRead)
+{
+ if (startMsgId == 0)
+ startMsgId = -1;
+
+ __int64 patchVer = getId(hContact, DB_KEY_PATCHVER);
+ if (patchVer == 0)
+ patchVer = 1;
+
+ auto *pReq = new AsyncRapiRequest(this, "getHistory", &CIcqProto::OnGetUserHistory);
+ #ifndef _DEBUG
+ pReq->flags |= NLHRF_NODUMPSEND;
+ #endif
+ pReq->hContact = hContact;
+ pReq->pUserInfo = (bCreateRead) ? pReq : 0;
+ pReq->params << WCHAR_PARAM("sn", GetUserId(hContact)) << INT64_PARAM("fromMsgId", startMsgId) << INT_PARAM("count", 1000)
+ << SINT64_PARAM("patchVersion", patchVer) << CHAR_PARAM("language", "ru-ru");
+ Push(pReq);
+}
+
+void CIcqProto::SetServerStatus(int iStatus)
+{
+ const char *szStatus = "online";
+ int invisible = 0;
+
+ switch (iStatus) {
+ case ID_STATUS_OFFLINE: szStatus = "offline"; break;
+ case ID_STATUS_NA: szStatus = "occupied"; break;
+ case ID_STATUS_AWAY:
+ case ID_STATUS_DND: szStatus = "away"; break;
+ case ID_STATUS_INVISIBLE:
+ invisible = 1;
+ }
+
+ Push(new AsyncHttpRequest(CONN_MAIN, REQUEST_GET, ICQ_API_SERVER "/presence/setState")
+ << AIMSID(this) << CHAR_PARAM("view", szStatus) << INT_PARAM("invisible", invisible));
+
+ if (iStatus == ID_STATUS_OFFLINE && !getByte(DB_KEY_PHONEREG)) {
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, ICQ_API_SERVER "/aim/endSession", &CIcqProto::OnSessionEnd);
+ pReq << AIMSID(this) << INT_PARAM("invalidateToken", 1);
+ ExecuteRequest(pReq);
+ }
+
+ int iOldStatus = m_iStatus; m_iStatus = iStatus;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+}
+
+void CIcqProto::ShutdownSession()
+{
+ if (m_bTerminated)
+ return;
+
+ debugLogA("CIcqProto::ShutdownSession");
+
+ // shutdown all resources
+ DropQueue();
+
+ if (m_hWorkerThread)
+ SetEvent(m_evRequestsQueue);
+
+ OnLoggedOut();
+
+ for (auto &it : m_ConnPool) {
+ if (it.s) {
+ Netlib_Shutdown(it.s);
+ it.s = nullptr;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define EVENTS "myInfo,presence,buddylist,typing,dataIM,userAddedToBuddyList,mchat,hist,hiddenChat,diff,permitDeny,imState,notification,apps"
+#define FIELDS "aimId,buddyIcon,bigBuddyIcon,iconId,bigIconId,largeIconId,displayId,friendly,offlineMsg,state,statusMsg,userType,phoneNumber,cellNumber,smsNumber,workNumber,otherNumber,capabilities,ssl,abPhoneNumber,moodIcon,lastName,abPhones,abContactName,lastseen,mute,livechat,official"
+
+void CIcqProto::StartSession()
+{
+ ptrA szDeviceId(getStringA("DeviceId"));
+ if (szDeviceId == nullptr) {
+ UUID deviceId;
+ UuidCreate(&deviceId);
+ RPC_CSTR szId;
+ UuidToStringA(&deviceId, &szId);
+ szDeviceId = mir_strdup((char*)szId);
+ setString("DeviceId", szDeviceId);
+ RpcStringFreeA(&szId);
+ }
+
+ int ts = TS();
+ CMStringA nonce(FORMAT, "%d-2", ts);
+ CMStringA caps(WIM_CAP_UNIQ_REQ_ID "," WIM_CAP_EMOJI "," WIM_CAP_MAIL_NOTIFICATIONS "," WIM_CAP_INTRO_DLG_STATE);
+ if (g_bSecureIM) {
+ caps.AppendChar(',');
+ caps.Append(NG_CAP_SECUREIM);
+ }
+
+ MFileVersion v;
+ Miranda_GetFileVersion(&v);
+ caps.AppendFormat(",%02x%02x%02x%02x%02x%02x%02x%02x%04x%04x%04x%04x", 'M', 'i', 'N', 'G',
+ __MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM, v[0], v[1], v[2], v[3]);
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/aim/startSession", &CIcqProto::OnStartSession);
+ pReq << CHAR_PARAM("a", m_szAToken) << INT_PARAM("activeTimeout", 180) << CHAR_PARAM("assertCaps", caps)
+ << INT_PARAM("buildNumber", __BUILD_NUM) << CHAR_PARAM("deviceId", szDeviceId) << CHAR_PARAM("events", EVENTS)
+ << CHAR_PARAM("f", "json") << CHAR_PARAM("imf", "plain") << CHAR_PARAM("inactiveView", "offline")
+ << CHAR_PARAM("includePresenceFields", FIELDS) << CHAR_PARAM("invisible", "false")
+ << CHAR_PARAM("k", appId()) << INT_PARAM("mobile", 0) << CHAR_PARAM("nonce", nonce) << CHAR_PARAM("r", pReq->m_reqId)
+ << INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online");
+
+ CalcHash(pReq);
+ Push(pReq);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnAddBuddy(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ JsonReply root(pReply);
+ if (root.error() != 200)
+ return;
+
+ CMStringW wszId = getMStringW(pReq->hContact, DB_KEY_ID);
+ for (auto &it : root.data()["results"]) {
+ if (it["buddy"].as_mstring() != wszId)
+ continue;
+
+ switch (int iResultCode = it["resultCode"].as_int()) {
+ case 0: // success
+ case 3: // already in contact list
+ break;
+
+ default:
+ debugLogA("Contact %d failed to add: error %d", pReq->hContact, iResultCode);
+
+ POPUPDATAW Popup = {};
+ Popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR));
+ wcsncpy_s(Popup.lpwzText, TranslateT("Buddy addition failed"), _TRUNCATE);
+ wcsncpy_s(Popup.lpwzContactName, Clist_GetContactDisplayName(pReq->hContact), _TRUNCATE);
+ Popup.iSeconds = 20;
+ PUAddPopupW(&Popup);
+
+ // Contact::RemoveFromList(pReq->hContact);
+ }
+
+ RetrieveUserInfo(pReq->hContact);
+ Contact::PutOnList(pReq->hContact);
+ }
+}
+
+void CIcqProto::OnAddClient(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ bool *pRet = (bool*)pReq->pUserInfo;
+
+ RobustReply reply(pReply);
+ if (reply.error() != 20000) {
+ *pRet = false;
+ return;
+ }
+
+ const JSONNode &results = reply.results();
+ m_iRClientId = results["clientId"].as_int();
+ setDword(DB_KEY_RCLIENTID, m_iRClientId);
+ *pRet = true;
+}
+
+void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ JsonReply root(pReply);
+ switch (root.error()) {
+ case 200:
+ break;
+
+ case 330:
+ case 440:
+ ConnectionFailed(LOGINERR_WRONGPASSWORD, root.error());
+ return;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error());
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_szAToken = data["token"]["a"].as_mstring();
+ mir_urlDecode(m_szAToken.GetBuffer());
+ setString(DB_KEY_ATOKEN, m_szAToken);
+
+ CMStringA szSessionSecret = data["sessionSecret"].as_mstring();
+ CMStringA szPassTemp = m_szPassword;
+
+ unsigned int len;
+ uint8_t hashOut[MIR_SHA256_HASH_SIZE];
+ HMAC(EVP_sha256(), szPassTemp, szPassTemp.GetLength(), (uint8_t*)szSessionSecret.c_str(), szSessionSecret.GetLength(), hashOut, &len);
+ m_szSessionKey = ptrA(mir_base64_encode(hashOut, sizeof(hashOut)));
+ setString(DB_KEY_SESSIONKEY, m_szSessionKey);
+
+ CMStringW szUin = data["loginId"].as_mstring();
+ if (szUin)
+ m_szOwnId = szUin;
+
+ int srvTS = data["hostTime"].as_int();
+ m_iTimeShift = (srvTS) ? time(0) - srvTS : 0;
+
+ StartSession();
+}
+
+void CIcqProto::OnFileContinue(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld)
+{
+ IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo;
+ if (pTransfer->m_bCanceled) {
+LBL_Error:
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer);
+ delete pTransfer;
+ return;
+ }
+
+ switch (pReply->resultCode) {
+ case 200: // final ok
+ case 206: // partial ok
+ break;
+
+ default:
+ goto LBL_Error;
+ }
+
+ // file transfer succeeded?
+ if (pTransfer->pfts.currentFileProgress == pTransfer->pfts.currentFileSize) {
+ FileReply root(pReply);
+ if (root.error() != 200)
+ goto LBL_Error;
+
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, pTransfer);
+
+ const JSONNode &data = root.data();
+ CMStringW wszUrl(data["static_url"].as_mstring());
+
+ JSONNode bundle, contents; contents.set_name("captionedContent");
+ contents << WCHAR_PARAM("caption", pTransfer->m_wszDescr) << WCHAR_PARAM("url", wszUrl);
+ bundle << CHAR_PARAM("mediaType", "text") << CHAR_PARAM("text", "") << contents;
+ CMStringW wszParts(FORMAT, L"[%s]", ptrW(json_write(&bundle)).get());
+
+ if (!pTransfer->m_wszDescr.IsEmpty())
+ wszUrl += L" " + pTransfer->m_wszDescr;
+
+ int id = InterlockedIncrement(&m_msgId);
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/im/sendIM", &CIcqProto::OnSendMessage);
+
+ auto *pOwn = new IcqOwnMessage(pTransfer->pfts.hContact, id, pReq->m_reqId);
+ pReq->pUserInfo = pOwn;
+ {
+ mir_cslock lck(m_csOwnIds);
+ m_arOwnIds.insert(pOwn);
+ }
+
+ pReq << AIMSID(this) << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", appId()) << CHAR_PARAM("mentions", "") << WCHAR_PARAM("message", wszUrl)
+ << CHAR_PARAM("offlineIM", "true") << WCHAR_PARAM("parts", wszParts) << WCHAR_PARAM("t", GetUserId(pTransfer->pfts.hContact)) << INT_PARAM("ts", TS());
+ Push(pReq);
+
+ delete pTransfer;
+ return;
+ }
+
+ // else send the next portion
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue);
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", TS());
+ CalcHash(pReq);
+ pReq->m_szUrl.AppendChar('?');
+ pReq->m_szUrl += pReq->m_szParam; pReq->m_szParam.Empty();
+ pReq->pUserInfo = pTransfer;
+ pTransfer->FillHeaders(pReq);
+ Push(pReq);
+
+ pTransfer->pfts.currentFileTime = time(0);
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts);
+}
+
+void CIcqProto::OnFileInit(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld)
+{
+ IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo;
+ if (pTransfer->m_bCanceled) {
+LBL_Error:
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer);
+ delete pTransfer;
+ return;
+ }
+
+ FileReply root(pReply);
+ if (root.error() != 200)
+ goto LBL_Error;
+
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, pTransfer);
+ pTransfer->pfts.currentFileTime = time(0);
+
+ const JSONNode &data = root.data();
+ CMStringW wszHost(data["host"].as_mstring());
+ CMStringW wszUrl(data["url"].as_mstring());
+ pTransfer->m_szHost = L"https://" + wszHost + wszUrl;
+
+ auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue);
+ pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", appId()) << INT_PARAM("ts", TS());
+ CalcHash(pReq);
+ pReq->m_szUrl.AppendChar('?');
+ pReq->m_szUrl += pReq->m_szParam; pReq->m_szParam.Empty();
+ pReq->pUserInfo = pTransfer;
+ pTransfer->FillHeaders(pReq);
+ Push(pReq);
+
+ ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Support for stickers
+
+void CIcqProto::OnGetSticker(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ if (pReply->resultCode != 200) {
+ debugLogA("Error getting sticker: %d", pReply->resultCode);
+ return;
+ }
+
+ CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName);
+ CreateDirectoryTreeW(wszPath);
+
+ CMStringW wszFileName(FORMAT, L"%s\\STK{%s}.png", wszPath.c_str(), pReq->pUserInfo);
+ FILE *out = _wfopen(wszFileName, L"wb");
+ fwrite(pReply->pData, 1, pReply->dataLength, out);
+ fclose(out);
+
+ SMADD_CONT cont = { 1, m_szModuleName, wszFileName };
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// File info request
+
+void CIcqProto::OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ IcqFileInfo **res = (IcqFileInfo **)pReq->pUserInfo;
+ *res = nullptr;
+
+ RobustReply root(pReply);
+ if (root.error() != 200)
+ return;
+
+ auto &pData = root.result();
+ auto &pInfo = pData["info"] ;
+ std::string szUrl(pInfo["dlink"].as_string());
+ if (szUrl.empty())
+ return;
+
+ MarkAsRead(pReq->hContact);
+
+ bool bIsSticker;
+ CMStringW wszDescr(pInfo["file_name"].as_mstring());
+ if (!mir_wstrncmp(wszDescr, L"dnld", 4)) {
+ bIsSticker = true;
+
+ std::string szPreview = pData["previews"]["192"].as_string();
+ if (!szPreview.empty())
+ szUrl = szPreview;
+ }
+ else bIsSticker = false;
+
+ mir_urlDecode(&*szUrl.begin());
+
+ *res = new IcqFileInfo(szUrl, wszDescr, pInfo["file_size"].as_int());
+ res[0]->bIsSticker = bIsSticker;
+}
+
+void CIcqProto::OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ auto *ft = (IcqFileTransfer*)pReq->pUserInfo;
+
+ if (pReply->resultCode != 200) {
+LBL_Error:
+ FileCancel(pReq->hContact, ft);
+ return;
+ }
+
+ ft->hWaitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ if (ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&ft->pfts))
+ WaitForSingleObject(ft->hWaitEvent, INFINITE);
+ CloseHandle(ft->hWaitEvent);
+
+ debugLogW(L"Saving to [%s]", ft->pfts.szCurrentFile.w);
+ int fileId = _wopen(ft->pfts.szCurrentFile.w, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
+ if (fileId == -1) {
+ debugLogW(L"Cannot open [%s] for writing", ft->pfts.szCurrentFile.w);
+ goto LBL_Error;
+ }
+
+ int result = _write(fileId, pReply->pData, pReply->dataLength);
+ _close(fileId);
+ if (result != pReply->dataLength) {
+ debugLogW(L"Error writing data into [%s]", ft->pfts.szCurrentFile.w);
+ goto LBL_Error;
+ }
+
+ ft->pfts.totalProgress += pReply->dataLength;
+ ft->pfts.currentFileProgress += pReply->dataLength;
+ ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts);
+
+ ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft);
+ delete ft;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnGenToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
+{
+ RobustReply root(pReply);
+ if (root.error() != 20000)
+ return;
+
+ auto &results = root.results();
+ m_szRToken = results["authToken"].as_mstring();
+}
+
+void CIcqProto::OnGetUserHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ RobustReply root(pReply);
+ if (root.error() != 20000)
+ return;
+
+ __int64 lastMsgId = getId(pReq->hContact, DB_KEY_LASTMSGID);
+
+ int count = 0;
+ auto &results = root.results();
+ for (auto &it : results["messages"]) {
+ ParseMessage(pReq->hContact, lastMsgId, it, pReq->pUserInfo != nullptr, false);
+ count++;
+ }
+
+ setId(pReq->hContact, DB_KEY_LASTMSGID, lastMsgId);
+
+ if (count >= 999)
+ RetrieveUserHistory(pReq->hContact, lastMsgId, pReq->pUserInfo != nullptr);
+}
+
+void CIcqProto::OnGetUserInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ RobustReply root(pReply);
+ if (root.error() != 20000) {
+ ProtoBroadcastAck(pReq->hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, nullptr);
+ return;
+ }
+
+ ParseBuddyInfo(root.results(), pReq->hContact, true);
+
+ ProtoBroadcastAck(pReq->hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, nullptr);
+}
+
+void CIcqProto::OnStartSession(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *)
+{
+ JsonReply root(pReply);
+ switch (root.error()) {
+ case 200:
+ break;
+
+ case 451:
+ // session forcibly closed from site
+ delSetting(DB_KEY_ATOKEN);
+ delSetting(DB_KEY_SESSIONKEY);
+ CheckPassword();
+ return;
+
+ case 401:
+ delSetting(DB_KEY_ATOKEN);
+ delSetting(DB_KEY_SESSIONKEY);
+ if (root.detail() == 1002) // session expired
+ CheckPassword();
+ else
+ ConnectionFailed(LOGINERR_WRONGPASSWORD, root.error());
+ return;
+
+ case 400:
+ if (root.detail() == 1015 && m_iTimeShift == 0) { // wrong timestamp
+ JSONNode &data = root.data();
+ int srvTS = data["ts"].as_int();
+ m_iTimeShift = (srvTS) ? time(0) - srvTS : 0;
+ StartSession();
+ return;
+ }
+ __fallthrough;
+
+ default:
+ ConnectionFailed(LOGINERR_WRONGPROTOCOL, root.error());
+ return;
+ }
+
+ JSONNode &data = root.data();
+ m_fetchBaseURL = data["fetchBaseURL"].as_mstring();
+ m_aimsid = data["aimsid"].as_mstring();
+
+ ProcessMyInfo(data["myInfo"]);
+
+ int srvTS = data["ts"].as_int();
+ m_iTimeShift = (srvTS) ? time(0) - srvTS : 0;
+
+ OnLoggedIn();
+
+ for (auto &it : data["events"])
+ ProcessEvent(it);
+
+ ForkThread(&CIcqProto::PollThread);
+}
+
+void CIcqProto::OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ PROTO_AVATAR_INFORMATION ai = {};
+ ai.hContact = pReq->hContact;
+
+ if (pReply->resultCode != 200 || pReply->pData == nullptr) {
+LBL_Error:
+ ProtoBroadcastAck(pReq->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai), 0);
+ return;
+ }
+
+ const char *szContentType = Netlib_GetHeader(pReply, "Content-Type");
+ if (szContentType == nullptr)
+ szContentType = "image/jpeg";
+
+ ai.format = ProtoGetAvatarFormatByMimeType(szContentType);
+ setByte(pReq->hContact, "AvatarType", ai.format);
+ GetAvatarFileName(pReq->hContact, ai.filename, _countof(ai.filename));
+
+ FILE *out = _wfopen(ai.filename, L"wb");
+ if (out == nullptr)
+ goto LBL_Error;
+
+ fwrite(pReply->pData, pReply->dataLength, 1, out);
+ fclose(out);
+
+ if (pReq->hContact != 0) {
+ ProtoBroadcastAck(pReq->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, HANDLE(&ai), 0);
+ debugLogW(L"Broadcast new avatar: %s", ai.filename);
+ }
+ else ReportSelfAvatarChanged();
+}
+
+void CIcqProto::OnSearchResults(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ RobustReply root(pReply);
+ if (root.error() != 20000) {
+ ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)pReq, 0);
+ return;
+ }
+
+ const JSONNode &results = root.results();
+
+ PROTOSEARCHRESULT psr = {};
+ psr.cbSize = sizeof(psr);
+ psr.flags = PSR_UNICODE;
+ for (auto &it : results["persons"]) {
+ CMStringW wszId = it["sn"].as_mstring();
+ if (wszId == m_szOwnId)
+ continue;
+
+ CMStringW wszNick = it["friendly"].as_mstring();
+ CMStringW wszFirst = it["firstName"].as_mstring();
+ CMStringW wszLast = it["lastName"].as_mstring();
+
+ psr.id.w = wszId.GetBuffer();
+ psr.nick.w = wszNick.GetBuffer();
+ psr.firstName.w = wszFirst.GetBuffer();
+ psr.lastName.w = wszLast.GetBuffer();
+ ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)pReq, LPARAM(&psr));
+ }
+
+ ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pReq);
+}
+
+void CIcqProto::OnSendMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+ IcqOwnMessage *ownMsg = (IcqOwnMessage*)pReq->pUserInfo;
+
+ JsonReply root(pReply);
+ if (root.error() != 200) {
+ ProtoBroadcastAck(ownMsg->m_hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)ownMsg->m_msgid, 0);
+
+ mir_cslock lck(m_csOwnIds);
+ m_arOwnIds.remove(ownMsg);
+ }
+
+ if (g_bMessageState)
+ CallService(MS_MESSAGESTATE_UPDATE, ownMsg->m_hContact, MRD_TYPE_DELIVERED);
+
+ const JSONNode &data = root.data();
+ CMStringA reqId(root.requestId());
+ CMStringA msgId(data["histMsgId"].as_mstring());
+ CheckOwnMessage(reqId, msgId, false);
+ CheckLastId(ownMsg->m_hContact, data);
+}
+
+void CIcqProto::OnSessionEnd(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *)
+{
+ JsonReply root(pReply);
+ if (root.error() == 200) {
+ m_szAToken.Empty();
+ delSetting(DB_KEY_ATOKEN);
+
+ m_szSessionKey.Empty();
+ delSetting(DB_KEY_SESSIONKEY);
+
+ ShutdownSession();
+ }
+}
diff --git a/protocols/ICQ-WIM/src/stdafx.cxx b/protocols/ICQ-WIM/src/stdafx.cxx index d265a4c02e..8c570f6949 100644 --- a/protocols/ICQ-WIM/src/stdafx.cxx +++ b/protocols/ICQ-WIM/src/stdafx.cxx @@ -1,18 +1,18 @@ -/* -Copyright (C) 2012-22 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 <http://www.gnu.org/licenses/>. -*/ - -#include "stdafx.h" +/*
+Copyright (C) 2012-23 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
diff --git a/protocols/ICQ-WIM/src/stdafx.h b/protocols/ICQ-WIM/src/stdafx.h index 465a385f02..b45810a867 100644 --- a/protocols/ICQ-WIM/src/stdafx.h +++ b/protocols/ICQ-WIM/src/stdafx.h @@ -1,109 +1,109 @@ -// ---------------------------------------------------------------------------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 -// Copyright © 2012-2022 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: -// -// Includes all header files that should be precompiled to speed up compilation. -// ----------------------------------------------------------------------------- - -#pragma once - -// Windows includes -#include <windows.h> - -// Standard includes -#include <stdio.h> -#include <time.h> -#include <io.h> -#include <malloc.h> -#include <direct.h> -#include <fcntl.h> -#include <process.h> - -// Miranda IM SDK includes -#include <newpluginapi.h> // This must be included first -#include <m_avatars.h> -#include <m_chat_int.h> -#include <m_clistint.h> -#include <m_contacts.h> -#include <m_database.h> -#include <m_gui.h> -#include <m_idle.h> -#include <m_icolib.h> -#include <m_ignore.h> -#include <m_json.h> -#include <m_langpack.h> -#include <m_message.h> -#include <m_messagestate.h> -#include <m_netlib.h> -#include <m_protocols.h> -#include <m_protosvc.h> -#include <m_options.h> -#include <m_popup.h> -#include <m_skin.h> -#include <m_smileyadd.h> -#include <m_system.h> -#include <m_timezones.h> -#include <m_userinfo.h> -#include <m_utils.h> - -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/rand.h> -#include <openssl/sha.h> - -// Project resources -#include "resource.h" - -// ICQ plugin includes -#include "version.h" - -#define MODULENAME "ICQ" - -#define DB_KEY_ID "aimId" -#define DB_KEY_IDLE "IdleTS" -#define DB_KEY_ABOUT "About" -#define DB_KEY_PHONE "Phone" -#define DB_KEY_ATOKEN "AToken" -#define DB_KEY_ICQNICK "IcqNick" -#define DB_KEY_PHONEREG "PhoneReg" -#define DB_KEY_LASTSEEN "LastSeen" -#define DB_KEY_ONLINETS "OnlineTS" -#define DB_KEY_PATCHVER "PatchVersion" -#define DB_KEY_RCLIENTID "RClientID" -#define DB_KEY_LASTMSGID "LastMsgId" -#define DB_KEY_REMOTEREAD "RemoteReadId" -#define DB_KEY_SESSIONKEY "SessionKey" -#define DB_KEY_MEMBERSINCE "MemberSince" - -#include "http.h" -#include "proto.h" - -bool IsChat(const CMStringW &aimid); -bool IsValidType(const JSONNode &aimid); - -void RefreshGroups(void); -wchar_t* time2text(time_t time); - -extern bool g_bSecureIM, g_bMessageState; +// ---------------------------------------------------------------------------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
+// Copyright © 2012-2023 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:
+//
+// Includes all header files that should be precompiled to speed up compilation.
+// -----------------------------------------------------------------------------
+
+#pragma once
+
+// Windows includes
+#include <windows.h>
+
+// Standard includes
+#include <stdio.h>
+#include <time.h>
+#include <io.h>
+#include <malloc.h>
+#include <direct.h>
+#include <fcntl.h>
+#include <process.h>
+
+// Miranda IM SDK includes
+#include <newpluginapi.h> // This must be included first
+#include <m_avatars.h>
+#include <m_chat_int.h>
+#include <m_clistint.h>
+#include <m_contacts.h>
+#include <m_database.h>
+#include <m_gui.h>
+#include <m_idle.h>
+#include <m_icolib.h>
+#include <m_ignore.h>
+#include <m_json.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_messagestate.h>
+#include <m_netlib.h>
+#include <m_protocols.h>
+#include <m_protosvc.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_skin.h>
+#include <m_smileyadd.h>
+#include <m_system.h>
+#include <m_timezones.h>
+#include <m_userinfo.h>
+#include <m_utils.h>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+// Project resources
+#include "resource.h"
+
+// ICQ plugin includes
+#include "version.h"
+
+#define MODULENAME "ICQ"
+
+#define DB_KEY_ID "aimId"
+#define DB_KEY_IDLE "IdleTS"
+#define DB_KEY_ABOUT "About"
+#define DB_KEY_PHONE "Phone"
+#define DB_KEY_ATOKEN "AToken"
+#define DB_KEY_ICQNICK "IcqNick"
+#define DB_KEY_PHONEREG "PhoneReg"
+#define DB_KEY_LASTSEEN "LastSeen"
+#define DB_KEY_ONLINETS "OnlineTS"
+#define DB_KEY_PATCHVER "PatchVersion"
+#define DB_KEY_RCLIENTID "RClientID"
+#define DB_KEY_LASTMSGID "LastMsgId"
+#define DB_KEY_REMOTEREAD "RemoteReadId"
+#define DB_KEY_SESSIONKEY "SessionKey"
+#define DB_KEY_MEMBERSINCE "MemberSince"
+
+#include "http.h"
+#include "proto.h"
+
+bool IsChat(const CMStringW &aimid);
+bool IsValidType(const JSONNode &aimid);
+
+void RefreshGroups(void);
+wchar_t* time2text(time_t time);
+
+extern bool g_bSecureIM, g_bMessageState;
diff --git a/protocols/ICQ-WIM/src/userinfo.cpp b/protocols/ICQ-WIM/src/userinfo.cpp index 887d9c2d92..61f661a31b 100644 --- a/protocols/ICQ-WIM/src/userinfo.cpp +++ b/protocols/ICQ-WIM/src/userinfo.cpp @@ -1,64 +1,64 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -struct IcqUserInfoDlg : public CUserInfoPageDlg -{ - CIcqProto *ppro; - - IcqUserInfoDlg(CIcqProto *_ppro) : - CUserInfoPageDlg(g_plugin, IDD_INFO_ICQ), - ppro(_ppro) - { - } - - bool OnRefresh() override - { - SetDlgItemTextW(m_hwnd, IDC_UIN, ppro->GetUserId(m_hContact)); - SetDlgItemTextW(m_hwnd, IDC_NICK, ppro->getMStringW(m_hContact, DB_KEY_ICQNICK)); - SetDlgItemTextW(m_hwnd, IDC_PHONE, ppro->getMStringW(m_hContact, DB_KEY_PHONE)); - - SetDlgItemTextW(m_hwnd, IDC_IDLETIME, time2text(ppro->getDword(m_hContact, DB_KEY_IDLE))); - SetDlgItemTextW(m_hwnd, IDC_LASTSEEN, time2text(ppro->getDword(m_hContact, DB_KEY_LASTSEEN))); - SetDlgItemTextW(m_hwnd, IDC_MEMBERSINCE, time2text(ppro->getDword(m_hContact, DB_KEY_MEMBERSINCE))); - SetDlgItemTextW(m_hwnd, IDC_ONLINESINCE, time2text(time(0) - ppro->getDword(m_hContact, DB_KEY_ONLINETS))); - return false; - } -}; - -int CIcqProto::OnUserInfoInit(WPARAM wParam, LPARAM hContact) -{ - if (hContact && mir_strcmp(Proto_GetBaseAccountName(hContact), m_szModuleName)) - return 0; - - if (isChatRoom(hContact)) - return 0; - - USERINFOPAGE uip = {}; - uip.flags = ODPF_UNICODE | ODPF_USERINFOTAB | ODPF_DONTTRANSLATE | ODPF_ICON; - uip.dwInitParam = (LPARAM)Skin_GetProtoIcon(m_szModuleName, ID_STATUS_ONLINE); - uip.szTitle.w = L"ICQ"; - uip.szGroup.w = m_tszUserName; - uip.position = -1900000000; - uip.pDialog = new IcqUserInfoDlg(this); - g_plugin.addUserInfo(wParam, &uip); - return 0; -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+struct IcqUserInfoDlg : public CUserInfoPageDlg
+{
+ CIcqProto *ppro;
+
+ IcqUserInfoDlg(CIcqProto *_ppro) :
+ CUserInfoPageDlg(g_plugin, IDD_INFO_ICQ),
+ ppro(_ppro)
+ {
+ }
+
+ bool OnRefresh() override
+ {
+ SetDlgItemTextW(m_hwnd, IDC_UIN, ppro->GetUserId(m_hContact));
+ SetDlgItemTextW(m_hwnd, IDC_NICK, ppro->getMStringW(m_hContact, DB_KEY_ICQNICK));
+ SetDlgItemTextW(m_hwnd, IDC_PHONE, ppro->getMStringW(m_hContact, DB_KEY_PHONE));
+
+ SetDlgItemTextW(m_hwnd, IDC_IDLETIME, time2text(ppro->getDword(m_hContact, DB_KEY_IDLE)));
+ SetDlgItemTextW(m_hwnd, IDC_LASTSEEN, time2text(ppro->getDword(m_hContact, DB_KEY_LASTSEEN)));
+ SetDlgItemTextW(m_hwnd, IDC_MEMBERSINCE, time2text(ppro->getDword(m_hContact, DB_KEY_MEMBERSINCE)));
+ SetDlgItemTextW(m_hwnd, IDC_ONLINESINCE, time2text(time(0) - ppro->getDword(m_hContact, DB_KEY_ONLINETS)));
+ return false;
+ }
+};
+
+int CIcqProto::OnUserInfoInit(WPARAM wParam, LPARAM hContact)
+{
+ if (hContact && mir_strcmp(Proto_GetBaseAccountName(hContact), m_szModuleName))
+ return 0;
+
+ if (isChatRoom(hContact))
+ return 0;
+
+ USERINFOPAGE uip = {};
+ uip.flags = ODPF_UNICODE | ODPF_USERINFOTAB | ODPF_DONTTRANSLATE | ODPF_ICON;
+ uip.dwInitParam = (LPARAM)Skin_GetProtoIcon(m_szModuleName, ID_STATUS_ONLINE);
+ uip.szTitle.w = L"ICQ";
+ uip.szGroup.w = m_tszUserName;
+ uip.position = -1900000000;
+ uip.pDialog = new IcqUserInfoDlg(this);
+ g_plugin.addUserInfo(wParam, &uip);
+ return 0;
+}
diff --git a/protocols/ICQ-WIM/src/utils.cpp b/protocols/ICQ-WIM/src/utils.cpp index 4fa65d0302..dfa73b8fb4 100644 --- a/protocols/ICQ-WIM/src/utils.cpp +++ b/protocols/ICQ-WIM/src/utils.cpp @@ -1,396 +1,396 @@ -// ----------------------------------------------------------------------------- -// ICQ plugin for Miranda NG -// ----------------------------------------------------------------------------- -// Copyright © 2018-22 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. -// ----------------------------------------------------------------------------- - -#include "stdafx.h" - -void CIcqProto::InitContactCache() -{ - mir_cslock l(m_csCache); - for (auto &it : AccContacts()) { - if (isChatRoom(it)) - continue; - - // that was previously an ICQ contact - ptrW wszUin(GetUIN(it)); - if (wszUin != nullptr) { - delSetting(it, "UIN"); - setWString(it, DB_KEY_ID, wszUin); - } - // that was previously a MRA contact - else { - CMStringW wszEmail(getMStringW(it, "e-mail")); - if (!wszEmail.IsEmpty()) { - delSetting(it, "e-mail"); - setWString(it, DB_KEY_ID, wszEmail); - } - } - - CMStringW wszId = GetUserId(it); - auto *pCache = FindContactByUIN(wszId); - if (pCache == nullptr) { - pCache = new IcqCacheItem(wszId, it); - m_arCache.insert(pCache); - } - pCache->m_iProcessedMsgId = getId(it, DB_KEY_LASTMSGID); - } -} - -IcqCacheItem* CIcqProto::FindContactByUIN(const CMStringW &wszId) -{ - IcqCacheItem tmp(wszId, -1); - - mir_cslock l(m_csCache); - return m_arCache.find(&tmp); -} - -wchar_t* CIcqProto::GetUIN(MCONTACT hContact) -{ - DBVARIANT dbv = {}; - if (!db_get(hContact, m_szModuleName, "UIN", &dbv)) { - switch (dbv.type) { - case DBVT_DWORD: - wchar_t buf[40], *ret; - _itow_s(dbv.dVal, buf, 10); - return mir_wstrdup(buf); - - case DBVT_ASCIIZ: - ret = mir_a2u(dbv.pszVal); - db_free(&dbv); - return ret; - - case DBVT_UTF8: - ret = mir_utf8decodeW(dbv.pszVal); - db_free(&dbv); - return ret; - - case DBVT_WCHAR: - return dbv.pwszVal; - } - db_free(&dbv); - } - return nullptr; -} - -MCONTACT CIcqProto::CreateContact(const CMStringW &wszId, bool bTemporary) -{ - auto *pCache = FindContactByUIN(wszId); - if (pCache != nullptr) - return pCache->m_hContact; - - MCONTACT hContact = db_add_contact(); - setWString(hContact, DB_KEY_ID, wszId); - Proto_AddToContact(hContact, m_szModuleName); - RetrieveUserInfo(hContact); - - if (bTemporary) - Contact::RemoveFromList(hContact); - - return hContact; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::CalcHash(AsyncHttpRequest *pReq) -{ - CMStringA hashData(FORMAT, "%s&%s&%s", - pReq->requestType == REQUEST_POST ? "POST" : "GET", - mir_urlEncode(pReq->m_szUrl).c_str(), mir_urlEncode(pReq->m_szParam).c_str()); - - unsigned int len; - uint8_t hashOut[MIR_SHA256_HASH_SIZE]; - HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (uint8_t*)hashData.c_str(), hashData.GetLength(), hashOut, &len); - pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut)))); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CIcqProto::Json2int(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting, bool bIsPartial) -{ - const JSONNode &var = node[szJson]; - if (var) - setDword(hContact, szSetting, var.as_int()); - else if (!bIsPartial) - delSetting(hContact, szSetting); -} - -void CIcqProto::Json2string(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting, bool bIsPartial) -{ - const JSONNode &var = node[szJson]; - if (var) { - CMStringW wszStr(var.as_mstring()); - if (wszStr == L"[deleted]") { - setByte(hContact, "IcqDeleted", 1); - Contact::PutOnList(hContact); - } - else setWString(hContact, szSetting, wszStr); - } - else if (!bIsPartial) - delSetting(hContact, szSetting); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Avatars - -void CIcqProto::GetAvatarFileName(MCONTACT hContact, wchar_t* pszDest, size_t cbLen) -{ - int tPathLen = mir_snwprintf(pszDest, cbLen, L"%s\\%S", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName); - CreateDirectoryTreeW(pszDest); - pszDest[tPathLen++] = '\\'; - - CMStringW wszFileName(getMStringW(hContact, "IconId")); - const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG)); - mir_snwprintf(pszDest + tPathLen, MAX_PATH - tPathLen, L"%s%s", wszFileName.c_str(), szFileType); -} - -INT_PTR __cdecl CIcqProto::GetAvatar(WPARAM wParam, LPARAM lParam) -{ - wchar_t *buf = (wchar_t*)wParam; - int size = (int)lParam; - if (buf == nullptr || size <= 0) - return -1; - - GetAvatarFileName(0, buf, size); - return 0; -} - -INT_PTR __cdecl CIcqProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) -{ - switch (wParam) { - case AF_MAXSIZE: - ((POINT*)lParam)->x = -1; - ((POINT*)lParam)->y = -1; - return 0; - - case AF_FORMATSUPPORTED: // nobody - return 1; - - case AF_DELAYAFTERFAIL: - return 10 * 60 * 1000; - - case AF_ENABLED: - case AF_FETCHIFPROTONOTVISIBLE: - case AF_FETCHIFCONTACTOFFLINE: - return 1; - } - return 0; -} - -INT_PTR __cdecl CIcqProto::GetAvatarInfo(WPARAM, LPARAM lParam) -{ - PROTO_AVATAR_INFORMATION* pai = (PROTO_AVATAR_INFORMATION*)lParam; - - ptrW szIconId(getWStringA(pai->hContact, "IconId")); - if (szIconId == nullptr) { - debugLogA("No avatar"); - return GAIR_NOAVATAR; - } - - GetAvatarFileName(pai->hContact, pai->filename, _countof(pai->filename)); - pai->format = getByte(pai->hContact, "AvatarType", 0); - - if (::_waccess(pai->filename, 0) == 0) - return GAIR_SUCCESS; - - debugLogA("No avatar"); - return GAIR_NOAVATAR; -} - -INT_PTR __cdecl CIcqProto::SetAvatar(WPARAM, LPARAM lParam) -{ - wchar_t* pwszFileName = (wchar_t*)lParam; - - wchar_t wszOldName[MAX_PATH]; - GetAvatarFileName(0, wszOldName, _countof(wszOldName)); - _wremove(wszOldName); - - auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/expressions/upload"); - pReq->m_szUrl.AppendFormat("?f=json&aimsid=%s&r=%s&type=largeBuddyIcon", mir_urlEncode(m_aimsid.c_str()).c_str(), pReq->m_reqId); - - if (pwszFileName == nullptr) - delSetting("AvatarHash"); - else { - int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD); - if (fileId < 0) { - delete pReq; - return 1; - } - - unsigned dwSize = (unsigned)_filelengthi64(fileId); - char* pData = (char*)mir_alloc(dwSize); - if (pData == nullptr) { - _close(fileId); - delete pReq; - return 2; - } - - _read(fileId, pData, dwSize); - _close(fileId); - - pReq->pData = pData; - pReq->dataLength = dwSize; - - int iAvatarType = ProtoGetBufferFormat(pData); - if (iAvatarType == PA_FORMAT_UNKNOWN) { - delete pReq; - delete pData; - return 3; - } - - pReq->AddHeader("Content-Type", ProtoGetAvatarMimeType(iAvatarType)); - } - Push(pReq); - - return 0; // TODO -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CMStringW CIcqProto::GetUserId(MCONTACT hContact) -{ - if (isChatRoom(hContact)) - return getMStringW(hContact, "ChatRoomID"); - - return getMStringW(hContact, DB_KEY_ID); -} - -bool IsChat(const CMStringW &aimid) -{ - return aimid.Right(11) == "@chat.agent"; -} - -bool IsValidType(const JSONNode &n) -{ - auto type = n["userType"].as_string(); - return type == "icq" || type == "aim" || type == "interop" || type == ""; -} - -int CIcqProto::StatusFromPresence(const JSONNode &presence, MCONTACT hContact) -{ - CMStringW wszStatus = presence["state"].as_mstring(); - int iStatus; - if (wszStatus == L"online") - iStatus = ID_STATUS_ONLINE; - else if (wszStatus == L"offline") - iStatus = ID_STATUS_OFFLINE; - else if (wszStatus == L"n/a") - iStatus = ID_STATUS_NA; - else if (wszStatus == L"away") - iStatus = ID_STATUS_AWAY; - else if (wszStatus == L"occupied") - iStatus = ID_STATUS_OCCUPIED; - else if (wszStatus == L"dnd") - iStatus = ID_STATUS_DND; - else - iStatus = -1; - - int iLastSeen = presence["lastseen"].as_int(); - if (iLastSeen != 0) - setDword(hContact, DB_KEY_LASTSEEN, iLastSeen); - else if (getDword(hContact, DB_KEY_ONLINETS)) - iStatus = ID_STATUS_ONLINE; - - return iStatus; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -__int64 CIcqProto::getId(MCONTACT hContact, const char *szSetting) -{ - DBVARIANT dbv; - dbv.type = DBVT_BLOB; - if (db_get(hContact, m_szModuleName, szSetting, &dbv)) - return 0; - - __int64 result = (dbv.cpbVal == sizeof(__int64)) ? *(__int64*)dbv.pbVal : 0; - db_free(&dbv); - return result; -} - -void CIcqProto::setId(MCONTACT hContact, const char *szSetting, __int64 iValue) -{ - __int64 oldVal = getId(hContact, szSetting); - if (oldVal != iValue) - db_set_blob(hContact, m_szModuleName, szSetting, &iValue, sizeof(iValue)); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -wchar_t* time2text(time_t ts) -{ - if (ts == 0) - return L""; - - static wchar_t buf[100]; - TimeZone_PrintTimeStamp(NULL, ts, L"D t", buf, _countof(buf), 0); - return buf; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch (message) { - case WM_CONTEXTMENU: - PUDeletePopup(hWnd); - break; - - case WM_COMMAND: - CIcqProto *ppro = (CIcqProto*)PUGetPluginData(hWnd); - CallProtoService(ppro->m_szModuleName, PS_GOTO_INBOX); - PUDeletePopup(hWnd); - break; - } - - return DefWindowProc(hWnd, message, wParam, lParam); -} - -void CIcqProto::EmailNotification(const wchar_t *pwszText) -{ - POPUPDATAW Popup = {}; - Popup.lchIcon = g_plugin.getIcon(IDI_INBOX); - wcsncpy_s(Popup.lpwzText, pwszText, _TRUNCATE); - wcsncpy_s(Popup.lpwzContactName, m_tszUserName, _TRUNCATE); - Popup.iSeconds = 20; - Popup.PluginData = this; - Popup.PluginWindowProc = PopupDlgProc; - PUAddPopupW(&Popup); - - if (m_bUseTrayIcon) { - char szServiceFunction[MAX_PATH]; - if (m_bLaunchMailbox) - mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_GOTO_INBOX); - else - mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_DUMMY); - - int i = 0; - while (CLISTEVENT *pcle = g_clistApi.pfnGetEvent(-1, i++)) - if (!mir_strcmp(pcle->pszService, szServiceFunction)) - return; - - CLISTEVENT cle = {}; - cle.hDbEvent = ICQ_FAKE_EVENT_ID; - cle.moduleName = m_szModuleName; - cle.hIcon = g_plugin.getIcon(IDI_INBOX); - cle.flags = CLEF_UNICODE | CLEF_PROTOCOLGLOBAL; - cle.pszService = szServiceFunction; - cle.szTooltip.w = pwszText; - g_clistApi.pfnAddEvent(&cle); - } -} +// -----------------------------------------------------------------------------
+// ICQ plugin for Miranda NG
+// -----------------------------------------------------------------------------
+// Copyright © 2018-23 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.
+// -----------------------------------------------------------------------------
+
+#include "stdafx.h"
+
+void CIcqProto::InitContactCache()
+{
+ mir_cslock l(m_csCache);
+ for (auto &it : AccContacts()) {
+ if (isChatRoom(it))
+ continue;
+
+ // that was previously an ICQ contact
+ ptrW wszUin(GetUIN(it));
+ if (wszUin != nullptr) {
+ delSetting(it, "UIN");
+ setWString(it, DB_KEY_ID, wszUin);
+ }
+ // that was previously a MRA contact
+ else {
+ CMStringW wszEmail(getMStringW(it, "e-mail"));
+ if (!wszEmail.IsEmpty()) {
+ delSetting(it, "e-mail");
+ setWString(it, DB_KEY_ID, wszEmail);
+ }
+ }
+
+ CMStringW wszId = GetUserId(it);
+ auto *pCache = FindContactByUIN(wszId);
+ if (pCache == nullptr) {
+ pCache = new IcqCacheItem(wszId, it);
+ m_arCache.insert(pCache);
+ }
+ pCache->m_iProcessedMsgId = getId(it, DB_KEY_LASTMSGID);
+ }
+}
+
+IcqCacheItem* CIcqProto::FindContactByUIN(const CMStringW &wszId)
+{
+ IcqCacheItem tmp(wszId, -1);
+
+ mir_cslock l(m_csCache);
+ return m_arCache.find(&tmp);
+}
+
+wchar_t* CIcqProto::GetUIN(MCONTACT hContact)
+{
+ DBVARIANT dbv = {};
+ if (!db_get(hContact, m_szModuleName, "UIN", &dbv)) {
+ switch (dbv.type) {
+ case DBVT_DWORD:
+ wchar_t buf[40], *ret;
+ _itow_s(dbv.dVal, buf, 10);
+ return mir_wstrdup(buf);
+
+ case DBVT_ASCIIZ:
+ ret = mir_a2u(dbv.pszVal);
+ db_free(&dbv);
+ return ret;
+
+ case DBVT_UTF8:
+ ret = mir_utf8decodeW(dbv.pszVal);
+ db_free(&dbv);
+ return ret;
+
+ case DBVT_WCHAR:
+ return dbv.pwszVal;
+ }
+ db_free(&dbv);
+ }
+ return nullptr;
+}
+
+MCONTACT CIcqProto::CreateContact(const CMStringW &wszId, bool bTemporary)
+{
+ auto *pCache = FindContactByUIN(wszId);
+ if (pCache != nullptr)
+ return pCache->m_hContact;
+
+ MCONTACT hContact = db_add_contact();
+ setWString(hContact, DB_KEY_ID, wszId);
+ Proto_AddToContact(hContact, m_szModuleName);
+ RetrieveUserInfo(hContact);
+
+ if (bTemporary)
+ Contact::RemoveFromList(hContact);
+
+ return hContact;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::CalcHash(AsyncHttpRequest *pReq)
+{
+ CMStringA hashData(FORMAT, "%s&%s&%s",
+ pReq->requestType == REQUEST_POST ? "POST" : "GET",
+ mir_urlEncode(pReq->m_szUrl).c_str(), mir_urlEncode(pReq->m_szParam).c_str());
+
+ unsigned int len;
+ uint8_t hashOut[MIR_SHA256_HASH_SIZE];
+ HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (uint8_t*)hashData.c_str(), hashData.GetLength(), hashOut, &len);
+ pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut))));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::Json2int(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting, bool bIsPartial)
+{
+ const JSONNode &var = node[szJson];
+ if (var)
+ setDword(hContact, szSetting, var.as_int());
+ else if (!bIsPartial)
+ delSetting(hContact, szSetting);
+}
+
+void CIcqProto::Json2string(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting, bool bIsPartial)
+{
+ const JSONNode &var = node[szJson];
+ if (var) {
+ CMStringW wszStr(var.as_mstring());
+ if (wszStr == L"[deleted]") {
+ setByte(hContact, "IcqDeleted", 1);
+ Contact::PutOnList(hContact);
+ }
+ else setWString(hContact, szSetting, wszStr);
+ }
+ else if (!bIsPartial)
+ delSetting(hContact, szSetting);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Avatars
+
+void CIcqProto::GetAvatarFileName(MCONTACT hContact, wchar_t* pszDest, size_t cbLen)
+{
+ int tPathLen = mir_snwprintf(pszDest, cbLen, L"%s\\%S", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName);
+ CreateDirectoryTreeW(pszDest);
+ pszDest[tPathLen++] = '\\';
+
+ CMStringW wszFileName(getMStringW(hContact, "IconId"));
+ const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG));
+ mir_snwprintf(pszDest + tPathLen, MAX_PATH - tPathLen, L"%s%s", wszFileName.c_str(), szFileType);
+}
+
+INT_PTR __cdecl CIcqProto::GetAvatar(WPARAM wParam, LPARAM lParam)
+{
+ wchar_t *buf = (wchar_t*)wParam;
+ int size = (int)lParam;
+ if (buf == nullptr || size <= 0)
+ return -1;
+
+ GetAvatarFileName(0, buf, size);
+ return 0;
+}
+
+INT_PTR __cdecl CIcqProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ switch (wParam) {
+ case AF_MAXSIZE:
+ ((POINT*)lParam)->x = -1;
+ ((POINT*)lParam)->y = -1;
+ return 0;
+
+ case AF_FORMATSUPPORTED: // nobody
+ return 1;
+
+ case AF_DELAYAFTERFAIL:
+ return 10 * 60 * 1000;
+
+ case AF_ENABLED:
+ case AF_FETCHIFPROTONOTVISIBLE:
+ case AF_FETCHIFCONTACTOFFLINE:
+ return 1;
+ }
+ return 0;
+}
+
+INT_PTR __cdecl CIcqProto::GetAvatarInfo(WPARAM, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION* pai = (PROTO_AVATAR_INFORMATION*)lParam;
+
+ ptrW szIconId(getWStringA(pai->hContact, "IconId"));
+ if (szIconId == nullptr) {
+ debugLogA("No avatar");
+ return GAIR_NOAVATAR;
+ }
+
+ GetAvatarFileName(pai->hContact, pai->filename, _countof(pai->filename));
+ pai->format = getByte(pai->hContact, "AvatarType", 0);
+
+ if (::_waccess(pai->filename, 0) == 0)
+ return GAIR_SUCCESS;
+
+ debugLogA("No avatar");
+ return GAIR_NOAVATAR;
+}
+
+INT_PTR __cdecl CIcqProto::SetAvatar(WPARAM, LPARAM lParam)
+{
+ wchar_t* pwszFileName = (wchar_t*)lParam;
+
+ wchar_t wszOldName[MAX_PATH];
+ GetAvatarFileName(0, wszOldName, _countof(wszOldName));
+ _wremove(wszOldName);
+
+ auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/expressions/upload");
+ pReq->m_szUrl.AppendFormat("?f=json&aimsid=%s&r=%s&type=largeBuddyIcon", mir_urlEncode(m_aimsid.c_str()).c_str(), pReq->m_reqId);
+
+ if (pwszFileName == nullptr)
+ delSetting("AvatarHash");
+ else {
+ int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD);
+ if (fileId < 0) {
+ delete pReq;
+ return 1;
+ }
+
+ unsigned dwSize = (unsigned)_filelengthi64(fileId);
+ char* pData = (char*)mir_alloc(dwSize);
+ if (pData == nullptr) {
+ _close(fileId);
+ delete pReq;
+ return 2;
+ }
+
+ _read(fileId, pData, dwSize);
+ _close(fileId);
+
+ pReq->pData = pData;
+ pReq->dataLength = dwSize;
+
+ int iAvatarType = ProtoGetBufferFormat(pData);
+ if (iAvatarType == PA_FORMAT_UNKNOWN) {
+ delete pReq;
+ delete pData;
+ return 3;
+ }
+
+ pReq->AddHeader("Content-Type", ProtoGetAvatarMimeType(iAvatarType));
+ }
+ Push(pReq);
+
+ return 0; // TODO
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringW CIcqProto::GetUserId(MCONTACT hContact)
+{
+ if (isChatRoom(hContact))
+ return getMStringW(hContact, "ChatRoomID");
+
+ return getMStringW(hContact, DB_KEY_ID);
+}
+
+bool IsChat(const CMStringW &aimid)
+{
+ return aimid.Right(11) == "@chat.agent";
+}
+
+bool IsValidType(const JSONNode &n)
+{
+ auto type = n["userType"].as_string();
+ return type == "icq" || type == "aim" || type == "interop" || type == "";
+}
+
+int CIcqProto::StatusFromPresence(const JSONNode &presence, MCONTACT hContact)
+{
+ CMStringW wszStatus = presence["state"].as_mstring();
+ int iStatus;
+ if (wszStatus == L"online")
+ iStatus = ID_STATUS_ONLINE;
+ else if (wszStatus == L"offline")
+ iStatus = ID_STATUS_OFFLINE;
+ else if (wszStatus == L"n/a")
+ iStatus = ID_STATUS_NA;
+ else if (wszStatus == L"away")
+ iStatus = ID_STATUS_AWAY;
+ else if (wszStatus == L"occupied")
+ iStatus = ID_STATUS_OCCUPIED;
+ else if (wszStatus == L"dnd")
+ iStatus = ID_STATUS_DND;
+ else
+ iStatus = -1;
+
+ int iLastSeen = presence["lastseen"].as_int();
+ if (iLastSeen != 0)
+ setDword(hContact, DB_KEY_LASTSEEN, iLastSeen);
+ else if (getDword(hContact, DB_KEY_ONLINETS))
+ iStatus = ID_STATUS_ONLINE;
+
+ return iStatus;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+__int64 CIcqProto::getId(MCONTACT hContact, const char *szSetting)
+{
+ DBVARIANT dbv;
+ dbv.type = DBVT_BLOB;
+ if (db_get(hContact, m_szModuleName, szSetting, &dbv))
+ return 0;
+
+ __int64 result = (dbv.cpbVal == sizeof(__int64)) ? *(__int64*)dbv.pbVal : 0;
+ db_free(&dbv);
+ return result;
+}
+
+void CIcqProto::setId(MCONTACT hContact, const char *szSetting, __int64 iValue)
+{
+ __int64 oldVal = getId(hContact, szSetting);
+ if (oldVal != iValue)
+ db_set_blob(hContact, m_szModuleName, szSetting, &iValue, sizeof(iValue));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+wchar_t* time2text(time_t ts)
+{
+ if (ts == 0)
+ return L"";
+
+ static wchar_t buf[100];
+ TimeZone_PrintTimeStamp(NULL, ts, L"D t", buf, _countof(buf), 0);
+ return buf;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message) {
+ case WM_CONTEXTMENU:
+ PUDeletePopup(hWnd);
+ break;
+
+ case WM_COMMAND:
+ CIcqProto *ppro = (CIcqProto*)PUGetPluginData(hWnd);
+ CallProtoService(ppro->m_szModuleName, PS_GOTO_INBOX);
+ PUDeletePopup(hWnd);
+ break;
+ }
+
+ return DefWindowProc(hWnd, message, wParam, lParam);
+}
+
+void CIcqProto::EmailNotification(const wchar_t *pwszText)
+{
+ POPUPDATAW Popup = {};
+ Popup.lchIcon = g_plugin.getIcon(IDI_INBOX);
+ wcsncpy_s(Popup.lpwzText, pwszText, _TRUNCATE);
+ wcsncpy_s(Popup.lpwzContactName, m_tszUserName, _TRUNCATE);
+ Popup.iSeconds = 20;
+ Popup.PluginData = this;
+ Popup.PluginWindowProc = PopupDlgProc;
+ PUAddPopupW(&Popup);
+
+ if (m_bUseTrayIcon) {
+ char szServiceFunction[MAX_PATH];
+ if (m_bLaunchMailbox)
+ mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_GOTO_INBOX);
+ else
+ mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_DUMMY);
+
+ int i = 0;
+ while (CLISTEVENT *pcle = g_clistApi.pfnGetEvent(-1, i++))
+ if (!mir_strcmp(pcle->pszService, szServiceFunction))
+ return;
+
+ CLISTEVENT cle = {};
+ cle.hDbEvent = ICQ_FAKE_EVENT_ID;
+ cle.moduleName = m_szModuleName;
+ cle.hIcon = g_plugin.getIcon(IDI_INBOX);
+ cle.flags = CLEF_UNICODE | CLEF_PROTOCOLGLOBAL;
+ cle.pszService = szServiceFunction;
+ cle.szTooltip.w = pwszText;
+ g_clistApi.pfnAddEvent(&cle);
+ }
+}
diff --git a/protocols/ICQ-WIM/src/version.h b/protocols/ICQ-WIM/src/version.h index c1cb84630e..d5270a8de9 100644 --- a/protocols/ICQ-WIM/src/version.h +++ b/protocols/ICQ-WIM/src/version.h @@ -10,4 +10,4 @@ #define __DESCRIPTION "ICQ protocol support for Miranda NG."
#define __AUTHOR "George Hazan"
#define __AUTHORWEB "https://miranda-ng.org/p/ICQ"
-#define __COPYRIGHT "© 2018-22 George Hazan"
+#define __COPYRIGHT "© 2018-23 George Hazan"
|