diff options
author | jgeboski <jgeboski@gmail.com> | 2015-01-13 12:14:31 -0500 |
---|---|---|
committer | jgeboski <jgeboski@gmail.com> | 2015-01-14 21:55:37 -0500 |
commit | b29d66da918a912ce47551546a4cfa866325e56c (patch) | |
tree | 598449a681db97b7bb5fd5aee9b3c108722e5d1b /facebook/facebook-api.c | |
parent | 20cd31cc5e1320e7dde8a30380d67185c5b289c8 (diff) | |
download | bitlbee-facebook-b29d66da918a912ce47551546a4cfa866325e56c.tar.gz bitlbee-facebook-b29d66da918a912ce47551546a4cfa866325e56c.tar.bz2 bitlbee-facebook-b29d66da918a912ce47551546a4cfa866325e56c.tar.xz |
Implemented one-on-one messaging
Diffstat (limited to 'facebook/facebook-api.c')
-rw-r--r-- | facebook/facebook-api.c | 550 |
1 files changed, 416 insertions, 134 deletions
diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c index 144304a..8cbde7f 100644 --- a/facebook/facebook-api.c +++ b/facebook/facebook-api.c @@ -37,6 +37,134 @@ GQuark fb_api_error_quark(void) } /** + * Creates a new #json_value for JSON contents of the #fb_api. This + * function is special in that it handles all errors, unlike the parent + * function #fb_json_new(). The returned #json_value should be freed + * with #json_value_free() when no longer needed. + * + * @param api The #fb_api. + * @param data The data. + * @param size The size of the data. + * + * @return TRUE if the data was parsed without error, otherwise FALSE. + **/ +static json_value *fb_api_json_new(fb_api_t *api, const gchar *data, + gsize size) +{ + json_value *json; + const gchar *msg; + gint64 code; + + json = fb_json_new(data, size, &api->err); + + if (api->err != NULL) { + fb_api_error(api, 0, NULL); + return NULL; + } + + if (fb_json_int_chk(json, "error_code", &code)) { + if (!fb_json_str_chk(json, "error_msg", &msg)) + msg = "Generic Error"; + + fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg); + json_value_free(json); + return NULL; + } + + return json; +} + +/** + * Creates a new #fb_http_req for a #fb_api request. + * + * @param api The #fb_api. + * @param host The host. + * @param path The path. + * @param func The #fb_http_func. + * @param class The class. + * @param name The friendly name. + * @param method The method. + **/ +static fb_http_req_t *fb_api_req_new(fb_api_t *api, const gchar *host, + const gchar *path, fb_http_func_t func, + const gchar *class, const gchar *name, + const gchar *method) +{ + fb_http_req_t *req; + + req = fb_http_req_new(api->http, host, 443, path, func, api); + req->flags = FB_HTTP_REQ_FLAG_POST | FB_HTTP_REQ_FLAG_SSL; + + fb_http_req_params_set(req, + FB_HTTP_PAIR("api_key", FB_API_KEY), + FB_HTTP_PAIR("fb_api_caller_class", class), + FB_HTTP_PAIR("fb_api_req_friendly_name", name), + FB_HTTP_PAIR("method", method), + FB_HTTP_PAIR("client_country_code", "US"), + FB_HTTP_PAIR("format", "json"), + FB_HTTP_PAIR("locale", "en_US"), + NULL + ); + + return req; +} + +/** + * Sends a #fb_http_req for a #fb_api. This computes the signature for + * the request and sets the "sig" parameter. This sets the OAuth header + * for authorization. + * + * @param api The #fb_api. + * @param req The #fb_http_req. + **/ +static void fb_api_req_send(fb_api_t *api, fb_http_req_t *req) +{ + GString *gstr; + GList *keys; + GList *l; + const gchar *key; + const gchar *val; + gchar *hash; + gchar *auth; + + /* Ensure an old signature is not computed */ + g_hash_table_remove(req->params, "sig"); + + gstr = g_string_sized_new(128); + keys = g_hash_table_get_keys(req->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(req->params, key); + g_string_append_printf(gstr, "%s=%s", key, val); + } + + g_string_append(gstr, FB_API_SECRET); + hash = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); + + fb_http_req_params_set(req, + FB_HTTP_PAIR("sig", hash), + NULL + ); + + g_free(hash); + g_list_free(keys); + g_string_free(gstr, TRUE); + + if (api->token != NULL) { + auth = g_strdup_printf("OAuth %s", api->token); + fb_http_req_headers_set(req, + FB_HTTP_PAIR("Authorization", auth), + NULL + ); + g_free(auth); + } + + fb_http_req_send(req); +} + +/** * Implements #fb_mqtt_funcs->error(). * * @param mqtt The #fb_mqtt. @@ -74,7 +202,12 @@ static void fb_api_cb_mqtt_open(fb_mqtt_t *mqtt, gpointer data) "\"a\":\"" FB_API_AGENT "\"," "\"mqtt_sid\":%s," "\"d\":\"%s\"," - "\"chat_on\":true" + "\"chat_on\":true," + "\"no_auto_fg\":true," + "\"fg\":false," + "\"pf\":\"jz\"," + "\"nwt\":1," + "\"nwst\":0" "}", api->uid, api->mid, api->cuid); fb_mqtt_connect(mqtt, @@ -88,6 +221,73 @@ static void fb_api_cb_mqtt_open(fb_mqtt_t *mqtt, gpointer data) } /** + * Implemented #fb_http_func for the sequence identifier. + * + * @param req The #fb_http_req. + * @param data The user-defined data, which is #fb_api. + **/ +static void fb_api_cb_seqid(fb_http_req_t *req, gpointer data) +{ + fb_api_t *api = data; + json_value *json; + json_value *jv; + const gchar *str; + + json = fb_api_json_new(api, req->body, req->body_size); + + if (json == NULL) + return; + + /* Scattered values lead to a gnarly conditional... */ + if (!fb_json_val_chk(json, "data", json_array, &jv) || + + /* Obtain the first array element */ + (jv->u.array.length != 1) || + ((jv = jv->u.array.values[0]) == NULL) || + + /* Check the name */ + !fb_json_str_chk(jv, "name", &str) || + (g_ascii_strcasecmp(str, "thread_list_ids") != 0) || + + /* Obtain the sequence identifier */ + !fb_json_val_chk(jv, "fql_result_set", json_array, &jv) || + (jv->u.array.length != 1) || + !fb_json_str_chk(jv->u.array.values[0], "sync_sequence_id", &str)) + { + fb_api_error(api, FB_API_ERROR, "Failed to obtain SequenceID"); + goto finish; + } + + if (G_UNLIKELY(api->stoken == NULL)) { + fb_api_publish(api, "/messenger_sync_create_queue", "{" + "\"device_params\":{}," + "\"encoding\":\"JSON\"," + "\"max_deltas_able_to_process\":1250," + "\"initial_titan_sequence_id\":%s," + "\"sync_api_version\":2," + "\"delta_batch_size\":125," + "\"device_id\":\"%s\"" + "}", str, api->cuid); + + goto finish; + } + + fb_api_publish(api, "/messenger_sync_get_diffs", "{" + "\"encoding\":\"JSON\"," + "\"last_seq_id\":%s," + "\"max_deltas_able_to_process\":1250," + "\"sync_api_version\":2," + "\"sync_token\":\"%s\"," + "\"delta_batch_size\":125" + "}", str, api->stoken); + + FB_API_FUNC(api, connect); + +finish: + json_value_free(json); +} + +/** * Implements #fb_mqtt_funcs->connack(). * * @param mqtt The #fb_mqtt. @@ -95,9 +295,122 @@ static void fb_api_cb_mqtt_open(fb_mqtt_t *mqtt, gpointer data) **/ static void fb_api_cb_mqtt_connack(fb_mqtt_t *mqtt, gpointer data) { - fb_api_t *api = data; + fb_api_t *api = data; + fb_http_req_t *req; - FB_API_FUNC(api, connect); + fb_api_publish(api, "/foreground_state", "{" + "\"foreground\": true," + "\"keepalive_timeout\": %d" + "}", FB_MQTT_KA); + + fb_mqtt_subscribe(mqtt, + "/quick_promotion_refresh", 0, + "/webrtc", 0, + "/delete_messages_notification", 0, + "/orca_message_notifications", 0, + "/messaging_events", 0, + "/mercury", 0, + "/t_rtc", 0, + "/inbox", 0, + "/orca_presence", 0, + "/webrtc_response", 0, + "/push_notification", 0, + "/pp", 0, + "/orca_typing_notifications", 0, + "/t_ms", 0, + "/t_p", 0, + NULL + ); + + req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL, + fb_api_cb_seqid, + "com.facebook.orca.protocol.methods.u", + "fetchThreadList", + "GET"); + + static const gchar *query = "{" + "\"thread_list_ids\":\"" + "SELECT sync_sequence_id " + "FROM unified_thread " + "WHERE folder='inbox' " + "ORDER BY sync_sequence_id " + "DESC LIMIT 1\"" + "}"; + + fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL); + fb_api_req_send(api, req); +} + +/** + * Handles messages which are to be published to the user. + * + * @param api The #fb_api. + * @param pload The message payload. + **/ +static void fb_api_cb_publish_ms(fb_api_t *api, const GByteArray *pload) +{ + GSList *msgs; + fb_api_msg_t *msg; + json_value *json; + json_value *jv; + json_value *jx; + json_value *jy; + json_value *jz; + const gchar *str; + gint64 in; + gint64 auid; + guint i; + + /* Start at 1 to skip the NULL byte */ + json = fb_api_json_new(api, (gchar*) pload->data + 1, pload->len - 1); + auid = g_ascii_strtoll(api->uid, NULL, 10); + msgs = NULL; + + if (json == NULL) + return; + + if (fb_json_str_chk(json, "syncToken", &str)) { + g_free(api->stoken); + api->stoken = g_strdup(str); + FB_API_FUNC(api, connect); + goto finish; + } + + if (!fb_json_val_chk(json, "deltas", json_array, &jv)) + goto finish; + + for (i = 0; i < jv->u.array.length; i++) { + jx = jv->u.array.values[i]; + + if (!fb_json_val_chk(jx, "deltaNewMessage", json_object, &jy) || + !fb_json_val_chk(jy, "messageMetadata", json_object, &jz) || + !fb_json_int_chk(jz, "actorFbId", &in) || + (in == auid)) + { + continue; + } + + if (fb_json_str_chk(jy, "body", &str)) { + msg = fb_api_msg_new(NULL, str); + msg->uid = g_strdup_printf("%" G_GINT64_FORMAT, in); + msgs = g_slist_prepend(msgs, msg); + } + + if (fb_json_val_chk(jy, "attachments", json_array, &jy) && + (jy->u.array.length > 0)) + { + msg = fb_api_msg_new(NULL, "* Non-Displayable Attachments *"); + msg->uid = g_strdup_printf("%" G_GINT64_FORMAT, in); + msgs = g_slist_prepend(msgs, msg); + } + } + + msgs = g_slist_reverse(msgs); + FB_API_FUNC(api, message, msgs); + +finish: + g_slist_free_full(msgs, (GDestroyNotify) fb_api_msg_free); + json_value_free(json); } /** @@ -128,7 +441,10 @@ static void fb_api_cb_mqtt_publish(fb_mqtt_t *mqtt, const gchar *topic, bytes = (GByteArray*) pload; } - fb_util_hexdump(bytes, 2, "Publishing:"); + fb_util_hexdump(bytes, 2, "Reading message:"); + + if (g_ascii_strcasecmp(topic, "/t_ms") == 0) + fb_api_cb_publish_ms(api, bytes); if (G_LIKELY(comp)) g_byte_array_free(bytes, TRUE); @@ -183,7 +499,7 @@ void fb_api_rehash(fb_api_t *api) api->cid = g_compute_checksum_for_data(G_CHECKSUM_MD5, rb, sizeof rb); } - if (api->mid == NULL) + if (api->mid == 0) api->mid = g_strdup_printf("%" G_GUINT32_FORMAT, g_random_int()); if (api->cuid == NULL) { @@ -215,10 +531,10 @@ void fb_api_free(fb_api_t *api) fb_mqtt_free(api->mqtt); fb_http_free(api->http); - g_free(api->sid); g_free(api->cuid); g_free(api->mid); g_free(api->cid); + g_free(api->stoken); g_free(api->token); g_free(api->uid); g_free(api); @@ -256,134 +572,6 @@ void fb_api_error(fb_api_t *api, fb_api_error_t err, const gchar *fmt, ...) } /** - * Creates a new #json_value for JSON contents of the #fb_api. This - * function is special in that it handles all errors, unlike the parent - * function #fb_json_new(). The returned #json_value should be freed - * with #json_value_free() when no longer needed. - * - * @param api The #fb_api. - * @param data The data. - * @param size The size of the data. - * - * @return TRUE if the data was parsed without error, otherwise FALSE. - **/ -static json_value *fb_api_json_new(fb_api_t *api, const gchar *data, - gsize size) -{ - json_value *json; - const gchar *msg; - gint64 code; - - json = fb_json_new(data, size, &api->err); - - if (api->err != NULL) { - fb_api_error(api, 0, NULL); - return NULL; - } - - if (fb_json_int_chk(json, "error_code", &code)) { - if (!fb_json_str_chk(json, "error_msg", &msg)) - msg = "Generic Error"; - - fb_api_error(api, FB_API_ERROR_GENERAL, "%s", msg); - json_value_free(json); - return NULL; - } - - return json; -} - -/** - * Creates a new #fb_http_req for a #fb_api request. - * - * @param api The #fb_api. - * @param host The host. - * @param path The path. - * @param func The #fb_http_func. - * @param class The class. - * @param name The friendly name. - * @param method The method. - **/ -static fb_http_req_t *fb_api_req_new(fb_api_t *api, const gchar *host, - const gchar *path, fb_http_func_t func, - const gchar *class, const gchar *name, - const gchar *method) -{ - fb_http_req_t *req; - - req = fb_http_req_new(api->http, host, 443, path, func, api); - req->flags = FB_HTTP_REQ_FLAG_POST | FB_HTTP_REQ_FLAG_SSL; - - fb_http_req_params_set(req, - FB_HTTP_PAIR("api_key", FB_API_KEY), - FB_HTTP_PAIR("fb_api_caller_class", class), - FB_HTTP_PAIR("fb_api_req_friendly_name", name), - FB_HTTP_PAIR("method", method), - FB_HTTP_PAIR("client_country_code", "US"), - FB_HTTP_PAIR("format", "json"), - FB_HTTP_PAIR("locale", "en_US"), - NULL - ); - - return req; -} - -/** - * Sends a #fb_http_req for a #fb_api. This computes the signature for - * the request and sets the "sig" parameter. This sets the OAuth header - * for authorization. - * - * @param api The #fb_api. - * @param req The #fb_http_req. - **/ -static void fb_api_req_send(fb_api_t *api, fb_http_req_t *req) -{ - GString *gstr; - GList *keys; - GList *l; - const gchar *key; - const gchar *val; - gchar *hash; - gchar *auth; - - /* Ensure an old signature is not computed */ - g_hash_table_remove(req->params, "sig"); - - gstr = g_string_sized_new(128); - keys = g_hash_table_get_keys(req->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(req->params, key); - g_string_append_printf(gstr, "%s=%s", key, val); - } - - g_string_append(gstr, FB_API_SECRET); - hash = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); - - fb_http_req_params_set(req, - FB_HTTP_PAIR("sig", hash), - NULL - ); - - g_free(hash); - g_list_free(keys); - g_string_free(gstr, TRUE); - - if (api->token != NULL) { - auth = g_strdup_printf("OAuth %s", api->token); - fb_http_req_headers_set(req, - FB_HTTP_PAIR("Authorization", auth), - NULL - ); - g_free(auth); - } - - fb_http_req_send(req); -} - -/** * Implemented #fb_http_func for #fb_api_auth(). * * @param req The #fb_http_req. @@ -571,6 +759,100 @@ void fb_api_disconnect(fb_api_t *api) } /** + * Sends a message to a user. + * + * @param api The #fb_api. + * @param uid The target user identifier. + * @param msg The message. + **/ +void fb_api_message(fb_api_t *api, const gchar *uid, const gchar *msg) +{ + guint64 msgid; + + g_return_if_fail(api != NULL); + g_return_if_fail(uid != NULL); + g_return_if_fail(msg != NULL); + + msgid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int()); + + fb_api_publish(api, "/send_message2", "{" + "\"body\":\"%s\"," + "\"to\":\"%s\"," + "\"sender_fbid\":\"%s\"," + "\"msgid\":%" G_GUINT64_FORMAT + "}", msg, uid, api->uid, msgid); +} + +/** + * Publishes a string based message to the MQTT service. This enables + * compression of the message via zlib. + * + * @param api The #fb_api. + * @param topic The message topic. + * @param fmt The format string. + * @param ... The format arguments. + **/ +void fb_api_publish(fb_api_t *api, const gchar *topic, const gchar *fmt, ...) +{ + GByteArray *bytes; + GByteArray *cytes; + gchar *msg; + va_list ap; + + g_return_if_fail(api != NULL); + g_return_if_fail(topic != NULL); + g_return_if_fail(fmt != NULL); + + va_start(ap, fmt); + msg = g_strdup_vprintf(fmt, ap); + va_end(ap); + + bytes = g_byte_array_new_take((guint8*) msg, strlen(msg)); + cytes = fb_util_zcompress(bytes); + + fb_util_hexdump(bytes, 2, "Writing message:"); + fb_mqtt_publish(api->mqtt, topic, cytes); + + g_byte_array_free(cytes, TRUE); + g_byte_array_free(bytes, TRUE); +} + +/** + * Creates a new #fb_api_msg. The returned #fb_api_msg should be freed + * with #fb_api_msg_free() when no longer needed. + * + * @param uid The user identifier. + * @param text The message text. + * + * @return The #fb_api_msg or NULL on error. + **/ +fb_api_msg_t *fb_api_msg_new(const gchar *uid, const gchar *text) +{ + fb_api_msg_t *msg; + + msg = g_new0(fb_api_msg_t, 1); + msg->uid = g_strdup(uid); + msg->text = g_strdup(text); + + return msg; +} + +/** + * Frees all memory used by a #fb_api_msg. + * + * @param msg The #fb_api_msg. + **/ +void fb_api_msg_free(fb_api_msg_t *msg) +{ + if (G_UNLIKELY(msg == NULL)) + return; + + g_free(msg->text); + g_free(msg->uid); + g_free(msg); +} + +/** * Creates a new #fb_api_user. The returned #fb_api_user should be * freed with #fb_api_user_free() when no longer needed. * |