pidgin/purple-plugin-pack
Add Eion's ning, okcupid, and omegle prpls.
--- a/common/Makefile.am Sun Dec 12 12:43:31 2010 -0500
+++ b/common/Makefile.am Sun Dec 12 18:10:38 2010 -0500
@@ -1,5 +1,6 @@
Binary file common/libjson-glib-1.0.dll has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/libning.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,555 @@
+ * libning is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "ning_connection.h" +JsonObject *ning_json_parse(const gchar *data, gssize data_len) + parser = json_parser_new(); + json_parser_load_from_data(parser, data, (gssize) data_len, NULL); + root_node = json_parser_get_root(parser); + root_obj = json_node_dup_object(root_node); + g_object_unref(parser); +build_user_json(NingAccount *na) + escaped_name = g_strescape(na->name, ""); + escaped_name = g_strdup(""); + if (na && na->icon_url) + escaped_icon = g_strescape(na->icon_url, ""); + escaped_icon = g_strdup(""); + escaped_id = g_strescape(na->ning_id, ""); + escaped_id = g_strdup(""); + user_json = g_strdup_printf("{\"name\":\"%s\",\"iconUrl\":\"%s\",\"isAdmin\":\"0\",\"ningId\":\"%s\",\"isNC\":\"0\"}", + escaped_name, escaped_icon, escaped_id); +/******************************************************************************/ +/******************************************************************************/ +static const char *ning_list_icon(PurpleAccount *account, PurpleBuddy *buddy) +static GList *ning_statuses(PurpleAccount *account) + PurpleStatusType *status; + purple_debug_info("ning", "statuses\n"); + /* Ning people are either online or offline */ + status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE); + types = g_list_append(types, status); + status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE); + types = g_list_append(types, status); + purple_debug_info("ning", "statuses return\n"); +void ning_chat_login_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata) + obj = ning_json_parse(data, data_len); + purple_debug_info("ning", "chat_login_cb: %s\n", data?data:"(null)"); + if (data == NULL || data_len == 0) + //{"command": "login","result": "ok","roomId": "thoughtleaders.thoughtleaders", + // "count": 2,"token": "37lfxean70eqh_122d86d5cf7_6f95cb8e_122d49f8e48"} + result = json_node_get_string(json_object_get_member(obj, "result")); + if (!result || !g_str_equal(result, "ok")) + purple_connection_error(na->pc, _("Could not log on")); + purple_connection_update_progress(na->pc, _("Joining public chat"), 5, 5); + purple_connection_set_state(na->pc, PURPLE_CONNECTED); + g_free(na->chat_token); + na->chat_token = g_strdup(json_node_get_string(json_object_get_member(obj, "token"))); + roomId = json_node_get_string(json_object_get_member(obj, "roomId")); + ning_join_chat_by_name(na, roomId); + json_object_unref(obj); +void ning_chat_redir_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata) + gchar *postdata, *encoded_app, *encoded_id; + gchar *user_json, *user_encoded; + purple_debug_info("ning", "ning_chat_redir_cb: %s\n", data?data:"(null)"); + //We get a randomly generated chat domain to use + // eg {"domain": "3841.chat07.ningim.com"} + obj = ning_json_parse(data, data_len); + g_free(na->chat_domain); + na->chat_domain = g_strdup(json_node_get_string(json_object_get_member(obj, "domain"))); + json_object_unref(obj); + //Use our new domain to log into the chat servers + purple_connection_update_progress(na->pc, _("Logging into chat"), 4, 5); + encoded_app = g_strdup(purple_url_encode(na->ning_app)); + encoded_id = g_strdup(purple_url_encode(na->ning_id)); + user_json = build_user_json(na); + user_encoded = g_strdup(purple_url_encode(user_json)); + postdata = g_strdup_printf("a=%s&t=%s%s&i=%s&user=%s", encoded_app, encoded_app, encoded_id, encoded_id, user_encoded); + ning_post_or_get(na, NING_METHOD_POST, na->chat_domain, + "/xn/presence/login", postdata, ning_chat_login_cb, NULL, FALSE); +void ning_login_home_cb(NingAccount *na, gchar *data, gsize data_len, gpointer userdata) + //<script>window.bzplcm.add({"app":"thoughtleaders","user":"37lfxean70eqh" + //xg.token = 'b1a7f3ce1719481334cdcc5fe8eabcaa'; + const gchar *start_string = "\nning = "; + const gchar *mid_string = "}};\n"; + const gchar *xgtoken_start = "xg.token = '"; + gchar *tmp, *ning_json_string, *xg_token; + JsonObject *obj, *profile; + tmp = g_strstr_len(data, data_len, start_string); + purple_connection_error(na->pc, _("NingID not found")); + tmp += strlen(start_string); + ning_json_string = g_strndup(tmp, strstr(tmp, mid_string) - tmp + 2); + purple_debug_info("ning", "ning_json_string: %s\n", ning_json_string); + obj = ning_json_parse(ning_json_string, strlen(ning_json_string)); + profile = json_node_get_object(json_object_get_member(obj, "CurrentProfile")); + na->ning_id = g_strdup(json_node_get_string(json_object_get_member(profile, "id"))); + na->name = g_strdup(json_node_get_string(json_object_get_member(profile, "fullName"))); + na->icon_url = g_strdup_printf("%s&width=16&height=16", json_node_get_string(json_object_get_member(profile, "photoUrl"))); + tmp = g_strstr_len(data, data_len, xgtoken_start); + purple_connection_error(na->pc, _("xgToken not found")); + tmp += strlen(xgtoken_start); + xg_token = g_strndup(tmp, strchr(tmp, '\'') - tmp); + na->xg_token = xg_token; + //Now we should have everything we need to sign into chat + purple_connection_update_progress(na->pc, _("Fetching chat server"), 3, 5); + url = g_strdup_printf("/xn/redirector/redirect?a=%s", purple_url_encode(na->ning_app)); + ning_post_or_get(na, NING_METHOD_GET, "chat01.ningim.com", + url, NULL, ning_chat_redir_cb, NULL, FALSE); +void ning_scan_cookies_for_id(gchar *key, gchar *value, NingAccount *na) + if (g_str_has_prefix(key, "xn_id_")) + na->ning_app = g_strdup(&key[6]); +static void ning_login_cb(NingAccount *na, gchar *response, gsize len, + purple_connection_update_progress(na->pc, _("Fetching token"), 2, 4); + // ok, we're logged into the host website now + // Pull the host's Ning account id from the cookie + g_hash_table_foreach(na->cookie_table, (GHFunc)ning_scan_cookies_for_id, na); + //Load the homepage to grab the interesting info + ning_post_or_get(na, NING_METHOD_GET, purple_account_get_string(na->account, "host", NULL), + "/", NULL, ning_login_home_cb, NULL, FALSE); +static void ning_login(PurpleAccount *account) + gchar *postdata, *encoded_username, *encoded_password; + gchar *encoded_host, *url; + purple_debug_info("ning", "login\n"); + /* Create account and initialize state */ + na = g_new0(NingAccount, 1); + na->pc = purple_account_get_connection(account); + na->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */ + na->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, + na->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + g_hash_table_replace(na->cookie_table, g_strdup("xg_cookie_check"), + account->gc->proto_data = na; + purple_connection_set_state(na->pc, PURPLE_CONNECTING); + purple_connection_update_progress(na->pc, _("Logging in"), 1, 4); + encoded_username = g_strdup(purple_url_encode(purple_account_get_username(account))); + encoded_password = g_strdup(purple_url_encode(purple_account_get_password(account))); + postdata = g_strdup_printf("xg_token=&emailAddress=%s&password=%s", + encoded_username, encoded_password); + g_free(encoded_username); + g_free(encoded_password); + host = purple_account_get_string(account, "host", NULL); + if (host == NULL || host[0] == '\0') + purple_connection_error(na->pc, _("Host not set")); + encoded_host = g_strdup(purple_url_encode(host)); + url = g_strdup_printf("/main/authorization/doSignIn?target=http%%3A%%2F%%2F%s", host); + ning_post_or_get(na, NING_METHOD_POST | NING_METHOD_SSL, host, + url, postdata, ning_login_cb, NULL, FALSE); +static void ning_close(PurpleConnection *pc) + gchar *xg_token_encoded; + PurpleDnsQueryData *dns_query; + purple_debug_info("ning", "disconnecting account\n"); + host_encoded = g_strdup(purple_url_encode(purple_account_get_string(na->account, "host", ""))); + xg_token_encoded = g_strdup(purple_url_encode(na->xg_token)); + postdata = g_strdup_printf("target=%s&xg_token=%s", host_encoded, xg_token_encoded); + ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL), + "/main/authorization/signOut", postdata, NULL, NULL, FALSE); + g_free(xg_token_encoded); + purple_debug_info("ning", "destroying %d incomplete connections\n", + g_slist_length(na->conns)); + while (na->conns != NULL) + ning_connection_destroy(na->conns->data); + while (na->dns_queries != NULL) { + dns_query = na->dns_queries->data; + purple_debug_info("ning", "canceling dns query for %s\n", + purple_dnsquery_get_host(dns_query)); + na->dns_queries = g_slist_remove(na->dns_queries, dns_query); + purple_dnsquery_destroy(dns_query); + g_hash_table_destroy(na->cookie_table); + g_hash_table_destroy(na->hostname_ip_cache); + while (na->chats != NULL) + NingChat *chat = na->chats->data; + na->chats = g_list_remove(na->chats, chat); + purple_timeout_remove(chat->userlist_timer); + purple_timeout_remove(chat->message_poll_timer); + purple_conv_chat_left(PURPLE_CONV_CHAT(purple_find_chat(pc, chat->purple_id))); + g_free(chat->ning_hash); +#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5 +static GHashTable *ning_get_account_text_table(PurpleAccount *account) + table = g_hash_table_new(g_str_hash, g_str_equal); + g_hash_table_insert(table, "login_label", (gpointer)_("Email Address...")); +ning_change_passwd(PurpleConnection *pc, const char *old_pass, const char *new_pass) + PurpleAccount *account; + gchar *encoded_username; + gchar *encoded_password; + if (na == NULL || na->xg_token == NULL) + encoded_username = g_strdup(purple_url_encode(purple_account_get_username(account))); + encoded_password = g_strdup(purple_url_encode(new_pass)); + encoded_token = g_strdup(purple_url_encode(na->xg_token)); + postdata = g_strdup_printf("emailAddress=%s&password=%s&xg_token=%s", + encoded_username, encoded_password, encoded_token); + ning_post_or_get(na, NING_METHOD_POST, purple_account_get_string(na->account, "host", NULL), + "/profiles/settings/updateEmailAddress", postdata, NULL, NULL, FALSE); + g_free(encoded_password); + g_free(encoded_username); +/******************************************************************************/ +/******************************************************************************/ +static gboolean plugin_load(PurplePlugin *plugin) +static gboolean plugin_unload(PurplePlugin *plugin) +static void plugin_init(PurplePlugin *plugin) + PurpleAccountOption *option; + PurplePluginInfo *info = plugin->info; + PurplePluginProtocolInfo *prpl_info = info->extra_info; + option = purple_account_option_string_new("Host", "host", ""); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); +static PurplePluginProtocolInfo prpl_info = { + NULL, /* user_splits */ + NULL, /* protocol_options */ + NO_BUDDY_ICONS /* icon_spec */ + /*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */ + ning_list_icon, /* list_icon */ + NULL, /* list_emblems */ + NULL, /* status_text */ + NULL, /* tooltip_text */ + ning_statuses, /* status_types */ + NULL, /* blist_node_menu */ + NULL, /* chat_info_defaults */ + ning_login, /* login */ + ning_close, /* close */ + ning_send_im, /* send_im */ + NULL, /* send_typing */ + ning_change_passwd, /* change_passwd */ + NULL, /* add_buddies */ + NULL, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* set_permit_deny */ + ning_join_chat, /* join_chat */ + NULL, /* reject chat invite */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + ning_chat_whisper, /* chat_whisper */ + ning_chat_send, /* chat_send */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + NULL, /* convo_closed */ + purple_normalize_nocase,/* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* attention_types */ +#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5 + sizeof(PurplePluginProtocolInfo), /* struct_size */ + ning_get_account_text_table, /* get_account_text_table */ + (gpointer) sizeof(PurplePluginProtocolInfo) +static PurplePluginInfo info = { + PURPLE_PLUGIN_PROTOCOL, /* type */ + NULL, /* ui_requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + "prpl-bigbrownchunx-ning", /* id */ + NING_PLUGIN_VERSION, /* version */ + N_("Ning Protocol Plugin"), /* summary */ + N_("Ning Protocol Plugin"), /* description */ + "Eion Robb <eionrobb@gmail.com>", /* author */ + plugin_load, /* load */ + plugin_unload, /* unload */ + &prpl_info, /* extra_info */ +PURPLE_INIT_PLUGIN(ning, plugin_init, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/libning.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,100 @@
+ * libning is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#define NING_PLUGIN_VERSION "0.01" +#define NING_TEMP_GROUP_NAME "Ning Temp" +#include <json-glib/json-glib.h> +#ifndef G_GNUC_NULL_TERMINATED +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +# define G_GNUC_NULL_TERMINATED +# endif /* __GNUC__ >= 4 */ +#endif /* G_GNUC_NULL_TERMINATED */ +# define dlopen(a,b) LoadLibrary(a) +# define dlsym(a,b) GetProcAddress(a,b) +# define dlclose(a) FreeLibrary(a) +# include <netinet/in.h> +# include <sys/socket.h> +typedef struct _NingAccount NingAccount; + PurpleAccount *account; + GHashTable *hostname_ip_cache; + GSList *conns; /**< A list of all active connections */ + GHashTable *cookie_table; + time_t last_messages_download_time; +JsonObject *ning_json_parse(const gchar *data, gssize data_len); +gchar *build_user_json(NingAccount *na); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_chat.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,454 @@
+ * Created by MyMacSpace on 6/08/09. + * Copyright 2009 __MyCompanyName__. All rights reserved. +void (*chat_leave)(PurpleConnection *, int id); +void (*chat_whisper)(PurpleConnection *, int id, const char *who, const char *message); +int (*chat_send)(PurpleConnection *, int id, const char *message, PurpleMessageFlags flags); +void (*join_chat)(PurpleConnection *, GHashTable *components); +PurpleConversation* purple_find_chat(const PurpleConnection *gc, int id); +gint64 ning_time_kludge(gint initial_time) + if (sizeof(gint) >= sizeof(gint64)) + gint64 now_millis = (gint64) time(NULL); + now_millis &= 0xFFFFFFFF00000000LL; + gint64 final_time = now_millis | ((guint)initial_time); +ning_chat_messages_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata) + purple_debug_info("ning", "ning_chat_messages_cb: %s\n", response?response:"(null)"); + obj = ning_json_parse(response, len); + if (json_object_has_member(obj, "hash")) + g_free(chat->ning_hash); + chat->ning_hash = g_strdup(json_node_get_string(json_object_get_member(obj, "hash"))); + array = json_node_get_array(json_object_get_member(obj, "messages")); + for(i = 0; i < json_array_get_length(array); i++) + msgobj = json_node_get_object(json_array_get_element(array, i)); + //type (message or private) + //body, date, roomId, targetId, sender(obj) + type = json_node_get_string(json_object_get_member(msgobj, "type")); + body = json_node_get_string(json_object_get_member(msgobj, "body")); + date = json_node_get_int(json_object_get_member(msgobj, "date")); + roomId = json_node_get_string(json_object_get_member(msgobj, "roomId")); + targetId = json_node_get_string(json_object_get_member(msgobj, "targetId")); + sender = json_node_get_object(json_object_get_member(msgobj, "sender")); + senderId = json_node_get_string(json_object_get_member(sender, "ningId")); + //Check that they're on the buddy list + buddy = purple_find_buddy(na->account, senderId); + //They aren't, so lets fake it + buddy = purple_buddy_new(na->account, senderId, + json_node_get_string(json_object_get_member(sender, "name"))); + group = purple_find_group(NING_TEMP_GROUP_NAME); + group = purple_group_new(NING_TEMP_GROUP_NAME); + purple_blist_add_group(group, NULL); + purple_blist_node_set_flags(&group->node, PURPLE_BLIST_NODE_FLAG_NO_SAVE); + purple_blist_add_buddy(buddy, NULL, group, NULL); + purple_blist_node_set_flags(&buddy->node, PURPLE_BLIST_NODE_FLAG_NO_SAVE); + time_kludge = ning_time_kludge(date); + if (g_str_equal(type, "message")) + serv_got_chat_in(na->pc, chat->purple_id, senderId, PURPLE_MESSAGE_RECV, body, time_kludge); + } else if (g_str_equal(type, "private")) + serv_got_chat_in(na->pc, chat->purple_id, senderId, + PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_WHISPER, body, time_kludge); + purple_debug_info("ning", "unknown message type: %s\n", type); + json_object_unref(obj); +ning_chat_get_history(NingChat *chat) + encoded_hash = g_strdup(purple_url_encode(chat->ning_hash)); + encoded_app = g_strdup(purple_url_encode(na->ning_app)); + encoded_id = g_strdup(purple_url_encode(na->ning_id)); + encoded_token = g_strdup(purple_url_encode(na->chat_token)); + encoded_room = g_strdup(purple_url_encode(chat->roomId)); + url = g_strdup_printf("/xn/groupchat/list?h=%s&a=%s&i=%s&t=%s&r=%s", encoded_hash, encoded_app, + encoded_id, encoded_token, encoded_room); + ning_post_or_get(na, NING_METHOD_GET, na->chat_domain, + ning_chat_messages_cb, chat, FALSE); +ning_chat_get_users_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata) + //const gchar *iconUrl; + PurpleConversation *conv; + PurpleConvChatBuddy *cbuddy; + PurpleConversationUiOps *uiops; + purple_debug_info("ning", "chat users: %s\n", response?response:"(null)"); + obj = ning_json_parse(response, len); + if (json_object_has_member(obj, "hash")) + g_free(chat->ning_hash); + chat->ning_hash = g_strdup(json_node_get_string(json_object_get_member(obj, "hash"))); + conv = purple_find_chat(na->pc, chat->purple_id); + uiops = purple_conversation_get_ui_ops(conv); + array = json_node_get_array(json_object_get_member(obj, "expired")); + for(i = 0; i < json_array_get_length(array); i++) + ningId = json_node_get_string(json_array_get_element(array, i)); + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(conv), ningId, NULL); + array = json_node_get_array(json_object_get_member(obj, "users")); + if (array && json_array_get_length(array) > 0) + purple_conv_chat_clear_users(PURPLE_CONV_CHAT(conv)); + for(i = 0; i < json_array_get_length(array); i++) + userobj = json_node_get_object(json_array_get_element(array, i)); + //isNC, iconUrl, name, ningId, isAdmin + ningId = json_node_get_string(json_object_get_member(userobj, "ningId")); + name = json_node_get_string(json_object_get_member(userobj, "name")); + isAdmin = json_node_get_boolean(json_object_get_member(userobj, "isAdmin")); + if (!purple_conv_chat_find_user(PURPLE_CONV_CHAT(conv), ningId)) + purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv), ningId, + NULL, isAdmin?PURPLE_CBFLAGS_OP:PURPLE_CBFLAGS_NONE, + cbuddy = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), ningId); + cbuddy->alias = g_strdup(name); + if (uiops && uiops->chat_rename_user) + purple_debug_info("ning", "try rename user %s to %s\n", ningId, name); + uiops->chat_rename_user(conv, ningId, ningId, name); + else if (uiops && uiops->chat_update_user) + purple_debug_info("ning", "try update user %s\n", ningId); + uiops->chat_update_user(conv, ningId); + json_object_unref(obj); +ning_chat_get_users(NingChat *chat) + encoded_hash = g_strdup(purple_url_encode(chat->ning_hash)); + encoded_app = g_strdup(purple_url_encode(na->ning_app)); + encoded_id = g_strdup(purple_url_encode(na->ning_id)); + encoded_token = g_strdup(purple_url_encode(na->chat_token)); + encoded_room = g_strdup(purple_url_encode(chat->roomId)); + url = g_strdup_printf("/xn/presence/list?h=%s&a=%s&i=%s&t=%s&r=%s", encoded_hash, encoded_app, + encoded_id, encoded_token, encoded_room); + ning_post_or_get(na, NING_METHOD_GET, na->chat_domain, + ning_chat_get_users_cb, chat, FALSE); +ning_chat_poll_messages(NingChat *chat) + encoded_app = g_strdup(purple_url_encode(na->ning_app)); + encoded_id = g_strdup(purple_url_encode(na->ning_id)); + encoded_token = g_strdup(purple_url_encode(na->chat_token)); + encoded_room = g_strdup(purple_url_encode(chat->roomId)); + url = g_strdup_printf("/xn/groupchat/poll?a=%s&i=%s&t=%s&r=%s", encoded_app, + encoded_id, encoded_token, encoded_room); + ning_post_or_get(na, NING_METHOD_GET, na->chat_domain, + ning_chat_messages_cb, chat, FALSE); +ning_chat_cb(NingAccount *na, gchar *response, gsize len, gpointer userdata) + PurpleConversation *conv; + purple_debug_info("ning", "ning_chat_cb: %s\n", response?response:"(null)"); +ning_chat_whisper(PurpleConnection *pc, int id, const char *who, const char *message) + PurpleConversation *conv; + gchar *message_escaped; + gchar *ning_id_escaped; + purple_debug_info("ning", "chat whisper %s %s\n", who, message); + conv = purple_find_chat(pc, id); + app_escaped = g_strdup(purple_url_encode(na->ning_app)); + token_escaped = g_strdup(purple_url_encode(na->xg_token)); + room_escaped = g_strdup(purple_url_encode(conv->name)); + ning_id_escaped = g_strdup(purple_url_encode(na->ning_id)); + sender = build_user_json(na); + stripped = purple_markup_strip_html(message); + message_json = g_strdup_printf("{ \"roomId\":\"%s\", \"type\":\"private\", \"targetId\":\"%s\", \"body\":\"%s\", \"sender\":%s }", + conv->name, who, stripped, + message_json = g_strdup_printf("{ \"roomId\":\"%s\", \"type\":\"message\", \"targetId\":null, \"body\":\"%s\", \"sender\":%s }", + message_escaped = g_strdup(purple_url_encode(message_json)); + postdata = g_strdup_printf("a=%s&i=%s&t=%s&r=%s&message=%s", + app_escaped, ning_id_escaped, + token_escaped, room_escaped, + ning_post_or_get(na, NING_METHOD_POST, na->chat_domain, + "/xn/groupchat/publish", postdata, + ning_chat_cb, conv, FALSE); + g_free(message_escaped); + g_free(ning_id_escaped); +ning_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags) + PurpleConversation *conv; + if (flags != PURPLE_MESSAGE_SEND) + chats = purple_get_chats(); + for (;chats;chats = chats->next) + chat = PURPLE_CONV_CHAT(conv); + if (purple_conv_chat_find_user(chat, who)) + chat_id = purple_conv_chat_get_id(chat); + ning_chat_whisper(pc, chat_id, who, message); +ning_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags) + if (flags != PURPLE_MESSAGE_SEND) + ning_chat_whisper(pc, id, NULL, message); +ning_join_chat_by_name(NingAccount *na, const gchar *roomId) + if (na == NULL || roomId == NULL) + chat = g_new0(NingChat, 1); + chat->roomId = g_strdup(roomId); + chat->purple_id = g_str_hash(roomId); + chat->ning_hash = g_strdup("null"); + serv_got_joined_chat(na->pc, g_str_hash(roomId), roomId); + ning_chat_get_history(chat); + ning_chat_get_users(chat); + chat->userlist_timer = purple_timeout_add_seconds(60, (GSourceFunc) ning_chat_get_users, chat); + ning_chat_poll_messages(chat); + chat->message_poll_timer = purple_timeout_add_seconds(180, (GSourceFunc) ning_chat_poll_messages, chat); + na->chats = g_list_append(na->chats, chat); +ning_join_chat(PurpleConnection *pc, GHashTable *components) + if (pc == NULL || pc->proto_data == NULL || components == NULL) + ning_join_chat_by_name(na, g_hash_table_lookup(components, "name")); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_chat.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,34 @@
+ * Created by MyMacSpace on 6/08/09. + * Copyright 2009 __MyCompanyName__. All rights reserved. +#include "ning_connection.h" +typedef struct _NingChat { + guint message_poll_timer; +void ning_chat_whisper(PurpleConnection *pc, int id, const char *who, const char *message); +int ning_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags); +void ning_join_chat_by_name(NingAccount *na, const gchar *roomId); +void ning_join_chat(PurpleConnection *pc, GHashTable *components); +int ning_send_im(PurpleConnection *pc, const char *who, const char *message, PurpleMessageFlags flags); +#endif /* NING_CHAT_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,624 @@
+ * libning is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "ning_connection.h" +static void ning_attempt_connection(NingConnection *); +static gchar *ning_gunzip(const guchar *gzip_data, ssize_t *len_ptr) + gsize gzip_data_len = *len_ptr; + gulong gzip_len = G_MAXUINT16; + GString *output_string = NULL; + data_buffer = g_new0(gchar, gzip_len); + gzip_err = inflateInit2(&zstr, MAX_WBITS+32); + purple_debug_error("ning", "no built-in gzip support in zlib\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_DATA_ERROR) + inflateInit2(&zstr, -MAX_WBITS); + purple_debug_error("ning", "Cannot decode gzip header\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + output_string = g_string_new(""); + while (gzip_err == Z_OK) + //append data to buffer + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_STREAM_END) + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + purple_debug_error("ning", "gzip inflate error\n"); + gchar *output_data = g_strdup(output_string->str); + *len_ptr = output_string->len; + g_string_free(output_string, TRUE); +void ning_connection_destroy(NingConnection *conn) + conn->na->conns = g_slist_remove(conn->na->conns, conn); + if (conn->request != NULL) + g_string_free(conn->request, TRUE); + if (conn->connect_data != NULL) + purple_proxy_connect_cancel(conn->connect_data); + if (conn->ssl_conn != NULL) + purple_ssl_close(conn->ssl_conn); + if (conn->input_watcher > 0) + purple_input_remove(conn->input_watcher); + g_free(conn->hostname); +static void ning_update_cookies(NingAccount *na, const gchar *headers) + const gchar *cookie_start; + const gchar *cookie_end; + g_return_if_fail(headers != NULL); + header_len = strlen(headers); + /* look for the next "Set-Cookie: " */ + /* grab the data up until ';' */ + cookie_start = headers; + while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && + (headers-cookie_start) < header_len) + cookie_end = strchr(cookie_start, '='); + cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end + 1; + cookie_end = strchr(cookie_start, ';'); + cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end; + purple_debug_info("ning", "got cookie %s=%s\n", + cookie_name, cookie_value); + g_hash_table_replace(na->cookie_table, cookie_name, +static void ning_connection_process_data(NingConnection *conn) + tmp = g_strstr_len(conn->rx_buf, len, "\r\n\r\n"); + /* This is a corner case that occurs when the connection is + * prematurely closed either on the client or the server. + * This can either be no data at all or a partial set of + * headers. We pass along the data to be good, but don't + * do any fancy massaging. In all likelihood the result will + * be tossed by the connection callback func anyways + tmp = g_strndup(conn->rx_buf, len); + len -= g_strstr_len(conn->rx_buf, len, "\r\n\r\n") - + tmp = g_memdup(tmp, len + 1); + conn->rx_buf[conn->rx_len - len] = '\0'; + purple_debug_misc("ning", "response headers\n%s\n", + ning_update_cookies(conn->na, conn->rx_buf); + if (strstr(conn->rx_buf, "Content-Encoding: gzip")) + /* we've received compressed gzip data, decompress */ + gunzipped = ning_gunzip((const guchar *)tmp, &len); + if (conn->callback != NULL) + conn->callback(conn->na, tmp, len, conn->user_data); +static void ning_fatal_connection_cb(NingConnection *conn) + PurpleConnection *pc = conn->na->pc; + purple_debug_error("ning", "fatal connection error\n"); + ning_connection_destroy(conn); + /* We died. Do not pass Go. Do not collect $200 */ + /* In all seriousness, don't attempt to call the normal callback here. + * That may lead to the wrong error message being displayed */ + purple_connection_error_reason(pc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); +static void ning_post_or_get_readdata_cb(gpointer data, gint source, + PurpleInputCondition cond) + if (conn->method & NING_METHOD_SSL) { + len = purple_ssl_read(conn->ssl_conn, + len = recv(conn->fd, buf, sizeof(buf) - 1, 0); + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + if (conn->method & NING_METHOD_SSL && conn->rx_len > 0) { + * This is a slightly hacky workaround for a bug in either + * GNU TLS or in the SSL implementation on Facebook's web + * servers. The sequence of events is: + * 1. We attempt to read the first time and successfully read + * the server's response. + * 2. We attempt to read a second time and libpurple's call + * to gnutls_record_recv() returns the error + * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or + * "A TLS packet with unexpected length was received." + * Normally the server would have closed the connection + * cleanly and this second read() request would have returned + * 0. Or maybe it's normal for SSL connections to be severed + * in this manner? In any case, this differs from the behavior + * of the standard recv() system call. + purple_debug_warning("ning", + "ssl error, but data received. attempting to continue\n"); + /* TODO: Is this a regular occurrence? If so then maybe resend the request? */ + ning_fatal_connection_cb(conn); + conn->rx_buf = g_realloc(conn->rx_buf, + conn->rx_len + len + 1); + memcpy(conn->rx_buf + conn->rx_len, buf, len + 1); + /* Wait for more data before processing */ + /* The server closed the connection, let's parse the data */ + ning_connection_process_data(conn); + ning_connection_destroy(conn); +static void ning_post_or_get_ssl_readdata_cb (gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + ning_post_or_get_readdata_cb(data, -1, cond); +static void ning_post_or_get_connect_cb(gpointer data, gint source, + const gchar *error_message) + conn->connect_data = NULL; + purple_debug_error("ning", "post_or_get_connect_cb %s\n", + ning_fatal_connection_cb(conn); + purple_debug_info("ning", "post_or_get_connect_cb\n"); + /* TODO: Check the return value of write() */ + len = write(conn->fd, conn->request->str, + conn->input_watcher = purple_input_add(conn->fd, + ning_post_or_get_readdata_cb, conn); +static void ning_post_or_get_ssl_connect_cb(gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + purple_debug_info("ning", "post_or_get_ssl_connect_cb\n"); + /* TODO: Check the return value of write() */ + len = purple_ssl_write(conn->ssl_conn, + conn->request->str, conn->request->len); + purple_ssl_input_add(conn->ssl_conn, + ning_post_or_get_ssl_readdata_cb, conn); +static void ning_host_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) + GSList *host_lookup_list; + struct sockaddr_in *addr; + PurpleDnsQueryData *query; + purple_debug_info("ning", "updating cache of dns addresses\n"); + /* Extract variables */ + host_lookup_list = data; + na = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + hostname = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + query = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + /* The callback has executed, so we no longer need to keep track of + * the original query. This always needs to run when the cb is + na->dns_queries = g_slist_remove(na->dns_queries, query); + /* Any problems, capt'n? */ + if (error_message != NULL) + purple_debug_warning("ning", + "Error doing host lookup: %s\n", error_message); + purple_debug_warning("ning", + "Could not resolve host name\n"); + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Copy the address then free it... */ + ip_address = g_strdup(inet_ntoa(addr->sin_addr)); + hosts = g_slist_delete_link(hosts, hosts); + * DNS lookups can return a list of IP addresses, but we only cache + * the first one. So free the rest. + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address... */ + hosts = g_slist_delete_link(hosts, hosts); + purple_debug_info("ning", "Host %s has IP %s\n", + g_hash_table_insert(na->hostname_ip_cache, hostname, ip_address); +static void ning_cookie_foreach_cb(gchar *cookie_name, + gchar *cookie_value, GString *str) + /* TODO: Need to escape name and value? */ + g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); +static gchar *ning_cookies_to_string(NingAccount *na) + str = g_string_new(NULL); + g_hash_table_foreach(na->cookie_table, + (GHFunc)ning_cookie_foreach_cb, str); + return g_string_free(str, FALSE); +static void ning_ssl_connection_error(PurpleSslConnection *ssl, + PurpleSslErrorType errortype, gpointer data) + NingConnection *conn = data; + PurpleConnection *pc = conn->na->pc; + ning_connection_destroy(conn); + purple_connection_ssl_error(pc, errortype); +void ning_post_or_get(NingAccount *na, NingMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + NingProxyCallbackFunc callback_func, gpointer user_data, + gboolean is_proxy = FALSE; + const gchar *user_agent; + const gchar* const *languages; + PurpleProxyInfo *proxy_info = NULL; + gchar *proxy_auth_base64; + purple_debug_info("ning", "post_or_get\n"); + /* TODO: Fix keepalive and use it as much as possible */ + purple_debug_error("ning", "no host specified\n"); + if (na && na->account && !(method & NING_METHOD_SSL)) + proxy_info = purple_proxy_get_setup(na->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + real_url = g_strdup_printf("http://%s%s", host, url); + real_url = g_strdup(url); + cookies = ning_cookies_to_string(na); + user_agent = purple_account_get_string(na->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)"); + if (method & NING_METHOD_POST && !postdata) + /* Build the request */ + request = g_string_new(NULL); + g_string_append_printf(request, "%s %s HTTP/1.0\r\n", + (method & NING_METHOD_POST) ? "POST" : "GET", + g_string_append_printf(request, "Host: %s\r\n", host); + g_string_append_printf(request, "Connection: %s\r\n", + (keepalive ? "Keep-Alive" : "close")); + g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); + if (method & NING_METHOD_POST) { + g_string_append_printf(request, + "Content-Type: application/x-www-form-urlencoded\r\n"); + g_string_append_printf(request, + "Content-length: %zu\r\n", strlen(postdata)); + g_string_append_printf(request, "Accept: */*\r\n"); + g_string_append_printf(request, "Cookie: %s\r\n", cookies); + g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); + if (purple_proxy_info_get_username(proxy_info) && + purple_proxy_info_get_password(proxy_info)) + proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); + proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); + g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); + g_free(proxy_auth_base64); + /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ + languages = g_get_language_names(); + language_names = g_strjoinv(", ", (gchar **)languages); + purple_util_chrreplace(language_names, '_', '-'); + g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); + g_free(language_names); + purple_debug_misc("ning", "sending request headers:\n%s\n", + g_string_append_printf(request, "\r\n"); + if (method & NING_METHOD_POST) + g_string_append_printf(request, "%s", postdata); + /* If it needs to go over a SSL connection, we probably shouldn't print + * it in the debug log. Without this condition a user's password is + * printed in the debug log */ + if (method == NING_METHOD_POST) + purple_debug_misc("ning", "sending request data:\n%s\n", + * Do a separate DNS lookup for the given host name and cache it + * TODO: It would be better if we did this before we call + * purple_proxy_connect(), so we could re-use the result. + * Or even better: Use persistent HTTP connections for servers + * that we access continually. + * TODO: This cache of the hostname<-->IP address does not respect + * the TTL returned by the DNS server. We should expire things + * from the cache after some amount of time. + /* Don't do this for proxy connections, since proxies do the DNS lookup */ + host_ip = g_hash_table_lookup(na->hostname_ip_cache, host); + purple_debug_info("ning", + "swapping original host %s with cached value of %s\n", + } else if (na->account && !na->account->disconnecting) { + GSList *host_lookup_list = NULL; + PurpleDnsQueryData *query; + host_lookup_list = g_slist_prepend( + host_lookup_list, g_strdup(host)); + host_lookup_list = g_slist_prepend( + query = purple_dnsquery_a(host, 80, + ning_host_lookup_cb, host_lookup_list); + na->dns_queries = g_slist_prepend(na->dns_queries, query); + host_lookup_list = g_slist_append(host_lookup_list, query); + conn = g_new0(NingConnection, 1); + conn->hostname = g_strdup(host); + conn->request = request; + conn->callback = callback_func; + conn->user_data = user_data; + conn->connection_keepalive = keepalive; + conn->request_time = time(NULL); + na->conns = g_slist_prepend(na->conns, conn); + ning_attempt_connection(conn); +static void ning_attempt_connection(NingConnection *conn) + NingAccount *na = conn->na; + if (conn->method & NING_METHOD_SSL) { + conn->ssl_conn = purple_ssl_connect(na->account, conn->hostname, + 443, ning_post_or_get_ssl_connect_cb, + ning_ssl_connection_error, conn); + conn->connect_data = purple_proxy_connect(NULL, na->account, + conn->hostname, 80, ning_post_or_get_connect_cb, conn); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ning/ning_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,63 @@
+ * libning is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#ifndef NING_CONNECTION_H +#define NING_CONNECTION_H +typedef void (*NingProxyCallbackFunc) + (NingAccount *na, gchar *data, gsize data_len, gpointer user_data); + NING_METHOD_GET = 0x0001, + NING_METHOD_POST = 0x0002, + NING_METHOD_SSL = 0x0004 +typedef struct _NingConnection NingConnection; +struct _NingConnection { + NingProxyCallbackFunc callback; + PurpleProxyConnectData *connect_data; + PurpleSslConnection *ssl_conn; + gboolean connection_keepalive; +void ning_connection_destroy(NingConnection *okcconn); +void ning_post_or_get(NingAccount *oca, NingMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + NingProxyCallbackFunc callback_func, gpointer user_data, +#endif /* NING_CONNECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/libokcupid.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,433 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "okc_connection.h" +#include "okc_messages.h" +/******************************************************************************/ +/******************************************************************************/ +gchar *okc_convert_unicode(const gchar *input) + gchar unicode_char_str[6]; + next_pos = input_string = g_strdup(input); + while ((next_pos = strstr(next_pos, "\\u"))) + sscanf(next_pos, "\\u%4x", &unicode_char); + /* turn it to a char* */ + unicode_char_len = g_unichar_to_utf8(unicode_char, unicode_char_str); + /* shove it back into the string */ + g_memmove(next_pos, unicode_char_str, unicode_char_len); + /* move all the data after the \u0000 along */ + g_stpcpy(next_pos + unicode_char_len, next_pos + 6); + output_string = g_strcompress(input_string); +/* Like purple_strdup_withhtml, but escapes htmlentities too */ +gchar *okc_strdup_withhtml(const gchar *src) + g_return_val_if_fail(src != NULL, NULL); + /* New length is (length of src) + (number of \n's * 3) + (number of &'s * 5) + + (number of <'s * 4) + (number of >'s *4) + (number of "'s * 6) - + (number of \r's) + 1 */ + for (i = 0; src[i] != '\0'; i++) + if (src[i] == '\n' || src[i] == '<' || src[i] == '>') + else if (src[i] == '&') + else if (src[i] == '"') + else if (src[i] != '\r') + dest = g_malloc(destsize); + /* Copy stuff, ignoring \r's, because they are dumb */ + for (i = 0, j = 0; src[i] != '\0'; i++) { + strcpy(&dest[j], "<BR>"); + } else if (src[i] == '<') { + strcpy(&dest[j], "<"); + } else if (src[i] == '>') { + strcpy(&dest[j], ">"); + } else if (src[i] == '&') { + strcpy(&dest[j], "&"); + } else if (src[i] == '"') { + strcpy(&dest[j], """); + } else if (src[i] != '\r') + dest[destsize-1] = '\0'; +/******************************************************************************/ +/******************************************************************************/ +static const char *okc_list_icon(PurpleAccount *account, PurpleBuddy *buddy) +static GList *okc_statuses(PurpleAccount *account) + PurpleStatusType *status; + purple_debug_info("okcupid", "statuses\n"); + /* OkCupid people are either online, online with no IM, offline */ + status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE); + types = g_list_append(types, status); + status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE); + types = g_list_append(types, status); + purple_debug_info("okcupid", "statuses return\n"); +static gboolean okc_get_messages_failsafe(OkCupidAccount *oca) + if (oca->last_messages_download_time < (time(NULL) - (60*5))) { + /* Messages haven't been downloaded in a while- + * something is probably wrong */ + purple_debug_warning("okcupid", + "executing message check failsafe\n"); + okc_get_new_messages(oca); +static void okc_login_cb(OkCupidAccount *oca, gchar *response, gsize len, + purple_connection_update_progress(oca->pc, _("Authenticating"), 2, 3); + purple_connection_error(oca->pc, "No login response"); + parser = json_parser_new(); + if (!json_parser_load_from_data(parser, response, len, NULL)) + purple_connection_error(oca->pc, "Error parsing login response"); + root = json_parser_get_root(parser); + message = json_node_get_object(root); + status = json_node_get_int(json_object_get_member(message, "status")); + purple_connection_error(oca->pc, "Bad username or password"); + /* ok, we're logged in now! */ + purple_connection_set_state(oca->pc, PURPLE_CONNECTED); + /* This will kick off our long-poll message retrieval loop */ + okc_get_new_messages_now(oca); + oca->perpetual_messages_timer = purple_timeout_add_seconds(15, + (GSourceFunc)okc_get_messages_failsafe, oca); +static void okc_login(PurpleAccount *account) + gchar *postdata, *encoded_username, *encoded_password; + purple_debug_info("okcupid", "login\n"); + /* Create account and initialize state */ + oca = g_new0(OkCupidAccount, 1); + oca->account = account; + oca->pc = purple_account_get_connection(account); + oca->last_messages_download_time = time(NULL) - 60; /* 60 secs is a safe buffer */ + oca->server_seqid = purple_account_get_int(oca->account, "server_seqid", 0); + oca->server_gmt = purple_account_get_int(oca->account, "server_gmt", 0); + oca->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, + oca->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + oca->waiting_conns = g_queue_new(); + account->gc->proto_data = oca; + purple_connection_set_state(oca->pc, PURPLE_CONNECTING); + purple_connection_update_progress(oca->pc, _("Connecting"), 1, 3); + encoded_username = g_strdup(purple_url_encode( + purple_account_get_username(account))); + encoded_password = g_strdup(purple_url_encode( + purple_account_get_password(account))); + postdata = g_strdup_printf("username=%s&password=%s", + encoded_username, encoded_password); + g_free(encoded_username); + g_free(encoded_password); + okc_post_or_get(oca, OKC_METHOD_POST | OKC_METHOD_SSL, "www.okcupid.com", + "/login", postdata, okc_login_cb, NULL, FALSE); +static void okc_close(PurpleConnection *pc) + purple_debug_info("okcupid", "disconnecting account\n"); + okc_post_or_get(oca, OKC_METHOD_POST, NULL, "/logout", + "ajax=1", NULL, NULL, FALSE); + if (oca->new_messages_check_timer) + purple_timeout_remove(oca->new_messages_check_timer); + if (oca->buddy_presence_timer) + purple_timeout_remove(oca->buddy_presence_timer); + if (oca->perpetual_messages_timer) + purple_timeout_remove(oca->perpetual_messages_timer); + purple_debug_info("okcupid", "destroying %d waiting connections\n", + g_queue_get_length(oca->waiting_conns)); + while (!g_queue_is_empty(oca->waiting_conns)) + okc_connection_destroy(g_queue_pop_tail(oca->waiting_conns)); + g_queue_free(oca->waiting_conns); + purple_debug_info("okcupid", "destroying %d incomplete connections\n", + g_slist_length(oca->conns)); + while (oca->conns != NULL) + okc_connection_destroy(oca->conns->data); + while (oca->dns_queries != NULL) { + PurpleDnsQueryData *dns_query = oca->dns_queries->data; + purple_debug_info("okcupid", "canceling dns query for %s\n", + purple_dnsquery_get_host(dns_query)); + oca->dns_queries = g_slist_remove(oca->dns_queries, dns_query); + purple_dnsquery_destroy(dns_query); + g_hash_table_destroy(oca->cookie_table); + g_hash_table_destroy(oca->hostname_ip_cache); + //Store server_seqid and server_gmt so that we dont download all the messages on startup + purple_account_set_int(oca->account, "server_seqid", oca->server_seqid); + purple_account_set_int(oca->account, "server_gmt", oca->server_gmt); +static void okc_buddy_free(PurpleBuddy *buddy) + OkCupidBuddy *obuddy = buddy->proto_data; + buddy->proto_data = NULL; + g_free(obuddy->thumb_url); +/******************************************************************************/ +/******************************************************************************/ +static gboolean plugin_load(PurplePlugin *plugin) +static gboolean plugin_unload(PurplePlugin *plugin) +static void plugin_init(PurplePlugin *plugin) + PurpleAccountOption *option; + PurplePluginInfo *info = plugin->info; + PurplePluginProtocolInfo *prpl_info = info->extra_info; + option = purple_account_option_string_new("Host", "host", "api.okcupid.com"); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + option = purple_account_option_bool_new("Show messages sent from website", "show_sent_messages", FALSE); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); + option = purple_account_option_bool_new("Show people who visit your profile", "show_stalkers", TRUE); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); +static PurplePluginProtocolInfo prpl_info = { + OPT_PROTO_UNIQUE_CHATNAME, + NULL, /* user_splits */ + NULL, /* protocol_options */ + NO_BUDDY_ICONS /* icon_spec */ + /*{"jpg", 0, 0, 50, 50, -1, PURPLE_ICON_SCALE_SEND}*/, /* icon_spec */ + okc_list_icon, /* list_icon */ + NULL, /* list_emblems */ + NULL, /* status_text */ + NULL, /* tooltip_text */ + okc_statuses, /* status_types */ + okc_blist_node_menu, /* blist_node_menu */ + NULL, /* chat_info_defaults */ + okc_send_im, /* send_im */ + NULL, /* send_typing */ + okc_get_info, /* get_info */ + NULL, /* change_passwd */ + okc_add_buddy, /* add_buddy */ + NULL, /* add_buddies */ + okc_remove_buddy, /* remove_buddy */ + NULL, /* remove_buddies */ + okc_block_buddy, /* add_deny */ + NULL, /* set_permit_deny */ + NULL, /* reject chat invite */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_whisper */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + okc_buddy_free, /* buddy_free */ + NULL, /* convo_closed */ + purple_normalize_nocase,/* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ +static PurplePluginInfo info = { + PURPLE_PLUGIN_PROTOCOL, /* type */ + NULL, /* ui_requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + "prpl-bigbrownchunx-okcupid", /* id */ + OKCUPID_PLUGIN_VERSION, /* version */ + N_("OkCupid Protocol Plugin"), /* summary */ + N_("OkCupid Protocol Plugin"), /* description */ + "Eion Robb <eionrobb@gmail.com>", /* author */ + plugin_load, /* load */ + plugin_unload, /* unload */ + &prpl_info, /* extra_info */ +PURPLE_INIT_PLUGIN(okcupid, plugin_init, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/libokcupid.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,103 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#define OKCUPID_PLUGIN_VERSION "2.03" +#define OKC_MAX_CONNECTIONS 16 +#include <json-glib/json-glib.h> +#ifndef G_GNUC_NULL_TERMINATED +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +# define G_GNUC_NULL_TERMINATED +# endif /* __GNUC__ >= 4 */ +#endif /* G_GNUC_NULL_TERMINATED */ +# define dlopen(a,b) LoadLibrary(a) +# define dlsym(a,b) GetProcAddress(a,b) +# define dlclose(a) FreeLibrary(a) +# include <netinet/in.h> +# include <sys/socket.h> +typedef struct _OkCupidAccount OkCupidAccount; +typedef struct _OkCupidBuddy OkCupidBuddy; +typedef void (*OkCupidProxyCallbackFunc)(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data); +struct _OkCupidAccount { + PurpleAccount *account; + GHashTable *hostname_ip_cache; + GSList *conns; /**< A list of all active OkCupidConnections */ + GQueue *waiting_conns; /**< A list of all OkCupidConnections waiting to process */ + GHashTable *cookie_table; + time_t last_messages_download_time; + guint new_messages_check_timer; + guint perpetual_messages_timer; + guint buddy_presence_timer; + guint last_message_count; +gchar *okc_strdup_withhtml(const gchar *src); +gchar *okc_convert_unicode(const gchar *input); +#endif /* LIBOKCUPID_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_blist.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,252 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "okc_connection.h" +#include "okc_messages.h" +void okc_blist_wink_buddy(PurpleBlistNode *node, gpointer data) + if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) + buddy = (PurpleBuddy *) node; + if (!buddy || !buddy->account) + pc = purple_account_get_connection(buddy->account); + if (!pc || !pc->proto_data) + postdata = g_strdup_printf("woo=1&u=%s&ajax=1", purple_url_encode(buddy->name)); + okc_post_or_get(oca, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE); +void okc_got_info(OkCupidAccount *oca, gchar *data, + gsize data_len, gpointer userdata) + gchar *username = userdata; + PurpleNotifyUserInfo *user_info; + if (!data || !data_len) + purple_debug_info("okcupid", "okc_got_info: %s\n", data); + user_info = purple_notify_user_info_new(); + /* Insert link to profile at top */ + value_tmp = g_strdup_printf("<a href=\"http://www.okcupid.com/profile/%s\">%s</a>", + username, _("View web profile")); + purple_notify_user_info_add_pair(user_info, NULL, value_tmp); + purple_notify_user_info_add_section_break(user_info); + parser = json_parser_new(); + if(!json_parser_load_from_data(parser, data, data_len, &error)) + purple_debug_error("okcupid", "got_info error: %s\n", error->message); + purple_notify_userinfo(oca->pc, username, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); + root = json_parser_get_root(parser); + info = json_node_get_object(root); + value_tmp = g_strdup_printf("%" G_GINT64_FORMAT " years", json_node_get_int(json_object_get_member(info, "age"))); + purple_notify_user_info_add_pair(user_info, _("Age"), value_tmp); + purple_notify_user_info_add_pair(user_info, _("Gender"), json_node_get_string(json_object_get_member(info, "sex"))); + purple_notify_user_info_add_pair(user_info, _("Sexual Preference"), json_node_get_string(json_object_get_member(info, "orientation"))); + purple_notify_user_info_add_pair(user_info, _("Relationship Status"), json_node_get_string(json_object_get_member(info, "status"))); + purple_notify_user_info_add_pair(user_info, _("Location"), json_node_get_string(json_object_get_member(info, "location"))); + value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "matchpercentage"))); + purple_notify_user_info_add_pair(user_info, _("Match"), value_tmp); + value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "friendpercentage"))); + purple_notify_user_info_add_pair(user_info, _("Friend"), value_tmp); + value_tmp = g_strdup_printf("%" G_GINT64_FORMAT "%%", json_node_get_int(json_object_get_member(info, "enemypercentage"))); + purple_notify_user_info_add_pair(user_info, _("Enemy"), value_tmp); + const gchar *buddy_icon = json_node_get_string(json_object_get_member(info, "thumbnail")); + PurpleBuddy *buddy = purple_find_buddy(oca->account, username); + OkCupidBuddy *obuddy = buddy->proto_data; + obuddy = g_new0(OkCupidBuddy, 1); + // load the old buddy icon url from the icon 'checksum' + const gchar *buddy_icon_url = purple_buddy_icons_get_checksum_for_user(buddy); + if (buddy_icon_url != NULL) + obuddy->thumb_url = g_strdup(buddy_icon_url); + buddy->proto_data = obuddy; + if (!obuddy->thumb_url || !g_str_equal(obuddy->thumb_url, buddy_icon)) + gchar *host, *path, *path2; + g_free(obuddy->thumb_url); + obuddy->thumb_url = purple_strreplace(buddy_icon, "/60x60/", "/256x256/"); + purple_url_parse(obuddy->thumb_url, &host, NULL, &path, NULL, NULL); + path2 = g_strdup_printf("/%s", path); + path2 = g_strdup(path); + okc_post_or_get(oca, OKC_METHOD_GET, host, path2, NULL, okc_buddy_icon_cb, g_strdup(username), FALSE); + purple_notify_user_info_add_section_break(user_info); + purple_notify_user_info_add_section_header(user_info, _("The Skinny")); + info = json_node_get_object(json_object_get_member(info, "skinny")); + purple_notify_user_info_add_pair(user_info, _("Last Online"), json_node_get_string(json_object_get_member(info, "last_online"))); + purple_notify_user_info_add_pair(user_info, _("Join Date"), json_node_get_string(json_object_get_member(info, "join_date"))); + purple_notify_user_info_add_pair(user_info, _("Ethnicity"), json_node_get_string(json_object_get_member(info, "ethnicities"))); + purple_notify_user_info_add_pair(user_info, _("Height"), json_node_get_string(json_object_get_member(info, "height"))); + purple_notify_user_info_add_pair(user_info, _("Body Type"), json_node_get_string(json_object_get_member(info, "bodytype"))); + purple_notify_user_info_add_pair(user_info, _("Looking For"), json_node_get_string(json_object_get_member(info, "lookingfor"))); + purple_notify_user_info_add_pair(user_info, _("Smokes"), json_node_get_string(json_object_get_member(info, "smoker"))); + purple_notify_user_info_add_pair(user_info, _("Drinks"), json_node_get_string(json_object_get_member(info, "drinker"))); + purple_notify_user_info_add_pair(user_info, _("Drugs"), json_node_get_string(json_object_get_member(info, "drugs"))); + if (json_object_has_member(info, "religion")) + value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "religion")), + json_node_get_string(json_object_get_member(info, "religionserious"))); + purple_notify_user_info_add_pair(user_info, _("Religion"), value_tmp); + value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "sign")), + json_node_get_string(json_object_get_member(info, "sign_status"))); + purple_notify_user_info_add_pair(user_info, _("Star sign"), value_tmp); + value_tmp = g_strdup_printf("%s %s", json_node_get_string(json_object_get_member(info, "education_status")), + json_node_get_string(json_object_get_member(info, "education"))); + purple_notify_user_info_add_pair(user_info, _("Education"), value_tmp); + purple_notify_user_info_add_pair(user_info, _("Job"), json_node_get_string(json_object_get_member(info, "job"))); + purple_notify_user_info_add_pair(user_info, _("Income"), json_node_get_string(json_object_get_member(info, "income"))); + purple_notify_user_info_add_pair(user_info, _("Kids"), json_node_get_string(json_object_get_member(info, "children"))); + value_tmp = g_strdup_printf("%s and %s", json_node_get_string(json_object_get_member(info, "dogs")), + json_node_get_string(json_object_get_member(info, "cats"))); + purple_notify_user_info_add_pair(user_info, _("Pets"), value_tmp); + purple_notify_user_info_add_pair(user_info, _("Languages"), json_node_get_string(json_object_get_member(info, "languagestr"))); + purple_notify_userinfo(oca->pc, username, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); + g_object_unref(parser); +void okc_get_info(PurpleConnection *pc, const gchar *uid) + profile_url = g_strdup_printf("/profile/%s?json=2", purple_url_encode(uid)); + okc_post_or_get(pc->proto_data, OKC_METHOD_GET, NULL, profile_url, NULL, okc_got_info, g_strdup(uid), FALSE); +void okc_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) + postdata = g_strdup_printf("addbuddy=1&u=%s&ajax=1", purple_url_encode(buddy->name)); + okc_post_or_get(pc->proto_data, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE); +void okc_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) + postdata = g_strdup_printf("removebuddy=1&u=%s&ajax=1", purple_url_encode(buddy->name)); + okc_post_or_get(pc->proto_data, OKC_METHOD_POST, NULL, "/profile", postdata, NULL, NULL, FALSE); +void okc_block_buddy(PurpleConnection *pc, const char *name) + block_url = g_strdup_printf("/instantevents?im_block=1&target_screenname=%s", purple_url_encode(name)); + okc_post_or_get(pc->proto_data, OKC_METHOD_GET, NULL, block_url, NULL, NULL, NULL, FALSE); +GList *okc_blist_node_menu(PurpleBlistNode *node) + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) + act = purple_menu_action_new(_("_Wink"), + PURPLE_CALLBACK(okc_blist_wink_buddy), + m = g_list_append(m, act); + } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) + } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_blist.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,33 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +void okc_blist_wink_buddy(PurpleBlistNode *node, gpointer data); +void okc_get_info(PurpleConnection *pc, const gchar *uid); +void okc_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group); +void okc_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group); +void okc_block_buddy(PurpleConnection *, const char *name); +GList *okc_blist_node_menu(PurpleBlistNode *node); +#endif /* OKCUPID_BLIST_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,647 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "okc_connection.h" +#if !GLIB_CHECK_VERSION (2, 22, 0) +#define g_hostname_is_ip_address(hostname) (g_ascii_isdigit(hostname[0]) && g_strstr_len(hostname, 4, ".")) +static void okc_attempt_connection(OkCupidConnection *); +static void okc_next_connection(OkCupidAccount *oca); +static gchar *okc_gunzip(const guchar *gzip_data, ssize_t *len_ptr) + gsize gzip_data_len = *len_ptr; + gulong gzip_len = G_MAXUINT16; + GString *output_string = NULL; + data_buffer = g_new0(gchar, gzip_len); + gzip_err = inflateInit2(&zstr, MAX_WBITS+32); + purple_debug_error("okcupid", "no built-in gzip support in zlib\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_DATA_ERROR) + inflateInit2(&zstr, -MAX_WBITS); + purple_debug_error("okcupid", "Cannot decode gzip header\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + output_string = g_string_new(""); + while (gzip_err == Z_OK) + //append data to buffer + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_STREAM_END) + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + purple_debug_error("okcupid", "gzip inflate error\n"); + gchar *output_data = g_strdup(output_string->str); + *len_ptr = output_string->len; + g_string_free(output_string, TRUE); +void okc_connection_destroy(OkCupidConnection *okconn) + okconn->oca->conns = g_slist_remove(okconn->oca->conns, okconn); + if (okconn->request != NULL) + g_string_free(okconn->request, TRUE); + g_free(okconn->rx_buf); + if (okconn->connect_data != NULL) + purple_proxy_connect_cancel(okconn->connect_data); + if (okconn->ssl_conn != NULL) + purple_ssl_close(okconn->ssl_conn); + if (okconn->input_watcher > 0) + purple_input_remove(okconn->input_watcher); + g_free(okconn->hostname); +static void okc_update_cookies(OkCupidAccount *oca, const gchar *headers) + const gchar *cookie_start; + const gchar *cookie_end; + g_return_if_fail(headers != NULL); + header_len = strlen(headers); + /* look for the next "Set-Cookie: " */ + /* grab the data up until ';' */ + cookie_start = headers; + while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && + (headers-cookie_start) < header_len) + cookie_end = strchr(cookie_start, '='); + cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end + 1; + cookie_end = strchr(cookie_start, ';'); + cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end; + purple_debug_info("okcupid", "got cookie %s=%s\n", + cookie_name, cookie_value); + g_hash_table_replace(oca->cookie_table, cookie_name, +static void okc_connection_process_data(OkCupidConnection *okconn) + tmp = g_strstr_len(okconn->rx_buf, len, "\r\n\r\n"); + /* This is a corner case that occurs when the connection is + * prematurely closed either on the client or the server. + * This can either be no data at all or a partial set of + * headers. We pass along the data to be good, but don't + * do any fancy massaging. In all likelihood the result will + * be tossed by the connection callback func anyways + tmp = g_strndup(okconn->rx_buf, len); + len -= g_strstr_len(okconn->rx_buf, len, "\r\n\r\n") - + tmp = g_memdup(tmp, len + 1); + okconn->rx_buf[okconn->rx_len - len] = '\0'; + //purple_debug_misc("okcupid", "response headers\n%s\n", + okc_update_cookies(okconn->oca, okconn->rx_buf); + if (strstr(okconn->rx_buf, "Content-Encoding: gzip")) + /* we've received compressed gzip data, decompress */ + gunzipped = okc_gunzip((const guchar *)tmp, &len); + g_free(okconn->rx_buf); + if (okconn->callback != NULL) + okconn->callback(okconn->oca, tmp, len, okconn->user_data); +static void okc_fatal_connection_cb(OkCupidConnection *okconn) + PurpleConnection *pc = okconn->oca->pc; + purple_debug_error("okcupid", "fatal connection error\n"); + okc_connection_destroy(okconn); + /* We died. Do not pass Go. Do not collect $200 */ + /* In all seriousness, don't attempt to call the normal callback here. + * That may lead to the wrong error message being displayed */ + purple_connection_error_reason(pc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); +static void okc_post_or_get_readdata_cb(gpointer data, gint source, + PurpleInputCondition cond) + OkCupidConnection *okconn; + if (okconn->method & OKC_METHOD_SSL) { + len = purple_ssl_read(okconn->ssl_conn, + len = recv(okconn->fd, buf, sizeof(buf) - 1, 0); + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + if (okconn->method & OKC_METHOD_SSL && okconn->rx_len > 0) { + * This is a slightly hacky workaround for a bug in either + * GNU TLS or in the SSL implementation on Facebook's web + * servers. The sequence of events is: + * 1. We attempt to read the first time and successfully read + * the server's response. + * 2. We attempt to read a second time and libpurple's call + * to gnutls_record_recv() returns the error + * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or + * "A TLS packet with unexpected length was received." + * Normally the server would have closed the connection + * cleanly and this second read() request would have returned + * 0. Or maybe it's normal for SSL connections to be severed + * in this manner? In any case, this differs from the behavior + * of the standard recv() system call. + purple_debug_warning("okcupid", + "ssl error, but data received. attempting to continue\n"); + /* TODO: Is this a regular occurrence? If so then maybe resend the request? */ + okc_fatal_connection_cb(okconn); + okconn->rx_buf = g_realloc(okconn->rx_buf, + okconn->rx_len + len + 1); + memcpy(okconn->rx_buf + okconn->rx_len, buf, len + 1); + /* Wait for more data before processing */ + /* The server closed the connection, let's parse the data */ + okc_connection_process_data(okconn); + okc_connection_destroy(okconn); + okc_next_connection(oca); +static void okc_post_or_get_ssl_readdata_cb (gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + okc_post_or_get_readdata_cb(data, -1, cond); +static void okc_post_or_get_connect_cb(gpointer data, gint source, + const gchar *error_message) + OkCupidConnection *okconn; + okconn->connect_data = NULL; + purple_debug_error("okcupid", "post_or_get_connect_cb %s\n", + okc_fatal_connection_cb(okconn); + purple_debug_info("okcupid", "post_or_get_connect_cb\n"); + /* TODO: Check the return value of write() */ + len = write(okconn->fd, okconn->request->str, + okconn->input_watcher = purple_input_add(okconn->fd, + okc_post_or_get_readdata_cb, okconn); +static void okc_post_or_get_ssl_connect_cb(gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + OkCupidConnection *okconn; + purple_debug_info("okcupid", "post_or_get_ssl_connect_cb\n"); + /* TODO: Check the return value of write() */ + len = purple_ssl_write(okconn->ssl_conn, + okconn->request->str, okconn->request->len); + purple_ssl_input_add(okconn->ssl_conn, + okc_post_or_get_ssl_readdata_cb, okconn); +static void okc_host_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) + GSList *host_lookup_list; + struct sockaddr_in *addr; + PurpleDnsQueryData *query; + purple_debug_info("okcupid", "updating cache of dns addresses\n"); + /* Extract variables */ + host_lookup_list = data; + oca = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + hostname = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + query = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + /* The callback has executed, so we no longer need to keep track of + * the original query. This always needs to run when the cb is + oca->dns_queries = g_slist_remove(oca->dns_queries, query); + /* Any problems, capt'n? */ + if (error_message != NULL) + purple_debug_warning("okcupid", + "Error doing host lookup: %s\n", error_message); + purple_debug_warning("okcupid", + "Could not resolve host name\n"); + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Copy the address then free it... */ + ip_address = g_strdup(inet_ntoa(addr->sin_addr)); + hosts = g_slist_delete_link(hosts, hosts); + * DNS lookups can return a list of IP addresses, but we only cache + * the first one. So free the rest. + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address... */ + hosts = g_slist_delete_link(hosts, hosts); + purple_debug_info("okcupid", "Host %s has IP %s\n", + g_hash_table_insert(oca->hostname_ip_cache, hostname, ip_address); +static void okc_cookie_foreach_cb(gchar *cookie_name, + gchar *cookie_value, GString *str) + /* TODO: Need to escape name and value? */ + g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); +static gchar *okc_cookies_to_string(OkCupidAccount *oca) + str = g_string_new(NULL); + g_hash_table_foreach(oca->cookie_table, + (GHFunc)okc_cookie_foreach_cb, str); + return g_string_free(str, FALSE); +static void okc_ssl_connection_error(PurpleSslConnection *ssl, + PurpleSslErrorType errortype, gpointer data) + OkCupidConnection *okconn = data; + PurpleConnection *pc = okconn->oca->pc; + okconn->ssl_conn = NULL; + okc_connection_destroy(okconn); + purple_connection_ssl_error(pc, errortype); +void okc_post_or_get(OkCupidAccount *oca, OkCupidMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + OkCupidProxyCallbackFunc callback_func, gpointer user_data, + OkCupidConnection *okconn; + gboolean is_proxy = FALSE; + const gchar* const *languages; + PurpleProxyInfo *proxy_info = NULL; + gchar *proxy_auth_base64; + /* TODO: Fix keepalive and use it as much as possible */ + if (method & OKC_METHOD_SSL) + host = "www.okcupid.com"; + if (host == NULL && oca && oca->account) + host = purple_account_get_string(oca->account, "host", "api.okcupid.com"); + host = "api.okcupid.com"; + if (oca && oca->account && !(method & OKC_METHOD_SSL)) + proxy_info = purple_proxy_get_setup(oca->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + real_url = g_strdup_printf("http://%s%s", host, url); + real_url = g_strdup(url); + cookies = okc_cookies_to_string(oca); + if (method & OKC_METHOD_POST && !postdata) + /* Build the request */ + request = g_string_new(NULL); + g_string_append_printf(request, "%s %s HTTP/1.0\r\n", + (method & OKC_METHOD_POST) ? "POST" : "GET", + g_string_append_printf(request, "Host: %s\r\n", host); + g_string_append_printf(request, "Connection: %s\r\n", + (keepalive ? "Keep-Alive" : "close")); + g_string_append_printf(request, "User-Agent: %s (libpurple %s)\r\n", purple_core_get_ui(), purple_core_get_version()); + if (method & OKC_METHOD_POST) { + g_string_append_printf(request, + "Content-Type: application/x-www-form-urlencoded\r\n"); + g_string_append_printf(request, + "Content-length: %zu\r\n", strlen(postdata)); + g_string_append_printf(request, "Accept: */*\r\n"); + g_string_append_printf(request, "Cookie: %s\r\n", cookies); + g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); + g_string_append_printf(request, "X-OkCupid-Api-Version: 1\r\n"); + if (purple_proxy_info_get_username(proxy_info) && + purple_proxy_info_get_password(proxy_info)) + proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); + proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); + g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); + g_free(proxy_auth_base64); + /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ + languages = g_get_language_names(); + language_names = g_strjoinv(", ", (gchar **)languages); + purple_util_chrreplace(language_names, '_', '-'); + g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); + g_free(language_names); + purple_debug_misc("okcupid", "requesting url %s\n", url); + g_string_append_printf(request, "\r\n"); + if (method & OKC_METHOD_POST) + g_string_append_printf(request, "%s", postdata); + /* If it needs to go over a SSL connection, we probably shouldn't print + * it in the debug log. Without this condition a user's password is + * printed in the debug log */ + if (method == OKC_METHOD_POST) + purple_debug_misc("okcupid", "sending request data:\n%s\n", + * Do a separate DNS lookup for the given host name and cache it + * TODO: It would be better if we did this before we call + * purple_proxy_connect(), so we could re-use the result. + * Or even better: Use persistent HTTP connections for servers + * that we access continually. + * TODO: This cache of the hostname<-->IP address does not respect + * the TTL returned by the DNS server. We should expire things + * from the cache after some amount of time. + if (!is_proxy && !g_hostname_is_ip_address(host)) + /* Don't do this for proxy connections, since proxies do the DNS lookup */ + host_ip = g_hash_table_lookup(oca->hostname_ip_cache, host); + purple_debug_info("okcupid", + "swapping original host %s with cached value of %s\n", + } else if (oca->account && !oca->account->disconnecting) { + GSList *host_lookup_list = NULL; + PurpleDnsQueryData *query; + host_lookup_list = g_slist_prepend( + host_lookup_list, g_strdup(host)); + host_lookup_list = g_slist_prepend( + host_lookup_list, oca); + query = purple_dnsquery_a(host, 80, + okc_host_lookup_cb, host_lookup_list); + oca->dns_queries = g_slist_prepend(oca->dns_queries, query); + host_lookup_list = g_slist_append(host_lookup_list, query); + okconn = g_new0(OkCupidConnection, 1); + okconn->method = method; + okconn->hostname = g_strdup(host); + okconn->request = request; + okconn->callback = callback_func; + okconn->user_data = user_data; + okconn->connection_keepalive = keepalive; + okconn->request_time = time(NULL); + g_queue_push_head(oca->waiting_conns, okconn); + okc_next_connection(oca); +static void okc_next_connection(OkCupidAccount *oca) + OkCupidConnection *okconn; + g_return_if_fail(oca != NULL); + if (!g_queue_is_empty(oca->waiting_conns)) + if(g_slist_length(oca->conns) < OKC_MAX_CONNECTIONS) + okconn = g_queue_pop_tail(oca->waiting_conns); + okc_attempt_connection(okconn); +static void okc_attempt_connection(OkCupidConnection *okconn) + OkCupidAccount *oca = okconn->oca; + oca->conns = g_slist_prepend(oca->conns, okconn); + if (okconn->method & OKC_METHOD_SSL) { + okconn->ssl_conn = purple_ssl_connect(oca->account, okconn->hostname, + 443, okc_post_or_get_ssl_connect_cb, + okc_ssl_connection_error, okconn); + okconn->connect_data = purple_proxy_connect(NULL, oca->account, + okconn->hostname, 80, okc_post_or_get_connect_cb, okconn); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,62 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#ifndef OKCUPID_CONNECTION_H +#define OKCUPID_CONNECTION_H + OKC_METHOD_GET = 0x0001, + OKC_METHOD_POST = 0x0002, + OKC_METHOD_SSL = 0x0004 +typedef struct _OkCupidConnection OkCupidConnection; +struct _OkCupidConnection { + OkCupidProxyCallbackFunc callback; + PurpleProxyConnectData *connect_data; + PurpleSslConnection *ssl_conn; + gboolean connection_keepalive; +void okc_connection_destroy(OkCupidConnection *okcconn); +void okc_post_or_get(OkCupidAccount *oca, OkCupidMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + OkCupidProxyCallbackFunc callback_func, gpointer user_data, +#endif /* OKCUPID_CONNECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_messages.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,537 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "okc_messages.h" +#include "okc_connection.h" +typedef struct _OkCupidOutgoingMessage OkCupidOutgoingMessage; +struct _OkCupidOutgoingMessage { +gboolean okc_send_im_fom(OkCupidOutgoingMessage *msg); +void okc_check_inbox_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data); +void okc_buddy_icon_cb(OkCupidAccount *oca, gchar *data, gsize data_len, + gpointer buddy_icon_data; + purple_debug_info("okcupid", + "buddy icon for buddy %s %" G_GSIZE_FORMAT "\n", + buddy = purple_find_buddy(oca->account, buddyname); + if (buddy == NULL || buddy->proto_data == NULL) + obuddy = buddy->proto_data; + buddy_icon_data = g_memdup(data, data_len); + purple_buddy_icons_set_for_user(oca->account, buddy->name, + buddy_icon_data, data_len, obuddy->thumb_url); +void got_new_messages(OkCupidAccount *oca, gchar *data, + gsize data_len, gpointer userdata) + PurpleConnection *pc = userdata; + /* NULL data will crash on Windows */ + purple_debug_misc("okcupid", "got new messages: %s\n", data); + /* Process incomming messages here */ + gchar *start_of_json = strchr(data, '{'); + gchar *end_of_json = strrchr(data, '}'); + if (!start_of_json || !end_of_json || start_of_json >= end_of_json) + okc_get_new_messages(oca); + gchar *json_string = g_strndup(start_of_json, end_of_json-start_of_json+1); + parser = json_parser_new(); + if(!json_parser_load_from_data(parser, json_string, -1, NULL)) + okc_get_new_messages(oca); + root = json_parser_get_root(parser); + objnode = json_node_get_object(root); + JsonArray *events = NULL; + JsonArray *people = NULL; + int unread_message_count = 0; + if(json_object_has_member(objnode, "people")) + people = json_node_get_array(json_object_get_member(objnode, "people")); + if(json_object_has_member(objnode, "events")) + events = json_node_get_array(json_object_get_member(objnode, "events")); + if(json_object_has_member(objnode, "num_unread")) + unread_message_count = json_node_get_int(json_object_get_member(objnode, "num_unread")); + // Look through the buddy list for people to add first + GList *people_list = json_array_get_elements(people); + for (current = people_list; current; current = g_list_next(current)) + JsonNode *currentNode = current->data; + JsonObject *person = json_node_get_object(currentNode); + if (!json_node_get_int(json_object_get_member(person, "is_buddy")) && + !purple_account_get_bool(oca->account, "show_stalkers", TRUE)) + const gchar *buddy_name = json_node_get_string(json_object_get_member(person, "screenname")); + const gchar *buddy_icon = json_node_get_string(json_object_get_member(person, "thumbnail")); + gint is_online = json_node_get_int(json_object_get_member(person, "im_ok")); + if (!json_object_has_member(person, "im_ok")) + PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name); + //Not everyone we talk to will be on our buddylist + pbuddy = purple_buddy_new(oca->account, buddy_name, NULL); + purple_blist_add_buddy(pbuddy, NULL, NULL, NULL); + if (!json_node_get_int(json_object_get_member(person, "is_buddy"))) + purple_blist_node_set_flags(&(pbuddy->node), PURPLE_BLIST_NODE_FLAG_NO_SAVE); + purple_blist_node_set_flags(&(pbuddy->node), 0); + if (is_online && !PURPLE_BUDDY_IS_ONLINE(pbuddy)) + purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL); + } else if (!is_online && PURPLE_BUDDY_IS_ONLINE(pbuddy)) + purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE), NULL); + OkCupidBuddy *obuddy = pbuddy->proto_data; + const gchar *buddy_icon_url; + obuddy = g_new0(OkCupidBuddy, 1); + obuddy->buddy = pbuddy; + // load the old buddy icon url from the icon 'checksum' + buddy_icon_url = purple_buddy_icons_get_checksum_for_user(pbuddy); + if (buddy_icon_url != NULL) + obuddy->thumb_url = g_strdup(buddy_icon_url); + pbuddy->proto_data = obuddy; + if (!obuddy->thumb_url || !g_str_equal(obuddy->thumb_url, buddy_icon)) + gchar *host, *path, *path2; + g_free(obuddy->thumb_url); + obuddy->thumb_url = purple_strreplace(buddy_icon, "/60x60/", "/256x256/"); + purple_url_parse(obuddy->thumb_url, &host, NULL, &path, NULL, NULL); + path2 = g_strdup_printf("/%s", path); + path2 = g_strdup(path); + okc_post_or_get(oca, OKC_METHOD_GET, host, path2, NULL, okc_buddy_icon_cb, g_strdup(buddy_name), FALSE); + g_list_free(people_list); + //loop through events looking for messages + GList *event_list = json_array_get_elements(events); + for (current = event_list; current; current = g_list_next(current)) + JsonNode *currentNode = current->data; + JsonObject *event = json_node_get_object(currentNode); + const gchar *event_type; + event_type = json_node_get_string(json_object_get_member(event, "type")); + if (g_str_equal(event_type, "im")) + const gchar *message = json_node_get_string(json_object_get_member(event, "contents")); + gchar *message_html = okc_strdup_withhtml(message); + const gchar *who = NULL; + PurpleMessageFlags flags; + if (json_object_has_member(event, "to")) + who = json_node_get_string(json_object_get_member(event, "to")); + flags = PURPLE_MESSAGE_SEND; + } else if (json_object_has_member(event, "from")) + who = json_node_get_string(json_object_get_member(event, "from")); + flags = PURPLE_MESSAGE_RECV; + if (who && (flags != PURPLE_MESSAGE_SEND || purple_account_get_bool(oca->account, "show_sent_messages", FALSE))) + serv_got_im (pc, who, message_html, flags, time(NULL)); + } else if (g_str_equal(event_type, "orbit_user_signoff")) + const gchar *buddy_name = json_node_get_string(json_object_get_member(event, "from")); + PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name); + if (pbuddy && PURPLE_BUDDY_IS_ONLINE(pbuddy)) + purple_prpl_got_user_status(oca->account, buddy_name, purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE), NULL); + } else if (g_str_equal(event_type, "stalk") && purple_account_get_bool(oca->account, "show_stalkers", TRUE)) + //someone looked at the profile page (ie 'stalked' the user) + const gchar *buddy_name = json_node_get_string(json_object_get_member(event, "from")); + PurpleBuddy *pbuddy = purple_find_buddy(oca->account, buddy_name); + gchar *stalk_message = g_strdup_printf("%s just viewed your profile", buddy_name); + serv_got_im(oca->pc, buddy_name, stalk_message, PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_list_free(event_list); + if (unread_message_count != oca->last_message_count) + oca->last_message_count = unread_message_count; + if (unread_message_count > 0) + okc_post_or_get(oca, OKC_METHOD_GET, NULL, "/mailbox?folderid=1&low=1", NULL, okc_check_inbox_cb, NULL, FALSE); + if (json_object_has_member(objnode, "server_seqid")) + oca->server_seqid = json_node_get_int(json_object_get_member(objnode, "server_seqid")); + if (json_object_has_member(objnode, "server_gmt")) + oca->server_gmt = json_node_get_int(json_object_get_member(objnode, "server_gmt")); + g_object_unref(parser); + /* Continue looping, waiting for more messages */ + okc_get_new_messages(oca); +okc_get_stalkers(OkCupidAccount *oca) + buddies = purple_find_buddies(oca->account, NULL); + stalkers = g_string_new(NULL); + for(l = buddies; l; l = l->next) + if (buddy->node.flags & PURPLE_BLIST_NODE_FLAG_NO_SAVE) + // A temp buddy? They're a stalker! + g_string_append_printf(stalkers, "%s,", buddy->name); + return g_string_free(stalkers, FALSE); +void okc_get_new_messages_now(OkCupidAccount *oca) + purple_debug_info("okcupid", "getting new messages now\n"); + stalkers = okc_get_stalkers(oca); + fetch_url = g_strdup_printf("/instantevents?rand=0.%u&server_seqid=%u&server_gmt=%u&" + "load_thumbnails=1&buddylist=1&" + "show_offline=1&num_unread=1&im_status=1&" + g_random_int(), oca->server_seqid, oca->server_gmt, + okc_post_or_get(oca, OKC_METHOD_GET, NULL, fetch_url, NULL, got_new_messages, oca->pc, FALSE); +gboolean okc_get_new_messages(OkCupidAccount *oca) + oca->new_messages_check_timer = 0; + if (oca->last_messages_download_time > now - 3) { + * Wait a bit before fetching more messages, to make sure we + * never hammer their servers. + * TODO: This could be smarter. Like, allow 3 requests per + * 10 seconds or something. + oca->new_messages_check_timer = purple_timeout_add_seconds( + 3 - (now - oca->last_messages_download_time), + (GSourceFunc)okc_get_new_messages, oca); + purple_debug_info("okcupid", "getting new messages\n"); + stalkers = okc_get_stalkers(oca); + fetch_url = g_strdup_printf("/instantevents?rand=0.%u&server_seqid=%u&server_gmt=%u&" + "load_thumbnails=1&do_event_poll=1&buddylist=1&" + "show_offline=1&num_unread=1&im_status=1&" + "do_post_read=1&usernames=%s", + g_random_int(), oca->server_seqid, oca->server_gmt, + okc_post_or_get(oca, OKC_METHOD_GET, NULL, fetch_url, NULL, got_new_messages, oca->pc, TRUE); + oca->last_messages_download_time = now; +void okc_msg_destroy(OkCupidOutgoingMessage *msg) +void okc_send_im_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data) + OkCupidOutgoingMessage *msg = user_data; + if (data == NULL || data_len == 0) + //No response, resend message + purple_debug_misc("okcupid", "sent im response: %s\n", data); + "reason" : "recip_not_online" + parser = json_parser_new(); + if(!json_parser_load_from_data(parser, data, data_len, NULL)) + root = json_parser_get_root(parser); + response = json_node_get_object(root); + gint message_sent = json_node_get_int(json_object_get_member(response, "status")); + if (message_sent < 100) + //Save the message we sent + purple_debug_info("okcupid", "putting message into hashtable: '%s'\n", msg->message); + g_object_unref(parser); + const gchar *reason = json_node_get_string(json_object_get_member(response, "status_str")); + if (g_str_equal(reason, "recip_not_online")) + serv_got_im(oca->pc, msg->who, _("Recipient not online"), PURPLE_MESSAGE_ERROR, time(NULL)); + } else if (g_str_equal(reason, "im_self")) + serv_got_im(oca->pc, msg->who, _("You cannot send an IM to yourself"), PURPLE_MESSAGE_ERROR, time(NULL)); + } else if (g_str_equal(reason, "im_not_ok")) + serv_got_im(oca->pc, msg->who, _("Recipient is 'missing'"), PURPLE_MESSAGE_ERROR, time(NULL)); + } else if (g_str_equal(reason, "recip_im_off")) + serv_got_im(oca->pc, msg->who, _("Recipient turned IM off"), PURPLE_MESSAGE_ERROR, time(NULL)); + g_object_unref(parser); +gboolean okc_send_im_fom(OkCupidOutgoingMessage *msg) + gchar *encoded_message; + gchar *encoded_recipient; + encoded_message = g_strdup(purple_url_encode(msg->message)); + encoded_recipient = g_strdup(purple_url_encode(msg->who)); + postdata = g_strdup_printf("send=1&attempt=%d&rid=%d&recipient=%s&topic=false&body=%s", + g_free(encoded_message); + g_free(encoded_recipient); + okc_post_or_get(msg->oca, OKC_METHOD_POST, NULL, "/instantevents", postdata, okc_send_im_cb, msg, FALSE); +int okc_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, PurpleMessageFlags flags) + OkCupidOutgoingMessage *msg; + msg = g_new0(OkCupidOutgoingMessage, 1); + msg->oca = pc->proto_data; + /* convert html to plaintext, removing trailing spaces */ + msg->message = purple_markup_strip_html(message); + msg->rid = g_random_int_range(0, 2000000000); /* just fits inside a 32bit int */ + msg->who = g_strdup(who); + msg->time = time(NULL); + if (purple_account_get_bool(pc->account, "show_sent_messages", FALSE)) + return strlen(message); +void okc_check_inbox_cb(OkCupidAccount *oca, gchar *data, gsize data_len, gpointer user_data) + purple_debug_misc("okcupid", "check_inbox_cb\n%s", (data?data:"(null)")); + parser = json_parser_new(); + if(!json_parser_load_from_data(parser, data, data_len, NULL)) + purple_debug_warning("okcupid", "Could not parse mailbox data\n"); + root = json_parser_get_root(parser); + mailbox = json_node_get_object(root); + if (json_object_has_member(mailbox, "messages")) + messages = json_node_get_array(json_object_get_member(mailbox, "messages")); + GList *message_list = json_array_get_elements(messages); + for (current = message_list; current; current = g_list_next(current)) + JsonNode *currentNode = current->data; + JsonObject *message = json_node_get_object(currentNode); + gboolean is_new = (gboolean) json_node_get_int(json_object_get_member(message, "is_new")); + const gchar *subject = json_node_get_string(json_object_get_member(message, "subject")); + const gchar *from = json_node_get_string(json_object_get_member(message, "person")); + const gchar *to = oca->account->username; + const gchar *thread_id = json_node_get_string(json_object_get_member(message, "thread_id")); + gchar *url = g_strdup_printf("http://www.okcupid.com/messages?readmsg=true&threadid=%s&folder=1", thread_id); + purple_notify_email(oca->pc, subject, from, to, url, NULL, NULL); + g_list_free(message_list); + g_object_unref(parser); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/okcupid/okc_messages.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,33 @@
+ * libokcupid is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#ifndef OKCUPID_MESSAGES_H +#define OKCUPID_MESSAGES_H +int okc_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, + PurpleMessageFlags flags); +gboolean okc_get_new_messages(OkCupidAccount *oca); +void okc_get_new_messages_now(OkCupidAccount *oca); +void okc_buddy_icon_cb(OkCupidAccount *oca, gchar *data, gsize data_len, +#endif /* OKCUPID_MESSAGES_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/libomegle.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,428 @@
+ * libfacebook is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "om_connection.h" +#include <json-glib/json-glib.h> +static void om_got_events(OmegleAccount *oma, gchar *response, gsize len, +/******************************************************************************/ +/******************************************************************************/ +static const char *om_list_icon(PurpleAccount *account, PurpleBuddy *buddy) +static GList *om_statuses(PurpleAccount *account) + PurpleStatusType *status; + status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, NULL, FALSE, FALSE, FALSE); + types = g_list_append(types, status); +static void om_login(PurpleAccount *account) + //make sure there's an Omegle buddy on the buddy list + bud = purple_find_buddy(account, "omegle"); + bud = purple_buddy_new(account, "omegle", "Omegle"); + purple_blist_add_buddy(bud, NULL, NULL, NULL); + purple_prpl_got_user_status(account, "omegle", purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL); + oma = g_new0(OmegleAccount, 1); + oma->account = account; + oma->pc = purple_account_get_connection(account); + oma->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, + oma->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, + account->gc->proto_data = oma; + //No such thing as a login + purple_connection_set_state(purple_account_get_connection(account), PURPLE_CONNECTED); +static void om_close(PurpleConnection *pc) + g_return_if_fail(pc != NULL); + g_return_if_fail(pc->proto_data != NULL); + ims = purple_get_ims(); + //TODO: Loop through all im's and disconnect them all + while (oma->conns != NULL) + om_connection_destroy(oma->conns->data); + while (oma->dns_queries != NULL) { + PurpleDnsQueryData *dns_query = oma->dns_queries->data; + oma->dns_queries = g_slist_remove(oma->dns_queries, dns_query); + purple_dnsquery_destroy(dns_query); + g_hash_table_destroy(oma->cookie_table); + g_hash_table_destroy(oma->hostname_ip_cache); +static void om_convo_closed(PurpleConnection *pc, const char *who) + postdata = g_strdup_printf("id=%s", purple_url_encode(who)); + om_post_or_get(oma, OM_METHOD_POST, NULL, "/disconnect", + postdata, NULL, NULL, FALSE); +static void om_fetch_events(OmegleAccount *oma, gchar *who) + postdata = g_strdup_printf("id=%s", purple_url_encode(who)); + om_post_or_get(oma, OM_METHOD_POST, NULL, "/events", + postdata, om_got_events, who, FALSE); +static void om_got_events(OmegleAccount *oma, gchar *response, gsize len, + //[["waiting"], ["connected"]] + const gchar *event_type; + JsonNode *rootnode, *currentnode; + JsonArray *array, *current; + purple_debug_info("omegle", "got events: %s\n", response?response:"(null)"); + if (!response || g_str_equal(response, "null")) + parser = json_parser_new(); + json_parser_load_from_data(parser, response, len, NULL); + rootnode = json_parser_get_root(parser); + g_object_unref(parser); + array = json_node_get_array(rootnode); + for(i=0; i<json_array_get_length(array); i++) + currentnode = json_array_get_element(array, i); + current = json_node_get_array(currentnode); + event_type = json_node_get_string(json_array_get_element(current, 0)); + } else if (g_str_equal(event_type, "waiting")) { + serv_got_im(oma->pc, who, "Looking for someone you can chat with. Hang on.", PURPLE_MESSAGE_SYSTEM, time(NULL)); + } else if (g_str_equal(event_type, "connected")) { + serv_got_im(oma->pc, who, "You're now chatting with a random stranger. Say hi!", PURPLE_MESSAGE_SYSTEM, time(NULL)); + } else if (g_str_equal(event_type, "gotMessage")) { + //[["gotMessage","message goes here"]] + message = json_node_get_string(json_array_get_element(current, 1)); + serv_got_im(oma->pc, who, message, PURPLE_MESSAGE_RECV, time(NULL)); + } else if (g_str_equal(event_type, "typing")) { + serv_got_typing(oma->pc, who, 10, PURPLE_TYPING); + } else if (g_str_equal(event_type, "stoppedTyping")) { + serv_got_typing(oma->pc, who, 10, PURPLE_TYPED); + } else if (g_str_equal(event_type, "strangerDisconnected")) { + serv_got_im(oma->pc, who, "Your conversational partner has disconnected", PURPLE_MESSAGE_SYSTEM, time(NULL)); + om_fetch_events(oma, g_strdup(who)); + g_object_unref(parser); +static void om_start_im_cb(OmegleAccount *oma, gchar *response, gsize len, + //This should come back with an ID that we pass around + id = g_strdup(response); + purple_str_strip_char(id, '"'); + om_fetch_events(oma, g_strdup(id)); +static void om_start_im(PurpleBlistNode *node, gpointer data) + if(!PURPLE_BLIST_NODE_IS_BUDDY(node)) + buddy = (PurpleBuddy *) node; + pc = purple_account_get_connection(buddy->account); + om_post_or_get(oma, OM_METHOD_POST, NULL, "/start", + NULL, om_start_im_cb, NULL, FALSE); +static GList *om_node_menu(PurpleBlistNode *node) + if(PURPLE_BLIST_NODE_IS_BUDDY(node)) + buddy = (PurpleBuddy *)node; + act = purple_menu_action_new(_("_Start random IM"), + PURPLE_CALLBACK(om_start_im), + m = g_list_append(m, act); +static unsigned int om_send_typing(PurpleConnection *pc, const gchar *name, + PurpleTypingState state) + OmegleAccount *oma = pc->proto_data; + g_return_val_if_fail(oma != NULL, 0); + if (state == PURPLE_TYPING) + } else if (state == PURPLE_TYPED) + url = "/stoppedtyping"; + postdata = g_strdup_printf("id=%s", purple_url_encode(name)); + om_post_or_get(oma, OM_METHOD_POST, NULL, url, postdata, NULL, NULL, FALSE); +static int om_send_im(PurpleConnection *pc, const gchar *who, const gchar *message, PurpleMessageFlags flags) + gchar *encoded_message; + encoded_name = g_strdup(purple_url_encode(who)); + encoded_message = g_strdup(purple_url_encode(message)); + postdata = g_strdup_printf("id=%s&msg=%s", encoded_name, encoded_message); + om_post_or_get(oma, OM_METHOD_POST, NULL, "/send", postdata, NULL, NULL, FALSE); + g_free(encoded_message); + return strlen(message); +/******************************************************************************/ +/******************************************************************************/ +static gboolean plugin_load(PurplePlugin *plugin) + PurpleAccountOption *option; + PurplePluginInfo *info = plugin->info; + PurplePluginProtocolInfo *prpl_info = info->extra_info; + option = purple_account_option_string_new("Server", "host", "bajor.omegle.com"); + prpl_info->protocol_options = g_list_append( + prpl_info->protocol_options, option); +static gboolean plugin_unload(PurplePlugin *plugin) +static void plugin_init(PurplePlugin *plugin) +static PurplePluginProtocolInfo prpl_info = { + NULL, /* user_splits */ + NULL, /* protocol_options */ + NO_BUDDY_ICONS, /* icon_spec */ + om_list_icon, /* list_icon */ + NULL, /* list_emblems */ + NULL, /* status_text */ + NULL, /* tooltip_text */ + om_statuses, /* status_types */ + om_node_menu, /* blist_node_menu */ + NULL, /* chat_info_defaults */ + om_send_im, /* send_im */ + om_send_typing, /* send_typing */ + NULL, /* change_passwd */ + NULL, /* add_buddies */ + NULL, /* remove_buddy */ + NULL, /* remove_buddies */ + NULL, /* set_permit_deny */ + NULL, /* reject chat invite */ + NULL, /* get_chat_name */ + NULL, /* chat_invite */ + NULL, /* chat_whisper */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + NULL, /* group_buddy */ + NULL, /* rename_group */ + om_convo_closed, /* convo_closed */ + purple_normalize_nocase,/* normalize */ + NULL, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + NULL, /* can_receive_file */ + NULL, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* attention_types */ +#if PURPLE_MAJOR_VERSION >= 2 && PURPLE_MINOR_VERSION >= 5 + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + (gpointer) sizeof(PurplePluginProtocolInfo) +static PurplePluginInfo info = { + PURPLE_PLUGIN_PROTOCOL, /* type */ + NULL, /* ui_requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + OMEGLE_PLUGIN_ID, /* id */ + OMEGLE_PLUGIN_VERSION, /* version */ + N_("Omegle Protocol Plugin"), /* summary */ + N_("Omegle Protocol Plugin"), /* description */ + "Eion Robb <eionrobb@gmail.com>", /* author */ + "http://pidgin-omegle.googlecode.com/", /* homepage */ + plugin_load, /* load */ + plugin_unload, /* unload */ + &prpl_info, /* extra_info */ +PURPLE_INIT_PLUGIN(facebook, plugin_init, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/libomegle.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,90 @@
+ * libomegle is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#define OMEGLE_PLUGIN_VERSION "0" +#define OMEGLE_PLUGIN_ID "prpl-bigbrownchunx-omegle" +#ifndef G_GNUC_NULL_TERMINATED +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +# define G_GNUC_NULL_TERMINATED +# endif /* __GNUC__ >= 4 */ +#endif /* G_GNUC_NULL_TERMINATED */ +# define dlopen(a,b) LoadLibrary(a) +# define dlsym(a,b) GetProcAddress(a,b) +# define dlclose(a) FreeLibrary(a) +# include <netinet/in.h> +# include <sys/socket.h> +#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 12 +# define atoll(a) g_ascii_strtoll(a, NULL, 0) +#define OM_MAX_MSG_RETRY 2 +typedef struct _OmegleAccount OmegleAccount; +typedef struct _OmegleBuddy OmegleBuddy; +typedef void (*OmegleProxyCallbackFunc)(OmegleAccount *oma, gchar *data, gsize data_len, gpointer user_data); + PurpleAccount *account; + GSList *conns; /**< A list of all active OmegleConnections */ + GHashTable *cookie_table; + GHashTable *hostname_ip_cache; +#endif /* LIBOMEGLE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/om_connection.c Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,632 @@
+ * libomegle is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#include "om_connection.h" +static void om_attempt_connection(OmegleConnection *); +static gchar *om_gunzip(const guchar *gzip_data, ssize_t *len_ptr) + gsize gzip_data_len = *len_ptr; + gulong gzip_len = G_MAXUINT16; + GString *output_string = NULL; + data_buffer = g_new0(gchar, gzip_len); + gzip_err = inflateInit2(&zstr, MAX_WBITS+32); + purple_debug_error("omegle", "no built-in gzip support in zlib\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_DATA_ERROR) + inflateInit2(&zstr, -MAX_WBITS); + purple_debug_error("omegle", "Cannot decode gzip header\n"); + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + output_string = g_string_new(""); + while (gzip_err == Z_OK) + //append data to buffer + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + if (gzip_err == Z_STREAM_END) + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + purple_debug_error("omegle", "gzip inflate error\n"); + gchar *output_data = g_strdup(output_string->str); + *len_ptr = output_string->len; + g_string_free(output_string, TRUE); +void om_connection_destroy(OmegleConnection *omconn) + omconn->oma->conns = g_slist_remove(omconn->oma->conns, omconn); + if (omconn->request != NULL) + g_string_free(omconn->request, TRUE); + g_free(omconn->rx_buf); + if (omconn->connect_data != NULL) + purple_proxy_connect_cancel(omconn->connect_data); + if (omconn->ssl_conn != NULL) + purple_ssl_close(omconn->ssl_conn); + if (omconn->input_watcher > 0) + purple_input_remove(omconn->input_watcher); + g_free(omconn->hostname); +static void om_update_cookies(OmegleAccount *oma, const gchar *headers) + const gchar *cookie_start; + const gchar *cookie_end; + g_return_if_fail(headers != NULL); + header_len = strlen(headers); + /* look for the next "Set-Cookie: " */ + /* grab the data up until ';' */ + cookie_start = headers; + while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && + (cookie_start - headers) < header_len) + cookie_end = strchr(cookie_start, '='); + cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end + 1; + cookie_end = strchr(cookie_start, ';'); + cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end; + g_hash_table_replace(oma->cookie_table, cookie_name, +static void om_connection_process_data(OmegleConnection *omconn) + tmp = g_strstr_len(omconn->rx_buf, len, "\r\n\r\n"); + /* This is a corner case that occurs when the connection is + * prematurely closed either on the client or the server. + * This can either be no data at all or a partial set of + * headers. We pass along the data to be good, but don't + * do any fancy massaging. In all likelihood the result will + * be tossed by the connection callback func anyways + tmp = g_strndup(omconn->rx_buf, len); + len -= g_strstr_len(omconn->rx_buf, len, "\r\n\r\n") - + tmp = g_memdup(tmp, len + 1); + omconn->rx_buf[omconn->rx_len - len] = '\0'; + om_update_cookies(omconn->oma, omconn->rx_buf); + if (strstr(omconn->rx_buf, "Content-Encoding: gzip")) + /* we've received compressed gzip data, decompress */ + gunzipped = om_gunzip((const guchar *)tmp, &len); + g_free(omconn->rx_buf); + if (omconn->callback != NULL) { + purple_debug_info("omegle", "executing callback for %s\n", omconn->url); + omconn->callback(omconn->oma, tmp, len, omconn->user_data); +static void om_fatal_connection_cb(OmegleConnection *omconn) + PurpleConnection *pc = omconn->oma->pc; + purple_debug_error("omegle", "fatal connection error\n"); + om_connection_destroy(omconn); + /* We died. Do not pass Go. Do not collect $200 */ + /* In all seriousness, don't attempt to call the normal callback here. + * That may lead to the wrong error message being displayed */ + purple_connection_error_reason(pc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); +static void om_post_or_get_readdata_cb(gpointer data, gint source, + PurpleInputCondition cond) + OmegleConnection *omconn; + if (omconn->method & OM_METHOD_SSL) { + len = purple_ssl_read(omconn->ssl_conn, + len = recv(omconn->fd, buf, sizeof(buf) - 1, 0); + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + if (omconn->method & OM_METHOD_SSL && omconn->rx_len > 0) { + * This is a slightly hacky workaround for a bug in either + * GNU TLS or in the SSL implementation on Omegle's web + * servers. The sequence of events is: + * 1. We attempt to read the first time and successfully read + * the server's response. + * 2. We attempt to read a second time and libpurple's call + * to gnutls_record_recv() returns the error + * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or + * "A TLS packet with unexpected length was received." + * Normally the server would have closed the connection + * cleanly and this second read() request would have returned + * 0. Or maybe it's normal for SSL connections to be severed + * in this manner? In any case, this differs from the behavior + * of the standard recv() system call. + purple_debug_warning("omegle", + "ssl error, but data received. attempting to continue\n"); + /* TODO: Is this a regular occurrence? If so then maybe resend the request? */ + om_fatal_connection_cb(omconn); + omconn->rx_buf = g_realloc(omconn->rx_buf, + omconn->rx_len + len + 1); + memcpy(omconn->rx_buf + omconn->rx_len, buf, len + 1); + /* Wait for more data before processing */ + /* The server closed the connection, let's parse the data */ + om_connection_process_data(omconn); + om_connection_destroy(omconn); +static void om_post_or_get_ssl_readdata_cb (gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + om_post_or_get_readdata_cb(data, -1, cond); +static void om_post_or_get_connect_cb(gpointer data, gint source, + const gchar *error_message) + OmegleConnection *omconn; + omconn->connect_data = NULL; + purple_debug_error("omegle", "post_or_get_connect failure to %s\n", omconn->url); + purple_debug_error("omegle", "post_or_get_connect_cb %s\n", + om_fatal_connection_cb(omconn); + /* TODO: Check the return value of write() */ + len = write(omconn->fd, omconn->request->str, + omconn->input_watcher = purple_input_add(omconn->fd, + om_post_or_get_readdata_cb, omconn); +static void om_post_or_get_ssl_connect_cb(gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) + OmegleConnection *omconn; + purple_debug_info("omegle", "post_or_get_ssl_connect_cb\n"); + /* TODO: Check the return value of write() */ + len = purple_ssl_write(omconn->ssl_conn, + omconn->request->str, omconn->request->len); + purple_ssl_input_add(omconn->ssl_conn, + om_post_or_get_ssl_readdata_cb, omconn); +static void om_host_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) + GSList *host_lookup_list; + struct sockaddr_in *addr; + PurpleDnsQueryData *query; + /* Extract variables */ + host_lookup_list = data; + oma = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + hostname = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + query = host_lookup_list->data; + g_slist_delete_link(host_lookup_list, host_lookup_list); + /* The callback has executed, so we no longer need to keep track of + * the original query. This always needs to run when the cb is + oma->dns_queries = g_slist_remove(oma->dns_queries, query); + /* Any problems, capt'n? */ + if (error_message != NULL) + purple_debug_warning("omegle", + "Error doing host lookup: %s\n", error_message); + purple_debug_warning("omegle", + "Could not resolve host name\n"); + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Copy the address then free it... */ + ip_address = g_strdup(inet_ntoa(addr->sin_addr)); + hosts = g_slist_delete_link(hosts, hosts); + * DNS lookups can return a list of IP addresses, but we only cache + * the first one. So free the rest. + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address... */ + hosts = g_slist_delete_link(hosts, hosts); + g_hash_table_insert(oma->hostname_ip_cache, hostname, ip_address); +static void om_cookie_foreach_cb(gchar *cookie_name, + gchar *cookie_value, GString *str) + /* TODO: Need to escape name and value? */ + g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); + * Serialize the oma->cookie_table hash table to a string. +static gchar *om_cookies_to_string(OmegleAccount *oma) + str = g_string_new(NULL); + g_hash_table_foreach(oma->cookie_table, + (GHFunc)om_cookie_foreach_cb, str); + return g_string_free(str, FALSE); +static void om_ssl_connection_error(PurpleSslConnection *ssl, + PurpleSslErrorType errortype, gpointer data) + OmegleConnection *omconn = data; + PurpleConnection *pc = omconn->oma->pc; + omconn->ssl_conn = NULL; + om_connection_destroy(omconn); + purple_connection_ssl_error(pc, errortype); +void om_post_or_get(OmegleAccount *oma, OmegleMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + OmegleProxyCallbackFunc callback_func, gpointer user_data, + OmegleConnection *omconn; + gboolean is_proxy = FALSE; + const gchar *user_agent; + const gchar* const *languages; + PurpleProxyInfo *proxy_info = NULL; + gchar *proxy_auth_base64; + /* TODO: Fix keepalive and use it as much as possible */ + host = purple_account_get_string(oma->account, "host", "bajor.omegle.com"); + if (oma && oma->account && !(method & OM_METHOD_SSL)) + proxy_info = purple_proxy_get_setup(oma->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + real_url = g_strdup_printf("http://%s%s", host, url); + real_url = g_strdup(url); + cookies = om_cookies_to_string(oma); + user_agent = purple_account_get_string(oma->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)"); + if (method & OM_METHOD_POST && !postdata) + /* Build the request */ + request = g_string_new(NULL); + g_string_append_printf(request, "%s %s HTTP/1.0\r\n", + (method & OM_METHOD_POST) ? "POST" : "GET", + g_string_append_printf(request, "Host: %s\r\n", host); + g_string_append_printf(request, "Connection: %s\r\n", + (keepalive ? "Keep-Alive" : "close")); + g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); + if (method & OM_METHOD_POST) { + g_string_append_printf(request, + "Content-Type: application/x-www-form-urlencoded\r\n"); + g_string_append_printf(request, + "Content-length: %zu\r\n", strlen(postdata)); + g_string_append_printf(request, "Accept: application/json, text/html, */*\r\n"); + g_string_append_printf(request, "Cookie: %s\r\n", cookies); + g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); + if (purple_proxy_info_get_username(proxy_info) && + purple_proxy_info_get_password(proxy_info)) + proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); + proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); + g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); + g_free(proxy_auth_base64); + /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ + languages = g_get_language_names(); + language_names = g_strjoinv(", ", (gchar **)languages); + purple_util_chrreplace(language_names, '_', '-'); + g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); + g_free(language_names); + purple_debug_info("omegle", "getting url %s\n", url); + g_string_append_printf(request, "\r\n"); + if (method & OM_METHOD_POST) + g_string_append_printf(request, "%s", postdata); + /* If it needs to go over a SSL connection, we probably shouldn't print + * it in the debug log. Without this condition a user's password is + * printed in the debug log */ + if (method == OM_METHOD_POST) + purple_debug_info("omegle", "sending request data:\n%s\n", + * Do a separate DNS lookup for the given host name and cache it + * TODO: It would be better if we did this before we call + * purple_proxy_connect(), so we could re-use the result. + * Or even better: Use persistent HTTP connections for servers + * that we access continually. + * TODO: This cache of the hostname<-->IP address does not respect + * the TTL returned by the DNS server. We should expire things + * from the cache after some amount of time. + /* Don't do this for proxy connections, since proxies do the DNS lookup */ + host_ip = g_hash_table_lookup(oma->hostname_ip_cache, host); + } else if (oma->account && !oma->account->disconnecting) { + GSList *host_lookup_list = NULL; + PurpleDnsQueryData *query; + host_lookup_list = g_slist_prepend( + host_lookup_list, g_strdup(host)); + host_lookup_list = g_slist_prepend( + host_lookup_list, oma); + query = purple_dnsquery_a(host, 80, + om_host_lookup_cb, host_lookup_list); + oma->dns_queries = g_slist_prepend(oma->dns_queries, query); + host_lookup_list = g_slist_append(host_lookup_list, query); + omconn = g_new0(OmegleConnection, 1); + omconn->url = real_url; + omconn->method = method; + omconn->hostname = g_strdup(host); + omconn->request = request; + omconn->callback = callback_func; + omconn->user_data = user_data; + omconn->connection_keepalive = keepalive; + omconn->request_time = time(NULL); + oma->conns = g_slist_prepend(oma->conns, omconn); + om_attempt_connection(omconn); +static void om_attempt_connection(OmegleConnection *omconn) + OmegleAccount *oma = omconn->oma; + /* Connection to attempt retries. This code doesn't work perfectly, but + * remains here for future reference if needed */ + if (time(NULL) - omconn->request_time > 5) { + /* We've continuously tried to remake this connection for a + * bit now. It isn't happening, sadly. Time to die. */ + purple_debug_error("omegle", "could not connect after retries\n"); + om_fatal_connection_cb(omconn); + purple_debug_info("omegle", "making connection attempt\n"); + /* TODO: If we're retrying the connection, consider clearing the cached + * DNS value. This will require some juggling with the hostname param */ + /* TODO/FIXME: This retries almost instantenously, which in some cases + * runs at blinding speed. Slow it down. */ + /* TODO/FIXME: this doesn't retry properly on non-ssl connections */ + if (omconn->method & OM_METHOD_SSL) { + omconn->ssl_conn = purple_ssl_connect(oma->account, omconn->hostname, + 443, om_post_or_get_ssl_connect_cb, + om_ssl_connection_error, omconn); + omconn->connect_data = purple_proxy_connect(NULL, oma->account, + omconn->hostname, 80, om_post_or_get_connect_cb, omconn); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/omegle/om_connection.h Sun Dec 12 18:10:38 2010 -0500
@@ -0,0 +1,61 @@
+ * libomegle is the property of its developers. See the COPYRIGHT file + * 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 3 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, see <http://www.gnu.org/licenses/>. +#ifndef OMEGLE_CONNECTION_H +#define OMEGLE_CONNECTION_H + OM_METHOD_GET = 0x0001, + OM_METHOD_POST = 0x0002, +typedef struct _OmegleConnection OmegleConnection; +struct _OmegleConnection { + OmegleProxyCallbackFunc callback; + PurpleProxyConnectData *connect_data; + PurpleSslConnection *ssl_conn; + gboolean connection_keepalive; +void om_connection_destroy(OmegleConnection *omconn); +void om_post_or_get(OmegleAccount *oma, OmegleMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + OmegleProxyCallbackFunc callback_func, gpointer user_data, +#endif /* OMEGLE_CONNECTION_H */