diff options
author | jgeboski <jgeboski@gmail.com> | 2015-01-16 11:32:37 -0500 |
---|---|---|
committer | jgeboski <jgeboski@gmail.com> | 2015-01-16 13:46:28 -0500 |
commit | adc6f0329103651e8458f32bd1b92e7dce9d94e5 (patch) | |
tree | c074011517247e5b14c523e10d16c87f908c662f | |
parent | e01da660d6c4e8078281dbc278da9180a3ee40ff (diff) | |
download | bitlbee-facebook-adc6f0329103651e8458f32bd1b92e7dce9d94e5.tar.gz bitlbee-facebook-adc6f0329103651e8458f32bd1b92e7dce9d94e5.tar.bz2 bitlbee-facebook-adc6f0329103651e8458f32bd1b92e7dce9d94e5.tar.xz |
Implemented group chats
-rw-r--r-- | README | 14 | ||||
-rw-r--r-- | facebook/facebook-api.c | 443 | ||||
-rw-r--r-- | facebook/facebook-api.h | 69 | ||||
-rw-r--r-- | facebook/facebook.c | 417 | ||||
-rw-r--r-- | facebook/facebook.h | 4 |
5 files changed, 921 insertions, 26 deletions
@@ -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. **/ }; |