libpurple/protocols/facebook/api.c

branch
facebook
changeset 37250
3f5570a17b15
child 37252
be11d9f148a6
equal deleted inserted replaced
37249:dfa58c08a4fd 37250:3f5570a17b15
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 <json-glib/json-glib.h>
23 #include <stdarg.h>
24 #include <string.h>
25
26 #include "api.h"
27 #include "http.h"
28 #include "json.h"
29 #include "marshal.h"
30 #include "thrift.h"
31 #include "util.h"
32
33 enum
34 {
35 PROP_0,
36
37 PROP_CID,
38 PROP_CUID,
39 PROP_MID,
40 PROP_STOKEN,
41 PROP_TOKEN,
42 PROP_UID,
43
44 PROP_N
45 };
46
47 struct _FbApiPrivate
48 {
49 PurpleConnection *gc;
50 FbMqtt *mqtt;
51
52 FbId uid;
53 guint64 mid;
54 gchar *cid;
55 gchar *cuid;
56 gchar *stoken;
57 gchar *token;
58
59 };
60
61 G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT);
62
63 static void
64 fb_api_set_property(GObject *obj, guint prop, const GValue *val,
65 GParamSpec *pspec)
66 {
67 FbApiPrivate *priv = FB_API(obj)->priv;
68
69 switch (prop) {
70 case PROP_CID:
71 g_free(priv->cid);
72 priv->cid = g_value_dup_string(val);
73 break;
74 case PROP_CUID:
75 g_free(priv->cuid);
76 priv->cuid = g_value_dup_string(val);
77 break;
78 case PROP_MID:
79 priv->mid = g_value_get_uint64(val);
80 break;
81 case PROP_STOKEN:
82 g_free(priv->stoken);
83 priv->stoken = g_value_dup_string(val);
84 break;
85 case PROP_TOKEN:
86 g_free(priv->token);
87 priv->token = g_value_dup_string(val);
88 break;
89 case PROP_UID:
90 priv->uid = g_value_get_int64(val);
91 break;
92
93 default:
94 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
95 break;
96 }
97 }
98
99 static void
100 fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec)
101 {
102 FbApiPrivate *priv = FB_API(obj)->priv;
103
104 switch (prop) {
105 case PROP_CID:
106 g_value_set_string(val, priv->cid);
107 break;
108 case PROP_CUID:
109 g_value_set_string(val, priv->cuid);
110 break;
111 case PROP_MID:
112 g_value_set_uint64(val, priv->mid);
113 break;
114 case PROP_STOKEN:
115 g_value_set_string(val, priv->stoken);
116 break;
117 case PROP_TOKEN:
118 g_value_set_string(val, priv->token);
119 break;
120 case PROP_UID:
121 g_value_set_int64(val, priv->uid);
122 break;
123
124 default:
125 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
126 break;
127 }
128 }
129
130
131 static void
132 fb_api_dispose(GObject *obj)
133 {
134 FbApiPrivate *priv = FB_API(obj)->priv;
135
136 if (G_LIKELY(priv->gc != NULL)) {
137 purple_http_conn_cancel_all(priv->gc);
138 }
139
140 if (G_UNLIKELY(priv->mqtt != NULL)) {
141 g_object_unref(priv->mqtt);
142 }
143
144 g_free(priv->cid);
145 g_free(priv->cuid);
146 g_free(priv->stoken);
147 g_free(priv->token);
148 }
149
150 static void
151 fb_api_class_init(FbApiClass *klass)
152 {
153 GObjectClass *gklass = G_OBJECT_CLASS(klass);
154 GParamSpec *props[PROP_N] = {NULL};
155
156 gklass->set_property = fb_api_set_property;
157 gklass->get_property = fb_api_get_property;
158 gklass->dispose = fb_api_dispose;
159 g_type_class_add_private(klass, sizeof (FbApiPrivate));
160
161 props[PROP_CID] = g_param_spec_string(
162 "cid",
163 "Client ID",
164 "Client identifier for MQTT",
165 NULL,
166 G_PARAM_READWRITE);
167 props[PROP_CUID] = g_param_spec_string(
168 "cuid",
169 "Client Unique ID",
170 "Client unique identifier for the MQTT queue",
171 NULL,
172 G_PARAM_READWRITE);
173 props[PROP_MID] = g_param_spec_uint64(
174 "mid",
175 "MQTT ID",
176 "MQTT identifier for the MQTT queuer",
177 0, G_MAXUINT64, 0,
178 G_PARAM_READWRITE);
179 props[PROP_STOKEN] = g_param_spec_string(
180 "stoken",
181 "Sync Token",
182 "Synchronization token for the MQTT queue",
183 NULL,
184 G_PARAM_READWRITE);
185 props[PROP_TOKEN] = g_param_spec_string(
186 "token",
187 "Access Token",
188 "Access token from authenticating",
189 NULL,
190 G_PARAM_READWRITE);
191 props[PROP_UID] = g_param_spec_int64(
192 "uid",
193 "User ID",
194 "User identifier",
195 0, G_MAXINT64, 0,
196 G_PARAM_READWRITE);
197 g_object_class_install_properties(gklass, PROP_N, props);
198
199 g_signal_new("auth",
200 G_TYPE_FROM_CLASS(klass),
201 G_SIGNAL_ACTION,
202 0,
203 NULL, NULL,
204 fb_marshal_VOID__VOID,
205 G_TYPE_NONE,
206 0);
207 g_signal_new("connect",
208 G_TYPE_FROM_CLASS(klass),
209 G_SIGNAL_ACTION,
210 0,
211 NULL, NULL,
212 fb_marshal_VOID__VOID,
213 G_TYPE_NONE,
214 0);
215 g_signal_new("contacts",
216 G_TYPE_FROM_CLASS(klass),
217 G_SIGNAL_ACTION,
218 0,
219 NULL, NULL,
220 fb_marshal_VOID__POINTER,
221 G_TYPE_NONE,
222 1, G_TYPE_POINTER);
223 g_signal_new("error",
224 G_TYPE_FROM_CLASS(klass),
225 G_SIGNAL_ACTION,
226 0,
227 NULL, NULL,
228 fb_marshal_VOID__OBJECT,
229 G_TYPE_NONE,
230 1, G_TYPE_ERROR);
231 g_signal_new("message",
232 G_TYPE_FROM_CLASS(klass),
233 G_SIGNAL_ACTION,
234 0,
235 NULL, NULL,
236 fb_marshal_VOID__POINTER,
237 G_TYPE_NONE,
238 1, G_TYPE_POINTER);
239 g_signal_new("presence",
240 G_TYPE_FROM_CLASS(klass),
241 G_SIGNAL_ACTION,
242 0,
243 NULL, NULL,
244 fb_marshal_VOID__POINTER,
245 G_TYPE_NONE,
246 1, G_TYPE_POINTER);
247 g_signal_new("thread-create",
248 G_TYPE_FROM_CLASS(klass),
249 G_SIGNAL_ACTION,
250 0,
251 NULL, NULL,
252 fb_marshal_VOID__INT64,
253 G_TYPE_NONE,
254 1, FB_TYPE_ID);
255 g_signal_new("thread-info",
256 G_TYPE_FROM_CLASS(klass),
257 G_SIGNAL_ACTION,
258 0,
259 NULL, NULL,
260 fb_marshal_VOID__POINTER,
261 G_TYPE_NONE,
262 1, G_TYPE_POINTER);
263 g_signal_new("thread-list",
264 G_TYPE_FROM_CLASS(klass),
265 G_SIGNAL_ACTION,
266 0,
267 NULL, NULL,
268 fb_marshal_VOID__POINTER,
269 G_TYPE_NONE,
270 1, G_TYPE_POINTER);
271 g_signal_new("typing",
272 G_TYPE_FROM_CLASS(klass),
273 G_SIGNAL_ACTION,
274 0,
275 NULL, NULL,
276 fb_marshal_VOID__POINTER,
277 G_TYPE_NONE,
278 1, G_TYPE_POINTER);
279 }
280
281 static void
282 fb_api_init(FbApi *api)
283 {
284 FbApiPrivate *priv;
285
286 priv = G_TYPE_INSTANCE_GET_PRIVATE(api, FB_TYPE_API, FbApiPrivate);
287 api->priv = priv;
288 }
289
290 GQuark
291 fb_api_error_quark(void)
292 {
293 static GQuark q;
294
295 if (G_UNLIKELY(q == 0)) {
296 q = g_quark_from_static_string("fb-api-error-quark");
297 }
298
299 return q;
300 }
301
302 static gboolean
303 fb_api_json_chk(FbApi *api, gconstpointer data, gsize size, JsonNode **node)
304 {
305 const gchar *msg;
306 FbApiPrivate *priv;
307 GError *err = NULL;
308 gint64 code;
309 JsonNode *root;
310
311 g_return_val_if_fail(FB_IS_API(api), FALSE);
312 priv = api->priv;
313
314 root = fb_json_node_new(data, size, &err);
315 FB_API_ERROR_CHK(api, err, return FALSE);
316
317 if (fb_json_node_chk_int(root, "$.error_code", &code)) {
318 if (!fb_json_node_chk_str(root, "$.error_msg", &msg)) {
319 msg = _("Generic error");
320 }
321
322 fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg);
323 json_node_free(root);
324 return FALSE;
325 }
326
327 if (fb_json_node_chk_str(root, "$.errorCode", &msg)) {
328 if ((g_ascii_strcasecmp(msg, "ERROR_QUEUE_NOT_FOUND") == 0) ||
329 (g_ascii_strcasecmp(msg, "ERROR_QUEUE_LOST") == 0))
330 {
331 g_free(priv->stoken);
332 priv->stoken = NULL;
333 }
334
335 fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg);
336 json_node_free(root);
337 return FALSE;
338 }
339
340 if (node != NULL) {
341 *node = root;
342 }
343
344 return TRUE;
345 }
346
347 static gboolean
348 fb_api_http_chk(FbApi *api, PurpleHttpResponse *res, JsonNode **root)
349 {
350 const gchar *data;
351 GError *err = NULL;
352 gsize size;
353
354 if ((res != NULL) && !fb_http_error_chk(res, &err)) {
355 FB_API_ERROR_CHK(api, err, return FALSE);
356 }
357
358 if (root != NULL) {
359 data = purple_http_response_get_data(res, &size);
360
361 if (!fb_api_json_chk(api, data, size, root)) {
362 return FALSE;
363 }
364 }
365
366 return TRUE;
367 }
368
369 static PurpleHttpConnection *
370 fb_api_http_req(FbApi *api, const FbApiHttpInfo *info,
371 FbHttpParams *params, const gchar *url)
372 {
373 FbApiPrivate *priv = api->priv;
374 gchar *data;
375 gchar *key;
376 gchar *val;
377 GList *keys;
378 GList *l;
379 gsize size;
380 GString *gstr;
381 PurpleHttpConnection *ret;
382 PurpleHttpRequest *req;
383
384 fb_http_params_set_str(params, "api_key", FB_API_KEY);
385 fb_http_params_set_str(params, "client_country_code", "US");
386 fb_http_params_set_str(params, "fb_api_caller_class", info->klass);
387 fb_http_params_set_str(params, "fb_api_req_friendly_name", info->name);
388 fb_http_params_set_str(params, "format", "json");
389 fb_http_params_set_str(params, "locale", "en_US");
390 fb_http_params_set_str(params, "method", info->method);
391
392 req = purple_http_request_new(url);
393 purple_http_request_set_method(req, "POST");
394
395 /* Ensure an old signature is not computed */
396 g_hash_table_remove(params, "sig");
397
398 gstr = g_string_new(NULL);
399 keys = g_hash_table_get_keys(params);
400 keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp);
401
402 for (l = keys; l != NULL; l = l->next) {
403 key = l->data;
404 val = g_hash_table_lookup(params, key);
405 g_string_append_printf(gstr, "%s=%s", key, val);
406 }
407
408 g_string_append(gstr, FB_API_SECRET);
409 data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str,
410 gstr->len);
411 fb_http_params_set_str(params, "sig", data);
412 g_string_free(gstr, TRUE);
413 g_free(data);
414
415 data = fb_http_params_close(params, &size);
416 purple_http_request_set_contents(req, data, size);
417 g_free(data);
418
419 if (priv->token != NULL) {
420 data = g_strdup_printf("OAuth %s", priv->token);
421 purple_http_request_header_set(req, "Authorization", data);
422 g_free(data);
423 }
424
425 ret = purple_http_request(priv->gc, req, info->callback, api);
426 purple_http_request_unref(req);
427 return ret;
428 }
429
430 static void
431 fb_api_cb_http_bool(PurpleHttpConnection *con, PurpleHttpResponse *res,
432 gpointer data)
433 {
434 const gchar *hata;
435 FbApi *api = data;
436
437 if (!fb_api_http_chk(api, res, NULL)) {
438 return;
439 }
440
441 hata = purple_http_response_get_data(res, NULL);
442
443 if (g_ascii_strcasecmp(hata, "true") != 0) {
444 fb_api_error(api, FB_API_ERROR,
445 _("Failed generic API operation"));
446 }
447 }
448
449 static void
450 fb_api_cb_mqtt_error(FbMqtt *mqtt, GError *error, gpointer data)
451 {
452 FbApi *api = data;
453 g_signal_emit_by_name(api, "error", error);
454 }
455
456 static void
457 fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data)
458 {
459 FbApi *api = data;
460 FbApiPrivate *priv = api->priv;
461 gchar *json;
462 JsonBuilder *bldr;
463
464 static guint8 flags = FB_MQTT_CONNECT_FLAG_USER |
465 FB_MQTT_CONNECT_FLAG_PASS |
466 FB_MQTT_CONNECT_FLAG_CLR;
467
468 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
469 fb_json_bldr_add_bool(bldr, "chat_on", TRUE);
470 fb_json_bldr_add_bool(bldr, "fg", FALSE);
471 fb_json_bldr_add_bool(bldr, "no_auto_fg", TRUE);
472 fb_json_bldr_add_int(bldr, "mqtt_sid", priv->mid);
473 fb_json_bldr_add_int(bldr, "nwt", 1);
474 fb_json_bldr_add_int(bldr, "nwst", 0);
475 fb_json_bldr_add_str(bldr, "a", FB_API_AGENT);
476 fb_json_bldr_add_str(bldr, "d", priv->cuid);
477 fb_json_bldr_add_str(bldr, "pf", "jz");
478 fb_json_bldr_add_strf(bldr, "u", "%" FB_ID_FORMAT, priv->uid);
479
480 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
481 fb_mqtt_connect(mqtt,
482 flags, /* Flags */
483 priv->cid, /* Client identifier */
484 json, /* Will message */
485 priv->token, /* Username */
486 NULL);
487
488 g_free(json);
489 }
490
491 static void
492 fb_api_cb_seqid(PurpleHttpConnection *con, PurpleHttpResponse *res,
493 gpointer data)
494 {
495 const gchar *str;
496 FbApi *api = data;
497 FbApiPrivate *priv = api->priv;
498 gchar *json;
499 GError *err = NULL;
500 gint64 nid;
501 JsonBuilder *bldr;
502 JsonNode *root;
503
504 static const gchar *expr =
505 "$.data[0].fql_result_set[0].sync_sequence_id";
506
507 if (!fb_api_http_chk(api, res, &root)) {
508 return;
509 }
510
511 str = fb_json_node_get_str(root, expr, &err);
512 FB_API_ERROR_CHK(api, err, return);
513 nid = g_ascii_strtoll(str, NULL, 10);
514 json_node_free(root);
515
516 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
517 fb_json_bldr_add_int(bldr, "delta_batch_size", 125);
518 fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250);
519 fb_json_bldr_add_int(bldr, "sync_api_version", 2);
520 fb_json_bldr_add_str(bldr, "encoding", "JSON");
521
522 if (priv->stoken == NULL) {
523 fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", nid);
524 fb_json_bldr_add_str(bldr, "device_id", priv->cuid);
525 fb_json_bldr_obj_begin(bldr, "device_params");
526 fb_json_bldr_obj_end(bldr);
527
528 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
529 fb_api_publish(api, "/messenger_sync_create_queue", "%s",
530 json);
531 g_free(json);
532 return;
533 }
534
535 fb_json_bldr_add_int(bldr, "last_seq_id", nid);
536 fb_json_bldr_add_str(bldr, "sync_token", priv->stoken);
537
538 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
539 fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json);
540 g_signal_emit_by_name(api, "connect");
541 g_free(json);
542 }
543
544 static void
545 fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data)
546 {
547 FbApi *api = data;
548 FbHttpParams *prms;
549 gchar *json;
550 JsonBuilder *bldr;
551
552 static const FbApiHttpInfo info = {
553 fb_api_cb_seqid,
554 "com.facebook.orca.protocol.methods.u",
555 "fetchThreadList",
556 "GET"
557 };
558
559 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
560 fb_json_bldr_add_bool(bldr, "foreground", TRUE);
561 fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA);
562
563 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
564 fb_api_publish(api, "/foreground_state", "%s", json);
565 g_free(json);
566
567 fb_mqtt_subscribe(mqtt,
568 "/inbox", 0,
569 "/mercury", 0,
570 "/messaging_events", 0,
571 "/orca_presence", 0,
572 "/orca_typing_notifications", 0,
573 "/pp", 0,
574 "/t_ms", 0,
575 "/t_p", 0,
576 "/t_rtc", 0,
577 "/webrtc", 0,
578 "/webrtc_response", 0,
579 NULL
580 );
581
582 /* Notifications seem to lead to some sort of sending rate limit */
583 fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL);
584
585 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
586 fb_json_bldr_add_str(bldr, "thread_list_ids",
587 "SELECT sync_sequence_id "
588 "FROM unified_thread "
589 "WHERE folder='inbox' "
590 "ORDER BY sync_sequence_id "
591 "DESC LIMIT 1");
592 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
593
594 prms = fb_http_params_new();
595 fb_http_params_set_str(prms, "q", json);
596 fb_api_http_req(api, &info, prms, FB_API_URL_FQL);
597 g_free(json);
598 }
599
600 static void
601 fb_api_cb_publish_tn(FbApi *api, const GByteArray *pload)
602 {
603 const gchar *str;
604 FbApiTyping typg;
605 GError *err = NULL;
606 gint64 state;
607 gint64 uid;
608 JsonNode *root;
609
610 if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
611 return;
612 }
613
614 if (!fb_json_node_chk_str(root, "$.type", &str) ||
615 (g_ascii_strcasecmp(str, "typ") != 0)) {
616 goto finish;
617 }
618
619 uid = fb_json_node_get_int(root, "$.sender_fbid", &err);
620 FB_API_ERROR_CHK(api, err, goto finish);
621
622 state = fb_json_node_get_int(root, "$.state", &err);
623 FB_API_ERROR_CHK(api, err, goto finish);
624
625 typg.uid = uid;
626 typg.state = state;
627 g_signal_emit_by_name(api, "typing", &typg);
628
629 finish:
630 json_node_free(root);
631 }
632
633 static void
634 fb_api_cb_publish_ms(FbApi *api, const GByteArray *pload)
635 {
636 const gchar *str;
637 FbApiMessage msg;
638 FbApiPrivate *priv = api->priv;
639 GError *err = NULL;
640 gint64 tid;
641 gint64 uid;
642 GList *elms = NULL;
643 GList *l;
644 gpointer mptr;
645 GSList *msgs = NULL;
646 JsonArray *arr;
647 JsonNode *mode;
648 JsonNode *node;
649 JsonNode *root;
650
651 /* Start at 1 to skip the NULL byte */
652 if (!fb_api_json_chk(api, pload->data + 1, pload->len - 1, &root)) {
653 return;
654 }
655
656 if (fb_json_node_chk_str(root, "$.syncToken", &str)) {
657 g_free(priv->stoken);
658 priv->stoken = g_strdup(str);
659 g_signal_emit_by_name(api, "connect");
660 goto finish;
661 }
662
663 arr = fb_json_node_get_arr(root, "$.deltas", &err);
664 FB_API_ERROR_CHK(api, err, goto finish);
665 elms = json_array_get_elements(arr);
666
667 for (l = elms; l != NULL; l = l->next) {
668 node = l->data;
669
670 if (!fb_json_node_chk(node, "$.deltaNewMessage", &node)) {
671 continue;
672 }
673
674 mode = fb_json_node_get(node, "$.messageMetadata", &err);
675 FB_API_ERROR_CHK(api, err, goto next);
676
677 uid = fb_json_node_get_int(mode, "$.actorFbId", &err);
678 FB_API_ERROR_CHK(api, err, goto next);
679
680 if (uid == priv->uid) {
681 goto next;
682 }
683
684 msg.uid = uid;
685 msg.tid = 0;
686
687 if (fb_json_node_chk_int(mode, "$.threadKey.threadFbId",
688 &tid))
689 {
690 msg.tid = tid;
691 }
692
693 if (fb_json_node_chk_str(node, "$.body", &str)) {
694 msg.text = str;
695 mptr = g_memdup(&msg, sizeof msg);
696 msgs = g_slist_prepend(msgs, mptr);
697 }
698
699 if (fb_json_node_chk_arr(node, "$.attachments", &arr) &&
700 (json_array_get_length(arr) > 0))
701 {
702 msg.text = _("* Non-Displayable Attachments *");
703 mptr = g_memdup(&msg, sizeof msg);
704 msgs = g_slist_prepend(msgs, mptr);
705 }
706
707 next:
708 json_node_free(node);
709 json_node_free(mode);
710 }
711
712 msgs = g_slist_reverse(msgs);
713 g_signal_emit_by_name(api, "message", msgs);
714
715 finish:
716 g_list_free(elms);
717 g_slist_free_full(msgs, g_free);
718 json_node_free(root);
719 }
720
721 static void
722 fb_api_cb_publish_p(FbApi *api, const GByteArray *pload)
723 {
724 FbApiPresence pres;
725 FbThrift *thft;
726 FbThriftType type;
727 gint32 i32;
728 gint64 i64;
729 gpointer mptr;
730 GSList *press;
731 guint i;
732 guint size;
733
734 /* Start at 1 to skip the NULL byte */
735 thft = fb_thrift_new((GByteArray*) pload, 1, TRUE);
736 press = NULL;
737
738 /* Skip the full list boolean field */
739 fb_thrift_read_field(thft, &type, NULL);
740 g_warn_if_fail(type == FB_THRIFT_TYPE_BOOL);
741 fb_thrift_read_bool(thft, NULL);
742
743 /* Read the list field */
744 fb_thrift_read_field(thft, &type, NULL);
745 g_warn_if_fail(type == FB_THRIFT_TYPE_LIST);
746
747 /* Read the list */
748 fb_thrift_read_list(thft, &type, &size);
749 g_warn_if_fail(type == FB_THRIFT_TYPE_STRUCT);
750
751 for (i = 0; i < size; i++) {
752 /* Read the user identifier field */
753 fb_thrift_read_field(thft, &type, NULL);
754 g_warn_if_fail(type == FB_THRIFT_TYPE_I64);
755 fb_thrift_read_i64(thft, &i64);
756
757 /* Read the active field */
758 fb_thrift_read_field(thft, &type, NULL);
759 g_warn_if_fail(type == FB_THRIFT_TYPE_I32);
760 fb_thrift_read_i32(thft, &i32);
761
762 pres.uid = i64;
763 pres.active = i32 != 0;
764
765 mptr = g_memdup(&pres, sizeof pres);
766 press = g_slist_prepend(press, mptr);
767
768 /* Skip the last active timestamp field */
769 if (!fb_thrift_read_field(thft, &type, NULL)) {
770 continue;
771 }
772
773 g_warn_if_fail(type == FB_THRIFT_TYPE_I64);
774 fb_thrift_read_i64(thft, NULL);
775
776 /* Skip the active client bits field */
777 if (!fb_thrift_read_field(thft, &type, NULL)) {
778 continue;
779 }
780
781 g_warn_if_fail(type == FB_THRIFT_TYPE_I16);
782 fb_thrift_read_i16(thft, NULL);
783
784 /* Skip the VoIP compatibility bits field */
785 if (!fb_thrift_read_field(thft, &type, NULL)) {
786 continue;
787 }
788
789 g_warn_if_fail(type == FB_THRIFT_TYPE_I64);
790 fb_thrift_read_i64(thft, NULL);
791
792 /* Read the field stop */
793 fb_thrift_read_stop(thft);
794 }
795
796 /* Read the field stop */
797 fb_thrift_read_stop(thft);
798 g_object_unref(thft);
799
800 press = g_slist_reverse(press);
801 g_signal_emit_by_name(api, "presence", press);
802 g_slist_free_full(press, g_free);
803 }
804
805 static void
806 fb_api_cb_mqtt_publish(FbMqtt *mqtt, const gchar *topic,
807 const GByteArray *pload, gpointer data)
808 {
809 FbApi *api = data;
810 gboolean comp;
811 GByteArray *bytes;
812
813 comp = fb_util_zcompressed(pload);
814
815 if (G_LIKELY(comp)) {
816 bytes = fb_util_zuncompress(pload);
817
818 if (G_UNLIKELY(bytes == NULL)) {
819 fb_api_error(api, FB_API_ERROR,
820 _("Failed to decompress"));
821 return;
822 }
823 } else {
824 bytes = (GByteArray*) pload;
825 }
826
827 if (g_ascii_strcasecmp(topic, "/orca_typing_notifications") == 0) {
828 fb_api_cb_publish_tn(api, bytes);
829 } else if (g_ascii_strcasecmp(topic, "/t_ms") == 0) {
830 fb_api_cb_publish_ms(api, bytes);
831 } else if (g_ascii_strcasecmp(topic, "/t_p") == 0) {
832 fb_api_cb_publish_p(api, bytes);
833 }
834
835 if (G_LIKELY(comp)) {
836 g_byte_array_free(bytes, TRUE);
837 }
838 }
839
840 FbApi *
841 fb_api_new(PurpleConnection *gc)
842 {
843 FbApi *api;
844 FbApiPrivate *priv;
845
846 api = g_object_new(FB_TYPE_API, NULL);
847 priv = api->priv;
848
849 priv->gc = gc;
850 priv->mqtt = fb_mqtt_new(gc);
851
852 g_signal_connect(priv->mqtt,
853 "connect",
854 G_CALLBACK(fb_api_cb_mqtt_connect),
855 api);
856 g_signal_connect(priv->mqtt,
857 "error",
858 G_CALLBACK(fb_api_cb_mqtt_error),
859 api);
860 g_signal_connect(priv->mqtt,
861 "open",
862 G_CALLBACK(fb_api_cb_mqtt_open),
863 api);
864 g_signal_connect(priv->mqtt,
865 "publish",
866 G_CALLBACK(fb_api_cb_mqtt_publish),
867 api);
868
869 return api;
870 }
871
872 void
873 fb_api_rehash(FbApi *api)
874 {
875 FbApiPrivate *priv;
876
877 g_return_if_fail(FB_IS_API(api));
878 priv = api->priv;
879
880 if (priv->cid == NULL) {
881 priv->cid = fb_util_randstr(32);
882 }
883
884 if (priv->mid == 0) {
885 priv->mid = g_random_int();
886 }
887
888 if (priv->cuid == NULL) {
889 priv->cuid = purple_uuid_random();
890 }
891
892 if (strlen(priv->cid) > 20) {
893 priv->cid = g_realloc_n(priv->cid , 21, sizeof *priv->cid);
894 priv->cid[20] = 0;
895 }
896 }
897
898 void
899 fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...)
900 {
901 gchar *str;
902 GError *err = NULL;
903 va_list ap;
904
905 g_return_if_fail(FB_IS_API(api));
906
907 va_start(ap, format);
908 str = g_strdup_vprintf(format, ap);
909 va_end(ap);
910
911 g_set_error(&err, FB_API_ERROR, error, "%s", str);
912 g_free(str);
913
914 g_signal_emit_by_name(api, "error", err);
915 g_error_free(err);
916 }
917
918 static void
919 fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res,
920 gpointer data)
921 {
922 const gchar *token;
923 FbApi *api = data;
924 FbApiPrivate *priv = api->priv;
925 GError *err = NULL;
926 gint64 uid;
927 JsonNode *root;
928
929 if (!fb_api_http_chk(api, res, &root)) {
930 return;
931 }
932
933 uid = fb_json_node_get_int(root, "$.uid", &err);
934 FB_API_ERROR_CHK(api, err, goto finish);
935
936 token = fb_json_node_get_str(root, "$.access_token", &err);
937 FB_API_ERROR_CHK(api, err, goto finish);
938
939 g_free(priv->token);
940 priv->token = g_strdup(token);
941 priv->uid = uid;
942 g_signal_emit_by_name(api, "auth");
943
944 finish:
945 json_node_free(root);
946 }
947
948 void
949 fb_api_auth(FbApi *api, const gchar *user, const gchar *pass)
950 {
951 FbHttpParams *prms;
952
953 static const FbApiHttpInfo info = {
954 fb_api_cb_auth,
955 "com.facebook.auth.protocol.d",
956 "authenticate",
957 "auth.login"
958 };
959
960 prms = fb_http_params_new();
961 fb_http_params_set_str(prms, "email", user);
962 fb_http_params_set_str(prms, "password", pass);
963 fb_api_http_req(api, &info, prms, FB_API_URL_AUTH);
964 }
965
966 static void
967 fb_api_cb_contacts(PurpleHttpConnection *con, PurpleHttpResponse *res,
968 gpointer data)
969 {
970 const gchar *name;
971 const gchar *uid;
972 FbApi *api = data;
973 FbApiPrivate *priv = api->priv;
974 FbApiUser user;
975 GError *err = NULL;
976 GList *elms = NULL;
977 GList *l;
978 gpointer mptr;
979 GSList *users = NULL;
980 JsonArray *arr;
981 JsonNode *node;
982 JsonNode *root;
983
984 static const gchar *expr =
985 "$.viewer.messenger_contacts.nodes";
986
987 if (!fb_api_http_chk(api, res, &root)) {
988 return;
989 }
990
991 arr = fb_json_node_get_arr(root, expr, &err);
992 FB_API_ERROR_CHK(api, err, goto finish);
993 elms = json_array_get_elements(arr);
994
995 for (l = elms; l != NULL; l = l->next) {
996 node = l->data;
997 uid = fb_json_node_get_str(node, "$.represented_profile.id",
998 &err);
999 FB_API_ERROR_CHK(api, err, goto finish);
1000 user.uid = FB_ID_FROM_STR(uid);
1001
1002 if (user.uid == priv->uid) {
1003 continue;
1004 }
1005
1006 name = fb_json_node_get_str(node, "$.structured_name.text",
1007 &err);
1008 FB_API_ERROR_CHK(api, err, goto finish);
1009 user.name = name;
1010
1011 mptr = g_memdup(&user, sizeof user);
1012 users = g_slist_prepend(users, mptr);
1013 }
1014
1015 g_signal_emit_by_name(api, "contacts", users);
1016
1017 finish:
1018 g_list_free(elms);
1019 g_slist_free_full(users, g_free);
1020 json_node_free(root);
1021 }
1022
1023 void
1024 fb_api_contacts(FbApi *api)
1025 {
1026 FbHttpParams *prms;
1027
1028 static const FbApiHttpInfo info = {
1029 fb_api_cb_contacts,
1030 "com.facebook.contacts.service.d",
1031 "FetchContactsFullQuery",
1032 "get"
1033 };
1034
1035 prms = fb_http_params_new();
1036 fb_http_params_set_str(prms, "query_id", FB_API_QRYID_CONTACTS);
1037 fb_http_params_set_str(prms, "query_params", "{}");
1038 fb_api_http_req(api, &info, prms, FB_API_URL_GQL);
1039 }
1040
1041 void
1042 fb_api_connect(FbApi *api)
1043 {
1044 FbApiPrivate *priv;
1045
1046 g_return_if_fail(FB_IS_API(api));
1047 priv = api->priv;
1048
1049 fb_mqtt_open(priv->mqtt, FB_MQTT_HOST, FB_MQTT_PORT);
1050 }
1051
1052 void
1053 fb_api_disconnect(FbApi *api)
1054 {
1055 FbApiPrivate *priv;
1056
1057 g_return_if_fail(FB_IS_API(api));
1058 priv = api->priv;
1059
1060 fb_mqtt_disconnect(priv->mqtt);
1061 }
1062
1063 void
1064 fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *msg)
1065 {
1066 const gchar *tpfx;
1067 FbApiPrivate *priv;
1068 gchar *json;
1069 guint64 msgid;
1070 JsonBuilder *bldr;
1071
1072 g_return_if_fail(FB_IS_API(api));
1073 g_return_if_fail(msg != NULL);
1074 priv = api->priv;
1075
1076 msgid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int());
1077 tpfx = thread ? "tfbid_" : "";
1078
1079 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1080 fb_json_bldr_add_int(bldr, "msgid", msgid);
1081 fb_json_bldr_add_str(bldr, "body", msg);
1082 fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, priv->uid);
1083 fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id);
1084
1085 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
1086 fb_api_publish(api, "/send_message2", "%s", json);
1087 g_free(json);
1088 }
1089
1090 void
1091 fb_api_publish(FbApi *api, const gchar *topic, const gchar *fmt, ...)
1092 {
1093 FbApiPrivate *priv;
1094 GByteArray *bytes;
1095 GByteArray *cytes;
1096 gchar *msg;
1097 va_list ap;
1098
1099 g_return_if_fail(FB_IS_API(api));
1100 g_return_if_fail(topic != NULL);
1101 g_return_if_fail(fmt != NULL);
1102 priv = api->priv;
1103
1104 va_start(ap, fmt);
1105 msg = g_strdup_vprintf(fmt, ap);
1106 va_end(ap);
1107
1108 bytes = g_byte_array_new_take((guint8*) msg, strlen(msg));
1109 cytes = fb_util_zcompress(bytes);
1110 fb_mqtt_publish(priv->mqtt, topic, cytes);
1111
1112 g_byte_array_free(cytes, TRUE);
1113 g_byte_array_free(bytes, TRUE);
1114 }
1115
1116 static void
1117 fb_api_cb_thread_create(PurpleHttpConnection *con, PurpleHttpResponse *res,
1118 gpointer data)
1119 {
1120 const gchar *str;
1121 FbApi *api = data;
1122 FbId tid;
1123 GError *err = NULL;
1124 JsonNode *root;
1125
1126 if (!fb_api_http_chk(api, res, &root)) {
1127 return;
1128 }
1129
1130 str = fb_json_node_get_str(root, "$.thread_fbid", &err);
1131 FB_API_ERROR_CHK(api, err, goto finish);
1132
1133 tid = FB_ID_FROM_STR(str);
1134 g_signal_emit_by_name(api, "thread-create", tid);
1135
1136 finish:
1137 json_node_free(root);
1138 }
1139
1140 void
1141 fb_api_thread_create(FbApi *api, GSList *uids)
1142 {
1143 FbApiPrivate *priv;
1144 FbHttpParams *prms;
1145 FbId *uid;
1146 gchar *json;
1147 GSList *l;
1148 JsonBuilder *bldr;
1149
1150 static const FbApiHttpInfo info = {
1151 fb_api_cb_thread_create,
1152 "ccom.facebook.orca.send.service.l",
1153 "createThread",
1154 "POST"
1155 };
1156
1157 g_return_if_fail(FB_IS_API(api));
1158 g_warn_if_fail((uids != NULL) && (uids->next != NULL));
1159 priv = api->priv;
1160
1161 bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
1162 fb_json_bldr_obj_begin(bldr, NULL);
1163 fb_json_bldr_add_str(bldr, "type", "id");
1164 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, priv->uid);
1165 fb_json_bldr_obj_end(bldr);
1166
1167 for (l = uids; l != NULL; l = l->next) {
1168 uid = l->data;
1169 fb_json_bldr_obj_begin(bldr, NULL);
1170 fb_json_bldr_add_str(bldr, "type", "id");
1171 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid);
1172 fb_json_bldr_obj_end(bldr);
1173 }
1174
1175 json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
1176 prms = fb_http_params_new();
1177 fb_http_params_set_str(prms, "to", json);
1178 fb_api_http_req(api, &info, prms, FB_API_URL_FQL);
1179 g_free(json);
1180 }
1181
1182 static void
1183 fb_api_cb_thread_info(PurpleHttpConnection *con, PurpleHttpResponse *res,
1184 gpointer data)
1185 {
1186 const gchar *str;
1187 FbApi *api = data;
1188 FbApiPrivate *priv = api->priv;
1189 FbApiThread thrd;
1190 FbApiUser user;
1191 GError *err = NULL;
1192 GList *elms = NULL;
1193 GList *l;
1194 gpointer mptr;
1195 JsonArray *arr;
1196 JsonNode *mode;
1197 JsonNode *node;
1198 JsonNode *root;
1199
1200 static const gchar *expr = "$.data[0].fql_result_set[0]";
1201
1202 if (!fb_api_http_chk(api, res, &root)) {
1203 return;
1204 }
1205
1206 memset(&thrd, 0, sizeof thrd);
1207 node = fb_json_node_get(root, expr, &err);
1208 FB_API_ERROR_CHK(api, err, goto finish);
1209
1210 str = fb_json_node_get_str(node, "$.thread_fbid", &err);
1211 FB_API_ERROR_CHK(api, err, goto finish);
1212 thrd.tid = FB_ID_FROM_STR(str);
1213
1214 if (fb_json_node_chk_str(node, "$.name", &str) && (strlen(str) > 0)) {
1215 thrd.topic = str;
1216 }
1217
1218 arr = fb_json_node_get_arr(node, "$.participants", &err);
1219 FB_API_ERROR_CHK(api, err, goto finish);
1220 elms = json_array_get_elements(arr);
1221
1222 for (l = elms; l != NULL; l = l->next) {
1223 mode = l->data;
1224
1225 str = fb_json_node_get_str(mode, "$.user_id", &err);
1226 FB_API_ERROR_CHK(api, err, goto finish);
1227 user.uid = FB_ID_FROM_STR(str);
1228
1229 str = fb_json_node_get_str(mode, "$.name", &err);
1230 FB_API_ERROR_CHK(api, err, goto finish);
1231 user.name = str;
1232
1233 if (user.uid != priv->uid) {
1234 mptr = g_memdup(&user, sizeof user);
1235 thrd.users = g_slist_prepend(thrd.users, mptr);
1236 }
1237 }
1238
1239 g_signal_emit_by_name(api, "thread-info", &thrd);
1240
1241 finish:
1242 g_list_free(elms);
1243 g_slist_free_full(thrd.users, g_free);
1244 json_node_free(node);
1245 json_node_free(root);
1246 }
1247
1248 void
1249 fb_api_thread_info(FbApi *api, FbId tid)
1250 {
1251 FbHttpParams *prms;
1252 gchar *json;
1253 JsonBuilder *bldr;
1254
1255 static const FbApiHttpInfo info = {
1256 fb_api_cb_thread_info,
1257 "com.facebook.orca.protocol.methods.u",
1258 "fetchThreadList",
1259 "GET"
1260 };
1261
1262 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1263 fb_json_bldr_add_strf(bldr, "threads",
1264 "SELECT thread_fbid, participants, name "
1265 "FROM unified_thread "
1266 "WHERE thread_fbid='%" FB_ID_FORMAT "' "
1267 "LIMIT 1",
1268 tid);
1269 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
1270
1271 prms = fb_http_params_new();
1272 fb_http_params_set_str(prms, "q", json);
1273 fb_api_http_req(api, &info, prms, FB_API_URL_FQL);
1274 g_free(json);
1275 }
1276
1277 void
1278 fb_api_thread_invite(FbApi *api, FbId tid, FbId uid)
1279 {
1280 FbHttpParams *prms;
1281 gchar *json;
1282 JsonBuilder *bldr;
1283
1284 static const FbApiHttpInfo info = {
1285 fb_api_cb_http_bool,
1286 "com.facebook.orca.protocol.a",
1287 "addMembers",
1288 "POST"
1289 };
1290
1291 bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
1292 fb_json_bldr_obj_begin(bldr, NULL);
1293 fb_json_bldr_add_str(bldr, "type", "id");
1294 fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid);
1295 fb_json_bldr_obj_end(bldr);
1296 json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
1297
1298 prms = fb_http_params_new();
1299 fb_http_params_set_str(prms, "to", json);
1300 fb_http_params_set_strf(prms, "id", "t_id.%" FB_ID_FORMAT, tid);
1301 fb_api_http_req(api, &info, prms, FB_API_URL_PARTS);
1302 g_free(json);
1303 }
1304
1305 static void
1306 fb_api_cb_threads_free(FbApiThread *thrd)
1307 {
1308 g_slist_free_full(thrd->users, g_free);
1309 g_free(thrd);
1310 }
1311
1312 static void
1313 fb_api_cb_thread_list(PurpleHttpConnection *con, PurpleHttpResponse *res,
1314 gpointer data)
1315 {
1316 const gchar *str;
1317 FbApi *api = data;
1318 FbApiPrivate *priv = api->priv;
1319 FbApiThread thrd;
1320 FbApiUser user;
1321 GError *err = NULL;
1322 GList *elms = NULL;
1323 GList *elms2 = NULL;
1324 GList *l;
1325 GList *m;
1326 gpointer mptr;
1327 GSList *thrds = NULL;
1328 JsonArray *arr;
1329 JsonArray *arr2;
1330 JsonNode *node;
1331 JsonNode *node2;
1332 JsonNode *root;
1333
1334 static const gchar *expr = "$.data[0].fql_result_set";
1335
1336 if (!fb_api_http_chk(api, res, &root)) {
1337 return;
1338 }
1339
1340 arr = fb_json_node_get_arr(root, expr, &err);
1341 FB_API_ERROR_CHK(api, err, goto finish);
1342 elms = json_array_get_elements(arr);
1343
1344 for (l = elms; l != NULL; l = l->next) {
1345 node = l->data;
1346 memset(&thrd, 0, sizeof thrd);
1347
1348 str = fb_json_node_get_str(node, "$.thread_fbid", &err);
1349 FB_API_ERROR_CHK(api, err, goto finish);
1350 thrd.tid = FB_ID_FROM_STR(str);
1351
1352 if (fb_json_node_chk_str(node, "$.name", &str) &&
1353 (strlen(str) > 0))
1354 {
1355 thrd.topic = str;
1356 }
1357
1358 arr2 = fb_json_node_get_arr(node, "$.participants", &err);
1359 FB_API_ERROR_CHK(api, err, goto finish);
1360 elms2 = json_array_get_elements(arr2);
1361
1362 for (m = elms2; m != NULL; m = m->next) {
1363 node2 = m->data;
1364
1365 str = fb_json_node_get_str(node2, "$.user_id", &err);
1366 FB_API_ERROR_CHK(api, err, goto finish);
1367 user.uid = FB_ID_FROM_STR(str);
1368
1369 str = fb_json_node_get_str(node2, "$.name", &err);
1370 FB_API_ERROR_CHK(api, err, goto finish);
1371 user.name = str;
1372
1373 if (user.uid != priv->uid) {
1374 mptr = g_memdup(&user, sizeof user);
1375 thrd.users = g_slist_prepend(thrd.users, mptr);
1376 }
1377 }
1378
1379 g_list_free(elms2);
1380 elms2 = NULL;
1381
1382 mptr = g_memdup(&thrd, sizeof thrd);
1383 thrds = g_slist_prepend(thrds, mptr);
1384 }
1385
1386 thrds = g_slist_reverse(thrds);
1387 g_signal_emit_by_name(api, "thread-list", thrds);
1388
1389 finish:
1390 g_list_free(elms2);
1391 g_list_free(elms);
1392 g_slist_free_full(thrds, (GDestroyNotify) fb_api_cb_threads_free);
1393 json_node_free(root);
1394 }
1395
1396 void
1397 fb_api_thread_list(FbApi *api, guint limit)
1398 {
1399 FbHttpParams *prms;
1400 gchar *json;
1401 JsonBuilder *bldr;
1402
1403 static const FbApiHttpInfo info = {
1404 fb_api_cb_thread_list,
1405 "com.facebook.orca.protocol.methods.u",
1406 "fetchThreadList",
1407 "GET"
1408 };
1409
1410 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1411 fb_json_bldr_add_strf(bldr, "threads",
1412 "SELECT thread_fbid, participants, name "
1413 "FROM unified_thread "
1414 "WHERE folder='inbox' "
1415 "ORDER BY timestamp DESC "
1416 "LIMIT %u",
1417 limit);
1418 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
1419
1420 prms = fb_http_params_new();
1421 fb_http_params_set_str(prms, "q", json);
1422 fb_api_http_req(api, &info, prms, FB_API_URL_FQL);
1423 g_free(json);
1424 }
1425
1426 void
1427 fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic)
1428 {
1429 FbHttpParams *prms;
1430
1431 static const FbApiHttpInfo info = {
1432 fb_api_cb_http_bool,
1433 "com.facebook.orca.protocol.a",
1434 "setThreadName",
1435 "messaging.setthreadname"
1436 };
1437
1438 prms = fb_http_params_new();
1439 fb_http_params_set_str(prms, "name", topic);
1440 fb_http_params_set_strf(prms, "tid", "t_id.%" FB_ID_FORMAT, tid);
1441 fb_api_http_req(api, &info, prms, FB_API_URL_TOPIC);
1442 }
1443
1444 void
1445 fb_api_typing(FbApi *api, FbId uid, gboolean state)
1446 {
1447 gchar *json;
1448 JsonBuilder *bldr;
1449
1450 bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
1451 fb_json_bldr_add_int(bldr, "state", state != 0);
1452 fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid);
1453
1454 json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
1455 fb_api_publish(api, "/typing", "%s", json);
1456 g_free(json);
1457 }

mercurial