/* Facebook plugin for Miranda NG Copyright © 2019-20 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 . */ #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/248.0.0.7.127;FBPN/com.facebook.orca;FBLC/en_US;FBBV/194293860]" /** * 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 { struct Param { Param(const char *p1, const char *p2) : key(p1), val(p2) {} CMStringA key, val; }; OBJLIST 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 { 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(); 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 arOwnMessages; bool ExtractOwnMessage(__int64 msgId, COwnMessage &res); OBJLIST m_users; FacebookUser* FindUser(__int64 id) { return m_users.find((FacebookUser *)&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(); bool RefreshSid(); bool RefreshToken(); FacebookUser* RefreshThread(JSONNode &n); FacebookUser* RefreshThread(CMStringW &wszId); 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); ////////////////////////////////////////////////////////////////////////////////////// // options CMOption m_wszDefaultGroup; // clist group to store contacts CMOption m_bUseBigAvatars; // use big or small avatars by default CMOption m_bUseGroupchats; // do we need group chats at all? CMOption m_bHideGroupchats; // do not open chat windows on creation CMOption m_bLoginInvisible; // login in the invisible mode CMOption m_bKeepUnread; // do not mark incoming messages as read CMOption m_bLoadAll; // load all contacts, not only those who have ARE_FRIENDS status //////////////////////////////////////////////////////////////////////////////////////// // PROTO_INTERFACE 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); 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 CFBDlgBase; struct CMPlugin : public ACCPROTOPLUGIN { CMPlugin(); int Load() override; };