3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include <json-glib/json-glib.h>
26 #include "glibcompat.h"
34 typedef struct _FbApiData FbApiData
;
70 gchar
*contacts_delta
;
76 * Represents a Facebook Messenger connection.
91 fb_api_attach(FbApi
*api
, FbId aid
, const gchar
*msgid
, FbApiMessage
*msg
);
94 fb_api_contacts_after(FbApi
*api
, const gchar
*cursor
);
97 fb_api_message_send(FbApi
*api
, FbApiMessage
*msg
);
100 fb_api_sticker(FbApi
*api
, FbId sid
, FbApiMessage
*msg
);
103 fb_api_contacts_delta(FbApi
*api
, const gchar
*delta_cursor
);
105 G_DEFINE_TYPE_WITH_PRIVATE(FbApi
, fb_api
, G_TYPE_OBJECT
);
108 fb_api_set_property(GObject
*obj
, guint prop
, const GValue
*val
,
111 FbApiPrivate
*priv
= FB_API(obj
)->priv
;
116 priv
->cid
= g_value_dup_string(val
);
120 priv
->did
= g_value_dup_string(val
);
123 priv
->mid
= g_value_get_uint64(val
);
126 g_free(priv
->stoken
);
127 priv
->stoken
= g_value_dup_string(val
);
131 priv
->token
= g_value_dup_string(val
);
134 priv
->uid
= g_value_get_int64(val
);
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj
, prop
, pspec
);
144 fb_api_get_property(GObject
*obj
, guint prop
, GValue
*val
, GParamSpec
*pspec
)
146 FbApiPrivate
*priv
= FB_API(obj
)->priv
;
150 g_value_set_string(val
, priv
->cid
);
153 g_value_set_string(val
, priv
->did
);
156 g_value_set_uint64(val
, priv
->mid
);
159 g_value_set_string(val
, priv
->stoken
);
162 g_value_set_string(val
, priv
->token
);
165 g_value_set_int64(val
, priv
->uid
);
169 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj
, prop
, pspec
);
176 fb_api_dispose(GObject
*obj
)
179 FbApiPrivate
*priv
= FB_API(obj
)->priv
;
182 fb_http_conns_cancel_all(priv
->cons
);
183 g_hash_table_iter_init(&iter
, priv
->data
);
185 while (g_hash_table_iter_next(&iter
, NULL
, (gpointer
) &fata
)) {
186 fata
->func(fata
->data
);
190 if (G_UNLIKELY(priv
->mqtt
!= NULL
)) {
191 g_object_unref(priv
->mqtt
);
194 fb_http_conns_free(priv
->cons
);
195 g_hash_table_destroy(priv
->data
);
196 g_queue_free_full(priv
->msgs
, (GDestroyNotify
) fb_api_message_free
);
200 g_free(priv
->stoken
);
202 g_free(priv
->contacts_delta
);
206 fb_api_class_init(FbApiClass
*klass
)
208 GObjectClass
*gklass
= G_OBJECT_CLASS(klass
);
209 GParamSpec
*props
[PROP_N
] = {NULL
};
211 gklass
->set_property
= fb_api_set_property
;
212 gklass
->get_property
= fb_api_get_property
;
213 gklass
->dispose
= fb_api_dispose
;
218 * The client identifier for MQTT. This value should be saved
219 * and loaded for persistence.
221 props
[PROP_CID
] = g_param_spec_string(
224 "Client identifier for MQTT",
231 * The device identifier for the MQTT message queue. This value
232 * should be saved and loaded for persistence.
234 props
[PROP_DID
] = g_param_spec_string(
237 "Device identifier for the MQTT message queue",
244 * The MQTT identifier. This value should be saved and loaded
247 props
[PROP_MID
] = g_param_spec_uint64(
257 * The synchronization token for the MQTT message queue. This
258 * value should be saved and loaded for persistence.
260 props
[PROP_STOKEN
] = g_param_spec_string(
263 "Synchronization token for the MQTT message queue",
270 * The access token for authentication. This value should be
271 * saved and loaded for persistence.
273 props
[PROP_TOKEN
] = g_param_spec_string(
276 "Access token for authentication",
283 * The #FbId of the user of the #FbApi.
285 props
[PROP_UID
] = g_param_spec_int64(
291 g_object_class_install_properties(gklass
, PROP_N
, props
);
297 * Emitted upon the successful completion of the authentication
298 * process. This is emitted as a result of #fb_api_auth().
301 G_TYPE_FROM_CLASS(klass
),
312 * Emitted upon the successful completion of the connection
313 * process. This is emitted as a result of #fb_api_connect().
315 g_signal_new("connect",
316 G_TYPE_FROM_CLASS(klass
),
326 * @user: The #FbApiUser.
328 * Emitted upon the successful reply of a contact request. This
329 * is emitted as a result of #fb_api_contact().
331 g_signal_new("contact",
332 G_TYPE_FROM_CLASS(klass
),
342 * @users: The #GSList of #FbApiUser's.
343 * @complete: #TRUE if the list is fetched, otherwise #FALSE.
345 * Emitted upon the successful reply of a contacts request.
346 * This is emitted as a result of #fb_api_contacts(). This can
347 * be emitted multiple times before the entire contacts list
348 * has been fetched. Use @complete for detecting the completion
349 * status of the list fetch.
351 g_signal_new("contacts",
352 G_TYPE_FROM_CLASS(klass
),
357 2, G_TYPE_POINTER
, G_TYPE_BOOLEAN
);
360 * FbApi::contacts-delta:
362 * @added: The #GSList of added #FbApiUser's.
363 * @removed: The #GSList of strings with removed user ids.
365 * Like 'contacts', but only the deltas.
367 g_signal_new("contacts-delta",
368 G_TYPE_FROM_CLASS(klass
),
373 2, G_TYPE_POINTER
, G_TYPE_POINTER
);
378 * @error: The #GError.
380 * Emitted whenever an error is hit within the #FbApi. This
381 * should disconnect the #FbApi with #fb_api_disconnect().
383 g_signal_new("error",
384 G_TYPE_FROM_CLASS(klass
),
394 * @events: The #GSList of #FbApiEvent's.
396 * Emitted upon incoming events from the stream.
398 g_signal_new("events",
399 G_TYPE_FROM_CLASS(klass
),
409 * @msgs: The #GSList of #FbApiMessage's.
411 * Emitted upon incoming messages from the stream.
413 g_signal_new("messages",
414 G_TYPE_FROM_CLASS(klass
),
424 * @press: The #GSList of #FbApiPresence's.
426 * Emitted upon incoming presences from the stream.
428 g_signal_new("presences",
429 G_TYPE_FROM_CLASS(klass
),
439 * @thrd: The #FbApiThread.
441 * Emitted upon the successful reply of a thread request. This
442 * is emitted as a result of #fb_api_thread().
444 g_signal_new("thread",
445 G_TYPE_FROM_CLASS(klass
),
453 * FbApi::thread-create:
455 * @tid: The thread #FbId.
457 * Emitted upon the successful reply of a thread creation
458 * request. This is emitted as a result of
459 * #fb_api_thread_create().
461 g_signal_new("thread-create",
462 G_TYPE_FROM_CLASS(klass
),
470 * FbApi::thread-kicked:
472 * @thrd: The #FbApiThread.
474 * Emitted upon the reply of a thread request when the user is no longer
475 * part of that thread. This is emitted as a result of #fb_api_thread().
477 g_signal_new("thread-kicked",
478 G_TYPE_FROM_CLASS(klass
),
488 * @thrds: The #GSList of #FbApiThread's.
490 * Emitted upon the successful reply of a threads request. This
491 * is emitted as a result of #fb_api_threads().
493 g_signal_new("threads",
494 G_TYPE_FROM_CLASS(klass
),
504 * @typg: The #FbApiTyping.
506 * Emitted upon an incoming typing state from the stream.
508 g_signal_new("typing",
509 G_TYPE_FROM_CLASS(klass
),
518 fb_api_init(FbApi
*api
)
520 FbApiPrivate
*priv
= fb_api_get_instance_private(api
);
524 priv
->cons
= fb_http_conns_new();
525 priv
->msgs
= g_queue_new();
526 priv
->data
= g_hash_table_new_full(g_direct_hash
, g_direct_equal
,
531 fb_api_error_quark(void)
535 if (G_UNLIKELY(q
== 0)) {
536 q
= g_quark_from_static_string("fb-api-error-quark");
543 fb_api_data_set(FbApi
*api
, gpointer handle
, gpointer data
,
547 FbApiPrivate
*priv
= api
->priv
;
549 fata
= g_new0(FbApiData
, 1);
552 g_hash_table_replace(priv
->data
, handle
, fata
);
556 fb_api_data_take(FbApi
*api
, gconstpointer handle
)
559 FbApiPrivate
*priv
= api
->priv
;
562 fata
= g_hash_table_lookup(priv
->data
, handle
);
569 g_hash_table_remove(priv
->data
, handle
);
575 fb_api_json_chk(FbApi
*api
, gconstpointer data
, gssize size
, JsonNode
**node
)
578 FbApiError errc
= FB_API_ERROR_GENERAL
;
580 FbJsonValues
*values
;
581 gboolean success
= TRUE
;
588 static const gchar
*exprs
[] = {
593 "$.failedSend.errorMessage",
596 g_return_val_if_fail(FB_IS_API(api
), FALSE
);
599 if (G_UNLIKELY(size
== 0)) {
600 fb_api_error(api
, FB_API_ERROR_GENERAL
, _("Empty JSON data"));
604 fb_util_debug(FB_UTIL_DEBUG_INFO
, "Parsing JSON: %.*s\n",
605 (gint
) size
, (const gchar
*) data
);
607 root
= fb_json_node_new(data
, size
, &err
);
608 FB_API_ERROR_EMIT(api
, err
, return FALSE
);
610 values
= fb_json_values_new(root
);
611 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
, "$.error_code");
612 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.error.type");
613 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.errorCode");
614 fb_json_values_update(values
, &err
);
616 FB_API_ERROR_EMIT(api
, err
,
617 g_object_unref(values
);
618 json_node_free(root
);
622 code
= fb_json_values_next_int(values
, 0);
623 str
= fb_json_values_next_str(values
, NULL
);
625 if (purple_strequal(str
, "OAuthException") || (code
== 401)) {
626 errc
= FB_API_ERROR_AUTH
;
629 g_free(priv
->stoken
);
636 /* 509 is used for "invalid attachment id" */
638 errc
= FB_API_ERROR_NONFATAL
;
642 str
= fb_json_values_next_str(values
, NULL
);
644 if (purple_strequal(str
, "ERROR_QUEUE_NOT_FOUND") ||
645 purple_strequal(str
, "ERROR_QUEUE_LOST"))
647 errc
= FB_API_ERROR_QUEUE
;
650 g_free(priv
->stoken
);
654 g_object_unref(values
);
656 for (msg
= NULL
, i
= 0; i
< G_N_ELEMENTS(exprs
); i
++) {
657 msg
= fb_json_node_get_str(root
, exprs
[i
], NULL
);
665 if (!success
&& (msg
== NULL
)) {
666 msg
= g_strdup(_("Unknown error"));
670 fb_api_error(api
, errc
, "%s", msg
);
671 json_node_free(root
);
679 json_node_free(root
);
686 fb_api_http_chk(FbApi
*api
, PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
691 FbApiPrivate
*priv
= api
->priv
;
697 if (fb_http_conns_is_canceled(priv
->cons
)) {
701 msg
= purple_http_response_get_error(res
);
702 code
= purple_http_response_get_code(res
);
703 data
= purple_http_response_get_data(res
, &size
);
704 fb_http_conns_remove(priv
->cons
, con
);
707 emsg
= g_strdup_printf("%s (%d)", msg
, code
);
709 emsg
= g_strdup_printf("%d", code
);
712 fb_util_debug(FB_UTIL_DEBUG_INFO
, "HTTP Response (%p):", con
);
713 fb_util_debug(FB_UTIL_DEBUG_INFO
, " Response Error: %s", emsg
);
716 if (G_LIKELY(size
> 0)) {
717 fb_util_debug(FB_UTIL_DEBUG_INFO
, " Response Data: %.*s",
721 if (fb_http_error_chk(res
, &err
) && (root
== NULL
)) {
725 /* Rudimentary check to prevent wrongful error parsing */
726 if ((size
< 2) || (data
[0] != '{') || (data
[size
- 1] != '}')) {
727 FB_API_ERROR_EMIT(api
, err
, return FALSE
);
730 if (!fb_api_json_chk(api
, data
, size
, root
)) {
731 if (G_UNLIKELY(err
!= NULL
)) {
738 FB_API_ERROR_EMIT(api
, err
, return FALSE
);
742 static PurpleHttpConnection
*
743 fb_api_http_req(FbApi
*api
, const gchar
*url
, const gchar
*name
,
744 const gchar
*method
, FbHttpParams
*params
,
745 PurpleHttpCallback callback
)
747 FbApiPrivate
*priv
= api
->priv
;
754 PurpleHttpConnection
*ret
;
755 PurpleHttpRequest
*req
;
757 fb_http_params_set_str(params
, "api_key", FB_API_KEY
);
758 fb_http_params_set_str(params
, "device_id", priv
->did
);
759 fb_http_params_set_str(params
, "fb_api_req_friendly_name", name
);
760 fb_http_params_set_str(params
, "format", "json");
761 fb_http_params_set_str(params
, "method", method
);
763 val
= fb_util_get_locale();
764 fb_http_params_set_str(params
, "locale", val
);
767 req
= purple_http_request_new(url
);
768 purple_http_request_set_max_len(req
, -1);
769 purple_http_request_set_method(req
, "POST");
771 /* Ensure an old signature is not computed */
772 g_hash_table_remove(params
, "sig");
774 gstr
= g_string_new(NULL
);
775 keys
= g_hash_table_get_keys(params
);
776 keys
= g_list_sort(keys
, (GCompareFunc
) g_ascii_strcasecmp
);
778 for (l
= keys
; l
!= NULL
; l
= l
->next
) {
780 val
= g_hash_table_lookup(params
, key
);
781 g_string_append_printf(gstr
, "%s=%s", key
, val
);
784 g_string_append(gstr
, FB_API_SECRET
);
785 data
= g_compute_checksum_for_string(G_CHECKSUM_MD5
, gstr
->str
,
787 fb_http_params_set_str(params
, "sig", data
);
788 g_string_free(gstr
, TRUE
);
792 if (priv
->token
!= NULL
) {
793 data
= g_strdup_printf("OAuth %s", priv
->token
);
794 purple_http_request_header_set(req
, "Authorization", data
);
798 purple_http_request_header_set(req
, "User-Agent", FB_API_AGENT
);
799 purple_http_request_header_set(req
, "Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
801 data
= fb_http_params_close(params
, NULL
);
802 purple_http_request_set_contents(req
, data
, -1);
803 ret
= purple_http_request(priv
->gc
, req
, callback
, api
);
804 fb_http_conns_add(priv
->cons
, ret
);
805 purple_http_request_unref(req
);
807 fb_util_debug(FB_UTIL_DEBUG_INFO
, "HTTP Request (%p):", ret
);
808 fb_util_debug(FB_UTIL_DEBUG_INFO
, " Request URL: %s", url
);
809 fb_util_debug(FB_UTIL_DEBUG_INFO
, " Request Data: %s", data
);
815 static PurpleHttpConnection
*
816 fb_api_http_query(FbApi
*api
, gint64 query
, JsonBuilder
*builder
,
817 PurpleHttpCallback hcb
)
824 case FB_API_QUERY_CONTACT
:
827 case FB_API_QUERY_CONTACTS
:
828 name
= "FetchContactsFullQuery";
830 case FB_API_QUERY_CONTACTS_AFTER
:
831 name
= "FetchContactsFullWithAfterQuery";
833 case FB_API_QUERY_CONTACTS_DELTA
:
834 name
= "FetchContactsDeltaQuery";
836 case FB_API_QUERY_STICKER
:
837 name
= "FetchStickersWithPreviewsQuery";
839 case FB_API_QUERY_THREAD
:
840 name
= "ThreadQuery";
842 case FB_API_QUERY_SEQ_ID
:
843 case FB_API_QUERY_THREADS
:
844 name
= "ThreadListQuery";
846 case FB_API_QUERY_XMA
:
850 g_return_val_if_reached(NULL
);
854 prms
= fb_http_params_new();
855 json
= fb_json_bldr_close(builder
, JSON_NODE_OBJECT
, NULL
);
857 fb_http_params_set_strf(prms
, "query_id", "%" G_GINT64_FORMAT
, query
);
858 fb_http_params_set_str(prms
, "query_params", json
);
861 return fb_api_http_req(api
, FB_API_URL_GQL
, name
, "get", prms
, hcb
);
865 fb_api_cb_http_bool(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
871 if (!fb_api_http_chk(api
, con
, res
, NULL
)) {
875 hata
= purple_http_response_get_data(res
, NULL
);
877 if (!purple_strequal(hata
, "true")) {
878 fb_api_error(api
, FB_API_ERROR
,
879 _("Failed generic API operation"));
884 fb_api_cb_mqtt_error(FbMqtt
*mqtt
, GError
*error
, gpointer data
)
887 FbApiPrivate
*priv
= api
->priv
;
889 if (!priv
->retrying
) {
890 priv
->retrying
= TRUE
;
891 fb_util_debug_info("Attempting to reconnect the MQTT stream...");
892 fb_api_connect(api
, priv
->invisible
);
894 g_signal_emit_by_name(api
, "error", error
);
899 fb_api_cb_mqtt_open(FbMqtt
*mqtt
, gpointer data
)
901 const GByteArray
*bytes
;
903 FbApiPrivate
*priv
= api
->priv
;
908 static guint8 flags
= FB_MQTT_CONNECT_FLAG_USER
|
909 FB_MQTT_CONNECT_FLAG_PASS
|
910 FB_MQTT_CONNECT_FLAG_CLR
;
912 thft
= fb_thrift_new(NULL
, 0);
914 /* Write the client identifier */
915 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_STRING
, 1, 0);
916 fb_thrift_write_str(thft
, priv
->cid
);
918 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_STRUCT
, 4, 1);
920 /* Write the user identifier */
921 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I64
, 1, 0);
922 fb_thrift_write_i64(thft
, priv
->uid
);
924 /* Write the information string */
925 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_STRING
, 2, 1);
926 fb_thrift_write_str(thft
, FB_API_MQTT_AGENT
);
928 /* Write the UNKNOWN ("cp"?) */
929 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I64
, 3, 2);
930 fb_thrift_write_i64(thft
, 23);
932 /* Write the UNKNOWN ("ecp"?) */
933 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I64
, 4, 3);
934 fb_thrift_write_i64(thft
, 26);
936 /* Write the UNKNOWN */
937 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I32
, 5, 4);
938 fb_thrift_write_i32(thft
, 1);
940 /* Write the UNKNOWN ("no_auto_fg"?) */
941 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_BOOL
, 6, 5);
942 fb_thrift_write_bool(thft
, TRUE
);
944 /* Write the visibility state */
945 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_BOOL
, 7, 6);
946 fb_thrift_write_bool(thft
, !priv
->invisible
);
948 /* Write the device identifier */
949 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_STRING
, 8, 7);
950 fb_thrift_write_str(thft
, priv
->did
);
952 /* Write the UNKNOWN ("fg"?) */
953 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_BOOL
, 9, 8);
954 fb_thrift_write_bool(thft
, TRUE
);
956 /* Write the UNKNOWN ("nwt"?) */
957 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I32
, 10, 9);
958 fb_thrift_write_i32(thft
, 1);
960 /* Write the UNKNOWN ("nwst"?) */
961 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I32
, 11, 10);
962 fb_thrift_write_i32(thft
, 0);
964 /* Write the MQTT identifier */
965 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_I64
, 12, 11);
966 fb_thrift_write_i64(thft
, priv
->mid
);
968 /* Write the UNKNOWN */
969 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_LIST
, 14, 12);
970 fb_thrift_write_list(thft
, FB_THRIFT_TYPE_I32
, 0);
971 fb_thrift_write_stop(thft
);
973 /* Write the token */
974 fb_thrift_write_field(thft
, FB_THRIFT_TYPE_STRING
, 15, 14);
975 fb_thrift_write_str(thft
, priv
->token
);
977 /* Write the STOP for the struct */
978 fb_thrift_write_stop(thft
);
980 bytes
= fb_thrift_get_bytes(thft
);
981 cytes
= fb_util_zlib_deflate(bytes
, &err
);
983 FB_API_ERROR_EMIT(api
, err
,
984 g_object_unref(thft
);
988 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO
, bytes
, "Writing connect");
989 fb_mqtt_connect(mqtt
, flags
, cytes
);
991 g_byte_array_free(cytes
, TRUE
);
992 g_object_unref(thft
);
996 fb_api_connect_queue(FbApi
*api
)
999 FbApiPrivate
*priv
= api
->priv
;
1003 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
1004 fb_json_bldr_add_int(bldr
, "delta_batch_size", 125);
1005 fb_json_bldr_add_int(bldr
, "max_deltas_able_to_process", 1250);
1006 fb_json_bldr_add_int(bldr
, "sync_api_version", 3);
1007 fb_json_bldr_add_str(bldr
, "encoding", "JSON");
1009 if (priv
->stoken
== NULL
) {
1010 fb_json_bldr_add_int(bldr
, "initial_titan_sequence_id",
1012 fb_json_bldr_add_str(bldr
, "device_id", priv
->did
);
1013 fb_json_bldr_add_int(bldr
, "entity_fbid", priv
->uid
);
1015 fb_json_bldr_obj_begin(bldr
, "queue_params");
1016 fb_json_bldr_add_str(bldr
, "buzz_on_deltas_enabled", "false");
1018 fb_json_bldr_obj_begin(bldr
, "graphql_query_hashes");
1019 fb_json_bldr_add_str(bldr
, "xma_query_id",
1020 G_STRINGIFY(FB_API_QUERY_XMA
));
1021 fb_json_bldr_obj_end(bldr
);
1023 fb_json_bldr_obj_begin(bldr
, "graphql_query_params");
1024 fb_json_bldr_obj_begin(bldr
, G_STRINGIFY(FB_API_QUERY_XMA
));
1025 fb_json_bldr_add_str(bldr
, "xma_id", "<ID>");
1026 fb_json_bldr_obj_end(bldr
);
1027 fb_json_bldr_obj_end(bldr
);
1028 fb_json_bldr_obj_end(bldr
);
1030 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
1031 fb_api_publish(api
, "/messenger_sync_create_queue", "%s",
1037 fb_json_bldr_add_int(bldr
, "last_seq_id", priv
->sid
);
1038 fb_json_bldr_add_str(bldr
, "sync_token", priv
->stoken
);
1040 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
1041 fb_api_publish(api
, "/messenger_sync_get_diffs", "%s", json
);
1042 g_signal_emit_by_name(api
, "connect");
1045 if (!g_queue_is_empty(priv
->msgs
)) {
1046 msg
= g_queue_peek_head(priv
->msgs
);
1047 fb_api_message_send(api
, msg
);
1050 if (priv
->retrying
) {
1051 priv
->retrying
= FALSE
;
1052 fb_util_debug_info("Reconnected the MQTT stream");
1057 fb_api_cb_seqid(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
1062 FbApiPrivate
*priv
= api
->priv
;
1063 FbJsonValues
*values
;
1067 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
1071 values
= fb_json_values_new(root
);
1072 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1073 "$.viewer.message_threads.sync_sequence_id");
1074 fb_json_values_add(values
, FB_JSON_TYPE_INT
, TRUE
,
1075 "$.viewer.message_threads.unread_count");
1076 fb_json_values_update(values
, &err
);
1078 FB_API_ERROR_EMIT(api
, err
,
1079 g_object_unref(values
);
1080 json_node_free(root
);
1084 str
= fb_json_values_next_str(values
, "0");
1085 priv
->sid
= g_ascii_strtoll(str
, NULL
, 10);
1086 priv
->unread
= fb_json_values_next_int(values
, 0);
1088 if (priv
->sid
== 0) {
1089 fb_api_error(api
, FB_API_ERROR_GENERAL
,
1090 _("Failed to get sync_sequence_id"));
1092 fb_api_connect_queue(api
);
1095 g_object_unref(values
);
1096 json_node_free(root
);
1100 fb_api_cb_mqtt_connect(FbMqtt
*mqtt
, gpointer data
)
1103 FbApiPrivate
*priv
= api
->priv
;
1107 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
1108 fb_json_bldr_add_bool(bldr
, "foreground", TRUE
);
1109 fb_json_bldr_add_int(bldr
, "keepalive_timeout", FB_MQTT_KA
);
1111 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
1112 fb_api_publish(api
, "/foreground_state", "%s", json
);
1115 fb_mqtt_subscribe(mqtt
,
1118 "/messaging_events", 0,
1119 "/orca_presence", 0,
1120 "/orca_typing_notifications", 0,
1126 "/webrtc_response", 0,
1130 /* Notifications seem to lead to some sort of sending rate limit */
1131 fb_mqtt_unsubscribe(mqtt
, "/orca_message_notifications", NULL
);
1133 if (priv
->sid
== 0) {
1134 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
1135 fb_json_bldr_add_str(bldr
, "1", "0");
1136 fb_api_http_query(api
, FB_API_QUERY_SEQ_ID
, bldr
,
1139 fb_api_connect_queue(api
);
1144 fb_api_cb_publish_mark(FbApi
*api
, GByteArray
*pload
)
1146 FbJsonValues
*values
;
1150 if (!fb_api_json_chk(api
, pload
->data
, pload
->len
, &root
)) {
1154 values
= fb_json_values_new(root
);
1155 fb_json_values_add(values
, FB_JSON_TYPE_BOOL
, FALSE
, "$.succeeded");
1156 fb_json_values_update(values
, &err
);
1158 FB_API_ERROR_EMIT(api
, err
,
1159 g_object_unref(values
);
1160 json_node_free(root
);
1164 if (!fb_json_values_next_bool(values
, TRUE
)) {
1165 fb_api_error(api
, FB_API_ERROR_GENERAL
,
1166 _("Failed to mark thread as read"));
1169 g_object_unref(values
);
1170 json_node_free(root
);
1174 fb_api_event_parse(FbApi
*api
, FbApiEvent
*event
, GSList
*events
,
1175 JsonNode
*root
, GError
**error
)
1179 FbJsonValues
*values
;
1183 static const struct {
1184 FbApiEventType type
;
1188 FB_API_EVENT_TYPE_THREAD_USER_ADDED
,
1189 "$.log_message_data.added_participants"
1191 FB_API_EVENT_TYPE_THREAD_USER_REMOVED
,
1192 "$.log_message_data.removed_participants"
1196 values
= fb_json_values_new(root
);
1197 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1198 "$.log_message_type");
1199 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.author");
1200 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1201 "$.log_message_data.name");
1202 fb_json_values_update(values
, &err
);
1204 if (G_UNLIKELY(err
!= NULL
)) {
1205 g_propagate_error(error
, err
);
1206 g_object_unref(values
);
1210 str
= fb_json_values_next_str(values
, NULL
);
1212 if (g_strcmp0(str
, "log:thread-name") == 0) {
1213 str
= fb_json_values_next_str(values
, "");
1214 str
= strrchr(str
, ':');
1217 devent
= fb_api_event_dup(event
, FALSE
);
1218 devent
->type
= FB_API_EVENT_TYPE_THREAD_TOPIC
;
1219 devent
->uid
= FB_ID_FROM_STR(str
+ 1);
1220 devent
->text
= fb_json_values_next_str_dup(values
, NULL
);
1221 events
= g_slist_prepend(events
, devent
);
1225 g_object_unref(values
);
1227 for (i
= 0; i
< G_N_ELEMENTS(evtypes
); i
++) {
1228 values
= fb_json_values_new(root
);
1229 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$");
1230 fb_json_values_set_array(values
, FALSE
, evtypes
[i
].expr
);
1232 while (fb_json_values_update(values
, &err
)) {
1233 str
= fb_json_values_next_str(values
, "");
1234 str
= strrchr(str
, ':');
1237 devent
= fb_api_event_dup(event
, FALSE
);
1238 devent
->type
= evtypes
[i
].type
;
1239 devent
->uid
= FB_ID_FROM_STR(str
+ 1);
1240 events
= g_slist_prepend(events
, devent
);
1244 g_object_unref(values
);
1246 if (G_UNLIKELY(err
!= NULL
)) {
1247 g_propagate_error(error
, err
);
1256 fb_api_cb_publish_mercury(FbApi
*api
, GByteArray
*pload
)
1260 FbJsonValues
*values
;
1262 GSList
*events
= NULL
;
1266 if (!fb_api_json_chk(api
, pload
->data
, pload
->len
, &root
)) {
1270 values
= fb_json_values_new(root
);
1271 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.thread_fbid");
1272 fb_json_values_set_array(values
, FALSE
, "$.actions");
1274 while (fb_json_values_update(values
, &err
)) {
1275 fb_api_event_reset(&event
, FALSE
);
1276 str
= fb_json_values_next_str(values
, "0");
1277 event
.tid
= FB_ID_FROM_STR(str
);
1279 node
= fb_json_values_get_root(values
);
1280 events
= fb_api_event_parse(api
, &event
, events
, node
, &err
);
1283 if (G_LIKELY(err
== NULL
)) {
1284 events
= g_slist_reverse(events
);
1285 g_signal_emit_by_name(api
, "events", events
);
1287 fb_api_error_emit(api
, err
);
1290 g_slist_free_full(events
, (GDestroyNotify
) fb_api_event_free
);
1291 g_object_unref(values
);
1292 json_node_free(root
);
1297 fb_api_cb_publish_typing(FbApi
*api
, GByteArray
*pload
)
1300 FbApiPrivate
*priv
= api
->priv
;
1302 FbJsonValues
*values
;
1306 if (!fb_api_json_chk(api
, pload
->data
, pload
->len
, &root
)) {
1310 values
= fb_json_values_new(root
);
1311 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.type");
1312 fb_json_values_add(values
, FB_JSON_TYPE_INT
, TRUE
, "$.sender_fbid");
1313 fb_json_values_add(values
, FB_JSON_TYPE_INT
, TRUE
, "$.state");
1314 fb_json_values_update(values
, &err
);
1316 FB_API_ERROR_EMIT(api
, err
,
1317 g_object_unref(values
);
1318 json_node_free(root
);
1322 str
= fb_json_values_next_str(values
, NULL
);
1324 if (g_ascii_strcasecmp(str
, "typ") == 0) {
1325 typg
.uid
= fb_json_values_next_int(values
, 0);
1327 if (typg
.uid
!= priv
->uid
) {
1328 typg
.state
= fb_json_values_next_int(values
, 0);
1329 g_signal_emit_by_name(api
, "typing", &typg
);
1333 g_object_unref(values
);
1334 json_node_free(root
);
1338 fb_api_cb_publish_ms_r(FbApi
*api
, GByteArray
*pload
)
1341 FbApiPrivate
*priv
= api
->priv
;
1342 FbJsonValues
*values
;
1346 if (!fb_api_json_chk(api
, pload
->data
, pload
->len
, &root
)) {
1350 values
= fb_json_values_new(root
);
1351 fb_json_values_add(values
, FB_JSON_TYPE_BOOL
, TRUE
, "$.succeeded");
1352 fb_json_values_update(values
, &err
);
1354 FB_API_ERROR_EMIT(api
, err
,
1355 g_object_unref(values
);
1356 json_node_free(root
);
1360 if (fb_json_values_next_bool(values
, TRUE
)) {
1361 /* Pop and free the successful message */
1362 msg
= g_queue_pop_head(priv
->msgs
);
1363 fb_api_message_free(msg
);
1365 if (!g_queue_is_empty(priv
->msgs
)) {
1366 msg
= g_queue_peek_head(priv
->msgs
);
1367 fb_api_message_send(api
, msg
);
1370 fb_api_error(api
, FB_API_ERROR_GENERAL
,
1371 "Failed to send message");
1374 g_object_unref(values
);
1375 json_node_free(root
);
1379 fb_api_xma_parse(FbApi
*api
, const gchar
*body
, JsonNode
*root
, GError
**error
)
1383 FbHttpParams
*params
;
1384 FbJsonValues
*values
;
1388 values
= fb_json_values_new(root
);
1389 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1390 "$.story_attachment.target.__type__.name");
1391 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1392 "$.story_attachment.url");
1393 fb_json_values_update(values
, &err
);
1395 if (G_UNLIKELY(err
!= NULL
)) {
1396 g_propagate_error(error
, err
);
1397 g_object_unref(values
);
1401 str
= fb_json_values_next_str(values
, NULL
);
1402 url
= fb_json_values_next_str(values
, NULL
);
1404 if ((str
== NULL
) || (url
== NULL
)) {
1405 text
= g_strdup(_("<Unsupported Attachment>"));
1406 g_object_unref(values
);
1410 if (purple_strequal(str
, "ExternalUrl")) {
1411 params
= fb_http_params_new_parse(url
, TRUE
);
1412 if (g_str_has_prefix(url
, FB_API_FBRPC_PREFIX
)) {
1413 text
= fb_http_params_dup_str(params
, "target_url", NULL
);
1415 text
= fb_http_params_dup_str(params
, "u", NULL
);
1417 fb_http_params_free(params
);
1419 text
= g_strdup(url
);
1422 if (fb_http_urlcmp(body
, text
, FALSE
)) {
1424 g_object_unref(values
);
1428 g_object_unref(values
);
1433 fb_api_message_parse_attach(FbApi
*api
, const gchar
*mid
, FbApiMessage
*msg
,
1434 GSList
*msgs
, const gchar
*body
, JsonNode
*root
,
1440 FbJsonValues
*values
;
1446 values
= fb_json_values_new(root
);
1447 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.xmaGraphQL");
1448 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
, "$.fbid");
1449 fb_json_values_set_array(values
, FALSE
, "$.attachments");
1451 while (fb_json_values_update(values
, &err
)) {
1452 str
= fb_json_values_next_str(values
, NULL
);
1455 id
= fb_json_values_next_int(values
, 0);
1456 dmsg
= fb_api_message_dup(msg
, FALSE
);
1457 fb_api_attach(api
, id
, mid
, dmsg
);
1461 node
= fb_json_node_new(str
, -1, &err
);
1463 if (G_UNLIKELY(err
!= NULL
)) {
1467 xode
= fb_json_node_get_nth(node
, 0);
1468 xma
= fb_api_xma_parse(api
, body
, xode
, &err
);
1471 dmsg
= fb_api_message_dup(msg
, FALSE
);
1473 msgs
= g_slist_prepend(msgs
, dmsg
);
1476 json_node_free(node
);
1478 if (G_UNLIKELY(err
!= NULL
)) {
1483 if (G_UNLIKELY(err
!= NULL
)) {
1484 g_propagate_error(error
, err
);
1487 g_object_unref(values
);
1493 fb_api_cb_publish_ms_new_message(FbApi
*api
, JsonNode
*root
, GSList
*msgs
, GError
**error
);
1496 fb_api_cb_publish_ms_event(FbApi
*api
, JsonNode
*root
, GSList
*events
, FbApiEventType type
, GError
**error
);
1499 fb_api_cb_publish_ms(FbApi
*api
, GByteArray
*pload
)
1502 FbApiPrivate
*priv
= api
->priv
;
1503 FbJsonValues
*values
;
1508 GSList
*msgs
= NULL
;
1509 GSList
*events
= NULL
;
1515 static const struct {
1516 const gchar
*member
;
1517 FbApiEventType type
;
1518 gboolean is_message
;
1520 {"deltaNewMessage", 0, 1},
1521 {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC
, 0},
1522 {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED
, 0},
1523 {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED
, 0},
1526 /* Read identifier string (for Facebook employees) */
1527 thft
= fb_thrift_new(pload
, 0);
1528 fb_thrift_read_str(thft
, NULL
);
1529 size
= fb_thrift_get_pos(thft
);
1530 g_object_unref(thft
);
1532 g_return_if_fail(size
< pload
->len
);
1533 data
= (gchar
*) pload
->data
+ size
;
1534 size
= pload
->len
- size
;
1536 if (!fb_api_json_chk(api
, data
, size
, &root
)) {
1540 values
= fb_json_values_new(root
);
1541 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1542 "$.lastIssuedSeqId");
1543 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.syncToken");
1544 fb_json_values_update(values
, &err
);
1546 FB_API_ERROR_EMIT(api
, err
,
1547 g_object_unref(values
);
1548 json_node_free(root
);
1552 priv
->sid
= fb_json_values_next_int(values
, 0);
1553 stoken
= fb_json_values_next_str_dup(values
, NULL
);
1554 g_object_unref(values
);
1556 if (G_UNLIKELY(stoken
!= NULL
)) {
1557 g_free(priv
->stoken
);
1558 priv
->stoken
= stoken
;
1559 g_signal_emit_by_name(api
, "connect");
1560 json_node_free(root
);
1564 arr
= fb_json_node_get_arr(root
, "$.deltas", NULL
);
1565 elms
= json_array_get_elements(arr
);
1567 for (l
= elms
; l
!= NULL
; l
= l
->next
) {
1569 JsonObject
*o
= json_node_get_object(l
->data
);
1571 for (i
= 0; i
< G_N_ELEMENTS(event_types
); i
++) {
1572 if ((node
= json_object_get_member(o
, event_types
[i
].member
))) {
1573 if (event_types
[i
].is_message
) {
1574 msgs
= fb_api_cb_publish_ms_new_message(
1575 api
, node
, msgs
, &err
1578 events
= fb_api_cb_publish_ms_event(
1579 api
, node
, events
, event_types
[i
].type
, &err
1585 if (G_UNLIKELY(err
!= NULL
)) {
1591 json_array_unref(arr
);
1593 if (G_LIKELY(err
== NULL
)) {
1595 msgs
= g_slist_reverse(msgs
);
1596 g_signal_emit_by_name(api
, "messages", msgs
);
1600 events
= g_slist_reverse(events
);
1601 g_signal_emit_by_name(api
, "events", events
);
1604 fb_api_error_emit(api
, err
);
1607 g_slist_free_full(msgs
, (GDestroyNotify
) fb_api_message_free
);
1608 g_slist_free_full(events
, (GDestroyNotify
) fb_api_event_free
);
1609 json_node_free(root
);
1613 fb_api_cb_publish_ms_new_message(FbApi
*api
, JsonNode
*root
, GSList
*msgs
, GError
**error
)
1618 FbApiPrivate
*priv
= api
->priv
;
1623 FbJsonValues
*values
;
1626 values
= fb_json_values_new(root
);
1627 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1628 "$.messageMetadata.offlineThreadingId");
1629 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1630 "$.messageMetadata.actorFbId");
1631 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1633 ".threadKey.otherUserFbId");
1634 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1636 ".threadKey.threadFbId");
1637 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1638 "$.messageMetadata.timestamp");
1639 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1641 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1643 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1644 "$.messageMetadata.messageId");
1646 if (fb_json_values_update(values
, &err
)) {
1647 id
= fb_json_values_next_int(values
, 0);
1649 /* Ignore everything but new messages */
1654 /* Ignore sequential duplicates */
1655 if (id
== priv
->lastmid
) {
1656 fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT
, id
);
1661 fb_api_message_reset(&msg
, FALSE
);
1662 msg
.uid
= fb_json_values_next_int(values
, 0);
1663 oid
= fb_json_values_next_int(values
, 0);
1664 msg
.tid
= fb_json_values_next_int(values
, 0);
1665 msg
.tstamp
= fb_json_values_next_int(values
, 0);
1667 if (msg
.uid
== priv
->uid
) {
1668 msg
.flags
|= FB_API_MESSAGE_FLAG_SELF
;
1675 body
= fb_json_values_next_str(values
, NULL
);
1678 dmsg
= fb_api_message_dup(&msg
, FALSE
);
1679 dmsg
->text
= g_strdup(body
);
1680 msgs
= g_slist_prepend(msgs
, dmsg
);
1683 id
= fb_json_values_next_int(values
, 0);
1686 dmsg
= fb_api_message_dup(&msg
, FALSE
);
1687 fb_api_sticker(api
, id
, dmsg
);
1690 str
= fb_json_values_next_str(values
, NULL
);
1696 node
= fb_json_values_get_root(values
);
1697 msgs
= fb_api_message_parse_attach(api
, str
, &msg
, msgs
, body
,
1700 if (G_UNLIKELY(err
!= NULL
)) {
1701 g_propagate_error(error
, err
);
1707 g_object_unref(values
);
1712 fb_api_cb_publish_ms_event(FbApi
*api
, JsonNode
*root
, GSList
*events
, FbApiEventType type
, GError
**error
)
1715 FbJsonValues
*values
= NULL
;
1716 FbJsonValues
*values_inner
= NULL
;
1719 values
= fb_json_values_new(root
);
1720 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1721 "$.messageMetadata.threadKey.threadFbId");
1722 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1723 "$.messageMetadata.actorFbId");
1726 case FB_API_EVENT_TYPE_THREAD_TOPIC
:
1727 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1731 case FB_API_EVENT_TYPE_THREAD_USER_ADDED
:
1732 values_inner
= fb_json_values_new(root
);
1734 fb_json_values_add(values_inner
, FB_JSON_TYPE_INT
, FALSE
,
1737 /* use the text field for the full name */
1738 fb_json_values_add(values_inner
, FB_JSON_TYPE_STR
, FALSE
,
1741 fb_json_values_set_array(values_inner
, FALSE
,
1742 "$.addedParticipants");
1745 case FB_API_EVENT_TYPE_THREAD_USER_REMOVED
:
1746 fb_json_values_add(values
, FB_JSON_TYPE_INT
, FALSE
,
1747 "$.leftParticipantFbId");
1749 /* use the text field for the kick message */
1750 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
1751 "$.messageMetadata.adminText");
1755 fb_json_values_update(values
, &err
);
1757 event
= fb_api_event_dup(NULL
, FALSE
);
1759 event
->tid
= fb_json_values_next_int(values
, 0);
1760 event
->uid
= fb_json_values_next_int(values
, 0);
1762 if (type
== FB_API_EVENT_TYPE_THREAD_TOPIC
) {
1763 event
->text
= fb_json_values_next_str_dup(values
, NULL
);
1764 } else if (type
== FB_API_EVENT_TYPE_THREAD_USER_REMOVED
) {
1765 /* overwrite actor with subject */
1766 event
->uid
= fb_json_values_next_int(values
, 0);
1767 event
->text
= fb_json_values_next_str_dup(values
, NULL
);
1768 } else if (type
== FB_API_EVENT_TYPE_THREAD_USER_ADDED
) {
1770 while (fb_json_values_update(values_inner
, &err
)) {
1771 FbApiEvent
*devent
= fb_api_event_dup(event
, FALSE
);
1773 devent
->uid
= fb_json_values_next_int(values_inner
, 0);
1774 devent
->text
= fb_json_values_next_str_dup(values_inner
, NULL
);
1776 events
= g_slist_prepend(events
, devent
);
1778 fb_api_event_free(event
);
1780 g_object_unref(values_inner
);
1783 g_object_unref(values
);
1785 if (G_UNLIKELY(err
!= NULL
)) {
1786 g_propagate_error(error
, err
);
1788 events
= g_slist_prepend(events
, event
);
1795 fb_api_cb_publish_pt(FbThrift
*thft
, GSList
**press
, GError
**error
)
1797 FbApiPresence
*pres
;
1805 /* Read identifier string (for Facebook employees) */
1806 FB_API_TCHK(fb_thrift_read_str(thft
, NULL
));
1808 /* Read the full list boolean field */
1809 FB_API_TCHK(fb_thrift_read_field(thft
, &type
, &id
, 0));
1810 FB_API_TCHK(type
== FB_THRIFT_TYPE_BOOL
);
1811 FB_API_TCHK(id
== 1);
1812 FB_API_TCHK(fb_thrift_read_bool(thft
, NULL
));
1814 /* Read the list field */
1815 FB_API_TCHK(fb_thrift_read_field(thft
, &type
, &id
, id
));
1816 FB_API_TCHK(type
== FB_THRIFT_TYPE_LIST
);
1817 FB_API_TCHK(id
== 2);
1820 FB_API_TCHK(fb_thrift_read_list(thft
, &type
, &size
));
1821 FB_API_TCHK(type
== FB_THRIFT_TYPE_STRUCT
);
1823 for (i
= 0; i
< size
; i
++) {
1824 /* Read the user identifier field */
1825 FB_API_TCHK(fb_thrift_read_field(thft
, &type
, &id
, 0));
1826 FB_API_TCHK(type
== FB_THRIFT_TYPE_I64
);
1827 FB_API_TCHK(id
== 1);
1828 FB_API_TCHK(fb_thrift_read_i64(thft
, &i64
));
1830 /* Read the active field */
1831 FB_API_TCHK(fb_thrift_read_field(thft
, &type
, &id
, id
));
1832 FB_API_TCHK(type
== FB_THRIFT_TYPE_I32
);
1833 FB_API_TCHK(id
== 2);
1834 FB_API_TCHK(fb_thrift_read_i32(thft
, &i32
));
1836 pres
= fb_api_presence_dup(NULL
);
1838 pres
->active
= i32
!= 0;
1839 *press
= g_slist_prepend(*press
, pres
);
1841 fb_util_debug_info("Presence: %" FB_ID_FORMAT
" (%d)",
1845 if (fb_thrift_read_isstop(thft
)) {
1849 FB_API_TCHK(fb_thrift_read_field(thft
, &type
, &id
, id
));
1853 /* Read the last active timestamp field */
1854 FB_API_TCHK(type
== FB_THRIFT_TYPE_I64
);
1855 FB_API_TCHK(fb_thrift_read_i64(thft
, NULL
));
1859 /* Read the active client bits field */
1860 FB_API_TCHK(type
== FB_THRIFT_TYPE_I16
);
1861 FB_API_TCHK(fb_thrift_read_i16(thft
, NULL
));
1865 /* Read the VoIP compatibility bits field */
1866 FB_API_TCHK(type
== FB_THRIFT_TYPE_I64
);
1867 FB_API_TCHK(fb_thrift_read_i64(thft
, NULL
));
1871 /* Unknown new field */
1872 FB_API_TCHK(type
== FB_THRIFT_TYPE_I64
);
1873 FB_API_TCHK(fb_thrift_read_i64(thft
, NULL
));
1877 /* Try to read unknown fields as varint */
1878 FB_API_TCHK(type
== FB_THRIFT_TYPE_I16
||
1879 type
== FB_THRIFT_TYPE_I32
||
1880 type
== FB_THRIFT_TYPE_I64
);
1881 FB_API_TCHK(fb_thrift_read_i64(thft
, NULL
));
1886 /* Read the field stop */
1887 FB_API_TCHK(fb_thrift_read_stop(thft
));
1890 /* Read the field stop */
1891 FB_API_TCHK(fb_thrift_read_stop(thft
));
1895 fb_api_cb_publish_p(FbApi
*api
, GByteArray
*pload
)
1899 GSList
*press
= NULL
;
1901 thft
= fb_thrift_new(pload
, 0);
1902 fb_api_cb_publish_pt(thft
, &press
, &err
);
1903 g_object_unref(thft
);
1905 if (G_LIKELY(err
== NULL
)) {
1906 g_signal_emit_by_name(api
, "presences", press
);
1908 fb_api_error_emit(api
, err
);
1911 g_slist_free_full(press
, (GDestroyNotify
) fb_api_presence_free
);
1915 fb_api_cb_mqtt_publish(FbMqtt
*mqtt
, const gchar
*topic
, GByteArray
*pload
,
1924 static const struct {
1926 void (*func
) (FbApi
*api
, GByteArray
*pload
);
1928 {"/mark_thread_response", fb_api_cb_publish_mark
},
1929 {"/mercury", fb_api_cb_publish_mercury
},
1930 {"/orca_typing_notifications", fb_api_cb_publish_typing
},
1931 {"/send_message_response", fb_api_cb_publish_ms_r
},
1932 {"/t_ms", fb_api_cb_publish_ms
},
1933 {"/t_p", fb_api_cb_publish_p
}
1936 comp
= fb_util_zlib_test(pload
);
1938 if (G_LIKELY(comp
)) {
1939 bytes
= fb_util_zlib_inflate(pload
, &err
);
1940 FB_API_ERROR_EMIT(api
, err
, return);
1942 bytes
= (GByteArray
*) pload
;
1945 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO
, bytes
,
1946 "Reading message (topic: %s)",
1949 for (i
= 0; i
< G_N_ELEMENTS(parsers
); i
++) {
1950 if (g_ascii_strcasecmp(topic
, parsers
[i
].topic
) == 0) {
1951 parsers
[i
].func(api
, bytes
);
1956 if (G_LIKELY(comp
)) {
1957 g_byte_array_free(bytes
, TRUE
);
1962 fb_api_new(PurpleConnection
*gc
)
1967 api
= g_object_new(FB_TYPE_API
, NULL
);
1971 priv
->mqtt
= fb_mqtt_new(gc
);
1973 g_signal_connect(priv
->mqtt
,
1975 G_CALLBACK(fb_api_cb_mqtt_connect
),
1977 g_signal_connect(priv
->mqtt
,
1979 G_CALLBACK(fb_api_cb_mqtt_error
),
1981 g_signal_connect(priv
->mqtt
,
1983 G_CALLBACK(fb_api_cb_mqtt_open
),
1985 g_signal_connect(priv
->mqtt
,
1987 G_CALLBACK(fb_api_cb_mqtt_publish
),
1994 fb_api_rehash(FbApi
*api
)
1998 g_return_if_fail(FB_IS_API(api
));
2001 if (priv
->cid
== NULL
) {
2002 priv
->cid
= fb_util_rand_alnum(32);
2005 if (priv
->did
== NULL
) {
2006 priv
->did
= purple_uuid_random();
2009 if (priv
->mid
== 0) {
2010 priv
->mid
= g_random_int();
2013 if (strlen(priv
->cid
) > 20) {
2014 priv
->cid
= g_realloc_n(priv
->cid
, 21, sizeof *priv
->cid
);
2020 fb_api_is_invisible(FbApi
*api
)
2024 g_return_val_if_fail(FB_IS_API(api
), FALSE
);
2027 return priv
->invisible
;
2031 fb_api_error(FbApi
*api
, FbApiError error
, const gchar
*format
, ...)
2036 g_return_if_fail(FB_IS_API(api
));
2038 va_start(ap
, format
);
2039 err
= g_error_new_valist(FB_API_ERROR
, error
, format
, ap
);
2042 fb_api_error_emit(api
, err
);
2046 fb_api_error_emit(FbApi
*api
, GError
*error
)
2048 g_return_if_fail(FB_IS_API(api
));
2049 g_return_if_fail(error
!= NULL
);
2051 g_signal_emit_by_name(api
, "error", error
);
2052 g_error_free(error
);
2056 fb_api_cb_attach(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2062 FbJsonValues
*values
;
2065 GSList
*msgs
= NULL
;
2069 static const gchar
*imgexts
[] = {".jpg", ".png", ".gif"};
2071 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2075 values
= fb_json_values_new(root
);
2076 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.filename");
2077 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.redirect_uri");
2078 fb_json_values_update(values
, &err
);
2080 FB_API_ERROR_EMIT(api
, err
,
2081 g_object_unref(values
);
2082 json_node_free(root
);
2086 msg
= fb_api_data_take(api
, con
);
2087 str
= fb_json_values_next_str(values
, NULL
);
2088 name
= g_ascii_strdown(str
, -1);
2090 for (i
= 0; i
< G_N_ELEMENTS(imgexts
); i
++) {
2091 if (g_str_has_suffix(name
, imgexts
[i
])) {
2092 msg
->flags
|= FB_API_MESSAGE_FLAG_IMAGE
;
2098 msg
->text
= fb_json_values_next_str_dup(values
, NULL
);
2099 msgs
= g_slist_prepend(msgs
, msg
);
2101 g_signal_emit_by_name(api
, "messages", msgs
);
2102 g_slist_free_full(msgs
, (GDestroyNotify
) fb_api_message_free
);
2103 g_object_unref(values
);
2104 json_node_free(root
);
2109 fb_api_attach(FbApi
*api
, FbId aid
, const gchar
*msgid
, FbApiMessage
*msg
)
2112 PurpleHttpConnection
*http
;
2114 prms
= fb_http_params_new();
2115 fb_http_params_set_str(prms
, "mid", msgid
);
2116 fb_http_params_set_strf(prms
, "aid", "%" FB_ID_FORMAT
, aid
);
2118 http
= fb_api_http_req(api
, FB_API_URL_ATTACH
, "getAttachment",
2119 "messaging.getAttachment", prms
,
2121 fb_api_data_set(api
, http
, msg
, (GDestroyNotify
) fb_api_message_free
);
2125 fb_api_cb_auth(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2129 FbApiPrivate
*priv
= api
->priv
;
2130 FbJsonValues
*values
;
2134 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2138 values
= fb_json_values_new(root
);
2139 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.access_token");
2140 fb_json_values_add(values
, FB_JSON_TYPE_INT
, TRUE
, "$.uid");
2141 fb_json_values_update(values
, &err
);
2143 FB_API_ERROR_EMIT(api
, err
,
2144 g_object_unref(values
);
2145 json_node_free(root
);
2149 g_free(priv
->token
);
2150 priv
->token
= fb_json_values_next_str_dup(values
, NULL
);
2151 priv
->uid
= fb_json_values_next_int(values
, 0);
2153 g_signal_emit_by_name(api
, "auth");
2154 g_object_unref(values
);
2155 json_node_free(root
);
2159 fb_api_auth(FbApi
*api
, const gchar
*user
, const gchar
*pass
)
2163 prms
= fb_http_params_new();
2164 fb_http_params_set_str(prms
, "email", user
);
2165 fb_http_params_set_str(prms
, "password", pass
);
2166 fb_api_http_req(api
, FB_API_URL_AUTH
, "authenticate", "auth.login",
2167 prms
, fb_api_cb_auth
);
2171 fb_api_user_icon_checksum(gchar
*icon
)
2176 if (G_UNLIKELY(icon
== NULL
)) {
2180 prms
= fb_http_params_new_parse(icon
, TRUE
);
2181 csum
= fb_http_params_dup_str(prms
, "oh", NULL
);
2182 fb_http_params_free(prms
);
2184 if (G_UNLIKELY(csum
== NULL
)) {
2185 /* Revert to the icon URL as the unique checksum */
2186 csum
= g_strdup(icon
);
2193 fb_api_cb_contact(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2199 FbJsonValues
*values
;
2204 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2208 node
= fb_json_node_get_nth(root
, 0);
2211 fb_api_error(api
, FB_API_ERROR_GENERAL
,
2212 _("Failed to obtain contact information"));
2213 json_node_free(root
);
2217 values
= fb_json_values_new(node
);
2218 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.id");
2219 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.name");
2220 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2221 "$.profile_pic_large.uri");
2222 fb_json_values_update(values
, &err
);
2224 FB_API_ERROR_EMIT(api
, err
,
2225 g_object_unref(values
);
2226 json_node_free(root
);
2230 fb_api_user_reset(&user
, FALSE
);
2231 str
= fb_json_values_next_str(values
, "0");
2232 user
.uid
= FB_ID_FROM_STR(str
);
2233 user
.name
= fb_json_values_next_str_dup(values
, NULL
);
2234 user
.icon
= fb_json_values_next_str_dup(values
, NULL
);
2236 user
.csum
= fb_api_user_icon_checksum(user
.icon
);
2238 g_signal_emit_by_name(api
, "contact", &user
);
2239 fb_api_user_reset(&user
, TRUE
);
2240 g_object_unref(values
);
2241 json_node_free(root
);
2245 fb_api_contact(FbApi
*api
, FbId uid
)
2249 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2250 fb_json_bldr_arr_begin(bldr
, "0");
2251 fb_json_bldr_add_strf(bldr
, NULL
, "%" FB_ID_FORMAT
, uid
);
2252 fb_json_bldr_arr_end(bldr
);
2254 fb_json_bldr_add_str(bldr
, "1", "true");
2255 fb_api_http_query(api
, FB_API_QUERY_CONTACT
, bldr
, fb_api_cb_contact
);
2259 fb_api_cb_contacts_nodes(FbApi
*api
, JsonNode
*root
, GSList
*users
)
2262 FbApiPrivate
*priv
= api
->priv
;
2265 FbJsonValues
*values
;
2269 values
= fb_json_values_new(root
);
2270 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2271 "$.represented_profile.id");
2272 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2273 "$.represented_profile.friendship_status");
2274 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2275 "$.structured_name.text");
2276 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2277 "$.hugePictureUrl.uri");
2279 is_array
= (JSON_NODE_TYPE(root
) == JSON_NODE_ARRAY
);
2282 fb_json_values_set_array(values
, FALSE
, "$");
2285 while (fb_json_values_update(values
, &err
)) {
2286 str
= fb_json_values_next_str(values
, "0");
2287 uid
= FB_ID_FROM_STR(str
);
2288 str
= fb_json_values_next_str(values
, NULL
);
2290 if ((!purple_strequal(str
, "ARE_FRIENDS") &&
2291 (uid
!= priv
->uid
)) || (uid
== 0))
2299 user
= fb_api_user_dup(NULL
, FALSE
);
2301 user
->name
= fb_json_values_next_str_dup(values
, NULL
);
2302 user
->icon
= fb_json_values_next_str_dup(values
, NULL
);
2304 user
->csum
= fb_api_user_icon_checksum(user
->icon
);
2306 users
= g_slist_prepend(users
, user
);
2313 g_object_unref(values
);
2318 /* base64(contact:<our id>:<their id>:<whatever>) */
2320 fb_api_cb_contacts_parse_removed(FbApi
*api
, JsonNode
*node
, GSList
*users
)
2324 char *decoded
= (char *) g_base64_decode(json_node_get_string(node
), &len
);
2326 g_return_val_if_fail(decoded
[len
] == '\0', users
);
2327 g_return_val_if_fail(len
== strlen(decoded
), users
);
2328 g_return_val_if_fail(g_str_has_prefix(decoded
, "contact:"), users
);
2330 split
= g_strsplit_set(decoded
, ":", 4);
2332 g_return_val_if_fail(g_strv_length(split
) == 4, users
);
2334 users
= g_slist_prepend(users
, g_strdup(split
[2]));
2343 fb_api_cb_contacts(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2346 const gchar
*cursor
;
2347 const gchar
*delta_cursor
;
2349 FbApiPrivate
*priv
= api
->priv
;
2350 FbJsonValues
*values
;
2355 GSList
*users
= NULL
;
2360 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2364 croot
= fb_json_node_get(root
, "$.viewer.messenger_contacts.deltas", NULL
);
2365 is_delta
= (croot
!= NULL
);
2368 croot
= fb_json_node_get(root
, "$.viewer.messenger_contacts", NULL
);
2369 node
= fb_json_node_get(croot
, "$.nodes", NULL
);
2370 users
= fb_api_cb_contacts_nodes(api
, node
, users
);
2371 json_node_free(node
);
2374 GSList
*added
= NULL
;
2375 GSList
*removed
= NULL
;
2376 JsonArray
*arr
= fb_json_node_get_arr(croot
, "$.nodes", NULL
);
2377 GList
*elms
= json_array_get_elements(arr
);
2379 for (l
= elms
; l
!= NULL
; l
= l
->next
) {
2380 if ((node
= fb_json_node_get(l
->data
, "$.added", NULL
))) {
2381 added
= fb_api_cb_contacts_nodes(api
, node
, added
);
2382 json_node_free(node
);
2385 if ((node
= fb_json_node_get(l
->data
, "$.removed", NULL
))) {
2386 removed
= fb_api_cb_contacts_parse_removed(api
, node
, removed
);
2387 json_node_free(node
);
2391 g_signal_emit_by_name(api
, "contacts-delta", added
, removed
);
2393 g_slist_free_full(added
, (GDestroyNotify
) fb_api_user_free
);
2394 g_slist_free_full(removed
, (GDestroyNotify
) g_free
);
2397 json_array_unref(arr
);
2400 values
= fb_json_values_new(croot
);
2401 fb_json_values_add(values
, FB_JSON_TYPE_BOOL
, FALSE
,
2402 "$.page_info.has_next_page");
2403 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2404 "$.page_info.delta_cursor");
2405 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2406 "$.page_info.end_cursor");
2407 fb_json_values_update(values
, NULL
);
2409 complete
= !fb_json_values_next_bool(values
, FALSE
);
2411 delta_cursor
= fb_json_values_next_str(values
, NULL
);
2413 cursor
= fb_json_values_next_str(values
, NULL
);
2415 if (G_UNLIKELY(err
== NULL
)) {
2416 if (is_delta
|| complete
) {
2417 g_free(priv
->contacts_delta
);
2418 priv
->contacts_delta
= g_strdup(is_delta
? cursor
: delta_cursor
);
2422 g_signal_emit_by_name(api
, "contacts", users
, complete
);
2426 fb_api_contacts_after(api
, cursor
);
2429 fb_api_error_emit(api
, err
);
2432 g_slist_free_full(users
, (GDestroyNotify
) fb_api_user_free
);
2433 g_object_unref(values
);
2435 json_node_free(croot
);
2436 json_node_free(root
);
2440 fb_api_contacts(FbApi
*api
)
2445 g_return_if_fail(FB_IS_API(api
));
2448 if (priv
->contacts_delta
) {
2449 fb_api_contacts_delta(api
, priv
->contacts_delta
);
2453 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2454 fb_json_bldr_arr_begin(bldr
, "0");
2455 fb_json_bldr_add_str(bldr
, NULL
, "user");
2456 fb_json_bldr_arr_end(bldr
);
2458 fb_json_bldr_add_str(bldr
, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT
));
2459 fb_api_http_query(api
, FB_API_QUERY_CONTACTS
, bldr
,
2460 fb_api_cb_contacts
);
2464 fb_api_contacts_after(FbApi
*api
, const gchar
*cursor
)
2468 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2469 fb_json_bldr_arr_begin(bldr
, "0");
2470 fb_json_bldr_add_str(bldr
, NULL
, "user");
2471 fb_json_bldr_arr_end(bldr
);
2473 fb_json_bldr_add_str(bldr
, "1", cursor
);
2474 fb_json_bldr_add_str(bldr
, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT
));
2475 fb_api_http_query(api
, FB_API_QUERY_CONTACTS_AFTER
, bldr
,
2476 fb_api_cb_contacts
);
2480 fb_api_contacts_delta(FbApi
*api
, const gchar
*delta_cursor
)
2484 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2486 fb_json_bldr_add_str(bldr
, "0", delta_cursor
);
2488 fb_json_bldr_arr_begin(bldr
, "1");
2489 fb_json_bldr_add_str(bldr
, NULL
, "user");
2490 fb_json_bldr_arr_end(bldr
);
2492 fb_json_bldr_add_str(bldr
, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT
));
2493 fb_api_http_query(api
, FB_API_QUERY_CONTACTS_DELTA
, bldr
,
2494 fb_api_cb_contacts
);
2498 fb_api_connect(FbApi
*api
, gboolean invisible
)
2502 g_return_if_fail(FB_IS_API(api
));
2505 priv
->invisible
= invisible
;
2506 fb_mqtt_open(priv
->mqtt
, FB_MQTT_HOST
, FB_MQTT_PORT
);
2510 fb_api_disconnect(FbApi
*api
)
2514 g_return_if_fail(FB_IS_API(api
));
2517 fb_mqtt_disconnect(priv
->mqtt
);
2521 fb_api_message_send(FbApi
*api
, FbApiMessage
*msg
)
2524 FbApiPrivate
*priv
= api
->priv
;
2530 mid
= FB_API_MSGID(g_get_real_time() / 1000, g_random_int());
2531 priv
->lastmid
= mid
;
2533 if (msg
->tid
!= 0) {
2541 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2542 fb_json_bldr_add_str(bldr
, "body", msg
->text
);
2543 fb_json_bldr_add_strf(bldr
, "msgid", "%" FB_ID_FORMAT
, mid
);
2544 fb_json_bldr_add_strf(bldr
, "sender_fbid", "%" FB_ID_FORMAT
, priv
->uid
);
2545 fb_json_bldr_add_strf(bldr
, "to", "%s%" FB_ID_FORMAT
, tpfx
, id
);
2547 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
2548 fb_api_publish(api
, "/send_message2", "%s", json
);
2553 fb_api_message(FbApi
*api
, FbId id
, gboolean thread
, const gchar
*text
)
2559 g_return_if_fail(FB_IS_API(api
));
2560 g_return_if_fail(text
!= NULL
);
2563 msg
= fb_api_message_dup(NULL
, FALSE
);
2564 msg
->text
= g_strdup(text
);
2572 empty
= g_queue_is_empty(priv
->msgs
);
2573 g_queue_push_tail(priv
->msgs
, msg
);
2575 if (empty
&& fb_mqtt_connected(priv
->mqtt
, FALSE
)) {
2576 fb_api_message_send(api
, msg
);
2581 fb_api_publish(FbApi
*api
, const gchar
*topic
, const gchar
*format
, ...)
2590 g_return_if_fail(FB_IS_API(api
));
2591 g_return_if_fail(topic
!= NULL
);
2592 g_return_if_fail(format
!= NULL
);
2595 va_start(ap
, format
);
2596 msg
= g_strdup_vprintf(format
, ap
);
2599 bytes
= g_byte_array_new_take((guint8
*) msg
, strlen(msg
));
2600 cytes
= fb_util_zlib_deflate(bytes
, &err
);
2602 FB_API_ERROR_EMIT(api
, err
,
2603 g_byte_array_free(bytes
, TRUE
);
2607 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO
, bytes
,
2608 "Writing message (topic: %s)",
2611 fb_mqtt_publish(priv
->mqtt
, topic
, cytes
);
2612 g_byte_array_free(cytes
, TRUE
);
2613 g_byte_array_free(bytes
, TRUE
);
2617 fb_api_read(FbApi
*api
, FbId id
, gboolean thread
)
2624 g_return_if_fail(FB_IS_API(api
));
2627 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2628 fb_json_bldr_add_bool(bldr
, "state", TRUE
);
2629 fb_json_bldr_add_int(bldr
, "syncSeqId", priv
->sid
);
2630 fb_json_bldr_add_str(bldr
, "mark", "read");
2632 key
= thread
? "threadFbId" : "otherUserFbId";
2633 fb_json_bldr_add_strf(bldr
, key
, "%" FB_ID_FORMAT
, id
);
2635 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
2636 fb_api_publish(api
, "/mark_thread", "%s", json
);
2641 fb_api_cb_unread_parse_attach(FbApi
*api
, const gchar
*mid
, FbApiMessage
*msg
,
2642 GSList
*msgs
, JsonNode
*root
, GError
**error
)
2647 FbJsonValues
*values
;
2650 values
= fb_json_values_new(root
);
2651 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2652 "$.attachment_fbid");
2653 fb_json_values_set_array(values
, FALSE
, "$.blob_attachments");
2655 while (fb_json_values_update(values
, &err
)) {
2656 str
= fb_json_values_next_str(values
, NULL
);
2657 id
= FB_ID_FROM_STR(str
);
2658 dmsg
= fb_api_message_dup(msg
, FALSE
);
2659 fb_api_attach(api
, id
, mid
, dmsg
);
2662 if (G_UNLIKELY(err
!= NULL
)) {
2663 g_propagate_error(error
, err
);
2666 g_object_unref(values
);
2671 fb_api_cb_unread_msgs(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2681 FbJsonValues
*values
;
2684 GSList
*msgs
= NULL
;
2689 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2693 node
= fb_json_node_get_nth(root
, 0);
2696 fb_api_error(api
, FB_API_ERROR_GENERAL
,
2697 _("Failed to obtain unread messages"));
2698 json_node_free(root
);
2702 values
= fb_json_values_new(node
);
2703 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2704 "$.thread_key.thread_fbid");
2705 fb_json_values_update(values
, &err
);
2707 FB_API_ERROR_EMIT(api
, err
,
2708 g_object_unref(values
);
2712 fb_api_message_reset(&msg
, FALSE
);
2713 str
= fb_json_values_next_str(values
, "0");
2714 tid
= FB_ID_FROM_STR(str
);
2715 g_object_unref(values
);
2717 values
= fb_json_values_new(node
);
2718 fb_json_values_add(values
, FB_JSON_TYPE_BOOL
, TRUE
, "$.unread");
2719 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2720 "$.message_sender.messaging_actor.id");
2721 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.message.text");
2722 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2723 "$.timestamp_precise");
2724 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.sticker.id");
2725 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.message_id");
2726 fb_json_values_set_array(values
, FALSE
, "$.messages.nodes");
2728 while (fb_json_values_update(values
, &err
)) {
2729 if (!fb_json_values_next_bool(values
, FALSE
)) {
2733 str
= fb_json_values_next_str(values
, "0");
2734 body
= fb_json_values_next_str(values
, NULL
);
2736 fb_api_message_reset(&msg
, FALSE
);
2737 msg
.uid
= FB_ID_FROM_STR(str
);
2740 str
= fb_json_values_next_str(values
, "0");
2741 msg
.tstamp
= g_ascii_strtoll(str
, NULL
, 10);
2744 dmsg
= fb_api_message_dup(&msg
, FALSE
);
2745 dmsg
->text
= g_strdup(body
);
2746 msgs
= g_slist_prepend(msgs
, dmsg
);
2749 str
= fb_json_values_next_str(values
, NULL
);
2752 dmsg
= fb_api_message_dup(&msg
, FALSE
);
2753 id
= FB_ID_FROM_STR(str
);
2754 fb_api_sticker(api
, id
, dmsg
);
2757 node
= fb_json_values_get_root(values
);
2758 xode
= fb_json_node_get(node
, "$.extensible_attachment", NULL
);
2761 xma
= fb_api_xma_parse(api
, body
, xode
, &err
);
2764 dmsg
= fb_api_message_dup(&msg
, FALSE
);
2766 msgs
= g_slist_prepend(msgs
, dmsg
);
2769 json_node_free(xode
);
2771 if (G_UNLIKELY(err
!= NULL
)) {
2776 str
= fb_json_values_next_str(values
, NULL
);
2782 msgs
= fb_api_cb_unread_parse_attach(api
, str
, &msg
, msgs
,
2785 if (G_UNLIKELY(err
!= NULL
)) {
2790 if (G_UNLIKELY(err
== NULL
)) {
2791 msgs
= g_slist_reverse(msgs
);
2792 g_signal_emit_by_name(api
, "messages", msgs
);
2794 fb_api_error_emit(api
, err
);
2797 g_slist_free_full(msgs
, (GDestroyNotify
) fb_api_message_free
);
2798 g_object_unref(values
);
2799 json_node_free(root
);
2803 fb_api_cb_unread(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2808 FbJsonValues
*values
;
2814 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2818 values
= fb_json_values_new(root
);
2819 fb_json_values_add(values
, FB_JSON_TYPE_INT
, TRUE
, "$.unread_count");
2820 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2821 "$.thread_key.other_user_id");
2822 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2823 "$.thread_key.thread_fbid");
2824 fb_json_values_set_array(values
, FALSE
, "$.viewer.message_threads"
2827 while (fb_json_values_update(values
, &err
)) {
2828 count
= fb_json_values_next_int(values
, -5);
2834 id
= fb_json_values_next_str(values
, NULL
);
2837 id
= fb_json_values_next_str(values
, "0");
2840 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2841 fb_json_bldr_arr_begin(bldr
, "0");
2842 fb_json_bldr_add_str(bldr
, NULL
, id
);
2843 fb_json_bldr_arr_end(bldr
);
2845 fb_json_bldr_add_str(bldr
, "10", "true");
2846 fb_json_bldr_add_str(bldr
, "11", "true");
2847 fb_json_bldr_add_int(bldr
, "12", count
);
2848 fb_json_bldr_add_str(bldr
, "13", "false");
2849 fb_api_http_query(api
, FB_API_QUERY_THREAD
, bldr
,
2850 fb_api_cb_unread_msgs
);
2853 if (G_UNLIKELY(err
!= NULL
)) {
2854 fb_api_error_emit(api
, err
);
2857 g_object_unref(values
);
2858 json_node_free(root
);
2862 fb_api_unread(FbApi
*api
)
2867 g_return_if_fail(FB_IS_API(api
));
2870 if (priv
->unread
< 1) {
2874 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2875 fb_json_bldr_add_str(bldr
, "2", "true");
2876 fb_json_bldr_add_int(bldr
, "1", priv
->unread
);
2877 fb_json_bldr_add_str(bldr
, "12", "true");
2878 fb_json_bldr_add_str(bldr
, "13", "false");
2879 fb_api_http_query(api
, FB_API_QUERY_THREADS
, bldr
,
2884 fb_api_cb_sticker(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
2889 FbJsonValues
*values
;
2891 GSList
*msgs
= NULL
;
2895 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
2899 node
= fb_json_node_get_nth(root
, 0);
2900 values
= fb_json_values_new(node
);
2901 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2902 "$.thread_image.uri");
2903 fb_json_values_update(values
, &err
);
2905 FB_API_ERROR_EMIT(api
, err
,
2906 g_object_unref(values
);
2907 json_node_free(root
);
2911 msg
= fb_api_data_take(api
, con
);
2912 msg
->flags
|= FB_API_MESSAGE_FLAG_IMAGE
;
2913 msg
->text
= fb_json_values_next_str_dup(values
, NULL
);
2914 msgs
= g_slist_prepend(msgs
, msg
);
2916 g_signal_emit_by_name(api
, "messages", msgs
);
2917 g_slist_free_full(msgs
, (GDestroyNotify
) fb_api_message_free
);
2918 g_object_unref(values
);
2919 json_node_free(root
);
2923 fb_api_sticker(FbApi
*api
, FbId sid
, FbApiMessage
*msg
)
2926 PurpleHttpConnection
*http
;
2928 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
2929 fb_json_bldr_arr_begin(bldr
, "0");
2930 fb_json_bldr_add_strf(bldr
, NULL
, "%" FB_ID_FORMAT
, sid
);
2931 fb_json_bldr_arr_end(bldr
);
2933 http
= fb_api_http_query(api
, FB_API_QUERY_STICKER
, bldr
,
2935 fb_api_data_set(api
, http
, msg
, (GDestroyNotify
) fb_api_message_free
);
2939 fb_api_thread_parse(FbApi
*api
, FbApiThread
*thrd
, JsonNode
*root
,
2943 FbApiPrivate
*priv
= api
->priv
;
2946 FbJsonValues
*values
;
2947 gboolean haself
= FALSE
;
2948 guint num_users
= 0;
2951 values
= fb_json_values_new(root
);
2952 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
,
2953 "$.thread_key.thread_fbid");
2954 fb_json_values_add(values
, FB_JSON_TYPE_STR
, FALSE
, "$.name");
2955 fb_json_values_update(values
, &err
);
2957 if (G_UNLIKELY(err
!= NULL
)) {
2958 g_propagate_error(error
, err
);
2959 g_object_unref(values
);
2963 str
= fb_json_values_next_str(values
, NULL
);
2966 g_object_unref(values
);
2970 thrd
->tid
= FB_ID_FROM_STR(str
);
2971 thrd
->topic
= fb_json_values_next_str_dup(values
, NULL
);
2972 g_object_unref(values
);
2974 values
= fb_json_values_new(root
);
2975 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2976 "$.messaging_actor.id");
2977 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
,
2978 "$.messaging_actor.name");
2979 fb_json_values_set_array(values
, TRUE
, "$.all_participants.nodes");
2981 while (fb_json_values_update(values
, &err
)) {
2982 str
= fb_json_values_next_str(values
, "0");
2983 uid
= FB_ID_FROM_STR(str
);
2986 if (uid
!= priv
->uid
) {
2987 user
= fb_api_user_dup(NULL
, FALSE
);
2989 user
->name
= fb_json_values_next_str_dup(values
, NULL
);
2990 thrd
->users
= g_slist_prepend(thrd
->users
, user
);
2996 if (G_UNLIKELY(err
!= NULL
)) {
2997 g_propagate_error(error
, err
);
2998 fb_api_thread_reset(thrd
, TRUE
);
2999 g_object_unref(values
);
3003 if (num_users
< 2 || !haself
) {
3004 g_object_unref(values
);
3008 g_object_unref(values
);
3013 fb_api_cb_thread(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
3022 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
3026 node
= fb_json_node_get_nth(root
, 0);
3029 fb_api_error(api
, FB_API_ERROR_GENERAL
,
3030 _("Failed to obtain thread information"));
3031 json_node_free(root
);
3035 fb_api_thread_reset(&thrd
, FALSE
);
3037 if (!fb_api_thread_parse(api
, &thrd
, node
, &err
)) {
3038 if (G_LIKELY(err
== NULL
)) {
3040 g_signal_emit_by_name(api
, "thread-kicked", &thrd
);
3042 fb_api_error(api
, FB_API_ERROR_GENERAL
,
3043 _("Failed to parse thread information"));
3046 fb_api_error_emit(api
, err
);
3049 g_signal_emit_by_name(api
, "thread", &thrd
);
3052 fb_api_thread_reset(&thrd
, TRUE
);
3053 json_node_free(root
);
3057 fb_api_thread(FbApi
*api
, FbId tid
)
3061 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
3062 fb_json_bldr_arr_begin(bldr
, "0");
3063 fb_json_bldr_add_strf(bldr
, NULL
, "%" FB_ID_FORMAT
, tid
);
3064 fb_json_bldr_arr_end(bldr
);
3066 fb_json_bldr_add_str(bldr
, "10", "false");
3067 fb_json_bldr_add_str(bldr
, "11", "false");
3068 fb_json_bldr_add_str(bldr
, "13", "false");
3069 fb_api_http_query(api
, FB_API_QUERY_THREAD
, bldr
, fb_api_cb_thread
);
3073 fb_api_cb_thread_create(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
3079 FbJsonValues
*values
;
3083 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
3087 values
= fb_json_values_new(root
);
3088 fb_json_values_add(values
, FB_JSON_TYPE_STR
, TRUE
, "$.id");
3089 fb_json_values_update(values
, &err
);
3091 FB_API_ERROR_EMIT(api
, err
,
3092 g_object_unref(values
);
3093 json_node_free(root
);
3097 str
= fb_json_values_next_str(values
, "0");
3098 tid
= FB_ID_FROM_STR(str
);
3099 g_signal_emit_by_name(api
, "thread-create", tid
);
3101 g_object_unref(values
);
3102 json_node_free(root
);
3106 fb_api_thread_create(FbApi
*api
, GSList
*uids
)
3115 g_return_if_fail(FB_IS_API(api
));
3116 g_warn_if_fail(g_slist_length(uids
) > 1);
3119 bldr
= fb_json_bldr_new(JSON_NODE_ARRAY
);
3120 fb_json_bldr_obj_begin(bldr
, NULL
);
3121 fb_json_bldr_add_str(bldr
, "type", "id");
3122 fb_json_bldr_add_strf(bldr
, "id", "%" FB_ID_FORMAT
, priv
->uid
);
3123 fb_json_bldr_obj_end(bldr
);
3125 for (l
= uids
; l
!= NULL
; l
= l
->next
) {
3127 fb_json_bldr_obj_begin(bldr
, NULL
);
3128 fb_json_bldr_add_str(bldr
, "type", "id");
3129 fb_json_bldr_add_strf(bldr
, "id", "%" FB_ID_FORMAT
, *uid
);
3130 fb_json_bldr_obj_end(bldr
);
3133 json
= fb_json_bldr_close(bldr
, JSON_NODE_ARRAY
, NULL
);
3134 prms
= fb_http_params_new();
3135 fb_http_params_set_str(prms
, "recipients", json
);
3136 fb_api_http_req(api
, FB_API_URL_THREADS
, "createGroup", "POST",
3137 prms
, fb_api_cb_thread_create
);
3142 fb_api_thread_invite(FbApi
*api
, FbId tid
, FbId uid
)
3148 bldr
= fb_json_bldr_new(JSON_NODE_ARRAY
);
3149 fb_json_bldr_obj_begin(bldr
, NULL
);
3150 fb_json_bldr_add_str(bldr
, "type", "id");
3151 fb_json_bldr_add_strf(bldr
, "id", "%" FB_ID_FORMAT
, uid
);
3152 fb_json_bldr_obj_end(bldr
);
3153 json
= fb_json_bldr_close(bldr
, JSON_NODE_ARRAY
, NULL
);
3155 prms
= fb_http_params_new();
3156 fb_http_params_set_str(prms
, "to", json
);
3157 fb_http_params_set_strf(prms
, "id", "t_%" FB_ID_FORMAT
, tid
);
3158 fb_api_http_req(api
, FB_API_URL_PARTS
, "addMembers", "POST",
3159 prms
, fb_api_cb_http_bool
);
3164 fb_api_thread_remove(FbApi
*api
, FbId tid
, FbId uid
)
3171 g_return_if_fail(FB_IS_API(api
));
3174 prms
= fb_http_params_new();
3175 fb_http_params_set_strf(prms
, "id", "t_%" FB_ID_FORMAT
, tid
);
3181 if (uid
!= priv
->uid
) {
3182 bldr
= fb_json_bldr_new(JSON_NODE_ARRAY
);
3183 fb_json_bldr_add_strf(bldr
, NULL
, "%" FB_ID_FORMAT
, uid
);
3184 json
= fb_json_bldr_close(bldr
, JSON_NODE_ARRAY
, NULL
);
3185 fb_http_params_set_str(prms
, "to", json
);
3189 fb_api_http_req(api
, FB_API_URL_PARTS
, "removeMembers", "DELETE",
3190 prms
, fb_api_cb_http_bool
);
3194 fb_api_thread_topic(FbApi
*api
, FbId tid
, const gchar
*topic
)
3198 prms
= fb_http_params_new();
3199 fb_http_params_set_str(prms
, "name", topic
);
3200 fb_http_params_set_int(prms
, "tid", tid
);
3201 fb_api_http_req(api
, FB_API_URL_TOPIC
, "setThreadName",
3202 "messaging.setthreadname", prms
,
3203 fb_api_cb_http_bool
);
3207 fb_api_cb_threads(PurpleHttpConnection
*con
, PurpleHttpResponse
*res
,
3216 GSList
*thrds
= NULL
;
3220 if (!fb_api_http_chk(api
, con
, res
, &root
)) {
3224 arr
= fb_json_node_get_arr(root
, "$.viewer.message_threads.nodes",
3226 FB_API_ERROR_EMIT(api
, err
,
3227 json_node_free(root
);
3231 elms
= json_array_get_elements(arr
);
3233 for (l
= elms
; l
!= NULL
; l
= l
->next
) {
3234 fb_api_thread_reset(&thrd
, FALSE
);
3236 if (fb_api_thread_parse(api
, &thrd
, l
->data
, &err
)) {
3237 dthrd
= fb_api_thread_dup(&thrd
, FALSE
);
3238 thrds
= g_slist_prepend(thrds
, dthrd
);
3240 fb_api_thread_reset(&thrd
, TRUE
);
3243 if (G_UNLIKELY(err
!= NULL
)) {
3248 if (G_LIKELY(err
== NULL
)) {
3249 thrds
= g_slist_reverse(thrds
);
3250 g_signal_emit_by_name(api
, "threads", thrds
);
3252 fb_api_error_emit(api
, err
);
3255 g_slist_free_full(thrds
, (GDestroyNotify
) fb_api_thread_free
);
3257 json_array_unref(arr
);
3258 json_node_free(root
);
3262 fb_api_threads(FbApi
*api
)
3266 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
3267 fb_json_bldr_add_str(bldr
, "2", "true");
3268 fb_json_bldr_add_str(bldr
, "12", "false");
3269 fb_json_bldr_add_str(bldr
, "13", "false");
3270 fb_api_http_query(api
, FB_API_QUERY_THREADS
, bldr
, fb_api_cb_threads
);
3274 fb_api_typing(FbApi
*api
, FbId uid
, gboolean state
)
3279 bldr
= fb_json_bldr_new(JSON_NODE_OBJECT
);
3280 fb_json_bldr_add_int(bldr
, "state", state
!= 0);
3281 fb_json_bldr_add_strf(bldr
, "to", "%" FB_ID_FORMAT
, uid
);
3283 json
= fb_json_bldr_close(bldr
, JSON_NODE_OBJECT
, NULL
);
3284 fb_api_publish(api
, "/typing", "%s", json
);
3289 fb_api_event_dup(const FbApiEvent
*event
, gboolean deep
)
3293 if (event
== NULL
) {
3294 return g_new0(FbApiEvent
, 1);
3297 ret
= g_memdup(event
, sizeof *event
);
3300 ret
->text
= g_strdup(event
->text
);
3307 fb_api_event_reset(FbApiEvent
*event
, gboolean deep
)
3309 g_return_if_fail(event
!= NULL
);
3312 g_free(event
->text
);
3315 memset(event
, 0, sizeof *event
);
3319 fb_api_event_free(FbApiEvent
*event
)
3321 if (G_LIKELY(event
!= NULL
)) {
3322 g_free(event
->text
);
3328 fb_api_message_dup(const FbApiMessage
*msg
, gboolean deep
)
3333 return g_new0(FbApiMessage
, 1);
3336 ret
= g_memdup(msg
, sizeof *msg
);
3339 ret
->text
= g_strdup(msg
->text
);
3346 fb_api_message_reset(FbApiMessage
*msg
, gboolean deep
)
3348 g_return_if_fail(msg
!= NULL
);
3354 memset(msg
, 0, sizeof *msg
);
3358 fb_api_message_free(FbApiMessage
*msg
)
3360 if (G_LIKELY(msg
!= NULL
)) {
3367 fb_api_presence_dup(const FbApiPresence
*pres
)
3370 return g_new0(FbApiPresence
, 1);
3373 return g_memdup(pres
, sizeof *pres
);
3377 fb_api_presence_reset(FbApiPresence
*pres
)
3379 g_return_if_fail(pres
!= NULL
);
3380 memset(pres
, 0, sizeof *pres
);
3384 fb_api_presence_free(FbApiPresence
*pres
)
3386 if (G_LIKELY(pres
!= NULL
)) {
3392 fb_api_thread_dup(const FbApiThread
*thrd
, gboolean deep
)
3399 return g_new0(FbApiThread
, 1);
3402 ret
= g_memdup(thrd
, sizeof *thrd
);
3407 for (l
= thrd
->users
; l
!= NULL
; l
= l
->next
) {
3408 user
= fb_api_user_dup(l
->data
, TRUE
);
3409 ret
->users
= g_slist_prepend(ret
->users
, user
);
3412 ret
->topic
= g_strdup(thrd
->topic
);
3413 ret
->users
= g_slist_reverse(ret
->users
);
3420 fb_api_thread_reset(FbApiThread
*thrd
, gboolean deep
)
3422 g_return_if_fail(thrd
!= NULL
);
3425 g_slist_free_full(thrd
->users
, (GDestroyNotify
) fb_api_user_free
);
3426 g_free(thrd
->topic
);
3429 memset(thrd
, 0, sizeof *thrd
);
3433 fb_api_thread_free(FbApiThread
*thrd
)
3435 if (G_LIKELY(thrd
!= NULL
)) {
3436 g_slist_free_full(thrd
->users
, (GDestroyNotify
) fb_api_user_free
);
3437 g_free(thrd
->topic
);
3443 fb_api_typing_dup(const FbApiTyping
*typg
)
3446 return g_new0(FbApiTyping
, 1);
3449 return g_memdup(typg
, sizeof *typg
);
3453 fb_api_typing_reset(FbApiTyping
*typg
)
3455 g_return_if_fail(typg
!= NULL
);
3456 memset(typg
, 0, sizeof *typg
);
3460 fb_api_typing_free(FbApiTyping
*typg
)
3462 if (G_LIKELY(typg
!= NULL
)) {
3468 fb_api_user_dup(const FbApiUser
*user
, gboolean deep
)
3473 return g_new0(FbApiUser
, 1);
3476 ret
= g_memdup(user
, sizeof *user
);
3479 ret
->name
= g_strdup(user
->name
);
3480 ret
->icon
= g_strdup(user
->icon
);
3481 ret
->csum
= g_strdup(user
->csum
);
3488 fb_api_user_reset(FbApiUser
*user
, gboolean deep
)
3490 g_return_if_fail(user
!= NULL
);
3498 memset(user
, 0, sizeof *user
);
3502 fb_api_user_free(FbApiUser
*user
)
3504 if (G_LIKELY(user
!= NULL
)) {