summaryrefslogtreecommitdiff
path: root/protocols/Facebook
diff options
context:
space:
mode:
authordartraiden <wowemuh@gmail.com>2023-01-02 21:10:29 +0300
committerdartraiden <wowemuh@gmail.com>2023-01-02 21:10:29 +0300
commit1979fd80424d16b2e489f9b57d01d9c7811d25a2 (patch)
tree960d42c5fe4a51f0fe2850bea91256e226bce221 /protocols/Facebook
parentadfbbb217d4f4a05acf198755f219a5223d31c27 (diff)
Update copyrights
Diffstat (limited to 'protocols/Facebook')
-rw-r--r--protocols/Facebook/src/avatars.cpp262
-rw-r--r--protocols/Facebook/src/db.h88
-rw-r--r--protocols/Facebook/src/dialogs.cpp156
-rw-r--r--protocols/Facebook/src/dialogs.h2
-rw-r--r--protocols/Facebook/src/groupchats.cpp510
-rw-r--r--protocols/Facebook/src/http.cpp370
-rw-r--r--protocols/Facebook/src/main.cpp144
-rw-r--r--protocols/Facebook/src/mqtt.cpp688
-rw-r--r--protocols/Facebook/src/mqtt.h278
-rw-r--r--protocols/Facebook/src/options.cpp168
-rw-r--r--protocols/Facebook/src/proto.cpp604
-rw-r--r--protocols/Facebook/src/proto.h1114
-rw-r--r--protocols/Facebook/src/server.cpp2102
-rw-r--r--protocols/Facebook/src/stdafx.cxx36
-rw-r--r--protocols/Facebook/src/stdafx.h134
-rw-r--r--protocols/Facebook/src/thrift.cpp616
-rw-r--r--protocols/Facebook/src/version.h2
17 files changed, 3637 insertions, 3637 deletions
diff --git a/protocols/Facebook/src/avatars.cpp b/protocols/Facebook/src/avatars.cpp
index 0ced4b1b25..0e73a9241b 100644
--- a/protocols/Facebook/src/avatars.cpp
+++ b/protocols/Facebook/src/avatars.cpp
@@ -1,131 +1,131 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void FacebookProto::GetAvatarFilename(MCONTACT hContact, wchar_t *pwszFileName)
-{
- wchar_t wszPath[MAX_PATH];
- mir_snwprintf(wszPath, MAX_PATH, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName);
- CreateDirectoryTreeW(wszPath);
-
- CMStringW id(getMStringW(hContact, DBKEY_ID));
- mir_snwprintf(pwszFileName, MAX_PATH, L"%s\\%s.jpg", wszPath, id.c_str());
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void __cdecl FacebookProto::AvatarsUpdate(void *)
-{
- NETLIBHTTPREQUEST req = {};
- req.cbSize = sizeof(req);
- req.flags = NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
- req.requestType = REQUEST_GET;
-
- CMStringA szParams((m_bUseBigAvatars) ? "type=large" : "type=normal");
- szParams.AppendFormat("&access_token=%s", m_szAuthToken.c_str());
-
- for (auto &cc : AccContacts()) {
- if (Miranda_IsTerminated())
- break;
-
- if (!getByte(cc, "UpdateNeeded"))
- continue;
-
- delSetting(cc, "UpdateNeeded");
-
- CMStringA szUrl(FORMAT, "https://graph.facebook.com/%s/picture?%s", getMStringA(cc, DBKEY_ID).c_str(), szParams.c_str());
- req.szUrl = szUrl.GetBuffer();
-
- NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(m_hNetlibUser, &req);
- if (pReply == nullptr) {
- debugLogA("Failed to retrieve avatar from url: %s", szUrl.c_str());
- continue;
- }
-
- PROTO_AVATAR_INFORMATION ai;
- ai.hContact = cc;
- ai.format = PA_FORMAT_UNKNOWN;
- GetAvatarFilename(cc, ai.filename);
-
- bool bSuccess = false;
- if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
- if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
- ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);
-
- if (ai.format != PA_FORMAT_UNKNOWN) {
- FILE *fout = _wfopen(ai.filename, L"wb");
- if (fout) {
- fwrite(pReply->pData, 1, pReply->dataLength, fout);
- fclose(fout);
- bSuccess = true;
- }
- else debugLogA("Error saving avatar to file %S", ai.filename);
- }
- else debugLogA("unknown avatar mime type");
- }
- else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, szUrl.c_str());
-
- ProtoBroadcastAck(cc, ACKTYPE_AVATAR, bSuccess ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)&ai);
-
- Netlib_FreeHttpRequest(pReply);
- }
-}
-
-INT_PTR FacebookProto::GetAvatarInfo(WPARAM flags, LPARAM lParam)
-{
- PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam;
- GetAvatarFilename(pai->hContact, pai->filename);
-
- bool bFileExist = _waccess(pai->filename, 0) == 0;
-
- // if we still need to load an avatar
- if ((flags & GAIF_FORCE) || !bFileExist) {
- setByte(pai->hContact, "UpdateNeeded", 1);
- ForkThread(&FacebookProto::AvatarsUpdate);
- return GAIR_WAITFOR;
- }
-
- return (bFileExist) ? GAIR_SUCCESS : GAIR_NOAVATAR;
-}
-
-INT_PTR FacebookProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
-{
- int res = 0;
-
- switch (wParam) {
- case AF_MAXSIZE:
- ((POINT *)lParam)->x = ((POINT *)lParam)->y = 128;
- break;
-
- case AF_FORMATSUPPORTED:
- res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG;
- break;
-
- case AF_ENABLED:
- case AF_FETCHIFPROTONOTVISIBLE:
- case AF_FETCHIFCONTACTOFFLINE:
- return 1;
- }
-
- return res;
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void FacebookProto::GetAvatarFilename(MCONTACT hContact, wchar_t *pwszFileName)
+{
+ wchar_t wszPath[MAX_PATH];
+ mir_snwprintf(wszPath, MAX_PATH, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName);
+ CreateDirectoryTreeW(wszPath);
+
+ CMStringW id(getMStringW(hContact, DBKEY_ID));
+ mir_snwprintf(pwszFileName, MAX_PATH, L"%s\\%s.jpg", wszPath, id.c_str());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void __cdecl FacebookProto::AvatarsUpdate(void *)
+{
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.flags = NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ req.requestType = REQUEST_GET;
+
+ CMStringA szParams((m_bUseBigAvatars) ? "type=large" : "type=normal");
+ szParams.AppendFormat("&access_token=%s", m_szAuthToken.c_str());
+
+ for (auto &cc : AccContacts()) {
+ if (Miranda_IsTerminated())
+ break;
+
+ if (!getByte(cc, "UpdateNeeded"))
+ continue;
+
+ delSetting(cc, "UpdateNeeded");
+
+ CMStringA szUrl(FORMAT, "https://graph.facebook.com/%s/picture?%s", getMStringA(cc, DBKEY_ID).c_str(), szParams.c_str());
+ req.szUrl = szUrl.GetBuffer();
+
+ NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(m_hNetlibUser, &req);
+ if (pReply == nullptr) {
+ debugLogA("Failed to retrieve avatar from url: %s", szUrl.c_str());
+ continue;
+ }
+
+ PROTO_AVATAR_INFORMATION ai;
+ ai.hContact = cc;
+ ai.format = PA_FORMAT_UNKNOWN;
+ GetAvatarFilename(cc, ai.filename);
+
+ bool bSuccess = false;
+ if (pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
+ if (auto *pszHdr = Netlib_GetHeader(pReply, "Content-Type"))
+ ai.format = ProtoGetAvatarFormatByMimeType(pszHdr);
+
+ if (ai.format != PA_FORMAT_UNKNOWN) {
+ FILE *fout = _wfopen(ai.filename, L"wb");
+ if (fout) {
+ fwrite(pReply->pData, 1, pReply->dataLength, fout);
+ fclose(fout);
+ bSuccess = true;
+ }
+ else debugLogA("Error saving avatar to file %S", ai.filename);
+ }
+ else debugLogA("unknown avatar mime type");
+ }
+ else debugLogA("Error %d reading avatar from url: %s", pReply->resultCode, szUrl.c_str());
+
+ ProtoBroadcastAck(cc, ACKTYPE_AVATAR, bSuccess ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)&ai);
+
+ Netlib_FreeHttpRequest(pReply);
+ }
+}
+
+INT_PTR FacebookProto::GetAvatarInfo(WPARAM flags, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam;
+ GetAvatarFilename(pai->hContact, pai->filename);
+
+ bool bFileExist = _waccess(pai->filename, 0) == 0;
+
+ // if we still need to load an avatar
+ if ((flags & GAIF_FORCE) || !bFileExist) {
+ setByte(pai->hContact, "UpdateNeeded", 1);
+ ForkThread(&FacebookProto::AvatarsUpdate);
+ return GAIR_WAITFOR;
+ }
+
+ return (bFileExist) ? GAIR_SUCCESS : GAIR_NOAVATAR;
+}
+
+INT_PTR FacebookProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ int res = 0;
+
+ switch (wParam) {
+ case AF_MAXSIZE:
+ ((POINT *)lParam)->x = ((POINT *)lParam)->y = 128;
+ break;
+
+ case AF_FORMATSUPPORTED:
+ res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG;
+ break;
+
+ case AF_ENABLED:
+ case AF_FETCHIFPROTONOTVISIBLE:
+ case AF_FETCHIFCONTACTOFFLINE:
+ return 1;
+ }
+
+ return res;
+}
diff --git a/protocols/Facebook/src/db.h b/protocols/Facebook/src/db.h
index eb31b19ef0..8095cfad65 100644
--- a/protocols/Facebook/src/db.h
+++ b/protocols/Facebook/src/db.h
@@ -1,44 +1,44 @@
-/*
-
-Facebook plugin for Miranda Instant Messenger
-_____________________________________________
-
-Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#pragma once
-
-#define MODULENAME "Facebook"
-
-// Contact DB keys
-#define DBKEY_LOGIN "Email"
-#define DBKEY_ID "ID"
-#define DBKEY_SID "SID"
-#define DBKEY_NICK "Nick"
-#define DBKEY_PASS "Password"
-#define DBKEY_CLIENT_ID "ClientID"
-#define DBKEY_DEVICE_ID "DeviceID"
-#define DBKEY_AVATAR "Avatar"
-#define DBKEY_CONTACT_TYPE "ContactType"
-#define DBKEY_TOKEN "Token"
-#define DBKEY_SYNC_TOKEN "SyncToken"
-
-// Account DB keys
-#define DBKEY_SET_MIRANDA_STATUS "SetMirandaStatus"
-
-// Hidden account DB keys (can't be changed through GUI)
-#define DBKEY_LOCALE "Locale" // [HIDDEN] - (string) en_US, cs_CZ, etc. (requires restart to apply)
+/*
+
+Facebook plugin for Miranda Instant Messenger
+_____________________________________________
+
+Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#pragma once
+
+#define MODULENAME "Facebook"
+
+// Contact DB keys
+#define DBKEY_LOGIN "Email"
+#define DBKEY_ID "ID"
+#define DBKEY_SID "SID"
+#define DBKEY_NICK "Nick"
+#define DBKEY_PASS "Password"
+#define DBKEY_CLIENT_ID "ClientID"
+#define DBKEY_DEVICE_ID "DeviceID"
+#define DBKEY_AVATAR "Avatar"
+#define DBKEY_CONTACT_TYPE "ContactType"
+#define DBKEY_TOKEN "Token"
+#define DBKEY_SYNC_TOKEN "SyncToken"
+
+// Account DB keys
+#define DBKEY_SET_MIRANDA_STATUS "SetMirandaStatus"
+
+// Hidden account DB keys (can't be changed through GUI)
+#define DBKEY_LOCALE "Locale" // [HIDDEN] - (string) en_US, cs_CZ, etc. (requires restart to apply)
diff --git a/protocols/Facebook/src/dialogs.cpp b/protocols/Facebook/src/dialogs.cpp
index 4e9e215b86..1938c94448 100644
--- a/protocols/Facebook/src/dialogs.cpp
+++ b/protocols/Facebook/src/dialogs.cpp
@@ -1,78 +1,78 @@
-/*
-
-Facebook plugin for Miranda Instant Messenger
-_____________________________________________
-
-Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-INT_PTR CALLBACK FBAccountProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
-{
- FacebookProto *proto = reinterpret_cast<FacebookProto*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
-
- switch (message) {
-
- case WM_INITDIALOG:
- {
- TranslateDialogDefault(hwnd);
-
- proto = reinterpret_cast<FacebookProto*>(lparam);
- SetWindowLongPtr(hwnd, GWLP_USERDATA, lparam);
-
- ptrA login(db_get_sa(0, proto->ModuleName(), DBKEY_LOGIN));
- if (login != nullptr)
- SetDlgItemTextA(hwnd, IDC_UN, login);
-
- ptrA password(db_get_sa(0, proto->ModuleName(), DBKEY_PASS));
- if (password != nullptr)
- SetDlgItemTextA(hwnd, IDC_PW, password);
-
- //if (!proto->isOffline()) {
- // SendDlgItemMessage(hwnd, IDC_UN, EM_SETREADONLY, 1, 0);
- // SendDlgItemMessage(hwnd, IDC_PW, EM_SETREADONLY, 1, 0);
- //}
- return TRUE;
- }
- case WM_COMMAND:
- if (HIWORD(wparam) == EN_CHANGE && reinterpret_cast<HWND>(lparam) == GetFocus()) {
- switch (LOWORD(wparam)) {
- case IDC_UN:
- case IDC_PW:
- SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
- }
- }
- break;
-
- case WM_NOTIFY:
- if (reinterpret_cast<NMHDR*>(lparam)->code == PSN_APPLY) {
- char str[128];
-
- GetDlgItemTextA(hwnd, IDC_UN, str, _countof(str));
- db_set_s(0, proto->ModuleName(), DBKEY_LOGIN, str);
-
- GetDlgItemTextA(hwnd, IDC_PW, str, _countof(str));
- db_set_s(0, proto->ModuleName(), DBKEY_PASS, str);
- return TRUE;
- }
- break;
-
- }
-
- return FALSE;
-}
+/*
+
+Facebook plugin for Miranda Instant Messenger
+_____________________________________________
+
+Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+INT_PTR CALLBACK FBAccountProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
+{
+ FacebookProto *proto = reinterpret_cast<FacebookProto*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+
+ switch (message) {
+
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hwnd);
+
+ proto = reinterpret_cast<FacebookProto*>(lparam);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, lparam);
+
+ ptrA login(db_get_sa(0, proto->ModuleName(), DBKEY_LOGIN));
+ if (login != nullptr)
+ SetDlgItemTextA(hwnd, IDC_UN, login);
+
+ ptrA password(db_get_sa(0, proto->ModuleName(), DBKEY_PASS));
+ if (password != nullptr)
+ SetDlgItemTextA(hwnd, IDC_PW, password);
+
+ //if (!proto->isOffline()) {
+ // SendDlgItemMessage(hwnd, IDC_UN, EM_SETREADONLY, 1, 0);
+ // SendDlgItemMessage(hwnd, IDC_PW, EM_SETREADONLY, 1, 0);
+ //}
+ return TRUE;
+ }
+ case WM_COMMAND:
+ if (HIWORD(wparam) == EN_CHANGE && reinterpret_cast<HWND>(lparam) == GetFocus()) {
+ switch (LOWORD(wparam)) {
+ case IDC_UN:
+ case IDC_PW:
+ SendMessage(GetParent(hwnd), PSM_CHANGED, 0, 0);
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (reinterpret_cast<NMHDR*>(lparam)->code == PSN_APPLY) {
+ char str[128];
+
+ GetDlgItemTextA(hwnd, IDC_UN, str, _countof(str));
+ db_set_s(0, proto->ModuleName(), DBKEY_LOGIN, str);
+
+ GetDlgItemTextA(hwnd, IDC_PW, str, _countof(str));
+ db_set_s(0, proto->ModuleName(), DBKEY_PASS, str);
+ return TRUE;
+ }
+ break;
+
+ }
+
+ return FALSE;
+}
diff --git a/protocols/Facebook/src/dialogs.h b/protocols/Facebook/src/dialogs.h
index 23d821c697..5dcaeb667b 100644
--- a/protocols/Facebook/src/dialogs.h
+++ b/protocols/Facebook/src/dialogs.h
@@ -3,7 +3,7 @@
Facebook plugin for Miranda Instant Messenger
_____________________________________________
-Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-22 Miranda NG team
+Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-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
diff --git a/protocols/Facebook/src/groupchats.cpp b/protocols/Facebook/src/groupchats.cpp
index 92266e3e2d..b43e05696a 100644
--- a/protocols/Facebook/src/groupchats.cpp
+++ b/protocols/Facebook/src/groupchats.cpp
@@ -1,255 +1,255 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Invitation dialog
-
-class CGroupchatInviteDlg : public CFBDlgBase
-{
- 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);
- m_clc.SetOfflineModes(PF2_NONE);
- }
-
-public:
- CGroupchatInviteDlg(FacebookProto *ppro, SESSION_INFO *si) :
- CFBDlgBase(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
- {
- JSONNode list(JSON_ARRAY);
-
- for (auto &hContact : m_proto->AccContacts()) {
- if (m_proto->isChatRoom(hContact))
- continue;
-
- if (HANDLE hItem = m_clc.FindContact(hContact)) {
- if (m_clc.GetCheck(hItem)) {
- JSONNode user; user << CHAR_PARAM("type", "id") << CHAR_PARAM("id", m_proto->getMStringA(hContact, DBKEY_ID));
- list << user;
- }
- }
- }
-
- auto *pReq = m_proto->CreateRequest(FB_API_URL_PARTS, "addMembers", "POST");
- pReq << CHAR_PARAM("to", list.write().c_str()) << WCHAR_PARAM("id", CMStringW(FORMAT, L"t_%s", m_si->ptszID));
- pReq->CalcSig();
-
- JsonReply reply(m_proto->ExecuteRequest(pReq));
- return true;
- }
-};
-
-void FacebookProto::Chat_InviteUser(SESSION_INFO *si)
-{
- CGroupchatInviteDlg dlg(this, si);
- if (si->pDlg)
- dlg.SetParent(((CDlgBase *)si->pDlg)->GetHwnd());
- dlg.DoModal();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Group chats
-
-enum ChatMenuItems
-{
- IDM_INVITE = 10, IDM_LEAVE,
-
- IDM_KICK = 20
-};
-
-static gc_item sttLogListItems[] =
-{
- { LPGENW("&Invite a user"), IDM_INVITE, MENU_ITEM },
- { nullptr, 0, MENU_SEPARATOR },
- { LPGENW("&Leave/destroy chat"), IDM_LEAVE, MENU_ITEM },
-};
-
-static gc_item sttNickListItems[] =
-{
- { LPGENW("&Kick user"), IDM_KICK, MENU_ITEM },
-};
-
-int FacebookProto::GroupchatMenuHook(WPARAM, LPARAM lParam)
-{
- GCMENUITEMS *gcmi = (GCMENUITEMS *)lParam;
- if (gcmi == nullptr)
- return 0;
-
- if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
- return 0;
-
- if (SESSION_INFO *si = g_chatApi.SM_FindSession(gcmi->pszID, gcmi->pszModule)) {
- if (gcmi->Type == MENU_ON_LOG)
- Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin);
- if (gcmi->Type == MENU_ON_NICKLIST)
- Chat_AddMenuItems(gcmi->hMenu, _countof(sttNickListItems), sttNickListItems, &g_plugin);
- }
-
- return 0;
-}
-
-int FacebookProto::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);
-
- int mid = SendMsg(si->hContact, 0, T2Utf(wszText));
-
- mir_cslock lck(m_csOwnMessages);
- for (auto &msg : arOwnMessages)
- if (msg->reqId == mid)
- msg->wszText = wszText;
- }
- break;
-
- case GC_USER_PRIVMESS:
- Chat_SendPrivateMessage(gch);
- break;
-
- case GC_USER_LOGMENU:
- Chat_ProcessLogMenu(si, gch);
- break;
-
- case GC_USER_NICKLISTMENU:
- Chat_ProcessNickMenu(si, gch);
- break;
- }
-
- return 1;
-}
-
-void FacebookProto::Chat_ProcessLogMenu(SESSION_INFO *si, GCHOOK *gch)
-{
- switch (gch->dwData) {
- case IDM_INVITE:
- Chat_InviteUser(si);
- break;
-
- case IDM_LEAVE:
- Chat_Leave(si);
- break;
- }
-}
-
-void FacebookProto::Chat_ProcessNickMenu(SESSION_INFO *si, GCHOOK *gch)
-{
- switch (gch->dwData) {
- case IDM_KICK:
- Chat_KickUser(si, gch->ptszUID);
- break;
- }
-}
-
-void FacebookProto::Chat_SendPrivateMessage(GCHOOK *gch)
-{
- auto *pUser = FindUser(_wtoi64(gch->ptszUID));
- if (pUser == nullptr) {
- pUser = AddContact(gch->ptszUID, true);
- setWString(pUser->hContact, "Nick", gch->ptszNick);
- db_set_b(pUser->hContact, "CList", "Hidden", 1);
- db_set_dw(pUser->hContact, "Ignore", "Mask1", 0);
- }
-
- CallService(MS_MSG_SENDMESSAGE, pUser->hContact, 0);
-}
-
-int FacebookProto::Chat_KickUser(SESSION_INFO *si, const wchar_t *pwszUid)
-{
- auto *pReq = CreateRequest(FB_API_URL_PARTS, "removeMembers", "DELETE");
- pReq << WCHAR_PARAM("id", CMStringW(FORMAT, L"t_%s", si->ptszID));
- if (pwszUid != nullptr) {
- JSONNode list(JSON_ARRAY);
- JSONNode user; user << CHAR_PARAM("type", "id") << WCHAR_PARAM("id", pwszUid);
- list << user;
- pReq << CHAR_PARAM("to", list.write().c_str());
- }
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- return reply.error();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static void __cdecl DestroyRoomThread(SESSION_INFO *si)
-{
- ::Sleep(100);
- Chat_Terminate(si->pszModule, si->ptszID, true);
-}
-
-void FacebookProto::Chat_Leave(SESSION_INFO *si)
-{
- if (Chat_KickUser(si, nullptr) == 0)
- mir_forkThread<SESSION_INFO>(DestroyRoomThread, si);
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Invitation dialog
+
+class CGroupchatInviteDlg : public CFBDlgBase
+{
+ 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);
+ m_clc.SetOfflineModes(PF2_NONE);
+ }
+
+public:
+ CGroupchatInviteDlg(FacebookProto *ppro, SESSION_INFO *si) :
+ CFBDlgBase(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
+ {
+ JSONNode list(JSON_ARRAY);
+
+ for (auto &hContact : m_proto->AccContacts()) {
+ if (m_proto->isChatRoom(hContact))
+ continue;
+
+ if (HANDLE hItem = m_clc.FindContact(hContact)) {
+ if (m_clc.GetCheck(hItem)) {
+ JSONNode user; user << CHAR_PARAM("type", "id") << CHAR_PARAM("id", m_proto->getMStringA(hContact, DBKEY_ID));
+ list << user;
+ }
+ }
+ }
+
+ auto *pReq = m_proto->CreateRequest(FB_API_URL_PARTS, "addMembers", "POST");
+ pReq << CHAR_PARAM("to", list.write().c_str()) << WCHAR_PARAM("id", CMStringW(FORMAT, L"t_%s", m_si->ptszID));
+ pReq->CalcSig();
+
+ JsonReply reply(m_proto->ExecuteRequest(pReq));
+ return true;
+ }
+};
+
+void FacebookProto::Chat_InviteUser(SESSION_INFO *si)
+{
+ CGroupchatInviteDlg dlg(this, si);
+ if (si->pDlg)
+ dlg.SetParent(((CDlgBase *)si->pDlg)->GetHwnd());
+ dlg.DoModal();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Group chats
+
+enum ChatMenuItems
+{
+ IDM_INVITE = 10, IDM_LEAVE,
+
+ IDM_KICK = 20
+};
+
+static gc_item sttLogListItems[] =
+{
+ { LPGENW("&Invite a user"), IDM_INVITE, MENU_ITEM },
+ { nullptr, 0, MENU_SEPARATOR },
+ { LPGENW("&Leave/destroy chat"), IDM_LEAVE, MENU_ITEM },
+};
+
+static gc_item sttNickListItems[] =
+{
+ { LPGENW("&Kick user"), IDM_KICK, MENU_ITEM },
+};
+
+int FacebookProto::GroupchatMenuHook(WPARAM, LPARAM lParam)
+{
+ GCMENUITEMS *gcmi = (GCMENUITEMS *)lParam;
+ if (gcmi == nullptr)
+ return 0;
+
+ if (mir_strcmpi(gcmi->pszModule, m_szModuleName))
+ return 0;
+
+ if (SESSION_INFO *si = g_chatApi.SM_FindSession(gcmi->pszID, gcmi->pszModule)) {
+ if (gcmi->Type == MENU_ON_LOG)
+ Chat_AddMenuItems(gcmi->hMenu, _countof(sttLogListItems), sttLogListItems, &g_plugin);
+ if (gcmi->Type == MENU_ON_NICKLIST)
+ Chat_AddMenuItems(gcmi->hMenu, _countof(sttNickListItems), sttNickListItems, &g_plugin);
+ }
+
+ return 0;
+}
+
+int FacebookProto::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);
+
+ int mid = SendMsg(si->hContact, 0, T2Utf(wszText));
+
+ mir_cslock lck(m_csOwnMessages);
+ for (auto &msg : arOwnMessages)
+ if (msg->reqId == mid)
+ msg->wszText = wszText;
+ }
+ break;
+
+ case GC_USER_PRIVMESS:
+ Chat_SendPrivateMessage(gch);
+ break;
+
+ case GC_USER_LOGMENU:
+ Chat_ProcessLogMenu(si, gch);
+ break;
+
+ case GC_USER_NICKLISTMENU:
+ Chat_ProcessNickMenu(si, gch);
+ break;
+ }
+
+ return 1;
+}
+
+void FacebookProto::Chat_ProcessLogMenu(SESSION_INFO *si, GCHOOK *gch)
+{
+ switch (gch->dwData) {
+ case IDM_INVITE:
+ Chat_InviteUser(si);
+ break;
+
+ case IDM_LEAVE:
+ Chat_Leave(si);
+ break;
+ }
+}
+
+void FacebookProto::Chat_ProcessNickMenu(SESSION_INFO *si, GCHOOK *gch)
+{
+ switch (gch->dwData) {
+ case IDM_KICK:
+ Chat_KickUser(si, gch->ptszUID);
+ break;
+ }
+}
+
+void FacebookProto::Chat_SendPrivateMessage(GCHOOK *gch)
+{
+ auto *pUser = FindUser(_wtoi64(gch->ptszUID));
+ if (pUser == nullptr) {
+ pUser = AddContact(gch->ptszUID, true);
+ setWString(pUser->hContact, "Nick", gch->ptszNick);
+ db_set_b(pUser->hContact, "CList", "Hidden", 1);
+ db_set_dw(pUser->hContact, "Ignore", "Mask1", 0);
+ }
+
+ CallService(MS_MSG_SENDMESSAGE, pUser->hContact, 0);
+}
+
+int FacebookProto::Chat_KickUser(SESSION_INFO *si, const wchar_t *pwszUid)
+{
+ auto *pReq = CreateRequest(FB_API_URL_PARTS, "removeMembers", "DELETE");
+ pReq << WCHAR_PARAM("id", CMStringW(FORMAT, L"t_%s", si->ptszID));
+ if (pwszUid != nullptr) {
+ JSONNode list(JSON_ARRAY);
+ JSONNode user; user << CHAR_PARAM("type", "id") << WCHAR_PARAM("id", pwszUid);
+ list << user;
+ pReq << CHAR_PARAM("to", list.write().c_str());
+ }
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ return reply.error();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void __cdecl DestroyRoomThread(SESSION_INFO *si)
+{
+ ::Sleep(100);
+ Chat_Terminate(si->pszModule, si->ptszID, true);
+}
+
+void FacebookProto::Chat_Leave(SESSION_INFO *si)
+{
+ if (Chat_KickUser(si, nullptr) == 0)
+ mir_forkThread<SESSION_INFO>(DestroyRoomThread, si);
+}
diff --git a/protocols/Facebook/src/http.cpp b/protocols/Facebook/src/http.cpp
index 7bc6e5082c..6f520c0486 100644
--- a/protocols/Facebook/src/http.cpp
+++ b/protocols/Facebook/src/http.cpp
@@ -1,185 +1,185 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-static int CompareParams(const AsyncHttpRequest::Param *p1, const AsyncHttpRequest::Param *p2)
-{
- return strcmp(p1->key, p2->key);
-}
-
-AsyncHttpRequest::AsyncHttpRequest() :
- params(5, CompareParams)
-{
-}
-
-void AsyncHttpRequest::CalcSig()
-{
- CMStringA buf;
- for (auto &it : params)
- buf.AppendFormat("%s=%s", it->key.c_str(), it->val.c_str());
-
- buf.Append(FB_API_SECRET);
-
- char szHash[33];
- uint8_t digest[16];
- mir_md5_hash((uint8_t*)buf.c_str(), buf.GetLength(), digest);
- bin2hex(digest, sizeof(digest), szHash);
- this << CHAR_PARAM("sig", szHash);
-
- for (auto &it : params) {
- if (!m_szParam.IsEmpty())
- m_szParam.AppendChar('&');
- m_szParam.AppendFormat("%s=%s", it->key.c_str(), mir_urlEncode(it->val.c_str()).c_str());
- }
-}
-
-AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const CHAR_PARAM &param)
-{
- pReq->params.insert(new AsyncHttpRequest::Param(param.szName, param.szValue));
- return pReq;
-}
-
-AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM &param)
-{
- char value[40];
- itoa(param.iValue, value, 10);
- pReq->params.insert(new AsyncHttpRequest::Param(param.szName, value));
- 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;
- }
-
- m_errorCode = (*m_root)["error_code"].as_int();
-}
-
-JsonReply::~JsonReply()
-{
- json_delete(m_root);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-AsyncHttpRequest* FacebookProto::CreateRequest(const char *szUrl, const char *szName, const char *szMethod)
-{
- AsyncHttpRequest *pReq = new AsyncHttpRequest();
- pReq->m_szUrl = szUrl;
- pReq->requestType = REQUEST_POST;
- pReq << CHAR_PARAM("api_key", FB_API_KEY)
- << CHAR_PARAM("device_id", m_szDeviceID)
- << CHAR_PARAM("fb_api_req_friendly_name", szName)
- << CHAR_PARAM("format", "json")
- << CHAR_PARAM("method", szMethod);
-
- CMStringA szLocale = getMStringA(DBKEY_LOCALE);
- if (szLocale.IsEmpty())
- szLocale = "en";
- pReq << CHAR_PARAM("locale", szLocale);
-
- if (!m_szAuthToken.IsEmpty()) {
- pReq->flags |= NLHRF_NODUMPHEADERS;
- pReq->AddHeader("Authorization", "OAuth " + m_szAuthToken);
- }
-
- pReq->AddHeader("User-Agent", FB_API_AGENT);
- pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
- return pReq;
-}
-
-AsyncHttpRequest* FacebookProto::CreateRequestGQL(int64_t query_id) {
- const char* szName;
-
- switch (query_id) {
- case FB_API_QUERY_CONTACT:
- szName = "UsersQuery";
- break;
- case FB_API_QUERY_CONTACTS:
- szName = "FetchContactsFullQuery";
- break;
- case FB_API_QUERY_CONTACTS_AFTER:
- szName = "FetchContactsFullWithAfterQuery";
- break;
- case FB_API_QUERY_CONTACTS_DELTA:
- szName = "FetchContactsDeltaQuery";
- break;
- case FB_API_QUERY_STICKER:
- szName = "FetchStickersWithPreviewsQuery";
- break;
- case FB_API_QUERY_THREAD:
- szName = "ThreadQuery";
- break;
- case FB_API_QUERY_SEQ_ID:
- case FB_API_QUERY_THREADS:
- szName = "ThreadListQuery";
- break;
- case FB_API_QUERY_XMA:
- szName = "XMAQuery";
- break;
- default:
- return nullptr;
- }
-
- AsyncHttpRequest* pReq = CreateRequest(FB_API_URL_GQL, szName, "get");
- pReq << INT64_PARAM("query_id", query_id);
- return pReq;
-}
-
-NETLIBHTTPREQUEST* FacebookProto::ExecuteRequest(AsyncHttpRequest *pReq)
-{
- CMStringA str;
-
- pReq->flags |= NLHRF_HTTP11;
- 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);
- }
- }
-
- debugLogA("Executing request:\n%s", pReq->szUrl);
-
- NETLIBHTTPREQUEST *reply = Netlib_HttpTransaction(m_hNetlibUser, pReq);
- delete pReq;
- return reply;
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int CompareParams(const AsyncHttpRequest::Param *p1, const AsyncHttpRequest::Param *p2)
+{
+ return strcmp(p1->key, p2->key);
+}
+
+AsyncHttpRequest::AsyncHttpRequest() :
+ params(5, CompareParams)
+{
+}
+
+void AsyncHttpRequest::CalcSig()
+{
+ CMStringA buf;
+ for (auto &it : params)
+ buf.AppendFormat("%s=%s", it->key.c_str(), it->val.c_str());
+
+ buf.Append(FB_API_SECRET);
+
+ char szHash[33];
+ uint8_t digest[16];
+ mir_md5_hash((uint8_t*)buf.c_str(), buf.GetLength(), digest);
+ bin2hex(digest, sizeof(digest), szHash);
+ this << CHAR_PARAM("sig", szHash);
+
+ for (auto &it : params) {
+ if (!m_szParam.IsEmpty())
+ m_szParam.AppendChar('&');
+ m_szParam.AppendFormat("%s=%s", it->key.c_str(), mir_urlEncode(it->val.c_str()).c_str());
+ }
+}
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const CHAR_PARAM &param)
+{
+ pReq->params.insert(new AsyncHttpRequest::Param(param.szName, param.szValue));
+ return pReq;
+}
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM &param)
+{
+ char value[40];
+ itoa(param.iValue, value, 10);
+ pReq->params.insert(new AsyncHttpRequest::Param(param.szName, value));
+ 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;
+ }
+
+ m_errorCode = (*m_root)["error_code"].as_int();
+}
+
+JsonReply::~JsonReply()
+{
+ json_delete(m_root);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest* FacebookProto::CreateRequest(const char *szUrl, const char *szName, const char *szMethod)
+{
+ AsyncHttpRequest *pReq = new AsyncHttpRequest();
+ pReq->m_szUrl = szUrl;
+ pReq->requestType = REQUEST_POST;
+ pReq << CHAR_PARAM("api_key", FB_API_KEY)
+ << CHAR_PARAM("device_id", m_szDeviceID)
+ << CHAR_PARAM("fb_api_req_friendly_name", szName)
+ << CHAR_PARAM("format", "json")
+ << CHAR_PARAM("method", szMethod);
+
+ CMStringA szLocale = getMStringA(DBKEY_LOCALE);
+ if (szLocale.IsEmpty())
+ szLocale = "en";
+ pReq << CHAR_PARAM("locale", szLocale);
+
+ if (!m_szAuthToken.IsEmpty()) {
+ pReq->flags |= NLHRF_NODUMPHEADERS;
+ pReq->AddHeader("Authorization", "OAuth " + m_szAuthToken);
+ }
+
+ pReq->AddHeader("User-Agent", FB_API_AGENT);
+ pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
+ return pReq;
+}
+
+AsyncHttpRequest* FacebookProto::CreateRequestGQL(int64_t query_id) {
+ const char* szName;
+
+ switch (query_id) {
+ case FB_API_QUERY_CONTACT:
+ szName = "UsersQuery";
+ break;
+ case FB_API_QUERY_CONTACTS:
+ szName = "FetchContactsFullQuery";
+ break;
+ case FB_API_QUERY_CONTACTS_AFTER:
+ szName = "FetchContactsFullWithAfterQuery";
+ break;
+ case FB_API_QUERY_CONTACTS_DELTA:
+ szName = "FetchContactsDeltaQuery";
+ break;
+ case FB_API_QUERY_STICKER:
+ szName = "FetchStickersWithPreviewsQuery";
+ break;
+ case FB_API_QUERY_THREAD:
+ szName = "ThreadQuery";
+ break;
+ case FB_API_QUERY_SEQ_ID:
+ case FB_API_QUERY_THREADS:
+ szName = "ThreadListQuery";
+ break;
+ case FB_API_QUERY_XMA:
+ szName = "XMAQuery";
+ break;
+ default:
+ return nullptr;
+ }
+
+ AsyncHttpRequest* pReq = CreateRequest(FB_API_URL_GQL, szName, "get");
+ pReq << INT64_PARAM("query_id", query_id);
+ return pReq;
+}
+
+NETLIBHTTPREQUEST* FacebookProto::ExecuteRequest(AsyncHttpRequest *pReq)
+{
+ CMStringA str;
+
+ pReq->flags |= NLHRF_HTTP11;
+ 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);
+ }
+ }
+
+ debugLogA("Executing request:\n%s", pReq->szUrl);
+
+ NETLIBHTTPREQUEST *reply = Netlib_HttpTransaction(m_hNetlibUser, pReq);
+ delete pReq;
+ return reply;
+}
diff --git a/protocols/Facebook/src/main.cpp b/protocols/Facebook/src/main.cpp
index 9e308016fc..2b9215e2ed 100644
--- a/protocols/Facebook/src/main.cpp
+++ b/protocols/Facebook/src/main.cpp
@@ -1,72 +1,72 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-#pragma comment(lib, "Rpcrt4.lib")
-
-CMPlugin g_plugin;
-
-bool g_bMessageState;
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-PLUGININFOEX pluginInfoEx = {
- sizeof(PLUGININFOEX),
- __PLUGIN_NAME,
- PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
- __DESCRIPTION,
- __AUTHOR,
- __COPYRIGHT,
- __AUTHORWEB,
- UNICODE_AWARE,
- // {86033E58-A1E3-43AD-AE8E-305E15E72A91}
- { 0xee0543fb, 0x711d, 0x4ac8, { 0xb6, 0xc0, 0x1d, 0xda, 0x48, 0x38, 0x10, 0x7e }}
-};
-
-CMPlugin::CMPlugin() :
- ACCPROTOPLUGIN<FacebookProto>(MODULENAME, pluginInfoEx)
-{
- SetUniqueId(DBKEY_ID);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Interface information
-
-extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Load
-
-static int OnModuleLoaded(WPARAM, LPARAM)
-{
- g_bMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE) != 0;
- return 0;
-}
-
-int CMPlugin::Load()
-{
- HookEvent(ME_SYSTEM_MODULELOAD, OnModuleLoaded);
- HookEvent(ME_SYSTEM_MODULEUNLOAD, OnModuleLoaded);
- HookEvent(ME_SYSTEM_MODULESLOADED, OnModuleLoaded);
-
- // Initialize random generator (used only as fallback in utils)
- return 0;
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+#pragma comment(lib, "Rpcrt4.lib")
+
+CMPlugin g_plugin;
+
+bool g_bMessageState;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+PLUGININFOEX pluginInfoEx = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {86033E58-A1E3-43AD-AE8E-305E15E72A91}
+ { 0xee0543fb, 0x711d, 0x4ac8, { 0xb6, 0xc0, 0x1d, 0xda, 0x48, 0x38, 0x10, 0x7e }}
+};
+
+CMPlugin::CMPlugin() :
+ ACCPROTOPLUGIN<FacebookProto>(MODULENAME, pluginInfoEx)
+{
+ SetUniqueId(DBKEY_ID);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Interface information
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load
+
+static int OnModuleLoaded(WPARAM, LPARAM)
+{
+ g_bMessageState = ServiceExists(MS_MESSAGESTATE_UPDATE) != 0;
+ return 0;
+}
+
+int CMPlugin::Load()
+{
+ HookEvent(ME_SYSTEM_MODULELOAD, OnModuleLoaded);
+ HookEvent(ME_SYSTEM_MODULEUNLOAD, OnModuleLoaded);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModuleLoaded);
+
+ // Initialize random generator (used only as fallback in utils)
+ return 0;
+}
diff --git a/protocols/Facebook/src/mqtt.cpp b/protocols/Facebook/src/mqtt.cpp
index 7c2eb5ef5f..b7fbe0a3bd 100644
--- a/protocols/Facebook/src/mqtt.cpp
+++ b/protocols/Facebook/src/mqtt.cpp
@@ -1,344 +1,344 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-uint8_t *FacebookProto::doZip(size_t cbData, const void *pData, size_t &cbRes)
-{
- size_t dataSize = cbData + 100;
- uint8_t *pRes = (uint8_t *)mir_alloc(dataSize);
-
- z_stream zStreamOut = {};
- deflateInit(&zStreamOut, Z_BEST_COMPRESSION);
- zStreamOut.avail_in = (unsigned)cbData;
- zStreamOut.next_in = (uint8_t *)pData;
- zStreamOut.avail_out = (unsigned)dataSize;
- zStreamOut.next_out = (uint8_t *)pRes;
- deflate(&zStreamOut, Z_FINISH);
- deflateEnd(&zStreamOut);
-
- cbRes = dataSize - zStreamOut.avail_out;
- return pRes;
-}
-
-uint8_t *FacebookProto::doUnzip(size_t cbData, const void *pData, size_t &cbRes)
-{
- size_t dataSize = cbData * 10;
- uint8_t *pRes = (uint8_t *)mir_alloc(dataSize);
-
- z_stream zStreamOut = {};
- inflateInit(&zStreamOut);
- zStreamOut.avail_in = (unsigned)cbData;
- zStreamOut.next_in = (uint8_t *)pData;
- zStreamOut.avail_out = (unsigned)dataSize;
- zStreamOut.next_out = (uint8_t *)pRes;
- int rc = inflate(&zStreamOut, Z_FINISH);
- inflateEnd(&zStreamOut);
-
- switch (rc) {
- case Z_OK:
- case Z_STREAM_END:
- cbRes = dataSize - zStreamOut.avail_out;
- return pRes;
- }
-
- mir_free(pRes);
- cbRes = 0;
- return nullptr;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// MqttMessage class members
-
-MqttMessage::MqttMessage() :
- m_leadingByte(0)
-{
-}
-
-MqttMessage::MqttMessage(FbMqttMessageType type, uint8_t flags)
-{
- m_leadingByte = ((type & 0x0F) << 4) | (flags & 0x0F);
-}
-
-char* MqttMessage::readStr(const uint8_t *&pData) const
-{
- u_short len = ntohs(*(u_short *)pData); pData += sizeof(u_short);
- if (len == 0)
- return nullptr;
-
- char *res = (char*)mir_alloc(len + 1);
- memcpy(res, pData, len);
- res[len] = 0;
- pData += len;
- return res;
-}
-
-void MqttMessage::writeStr(const char *str)
-{
- size_t len = mir_strlen(str);
- writeInt16((uint16_t)len);
- writeBuf(str, len);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// MQTT functions
-
-bool FacebookProto::MqttParse(const MqttMessage &payload)
-{
- auto *pData = (const uint8_t *)payload.data(), *pBeg = pData;
- int flags = payload.getFlags();
- uint16_t mid;
-
- switch (payload.getType()) {
- case FB_MQTT_MESSAGE_TYPE_CONNACK:
- if (pData[1] != 0) { // connection failed;
- int iErrorCode = ntohs(*(u_short *)pData);
- debugLogA("Login failed with error %d", iErrorCode);
-
- if (iErrorCode == 4) { // invalid login/password
- delSetting(DBKEY_TOKEN);
- m_szAuthToken.Empty();
- ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPASSWORD);
- }
- else ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPROTOCOL);
- return false;
- }
-
- OnLoggedIn();
- break;
-
- case FB_MQTT_MESSAGE_TYPE_PUBREL:
- mid = ntohs(*(u_short *)pData);
- pData += 2;
- {
- MqttMessage reply(FB_MQTT_MESSAGE_TYPE_PUBCOMP);
- reply.writeInt16(mid);
- MqttSend(reply);
- }
- break;
-
- case FB_MQTT_MESSAGE_TYPE_PUBLISH:
- char *str = payload.readStr(pData);
-
- if ((flags & FB_MQTT_MESSAGE_FLAG_QOS1) || (flags & FB_MQTT_MESSAGE_FLAG_QOS2)) {
- mid = ntohs(*(u_short *)pData);
- pData += 2;
-
- MqttMessage reply((flags & FB_MQTT_MESSAGE_FLAG_QOS1) ? FB_MQTT_MESSAGE_TYPE_PUBACK : FB_MQTT_MESSAGE_TYPE_PUBREC);
- reply.writeInt16(mid);
- MqttSend(reply);
- }
-
- OnPublish(str, pData, payload.size() - (pData - pBeg));
- mir_free(str);
- break;
- }
-
- return true;
-}
-
-bool FacebookProto::MqttRead(MqttMessage &payload)
-{
- uint8_t b;
- int res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP);
- if (res != 1)
- return false;
-
- payload.m_leadingByte = b;
-
- uint32_t m = 1, remainingBytes = 0;
- do {
- if ((res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP)) != 1)
- return false;
-
- remainingBytes += (b & 0x7F) * m;
- m *= 128;
- } while ((b & 0x80) != 0);
-
- debugLogA("Received message of type=%d, flags=%x, body length=%d", payload.getType(), payload.getFlags(), remainingBytes);
-
- if (remainingBytes != 0) {
- while (remainingBytes > 0) {
- uint8_t buf[1024];
- int size = min(remainingBytes, sizeof(buf));
- if ((res = Netlib_Recv(m_mqttConn, (char *)buf, size)) <= 0)
- return false;
-
- payload.writeBuf(buf, res);
- remainingBytes -= res;
- }
- }
-
- return true;
-}
-
-void FacebookProto::MqttSend(const MqttMessage &payload)
-{
- FbThrift msg;
- msg << payload.m_leadingByte;
- msg.writeIntV(payload.size());
- msg.writeBuf(payload.data(), payload.size());
- Netlib_Send(m_mqttConn, (char*)msg.data(), (unsigned)msg.size());
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// creates initial MQTT will and sends initialization packet
-
-void FacebookProto::MqttLogin()
-{
- uint8_t zeroByte = 0;
- Utils_GetRandom(&m_iMqttId, sizeof(m_iMqttId) / 2);
-
- FbThrift thrift;
- thrift.writeField(FB_THRIFT_TYPE_STRING); // Client identifier
- thrift << m_szClientID;
-
- thrift.writeField(FB_THRIFT_TYPE_STRUCT, 4, 1);
-
- thrift.writeField(FB_THRIFT_TYPE_I64); // User identifier
- thrift.writeInt64(m_uid);
-
- thrift.writeField(FB_THRIFT_TYPE_STRING); // User agent
- thrift << FB_API_MQTT_AGENT;
-
- thrift.writeField(FB_THRIFT_TYPE_I64);
- thrift.writeInt64(23);
- thrift.writeField(FB_THRIFT_TYPE_I64);
- thrift.writeInt64(26);
- thrift.writeField(FB_THRIFT_TYPE_I32);
- thrift.writeInt32(1);
-
- thrift.writeBool(true);
- thrift.writeBool(!m_bLoginInvisible); // visibility
-
- thrift.writeField(FB_THRIFT_TYPE_STRING); // device id
- thrift << m_szDeviceID;
-
- thrift.writeBool(true);
- thrift.writeField(FB_THRIFT_TYPE_I32);
- thrift.writeInt32(1);
- thrift.writeField(FB_THRIFT_TYPE_I32);
- thrift.writeInt32(0);
- thrift.writeField(FB_THRIFT_TYPE_I64);
- thrift.writeInt64(m_iMqttId);
-
- thrift.writeField(FB_THRIFT_TYPE_LIST, 14, 12);
- thrift.writeList(FB_THRIFT_TYPE_I32, 0);
- thrift << zeroByte;
-
- thrift.writeField(FB_THRIFT_TYPE_STRING);
- thrift << m_szAuthToken << zeroByte;
-
- size_t dataSize;
- mir_ptr<uint8_t> pData(doZip(thrift.size(), thrift.data(), dataSize));
-
- uint8_t protocolVersion = 3;
- uint8_t flags = FB_MQTT_CONNECT_FLAG_USER | FB_MQTT_CONNECT_FLAG_PASS | FB_MQTT_CONNECT_FLAG_CLR | FB_MQTT_CONNECT_FLAG_QOS1;
- MqttMessage payload(FB_MQTT_MESSAGE_TYPE_CONNECT);
- payload.writeStr("MQTToT");
- payload << protocolVersion << flags;
- payload.writeInt16(60); // timeout
- payload.writeBuf(pData, dataSize);
- MqttSend(payload);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// various MQTT send commands
-
-void FacebookProto::MqttPing()
-{
- MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PINGREQ, FB_MQTT_MESSAGE_FLAG_QOS1);
- MqttSend(payload);
-}
-
-void FacebookProto::MqttPublish(const char *topic, const JSONNode &value)
-{
- auto str = value.write();
- debugLogA("Publish: <%s> -> <%s>", topic, str.c_str());
-
- size_t dataSize;
- mir_ptr<uint8_t> pData(doZip(str.length(), str.c_str(), dataSize));
-
- MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PUBLISH, FB_MQTT_MESSAGE_FLAG_QOS1);
- payload.writeStr(topic);
- payload.writeInt16(++m_mid);
- payload.writeBuf(pData, dataSize);
- MqttSend(payload);
-}
-
-void FacebookProto::MqttSubscribe(const char *topic, ...)
-{
- uint8_t zeroByte = 0;
-
- MqttMessage payload(FB_MQTT_MESSAGE_TYPE_SUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1);
- payload.writeInt16(++m_mid);
- payload.writeStr(topic);
- payload << zeroByte;
-
- va_list ap;
- va_start(ap, topic);
- while ((topic = va_arg(ap, const char *)) != nullptr) {
- payload.writeStr(topic);
- payload << zeroByte;
- }
- va_end(ap);
-
- MqttSend(payload);
-}
-
-void FacebookProto::MqttUnsubscribe(const char *topic, ...)
-{
- MqttMessage payload(FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1);
- payload.writeInt16(++m_mid);
- payload.writeStr(topic);
-
- va_list ap;
- va_start(ap, topic);
- while ((topic = va_arg(ap, const char *)) != nullptr)
- payload.writeStr(topic);
- va_end(ap);
-
- MqttSend(payload);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// MQTT queue
-
-void FacebookProto::MqttQueueConnect()
-{
- JSONNode query;
- query << INT_PARAM("delta_batch_size", 125) << INT_PARAM("max_deltas_able_to_process", 1000) << INT_PARAM("sync_api_version", 3) << CHAR_PARAM("encoding", "JSON");
- if (m_szSyncToken.IsEmpty()) {
- JSONNode hashes; hashes.set_name("graphql_query_hashes"); hashes << CHAR_PARAM("xma_query_id", __STRINGIFY(FB_API_QUERY_XMA));
-
- JSONNode xma; xma.set_name(__STRINGIFY(FB_API_QUERY_XMA)); xma << CHAR_PARAM("xma_id", "<ID>");
- JSONNode hql; hql.set_name("graphql_query_params"); hql << xma;
-
- JSONNode params; params.set_name("queue_params");
- params << CHAR_PARAM("buzz_on_deltas_enabled", "false") << hashes << hql;
-
- query << INT64_PARAM("initial_titan_sequence_id", m_sid) << CHAR_PARAM("device_id", m_szDeviceID) << INT64_PARAM("entity_fbid", m_uid) << params;
- MqttPublish("/messenger_sync_create_queue", query);
- }
- else {
- query << INT64_PARAM("last_seq_id", m_sid) << CHAR_PARAM("sync_token", m_szSyncToken);
- MqttPublish("/messenger_sync_get_diffs", query);
- }
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+uint8_t *FacebookProto::doZip(size_t cbData, const void *pData, size_t &cbRes)
+{
+ size_t dataSize = cbData + 100;
+ uint8_t *pRes = (uint8_t *)mir_alloc(dataSize);
+
+ z_stream zStreamOut = {};
+ deflateInit(&zStreamOut, Z_BEST_COMPRESSION);
+ zStreamOut.avail_in = (unsigned)cbData;
+ zStreamOut.next_in = (uint8_t *)pData;
+ zStreamOut.avail_out = (unsigned)dataSize;
+ zStreamOut.next_out = (uint8_t *)pRes;
+ deflate(&zStreamOut, Z_FINISH);
+ deflateEnd(&zStreamOut);
+
+ cbRes = dataSize - zStreamOut.avail_out;
+ return pRes;
+}
+
+uint8_t *FacebookProto::doUnzip(size_t cbData, const void *pData, size_t &cbRes)
+{
+ size_t dataSize = cbData * 10;
+ uint8_t *pRes = (uint8_t *)mir_alloc(dataSize);
+
+ z_stream zStreamOut = {};
+ inflateInit(&zStreamOut);
+ zStreamOut.avail_in = (unsigned)cbData;
+ zStreamOut.next_in = (uint8_t *)pData;
+ zStreamOut.avail_out = (unsigned)dataSize;
+ zStreamOut.next_out = (uint8_t *)pRes;
+ int rc = inflate(&zStreamOut, Z_FINISH);
+ inflateEnd(&zStreamOut);
+
+ switch (rc) {
+ case Z_OK:
+ case Z_STREAM_END:
+ cbRes = dataSize - zStreamOut.avail_out;
+ return pRes;
+ }
+
+ mir_free(pRes);
+ cbRes = 0;
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MqttMessage class members
+
+MqttMessage::MqttMessage() :
+ m_leadingByte(0)
+{
+}
+
+MqttMessage::MqttMessage(FbMqttMessageType type, uint8_t flags)
+{
+ m_leadingByte = ((type & 0x0F) << 4) | (flags & 0x0F);
+}
+
+char* MqttMessage::readStr(const uint8_t *&pData) const
+{
+ u_short len = ntohs(*(u_short *)pData); pData += sizeof(u_short);
+ if (len == 0)
+ return nullptr;
+
+ char *res = (char*)mir_alloc(len + 1);
+ memcpy(res, pData, len);
+ res[len] = 0;
+ pData += len;
+ return res;
+}
+
+void MqttMessage::writeStr(const char *str)
+{
+ size_t len = mir_strlen(str);
+ writeInt16((uint16_t)len);
+ writeBuf(str, len);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MQTT functions
+
+bool FacebookProto::MqttParse(const MqttMessage &payload)
+{
+ auto *pData = (const uint8_t *)payload.data(), *pBeg = pData;
+ int flags = payload.getFlags();
+ uint16_t mid;
+
+ switch (payload.getType()) {
+ case FB_MQTT_MESSAGE_TYPE_CONNACK:
+ if (pData[1] != 0) { // connection failed;
+ int iErrorCode = ntohs(*(u_short *)pData);
+ debugLogA("Login failed with error %d", iErrorCode);
+
+ if (iErrorCode == 4) { // invalid login/password
+ delSetting(DBKEY_TOKEN);
+ m_szAuthToken.Empty();
+ ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPASSWORD);
+ }
+ else ProtoBroadcastAck(0, ACKTYPE_LOGIN, ACKRESULT_FAILED, 0, LOGINERR_WRONGPROTOCOL);
+ return false;
+ }
+
+ OnLoggedIn();
+ break;
+
+ case FB_MQTT_MESSAGE_TYPE_PUBREL:
+ mid = ntohs(*(u_short *)pData);
+ pData += 2;
+ {
+ MqttMessage reply(FB_MQTT_MESSAGE_TYPE_PUBCOMP);
+ reply.writeInt16(mid);
+ MqttSend(reply);
+ }
+ break;
+
+ case FB_MQTT_MESSAGE_TYPE_PUBLISH:
+ char *str = payload.readStr(pData);
+
+ if ((flags & FB_MQTT_MESSAGE_FLAG_QOS1) || (flags & FB_MQTT_MESSAGE_FLAG_QOS2)) {
+ mid = ntohs(*(u_short *)pData);
+ pData += 2;
+
+ MqttMessage reply((flags & FB_MQTT_MESSAGE_FLAG_QOS1) ? FB_MQTT_MESSAGE_TYPE_PUBACK : FB_MQTT_MESSAGE_TYPE_PUBREC);
+ reply.writeInt16(mid);
+ MqttSend(reply);
+ }
+
+ OnPublish(str, pData, payload.size() - (pData - pBeg));
+ mir_free(str);
+ break;
+ }
+
+ return true;
+}
+
+bool FacebookProto::MqttRead(MqttMessage &payload)
+{
+ uint8_t b;
+ int res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP);
+ if (res != 1)
+ return false;
+
+ payload.m_leadingByte = b;
+
+ uint32_t m = 1, remainingBytes = 0;
+ do {
+ if ((res = Netlib_Recv(m_mqttConn, (char *)&b, sizeof(b), MSG_NODUMP)) != 1)
+ return false;
+
+ remainingBytes += (b & 0x7F) * m;
+ m *= 128;
+ } while ((b & 0x80) != 0);
+
+ debugLogA("Received message of type=%d, flags=%x, body length=%d", payload.getType(), payload.getFlags(), remainingBytes);
+
+ if (remainingBytes != 0) {
+ while (remainingBytes > 0) {
+ uint8_t buf[1024];
+ int size = min(remainingBytes, sizeof(buf));
+ if ((res = Netlib_Recv(m_mqttConn, (char *)buf, size)) <= 0)
+ return false;
+
+ payload.writeBuf(buf, res);
+ remainingBytes -= res;
+ }
+ }
+
+ return true;
+}
+
+void FacebookProto::MqttSend(const MqttMessage &payload)
+{
+ FbThrift msg;
+ msg << payload.m_leadingByte;
+ msg.writeIntV(payload.size());
+ msg.writeBuf(payload.data(), payload.size());
+ Netlib_Send(m_mqttConn, (char*)msg.data(), (unsigned)msg.size());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// creates initial MQTT will and sends initialization packet
+
+void FacebookProto::MqttLogin()
+{
+ uint8_t zeroByte = 0;
+ Utils_GetRandom(&m_iMqttId, sizeof(m_iMqttId) / 2);
+
+ FbThrift thrift;
+ thrift.writeField(FB_THRIFT_TYPE_STRING); // Client identifier
+ thrift << m_szClientID;
+
+ thrift.writeField(FB_THRIFT_TYPE_STRUCT, 4, 1);
+
+ thrift.writeField(FB_THRIFT_TYPE_I64); // User identifier
+ thrift.writeInt64(m_uid);
+
+ thrift.writeField(FB_THRIFT_TYPE_STRING); // User agent
+ thrift << FB_API_MQTT_AGENT;
+
+ thrift.writeField(FB_THRIFT_TYPE_I64);
+ thrift.writeInt64(23);
+ thrift.writeField(FB_THRIFT_TYPE_I64);
+ thrift.writeInt64(26);
+ thrift.writeField(FB_THRIFT_TYPE_I32);
+ thrift.writeInt32(1);
+
+ thrift.writeBool(true);
+ thrift.writeBool(!m_bLoginInvisible); // visibility
+
+ thrift.writeField(FB_THRIFT_TYPE_STRING); // device id
+ thrift << m_szDeviceID;
+
+ thrift.writeBool(true);
+ thrift.writeField(FB_THRIFT_TYPE_I32);
+ thrift.writeInt32(1);
+ thrift.writeField(FB_THRIFT_TYPE_I32);
+ thrift.writeInt32(0);
+ thrift.writeField(FB_THRIFT_TYPE_I64);
+ thrift.writeInt64(m_iMqttId);
+
+ thrift.writeField(FB_THRIFT_TYPE_LIST, 14, 12);
+ thrift.writeList(FB_THRIFT_TYPE_I32, 0);
+ thrift << zeroByte;
+
+ thrift.writeField(FB_THRIFT_TYPE_STRING);
+ thrift << m_szAuthToken << zeroByte;
+
+ size_t dataSize;
+ mir_ptr<uint8_t> pData(doZip(thrift.size(), thrift.data(), dataSize));
+
+ uint8_t protocolVersion = 3;
+ uint8_t flags = FB_MQTT_CONNECT_FLAG_USER | FB_MQTT_CONNECT_FLAG_PASS | FB_MQTT_CONNECT_FLAG_CLR | FB_MQTT_CONNECT_FLAG_QOS1;
+ MqttMessage payload(FB_MQTT_MESSAGE_TYPE_CONNECT);
+ payload.writeStr("MQTToT");
+ payload << protocolVersion << flags;
+ payload.writeInt16(60); // timeout
+ payload.writeBuf(pData, dataSize);
+ MqttSend(payload);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// various MQTT send commands
+
+void FacebookProto::MqttPing()
+{
+ MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PINGREQ, FB_MQTT_MESSAGE_FLAG_QOS1);
+ MqttSend(payload);
+}
+
+void FacebookProto::MqttPublish(const char *topic, const JSONNode &value)
+{
+ auto str = value.write();
+ debugLogA("Publish: <%s> -> <%s>", topic, str.c_str());
+
+ size_t dataSize;
+ mir_ptr<uint8_t> pData(doZip(str.length(), str.c_str(), dataSize));
+
+ MqttMessage payload(FB_MQTT_MESSAGE_TYPE_PUBLISH, FB_MQTT_MESSAGE_FLAG_QOS1);
+ payload.writeStr(topic);
+ payload.writeInt16(++m_mid);
+ payload.writeBuf(pData, dataSize);
+ MqttSend(payload);
+}
+
+void FacebookProto::MqttSubscribe(const char *topic, ...)
+{
+ uint8_t zeroByte = 0;
+
+ MqttMessage payload(FB_MQTT_MESSAGE_TYPE_SUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1);
+ payload.writeInt16(++m_mid);
+ payload.writeStr(topic);
+ payload << zeroByte;
+
+ va_list ap;
+ va_start(ap, topic);
+ while ((topic = va_arg(ap, const char *)) != nullptr) {
+ payload.writeStr(topic);
+ payload << zeroByte;
+ }
+ va_end(ap);
+
+ MqttSend(payload);
+}
+
+void FacebookProto::MqttUnsubscribe(const char *topic, ...)
+{
+ MqttMessage payload(FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE, FB_MQTT_MESSAGE_FLAG_QOS1);
+ payload.writeInt16(++m_mid);
+ payload.writeStr(topic);
+
+ va_list ap;
+ va_start(ap, topic);
+ while ((topic = va_arg(ap, const char *)) != nullptr)
+ payload.writeStr(topic);
+ va_end(ap);
+
+ MqttSend(payload);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MQTT queue
+
+void FacebookProto::MqttQueueConnect()
+{
+ JSONNode query;
+ query << INT_PARAM("delta_batch_size", 125) << INT_PARAM("max_deltas_able_to_process", 1000) << INT_PARAM("sync_api_version", 3) << CHAR_PARAM("encoding", "JSON");
+ if (m_szSyncToken.IsEmpty()) {
+ JSONNode hashes; hashes.set_name("graphql_query_hashes"); hashes << CHAR_PARAM("xma_query_id", __STRINGIFY(FB_API_QUERY_XMA));
+
+ JSONNode xma; xma.set_name(__STRINGIFY(FB_API_QUERY_XMA)); xma << CHAR_PARAM("xma_id", "<ID>");
+ JSONNode hql; hql.set_name("graphql_query_params"); hql << xma;
+
+ JSONNode params; params.set_name("queue_params");
+ params << CHAR_PARAM("buzz_on_deltas_enabled", "false") << hashes << hql;
+
+ query << INT64_PARAM("initial_titan_sequence_id", m_sid) << CHAR_PARAM("device_id", m_szDeviceID) << INT64_PARAM("entity_fbid", m_uid) << params;
+ MqttPublish("/messenger_sync_create_queue", query);
+ }
+ else {
+ query << INT64_PARAM("last_seq_id", m_sid) << CHAR_PARAM("sync_token", m_szSyncToken);
+ MqttPublish("/messenger_sync_get_diffs", query);
+ }
+}
diff --git a/protocols/Facebook/src/mqtt.h b/protocols/Facebook/src/mqtt.h
index 08849fbc4c..8e1b4964c7 100644
--- a/protocols/Facebook/src/mqtt.h
+++ b/protocols/Facebook/src/mqtt.h
@@ -1,139 +1,139 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#pragma once
-
-#define FACEBOOK_ORCA_AGENT FB_API_MQTT_AGENT
-
-#define FB_THRIFT_TYPE_STOP 0
-#define FB_THRIFT_TYPE_VOID 1
-#define FB_THRIFT_TYPE_BOOL 2
-#define FB_THRIFT_TYPE_BYTE 3
-#define FB_THRIFT_TYPE_DOUBLE 4
-#define FB_THRIFT_TYPE_I16 6
-#define FB_THRIFT_TYPE_I32 8
-#define FB_THRIFT_TYPE_I64 10
-#define FB_THRIFT_TYPE_STRING 11
-#define FB_THRIFT_TYPE_STRUCT 12
-#define FB_THRIFT_TYPE_MAP 13
-#define FB_THRIFT_TYPE_SET 14
-#define FB_THRIFT_TYPE_LIST 15
-
-enum FbMqttMessageType
-{
- FB_MQTT_MESSAGE_TYPE_CONNECT = 1,
- FB_MQTT_MESSAGE_TYPE_CONNACK = 2,
- FB_MQTT_MESSAGE_TYPE_PUBLISH = 3,
- FB_MQTT_MESSAGE_TYPE_PUBACK = 4,
- FB_MQTT_MESSAGE_TYPE_PUBREC = 5,
- FB_MQTT_MESSAGE_TYPE_PUBREL = 6,
- FB_MQTT_MESSAGE_TYPE_PUBCOMP = 7,
- FB_MQTT_MESSAGE_TYPE_SUBSCRIBE = 8,
- FB_MQTT_MESSAGE_TYPE_SUBACK = 9,
- FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE = 10,
- FB_MQTT_MESSAGE_TYPE_UNSUBACK = 11,
- FB_MQTT_MESSAGE_TYPE_PINGREQ = 12,
- FB_MQTT_MESSAGE_TYPE_PINGRESP = 13,
- FB_MQTT_MESSAGE_TYPE_DISCONNECT = 14
-};
-
-class FbThrift
-{
- MBinBuffer m_buf;
-
-public:
- __forceinline void* data() const { return m_buf.data(); }
- __forceinline size_t size() const { return m_buf.length(); }
-
- __forceinline void reset(size_t cbLen, void *pData)
- { m_buf.assign(pData, cbLen);
- }
-
- FbThrift& operator<<(uint8_t);
- FbThrift& operator<<(const char *);
-
- void writeBool(bool value);
- void writeBuf(const void *pData, size_t cbLen);
- void writeInt16(uint16_t value);
- void writeInt32(int32_t value);
- void writeInt64(int64_t value);
- void writeIntV(uint64_t value);
- void writeField(int type);
- void writeField(int type, int id, int lastid);
- void writeList(int iType, int size);
-};
-
-class FbThriftReader : public FbThrift
-{
- bool m_lastBool = false, m_lastBval = false;
- size_t offset = 0;
-
- uint8_t decodeType(int type);
-
-public:
- __forceinline CMStringA rest() const { return CMStringA((char*)data() + offset, int(size() - offset)); }
-
- bool isStop();
- bool readBool(bool &bVal);
- bool readByte(uint8_t &val);
- bool readField(uint8_t &type, uint16_t &id);
- bool readInt16(uint16_t &val);
- bool readInt32(uint32_t &val);
- bool readInt64(uint64_t &val);
- bool readIntV(uint64_t &val);
- bool readList(uint8_t &type, uint32_t &size);
- bool readStr(char *&val); // val must be freed via mir_free()
-};
-
-class MqttMessage : public FbThrift
-{
- friend class FacebookProto;
-
- uint8_t m_leadingByte;
-
-public:
- MqttMessage();
- MqttMessage(FbMqttMessageType type, uint8_t flags = 0);
-
- __forceinline int getType() const
- {
- return m_leadingByte >> 4;
- }
-
- __forceinline int getFlags() const
- { return m_leadingByte & 0x0F;
- }
-
- char* readStr(const uint8_t *&pData) const;
- void writeStr(const char *str);
-};
-
-#define FB_MQTT_CONNECT_FLAG_CLR 0x0002
-#define FB_MQTT_CONNECT_FLAG_WILL 0x0004
-#define FB_MQTT_CONNECT_FLAG_QOS0 0x0000
-#define FB_MQTT_CONNECT_FLAG_QOS1 0x0008
-#define FB_MQTT_CONNECT_FLAG_QOS2 0x0010
-#define FB_MQTT_CONNECT_FLAG_RET 0x0020
-#define FB_MQTT_CONNECT_FLAG_PASS 0x0040
-#define FB_MQTT_CONNECT_FLAG_USER 0x0080
-
-#define FB_MQTT_MESSAGE_FLAG_QOS0 0x0000
-#define FB_MQTT_MESSAGE_FLAG_QOS1 0x0002
-#define FB_MQTT_MESSAGE_FLAG_QOS2 0x0004
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#pragma once
+
+#define FACEBOOK_ORCA_AGENT FB_API_MQTT_AGENT
+
+#define FB_THRIFT_TYPE_STOP 0
+#define FB_THRIFT_TYPE_VOID 1
+#define FB_THRIFT_TYPE_BOOL 2
+#define FB_THRIFT_TYPE_BYTE 3
+#define FB_THRIFT_TYPE_DOUBLE 4
+#define FB_THRIFT_TYPE_I16 6
+#define FB_THRIFT_TYPE_I32 8
+#define FB_THRIFT_TYPE_I64 10
+#define FB_THRIFT_TYPE_STRING 11
+#define FB_THRIFT_TYPE_STRUCT 12
+#define FB_THRIFT_TYPE_MAP 13
+#define FB_THRIFT_TYPE_SET 14
+#define FB_THRIFT_TYPE_LIST 15
+
+enum FbMqttMessageType
+{
+ FB_MQTT_MESSAGE_TYPE_CONNECT = 1,
+ FB_MQTT_MESSAGE_TYPE_CONNACK = 2,
+ FB_MQTT_MESSAGE_TYPE_PUBLISH = 3,
+ FB_MQTT_MESSAGE_TYPE_PUBACK = 4,
+ FB_MQTT_MESSAGE_TYPE_PUBREC = 5,
+ FB_MQTT_MESSAGE_TYPE_PUBREL = 6,
+ FB_MQTT_MESSAGE_TYPE_PUBCOMP = 7,
+ FB_MQTT_MESSAGE_TYPE_SUBSCRIBE = 8,
+ FB_MQTT_MESSAGE_TYPE_SUBACK = 9,
+ FB_MQTT_MESSAGE_TYPE_UNSUBSCRIBE = 10,
+ FB_MQTT_MESSAGE_TYPE_UNSUBACK = 11,
+ FB_MQTT_MESSAGE_TYPE_PINGREQ = 12,
+ FB_MQTT_MESSAGE_TYPE_PINGRESP = 13,
+ FB_MQTT_MESSAGE_TYPE_DISCONNECT = 14
+};
+
+class FbThrift
+{
+ MBinBuffer m_buf;
+
+public:
+ __forceinline void* data() const { return m_buf.data(); }
+ __forceinline size_t size() const { return m_buf.length(); }
+
+ __forceinline void reset(size_t cbLen, void *pData)
+ { m_buf.assign(pData, cbLen);
+ }
+
+ FbThrift& operator<<(uint8_t);
+ FbThrift& operator<<(const char *);
+
+ void writeBool(bool value);
+ void writeBuf(const void *pData, size_t cbLen);
+ void writeInt16(uint16_t value);
+ void writeInt32(int32_t value);
+ void writeInt64(int64_t value);
+ void writeIntV(uint64_t value);
+ void writeField(int type);
+ void writeField(int type, int id, int lastid);
+ void writeList(int iType, int size);
+};
+
+class FbThriftReader : public FbThrift
+{
+ bool m_lastBool = false, m_lastBval = false;
+ size_t offset = 0;
+
+ uint8_t decodeType(int type);
+
+public:
+ __forceinline CMStringA rest() const { return CMStringA((char*)data() + offset, int(size() - offset)); }
+
+ bool isStop();
+ bool readBool(bool &bVal);
+ bool readByte(uint8_t &val);
+ bool readField(uint8_t &type, uint16_t &id);
+ bool readInt16(uint16_t &val);
+ bool readInt32(uint32_t &val);
+ bool readInt64(uint64_t &val);
+ bool readIntV(uint64_t &val);
+ bool readList(uint8_t &type, uint32_t &size);
+ bool readStr(char *&val); // val must be freed via mir_free()
+};
+
+class MqttMessage : public FbThrift
+{
+ friend class FacebookProto;
+
+ uint8_t m_leadingByte;
+
+public:
+ MqttMessage();
+ MqttMessage(FbMqttMessageType type, uint8_t flags = 0);
+
+ __forceinline int getType() const
+ {
+ return m_leadingByte >> 4;
+ }
+
+ __forceinline int getFlags() const
+ { return m_leadingByte & 0x0F;
+ }
+
+ char* readStr(const uint8_t *&pData) const;
+ void writeStr(const char *str);
+};
+
+#define FB_MQTT_CONNECT_FLAG_CLR 0x0002
+#define FB_MQTT_CONNECT_FLAG_WILL 0x0004
+#define FB_MQTT_CONNECT_FLAG_QOS0 0x0000
+#define FB_MQTT_CONNECT_FLAG_QOS1 0x0008
+#define FB_MQTT_CONNECT_FLAG_QOS2 0x0010
+#define FB_MQTT_CONNECT_FLAG_RET 0x0020
+#define FB_MQTT_CONNECT_FLAG_PASS 0x0040
+#define FB_MQTT_CONNECT_FLAG_USER 0x0080
+
+#define FB_MQTT_MESSAGE_FLAG_QOS0 0x0000
+#define FB_MQTT_MESSAGE_FLAG_QOS1 0x0002
+#define FB_MQTT_MESSAGE_FLAG_QOS2 0x0004
diff --git a/protocols/Facebook/src/options.cpp b/protocols/Facebook/src/options.cpp
index 0d8c1eb38a..2ed98b1dfc 100644
--- a/protocols/Facebook/src/options.cpp
+++ b/protocols/Facebook/src/options.cpp
@@ -1,85 +1,85 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-class CFacebookAccOptsDlg : public CFBDlgBase
-{
- CCtrlEdit edtGroup;
- CCtrlCheck chkEnableChats, chkHideChats, chkKeepUnread, chkLoginInvis, chkLoadAll;
- CCtrlHyperlink linkMainPage;
-
-public:
- CFacebookAccOptsDlg(FacebookProto *pThis) :
- CFBDlgBase(pThis, IDD_OPTIONS),
- edtGroup(this, IDC_GROUP),
- chkLoadAll(this, IDC_LOADALL),
- chkHideChats(this, IDC_HIDECHATS),
- chkKeepUnread(this, IDC_KEEP_UNREAD),
- chkLoginInvis(this, IDC_INVIS_LOGIN),
- chkEnableChats(this, IDC_ENABLECHATS),
- linkMainPage(this, IDC_NEWACCOUNTLINK, "https://www.facebook.com")
- {
- CreateLink(edtGroup, pThis->m_wszDefaultGroup);
- CreateLink(chkLoadAll, pThis->m_bLoadAll);
- CreateLink(chkHideChats, pThis->m_bHideGroupchats);
- CreateLink(chkKeepUnread, pThis->m_bKeepUnread);
- CreateLink(chkLoginInvis, pThis->m_bLoginInvisible);
- CreateLink(chkEnableChats, pThis->m_bUseGroupchats);
- }
-
- bool OnInitDialog() override
- {
- ptrA login(m_proto->getStringA(DBKEY_LOGIN));
- if (login != nullptr)
- SetDlgItemTextA(m_hwnd, IDC_UN, login);
-
- ptrA password(m_proto->getStringA(DBKEY_PASS));
- if (password != nullptr)
- SetDlgItemTextA(m_hwnd, IDC_PW, password);
- return true;
- }
-
- bool OnApply() override
- {
- char str[128];
-
- GetDlgItemTextA(m_hwnd, IDC_UN, str, _countof(str));
- m_proto->setString(DBKEY_LOGIN, str);
-
- GetDlgItemTextA(m_hwnd, IDC_PW, str, _countof(str));
- m_proto->setString(DBKEY_PASS, str);
- return true;
- }
-};
-
-int FacebookProto::OnOptionsInit(WPARAM wParam, LPARAM)
-{
- OPTIONSDIALOGPAGE odp = {};
- odp.position = -790000000;
- odp.szTitle.w = m_tszUserName;
- odp.szGroup.w = LPGENW("Network");
- odp.flags = ODPF_UNICODE;
-
- odp.szTab.w = LPGENW("Account");
- odp.pDialog = new CFacebookAccOptsDlg(this);
- g_plugin.addOptions(wParam, &odp);
- return 0;
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+class CFacebookAccOptsDlg : public CFBDlgBase
+{
+ CCtrlEdit edtGroup;
+ CCtrlCheck chkEnableChats, chkHideChats, chkKeepUnread, chkLoginInvis, chkLoadAll;
+ CCtrlHyperlink linkMainPage;
+
+public:
+ CFacebookAccOptsDlg(FacebookProto *pThis) :
+ CFBDlgBase(pThis, IDD_OPTIONS),
+ edtGroup(this, IDC_GROUP),
+ chkLoadAll(this, IDC_LOADALL),
+ chkHideChats(this, IDC_HIDECHATS),
+ chkKeepUnread(this, IDC_KEEP_UNREAD),
+ chkLoginInvis(this, IDC_INVIS_LOGIN),
+ chkEnableChats(this, IDC_ENABLECHATS),
+ linkMainPage(this, IDC_NEWACCOUNTLINK, "https://www.facebook.com")
+ {
+ CreateLink(edtGroup, pThis->m_wszDefaultGroup);
+ CreateLink(chkLoadAll, pThis->m_bLoadAll);
+ CreateLink(chkHideChats, pThis->m_bHideGroupchats);
+ CreateLink(chkKeepUnread, pThis->m_bKeepUnread);
+ CreateLink(chkLoginInvis, pThis->m_bLoginInvisible);
+ CreateLink(chkEnableChats, pThis->m_bUseGroupchats);
+ }
+
+ bool OnInitDialog() override
+ {
+ ptrA login(m_proto->getStringA(DBKEY_LOGIN));
+ if (login != nullptr)
+ SetDlgItemTextA(m_hwnd, IDC_UN, login);
+
+ ptrA password(m_proto->getStringA(DBKEY_PASS));
+ if (password != nullptr)
+ SetDlgItemTextA(m_hwnd, IDC_PW, password);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ char str[128];
+
+ GetDlgItemTextA(m_hwnd, IDC_UN, str, _countof(str));
+ m_proto->setString(DBKEY_LOGIN, str);
+
+ GetDlgItemTextA(m_hwnd, IDC_PW, str, _countof(str));
+ m_proto->setString(DBKEY_PASS, str);
+ return true;
+ }
+};
+
+int FacebookProto::OnOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = -790000000;
+ odp.szTitle.w = m_tszUserName;
+ odp.szGroup.w = LPGENW("Network");
+ odp.flags = ODPF_UNICODE;
+
+ odp.szTab.w = LPGENW("Account");
+ odp.pDialog = new CFacebookAccOptsDlg(this);
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
} \ No newline at end of file
diff --git a/protocols/Facebook/src/proto.cpp b/protocols/Facebook/src/proto.cpp
index 7a1d8b1c93..db2d401e21 100644
--- a/protocols/Facebook/src/proto.cpp
+++ b/protocols/Facebook/src/proto.cpp
@@ -1,302 +1,302 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-static int CompareUsers(const FacebookUser *p1, const FacebookUser *p2)
-{
- if (p1->id == p2->id)
- return 0;
-
- return (p1->id < p2->id) ? -1 : 1;
-}
-
-static int CompareMessages(const COwnMessage *p1, const COwnMessage *p2)
-{
- if (p1->msgId == p2->msgId)
- return 0;
-
- return (p1->msgId < p2->msgId) ? -1 : 1;
-}
-
-FacebookProto::FacebookProto(const char *proto_name, const wchar_t *username) :
- PROTO<FacebookProto>(proto_name, username),
- m_impl(*this),
- m_users(50, CompareUsers),
- arOwnMessages(1, CompareMessages),
- m_bLoadAll(this, "LoadAllContacts", false),
- m_bKeepUnread(this, "KeepUnread", false),
- m_bUseBigAvatars(this, "UseBigAvatars", true),
- m_bUseGroupchats(this, "UseGroupChats", true),
- m_bHideGroupchats(this, "HideGroupChats", true),
- m_bLoginInvisible(this, "LoginInvisible", false),
- m_wszDefaultGroup(this, "DefaultGroup", L"Facebook")
-{
- // to upgrade previous settings
- if (getByte("Compatibility") < 1) {
- setByte("Compatibility", 1);
- delSetting(DBKEY_DEVICE_ID);
- }
-
- m_szDeviceID = getMStringA(DBKEY_DEVICE_ID);
- if (m_szDeviceID.IsEmpty()) {
- UUID deviceId;
- UuidCreate(&deviceId);
- RPC_CSTR szId;
- UuidToStringA(&deviceId, &szId);
- m_szDeviceID = szId;
- setString(DBKEY_DEVICE_ID, m_szDeviceID);
- RpcStringFreeA(&szId);
- }
-
- m_szClientID = getMStringA(DBKEY_CLIENT_ID);
- if (m_szClientID.IsEmpty()) {
- for (int i = 0; i < 20; i++) {
- uint32_t dwRandon;
- Utils_GetRandom(&dwRandon, sizeof(dwRandon));
- int c = dwRandon % 62;
- if (c >= 0 && c < 26)
- c += 'a';
- else if (c >= 26 && c < 52)
- c += 'A' - 26;
- else if (c >= 52 && c < 62)
- c += '0' - 52;
- m_szClientID.AppendChar(c);
- }
- setString(DBKEY_CLIENT_ID, m_szClientID);
- }
-
- m_uid = _atoi64(getMStringA(DBKEY_ID));
- m_sid = _atoi64(getMStringA(DBKEY_SID));
- m_szSyncToken = getMStringA(DBKEY_SYNC_TOKEN);
-
- // Create standard network connection
- NETLIBUSER nlu = {};
- nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
- nlu.szSettingsModule = m_szModuleName;
- nlu.szDescriptiveName.w = m_tszUserName;
- m_hNetlibUser = Netlib_RegisterUser(&nlu);
-
- db_set_resident(m_szModuleName, "UpdateNeeded");
-
- // Services
- CreateProtoService(PS_CREATEACCMGRUI, &FacebookProto::SvcCreateAccMgrUI);
- CreateProtoService(PS_GETAVATARINFO, &FacebookProto::GetAvatarInfo);
- CreateProtoService(PS_GETAVATARCAPS, &FacebookProto::GetAvatarCaps);
-
- // Events
- HookProtoEvent(ME_GC_EVENT, &FacebookProto::GroupchatEventHook);
- HookProtoEvent(ME_GC_BUILDMENU, &FacebookProto::GroupchatMenuHook);
- HookProtoEvent(ME_OPT_INITIALISE, &FacebookProto::OnOptionsInit);
- HookProtoEvent(ME_DB_EVENT_MARKED_READ, &FacebookProto::OnMarkedRead);
-
- // Group chats
- GCREGISTER gcr = {};
- gcr.dwFlags = GC_TYPNOTIF;
- gcr.ptszDispName = m_tszUserName;
- gcr.pszModule = m_szModuleName;
- Chat_Register(&gcr);
-}
-
-FacebookProto::~FacebookProto()
-{
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// protocol events
-
-void FacebookProto::OnContactAdded(MCONTACT hContact)
-{
- __int64 userId = _atoi64(getMStringA(hContact, DBKEY_ID));
- if (userId && !FindUser(userId)) {
- mir_cslock lck(m_csUsers);
- m_users.insert(new FacebookUser(userId, hContact));
- }
-}
-
-void FacebookProto::OnModulesLoaded()
-{
- VARSW wszCache(L"%miranda_avatarcache%");
-
- CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers\\*.png", wszCache.get(), m_szModuleName);
- SMADD_CONT cont = { 2, m_szModuleName, wszPath };
- CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
-
- wszPath.Format(L"%s\\%S\\Stickers\\*.webp", wszCache.get(), m_szModuleName);
- cont.path = wszPath;
- CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
-
- // contacts cache
- for (auto &cc : AccContacts()) {
- CMStringA szId(getMStringA(cc, DBKEY_ID));
- if (!szId.IsEmpty())
- m_users.insert(new FacebookUser(_atoi64(szId), cc, isChatRoom(cc)));
- }
-
- // Default group
- Clist_GroupCreate(0, m_wszDefaultGroup);
-}
-
-void FacebookProto::OnShutdown()
-{
- if (m_mqttConn != nullptr)
- Netlib_Shutdown(m_mqttConn);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-MCONTACT FacebookProto::AddToList(int, PROTOSEARCHRESULT *psr)
-{
- if (!mir_wstrlen(psr->id.w))
- return 0;
-
- if (auto *pUser = FindUser(_wtoi64(psr->id.w)))
- return pUser->hContact;
-
- MCONTACT hContact = db_add_contact();
- setWString(hContact, DBKEY_ID, psr->id.w);
- Proto_AddToContact(hContact, m_szModuleName);
- return hContact;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-INT_PTR FacebookProto::GetCaps(int type, MCONTACT)
-{
- switch (type) {
- case PFLAGNUM_1:
- {
- DWORD_PTR flags = PF1_IM | PF1_CHAT | PF1_SERVERCLIST | PF1_AUTHREQ;
-
- if (getByte(DBKEY_SET_MIRANDA_STATUS))
- return flags |= PF1_MODEMSG;
- else
- return flags |= PF1_MODEMSGRECV;
- }
-
- case PFLAGNUM_2:
- return PF2_ONLINE | PF2_SHORTAWAY | PF2_INVISIBLE | PF2_IDLE;
-
- case PFLAGNUM_3:
- if (getByte(DBKEY_SET_MIRANDA_STATUS))
- return PF2_ONLINE; // | PF2_SHORTAWAY;
- else
- return 0;
-
- case PFLAGNUM_4:
- return PF4_NOCUSTOMAUTH | PF4_AVATARS | PF4_SUPPORTTYPING | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_READNOTIFY;
-
- case PFLAG_MAXLENOFMESSAGE:
- return FACEBOOK_MESSAGE_LIMIT;
-
- case PFLAG_UNIQUEIDTEXT:
- return (INT_PTR) L"Facebook ID";
- }
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int FacebookProto::SendMsg(MCONTACT hContact, int, const char *pszSrc)
-{
- if (!m_bOnline) {
- ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)TranslateT("Protocol is offline or user isn't authorized yet"));
- return 1;
- }
-
- CMStringA userId(getMStringA(hContact, DBKEY_ID));
-
- __int64 msgId;
- Utils_GetRandom(&msgId, sizeof(msgId));
- msgId = abs(msgId);
-
- JSONNode root; root << CHAR_PARAM("body", pszSrc) << INT64_PARAM("msgid", msgId) << INT64_PARAM("sender_fbid", m_uid) << CHAR_PARAM("to", userId);
- MqttPublish("/send_message2", root);
-
- mir_cslock lck(m_csOwnMessages);
- arOwnMessages.insert(new COwnMessage(msgId, m_mid, hContact));
- return m_mid;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int FacebookProto::SetStatus(int iNewStatus)
-{
- if (iNewStatus != ID_STATUS_OFFLINE && IsStatusConnecting(m_iStatus)) {
- debugLogA("=== Status is already connecting, no change");
- return 0;
- }
-
- // Routing statuses not supported by Facebook
- switch (iNewStatus) {
- case ID_STATUS_ONLINE:
- case ID_STATUS_OFFLINE:
- break;
-
- default:
- iNewStatus = ID_STATUS_AWAY;
- break;
- }
-
- if (m_iStatus == iNewStatus) {
- debugLogA("=== Statuses are same, no change");
- return 0;
- }
-
- m_iDesiredStatus = iNewStatus;
-
- int iOldStatus = m_iStatus;
-
- // log off & free all resources
- if (iNewStatus == ID_STATUS_OFFLINE) {
- OnShutdown();
-
- m_iStatus = ID_STATUS_OFFLINE;
- }
- else if (m_iStatus == ID_STATUS_OFFLINE) { // we gonna connect
- debugLogA("*** Beginning SignOn process");
-
- m_iStatus = ID_STATUS_CONNECTING;
-
- ForkThread(&FacebookProto::ServerThread);
- }
- else m_iStatus = iNewStatus;
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
- return 0;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-int FacebookProto::UserIsTyping(MCONTACT hContact, int type)
-{
- JSONNode root; root << INT_PARAM("state", type == PROTOTYPE_SELFTYPING_ON) << CHAR_PARAM("to", getMStringA(hContact, DBKEY_ID));
- MqttPublish("/typing", root);
- return 0;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Services
-
-INT_PTR FacebookProto::SvcCreateAccMgrUI(WPARAM, LPARAM lParam)
-{
- return (INT_PTR) CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FACEBOOKACCOUNT),
- (HWND) lParam, FBAccountProc, (LPARAM) this);
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+static int CompareUsers(const FacebookUser *p1, const FacebookUser *p2)
+{
+ if (p1->id == p2->id)
+ return 0;
+
+ return (p1->id < p2->id) ? -1 : 1;
+}
+
+static int CompareMessages(const COwnMessage *p1, const COwnMessage *p2)
+{
+ if (p1->msgId == p2->msgId)
+ return 0;
+
+ return (p1->msgId < p2->msgId) ? -1 : 1;
+}
+
+FacebookProto::FacebookProto(const char *proto_name, const wchar_t *username) :
+ PROTO<FacebookProto>(proto_name, username),
+ m_impl(*this),
+ m_users(50, CompareUsers),
+ arOwnMessages(1, CompareMessages),
+ m_bLoadAll(this, "LoadAllContacts", false),
+ m_bKeepUnread(this, "KeepUnread", false),
+ m_bUseBigAvatars(this, "UseBigAvatars", true),
+ m_bUseGroupchats(this, "UseGroupChats", true),
+ m_bHideGroupchats(this, "HideGroupChats", true),
+ m_bLoginInvisible(this, "LoginInvisible", false),
+ m_wszDefaultGroup(this, "DefaultGroup", L"Facebook")
+{
+ // to upgrade previous settings
+ if (getByte("Compatibility") < 1) {
+ setByte("Compatibility", 1);
+ delSetting(DBKEY_DEVICE_ID);
+ }
+
+ m_szDeviceID = getMStringA(DBKEY_DEVICE_ID);
+ if (m_szDeviceID.IsEmpty()) {
+ UUID deviceId;
+ UuidCreate(&deviceId);
+ RPC_CSTR szId;
+ UuidToStringA(&deviceId, &szId);
+ m_szDeviceID = szId;
+ setString(DBKEY_DEVICE_ID, m_szDeviceID);
+ RpcStringFreeA(&szId);
+ }
+
+ m_szClientID = getMStringA(DBKEY_CLIENT_ID);
+ if (m_szClientID.IsEmpty()) {
+ for (int i = 0; i < 20; i++) {
+ uint32_t dwRandon;
+ Utils_GetRandom(&dwRandon, sizeof(dwRandon));
+ int c = dwRandon % 62;
+ if (c >= 0 && c < 26)
+ c += 'a';
+ else if (c >= 26 && c < 52)
+ c += 'A' - 26;
+ else if (c >= 52 && c < 62)
+ c += '0' - 52;
+ m_szClientID.AppendChar(c);
+ }
+ setString(DBKEY_CLIENT_ID, m_szClientID);
+ }
+
+ m_uid = _atoi64(getMStringA(DBKEY_ID));
+ m_sid = _atoi64(getMStringA(DBKEY_SID));
+ m_szSyncToken = getMStringA(DBKEY_SYNC_TOKEN);
+
+ // Create standard network connection
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.szDescriptiveName.w = m_tszUserName;
+ m_hNetlibUser = Netlib_RegisterUser(&nlu);
+
+ db_set_resident(m_szModuleName, "UpdateNeeded");
+
+ // Services
+ CreateProtoService(PS_CREATEACCMGRUI, &FacebookProto::SvcCreateAccMgrUI);
+ CreateProtoService(PS_GETAVATARINFO, &FacebookProto::GetAvatarInfo);
+ CreateProtoService(PS_GETAVATARCAPS, &FacebookProto::GetAvatarCaps);
+
+ // Events
+ HookProtoEvent(ME_GC_EVENT, &FacebookProto::GroupchatEventHook);
+ HookProtoEvent(ME_GC_BUILDMENU, &FacebookProto::GroupchatMenuHook);
+ HookProtoEvent(ME_OPT_INITIALISE, &FacebookProto::OnOptionsInit);
+ HookProtoEvent(ME_DB_EVENT_MARKED_READ, &FacebookProto::OnMarkedRead);
+
+ // Group chats
+ GCREGISTER gcr = {};
+ gcr.dwFlags = GC_TYPNOTIF;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ Chat_Register(&gcr);
+}
+
+FacebookProto::~FacebookProto()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// protocol events
+
+void FacebookProto::OnContactAdded(MCONTACT hContact)
+{
+ __int64 userId = _atoi64(getMStringA(hContact, DBKEY_ID));
+ if (userId && !FindUser(userId)) {
+ mir_cslock lck(m_csUsers);
+ m_users.insert(new FacebookUser(userId, hContact));
+ }
+}
+
+void FacebookProto::OnModulesLoaded()
+{
+ VARSW wszCache(L"%miranda_avatarcache%");
+
+ CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers\\*.png", wszCache.get(), m_szModuleName);
+ SMADD_CONT cont = { 2, m_szModuleName, wszPath };
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
+
+ wszPath.Format(L"%s\\%S\\Stickers\\*.webp", wszCache.get(), m_szModuleName);
+ cont.path = wszPath;
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
+
+ // contacts cache
+ for (auto &cc : AccContacts()) {
+ CMStringA szId(getMStringA(cc, DBKEY_ID));
+ if (!szId.IsEmpty())
+ m_users.insert(new FacebookUser(_atoi64(szId), cc, isChatRoom(cc)));
+ }
+
+ // Default group
+ Clist_GroupCreate(0, m_wszDefaultGroup);
+}
+
+void FacebookProto::OnShutdown()
+{
+ if (m_mqttConn != nullptr)
+ Netlib_Shutdown(m_mqttConn);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MCONTACT FacebookProto::AddToList(int, PROTOSEARCHRESULT *psr)
+{
+ if (!mir_wstrlen(psr->id.w))
+ return 0;
+
+ if (auto *pUser = FindUser(_wtoi64(psr->id.w)))
+ return pUser->hContact;
+
+ MCONTACT hContact = db_add_contact();
+ setWString(hContact, DBKEY_ID, psr->id.w);
+ Proto_AddToContact(hContact, m_szModuleName);
+ return hContact;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR FacebookProto::GetCaps(int type, MCONTACT)
+{
+ switch (type) {
+ case PFLAGNUM_1:
+ {
+ DWORD_PTR flags = PF1_IM | PF1_CHAT | PF1_SERVERCLIST | PF1_AUTHREQ;
+
+ if (getByte(DBKEY_SET_MIRANDA_STATUS))
+ return flags |= PF1_MODEMSG;
+ else
+ return flags |= PF1_MODEMSGRECV;
+ }
+
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_INVISIBLE | PF2_IDLE;
+
+ case PFLAGNUM_3:
+ if (getByte(DBKEY_SET_MIRANDA_STATUS))
+ return PF2_ONLINE; // | PF2_SHORTAWAY;
+ else
+ return 0;
+
+ case PFLAGNUM_4:
+ return PF4_NOCUSTOMAUTH | PF4_AVATARS | PF4_SUPPORTTYPING | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE | PF4_READNOTIFY;
+
+ case PFLAG_MAXLENOFMESSAGE:
+ return FACEBOOK_MESSAGE_LIMIT;
+
+ case PFLAG_UNIQUEIDTEXT:
+ return (INT_PTR) L"Facebook ID";
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int FacebookProto::SendMsg(MCONTACT hContact, int, const char *pszSrc)
+{
+ if (!m_bOnline) {
+ ProtoBroadcastAsync(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)1, (LPARAM)TranslateT("Protocol is offline or user isn't authorized yet"));
+ return 1;
+ }
+
+ CMStringA userId(getMStringA(hContact, DBKEY_ID));
+
+ __int64 msgId;
+ Utils_GetRandom(&msgId, sizeof(msgId));
+ msgId = abs(msgId);
+
+ JSONNode root; root << CHAR_PARAM("body", pszSrc) << INT64_PARAM("msgid", msgId) << INT64_PARAM("sender_fbid", m_uid) << CHAR_PARAM("to", userId);
+ MqttPublish("/send_message2", root);
+
+ mir_cslock lck(m_csOwnMessages);
+ arOwnMessages.insert(new COwnMessage(msgId, m_mid, hContact));
+ return m_mid;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int FacebookProto::SetStatus(int iNewStatus)
+{
+ if (iNewStatus != ID_STATUS_OFFLINE && IsStatusConnecting(m_iStatus)) {
+ debugLogA("=== Status is already connecting, no change");
+ return 0;
+ }
+
+ // Routing statuses not supported by Facebook
+ switch (iNewStatus) {
+ case ID_STATUS_ONLINE:
+ case ID_STATUS_OFFLINE:
+ break;
+
+ default:
+ iNewStatus = ID_STATUS_AWAY;
+ break;
+ }
+
+ if (m_iStatus == iNewStatus) {
+ debugLogA("=== Statuses are same, no change");
+ return 0;
+ }
+
+ m_iDesiredStatus = iNewStatus;
+
+ int iOldStatus = m_iStatus;
+
+ // log off & free all resources
+ if (iNewStatus == ID_STATUS_OFFLINE) {
+ OnShutdown();
+
+ m_iStatus = ID_STATUS_OFFLINE;
+ }
+ else if (m_iStatus == ID_STATUS_OFFLINE) { // we gonna connect
+ debugLogA("*** Beginning SignOn process");
+
+ m_iStatus = ID_STATUS_CONNECTING;
+
+ ForkThread(&FacebookProto::ServerThread);
+ }
+ else m_iStatus = iNewStatus;
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)iOldStatus, m_iStatus);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+int FacebookProto::UserIsTyping(MCONTACT hContact, int type)
+{
+ JSONNode root; root << INT_PARAM("state", type == PROTOTYPE_SELFTYPING_ON) << CHAR_PARAM("to", getMStringA(hContact, DBKEY_ID));
+ MqttPublish("/typing", root);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Services
+
+INT_PTR FacebookProto::SvcCreateAccMgrUI(WPARAM, LPARAM lParam)
+{
+ return (INT_PTR) CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FACEBOOKACCOUNT),
+ (HWND) lParam, FBAccountProc, (LPARAM) this);
+}
diff --git a/protocols/Facebook/src/proto.h b/protocols/Facebook/src/proto.h
index 14dc343d18..7fbeebd2cb 100644
--- a/protocols/Facebook/src/proto.h
+++ b/protocols/Facebook/src/proto.h
@@ -1,557 +1,557 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#pragma once
-
-
-/**
- * FB_API_AHOST:
- *
- * The HTTP host for the Facebook API.
- */
-#define FB_API_AHOST "https://api.facebook.com"
-
-/**
- * FB_API_BHOST:
- *
- * The HTTP host for the Facebook BAPI.
- */
-#define FB_API_BHOST "https://b-api.facebook.com"
-
-/**
- * FB_API_GHOST:
- *
- * The HTTP host for the Facebook Graph API.
- */
-#define FB_API_GHOST "https://graph.facebook.com"
-
-/**
- * FB_API_WHOST:
- *
- * The HTTP host for the Facebook website.
- */
-#define FB_API_WHOST "https://www.facebook.com"
-
-/**
- * FB_API_FBRPC_PREFIX
- *
- * The fbrpc URL prefix used in links shared from the mobile app.
- */
-#define FB_API_FBRPC_PREFIX "fbrpc://facebook/nativethirdparty"
-
-/**
- * FB_API_KEY:
- *
- * The Facebook API key.
- */
-#define FB_API_KEY "256002347743983"
-
-/**
- * FB_API_SECRET:
- *
- * The Facebook API secret.
- */
-#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895"
-
-/**
- * FB_ORCA_AGENT
- *
- * The part of the user agent that looks like the official client, since the
- * server started checking this.
- */
-
-#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]"
-
-/**
- * FB_API_AGENT:
- *
- * The HTTP User-Agent header.
- */
-#define FB_API_AGENT "Facebook plugin / Purple / 0.9.6 " FB_ORCA_AGENT
-
-/**
- * FB_API_MQTT_AGENT
- *
- * The client information string sent in the MQTT CONNECT message
- */
-
-#define FB_API_MQTT_AGENT FB_API_AGENT
-
-/**
- * FB_API_URL_ATTACH:
- *
- * The URL for attachment URL requests.
- */
-#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.getAttachment"
-//#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.attachmentRedirect"
-
-/**
- * FB_API_URL_AUTH:
- *
- * The URL for authentication requests.
- */
-#define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login"
-
-/**
- * FB_API_URL_GQL:
- *
- * The URL for GraphQL requests.
- */
-#define FB_API_URL_GQL FB_API_GHOST "/graphql"
-
-/**
- * FB_API_URL_MESSAGES:
- *
- * The URL for linking message threads.
- */
-#define FB_API_URL_MESSAGES FB_API_WHOST "/messages"
-
-/**
- * FB_API_URL_PARTS:
- *
- * The URL for participant management requests.
- */
-#define FB_API_URL_PARTS FB_API_GHOST "/participants"
-
-/**
- * FB_API_URL_THREADS:
- *
- * The URL for thread management requests.
- */
-#define FB_API_URL_THREADS FB_API_GHOST "/me/group_threads"
-
-/**
- * FB_API_URL_TOPIC:
- *
- * The URL for thread topic requests.
- */
-#define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname"
-
-/**
- * FB_API_QUERY_CONTACT:
- *
- * The query hash for the `UsersQuery`.
- *
- * Key mapping:
- * 0: user_fbids
- * 1: include_full_user_info
- * 2: profile_pic_large_size
- * 3: profile_pic_medium_size
- * 4: profile_pic_small_size
- */
-#define FB_API_QUERY_CONTACT 10153915107411729
-
-/**
- * FB_API_QUERY_CONTACTS:
- *
- * The query hash for the `FetchContactsFullQuery`.
- *
- * Key mapping:
- * 0: profile_types
- * 1: limit
- * 2: big_img_size
- * 3: huge_img_size
- * 4: small_img_size
- */
-#define FB_API_QUERY_CONTACTS 10154444360806729
-
-/**
- * FB_API_QUERY_CONTACTS_AFTER:
- *
- * The query hash for the `FetchContactsFullWithAfterQuery`.
- *
- * Key mapping:
- * 0: profile_types
- * 1: after
- * 2: limit
- * 3: big_img_size
- * 4: huge_img_size
- * 5: small_img_size
- */
-#define FB_API_QUERY_CONTACTS_AFTER 10154444360816729
-
-
-/**
- * FB_API_QUERY_CONTACTS_DELTA:
- *
- * The query hash for the `FetchContactsDeltaQuery`.
- *
- * Key mapping:
- * 0: after
- * 1: profile_types
- * 2: limit
- * 3: big_img_size
- * 4: huge_img_size
- * 5: small_img_size
- */
-#define FB_API_QUERY_CONTACTS_DELTA 10154444360801729
-
-/**
- * FB_API_QUERY_STICKER:
- *
- * The query hash for the `FetchStickersWithPreviewsQuery`.
- *
- * Key mapping:
- * 0: sticker_ids
- * 1: media_type
- * 2: preview_size
- * 3: scaling_factor
- * 4: animated_media_type
- */
-#define FB_API_QUERY_STICKER 10152877994321729
-
-/**
- * FB_API_QUERY_THREAD:
- *
- * The query hash for the `ThreadQuery`.
- *
- * Key mapping:
- * 0: thread_ids
- * 1: verification_type
- * 2: hash_key
- * 3: small_preview_size
- * 4: large_preview_size
- * 5: item_count
- * 6: event_count
- * 7: full_screen_height
- * 8: full_screen_width
- * 9: medium_preview_size
- * 10: fetch_users_separately
- * 11: include_message_info
- * 12: msg_count
- * 13: include_full_user_info
- * 14: profile_pic_large_size
- * 15: profile_pic_medium_size
- * 16: profile_pic_small_size
- */
-#define FB_API_QUERY_THREAD 10153919752036729
-
-/**
- * FB_API_QUERY_THREADS:
- *
- * The query hash for the `ThreadListQuery`.
- *
- * Key mapping:
- * 0: folder_tag (INBOX, PENDING, ARCHIVED, OTHER, UNREAD)
- * 1: thread_count (result is sorted from newest to oldest when parameter is sent)
- * 2: include_thread_info
- * 3: verification_type
- * 4: hash_key
- * 5: small_preview_size
- * 6: large_preview_size
- * 7: item_count
- * 8: event_count
- * 9: full_screen_height
- * 10: full_screen_width
- * 11: medium_preview_size
- * 12: fetch_users_separately
- * 13: include_message_info
- * 14: msg_count
- * 15: UNKNOWN
- * 16: profile_pic_large_size
- * 17: profile_pic_medium_size
- * 18: profile_pic_small_size
- */
-#define FB_API_QUERY_THREADS 10153919752026729
-
-/**
- * FB_API_QUERY_SEQ_ID:
- *
- * A variant of ThreadListQuery with sequence ID
- *
- * TODO: parameters.
- */
-
-#define FB_API_QUERY_SEQ_ID 10155268192741729
-
-/**
- * FB_API_QUERY_XMA:
- *
- * The query hash for the `XMAQuery`.
- *
- * Key mapping:
- * 0: xma_id
- */
-#define FB_API_QUERY_XMA 10153919431161729
-
-/**
- * FB_API_CONTACTS_COUNT:
- *
- * The maximum amount of contacts to fetch in a single request. If this
- * value is set too high, HTTP request will fail. This is due to the
- * request data being too large.
- */
-#define FB_API_CONTACTS_COUNT 500
-
-#define FACEBOOK_MESSAGE_LIMIT 100000
-
-class FacebookProto;
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct AsyncHttpRequest : public MTHttpRequest<FacebookProto>
-{
- struct Param
- {
- Param(const char *p1, const char *p2) :
- key(p1), val(p2)
- {}
-
- CMStringA key, val;
- };
- OBJLIST<Param> params;
-
- AsyncHttpRequest();
-
- void CalcSig();
-};
-
-AsyncHttpRequest *operator<<(AsyncHttpRequest *, const CHAR_PARAM &);
-AsyncHttpRequest *operator<<(AsyncHttpRequest *, const INT_PARAM &);
-
-class JsonReply
-{
- JSONNode *m_root = nullptr;
- int m_errorCode = 0;
-
-public:
- JsonReply(NETLIBHTTPREQUEST *);
- ~JsonReply();
-
- __forceinline JSONNode &data() const { return *m_root; }
- __forceinline int error() const { return m_errorCode; }
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct FacebookUser
-{
- FacebookUser(__int64 _p1, MCONTACT _p2, bool _p3 = false, bool _p4 = false) :
- id(_p1),
- hContact(_p2),
- bIsChat(_p3),
- bIsChatInitialized(_p4)
- {}
-
- __int64 id;
- MCONTACT hContact;
- bool bIsChat;
- bool bIsChatInitialized;
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct COwnMessage
-{
- __int64 msgId;
- int reqId;
- MCONTACT hContact;
- CMStringW wszText;
-
- COwnMessage() :
- msgId(0),
- reqId(0),
- hContact(0)
- {
- }
-
- COwnMessage(__int64 _id, int _reqId, MCONTACT _hContact) :
- msgId(_id),
- reqId(_reqId),
- hContact(_hContact)
- {
- }
-};
-
-class FacebookProto : public PROTO<FacebookProto>
-{
- friend class CGroupchatInviteDlg;
-
- class FacebookImpl
- {
- friend class FacebookProto;
-
- FacebookProto &m_proto;
- CTimer m_heartBeat;
-
- void OnHeartBeat(CTimer *)
- {
- m_proto.MqttPing();
- }
-
- FacebookImpl(FacebookProto &pro) :
- m_proto(pro),
- m_heartBeat(Miranda_GetSystemWindow(), (UINT_PTR)this)
- {
- m_heartBeat.OnEvent = Callback(this, &FacebookImpl::OnHeartBeat);
- }
- } m_impl;
-
- uint8_t *doZip(size_t cbData, const void *pData, size_t &cbRes);
- uint8_t *doUnzip(size_t cbData, const void *pData, size_t &cbRes);
-
- void ConnectionFailed(int iErrorCode = 0);
-
- AsyncHttpRequest *CreateRequest(const char *szUrl, const char *szName, const char *szMethod);
- AsyncHttpRequest *CreateRequestGQL(int64_t id);
- NETLIBHTTPREQUEST *ExecuteRequest(AsyncHttpRequest *pReq);
-
- // Avatars
- void __cdecl AvatarsUpdate(void *);
- void GetAvatarFilename(MCONTACT hContact, wchar_t *pwszFileName);
-
- // Group chats
- void Chat_InviteUser(SESSION_INFO *si);
- int Chat_KickUser(SESSION_INFO *si, const wchar_t *pwszUid);
- void Chat_Leave(SESSION_INFO *si);
- void Chat_SendPrivateMessage(GCHOOK *gch);
- void Chat_ProcessLogMenu(SESSION_INFO *si, GCHOOK *gch);
- void Chat_ProcessNickMenu(SESSION_INFO *si, GCHOOK *gch);
-
- // MQTT
- void MqttLogin();
-
- void MqttPing();
- void MqttPublish(const char *topic, const JSONNode &value);
- void MqttSubscribe(const char *topic, ...);
- void MqttUnsubscribe(const char *topic, ...);
-
- bool MqttRead(MqttMessage &payload);
- bool MqttParse(const MqttMessage &payload);
- void MqttSend(const MqttMessage &payload);
-
- void MqttQueueConnect();
-
- void OnPublish(const char *str, const uint8_t *payLoad, size_t cbLen);
- void OnPublishMessage(FbThriftReader &rdr);
- void OnPublishPresence(FbThriftReader &rdr);
- void OnPublishUtn(FbThriftReader &rdr);
-
- HNETLIBCONN m_mqttConn;
- __int64 m_iMqttId;
- int16_t m_mid; // MQTT message id
-
- // internal data
- CMStringA m_szDeviceID; // stored, GUID that identifies this miranda's account
- CMStringA m_szClientID; // stored, random alphanumeric string of 20 chars
- __int64 m_uid; // stored, Facebook user id
-
- CMStringA m_szSyncToken; // stored, sequence query token
- __int64 m_sid; // stored, Facebook sequence id
-
- int m_iUnread;
- bool m_bOnline;
- bool m_QueueCreated;
-
- CMStringA m_szAuthToken; // calculated
-
- mir_cs m_csOwnMessages;
- OBJLIST<COwnMessage> arOwnMessages;
- bool ExtractOwnMessage(__int64 msgId, COwnMessage &res);
-
- mir_cs m_csUsers;
- OBJLIST<FacebookUser> m_users;
-
- FacebookUser* FindUser(__int64 id);
-
- FacebookUser *UserFromJson(const JSONNode &root, CMStringW &wszId, bool &bIsChat);
-
- bool CheckOwnMessage(FacebookUser *pUser, __int64 offlineId, const char *pszMsgId);
- void FetchAttach(const CMStringA &mid, __int64 fbid, CMStringA &szBody);
-
- void OnLoggedIn();
- void OnLoggedOut();
-
- FacebookUser* RefreshThread(JSONNode &n);
- FacebookUser* RefreshThread(CMStringW &wszId);
- bool RefreshSid();
- int RefreshToken();
- void RefreshThreads();
- int RefreshContacts();
-
- FacebookUser *AddContact(const CMStringW &wszId, bool bTemp = true);
-
- void __cdecl ServerThread(void *);
-
-public:
- FacebookProto(const char *proto_name, const wchar_t *username);
- ~FacebookProto();
-
- inline const char *ModuleName() const
- {
- return m_szModuleName;
- }
-
- void OnPublishPrivateMessage(const JSONNode &json);
- void OnPublishReadReceipt(const JSONNode &json);
- void OnPublishSentMessage(const JSONNode &json);
- void OnPublishThreadName(const JSONNode &json);
- void OnPublishChatJoin(const JSONNode &json);
- void OnPublishChatLeave(const JSONNode &json);
-
- //////////////////////////////////////////////////////////////////////////////////////
- // options
-
- CMOption<wchar_t *> m_wszDefaultGroup; // clist group to store contacts
- CMOption<bool> m_bUseBigAvatars; // use big or small avatars by default
- CMOption<bool> m_bUseGroupchats; // do we need group chats at all?
- CMOption<bool> m_bHideGroupchats; // do not open chat windows on creation
- CMOption<bool> m_bLoginInvisible; // login in the invisible mode
- CMOption<bool> m_bKeepUnread; // do not mark incoming messages as read
- CMOption<bool> m_bLoadAll; // load all contacts, not only those who have ARE_FRIENDS status
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // PROTO_INTERFACE
-
- void OnContactAdded(MCONTACT) override;
- void OnModulesLoaded() override;
- void OnShutdown() override;
-
- MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override;
- INT_PTR GetCaps(int type, MCONTACT hContact) override;
- int SendMsg(MCONTACT hContact, int flags, const char *pszSrc) override;
- int SetStatus(int iNewStatus) override;
- int UserIsTyping(MCONTACT hContact, int type) override;
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // Events
-
- int __cdecl OnMarkedRead(WPARAM, LPARAM);
- int __cdecl OnOptionsInit(WPARAM, LPARAM);
-
- int __cdecl GroupchatMenuHook(WPARAM, LPARAM);
- int __cdecl GroupchatEventHook(WPARAM, LPARAM);
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // Services
-
- INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM);
- INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM);
- INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM);
-};
-
-typedef CProtoDlgBase<FacebookProto> CFBDlgBase;
-
-struct CMPlugin : public ACCPROTOPLUGIN<FacebookProto>
-{
- CMPlugin();
-
- int Load() override;
-};
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#pragma once
+
+
+/**
+ * FB_API_AHOST:
+ *
+ * The HTTP host for the Facebook API.
+ */
+#define FB_API_AHOST "https://api.facebook.com"
+
+/**
+ * FB_API_BHOST:
+ *
+ * The HTTP host for the Facebook BAPI.
+ */
+#define FB_API_BHOST "https://b-api.facebook.com"
+
+/**
+ * FB_API_GHOST:
+ *
+ * The HTTP host for the Facebook Graph API.
+ */
+#define FB_API_GHOST "https://graph.facebook.com"
+
+/**
+ * FB_API_WHOST:
+ *
+ * The HTTP host for the Facebook website.
+ */
+#define FB_API_WHOST "https://www.facebook.com"
+
+/**
+ * FB_API_FBRPC_PREFIX
+ *
+ * The fbrpc URL prefix used in links shared from the mobile app.
+ */
+#define FB_API_FBRPC_PREFIX "fbrpc://facebook/nativethirdparty"
+
+/**
+ * FB_API_KEY:
+ *
+ * The Facebook API key.
+ */
+#define FB_API_KEY "256002347743983"
+
+/**
+ * FB_API_SECRET:
+ *
+ * The Facebook API secret.
+ */
+#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895"
+
+/**
+ * FB_ORCA_AGENT
+ *
+ * The part of the user agent that looks like the official client, since the
+ * server started checking this.
+ */
+
+#define FB_ORCA_AGENT "[FBAN/Orca-Android;FBAV/537.0.0.31.101;FBPN/com.facebook.orca;FBLC/en_US;FBBV/52182662]"
+
+/**
+ * FB_API_AGENT:
+ *
+ * The HTTP User-Agent header.
+ */
+#define FB_API_AGENT "Facebook plugin / Purple / 0.9.6 " FB_ORCA_AGENT
+
+/**
+ * FB_API_MQTT_AGENT
+ *
+ * The client information string sent in the MQTT CONNECT message
+ */
+
+#define FB_API_MQTT_AGENT FB_API_AGENT
+
+/**
+ * FB_API_URL_ATTACH:
+ *
+ * The URL for attachment URL requests.
+ */
+#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.getAttachment"
+//#define FB_API_URL_ATTACH FB_API_AHOST "/method/messaging.attachmentRedirect"
+
+/**
+ * FB_API_URL_AUTH:
+ *
+ * The URL for authentication requests.
+ */
+#define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login"
+
+/**
+ * FB_API_URL_GQL:
+ *
+ * The URL for GraphQL requests.
+ */
+#define FB_API_URL_GQL FB_API_GHOST "/graphql"
+
+/**
+ * FB_API_URL_MESSAGES:
+ *
+ * The URL for linking message threads.
+ */
+#define FB_API_URL_MESSAGES FB_API_WHOST "/messages"
+
+/**
+ * FB_API_URL_PARTS:
+ *
+ * The URL for participant management requests.
+ */
+#define FB_API_URL_PARTS FB_API_GHOST "/participants"
+
+/**
+ * FB_API_URL_THREADS:
+ *
+ * The URL for thread management requests.
+ */
+#define FB_API_URL_THREADS FB_API_GHOST "/me/group_threads"
+
+/**
+ * FB_API_URL_TOPIC:
+ *
+ * The URL for thread topic requests.
+ */
+#define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname"
+
+/**
+ * FB_API_QUERY_CONTACT:
+ *
+ * The query hash for the `UsersQuery`.
+ *
+ * Key mapping:
+ * 0: user_fbids
+ * 1: include_full_user_info
+ * 2: profile_pic_large_size
+ * 3: profile_pic_medium_size
+ * 4: profile_pic_small_size
+ */
+#define FB_API_QUERY_CONTACT 10153915107411729
+
+/**
+ * FB_API_QUERY_CONTACTS:
+ *
+ * The query hash for the `FetchContactsFullQuery`.
+ *
+ * Key mapping:
+ * 0: profile_types
+ * 1: limit
+ * 2: big_img_size
+ * 3: huge_img_size
+ * 4: small_img_size
+ */
+#define FB_API_QUERY_CONTACTS 10154444360806729
+
+/**
+ * FB_API_QUERY_CONTACTS_AFTER:
+ *
+ * The query hash for the `FetchContactsFullWithAfterQuery`.
+ *
+ * Key mapping:
+ * 0: profile_types
+ * 1: after
+ * 2: limit
+ * 3: big_img_size
+ * 4: huge_img_size
+ * 5: small_img_size
+ */
+#define FB_API_QUERY_CONTACTS_AFTER 10154444360816729
+
+
+/**
+ * FB_API_QUERY_CONTACTS_DELTA:
+ *
+ * The query hash for the `FetchContactsDeltaQuery`.
+ *
+ * Key mapping:
+ * 0: after
+ * 1: profile_types
+ * 2: limit
+ * 3: big_img_size
+ * 4: huge_img_size
+ * 5: small_img_size
+ */
+#define FB_API_QUERY_CONTACTS_DELTA 10154444360801729
+
+/**
+ * FB_API_QUERY_STICKER:
+ *
+ * The query hash for the `FetchStickersWithPreviewsQuery`.
+ *
+ * Key mapping:
+ * 0: sticker_ids
+ * 1: media_type
+ * 2: preview_size
+ * 3: scaling_factor
+ * 4: animated_media_type
+ */
+#define FB_API_QUERY_STICKER 10152877994321729
+
+/**
+ * FB_API_QUERY_THREAD:
+ *
+ * The query hash for the `ThreadQuery`.
+ *
+ * Key mapping:
+ * 0: thread_ids
+ * 1: verification_type
+ * 2: hash_key
+ * 3: small_preview_size
+ * 4: large_preview_size
+ * 5: item_count
+ * 6: event_count
+ * 7: full_screen_height
+ * 8: full_screen_width
+ * 9: medium_preview_size
+ * 10: fetch_users_separately
+ * 11: include_message_info
+ * 12: msg_count
+ * 13: include_full_user_info
+ * 14: profile_pic_large_size
+ * 15: profile_pic_medium_size
+ * 16: profile_pic_small_size
+ */
+#define FB_API_QUERY_THREAD 10153919752036729
+
+/**
+ * FB_API_QUERY_THREADS:
+ *
+ * The query hash for the `ThreadListQuery`.
+ *
+ * Key mapping:
+ * 0: folder_tag (INBOX, PENDING, ARCHIVED, OTHER, UNREAD)
+ * 1: thread_count (result is sorted from newest to oldest when parameter is sent)
+ * 2: include_thread_info
+ * 3: verification_type
+ * 4: hash_key
+ * 5: small_preview_size
+ * 6: large_preview_size
+ * 7: item_count
+ * 8: event_count
+ * 9: full_screen_height
+ * 10: full_screen_width
+ * 11: medium_preview_size
+ * 12: fetch_users_separately
+ * 13: include_message_info
+ * 14: msg_count
+ * 15: UNKNOWN
+ * 16: profile_pic_large_size
+ * 17: profile_pic_medium_size
+ * 18: profile_pic_small_size
+ */
+#define FB_API_QUERY_THREADS 10153919752026729
+
+/**
+ * FB_API_QUERY_SEQ_ID:
+ *
+ * A variant of ThreadListQuery with sequence ID
+ *
+ * TODO: parameters.
+ */
+
+#define FB_API_QUERY_SEQ_ID 10155268192741729
+
+/**
+ * FB_API_QUERY_XMA:
+ *
+ * The query hash for the `XMAQuery`.
+ *
+ * Key mapping:
+ * 0: xma_id
+ */
+#define FB_API_QUERY_XMA 10153919431161729
+
+/**
+ * FB_API_CONTACTS_COUNT:
+ *
+ * The maximum amount of contacts to fetch in a single request. If this
+ * value is set too high, HTTP request will fail. This is due to the
+ * request data being too large.
+ */
+#define FB_API_CONTACTS_COUNT 500
+
+#define FACEBOOK_MESSAGE_LIMIT 100000
+
+class FacebookProto;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct AsyncHttpRequest : public MTHttpRequest<FacebookProto>
+{
+ struct Param
+ {
+ Param(const char *p1, const char *p2) :
+ key(p1), val(p2)
+ {}
+
+ CMStringA key, val;
+ };
+ OBJLIST<Param> params;
+
+ AsyncHttpRequest();
+
+ void CalcSig();
+};
+
+AsyncHttpRequest *operator<<(AsyncHttpRequest *, const CHAR_PARAM &);
+AsyncHttpRequest *operator<<(AsyncHttpRequest *, const INT_PARAM &);
+
+class JsonReply
+{
+ JSONNode *m_root = nullptr;
+ int m_errorCode = 0;
+
+public:
+ JsonReply(NETLIBHTTPREQUEST *);
+ ~JsonReply();
+
+ __forceinline JSONNode &data() const { return *m_root; }
+ __forceinline int error() const { return m_errorCode; }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct FacebookUser
+{
+ FacebookUser(__int64 _p1, MCONTACT _p2, bool _p3 = false, bool _p4 = false) :
+ id(_p1),
+ hContact(_p2),
+ bIsChat(_p3),
+ bIsChatInitialized(_p4)
+ {}
+
+ __int64 id;
+ MCONTACT hContact;
+ bool bIsChat;
+ bool bIsChatInitialized;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct COwnMessage
+{
+ __int64 msgId;
+ int reqId;
+ MCONTACT hContact;
+ CMStringW wszText;
+
+ COwnMessage() :
+ msgId(0),
+ reqId(0),
+ hContact(0)
+ {
+ }
+
+ COwnMessage(__int64 _id, int _reqId, MCONTACT _hContact) :
+ msgId(_id),
+ reqId(_reqId),
+ hContact(_hContact)
+ {
+ }
+};
+
+class FacebookProto : public PROTO<FacebookProto>
+{
+ friend class CGroupchatInviteDlg;
+
+ class FacebookImpl
+ {
+ friend class FacebookProto;
+
+ FacebookProto &m_proto;
+ CTimer m_heartBeat;
+
+ void OnHeartBeat(CTimer *)
+ {
+ m_proto.MqttPing();
+ }
+
+ FacebookImpl(FacebookProto &pro) :
+ m_proto(pro),
+ m_heartBeat(Miranda_GetSystemWindow(), (UINT_PTR)this)
+ {
+ m_heartBeat.OnEvent = Callback(this, &FacebookImpl::OnHeartBeat);
+ }
+ } m_impl;
+
+ uint8_t *doZip(size_t cbData, const void *pData, size_t &cbRes);
+ uint8_t *doUnzip(size_t cbData, const void *pData, size_t &cbRes);
+
+ void ConnectionFailed(int iErrorCode = 0);
+
+ AsyncHttpRequest *CreateRequest(const char *szUrl, const char *szName, const char *szMethod);
+ AsyncHttpRequest *CreateRequestGQL(int64_t id);
+ NETLIBHTTPREQUEST *ExecuteRequest(AsyncHttpRequest *pReq);
+
+ // Avatars
+ void __cdecl AvatarsUpdate(void *);
+ void GetAvatarFilename(MCONTACT hContact, wchar_t *pwszFileName);
+
+ // Group chats
+ void Chat_InviteUser(SESSION_INFO *si);
+ int Chat_KickUser(SESSION_INFO *si, const wchar_t *pwszUid);
+ void Chat_Leave(SESSION_INFO *si);
+ void Chat_SendPrivateMessage(GCHOOK *gch);
+ void Chat_ProcessLogMenu(SESSION_INFO *si, GCHOOK *gch);
+ void Chat_ProcessNickMenu(SESSION_INFO *si, GCHOOK *gch);
+
+ // MQTT
+ void MqttLogin();
+
+ void MqttPing();
+ void MqttPublish(const char *topic, const JSONNode &value);
+ void MqttSubscribe(const char *topic, ...);
+ void MqttUnsubscribe(const char *topic, ...);
+
+ bool MqttRead(MqttMessage &payload);
+ bool MqttParse(const MqttMessage &payload);
+ void MqttSend(const MqttMessage &payload);
+
+ void MqttQueueConnect();
+
+ void OnPublish(const char *str, const uint8_t *payLoad, size_t cbLen);
+ void OnPublishMessage(FbThriftReader &rdr);
+ void OnPublishPresence(FbThriftReader &rdr);
+ void OnPublishUtn(FbThriftReader &rdr);
+
+ HNETLIBCONN m_mqttConn;
+ __int64 m_iMqttId;
+ int16_t m_mid; // MQTT message id
+
+ // internal data
+ CMStringA m_szDeviceID; // stored, GUID that identifies this miranda's account
+ CMStringA m_szClientID; // stored, random alphanumeric string of 20 chars
+ __int64 m_uid; // stored, Facebook user id
+
+ CMStringA m_szSyncToken; // stored, sequence query token
+ __int64 m_sid; // stored, Facebook sequence id
+
+ int m_iUnread;
+ bool m_bOnline;
+ bool m_QueueCreated;
+
+ CMStringA m_szAuthToken; // calculated
+
+ mir_cs m_csOwnMessages;
+ OBJLIST<COwnMessage> arOwnMessages;
+ bool ExtractOwnMessage(__int64 msgId, COwnMessage &res);
+
+ mir_cs m_csUsers;
+ OBJLIST<FacebookUser> m_users;
+
+ FacebookUser* FindUser(__int64 id);
+
+ FacebookUser *UserFromJson(const JSONNode &root, CMStringW &wszId, bool &bIsChat);
+
+ bool CheckOwnMessage(FacebookUser *pUser, __int64 offlineId, const char *pszMsgId);
+ void FetchAttach(const CMStringA &mid, __int64 fbid, CMStringA &szBody);
+
+ void OnLoggedIn();
+ void OnLoggedOut();
+
+ FacebookUser* RefreshThread(JSONNode &n);
+ FacebookUser* RefreshThread(CMStringW &wszId);
+ bool RefreshSid();
+ int RefreshToken();
+ void RefreshThreads();
+ int RefreshContacts();
+
+ FacebookUser *AddContact(const CMStringW &wszId, bool bTemp = true);
+
+ void __cdecl ServerThread(void *);
+
+public:
+ FacebookProto(const char *proto_name, const wchar_t *username);
+ ~FacebookProto();
+
+ inline const char *ModuleName() const
+ {
+ return m_szModuleName;
+ }
+
+ void OnPublishPrivateMessage(const JSONNode &json);
+ void OnPublishReadReceipt(const JSONNode &json);
+ void OnPublishSentMessage(const JSONNode &json);
+ void OnPublishThreadName(const JSONNode &json);
+ void OnPublishChatJoin(const JSONNode &json);
+ void OnPublishChatLeave(const JSONNode &json);
+
+ //////////////////////////////////////////////////////////////////////////////////////
+ // options
+
+ CMOption<wchar_t *> m_wszDefaultGroup; // clist group to store contacts
+ CMOption<bool> m_bUseBigAvatars; // use big or small avatars by default
+ CMOption<bool> m_bUseGroupchats; // do we need group chats at all?
+ CMOption<bool> m_bHideGroupchats; // do not open chat windows on creation
+ CMOption<bool> m_bLoginInvisible; // login in the invisible mode
+ CMOption<bool> m_bKeepUnread; // do not mark incoming messages as read
+ CMOption<bool> m_bLoadAll; // load all contacts, not only those who have ARE_FRIENDS status
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // PROTO_INTERFACE
+
+ void OnContactAdded(MCONTACT) override;
+ void OnModulesLoaded() override;
+ void OnShutdown() override;
+
+ MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override;
+ INT_PTR GetCaps(int type, MCONTACT hContact) override;
+ int SendMsg(MCONTACT hContact, int flags, const char *pszSrc) override;
+ int SetStatus(int iNewStatus) override;
+ int UserIsTyping(MCONTACT hContact, int type) override;
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Events
+
+ int __cdecl OnMarkedRead(WPARAM, LPARAM);
+ int __cdecl OnOptionsInit(WPARAM, LPARAM);
+
+ int __cdecl GroupchatMenuHook(WPARAM, LPARAM);
+ int __cdecl GroupchatEventHook(WPARAM, LPARAM);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Services
+
+ INT_PTR __cdecl GetAvatarCaps(WPARAM, LPARAM);
+ INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM);
+ INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM);
+};
+
+typedef CProtoDlgBase<FacebookProto> CFBDlgBase;
+
+struct CMPlugin : public ACCPROTOPLUGIN<FacebookProto>
+{
+ CMPlugin();
+
+ int Load() override;
+};
diff --git a/protocols/Facebook/src/server.cpp b/protocols/Facebook/src/server.cpp
index b35cc5461a..39ba67f83d 100644
--- a/protocols/Facebook/src/server.cpp
+++ b/protocols/Facebook/src/server.cpp
@@ -1,1051 +1,1051 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "stdafx.h"
-
-void FacebookProto::ConnectionFailed(int iErrorCode)
-{
- if (iErrorCode) {
- POPUPDATAW popup;
- popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true);
- wcscpy_s(popup.lpwzContactName, m_tszUserName);
- mir_snwprintf(popup.lpwzText, TranslateT("Connection failed with error code %d"), iErrorCode);
- PUAddPopupW(&popup);
- }
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_FAILED, (HANDLE)m_iStatus, m_iDesiredStatus);
-
- m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
-
- OnShutdown();
-}
-
-bool FacebookProto::ExtractOwnMessage(__int64 msgId, COwnMessage &res)
-{
- mir_cslock lck(m_csOwnMessages);
- for (auto &it : arOwnMessages)
- if (it->msgId == msgId) {
- res = *it;
- arOwnMessages.removeItem(&it);
- return true;
- }
-
- return false;
-}
-
-void FacebookProto::OnLoggedIn()
-{
- m_mid = 0;
-
- JSONNode root; root << BOOL_PARAM("foreground", true) << INT_PARAM("keepalive_timeout", 60);
- MqttPublish("/foreground_state", root);
-
- MqttSubscribe("/inbox", "/mercury", "/messaging_events", "/orca_presence", "/orca_typing_notifications", "/pp", "/t_ms", "/t_p", "/t_rtc", "/webrtc", "/webrtc_response", 0);
- MqttUnsubscribe("/orca_message_notifications", 0);
-
- // if sequence is not initialized, request SID from the server
- if (m_sid == 0) {
- if (!RefreshSid()) {
- ConnectionFailed();
- return;
- }
- }
-
- // point of no return;
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
- m_iStatus = m_iDesiredStatus;
- m_bOnline = true;
- m_impl.m_heartBeat.Start(60000);
-
- // connect message queue
- MqttQueueConnect();
-
- // request message threads if needed
- if (m_bUseGroupchats)
- RefreshThreads();
-}
-
-void FacebookProto::OnLoggedOut()
-{
- m_impl.m_heartBeat.Stop();
- m_bOnline = false;
-
- ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
- m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
-
- setAllContactStatuses(ID_STATUS_OFFLINE, false);
-}
-
-FacebookUser* FacebookProto::AddContact(const CMStringW &wszId, bool bTemp)
-{
- MCONTACT hContact = db_add_contact();
- setWString(hContact, DBKEY_ID, wszId);
- Proto_AddToContact(hContact, m_szModuleName);
- Clist_SetGroup(hContact, m_wszDefaultGroup);
- if (bTemp)
- Contact::RemoveFromList(hContact);
-
- return FindUser(_wtoi64(wszId));
-}
-
-FacebookUser* FacebookProto::FindUser(__int64 id)
-{
- mir_cslock lck(m_csUsers);
- return m_users.find((FacebookUser *)&id);
-}
-
-FacebookUser* FacebookProto::UserFromJson(const JSONNode &root, CMStringW &wszUserId, bool &bIsChat)
-{
- bIsChat = false;
- wszUserId = root["threadKey"]["otherUserFbId"].as_mstring();
- if (wszUserId.IsEmpty()) {
- // if only thread id is present, it must be a group chat
- wszUserId = root["threadKey"]["threadFbId"].as_mstring();
- bIsChat = true;
- }
-
- auto *pUser = FindUser(_wtoi64(wszUserId));
- if (pUser == nullptr) {
- debugLogA("Message from unknown contact %s, ignored", wszUserId.c_str());
- return nullptr;
- }
-
- if (pUser->bIsChat != bIsChat) {
- debugLogA("Wrong chat user: %d vs %d for user %lld, ignored", pUser->bIsChat, bIsChat, pUser->id);
- return nullptr;
- }
-
- return pUser;
-}
-
-int FacebookProto::RefreshContacts()
-{
- CMStringA szCursor;
- bool bNeedUpdate = false;
-
- while (true) {
- JSONNode root; root << CHAR_PARAM("0", "user");
-
- AsyncHttpRequest *pReq;
- if (szCursor.IsEmpty()) {
- pReq = CreateRequestGQL(FB_API_QUERY_CONTACTS);
- root << INT_PARAM("1", FB_API_CONTACTS_COUNT);
- }
- else {
- pReq = CreateRequestGQL(FB_API_QUERY_CONTACTS_AFTER);
- root << CHAR_PARAM("1", szCursor) << INT_PARAM("2", FB_API_CONTACTS_COUNT);
- }
- pReq << CHAR_PARAM("query_params", root.write().c_str());
- pReq->flags |= NLHRF_NODUMPSEND;
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (int iErrorCode = reply.error())
- return iErrorCode; // unknown error
-
- bool bLoadAll = m_bLoadAll;
- auto &data = reply.data()["viewer"]["messenger_contacts"];
-
- for (auto &it : data["nodes"]) {
- auto &n = it["represented_profile"];
- CMStringW wszId(n["id"].as_mstring());
- __int64 id = _wtoi64(wszId);
-
- MCONTACT hContact;
- if (id != m_uid) {
- bool bIsFriend = bLoadAll || n["friendship_status"].as_mstring() == L"ARE_FRIENDS";
-
- auto *pUser = FindUser(id);
- if (pUser == nullptr) {
- if (!bIsFriend)
- continue;
- pUser = AddContact(wszId, false);
- }
- else if (!bIsFriend)
- Contact::RemoveFromList(pUser->hContact); // adios!
-
- hContact = pUser->hContact;
- }
- else hContact = 0;
-
- if (auto &nName = it["structured_name"]) {
- CMStringW wszName(nName["text"].as_mstring());
- setWString(hContact, DBKEY_NICK, wszName);
- for (auto &nn : nName["parts"]) {
- CMStringW wszPart(nn["part"].as_mstring());
- int offset = nn["offset"].as_int(), length = nn["length"].as_int();
- if (wszPart == L"first")
- setWString(hContact, "FirstName", wszName.Mid(offset, length));
- else if (wszPart == L"last")
- setWString(hContact, "LastName", wszName.Mid(offset, length));
- }
- }
-
- if (auto &nBirth = n["birthdate"]) {
- setDword(hContact, "BirthDay", nBirth["day"].as_int());
- setDword(hContact, "BirthMonth", nBirth["month"].as_int());
- }
-
- if (auto &nCity = n["current_city"])
- setWString(hContact, "City", nCity["name"].as_mstring());
-
- if (auto &nAva = it[(m_bUseBigAvatars) ? "hugePictureUrl" : "bigPictureUrl"]) {
- CMStringW wszOldUrl(getMStringW(hContact, DBKEY_AVATAR)), wszNewUrl(nAva["uri"].as_mstring());
- if (wszOldUrl != wszNewUrl) {
- bNeedUpdate = true;
- setByte(hContact, "UpdateNeeded", 1);
- setWString(hContact, DBKEY_AVATAR, wszNewUrl);
- }
- }
- }
-
- if (!data["page_info"]["has_next_page"].as_bool()) {
- debugLogA("Got no next page, exiting", szCursor.c_str());
- break;
- }
-
- szCursor = data["page_info"]["end_cursor"].as_mstring();
- debugLogA("Got cursor: %s", szCursor.c_str());
- }
-
- if (bNeedUpdate)
- ForkThread(&FacebookProto::AvatarsUpdate);
- return 0;
-}
-
-bool FacebookProto::RefreshSid()
-{
- auto *pReq = CreateRequestGQL(FB_API_QUERY_SEQ_ID);
- JSONNode root; root << CHAR_PARAM("1", "0");
- pReq << CHAR_PARAM("query_params", root.write().c_str());
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (reply.error())
- return false;
-
- auto &n = reply.data()["viewer"]["message_threads"];
- CMStringW wszSid(n["sync_sequence_id"].as_mstring());
- setWString(DBKEY_SID, wszSid);
- m_sid = _wtoi64(wszSid);
- m_iUnread = n["unread_count"].as_int();
- return true;
-}
-
-FacebookUser* FacebookProto::RefreshThread(JSONNode &n)
-{
- if (!n["is_group_thread"].as_bool())
- return nullptr;
-
- CMStringW chatId(n["thread_key"]["thread_fbid"].as_mstring());
- CMStringW name(n["name"].as_mstring());
- if (name.IsEmpty()) {
- for (auto &u : n["all_participants"]["nodes"]) {
- auto &ur = u["messaging_actor"];
- CMStringW userId(ur["id"].as_mstring());
- if (_wtoi64(userId) == m_uid)
- continue;
-
- if (!name.IsEmpty())
- name.Append(L", ");
- name += ur["name"].as_mstring();
- }
-
- if (name.GetLength() > 128) {
- name.Truncate(125);
- name.Append(L"...");
- }
- }
-
- auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, chatId, name);
- if (si == nullptr)
- return nullptr;
-
- setWString(si->hContact, DBKEY_ID, chatId);
- Chat_AddGroup(si, TranslateT("Participant"));
-
- for (auto &u : n["all_participants"]["nodes"]) {
- auto &ur = u["messaging_actor"];
- CMStringW userId(ur["id"].as_mstring());
- CMStringW userName(ur["name"].as_mstring());
-
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN };
- gce.pszID.w = chatId;
- gce.pszUID.w = userId;
- gce.pszNick.w = userName;
- gce.bIsMe = _wtoi64(userId) == m_uid;
- gce.time = time(0);
- Chat_Event(&gce);
- }
-
- Chat_Control(m_szModuleName, chatId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
- Chat_Control(m_szModuleName, chatId, SESSION_ONLINE);
-
- __int64 userId = _wtoi64(chatId);
- auto *pUser = FindUser(userId);
-
- if (pUser == nullptr) {
- mir_cslock lck(m_csUsers);
- pUser = new FacebookUser(userId, si->hContact, true, true);
- m_users.insert(pUser);
- }
- else {
- pUser->hContact = si->hContact;
- pUser->bIsChatInitialized = true;
- }
-
- return pUser;
-}
-
-FacebookUser* FacebookProto::RefreshThread(CMStringW &wszId)
-{
- auto *pReq = CreateRequestGQL(FB_API_QUERY_THREAD);
- pReq << WCHAR_PARAM("query_params", CMStringW(FORMAT, L"{\"0\":[\"%s\"], \"12\":0, \"13\":\"false\"}", wszId.c_str()));
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (!reply.error()) {
- auto &root = reply.data();
- for (auto &n : root)
- return RefreshThread(n);
- }
-
- return nullptr;
-}
-
-void FacebookProto::RefreshThreads()
-{
- int threadsLimit = 40;
-
- auto * pReq = CreateRequestGQL(FB_API_QUERY_THREADS);
- JSONNode json; json << INT_PARAM("1", threadsLimit) << CHAR_PARAM("2", "true") << CHAR_PARAM("12", "false") << CHAR_PARAM("13", "false");
- pReq << CHAR_PARAM("query_params", json.write().c_str());
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (!reply.error()) {
- auto &root = reply.data()["viewer"]["message_threads"];
-
- for (auto &n : root["nodes"]) {
- if (n["is_group_thread"].as_bool() && n["is_viewer_subscribed"].as_bool() && !n["has_viewer_archived"].as_bool())
- RefreshThread(n);
- }
-
- // TODO: save timestamp of last message/action/... into DB
- // TODO: lower threadsLimit to 10, load next pages if timestamp of last message is higher than timestamp in DB
- }
-}
-
-int FacebookProto::RefreshToken()
-{
- auto *pReq = CreateRequest(FB_API_URL_AUTH, "authenticate", "auth.login");
- pReq->flags |= NLHRF_NODUMP;
- pReq << CHAR_PARAM("email", getMStringA(DBKEY_LOGIN));
- pReq << CHAR_PARAM("password", getMStringA(DBKEY_PASS));
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (reply.error())
- return reply.error();
-
- m_szAuthToken = reply.data()["access_token"].as_mstring();
- setString(DBKEY_TOKEN, m_szAuthToken);
-
- CMStringA m_szUid = reply.data()["uid"].as_mstring();
- setString(DBKEY_ID, m_szUid);
- m_uid = _atoi64(m_szUid);
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void FacebookProto::ServerThread(void *)
-{
- m_QueueCreated = false;
-
-LBL_Begin:
- m_szAuthToken = getMStringA(DBKEY_TOKEN);
- if (m_szAuthToken.IsEmpty()) {
- if (int iErrorCode = RefreshToken()) {
- ConnectionFailed(iErrorCode);
- return;
- }
- }
-
- int iErrorCode = RefreshContacts();
- if (iErrorCode != 0) {
- if (iErrorCode == 401) {
- delSetting(DBKEY_TOKEN);
- goto LBL_Begin;
- }
-
- ConnectionFailed(iErrorCode);
- return;
- }
-
- // connect to MQTT server
- m_mqttConn = Netlib_OpenConnection(m_hNetlibUser, "mqtt.facebook.com", 443, 0, NLOCF_SSL);
- if (m_mqttConn == nullptr) {
- debugLogA("connection failed, exiting");
- ConnectionFailed();
- return;
- }
-
- // send initial packet
- MqttLogin();
-
- while (!Miranda_IsTerminated()) {
- NETLIBSELECT nls = {};
- nls.hReadConns[0] = m_mqttConn;
- nls.dwTimeout = 1000;
- int ret = Netlib_Select(&nls);
- if (ret == SOCKET_ERROR) {
- debugLogA("Netlib_Recv() failed, error=%d", WSAGetLastError());
- break;
- }
-
- // no data, continue waiting
- if (ret == 0)
- continue;
-
- MqttMessage msg;
- if (!MqttRead(msg)) {
- debugLogA("MqttRead() failed");
- break;
- }
-
- if (!MqttParse(msg)) {
- debugLogA("MqttParse() failed");
- break;
- }
- }
-
- debugLogA("exiting ServerThread");
-
- Netlib_CloseHandle(m_mqttConn);
- m_mqttConn = nullptr;
-
- OnLoggedOut();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-int FacebookProto::OnMarkedRead(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;
-
- if (m_bKeepUnread)
- return 0;
-
- JSONNode root; root << BOOL_PARAM("state", true) << INT_PARAM("syncSeqId", m_sid) << CHAR_PARAM("mark", "read");
- if (isChatRoom(hContact))
- root << CHAR_PARAM("threadFbId", getMStringA(hContact, DBKEY_ID));
- else
- root << CHAR_PARAM("otherUserFbId", getMStringA(hContact, DBKEY_ID));
- MqttPublish("/mark_thread", root);
- return 0;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void FacebookProto::OnPublish(const char *topic, const uint8_t *p, size_t cbLen)
-{
- FbThriftReader rdr;
-
- // that might be a zipped buffer
- if (cbLen >= 2) {
- size_t dataSize;
- void *pData = doUnzip(cbLen, p, dataSize);
- if (pData != nullptr) {
- debugLogA("UNZIP: %d bytes unzipped ok", dataSize);
- Netlib_Dump(m_mqttConn, pData, dataSize, false, 0);
- rdr.reset(dataSize, pData);
- mir_free(pData);
- }
- }
-
- if (rdr.size() == 0)
- rdr.reset(cbLen, (void*)p);
-
- if (!strcmp(topic, "/t_p"))
- OnPublishPresence(rdr);
- else if (!strcmp(topic, "/t_ms"))
- OnPublishMessage(rdr);
- else if (!strcmp(topic, "/orca_typing_notifications"))
- OnPublishUtn(rdr);
-}
-
-void FacebookProto::OnPublishPresence(FbThriftReader &rdr)
-{
- char *str = nullptr;
- rdr.readStr(str);
- mir_free(str);
-
- bool bVal;
- uint8_t fieldType;
- uint16_t fieldId;
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_BOOL);
- assert(fieldId == 1);
- rdr.readBool(bVal);
-
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_LIST);
- assert(fieldId == 1);
-
- uint32_t size;
- rdr.readList(fieldType, size);
- assert(fieldType == FB_THRIFT_TYPE_STRUCT);
-
- debugLogA("Received list of presences: %d records", size);
- for (uint32_t i = 0; i < size; i++) {
- uint64_t userId, timestamp, voipBits;
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_I64);
- assert(fieldId == 1);
- rdr.readInt64(userId);
-
- uint32_t u32;
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_I32);
- assert(fieldId == 1);
- rdr.readInt32(u32);
-
- auto *pUser = FindUser(userId);
- if (pUser == nullptr)
- debugLogA("Skipping presence from unknown user %lld", userId);
- else {
- debugLogA("Presence from user %lld => %d", userId, u32);
- setWord(pUser->hContact, "Status", (u32 != 0) ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE);
- }
-
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_I64);
- assert(fieldId == 1 || fieldId == 3 || fieldId == 4);
- rdr.readInt64(timestamp);
-
- while (!rdr.isStop()) {
- rdr.readField(fieldType, fieldId);
- assert(fieldType == FB_THRIFT_TYPE_I64 || fieldType == FB_THRIFT_TYPE_I16 || fieldType == FB_THRIFT_TYPE_I32);
- rdr.readIntV(voipBits);
- }
-
- rdr.readByte(fieldType);
- assert(fieldType == FB_THRIFT_TYPE_STOP);
- }
-
- rdr.readByte(fieldType);
- assert(fieldType == FB_THRIFT_TYPE_STOP);
-}
-
-void FacebookProto::OnPublishUtn(FbThriftReader &rdr)
-{
- JSONNode root = JSONNode::parse(rdr.rest());
- auto *pUser = FindUser(_wtoi64(root["sender_fbid"].as_mstring()));
- if (pUser != nullptr) {
- int length = (root["state"].as_int() == 0) ? PROTOTYPE_CONTACTTYPING_OFF : 60;
- CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, length);
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-struct
-{
- const char *messageType;
- void (FacebookProto:: *pFunc)(const JSONNode &);
-}
-static MsgHandlers[] =
-{
- { "deltaNewMessage", &FacebookProto::OnPublishPrivateMessage },
- { "deltaThreadName", &FacebookProto::OnPublishThreadName },
- { "deltaSentMessage", &FacebookProto::OnPublishSentMessage },
- { "deltaReadReceipt", &FacebookProto::OnPublishReadReceipt },
- { "deltaParticipantsAddedToGroupThread", &FacebookProto::OnPublishChatJoin },
- { "deltaParticipantLeftGroupThread", &FacebookProto::OnPublishChatLeave },
-};
-
-void FacebookProto::OnPublishMessage(FbThriftReader &rdr)
-{
- uint8_t stop;
- if (rdr.isStop())
- rdr.readByte(stop);
- else {
- uint8_t type;
- uint16_t id;
- rdr.readField(type, id);
- _ASSERT(type == FB_THRIFT_TYPE_STRING);
- _ASSERT(id == 1 || id == 2);
-
- char *szShit = nullptr;
- rdr.readStr(szShit);
- mir_free(szShit);
-
- rdr.readByte(stop);
- }
-
- CMStringA szJson(rdr.rest());
- debugLogA("MS: <%s>", szJson.c_str());
- JSONNode root = JSONNode::parse(szJson);
-
- CMStringA errorCode = root["errorCode"].as_mstring();
- if (!errorCode.IsEmpty()) {
- if (!m_QueueCreated && (errorCode == "ERROR_QUEUE_OVERFLOW" || errorCode == "ERROR_QUEUE_NOT_FOUND" || errorCode == "ERROR_QUEUE_LOST" || errorCode == "ERROR_QUEUE_EXCEEDS_MAX_DELTAS")) {
- m_QueueCreated = true; // prevent queue creation request from being sent twice
- delSetting(DBKEY_SYNC_TOKEN); m_szSyncToken.Empty();
- delSetting(DBKEY_SID); m_sid = 0;
- if (!RefreshSid()) {
- ConnectionFailed();
- return;
- }
-
- MqttQueueConnect();
- }
- }
-
- CMStringW str = root["lastIssuedSeqId"].as_mstring();
- if (!str.IsEmpty()) {
- setWString(DBKEY_SID, str);
- m_sid = _wtoi64(str);
- }
-
- str = root["syncToken"].as_mstring();
- if (!str.IsEmpty()) {
- m_szSyncToken = str;
- setString(DBKEY_SYNC_TOKEN, m_szSyncToken);
- return;
- }
-
- for (auto &it : root["deltas"]) {
- for (auto &handler : MsgHandlers) {
- auto &json = it[handler.messageType];
- if (json) {
- (this->*(handler.pFunc))(json);
- break;
- }
- }
- }
-}
-
-// new message arrived
-struct
-{
- const char *szTag, *szClientVersion;
-}
-static facebookClients[] =
-{
- { "source:titan:web", "Facebook (website)" },
- { "app_id:256002347743983", "Facebook (Facebook Messenger)" }
-};
-
-void FacebookProto::FetchAttach(const CMStringA &mid, __int64 fbid, CMStringA &szBody)
-{
- for (int iAttempt = 0; iAttempt < 5; iAttempt++) {
- auto *pReq = CreateRequest(FB_API_URL_ATTACH, "getAttachment", "messaging.getAttachment");
- pReq << CHAR_PARAM("mid", mid) << INT64_PARAM("aid", fbid);
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- switch (reply.error()) {
- case 0:
- {
- std::string uri = reply.data()["redirect_uri"].as_string();
- std::string type = reply.data()["content_type"].as_string();
- if (!uri.empty())
- szBody.AppendFormat("\r\n%s: %s", TranslateU(type.find("image/") != -1 ? "Picture attachment" : "File attachment"), uri.c_str());
- }
- return;
-
- case 509: // attachment isn't ready, wait a bit and retry
- ::Sleep(100);
- continue;
-
- default: // shit happened, exiting
- return;
- }
- }
-}
-
-void FacebookProto::OnPublishPrivateMessage(const JSONNode &root)
-{
- auto &metadata = root["messageMetadata"];
- __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
- if (!offlineId) {
- debugLogA("We care about messages only, event skipped");
- return;
- }
-
- bool bIsChat;
- CMStringW wszUserId;
- auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
-
- if (!bIsChat && pUser == nullptr)
- pUser = AddContact(wszUserId, true);
- else if (bIsChat && (pUser == nullptr || !pUser->bIsChatInitialized)) // chat room does not exists or is not initialized
- pUser = RefreshThread(wszUserId);
-
- if (pUser == nullptr) {
- debugLogA("User not found and adding failed, event skipped");
- return;
- }
-
- for (auto &it : metadata["tags"]) {
- auto *szTagName = it.name();
- for (auto &cli : facebookClients) {
- if (!mir_strcmp(szTagName, cli.szTag)) {
- setString(pUser->hContact, "MirVer", cli.szClientVersion);
- break;
- }
- }
- }
-
- CMStringA szId(metadata["messageId"].as_mstring());
- if (CheckOwnMessage(pUser, offlineId, szId)) {
- debugLogA("own message <%s> skipped", szId.c_str());
- return;
- }
-
- if (db_event_getById(m_szModuleName, szId)) {
- debugLogA("this message <%s> was already stored, exiting", szId.c_str());
- return;
- }
-
- // parse message body
- CMStringA szBody(root["body"].as_string().c_str());
- if (szBody.IsEmpty())
- szBody = metadata["snippet"].as_string().c_str();
-
- // parse stickers
- CMStringA stickerId = root["stickerId"].as_mstring();
- if (!stickerId.IsEmpty()) {
- if (ServiceExists(MS_SMILEYADD_LOADCONTACTSMILEYS)) {
- CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName);
- CreateDirectoryTreeW(wszPath);
-
- bool bSuccess = false;
- CMStringW wszFileName(FORMAT, L"%s\\STK{%S}.png", wszPath.c_str(), stickerId.c_str());
- uint32_t dwAttrib = GetFileAttributesW(wszFileName);
- if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
- wszFileName.Format(L"%s\\STK{%S}.webp", wszPath.c_str(), stickerId.c_str());
- dwAttrib = GetFileAttributesW(wszFileName);
- }
-
- // new sticker
- if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
- auto *pReq = CreateRequestGQL(FB_API_QUERY_STICKER);
- pReq << CHAR_PARAM("query_params", CMStringA(FORMAT, "{\"0\":[\"%s\"]}", stickerId.c_str()));
- pReq->CalcSig();
-
- JsonReply reply(ExecuteRequest(pReq));
- if (!reply.error()) {
- for (auto &sticker : reply.data()) {
- // std::string szUrl = sticker["animated_image"]["uri"].as_string();
- // if (szUrl.empty())
- // szUrl = sticker["thread_image"]["uri"].as_string();
- // else
- // wszFileName.Format(L"%s\\STK{%S}.webp", wszPath.c_str(), stickerId.c_str());
- std::string szUrl = sticker["thread_image"]["uri"].as_string();
-
- NETLIBHTTPREQUEST req = {};
- req.cbSize = sizeof(req);
- req.flags = NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
- req.requestType = REQUEST_GET;
- req.szUrl = (char*)szUrl.c_str();
-
- NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(m_hNetlibUser, &req);
- if (pReply != nullptr && pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
- bSuccess = true;
- FILE *out = _wfopen(wszFileName, L"wb");
- fwrite(pReply->pData, 1, pReply->dataLength, out);
- fclose(out);
- }
- }
- }
- }
- else bSuccess = true;
-
- if (bSuccess) {
- if (!szBody.IsEmpty())
- szBody += "\r\n";
- szBody += "STK{" + stickerId + "}";
-
- SMADD_CONT cont = { 1, m_szModuleName, wszFileName };
- CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
- }
- else szBody += TranslateU("Sticker received");
- }
- else szBody += TranslateU("SmileyAdd plugin required to support stickers");
- }
-
- // parse attachments (links, files, ...)
- for (auto &it : root["attachments"]) {
- // madness... json inside json
- CMStringA szJson(it["xmaGraphQL"].as_mstring());
- if (szJson.IsEmpty()) {
- __int64 fbid = _wtoi64(it["fbid"].as_mstring());
- if (fbid == 0) {
- debugLogA("Neither a GQL nor an inline attachment, nothing to do");
- continue;
- }
-
- // inline attachment, request its description
- FetchAttach(szId, fbid, szBody);
- continue;
- }
-
- JSONROOT nBody(szJson);
- if (!nBody)
- continue;
-
- const JSONNode &attach = (*nBody).at((json_index_t)0)["story_attachment"];
- szBody += "\r\n-----------------------------------";
-
- CMStringA str = attach["url"].as_mstring();
- if (!str.IsEmpty()) {
- if (str.Left(8) == "fbrpc://") {
- int iStart = str.Find("target_url=");
- if (iStart != 0) {
- CMStringA tmp;
-
- iStart += 11;
- int iEnd = str.Find("&", iStart);
- if (iEnd != -1)
- tmp = str.Mid(iStart, iEnd - iStart);
- else
- tmp = str.Right(iStart);
-
- mir_urlDecode(tmp.GetBuffer());
- szBody.AppendFormat("\r\n\t%s: %s", TranslateU("URL"), tmp.c_str());
- }
- }
- else szBody.AppendFormat("\r\n\t%s: %s", TranslateU("URL"), str.c_str());
- }
-
- str = attach["title"].as_string().c_str();
- if (!str.IsEmpty())
- szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Title"), str.c_str());
-
- str = attach["source"]["text"].as_string().c_str();
- if (!str.IsEmpty())
- szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Source"), str.c_str());
-
- str = attach["description"]["text"].as_string().c_str();
- if (!str.IsEmpty())
- szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Description"), str.c_str());
-
- str = attach["media"]["playable_url"].as_string().c_str();
- if (!str.IsEmpty())
- szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Playable media"), str.c_str());
- }
-
- // if that's a group chat, send it to the room
- CMStringW wszActorFbId(metadata["actorFbId"].as_mstring());
- __int64 actorFbId = _wtoi64(wszActorFbId);
-
- if (pUser->bIsChat) {
- szBody.Replace("%", "%%");
- ptrW wszText(mir_utf8decodeW(szBody));
-
- // TODO: GC_EVENT_JOIN for chat participants which are missing (for example added later during group chat)
-
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE };
- gce.pszID.w = wszUserId;
- gce.dwFlags = GCEF_ADDTOLOG;
- gce.pszUID.w = wszActorFbId;
- gce.pszText.w = wszText;
- gce.time = time(0);
- gce.bIsMe = actorFbId == m_uid;
- Chat_Event(&gce);
-
- debugLogA("New channel %lld message from %S: %s", pUser->id, gce.pszUID.w, gce.pszText.w);
- }
- else { // otherwise store a private message
- PROTORECVEVENT pre = {};
- pre.timestamp = uint32_t(_wtoi64(metadata["timestamp"].as_mstring()) / 1000);
- pre.szMessage = (char *)szBody.c_str();
- pre.szMsgId = (char *)szId.c_str();
-
- if (m_uid == actorFbId)
- pre.flags |= PREF_SENT;
-
- ProtoChainRecvMsg(pUser->hContact, &pre);
- }
-}
-
-// changing thread name
-void FacebookProto::OnPublishThreadName(const JSONNode &root)
-{
- auto &metadata = root["messageMetadata"];
- __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
- if (!offlineId) {
- debugLogA("We care about messages only, event skipped");
- return;
- }
-
- bool bIsChat;
- CMStringW wszUserId;
- auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
- if (!bIsChat || pUser == nullptr)
- return;
-
- CMStringW wszTitle = root["name"].as_mstring();
- if (!wszTitle.IsEmpty())
- setWString(pUser->hContact, DBKEY_NICK, wszTitle);
- else
- delSetting(pUser->hContact, DBKEY_NICK);
-}
-
-// user joined chat
-void FacebookProto::OnPublishChatJoin(const JSONNode &root)
-{
- auto &metadata = root["messageMetadata"];
- __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
- if (!offlineId) {
- debugLogA("We care about messages only, event skipped");
- return;
- }
-
- bool bIsChat;
- CMStringW wszUserId;
- auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
- if (!bIsChat || pUser == nullptr)
- return;
-
- CMStringW wszText(metadata["adminText"].as_mstring());
- for (auto &it : root["addedParticipants"]) {
- CMStringW wszNick(it["fullName"].as_mstring()), wszId(it["userFbId"].as_mstring());
-
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN };
- gce.pszID.w = wszUserId;
- gce.dwFlags = GCEF_ADDTOLOG;
- gce.pszNick.w = wszNick;
- gce.pszUID.w = wszId;
- gce.pszText.w = wszText;
- gce.time = time(0);
- gce.bIsMe = _wtoi64(wszId) == m_uid;
- Chat_Event(&gce);
- }
-}
-
-// user left chat
-void FacebookProto::OnPublishChatLeave(const JSONNode &root)
-{
- auto &metadata = root["messageMetadata"];
- __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
- if (!offlineId) {
- debugLogA("We care about messages only, event skipped");
- return;
- }
-
- bool bIsChat;
- CMStringW wszUserId;
- auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
- if (!bIsChat || pUser == nullptr)
- return;
-
- CMStringW wszText(metadata["adminText"].as_mstring()), wszId(root["leftParticipantFbId"].as_mstring());
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_PART };
- gce.pszID.w = wszUserId;
- gce.dwFlags = GCEF_ADDTOLOG;
- gce.pszUID.w = wszId;
- gce.pszText.w = wszText;
- gce.time = time(0);
- gce.bIsMe = _wtoi64(wszId) == m_uid;
- Chat_Event(&gce);
-}
-
-// read notification
-void FacebookProto::OnPublishReadReceipt(const JSONNode &root)
-{
- CMStringW wszUserId;
- bool bIsChat;
- auto *pUser = UserFromJson(root, wszUserId, bIsChat);
- if (pUser == nullptr) {
- debugLogA("Message from unknown contact %S, ignored", wszUserId.c_str());
- return;
- }
-
- uint32_t timestamp = _wtoi64(root["watermarkTimestampMs"].as_mstring());
- for (MEVENT ev = db_event_firstUnread(pUser->hContact); ev != 0; ev = db_event_next(pUser->hContact, ev)) {
- DBEVENTINFO dbei = {};
- if (db_event_get(ev, &dbei))
- continue;
-
- if (dbei.timestamp > timestamp)
- break;
-
- if (!dbei.markedRead())
- db_event_markRead(pUser->hContact, ev);
- }
-}
-
-// my own message was sent
-bool FacebookProto::CheckOwnMessage(FacebookUser *pUser, __int64 offlineId, const char *pszMsgId)
-{
- COwnMessage tmp;
- if (!ExtractOwnMessage(offlineId, tmp))
- return false;
-
- if (pUser->bIsChat) {
- CMStringW wszId(FORMAT, L"%lld", m_uid);
- tmp.wszText.Replace(L"%", L"%%");
-
- wchar_t userId[100];
- _i64tow_s(pUser->id, userId, _countof(userId), 10);
-
- GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE };
- gce.pszID.w = userId;
- gce.dwFlags = GCEF_ADDTOLOG;
- gce.pszUID.w = wszId;
- gce.pszText.w = tmp.wszText;
- gce.time = time(0);
- gce.bIsMe = true;
- Chat_Event(&gce);
- }
- else ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.reqId, (LPARAM)pszMsgId);
-
- return true;
-}
-
-void FacebookProto::OnPublishSentMessage(const JSONNode &root)
-{
- auto &metadata = root["messageMetadata"];
-
- __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
-
- CMStringW wszUserId;
- bool bIsChat;
- auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
- if (pUser == nullptr) {
- debugLogA("Message from unknown contact %s, ignored", wszUserId.c_str());
- return;
- }
-
- std::string szMsgId(metadata["messageId"].as_string());
- CheckOwnMessage(pUser, offlineId, szMsgId.c_str());
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "stdafx.h"
+
+void FacebookProto::ConnectionFailed(int iErrorCode)
+{
+ if (iErrorCode) {
+ POPUPDATAW popup;
+ popup.lchIcon = IcoLib_GetIconByHandle(Skin_GetIconHandle(SKINICON_ERROR), true);
+ wcscpy_s(popup.lpwzContactName, m_tszUserName);
+ mir_snwprintf(popup.lpwzText, TranslateT("Connection failed with error code %d"), iErrorCode);
+ PUAddPopupW(&popup);
+ }
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_FAILED, (HANDLE)m_iStatus, m_iDesiredStatus);
+
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
+
+ OnShutdown();
+}
+
+bool FacebookProto::ExtractOwnMessage(__int64 msgId, COwnMessage &res)
+{
+ mir_cslock lck(m_csOwnMessages);
+ for (auto &it : arOwnMessages)
+ if (it->msgId == msgId) {
+ res = *it;
+ arOwnMessages.removeItem(&it);
+ return true;
+ }
+
+ return false;
+}
+
+void FacebookProto::OnLoggedIn()
+{
+ m_mid = 0;
+
+ JSONNode root; root << BOOL_PARAM("foreground", true) << INT_PARAM("keepalive_timeout", 60);
+ MqttPublish("/foreground_state", root);
+
+ MqttSubscribe("/inbox", "/mercury", "/messaging_events", "/orca_presence", "/orca_typing_notifications", "/pp", "/t_ms", "/t_p", "/t_rtc", "/webrtc", "/webrtc_response", 0);
+ MqttUnsubscribe("/orca_message_notifications", 0);
+
+ // if sequence is not initialized, request SID from the server
+ if (m_sid == 0) {
+ if (!RefreshSid()) {
+ ConnectionFailed();
+ return;
+ }
+ }
+
+ // point of no return;
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus);
+ m_iStatus = m_iDesiredStatus;
+ m_bOnline = true;
+ m_impl.m_heartBeat.Start(60000);
+
+ // connect message queue
+ MqttQueueConnect();
+
+ // request message threads if needed
+ if (m_bUseGroupchats)
+ RefreshThreads();
+}
+
+void FacebookProto::OnLoggedOut()
+{
+ m_impl.m_heartBeat.Stop();
+ m_bOnline = false;
+
+ ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+
+ setAllContactStatuses(ID_STATUS_OFFLINE, false);
+}
+
+FacebookUser* FacebookProto::AddContact(const CMStringW &wszId, bool bTemp)
+{
+ MCONTACT hContact = db_add_contact();
+ setWString(hContact, DBKEY_ID, wszId);
+ Proto_AddToContact(hContact, m_szModuleName);
+ Clist_SetGroup(hContact, m_wszDefaultGroup);
+ if (bTemp)
+ Contact::RemoveFromList(hContact);
+
+ return FindUser(_wtoi64(wszId));
+}
+
+FacebookUser* FacebookProto::FindUser(__int64 id)
+{
+ mir_cslock lck(m_csUsers);
+ return m_users.find((FacebookUser *)&id);
+}
+
+FacebookUser* FacebookProto::UserFromJson(const JSONNode &root, CMStringW &wszUserId, bool &bIsChat)
+{
+ bIsChat = false;
+ wszUserId = root["threadKey"]["otherUserFbId"].as_mstring();
+ if (wszUserId.IsEmpty()) {
+ // if only thread id is present, it must be a group chat
+ wszUserId = root["threadKey"]["threadFbId"].as_mstring();
+ bIsChat = true;
+ }
+
+ auto *pUser = FindUser(_wtoi64(wszUserId));
+ if (pUser == nullptr) {
+ debugLogA("Message from unknown contact %s, ignored", wszUserId.c_str());
+ return nullptr;
+ }
+
+ if (pUser->bIsChat != bIsChat) {
+ debugLogA("Wrong chat user: %d vs %d for user %lld, ignored", pUser->bIsChat, bIsChat, pUser->id);
+ return nullptr;
+ }
+
+ return pUser;
+}
+
+int FacebookProto::RefreshContacts()
+{
+ CMStringA szCursor;
+ bool bNeedUpdate = false;
+
+ while (true) {
+ JSONNode root; root << CHAR_PARAM("0", "user");
+
+ AsyncHttpRequest *pReq;
+ if (szCursor.IsEmpty()) {
+ pReq = CreateRequestGQL(FB_API_QUERY_CONTACTS);
+ root << INT_PARAM("1", FB_API_CONTACTS_COUNT);
+ }
+ else {
+ pReq = CreateRequestGQL(FB_API_QUERY_CONTACTS_AFTER);
+ root << CHAR_PARAM("1", szCursor) << INT_PARAM("2", FB_API_CONTACTS_COUNT);
+ }
+ pReq << CHAR_PARAM("query_params", root.write().c_str());
+ pReq->flags |= NLHRF_NODUMPSEND;
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (int iErrorCode = reply.error())
+ return iErrorCode; // unknown error
+
+ bool bLoadAll = m_bLoadAll;
+ auto &data = reply.data()["viewer"]["messenger_contacts"];
+
+ for (auto &it : data["nodes"]) {
+ auto &n = it["represented_profile"];
+ CMStringW wszId(n["id"].as_mstring());
+ __int64 id = _wtoi64(wszId);
+
+ MCONTACT hContact;
+ if (id != m_uid) {
+ bool bIsFriend = bLoadAll || n["friendship_status"].as_mstring() == L"ARE_FRIENDS";
+
+ auto *pUser = FindUser(id);
+ if (pUser == nullptr) {
+ if (!bIsFriend)
+ continue;
+ pUser = AddContact(wszId, false);
+ }
+ else if (!bIsFriend)
+ Contact::RemoveFromList(pUser->hContact); // adios!
+
+ hContact = pUser->hContact;
+ }
+ else hContact = 0;
+
+ if (auto &nName = it["structured_name"]) {
+ CMStringW wszName(nName["text"].as_mstring());
+ setWString(hContact, DBKEY_NICK, wszName);
+ for (auto &nn : nName["parts"]) {
+ CMStringW wszPart(nn["part"].as_mstring());
+ int offset = nn["offset"].as_int(), length = nn["length"].as_int();
+ if (wszPart == L"first")
+ setWString(hContact, "FirstName", wszName.Mid(offset, length));
+ else if (wszPart == L"last")
+ setWString(hContact, "LastName", wszName.Mid(offset, length));
+ }
+ }
+
+ if (auto &nBirth = n["birthdate"]) {
+ setDword(hContact, "BirthDay", nBirth["day"].as_int());
+ setDword(hContact, "BirthMonth", nBirth["month"].as_int());
+ }
+
+ if (auto &nCity = n["current_city"])
+ setWString(hContact, "City", nCity["name"].as_mstring());
+
+ if (auto &nAva = it[(m_bUseBigAvatars) ? "hugePictureUrl" : "bigPictureUrl"]) {
+ CMStringW wszOldUrl(getMStringW(hContact, DBKEY_AVATAR)), wszNewUrl(nAva["uri"].as_mstring());
+ if (wszOldUrl != wszNewUrl) {
+ bNeedUpdate = true;
+ setByte(hContact, "UpdateNeeded", 1);
+ setWString(hContact, DBKEY_AVATAR, wszNewUrl);
+ }
+ }
+ }
+
+ if (!data["page_info"]["has_next_page"].as_bool()) {
+ debugLogA("Got no next page, exiting", szCursor.c_str());
+ break;
+ }
+
+ szCursor = data["page_info"]["end_cursor"].as_mstring();
+ debugLogA("Got cursor: %s", szCursor.c_str());
+ }
+
+ if (bNeedUpdate)
+ ForkThread(&FacebookProto::AvatarsUpdate);
+ return 0;
+}
+
+bool FacebookProto::RefreshSid()
+{
+ auto *pReq = CreateRequestGQL(FB_API_QUERY_SEQ_ID);
+ JSONNode root; root << CHAR_PARAM("1", "0");
+ pReq << CHAR_PARAM("query_params", root.write().c_str());
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (reply.error())
+ return false;
+
+ auto &n = reply.data()["viewer"]["message_threads"];
+ CMStringW wszSid(n["sync_sequence_id"].as_mstring());
+ setWString(DBKEY_SID, wszSid);
+ m_sid = _wtoi64(wszSid);
+ m_iUnread = n["unread_count"].as_int();
+ return true;
+}
+
+FacebookUser* FacebookProto::RefreshThread(JSONNode &n)
+{
+ if (!n["is_group_thread"].as_bool())
+ return nullptr;
+
+ CMStringW chatId(n["thread_key"]["thread_fbid"].as_mstring());
+ CMStringW name(n["name"].as_mstring());
+ if (name.IsEmpty()) {
+ for (auto &u : n["all_participants"]["nodes"]) {
+ auto &ur = u["messaging_actor"];
+ CMStringW userId(ur["id"].as_mstring());
+ if (_wtoi64(userId) == m_uid)
+ continue;
+
+ if (!name.IsEmpty())
+ name.Append(L", ");
+ name += ur["name"].as_mstring();
+ }
+
+ if (name.GetLength() > 128) {
+ name.Truncate(125);
+ name.Append(L"...");
+ }
+ }
+
+ auto *si = Chat_NewSession(GCW_CHATROOM, m_szModuleName, chatId, name);
+ if (si == nullptr)
+ return nullptr;
+
+ setWString(si->hContact, DBKEY_ID, chatId);
+ Chat_AddGroup(si, TranslateT("Participant"));
+
+ for (auto &u : n["all_participants"]["nodes"]) {
+ auto &ur = u["messaging_actor"];
+ CMStringW userId(ur["id"].as_mstring());
+ CMStringW userName(ur["name"].as_mstring());
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN };
+ gce.pszID.w = chatId;
+ gce.pszUID.w = userId;
+ gce.pszNick.w = userName;
+ gce.bIsMe = _wtoi64(userId) == m_uid;
+ gce.time = time(0);
+ Chat_Event(&gce);
+ }
+
+ Chat_Control(m_szModuleName, chatId, m_bHideGroupchats ? WINDOW_HIDDEN : SESSION_INITDONE);
+ Chat_Control(m_szModuleName, chatId, SESSION_ONLINE);
+
+ __int64 userId = _wtoi64(chatId);
+ auto *pUser = FindUser(userId);
+
+ if (pUser == nullptr) {
+ mir_cslock lck(m_csUsers);
+ pUser = new FacebookUser(userId, si->hContact, true, true);
+ m_users.insert(pUser);
+ }
+ else {
+ pUser->hContact = si->hContact;
+ pUser->bIsChatInitialized = true;
+ }
+
+ return pUser;
+}
+
+FacebookUser* FacebookProto::RefreshThread(CMStringW &wszId)
+{
+ auto *pReq = CreateRequestGQL(FB_API_QUERY_THREAD);
+ pReq << WCHAR_PARAM("query_params", CMStringW(FORMAT, L"{\"0\":[\"%s\"], \"12\":0, \"13\":\"false\"}", wszId.c_str()));
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (!reply.error()) {
+ auto &root = reply.data();
+ for (auto &n : root)
+ return RefreshThread(n);
+ }
+
+ return nullptr;
+}
+
+void FacebookProto::RefreshThreads()
+{
+ int threadsLimit = 40;
+
+ auto * pReq = CreateRequestGQL(FB_API_QUERY_THREADS);
+ JSONNode json; json << INT_PARAM("1", threadsLimit) << CHAR_PARAM("2", "true") << CHAR_PARAM("12", "false") << CHAR_PARAM("13", "false");
+ pReq << CHAR_PARAM("query_params", json.write().c_str());
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (!reply.error()) {
+ auto &root = reply.data()["viewer"]["message_threads"];
+
+ for (auto &n : root["nodes"]) {
+ if (n["is_group_thread"].as_bool() && n["is_viewer_subscribed"].as_bool() && !n["has_viewer_archived"].as_bool())
+ RefreshThread(n);
+ }
+
+ // TODO: save timestamp of last message/action/... into DB
+ // TODO: lower threadsLimit to 10, load next pages if timestamp of last message is higher than timestamp in DB
+ }
+}
+
+int FacebookProto::RefreshToken()
+{
+ auto *pReq = CreateRequest(FB_API_URL_AUTH, "authenticate", "auth.login");
+ pReq->flags |= NLHRF_NODUMP;
+ pReq << CHAR_PARAM("email", getMStringA(DBKEY_LOGIN));
+ pReq << CHAR_PARAM("password", getMStringA(DBKEY_PASS));
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (reply.error())
+ return reply.error();
+
+ m_szAuthToken = reply.data()["access_token"].as_mstring();
+ setString(DBKEY_TOKEN, m_szAuthToken);
+
+ CMStringA m_szUid = reply.data()["uid"].as_mstring();
+ setString(DBKEY_ID, m_szUid);
+ m_uid = _atoi64(m_szUid);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void FacebookProto::ServerThread(void *)
+{
+ m_QueueCreated = false;
+
+LBL_Begin:
+ m_szAuthToken = getMStringA(DBKEY_TOKEN);
+ if (m_szAuthToken.IsEmpty()) {
+ if (int iErrorCode = RefreshToken()) {
+ ConnectionFailed(iErrorCode);
+ return;
+ }
+ }
+
+ int iErrorCode = RefreshContacts();
+ if (iErrorCode != 0) {
+ if (iErrorCode == 401) {
+ delSetting(DBKEY_TOKEN);
+ goto LBL_Begin;
+ }
+
+ ConnectionFailed(iErrorCode);
+ return;
+ }
+
+ // connect to MQTT server
+ m_mqttConn = Netlib_OpenConnection(m_hNetlibUser, "mqtt.facebook.com", 443, 0, NLOCF_SSL);
+ if (m_mqttConn == nullptr) {
+ debugLogA("connection failed, exiting");
+ ConnectionFailed();
+ return;
+ }
+
+ // send initial packet
+ MqttLogin();
+
+ while (!Miranda_IsTerminated()) {
+ NETLIBSELECT nls = {};
+ nls.hReadConns[0] = m_mqttConn;
+ nls.dwTimeout = 1000;
+ int ret = Netlib_Select(&nls);
+ if (ret == SOCKET_ERROR) {
+ debugLogA("Netlib_Recv() failed, error=%d", WSAGetLastError());
+ break;
+ }
+
+ // no data, continue waiting
+ if (ret == 0)
+ continue;
+
+ MqttMessage msg;
+ if (!MqttRead(msg)) {
+ debugLogA("MqttRead() failed");
+ break;
+ }
+
+ if (!MqttParse(msg)) {
+ debugLogA("MqttParse() failed");
+ break;
+ }
+ }
+
+ debugLogA("exiting ServerThread");
+
+ Netlib_CloseHandle(m_mqttConn);
+ m_mqttConn = nullptr;
+
+ OnLoggedOut();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int FacebookProto::OnMarkedRead(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;
+
+ if (m_bKeepUnread)
+ return 0;
+
+ JSONNode root; root << BOOL_PARAM("state", true) << INT_PARAM("syncSeqId", m_sid) << CHAR_PARAM("mark", "read");
+ if (isChatRoom(hContact))
+ root << CHAR_PARAM("threadFbId", getMStringA(hContact, DBKEY_ID));
+ else
+ root << CHAR_PARAM("otherUserFbId", getMStringA(hContact, DBKEY_ID));
+ MqttPublish("/mark_thread", root);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void FacebookProto::OnPublish(const char *topic, const uint8_t *p, size_t cbLen)
+{
+ FbThriftReader rdr;
+
+ // that might be a zipped buffer
+ if (cbLen >= 2) {
+ size_t dataSize;
+ void *pData = doUnzip(cbLen, p, dataSize);
+ if (pData != nullptr) {
+ debugLogA("UNZIP: %d bytes unzipped ok", dataSize);
+ Netlib_Dump(m_mqttConn, pData, dataSize, false, 0);
+ rdr.reset(dataSize, pData);
+ mir_free(pData);
+ }
+ }
+
+ if (rdr.size() == 0)
+ rdr.reset(cbLen, (void*)p);
+
+ if (!strcmp(topic, "/t_p"))
+ OnPublishPresence(rdr);
+ else if (!strcmp(topic, "/t_ms"))
+ OnPublishMessage(rdr);
+ else if (!strcmp(topic, "/orca_typing_notifications"))
+ OnPublishUtn(rdr);
+}
+
+void FacebookProto::OnPublishPresence(FbThriftReader &rdr)
+{
+ char *str = nullptr;
+ rdr.readStr(str);
+ mir_free(str);
+
+ bool bVal;
+ uint8_t fieldType;
+ uint16_t fieldId;
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_BOOL);
+ assert(fieldId == 1);
+ rdr.readBool(bVal);
+
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_LIST);
+ assert(fieldId == 1);
+
+ uint32_t size;
+ rdr.readList(fieldType, size);
+ assert(fieldType == FB_THRIFT_TYPE_STRUCT);
+
+ debugLogA("Received list of presences: %d records", size);
+ for (uint32_t i = 0; i < size; i++) {
+ uint64_t userId, timestamp, voipBits;
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_I64);
+ assert(fieldId == 1);
+ rdr.readInt64(userId);
+
+ uint32_t u32;
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_I32);
+ assert(fieldId == 1);
+ rdr.readInt32(u32);
+
+ auto *pUser = FindUser(userId);
+ if (pUser == nullptr)
+ debugLogA("Skipping presence from unknown user %lld", userId);
+ else {
+ debugLogA("Presence from user %lld => %d", userId, u32);
+ setWord(pUser->hContact, "Status", (u32 != 0) ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE);
+ }
+
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_I64);
+ assert(fieldId == 1 || fieldId == 3 || fieldId == 4);
+ rdr.readInt64(timestamp);
+
+ while (!rdr.isStop()) {
+ rdr.readField(fieldType, fieldId);
+ assert(fieldType == FB_THRIFT_TYPE_I64 || fieldType == FB_THRIFT_TYPE_I16 || fieldType == FB_THRIFT_TYPE_I32);
+ rdr.readIntV(voipBits);
+ }
+
+ rdr.readByte(fieldType);
+ assert(fieldType == FB_THRIFT_TYPE_STOP);
+ }
+
+ rdr.readByte(fieldType);
+ assert(fieldType == FB_THRIFT_TYPE_STOP);
+}
+
+void FacebookProto::OnPublishUtn(FbThriftReader &rdr)
+{
+ JSONNode root = JSONNode::parse(rdr.rest());
+ auto *pUser = FindUser(_wtoi64(root["sender_fbid"].as_mstring()));
+ if (pUser != nullptr) {
+ int length = (root["state"].as_int() == 0) ? PROTOTYPE_CONTACTTYPING_OFF : 60;
+ CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, length);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct
+{
+ const char *messageType;
+ void (FacebookProto:: *pFunc)(const JSONNode &);
+}
+static MsgHandlers[] =
+{
+ { "deltaNewMessage", &FacebookProto::OnPublishPrivateMessage },
+ { "deltaThreadName", &FacebookProto::OnPublishThreadName },
+ { "deltaSentMessage", &FacebookProto::OnPublishSentMessage },
+ { "deltaReadReceipt", &FacebookProto::OnPublishReadReceipt },
+ { "deltaParticipantsAddedToGroupThread", &FacebookProto::OnPublishChatJoin },
+ { "deltaParticipantLeftGroupThread", &FacebookProto::OnPublishChatLeave },
+};
+
+void FacebookProto::OnPublishMessage(FbThriftReader &rdr)
+{
+ uint8_t stop;
+ if (rdr.isStop())
+ rdr.readByte(stop);
+ else {
+ uint8_t type;
+ uint16_t id;
+ rdr.readField(type, id);
+ _ASSERT(type == FB_THRIFT_TYPE_STRING);
+ _ASSERT(id == 1 || id == 2);
+
+ char *szShit = nullptr;
+ rdr.readStr(szShit);
+ mir_free(szShit);
+
+ rdr.readByte(stop);
+ }
+
+ CMStringA szJson(rdr.rest());
+ debugLogA("MS: <%s>", szJson.c_str());
+ JSONNode root = JSONNode::parse(szJson);
+
+ CMStringA errorCode = root["errorCode"].as_mstring();
+ if (!errorCode.IsEmpty()) {
+ if (!m_QueueCreated && (errorCode == "ERROR_QUEUE_OVERFLOW" || errorCode == "ERROR_QUEUE_NOT_FOUND" || errorCode == "ERROR_QUEUE_LOST" || errorCode == "ERROR_QUEUE_EXCEEDS_MAX_DELTAS")) {
+ m_QueueCreated = true; // prevent queue creation request from being sent twice
+ delSetting(DBKEY_SYNC_TOKEN); m_szSyncToken.Empty();
+ delSetting(DBKEY_SID); m_sid = 0;
+ if (!RefreshSid()) {
+ ConnectionFailed();
+ return;
+ }
+
+ MqttQueueConnect();
+ }
+ }
+
+ CMStringW str = root["lastIssuedSeqId"].as_mstring();
+ if (!str.IsEmpty()) {
+ setWString(DBKEY_SID, str);
+ m_sid = _wtoi64(str);
+ }
+
+ str = root["syncToken"].as_mstring();
+ if (!str.IsEmpty()) {
+ m_szSyncToken = str;
+ setString(DBKEY_SYNC_TOKEN, m_szSyncToken);
+ return;
+ }
+
+ for (auto &it : root["deltas"]) {
+ for (auto &handler : MsgHandlers) {
+ auto &json = it[handler.messageType];
+ if (json) {
+ (this->*(handler.pFunc))(json);
+ break;
+ }
+ }
+ }
+}
+
+// new message arrived
+struct
+{
+ const char *szTag, *szClientVersion;
+}
+static facebookClients[] =
+{
+ { "source:titan:web", "Facebook (website)" },
+ { "app_id:256002347743983", "Facebook (Facebook Messenger)" }
+};
+
+void FacebookProto::FetchAttach(const CMStringA &mid, __int64 fbid, CMStringA &szBody)
+{
+ for (int iAttempt = 0; iAttempt < 5; iAttempt++) {
+ auto *pReq = CreateRequest(FB_API_URL_ATTACH, "getAttachment", "messaging.getAttachment");
+ pReq << CHAR_PARAM("mid", mid) << INT64_PARAM("aid", fbid);
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ switch (reply.error()) {
+ case 0:
+ {
+ std::string uri = reply.data()["redirect_uri"].as_string();
+ std::string type = reply.data()["content_type"].as_string();
+ if (!uri.empty())
+ szBody.AppendFormat("\r\n%s: %s", TranslateU(type.find("image/") != -1 ? "Picture attachment" : "File attachment"), uri.c_str());
+ }
+ return;
+
+ case 509: // attachment isn't ready, wait a bit and retry
+ ::Sleep(100);
+ continue;
+
+ default: // shit happened, exiting
+ return;
+ }
+ }
+}
+
+void FacebookProto::OnPublishPrivateMessage(const JSONNode &root)
+{
+ auto &metadata = root["messageMetadata"];
+ __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
+ if (!offlineId) {
+ debugLogA("We care about messages only, event skipped");
+ return;
+ }
+
+ bool bIsChat;
+ CMStringW wszUserId;
+ auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
+
+ if (!bIsChat && pUser == nullptr)
+ pUser = AddContact(wszUserId, true);
+ else if (bIsChat && (pUser == nullptr || !pUser->bIsChatInitialized)) // chat room does not exists or is not initialized
+ pUser = RefreshThread(wszUserId);
+
+ if (pUser == nullptr) {
+ debugLogA("User not found and adding failed, event skipped");
+ return;
+ }
+
+ for (auto &it : metadata["tags"]) {
+ auto *szTagName = it.name();
+ for (auto &cli : facebookClients) {
+ if (!mir_strcmp(szTagName, cli.szTag)) {
+ setString(pUser->hContact, "MirVer", cli.szClientVersion);
+ break;
+ }
+ }
+ }
+
+ CMStringA szId(metadata["messageId"].as_mstring());
+ if (CheckOwnMessage(pUser, offlineId, szId)) {
+ debugLogA("own message <%s> skipped", szId.c_str());
+ return;
+ }
+
+ if (db_event_getById(m_szModuleName, szId)) {
+ debugLogA("this message <%s> was already stored, exiting", szId.c_str());
+ return;
+ }
+
+ // parse message body
+ CMStringA szBody(root["body"].as_string().c_str());
+ if (szBody.IsEmpty())
+ szBody = metadata["snippet"].as_string().c_str();
+
+ // parse stickers
+ CMStringA stickerId = root["stickerId"].as_mstring();
+ if (!stickerId.IsEmpty()) {
+ if (ServiceExists(MS_SMILEYADD_LOADCONTACTSMILEYS)) {
+ CMStringW wszPath(FORMAT, L"%s\\%S\\Stickers", VARSW(L"%miranda_avatarcache%").get(), m_szModuleName);
+ CreateDirectoryTreeW(wszPath);
+
+ bool bSuccess = false;
+ CMStringW wszFileName(FORMAT, L"%s\\STK{%S}.png", wszPath.c_str(), stickerId.c_str());
+ uint32_t dwAttrib = GetFileAttributesW(wszFileName);
+ if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
+ wszFileName.Format(L"%s\\STK{%S}.webp", wszPath.c_str(), stickerId.c_str());
+ dwAttrib = GetFileAttributesW(wszFileName);
+ }
+
+ // new sticker
+ if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
+ auto *pReq = CreateRequestGQL(FB_API_QUERY_STICKER);
+ pReq << CHAR_PARAM("query_params", CMStringA(FORMAT, "{\"0\":[\"%s\"]}", stickerId.c_str()));
+ pReq->CalcSig();
+
+ JsonReply reply(ExecuteRequest(pReq));
+ if (!reply.error()) {
+ for (auto &sticker : reply.data()) {
+ // std::string szUrl = sticker["animated_image"]["uri"].as_string();
+ // if (szUrl.empty())
+ // szUrl = sticker["thread_image"]["uri"].as_string();
+ // else
+ // wszFileName.Format(L"%s\\STK{%S}.webp", wszPath.c_str(), stickerId.c_str());
+ std::string szUrl = sticker["thread_image"]["uri"].as_string();
+
+ NETLIBHTTPREQUEST req = {};
+ req.cbSize = sizeof(req);
+ req.flags = NLHRF_NODUMP | NLHRF_SSL | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ req.requestType = REQUEST_GET;
+ req.szUrl = (char*)szUrl.c_str();
+
+ NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(m_hNetlibUser, &req);
+ if (pReply != nullptr && pReply->resultCode == 200 && pReply->pData && pReply->dataLength) {
+ bSuccess = true;
+ FILE *out = _wfopen(wszFileName, L"wb");
+ fwrite(pReply->pData, 1, pReply->dataLength, out);
+ fclose(out);
+ }
+ }
+ }
+ }
+ else bSuccess = true;
+
+ if (bSuccess) {
+ if (!szBody.IsEmpty())
+ szBody += "\r\n";
+ szBody += "STK{" + stickerId + "}";
+
+ SMADD_CONT cont = { 1, m_szModuleName, wszFileName };
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
+ }
+ else szBody += TranslateU("Sticker received");
+ }
+ else szBody += TranslateU("SmileyAdd plugin required to support stickers");
+ }
+
+ // parse attachments (links, files, ...)
+ for (auto &it : root["attachments"]) {
+ // madness... json inside json
+ CMStringA szJson(it["xmaGraphQL"].as_mstring());
+ if (szJson.IsEmpty()) {
+ __int64 fbid = _wtoi64(it["fbid"].as_mstring());
+ if (fbid == 0) {
+ debugLogA("Neither a GQL nor an inline attachment, nothing to do");
+ continue;
+ }
+
+ // inline attachment, request its description
+ FetchAttach(szId, fbid, szBody);
+ continue;
+ }
+
+ JSONROOT nBody(szJson);
+ if (!nBody)
+ continue;
+
+ const JSONNode &attach = (*nBody).at((json_index_t)0)["story_attachment"];
+ szBody += "\r\n-----------------------------------";
+
+ CMStringA str = attach["url"].as_mstring();
+ if (!str.IsEmpty()) {
+ if (str.Left(8) == "fbrpc://") {
+ int iStart = str.Find("target_url=");
+ if (iStart != 0) {
+ CMStringA tmp;
+
+ iStart += 11;
+ int iEnd = str.Find("&", iStart);
+ if (iEnd != -1)
+ tmp = str.Mid(iStart, iEnd - iStart);
+ else
+ tmp = str.Right(iStart);
+
+ mir_urlDecode(tmp.GetBuffer());
+ szBody.AppendFormat("\r\n\t%s: %s", TranslateU("URL"), tmp.c_str());
+ }
+ }
+ else szBody.AppendFormat("\r\n\t%s: %s", TranslateU("URL"), str.c_str());
+ }
+
+ str = attach["title"].as_string().c_str();
+ if (!str.IsEmpty())
+ szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Title"), str.c_str());
+
+ str = attach["source"]["text"].as_string().c_str();
+ if (!str.IsEmpty())
+ szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Source"), str.c_str());
+
+ str = attach["description"]["text"].as_string().c_str();
+ if (!str.IsEmpty())
+ szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Description"), str.c_str());
+
+ str = attach["media"]["playable_url"].as_string().c_str();
+ if (!str.IsEmpty())
+ szBody.AppendFormat("\r\n\t%s: %s", TranslateU("Playable media"), str.c_str());
+ }
+
+ // if that's a group chat, send it to the room
+ CMStringW wszActorFbId(metadata["actorFbId"].as_mstring());
+ __int64 actorFbId = _wtoi64(wszActorFbId);
+
+ if (pUser->bIsChat) {
+ szBody.Replace("%", "%%");
+ ptrW wszText(mir_utf8decodeW(szBody));
+
+ // TODO: GC_EVENT_JOIN for chat participants which are missing (for example added later during group chat)
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE };
+ gce.pszID.w = wszUserId;
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszUID.w = wszActorFbId;
+ gce.pszText.w = wszText;
+ gce.time = time(0);
+ gce.bIsMe = actorFbId == m_uid;
+ Chat_Event(&gce);
+
+ debugLogA("New channel %lld message from %S: %s", pUser->id, gce.pszUID.w, gce.pszText.w);
+ }
+ else { // otherwise store a private message
+ PROTORECVEVENT pre = {};
+ pre.timestamp = uint32_t(_wtoi64(metadata["timestamp"].as_mstring()) / 1000);
+ pre.szMessage = (char *)szBody.c_str();
+ pre.szMsgId = (char *)szId.c_str();
+
+ if (m_uid == actorFbId)
+ pre.flags |= PREF_SENT;
+
+ ProtoChainRecvMsg(pUser->hContact, &pre);
+ }
+}
+
+// changing thread name
+void FacebookProto::OnPublishThreadName(const JSONNode &root)
+{
+ auto &metadata = root["messageMetadata"];
+ __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
+ if (!offlineId) {
+ debugLogA("We care about messages only, event skipped");
+ return;
+ }
+
+ bool bIsChat;
+ CMStringW wszUserId;
+ auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
+ if (!bIsChat || pUser == nullptr)
+ return;
+
+ CMStringW wszTitle = root["name"].as_mstring();
+ if (!wszTitle.IsEmpty())
+ setWString(pUser->hContact, DBKEY_NICK, wszTitle);
+ else
+ delSetting(pUser->hContact, DBKEY_NICK);
+}
+
+// user joined chat
+void FacebookProto::OnPublishChatJoin(const JSONNode &root)
+{
+ auto &metadata = root["messageMetadata"];
+ __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
+ if (!offlineId) {
+ debugLogA("We care about messages only, event skipped");
+ return;
+ }
+
+ bool bIsChat;
+ CMStringW wszUserId;
+ auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
+ if (!bIsChat || pUser == nullptr)
+ return;
+
+ CMStringW wszText(metadata["adminText"].as_mstring());
+ for (auto &it : root["addedParticipants"]) {
+ CMStringW wszNick(it["fullName"].as_mstring()), wszId(it["userFbId"].as_mstring());
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_JOIN };
+ gce.pszID.w = wszUserId;
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszNick.w = wszNick;
+ gce.pszUID.w = wszId;
+ gce.pszText.w = wszText;
+ gce.time = time(0);
+ gce.bIsMe = _wtoi64(wszId) == m_uid;
+ Chat_Event(&gce);
+ }
+}
+
+// user left chat
+void FacebookProto::OnPublishChatLeave(const JSONNode &root)
+{
+ auto &metadata = root["messageMetadata"];
+ __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
+ if (!offlineId) {
+ debugLogA("We care about messages only, event skipped");
+ return;
+ }
+
+ bool bIsChat;
+ CMStringW wszUserId;
+ auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
+ if (!bIsChat || pUser == nullptr)
+ return;
+
+ CMStringW wszText(metadata["adminText"].as_mstring()), wszId(root["leftParticipantFbId"].as_mstring());
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_PART };
+ gce.pszID.w = wszUserId;
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszUID.w = wszId;
+ gce.pszText.w = wszText;
+ gce.time = time(0);
+ gce.bIsMe = _wtoi64(wszId) == m_uid;
+ Chat_Event(&gce);
+}
+
+// read notification
+void FacebookProto::OnPublishReadReceipt(const JSONNode &root)
+{
+ CMStringW wszUserId;
+ bool bIsChat;
+ auto *pUser = UserFromJson(root, wszUserId, bIsChat);
+ if (pUser == nullptr) {
+ debugLogA("Message from unknown contact %S, ignored", wszUserId.c_str());
+ return;
+ }
+
+ uint32_t timestamp = _wtoi64(root["watermarkTimestampMs"].as_mstring());
+ for (MEVENT ev = db_event_firstUnread(pUser->hContact); ev != 0; ev = db_event_next(pUser->hContact, ev)) {
+ DBEVENTINFO dbei = {};
+ if (db_event_get(ev, &dbei))
+ continue;
+
+ if (dbei.timestamp > timestamp)
+ break;
+
+ if (!dbei.markedRead())
+ db_event_markRead(pUser->hContact, ev);
+ }
+}
+
+// my own message was sent
+bool FacebookProto::CheckOwnMessage(FacebookUser *pUser, __int64 offlineId, const char *pszMsgId)
+{
+ COwnMessage tmp;
+ if (!ExtractOwnMessage(offlineId, tmp))
+ return false;
+
+ if (pUser->bIsChat) {
+ CMStringW wszId(FORMAT, L"%lld", m_uid);
+ tmp.wszText.Replace(L"%", L"%%");
+
+ wchar_t userId[100];
+ _i64tow_s(pUser->id, userId, _countof(userId), 10);
+
+ GCEVENT gce = { m_szModuleName, 0, GC_EVENT_MESSAGE };
+ gce.pszID.w = userId;
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.pszUID.w = wszId;
+ gce.pszText.w = tmp.wszText;
+ gce.time = time(0);
+ gce.bIsMe = true;
+ Chat_Event(&gce);
+ }
+ else ProtoBroadcastAck(pUser->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)tmp.reqId, (LPARAM)pszMsgId);
+
+ return true;
+}
+
+void FacebookProto::OnPublishSentMessage(const JSONNode &root)
+{
+ auto &metadata = root["messageMetadata"];
+
+ __int64 offlineId = _wtoi64(metadata["offlineThreadingId"].as_mstring());
+
+ CMStringW wszUserId;
+ bool bIsChat;
+ auto *pUser = UserFromJson(metadata, wszUserId, bIsChat);
+ if (pUser == nullptr) {
+ debugLogA("Message from unknown contact %s, ignored", wszUserId.c_str());
+ return;
+ }
+
+ std::string szMsgId(metadata["messageId"].as_string());
+ CheckOwnMessage(pUser, offlineId, szMsgId.c_str());
+}
diff --git a/protocols/Facebook/src/stdafx.cxx b/protocols/Facebook/src/stdafx.cxx
index d265a4c02e..8c570f6949 100644
--- a/protocols/Facebook/src/stdafx.cxx
+++ b/protocols/Facebook/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/Facebook/src/stdafx.h b/protocols/Facebook/src/stdafx.h
index 3d5f28057c..aadac15f0e 100644
--- a/protocols/Facebook/src/stdafx.h
+++ b/protocols/Facebook/src/stdafx.h
@@ -1,67 +1,67 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#pragma once
-
-#include <windows.h>
-#include <malloc.h>
-#include <time.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <assert.h>
-
-#include <newpluginapi.h>
-#include <m_avatars.h>
-#include <m_chat_int.h>
-#include <m_clistint.h>
-#include <m_contacts.h>
-#include <m_database.h>
-#include <m_idle.h>
-#include <m_ignore.h>
-#include <m_langpack.h>
-#include <m_message.h>
-#include <m_netlib.h>
-#include <m_options.h>
-#include <m_popup.h>
-#include <m_protosvc.h>
-#include <m_protoint.h>
-#include <m_skin.h>
-#include <m_icolib.h>
-#include <m_hotkeys.h>
-#include <m_folders.h>
-#include <m_smileyadd.h>
-#include <m_toptoolbar.h>
-#include <m_json.h>
-#include <m_imgsrvc.h>
-#include <m_http.h>
-#include <m_messagestate.h>
-#include <m_gui.h>
-
-#include "../../libs/zlib/src/zlib.h"
-
-#include "db.h"
-#include "dialogs.h"
-#include "mqtt.h"
-#include "proto.h"
-#include "resource.h"
-#include "version.h"
-
-extern bool g_bMessageState;
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#pragma once
+
+#include <windows.h>
+#include <malloc.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+#include <newpluginapi.h>
+#include <m_avatars.h>
+#include <m_chat_int.h>
+#include <m_clistint.h>
+#include <m_contacts.h>
+#include <m_database.h>
+#include <m_idle.h>
+#include <m_ignore.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_skin.h>
+#include <m_icolib.h>
+#include <m_hotkeys.h>
+#include <m_folders.h>
+#include <m_smileyadd.h>
+#include <m_toptoolbar.h>
+#include <m_json.h>
+#include <m_imgsrvc.h>
+#include <m_http.h>
+#include <m_messagestate.h>
+#include <m_gui.h>
+
+#include "../../libs/zlib/src/zlib.h"
+
+#include "db.h"
+#include "dialogs.h"
+#include "mqtt.h"
+#include "proto.h"
+#include "resource.h"
+#include "version.h"
+
+extern bool g_bMessageState;
diff --git a/protocols/Facebook/src/thrift.cpp b/protocols/Facebook/src/thrift.cpp
index af0ceb4dd7..c7718c1a60 100644
--- a/protocols/Facebook/src/thrift.cpp
+++ b/protocols/Facebook/src/thrift.cpp
@@ -1,308 +1,308 @@
-/*
-
-Facebook plugin for Miranda NG
-Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
-
-*/
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// FbThrift class members
-
-#include "stdafx.h"
-
-static uint8_t encodeType(int type)
-{
- switch (type) {
- case FB_THRIFT_TYPE_BOOL:
- return 2;
- case FB_THRIFT_TYPE_BYTE:
- return 3;
- case FB_THRIFT_TYPE_I16:
- return 4;
- case FB_THRIFT_TYPE_I32:
- return 5;
- case FB_THRIFT_TYPE_I64:
- return 6;
- case FB_THRIFT_TYPE_DOUBLE:
- return 7;
- case FB_THRIFT_TYPE_STRING:
- return 8;
- case FB_THRIFT_TYPE_LIST:
- return 9;
- case FB_THRIFT_TYPE_SET:
- return 10;
- case FB_THRIFT_TYPE_MAP:
- return 11;
- case FB_THRIFT_TYPE_STRUCT:
- return 12;
- default:
- return 0;
- }
-}
-
-FbThrift& FbThrift::operator<<(uint8_t value)
-{
- m_buf.append(&value, 1);
- return *this;
-}
-
-FbThrift& FbThrift::operator<<(const char *str)
-{
- size_t len = mir_strlen(str);
- writeIntV(len);
- m_buf.append(str, len);
- return *this;
-}
-
-void FbThrift::writeBool(bool bValue)
-{
- uint8_t b = (bValue) ? 0x11 : 0x12;
- m_buf.append(&b, 1);
-}
-
-void FbThrift::writeBuf(const void *pData, size_t cbLen)
-{
- m_buf.append(pData, cbLen);
-}
-
-void FbThrift::writeField(int iType)
-{
- uint8_t type = encodeType(iType) + 0x10;
- m_buf.append(&type, 1);
-}
-
-void FbThrift::writeField(int iType, int id, int lastid)
-{
- uint8_t type = encodeType(iType);
- uint8_t diff = uint8_t(id - lastid);
- if (diff > 0x0F) {
- m_buf.append(&type, 1);
- writeInt64(id);
- }
- else {
- type += (diff << 4);
- m_buf.append(&type, 1);
- }
-}
-
-void FbThrift::writeList(int iType, int size)
-{
- uint8_t type = encodeType(iType);
- if (size > 14) {
- writeIntV(size);
- *this << (type | 0xF0);
- }
- else *this << (type | (size << 4));
-}
-
-void FbThrift::writeInt16(uint16_t value)
-{
- value = htons(value);
- m_buf.append(&value, sizeof(value));
-}
-
-void FbThrift::writeInt32(int32_t value)
-{
- writeIntV((value << 1) ^ (value >> 31));
-}
-
-void FbThrift::writeInt64(int64_t value)
-{
- writeIntV((value << 1) ^ (value >> 63));
-}
-
-void FbThrift::writeIntV(uint64_t value)
-{
- bool bLast;
- do {
- bLast = (value & ~0x7F) == 0;
- uint8_t b = value & 0x7F;
- if (!bLast) {
- b |= 0x80;
- value >>= 7;
- }
- m_buf.append(&b, 1);
- } while (!bLast);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// FbThriftReader class members
-
-uint8_t FbThriftReader::decodeType(int type)
-{
- switch (type) {
- case 0:
- return FB_THRIFT_TYPE_STOP;
- case 1:
- m_lastBval = m_lastBool = true;
- return FB_THRIFT_TYPE_BOOL;
- case 2:
- m_lastBool = true;
- m_lastBval = false;
- return FB_THRIFT_TYPE_BOOL;
- case 3:
- return FB_THRIFT_TYPE_BYTE;
- case 4:
- return FB_THRIFT_TYPE_I16;
- case 5:
- return FB_THRIFT_TYPE_I32;
- case 6:
- return FB_THRIFT_TYPE_I64;
- case 7:
- return FB_THRIFT_TYPE_DOUBLE;
- case 8:
- return FB_THRIFT_TYPE_STRING;
- case 9:
- return FB_THRIFT_TYPE_LIST;
- case 10:
- return FB_THRIFT_TYPE_SET;
- case 11:
- return FB_THRIFT_TYPE_MAP;
- case 12:
- return FB_THRIFT_TYPE_STRUCT;
- default:
- return 0;
- }
-}
-
-bool FbThriftReader::isStop()
-{
- byte b;
- if (!readByte(b))
- return true;
-
- offset--;
- return b == FB_THRIFT_TYPE_STOP;
-}
-
-bool FbThriftReader::readBool(bool &bVal)
-{
- if (m_lastBool) {
- bVal = m_lastBval;
- m_lastBool = false;
- return true;
- }
-
- byte b;
- if (!readByte(b))
- return false;
-
- bVal = b == 0x11;
- return true;
-}
-
-bool FbThriftReader::readByte(uint8_t &b)
-{
- if (offset >= size())
- return false;
-
- b = *((uint8_t *)data() + offset);
- offset++;
- return true;
-}
-
-bool FbThriftReader::readField(uint8_t &type, uint16_t &id)
-{
- byte b;
- if (!readByte(b))
- return false;
-
- type = decodeType(b & 0x0F);
- id = (b >> 4);
- return (id == 0) ? readInt16(id) : true;
-}
-
-bool FbThriftReader::readIntV(uint64_t &val)
-{
- uint8_t b;
- unsigned i = 0;
- val = 0;
-
- do {
- if (!readByte(b))
- return false;
-
- val |= (uint64_t(b & 0x7F) << i);
- i += 7;
- } while ((b & 0x80) != 0);
-
- return true;
-}
-
-bool FbThriftReader::readList(uint8_t &type, uint32_t &size)
-{
- byte b;
- if (!readByte(b))
- return false;
-
- type = decodeType(b & 0x0F);
- size = b >> 4;
- if (size == 0x0F) {
- uint64_t tmp;
- if (!readIntV(tmp))
- return false;
- size = (uint32_t)tmp;
- }
- return true;
-}
-
-bool FbThriftReader::readStr(char *&val)
-{
- uint64_t tmp;
- if (!readIntV(tmp))
- return false;
-
- uint32_t cbLen = (uint32_t)tmp;
- if (offset + cbLen >= size())
- return false;
-
- if (cbLen > 0) {
- val = mir_strndup((char *)data() + offset, cbLen);
- offset += cbLen;
- }
- else val = nullptr;
- return true;
-}
-
-bool FbThriftReader::readInt16(uint16_t &val)
-{
- if (offset + 2 >= size())
- return false;
-
- val = ntohs(*(u_short *)((char *)data() + offset));
- offset += 2;
- return true;
-}
-
-bool FbThriftReader::readInt32(uint32_t &val)
-{
- uint64_t tmp;
- if (!readIntV(tmp))
- return false;
-
- val = (uint32_t )tmp;
- return true;
-}
-
-bool FbThriftReader::readInt64(uint64_t &val)
-{
- uint64_t tmp;
- if (!readIntV(tmp))
- return false;
-
- val = (tmp >> 0x01) ^ -(tmp & 0x01);
- return true;
-}
+/*
+
+Facebook plugin for Miranda NG
+Copyright © 2019-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, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// FbThrift class members
+
+#include "stdafx.h"
+
+static uint8_t encodeType(int type)
+{
+ switch (type) {
+ case FB_THRIFT_TYPE_BOOL:
+ return 2;
+ case FB_THRIFT_TYPE_BYTE:
+ return 3;
+ case FB_THRIFT_TYPE_I16:
+ return 4;
+ case FB_THRIFT_TYPE_I32:
+ return 5;
+ case FB_THRIFT_TYPE_I64:
+ return 6;
+ case FB_THRIFT_TYPE_DOUBLE:
+ return 7;
+ case FB_THRIFT_TYPE_STRING:
+ return 8;
+ case FB_THRIFT_TYPE_LIST:
+ return 9;
+ case FB_THRIFT_TYPE_SET:
+ return 10;
+ case FB_THRIFT_TYPE_MAP:
+ return 11;
+ case FB_THRIFT_TYPE_STRUCT:
+ return 12;
+ default:
+ return 0;
+ }
+}
+
+FbThrift& FbThrift::operator<<(uint8_t value)
+{
+ m_buf.append(&value, 1);
+ return *this;
+}
+
+FbThrift& FbThrift::operator<<(const char *str)
+{
+ size_t len = mir_strlen(str);
+ writeIntV(len);
+ m_buf.append(str, len);
+ return *this;
+}
+
+void FbThrift::writeBool(bool bValue)
+{
+ uint8_t b = (bValue) ? 0x11 : 0x12;
+ m_buf.append(&b, 1);
+}
+
+void FbThrift::writeBuf(const void *pData, size_t cbLen)
+{
+ m_buf.append(pData, cbLen);
+}
+
+void FbThrift::writeField(int iType)
+{
+ uint8_t type = encodeType(iType) + 0x10;
+ m_buf.append(&type, 1);
+}
+
+void FbThrift::writeField(int iType, int id, int lastid)
+{
+ uint8_t type = encodeType(iType);
+ uint8_t diff = uint8_t(id - lastid);
+ if (diff > 0x0F) {
+ m_buf.append(&type, 1);
+ writeInt64(id);
+ }
+ else {
+ type += (diff << 4);
+ m_buf.append(&type, 1);
+ }
+}
+
+void FbThrift::writeList(int iType, int size)
+{
+ uint8_t type = encodeType(iType);
+ if (size > 14) {
+ writeIntV(size);
+ *this << (type | 0xF0);
+ }
+ else *this << (type | (size << 4));
+}
+
+void FbThrift::writeInt16(uint16_t value)
+{
+ value = htons(value);
+ m_buf.append(&value, sizeof(value));
+}
+
+void FbThrift::writeInt32(int32_t value)
+{
+ writeIntV((value << 1) ^ (value >> 31));
+}
+
+void FbThrift::writeInt64(int64_t value)
+{
+ writeIntV((value << 1) ^ (value >> 63));
+}
+
+void FbThrift::writeIntV(uint64_t value)
+{
+ bool bLast;
+ do {
+ bLast = (value & ~0x7F) == 0;
+ uint8_t b = value & 0x7F;
+ if (!bLast) {
+ b |= 0x80;
+ value >>= 7;
+ }
+ m_buf.append(&b, 1);
+ } while (!bLast);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// FbThriftReader class members
+
+uint8_t FbThriftReader::decodeType(int type)
+{
+ switch (type) {
+ case 0:
+ return FB_THRIFT_TYPE_STOP;
+ case 1:
+ m_lastBval = m_lastBool = true;
+ return FB_THRIFT_TYPE_BOOL;
+ case 2:
+ m_lastBool = true;
+ m_lastBval = false;
+ return FB_THRIFT_TYPE_BOOL;
+ case 3:
+ return FB_THRIFT_TYPE_BYTE;
+ case 4:
+ return FB_THRIFT_TYPE_I16;
+ case 5:
+ return FB_THRIFT_TYPE_I32;
+ case 6:
+ return FB_THRIFT_TYPE_I64;
+ case 7:
+ return FB_THRIFT_TYPE_DOUBLE;
+ case 8:
+ return FB_THRIFT_TYPE_STRING;
+ case 9:
+ return FB_THRIFT_TYPE_LIST;
+ case 10:
+ return FB_THRIFT_TYPE_SET;
+ case 11:
+ return FB_THRIFT_TYPE_MAP;
+ case 12:
+ return FB_THRIFT_TYPE_STRUCT;
+ default:
+ return 0;
+ }
+}
+
+bool FbThriftReader::isStop()
+{
+ byte b;
+ if (!readByte(b))
+ return true;
+
+ offset--;
+ return b == FB_THRIFT_TYPE_STOP;
+}
+
+bool FbThriftReader::readBool(bool &bVal)
+{
+ if (m_lastBool) {
+ bVal = m_lastBval;
+ m_lastBool = false;
+ return true;
+ }
+
+ byte b;
+ if (!readByte(b))
+ return false;
+
+ bVal = b == 0x11;
+ return true;
+}
+
+bool FbThriftReader::readByte(uint8_t &b)
+{
+ if (offset >= size())
+ return false;
+
+ b = *((uint8_t *)data() + offset);
+ offset++;
+ return true;
+}
+
+bool FbThriftReader::readField(uint8_t &type, uint16_t &id)
+{
+ byte b;
+ if (!readByte(b))
+ return false;
+
+ type = decodeType(b & 0x0F);
+ id = (b >> 4);
+ return (id == 0) ? readInt16(id) : true;
+}
+
+bool FbThriftReader::readIntV(uint64_t &val)
+{
+ uint8_t b;
+ unsigned i = 0;
+ val = 0;
+
+ do {
+ if (!readByte(b))
+ return false;
+
+ val |= (uint64_t(b & 0x7F) << i);
+ i += 7;
+ } while ((b & 0x80) != 0);
+
+ return true;
+}
+
+bool FbThriftReader::readList(uint8_t &type, uint32_t &size)
+{
+ byte b;
+ if (!readByte(b))
+ return false;
+
+ type = decodeType(b & 0x0F);
+ size = b >> 4;
+ if (size == 0x0F) {
+ uint64_t tmp;
+ if (!readIntV(tmp))
+ return false;
+ size = (uint32_t)tmp;
+ }
+ return true;
+}
+
+bool FbThriftReader::readStr(char *&val)
+{
+ uint64_t tmp;
+ if (!readIntV(tmp))
+ return false;
+
+ uint32_t cbLen = (uint32_t)tmp;
+ if (offset + cbLen >= size())
+ return false;
+
+ if (cbLen > 0) {
+ val = mir_strndup((char *)data() + offset, cbLen);
+ offset += cbLen;
+ }
+ else val = nullptr;
+ return true;
+}
+
+bool FbThriftReader::readInt16(uint16_t &val)
+{
+ if (offset + 2 >= size())
+ return false;
+
+ val = ntohs(*(u_short *)((char *)data() + offset));
+ offset += 2;
+ return true;
+}
+
+bool FbThriftReader::readInt32(uint32_t &val)
+{
+ uint64_t tmp;
+ if (!readIntV(tmp))
+ return false;
+
+ val = (uint32_t )tmp;
+ return true;
+}
+
+bool FbThriftReader::readInt64(uint64_t &val)
+{
+ uint64_t tmp;
+ if (!readIntV(tmp))
+ return false;
+
+ val = (tmp >> 0x01) ^ -(tmp & 0x01);
+ return true;
+}
diff --git a/protocols/Facebook/src/version.h b/protocols/Facebook/src/version.h
index 4a483c02f1..660e8cfe94 100644
--- a/protocols/Facebook/src/version.h
+++ b/protocols/Facebook/src/version.h
@@ -10,4 +10,4 @@
#define __DESCRIPTION "Facebook protocol support for Miranda NG."
#define __AUTHOR "Miranda NG Team"
#define __AUTHORWEB "https://miranda-ng.org/p/Facebook"
-#define __COPYRIGHT "© 2019-22 Miranda NG team"
+#define __COPYRIGHT "© 2019-23 Miranda NG team"