aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README14
-rw-r--r--facebook/facebook-api.c443
-rw-r--r--facebook/facebook-api.h69
-rw-r--r--facebook/facebook.c417
-rw-r--r--facebook/facebook.h4
5 files changed, 921 insertions, 26 deletions
diff --git a/README b/README
index c60d676..65be03d 100644
--- a/README
+++ b/README
@@ -40,6 +40,20 @@ Usage:
> account add facebook <username> <password>
> account <acc> on
+ Group Chats (existing chat):
+ > fbchats <acc>
+ > fbjoin <acc> <index> <channel>
+ > /join #<channel>
+ > /topic <message>
+ > /invite <user>
+
+ Group Chats (creating chat):
+ > fbcreate <acc> <user,user,...>
+ > fbjoin <acc> 1 <channel>
+ > /join #<channel>
+ > /topic <message>
+ > /invite <user>
+
Debugging:
Before debugging can begin, the plugin must be compiled with debugging
support. Once debugging support has been enabled, one of the two
diff --git a/facebook/facebook-api.c b/facebook/facebook-api.c
index cc9ba5a..48a693c 100644
--- a/facebook/facebook-api.c
+++ b/facebook/facebook-api.c
@@ -196,6 +196,24 @@ static void fb_api_req_send(fb_api_t *api, fb_http_req_t *req)
}
/**
+ * Implemented #fb_http_func for simple boolean error checking.
+ *
+ * @param req The #fb_http_req.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_http_bool(fb_http_req_t *req, gpointer data)
+{
+ fb_api_t *api = data;
+
+ if (!fb_api_http_chk(api, req))
+ return;
+
+ if (bool2int(req->body) == 0)
+ fb_api_error(api, FB_API_ERROR, "Failed generic API operation");
+}
+
+
+/**
* Implements #fb_mqtt_funcs->error().
*
* @param mqtt The #fb_mqtt.
@@ -490,6 +508,13 @@ static void fb_api_cb_publish_ms(fb_api_t *api, const GByteArray *pload)
}
msg.uid = in;
+ msg.tid = 0;
+
+ if (fb_json_val_chk(jz, "threadKey", json_object, &jz) &&
+ fb_json_int_chk(jz, "threadFbId", &in))
+ {
+ msg.tid = in;
+ }
if (fb_json_str_chk(jy, "body", &str)) {
msg.text = str;
@@ -959,26 +984,30 @@ void fb_api_disconnect(fb_api_t *api)
/**
* Sends a message to a user.
*
- * @param api The #fb_api.
- * @param uid The #fb_id of the user.
- * @param msg The message.
+ * @param api The #fb_api.
+ * @param id The #fb_id of the user.
+ * @param thread TRUE to send to a thread identifier, otherwise FALSE.
+ * @param msg The message.
**/
-void fb_api_message(fb_api_t *api, fb_id_t uid, const gchar *msg)
+void fb_api_message(fb_api_t *api, fb_id_t id, gboolean thread,
+ const gchar *msg)
{
- guint64 msgid;
- gchar *rmsg;
+ guint64 msgid;
+ const gchar *tpfx;
+ gchar *rmsg;
g_return_if_fail(api != NULL);
g_return_if_fail(msg != NULL);
msgid = FB_API_MSGID(g_get_real_time() / 1000, g_random_int());
+ tpfx = thread ? "tfbid_" : "";
rmsg = g_strdup_printf("{"
"\"body\":\"%s\","
- "\"to\":\"%" FB_ID_FORMAT "\","
+ "\"to\":\"%s%" FB_ID_FORMAT "\","
"\"sender_fbid\":\"%" FB_ID_FORMAT "\","
"\"msgid\":%" G_GUINT64_FORMAT
- "}", msg, uid, api->uid, msgid);
+ "}", msg, tpfx, id, api->uid, msgid);
if (g_queue_is_empty(api->msgs))
fb_api_publish(api, "/send_message2", rmsg, NULL);
@@ -1021,6 +1050,404 @@ void fb_api_publish(fb_api_t *api, const gchar *topic, const gchar *fmt, ...)
}
/**
+ * Implemented #fb_http_func for #fb_api_thread_create().
+ *
+ * @param req The #fb_http_req.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_thread_create(fb_http_req_t *req, gpointer data)
+{
+ fb_api_t *api = data;
+ json_value *json;
+ const gchar *str;
+ fb_id_t tid;
+
+ if (!fb_api_http_chk(api, req) ||
+ !fb_api_json_new(api, req->body, req->body_size, &json))
+ {
+ return;
+ }
+
+ if (!fb_json_str_chk(json, "thread_fbid", &str)) {
+ fb_api_error(api, FB_API_ERROR, "Failed to create thread");
+ goto finish;
+ }
+
+ tid = FB_ID_FROM_STR(str);
+ FB_API_FUNC(api, thread_create, tid);
+
+finish:
+ json_value_free(json);
+}
+
+/**
+ * Sends a thread creation request.
+ *
+ * @param api The #fb_api.
+ * @param uids The #GSList of #fb_id.
+ **/
+void fb_api_thread_create(fb_api_t *api, GSList *uids)
+{
+ fb_http_req_t *req;
+ const GSList *l;
+ GString *to;
+ fb_id_t *uid;
+
+ g_return_if_fail(api != NULL);
+ g_return_if_fail(uids != NULL);
+ g_warn_if_fail(g_slist_length(uids) < 2);
+
+ req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_THRDS,
+ fb_api_cb_thread_create,
+ "ccom.facebook.orca.send.service.l",
+ "createThread",
+ "POST");
+
+ to = g_string_new(NULL);
+ uids = g_slist_prepend(uids, &api->uid);
+
+ for (l = uids; l != NULL; l = l->next) {
+ uid = l->data;
+
+ if (to->len > 0)
+ g_string_append_c(to, ',');
+
+ g_string_append_printf(to, "{"
+ "\"type\":\"id\","
+ "\"id\":\"%" FB_ID_FORMAT "\""
+ "}", *uid);
+ }
+
+ /* Pop the #fb_api->uid off */
+ uids = g_slist_delete_link(uids, uids);
+ g_string_prepend_c(to, '[');
+ g_string_append_c(to, ']');
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("to", to->str),
+ NULL
+ );
+
+ fb_api_req_send(api, req);
+ g_string_free(to, TRUE);
+}
+
+/**
+ * Implemented #fb_http_func for #fb_api_thread().
+ *
+ * @param req The #fb_http_req.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_thread_info(fb_http_req_t *req, gpointer data)
+{
+ fb_api_t *api = data;
+ fb_api_thread_t thrd;
+ fb_api_user_t user;
+ json_value *json;
+ json_value *jv;
+ json_value *jx;
+ gpointer mptr;
+ const gchar *str;
+ guint i;
+
+ if (!fb_api_http_chk(api, req) ||
+ !fb_api_json_new(api, req->body, req->body_size, &json))
+ {
+ 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, "threads") != 0) ||
+
+ /* Obtain result array */
+ !fb_json_val_chk(jv, "fql_result_set", json_array, &jv) ||
+ (jv->u.array.length != 1) ||
+ ((jv = jv->u.array.values[0]) == NULL) ||
+
+ /* Obtain the thread identifier */
+ !fb_json_str_chk(jv, "thread_fbid", &str))
+ {
+ fb_api_error(api, FB_API_ERROR, "Failed to fetch thread info");
+ goto finish;
+ }
+
+ thrd.tid = FB_ID_FROM_STR(str);
+ thrd.topic = NULL;
+ thrd.users = NULL;
+
+ if (fb_json_str_chk(jv, "name", &str) && (strlen(str) > 0))
+ thrd.topic = str;
+
+ if (fb_json_val_chk(jv, "participants", json_array, &jv)) {
+ for (i = 0; i < jv->u.array.length; i++) {
+ jx = jv->u.array.values[i];
+
+ if (!fb_json_str_chk(jx, "user_id", &str))
+ continue;
+
+ user.uid = FB_ID_FROM_STR(str);
+
+ if (fb_json_str_chk(jx, "name", &str) && (user.uid != api->uid)) {
+ user.name = str;
+ mptr = g_memdup(&user, sizeof user);
+ thrd.users = g_slist_prepend(thrd.users, mptr);
+ }
+ }
+ }
+
+ FB_API_FUNC(api, thread_info, &thrd);
+ g_slist_free_full(thrd.users, g_free);
+
+finish:
+ json_value_free(json);
+}
+
+/**
+ * Sends a thread info request.
+ *
+ * @param api The #fb_api.
+ * @param tid The thread #fb_id.
+ **/
+void fb_api_thread_info(fb_api_t *api, fb_id_t tid)
+{
+ fb_http_req_t *req;
+ gchar *query;
+
+ g_return_if_fail(api != NULL);
+
+ req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL,
+ fb_api_cb_thread_info,
+ "com.facebook.orca.protocol.methods.u",
+ "fetchThreadList",
+ "GET");
+
+ query = g_strdup_printf("{"
+ "\"threads\":\""
+ "SELECT thread_fbid, participants, name "
+ "FROM unified_thread "
+ "WHERE thread_fbid='%" FB_ID_FORMAT "' "
+ "LIMIT 1\""
+ "}", tid);
+
+ fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL);
+ fb_api_req_send(api, req);
+ g_free(query);
+}
+
+/**
+ * Sends a thread invite request.
+ *
+ * @param api The #fb_api.
+ * @param tid The thread #fb_id.
+ * @param uid The user #fb_id.
+ **/
+void fb_api_thread_invite(fb_api_t *api, fb_id_t tid, fb_id_t uid)
+{
+ fb_http_req_t *req;
+ gchar *stid;
+ gchar *to;
+
+ g_return_if_fail(api != NULL);
+
+ req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_PARTS,
+ fb_api_cb_http_bool,
+ "com.facebook.orca.protocol.a",
+ "addMembers",
+ "POST");
+
+ stid = g_strdup_printf("t_id.%" FB_ID_FORMAT, tid);
+ to = g_strdup_printf("[{"
+ "\"type\":\"id\","
+ "\"id\":\"%" FB_ID_FORMAT "\""
+ "}]", uid);
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("id", stid),
+ FB_HTTP_PAIR("to", to),
+ NULL
+ );
+
+ fb_api_req_send(api, req);
+ g_free(stid);
+ g_free(to);
+}
+
+/**
+ * Frees all memory used by a #fb_api_thread.
+ *
+ * @param thrd The #fb_api_thread.
+ **/
+static void fb_api_cb_threads_free(fb_api_thread_t *thrd)
+{
+ g_slist_free_full(thrd->users, g_free);
+ g_free(thrd);
+}
+
+/**
+ * Implemented #fb_http_func for #fb_api_threads().
+ *
+ * @param req The #fb_http_req.
+ * @param data The user-defined data, which is #fb_api.
+ **/
+static void fb_api_cb_thread_list(fb_http_req_t *req, gpointer data)
+{
+ fb_api_t *api = data;
+ fb_api_thread_t thrd;
+ fb_api_user_t user;
+ GSList *thrds;
+ json_value *json;
+ json_value *jv;
+ json_value *jx;
+ json_value *jy;
+ gpointer mptr;
+ const gchar *str;
+ guint i;
+ guint j;
+
+ if (!fb_api_http_chk(api, req) ||
+ !fb_api_json_new(api, req->body, req->body_size, &json))
+ {
+ return;
+ }
+
+ thrds = NULL;
+
+ /* 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, "threads") != 0) ||
+
+ /* Obtain result array */
+ !fb_json_val_chk(jv, "fql_result_set", json_array, &jv))
+ {
+ fb_api_error(api, FB_API_ERROR, "Failed to fetch thread list");
+ goto finish;
+ }
+
+ for (i = 0; i < jv->u.array.length; i++) {
+ jx = jv->u.array.values[i];
+
+ if (!fb_json_str_chk(jx, "thread_fbid", &str))
+ continue;
+
+ thrd.tid = FB_ID_FROM_STR(str);
+ thrd.topic = NULL;
+ thrd.users = NULL;
+
+ if (fb_json_str_chk(jx, "name", &str) && (strlen(str) > 0))
+ thrd.topic = str;
+
+ if (!fb_json_val_chk(jx, "participants", json_array, &jx)) {
+ thrds = g_slist_prepend(thrds, g_memdup(&thrd, sizeof thrd));
+ continue;
+ }
+
+ for (j = 0; j < jx->u.array.length; j++) {
+ jy = jx->u.array.values[j];
+
+ if (!fb_json_str_chk(jy, "user_id", &str))
+ continue;
+
+ user.uid = FB_ID_FROM_STR(str);
+
+ if (fb_json_str_chk(jy, "name", &str) && (user.uid != api->uid)) {
+ user.name = str;
+ mptr = g_memdup(&user, sizeof user);
+ thrd.users = g_slist_prepend(thrd.users, mptr);
+ }
+ }
+
+ thrds = g_slist_prepend(thrds, g_memdup(&thrd, sizeof thrd));
+ }
+
+ thrds = g_slist_reverse(thrds);
+ FB_API_FUNC(api, thread_list, thrds);
+
+finish:
+ g_slist_free_full(thrds, (GDestroyNotify) fb_api_cb_threads_free);
+ json_value_free(json);
+}
+
+/**
+ * Sends a thread list request.
+ *
+ * @param api The #fb_api.
+ * @param limit The thread count limit.
+ **/
+void fb_api_thread_list(fb_api_t *api, guint limit)
+{
+ fb_http_req_t *req;
+ gchar *query;
+
+ g_return_if_fail(api != NULL);
+
+ req = fb_api_req_new(api, FB_API_GHOST, FB_API_PATH_FQL,
+ fb_api_cb_thread_list,
+ "com.facebook.orca.protocol.methods.u",
+ "fetchThreadList",
+ "GET");
+
+ query = g_strdup_printf("{"
+ "\"threads\":\""
+ "SELECT thread_fbid, participants, name "
+ "FROM unified_thread "
+ "WHERE folder='inbox' "
+ "ORDER BY timestamp DESC "
+ "LIMIT %u\""
+ "}", limit);
+
+ fb_http_req_params_set(req, FB_HTTP_PAIR("q", query), NULL);
+ fb_api_req_send(api, req);
+ g_free(query);
+}
+
+/**
+ * Sends a thread topic request.
+ *
+ * @param api The #fb_api.
+ * @param tid The thread #fb_id.
+ * @param topic The topic message.
+ **/
+void fb_api_thread_topic(fb_api_t *api, fb_id_t tid, const gchar *topic)
+{
+ fb_http_req_t *req;
+ gchar *stid;
+
+ g_return_if_fail(api != NULL);
+
+ req = fb_api_req_new(api, FB_API_HOST, FB_API_PATH_TOPIC,
+ fb_api_cb_http_bool,
+ "com.facebook.orca.protocol.a",
+ "setThreadName",
+ "messaging.setthreadname");
+
+ stid = g_strdup_printf("t_id.%" FB_ID_FORMAT, tid);
+
+ fb_http_req_params_set(req,
+ FB_HTTP_PAIR("tid", stid),
+ FB_HTTP_PAIR("name", topic),
+ NULL
+ );
+
+ fb_api_req_send(api, req);
+ g_free(stid);
+}
+
+/**
* Sends a typing state to a user.
*
* @param api The #fb_api.
diff --git a/facebook/facebook-api.h b/facebook/facebook-api.h
index d6433c2..5a7b87f 100644
--- a/facebook/facebook-api.h
+++ b/facebook/facebook-api.h
@@ -34,9 +34,12 @@
#define FB_API_KEY "256002347743983"
#define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895"
-#define FB_API_PATH_AUTH "/method/auth.login"
-#define FB_API_PATH_FQL "/fql"
-#define FB_API_PATH_GQL "/graphql"
+#define FB_API_PATH_AUTH "/method/auth.login"
+#define FB_API_PATH_FQL "/fql"
+#define FB_API_PATH_GQL "/graphql"
+#define FB_API_PATH_PARTS "/participants"
+#define FB_API_PATH_THRDS "/me/threads"
+#define FB_API_PATH_TOPIC "/method/messaging.setthreadname"
#define FB_API_QRYID_CONTACTS "10153122424521729"
@@ -83,6 +86,9 @@ typedef struct fb_api_msg fb_api_msg_t;
/** The structure for representing an #fb_api presence. **/
typedef struct fb_api_pres fb_api_pres_t;
+/** The structure for representing an #fb_api thread. **/
+typedef struct fb_api_thread fb_api_thread_t;
+
/** The structure for representing an #fb_api user typing state. **/
typedef struct fb_api_typing fb_api_typing_t;
@@ -165,6 +171,39 @@ struct fb_api_funcs
void (*presence) (fb_api_t *api, const GSList *press, gpointer data);
/**
+ * The thread_create function. This is called whenever the #fb_api
+ * has created a thread. This is called as a result of
+ * #fb_api_thread_create().
+ *
+ * @param api The #fb_api.
+ * @param tid The thread #fb_id.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*thread_create) (fb_api_t *api, fb_id_t tid, gpointer data);
+
+ /**
+ * The thread_info function. This is called whenever the #fb_api
+ * has retrieved thread information. This is called as a result of
+ * #fb_api_thread_info().
+ *
+ * @param api The #fb_api.
+ * @param thrd The #fb_api_thread.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*thread_info) (fb_api_t *api, fb_api_thread_t *thrd, gpointer data);
+
+ /**
+ * The thread_list function. This is called whenever the #fb_api
+ * has retrieved a set of threads. This is called as a result of
+ * #fb_api_thread_list().
+ *
+ * @param api The #fb_api.
+ * @param thrds The #GSList of #fb_api_thread.
+ * @param data The user-defined data or NULL.
+ **/
+ void (*thread_list) (fb_api_t *api, const GSList *thrds, gpointer data);
+
+ /**
* The typing function. This is called whenever the #fb_api has
* retrieved a typing state update.
*
@@ -202,6 +241,7 @@ struct fb_api
struct fb_api_msg
{
fb_id_t uid; /** The #fb_id of the user. **/
+ fb_id_t tid; /** The #fb_id of the thread. **/
const gchar *text; /** The message text. **/
};
@@ -215,6 +255,16 @@ struct fb_api_pres
};
/**
+ * The structure for representing an #fb_api thread.
+ **/
+struct fb_api_thread
+{
+ fb_id_t tid; /** The #fb_id of the thread. **/
+ const gchar *topic; /** The topic of the thread or NULL. **/
+ GSList *users; /** The #GList of #fb_api_user. **/
+};
+
+/**
* The structure for representing an #fb_api user typing state.
**/
struct fb_api_typing
@@ -253,10 +303,21 @@ void fb_api_connect(fb_api_t *api);
void fb_api_disconnect(fb_api_t *api);
-void fb_api_message(fb_api_t *api, fb_id_t uid, const gchar *msg);
+void fb_api_message(fb_api_t *api, fb_id_t id, gboolean thread,
+ const gchar *msg);
void fb_api_publish(fb_api_t *api, const gchar *topic, const gchar *fmt, ...);
+void fb_api_thread_create(fb_api_t *api, GSList *uids);
+
+void fb_api_thread_info(fb_api_t *api, fb_id_t tid);
+
+void fb_api_thread_invite(fb_api_t *api, fb_id_t tid, fb_id_t uid);
+
+void fb_api_thread_list(fb_api_t *api, guint limit);
+
+void fb_api_thread_topic(fb_api_t *api, fb_id_t tid, const gchar *topic);
+
void fb_api_typing(fb_api_t *api, fb_id_t uid, gboolean state);
#endif /* _FACEBOOK_API_H */
diff --git a/facebook/facebook.c b/facebook/facebook.c
index ab9427b..fa102b9 100644
--- a/facebook/facebook.c
+++ b/facebook/facebook.c
@@ -106,15 +106,27 @@ static void fb_cb_api_contacts(fb_api_t *api, const GSList *users,
**/
static void fb_cb_api_message(fb_api_t *api, const GSList *msgs, gpointer data)
{
- fb_data_t *fata = data;
- fb_api_msg_t *msg;
- const GSList *l;
- gchar uid[FB_ID_STRMAX];
+ fb_data_t *fata = data;
+ fb_api_msg_t *msg;
+ struct groupchat *gc;
+ const GSList *l;
+ gchar uid[FB_ID_STRMAX];
+ gchar tid[FB_ID_STRMAX];
for (l = msgs; l != NULL; l = l->next) {
msg = l->data;
FB_ID_TO_STR(msg->uid, uid);
- imcb_buddy_msg(fata->ic, uid, (gchar*) msg->text, 0, 0);
+
+ if (msg->tid == 0) {
+ imcb_buddy_msg(fata->ic, uid, (gchar*) msg->text, 0, 0);
+ continue;
+ }
+
+ FB_ID_TO_STR(msg->tid, tid);
+ gc = bee_chat_by_title(fata->ic->bee, fata->ic, tid);
+
+ if (gc != NULL)
+ imcb_chat_msg(gc, uid, (gchar*) msg->text, 0, 0);
}
}
@@ -147,6 +159,160 @@ static void fb_cb_api_presence(fb_api_t *api, const GSList *press,
}
/**
+ * Implemented #fb_api_funcs->thread_create().
+ *
+ * @param api The #fb_api.
+ * @param tid The thread #fb_id.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_thread_create(fb_api_t *api, fb_id_t tid, gpointer data)
+{
+ fb_data_t *fata = data;
+ account_t *acc = fata->ic->acc;
+
+ fata->tids = g_slist_prepend(fata->tids, g_memdup(&tid, sizeof tid));
+ imcb_log(fata->ic, "Created chat thread %" FB_ID_FORMAT, tid);
+ imcb_log(fata->ic, "Join: fbjoin %s %d <channel-name>", acc->tag, 1);
+}
+
+/**
+ * Implemented #fb_api_funcs->thread_info().
+ *
+ * @param api The #fb_api.
+ * @param thrd The #fb_api_thread.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_thread_info(fb_api_t *api, fb_api_thread_t *thrd,
+ gpointer data)
+{
+ fb_data_t *fata = data;
+ fb_api_user_t *user;
+ bee_user_t *bu;
+ struct groupchat *gc;
+ GSList *l;
+ GString *gstr;
+ gchar id[FB_ID_STRMAX];
+
+ FB_ID_TO_STR(thrd->tid, id);
+ gc = bee_chat_by_title(fata->ic->bee, fata->ic, id);
+
+ if (G_UNLIKELY(gc == NULL))
+ return;
+
+ if (thrd->topic == NULL) {
+ gstr = g_string_new(NULL);
+
+ for (l = thrd->users; l != NULL; l = l->next) {
+ user = l->data;
+
+ if (gstr->len > 0)
+ g_string_append(gstr, ", ");
+
+ g_string_append(gstr, user->name);
+ }
+
+ imcb_chat_topic(gc, NULL, gstr->str, 0);
+ g_string_free(gstr, TRUE);
+ } else {
+ imcb_chat_topic(gc, NULL, (gchar*) thrd->topic, 0);
+ }
+
+ for (l = thrd->users; l != NULL; l = l->next) {
+ user = l->data;
+ FB_ID_TO_STR(user->uid, id);
+ bu = bee_user_by_handle(fata->ic->bee, fata->ic, id);
+
+ imcb_chat_add_buddy(gc, id);
+
+ if (bu == NULL) {
+ imcb_buddy_nick_hint(fata->ic, id, user->name);
+ imcb_rename_buddy(fata->ic, id, user->name);
+ }
+ }
+}
+
+/**
+ * Implemented #fb_api_funcs->thread_list().
+ *
+ * @param api The #fb_api.
+ * @param thrds The #GSList of #fb_api_thread.
+ * @param data The user defined data, which is #fb_data.
+ **/
+static void fb_cb_api_thread_list(fb_api_t *api, const GSList *thrds,
+ gpointer data)
+{
+ fb_data_t *fata = data;
+ fb_api_thread_t *thrd;
+ fb_api_user_t *user;
+ GSList *phrds;
+ const GSList *l;
+ const GSList *m;
+ GString *ln;
+ gpointer mptr;
+ guint i;
+ guint j;
+
+ g_slist_free_full(fata->tids, g_free);
+ fata->tids = NULL;
+ phrds = NULL;
+
+ for (l = thrds, i = 0; (l != NULL) && (i < 25); l = l->next, i++) {
+ thrd = l->data;
+
+ if (g_slist_length(thrd->users) >= 2)
+ phrds = g_slist_prepend(phrds, thrd);
+ }
+
+ if (phrds == NULL) {
+ imcb_log(fata->ic, "No chats to display.");
+ return;
+ }
+
+ ln = g_string_new(NULL);
+ imcb_log(fata->ic, "%2s %-20s %s", "ID", "Topic", "Participants");
+ phrds = g_slist_reverse(phrds);
+
+ for (l = phrds, i = 1; l != NULL; l = l->next, i++) {
+ thrd = l->data;
+
+ if (g_slist_length(thrd->users) < 2)
+ continue;
+
+ mptr = g_memdup(&thrd->tid, sizeof thrd->tid);
+ fata->tids = g_slist_prepend(fata->tids, mptr);
+
+ g_string_printf(ln, "%2d", i);
+
+ if (thrd->topic != NULL) {
+ if (strlen(thrd->topic) > 20) {
+ for (j = 16; g_ascii_isspace(thrd->topic[j]) && (j > 0); j--);
+ g_string_append_printf(ln, " %-.*s...", ++j, thrd->topic);
+ g_string_append_printf(ln, "%*s", 17 - j, "");
+ } else {
+ g_string_append_printf(ln, " %-20s", thrd->topic);
+ }
+ } else {
+ g_string_append_printf(ln, " %20s", "");
+ }
+
+ for (m = thrd->users, j = 0; (m != NULL) && (j < 3); m = m->next, j++) {
+ user = m->data;
+ g_string_append(ln, (j != 0) ? ", " : " ");
+ g_string_append(ln, user->name);
+ }
+
+ if (m != NULL)
+ g_string_append(ln, "...");
+
+ imcb_log(fata->ic, "%s", ln->str);
+ }
+
+ fata->tids = g_slist_reverse(fata->tids);
+ g_string_free(ln, TRUE);
+ g_slist_free(phrds);
+}
+
+/**
* Implemented #fb_api_funcs->typing().
*
* @param api The #fb_api.
@@ -179,13 +345,16 @@ fb_data_t *fb_data_new(account_t *acc)
gchar *uid;
static const fb_api_funcs_t funcs = {
- .error = fb_cb_api_error,
- .auth = fb_cb_api_auth,
- .connect = fb_cb_api_connect,
- .contacts = fb_cb_api_contacts,
- .message = fb_cb_api_message,
- .presence = fb_cb_api_presence,
- .typing = fb_cb_api_typing
+ .error = fb_cb_api_error,
+ .auth = fb_cb_api_auth,
+ .connect = fb_cb_api_connect,
+ .contacts = fb_cb_api_contacts,
+ .message = fb_cb_api_message,
+ .presence = fb_cb_api_presence,
+ .thread_create = fb_cb_api_thread_create,
+ .thread_info = fb_cb_api_thread_info,
+ .thread_list = fb_cb_api_thread_list,
+ .typing = fb_cb_api_typing
};
g_return_val_if_fail(acc != NULL, NULL);
@@ -227,6 +396,8 @@ void fb_data_free(fb_data_t *fata)
return;
fb_api_free(fata->api);
+ g_slist_free_full(fata->tids, g_free);
+ g_slist_free_full(fata->gcs, (GDestroyNotify) imcb_chat_free);
g_free(fata);
}
@@ -310,7 +481,7 @@ static int fb_buddy_msg(struct im_connection *ic, char *to, char *message,
fb_id_t uid;
uid = FB_ID_FROM_STR(to);
- fb_api_message(fata->api, uid, message);
+ fb_api_message(fata->api, uid, FALSE, message);
return 0;
}
@@ -415,6 +586,97 @@ static void fb_get_info(struct im_connection *ic, char *who)
}
/**
+ * Implements #prpl->chat_invite(). This invites a user to a #groupchat.
+ *
+ * @param gc The #groupchat.
+ * @param who Ignored.
+ * @param message The handle to invite.
+ **/
+void fb_chat_invite(struct groupchat *gc, char *who, char *message)
+{
+ fb_data_t *fata = gc->ic->proto_data;
+ fb_id_t tid;
+ fb_id_t uid;
+
+ tid = FB_ID_FROM_STR(gc->title);
+ uid = FB_ID_FROM_STR(who);
+ fb_api_thread_invite(fata->api, tid, uid);
+ imcb_chat_add_buddy(gc, who);
+}
+
+/**
+ * Implements #prpl->chat_leave(). This leaves a #groupchat.
+ *
+ * @param gc The #groupchat.
+ **/
+void fb_chat_leave(struct groupchat *gc)
+{
+ fb_data_t *fata = gc->ic->proto_data;
+
+ fata->gcs = g_slist_remove(fata->gcs, gc);
+ imcb_chat_free(gc);
+}
+
+/**
+ * Implements #prpl->chat_msg(). This sends a message to a #groupchat.
+ *
+ * @param gc The #groupchat.
+ * @param message The message to send.
+ * @param flags Ignored.
+ **/
+void fb_chat_msg(struct groupchat *gc, char *message, int flags)
+{
+ fb_data_t *fata = gc->ic->proto_data;
+ fb_id_t tid;
+
+ tid = FB_ID_FROM_STR(gc->title);
+ fb_api_message(fata->api, tid, TRUE, message);
+}
+
+/**
+ * Implements #prpl->chat_join(). This joins a #groupchat.
+ *
+ * @param ic The #im_connection.
+ * @param room The room name.
+ * @param nick The nick name.
+ * @param password The password.
+ * @param sets The #set array.
+ **/
+struct groupchat *fb_chat_join(struct im_connection *ic, const char *room,
+ const char *nick, const char *password,
+ set_t **sets)
+{
+ fb_data_t *fata = ic->proto_data;
+ fb_id_t tid;
+ struct groupchat *gc;
+
+ gc = imcb_chat_new(ic, room);
+ fata->gcs = g_slist_prepend(fata->gcs, gc);
+ imcb_chat_add_buddy(gc, ic->acc->user);
+
+ tid = FB_ID_FROM_STR(room);
+ fb_api_thread_info(fata->api, tid);
+
+ return gc;
+}
+
+/**
+ * Implements #prpl->chat_topic(). This sets a #groupchat topic.
+ *
+ * @param gc The #groupchat.
+ * @param topic The topic
+ **/
+void fb_chat_topic(struct groupchat *gc, char *topic)
+{
+ fb_data_t *fata = gc->ic->proto_data;
+ fb_id_t tid;
+
+ tid = FB_ID_FROM_STR(gc->title);
+ fb_api_thread_topic(fata->api, tid, topic);
+ imcb_chat_topic(gc, NULL, topic, 0);
+}
+
+/**
* Implements #prpl->auth_allow(). This accepts buddy requests.
*
* @param ic The #im_connection.
@@ -457,6 +719,126 @@ static void fb_buddy_data_free(struct bee_user *bu)
}
/**
+ * Obtains a #account from command arguments.
+ *
+ * @param irc The #irc.
+ * @param args The command arguments.
+ **/
+static account_t *fb_cmd_account(irc_t *irc, char **args)
+{
+ account_t *acc;
+
+ acc = account_get(irc->b, args[1]);
+
+ if (acc == NULL) {
+ irc_rootmsg(irc, "Unknown account: %s", args[1]);
+ return NULL;
+ }
+
+ if (g_ascii_strcasecmp(acc->prpl->name, "facebook") != 0) {
+ irc_rootmsg(irc, "Unknown Facebook account: %s", acc->tag);
+ return NULL;
+ }
+
+ return acc;
+}
+
+/**
+ * Implemented #root_command_add() callback for the 'fbchats' command.
+ *
+ * @param irc The #irc.
+ * @param args The command arguments.
+ **/
+static void fb_cmd_fbchats(irc_t *irc, char **args)
+{
+ account_t *acc;
+ fb_data_t *fata;
+
+ acc = fb_cmd_account(irc, args);
+
+ if (acc == NULL)
+ return;
+
+ fata = acc->ic->proto_data;
+ fb_api_thread_list(fata->api, 25);
+}
+
+/**
+ * Implemented #root_command_add() callback for the 'fbcreate' command.
+ *
+ * @param irc The #irc.
+ * @param args The command arguments.
+ **/
+static void fb_cmd_fbcreate(irc_t *irc, char **args)
+{
+ account_t *acc;
+ fb_data_t *fata;
+ fb_id_t uid;
+ irc_user_t *iu;
+ GSList *uids;
+ guint i;
+
+ acc = fb_cmd_account(irc, args);
+ uids = NULL;
+
+ if (acc == NULL)
+ return;
+
+ fata = acc->ic->proto_data;
+
+ for (i = 2; args[i] != NULL; i++) {
+ iu = irc_user_by_name(irc, args[i]);
+
+ if (iu != NULL) {
+ uid = FB_ID_FROM_STR(iu->bu->handle);
+ uids = g_slist_prepend(uids, g_memdup(&uid, sizeof uid));
+ }
+ }
+
+ if (uids == NULL) {
+ imcb_error(fata->ic, "No valid users specified");
+ return;
+ }
+
+ fb_api_thread_create(fata->api, uids);
+ g_slist_free_full(uids, g_free);
+}
+
+/**
+ * Implemented #root_command_add() callback for the 'fbjoin' command.
+ *
+ * @param irc The #irc.
+ * @param args The command arguments.
+ **/
+static void fb_cmd_fbjoin(irc_t *irc, char **args)
+{
+ account_t *acc;
+ fb_data_t *fata;
+ fb_id_t *tid;
+ gint64 indx;
+ gchar stid[FB_ID_STRMAX];
+
+ acc = fb_cmd_account(irc, args);
+
+ if (acc == NULL)
+ return;
+
+ fata = acc->ic->proto_data;
+ indx = g_ascii_strtoll(args[2], NULL, 10);
+ tid = g_slist_nth_data(fata->tids, indx - 1);
+
+ if ((indx < 1) || (tid == NULL)) {
+ imcb_error(fata->ic, "Invalid index: %" G_GINT64_FORMAT, indx);
+ return;
+ }
+
+ FB_ID_TO_STR(*tid, stid);
+
+ gchar *cmd[] = {"chat", "add", acc->tag, stid, args[3], NULL};
+ root_command(irc, cmd);
+}
+
+/**
* Implements the #init_plugin() function. BitlBee looks for this
* function and executes it to register the protocol and its related
* callbacks.
@@ -481,6 +863,11 @@ void init_plugin()
pp->rem_permit = fb_rem_permit;
pp->rem_deny = fb_rem_deny;
pp->get_info = fb_get_info;
+ pp->chat_invite = fb_chat_invite;
+ pp->chat_leave = fb_chat_leave;
+ pp->chat_msg = fb_chat_msg;
+ pp->chat_join = fb_chat_join;
+ pp->chat_topic = fb_chat_topic;
pp->handle_cmp = g_ascii_strcasecmp;
pp->auth_allow = fb_auth_allow;
pp->auth_deny = fb_auth_deny;
@@ -488,4 +875,8 @@ void init_plugin()
pp->buddy_data_free = fb_buddy_data_free;
register_protocol(pp);
+
+ root_command_add("fbchats", 1, fb_cmd_fbchats, 0);
+ root_command_add("fbcreate", 3, fb_cmd_fbcreate, 0);
+ root_command_add("fbjoin", 3, fb_cmd_fbjoin, 0);
}
diff --git a/facebook/facebook.h b/facebook/facebook.h
index e30ba26..f397fa8 100644
--- a/facebook/facebook.h
+++ b/facebook/facebook.h
@@ -35,7 +35,9 @@ typedef struct fb_data fb_data_t;
struct fb_data
{
struct im_connection *ic; /** The #im_connection. **/
- fb_api_t *api; /** The #fb_api. **/
+ fb_api_t *api; /** The #fb_api. **/
+ GSList *gcs; /** The #GSList of #groupchats. **/
+ GSList *tids; /** The #GSList of thread identifiers. **/
};