pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Merged in default (pull request #665)
2019-12-30, Gary Kramlich
8066acc5ed93
Merged in default (pull request #665)
Convert most Xfer UI ops to signals
Approved-by: Gary Kramlich
/* 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
<json-glib/json-glib.h>
#include
<libsoup/soup.h>
#include
<stdarg.h>
#include
<string.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_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_thrift_read_str
(
thft
,
NULL
);
size
=
fb_thrift_get_pos
(
thft
);
g_object_unref
(
thft
);
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
**
press
,
GError
**
error
)
{
FbApiPresence
*
pres
;
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
));
pres
=
fb_api_presence_dup
(
NULL
);
pres
->
uid
=
i64
;
pres
->
active
=
i32
!=
0
;
*
press
=
g_slist_prepend
(
*
press
,
pres
);
fb_util_debug_info
(
"Presence: %"
FB_ID_FORMAT
" (%d)"
,
i64
,
i32
!=
0
);
while
(
id
<=
5
)
{
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 */
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
*
press
=
NULL
;
thft
=
fb_thrift_new
(
pload
,
0
);
fb_api_cb_publish_pt
(
thft
,
&
press
,
&
err
);
g_object_unref
(
thft
);
if
(
G_LIKELY
(
err
==
NULL
))
{
g_signal_emit_by_name
(
api
,
"presences"
,
press
);
}
else
{
fb_api_error_emit
(
api
,
err
);
}
g_slist_free_full
(
press
,
(
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
(
SOUP_SESSION_PROXY_RESOLVER
,
resolver
,
SOUP_SESSION_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
=
purple_uuid_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_memdup
(
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_memdup
(
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
*
pres
)
{
if
(
pres
==
NULL
)
{
return
g_new0
(
FbApiPresence
,
1
);
}
return
g_memdup
(
pres
,
sizeof
*
pres
);
}
void
fb_api_presence_reset
(
FbApiPresence
*
pres
)
{
g_return_if_fail
(
pres
!=
NULL
);
memset
(
pres
,
0
,
sizeof
*
pres
);
}
void
fb_api_presence_free
(
FbApiPresence
*
pres
)
{
if
(
G_LIKELY
(
pres
!=
NULL
))
{
g_free
(
pres
);
}
}
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_memdup
(
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_memdup
(
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_memdup
(
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
);
}
}