diff options
author | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
---|---|---|
committer | dartraiden <wowemuh@gmail.com> | 2023-01-02 21:10:29 +0300 |
commit | 1979fd80424d16b2e489f9b57d01d9c7811d25a2 (patch) | |
tree | 960d42c5fe4a51f0fe2850bea91256e226bce221 /protocols/Facebook/src | |
parent | adfbbb217d4f4a05acf198755f219a5223d31c27 (diff) |
Update copyrights
Diffstat (limited to 'protocols/Facebook/src')
-rw-r--r-- | protocols/Facebook/src/avatars.cpp | 262 | ||||
-rw-r--r-- | protocols/Facebook/src/db.h | 88 | ||||
-rw-r--r-- | protocols/Facebook/src/dialogs.cpp | 156 | ||||
-rw-r--r-- | protocols/Facebook/src/dialogs.h | 2 | ||||
-rw-r--r-- | protocols/Facebook/src/groupchats.cpp | 510 | ||||
-rw-r--r-- | protocols/Facebook/src/http.cpp | 370 | ||||
-rw-r--r-- | protocols/Facebook/src/main.cpp | 144 | ||||
-rw-r--r-- | protocols/Facebook/src/mqtt.cpp | 688 | ||||
-rw-r--r-- | protocols/Facebook/src/mqtt.h | 278 | ||||
-rw-r--r-- | protocols/Facebook/src/options.cpp | 168 | ||||
-rw-r--r-- | protocols/Facebook/src/proto.cpp | 604 | ||||
-rw-r--r-- | protocols/Facebook/src/proto.h | 1114 | ||||
-rw-r--r-- | protocols/Facebook/src/server.cpp | 2102 | ||||
-rw-r--r-- | protocols/Facebook/src/stdafx.cxx | 36 | ||||
-rw-r--r-- | protocols/Facebook/src/stdafx.h | 134 | ||||
-rw-r--r-- | protocols/Facebook/src/thrift.cpp | 616 | ||||
-rw-r--r-- | protocols/Facebook/src/version.h | 2 |
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 ¶m) -{ - pReq->params.insert(new AsyncHttpRequest::Param(param.szName, param.szValue)); - return pReq; -} - -AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM ¶m) -{ - 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 ¶m)
+{
+ pReq->params.insert(new AsyncHttpRequest::Param(param.szName, param.szValue));
+ return pReq;
+}
+
+AsyncHttpRequest* operator<<(AsyncHttpRequest *pReq, const INT_PARAM ¶m)
+{
+ 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"
|