/* * Facebook plugin * * Copyright (C) 2007 Neaveru Freedland. * * 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #define FACEBOOK_PLUGIN_ID "core-neaveru-facebook" #include "internal.h" #include "cipher.h" #include "debug.h" #include "signals.h" #include "version.h" #include "notify.h" #include "xmlnode.h" #include "util.h" #include "request.h" //CONFIGURABLE: #define FACEBOOK_APP_DEFAULT_API_KEY "e4dfa9bd876978055a06f8aed5307164" #define FACEBOOK_APP_DEFAULT_SECRET "9e4081d6b78379f2e593e41d3560696c" #define FACEBOOK_APP_KEY_EDITABLE 1 //Change to 0 if you don't want a user to be able to change the key #define PREFS_BASE "/plugins/core/facebook" #define PREF_TIMEOUT PREFS_BASE "/request_timer" #define PREF_ONLOAD PREFS_BASE "/autostart" #define PREF_API_KEY PREFS_BASE "/api_key" #define PREF_SECRET PREFS_BASE "/secret" #define FACEBOOK_DEBUG_ID "facebook" #define FACEBOOK_REQUEST_HOST "api.facebook.com" #define FACEBOOK_REQUEST_PAGE "/restserver.php" #define FACEBOOK_REQUEST_URL "http://" FACEBOOK_REQUEST_HOST FACEBOOK_REQUEST_PAGE #define FACEBOOK_REQUEST_HEADER \ "POST " FACEBOOK_REQUEST_PAGE " HTTP/1.1\r\n" \ "HOST: " FACEBOOK_REQUEST_HOST "\r\n" \ "CONNECTION: close\r\n" \ "CONTENT-TYPE: application/x-www-form-urlencoded\r\n" \ "CONTENT-LENGTH: %d\r\n\r\n%s" #define FACEBOOK_APP_VERSION "1.0" #define FACEBOOK_ERROR_NODE_NAME "error_response" static void facebook_request_auth_get_session_notify_cb(gpointer data); static char* facebook_secret = NULL; //starts as app secret, but changes once we get a session static char* facebook_auth_token = NULL; //http://api.facebook.com static char* facebook_session_key = NULL; //http://api.facebook.com static char* facebook_uid = NULL; //the current users uid static GHashTable* facebook_hijacked_tooltips_table; //will store our hijacked tooltip functions static GHashTable* facebook_friends_table; //store our friends static guint facebook_request_timer_handle; typedef void (*facebook_xml_parse_callback) (xmlnode *node); //CB for parsed data returned from facebook typedef gboolean (*facebook_xml_parse_error_callback) (char *error_code, char *error_msg, xmlnode *node); //error returned from fb //the previous ops->notify_userinfo function, see below static void *(*hijacked_notify_userinfo) (PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info); //Will hold the information we collect for our facebook friends typedef struct _facebook_friend { char *uid; //facebook user id, unique per user (should be a long?) char *status; //user's status, like an away char *first_name; char *last_name; } facebook_friend; //Will hold callback data for facebook requests typedef struct _facebook_request_callback_data { char *method; facebook_xml_parse_callback success_cb; //cb when success facebook_xml_parse_error_callback error_cb; //cb when error } facebook_request_callback_data; //Wrapper to get a child node's data //free me static char * xmlnode_get_child_data(xmlnode *node, char *child_name) { xmlnode *child; if (!node) return g_strdup(""); child = xmlnode_get_child(node, child_name); if (child) return xmlnode_get_data(child); return g_strdup(""); } static const char * facebook_get_api_key() { #if FACEBOOK_APP_KEY_EDITABLE return purple_prefs_get_string(PREF_API_KEY); #else return FACEBOOK_APP_DEFAULT_API_KEY; #endif } static const char * facebook_get_secret() { if (facebook_secret) return facebook_secret; #if FACEBOOK_APP_KEY_EDITABLE return purple_prefs_get_string(PREF_SECRET); #else return FACEBOOK_APP_DEFAULT_SECRET; #endif } //Create new facebook friend static facebook_friend * facebook_friend_new(char *uid, char *first_name, char *last_name, char *status) { facebook_friend *f; if (!uid || !first_name || !last_name) return NULL; f = (facebook_friend*) malloc(sizeof(facebook_friend)); f->uid = g_strdup(uid); f->first_name = g_strdup(first_name); f->last_name = g_strdup(last_name); f->status = g_strdup(status ? status : ""); return f; } static char * facebook_friend_get_uid(facebook_friend *f) { return f->uid; } static const char * facebook_friend_get_status(facebook_friend *f) { return f->status; } static const char * facebook_friend_get_first_name(facebook_friend *f) { return f->first_name; } /* Not being used right now static const char * facebook_friend_get_last_name(facebook_friend *f) { return f->last_name; }*/ //Full name = first_name last_name //free me when done static char * facebook_friend_get_full_name(facebook_friend *f) { return g_strdup_printf("%s %s", f->first_name, f->last_name); } /* Not being used //Set the status for the facebook friend //Note that this frees the previous status, so strdup it if you still need it static void facebook_friend_set_status(facebook_friend *f, char *status) { g_free(f->status); f->status = g_strdup(status == NULL ? "" : status); }*/ //For sorting friends static int facebook_friend_name_cmp(const facebook_friend *a, const facebook_friend * b) { int rv = strcmp(a->first_name, b->first_name); if (rv != 0) return rv; return strcmp(a->last_name, b->last_name); } static void facebook_friend_free(facebook_friend *f) { g_free(f->uid); g_free(f->first_name); g_free(f->last_name); g_free(f->status); g_free(f); } //Get a facebook friend based on uid static facebook_friend * facebook_find_friend(const char *uid) { return (facebook_friend *) g_hash_table_lookup(facebook_friends_table, uid); } //Save the friend into our table //Note, this uses replace, so save any previous friend data or it gets destroyed static void purple_facebook_save_facebook_friend(facebook_friend *f) { //We're just going to do a replace instead of a status update //because maybe they updated their name etc g_hash_table_replace(facebook_friends_table, facebook_friend_get_uid(f), f); } //Gets the buddy's facebook uid or null static const char * purple_buddy_get_facebook_friend_uid(PurpleBuddy *b) { return purple_blist_node_get_string((PurpleBlistNode *)b, "facebook_uid"); } //Sets the facebook friend uid for a buddy static void purple_buddy_set_facebook_friend_uid(PurpleBuddy *b, char *uid) { purple_blist_node_set_string((PurpleBlistNode *)b, "facebook_uid", uid); } //Sets the facebook friend for a buddy //This is just a wrapper for set facebook uid static void purple_buddy_set_facebook_friend(PurpleBuddy *b, facebook_friend *f) { purple_buddy_set_facebook_friend_uid(b, facebook_friend_get_uid(f)); } static facebook_friend * purple_buddy_get_facebook_friend(PurpleBuddy *b) { const char *uid = purple_buddy_get_facebook_friend_uid(b); if (uid == NULL) return NULL; return facebook_find_friend(uid); } //Creates a parameter to be sent through the http request //free me when done static char * create_param(const char *name, const char *value) { return g_strdup_printf("%s=%s", name, value); } //Wrapper to create a call_id param, api.facebook.com static char * create_call_id_param() { GTimeVal time; char call_id[255]; g_get_current_time(&time); sprintf(call_id, "%ld%06ld", time.tv_sec, time.tv_usec); return create_param("call_id", call_id); } //Used to sort parameters, just a wrapper for strcmp static int param_sort(const void *p1, const void *p2) { return strcmp(*(char **) p1, *(char **) p2); } //creates a base 16 encoded md5 'encrypted' key //free me static char * hexed_md5(unsigned char *text, int len) { const int md5len = 16; PurpleCipher *cipher; PurpleCipherContext *context; guchar md5[md5len]; cipher = purple_ciphers_find_cipher("md5"); context = purple_cipher_context_new(cipher, NULL); purple_cipher_context_append(context, text, len); purple_cipher_context_digest(context, md5len, md5, NULL); purple_cipher_context_destroy(context); return purple_base16_encode(md5, md5len); } //Creates our signature, before it gets md5ed and hexed static char * create_premd5_sig(char **params, int n_param) { int i; char *rv; char **new_params = malloc((n_param + 2) * sizeof(char *)); for (i = 0; i < n_param; i++) new_params[i] = params[i]; new_params[i++] = (char *) facebook_get_secret(); new_params[i] = NULL; rv = g_strjoinv("", new_params); g_free(new_params); return rv; } //Our signature, look at facebook api //Note that params must be sorted! static char * create_sig(char **params, int n_param) { char *premd5sig, *sig; premd5sig = create_premd5_sig(params, n_param); sig = hexed_md5((unsigned char *) premd5sig, strlen(premd5sig)); g_free(premd5sig); return sig; } //Create our post request parameter string //ie, api_key=ABC&method=auth.createToken&... static char * create_facebook_request_param_string(char *method, char **params, int n_param) { const int added_param_cnt_pre_sig = 3; const int added_param_cnt_post_sig = 4; const int total_params = n_param + added_param_cnt_post_sig; //new params will contain all of our parameters and will be NULL terminated, for sorting char **new_params = (char **) malloc(sizeof(char*) * (total_params + 1)); char *param_string; char *sig; int i = 0; char *param_method, *param_version, *param_api_key, *param_sig;; for (i = 0; i < n_param; i++) new_params[i] = params[i]; //These will need to be freed //this should probably check the method and add session where needed new_params[i++] = param_method = create_param("method", method); new_params[i++] = param_version = create_param("v", FACEBOOK_APP_VERSION); new_params[i++] = param_api_key = create_param("api_key", facebook_get_api_key()); //Sort qsort(new_params, n_param + added_param_cnt_pre_sig, sizeof(char *), param_sort); //Finally create and add the sig sig = create_sig(new_params, n_param + added_param_cnt_pre_sig); new_params[i++] = param_sig = create_param("sig", sig); new_params[i] = NULL; //Join and we're done param_string = g_strjoinv("&", new_params); g_free(param_method); g_free(param_version); g_free(param_api_key); g_free(param_sig); g_free(sig); g_free(new_params); return param_string; } //This function takes care of any unhandled facebook request errors static void send_facebook_request_unhandled_error(char *error_code, char *error_msg, xmlnode *node) { char *node_str; node_str = xmlnode_to_str(node, NULL); purple_debug_error(FACEBOOK_DEBUG_ID, "Unhandled error, code (%d), message (%d), data (%s)\n", error_code, error_msg, node_str); g_free(node_str); } static void send_facebook_request_cb(PurpleUtilFetchUrlData *url_data, gpointer cb_data, const gchar *url_text, gsize len, const gchar *error_message) { xmlnode *node = NULL; facebook_request_callback_data *callback_data = (facebook_request_callback_data *) cb_data; purple_debug_info(FACEBOOK_DEBUG_ID, "Received request data, method %s\n", callback_data->method); if (!len) { //Error receiving data? purple_debug_error(FACEBOOK_DEBUG_ID, "Error sending out facebook request: %s\n", error_message); } else { //Create a node starting from the first < received to the last > received char *start = index(url_text, '<'); char *end = rindex(url_text, '>') + 1; node = xmlnode_from_str(start, end - start); if (!node) { //malformed response by facebook, this shouldn't happen purple_debug_error(FACEBOOK_DEBUG_ID, "Error creating node from text: (%s)\n", url_text); } else if (!strcmp(node->name, FACEBOOK_ERROR_NODE_NAME)) { //We got a valid response from facebook, but it was an error message char *error_code = xmlnode_get_child_data(node, "error_code"); //Is this an int? char *error_msg = xmlnode_get_child_data(node, "error_msg"); if (callback_data->error_cb == NULL || !callback_data->error_cb(error_code, error_msg, node)) { //If no error handler or error handler didn't handle it, call generic function send_facebook_request_unhandled_error(error_code, error_msg, node); } g_free(error_code); g_free(error_msg); } else { //Got valid data, call the handler if (callback_data->success_cb != NULL) callback_data->success_cb(node); } xmlnode_free(node); } g_free(callback_data->method); g_free(callback_data); } //Send a request to facebook //success_cb will be called if its a successful call //error_cb will be called if error xml is received //params is an array of create_param()'s //n_param is the count of params //Note: this should be updated to automatically add session param when needed static void send_facebook_request(char *method, facebook_xml_parse_callback success_cb, facebook_xml_parse_error_callback error_cb, char **params, int n_param) { char *param_string, *request; //This will be send to the send_facebook_request_cb() //must free facebook_request_callback_data *callback_data = (facebook_request_callback_data *) malloc(sizeof(facebook_request_callback_data)); callback_data->method = g_strdup(method); callback_data->success_cb = success_cb; callback_data->error_cb = error_cb; //Create our request param_string = create_facebook_request_param_string(method, params, n_param); request = g_strdup_printf(FACEBOOK_REQUEST_HEADER, (int)strlen(param_string), param_string); purple_debug_info(FACEBOOK_DEBUG_ID, "Created request: (%s)\n", param_string); //util.h is my friend purple_util_fetch_url_request( FACEBOOK_REQUEST_URL, //URL TRUE, //full url NULL, //user agent TRUE, //HTTP 1.1 request, //headers to send FALSE, //response includes headers send_facebook_request_cb, //callback callback_data //user data ); g_free(param_string); g_free(request); } /*** Start: custom fql request, gets friends & status ***/ static void facebook_request_fql_friends_status_cb(xmlnode *node) { xmlnode *user_node; //Loop through the nodes and create & save the friends for (user_node = xmlnode_get_child(node, "user"); user_node; user_node = xmlnode_get_next_twin(user_node)) { xmlnode *status_node; char *uid, *status_message, *first_name, *last_name; facebook_friend *f; status_node = xmlnode_get_child(user_node, "status"); uid = xmlnode_get_child_data(user_node, "uid"); first_name = xmlnode_get_child_data(user_node, "first_name"); last_name = xmlnode_get_child_data(user_node, "last_name"); status_message = xmlnode_get_child_data(status_node, "message"); f = facebook_friend_new(uid, first_name, last_name, status_message); if (!f) { purple_debug_error(FACEBOOK_DEBUG_ID, "Couldn't create facebook friend uid (%s) first_name (%s) last_name (%s)\n", uid, first_name, last_name); } else { purple_facebook_save_facebook_friend(f); } g_free(uid); g_free(first_name); g_free(last_name); g_free(status_message); } } static gboolean facebook_request_fql_friends_status() { //Selects all of the users friends as well as the actual user const int param_count = 3; int i = 0; char *params[param_count]; char *query; if (!facebook_uid || !facebook_session_key) return FALSE; query = g_strdup_printf( "SELECT uid, first_name, last_name, status.message FROM user WHERE uid IN (SELECT uid2 FROM friend WHERE uid1 = %s) OR uid = %s", facebook_uid, facebook_uid); params[i++] = create_param("session_key", facebook_session_key); params[i++] = create_call_id_param(); params[i++] = create_param("query", query); send_facebook_request("fql.query", facebook_request_fql_friends_status_cb, NULL, params, 3); for (i = 0; i < param_count; i++) g_free(params[i]); g_free(query); return TRUE; } /*** End: custom fql request ***/ static gboolean facebook_request_send_timer(gpointer data) { return facebook_request_fql_friends_status(); } static void change_facebook_request_timer(int timeout) { if (facebook_request_timer_handle) purple_timeout_remove(facebook_request_timer_handle); facebook_request_timer_handle = purple_timeout_add(timeout * 1000 * 60, facebook_request_send_timer, NULL); } /*** Start: facebook.auth.getSession ***/ static gboolean facebook_request_auth_get_session_error_cb(char *error_code, char *error_msg, xmlnode *node) { if (!strcmp(error_code, "100")) { //Invalid parameter... generally means user didn't authenticate purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, "Could not authenticate", "Could not authenticate", "Couldn't authenticate with the facebook server. \nClick on Get Session to try again", NULL, NULL); return TRUE; } else { return FALSE; } } static void facebook_request_auth_get_session_cb(xmlnode *node) { char *new_uid, *new_session_key, *new_secret; new_uid = xmlnode_get_child_data(node, "uid"); new_session_key = xmlnode_get_child_data(node, "session_key"); new_secret = xmlnode_get_child_data(node, "secret"); if (new_uid && new_session_key && new_secret) { g_free(facebook_uid); g_free(facebook_session_key); g_free(facebook_secret); facebook_uid = new_uid; facebook_session_key = new_session_key; facebook_secret = new_secret; } change_facebook_request_timer(purple_prefs_get_int(PREF_TIMEOUT)); facebook_request_fql_friends_status(); } static void facebook_request_auth_get_session() { char* auth_token = create_param("auth_token", facebook_auth_token); char* params[] = {auth_token}; send_facebook_request("auth.getSession", facebook_request_auth_get_session_cb, facebook_request_auth_get_session_error_cb, params, 1); g_free(auth_token); } /*** End: facebook.auth.getSession ***/ static void facebook_request_auth_get_session_notify_cb(gpointer data) { facebook_request_auth_get_session(); } /* Asks to visit the facebook login page */ static void request_facebook_login(void) { char *link, *message; g_return_if_fail(facebook_auth_token != NULL); link = g_strdup_printf( "http://www.facebook.com/login.php?api_key=%s" "&v=" FACEBOOK_APP_VERSION "&auth_token=%s", facebook_get_api_key(), facebook_auth_token); message = g_strdup_printf( "Please visit the following to enable:<BR/>" "<a href=\"%s\">%s</a><BR/>Then click Close", link, link); //This should be an action, OK/Cancel type of thing purple_notify_formatted(NULL, "Enable facebook plugin", "Enable facebook plugin", "", message, facebook_request_auth_get_session_notify_cb, NULL); g_free(link); g_free(message); } /*** Start: facebook.auth.createToken ***/ static gboolean facebook_request_auth_create_token_error_cb(char *error_code, char *error_msg, xmlnode *node) { if (!strcmp(error_code, "101") || !strcmp(error_code, "104")) { //Invalid api key or invalid signature char *message = g_strdup_printf("Facebook error: %s", error_msg); purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, "Could not create auth token", "Could not create auth token", message, NULL, NULL); g_free(message); return TRUE; //handled } else { //unhandled return FALSE; } } static void facebook_request_auth_create_token_cb(xmlnode *node) { if (facebook_auth_token) g_free(facebook_auth_token); facebook_auth_token = xmlnode_get_data(node); request_facebook_login(); purple_debug_misc("facebook", "Authentication link: http://www.facebook.com/login.php?api_key=%s&v=%s&auth_token=%s\n", facebook_get_api_key(), facebook_get_secret(), facebook_auth_token); } static void facebook_request_auth_create_token() { if (facebook_secret) g_free(facebook_secret); facebook_secret = NULL; send_facebook_request("auth.createToken", facebook_request_auth_create_token_cb, facebook_request_auth_create_token_error_cb, NULL, 0); } /*** End: facebook.auth.createToken ***/ static void facebook_friend_chosen_cb(PurpleBlistNode *node, PurpleRequestFields *fields) { PurpleRequestField *field = purple_request_fields_get_field(fields, "uid"); const GList *l; g_return_if_fail(node != NULL); g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); for (l = purple_request_field_list_get_selected(field); l != NULL; l = l->next) { purple_buddy_set_facebook_friend((PurpleBuddy *) node, (facebook_friend*) purple_request_field_list_get_data(field, l->data)); } } static void facebook_getsession_action(PurplePluginAction *action) { facebook_request_auth_create_token(); } static void facebook_refresh_action(PurplePluginAction *action) { if (facebook_session_key) facebook_request_fql_friends_status(); else purple_notify_message(NULL, PURPLE_NOTIFY_MSG_ERROR, "Not connected to facebook server", "Not connected to facebook server", "You must be connected to the facebook server to get data.\n" "Click on Get Session to connect", NULL, NULL); } static GList * actions(PurplePlugin *plugin, gpointer context) { GList *l = NULL; PurplePluginAction *act = NULL; //This seems to have broke in pidgin 2.0 //Temporary fix in place /*if (facebook_session_key == NULL) //No session, should try to get one { act = purple_plugin_action_new("Get Session", facebook_getsession_action); l = g_list_append(l, act); } else { act = purple_plugin_action_new("Force refresh", facebook_refresh_action); l = g_list_append(l, act); }*/ act = purple_plugin_action_new("Get/Reset Session", facebook_getsession_action); l = g_list_append(l, act); act = purple_plugin_action_new("Force data refresh", facebook_refresh_action); l = g_list_append(l, act); return l; } static PurpleNotifyUserInfoEntry* purple_buddy_get_facebook_user_info_entry(PurpleBuddy *b) { const char *status; char *status_message; PurpleNotifyUserInfoEntry *entry; facebook_friend *f = purple_buddy_get_facebook_friend(b); if (!f) return NULL; status = facebook_friend_get_status(f); status_message = (status && strcmp(status, "") ? g_strdup_printf("%s is %s", facebook_friend_get_first_name(f), facebook_friend_get_status(f)) : g_strdup("")); entry = purple_notify_user_info_entry_new("Facebook Status", status_message); g_free(status_message); return entry; } static void facebook_prpl_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) { GList *ignore; //sigh void (*original_tooltip_text)(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full); PurplePluginProtocolInfo *prpl_info; PurpleNotifyUserInfoEntry *entry = purple_buddy_get_facebook_user_info_entry(b);; if (entry) ignore = g_list_append(purple_notify_user_info_get_entries(user_info), entry); //yuck //Get and call original tooltip text function prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(b->account))); original_tooltip_text = g_hash_table_lookup(facebook_hijacked_tooltips_table, prpl_info); //Oh I'm clever if (!g_ascii_strcasecmp(b->name, "robflynn")) purple_notify_user_info_add_pair(user_info, _("Facebook Description"), _("AOL slayer")); if (!g_ascii_strcasecmp(b->name, "seanegn")) purple_notify_user_info_add_pair(user_info, _("Facebook Status"), _("AOL CEO")); if (!g_ascii_strcasecmp(b->name, "chipx86")) purple_notify_user_info_add_pair(user_info, _("Facebook Status"), _("Eats AOL CDs for breakfast")); if (!g_ascii_strcasecmp(b->name, "neaveru")) purple_notify_user_info_add_pair(user_info, _("Facebook Description"), _("Please don't sue me")); //seriously, please don't, I have no money, I swear //I'm going to call this plugin "AIM ENHANCED, BY AOL" if (original_tooltip_text != NULL) original_tooltip_text(b, user_info, full); } //free our hijacked tooltips static void facebook_hijacked_tooltip_free(gpointer key) { //Set it back! ((PurplePluginProtocolInfo* ) key)->tooltip_text = g_hash_table_lookup(facebook_hijacked_tooltips_table, key); } //gets all the protocol plugins and steal there tooltip making capabilities static void hijack_prpl_info_tooltip_text() { GList *l; PurplePlugin *plugin; PurplePluginProtocolInfo *prpl_info; for (l = purple_plugins_get_protocols(); l != NULL; l = l->next) { plugin = (PurplePlugin *)l->data; prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); //We want to save it in a table, so we can call it later g_hash_table_insert(facebook_hijacked_tooltips_table, prpl_info, prpl_info->tooltip_text); prpl_info->tooltip_text = facebook_prpl_tooltip_text; //YOINK } } static void facebook_table_add_friend_to_list(gpointer key, gpointer value, gpointer user_data) { GList *ignore; ignore = g_list_insert_sorted((GList *) user_data, value, (int (*) (gconstpointer, gconstpointer)) facebook_friend_name_cmp); } //Create a request to assign a friend to a node static void node_assign_friend_cb(PurpleBlistNode *node, gpointer data) { PurpleRequestFields *request; PurpleRequestFieldGroup *group; PurpleRequestField *field; //Create a list, with a dummy node, which will always be first facebook_friend* dummy; GList *l = NULL; char *tmp; g_return_if_fail(node != NULL); g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); //Create a list with a dummy node (easier for cmp and hash run through function) dummy = facebook_friend_new("0", "", "", ""); l = g_list_append(l, dummy); //Populate the list g_hash_table_foreach(facebook_friends_table, facebook_table_add_friend_to_list, l); group = purple_request_field_group_new(NULL); tmp = g_strdup_printf("Who is %s", ((PurpleBuddy *)node)->name); field = purple_request_field_list_new("uid", tmp); g_free(tmp); //Populate the request field with our list, skip the dummy for (l = g_list_first(l)->next; l != NULL; l = l->next) { char *name = facebook_friend_get_full_name((facebook_friend *) (l->data)); if (l->data != dummy) purple_request_field_list_add(field, name, l->data); g_free(name); } purple_request_field_group_add_field(group, field); request = purple_request_fields_new(); purple_request_fields_add_group(request, group); purple_request_fields(node, N_("Facebook Friend Picker"), "", NULL, request, _("_Set"), G_CALLBACK(facebook_friend_chosen_cb), _("_Cancel"), NULL, ((PurpleBuddy *)node)->account, ((PurpleBuddy *)node)->name, NULL, node); facebook_friend_free(dummy); } //Add option to assign a friend to a node static void facebook_extended_menu_cb(PurpleBlistNode *node, GList **m) { PurpleMenuAction *bna = NULL; if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) return; *m = g_list_append(*m, NULL); bna = purple_menu_action_new(_("Assign Facebook User"), PURPLE_CALLBACK(node_assign_friend_cb), NULL, NULL); *m = g_list_append(*m, bna); } static gboolean plugin_unload(PurplePlugin *plugin) { PurpleNotifyUiOps *ops = purple_notify_get_ui_ops(); purple_timeout_remove(facebook_request_timer_handle); g_free(facebook_secret); g_free(facebook_auth_token); g_free(facebook_session_key); g_free(facebook_uid); g_hash_table_destroy(facebook_hijacked_tooltips_table); g_hash_table_destroy(facebook_friends_table); if (ops) ops->notify_userinfo = hijacked_notify_userinfo; return TRUE; } //Adds facebook status for a user info static void * facebook_notify_userinfo(PurpleConnection *gc, const char *who, PurpleNotifyUserInfo *user_info) { GList *l, *start, *ignore; PurpleNotifyUserInfoEntry *entry; start = purple_notify_user_info_get_entries(user_info); //Set l to the first divider in the list for (l = start; l != NULL; l = l->next) { if (purple_notify_user_info_entry_get_type((PurpleNotifyUserInfoEntry *) (l->data)) == PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK) break; } entry = purple_buddy_get_facebook_user_info_entry(purple_find_buddy(purple_connection_get_account(gc), who)); if (entry && start) { //Note: Since we don't have direct access to user_info, we can't make this the first element //in the list if (l == start && l->next) { //if the first entry is a divider (why?), just insert it afterwards ignore = g_list_insert_before(start, l->next, entry); } else if (l == start) { //if the first entry is a divider and no other entries, insert at end ignore = g_list_append(start, entry); } else { //insert entry before first divider ignore = g_list_insert_before(start, l, entry); } } //And pretend like nothing happened return hijacked_notify_userinfo(gc, who, user_info); } static void hijack_user_info_handler(void) { PurpleNotifyUiOps *ops; ops = purple_notify_get_ui_ops(); if (!ops || !ops->notify_userinfo) return; hijacked_notify_userinfo = ops->notify_userinfo; ops->notify_userinfo = facebook_notify_userinfo; } static gboolean plugin_load(PurplePlugin *plugin) { //Some of this should probably be moved to init_plugin facebook_auth_token = NULL; facebook_session_key = NULL; facebook_uid = NULL; facebook_secret = NULL; facebook_hijacked_tooltips_table = g_hash_table_new_full(NULL, NULL, facebook_hijacked_tooltip_free, NULL); facebook_friends_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (void(*)(gpointer)) facebook_friend_free); hijack_prpl_info_tooltip_text(); hijack_user_info_handler(); if (purple_prefs_get_bool(PREF_ONLOAD)) facebook_request_auth_create_token(); purple_signal_connect(purple_blist_get_handle(), "blist-node-extended-menu", plugin, PURPLE_CALLBACK(facebook_extended_menu_cb), NULL); return TRUE; } static void facebook_timeout_pref_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer data) { change_facebook_request_timer((int) val); } static PurplePluginPrefFrame * get_plugin_pref_frame(PurplePlugin *plugin) { PurplePluginPrefFrame *frame; PurplePluginPref *ppref; frame = purple_plugin_pref_frame_new(); ppref = purple_plugin_pref_new_with_name_and_label( PREF_ONLOAD, _("_Connect on start")); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( PREF_TIMEOUT, _("Time to update facebook statuses (mins)")); purple_plugin_pref_set_bounds(ppref, 5, 60); //if you want it below 5 minutes... go outside purple_plugin_pref_frame_add(frame, ppref); #if FACEBOOK_APP_KEY_EDITABLE ppref = purple_plugin_pref_new_with_name_and_label( PREF_API_KEY, _("Facebook API Key")); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( PREF_SECRET, _("Facebook Secret")); purple_plugin_pref_frame_add(frame, ppref); #endif return frame; } static PurplePluginUiInfo prefs_info = { get_plugin_pref_frame, 0, /* page_num (Reserved) */ NULL, /* frame (Reserved) */ /* Padding */ NULL, NULL, NULL, NULL }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_STANDARD, /**< type */ NULL, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ PURPLE_PRIORITY_DEFAULT, /**< priority */ FACEBOOK_PLUGIN_ID, /**< id */ N_("Facebook"), /**< name */ VERSION, /**< version */ /** summary */ N_("Purple2 facebook plugin"), /** description */ N_("Integrate facebook with purple"), "Neaveru <neaveru@gmail.com>", /**< author */ "http://www.neaveru.com", /**< homepage */ plugin_load, /**< load */ plugin_unload, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ NULL, /**< extra_info */ &prefs_info, /**< prefs_info */ actions, /* padding */ NULL, NULL, NULL, NULL }; static void init_plugin(PurplePlugin *plugin) { purple_prefs_add_none(PREFS_BASE); purple_prefs_add_bool(PREF_ONLOAD, TRUE); purple_prefs_add_int(PREF_TIMEOUT, 15); purple_prefs_connect_callback(NULL, PREF_TIMEOUT, facebook_timeout_pref_cb, NULL); //should this be here? #if FACEBOOK_APP_KEY_EDITABLE purple_prefs_add_string(PREF_API_KEY, FACEBOOK_APP_DEFAULT_API_KEY); purple_prefs_add_string(PREF_SECRET, FACEBOOK_APP_DEFAULT_SECRET); #endif } PURPLE_INIT_PLUGIN(facebook, init_plugin, info)