summaryrefslogtreecommitdiff
path: root/protocols/ICQ-WIM/src/proto.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/ICQ-WIM/src/proto.cpp')
-rw-r--r--protocols/ICQ-WIM/src/proto.cpp1356
1 files changed, 678 insertions, 678 deletions
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;
+}