/*
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;
};