pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
1896a80ff8e3
Route GLib debug logging directly to the Finch debug window
Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.
Testing Done:
Open the Debug window an see some `GLib-*` outputs.
Reviewed at https://reviews.imfreedom.org/r/1057/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
<glib/gi18n-lib.h>
#include
<json-glib/json-glib.h>
#include
<libsoup/soup.h>
#include
<stdarg.h>
#include
<string.h>
#include
"libpurple/glibcompat.h"
#include
"api.h"
#include
"http.h"
#include
"json.h"
#include
"thrift.h"
#include
"util.h"
enum
{
PROP_0
,
PROP_CID
,
PROP_DID
,
PROP_MID
,
PROP_STOKEN
,
PROP_TOKEN
,
PROP_UID
,
PROP_N
};
typedef
struct
{
FbMqtt
*
mqtt
;
SoupSession
*
cons
;
PurpleConnection
*
gc
;
gboolean
retrying
;
FbId
uid
;
gint64
sid
;
guint64
mid
;
gchar
*
cid
;
gchar
*
did
;
gchar
*
stoken
;
gchar
*
token
;
GQueue
*
msgs
;
gboolean
invisible
;
guint
unread
;
FbId
lastmid
;
gchar
*
contacts_delta
;
}
FbApiPrivate
;
/**
* FbApi:
*
* Represents a Facebook Messenger connection.
*/
struct
_FbApi
{
GObject
parent
;
FbApiPrivate
*
priv
;
};
static
void
fb_api_error_literal
(
FbApi
*
api
,
FbApiError
error
,
const
gchar
*
msg
);
static
void
fb_api_attach
(
FbApi
*
api
,
FbId
aid
,
const
gchar
*
msgid
,
FbApiMessage
*
msg
);
static
void
fb_api_contacts_after
(
FbApi
*
api
,
const
gchar
*
cursor
);
static
void
fb_api_message_send
(
FbApi
*
api
,
FbApiMessage
*
msg
);
static
void
fb_api_sticker
(
FbApi
*
api
,
FbId
sid
,
FbApiMessage
*
msg
);
void
fb_api_contacts_delta
(
FbApi
*
api
,
const
gchar
*
delta_cursor
);
G_DEFINE_TYPE_WITH_PRIVATE
(
FbApi
,
fb_api
,
G_TYPE_OBJECT
);
static
void
fb_api_set_property
(
GObject
*
obj
,
guint
prop
,
const
GValue
*
val
,
GParamSpec
*
pspec
)
{
FbApiPrivate
*
priv
=
FB_API
(
obj
)
->
priv
;
switch
(
prop
)
{
case
PROP_CID
:
g_free
(
priv
->
cid
);
priv
->
cid
=
g_value_dup_string
(
val
);
break
;
case
PROP_DID
:
g_free
(
priv
->
did
);
priv
->
did
=
g_value_dup_string
(
val
);
break
;
case
PROP_MID
:
priv
->
mid
=
g_value_get_uint64
(
val
);
break
;
case
PROP_STOKEN
:
g_free
(
priv
->
stoken
);
priv
->
stoken
=
g_value_dup_string
(
val
);
break
;
case
PROP_TOKEN
:
g_free
(
priv
->
token
);
priv
->
token
=
g_value_dup_string
(
val
);
break
;
case
PROP_UID
:
priv
->
uid
=
g_value_get_int64
(
val
);
break
;
default
:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
obj
,
prop
,
pspec
);
break
;
}
}
static
void
fb_api_get_property
(
GObject
*
obj
,
guint
prop
,
GValue
*
val
,
GParamSpec
*
pspec
)
{
FbApiPrivate
*
priv
=
FB_API
(
obj
)
->
priv
;
switch
(
prop
)
{
case
PROP_CID
:
g_value_set_string
(
val
,
priv
->
cid
);
break
;
case
PROP_DID
:
g_value_set_string
(
val
,
priv
->
did
);
break
;
case
PROP_MID
:
g_value_set_uint64
(
val
,
priv
->
mid
);
break
;
case
PROP_STOKEN
:
g_value_set_string
(
val
,
priv
->
stoken
);
break
;
case
PROP_TOKEN
:
g_value_set_string
(
val
,
priv
->
token
);
break
;
case
PROP_UID
:
g_value_set_int64
(
val
,
priv
->
uid
);
break
;
default
:
G_OBJECT_WARN_INVALID_PROPERTY_ID
(
obj
,
prop
,
pspec
);
break
;
}
}
static
void
fb_api_dispose
(
GObject
*
obj
)
{
FbApiPrivate
*
priv
=
FB_API
(
obj
)
->
priv
;
soup_session_abort
(
priv
->
cons
);
if
(
G_UNLIKELY
(
priv
->
mqtt
!=
NULL
))
{
g_object_unref
(
priv
->
mqtt
);
}
g_object_unref
(
priv
->
cons
);
g_queue_free_full
(
priv
->
msgs
,
(
GDestroyNotify
)
fb_api_message_free
);
g_free
(
priv
->
cid
);
g_free
(
priv
->
did
);
g_free
(
priv
->
stoken
);
g_free
(
priv
->
token
);
g_free
(
priv
->
contacts_delta
);
}
static
void
fb_api_class_init
(
FbApiClass
*
klass
)
{
GObjectClass
*
gklass
=
G_OBJECT_CLASS
(
klass
);
GParamSpec
*
props
[
PROP_N
]
=
{
NULL
};
gklass
->
set_property
=
fb_api_set_property
;
gklass
->
get_property
=
fb_api_get_property
;
gklass
->
dispose
=
fb_api_dispose
;
/**
* FbApi:cid:
*
* The client identifier for MQTT. This value should be saved
* and loaded for persistence.
*/
props
[
PROP_CID
]
=
g_param_spec_string
(
"cid"
,
"Client ID"
,
"Client identifier for MQTT"
,
NULL
,
G_PARAM_READWRITE
);
/**
* FbApi:did:
*
* The device identifier for the MQTT message queue. This value
* should be saved and loaded for persistence.
*/
props
[
PROP_DID
]
=
g_param_spec_string
(
"did"
,
"Device ID"
,
"Device identifier for the MQTT message queue"
,
NULL
,
G_PARAM_READWRITE
);
/**
* FbApi:mid:
*
* The MQTT identifier. This value should be saved and loaded
* for persistence.
*/
props
[
PROP_MID
]
=
g_param_spec_uint64
(
"mid"
,
"MQTT ID"
,
"MQTT identifier"
,
0
,
G_MAXUINT64
,
0
,
G_PARAM_READWRITE
);
/**
* FbApi:stoken:
*
* The synchronization token for the MQTT message queue. This
* value should be saved and loaded for persistence.
*/
props
[
PROP_STOKEN
]
=
g_param_spec_string
(
"stoken"
,
"Sync Token"
,
"Synchronization token for the MQTT message queue"
,
NULL
,
G_PARAM_READWRITE
);
/**
* FbApi:token:
*
* The access token for authentication. This value should be
* saved and loaded for persistence.
*/
props
[
PROP_TOKEN
]
=
g_param_spec_string
(
"token"
,
"Access Token"
,
"Access token for authentication"
,
NULL
,
G_PARAM_READWRITE
);
/**
* FbApi:uid:
*
* The #FbId of the user of the #FbApi.
*/
props
[
PROP_UID
]
=
g_param_spec_int64
(
"uid"
,
"User ID"
,
"User identifier"
,
0
,
G_MAXINT64
,
0
,
G_PARAM_READWRITE
);
g_object_class_install_properties
(
gklass
,
PROP_N
,
props
);
/**
* FbApi::auth:
* @api: The #FbApi.
*
* Emitted upon the successful completion of the authentication
* process. This is emitted as a result of #fb_api_auth().
*/
g_signal_new
(
"auth"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
0
);
/**
* FbApi::connect:
* @api: The #FbApi.
*
* Emitted upon the successful completion of the connection
* process. This is emitted as a result of #fb_api_connect().
*/
g_signal_new
(
"connect"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
0
);
/**
* FbApi::contact:
* @api: The #FbApi.
* @user: The #FbApiUser.
*
* Emitted upon the successful reply of a contact request. This
* is emitted as a result of #fb_api_contact().
*/
g_signal_new
(
"contact"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::contacts:
* @api: The #FbApi.
* @users: The #GSList of #FbApiUser's.
* @complete: #TRUE if the list is fetched, otherwise #FALSE.
*
* Emitted upon the successful reply of a contacts request.
* This is emitted as a result of #fb_api_contacts(). This can
* be emitted multiple times before the entire contacts list
* has been fetched. Use @complete for detecting the completion
* status of the list fetch.
*/
g_signal_new
(
"contacts"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
2
,
G_TYPE_POINTER
,
G_TYPE_BOOLEAN
);
/**
* FbApi::contacts-delta:
* @api: The #FbApi.
* @added: The #GSList of added #FbApiUser's.
* @removed: The #GSList of strings with removed user ids.
*
* Like 'contacts', but only the deltas.
*/
g_signal_new
(
"contacts-delta"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
2
,
G_TYPE_POINTER
,
G_TYPE_POINTER
);
/**
* FbApi::error:
* @api: The #FbApi.
* @error: The #GError.
*
* Emitted whenever an error is hit within the #FbApi. This
* should disconnect the #FbApi with #fb_api_disconnect().
*/
g_signal_new
(
"error"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::events:
* @api: The #FbApi.
* @events: The #GSList of #FbApiEvent's.
*
* Emitted upon incoming events from the stream.
*/
g_signal_new
(
"events"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::messages:
* @api: The #FbApi.
* @msgs: The #GSList of #FbApiMessage's.
*
* Emitted upon incoming messages from the stream.
*/
g_signal_new
(
"messages"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::presences:
* @api: The #FbApi.
* @press: The #GSList of #FbApiPresence's.
*
* Emitted upon incoming presences from the stream.
*/
g_signal_new
(
"presences"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::thread:
* @api: The #FbApi.
* @thrd: The #FbApiThread.
*
* Emitted upon the successful reply of a thread request. This
* is emitted as a result of #fb_api_thread().
*/
g_signal_new
(
"thread"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::thread-create:
* @api: The #FbApi.
* @tid: The thread #FbId.
*
* Emitted upon the successful reply of a thread creation
* request. This is emitted as a result of
* #fb_api_thread_create().
*/
g_signal_new
(
"thread-create"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
FB_TYPE_ID
);
/**
* FbApi::thread-kicked:
* @api: The #FbApi.
* @thrd: The #FbApiThread.
*
* Emitted upon the reply of a thread request when the user is no longer
* part of that thread. This is emitted as a result of #fb_api_thread().
*/
g_signal_new
(
"thread-kicked"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::threads:
* @api: The #FbApi.
* @thrds: The #GSList of #FbApiThread's.
*
* Emitted upon the successful reply of a threads request. This
* is emitted as a result of #fb_api_threads().
*/
g_signal_new
(
"threads"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
/**
* FbApi::typing:
* @api: The #FbApi.
* @typg: The #FbApiTyping.
*
* Emitted upon an incoming typing state from the stream.
*/
g_signal_new
(
"typing"
,
G_TYPE_FROM_CLASS
(
klass
),
G_SIGNAL_ACTION
,
0
,
NULL
,
NULL
,
NULL
,
G_TYPE_NONE
,
1
,
G_TYPE_POINTER
);
}
static
void
fb_api_init
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
=
fb_api_get_instance_private
(
api
);
api
->
priv
=
priv
;
priv
->
msgs
=
g_queue_new
();
}
GQuark
fb_api_error_quark
(
void
)
{
static
GQuark
q
=
0
;
if
(
G_UNLIKELY
(
q
==
0
))
{
q
=
g_quark_from_static_string
(
"fb-api-error-quark"
);
}
return
q
;
}
static
gboolean
fb_api_json_chk
(
FbApi
*
api
,
gconstpointer
data
,
gssize
size
,
JsonNode
**
node
)
{
const
gchar
*
str
;
FbApiError
errc
=
FB_API_ERROR_GENERAL
;
FbApiPrivate
*
priv
;
FbJsonValues
*
values
;
gboolean
success
=
TRUE
;
gchar
*
msg
;
GError
*
err
=
NULL
;
gint64
code
;
guint
i
;
JsonNode
*
root
;
static
const
gchar
*
exprs
[]
=
{
"$.error.message"
,
"$.error.summary"
,
"$.error_msg"
,
"$.errorCode"
,
"$.failedSend.errorMessage"
,
};
g_return_val_if_fail
(
FB_IS_API
(
api
),
FALSE
);
priv
=
api
->
priv
;
if
(
G_UNLIKELY
(
size
==
0
))
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Empty JSON data"
));
return
FALSE
;
}
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
"Parsing JSON: %.*s
\n
"
,
(
gint
)
size
,
(
const
gchar
*
)
data
);
root
=
fb_json_node_new
(
data
,
size
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
return
FALSE
);
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.error_code"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.error.type"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.errorCode"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
FALSE
);
code
=
fb_json_values_next_int
(
values
,
0
);
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
purple_strequal
(
str
,
"OAuthException"
)
||
(
code
==
401
))
{
errc
=
FB_API_ERROR_AUTH
;
success
=
FALSE
;
g_free
(
priv
->
stoken
);
priv
->
stoken
=
NULL
;
g_free
(
priv
->
token
);
priv
->
token
=
NULL
;
}
/* 509 is used for "invalid attachment id" */
if
(
code
==
509
)
{
errc
=
FB_API_ERROR_NONFATAL
;
success
=
FALSE
;
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
purple_strequal
(
str
,
"ERROR_QUEUE_NOT_FOUND"
)
||
purple_strequal
(
str
,
"ERROR_QUEUE_LOST"
))
{
errc
=
FB_API_ERROR_QUEUE
;
success
=
FALSE
;
g_free
(
priv
->
stoken
);
priv
->
stoken
=
NULL
;
}
g_object_unref
(
values
);
for
(
msg
=
NULL
,
i
=
0
;
i
<
G_N_ELEMENTS
(
exprs
);
i
++
)
{
msg
=
fb_json_node_get_str
(
root
,
exprs
[
i
],
NULL
);
if
(
msg
!=
NULL
)
{
success
=
FALSE
;
break
;
}
}
if
(
!
success
&&
(
msg
==
NULL
))
{
msg
=
g_strdup
(
_
(
"Unknown error"
));
}
if
(
msg
!=
NULL
)
{
fb_api_error_literal
(
api
,
errc
,
msg
);
json_node_free
(
root
);
g_free
(
msg
);
return
FALSE
;
}
if
(
node
!=
NULL
)
{
*
node
=
root
;
}
else
{
json_node_free
(
root
);
}
return
TRUE
;
}
static
gboolean
fb_api_http_chk
(
FbApi
*
api
,
SoupMessage
*
res
,
JsonNode
**
root
)
{
const
gchar
*
data
;
const
gchar
*
msg
;
GError
*
err
=
NULL
;
gint
code
;
gsize
size
;
msg
=
res
->
reason_phrase
;
code
=
res
->
status_code
;
data
=
res
->
response_body
->
data
;
size
=
res
->
response_body
->
length
;
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
"HTTP Response (%p):"
,
res
);
if
(
msg
!=
NULL
)
{
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
" Response Error: %s (%d)"
,
msg
,
code
);
}
else
{
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
" Response Error: %d"
,
code
);
}
if
(
G_LIKELY
(
size
>
0
))
{
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
" Response Data: %.*s"
,
(
gint
)
size
,
data
);
}
if
(
fb_http_error_chk
(
res
,
&
err
)
&&
(
root
==
NULL
))
{
return
TRUE
;
}
/* Rudimentary check to prevent wrongful error parsing */
if
((
size
<
2
)
||
(
data
[
0
]
!=
'{'
)
||
(
data
[
size
-
1
]
!=
'}'
))
{
FB_API_ERROR_EMIT
(
api
,
err
,
return
FALSE
);
}
if
(
!
fb_api_json_chk
(
api
,
data
,
size
,
root
))
{
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_error_free
(
err
);
}
return
FALSE
;
}
FB_API_ERROR_EMIT
(
api
,
err
,
return
FALSE
);
return
TRUE
;
}
static
SoupMessage
*
fb_api_http_req
(
FbApi
*
api
,
const
gchar
*
url
,
const
gchar
*
name
,
const
gchar
*
method
,
FbHttpParams
*
params
,
SoupSessionCallback
callback
)
{
FbApiPrivate
*
priv
=
api
->
priv
;
gchar
*
data
;
gchar
*
key
;
gchar
*
val
;
GList
*
keys
;
GList
*
l
;
GString
*
gstr
;
SoupMessage
*
msg
;
fb_http_params_set_str
(
params
,
"api_key"
,
FB_API_KEY
);
fb_http_params_set_str
(
params
,
"device_id"
,
priv
->
did
);
fb_http_params_set_str
(
params
,
"fb_api_req_friendly_name"
,
name
);
fb_http_params_set_str
(
params
,
"format"
,
"json"
);
fb_http_params_set_str
(
params
,
"method"
,
method
);
val
=
fb_util_get_locale
();
fb_http_params_set_str
(
params
,
"locale"
,
val
);
g_free
(
val
);
/* Ensure an old signature is not computed */
g_hash_table_remove
(
params
,
"sig"
);
gstr
=
g_string_new
(
NULL
);
keys
=
g_hash_table_get_keys
(
params
);
keys
=
g_list_sort
(
keys
,
(
GCompareFunc
)
g_ascii_strcasecmp
);
for
(
l
=
keys
;
l
!=
NULL
;
l
=
l
->
next
)
{
key
=
l
->
data
;
val
=
g_hash_table_lookup
(
params
,
key
);
g_string_append_printf
(
gstr
,
"%s=%s"
,
key
,
val
);
}
g_string_append
(
gstr
,
FB_API_SECRET
);
data
=
g_compute_checksum_for_string
(
G_CHECKSUM_MD5
,
gstr
->
str
,
gstr
->
len
);
fb_http_params_set_str
(
params
,
"sig"
,
data
);
g_string_free
(
gstr
,
TRUE
);
g_list_free
(
keys
);
g_free
(
data
);
msg
=
soup_form_request_new_from_hash
(
"POST"
,
url
,
params
);
fb_http_params_free
(
params
);
if
(
priv
->
token
!=
NULL
)
{
data
=
g_strdup_printf
(
"OAuth %s"
,
priv
->
token
);
soup_message_headers_replace
(
msg
->
request_headers
,
"Authorization"
,
data
);
g_free
(
data
);
}
soup_session_queue_message
(
priv
->
cons
,
msg
,
callback
,
api
);
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
"HTTP Request (%p):"
,
msg
);
fb_util_debug
(
FB_UTIL_DEBUG_INFO
,
" Request URL: %s"
,
url
);
return
msg
;
}
static
SoupMessage
*
fb_api_http_query
(
FbApi
*
api
,
gint64
query
,
JsonBuilder
*
builder
,
SoupSessionCallback
hcb
)
{
const
gchar
*
name
;
FbHttpParams
*
prms
;
gchar
*
json
;
switch
(
query
)
{
case
FB_API_QUERY_CONTACT
:
name
=
"UsersQuery"
;
break
;
case
FB_API_QUERY_CONTACTS
:
name
=
"FetchContactsFullQuery"
;
break
;
case
FB_API_QUERY_CONTACTS_AFTER
:
name
=
"FetchContactsFullWithAfterQuery"
;
break
;
case
FB_API_QUERY_CONTACTS_DELTA
:
name
=
"FetchContactsDeltaQuery"
;
break
;
case
FB_API_QUERY_STICKER
:
name
=
"FetchStickersWithPreviewsQuery"
;
break
;
case
FB_API_QUERY_THREAD
:
name
=
"ThreadQuery"
;
break
;
case
FB_API_QUERY_SEQ_ID
:
case
FB_API_QUERY_THREADS
:
name
=
"ThreadListQuery"
;
break
;
case
FB_API_QUERY_XMA
:
name
=
"XMAQuery"
;
break
;
default
:
g_return_val_if_reached
(
NULL
);
return
NULL
;
}
prms
=
fb_http_params_new
();
json
=
fb_json_bldr_close
(
builder
,
JSON_NODE_OBJECT
,
NULL
);
fb_http_params_set_strf
(
prms
,
"query_id"
,
"%"
G_GINT64_FORMAT
,
query
);
fb_http_params_set_str
(
prms
,
"query_params"
,
json
);
g_free
(
json
);
return
fb_api_http_req
(
api
,
FB_API_URL_GQL
,
name
,
"get"
,
prms
,
hcb
);
}
static
void
fb_api_cb_http_bool
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
FbApi
*
api
=
data
;
if
(
!
fb_api_http_chk
(
api
,
res
,
NULL
))
{
return
;
}
if
(
!
purple_strequal
(
res
->
response_body
->
data
,
"true"
))
{
fb_api_error_literal
(
api
,
FB_API_ERROR
,
_
(
"Failed generic API operation"
));
}
}
static
void
fb_api_cb_mqtt_error
(
FbMqtt
*
mqtt
,
GError
*
error
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
if
(
!
priv
->
retrying
)
{
priv
->
retrying
=
TRUE
;
fb_util_debug_info
(
"Attempting to reconnect the MQTT stream..."
);
fb_api_connect
(
api
,
priv
->
invisible
);
}
else
{
g_signal_emit_by_name
(
api
,
"error"
,
error
);
}
}
static
void
fb_api_cb_mqtt_open
(
FbMqtt
*
mqtt
,
gpointer
data
)
{
const
GByteArray
*
bytes
;
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbThrift
*
thft
;
GByteArray
*
cytes
;
GError
*
err
=
NULL
;
static
guint8
flags
=
FB_MQTT_CONNECT_FLAG_USER
|
FB_MQTT_CONNECT_FLAG_PASS
|
FB_MQTT_CONNECT_FLAG_CLR
;
thft
=
fb_thrift_new
(
NULL
,
0
);
/* Write the client identifier */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_STRING
,
1
,
0
);
fb_thrift_write_str
(
thft
,
priv
->
cid
);
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_STRUCT
,
4
,
1
);
/* Write the user identifier */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I64
,
1
,
0
);
fb_thrift_write_i64
(
thft
,
priv
->
uid
);
/* Write the information string */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_STRING
,
2
,
1
);
fb_thrift_write_str
(
thft
,
FB_API_MQTT_AGENT
);
/* Write the UNKNOWN ("cp"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I64
,
3
,
2
);
fb_thrift_write_i64
(
thft
,
23
);
/* Write the UNKNOWN ("ecp"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I64
,
4
,
3
);
fb_thrift_write_i64
(
thft
,
26
);
/* Write the UNKNOWN */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I32
,
5
,
4
);
fb_thrift_write_i32
(
thft
,
1
);
/* Write the UNKNOWN ("no_auto_fg"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_BOOL
,
6
,
5
);
fb_thrift_write_bool
(
thft
,
TRUE
);
/* Write the visibility state */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_BOOL
,
7
,
6
);
fb_thrift_write_bool
(
thft
,
!
priv
->
invisible
);
/* Write the device identifier */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_STRING
,
8
,
7
);
fb_thrift_write_str
(
thft
,
priv
->
did
);
/* Write the UNKNOWN ("fg"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_BOOL
,
9
,
8
);
fb_thrift_write_bool
(
thft
,
TRUE
);
/* Write the UNKNOWN ("nwt"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I32
,
10
,
9
);
fb_thrift_write_i32
(
thft
,
1
);
/* Write the UNKNOWN ("nwst"?) */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I32
,
11
,
10
);
fb_thrift_write_i32
(
thft
,
0
);
/* Write the MQTT identifier */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_I64
,
12
,
11
);
fb_thrift_write_i64
(
thft
,
priv
->
mid
);
/* Write the UNKNOWN */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_LIST
,
14
,
12
);
fb_thrift_write_list
(
thft
,
FB_THRIFT_TYPE_I32
,
0
);
fb_thrift_write_stop
(
thft
);
/* Write the token */
fb_thrift_write_field
(
thft
,
FB_THRIFT_TYPE_STRING
,
15
,
14
);
fb_thrift_write_str
(
thft
,
priv
->
token
);
/* Write the STOP for the struct */
fb_thrift_write_stop
(
thft
);
bytes
=
fb_thrift_get_bytes
(
thft
);
cytes
=
fb_util_zlib_deflate
(
bytes
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
thft
);
return
;
);
fb_util_debug_hexdump
(
FB_UTIL_DEBUG_INFO
,
bytes
,
"Writing connect"
);
fb_mqtt_connect
(
mqtt
,
flags
,
cytes
);
g_byte_array_free
(
cytes
,
TRUE
);
g_object_unref
(
thft
);
}
static
void
fb_api_connect_queue
(
FbApi
*
api
)
{
FbApiMessage
*
msg
;
FbApiPrivate
*
priv
=
api
->
priv
;
gchar
*
json
;
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_int
(
bldr
,
"delta_batch_size"
,
125
);
fb_json_bldr_add_int
(
bldr
,
"max_deltas_able_to_process"
,
1250
);
fb_json_bldr_add_int
(
bldr
,
"sync_api_version"
,
3
);
fb_json_bldr_add_str
(
bldr
,
"encoding"
,
"JSON"
);
if
(
priv
->
stoken
==
NULL
)
{
fb_json_bldr_add_int
(
bldr
,
"initial_titan_sequence_id"
,
priv
->
sid
);
fb_json_bldr_add_str
(
bldr
,
"device_id"
,
priv
->
did
);
fb_json_bldr_add_int
(
bldr
,
"entity_fbid"
,
priv
->
uid
);
fb_json_bldr_obj_begin
(
bldr
,
"queue_params"
);
fb_json_bldr_add_str
(
bldr
,
"buzz_on_deltas_enabled"
,
"false"
);
fb_json_bldr_obj_begin
(
bldr
,
"graphql_query_hashes"
);
fb_json_bldr_add_str
(
bldr
,
"xma_query_id"
,
G_STRINGIFY
(
FB_API_QUERY_XMA
));
fb_json_bldr_obj_end
(
bldr
);
fb_json_bldr_obj_begin
(
bldr
,
"graphql_query_params"
);
fb_json_bldr_obj_begin
(
bldr
,
G_STRINGIFY
(
FB_API_QUERY_XMA
));
fb_json_bldr_add_str
(
bldr
,
"xma_id"
,
"<ID>"
);
fb_json_bldr_obj_end
(
bldr
);
fb_json_bldr_obj_end
(
bldr
);
fb_json_bldr_obj_end
(
bldr
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/messenger_sync_create_queue"
,
"%s"
,
json
);
g_free
(
json
);
return
;
}
fb_json_bldr_add_int
(
bldr
,
"last_seq_id"
,
priv
->
sid
);
fb_json_bldr_add_str
(
bldr
,
"sync_token"
,
priv
->
stoken
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/messenger_sync_get_diffs"
,
"%s"
,
json
);
g_signal_emit_by_name
(
api
,
"connect"
);
g_free
(
json
);
if
(
!
g_queue_is_empty
(
priv
->
msgs
))
{
msg
=
g_queue_peek_head
(
priv
->
msgs
);
fb_api_message_send
(
api
,
msg
);
}
if
(
priv
->
retrying
)
{
priv
->
retrying
=
FALSE
;
fb_util_debug_info
(
"Reconnected the MQTT stream"
);
}
}
static
void
fb_api_cb_seqid
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
str
;
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.viewer.message_threads.sync_sequence_id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
TRUE
,
"$.viewer.message_threads.unread_count"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
str
=
fb_json_values_next_str
(
values
,
"0"
);
priv
->
sid
=
g_ascii_strtoll
(
str
,
NULL
,
10
);
priv
->
unread
=
fb_json_values_next_int
(
values
,
0
);
if
(
priv
->
sid
==
0
)
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to get sync_sequence_id"
));
}
else
{
fb_api_connect_queue
(
api
);
}
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_cb_mqtt_connect
(
FbMqtt
*
mqtt
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
gchar
*
json
;
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_bool
(
bldr
,
"foreground"
,
TRUE
);
fb_json_bldr_add_int
(
bldr
,
"keepalive_timeout"
,
FB_MQTT_KA
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/foreground_state"
,
"%s"
,
json
);
g_free
(
json
);
fb_mqtt_subscribe
(
mqtt
,
"/inbox"
,
0
,
"/mercury"
,
0
,
"/messaging_events"
,
0
,
"/orca_presence"
,
0
,
"/orca_typing_notifications"
,
0
,
"/pp"
,
0
,
"/t_ms"
,
0
,
"/t_p"
,
0
,
"/t_rtc"
,
0
,
"/webrtc"
,
0
,
"/webrtc_response"
,
0
,
NULL
);
/* Notifications seem to lead to some sort of sending rate limit */
fb_mqtt_unsubscribe
(
mqtt
,
"/orca_message_notifications"
,
NULL
);
if
(
priv
->
sid
==
0
)
{
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_str
(
bldr
,
"1"
,
"0"
);
fb_api_http_query
(
api
,
FB_API_QUERY_SEQ_ID
,
bldr
,
fb_api_cb_seqid
);
}
else
{
fb_api_connect_queue
(
api
);
}
}
static
void
fb_api_cb_publish_mark
(
FbApi
*
api
,
GByteArray
*
pload
)
{
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_json_chk
(
api
,
pload
->
data
,
pload
->
len
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_BOOL
,
FALSE
,
"$.succeeded"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
if
(
!
fb_json_values_next_bool
(
values
,
TRUE
))
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to mark thread as read"
));
}
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
GSList
*
fb_api_event_parse
(
FbApi
*
api
,
FbApiEvent
*
event
,
GSList
*
events
,
JsonNode
*
root
,
GError
**
error
)
{
const
gchar
*
str
;
FbApiEvent
*
devent
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
guint
i
;
static
const
struct
{
FbApiEventType
type
;
const
gchar
*
expr
;
}
evtypes
[]
=
{
{
FB_API_EVENT_TYPE_THREAD_USER_ADDED
,
"$.log_message_data.added_participants"
},
{
FB_API_EVENT_TYPE_THREAD_USER_REMOVED
,
"$.log_message_data.removed_participants"
}
};
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.log_message_type"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.author"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.log_message_data.name"
);
fb_json_values_update
(
values
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
g_object_unref
(
values
);
return
events
;
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
g_strcmp0
(
str
,
"log:thread-name"
)
==
0
)
{
str
=
fb_json_values_next_str
(
values
,
""
);
str
=
strrchr
(
str
,
':'
);
if
(
str
!=
NULL
)
{
devent
=
fb_api_event_dup
(
event
,
FALSE
);
devent
->
type
=
FB_API_EVENT_TYPE_THREAD_TOPIC
;
devent
->
uid
=
FB_ID_FROM_STR
(
str
+
1
);
devent
->
text
=
fb_json_values_next_str_dup
(
values
,
NULL
);
events
=
g_slist_prepend
(
events
,
devent
);
}
}
g_object_unref
(
values
);
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
evtypes
);
i
++
)
{
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$"
);
fb_json_values_set_array
(
values
,
FALSE
,
evtypes
[
i
].
expr
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
str
=
fb_json_values_next_str
(
values
,
""
);
str
=
strrchr
(
str
,
':'
);
if
(
str
!=
NULL
)
{
devent
=
fb_api_event_dup
(
event
,
FALSE
);
devent
->
type
=
evtypes
[
i
].
type
;
devent
->
uid
=
FB_ID_FROM_STR
(
str
+
1
);
events
=
g_slist_prepend
(
events
,
devent
);
}
}
g_object_unref
(
values
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
break
;
}
}
return
events
;
}
static
void
fb_api_cb_publish_mercury
(
FbApi
*
api
,
GByteArray
*
pload
)
{
const
gchar
*
str
;
FbApiEvent
event
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
GSList
*
events
=
NULL
;
JsonNode
*
root
;
JsonNode
*
node
;
if
(
!
fb_api_json_chk
(
api
,
pload
->
data
,
pload
->
len
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.thread_fbid"
);
fb_json_values_set_array
(
values
,
FALSE
,
"$.actions"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
fb_api_event_reset
(
&
event
,
FALSE
);
str
=
fb_json_values_next_str
(
values
,
"0"
);
event
.
tid
=
FB_ID_FROM_STR
(
str
);
node
=
fb_json_values_get_root
(
values
);
events
=
fb_api_event_parse
(
api
,
&
event
,
events
,
node
,
&
err
);
}
if
(
G_LIKELY
(
err
==
NULL
))
{
events
=
g_slist_reverse
(
events
);
g_signal_emit_by_name
(
api
,
"events"
,
events
);
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
events
,
(
GDestroyNotify
)
fb_api_event_free
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_cb_publish_typing
(
FbApi
*
api
,
GByteArray
*
pload
)
{
const
gchar
*
str
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbApiTyping
typg
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_json_chk
(
api
,
pload
->
data
,
pload
->
len
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.type"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
TRUE
,
"$.sender_fbid"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
TRUE
,
"$.state"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
g_ascii_strcasecmp
(
str
,
"typ"
)
==
0
)
{
typg
.
uid
=
fb_json_values_next_int
(
values
,
0
);
if
(
typg
.
uid
!=
priv
->
uid
)
{
typg
.
state
=
fb_json_values_next_int
(
values
,
0
);
g_signal_emit_by_name
(
api
,
"typing"
,
&
typg
);
}
}
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_cb_publish_ms_r
(
FbApi
*
api
,
GByteArray
*
pload
)
{
FbApiMessage
*
msg
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_json_chk
(
api
,
pload
->
data
,
pload
->
len
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_BOOL
,
TRUE
,
"$.succeeded"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
if
(
fb_json_values_next_bool
(
values
,
TRUE
))
{
/* Pop and free the successful message */
msg
=
g_queue_pop_head
(
priv
->
msgs
);
fb_api_message_free
(
msg
);
if
(
!
g_queue_is_empty
(
priv
->
msgs
))
{
msg
=
g_queue_peek_head
(
priv
->
msgs
);
fb_api_message_send
(
api
,
msg
);
}
}
else
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
"Failed to send message"
);
}
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
gchar
*
fb_api_xma_parse
(
FbApi
*
api
,
const
gchar
*
body
,
JsonNode
*
root
,
GError
**
error
)
{
const
gchar
*
str
;
const
gchar
*
url
;
FbHttpParams
*
params
;
FbJsonValues
*
values
;
gchar
*
text
;
GError
*
err
=
NULL
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.story_attachment.target.__type__.name"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.story_attachment.url"
);
fb_json_values_update
(
values
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
g_object_unref
(
values
);
return
NULL
;
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
url
=
fb_json_values_next_str
(
values
,
NULL
);
if
((
str
==
NULL
)
||
(
url
==
NULL
))
{
text
=
g_strdup
(
_
(
"<Unsupported Attachment>"
));
g_object_unref
(
values
);
return
text
;
}
if
(
purple_strequal
(
str
,
"ExternalUrl"
))
{
params
=
fb_http_params_new_parse
(
url
,
TRUE
);
if
(
g_str_has_prefix
(
url
,
FB_API_FBRPC_PREFIX
))
{
text
=
fb_http_params_dup_str
(
params
,
"target_url"
,
NULL
);
}
else
{
text
=
fb_http_params_dup_str
(
params
,
"u"
,
NULL
);
}
fb_http_params_free
(
params
);
}
else
{
text
=
g_strdup
(
url
);
}
if
(
fb_http_urlcmp
(
body
,
text
,
FALSE
))
{
g_free
(
text
);
g_object_unref
(
values
);
return
NULL
;
}
g_object_unref
(
values
);
return
text
;
}
static
GSList
*
fb_api_message_parse_attach
(
FbApi
*
api
,
const
gchar
*
mid
,
FbApiMessage
*
msg
,
GSList
*
msgs
,
const
gchar
*
body
,
JsonNode
*
root
,
GError
**
error
)
{
const
gchar
*
str
;
FbApiMessage
*
dmsg
;
FbId
id
;
FbJsonValues
*
values
;
gchar
*
xma
;
GError
*
err
=
NULL
;
JsonNode
*
node
;
JsonNode
*
xode
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.xmaGraphQL"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.fbid"
);
fb_json_values_set_array
(
values
,
FALSE
,
"$.attachments"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
str
==
NULL
)
{
id
=
fb_json_values_next_int
(
values
,
0
);
dmsg
=
fb_api_message_dup
(
msg
,
FALSE
);
fb_api_attach
(
api
,
id
,
mid
,
dmsg
);
continue
;
}
node
=
fb_json_node_new
(
str
,
-1
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
xode
=
fb_json_node_get_nth
(
node
,
0
);
xma
=
fb_api_xma_parse
(
api
,
body
,
xode
,
&
err
);
if
(
xma
!=
NULL
)
{
dmsg
=
fb_api_message_dup
(
msg
,
FALSE
);
dmsg
->
text
=
xma
;
msgs
=
g_slist_prepend
(
msgs
,
dmsg
);
}
json_node_free
(
node
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
}
g_object_unref
(
values
);
return
msgs
;
}
static
GSList
*
fb_api_cb_publish_ms_new_message
(
FbApi
*
api
,
JsonNode
*
root
,
GSList
*
msgs
,
GError
**
error
);
static
GSList
*
fb_api_cb_publish_ms_event
(
FbApi
*
api
,
JsonNode
*
root
,
GSList
*
events
,
FbApiEventType
type
,
GError
**
error
);
static
void
fb_api_cb_publish_mst
(
FbThrift
*
thft
,
GError
**
error
)
{
if
(
fb_thrift_read_isstop
(
thft
))
{
FB_API_TCHK
(
fb_thrift_read_stop
(
thft
));
}
else
{
FbThriftType
type
;
gint16
id
;
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
0
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_STRING
);
// FB_API_TCHK(id == 2);
FB_API_TCHK
(
fb_thrift_read_str
(
thft
,
NULL
));
FB_API_TCHK
(
fb_thrift_read_stop
(
thft
));
}
}
static
void
fb_api_cb_publish_ms
(
FbApi
*
api
,
GByteArray
*
pload
)
{
const
gchar
*
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbJsonValues
*
values
;
FbThrift
*
thft
;
gchar
*
stoken
;
GError
*
err
=
NULL
;
GList
*
elms
,
*
l
;
GSList
*
msgs
=
NULL
;
GSList
*
events
=
NULL
;
guint
size
;
JsonNode
*
root
;
JsonNode
*
node
;
JsonArray
*
arr
;
static
const
struct
{
const
gchar
*
member
;
FbApiEventType
type
;
gboolean
is_message
;
}
event_types
[]
=
{
{
"deltaNewMessage"
,
0
,
1
},
{
"deltaThreadName"
,
FB_API_EVENT_TYPE_THREAD_TOPIC
,
0
},
{
"deltaParticipantsAddedToGroupThread"
,
FB_API_EVENT_TYPE_THREAD_USER_ADDED
,
0
},
{
"deltaParticipantLeftGroupThread"
,
FB_API_EVENT_TYPE_THREAD_USER_REMOVED
,
0
},
};
/* Read identifier string (for Facebook employees) */
thft
=
fb_thrift_new
(
pload
,
0
);
fb_api_cb_publish_mst
(
thft
,
&
err
);
size
=
fb_thrift_get_pos
(
thft
);
g_object_unref
(
thft
);
FB_API_ERROR_EMIT
(
api
,
err
,
return
;
);
g_return_if_fail
(
size
<
pload
->
len
);
data
=
(
gchar
*
)
pload
->
data
+
size
;
size
=
pload
->
len
-
size
;
if
(
!
fb_api_json_chk
(
api
,
data
,
size
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.lastIssuedSeqId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.syncToken"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
priv
->
sid
=
fb_json_values_next_int
(
values
,
0
);
stoken
=
fb_json_values_next_str_dup
(
values
,
NULL
);
g_object_unref
(
values
);
if
(
G_UNLIKELY
(
stoken
!=
NULL
))
{
g_free
(
priv
->
stoken
);
priv
->
stoken
=
stoken
;
g_signal_emit_by_name
(
api
,
"connect"
);
json_node_free
(
root
);
return
;
}
arr
=
fb_json_node_get_arr
(
root
,
"$.deltas"
,
NULL
);
elms
=
json_array_get_elements
(
arr
);
for
(
l
=
elms
;
l
!=
NULL
;
l
=
l
->
next
)
{
guint
i
=
0
;
JsonObject
*
o
=
json_node_get_object
(
l
->
data
);
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
event_types
);
i
++
)
{
if
((
node
=
json_object_get_member
(
o
,
event_types
[
i
].
member
)))
{
if
(
event_types
[
i
].
is_message
)
{
msgs
=
fb_api_cb_publish_ms_new_message
(
api
,
node
,
msgs
,
&
err
);
}
else
{
events
=
fb_api_cb_publish_ms_event
(
api
,
node
,
events
,
event_types
[
i
].
type
,
&
err
);
}
}
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
}
g_list_free
(
elms
);
json_array_unref
(
arr
);
if
(
G_LIKELY
(
err
==
NULL
))
{
if
(
msgs
)
{
msgs
=
g_slist_reverse
(
msgs
);
g_signal_emit_by_name
(
api
,
"messages"
,
msgs
);
}
if
(
events
)
{
events
=
g_slist_reverse
(
events
);
g_signal_emit_by_name
(
api
,
"events"
,
events
);
}
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
msgs
,
(
GDestroyNotify
)
fb_api_message_free
);
g_slist_free_full
(
events
,
(
GDestroyNotify
)
fb_api_event_free
);
json_node_free
(
root
);
}
static
GSList
*
fb_api_cb_publish_ms_new_message
(
FbApi
*
api
,
JsonNode
*
root
,
GSList
*
msgs
,
GError
**
error
)
{
const
gchar
*
body
;
const
gchar
*
str
;
GError
*
err
=
NULL
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbApiMessage
*
dmsg
;
FbApiMessage
msg
;
FbId
id
;
FbId
oid
;
FbJsonValues
*
values
;
JsonNode
*
node
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata.offlineThreadingId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata.actorFbId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata"
".threadKey.otherUserFbId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata"
".threadKey.threadFbId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata.timestamp"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.body"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.stickerId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.messageMetadata.messageId"
);
if
(
fb_json_values_update
(
values
,
&
err
))
{
id
=
fb_json_values_next_int
(
values
,
0
);
/* Ignore everything but new messages */
if
(
id
==
0
)
{
goto
beach
;
}
/* Ignore sequential duplicates */
if
(
id
==
priv
->
lastmid
)
{
fb_util_debug_info
(
"Ignoring duplicate %"
FB_ID_FORMAT
,
id
);
goto
beach
;
}
priv
->
lastmid
=
id
;
fb_api_message_reset
(
&
msg
,
FALSE
);
msg
.
uid
=
fb_json_values_next_int
(
values
,
0
);
oid
=
fb_json_values_next_int
(
values
,
0
);
msg
.
tid
=
fb_json_values_next_int
(
values
,
0
);
msg
.
tstamp
=
fb_json_values_next_int
(
values
,
0
);
if
(
msg
.
uid
==
priv
->
uid
)
{
msg
.
flags
|=
FB_API_MESSAGE_FLAG_SELF
;
if
(
msg
.
tid
==
0
)
{
msg
.
uid
=
oid
;
}
}
body
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
body
!=
NULL
)
{
dmsg
=
fb_api_message_dup
(
&
msg
,
FALSE
);
dmsg
->
text
=
g_strdup
(
body
);
msgs
=
g_slist_prepend
(
msgs
,
dmsg
);
}
id
=
fb_json_values_next_int
(
values
,
0
);
if
(
id
!=
0
)
{
dmsg
=
fb_api_message_dup
(
&
msg
,
FALSE
);
fb_api_sticker
(
api
,
id
,
dmsg
);
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
str
==
NULL
)
{
goto
beach
;
}
node
=
fb_json_values_get_root
(
values
);
msgs
=
fb_api_message_parse_attach
(
api
,
str
,
&
msg
,
msgs
,
body
,
node
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
goto
beach
;
}
}
beach
:
g_object_unref
(
values
);
return
msgs
;
}
static
GSList
*
fb_api_cb_publish_ms_event
(
FbApi
*
api
,
JsonNode
*
root
,
GSList
*
events
,
FbApiEventType
type
,
GError
**
error
)
{
FbApiEvent
*
event
;
FbJsonValues
*
values
=
NULL
;
FbJsonValues
*
values_inner
=
NULL
;
GError
*
err
=
NULL
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata.threadKey.threadFbId"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.messageMetadata.actorFbId"
);
switch
(
type
)
{
case
FB_API_EVENT_TYPE_THREAD_TOPIC
:
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.name"
);
break
;
case
FB_API_EVENT_TYPE_THREAD_USER_ADDED
:
values_inner
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values_inner
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.userFbId"
);
/* use the text field for the full name */
fb_json_values_add
(
values_inner
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.fullName"
);
fb_json_values_set_array
(
values_inner
,
FALSE
,
"$.addedParticipants"
);
break
;
case
FB_API_EVENT_TYPE_THREAD_USER_REMOVED
:
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
FALSE
,
"$.leftParticipantFbId"
);
/* use the text field for the kick message */
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.messageMetadata.adminText"
);
break
;
}
fb_json_values_update
(
values
,
&
err
);
event
=
fb_api_event_dup
(
NULL
,
FALSE
);
event
->
type
=
type
;
event
->
tid
=
fb_json_values_next_int
(
values
,
0
);
event
->
uid
=
fb_json_values_next_int
(
values
,
0
);
if
(
type
==
FB_API_EVENT_TYPE_THREAD_TOPIC
)
{
event
->
text
=
fb_json_values_next_str_dup
(
values
,
NULL
);
}
else
if
(
type
==
FB_API_EVENT_TYPE_THREAD_USER_REMOVED
)
{
/* overwrite actor with subject */
event
->
uid
=
fb_json_values_next_int
(
values
,
0
);
event
->
text
=
fb_json_values_next_str_dup
(
values
,
NULL
);
}
else
if
(
type
==
FB_API_EVENT_TYPE_THREAD_USER_ADDED
)
{
while
(
fb_json_values_update
(
values_inner
,
&
err
))
{
FbApiEvent
*
devent
=
fb_api_event_dup
(
event
,
FALSE
);
devent
->
uid
=
fb_json_values_next_int
(
values_inner
,
0
);
devent
->
text
=
fb_json_values_next_str_dup
(
values_inner
,
NULL
);
events
=
g_slist_prepend
(
events
,
devent
);
}
fb_api_event_free
(
event
);
event
=
NULL
;
g_object_unref
(
values_inner
);
}
g_object_unref
(
values
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
}
else
if
(
event
)
{
events
=
g_slist_prepend
(
events
,
event
);
}
return
events
;
}
static
void
fb_api_cb_publish_pt
(
FbThrift
*
thft
,
GSList
**
presences
,
GError
**
error
)
{
FbApiPresence
*
api_presence
;
FbThriftType
type
;
gint16
id
;
gint32
i32
;
gint64
i64
;
guint
i
;
guint
size
=
0
;
/* Read identifier string (for Facebook employees) */
FB_API_TCHK
(
fb_thrift_read_str
(
thft
,
NULL
));
/* Read the full list boolean field */
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
0
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_BOOL
);
FB_API_TCHK
(
id
==
1
);
FB_API_TCHK
(
fb_thrift_read_bool
(
thft
,
NULL
));
/* Read the list field */
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
id
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_LIST
);
FB_API_TCHK
(
id
==
2
);
/* Read the list */
FB_API_TCHK
(
fb_thrift_read_list
(
thft
,
&
type
,
&
size
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_STRUCT
);
for
(
i
=
0
;
i
<
size
;
i
++
)
{
/* Read the user identifier field */
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
0
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I64
);
FB_API_TCHK
(
id
==
1
);
FB_API_TCHK
(
fb_thrift_read_i64
(
thft
,
&
i64
));
/* Read the active field */
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
id
));
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I32
);
FB_API_TCHK
(
id
==
2
);
FB_API_TCHK
(
fb_thrift_read_i32
(
thft
,
&
i32
));
api_presence
=
fb_api_presence_dup
(
NULL
);
api_presence
->
uid
=
i64
;
api_presence
->
active
=
i32
!=
0
;
*
presences
=
g_slist_prepend
(
*
presences
,
api_presence
);
fb_util_debug_info
(
"Presence: %"
FB_ID_FORMAT
" (%d) id: %d"
,
i64
,
i32
!=
0
,
id
);
while
(
id
<=
6
)
{
if
(
fb_thrift_read_isstop
(
thft
))
{
break
;
}
FB_API_TCHK
(
fb_thrift_read_field
(
thft
,
&
type
,
&
id
,
id
));
switch
(
id
)
{
case
3
:
/* Read the last active timestamp field */
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I64
);
FB_API_TCHK
(
fb_thrift_read_i64
(
thft
,
NULL
));
break
;
case
4
:
/* Read the active client bits field */
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I16
);
FB_API_TCHK
(
fb_thrift_read_i16
(
thft
,
NULL
));
break
;
case
5
:
/* Read the VoIP compatibility bits field */
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I64
);
FB_API_TCHK
(
fb_thrift_read_i64
(
thft
,
NULL
));
break
;
case
6
:
/* Unknown new field */
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I64
);
FB_API_TCHK
(
fb_thrift_read_i64
(
thft
,
NULL
));
break
;
default
:
/* Try to read unknown fields as varint */
FB_API_TCHK
(
type
==
FB_THRIFT_TYPE_I16
||
type
==
FB_THRIFT_TYPE_I32
||
type
==
FB_THRIFT_TYPE_I64
);
FB_API_TCHK
(
fb_thrift_read_i64
(
thft
,
NULL
));
break
;
}
}
/* Read the field stop */
FB_API_TCHK
(
fb_thrift_read_stop
(
thft
));
}
/* Read the field stop */
if
(
fb_thrift_read_isstop
(
thft
))
{
FB_API_TCHK
(
fb_thrift_read_stop
(
thft
));
}
}
static
void
fb_api_cb_publish_p
(
FbApi
*
api
,
GByteArray
*
pload
)
{
FbThrift
*
thft
;
GError
*
err
=
NULL
;
GSList
*
presences
=
NULL
;
thft
=
fb_thrift_new
(
pload
,
0
);
fb_api_cb_publish_pt
(
thft
,
&
presences
,
&
err
);
g_object_unref
(
thft
);
if
(
G_LIKELY
(
err
==
NULL
))
{
g_signal_emit_by_name
(
api
,
"presences"
,
presences
);
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
presences
,
(
GDestroyNotify
)
fb_api_presence_free
);
}
static
void
fb_api_cb_mqtt_publish
(
FbMqtt
*
mqtt
,
const
gchar
*
topic
,
GByteArray
*
pload
,
gpointer
data
)
{
FbApi
*
api
=
data
;
gboolean
comp
;
GByteArray
*
bytes
;
GError
*
err
=
NULL
;
guint
i
;
static
const
struct
{
const
gchar
*
topic
;
void
(
*
func
)
(
FbApi
*
api
,
GByteArray
*
pload
);
}
parsers
[]
=
{
{
"/mark_thread_response"
,
fb_api_cb_publish_mark
},
{
"/mercury"
,
fb_api_cb_publish_mercury
},
{
"/orca_typing_notifications"
,
fb_api_cb_publish_typing
},
{
"/send_message_response"
,
fb_api_cb_publish_ms_r
},
{
"/t_ms"
,
fb_api_cb_publish_ms
},
{
"/t_p"
,
fb_api_cb_publish_p
}
};
comp
=
fb_util_zlib_test
(
pload
);
if
(
G_LIKELY
(
comp
))
{
bytes
=
fb_util_zlib_inflate
(
pload
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
return
);
}
else
{
bytes
=
(
GByteArray
*
)
pload
;
}
fb_util_debug_hexdump
(
FB_UTIL_DEBUG_INFO
,
bytes
,
"Reading message (topic: %s)"
,
topic
);
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
parsers
);
i
++
)
{
if
(
g_ascii_strcasecmp
(
topic
,
parsers
[
i
].
topic
)
==
0
)
{
parsers
[
i
].
func
(
api
,
bytes
);
break
;
}
}
if
(
G_LIKELY
(
comp
))
{
g_byte_array_free
(
bytes
,
TRUE
);
}
}
FbApi
*
fb_api_new
(
PurpleConnection
*
gc
,
GProxyResolver
*
resolver
)
{
FbApi
*
api
;
FbApiPrivate
*
priv
;
api
=
g_object_new
(
FB_TYPE_API
,
NULL
);
priv
=
api
->
priv
;
priv
->
gc
=
gc
;
priv
->
cons
=
soup_session_new_with_options
(
"proxy-resolver"
,
resolver
,
"user-agent"
,
FB_API_AGENT
,
NULL
);
priv
->
mqtt
=
fb_mqtt_new
(
gc
);
g_signal_connect
(
priv
->
mqtt
,
"connect"
,
G_CALLBACK
(
fb_api_cb_mqtt_connect
),
api
);
g_signal_connect
(
priv
->
mqtt
,
"error"
,
G_CALLBACK
(
fb_api_cb_mqtt_error
),
api
);
g_signal_connect
(
priv
->
mqtt
,
"open"
,
G_CALLBACK
(
fb_api_cb_mqtt_open
),
api
);
g_signal_connect
(
priv
->
mqtt
,
"publish"
,
G_CALLBACK
(
fb_api_cb_mqtt_publish
),
api
);
return
api
;
}
void
fb_api_rehash
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
if
(
priv
->
cid
==
NULL
)
{
priv
->
cid
=
fb_util_rand_alnum
(
32
);
}
if
(
priv
->
did
==
NULL
)
{
priv
->
did
=
g_uuid_string_random
();
}
if
(
priv
->
mid
==
0
)
{
priv
->
mid
=
g_random_int
();
}
if
(
strlen
(
priv
->
cid
)
>
20
)
{
priv
->
cid
=
g_realloc_n
(
priv
->
cid
,
21
,
sizeof
*
priv
->
cid
);
priv
->
cid
[
20
]
=
0
;
}
}
gboolean
fb_api_is_invisible
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
;
g_return_val_if_fail
(
FB_IS_API
(
api
),
FALSE
);
priv
=
api
->
priv
;
return
priv
->
invisible
;
}
static
void
fb_api_error_literal
(
FbApi
*
api
,
FbApiError
error
,
const
gchar
*
msg
)
{
GError
*
err
;
g_return_if_fail
(
FB_IS_API
(
api
));
err
=
g_error_new_literal
(
FB_API_ERROR
,
error
,
msg
);
fb_api_error_emit
(
api
,
err
);
}
void
fb_api_error
(
FbApi
*
api
,
FbApiError
error
,
const
gchar
*
format
,
...)
{
GError
*
err
;
va_list
ap
;
g_return_if_fail
(
FB_IS_API
(
api
));
va_start
(
ap
,
format
);
err
=
g_error_new_valist
(
FB_API_ERROR
,
error
,
format
,
ap
);
va_end
(
ap
);
fb_api_error_emit
(
api
,
err
);
}
void
fb_api_error_emit
(
FbApi
*
api
,
GError
*
error
)
{
g_return_if_fail
(
FB_IS_API
(
api
));
g_return_if_fail
(
error
!=
NULL
);
g_signal_emit_by_name
(
api
,
"error"
,
error
);
g_error_free
(
error
);
}
static
void
fb_api_cb_attach
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
str
;
FbApi
*
api
=
data
;
FbApiMessage
*
msg
;
FbJsonValues
*
values
;
gchar
*
name
;
GError
*
err
=
NULL
;
GSList
*
msgs
=
NULL
;
guint
i
;
JsonNode
*
root
;
static
const
gchar
*
imgexts
[]
=
{
".jpg"
,
".png"
,
".gif"
};
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.filename"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.redirect_uri"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
msg
=
g_object_steal_data
(
G_OBJECT
(
res
),
"fb-api-msg"
);
str
=
fb_json_values_next_str
(
values
,
NULL
);
name
=
g_ascii_strdown
(
str
,
-1
);
for
(
i
=
0
;
i
<
G_N_ELEMENTS
(
imgexts
);
i
++
)
{
if
(
g_str_has_suffix
(
name
,
imgexts
[
i
]))
{
msg
->
flags
|=
FB_API_MESSAGE_FLAG_IMAGE
;
break
;
}
}
g_free
(
name
);
msg
->
text
=
fb_json_values_next_str_dup
(
values
,
NULL
);
msgs
=
g_slist_prepend
(
msgs
,
msg
);
g_signal_emit_by_name
(
api
,
"messages"
,
msgs
);
g_slist_free_full
(
msgs
,
(
GDestroyNotify
)
fb_api_message_free
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_attach
(
FbApi
*
api
,
FbId
aid
,
const
gchar
*
msgid
,
FbApiMessage
*
msg
)
{
FbHttpParams
*
prms
;
SoupMessage
*
http
;
prms
=
fb_http_params_new
();
fb_http_params_set_str
(
prms
,
"mid"
,
msgid
);
fb_http_params_set_strf
(
prms
,
"aid"
,
"%"
FB_ID_FORMAT
,
aid
);
http
=
fb_api_http_req
(
api
,
FB_API_URL_ATTACH
,
"getAttachment"
,
"messaging.getAttachment"
,
prms
,
fb_api_cb_attach
);
g_object_set_data_full
(
G_OBJECT
(
http
),
"fb-api-msg"
,
msg
,
(
GDestroyNotify
)
fb_api_message_free
);
}
static
void
fb_api_cb_auth
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.access_token"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
TRUE
,
"$.uid"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
g_free
(
priv
->
token
);
priv
->
token
=
fb_json_values_next_str_dup
(
values
,
NULL
);
priv
->
uid
=
fb_json_values_next_int
(
values
,
0
);
g_signal_emit_by_name
(
api
,
"auth"
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
void
fb_api_auth
(
FbApi
*
api
,
const
gchar
*
user
,
const
gchar
*
pass
)
{
FbHttpParams
*
prms
;
prms
=
fb_http_params_new
();
fb_http_params_set_str
(
prms
,
"email"
,
user
);
fb_http_params_set_str
(
prms
,
"password"
,
pass
);
fb_api_http_req
(
api
,
FB_API_URL_AUTH
,
"authenticate"
,
"auth.login"
,
prms
,
fb_api_cb_auth
);
}
static
gchar
*
fb_api_user_icon_checksum
(
gchar
*
icon
)
{
gchar
*
csum
;
FbHttpParams
*
prms
;
if
(
G_UNLIKELY
(
icon
==
NULL
))
{
return
NULL
;
}
prms
=
fb_http_params_new_parse
(
icon
,
TRUE
);
csum
=
fb_http_params_dup_str
(
prms
,
"oh"
,
NULL
);
fb_http_params_free
(
prms
);
if
(
G_UNLIKELY
(
csum
==
NULL
))
{
/* Revert to the icon URL as the unique checksum */
csum
=
g_strdup
(
icon
);
}
return
csum
;
}
static
void
fb_api_cb_contact
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
str
;
FbApi
*
api
=
data
;
FbApiUser
user
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
node
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
node
=
fb_json_node_get_nth
(
root
,
0
);
if
(
node
==
NULL
)
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to obtain contact information"
));
json_node_free
(
root
);
return
;
}
values
=
fb_json_values_new
(
node
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.name"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.profile_pic_large.uri"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
fb_api_user_reset
(
&
user
,
FALSE
);
str
=
fb_json_values_next_str
(
values
,
"0"
);
user
.
uid
=
FB_ID_FROM_STR
(
str
);
user
.
name
=
fb_json_values_next_str_dup
(
values
,
NULL
);
user
.
icon
=
fb_json_values_next_str_dup
(
values
,
NULL
);
user
.
csum
=
fb_api_user_icon_checksum
(
user
.
icon
);
g_signal_emit_by_name
(
api
,
"contact"
,
&
user
);
fb_api_user_reset
(
&
user
,
TRUE
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
void
fb_api_contact
(
FbApi
*
api
,
FbId
uid
)
{
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_strf
(
bldr
,
NULL
,
"%"
FB_ID_FORMAT
,
uid
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"1"
,
"true"
);
fb_api_http_query
(
api
,
FB_API_QUERY_CONTACT
,
bldr
,
fb_api_cb_contact
);
}
static
GSList
*
fb_api_cb_contacts_nodes
(
FbApi
*
api
,
JsonNode
*
root
,
GSList
*
users
)
{
const
gchar
*
str
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbApiUser
*
user
;
FbId
uid
;
FbJsonValues
*
values
;
gboolean
is_array
;
GError
*
err
=
NULL
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.represented_profile.id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.represented_profile.friendship_status"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.structured_name.text"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.hugePictureUrl.uri"
);
is_array
=
(
JSON_NODE_TYPE
(
root
)
==
JSON_NODE_ARRAY
);
if
(
is_array
)
{
fb_json_values_set_array
(
values
,
FALSE
,
"$"
);
}
while
(
fb_json_values_update
(
values
,
&
err
))
{
str
=
fb_json_values_next_str
(
values
,
"0"
);
uid
=
FB_ID_FROM_STR
(
str
);
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
((
!
purple_strequal
(
str
,
"ARE_FRIENDS"
)
&&
(
uid
!=
priv
->
uid
))
||
(
uid
==
0
))
{
if
(
!
is_array
)
{
break
;
}
continue
;
}
user
=
fb_api_user_dup
(
NULL
,
FALSE
);
user
->
uid
=
uid
;
user
->
name
=
fb_json_values_next_str_dup
(
values
,
NULL
);
user
->
icon
=
fb_json_values_next_str_dup
(
values
,
NULL
);
user
->
csum
=
fb_api_user_icon_checksum
(
user
->
icon
);
users
=
g_slist_prepend
(
users
,
user
);
if
(
!
is_array
)
{
break
;
}
}
g_object_unref
(
values
);
return
users
;
}
/* base64(contact:<our id>:<their id>:<whatever>) */
static
GSList
*
fb_api_cb_contacts_parse_removed
(
FbApi
*
api
,
JsonNode
*
node
,
GSList
*
users
)
{
gsize
len
;
char
**
split
;
char
*
decoded
=
(
char
*
)
g_base64_decode
(
json_node_get_string
(
node
),
&
len
);
g_return_val_if_fail
(
decoded
[
len
]
==
'\0'
,
users
);
g_return_val_if_fail
(
len
==
strlen
(
decoded
),
users
);
g_return_val_if_fail
(
g_str_has_prefix
(
decoded
,
"contact:"
),
users
);
split
=
g_strsplit_set
(
decoded
,
":"
,
4
);
if
(
g_strv_length
(
split
)
!=
4
)
{
g_strfreev
(
split
);
g_return_val_if_reached
(
users
);
}
users
=
g_slist_prepend
(
users
,
g_strdup
(
split
[
2
]));
g_strfreev
(
split
);
g_free
(
decoded
);
return
users
;
}
static
void
fb_api_cb_contacts
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
cursor
;
const
gchar
*
delta_cursor
;
FbApi
*
api
=
data
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbJsonValues
*
values
;
gboolean
complete
;
gboolean
is_delta
;
GError
*
err
=
NULL
;
GList
*
l
;
GSList
*
users
=
NULL
;
JsonNode
*
root
;
JsonNode
*
croot
;
JsonNode
*
node
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
croot
=
fb_json_node_get
(
root
,
"$.viewer.messenger_contacts.deltas"
,
NULL
);
is_delta
=
(
croot
!=
NULL
);
if
(
!
is_delta
)
{
croot
=
fb_json_node_get
(
root
,
"$.viewer.messenger_contacts"
,
NULL
);
node
=
fb_json_node_get
(
croot
,
"$.nodes"
,
NULL
);
users
=
fb_api_cb_contacts_nodes
(
api
,
node
,
users
);
json_node_free
(
node
);
}
else
{
GSList
*
added
=
NULL
;
GSList
*
removed
=
NULL
;
JsonArray
*
arr
=
fb_json_node_get_arr
(
croot
,
"$.nodes"
,
NULL
);
GList
*
elms
=
json_array_get_elements
(
arr
);
for
(
l
=
elms
;
l
!=
NULL
;
l
=
l
->
next
)
{
if
((
node
=
fb_json_node_get
(
l
->
data
,
"$.added"
,
NULL
)))
{
added
=
fb_api_cb_contacts_nodes
(
api
,
node
,
added
);
json_node_free
(
node
);
}
if
((
node
=
fb_json_node_get
(
l
->
data
,
"$.removed"
,
NULL
)))
{
removed
=
fb_api_cb_contacts_parse_removed
(
api
,
node
,
removed
);
json_node_free
(
node
);
}
}
g_signal_emit_by_name
(
api
,
"contacts-delta"
,
added
,
removed
);
g_slist_free_full
(
added
,
(
GDestroyNotify
)
fb_api_user_free
);
g_slist_free_full
(
removed
,
g_free
);
g_list_free
(
elms
);
json_array_unref
(
arr
);
}
values
=
fb_json_values_new
(
croot
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_BOOL
,
FALSE
,
"$.page_info.has_next_page"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.page_info.delta_cursor"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.page_info.end_cursor"
);
fb_json_values_update
(
values
,
NULL
);
complete
=
!
fb_json_values_next_bool
(
values
,
FALSE
);
delta_cursor
=
fb_json_values_next_str
(
values
,
NULL
);
cursor
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
G_UNLIKELY
(
err
==
NULL
))
{
if
(
is_delta
||
complete
)
{
g_free
(
priv
->
contacts_delta
);
priv
->
contacts_delta
=
g_strdup
(
is_delta
?
cursor
:
delta_cursor
);
}
if
(
users
)
{
g_signal_emit_by_name
(
api
,
"contacts"
,
users
,
complete
);
}
if
(
!
complete
)
{
fb_api_contacts_after
(
api
,
cursor
);
}
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
users
,
(
GDestroyNotify
)
fb_api_user_free
);
g_object_unref
(
values
);
json_node_free
(
croot
);
json_node_free
(
root
);
}
void
fb_api_contacts
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
;
JsonBuilder
*
bldr
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
if
(
priv
->
contacts_delta
)
{
fb_api_contacts_delta
(
api
,
priv
->
contacts_delta
);
return
;
}
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_str
(
bldr
,
NULL
,
"user"
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"1"
,
G_STRINGIFY
(
FB_API_CONTACTS_COUNT
));
fb_api_http_query
(
api
,
FB_API_QUERY_CONTACTS
,
bldr
,
fb_api_cb_contacts
);
}
static
void
fb_api_contacts_after
(
FbApi
*
api
,
const
gchar
*
cursor
)
{
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_str
(
bldr
,
NULL
,
"user"
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"1"
,
cursor
);
fb_json_bldr_add_str
(
bldr
,
"2"
,
G_STRINGIFY
(
FB_API_CONTACTS_COUNT
));
fb_api_http_query
(
api
,
FB_API_QUERY_CONTACTS_AFTER
,
bldr
,
fb_api_cb_contacts
);
}
void
fb_api_contacts_delta
(
FbApi
*
api
,
const
gchar
*
delta_cursor
)
{
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_str
(
bldr
,
"0"
,
delta_cursor
);
fb_json_bldr_arr_begin
(
bldr
,
"1"
);
fb_json_bldr_add_str
(
bldr
,
NULL
,
"user"
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"2"
,
G_STRINGIFY
(
FB_API_CONTACTS_COUNT
));
fb_api_http_query
(
api
,
FB_API_QUERY_CONTACTS_DELTA
,
bldr
,
fb_api_cb_contacts
);
}
void
fb_api_connect
(
FbApi
*
api
,
gboolean
invisible
)
{
FbApiPrivate
*
priv
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
priv
->
invisible
=
invisible
;
fb_mqtt_open
(
priv
->
mqtt
,
FB_MQTT_HOST
,
FB_MQTT_PORT
);
}
void
fb_api_disconnect
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
fb_mqtt_disconnect
(
priv
->
mqtt
);
}
static
void
fb_api_message_send
(
FbApi
*
api
,
FbApiMessage
*
msg
)
{
const
gchar
*
tpfx
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbId
id
;
FbId
mid
;
gchar
*
json
;
JsonBuilder
*
bldr
;
mid
=
FB_API_MSGID
(
g_get_real_time
()
/
1000
,
g_random_int
());
priv
->
lastmid
=
mid
;
if
(
msg
->
tid
!=
0
)
{
tpfx
=
"tfbid_"
;
id
=
msg
->
tid
;
}
else
{
tpfx
=
""
;
id
=
msg
->
uid
;
}
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_str
(
bldr
,
"body"
,
msg
->
text
);
fb_json_bldr_add_strf
(
bldr
,
"msgid"
,
"%"
FB_ID_FORMAT
,
mid
);
fb_json_bldr_add_strf
(
bldr
,
"sender_fbid"
,
"%"
FB_ID_FORMAT
,
priv
->
uid
);
fb_json_bldr_add_strf
(
bldr
,
"to"
,
"%s%"
FB_ID_FORMAT
,
tpfx
,
id
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/send_message2"
,
"%s"
,
json
);
g_free
(
json
);
}
void
fb_api_message
(
FbApi
*
api
,
FbId
id
,
gboolean
thread
,
const
gchar
*
text
)
{
FbApiMessage
*
msg
;
FbApiPrivate
*
priv
;
gboolean
empty
;
g_return_if_fail
(
FB_IS_API
(
api
));
g_return_if_fail
(
text
!=
NULL
);
priv
=
api
->
priv
;
msg
=
fb_api_message_dup
(
NULL
,
FALSE
);
msg
->
text
=
g_strdup
(
text
);
if
(
thread
)
{
msg
->
tid
=
id
;
}
else
{
msg
->
uid
=
id
;
}
empty
=
g_queue_is_empty
(
priv
->
msgs
);
g_queue_push_tail
(
priv
->
msgs
,
msg
);
if
(
empty
&&
fb_mqtt_connected
(
priv
->
mqtt
,
FALSE
))
{
fb_api_message_send
(
api
,
msg
);
}
}
void
fb_api_publish
(
FbApi
*
api
,
const
gchar
*
topic
,
const
gchar
*
format
,
...)
{
FbApiPrivate
*
priv
;
GByteArray
*
bytes
;
GByteArray
*
cytes
;
gchar
*
msg
;
GError
*
err
=
NULL
;
va_list
ap
;
g_return_if_fail
(
FB_IS_API
(
api
));
g_return_if_fail
(
topic
!=
NULL
);
g_return_if_fail
(
format
!=
NULL
);
priv
=
api
->
priv
;
va_start
(
ap
,
format
);
msg
=
g_strdup_vprintf
(
format
,
ap
);
va_end
(
ap
);
bytes
=
g_byte_array_new_take
((
guint8
*
)
msg
,
strlen
(
msg
));
cytes
=
fb_util_zlib_deflate
(
bytes
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_byte_array_free
(
bytes
,
TRUE
);
return
;
);
fb_util_debug_hexdump
(
FB_UTIL_DEBUG_INFO
,
bytes
,
"Writing message (topic: %s)"
,
topic
);
fb_mqtt_publish
(
priv
->
mqtt
,
topic
,
cytes
);
g_byte_array_free
(
cytes
,
TRUE
);
g_byte_array_free
(
bytes
,
TRUE
);
}
void
fb_api_read
(
FbApi
*
api
,
FbId
id
,
gboolean
thread
)
{
const
gchar
*
key
;
FbApiPrivate
*
priv
;
gchar
*
json
;
JsonBuilder
*
bldr
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_bool
(
bldr
,
"state"
,
TRUE
);
fb_json_bldr_add_int
(
bldr
,
"syncSeqId"
,
priv
->
sid
);
fb_json_bldr_add_str
(
bldr
,
"mark"
,
"read"
);
key
=
thread
?
"threadFbId"
:
"otherUserFbId"
;
fb_json_bldr_add_strf
(
bldr
,
key
,
"%"
FB_ID_FORMAT
,
id
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/mark_thread"
,
"%s"
,
json
);
g_free
(
json
);
}
static
GSList
*
fb_api_cb_unread_parse_attach
(
FbApi
*
api
,
const
gchar
*
mid
,
FbApiMessage
*
msg
,
GSList
*
msgs
,
JsonNode
*
root
,
GError
**
error
)
{
const
gchar
*
str
;
FbApiMessage
*
dmsg
;
FbId
id
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.attachment_fbid"
);
fb_json_values_set_array
(
values
,
FALSE
,
"$.blob_attachments"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
str
=
fb_json_values_next_str
(
values
,
NULL
);
id
=
FB_ID_FROM_STR
(
str
);
dmsg
=
fb_api_message_dup
(
msg
,
FALSE
);
fb_api_attach
(
api
,
id
,
mid
,
dmsg
);
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
}
g_object_unref
(
values
);
return
msgs
;
}
static
void
fb_api_cb_unread_msgs
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
body
;
const
gchar
*
str
;
FbApi
*
api
=
data
;
FbApiMessage
*
dmsg
;
FbApiMessage
msg
;
FbId
id
;
FbId
tid
;
FbJsonValues
*
values
;
gchar
*
xma
;
GError
*
err
=
NULL
;
GSList
*
msgs
=
NULL
;
JsonNode
*
node
;
JsonNode
*
root
;
JsonNode
*
xode
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
node
=
fb_json_node_get_nth
(
root
,
0
);
if
(
node
==
NULL
)
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to obtain unread messages"
));
json_node_free
(
root
);
return
;
}
values
=
fb_json_values_new
(
node
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.thread_key.thread_fbid"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
return
;
);
fb_api_message_reset
(
&
msg
,
FALSE
);
str
=
fb_json_values_next_str
(
values
,
"0"
);
tid
=
FB_ID_FROM_STR
(
str
);
g_object_unref
(
values
);
values
=
fb_json_values_new
(
node
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_BOOL
,
TRUE
,
"$.unread"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.message_sender.messaging_actor.id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.message.text"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.timestamp_precise"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.sticker.id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.message_id"
);
fb_json_values_set_array
(
values
,
FALSE
,
"$.messages.nodes"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
if
(
!
fb_json_values_next_bool
(
values
,
FALSE
))
{
continue
;
}
str
=
fb_json_values_next_str
(
values
,
"0"
);
body
=
fb_json_values_next_str
(
values
,
NULL
);
fb_api_message_reset
(
&
msg
,
FALSE
);
msg
.
uid
=
FB_ID_FROM_STR
(
str
);
msg
.
tid
=
tid
;
str
=
fb_json_values_next_str
(
values
,
"0"
);
msg
.
tstamp
=
g_ascii_strtoll
(
str
,
NULL
,
10
);
if
(
body
!=
NULL
)
{
dmsg
=
fb_api_message_dup
(
&
msg
,
FALSE
);
dmsg
->
text
=
g_strdup
(
body
);
msgs
=
g_slist_prepend
(
msgs
,
dmsg
);
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
str
!=
NULL
)
{
dmsg
=
fb_api_message_dup
(
&
msg
,
FALSE
);
id
=
FB_ID_FROM_STR
(
str
);
fb_api_sticker
(
api
,
id
,
dmsg
);
}
node
=
fb_json_values_get_root
(
values
);
xode
=
fb_json_node_get
(
node
,
"$.extensible_attachment"
,
NULL
);
if
(
xode
!=
NULL
)
{
xma
=
fb_api_xma_parse
(
api
,
body
,
xode
,
&
err
);
if
(
xma
!=
NULL
)
{
dmsg
=
fb_api_message_dup
(
&
msg
,
FALSE
);
dmsg
->
text
=
xma
;
msgs
=
g_slist_prepend
(
msgs
,
dmsg
);
}
json_node_free
(
xode
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
str
==
NULL
)
{
continue
;
}
msgs
=
fb_api_cb_unread_parse_attach
(
api
,
str
,
&
msg
,
msgs
,
node
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
}
if
(
G_UNLIKELY
(
err
==
NULL
))
{
msgs
=
g_slist_reverse
(
msgs
);
g_signal_emit_by_name
(
api
,
"messages"
,
msgs
);
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
msgs
,
(
GDestroyNotify
)
fb_api_message_free
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_cb_unread
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
id
;
FbApi
*
api
=
data
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
gint64
count
;
JsonBuilder
*
bldr
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_INT
,
TRUE
,
"$.unread_count"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.thread_key.other_user_id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.thread_key.thread_fbid"
);
fb_json_values_set_array
(
values
,
FALSE
,
"$.viewer.message_threads"
".nodes"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
count
=
fb_json_values_next_int
(
values
,
-5
);
if
(
count
<
1
)
{
continue
;
}
id
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
id
==
NULL
)
{
id
=
fb_json_values_next_str
(
values
,
"0"
);
}
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_str
(
bldr
,
NULL
,
id
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"10"
,
"true"
);
fb_json_bldr_add_str
(
bldr
,
"11"
,
"true"
);
fb_json_bldr_add_int
(
bldr
,
"12"
,
count
);
fb_json_bldr_add_str
(
bldr
,
"13"
,
"false"
);
fb_api_http_query
(
api
,
FB_API_QUERY_THREAD
,
bldr
,
fb_api_cb_unread_msgs
);
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
fb_api_error_emit
(
api
,
err
);
}
g_object_unref
(
values
);
json_node_free
(
root
);
}
void
fb_api_unread
(
FbApi
*
api
)
{
FbApiPrivate
*
priv
;
JsonBuilder
*
bldr
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
if
(
priv
->
unread
<
1
)
{
return
;
}
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_str
(
bldr
,
"2"
,
"true"
);
fb_json_bldr_add_int
(
bldr
,
"1"
,
priv
->
unread
);
fb_json_bldr_add_str
(
bldr
,
"12"
,
"true"
);
fb_json_bldr_add_str
(
bldr
,
"13"
,
"false"
);
fb_api_http_query
(
api
,
FB_API_QUERY_THREADS
,
bldr
,
fb_api_cb_unread
);
}
static
void
fb_api_cb_sticker
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiMessage
*
msg
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
GSList
*
msgs
=
NULL
;
JsonNode
*
node
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
node
=
fb_json_node_get_nth
(
root
,
0
);
values
=
fb_json_values_new
(
node
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.thread_image.uri"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
msg
=
g_object_steal_data
(
G_OBJECT
(
res
),
"fb-api-msg"
);
msg
->
flags
|=
FB_API_MESSAGE_FLAG_IMAGE
;
msg
->
text
=
fb_json_values_next_str_dup
(
values
,
NULL
);
msgs
=
g_slist_prepend
(
msgs
,
msg
);
g_signal_emit_by_name
(
api
,
"messages"
,
msgs
);
g_slist_free_full
(
msgs
,
(
GDestroyNotify
)
fb_api_message_free
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
static
void
fb_api_sticker
(
FbApi
*
api
,
FbId
sid
,
FbApiMessage
*
msg
)
{
JsonBuilder
*
bldr
;
SoupMessage
*
http
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_strf
(
bldr
,
NULL
,
"%"
FB_ID_FORMAT
,
sid
);
fb_json_bldr_arr_end
(
bldr
);
http
=
fb_api_http_query
(
api
,
FB_API_QUERY_STICKER
,
bldr
,
fb_api_cb_sticker
);
g_object_set_data_full
(
G_OBJECT
(
http
),
"fb-api-msg"
,
msg
,
(
GDestroyNotify
)
fb_api_message_free
);
}
static
gboolean
fb_api_thread_parse
(
FbApi
*
api
,
FbApiThread
*
thrd
,
JsonNode
*
root
,
GError
**
error
)
{
const
gchar
*
str
;
FbApiPrivate
*
priv
=
api
->
priv
;
FbApiUser
*
user
;
FbId
uid
;
FbJsonValues
*
values
;
gboolean
haself
=
FALSE
;
guint
num_users
=
0
;
GError
*
err
=
NULL
;
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.thread_key.thread_fbid"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
FALSE
,
"$.name"
);
fb_json_values_update
(
values
,
&
err
);
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
g_object_unref
(
values
);
return
FALSE
;
}
str
=
fb_json_values_next_str
(
values
,
NULL
);
if
(
str
==
NULL
)
{
g_object_unref
(
values
);
return
FALSE
;
}
thrd
->
tid
=
FB_ID_FROM_STR
(
str
);
thrd
->
topic
=
fb_json_values_next_str_dup
(
values
,
NULL
);
g_object_unref
(
values
);
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.messaging_actor.id"
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.messaging_actor.name"
);
fb_json_values_set_array
(
values
,
TRUE
,
"$.all_participants.nodes"
);
while
(
fb_json_values_update
(
values
,
&
err
))
{
str
=
fb_json_values_next_str
(
values
,
"0"
);
uid
=
FB_ID_FROM_STR
(
str
);
num_users
++
;
if
(
uid
!=
priv
->
uid
)
{
user
=
fb_api_user_dup
(
NULL
,
FALSE
);
user
->
uid
=
uid
;
user
->
name
=
fb_json_values_next_str_dup
(
values
,
NULL
);
thrd
->
users
=
g_slist_prepend
(
thrd
->
users
,
user
);
}
else
{
haself
=
TRUE
;
}
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
g_propagate_error
(
error
,
err
);
fb_api_thread_reset
(
thrd
,
TRUE
);
g_object_unref
(
values
);
return
FALSE
;
}
if
(
num_users
<
2
||
!
haself
)
{
g_object_unref
(
values
);
return
FALSE
;
}
g_object_unref
(
values
);
return
TRUE
;
}
static
void
fb_api_cb_thread
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiThread
thrd
;
GError
*
err
=
NULL
;
JsonNode
*
node
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
node
=
fb_json_node_get_nth
(
root
,
0
);
if
(
node
==
NULL
)
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to obtain thread information"
));
json_node_free
(
root
);
return
;
}
fb_api_thread_reset
(
&
thrd
,
FALSE
);
if
(
!
fb_api_thread_parse
(
api
,
&
thrd
,
node
,
&
err
))
{
if
(
G_LIKELY
(
err
==
NULL
))
{
if
(
thrd
.
tid
)
{
g_signal_emit_by_name
(
api
,
"thread-kicked"
,
&
thrd
);
}
else
{
fb_api_error_literal
(
api
,
FB_API_ERROR_GENERAL
,
_
(
"Failed to parse thread information"
));
}
}
else
{
fb_api_error_emit
(
api
,
err
);
}
}
else
{
g_signal_emit_by_name
(
api
,
"thread"
,
&
thrd
);
}
fb_api_thread_reset
(
&
thrd
,
TRUE
);
json_node_free
(
root
);
}
void
fb_api_thread
(
FbApi
*
api
,
FbId
tid
)
{
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_arr_begin
(
bldr
,
"0"
);
fb_json_bldr_add_strf
(
bldr
,
NULL
,
"%"
FB_ID_FORMAT
,
tid
);
fb_json_bldr_arr_end
(
bldr
);
fb_json_bldr_add_str
(
bldr
,
"10"
,
"false"
);
fb_json_bldr_add_str
(
bldr
,
"11"
,
"false"
);
fb_json_bldr_add_str
(
bldr
,
"13"
,
"false"
);
fb_api_http_query
(
api
,
FB_API_QUERY_THREAD
,
bldr
,
fb_api_cb_thread
);
}
static
void
fb_api_cb_thread_create
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
const
gchar
*
str
;
FbApi
*
api
=
data
;
FbId
tid
;
FbJsonValues
*
values
;
GError
*
err
=
NULL
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
values
=
fb_json_values_new
(
root
);
fb_json_values_add
(
values
,
FB_JSON_TYPE_STR
,
TRUE
,
"$.id"
);
fb_json_values_update
(
values
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
g_object_unref
(
values
);
json_node_free
(
root
);
return
;
);
str
=
fb_json_values_next_str
(
values
,
"0"
);
tid
=
FB_ID_FROM_STR
(
str
);
g_signal_emit_by_name
(
api
,
"thread-create"
,
tid
);
g_object_unref
(
values
);
json_node_free
(
root
);
}
void
fb_api_thread_create
(
FbApi
*
api
,
GSList
*
uids
)
{
FbApiPrivate
*
priv
;
FbHttpParams
*
prms
;
FbId
*
uid
;
gchar
*
json
;
GSList
*
l
;
JsonBuilder
*
bldr
;
g_return_if_fail
(
FB_IS_API
(
api
));
g_warn_if_fail
(
g_slist_length
(
uids
)
>
1
);
priv
=
api
->
priv
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_ARRAY
);
fb_json_bldr_obj_begin
(
bldr
,
NULL
);
fb_json_bldr_add_str
(
bldr
,
"type"
,
"id"
);
fb_json_bldr_add_strf
(
bldr
,
"id"
,
"%"
FB_ID_FORMAT
,
priv
->
uid
);
fb_json_bldr_obj_end
(
bldr
);
for
(
l
=
uids
;
l
!=
NULL
;
l
=
l
->
next
)
{
uid
=
l
->
data
;
fb_json_bldr_obj_begin
(
bldr
,
NULL
);
fb_json_bldr_add_str
(
bldr
,
"type"
,
"id"
);
fb_json_bldr_add_strf
(
bldr
,
"id"
,
"%"
FB_ID_FORMAT
,
*
uid
);
fb_json_bldr_obj_end
(
bldr
);
}
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_ARRAY
,
NULL
);
prms
=
fb_http_params_new
();
fb_http_params_set_str
(
prms
,
"recipients"
,
json
);
fb_api_http_req
(
api
,
FB_API_URL_THREADS
,
"createGroup"
,
"POST"
,
prms
,
fb_api_cb_thread_create
);
g_free
(
json
);
}
void
fb_api_thread_invite
(
FbApi
*
api
,
FbId
tid
,
FbId
uid
)
{
FbHttpParams
*
prms
;
gchar
*
json
;
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_ARRAY
);
fb_json_bldr_obj_begin
(
bldr
,
NULL
);
fb_json_bldr_add_str
(
bldr
,
"type"
,
"id"
);
fb_json_bldr_add_strf
(
bldr
,
"id"
,
"%"
FB_ID_FORMAT
,
uid
);
fb_json_bldr_obj_end
(
bldr
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_ARRAY
,
NULL
);
prms
=
fb_http_params_new
();
fb_http_params_set_str
(
prms
,
"to"
,
json
);
fb_http_params_set_strf
(
prms
,
"id"
,
"t_%"
FB_ID_FORMAT
,
tid
);
fb_api_http_req
(
api
,
FB_API_URL_PARTS
,
"addMembers"
,
"POST"
,
prms
,
fb_api_cb_http_bool
);
g_free
(
json
);
}
void
fb_api_thread_remove
(
FbApi
*
api
,
FbId
tid
,
FbId
uid
)
{
FbApiPrivate
*
priv
;
FbHttpParams
*
prms
;
gchar
*
json
;
JsonBuilder
*
bldr
;
g_return_if_fail
(
FB_IS_API
(
api
));
priv
=
api
->
priv
;
prms
=
fb_http_params_new
();
fb_http_params_set_strf
(
prms
,
"id"
,
"t_%"
FB_ID_FORMAT
,
tid
);
if
(
uid
==
0
)
{
uid
=
priv
->
uid
;
}
if
(
uid
!=
priv
->
uid
)
{
bldr
=
fb_json_bldr_new
(
JSON_NODE_ARRAY
);
fb_json_bldr_add_strf
(
bldr
,
NULL
,
"%"
FB_ID_FORMAT
,
uid
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_ARRAY
,
NULL
);
fb_http_params_set_str
(
prms
,
"to"
,
json
);
g_free
(
json
);
}
fb_api_http_req
(
api
,
FB_API_URL_PARTS
,
"removeMembers"
,
"DELETE"
,
prms
,
fb_api_cb_http_bool
);
}
void
fb_api_thread_topic
(
FbApi
*
api
,
FbId
tid
,
const
gchar
*
topic
)
{
FbHttpParams
*
prms
;
prms
=
fb_http_params_new
();
fb_http_params_set_str
(
prms
,
"name"
,
topic
);
fb_http_params_set_int
(
prms
,
"tid"
,
tid
);
fb_api_http_req
(
api
,
FB_API_URL_TOPIC
,
"setThreadName"
,
"messaging.setthreadname"
,
prms
,
fb_api_cb_http_bool
);
}
static
void
fb_api_cb_threads
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
res
,
gpointer
data
)
{
FbApi
*
api
=
data
;
FbApiThread
*
dthrd
;
FbApiThread
thrd
;
GError
*
err
=
NULL
;
GList
*
elms
;
GList
*
l
;
GSList
*
thrds
=
NULL
;
JsonArray
*
arr
;
JsonNode
*
root
;
if
(
!
fb_api_http_chk
(
api
,
res
,
&
root
))
{
return
;
}
arr
=
fb_json_node_get_arr
(
root
,
"$.viewer.message_threads.nodes"
,
&
err
);
FB_API_ERROR_EMIT
(
api
,
err
,
json_node_free
(
root
);
return
;
);
elms
=
json_array_get_elements
(
arr
);
for
(
l
=
elms
;
l
!=
NULL
;
l
=
l
->
next
)
{
fb_api_thread_reset
(
&
thrd
,
FALSE
);
if
(
fb_api_thread_parse
(
api
,
&
thrd
,
l
->
data
,
&
err
))
{
dthrd
=
fb_api_thread_dup
(
&
thrd
,
FALSE
);
thrds
=
g_slist_prepend
(
thrds
,
dthrd
);
}
else
{
fb_api_thread_reset
(
&
thrd
,
TRUE
);
}
if
(
G_UNLIKELY
(
err
!=
NULL
))
{
break
;
}
}
if
(
G_LIKELY
(
err
==
NULL
))
{
thrds
=
g_slist_reverse
(
thrds
);
g_signal_emit_by_name
(
api
,
"threads"
,
thrds
);
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
thrds
,
(
GDestroyNotify
)
fb_api_thread_free
);
g_list_free
(
elms
);
json_array_unref
(
arr
);
json_node_free
(
root
);
}
void
fb_api_threads
(
FbApi
*
api
)
{
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_str
(
bldr
,
"2"
,
"true"
);
fb_json_bldr_add_str
(
bldr
,
"12"
,
"false"
);
fb_json_bldr_add_str
(
bldr
,
"13"
,
"false"
);
fb_api_http_query
(
api
,
FB_API_QUERY_THREADS
,
bldr
,
fb_api_cb_threads
);
}
void
fb_api_typing
(
FbApi
*
api
,
FbId
uid
,
gboolean
state
)
{
gchar
*
json
;
JsonBuilder
*
bldr
;
bldr
=
fb_json_bldr_new
(
JSON_NODE_OBJECT
);
fb_json_bldr_add_int
(
bldr
,
"state"
,
state
!=
0
);
fb_json_bldr_add_strf
(
bldr
,
"to"
,
"%"
FB_ID_FORMAT
,
uid
);
json
=
fb_json_bldr_close
(
bldr
,
JSON_NODE_OBJECT
,
NULL
);
fb_api_publish
(
api
,
"/typing"
,
"%s"
,
json
);
g_free
(
json
);
}
FbApiEvent
*
fb_api_event_dup
(
const
FbApiEvent
*
event
,
gboolean
deep
)
{
FbApiEvent
*
ret
;
if
(
event
==
NULL
)
{
return
g_new0
(
FbApiEvent
,
1
);
}
ret
=
g_memdup2
(
event
,
sizeof
*
event
);
if
(
deep
)
{
ret
->
text
=
g_strdup
(
event
->
text
);
}
return
ret
;
}
void
fb_api_event_reset
(
FbApiEvent
*
event
,
gboolean
deep
)
{
g_return_if_fail
(
event
!=
NULL
);
if
(
deep
)
{
g_free
(
event
->
text
);
}
memset
(
event
,
0
,
sizeof
*
event
);
}
void
fb_api_event_free
(
FbApiEvent
*
event
)
{
if
(
G_LIKELY
(
event
!=
NULL
))
{
g_free
(
event
->
text
);
g_free
(
event
);
}
}
FbApiMessage
*
fb_api_message_dup
(
const
FbApiMessage
*
msg
,
gboolean
deep
)
{
FbApiMessage
*
ret
;
if
(
msg
==
NULL
)
{
return
g_new0
(
FbApiMessage
,
1
);
}
ret
=
g_memdup2
(
msg
,
sizeof
*
msg
);
if
(
deep
)
{
ret
->
text
=
g_strdup
(
msg
->
text
);
}
return
ret
;
}
void
fb_api_message_reset
(
FbApiMessage
*
msg
,
gboolean
deep
)
{
g_return_if_fail
(
msg
!=
NULL
);
if
(
deep
)
{
g_free
(
msg
->
text
);
}
memset
(
msg
,
0
,
sizeof
*
msg
);
}
void
fb_api_message_free
(
FbApiMessage
*
msg
)
{
if
(
G_LIKELY
(
msg
!=
NULL
))
{
g_free
(
msg
->
text
);
g_free
(
msg
);
}
}
FbApiPresence
*
fb_api_presence_dup
(
const
FbApiPresence
*
presence
)
{
if
(
presence
==
NULL
)
{
return
g_new0
(
FbApiPresence
,
1
);
}
return
g_memdup2
(
presence
,
sizeof
*
presence
);
}
void
fb_api_presence_reset
(
FbApiPresence
*
presence
)
{
g_return_if_fail
(
presence
!=
NULL
);
memset
(
presence
,
0
,
sizeof
*
presence
);
}
void
fb_api_presence_free
(
FbApiPresence
*
presence
)
{
if
(
G_LIKELY
(
presence
!=
NULL
))
{
g_free
(
presence
);
}
}
FbApiThread
*
fb_api_thread_dup
(
const
FbApiThread
*
thrd
,
gboolean
deep
)
{
FbApiThread
*
ret
;
FbApiUser
*
user
;
GSList
*
l
;
if
(
thrd
==
NULL
)
{
return
g_new0
(
FbApiThread
,
1
);
}
ret
=
g_memdup2
(
thrd
,
sizeof
*
thrd
);
if
(
deep
)
{
ret
->
users
=
NULL
;
for
(
l
=
thrd
->
users
;
l
!=
NULL
;
l
=
l
->
next
)
{
user
=
fb_api_user_dup
(
l
->
data
,
TRUE
);
ret
->
users
=
g_slist_prepend
(
ret
->
users
,
user
);
}
ret
->
topic
=
g_strdup
(
thrd
->
topic
);
ret
->
users
=
g_slist_reverse
(
ret
->
users
);
}
return
ret
;
}
void
fb_api_thread_reset
(
FbApiThread
*
thrd
,
gboolean
deep
)
{
g_return_if_fail
(
thrd
!=
NULL
);
if
(
deep
)
{
g_slist_free_full
(
thrd
->
users
,
(
GDestroyNotify
)
fb_api_user_free
);
g_free
(
thrd
->
topic
);
}
memset
(
thrd
,
0
,
sizeof
*
thrd
);
}
void
fb_api_thread_free
(
FbApiThread
*
thrd
)
{
if
(
G_LIKELY
(
thrd
!=
NULL
))
{
g_slist_free_full
(
thrd
->
users
,
(
GDestroyNotify
)
fb_api_user_free
);
g_free
(
thrd
->
topic
);
g_free
(
thrd
);
}
}
FbApiTyping
*
fb_api_typing_dup
(
const
FbApiTyping
*
typg
)
{
if
(
typg
==
NULL
)
{
return
g_new0
(
FbApiTyping
,
1
);
}
return
g_memdup2
(
typg
,
sizeof
*
typg
);
}
void
fb_api_typing_reset
(
FbApiTyping
*
typg
)
{
g_return_if_fail
(
typg
!=
NULL
);
memset
(
typg
,
0
,
sizeof
*
typg
);
}
void
fb_api_typing_free
(
FbApiTyping
*
typg
)
{
if
(
G_LIKELY
(
typg
!=
NULL
))
{
g_free
(
typg
);
}
}
FbApiUser
*
fb_api_user_dup
(
const
FbApiUser
*
user
,
gboolean
deep
)
{
FbApiUser
*
ret
;
if
(
user
==
NULL
)
{
return
g_new0
(
FbApiUser
,
1
);
}
ret
=
g_memdup2
(
user
,
sizeof
*
user
);
if
(
deep
)
{
ret
->
name
=
g_strdup
(
user
->
name
);
ret
->
icon
=
g_strdup
(
user
->
icon
);
ret
->
csum
=
g_strdup
(
user
->
csum
);
}
return
ret
;
}
void
fb_api_user_reset
(
FbApiUser
*
user
,
gboolean
deep
)
{
g_return_if_fail
(
user
!=
NULL
);
if
(
deep
)
{
g_free
(
user
->
name
);
g_free
(
user
->
icon
);
g_free
(
user
->
csum
);
}
memset
(
user
,
0
,
sizeof
*
user
);
}
void
fb_api_user_free
(
FbApiUser
*
user
)
{
if
(
G_LIKELY
(
user
!=
NULL
))
{
g_free
(
user
->
name
);
g_free
(
user
->
icon
);
g_free
(
user
->
csum
);
g_free
(
user
);
}
}