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