libpurple/protocols/facebook/api.c

changeset 42187
fc241db9162d
parent 42186
637ba5491231
child 42188
04c0398f1046
equal deleted inserted replaced
42186:637ba5491231 42187:fc241db9162d
1 /* purple
2 *
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
5 * source distribution.
6 *
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.
11 *
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.
16 *
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
20 */
21
22 #include <glib/gi18n-lib.h>
23
24 #include <json-glib/json-glib.h>
25 #include <libsoup/soup.h>
26 #include <stdarg.h>
27 #include <string.h>
28
29 #include "libpurple/glibcompat.h"
30
31 #include "api.h"
32 #include "http.h"
33 #include "json.h"
34 #include "thrift.h"
35 #include "util.h"
36
37 enum
38 {
39 PROP_0,
40
41 PROP_CID,
42 PROP_DID,
43 PROP_MID,
44 PROP_STOKEN,
45 PROP_TOKEN,
46 PROP_UID,
47
48 PROP_N
49 };
50
51 /**
52 * FbApi:
53 *
54 * Represents a Facebook Messenger connection.
55 */
56 struct _FbApi {
57 GObject parent;
58
59 FbMqtt *mqtt;
60 SoupSession *cons;
61 PurpleConnection *gc;
62 gboolean retrying;
63
64 FbId uid;
65 gint64 sid;
66 guint64 mid;
67 gchar *cid;
68 gchar *did;
69 gchar *stoken;
70 gchar *token;
71
72 GQueue *msgs;
73 gboolean invisible;
74 guint unread;
75 FbId lastmid;
76 gchar *contacts_delta;
77 };
78
79 static void fb_api_error_literal(FbApi *api, FbApiError error,
80 const gchar *msg);
81
82 static void
83 fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg);
84
85 static void
86 fb_api_contacts_after(FbApi *api, const gchar *cursor);
87
88 static void
89 fb_api_message_send(FbApi *api, FbApiMessage *msg);
90
91 static void
92 fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg);
93
94 void
95 fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor);
96
97 G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT);
98
99 static void
100 fb_api_set_property(GObject *obj, guint prop, const GValue *val,
101 GParamSpec *pspec)
102 {
103 FbApi *api = FB_API(obj);
104
105 switch (prop) {
106 case PROP_CID:
107 g_free(api->cid);
108 api->cid = g_value_dup_string(val);
109 break;
110 case PROP_DID:
111 g_free(api->did);
112 api->did = g_value_dup_string(val);
113 break;
114 case PROP_MID:
115 api->mid = g_value_get_uint64(val);
116 break;
117 case PROP_STOKEN:
118 g_free(api->stoken);
119 api->stoken = g_value_dup_string(val);
120 break;
121 case PROP_TOKEN:
122 g_free(api->token);
123 api->token = g_value_dup_string(val);
124 break;
125 case PROP_UID:
126 api->uid = g_value_get_int64(val);
127 break;
128
129 default:
130 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
131 break;
132 }
133 }
134
135 static void
136 fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec)
137 {
138 FbApi *api = FB_API(obj);
139
140 switch (prop) {
141 case PROP_CID:
142 g_value_set_string(val, api->cid);
143 break;
144 case PROP_DID:
145 g_value_set_string(val, api->did);
146 break;
147 case PROP_MID:
148 g_value_set_uint64(val, api->mid);
149 break;
150 case PROP_STOKEN:
151 g_value_set_string(val, api->stoken);
152 break;
153 case PROP_TOKEN:
154 g_value_set_string(val, api->token);
155 break;
156 case PROP_UID:
157 g_value_set_int64(val, api->uid);
158 break;
159
160 default:
161 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
162 break;
163 }
164 }
165
166
167 static void
168 fb_api_dispose(GObject *obj)
169 {
170 FbApi *api = FB_API(obj);
171
172 if(api->cons != NULL) {
173 soup_session_abort(api->cons);
174 }
175
176 g_clear_object(&api->mqtt);
177
178 g_clear_object(&api->cons);
179 if(api->msgs != NULL) {
180 g_queue_free_full(api->msgs, (GDestroyNotify)fb_api_message_free);
181 api->msgs = NULL;
182 }
183
184 g_clear_pointer(&api->cid, g_free);
185 g_clear_pointer(&api->did, g_free);
186 g_clear_pointer(&api->stoken, g_free);
187 g_clear_pointer(&api->token, g_free);
188 g_clear_pointer(&api->contacts_delta, g_free);
189 }
190
191 static void
192 fb_api_class_init(FbApiClass *klass)
193 {
194 GObjectClass *gklass = G_OBJECT_CLASS(klass);
195 GParamSpec *props[PROP_N] = {NULL};
196
197 gklass->set_property = fb_api_set_property;
198 gklass->get_property = fb_api_get_property;
199 gklass->dispose = fb_api_dispose;
200
201 /**
202 * FbApi:cid:
203 *
204 * The client identifier for MQTT. This value should be saved
205 * and loaded for persistence.
206 */
207 props[PROP_CID] = g_param_spec_string(
208 "cid",
209 "Client ID",
210 "Client identifier for MQTT",
211 NULL,
212 G_PARAM_READWRITE);
213
214 /**
215 * FbApi:did:
216 *
217 * The device identifier for the MQTT message queue. This value
218 * should be saved and loaded for persistence.
219 */
220 props[PROP_DID] = g_param_spec_string(
221 "did",
222 "Device ID",
223 "Device identifier for the MQTT message queue",
224 NULL,
225 G_PARAM_READWRITE);
226
227 /**
228 * FbApi:mid:
229 *
230 * The MQTT identifier. This value should be saved and loaded
231 * for persistence.
232 */
233 props[PROP_MID] = g_param_spec_uint64(
234 "mid",
235 "MQTT ID",
236 "MQTT identifier",
237 0, G_MAXUINT64, 0,
238 G_PARAM_READWRITE);
239
240 /**
241 * FbApi:stoken:
242 *
243 * The synchronization token for the MQTT message queue. This
244 * value should be saved and loaded for persistence.
245 */
246 props[PROP_STOKEN] = g_param_spec_string(
247 "stoken",
248 "Sync Token",
249 "Synchronization token for the MQTT message queue",
250 NULL,
251 G_PARAM_READWRITE);
252
253 /**
254 * FbApi:token:
255 *
256 * The access token for authentication. This value should be
257 * saved and loaded for persistence.
258 */
259 props[PROP_TOKEN] = g_param_spec_string(
260 "token",
261 "Access Token",
262 "Access token for authentication",
263 NULL,
264 G_PARAM_READWRITE);
265
266 /**
267 * FbApi:uid:
268 *
269 * The #FbId of the user of the #FbApi.
270 */
271 props[PROP_UID] = g_param_spec_int64(
272 "uid",
273 "User ID",
274 "User identifier",
275 0, G_MAXINT64, 0,
276 G_PARAM_READWRITE);
277 g_object_class_install_properties(gklass, PROP_N, props);
278
279 /**
280 * FbApi::auth:
281 * @api: The #FbApi.
282 *
283 * Emitted upon the successful completion of the authentication
284 * process. This is emitted as a result of #fb_api_auth().
285 */
286 g_signal_new("auth",
287 G_TYPE_FROM_CLASS(klass),
288 G_SIGNAL_ACTION,
289 0,
290 NULL, NULL, NULL,
291 G_TYPE_NONE,
292 0);
293
294 /**
295 * FbApi::connect:
296 * @api: The #FbApi.
297 *
298 * Emitted upon the successful completion of the connection
299 * process. This is emitted as a result of #fb_api_connect().
300 */
301 g_signal_new("connect",
302 G_TYPE_FROM_CLASS(klass),
303 G_SIGNAL_ACTION,
304 0,
305 NULL, NULL, NULL,
306 G_TYPE_NONE,
307 0);
308
309 /**
310 * FbApi::contact:
311 * @api: The #FbApi.
312 * @user: The #FbApiUser.
313 *
314 * Emitted upon the successful reply of a contact request. This
315 * is emitted as a result of #fb_api_contact().
316 */
317 g_signal_new("contact",
318 G_TYPE_FROM_CLASS(klass),
319 G_SIGNAL_ACTION,
320 0,
321 NULL, NULL, NULL,
322 G_TYPE_NONE,
323 1, G_TYPE_POINTER);
324
325 /**
326 * FbApi::contacts:
327 * @api: The #FbApi.
328 * @users: The #GSList of #FbApiUser's.
329 * @complete: #TRUE if the list is fetched, otherwise #FALSE.
330 *
331 * Emitted upon the successful reply of a contacts request.
332 * This is emitted as a result of #fb_api_contacts(). This can
333 * be emitted multiple times before the entire contacts list
334 * has been fetched. Use @complete for detecting the completion
335 * status of the list fetch.
336 */
337 g_signal_new("contacts",
338 G_TYPE_FROM_CLASS(klass),
339 G_SIGNAL_ACTION,
340 0,
341 NULL, NULL, NULL,
342 G_TYPE_NONE,
343 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
344
345 /**
346 * FbApi::contacts-delta:
347 * @api: The #FbApi.
348 * @added: The #GSList of added #FbApiUser's.
349 * @removed: The #GSList of strings with removed user ids.
350 *
351 * Like 'contacts', but only the deltas.
352 */
353 g_signal_new("contacts-delta",
354 G_TYPE_FROM_CLASS(klass),
355 G_SIGNAL_ACTION,
356 0,
357 NULL, NULL, NULL,
358 G_TYPE_NONE,
359 2, G_TYPE_POINTER, G_TYPE_POINTER);
360
361 /**
362 * FbApi::error:
363 * @api: The #FbApi.
364 * @error: The #GError.
365 *
366 * Emitted whenever an error is hit within the #FbApi. This
367 * should disconnect the #FbApi with #fb_api_disconnect().
368 */
369 g_signal_new("error",
370 G_TYPE_FROM_CLASS(klass),
371 G_SIGNAL_ACTION,
372 0,
373 NULL, NULL, NULL,
374 G_TYPE_NONE,
375 1, G_TYPE_POINTER);
376
377 /**
378 * FbApi::events:
379 * @api: The #FbApi.
380 * @events: The #GSList of #FbApiEvent's.
381 *
382 * Emitted upon incoming events from the stream.
383 */
384 g_signal_new("events",
385 G_TYPE_FROM_CLASS(klass),
386 G_SIGNAL_ACTION,
387 0,
388 NULL, NULL, NULL,
389 G_TYPE_NONE,
390 1, G_TYPE_POINTER);
391
392 /**
393 * FbApi::messages:
394 * @api: The #FbApi.
395 * @msgs: The #GSList of #FbApiMessage's.
396 *
397 * Emitted upon incoming messages from the stream.
398 */
399 g_signal_new("messages",
400 G_TYPE_FROM_CLASS(klass),
401 G_SIGNAL_ACTION,
402 0,
403 NULL, NULL, NULL,
404 G_TYPE_NONE,
405 1, G_TYPE_POINTER);
406
407 /**
408 * FbApi::presences:
409 * @api: The #FbApi.
410 * @press: The #GSList of #FbApiPresence's.
411 *
412 * Emitted upon incoming presences from the stream.
413 */
414 g_signal_new("presences",
415 G_TYPE_FROM_CLASS(klass),
416 G_SIGNAL_ACTION,
417 0,
418 NULL, NULL, NULL,
419 G_TYPE_NONE,
420 1, G_TYPE_POINTER);
421
422 /**
423 * FbApi::thread:
424 * @api: The #FbApi.
425 * @thrd: The #FbApiThread.
426 *
427 * Emitted upon the successful reply of a thread request. This
428 * is emitted as a result of #fb_api_thread().
429 */
430 g_signal_new("thread",
431 G_TYPE_FROM_CLASS(klass),
432 G_SIGNAL_ACTION,
433 0,
434 NULL, NULL, NULL,
435 G_TYPE_NONE,
436 1, G_TYPE_POINTER);
437
438 /**
439 * FbApi::thread-create:
440 * @api: The #FbApi.
441 * @tid: The thread #FbId.
442 *
443 * Emitted upon the successful reply of a thread creation
444 * request. This is emitted as a result of
445 * #fb_api_thread_create().
446 */
447 g_signal_new("thread-create",
448 G_TYPE_FROM_CLASS(klass),
449 G_SIGNAL_ACTION,
450 0,
451 NULL, NULL, NULL,
452 G_TYPE_NONE,
453 1, FB_TYPE_ID);
454
455 /**
456 * FbApi::thread-kicked:
457 * @api: The #FbApi.
458 * @thrd: The #FbApiThread.
459 *
460 * Emitted upon the reply of a thread request when the user is no longer
461 * part of that thread. This is emitted as a result of #fb_api_thread().
462 */
463 g_signal_new("thread-kicked",
464 G_TYPE_FROM_CLASS(klass),
465 G_SIGNAL_ACTION,
466 0,
467 NULL, NULL, NULL,
468 G_TYPE_NONE,
469 1, G_TYPE_POINTER);
470
471 /**
472 * FbApi::threads:
473 * @api: The #FbApi.
474 * @thrds: The #GSList of #FbApiThread's.
475 *
476 * Emitted upon the successful reply of a threads request. This
477 * is emitted as a result of #fb_api_threads().
478 */
479 g_signal_new("threads",
480 G_TYPE_FROM_CLASS(klass),
481 G_SIGNAL_ACTION,
482 0,
483 NULL, NULL, NULL,
484 G_TYPE_NONE,
485 1, G_TYPE_POINTER);
486
487 /**
488 * FbApi::typing:
489 * @api: The #FbApi.
490 * @typg: The #FbApiTyping.
491 *
492 * Emitted upon an incoming typing state from the stream.
493 */
494 g_signal_new("typing",
495 G_TYPE_FROM_CLASS(klass),
496 G_SIGNAL_ACTION,
497 0,
498 NULL, NULL, NULL,
499 G_TYPE_NONE,
500 1, G_TYPE_POINTER);
501 }
502
503 static void
504 fb_api_init(FbApi *api)
505 {
506 api->msgs = g_queue_new();
507 }
508
509 GQuark
510 fb_api_error_quark(void)
511 {
512 static GQuark q = 0;
513
514 if (G_UNLIKELY(q == 0)) {
515 q = g_quark_from_static_string("fb-api-error-quark");
516 }
517
518 return q;
519 }
520
521 static gboolean
522 fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node)
523 {
524 const gchar *str;
525 FbApiError errc = FB_API_ERROR_GENERAL;
526 FbJsonValues *values;
527 gboolean success = TRUE;
528 gchar *msg;
529 GError *err = NULL;
530 gint64 code;
531 guint i;
532 JsonNode *root;
533
534 static const gchar *exprs[] = {
535 "$.error.message",
536 "$.error.summary",
537 "$.error_msg",
538 "$.errorCode",
539 "$.failedSend.errorMessage",
540 };
541
542 g_return_val_if_fail(FB_IS_API(api), FALSE);
543
544 if (G_UNLIKELY(size == 0)) {
545 fb_api_error_literal(api, FB_API_ERROR_GENERAL, _("Empty JSON data"));
546 return FALSE;
547 }
548
549 fb_util_debug(FB_UTIL_DEBUG_INFO, "Parsing JSON: %.*s\n",
550 (gint) size, (const gchar *) data);
551
552 root = fb_json_node_new(data, size, &err);
553 FB_API_ERROR_EMIT(api, err, return FALSE);
554
555 values = fb_json_values_new(root);
556 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code");
557 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type");
558 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode");
559 fb_json_values_update(values, &err);
560
561 FB_API_ERROR_EMIT(api, err,
562 g_object_unref(values);
563 json_node_free(root);
564 return FALSE
565 );
566
567 code = fb_json_values_next_int(values, 0);
568 str = fb_json_values_next_str(values, NULL);
569
570 if (purple_strequal(str, "OAuthException") || (code == 401)) {
571 errc = FB_API_ERROR_AUTH;
572 success = FALSE;
573
574 g_clear_pointer(&api->stoken, g_free);
575 g_clear_pointer(&api->token, g_free);
576 }
577
578 /* 509 is used for "invalid attachment id" */
579 if (code == 509) {
580 errc = FB_API_ERROR_NONFATAL;
581 success = FALSE;
582 }
583
584 str = fb_json_values_next_str(values, NULL);
585
586 if (purple_strequal(str, "ERROR_QUEUE_NOT_FOUND") ||
587 purple_strequal(str, "ERROR_QUEUE_LOST"))
588 {
589 errc = FB_API_ERROR_QUEUE;
590 success = FALSE;
591
592 g_clear_pointer(&api->stoken, g_free);
593 }
594
595 g_object_unref(values);
596
597 for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) {
598 msg = fb_json_node_get_str(root, exprs[i], NULL);
599
600 if (msg != NULL) {
601 success = FALSE;
602 break;
603 }
604 }
605
606 if (!success && (msg == NULL)) {
607 msg = g_strdup(_("Unknown error"));
608 }
609
610 if (msg != NULL) {
611 fb_api_error_literal(api, errc, msg);
612 json_node_free(root);
613 g_free(msg);
614 return FALSE;
615 }
616
617 if (node != NULL) {
618 *node = root;
619 } else {
620 json_node_free(root);
621 }
622
623 return TRUE;
624 }
625
626 static gboolean
627 fb_api_http_chk(FbApi *api, SoupSession *session, GAsyncResult *result,
628 SoupMessage *msg, JsonNode **root)
629 {
630 GBytes *response_body = NULL;
631 const gchar *reason = NULL;
632 const gchar *data = NULL;
633 GError *err = NULL;
634 gint code;
635 gsize size = 0;
636
637 reason = soup_message_get_reason_phrase(msg);
638 code = soup_message_get_status(msg);
639
640 fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Response (%p):", msg);
641 if (reason != NULL) {
642 fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %s (%d)", reason,
643 code);
644 } else {
645 fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %d", code);
646 }
647
648 if (fb_http_error_chk(msg, &err) && (root == NULL)) {
649 return TRUE;
650 }
651
652 response_body = soup_session_send_and_read_finish(session, result, &err);
653 if(response_body != NULL) {
654 data = g_bytes_get_data(response_body, &size);
655 }
656
657 if (G_LIKELY(size > 0)) {
658 fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Data: %.*s",
659 (gint) size, data);
660 }
661
662 /* Rudimentary check to prevent wrongful error parsing */
663 if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) {
664 FB_API_ERROR_EMIT(api, err, return FALSE);
665 }
666
667 if (!fb_api_json_chk(api, data, size, root)) {
668 if (G_UNLIKELY(err != NULL)) {
669 g_error_free(err);
670 }
671
672 return FALSE;
673 }
674
675 FB_API_ERROR_EMIT(api, err, return FALSE);
676 return TRUE;
677 }
678
679 static SoupMessage *
680 fb_api_http_req(FbApi *api, const gchar *url, const gchar *name,
681 const gchar *method, FbHttpParams *params,
682 GAsyncReadyCallback callback)
683 {
684 gchar *data;
685 gchar *key;
686 gchar *val;
687 GList *keys;
688 GList *l;
689 GString *gstr;
690 SoupMessage *msg;
691
692 fb_http_params_set_str(params, "api_key", FB_API_KEY);
693 fb_http_params_set_str(params, "device_id", api->did);
694 fb_http_params_set_str(params, "fb_api_req_friendly_name", name);
695 fb_http_params_set_str(params, "format", "json");
696 fb_http_params_set_str(params, "method", method);
697
698 val = fb_util_get_locale();
699 fb_http_params_set_str(params, "locale", val);
700 g_free(val);
701
702 /* Ensure an old signature is not computed */
703 g_hash_table_remove(params, "sig");
704
705 gstr = g_string_new(NULL);
706 keys = g_hash_table_get_keys(params);
707 keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp);
708
709 for (l = keys; l != NULL; l = l->next) {
710 key = l->data;
711 val = g_hash_table_lookup(params, key);
712 g_string_append_printf(gstr, "%s=%s", key, val);
713 }
714
715 g_string_append(gstr, FB_API_SECRET);
716 data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str,
717 gstr->len);
718 fb_http_params_set_str(params, "sig", data);
719 g_string_free(gstr, TRUE);
720 g_list_free(keys);
721 g_free(data);
722
723 msg = soup_message_new_from_encoded_form("POST", url, soup_form_encode_hash(params));
724 fb_http_params_free(params);
725
726 if (api->token != NULL) {
727 data = g_strdup_printf("OAuth %s", api->token);
728 soup_message_headers_replace(soup_message_get_request_headers(msg),
729 "Authorization", data);
730 g_free(data);
731 }
732
733 g_object_set_data(G_OBJECT(msg), "facebook-api", api);
734 soup_session_send_and_read_async(api->cons, msg, G_PRIORITY_DEFAULT, NULL,
735 callback, msg);
736
737 fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", msg);
738 fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url);
739
740 return msg;
741 }
742
743 static SoupMessage *
744 fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder,
745 GAsyncReadyCallback hcb)
746 {
747 const gchar *name;
748 FbHttpParams *prms;
749 gchar *json;
750
751 switch (query) {
752 case FB_API_QUERY_CONTACT:
753 name = "UsersQuery";
754 break;
755 case FB_API_QUERY_CONTACTS:
756 name = "FetchContactsFullQuery";
757 break;
758 case FB_API_QUERY_CONTACTS_AFTER:
759 name = "FetchContactsFullWithAfterQuery";
760 break;
761 case FB_API_QUERY_CONTACTS_DELTA:
762 name = "FetchContactsDeltaQuery";
763 break;
764 case FB_API_QUERY_STICKER:
765 name = "FetchStickersWithPreviewsQuery";
766 break;
767 case FB_API_QUERY_THREAD:
768 name = "ThreadQuery";
769 break;
770 case FB_API_QUERY_SEQ_ID:
771 case FB_API_QUERY_THREADS:
772 name = "ThreadListQuery";
773 break;
774 case FB_API_QUERY_XMA:
775 name = "XMAQuery";
776 break;
777 default:
778 g_return_val_if_reached(NULL);
779 return NULL;
780 }
781
782 prms = fb_http_params_new();
783 json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL);
784
785 fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query);
786 fb_http_params_set_str(prms, "query_params", json);
787 g_free(json);
788
789 return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, hcb);
790 }
791
792 static void
793 fb_api_cb_http_bool(GObject *source, GAsyncResult *result, gpointer data) {
794 SoupSession *session = SOUP_SESSION(source);
795 SoupMessage *soupmsg = data;
796 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
797 JsonNode *root;
798
799 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
800 g_object_unref(soupmsg);
801 return;
802 }
803
804 if (!json_node_get_boolean(root)) {
805 fb_api_error_literal(api, FB_API_ERROR,
806 _("Failed generic API operation"));
807 }
808
809 json_node_free(root);
810 g_object_unref(soupmsg);
811 }
812
813 static void
814 fb_api_cb_mqtt_error(G_GNUC_UNUSED FbMqtt *mqtt, GError *error, gpointer data)
815 {
816 FbApi *api = data;
817
818 if (!api->retrying) {
819 api->retrying = TRUE;
820 fb_util_debug_info("Attempting to reconnect the MQTT stream...");
821 fb_api_connect(api, api->invisible);
822 } else {
823 g_signal_emit_by_name(api, "error", error);
824 }
825 }
826
827 static void
828 fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data)
829 {
830 const GByteArray *bytes;
831 FbApi *api = data;
832 FbThrift *thft;
833 GByteArray *cytes;
834 GError *err = NULL;
835
836 static guint8 flags = FB_MQTT_CONNECT_FLAG_USER |
837 FB_MQTT_CONNECT_FLAG_PASS |
838 FB_MQTT_CONNECT_FLAG_CLR;
839
840 thft = fb_thrift_new(NULL, 0);
841
842 /* Write the client identifier */
843 fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1, 0);
844 fb_thrift_write_str(thft, api->cid);
845
846 fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4, 1);
847
848 /* Write the user identifier */
849 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 1, 0);
850 fb_thrift_write_i64(thft, api->uid);
851
852 /* Write the information string */
853 fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1);
854 fb_thrift_write_str(thft, FB_API_MQTT_AGENT);
855
856 /* Write the UNKNOWN ("cp"?) */
857 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2);
858 fb_thrift_write_i64(thft, 23);
859
860 /* Write the UNKNOWN ("ecp"?) */
861 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3);
862 fb_thrift_write_i64(thft, 26);
863
864 /* Write the UNKNOWN */
865 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4);
866 fb_thrift_write_i32(thft, 1);
867
868 /* Write the UNKNOWN ("no_auto_fg"?) */
869 fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5);
870 fb_thrift_write_bool(thft, TRUE);
871
872 /* Write the visibility state */
873 fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6);
874 fb_thrift_write_bool(thft, !api->invisible);
875
876 /* Write the device identifier */
877 fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7);
878 fb_thrift_write_str(thft, api->did);
879
880 /* Write the UNKNOWN ("fg"?) */
881 fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8);
882 fb_thrift_write_bool(thft, TRUE);
883
884 /* Write the UNKNOWN ("nwt"?) */
885 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9);
886 fb_thrift_write_i32(thft, 1);
887
888 /* Write the UNKNOWN ("nwst"?) */
889 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10);
890 fb_thrift_write_i32(thft, 0);
891
892 /* Write the MQTT identifier */
893 fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11);
894 fb_thrift_write_i64(thft, api->mid);
895
896 /* Write the UNKNOWN */
897 fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12);
898 fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0);
899 fb_thrift_write_stop(thft);
900
901 /* Write the token */
902 fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14);
903 fb_thrift_write_str(thft, api->token);
904
905 /* Write the STOP for the struct */
906 fb_thrift_write_stop(thft);
907
908 bytes = fb_thrift_get_bytes(thft);
909 cytes = fb_util_zlib_deflate(bytes, &err);
910
911 FB_API_ERROR_EMIT(api, err,
912 g_object_unref(thft);
913 return;
914 );
915
916 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing connect");
917 fb_mqtt_connect(mqtt, flags, cytes);
918
919 g_byte_array_free(cytes, TRUE);
920 g_object_unref(thft);
921 }
922
923 static void
924 fb_api_connect_queue(FbApi *api)
925 {
926 FbApiMessage *msg;
927 gchar *json;
928 JsonBuilder *bldr;
929
930 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
931 fb_json_bldr_add_int(bldr, "delta_batch_size", 125);
932 fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250);
933 fb_json_bldr_add_int(bldr, "sync_api_version", 3);
934 fb_json_bldr_add_str(bldr, "encoding", "JSON");
935
936 if (api->stoken == NULL) {
937 fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", api->sid);
938 fb_json_bldr_add_str(bldr, "device_id", api->did);
939 fb_json_bldr_add_int(bldr, "entity_fbid", api->uid);
940
941 fb_json_bldr_obj_begin(bldr, "queue_params");
942 fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false");
943
944 fb_json_bldr_obj_begin(bldr, "graphql_query_hashes");
945 fb_json_bldr_add_str(bldr, "xma_query_id",
946 G_STRINGIFY(FB_API_QUERY_XMA));
947 fb_json_bldr_obj_end(bldr);
948
949 fb_json_bldr_obj_begin(bldr, "graphql_query_params");
950 fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA));
951 fb_json_bldr_add_str(bldr, "xma_id", "<ID>");
952 fb_json_bldr_obj_end(bldr);
953 fb_json_bldr_obj_end(bldr);
954 fb_json_bldr_obj_end(bldr);
955
956 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
957 fb_api_publish(api, "/messenger_sync_create_queue", "%s",
958 json);
959 g_free(json);
960 return;
961 }
962
963 fb_json_bldr_add_int(bldr, "last_seq_id", api->sid);
964 fb_json_bldr_add_str(bldr, "sync_token", api->stoken);
965
966 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
967 fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json);
968 g_signal_emit_by_name(api, "connect");
969 g_free(json);
970
971 if (!g_queue_is_empty(api->msgs)) {
972 msg = g_queue_peek_head(api->msgs);
973 fb_api_message_send(api, msg);
974 }
975
976 if (api->retrying) {
977 api->retrying = FALSE;
978 fb_util_debug_info("Reconnected the MQTT stream");
979 }
980 }
981
982 static void
983 fb_api_cb_seqid(GObject *source, GAsyncResult *result, gpointer data) {
984 SoupSession *session = SOUP_SESSION(source);
985 SoupMessage *soupmsg = data;
986 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
987 const gchar *str;
988 FbJsonValues *values;
989 GError *err = NULL;
990 JsonNode *root;
991
992 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
993 g_object_unref(soupmsg);
994 return;
995 }
996
997 values = fb_json_values_new(root);
998 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
999 "$.viewer.message_threads.sync_sequence_id");
1000 fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE,
1001 "$.viewer.message_threads.unread_count");
1002 fb_json_values_update(values, &err);
1003
1004 FB_API_ERROR_EMIT(api, err,
1005 g_object_unref(values);
1006 json_node_free(root);
1007 g_object_unref(soupmsg);
1008 return;
1009 );
1010
1011 str = fb_json_values_next_str(values, "0");
1012 api->sid = g_ascii_strtoll(str, NULL, 10);
1013 api->unread = fb_json_values_next_int(values, 0);
1014
1015 if (api->sid == 0) {
1016 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
1017 _("Failed to get sync_sequence_id"));
1018 } else {
1019 fb_api_connect_queue(api);
1020 }
1021
1022 g_object_unref(values);
1023 json_node_free(root);
1024 g_object_unref(soupmsg);
1025 }
1026
1027 static void
1028 fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data)
1029 {
1030 FbApi *api = data;
1031 gchar *json;
1032 JsonBuilder *bldr;
1033
1034 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1035 fb_json_bldr_add_bool(bldr, "foreground", TRUE);
1036 fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA);
1037
1038 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
1039 fb_api_publish(api, "/foreground_state", "%s", json);
1040 g_free(json);
1041
1042 fb_mqtt_subscribe(mqtt,
1043 "/inbox", 0,
1044 "/mercury", 0,
1045 "/messaging_events", 0,
1046 "/orca_presence", 0,
1047 "/orca_typing_notifications", 0,
1048 "/pp", 0,
1049 "/t_ms", 0,
1050 "/t_p", 0,
1051 "/t_rtc", 0,
1052 "/webrtc", 0,
1053 "/webrtc_response", 0,
1054 NULL
1055 );
1056
1057 /* Notifications seem to lead to some sort of sending rate limit */
1058 fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL);
1059
1060 if (api->sid == 0) {
1061 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1062 fb_json_bldr_add_str(bldr, "1", "0");
1063 fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr,
1064 fb_api_cb_seqid);
1065 } else {
1066 fb_api_connect_queue(api);
1067 }
1068 }
1069
1070 static void
1071 fb_api_cb_publish_mark(FbApi *api, GByteArray *pload)
1072 {
1073 FbJsonValues *values;
1074 GError *err = NULL;
1075 JsonNode *root;
1076
1077 if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
1078 return;
1079 }
1080
1081 values = fb_json_values_new(root);
1082 fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded");
1083 fb_json_values_update(values, &err);
1084
1085 FB_API_ERROR_EMIT(api, err,
1086 g_object_unref(values);
1087 json_node_free(root);
1088 return;
1089 );
1090
1091 if (!fb_json_values_next_bool(values, TRUE)) {
1092 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
1093 _("Failed to mark thread as read"));
1094 }
1095
1096 g_object_unref(values);
1097 json_node_free(root);
1098 }
1099
1100 static GSList *
1101 fb_api_event_parse(G_GNUC_UNUSED FbApi *api, FbApiEvent *event, GSList *events,
1102 JsonNode *root, GError **error)
1103 {
1104 const gchar *str;
1105 FbApiEvent *devent;
1106 FbJsonValues *values;
1107 GError *err = NULL;
1108 guint i;
1109
1110 static const struct {
1111 FbApiEventType type;
1112 const gchar *expr;
1113 } evtypes[] = {
1114 {
1115 FB_API_EVENT_TYPE_THREAD_USER_ADDED,
1116 "$.log_message_data.added_participants"
1117 }, {
1118 FB_API_EVENT_TYPE_THREAD_USER_REMOVED,
1119 "$.log_message_data.removed_participants"
1120 }
1121 };
1122
1123 values = fb_json_values_new(root);
1124 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1125 "$.log_message_type");
1126 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author");
1127 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1128 "$.log_message_data.name");
1129 fb_json_values_update(values, &err);
1130
1131 if (G_UNLIKELY(err != NULL)) {
1132 g_propagate_error(error, err);
1133 g_object_unref(values);
1134 return events;
1135 }
1136
1137 str = fb_json_values_next_str(values, NULL);
1138
1139 if (g_strcmp0(str, "log:thread-name") == 0) {
1140 str = fb_json_values_next_str(values, "");
1141 str = strrchr(str, ':');
1142
1143 if (str != NULL) {
1144 devent = g_new(FbApiEvent, 1);
1145 devent->type = FB_API_EVENT_TYPE_THREAD_TOPIC;
1146 devent->uid = FB_ID_FROM_STR(str + 1);
1147 devent->tid = event->tid;
1148 devent->text = fb_json_values_next_str_dup(values, NULL);
1149 events = g_slist_prepend(events, devent);
1150 }
1151 }
1152
1153 g_object_unref(values);
1154
1155 for (i = 0; i < G_N_ELEMENTS(evtypes); i++) {
1156 values = fb_json_values_new(root);
1157 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$");
1158 fb_json_values_set_array(values, FALSE, evtypes[i].expr);
1159
1160 while (fb_json_values_update(values, &err)) {
1161 str = fb_json_values_next_str(values, "");
1162 str = strrchr(str, ':');
1163
1164 if (str != NULL) {
1165 devent = g_new0(FbApiEvent, 1);
1166 devent->type = evtypes[i].type;
1167 devent->uid = FB_ID_FROM_STR(str + 1);
1168 devent->tid = event->tid;
1169 events = g_slist_prepend(events, devent);
1170 }
1171 }
1172
1173 g_object_unref(values);
1174
1175 if (G_UNLIKELY(err != NULL)) {
1176 g_propagate_error(error, err);
1177 break;
1178 }
1179 }
1180
1181 return events;
1182 }
1183
1184 static void
1185 fb_api_cb_publish_mercury(FbApi *api, GByteArray *pload)
1186 {
1187 const gchar *str;
1188 FbApiEvent event;
1189 FbJsonValues *values;
1190 GError *err = NULL;
1191 GSList *events = NULL;
1192 JsonNode *root;
1193 JsonNode *node;
1194
1195 if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
1196 return;
1197 }
1198
1199 values = fb_json_values_new(root);
1200 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid");
1201 fb_json_values_set_array(values, FALSE, "$.actions");
1202
1203 while (fb_json_values_update(values, &err)) {
1204 fb_api_event_reset(&event, FALSE);
1205 str = fb_json_values_next_str(values, "0");
1206 event.tid = FB_ID_FROM_STR(str);
1207
1208 node = fb_json_values_get_root(values);
1209 events = fb_api_event_parse(api, &event, events, node, &err);
1210 }
1211
1212 if (G_LIKELY(err == NULL)) {
1213 events = g_slist_reverse(events);
1214 g_signal_emit_by_name(api, "events", events);
1215 } else {
1216 fb_api_error_emit(api, err);
1217 }
1218
1219 g_slist_free_full(events, (GDestroyNotify) fb_api_event_free);
1220 g_object_unref(values);
1221 json_node_free(root);
1222
1223 }
1224
1225 static void
1226 fb_api_cb_publish_typing(FbApi *api, GByteArray *pload)
1227 {
1228 const gchar *str;
1229 FbApiTyping typg;
1230 FbJsonValues *values;
1231 GError *err = NULL;
1232 JsonNode *root;
1233
1234 if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
1235 return;
1236 }
1237
1238 values = fb_json_values_new(root);
1239 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type");
1240 fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid");
1241 fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state");
1242 fb_json_values_update(values, &err);
1243
1244 FB_API_ERROR_EMIT(api, err,
1245 g_object_unref(values);
1246 json_node_free(root);
1247 return;
1248 );
1249
1250 str = fb_json_values_next_str(values, NULL);
1251
1252 if (g_ascii_strcasecmp(str, "typ") == 0) {
1253 typg.uid = fb_json_values_next_int(values, 0);
1254
1255 if (typg.uid != api->uid) {
1256 typg.state = fb_json_values_next_int(values, 0);
1257 g_signal_emit_by_name(api, "typing", &typg);
1258 }
1259 }
1260
1261 g_object_unref(values);
1262 json_node_free(root);
1263 }
1264
1265 static void
1266 fb_api_cb_publish_ms_r(FbApi *api, GByteArray *pload)
1267 {
1268 FbApiMessage *msg;
1269 FbJsonValues *values;
1270 GError *err = NULL;
1271 JsonNode *root;
1272
1273 if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
1274 return;
1275 }
1276
1277 values = fb_json_values_new(root);
1278 fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.succeeded");
1279 fb_json_values_update(values, &err);
1280
1281 FB_API_ERROR_EMIT(api, err,
1282 g_object_unref(values);
1283 json_node_free(root);
1284 return;
1285 );
1286
1287 if (fb_json_values_next_bool(values, TRUE)) {
1288 /* Pop and free the successful message */
1289 msg = g_queue_pop_head(api->msgs);
1290 fb_api_message_free(msg);
1291
1292 if (!g_queue_is_empty(api->msgs)) {
1293 msg = g_queue_peek_head(api->msgs);
1294 fb_api_message_send(api, msg);
1295 }
1296 } else {
1297 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
1298 "Failed to send message");
1299 }
1300
1301 g_object_unref(values);
1302 json_node_free(root);
1303 }
1304
1305 static gchar *
1306 fb_api_xma_parse(G_GNUC_UNUSED FbApi *api, const char *body, JsonNode *root,
1307 GError **error)
1308 {
1309 const gchar *str;
1310 const gchar *url;
1311 FbHttpParams *params;
1312 FbJsonValues *values;
1313 gchar *text;
1314 GError *err = NULL;
1315
1316 values = fb_json_values_new(root);
1317 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1318 "$.story_attachment.target.__type__.name");
1319 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1320 "$.story_attachment.url");
1321 fb_json_values_update(values, &err);
1322
1323 if (G_UNLIKELY(err != NULL)) {
1324 g_propagate_error(error, err);
1325 g_object_unref(values);
1326 return NULL;
1327 }
1328
1329 str = fb_json_values_next_str(values, NULL);
1330 url = fb_json_values_next_str(values, NULL);
1331
1332 if ((str == NULL) || (url == NULL)) {
1333 text = g_strdup(_("<Unsupported Attachment>"));
1334 g_object_unref(values);
1335 return text;
1336 }
1337
1338 if (purple_strequal(str, "ExternalUrl")) {
1339 params = fb_http_params_new_parse(url, TRUE);
1340 if (g_str_has_prefix(url, FB_API_FBRPC_PREFIX)) {
1341 text = fb_http_params_dup_str(params, "target_url", NULL);
1342 } else {
1343 text = fb_http_params_dup_str(params, "u", NULL);
1344 }
1345 fb_http_params_free(params);
1346 } else {
1347 text = g_strdup(url);
1348 }
1349
1350 if (fb_http_urlcmp(body, text, FALSE)) {
1351 g_free(text);
1352 g_object_unref(values);
1353 return NULL;
1354 }
1355
1356 g_object_unref(values);
1357 return text;
1358 }
1359
1360 static GSList *
1361 fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg,
1362 GSList *msgs, const gchar *body, JsonNode *root,
1363 GError **error)
1364 {
1365 const gchar *str;
1366 FbApiMessage *dmsg;
1367 FbId id;
1368 FbJsonValues *values;
1369 gchar *xma;
1370 GError *err = NULL;
1371 JsonNode *node;
1372 JsonNode *xode;
1373
1374 values = fb_json_values_new(root);
1375 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL");
1376 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid");
1377 fb_json_values_set_array(values, FALSE, "$.attachments");
1378
1379 while (fb_json_values_update(values, &err)) {
1380 str = fb_json_values_next_str(values, NULL);
1381
1382 if (str == NULL) {
1383 id = fb_json_values_next_int(values, 0);
1384 dmsg = g_memdup2(msg, sizeof(*msg));
1385 fb_api_attach(api, id, mid, dmsg);
1386 continue;
1387 }
1388
1389 node = fb_json_node_new(str, -1, &err);
1390
1391 if (G_UNLIKELY(err != NULL)) {
1392 break;
1393 }
1394
1395 xode = fb_json_node_get_nth(node, 0);
1396 xma = fb_api_xma_parse(api, body, xode, &err);
1397
1398 if (xma != NULL) {
1399 dmsg = g_memdup2(msg, sizeof(*msg));
1400 dmsg->text = xma;
1401 msgs = g_slist_prepend(msgs, dmsg);
1402 }
1403
1404 json_node_free(node);
1405
1406 if (G_UNLIKELY(err != NULL)) {
1407 break;
1408 }
1409 }
1410
1411 if (G_UNLIKELY(err != NULL)) {
1412 g_propagate_error(error, err);
1413 }
1414
1415 g_object_unref(values);
1416 return msgs;
1417 }
1418
1419
1420 static GSList *
1421 fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error);
1422
1423 static GSList *
1424 fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error);
1425
1426 static void
1427 fb_api_cb_publish_mst(FbThrift *thft, GError **error)
1428 {
1429 if (fb_thrift_read_isstop(thft)) {
1430 FB_API_TCHK(fb_thrift_read_stop(thft));
1431 } else {
1432 FbThriftType type;
1433 gint16 id;
1434
1435 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
1436 FB_API_TCHK(type == FB_THRIFT_TYPE_STRING);
1437 // FB_API_TCHK(id == 2);
1438 FB_API_TCHK(fb_thrift_read_str(thft, NULL));
1439 FB_API_TCHK(fb_thrift_read_stop(thft));
1440 }
1441 }
1442
1443 static void
1444 fb_api_cb_publish_ms(FbApi *api, GByteArray *pload)
1445 {
1446 const gchar *data;
1447 FbJsonValues *values;
1448 FbThrift *thft;
1449 gchar *stoken;
1450 GError *err = NULL;
1451 GList *elms, *l;
1452 GSList *msgs = NULL;
1453 GSList *events = NULL;
1454 guint size;
1455 JsonNode *root;
1456 JsonNode *node;
1457 JsonArray *arr;
1458
1459 static const struct {
1460 const gchar *member;
1461 FbApiEventType type;
1462 gboolean is_message;
1463 } event_types[] = {
1464 {"deltaNewMessage", 0, 1},
1465 {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC, 0},
1466 {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED, 0},
1467 {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED, 0},
1468 };
1469
1470 /* Read identifier string (for Facebook employees) */
1471 thft = fb_thrift_new(pload, 0);
1472 fb_api_cb_publish_mst(thft, &err);
1473 size = fb_thrift_get_pos(thft);
1474 g_object_unref(thft);
1475
1476 FB_API_ERROR_EMIT(api, err,
1477 return;
1478 );
1479
1480 g_return_if_fail(size < pload->len);
1481 data = (gchar *) pload->data + size;
1482 size = pload->len - size;
1483
1484 if (!fb_api_json_chk(api, data, size, &root)) {
1485 return;
1486 }
1487
1488 values = fb_json_values_new(root);
1489 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1490 "$.lastIssuedSeqId");
1491 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken");
1492 fb_json_values_update(values, &err);
1493
1494 FB_API_ERROR_EMIT(api, err,
1495 g_object_unref(values);
1496 json_node_free(root);
1497 return;
1498 );
1499
1500 api->sid = fb_json_values_next_int(values, 0);
1501 stoken = fb_json_values_next_str_dup(values, NULL);
1502 g_object_unref(values);
1503
1504 if (G_UNLIKELY(stoken != NULL)) {
1505 g_free(api->stoken);
1506 api->stoken = stoken;
1507 g_signal_emit_by_name(api, "connect");
1508 json_node_free(root);
1509 return;
1510 }
1511
1512 arr = fb_json_node_get_arr(root, "$.deltas", NULL);
1513 elms = json_array_get_elements(arr);
1514
1515 for (l = elms; l != NULL; l = l->next) {
1516 guint i = 0;
1517 JsonObject *o = json_node_get_object(l->data);
1518
1519 for (i = 0; i < G_N_ELEMENTS(event_types); i++) {
1520 if ((node = json_object_get_member(o, event_types[i].member))) {
1521 if (event_types[i].is_message) {
1522 msgs = fb_api_cb_publish_ms_new_message(
1523 api, node, msgs, &err
1524 );
1525 } else {
1526 events = fb_api_cb_publish_ms_event(
1527 api, node, events, event_types[i].type, &err
1528 );
1529 }
1530 }
1531 }
1532
1533 if (G_UNLIKELY(err != NULL)) {
1534 break;
1535 }
1536 }
1537
1538 g_list_free(elms);
1539 json_array_unref(arr);
1540
1541 if (G_LIKELY(err == NULL)) {
1542 if (msgs) {
1543 msgs = g_slist_reverse(msgs);
1544 g_signal_emit_by_name(api, "messages", msgs);
1545 }
1546
1547 if (events) {
1548 events = g_slist_reverse(events);
1549 g_signal_emit_by_name(api, "events", events);
1550 }
1551 } else {
1552 fb_api_error_emit(api, err);
1553 }
1554
1555 g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
1556 g_slist_free_full(events, (GDestroyNotify) fb_api_event_free);
1557 json_node_free(root);
1558 }
1559
1560 static GSList *
1561 fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error)
1562 {
1563 const gchar *body;
1564 const gchar *str;
1565 GError *err = NULL;
1566 FbApiMessage *dmsg;
1567 FbApiMessage msg;
1568 FbId id;
1569 FbId oid;
1570 FbJsonValues *values;
1571 JsonNode *node;
1572
1573 values = fb_json_values_new(root);
1574 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1575 "$.messageMetadata.offlineThreadingId");
1576 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1577 "$.messageMetadata.actorFbId");
1578 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1579 "$.messageMetadata"
1580 ".threadKey.otherUserFbId");
1581 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1582 "$.messageMetadata"
1583 ".threadKey.threadFbId");
1584 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1585 "$.messageMetadata.timestamp");
1586 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1587 "$.body");
1588 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1589 "$.stickerId");
1590 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1591 "$.messageMetadata.messageId");
1592
1593 if (fb_json_values_update(values, &err)) {
1594 id = fb_json_values_next_int(values, 0);
1595
1596 /* Ignore everything but new messages */
1597 if (id == 0) {
1598 goto beach;
1599 }
1600
1601 /* Ignore sequential duplicates */
1602 if (id == api->lastmid) {
1603 fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT, id);
1604 goto beach;
1605 }
1606
1607 api->lastmid = id;
1608 fb_api_message_reset(&msg, FALSE);
1609 msg.uid = fb_json_values_next_int(values, 0);
1610 oid = fb_json_values_next_int(values, 0);
1611 msg.tid = fb_json_values_next_int(values, 0);
1612 msg.tstamp = fb_json_values_next_int(values, 0);
1613
1614 if (msg.uid == api->uid) {
1615 msg.flags |= FB_API_MESSAGE_FLAG_SELF;
1616
1617 if (msg.tid == 0) {
1618 msg.uid = oid;
1619 }
1620 }
1621
1622 body = fb_json_values_next_str(values, NULL);
1623
1624 if (body != NULL) {
1625 dmsg = g_memdup2(&msg, sizeof(msg));
1626 dmsg->text = g_strdup(body);
1627 msgs = g_slist_prepend(msgs, dmsg);
1628 }
1629
1630 id = fb_json_values_next_int(values, 0);
1631
1632 if (id != 0) {
1633 dmsg = g_memdup2(&msg, sizeof(msg));
1634 fb_api_sticker(api, id, dmsg);
1635 }
1636
1637 str = fb_json_values_next_str(values, NULL);
1638
1639 if (str == NULL) {
1640 goto beach;
1641 }
1642
1643 node = fb_json_values_get_root(values);
1644 msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body,
1645 node, &err);
1646
1647 if (G_UNLIKELY(err != NULL)) {
1648 g_propagate_error(error, err);
1649 goto beach;
1650 }
1651 }
1652
1653 beach:
1654 g_object_unref(values);
1655 return msgs;
1656 }
1657
1658 static GSList *
1659 fb_api_cb_publish_ms_event(G_GNUC_UNUSED FbApi *api, JsonNode *root,
1660 GSList *events, FbApiEventType type, GError **error)
1661 {
1662 FbApiEvent *event;
1663 FbJsonValues *values = NULL;
1664 FbJsonValues *values_inner = NULL;
1665 GError *err = NULL;
1666
1667 values = fb_json_values_new(root);
1668 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1669 "$.messageMetadata.threadKey.threadFbId");
1670 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1671 "$.messageMetadata.actorFbId");
1672
1673 switch (type) {
1674 case FB_API_EVENT_TYPE_THREAD_TOPIC:
1675 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1676 "$.name");
1677 break;
1678
1679 case FB_API_EVENT_TYPE_THREAD_USER_ADDED:
1680 values_inner = fb_json_values_new(root);
1681
1682 fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE,
1683 "$.userFbId");
1684
1685 /* use the text field for the full name */
1686 fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE,
1687 "$.fullName");
1688
1689 fb_json_values_set_array(values_inner, FALSE,
1690 "$.addedParticipants");
1691 break;
1692
1693 case FB_API_EVENT_TYPE_THREAD_USER_REMOVED:
1694 fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
1695 "$.leftParticipantFbId");
1696
1697 /* use the text field for the kick message */
1698 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
1699 "$.messageMetadata.adminText");
1700 break;
1701 }
1702
1703 fb_json_values_update(values, &err);
1704
1705 event = g_new0(FbApiEvent, 1);
1706 event->type = type;
1707 event->tid = fb_json_values_next_int(values, 0);
1708 event->uid = fb_json_values_next_int(values, 0);
1709
1710 if (type == FB_API_EVENT_TYPE_THREAD_TOPIC) {
1711 event->text = fb_json_values_next_str_dup(values, NULL);
1712 } else if (type == FB_API_EVENT_TYPE_THREAD_USER_REMOVED) {
1713 /* overwrite actor with subject */
1714 event->uid = fb_json_values_next_int(values, 0);
1715 event->text = fb_json_values_next_str_dup(values, NULL);
1716 } else if (type == FB_API_EVENT_TYPE_THREAD_USER_ADDED) {
1717
1718 while (fb_json_values_update(values_inner, &err)) {
1719 FbApiEvent *devent = g_new0(FbApiEvent, 1);
1720
1721 devent->type = event->type;
1722 devent->uid = fb_json_values_next_int(values_inner, 0);
1723 devent->tid = event->tid;
1724 devent->text = fb_json_values_next_str_dup(values_inner, NULL);
1725
1726 events = g_slist_prepend(events, devent);
1727 }
1728 g_clear_pointer(&event, fb_api_event_free);
1729 g_object_unref(values_inner);
1730 }
1731
1732 g_object_unref(values);
1733
1734 if (G_UNLIKELY(err != NULL)) {
1735 g_propagate_error(error, err);
1736 } else if (event) {
1737 events = g_slist_prepend(events, event);
1738 }
1739
1740 return events;
1741 }
1742
1743 static void
1744 fb_api_cb_publish_pt(FbThrift *thft, GSList **presences, GError **error)
1745 {
1746 FbApiPresence *api_presence;
1747 FbThriftType type;
1748 gint16 id;
1749 gint32 i32;
1750 gint64 i64;
1751 guint i;
1752 guint size = 0;
1753
1754 /* Read identifier string (for Facebook employees) */
1755 FB_API_TCHK(fb_thrift_read_str(thft, NULL));
1756
1757 /* Read the full list boolean field */
1758 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
1759 FB_API_TCHK(type == FB_THRIFT_TYPE_BOOL);
1760 FB_API_TCHK(id == 1);
1761 FB_API_TCHK(fb_thrift_read_bool(thft, NULL));
1762
1763 /* Read the list field */
1764 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
1765 FB_API_TCHK(type == FB_THRIFT_TYPE_LIST);
1766 FB_API_TCHK(id == 2);
1767
1768 /* Read the list */
1769 FB_API_TCHK(fb_thrift_read_list(thft, &type, &size));
1770 FB_API_TCHK(type == FB_THRIFT_TYPE_STRUCT);
1771
1772 for (i = 0; i < size; i++) {
1773 /* Read the user identifier field */
1774 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
1775 FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
1776 FB_API_TCHK(id == 1);
1777 FB_API_TCHK(fb_thrift_read_i64(thft, &i64));
1778
1779 /* Read the active field */
1780 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
1781 FB_API_TCHK(type == FB_THRIFT_TYPE_I32);
1782 FB_API_TCHK(id == 2);
1783 FB_API_TCHK(fb_thrift_read_i32(thft, &i32));
1784
1785 api_presence = g_new0(FbApiPresence, 1);
1786 api_presence->uid = i64;
1787 api_presence->active = i32 != 0;
1788 *presences = g_slist_prepend(*presences, api_presence);
1789
1790 fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d",
1791 i64, i32 != 0, id);
1792
1793 while (id <= 6) {
1794 if (fb_thrift_read_isstop(thft)) {
1795 break;
1796 }
1797
1798 FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
1799
1800 switch (id) {
1801 case 3:
1802 /* Read the last active timestamp field */
1803 FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
1804 FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
1805 break;
1806
1807 case 4:
1808 /* Read the active client bits field */
1809 FB_API_TCHK(type == FB_THRIFT_TYPE_I16);
1810 FB_API_TCHK(fb_thrift_read_i16(thft, NULL));
1811 break;
1812
1813 case 5:
1814 /* Read the VoIP compatibility bits field */
1815 FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
1816 FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
1817 break;
1818
1819 case 6:
1820 /* Unknown new field */
1821 FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
1822 FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
1823 break;
1824
1825 default:
1826 /* Try to read unknown fields as varint */
1827 FB_API_TCHK(type == FB_THRIFT_TYPE_I16 ||
1828 type == FB_THRIFT_TYPE_I32 ||
1829 type == FB_THRIFT_TYPE_I64);
1830 FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
1831 break;
1832 }
1833 }
1834
1835 /* Read the field stop */
1836 FB_API_TCHK(fb_thrift_read_stop(thft));
1837 }
1838
1839 /* Read the field stop */
1840 if (fb_thrift_read_isstop(thft)) {
1841 FB_API_TCHK(fb_thrift_read_stop(thft));
1842 }
1843 }
1844
1845 static void
1846 fb_api_cb_publish_p(FbApi *api, GByteArray *pload)
1847 {
1848 FbThrift *thft;
1849 GError *err = NULL;
1850 GSList *presences = NULL;
1851
1852 thft = fb_thrift_new(pload, 0);
1853 fb_api_cb_publish_pt(thft, &presences, &err);
1854 g_object_unref(thft);
1855
1856 if (G_LIKELY(err == NULL)) {
1857 g_signal_emit_by_name(api, "presences", presences);
1858 } else {
1859 fb_api_error_emit(api, err);
1860 }
1861
1862 g_slist_free_full(presences, (GDestroyNotify)fb_api_presence_free);
1863 }
1864
1865 static void
1866 fb_api_cb_mqtt_publish(G_GNUC_UNUSED FbMqtt *mqtt, const char *topic,
1867 GByteArray *pload, gpointer data)
1868 {
1869 FbApi *api = data;
1870 gboolean comp;
1871 GByteArray *bytes;
1872 GError *err = NULL;
1873 guint i;
1874
1875 static const struct {
1876 const gchar *topic;
1877 void (*func) (FbApi *api, GByteArray *pload);
1878 } parsers[] = {
1879 {"/mark_thread_response", fb_api_cb_publish_mark},
1880 {"/mercury", fb_api_cb_publish_mercury},
1881 {"/orca_typing_notifications", fb_api_cb_publish_typing},
1882 {"/send_message_response", fb_api_cb_publish_ms_r},
1883 {"/t_ms", fb_api_cb_publish_ms},
1884 {"/t_p", fb_api_cb_publish_p}
1885 };
1886
1887 comp = fb_util_zlib_test(pload);
1888
1889 if (G_LIKELY(comp)) {
1890 bytes = fb_util_zlib_inflate(pload, &err);
1891 FB_API_ERROR_EMIT(api, err, return);
1892 } else {
1893 bytes = (GByteArray *) pload;
1894 }
1895
1896 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes,
1897 "Reading message (topic: %s)",
1898 topic);
1899
1900 for (i = 0; i < G_N_ELEMENTS(parsers); i++) {
1901 if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) {
1902 parsers[i].func(api, bytes);
1903 break;
1904 }
1905 }
1906
1907 if (G_LIKELY(comp)) {
1908 g_byte_array_free(bytes, TRUE);
1909 }
1910 }
1911
1912 FbApi *
1913 fb_api_new(PurpleConnection *gc, GProxyResolver *resolver)
1914 {
1915 FbApi *api;
1916
1917 api = g_object_new(FB_TYPE_API, NULL);
1918
1919 api->gc = gc;
1920 api->cons = soup_session_new_with_options(
1921 "proxy-resolver", resolver,
1922 "user-agent", FB_API_AGENT,
1923 NULL);
1924 api->mqtt = fb_mqtt_new(gc);
1925
1926 g_signal_connect(api->mqtt,
1927 "connect",
1928 G_CALLBACK(fb_api_cb_mqtt_connect),
1929 api);
1930 g_signal_connect(api->mqtt,
1931 "error",
1932 G_CALLBACK(fb_api_cb_mqtt_error),
1933 api);
1934 g_signal_connect(api->mqtt,
1935 "open",
1936 G_CALLBACK(fb_api_cb_mqtt_open),
1937 api);
1938 g_signal_connect(api->mqtt,
1939 "publish",
1940 G_CALLBACK(fb_api_cb_mqtt_publish),
1941 api);
1942
1943 return api;
1944 }
1945
1946 void
1947 fb_api_rehash(FbApi *api)
1948 {
1949 g_return_if_fail(FB_IS_API(api));
1950
1951 if (api->cid == NULL) {
1952 api->cid = fb_util_rand_alnum(32);
1953 }
1954
1955 if (api->did == NULL) {
1956 api->did = g_uuid_string_random();
1957 }
1958
1959 if (api->mid == 0) {
1960 api->mid = g_random_int();
1961 }
1962
1963 if (strlen(api->cid) > 20) {
1964 api->cid = g_realloc_n(api->cid , 21, sizeof *api->cid);
1965 api->cid[20] = 0;
1966 }
1967 }
1968
1969 gboolean
1970 fb_api_is_invisible(FbApi *api)
1971 {
1972 g_return_val_if_fail(FB_IS_API(api), FALSE);
1973
1974 return api->invisible;
1975 }
1976
1977 static void
1978 fb_api_error_literal(FbApi *api, FbApiError error, const gchar *msg)
1979 {
1980 GError *err;
1981
1982 g_return_if_fail(FB_IS_API(api));
1983
1984 err = g_error_new_literal(FB_API_ERROR, error, msg);
1985
1986 fb_api_error_emit(api, err);
1987 }
1988
1989 void
1990 fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...)
1991 {
1992 GError *err;
1993 va_list ap;
1994
1995 g_return_if_fail(FB_IS_API(api));
1996
1997 va_start(ap, format);
1998 err = g_error_new_valist(FB_API_ERROR, error, format, ap);
1999 va_end(ap);
2000
2001 fb_api_error_emit(api, err);
2002 }
2003
2004 void
2005 fb_api_error_emit(FbApi *api, GError *error)
2006 {
2007 g_return_if_fail(FB_IS_API(api));
2008 g_return_if_fail(error != NULL);
2009
2010 g_signal_emit_by_name(api, "error", error);
2011 g_error_free(error);
2012 }
2013
2014 static void
2015 fb_api_cb_attach(GObject *source, GAsyncResult *result, gpointer data) {
2016 SoupSession *session = SOUP_SESSION(source);
2017 SoupMessage *soupmsg = data;
2018 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2019 const gchar *str;
2020 FbApiMessage *msg;
2021 FbJsonValues *values;
2022 gchar *name;
2023 GError *err = NULL;
2024 GSList *msgs = NULL;
2025 guint i;
2026 JsonNode *root;
2027
2028 static const gchar *imgexts[] = {".jpg", ".png", ".gif"};
2029
2030 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2031 g_object_unref(soupmsg);
2032 return;
2033 }
2034
2035 values = fb_json_values_new(root);
2036 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename");
2037 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri");
2038 fb_json_values_update(values, &err);
2039
2040 FB_API_ERROR_EMIT(api, err,
2041 g_object_unref(values);
2042 json_node_free(root);
2043 g_object_unref(soupmsg);
2044 return;
2045 );
2046
2047 msg = g_object_steal_data(G_OBJECT(soupmsg), "fb-api-msg");
2048 str = fb_json_values_next_str(values, NULL);
2049 name = g_ascii_strdown(str, -1);
2050
2051 for (i = 0; i < G_N_ELEMENTS(imgexts); i++) {
2052 if (g_str_has_suffix(name, imgexts[i])) {
2053 msg->flags |= FB_API_MESSAGE_FLAG_IMAGE;
2054 break;
2055 }
2056 }
2057
2058 g_free(name);
2059 msg->text = fb_json_values_next_str_dup(values, NULL);
2060 msgs = g_slist_prepend(msgs, msg);
2061
2062 g_signal_emit_by_name(api, "messages", msgs);
2063 g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
2064 g_object_unref(values);
2065 json_node_free(root);
2066 g_object_unref(soupmsg);
2067 }
2068
2069 static void
2070 fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg)
2071 {
2072 FbHttpParams *prms;
2073 SoupMessage *http;
2074
2075 prms = fb_http_params_new();
2076 fb_http_params_set_str(prms, "mid", msgid);
2077 fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid);
2078
2079 http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment",
2080 "messaging.getAttachment", prms,
2081 fb_api_cb_attach);
2082 g_object_set_data_full(G_OBJECT(http), "fb-api-msg", msg,
2083 (GDestroyNotify)fb_api_message_free);
2084 }
2085
2086 static void
2087 fb_api_cb_auth(GObject *source, GAsyncResult *result, gpointer data) {
2088 SoupSession *session = SOUP_SESSION(source);
2089 SoupMessage *soupmsg = data;
2090 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2091 FbJsonValues *values;
2092 GError *err = NULL;
2093 JsonNode *root;
2094
2095 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2096 g_object_unref(soupmsg);
2097 return;
2098 }
2099
2100 values = fb_json_values_new(root);
2101 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token");
2102 fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid");
2103 fb_json_values_update(values, &err);
2104
2105 FB_API_ERROR_EMIT(api, err,
2106 g_object_unref(values);
2107 json_node_free(root);
2108 g_object_unref(soupmsg);
2109 return;
2110 );
2111
2112 g_free(api->token);
2113 api->token = fb_json_values_next_str_dup(values, NULL);
2114 api->uid = fb_json_values_next_int(values, 0);
2115
2116 g_signal_emit_by_name(api, "auth");
2117 g_object_unref(values);
2118 json_node_free(root);
2119 g_object_unref(soupmsg);
2120 }
2121
2122 void
2123 fb_api_auth(FbApi *api, const gchar *user, const gchar *pass)
2124 {
2125 FbHttpParams *prms;
2126
2127 prms = fb_http_params_new();
2128 fb_http_params_set_str(prms, "email", user);
2129 fb_http_params_set_str(prms, "password", pass);
2130 fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login",
2131 prms, fb_api_cb_auth);
2132 }
2133
2134 static gchar *
2135 fb_api_user_icon_checksum(gchar *icon)
2136 {
2137 gchar *csum;
2138 FbHttpParams *prms;
2139
2140 if (G_UNLIKELY(icon == NULL)) {
2141 return NULL;
2142 }
2143
2144 prms = fb_http_params_new_parse(icon, TRUE);
2145 csum = fb_http_params_dup_str(prms, "oh", NULL);
2146 fb_http_params_free(prms);
2147
2148 if (G_UNLIKELY(csum == NULL)) {
2149 /* Revert to the icon URL as the unique checksum */
2150 csum = g_strdup(icon);
2151 }
2152
2153 return csum;
2154 }
2155
2156 static void
2157 fb_api_cb_contact(GObject *source, GAsyncResult *result, gpointer data) {
2158 SoupSession *session = SOUP_SESSION(source);
2159 SoupMessage *soupmsg = data;
2160 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2161 const gchar *str;
2162 FbApiUser user;
2163 FbJsonValues *values;
2164 GError *err = NULL;
2165 JsonNode *node;
2166 JsonNode *root;
2167
2168 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2169 g_object_unref(soupmsg);
2170 return;
2171 }
2172
2173 node = fb_json_node_get_nth(root, 0);
2174
2175 if (node == NULL) {
2176 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
2177 _("Failed to obtain contact information"));
2178 json_node_free(root);
2179 g_object_unref(soupmsg);
2180 return;
2181 }
2182
2183 values = fb_json_values_new(node);
2184 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id");
2185 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.name");
2186 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2187 "$.profile_pic_large.uri");
2188 fb_json_values_update(values, &err);
2189
2190 FB_API_ERROR_EMIT(api, err,
2191 g_object_unref(values);
2192 json_node_free(root);
2193 g_object_unref(soupmsg);
2194 return;
2195 );
2196
2197 fb_api_user_reset(&user, FALSE);
2198 str = fb_json_values_next_str(values, "0");
2199 user.uid = FB_ID_FROM_STR(str);
2200 user.name = fb_json_values_next_str_dup(values, NULL);
2201 user.icon = fb_json_values_next_str_dup(values, NULL);
2202
2203 user.csum = fb_api_user_icon_checksum(user.icon);
2204
2205 g_signal_emit_by_name(api, "contact", &user);
2206 fb_api_user_reset(&user, TRUE);
2207 g_object_unref(values);
2208 json_node_free(root);
2209 g_object_unref(soupmsg);
2210 }
2211
2212 void
2213 fb_api_contact(FbApi *api, FbId uid)
2214 {
2215 JsonBuilder *bldr;
2216
2217 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2218 fb_json_bldr_arr_begin(bldr, "0");
2219 fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid);
2220 fb_json_bldr_arr_end(bldr);
2221
2222 fb_json_bldr_add_str(bldr, "1", "true");
2223 fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact);
2224 }
2225
2226 static GSList *
2227 fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
2228 {
2229 const gchar *str;
2230 FbApiUser *user;
2231 FbId uid;
2232 FbJsonValues *values;
2233 gboolean is_array;
2234 GError *err = NULL;
2235
2236 values = fb_json_values_new(root);
2237 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2238 "$.represented_profile.id");
2239 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2240 "$.represented_profile.friendship_status");
2241 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2242 "$.structured_name.text");
2243 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2244 "$.hugePictureUrl.uri");
2245
2246 is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY);
2247
2248 if (is_array) {
2249 fb_json_values_set_array(values, FALSE, "$");
2250 }
2251
2252 while (fb_json_values_update(values, &err)) {
2253 str = fb_json_values_next_str(values, "0");
2254 uid = FB_ID_FROM_STR(str);
2255 str = fb_json_values_next_str(values, NULL);
2256
2257 if ((!purple_strequal(str, "ARE_FRIENDS") &&
2258 (uid != api->uid)) || (uid == 0))
2259 {
2260 if (!is_array) {
2261 break;
2262 }
2263 continue;
2264 }
2265
2266 user = g_new0(FbApiUser, 1);
2267 user->uid = uid;
2268 user->name = fb_json_values_next_str_dup(values, NULL);
2269 user->icon = fb_json_values_next_str_dup(values, NULL);
2270
2271 user->csum = fb_api_user_icon_checksum(user->icon);
2272
2273 users = g_slist_prepend(users, user);
2274
2275 if (!is_array) {
2276 break;
2277 }
2278 }
2279
2280 g_object_unref(values);
2281
2282 return users;
2283 }
2284
2285 /* base64(contact:<our id>:<their id>:<whatever>) */
2286 static GSList *
2287 fb_api_cb_contacts_parse_removed(G_GNUC_UNUSED FbApi *api, JsonNode *node,
2288 GSList *users)
2289 {
2290 gsize len;
2291 char **split;
2292 char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len);
2293
2294 g_return_val_if_fail(decoded[len] == '\0', users);
2295 g_return_val_if_fail(len == strlen(decoded), users);
2296 g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users);
2297
2298 split = g_strsplit_set(decoded, ":", 4);
2299
2300 if (g_strv_length(split) != 4) {
2301 g_strfreev(split);
2302 g_return_val_if_reached(users);
2303 }
2304
2305 users = g_slist_prepend(users, g_strdup(split[2]));
2306
2307 g_strfreev(split);
2308 g_free(decoded);
2309
2310 return users;
2311 }
2312
2313 static void
2314 fb_api_cb_contacts(GObject *source, GAsyncResult *result, gpointer data) {
2315 SoupSession *session = SOUP_SESSION(source);
2316 SoupMessage *soupmsg = data;
2317 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2318 const gchar *cursor;
2319 const gchar *delta_cursor;
2320 FbJsonValues *values;
2321 gboolean complete;
2322 gboolean is_delta;
2323 GError *err = NULL;
2324 GList *l;
2325 GSList *users = NULL;
2326 JsonNode *root;
2327 JsonNode *croot;
2328 JsonNode *node;
2329
2330 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2331 g_object_unref(soupmsg);
2332 return;
2333 }
2334
2335 croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL);
2336 is_delta = (croot != NULL);
2337
2338 if (!is_delta) {
2339 croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL);
2340 node = fb_json_node_get(croot, "$.nodes", NULL);
2341 users = fb_api_cb_contacts_nodes(api, node, users);
2342 json_node_free(node);
2343
2344 } else {
2345 GSList *added = NULL;
2346 GSList *removed = NULL;
2347 JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL);
2348 GList *elms = json_array_get_elements(arr);
2349
2350 for (l = elms; l != NULL; l = l->next) {
2351 if ((node = fb_json_node_get(l->data, "$.added", NULL))) {
2352 added = fb_api_cb_contacts_nodes(api, node, added);
2353 json_node_free(node);
2354 }
2355
2356 if ((node = fb_json_node_get(l->data, "$.removed", NULL))) {
2357 removed = fb_api_cb_contacts_parse_removed(api, node, removed);
2358 json_node_free(node);
2359 }
2360 }
2361
2362 g_signal_emit_by_name(api, "contacts-delta", added, removed);
2363
2364 g_slist_free_full(added, (GDestroyNotify) fb_api_user_free);
2365 g_slist_free_full(removed, g_free);
2366
2367 g_list_free(elms);
2368 json_array_unref(arr);
2369 }
2370
2371 values = fb_json_values_new(croot);
2372 fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE,
2373 "$.page_info.has_next_page");
2374 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2375 "$.page_info.delta_cursor");
2376 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2377 "$.page_info.end_cursor");
2378 fb_json_values_update(values, NULL);
2379
2380 complete = !fb_json_values_next_bool(values, FALSE);
2381
2382 delta_cursor = fb_json_values_next_str(values, NULL);
2383
2384 cursor = fb_json_values_next_str(values, NULL);
2385
2386 if (G_UNLIKELY(err == NULL)) {
2387 if (is_delta || complete) {
2388 g_free(api->contacts_delta);
2389 api->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor);
2390 }
2391
2392 if (users) {
2393 g_signal_emit_by_name(api, "contacts", users, complete);
2394 }
2395
2396 if (!complete) {
2397 fb_api_contacts_after(api, cursor);
2398 }
2399 } else {
2400 fb_api_error_emit(api, err);
2401 }
2402
2403 g_slist_free_full(users, (GDestroyNotify) fb_api_user_free);
2404 g_object_unref(values);
2405
2406 json_node_free(croot);
2407 json_node_free(root);
2408 g_object_unref(soupmsg);
2409 }
2410
2411 void
2412 fb_api_contacts(FbApi *api)
2413 {
2414 JsonBuilder *bldr;
2415
2416 g_return_if_fail(FB_IS_API(api));
2417
2418 if (api->contacts_delta) {
2419 fb_api_contacts_delta(api, api->contacts_delta);
2420 return;
2421 }
2422
2423 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2424 fb_json_bldr_arr_begin(bldr, "0");
2425 fb_json_bldr_add_str(bldr, NULL, "user");
2426 fb_json_bldr_arr_end(bldr);
2427
2428 fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT));
2429 fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr,
2430 fb_api_cb_contacts);
2431 }
2432
2433 static void
2434 fb_api_contacts_after(FbApi *api, const gchar *cursor)
2435 {
2436 JsonBuilder *bldr;
2437
2438 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2439 fb_json_bldr_arr_begin(bldr, "0");
2440 fb_json_bldr_add_str(bldr, NULL, "user");
2441 fb_json_bldr_arr_end(bldr);
2442
2443 fb_json_bldr_add_str(bldr, "1", cursor);
2444 fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT));
2445 fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr,
2446 fb_api_cb_contacts);
2447 }
2448
2449 void
2450 fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor)
2451 {
2452 JsonBuilder *bldr;
2453
2454 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2455
2456 fb_json_bldr_add_str(bldr, "0", delta_cursor);
2457
2458 fb_json_bldr_arr_begin(bldr, "1");
2459 fb_json_bldr_add_str(bldr, NULL, "user");
2460 fb_json_bldr_arr_end(bldr);
2461
2462 fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT));
2463 fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr,
2464 fb_api_cb_contacts);
2465 }
2466
2467 void
2468 fb_api_connect(FbApi *api, gboolean invisible)
2469 {
2470 g_return_if_fail(FB_IS_API(api));
2471
2472 api->invisible = invisible;
2473 fb_mqtt_open(api->mqtt, FB_MQTT_HOST, FB_MQTT_PORT);
2474 }
2475
2476 void
2477 fb_api_disconnect(FbApi *api)
2478 {
2479 g_return_if_fail(FB_IS_API(api));
2480
2481 fb_mqtt_disconnect(api->mqtt);
2482 }
2483
2484 static void
2485 fb_api_message_send(FbApi *api, FbApiMessage *msg)
2486 {
2487 const gchar *tpfx;
2488 FbId id;
2489 FbId mid;
2490 gchar *json;
2491 JsonBuilder *bldr;
2492
2493 mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int());
2494 api->lastmid = mid;
2495
2496 if (msg->tid != 0) {
2497 tpfx = "tfbid_";
2498 id = msg->tid;
2499 } else {
2500 tpfx = "";
2501 id = msg->uid;
2502 }
2503
2504 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2505 fb_json_bldr_add_str(bldr, "body", msg->text);
2506 fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid);
2507 fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, api->uid);
2508 fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id);
2509
2510 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
2511 fb_api_publish(api, "/send_message2", "%s", json);
2512 g_free(json);
2513 }
2514
2515 void
2516 fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text)
2517 {
2518 FbApiMessage *msg;
2519 gboolean empty;
2520
2521 g_return_if_fail(FB_IS_API(api));
2522 g_return_if_fail(text != NULL);
2523
2524 msg = g_new0(FbApiMessage, 1);
2525 msg->text = g_strdup(text);
2526
2527 if (thread) {
2528 msg->tid = id;
2529 } else {
2530 msg->uid = id;
2531 }
2532
2533 empty = g_queue_is_empty(api->msgs);
2534 g_queue_push_tail(api->msgs, msg);
2535
2536 if (empty && fb_mqtt_connected(api->mqtt, FALSE)) {
2537 fb_api_message_send(api, msg);
2538 }
2539 }
2540
2541 void
2542 fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...)
2543 {
2544 GByteArray *bytes;
2545 GByteArray *cytes;
2546 gchar *msg;
2547 GError *err = NULL;
2548 va_list ap;
2549
2550 g_return_if_fail(FB_IS_API(api));
2551 g_return_if_fail(topic != NULL);
2552 g_return_if_fail(format != NULL);
2553
2554 va_start(ap, format);
2555 msg = g_strdup_vprintf(format, ap);
2556 va_end(ap);
2557
2558 bytes = g_byte_array_new_take((guint8 *) msg, strlen(msg));
2559 cytes = fb_util_zlib_deflate(bytes, &err);
2560
2561 FB_API_ERROR_EMIT(api, err,
2562 g_byte_array_free(bytes, TRUE);
2563 return;
2564 );
2565
2566 fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes,
2567 "Writing message (topic: %s)",
2568 topic);
2569
2570 fb_mqtt_publish(api->mqtt, topic, cytes);
2571 g_byte_array_free(cytes, TRUE);
2572 g_byte_array_free(bytes, TRUE);
2573 }
2574
2575 void
2576 fb_api_read(FbApi *api, FbId id, gboolean thread)
2577 {
2578 const gchar *key;
2579 gchar *json;
2580 JsonBuilder *bldr;
2581
2582 g_return_if_fail(FB_IS_API(api));
2583
2584 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2585 fb_json_bldr_add_bool(bldr, "state", TRUE);
2586 fb_json_bldr_add_int(bldr, "syncSeqId", api->sid);
2587 fb_json_bldr_add_str(bldr, "mark", "read");
2588
2589 key = thread ? "threadFbId" : "otherUserFbId";
2590 fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id);
2591
2592 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
2593 fb_api_publish(api, "/mark_thread", "%s", json);
2594 g_free(json);
2595 }
2596
2597 static GSList *
2598 fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg,
2599 GSList *msgs, JsonNode *root, GError **error)
2600 {
2601 const gchar *str;
2602 FbApiMessage *dmsg;
2603 FbId id;
2604 FbJsonValues *values;
2605 GError *err = NULL;
2606
2607 values = fb_json_values_new(root);
2608 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2609 "$.attachment_fbid");
2610 fb_json_values_set_array(values, FALSE, "$.blob_attachments");
2611
2612 while (fb_json_values_update(values, &err)) {
2613 str = fb_json_values_next_str(values, NULL);
2614 id = FB_ID_FROM_STR(str);
2615 dmsg = g_memdup2(msg, sizeof(*msg));
2616 fb_api_attach(api, id, mid, dmsg);
2617 }
2618
2619 if (G_UNLIKELY(err != NULL)) {
2620 g_propagate_error(error, err);
2621 }
2622
2623 g_object_unref(values);
2624 return msgs;
2625 }
2626
2627 static void
2628 fb_api_cb_unread_msgs(GObject *source, GAsyncResult *result, gpointer data) {
2629 SoupSession *session = SOUP_SESSION(source);
2630 SoupMessage *soupmsg = data;
2631 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2632 const gchar *body;
2633 const gchar *str;
2634 FbApiMessage *dmsg;
2635 FbApiMessage msg;
2636 FbId id;
2637 FbId tid;
2638 FbJsonValues *values;
2639 gchar *xma;
2640 GError *err = NULL;
2641 GSList *msgs = NULL;
2642 JsonNode *node;
2643 JsonNode *root;
2644 JsonNode *xode;
2645
2646 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2647 g_object_unref(soupmsg);
2648 return;
2649 }
2650
2651 node = fb_json_node_get_nth(root, 0);
2652
2653 if (node == NULL) {
2654 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
2655 _("Failed to obtain unread messages"));
2656 json_node_free(root);
2657 g_object_unref(soupmsg);
2658 return;
2659 }
2660
2661 values = fb_json_values_new(node);
2662 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2663 "$.thread_key.thread_fbid");
2664 fb_json_values_update(values, &err);
2665
2666 FB_API_ERROR_EMIT(api, err,
2667 g_object_unref(values);
2668 g_object_unref(soupmsg);
2669 return;
2670 );
2671
2672 fb_api_message_reset(&msg, FALSE);
2673 str = fb_json_values_next_str(values, "0");
2674 tid = FB_ID_FROM_STR(str);
2675 g_object_unref(values);
2676
2677 values = fb_json_values_new(node);
2678 fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread");
2679 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2680 "$.message_sender.messaging_actor.id");
2681 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text");
2682 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2683 "$.timestamp_precise");
2684 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id");
2685 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id");
2686 fb_json_values_set_array(values, FALSE, "$.messages.nodes");
2687
2688 while (fb_json_values_update(values, &err)) {
2689 if (!fb_json_values_next_bool(values, FALSE)) {
2690 continue;
2691 }
2692
2693 str = fb_json_values_next_str(values, "0");
2694 body = fb_json_values_next_str(values, NULL);
2695
2696 fb_api_message_reset(&msg, FALSE);
2697 msg.uid = FB_ID_FROM_STR(str);
2698 msg.tid = tid;
2699
2700 str = fb_json_values_next_str(values, "0");
2701 msg.tstamp = g_ascii_strtoll(str, NULL, 10);
2702
2703 if (body != NULL) {
2704 dmsg = g_memdup2(&msg, sizeof(msg));
2705 dmsg->text = g_strdup(body);
2706 msgs = g_slist_prepend(msgs, dmsg);
2707 }
2708
2709 str = fb_json_values_next_str(values, NULL);
2710
2711 if (str != NULL) {
2712 dmsg = g_memdup2(&msg, sizeof(msg));
2713 id = FB_ID_FROM_STR(str);
2714 fb_api_sticker(api, id, dmsg);
2715 }
2716
2717 node = fb_json_values_get_root(values);
2718 xode = fb_json_node_get(node, "$.extensible_attachment", NULL);
2719
2720 if (xode != NULL) {
2721 xma = fb_api_xma_parse(api, body, xode, &err);
2722
2723 if (xma != NULL) {
2724 dmsg = g_memdup2(&msg, sizeof(msg));
2725 dmsg->text = xma;
2726 msgs = g_slist_prepend(msgs, dmsg);
2727 }
2728
2729 json_node_free(xode);
2730
2731 if (G_UNLIKELY(err != NULL)) {
2732 break;
2733 }
2734 }
2735
2736 str = fb_json_values_next_str(values, NULL);
2737
2738 if (str == NULL) {
2739 continue;
2740 }
2741
2742 msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs,
2743 node, &err);
2744
2745 if (G_UNLIKELY(err != NULL)) {
2746 break;
2747 }
2748 }
2749
2750 if (G_UNLIKELY(err == NULL)) {
2751 msgs = g_slist_reverse(msgs);
2752 g_signal_emit_by_name(api, "messages", msgs);
2753 } else {
2754 fb_api_error_emit(api, err);
2755 }
2756
2757 g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
2758 g_object_unref(values);
2759 json_node_free(root);
2760 g_object_unref(soupmsg);
2761 }
2762
2763 static void
2764 fb_api_cb_unread(GObject *source, GAsyncResult *result, gpointer data) {
2765 SoupSession *session = SOUP_SESSION(source);
2766 SoupMessage *soupmsg = data;
2767 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2768 const gchar *id;
2769 FbJsonValues *values;
2770 GError *err = NULL;
2771 gint64 count;
2772 JsonBuilder *bldr;
2773 JsonNode *root;
2774
2775 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2776 g_object_unref(soupmsg);
2777 return;
2778 }
2779
2780 values = fb_json_values_new(root);
2781 fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count");
2782 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2783 "$.thread_key.other_user_id");
2784 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2785 "$.thread_key.thread_fbid");
2786 fb_json_values_set_array(values, FALSE, "$.viewer.message_threads"
2787 ".nodes");
2788
2789 while (fb_json_values_update(values, &err)) {
2790 count = fb_json_values_next_int(values, -5);
2791
2792 if (count < 1) {
2793 continue;
2794 }
2795
2796 id = fb_json_values_next_str(values, NULL);
2797
2798 if (id == NULL) {
2799 id = fb_json_values_next_str(values, "0");
2800 }
2801
2802 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2803 fb_json_bldr_arr_begin(bldr, "0");
2804 fb_json_bldr_add_str(bldr, NULL, id);
2805 fb_json_bldr_arr_end(bldr);
2806
2807 fb_json_bldr_add_str(bldr, "10", "true");
2808 fb_json_bldr_add_str(bldr, "11", "true");
2809 fb_json_bldr_add_int(bldr, "12", count);
2810 fb_json_bldr_add_str(bldr, "13", "false");
2811 fb_api_http_query(api, FB_API_QUERY_THREAD, bldr,
2812 fb_api_cb_unread_msgs);
2813 }
2814
2815 if (G_UNLIKELY(err != NULL)) {
2816 fb_api_error_emit(api, err);
2817 }
2818
2819 g_object_unref(values);
2820 json_node_free(root);
2821 g_object_unref(soupmsg);
2822 }
2823
2824 void
2825 fb_api_unread(FbApi *api)
2826 {
2827 JsonBuilder *bldr;
2828
2829 g_return_if_fail(FB_IS_API(api));
2830
2831 if (api->unread < 1) {
2832 return;
2833 }
2834
2835 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2836 fb_json_bldr_add_str(bldr, "2", "true");
2837 fb_json_bldr_add_int(bldr, "1", api->unread);
2838 fb_json_bldr_add_str(bldr, "12", "true");
2839 fb_json_bldr_add_str(bldr, "13", "false");
2840 fb_api_http_query(api, FB_API_QUERY_THREADS, bldr,
2841 fb_api_cb_unread);
2842 }
2843
2844 static void
2845 fb_api_cb_sticker(GObject *source, GAsyncResult *result, gpointer data) {
2846 SoupSession *session = SOUP_SESSION(source);
2847 SoupMessage *soupmsg = data;
2848 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2849 FbApiMessage *msg;
2850 FbJsonValues *values;
2851 GError *err = NULL;
2852 GSList *msgs = NULL;
2853 JsonNode *node;
2854 JsonNode *root;
2855
2856 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2857 g_object_unref(soupmsg);
2858 return;
2859 }
2860
2861 node = fb_json_node_get_nth(root, 0);
2862 values = fb_json_values_new(node);
2863 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2864 "$.thread_image.uri");
2865 fb_json_values_update(values, &err);
2866
2867 FB_API_ERROR_EMIT(api, err,
2868 g_object_unref(values);
2869 json_node_free(root);
2870 g_object_unref(soupmsg);
2871 return;
2872 );
2873
2874 msg = g_object_steal_data(G_OBJECT(soupmsg), "fb-api-msg");
2875 msg->flags |= FB_API_MESSAGE_FLAG_IMAGE;
2876 msg->text = fb_json_values_next_str_dup(values, NULL);
2877 msgs = g_slist_prepend(msgs, msg);
2878
2879 g_signal_emit_by_name(api, "messages", msgs);
2880 g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
2881 g_object_unref(values);
2882 json_node_free(root);
2883 g_object_unref(soupmsg);
2884 }
2885
2886 static void
2887 fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg)
2888 {
2889 JsonBuilder *bldr;
2890 SoupMessage *http;
2891
2892 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
2893 fb_json_bldr_arr_begin(bldr, "0");
2894 fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid);
2895 fb_json_bldr_arr_end(bldr);
2896
2897 http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr,
2898 fb_api_cb_sticker);
2899 g_object_set_data_full(G_OBJECT(http), "fb-api-msg", msg,
2900 (GDestroyNotify)fb_api_message_free);
2901 }
2902
2903 static gboolean
2904 fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root,
2905 GError **error)
2906 {
2907 const gchar *str;
2908 FbApiUser *user;
2909 FbId uid;
2910 FbJsonValues *values;
2911 gboolean haself = FALSE;
2912 guint num_users = 0;
2913 GError *err = NULL;
2914
2915 values = fb_json_values_new(root);
2916 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
2917 "$.thread_key.thread_fbid");
2918 fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name");
2919 fb_json_values_update(values, &err);
2920
2921 if (G_UNLIKELY(err != NULL)) {
2922 g_propagate_error(error, err);
2923 g_object_unref(values);
2924 return FALSE;
2925 }
2926
2927 str = fb_json_values_next_str(values, NULL);
2928
2929 if (str == NULL) {
2930 g_object_unref(values);
2931 return FALSE;
2932 }
2933
2934 thrd->tid = FB_ID_FROM_STR(str);
2935 thrd->topic = fb_json_values_next_str_dup(values, NULL);
2936 g_object_unref(values);
2937
2938 values = fb_json_values_new(root);
2939 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2940 "$.messaging_actor.id");
2941 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
2942 "$.messaging_actor.name");
2943 fb_json_values_set_array(values, TRUE, "$.all_participants.nodes");
2944
2945 while (fb_json_values_update(values, &err)) {
2946 str = fb_json_values_next_str(values, "0");
2947 uid = FB_ID_FROM_STR(str);
2948 num_users++;
2949
2950 if (uid != api->uid) {
2951 user = g_new0(FbApiUser, 1);
2952 user->uid = uid;
2953 user->name = fb_json_values_next_str_dup(values, NULL);
2954 thrd->users = g_slist_prepend(thrd->users, user);
2955 } else {
2956 haself = TRUE;
2957 }
2958 }
2959
2960 if (G_UNLIKELY(err != NULL)) {
2961 g_propagate_error(error, err);
2962 fb_api_thread_reset(thrd, TRUE);
2963 g_object_unref(values);
2964 return FALSE;
2965 }
2966
2967 if (num_users < 2 || !haself) {
2968 g_object_unref(values);
2969 return FALSE;
2970 }
2971
2972 g_object_unref(values);
2973 return TRUE;
2974 }
2975
2976 static void
2977 fb_api_cb_thread(GObject *source, GAsyncResult *result, gpointer data) {
2978 SoupSession *session = SOUP_SESSION(source);
2979 SoupMessage *soupmsg = data;
2980 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
2981 FbApiThread thrd;
2982 GError *err = NULL;
2983 JsonNode *node;
2984 JsonNode *root;
2985
2986 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
2987 g_object_unref(soupmsg);
2988 return;
2989 }
2990
2991 node = fb_json_node_get_nth(root, 0);
2992
2993 if (node == NULL) {
2994 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
2995 _("Failed to obtain thread information"));
2996 json_node_free(root);
2997 g_object_unref(soupmsg);
2998 return;
2999 }
3000
3001 fb_api_thread_reset(&thrd, FALSE);
3002
3003 if (!fb_api_thread_parse(api, &thrd, node, &err)) {
3004 if (G_LIKELY(err == NULL)) {
3005 if (thrd.tid) {
3006 g_signal_emit_by_name(api, "thread-kicked", &thrd);
3007 } else {
3008 fb_api_error_literal(api, FB_API_ERROR_GENERAL,
3009 _("Failed to parse thread information"));
3010 }
3011 } else {
3012 fb_api_error_emit(api, err);
3013 }
3014 } else {
3015 g_signal_emit_by_name(api, "thread", &thrd);
3016 }
3017
3018 fb_api_thread_reset(&thrd, TRUE);
3019 json_node_free(root);
3020 g_object_unref(soupmsg);
3021 }
3022
3023 void
3024 fb_api_thread(FbApi *api, FbId tid)
3025 {
3026 JsonBuilder *bldr;
3027
3028 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
3029 fb_json_bldr_arr_begin(bldr, "0");
3030 fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid);
3031 fb_json_bldr_arr_end(bldr);
3032
3033 fb_json_bldr_add_str(bldr, "10", "false");
3034 fb_json_bldr_add_str(bldr, "11", "false");
3035 fb_json_bldr_add_str(bldr, "13", "false");
3036 fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread);
3037 }
3038
3039 static void
3040 fb_api_cb_thread_create(GObject *source, GAsyncResult *result, gpointer data) {
3041 SoupSession *session = SOUP_SESSION(source);
3042 SoupMessage *soupmsg = data;
3043 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
3044 const gchar *str;
3045 FbId tid;
3046 FbJsonValues *values;
3047 GError *err = NULL;
3048 JsonNode *root;
3049
3050 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
3051 g_object_unref(soupmsg);
3052 return;
3053 }
3054
3055 values = fb_json_values_new(root);
3056 fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id");
3057 fb_json_values_update(values, &err);
3058
3059 FB_API_ERROR_EMIT(api, err,
3060 g_object_unref(values);
3061 json_node_free(root);
3062 g_object_unref(soupmsg);
3063 return;
3064 );
3065
3066 str = fb_json_values_next_str(values, "0");
3067 tid = FB_ID_FROM_STR(str);
3068 g_signal_emit_by_name(api, "thread-create", tid);
3069
3070 g_object_unref(values);
3071 json_node_free(root);
3072 g_object_unref(soupmsg);
3073 }
3074
3075 void
3076 fb_api_thread_create(FbApi *api, GSList *uids)
3077 {
3078 FbHttpParams *prms;
3079 FbId *uid;
3080 gchar *json;
3081 GSList *l;
3082 JsonBuilder *bldr;
3083
3084 g_return_if_fail(FB_IS_API(api));
3085 g_warn_if_fail(g_slist_length(uids) > 1);
3086
3087 bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
3088 fb_json_bldr_obj_begin(bldr, NULL);
3089 fb_json_bldr_add_str(bldr, "type", "id");
3090 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, api->uid);
3091 fb_json_bldr_obj_end(bldr);
3092
3093 for (l = uids; l != NULL; l = l->next) {
3094 uid = l->data;
3095 fb_json_bldr_obj_begin(bldr, NULL);
3096 fb_json_bldr_add_str(bldr, "type", "id");
3097 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid);
3098 fb_json_bldr_obj_end(bldr);
3099 }
3100
3101 json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
3102 prms = fb_http_params_new();
3103 fb_http_params_set_str(prms, "recipients", json);
3104 fb_api_http_req(api, FB_API_URL_THREADS, "createGroup", "POST",
3105 prms, fb_api_cb_thread_create);
3106 g_free(json);
3107 }
3108
3109 void
3110 fb_api_thread_invite(FbApi *api, FbId tid, FbId uid)
3111 {
3112 FbHttpParams *prms;
3113 gchar *json;
3114 JsonBuilder *bldr;
3115
3116 bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
3117 fb_json_bldr_obj_begin(bldr, NULL);
3118 fb_json_bldr_add_str(bldr, "type", "id");
3119 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid);
3120 fb_json_bldr_obj_end(bldr);
3121 json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
3122
3123 prms = fb_http_params_new();
3124 fb_http_params_set_str(prms, "to", json);
3125 fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid);
3126 fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST",
3127 prms, fb_api_cb_http_bool);
3128 g_free(json);
3129 }
3130
3131 void
3132 fb_api_thread_remove(FbApi *api, FbId tid, FbId uid)
3133 {
3134 FbHttpParams *prms;
3135 gchar *json;
3136 JsonBuilder *bldr;
3137
3138 g_return_if_fail(FB_IS_API(api));
3139
3140 prms = fb_http_params_new();
3141 fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid);
3142
3143 if (uid == 0) {
3144 uid = api->uid;
3145 }
3146
3147 if (uid != api->uid) {
3148 bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
3149 fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid);
3150 json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
3151 fb_http_params_set_str(prms, "to", json);
3152 g_free(json);
3153 }
3154
3155 fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE",
3156 prms, fb_api_cb_http_bool);
3157 }
3158
3159 void
3160 fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic)
3161 {
3162 FbHttpParams *prms;
3163
3164 prms = fb_http_params_new();
3165 fb_http_params_set_str(prms, "name", topic);
3166 fb_http_params_set_int(prms, "tid", tid);
3167 fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName",
3168 "messaging.setthreadname", prms, fb_api_cb_http_bool);
3169 }
3170
3171 static void
3172 fb_api_cb_threads(GObject *source, GAsyncResult *result, gpointer data) {
3173 SoupSession *session = SOUP_SESSION(source);
3174 SoupMessage *soupmsg = data;
3175 FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
3176 FbApiThread *dthrd;
3177 FbApiThread thrd;
3178 GError *err = NULL;
3179 GList *elms;
3180 GList *l;
3181 GSList *thrds = NULL;
3182 JsonArray *arr;
3183 JsonNode *root;
3184
3185 if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
3186 g_object_unref(soupmsg);
3187 return;
3188 }
3189
3190 arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes",
3191 &err);
3192 FB_API_ERROR_EMIT(api, err,
3193 json_node_free(root);
3194 g_object_unref(soupmsg);
3195 return;
3196 );
3197
3198 elms = json_array_get_elements(arr);
3199
3200 for (l = elms; l != NULL; l = l->next) {
3201 fb_api_thread_reset(&thrd, FALSE);
3202
3203 if (fb_api_thread_parse(api, &thrd, l->data, &err)) {
3204 dthrd = g_memdup2(&thrd, sizeof(thrd));
3205 thrds = g_slist_prepend(thrds, dthrd);
3206 } else {
3207 fb_api_thread_reset(&thrd, TRUE);
3208 }
3209
3210 if (G_UNLIKELY(err != NULL)) {
3211 break;
3212 }
3213 }
3214
3215 if (G_LIKELY(err == NULL)) {
3216 thrds = g_slist_reverse(thrds);
3217 g_signal_emit_by_name(api, "threads", thrds);
3218 } else {
3219 fb_api_error_emit(api, err);
3220 }
3221
3222 g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free);
3223 g_list_free(elms);
3224 json_array_unref(arr);
3225 json_node_free(root);
3226 g_object_unref(soupmsg);
3227 }
3228
3229 void
3230 fb_api_threads(FbApi *api)
3231 {
3232 JsonBuilder *bldr;
3233
3234 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
3235 fb_json_bldr_add_str(bldr, "2", "true");
3236 fb_json_bldr_add_str(bldr, "12", "false");
3237 fb_json_bldr_add_str(bldr, "13", "false");
3238 fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads);
3239 }
3240
3241 void
3242 fb_api_typing(FbApi *api, FbId uid, gboolean state)
3243 {
3244 gchar *json;
3245 JsonBuilder *bldr;
3246
3247 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
3248 fb_json_bldr_add_int(bldr, "state", state != 0);
3249 fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid);
3250
3251 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
3252 fb_api_publish(api, "/typing", "%s", json);
3253 g_free(json);
3254 }
3255
3256 FbApiEvent *
3257 fb_api_event_dup(const FbApiEvent *event)
3258 {
3259 FbApiEvent *ret;
3260
3261 if (event == NULL) {
3262 return NULL;
3263 }
3264
3265 ret = g_memdup2(event, sizeof *event);
3266 ret->text = g_strdup(event->text);
3267
3268 return ret;
3269 }
3270
3271 void
3272 fb_api_event_reset(FbApiEvent *event, gboolean deep)
3273 {
3274 g_return_if_fail(event != NULL);
3275
3276 if (deep) {
3277 g_free(event->text);
3278 }
3279
3280 memset(event, 0, sizeof *event);
3281 }
3282
3283 void
3284 fb_api_event_free(FbApiEvent *event)
3285 {
3286 if (G_LIKELY(event != NULL)) {
3287 g_free(event->text);
3288 g_free(event);
3289 }
3290 }
3291
3292 G_DEFINE_BOXED_TYPE(FbApiEvent, fb_api_event, fb_api_event_dup,
3293 fb_api_event_free)
3294
3295 FbApiMessage *
3296 fb_api_message_dup(const FbApiMessage *msg)
3297 {
3298 FbApiMessage *ret;
3299
3300 if (msg == NULL) {
3301 return NULL;
3302 }
3303
3304 ret = g_memdup2(msg, sizeof *msg);
3305 ret->text = g_strdup(msg->text);
3306
3307 return ret;
3308 }
3309
3310 void
3311 fb_api_message_reset(FbApiMessage *msg, gboolean deep)
3312 {
3313 g_return_if_fail(msg != NULL);
3314
3315 if (deep) {
3316 g_free(msg->text);
3317 }
3318
3319 memset(msg, 0, sizeof *msg);
3320 }
3321
3322 void
3323 fb_api_message_free(FbApiMessage *msg)
3324 {
3325 if (G_LIKELY(msg != NULL)) {
3326 g_free(msg->text);
3327 g_free(msg);
3328 }
3329 }
3330
3331 G_DEFINE_BOXED_TYPE(FbApiMessage, fb_api_message, fb_api_message_dup,
3332 fb_api_message_free)
3333
3334 FbApiPresence *
3335 fb_api_presence_dup(const FbApiPresence *presence)
3336 {
3337 return g_memdup2(presence, sizeof *presence);
3338 }
3339
3340 void
3341 fb_api_presence_reset(FbApiPresence *presence)
3342 {
3343 g_return_if_fail(presence != NULL);
3344 memset(presence, 0, sizeof *presence);
3345 }
3346
3347 void
3348 fb_api_presence_free(FbApiPresence *presence)
3349 {
3350 if (G_LIKELY(presence != NULL)) {
3351 g_free(presence);
3352 }
3353 }
3354
3355 G_DEFINE_BOXED_TYPE(FbApiPresence, fb_api_presence, fb_api_presence_dup,
3356 fb_api_presence_free)
3357
3358 FbApiThread *
3359 fb_api_thread_dup(const FbApiThread *thrd)
3360 {
3361 FbApiThread *ret;
3362
3363 if (thrd == NULL) {
3364 return NULL;
3365 }
3366
3367 ret = g_memdup2(thrd, sizeof *thrd);
3368 ret->topic = g_strdup(thrd->topic);
3369 ret->users = g_slist_copy_deep(thrd->users,
3370 (GCopyFunc)(GCallback)fb_api_user_dup,
3371 NULL);
3372
3373 return ret;
3374 }
3375
3376 void
3377 fb_api_thread_reset(FbApiThread *thrd, gboolean deep)
3378 {
3379 g_return_if_fail(thrd != NULL);
3380
3381 if (deep) {
3382 g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free);
3383 g_free(thrd->topic);
3384 }
3385
3386 memset(thrd, 0, sizeof *thrd);
3387 }
3388
3389 void
3390 fb_api_thread_free(FbApiThread *thrd)
3391 {
3392 if (G_LIKELY(thrd != NULL)) {
3393 g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free);
3394 g_free(thrd->topic);
3395 g_free(thrd);
3396 }
3397 }
3398
3399 G_DEFINE_BOXED_TYPE(FbApiThread, fb_api_thread, fb_api_thread_dup,
3400 fb_api_thread_free)
3401
3402 FbApiTyping *
3403 fb_api_typing_dup(const FbApiTyping *typg)
3404 {
3405 return g_memdup2(typg, sizeof *typg);
3406 }
3407
3408 void
3409 fb_api_typing_reset(FbApiTyping *typg)
3410 {
3411 g_return_if_fail(typg != NULL);
3412 memset(typg, 0, sizeof *typg);
3413 }
3414
3415 void
3416 fb_api_typing_free(FbApiTyping *typg)
3417 {
3418 if (G_LIKELY(typg != NULL)) {
3419 g_free(typg);
3420 }
3421 }
3422
3423 G_DEFINE_BOXED_TYPE(FbApiTyping, fb_api_typing, fb_api_typing_dup,
3424 fb_api_typing_free)
3425
3426 FbApiUser *
3427 fb_api_user_dup(const FbApiUser *user)
3428 {
3429 FbApiUser *ret;
3430
3431 if (user == NULL) {
3432 return NULL;
3433 }
3434
3435 ret = g_memdup2(user, sizeof *user);
3436 ret->name = g_strdup(user->name);
3437 ret->icon = g_strdup(user->icon);
3438 ret->csum = g_strdup(user->csum);
3439
3440 return ret;
3441 }
3442
3443 void
3444 fb_api_user_reset(FbApiUser *user, gboolean deep)
3445 {
3446 g_return_if_fail(user != NULL);
3447
3448 if (deep) {
3449 g_free(user->name);
3450 g_free(user->icon);
3451 g_free(user->csum);
3452 }
3453
3454 memset(user, 0, sizeof *user);
3455 }
3456
3457 void
3458 fb_api_user_free(FbApiUser *user)
3459 {
3460 if (G_LIKELY(user != NULL)) {
3461 g_free(user->name);
3462 g_free(user->icon);
3463 g_free(user->csum);
3464 g_free(user);
3465 }
3466 }
3467
3468 G_DEFINE_BOXED_TYPE(FbApiUser, fb_api_user, fb_api_user_dup, fb_api_user_free)

mercurial