| |
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 } |