summaryrefslogtreecommitdiff
path: root/libpurple/protocols/facebook/api.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/facebook/api.c')
-rw-r--r--libpurple/protocols/facebook/api.c3468
1 files changed, 0 insertions, 3468 deletions
diff --git a/libpurple/protocols/facebook/api.c b/libpurple/protocols/facebook/api.c
deleted file mode 100644
index 6e14195bcd..0000000000
--- a/libpurple/protocols/facebook/api.c
+++ /dev/null
@@ -1,3468 +0,0 @@
-/* purple
- *
- * Purple is the legal property of its developers, whose names are too numerous
- * to list here. Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
- */
-
-#include <glib/gi18n-lib.h>
-
-#include <json-glib/json-glib.h>
-#include <libsoup/soup.h>
-#include <stdarg.h>
-#include <string.h>
-
-#include "libpurple/glibcompat.h"
-
-#include "api.h"
-#include "http.h"
-#include "json.h"
-#include "thrift.h"
-#include "util.h"
-
-enum
-{
- PROP_0,
-
- PROP_CID,
- PROP_DID,
- PROP_MID,
- PROP_STOKEN,
- PROP_TOKEN,
- PROP_UID,
-
- PROP_N
-};
-
-/**
- * FbApi:
- *
- * Represents a Facebook Messenger connection.
- */
-struct _FbApi {
- GObject parent;
-
- FbMqtt *mqtt;
- SoupSession *cons;
- PurpleConnection *gc;
- gboolean retrying;
-
- FbId uid;
- gint64 sid;
- guint64 mid;
- gchar *cid;
- gchar *did;
- gchar *stoken;
- gchar *token;
-
- GQueue *msgs;
- gboolean invisible;
- guint unread;
- FbId lastmid;
- gchar *contacts_delta;
-};
-
-static void fb_api_error_literal(FbApi *api, FbApiError error,
- const gchar *msg);
-
-static void
-fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg);
-
-static void
-fb_api_contacts_after(FbApi *api, const gchar *cursor);
-
-static void
-fb_api_message_send(FbApi *api, FbApiMessage *msg);
-
-static void
-fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg);
-
-void
-fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor);
-
-G_DEFINE_TYPE(FbApi, fb_api, G_TYPE_OBJECT);
-
-static void
-fb_api_set_property(GObject *obj, guint prop, const GValue *val,
- GParamSpec *pspec)
-{
- FbApi *api = FB_API(obj);
-
- switch (prop) {
- case PROP_CID:
- g_free(api->cid);
- api->cid = g_value_dup_string(val);
- break;
- case PROP_DID:
- g_free(api->did);
- api->did = g_value_dup_string(val);
- break;
- case PROP_MID:
- api->mid = g_value_get_uint64(val);
- break;
- case PROP_STOKEN:
- g_free(api->stoken);
- api->stoken = g_value_dup_string(val);
- break;
- case PROP_TOKEN:
- g_free(api->token);
- api->token = g_value_dup_string(val);
- break;
- case PROP_UID:
- api->uid = g_value_get_int64(val);
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
- break;
- }
-}
-
-static void
-fb_api_get_property(GObject *obj, guint prop, GValue *val, GParamSpec *pspec)
-{
- FbApi *api = FB_API(obj);
-
- switch (prop) {
- case PROP_CID:
- g_value_set_string(val, api->cid);
- break;
- case PROP_DID:
- g_value_set_string(val, api->did);
- break;
- case PROP_MID:
- g_value_set_uint64(val, api->mid);
- break;
- case PROP_STOKEN:
- g_value_set_string(val, api->stoken);
- break;
- case PROP_TOKEN:
- g_value_set_string(val, api->token);
- break;
- case PROP_UID:
- g_value_set_int64(val, api->uid);
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec);
- break;
- }
-}
-
-
-static void
-fb_api_dispose(GObject *obj)
-{
- FbApi *api = FB_API(obj);
-
- if(api->cons != NULL) {
- soup_session_abort(api->cons);
- }
-
- g_clear_object(&api->mqtt);
-
- g_clear_object(&api->cons);
- if(api->msgs != NULL) {
- g_queue_free_full(api->msgs, (GDestroyNotify)fb_api_message_free);
- api->msgs = NULL;
- }
-
- g_clear_pointer(&api->cid, g_free);
- g_clear_pointer(&api->did, g_free);
- g_clear_pointer(&api->stoken, g_free);
- g_clear_pointer(&api->token, g_free);
- g_clear_pointer(&api->contacts_delta, g_free);
-}
-
-static void
-fb_api_class_init(FbApiClass *klass)
-{
- GObjectClass *gklass = G_OBJECT_CLASS(klass);
- GParamSpec *props[PROP_N] = {NULL};
-
- gklass->set_property = fb_api_set_property;
- gklass->get_property = fb_api_get_property;
- gklass->dispose = fb_api_dispose;
-
- /**
- * FbApi:cid:
- *
- * The client identifier for MQTT. This value should be saved
- * and loaded for persistence.
- */
- props[PROP_CID] = g_param_spec_string(
- "cid",
- "Client ID",
- "Client identifier for MQTT",
- NULL,
- G_PARAM_READWRITE);
-
- /**
- * FbApi:did:
- *
- * The device identifier for the MQTT message queue. This value
- * should be saved and loaded for persistence.
- */
- props[PROP_DID] = g_param_spec_string(
- "did",
- "Device ID",
- "Device identifier for the MQTT message queue",
- NULL,
- G_PARAM_READWRITE);
-
- /**
- * FbApi:mid:
- *
- * The MQTT identifier. This value should be saved and loaded
- * for persistence.
- */
- props[PROP_MID] = g_param_spec_uint64(
- "mid",
- "MQTT ID",
- "MQTT identifier",
- 0, G_MAXUINT64, 0,
- G_PARAM_READWRITE);
-
- /**
- * FbApi:stoken:
- *
- * The synchronization token for the MQTT message queue. This
- * value should be saved and loaded for persistence.
- */
- props[PROP_STOKEN] = g_param_spec_string(
- "stoken",
- "Sync Token",
- "Synchronization token for the MQTT message queue",
- NULL,
- G_PARAM_READWRITE);
-
- /**
- * FbApi:token:
- *
- * The access token for authentication. This value should be
- * saved and loaded for persistence.
- */
- props[PROP_TOKEN] = g_param_spec_string(
- "token",
- "Access Token",
- "Access token for authentication",
- NULL,
- G_PARAM_READWRITE);
-
- /**
- * FbApi:uid:
- *
- * The #FbId of the user of the #FbApi.
- */
- props[PROP_UID] = g_param_spec_int64(
- "uid",
- "User ID",
- "User identifier",
- 0, G_MAXINT64, 0,
- G_PARAM_READWRITE);
- g_object_class_install_properties(gklass, PROP_N, props);
-
- /**
- * FbApi::auth:
- * @api: The #FbApi.
- *
- * Emitted upon the successful completion of the authentication
- * process. This is emitted as a result of #fb_api_auth().
- */
- g_signal_new("auth",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 0);
-
- /**
- * FbApi::connect:
- * @api: The #FbApi.
- *
- * Emitted upon the successful completion of the connection
- * process. This is emitted as a result of #fb_api_connect().
- */
- g_signal_new("connect",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 0);
-
- /**
- * FbApi::contact:
- * @api: The #FbApi.
- * @user: The #FbApiUser.
- *
- * Emitted upon the successful reply of a contact request. This
- * is emitted as a result of #fb_api_contact().
- */
- g_signal_new("contact",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::contacts:
- * @api: The #FbApi.
- * @users: The #GSList of #FbApiUser's.
- * @complete: #TRUE if the list is fetched, otherwise #FALSE.
- *
- * Emitted upon the successful reply of a contacts request.
- * This is emitted as a result of #fb_api_contacts(). This can
- * be emitted multiple times before the entire contacts list
- * has been fetched. Use @complete for detecting the completion
- * status of the list fetch.
- */
- g_signal_new("contacts",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 2, G_TYPE_POINTER, G_TYPE_BOOLEAN);
-
- /**
- * FbApi::contacts-delta:
- * @api: The #FbApi.
- * @added: The #GSList of added #FbApiUser's.
- * @removed: The #GSList of strings with removed user ids.
- *
- * Like 'contacts', but only the deltas.
- */
- g_signal_new("contacts-delta",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 2, G_TYPE_POINTER, G_TYPE_POINTER);
-
- /**
- * FbApi::error:
- * @api: The #FbApi.
- * @error: The #GError.
- *
- * Emitted whenever an error is hit within the #FbApi. This
- * should disconnect the #FbApi with #fb_api_disconnect().
- */
- g_signal_new("error",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::events:
- * @api: The #FbApi.
- * @events: The #GSList of #FbApiEvent's.
- *
- * Emitted upon incoming events from the stream.
- */
- g_signal_new("events",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::messages:
- * @api: The #FbApi.
- * @msgs: The #GSList of #FbApiMessage's.
- *
- * Emitted upon incoming messages from the stream.
- */
- g_signal_new("messages",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::presences:
- * @api: The #FbApi.
- * @press: The #GSList of #FbApiPresence's.
- *
- * Emitted upon incoming presences from the stream.
- */
- g_signal_new("presences",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::thread:
- * @api: The #FbApi.
- * @thrd: The #FbApiThread.
- *
- * Emitted upon the successful reply of a thread request. This
- * is emitted as a result of #fb_api_thread().
- */
- g_signal_new("thread",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::thread-create:
- * @api: The #FbApi.
- * @tid: The thread #FbId.
- *
- * Emitted upon the successful reply of a thread creation
- * request. This is emitted as a result of
- * #fb_api_thread_create().
- */
- g_signal_new("thread-create",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, FB_TYPE_ID);
-
- /**
- * FbApi::thread-kicked:
- * @api: The #FbApi.
- * @thrd: The #FbApiThread.
- *
- * Emitted upon the reply of a thread request when the user is no longer
- * part of that thread. This is emitted as a result of #fb_api_thread().
- */
- g_signal_new("thread-kicked",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::threads:
- * @api: The #FbApi.
- * @thrds: The #GSList of #FbApiThread's.
- *
- * Emitted upon the successful reply of a threads request. This
- * is emitted as a result of #fb_api_threads().
- */
- g_signal_new("threads",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-
- /**
- * FbApi::typing:
- * @api: The #FbApi.
- * @typg: The #FbApiTyping.
- *
- * Emitted upon an incoming typing state from the stream.
- */
- g_signal_new("typing",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_ACTION,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1, G_TYPE_POINTER);
-}
-
-static void
-fb_api_init(FbApi *api)
-{
- api->msgs = g_queue_new();
-}
-
-GQuark
-fb_api_error_quark(void)
-{
- static GQuark q = 0;
-
- if (G_UNLIKELY(q == 0)) {
- q = g_quark_from_static_string("fb-api-error-quark");
- }
-
- return q;
-}
-
-static gboolean
-fb_api_json_chk(FbApi *api, gconstpointer data, gssize size, JsonNode **node)
-{
- const gchar *str;
- FbApiError errc = FB_API_ERROR_GENERAL;
- FbJsonValues *values;
- gboolean success = TRUE;
- gchar *msg;
- GError *err = NULL;
- gint64 code;
- guint i;
- JsonNode *root;
-
- static const gchar *exprs[] = {
- "$.error.message",
- "$.error.summary",
- "$.error_msg",
- "$.errorCode",
- "$.failedSend.errorMessage",
- };
-
- g_return_val_if_fail(FB_IS_API(api), FALSE);
-
- if (G_UNLIKELY(size == 0)) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL, _("Empty JSON data"));
- return FALSE;
- }
-
- fb_util_debug(FB_UTIL_DEBUG_INFO, "Parsing JSON: %.*s\n",
- (gint) size, (const gchar *) data);
-
- root = fb_json_node_new(data, size, &err);
- FB_API_ERROR_EMIT(api, err, return FALSE);
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.error_code");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.error.type");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.errorCode");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- return FALSE
- );
-
- code = fb_json_values_next_int(values, 0);
- str = fb_json_values_next_str(values, NULL);
-
- if (purple_strequal(str, "OAuthException") || (code == 401)) {
- errc = FB_API_ERROR_AUTH;
- success = FALSE;
-
- g_clear_pointer(&api->stoken, g_free);
- g_clear_pointer(&api->token, g_free);
- }
-
- /* 509 is used for "invalid attachment id" */
- if (code == 509) {
- errc = FB_API_ERROR_NONFATAL;
- success = FALSE;
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (purple_strequal(str, "ERROR_QUEUE_NOT_FOUND") ||
- purple_strequal(str, "ERROR_QUEUE_LOST"))
- {
- errc = FB_API_ERROR_QUEUE;
- success = FALSE;
-
- g_clear_pointer(&api->stoken, g_free);
- }
-
- g_object_unref(values);
-
- for (msg = NULL, i = 0; i < G_N_ELEMENTS(exprs); i++) {
- msg = fb_json_node_get_str(root, exprs[i], NULL);
-
- if (msg != NULL) {
- success = FALSE;
- break;
- }
- }
-
- if (!success && (msg == NULL)) {
- msg = g_strdup(_("Unknown error"));
- }
-
- if (msg != NULL) {
- fb_api_error_literal(api, errc, msg);
- json_node_free(root);
- g_free(msg);
- return FALSE;
- }
-
- if (node != NULL) {
- *node = root;
- } else {
- json_node_free(root);
- }
-
- return TRUE;
-}
-
-static gboolean
-fb_api_http_chk(FbApi *api, SoupSession *session, GAsyncResult *result,
- SoupMessage *msg, JsonNode **root)
-{
- GBytes *response_body = NULL;
- const gchar *reason = NULL;
- const gchar *data = NULL;
- GError *err = NULL;
- gint code;
- gsize size = 0;
-
- reason = soup_message_get_reason_phrase(msg);
- code = soup_message_get_status(msg);
-
- fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Response (%p):", msg);
- if (reason != NULL) {
- fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %s (%d)", reason,
- code);
- } else {
- fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Error: %d", code);
- }
-
- if (fb_http_error_chk(msg, &err) && (root == NULL)) {
- return TRUE;
- }
-
- response_body = soup_session_send_and_read_finish(session, result, &err);
- if(response_body != NULL) {
- data = g_bytes_get_data(response_body, &size);
- }
-
- if (G_LIKELY(size > 0)) {
- fb_util_debug(FB_UTIL_DEBUG_INFO, " Response Data: %.*s",
- (gint) size, data);
- }
-
- /* Rudimentary check to prevent wrongful error parsing */
- if ((size < 2) || (data[0] != '{') || (data[size - 1] != '}')) {
- FB_API_ERROR_EMIT(api, err, return FALSE);
- }
-
- if (!fb_api_json_chk(api, data, size, root)) {
- if (G_UNLIKELY(err != NULL)) {
- g_error_free(err);
- }
-
- return FALSE;
- }
-
- FB_API_ERROR_EMIT(api, err, return FALSE);
- return TRUE;
-}
-
-static SoupMessage *
-fb_api_http_req(FbApi *api, const gchar *url, const gchar *name,
- const gchar *method, FbHttpParams *params,
- GAsyncReadyCallback callback)
-{
- gchar *data;
- gchar *key;
- gchar *val;
- GList *keys;
- GList *l;
- GString *gstr;
- SoupMessage *msg;
-
- fb_http_params_set_str(params, "api_key", FB_API_KEY);
- fb_http_params_set_str(params, "device_id", api->did);
- fb_http_params_set_str(params, "fb_api_req_friendly_name", name);
- fb_http_params_set_str(params, "format", "json");
- fb_http_params_set_str(params, "method", method);
-
- val = fb_util_get_locale();
- fb_http_params_set_str(params, "locale", val);
- g_free(val);
-
- /* Ensure an old signature is not computed */
- g_hash_table_remove(params, "sig");
-
- gstr = g_string_new(NULL);
- keys = g_hash_table_get_keys(params);
- keys = g_list_sort(keys, (GCompareFunc) g_ascii_strcasecmp);
-
- for (l = keys; l != NULL; l = l->next) {
- key = l->data;
- val = g_hash_table_lookup(params, key);
- g_string_append_printf(gstr, "%s=%s", key, val);
- }
-
- g_string_append(gstr, FB_API_SECRET);
- data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str,
- gstr->len);
- fb_http_params_set_str(params, "sig", data);
- g_string_free(gstr, TRUE);
- g_list_free(keys);
- g_free(data);
-
- msg = soup_message_new_from_encoded_form("POST", url, soup_form_encode_hash(params));
- fb_http_params_free(params);
-
- if (api->token != NULL) {
- data = g_strdup_printf("OAuth %s", api->token);
- soup_message_headers_replace(soup_message_get_request_headers(msg),
- "Authorization", data);
- g_free(data);
- }
-
- g_object_set_data(G_OBJECT(msg), "facebook-api", api);
- soup_session_send_and_read_async(api->cons, msg, G_PRIORITY_DEFAULT, NULL,
- callback, msg);
-
- fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", msg);
- fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url);
-
- return msg;
-}
-
-static SoupMessage *
-fb_api_http_query(FbApi *api, gint64 query, JsonBuilder *builder,
- GAsyncReadyCallback hcb)
-{
- const gchar *name;
- FbHttpParams *prms;
- gchar *json;
-
- switch (query) {
- case FB_API_QUERY_CONTACT:
- name = "UsersQuery";
- break;
- case FB_API_QUERY_CONTACTS:
- name = "FetchContactsFullQuery";
- break;
- case FB_API_QUERY_CONTACTS_AFTER:
- name = "FetchContactsFullWithAfterQuery";
- break;
- case FB_API_QUERY_CONTACTS_DELTA:
- name = "FetchContactsDeltaQuery";
- break;
- case FB_API_QUERY_STICKER:
- name = "FetchStickersWithPreviewsQuery";
- break;
- case FB_API_QUERY_THREAD:
- name = "ThreadQuery";
- break;
- case FB_API_QUERY_SEQ_ID:
- case FB_API_QUERY_THREADS:
- name = "ThreadListQuery";
- break;
- case FB_API_QUERY_XMA:
- name = "XMAQuery";
- break;
- default:
- g_return_val_if_reached(NULL);
- return NULL;
- }
-
- prms = fb_http_params_new();
- json = fb_json_bldr_close(builder, JSON_NODE_OBJECT, NULL);
-
- fb_http_params_set_strf(prms, "query_id", "%" G_GINT64_FORMAT, query);
- fb_http_params_set_str(prms, "query_params", json);
- g_free(json);
-
- return fb_api_http_req(api, FB_API_URL_GQL, name, "get", prms, hcb);
-}
-
-static void
-fb_api_cb_http_bool(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- if (!json_node_get_boolean(root)) {
- fb_api_error_literal(api, FB_API_ERROR,
- _("Failed generic API operation"));
- }
-
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-static void
-fb_api_cb_mqtt_error(G_GNUC_UNUSED FbMqtt *mqtt, GError *error, gpointer data)
-{
- FbApi *api = data;
-
- if (!api->retrying) {
- api->retrying = TRUE;
- fb_util_debug_info("Attempting to reconnect the MQTT stream...");
- fb_api_connect(api, api->invisible);
- } else {
- g_signal_emit_by_name(api, "error", error);
- }
-}
-
-static void
-fb_api_cb_mqtt_open(FbMqtt *mqtt, gpointer data)
-{
- const GByteArray *bytes;
- FbApi *api = data;
- FbThrift *thft;
- GByteArray *cytes;
- GError *err = NULL;
-
- static guint8 flags = FB_MQTT_CONNECT_FLAG_USER |
- FB_MQTT_CONNECT_FLAG_PASS |
- FB_MQTT_CONNECT_FLAG_CLR;
-
- thft = fb_thrift_new(NULL, 0);
-
- /* Write the client identifier */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 1, 0);
- fb_thrift_write_str(thft, api->cid);
-
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRUCT, 4, 1);
-
- /* Write the user identifier */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 1, 0);
- fb_thrift_write_i64(thft, api->uid);
-
- /* Write the information string */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1);
- fb_thrift_write_str(thft, FB_API_MQTT_AGENT);
-
- /* Write the UNKNOWN ("cp"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2);
- fb_thrift_write_i64(thft, 23);
-
- /* Write the UNKNOWN ("ecp"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 4, 3);
- fb_thrift_write_i64(thft, 26);
-
- /* Write the UNKNOWN */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 5, 4);
- fb_thrift_write_i32(thft, 1);
-
- /* Write the UNKNOWN ("no_auto_fg"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 6, 5);
- fb_thrift_write_bool(thft, TRUE);
-
- /* Write the visibility state */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 7, 6);
- fb_thrift_write_bool(thft, !api->invisible);
-
- /* Write the device identifier */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 8, 7);
- fb_thrift_write_str(thft, api->did);
-
- /* Write the UNKNOWN ("fg"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_BOOL, 9, 8);
- fb_thrift_write_bool(thft, TRUE);
-
- /* Write the UNKNOWN ("nwt"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 10, 9);
- fb_thrift_write_i32(thft, 1);
-
- /* Write the UNKNOWN ("nwst"?) */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I32, 11, 10);
- fb_thrift_write_i32(thft, 0);
-
- /* Write the MQTT identifier */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 12, 11);
- fb_thrift_write_i64(thft, api->mid);
-
- /* Write the UNKNOWN */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_LIST, 14, 12);
- fb_thrift_write_list(thft, FB_THRIFT_TYPE_I32, 0);
- fb_thrift_write_stop(thft);
-
- /* Write the token */
- fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 15, 14);
- fb_thrift_write_str(thft, api->token);
-
- /* Write the STOP for the struct */
- fb_thrift_write_stop(thft);
-
- bytes = fb_thrift_get_bytes(thft);
- cytes = fb_util_zlib_deflate(bytes, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(thft);
- return;
- );
-
- fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes, "Writing connect");
- fb_mqtt_connect(mqtt, flags, cytes);
-
- g_byte_array_free(cytes, TRUE);
- g_object_unref(thft);
-}
-
-static void
-fb_api_connect_queue(FbApi *api)
-{
- FbApiMessage *msg;
- gchar *json;
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_int(bldr, "delta_batch_size", 125);
- fb_json_bldr_add_int(bldr, "max_deltas_able_to_process", 1250);
- fb_json_bldr_add_int(bldr, "sync_api_version", 3);
- fb_json_bldr_add_str(bldr, "encoding", "JSON");
-
- if (api->stoken == NULL) {
- fb_json_bldr_add_int(bldr, "initial_titan_sequence_id", api->sid);
- fb_json_bldr_add_str(bldr, "device_id", api->did);
- fb_json_bldr_add_int(bldr, "entity_fbid", api->uid);
-
- fb_json_bldr_obj_begin(bldr, "queue_params");
- fb_json_bldr_add_str(bldr, "buzz_on_deltas_enabled", "false");
-
- fb_json_bldr_obj_begin(bldr, "graphql_query_hashes");
- fb_json_bldr_add_str(bldr, "xma_query_id",
- G_STRINGIFY(FB_API_QUERY_XMA));
- fb_json_bldr_obj_end(bldr);
-
- fb_json_bldr_obj_begin(bldr, "graphql_query_params");
- fb_json_bldr_obj_begin(bldr, G_STRINGIFY(FB_API_QUERY_XMA));
- fb_json_bldr_add_str(bldr, "xma_id", "<ID>");
- fb_json_bldr_obj_end(bldr);
- fb_json_bldr_obj_end(bldr);
- fb_json_bldr_obj_end(bldr);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/messenger_sync_create_queue", "%s",
- json);
- g_free(json);
- return;
- }
-
- fb_json_bldr_add_int(bldr, "last_seq_id", api->sid);
- fb_json_bldr_add_str(bldr, "sync_token", api->stoken);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/messenger_sync_get_diffs", "%s", json);
- g_signal_emit_by_name(api, "connect");
- g_free(json);
-
- if (!g_queue_is_empty(api->msgs)) {
- msg = g_queue_peek_head(api->msgs);
- fb_api_message_send(api, msg);
- }
-
- if (api->retrying) {
- api->retrying = FALSE;
- fb_util_debug_info("Reconnected the MQTT stream");
- }
-}
-
-static void
-fb_api_cb_seqid(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *str;
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.viewer.message_threads.sync_sequence_id");
- fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE,
- "$.viewer.message_threads.unread_count");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- str = fb_json_values_next_str(values, "0");
- api->sid = g_ascii_strtoll(str, NULL, 10);
- api->unread = fb_json_values_next_int(values, 0);
-
- if (api->sid == 0) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to get sync_sequence_id"));
- } else {
- fb_api_connect_queue(api);
- }
-
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-static void
-fb_api_cb_mqtt_connect(FbMqtt *mqtt, gpointer data)
-{
- FbApi *api = data;
- gchar *json;
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_bool(bldr, "foreground", TRUE);
- fb_json_bldr_add_int(bldr, "keepalive_timeout", FB_MQTT_KA);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/foreground_state", "%s", json);
- g_free(json);
-
- fb_mqtt_subscribe(mqtt,
- "/inbox", 0,
- "/mercury", 0,
- "/messaging_events", 0,
- "/orca_presence", 0,
- "/orca_typing_notifications", 0,
- "/pp", 0,
- "/t_ms", 0,
- "/t_p", 0,
- "/t_rtc", 0,
- "/webrtc", 0,
- "/webrtc_response", 0,
- NULL
- );
-
- /* Notifications seem to lead to some sort of sending rate limit */
- fb_mqtt_unsubscribe(mqtt, "/orca_message_notifications", NULL);
-
- if (api->sid == 0) {
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_str(bldr, "1", "0");
- fb_api_http_query(api, FB_API_QUERY_SEQ_ID, bldr,
- fb_api_cb_seqid);
- } else {
- fb_api_connect_queue(api);
- }
-}
-
-static void
-fb_api_cb_publish_mark(FbApi *api, GByteArray *pload)
-{
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, "$.succeeded");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- return;
- );
-
- if (!fb_json_values_next_bool(values, TRUE)) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to mark thread as read"));
- }
-
- g_object_unref(values);
- json_node_free(root);
-}
-
-static GSList *
-fb_api_event_parse(G_GNUC_UNUSED FbApi *api, FbApiEvent *event, GSList *events,
- JsonNode *root, GError **error)
-{
- const gchar *str;
- FbApiEvent *devent;
- FbJsonValues *values;
- GError *err = NULL;
- guint i;
-
- static const struct {
- FbApiEventType type;
- const gchar *expr;
- } evtypes[] = {
- {
- FB_API_EVENT_TYPE_THREAD_USER_ADDED,
- "$.log_message_data.added_participants"
- }, {
- FB_API_EVENT_TYPE_THREAD_USER_REMOVED,
- "$.log_message_data.removed_participants"
- }
- };
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.log_message_type");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.author");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.log_message_data.name");
- fb_json_values_update(values, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- g_object_unref(values);
- return events;
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (g_strcmp0(str, "log:thread-name") == 0) {
- str = fb_json_values_next_str(values, "");
- str = strrchr(str, ':');
-
- if (str != NULL) {
- devent = g_new(FbApiEvent, 1);
- devent->type = FB_API_EVENT_TYPE_THREAD_TOPIC;
- devent->uid = FB_ID_FROM_STR(str + 1);
- devent->tid = event->tid;
- devent->text = fb_json_values_next_str_dup(values, NULL);
- events = g_slist_prepend(events, devent);
- }
- }
-
- g_object_unref(values);
-
- for (i = 0; i < G_N_ELEMENTS(evtypes); i++) {
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$");
- fb_json_values_set_array(values, FALSE, evtypes[i].expr);
-
- while (fb_json_values_update(values, &err)) {
- str = fb_json_values_next_str(values, "");
- str = strrchr(str, ':');
-
- if (str != NULL) {
- devent = g_new0(FbApiEvent, 1);
- devent->type = evtypes[i].type;
- devent->uid = FB_ID_FROM_STR(str + 1);
- devent->tid = event->tid;
- events = g_slist_prepend(events, devent);
- }
- }
-
- g_object_unref(values);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- break;
- }
- }
-
- return events;
-}
-
-static void
-fb_api_cb_publish_mercury(FbApi *api, GByteArray *pload)
-{
- const gchar *str;
- FbApiEvent event;
- FbJsonValues *values;
- GError *err = NULL;
- GSList *events = NULL;
- JsonNode *root;
- JsonNode *node;
-
- if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.thread_fbid");
- fb_json_values_set_array(values, FALSE, "$.actions");
-
- while (fb_json_values_update(values, &err)) {
- fb_api_event_reset(&event, FALSE);
- str = fb_json_values_next_str(values, "0");
- event.tid = FB_ID_FROM_STR(str);
-
- node = fb_json_values_get_root(values);
- events = fb_api_event_parse(api, &event, events, node, &err);
- }
-
- if (G_LIKELY(err == NULL)) {
- events = g_slist_reverse(events);
- g_signal_emit_by_name(api, "events", events);
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(events, (GDestroyNotify) fb_api_event_free);
- g_object_unref(values);
- json_node_free(root);
-
-}
-
-static void
-fb_api_cb_publish_typing(FbApi *api, GByteArray *pload)
-{
- const gchar *str;
- FbApiTyping typg;
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.type");
- fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.sender_fbid");
- fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.state");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- return;
- );
-
- str = fb_json_values_next_str(values, NULL);
-
- if (g_ascii_strcasecmp(str, "typ") == 0) {
- typg.uid = fb_json_values_next_int(values, 0);
-
- if (typg.uid != api->uid) {
- typg.state = fb_json_values_next_int(values, 0);
- g_signal_emit_by_name(api, "typing", &typg);
- }
- }
-
- g_object_unref(values);
- json_node_free(root);
-}
-
-static void
-fb_api_cb_publish_ms_r(FbApi *api, GByteArray *pload)
-{
- FbApiMessage *msg;
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_json_chk(api, pload->data, pload->len, &root)) {
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.succeeded");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- return;
- );
-
- if (fb_json_values_next_bool(values, TRUE)) {
- /* Pop and free the successful message */
- msg = g_queue_pop_head(api->msgs);
- fb_api_message_free(msg);
-
- if (!g_queue_is_empty(api->msgs)) {
- msg = g_queue_peek_head(api->msgs);
- fb_api_message_send(api, msg);
- }
- } else {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- "Failed to send message");
- }
-
- g_object_unref(values);
- json_node_free(root);
-}
-
-static gchar *
-fb_api_xma_parse(G_GNUC_UNUSED FbApi *api, const char *body, JsonNode *root,
- GError **error)
-{
- const gchar *str;
- const gchar *url;
- FbHttpParams *params;
- FbJsonValues *values;
- gchar *text;
- GError *err = NULL;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.story_attachment.target.__type__.name");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.story_attachment.url");
- fb_json_values_update(values, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- g_object_unref(values);
- return NULL;
- }
-
- str = fb_json_values_next_str(values, NULL);
- url = fb_json_values_next_str(values, NULL);
-
- if ((str == NULL) || (url == NULL)) {
- text = g_strdup(_("<Unsupported Attachment>"));
- g_object_unref(values);
- return text;
- }
-
- if (purple_strequal(str, "ExternalUrl")) {
- params = fb_http_params_new_parse(url, TRUE);
- if (g_str_has_prefix(url, FB_API_FBRPC_PREFIX)) {
- text = fb_http_params_dup_str(params, "target_url", NULL);
- } else {
- text = fb_http_params_dup_str(params, "u", NULL);
- }
- fb_http_params_free(params);
- } else {
- text = g_strdup(url);
- }
-
- if (fb_http_urlcmp(body, text, FALSE)) {
- g_free(text);
- g_object_unref(values);
- return NULL;
- }
-
- g_object_unref(values);
- return text;
-}
-
-static GSList *
-fb_api_message_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg,
- GSList *msgs, const gchar *body, JsonNode *root,
- GError **error)
-{
- const gchar *str;
- FbApiMessage *dmsg;
- FbId id;
- FbJsonValues *values;
- gchar *xma;
- GError *err = NULL;
- JsonNode *node;
- JsonNode *xode;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.xmaGraphQL");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE, "$.fbid");
- fb_json_values_set_array(values, FALSE, "$.attachments");
-
- while (fb_json_values_update(values, &err)) {
- str = fb_json_values_next_str(values, NULL);
-
- if (str == NULL) {
- id = fb_json_values_next_int(values, 0);
- dmsg = g_memdup2(msg, sizeof(*msg));
- fb_api_attach(api, id, mid, dmsg);
- continue;
- }
-
- node = fb_json_node_new(str, -1, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
-
- xode = fb_json_node_get_nth(node, 0);
- xma = fb_api_xma_parse(api, body, xode, &err);
-
- if (xma != NULL) {
- dmsg = g_memdup2(msg, sizeof(*msg));
- dmsg->text = xma;
- msgs = g_slist_prepend(msgs, dmsg);
- }
-
- json_node_free(node);
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
- }
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- }
-
- g_object_unref(values);
- return msgs;
-}
-
-
-static GSList *
-fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error);
-
-static GSList *
-fb_api_cb_publish_ms_event(FbApi *api, JsonNode *root, GSList *events, FbApiEventType type, GError **error);
-
-static void
-fb_api_cb_publish_mst(FbThrift *thft, GError **error)
-{
- if (fb_thrift_read_isstop(thft)) {
- FB_API_TCHK(fb_thrift_read_stop(thft));
- } else {
- FbThriftType type;
- gint16 id;
-
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
- FB_API_TCHK(type == FB_THRIFT_TYPE_STRING);
- // FB_API_TCHK(id == 2);
- FB_API_TCHK(fb_thrift_read_str(thft, NULL));
- FB_API_TCHK(fb_thrift_read_stop(thft));
- }
-}
-
-static void
-fb_api_cb_publish_ms(FbApi *api, GByteArray *pload)
-{
- const gchar *data;
- FbJsonValues *values;
- FbThrift *thft;
- gchar *stoken;
- GError *err = NULL;
- GList *elms, *l;
- GSList *msgs = NULL;
- GSList *events = NULL;
- guint size;
- JsonNode *root;
- JsonNode *node;
- JsonArray *arr;
-
- static const struct {
- const gchar *member;
- FbApiEventType type;
- gboolean is_message;
- } event_types[] = {
- {"deltaNewMessage", 0, 1},
- {"deltaThreadName", FB_API_EVENT_TYPE_THREAD_TOPIC, 0},
- {"deltaParticipantsAddedToGroupThread", FB_API_EVENT_TYPE_THREAD_USER_ADDED, 0},
- {"deltaParticipantLeftGroupThread", FB_API_EVENT_TYPE_THREAD_USER_REMOVED, 0},
- };
-
- /* Read identifier string (for Facebook employees) */
- thft = fb_thrift_new(pload, 0);
- fb_api_cb_publish_mst(thft, &err);
- size = fb_thrift_get_pos(thft);
- g_object_unref(thft);
-
- FB_API_ERROR_EMIT(api, err,
- return;
- );
-
- g_return_if_fail(size < pload->len);
- data = (gchar *) pload->data + size;
- size = pload->len - size;
-
- if (!fb_api_json_chk(api, data, size, &root)) {
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.lastIssuedSeqId");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.syncToken");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- return;
- );
-
- api->sid = fb_json_values_next_int(values, 0);
- stoken = fb_json_values_next_str_dup(values, NULL);
- g_object_unref(values);
-
- if (G_UNLIKELY(stoken != NULL)) {
- g_free(api->stoken);
- api->stoken = stoken;
- g_signal_emit_by_name(api, "connect");
- json_node_free(root);
- return;
- }
-
- arr = fb_json_node_get_arr(root, "$.deltas", NULL);
- elms = json_array_get_elements(arr);
-
- for (l = elms; l != NULL; l = l->next) {
- guint i = 0;
- JsonObject *o = json_node_get_object(l->data);
-
- for (i = 0; i < G_N_ELEMENTS(event_types); i++) {
- if ((node = json_object_get_member(o, event_types[i].member))) {
- if (event_types[i].is_message) {
- msgs = fb_api_cb_publish_ms_new_message(
- api, node, msgs, &err
- );
- } else {
- events = fb_api_cb_publish_ms_event(
- api, node, events, event_types[i].type, &err
- );
- }
- }
- }
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
- }
-
- g_list_free(elms);
- json_array_unref(arr);
-
- if (G_LIKELY(err == NULL)) {
- if (msgs) {
- msgs = g_slist_reverse(msgs);
- g_signal_emit_by_name(api, "messages", msgs);
- }
-
- if (events) {
- events = g_slist_reverse(events);
- g_signal_emit_by_name(api, "events", events);
- }
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
- g_slist_free_full(events, (GDestroyNotify) fb_api_event_free);
- json_node_free(root);
-}
-
-static GSList *
-fb_api_cb_publish_ms_new_message(FbApi *api, JsonNode *root, GSList *msgs, GError **error)
-{
- const gchar *body;
- const gchar *str;
- GError *err = NULL;
- FbApiMessage *dmsg;
- FbApiMessage msg;
- FbId id;
- FbId oid;
- FbJsonValues *values;
- JsonNode *node;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata.offlineThreadingId");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata.actorFbId");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata"
- ".threadKey.otherUserFbId");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata"
- ".threadKey.threadFbId");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata.timestamp");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.body");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.stickerId");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.messageMetadata.messageId");
-
- if (fb_json_values_update(values, &err)) {
- id = fb_json_values_next_int(values, 0);
-
- /* Ignore everything but new messages */
- if (id == 0) {
- goto beach;
- }
-
- /* Ignore sequential duplicates */
- if (id == api->lastmid) {
- fb_util_debug_info("Ignoring duplicate %" FB_ID_FORMAT, id);
- goto beach;
- }
-
- api->lastmid = id;
- fb_api_message_reset(&msg, FALSE);
- msg.uid = fb_json_values_next_int(values, 0);
- oid = fb_json_values_next_int(values, 0);
- msg.tid = fb_json_values_next_int(values, 0);
- msg.tstamp = fb_json_values_next_int(values, 0);
-
- if (msg.uid == api->uid) {
- msg.flags |= FB_API_MESSAGE_FLAG_SELF;
-
- if (msg.tid == 0) {
- msg.uid = oid;
- }
- }
-
- body = fb_json_values_next_str(values, NULL);
-
- if (body != NULL) {
- dmsg = g_memdup2(&msg, sizeof(msg));
- dmsg->text = g_strdup(body);
- msgs = g_slist_prepend(msgs, dmsg);
- }
-
- id = fb_json_values_next_int(values, 0);
-
- if (id != 0) {
- dmsg = g_memdup2(&msg, sizeof(msg));
- fb_api_sticker(api, id, dmsg);
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (str == NULL) {
- goto beach;
- }
-
- node = fb_json_values_get_root(values);
- msgs = fb_api_message_parse_attach(api, str, &msg, msgs, body,
- node, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- goto beach;
- }
- }
-
-beach:
- g_object_unref(values);
- return msgs;
-}
-
-static GSList *
-fb_api_cb_publish_ms_event(G_GNUC_UNUSED FbApi *api, JsonNode *root,
- GSList *events, FbApiEventType type, GError **error)
-{
- FbApiEvent *event;
- FbJsonValues *values = NULL;
- FbJsonValues *values_inner = NULL;
- GError *err = NULL;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata.threadKey.threadFbId");
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.messageMetadata.actorFbId");
-
- switch (type) {
- case FB_API_EVENT_TYPE_THREAD_TOPIC:
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.name");
- break;
-
- case FB_API_EVENT_TYPE_THREAD_USER_ADDED:
- values_inner = fb_json_values_new(root);
-
- fb_json_values_add(values_inner, FB_JSON_TYPE_INT, FALSE,
- "$.userFbId");
-
- /* use the text field for the full name */
- fb_json_values_add(values_inner, FB_JSON_TYPE_STR, FALSE,
- "$.fullName");
-
- fb_json_values_set_array(values_inner, FALSE,
- "$.addedParticipants");
- break;
-
- case FB_API_EVENT_TYPE_THREAD_USER_REMOVED:
- fb_json_values_add(values, FB_JSON_TYPE_INT, FALSE,
- "$.leftParticipantFbId");
-
- /* use the text field for the kick message */
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.messageMetadata.adminText");
- break;
- }
-
- fb_json_values_update(values, &err);
-
- event = g_new0(FbApiEvent, 1);
- event->type = type;
- event->tid = fb_json_values_next_int(values, 0);
- event->uid = fb_json_values_next_int(values, 0);
-
- if (type == FB_API_EVENT_TYPE_THREAD_TOPIC) {
- event->text = fb_json_values_next_str_dup(values, NULL);
- } else if (type == FB_API_EVENT_TYPE_THREAD_USER_REMOVED) {
- /* overwrite actor with subject */
- event->uid = fb_json_values_next_int(values, 0);
- event->text = fb_json_values_next_str_dup(values, NULL);
- } else if (type == FB_API_EVENT_TYPE_THREAD_USER_ADDED) {
-
- while (fb_json_values_update(values_inner, &err)) {
- FbApiEvent *devent = g_new0(FbApiEvent, 1);
-
- devent->type = event->type;
- devent->uid = fb_json_values_next_int(values_inner, 0);
- devent->tid = event->tid;
- devent->text = fb_json_values_next_str_dup(values_inner, NULL);
-
- events = g_slist_prepend(events, devent);
- }
- g_clear_pointer(&event, fb_api_event_free);
- g_object_unref(values_inner);
- }
-
- g_object_unref(values);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- } else if (event) {
- events = g_slist_prepend(events, event);
- }
-
- return events;
-}
-
-static void
-fb_api_cb_publish_pt(FbThrift *thft, GSList **presences, GError **error)
-{
- FbApiPresence *api_presence;
- FbThriftType type;
- gint16 id;
- gint32 i32;
- gint64 i64;
- guint i;
- guint size = 0;
-
- /* Read identifier string (for Facebook employees) */
- FB_API_TCHK(fb_thrift_read_str(thft, NULL));
-
- /* Read the full list boolean field */
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
- FB_API_TCHK(type == FB_THRIFT_TYPE_BOOL);
- FB_API_TCHK(id == 1);
- FB_API_TCHK(fb_thrift_read_bool(thft, NULL));
-
- /* Read the list field */
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
- FB_API_TCHK(type == FB_THRIFT_TYPE_LIST);
- FB_API_TCHK(id == 2);
-
- /* Read the list */
- FB_API_TCHK(fb_thrift_read_list(thft, &type, &size));
- FB_API_TCHK(type == FB_THRIFT_TYPE_STRUCT);
-
- for (i = 0; i < size; i++) {
- /* Read the user identifier field */
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, 0));
- FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
- FB_API_TCHK(id == 1);
- FB_API_TCHK(fb_thrift_read_i64(thft, &i64));
-
- /* Read the active field */
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
- FB_API_TCHK(type == FB_THRIFT_TYPE_I32);
- FB_API_TCHK(id == 2);
- FB_API_TCHK(fb_thrift_read_i32(thft, &i32));
-
- api_presence = g_new0(FbApiPresence, 1);
- api_presence->uid = i64;
- api_presence->active = i32 != 0;
- *presences = g_slist_prepend(*presences, api_presence);
-
- fb_util_debug_info("Presence: %" FB_ID_FORMAT " (%d) id: %d",
- i64, i32 != 0, id);
-
- while (id <= 6) {
- if (fb_thrift_read_isstop(thft)) {
- break;
- }
-
- FB_API_TCHK(fb_thrift_read_field(thft, &type, &id, id));
-
- switch (id) {
- case 3:
- /* Read the last active timestamp field */
- FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
- FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
- break;
-
- case 4:
- /* Read the active client bits field */
- FB_API_TCHK(type == FB_THRIFT_TYPE_I16);
- FB_API_TCHK(fb_thrift_read_i16(thft, NULL));
- break;
-
- case 5:
- /* Read the VoIP compatibility bits field */
- FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
- FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
- break;
-
- case 6:
- /* Unknown new field */
- FB_API_TCHK(type == FB_THRIFT_TYPE_I64);
- FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
- break;
-
- default:
- /* Try to read unknown fields as varint */
- FB_API_TCHK(type == FB_THRIFT_TYPE_I16 ||
- type == FB_THRIFT_TYPE_I32 ||
- type == FB_THRIFT_TYPE_I64);
- FB_API_TCHK(fb_thrift_read_i64(thft, NULL));
- break;
- }
- }
-
- /* Read the field stop */
- FB_API_TCHK(fb_thrift_read_stop(thft));
- }
-
- /* Read the field stop */
- if (fb_thrift_read_isstop(thft)) {
- FB_API_TCHK(fb_thrift_read_stop(thft));
- }
-}
-
-static void
-fb_api_cb_publish_p(FbApi *api, GByteArray *pload)
-{
- FbThrift *thft;
- GError *err = NULL;
- GSList *presences = NULL;
-
- thft = fb_thrift_new(pload, 0);
- fb_api_cb_publish_pt(thft, &presences, &err);
- g_object_unref(thft);
-
- if (G_LIKELY(err == NULL)) {
- g_signal_emit_by_name(api, "presences", presences);
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(presences, (GDestroyNotify)fb_api_presence_free);
-}
-
-static void
-fb_api_cb_mqtt_publish(G_GNUC_UNUSED FbMqtt *mqtt, const char *topic,
- GByteArray *pload, gpointer data)
-{
- FbApi *api = data;
- gboolean comp;
- GByteArray *bytes;
- GError *err = NULL;
- guint i;
-
- static const struct {
- const gchar *topic;
- void (*func) (FbApi *api, GByteArray *pload);
- } parsers[] = {
- {"/mark_thread_response", fb_api_cb_publish_mark},
- {"/mercury", fb_api_cb_publish_mercury},
- {"/orca_typing_notifications", fb_api_cb_publish_typing},
- {"/send_message_response", fb_api_cb_publish_ms_r},
- {"/t_ms", fb_api_cb_publish_ms},
- {"/t_p", fb_api_cb_publish_p}
- };
-
- comp = fb_util_zlib_test(pload);
-
- if (G_LIKELY(comp)) {
- bytes = fb_util_zlib_inflate(pload, &err);
- FB_API_ERROR_EMIT(api, err, return);
- } else {
- bytes = (GByteArray *) pload;
- }
-
- fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes,
- "Reading message (topic: %s)",
- topic);
-
- for (i = 0; i < G_N_ELEMENTS(parsers); i++) {
- if (g_ascii_strcasecmp(topic, parsers[i].topic) == 0) {
- parsers[i].func(api, bytes);
- break;
- }
- }
-
- if (G_LIKELY(comp)) {
- g_byte_array_free(bytes, TRUE);
- }
-}
-
-FbApi *
-fb_api_new(PurpleConnection *gc, GProxyResolver *resolver)
-{
- FbApi *api;
-
- api = g_object_new(FB_TYPE_API, NULL);
-
- api->gc = gc;
- api->cons = soup_session_new_with_options(
- "proxy-resolver", resolver,
- "user-agent", FB_API_AGENT,
- NULL);
- api->mqtt = fb_mqtt_new(gc);
-
- g_signal_connect(api->mqtt,
- "connect",
- G_CALLBACK(fb_api_cb_mqtt_connect),
- api);
- g_signal_connect(api->mqtt,
- "error",
- G_CALLBACK(fb_api_cb_mqtt_error),
- api);
- g_signal_connect(api->mqtt,
- "open",
- G_CALLBACK(fb_api_cb_mqtt_open),
- api);
- g_signal_connect(api->mqtt,
- "publish",
- G_CALLBACK(fb_api_cb_mqtt_publish),
- api);
-
- return api;
-}
-
-void
-fb_api_rehash(FbApi *api)
-{
- g_return_if_fail(FB_IS_API(api));
-
- if (api->cid == NULL) {
- api->cid = fb_util_rand_alnum(32);
- }
-
- if (api->did == NULL) {
- api->did = g_uuid_string_random();
- }
-
- if (api->mid == 0) {
- api->mid = g_random_int();
- }
-
- if (strlen(api->cid) > 20) {
- api->cid = g_realloc_n(api->cid , 21, sizeof *api->cid);
- api->cid[20] = 0;
- }
-}
-
-gboolean
-fb_api_is_invisible(FbApi *api)
-{
- g_return_val_if_fail(FB_IS_API(api), FALSE);
-
- return api->invisible;
-}
-
-static void
-fb_api_error_literal(FbApi *api, FbApiError error, const gchar *msg)
-{
- GError *err;
-
- g_return_if_fail(FB_IS_API(api));
-
- err = g_error_new_literal(FB_API_ERROR, error, msg);
-
- fb_api_error_emit(api, err);
-}
-
-void
-fb_api_error(FbApi *api, FbApiError error, const gchar *format, ...)
-{
- GError *err;
- va_list ap;
-
- g_return_if_fail(FB_IS_API(api));
-
- va_start(ap, format);
- err = g_error_new_valist(FB_API_ERROR, error, format, ap);
- va_end(ap);
-
- fb_api_error_emit(api, err);
-}
-
-void
-fb_api_error_emit(FbApi *api, GError *error)
-{
- g_return_if_fail(FB_IS_API(api));
- g_return_if_fail(error != NULL);
-
- g_signal_emit_by_name(api, "error", error);
- g_error_free(error);
-}
-
-static void
-fb_api_cb_attach(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *str;
- FbApiMessage *msg;
- FbJsonValues *values;
- gchar *name;
- GError *err = NULL;
- GSList *msgs = NULL;
- guint i;
- JsonNode *root;
-
- static const gchar *imgexts[] = {".jpg", ".png", ".gif"};
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.filename");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.redirect_uri");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- msg = g_object_steal_data(G_OBJECT(soupmsg), "fb-api-msg");
- str = fb_json_values_next_str(values, NULL);
- name = g_ascii_strdown(str, -1);
-
- for (i = 0; i < G_N_ELEMENTS(imgexts); i++) {
- if (g_str_has_suffix(name, imgexts[i])) {
- msg->flags |= FB_API_MESSAGE_FLAG_IMAGE;
- break;
- }
- }
-
- g_free(name);
- msg->text = fb_json_values_next_str_dup(values, NULL);
- msgs = g_slist_prepend(msgs, msg);
-
- g_signal_emit_by_name(api, "messages", msgs);
- g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-static void
-fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg)
-{
- FbHttpParams *prms;
- SoupMessage *http;
-
- prms = fb_http_params_new();
- fb_http_params_set_str(prms, "mid", msgid);
- fb_http_params_set_strf(prms, "aid", "%" FB_ID_FORMAT, aid);
-
- http = fb_api_http_req(api, FB_API_URL_ATTACH, "getAttachment",
- "messaging.getAttachment", prms,
- fb_api_cb_attach);
- g_object_set_data_full(G_OBJECT(http), "fb-api-msg", msg,
- (GDestroyNotify)fb_api_message_free);
-}
-
-static void
-fb_api_cb_auth(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token");
- fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- g_free(api->token);
- api->token = fb_json_values_next_str_dup(values, NULL);
- api->uid = fb_json_values_next_int(values, 0);
-
- g_signal_emit_by_name(api, "auth");
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_auth(FbApi *api, const gchar *user, const gchar *pass)
-{
- FbHttpParams *prms;
-
- prms = fb_http_params_new();
- fb_http_params_set_str(prms, "email", user);
- fb_http_params_set_str(prms, "password", pass);
- fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login",
- prms, fb_api_cb_auth);
-}
-
-static gchar *
-fb_api_user_icon_checksum(gchar *icon)
-{
- gchar *csum;
- FbHttpParams *prms;
-
- if (G_UNLIKELY(icon == NULL)) {
- return NULL;
- }
-
- prms = fb_http_params_new_parse(icon, TRUE);
- csum = fb_http_params_dup_str(prms, "oh", NULL);
- fb_http_params_free(prms);
-
- if (G_UNLIKELY(csum == NULL)) {
- /* Revert to the icon URL as the unique checksum */
- csum = g_strdup(icon);
- }
-
- return csum;
-}
-
-static void
-fb_api_cb_contact(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *str;
- FbApiUser user;
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *node;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- node = fb_json_node_get_nth(root, 0);
-
- if (node == NULL) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to obtain contact information"));
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(node);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.name");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.profile_pic_large.uri");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- fb_api_user_reset(&user, FALSE);
- str = fb_json_values_next_str(values, "0");
- user.uid = FB_ID_FROM_STR(str);
- user.name = fb_json_values_next_str_dup(values, NULL);
- user.icon = fb_json_values_next_str_dup(values, NULL);
-
- user.csum = fb_api_user_icon_checksum(user.icon);
-
- g_signal_emit_by_name(api, "contact", &user);
- fb_api_user_reset(&user, TRUE);
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_contact(FbApi *api, FbId uid)
-{
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid);
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "1", "true");
- fb_api_http_query(api, FB_API_QUERY_CONTACT, bldr, fb_api_cb_contact);
-}
-
-static GSList *
-fb_api_cb_contacts_nodes(FbApi *api, JsonNode *root, GSList *users)
-{
- const gchar *str;
- FbApiUser *user;
- FbId uid;
- FbJsonValues *values;
- gboolean is_array;
- GError *err = NULL;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.represented_profile.id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.represented_profile.friendship_status");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.structured_name.text");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.hugePictureUrl.uri");
-
- is_array = (JSON_NODE_TYPE(root) == JSON_NODE_ARRAY);
-
- if (is_array) {
- fb_json_values_set_array(values, FALSE, "$");
- }
-
- while (fb_json_values_update(values, &err)) {
- str = fb_json_values_next_str(values, "0");
- uid = FB_ID_FROM_STR(str);
- str = fb_json_values_next_str(values, NULL);
-
- if ((!purple_strequal(str, "ARE_FRIENDS") &&
- (uid != api->uid)) || (uid == 0))
- {
- if (!is_array) {
- break;
- }
- continue;
- }
-
- user = g_new0(FbApiUser, 1);
- user->uid = uid;
- user->name = fb_json_values_next_str_dup(values, NULL);
- user->icon = fb_json_values_next_str_dup(values, NULL);
-
- user->csum = fb_api_user_icon_checksum(user->icon);
-
- users = g_slist_prepend(users, user);
-
- if (!is_array) {
- break;
- }
- }
-
- g_object_unref(values);
-
- return users;
-}
-
-/* base64(contact:<our id>:<their id>:<whatever>) */
-static GSList *
-fb_api_cb_contacts_parse_removed(G_GNUC_UNUSED FbApi *api, JsonNode *node,
- GSList *users)
-{
- gsize len;
- char **split;
- char *decoded = (char *) g_base64_decode(json_node_get_string(node), &len);
-
- g_return_val_if_fail(decoded[len] == '\0', users);
- g_return_val_if_fail(len == strlen(decoded), users);
- g_return_val_if_fail(g_str_has_prefix(decoded, "contact:"), users);
-
- split = g_strsplit_set(decoded, ":", 4);
-
- if (g_strv_length(split) != 4) {
- g_strfreev(split);
- g_return_val_if_reached(users);
- }
-
- users = g_slist_prepend(users, g_strdup(split[2]));
-
- g_strfreev(split);
- g_free(decoded);
-
- return users;
-}
-
-static void
-fb_api_cb_contacts(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *cursor;
- const gchar *delta_cursor;
- FbJsonValues *values;
- gboolean complete;
- gboolean is_delta;
- GError *err = NULL;
- GList *l;
- GSList *users = NULL;
- JsonNode *root;
- JsonNode *croot;
- JsonNode *node;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- croot = fb_json_node_get(root, "$.viewer.messenger_contacts.deltas", NULL);
- is_delta = (croot != NULL);
-
- if (!is_delta) {
- croot = fb_json_node_get(root, "$.viewer.messenger_contacts", NULL);
- node = fb_json_node_get(croot, "$.nodes", NULL);
- users = fb_api_cb_contacts_nodes(api, node, users);
- json_node_free(node);
-
- } else {
- GSList *added = NULL;
- GSList *removed = NULL;
- JsonArray *arr = fb_json_node_get_arr(croot, "$.nodes", NULL);
- GList *elms = json_array_get_elements(arr);
-
- for (l = elms; l != NULL; l = l->next) {
- if ((node = fb_json_node_get(l->data, "$.added", NULL))) {
- added = fb_api_cb_contacts_nodes(api, node, added);
- json_node_free(node);
- }
-
- if ((node = fb_json_node_get(l->data, "$.removed", NULL))) {
- removed = fb_api_cb_contacts_parse_removed(api, node, removed);
- json_node_free(node);
- }
- }
-
- g_signal_emit_by_name(api, "contacts-delta", added, removed);
-
- g_slist_free_full(added, (GDestroyNotify) fb_api_user_free);
- g_slist_free_full(removed, g_free);
-
- g_list_free(elms);
- json_array_unref(arr);
- }
-
- values = fb_json_values_new(croot);
- fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE,
- "$.page_info.has_next_page");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.page_info.delta_cursor");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.page_info.end_cursor");
- fb_json_values_update(values, NULL);
-
- complete = !fb_json_values_next_bool(values, FALSE);
-
- delta_cursor = fb_json_values_next_str(values, NULL);
-
- cursor = fb_json_values_next_str(values, NULL);
-
- if (G_UNLIKELY(err == NULL)) {
- if (is_delta || complete) {
- g_free(api->contacts_delta);
- api->contacts_delta = g_strdup(is_delta ? cursor : delta_cursor);
- }
-
- if (users) {
- g_signal_emit_by_name(api, "contacts", users, complete);
- }
-
- if (!complete) {
- fb_api_contacts_after(api, cursor);
- }
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(users, (GDestroyNotify) fb_api_user_free);
- g_object_unref(values);
-
- json_node_free(croot);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_contacts(FbApi *api)
-{
- JsonBuilder *bldr;
-
- g_return_if_fail(FB_IS_API(api));
-
- if (api->contacts_delta) {
- fb_api_contacts_delta(api, api->contacts_delta);
- return;
- }
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_str(bldr, NULL, "user");
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "1", G_STRINGIFY(FB_API_CONTACTS_COUNT));
- fb_api_http_query(api, FB_API_QUERY_CONTACTS, bldr,
- fb_api_cb_contacts);
-}
-
-static void
-fb_api_contacts_after(FbApi *api, const gchar *cursor)
-{
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_str(bldr, NULL, "user");
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "1", cursor);
- fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT));
- fb_api_http_query(api, FB_API_QUERY_CONTACTS_AFTER, bldr,
- fb_api_cb_contacts);
-}
-
-void
-fb_api_contacts_delta(FbApi *api, const gchar *delta_cursor)
-{
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
-
- fb_json_bldr_add_str(bldr, "0", delta_cursor);
-
- fb_json_bldr_arr_begin(bldr, "1");
- fb_json_bldr_add_str(bldr, NULL, "user");
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "2", G_STRINGIFY(FB_API_CONTACTS_COUNT));
- fb_api_http_query(api, FB_API_QUERY_CONTACTS_DELTA, bldr,
- fb_api_cb_contacts);
-}
-
-void
-fb_api_connect(FbApi *api, gboolean invisible)
-{
- g_return_if_fail(FB_IS_API(api));
-
- api->invisible = invisible;
- fb_mqtt_open(api->mqtt, FB_MQTT_HOST, FB_MQTT_PORT);
-}
-
-void
-fb_api_disconnect(FbApi *api)
-{
- g_return_if_fail(FB_IS_API(api));
-
- fb_mqtt_disconnect(api->mqtt);
-}
-
-static void
-fb_api_message_send(FbApi *api, FbApiMessage *msg)
-{
- const gchar *tpfx;
- FbId id;
- FbId mid;
- gchar *json;
- JsonBuilder *bldr;
-
- mid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int());
- api->lastmid = mid;
-
- if (msg->tid != 0) {
- tpfx = "tfbid_";
- id = msg->tid;
- } else {
- tpfx = "";
- id = msg->uid;
- }
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_str(bldr, "body", msg->text);
- fb_json_bldr_add_strf(bldr, "msgid", "%" FB_ID_FORMAT, mid);
- fb_json_bldr_add_strf(bldr, "sender_fbid", "%" FB_ID_FORMAT, api->uid);
- fb_json_bldr_add_strf(bldr, "to", "%s%" FB_ID_FORMAT, tpfx, id);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/send_message2", "%s", json);
- g_free(json);
-}
-
-void
-fb_api_message(FbApi *api, FbId id, gboolean thread, const gchar *text)
-{
- FbApiMessage *msg;
- gboolean empty;
-
- g_return_if_fail(FB_IS_API(api));
- g_return_if_fail(text != NULL);
-
- msg = g_new0(FbApiMessage, 1);
- msg->text = g_strdup(text);
-
- if (thread) {
- msg->tid = id;
- } else {
- msg->uid = id;
- }
-
- empty = g_queue_is_empty(api->msgs);
- g_queue_push_tail(api->msgs, msg);
-
- if (empty && fb_mqtt_connected(api->mqtt, FALSE)) {
- fb_api_message_send(api, msg);
- }
-}
-
-void
-fb_api_publish(FbApi *api, const gchar *topic, const gchar *format, ...)
-{
- GByteArray *bytes;
- GByteArray *cytes;
- gchar *msg;
- GError *err = NULL;
- va_list ap;
-
- g_return_if_fail(FB_IS_API(api));
- g_return_if_fail(topic != NULL);
- g_return_if_fail(format != NULL);
-
- va_start(ap, format);
- msg = g_strdup_vprintf(format, ap);
- va_end(ap);
-
- bytes = g_byte_array_new_take((guint8 *) msg, strlen(msg));
- cytes = fb_util_zlib_deflate(bytes, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_byte_array_free(bytes, TRUE);
- return;
- );
-
- fb_util_debug_hexdump(FB_UTIL_DEBUG_INFO, bytes,
- "Writing message (topic: %s)",
- topic);
-
- fb_mqtt_publish(api->mqtt, topic, cytes);
- g_byte_array_free(cytes, TRUE);
- g_byte_array_free(bytes, TRUE);
-}
-
-void
-fb_api_read(FbApi *api, FbId id, gboolean thread)
-{
- const gchar *key;
- gchar *json;
- JsonBuilder *bldr;
-
- g_return_if_fail(FB_IS_API(api));
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_bool(bldr, "state", TRUE);
- fb_json_bldr_add_int(bldr, "syncSeqId", api->sid);
- fb_json_bldr_add_str(bldr, "mark", "read");
-
- key = thread ? "threadFbId" : "otherUserFbId";
- fb_json_bldr_add_strf(bldr, key, "%" FB_ID_FORMAT, id);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/mark_thread", "%s", json);
- g_free(json);
-}
-
-static GSList *
-fb_api_cb_unread_parse_attach(FbApi *api, const gchar *mid, FbApiMessage *msg,
- GSList *msgs, JsonNode *root, GError **error)
-{
- const gchar *str;
- FbApiMessage *dmsg;
- FbId id;
- FbJsonValues *values;
- GError *err = NULL;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.attachment_fbid");
- fb_json_values_set_array(values, FALSE, "$.blob_attachments");
-
- while (fb_json_values_update(values, &err)) {
- str = fb_json_values_next_str(values, NULL);
- id = FB_ID_FROM_STR(str);
- dmsg = g_memdup2(msg, sizeof(*msg));
- fb_api_attach(api, id, mid, dmsg);
- }
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- }
-
- g_object_unref(values);
- return msgs;
-}
-
-static void
-fb_api_cb_unread_msgs(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *body;
- const gchar *str;
- FbApiMessage *dmsg;
- FbApiMessage msg;
- FbId id;
- FbId tid;
- FbJsonValues *values;
- gchar *xma;
- GError *err = NULL;
- GSList *msgs = NULL;
- JsonNode *node;
- JsonNode *root;
- JsonNode *xode;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- node = fb_json_node_get_nth(root, 0);
-
- if (node == NULL) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to obtain unread messages"));
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(node);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.thread_key.thread_fbid");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- g_object_unref(soupmsg);
- return;
- );
-
- fb_api_message_reset(&msg, FALSE);
- str = fb_json_values_next_str(values, "0");
- tid = FB_ID_FROM_STR(str);
- g_object_unref(values);
-
- values = fb_json_values_new(node);
- fb_json_values_add(values, FB_JSON_TYPE_BOOL, TRUE, "$.unread");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.message_sender.messaging_actor.id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.message.text");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.timestamp_precise");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.sticker.id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.message_id");
- fb_json_values_set_array(values, FALSE, "$.messages.nodes");
-
- while (fb_json_values_update(values, &err)) {
- if (!fb_json_values_next_bool(values, FALSE)) {
- continue;
- }
-
- str = fb_json_values_next_str(values, "0");
- body = fb_json_values_next_str(values, NULL);
-
- fb_api_message_reset(&msg, FALSE);
- msg.uid = FB_ID_FROM_STR(str);
- msg.tid = tid;
-
- str = fb_json_values_next_str(values, "0");
- msg.tstamp = g_ascii_strtoll(str, NULL, 10);
-
- if (body != NULL) {
- dmsg = g_memdup2(&msg, sizeof(msg));
- dmsg->text = g_strdup(body);
- msgs = g_slist_prepend(msgs, dmsg);
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (str != NULL) {
- dmsg = g_memdup2(&msg, sizeof(msg));
- id = FB_ID_FROM_STR(str);
- fb_api_sticker(api, id, dmsg);
- }
-
- node = fb_json_values_get_root(values);
- xode = fb_json_node_get(node, "$.extensible_attachment", NULL);
-
- if (xode != NULL) {
- xma = fb_api_xma_parse(api, body, xode, &err);
-
- if (xma != NULL) {
- dmsg = g_memdup2(&msg, sizeof(msg));
- dmsg->text = xma;
- msgs = g_slist_prepend(msgs, dmsg);
- }
-
- json_node_free(xode);
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (str == NULL) {
- continue;
- }
-
- msgs = fb_api_cb_unread_parse_attach(api, str, &msg, msgs,
- node, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
- }
-
- if (G_UNLIKELY(err == NULL)) {
- msgs = g_slist_reverse(msgs);
- g_signal_emit_by_name(api, "messages", msgs);
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-static void
-fb_api_cb_unread(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *id;
- FbJsonValues *values;
- GError *err = NULL;
- gint64 count;
- JsonBuilder *bldr;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.unread_count");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.thread_key.other_user_id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.thread_key.thread_fbid");
- fb_json_values_set_array(values, FALSE, "$.viewer.message_threads"
- ".nodes");
-
- while (fb_json_values_update(values, &err)) {
- count = fb_json_values_next_int(values, -5);
-
- if (count < 1) {
- continue;
- }
-
- id = fb_json_values_next_str(values, NULL);
-
- if (id == NULL) {
- id = fb_json_values_next_str(values, "0");
- }
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_str(bldr, NULL, id);
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "10", "true");
- fb_json_bldr_add_str(bldr, "11", "true");
- fb_json_bldr_add_int(bldr, "12", count);
- fb_json_bldr_add_str(bldr, "13", "false");
- fb_api_http_query(api, FB_API_QUERY_THREAD, bldr,
- fb_api_cb_unread_msgs);
- }
-
- if (G_UNLIKELY(err != NULL)) {
- fb_api_error_emit(api, err);
- }
-
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_unread(FbApi *api)
-{
- JsonBuilder *bldr;
-
- g_return_if_fail(FB_IS_API(api));
-
- if (api->unread < 1) {
- return;
- }
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_str(bldr, "2", "true");
- fb_json_bldr_add_int(bldr, "1", api->unread);
- fb_json_bldr_add_str(bldr, "12", "true");
- fb_json_bldr_add_str(bldr, "13", "false");
- fb_api_http_query(api, FB_API_QUERY_THREADS, bldr,
- fb_api_cb_unread);
-}
-
-static void
-fb_api_cb_sticker(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- FbApiMessage *msg;
- FbJsonValues *values;
- GError *err = NULL;
- GSList *msgs = NULL;
- JsonNode *node;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- node = fb_json_node_get_nth(root, 0);
- values = fb_json_values_new(node);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.thread_image.uri");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- msg = g_object_steal_data(G_OBJECT(soupmsg), "fb-api-msg");
- msg->flags |= FB_API_MESSAGE_FLAG_IMAGE;
- msg->text = fb_json_values_next_str_dup(values, NULL);
- msgs = g_slist_prepend(msgs, msg);
-
- g_signal_emit_by_name(api, "messages", msgs);
- g_slist_free_full(msgs, (GDestroyNotify) fb_api_message_free);
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-static void
-fb_api_sticker(FbApi *api, FbId sid, FbApiMessage *msg)
-{
- JsonBuilder *bldr;
- SoupMessage *http;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, sid);
- fb_json_bldr_arr_end(bldr);
-
- http = fb_api_http_query(api, FB_API_QUERY_STICKER, bldr,
- fb_api_cb_sticker);
- g_object_set_data_full(G_OBJECT(http), "fb-api-msg", msg,
- (GDestroyNotify)fb_api_message_free);
-}
-
-static gboolean
-fb_api_thread_parse(FbApi *api, FbApiThread *thrd, JsonNode *root,
- GError **error)
-{
- const gchar *str;
- FbApiUser *user;
- FbId uid;
- FbJsonValues *values;
- gboolean haself = FALSE;
- guint num_users = 0;
- GError *err = NULL;
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE,
- "$.thread_key.thread_fbid");
- fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.name");
- fb_json_values_update(values, &err);
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- g_object_unref(values);
- return FALSE;
- }
-
- str = fb_json_values_next_str(values, NULL);
-
- if (str == NULL) {
- g_object_unref(values);
- return FALSE;
- }
-
- thrd->tid = FB_ID_FROM_STR(str);
- thrd->topic = fb_json_values_next_str_dup(values, NULL);
- g_object_unref(values);
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.messaging_actor.id");
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE,
- "$.messaging_actor.name");
- fb_json_values_set_array(values, TRUE, "$.all_participants.nodes");
-
- while (fb_json_values_update(values, &err)) {
- str = fb_json_values_next_str(values, "0");
- uid = FB_ID_FROM_STR(str);
- num_users++;
-
- if (uid != api->uid) {
- user = g_new0(FbApiUser, 1);
- user->uid = uid;
- user->name = fb_json_values_next_str_dup(values, NULL);
- thrd->users = g_slist_prepend(thrd->users, user);
- } else {
- haself = TRUE;
- }
- }
-
- if (G_UNLIKELY(err != NULL)) {
- g_propagate_error(error, err);
- fb_api_thread_reset(thrd, TRUE);
- g_object_unref(values);
- return FALSE;
- }
-
- if (num_users < 2 || !haself) {
- g_object_unref(values);
- return FALSE;
- }
-
- g_object_unref(values);
- return TRUE;
-}
-
-static void
-fb_api_cb_thread(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- FbApiThread thrd;
- GError *err = NULL;
- JsonNode *node;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- node = fb_json_node_get_nth(root, 0);
-
- if (node == NULL) {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to obtain thread information"));
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- }
-
- fb_api_thread_reset(&thrd, FALSE);
-
- if (!fb_api_thread_parse(api, &thrd, node, &err)) {
- if (G_LIKELY(err == NULL)) {
- if (thrd.tid) {
- g_signal_emit_by_name(api, "thread-kicked", &thrd);
- } else {
- fb_api_error_literal(api, FB_API_ERROR_GENERAL,
- _("Failed to parse thread information"));
- }
- } else {
- fb_api_error_emit(api, err);
- }
- } else {
- g_signal_emit_by_name(api, "thread", &thrd);
- }
-
- fb_api_thread_reset(&thrd, TRUE);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_thread(FbApi *api, FbId tid)
-{
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_arr_begin(bldr, "0");
- fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, tid);
- fb_json_bldr_arr_end(bldr);
-
- fb_json_bldr_add_str(bldr, "10", "false");
- fb_json_bldr_add_str(bldr, "11", "false");
- fb_json_bldr_add_str(bldr, "13", "false");
- fb_api_http_query(api, FB_API_QUERY_THREAD, bldr, fb_api_cb_thread);
-}
-
-static void
-fb_api_cb_thread_create(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- const gchar *str;
- FbId tid;
- FbJsonValues *values;
- GError *err = NULL;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- values = fb_json_values_new(root);
- fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.id");
- fb_json_values_update(values, &err);
-
- FB_API_ERROR_EMIT(api, err,
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- str = fb_json_values_next_str(values, "0");
- tid = FB_ID_FROM_STR(str);
- g_signal_emit_by_name(api, "thread-create", tid);
-
- g_object_unref(values);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_thread_create(FbApi *api, GSList *uids)
-{
- FbHttpParams *prms;
- FbId *uid;
- gchar *json;
- GSList *l;
- JsonBuilder *bldr;
-
- g_return_if_fail(FB_IS_API(api));
- g_warn_if_fail(g_slist_length(uids) > 1);
-
- bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
- fb_json_bldr_obj_begin(bldr, NULL);
- fb_json_bldr_add_str(bldr, "type", "id");
- fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, api->uid);
- fb_json_bldr_obj_end(bldr);
-
- for (l = uids; l != NULL; l = l->next) {
- uid = l->data;
- fb_json_bldr_obj_begin(bldr, NULL);
- fb_json_bldr_add_str(bldr, "type", "id");
- fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, *uid);
- fb_json_bldr_obj_end(bldr);
- }
-
- json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
- prms = fb_http_params_new();
- fb_http_params_set_str(prms, "recipients", json);
- fb_api_http_req(api, FB_API_URL_THREADS, "createGroup", "POST",
- prms, fb_api_cb_thread_create);
- g_free(json);
-}
-
-void
-fb_api_thread_invite(FbApi *api, FbId tid, FbId uid)
-{
- FbHttpParams *prms;
- gchar *json;
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
- fb_json_bldr_obj_begin(bldr, NULL);
- fb_json_bldr_add_str(bldr, "type", "id");
- fb_json_bldr_add_strf(bldr, "id", "%" FB_ID_FORMAT, uid);
- fb_json_bldr_obj_end(bldr);
- json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
-
- prms = fb_http_params_new();
- fb_http_params_set_str(prms, "to", json);
- fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid);
- fb_api_http_req(api, FB_API_URL_PARTS, "addMembers", "POST",
- prms, fb_api_cb_http_bool);
- g_free(json);
-}
-
-void
-fb_api_thread_remove(FbApi *api, FbId tid, FbId uid)
-{
- FbHttpParams *prms;
- gchar *json;
- JsonBuilder *bldr;
-
- g_return_if_fail(FB_IS_API(api));
-
- prms = fb_http_params_new();
- fb_http_params_set_strf(prms, "id", "t_%" FB_ID_FORMAT, tid);
-
- if (uid == 0) {
- uid = api->uid;
- }
-
- if (uid != api->uid) {
- bldr = fb_json_bldr_new(JSON_NODE_ARRAY);
- fb_json_bldr_add_strf(bldr, NULL, "%" FB_ID_FORMAT, uid);
- json = fb_json_bldr_close(bldr, JSON_NODE_ARRAY, NULL);
- fb_http_params_set_str(prms, "to", json);
- g_free(json);
- }
-
- fb_api_http_req(api, FB_API_URL_PARTS, "removeMembers", "DELETE",
- prms, fb_api_cb_http_bool);
-}
-
-void
-fb_api_thread_topic(FbApi *api, FbId tid, const gchar *topic)
-{
- FbHttpParams *prms;
-
- prms = fb_http_params_new();
- fb_http_params_set_str(prms, "name", topic);
- fb_http_params_set_int(prms, "tid", tid);
- fb_api_http_req(api, FB_API_URL_TOPIC, "setThreadName",
- "messaging.setthreadname", prms, fb_api_cb_http_bool);
-}
-
-static void
-fb_api_cb_threads(GObject *source, GAsyncResult *result, gpointer data) {
- SoupSession *session = SOUP_SESSION(source);
- SoupMessage *soupmsg = data;
- FbApi *api = g_object_get_data(G_OBJECT(soupmsg), "facebook-api");
- FbApiThread *dthrd;
- FbApiThread thrd;
- GError *err = NULL;
- GList *elms;
- GList *l;
- GSList *thrds = NULL;
- JsonArray *arr;
- JsonNode *root;
-
- if (!fb_api_http_chk(api, session, result, soupmsg, &root)) {
- g_object_unref(soupmsg);
- return;
- }
-
- arr = fb_json_node_get_arr(root, "$.viewer.message_threads.nodes",
- &err);
- FB_API_ERROR_EMIT(api, err,
- json_node_free(root);
- g_object_unref(soupmsg);
- return;
- );
-
- elms = json_array_get_elements(arr);
-
- for (l = elms; l != NULL; l = l->next) {
- fb_api_thread_reset(&thrd, FALSE);
-
- if (fb_api_thread_parse(api, &thrd, l->data, &err)) {
- dthrd = g_memdup2(&thrd, sizeof(thrd));
- thrds = g_slist_prepend(thrds, dthrd);
- } else {
- fb_api_thread_reset(&thrd, TRUE);
- }
-
- if (G_UNLIKELY(err != NULL)) {
- break;
- }
- }
-
- if (G_LIKELY(err == NULL)) {
- thrds = g_slist_reverse(thrds);
- g_signal_emit_by_name(api, "threads", thrds);
- } else {
- fb_api_error_emit(api, err);
- }
-
- g_slist_free_full(thrds, (GDestroyNotify) fb_api_thread_free);
- g_list_free(elms);
- json_array_unref(arr);
- json_node_free(root);
- g_object_unref(soupmsg);
-}
-
-void
-fb_api_threads(FbApi *api)
-{
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_str(bldr, "2", "true");
- fb_json_bldr_add_str(bldr, "12", "false");
- fb_json_bldr_add_str(bldr, "13", "false");
- fb_api_http_query(api, FB_API_QUERY_THREADS, bldr, fb_api_cb_threads);
-}
-
-void
-fb_api_typing(FbApi *api, FbId uid, gboolean state)
-{
- gchar *json;
- JsonBuilder *bldr;
-
- bldr = fb_json_bldr_new(JSON_NODE_OBJECT);
- fb_json_bldr_add_int(bldr, "state", state != 0);
- fb_json_bldr_add_strf(bldr, "to", "%" FB_ID_FORMAT, uid);
-
- json = fb_json_bldr_close(bldr, JSON_NODE_OBJECT, NULL);
- fb_api_publish(api, "/typing", "%s", json);
- g_free(json);
-}
-
-FbApiEvent *
-fb_api_event_dup(const FbApiEvent *event)
-{
- FbApiEvent *ret;
-
- if (event == NULL) {
- return NULL;
- }
-
- ret = g_memdup2(event, sizeof *event);
- ret->text = g_strdup(event->text);
-
- return ret;
-}
-
-void
-fb_api_event_reset(FbApiEvent *event, gboolean deep)
-{
- g_return_if_fail(event != NULL);
-
- if (deep) {
- g_free(event->text);
- }
-
- memset(event, 0, sizeof *event);
-}
-
-void
-fb_api_event_free(FbApiEvent *event)
-{
- if (G_LIKELY(event != NULL)) {
- g_free(event->text);
- g_free(event);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiEvent, fb_api_event, fb_api_event_dup,
- fb_api_event_free)
-
-FbApiMessage *
-fb_api_message_dup(const FbApiMessage *msg)
-{
- FbApiMessage *ret;
-
- if (msg == NULL) {
- return NULL;
- }
-
- ret = g_memdup2(msg, sizeof *msg);
- ret->text = g_strdup(msg->text);
-
- return ret;
-}
-
-void
-fb_api_message_reset(FbApiMessage *msg, gboolean deep)
-{
- g_return_if_fail(msg != NULL);
-
- if (deep) {
- g_free(msg->text);
- }
-
- memset(msg, 0, sizeof *msg);
-}
-
-void
-fb_api_message_free(FbApiMessage *msg)
-{
- if (G_LIKELY(msg != NULL)) {
- g_free(msg->text);
- g_free(msg);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiMessage, fb_api_message, fb_api_message_dup,
- fb_api_message_free)
-
-FbApiPresence *
-fb_api_presence_dup(const FbApiPresence *presence)
-{
- return g_memdup2(presence, sizeof *presence);
-}
-
-void
-fb_api_presence_reset(FbApiPresence *presence)
-{
- g_return_if_fail(presence != NULL);
- memset(presence, 0, sizeof *presence);
-}
-
-void
-fb_api_presence_free(FbApiPresence *presence)
-{
- if (G_LIKELY(presence != NULL)) {
- g_free(presence);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiPresence, fb_api_presence, fb_api_presence_dup,
- fb_api_presence_free)
-
-FbApiThread *
-fb_api_thread_dup(const FbApiThread *thrd)
-{
- FbApiThread *ret;
-
- if (thrd == NULL) {
- return NULL;
- }
-
- ret = g_memdup2(thrd, sizeof *thrd);
- ret->topic = g_strdup(thrd->topic);
- ret->users = g_slist_copy_deep(thrd->users,
- (GCopyFunc)(GCallback)fb_api_user_dup,
- NULL);
-
- return ret;
-}
-
-void
-fb_api_thread_reset(FbApiThread *thrd, gboolean deep)
-{
- g_return_if_fail(thrd != NULL);
-
- if (deep) {
- g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free);
- g_free(thrd->topic);
- }
-
- memset(thrd, 0, sizeof *thrd);
-}
-
-void
-fb_api_thread_free(FbApiThread *thrd)
-{
- if (G_LIKELY(thrd != NULL)) {
- g_slist_free_full(thrd->users, (GDestroyNotify) fb_api_user_free);
- g_free(thrd->topic);
- g_free(thrd);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiThread, fb_api_thread, fb_api_thread_dup,
- fb_api_thread_free)
-
-FbApiTyping *
-fb_api_typing_dup(const FbApiTyping *typg)
-{
- return g_memdup2(typg, sizeof *typg);
-}
-
-void
-fb_api_typing_reset(FbApiTyping *typg)
-{
- g_return_if_fail(typg != NULL);
- memset(typg, 0, sizeof *typg);
-}
-
-void
-fb_api_typing_free(FbApiTyping *typg)
-{
- if (G_LIKELY(typg != NULL)) {
- g_free(typg);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiTyping, fb_api_typing, fb_api_typing_dup,
- fb_api_typing_free)
-
-FbApiUser *
-fb_api_user_dup(const FbApiUser *user)
-{
- FbApiUser *ret;
-
- if (user == NULL) {
- return NULL;
- }
-
- ret = g_memdup2(user, sizeof *user);
- ret->name = g_strdup(user->name);
- ret->icon = g_strdup(user->icon);
- ret->csum = g_strdup(user->csum);
-
- return ret;
-}
-
-void
-fb_api_user_reset(FbApiUser *user, gboolean deep)
-{
- g_return_if_fail(user != NULL);
-
- if (deep) {
- g_free(user->name);
- g_free(user->icon);
- g_free(user->csum);
- }
-
- memset(user, 0, sizeof *user);
-}
-
-void
-fb_api_user_free(FbApiUser *user)
-{
- if (G_LIKELY(user != NULL)) {
- g_free(user->name);
- g_free(user->icon);
- g_free(user->csum);
- g_free(user);
- }
-}
-
-G_DEFINE_BOXED_TYPE(FbApiUser, fb_api_user, fb_api_user_dup, fb_api_user_free)