pidgin/purple-plugin-pack
Add Eion Robb's plugins pidgin-capsnot, purple-translate, and
pidgin-icon-override. This does not include the build infrastructure yet.
--- a/AUTHORS Sun Nov 28 03:45:01 2010 -0600
+++ b/AUTHORS Sun Nov 28 18:40:58 2010 -0500
@@ -14,6 +14,7 @@
Andrew Pangborn <gaim@andrewpangborn.com>
+Eion Robb <eion@robbmob.com> --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/capsnot/capsnot.c Sun Nov 28 18:40:58 2010 -0500
@@ -0,0 +1,345 @@
+ * Copyright (C) 2010 Eion Robb + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +#ifndef _PIDGIN_CONVERSATION_H_ +//# include <linux/kd.h> +# define KDGETLED 0x4B31 /* get current LED status */ +# define KDSETLED 0x4B32 /* set new LED status */ +# define LED_SCR 0x01 /* scroll lock */ +# define LED_NUM 0x02 /* num lock */ +# define LED_CAP 0x04 /* caps lock */ +static guint flash_timeout = 0; +static gint flashes_remaining = 0; +static gboolean flash_state = FALSE; + numlock = purple_prefs_get_bool("/plugins/core/eionrobb-capsnot/numlock"); + capslock = purple_prefs_get_bool("/plugins/core/eionrobb-capsnot/capslock"); + scrolllock = purple_prefs_get_bool("/plugins/core/eionrobb-capsnot/scrolllock"); + GetKeyboardState((LPBYTE) &keyState); + if ((state && !(keyState[VK_NUMLOCK] & 1)) || + (!state && (keyState[VK_NUMLOCK] & 1))) + keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_NUMLOCK, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + if ((state && !(keyState[VK_CAPITAL] & 1)) || + (!state && (keyState[VK_CAPITAL] & 1))) + keybd_event(VK_CAPITAL, 0x3a, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_CAPITAL, 0x3a, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + if ((state && !(keyState[VK_SCROLL] & 1)) || + (!state && (keyState[VK_SCROLL] & 1))) + keybd_event(VK_SCROLL, 0x46, KEYEVENTF_EXTENDEDKEY | 0, 0); + keybd_event(VK_SCROLL, 0x46, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + ioctl(0, KDGETLED, &keyState); + keyState = keyState | LED_NUM; + keyState = keyState & ~LED_NUM; + keyState = keyState | LED_CAP; + keyState = keyState & ~LED_CAP; + keyState = keyState | LED_SCR; + keyState = keyState & ~LED_SCR; + ioctl(0, KDSETLED, keyState); +flash_toggle(gpointer data) + flash_state = !flash_state; + if (flashes_remaining <= 0) + led_set(flash_state = FALSE); +capsnot_conversation_updated(PurpleConversation *conv, + PurpleConvUpdateType type) + if( type != PURPLE_CONV_UPDATE_UNSEEN ) { + const char *im = purple_prefs_get_string("/plugins/core/eionrobb-capsnot/im"); + const char *chat = purple_prefs_get_string("/plugins/core/eionrobb-capsnot/chat"); + if (im && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) + if (g_str_equal(im, "always")) + has_unseen = (GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-state")) >= PIDGIN_UNSEEN_TEXT); + else if (g_str_equal(im, "hidden")) + has_unseen = (GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-count")) > 0); + } else if (chat && purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) + if (g_str_equal(chat, "always")) + has_unseen = (GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-state")) >= PIDGIN_UNSEEN_TEXT); + else if (g_str_equal(chat, "nick")) + has_unseen = (GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-state")) >= PIDGIN_UNSEEN_NICK); + flashcount = purple_prefs_get_int("/plugins/core/eionrobb-capsnot/flashcount"); + flashseconds = purple_prefs_get_int("/plugins/core/eionrobb-capsnot/flashseconds"); + purple_timeout_remove(flash_timeout); + flashes_remaining = flashcount * 2; + flash_timeout = purple_timeout_add(flashseconds * 1000 / flashcount / 2, flash_toggle, NULL); +static PurplePluginPrefFrame * +plugin_config_frame(PurplePlugin *plugin) + PurplePluginPrefFrame *frame; + PurplePluginPref *ppref; + frame = purple_plugin_pref_frame_new(); + ppref = purple_plugin_pref_new_with_label("Inform about unread..."); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/im", + purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); + purple_plugin_pref_add_choice(ppref, "Never", "never"); + purple_plugin_pref_add_choice(ppref, "In hidden conversations", "hidden"); + purple_plugin_pref_add_choice(ppref, "Always", "always"); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/chat", + purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); + purple_plugin_pref_add_choice(ppref, "Never", "never"); + purple_plugin_pref_add_choice(ppref, "When my nick is said", "nick"); + purple_plugin_pref_add_choice(ppref, "Always", "always"); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_label("Keyboard LEDs:"); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/numlock", + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/capslock", + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/scrolllock", + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_label("Flash Rate:"); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/flashcount", + purple_plugin_pref_set_bounds(ppref, 1, 10); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-capsnot/flashseconds", + "Duration of flashes (seconds)"); + purple_plugin_pref_set_bounds(ppref, 1, 10); + purple_plugin_pref_frame_add(frame, ppref); +init_plugin(PurplePlugin *plugin) + purple_prefs_add_none("/plugins/core/eionrobb-capsnot"); + purple_prefs_add_string("/plugins/core/eionrobb-capsnot/im", "always"); + purple_prefs_add_string("/plugins/core/eionrobb-capsnot/chat", "nick"); + purple_prefs_add_bool("/plugins/core/eionrobb-capsnot/numlock", FALSE); + purple_prefs_add_bool("/plugins/core/eionrobb-capsnot/capslock", FALSE); + purple_prefs_add_bool("/plugins/core/eionrobb-capsnot/scrolllock", TRUE); + purple_prefs_add_int("/plugins/core/eionrobb-capsnot/flashcount", 8); + purple_prefs_add_int("/plugins/core/eionrobb-capsnot/flashseconds", 2); +plugin_load(PurplePlugin *plugin) + purple_signal_connect(purple_conversations_get_handle(), + "conversation-updated", plugin, + PURPLE_CALLBACK(capsnot_conversation_updated), NULL); +plugin_unload(PurplePlugin *plugin) + purple_signal_disconnect(purple_conversations_get_handle(), + "conversation-updated", plugin, + PURPLE_CALLBACK(capsnot_conversation_updated)); + purple_timeout_remove(flash_timeout); + led_set(flash_state = FALSE); +static PurplePluginUiInfo prefs_info = { + 0, /* page_num (Reserved) */ + NULL, /* frame (Reserved) */ +static PurplePluginInfo info = { + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, + "Led notification on keyboards", + "Informs for new messages with the NumLock, CapsLock or ScrollLock LEDs", + "Eion Robb <eionrobb@gmail.com>", + plugin_load, /* load */ + plugin_unload, /* unload */ +PURPLE_INIT_PLUGIN(capsnot, init_plugin, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/icon-override/icon_override.c Sun Nov 28 18:40:58 2010 -0500
@@ -0,0 +1,162 @@
+http://eion.robbmob.com/purple-blist-sort.png +#define NEW_ICON_ID "eionrobb_new_prpl_icon" +static GHashTable *original_list_icon = NULL; +new_list_icon(PurpleAccount *account, PurpleBuddy *buddy) + const char*(* list_icon_func)(PurpleAccount *account, PurpleBuddy *buddy); + else if (buddy != NULL) + new_icon = purple_account_get_string(account, NEW_ICON_ID, NULL); + if (new_icon && *new_icon) + prpl_id = account->protocol_id; + list_icon_func = g_hash_table_lookup(original_list_icon, prpl_id); + return list_icon_func(account, buddy); +plugin_load(PurplePlugin *plugin) + PurpleAccountOption *option; + PurplePluginProtocolInfo *prpl_info; + //Hook into every protocol's prpl_info->list_icon function + original_list_icon = g_hash_table_new(g_str_hash, g_str_equal); + proto_list = purple_plugins_get_protocols(); + for(; proto_list; proto_list = proto_list->next) + proto = proto_list->data; + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(proto); + g_hash_table_insert(original_list_icon, (gpointer)purple_plugin_get_id(proto), prpl_info->list_icon); + prpl_info->list_icon = new_list_icon; + //Add in some protocol preferences + option = purple_account_option_string_new(_("Protocol Icon"), NEW_ICON_ID, ""); + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); +plugin_unload(PurplePlugin *plugin) + PurplePluginProtocolInfo *prpl_info; + PurpleAccountOption *option; + proto_list = purple_plugins_get_protocols(); + for(; proto_list; proto_list = proto_list->next) + proto = (PurplePlugin *) proto_list->data; + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(proto); + func = g_hash_table_lookup(original_list_icon, purple_plugin_get_id(proto)); + prpl_info->list_icon = func; + g_hash_table_remove(original_list_icon, purple_plugin_get_id(proto)); + //Remove from the protocol options screen + list = prpl_info->protocol_options; + option = (PurpleAccountOption *) list->data; + if (g_str_equal(purple_account_option_get_setting(option), NEW_ICON_ID)) + llist->prev->next = llist->next; + llist->next->prev = llist->prev; + purple_account_option_destroy(option); + list = g_list_next(list); + list = g_list_next(list); + g_hash_table_destroy(original_list_icon); + original_list_icon = NULL; +plugin_init(PurplePlugin *plugin) +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_LOWEST, + "eionrobb-icon-override", + "Protocol Icon Override", + "Customise protocol icons", + "Lets you change protocol icons per-account so that you can tell the difference between, say, a personal XMPP account and one used for work", + "Eion Robb <eionrobb@gmail.com>", + "http://pidgin-icon-override.googlecode.com/", //URL +PURPLE_INIT_PLUGIN(icon_override, plugin_init, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/translate/translate.c Sun Nov 28 18:40:58 2010 -0500
@@ -0,0 +1,963 @@
+ * Copyright (C) 2010 Eion Robb + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +#define BING_APPID "0FFF5300CD157A2E748DFCCF6D67F8028E5B578D" +/** This is the list of languages we support, populated in plugin_init */ +static GList *supported_languages = NULL; +typedef void(* TranslateCallback)(const gchar *original_phrase, const gchar *translated_phrase, const gchar *detected_language, gpointer userdata); +struct _TranslateStore { + gchar *original_phrase; + TranslateCallback callback; + gchar *detected_language; //optional - needed for Bing +/** Converts unicode strings such as \003d into = + * Code blatantly nicked from the Facebook plugin */ +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); +get_language_name(const gchar *language_key) + const gchar *language_name = NULL; + PurpleKeyValuePair *pair = NULL; + for(l = supported_languages; l; l = l->next) + pair = (PurpleKeyValuePair *) l->data; + if (g_str_equal(pair->key, language_key)) + language_name = pair->value; +google_translate_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) + struct _TranslateStore *store = user_data; + const gchar *trans_start = "\"translatedText\":\""; + const gchar *lang_start = "\"detectedSourceLanguage\":\""; + gchar *strstart = NULL; + gchar *translated = NULL; + purple_debug_info("translate", "Got response: %s\n", url_text); + strstart = g_strstr_len(url_text, len, trans_start); + strstart = strstart + strlen(trans_start); + translated = g_strndup(strstart, strchr(strstart, '"') - strstart); + strstart = convert_unicode(translated); + strstart = g_strstr_len(url_text, len, lang_start); + strstart = strstart + strlen(lang_start); + lang = g_strndup(strstart, strchr(strstart, '"') - strstart); + store->callback(store->original_phrase, translated, lang, store->userdata); + g_free(store->original_phrase); +google_translate(const gchar *plain_phrase, const gchar *from_lang, const gchar *to_lang, TranslateCallback callback, gpointer userdata) + struct _TranslateStore *store; + encoded_phrase = g_strdup(purple_url_encode(plain_phrase)); + if (!from_lang || g_str_equal(from_lang, "auto")) + url = g_strdup_printf("http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&langpair=%s%%7C%s&q=%s", + from_lang, to_lang, encoded_phrase); + store = g_new0(struct _TranslateStore, 1); + store->original_phrase = g_strdup(plain_phrase); + store->callback = callback; + store->userdata = userdata; + purple_debug_info("translate", "Fetching %s\n", url); + purple_util_fetch_url_request(url, TRUE, "libpurple", FALSE, NULL, FALSE, google_translate_cb, store); + g_free(encoded_phrase); +bing_translate_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) + struct _TranslateStore *store = user_data; + gchar *translated = NULL; + purple_debug_info("translate", "Got response: %s\n", url_text); + temp = strchr(url_text, '"') + 1; + temp = g_strndup(temp, len - (temp - url_text) - 1); + translated = convert_unicode(temp); + store->callback(store->original_phrase, translated, store->detected_language, store->userdata); + g_free(store->detected_language); + g_free(store->original_phrase); +bing_translate_autodetect_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) + struct _TranslateStore *store = user_data; + gchar *from_lang = NULL; + purple_debug_info("translate", "Got response: %s\n", url_text); + if (!url_text || !len || g_strstr_len(url_text, len, "\"\"")) + store->callback(store->original_phrase, store->original_phrase, NULL, store->userdata); + g_free(store->detected_language); + g_free(store->original_phrase); + from_lang = strchr(url_text, '"') + 1; + from_lang = g_strndup(from_lang, len - (from_lang - url_text) - 1); + to_lang = store->detected_language; + store->detected_language = from_lang; + // Same as bing_translate() but we've already made the _TranslateStore + encoded_phrase = g_strescape(purple_url_encode(store->original_phrase), NULL); + url = g_strdup_printf("http://api.microsofttranslator.com/V2/Ajax.svc/Translate?appId=" BING_APPID "&text=%%22%s%%22&from=%s&to=%s", + encoded_phrase, from_lang, to_lang); + purple_debug_info("translate", "Fetching %s\n", url); + purple_util_fetch_url_request(url, TRUE, "libpurple", FALSE, NULL, FALSE, bing_translate_cb, store); + g_free(encoded_phrase); +bing_translate(const gchar *plain_phrase, const gchar *from_lang, const gchar *to_lang, TranslateCallback callback, gpointer userdata) + struct _TranslateStore *store; + PurpleUtilFetchUrlCallback urlcallback; + encoded_phrase = g_strescape(purple_url_encode(plain_phrase), NULL); + store = g_new0(struct _TranslateStore, 1); + store->original_phrase = g_strdup(plain_phrase); + store->callback = callback; + store->userdata = userdata; + if (!from_lang || !(*from_lang) || g_str_equal(from_lang, "auto")) + url = g_strdup_printf("http://api.microsofttranslator.com/V2/Ajax.svc/Detect?appId=" BING_APPID "&text=%%22%s%%22", + store->detected_language = g_strdup(to_lang); + urlcallback = bing_translate_autodetect_cb; + url = g_strdup_printf("http://api.microsofttranslator.com/V2/Ajax.svc/Translate?appId=" BING_APPID "&text=%%22%s%%22&from=%s&to=%s", + encoded_phrase, from_lang, to_lang); + urlcallback = bing_translate_cb; + purple_debug_info("translate", "Fetching %s\n", url); + purple_util_fetch_url_request(url, TRUE, "libpurple", FALSE, NULL, FALSE, urlcallback, store); + g_free(encoded_phrase); +struct TranslateConvMessage { + PurpleAccount *account; + PurpleConversation *conv; + PurpleMessageFlags flags; +translate_receiving_message_cb(const gchar *original_phrase, const gchar *translated_phrase, const gchar *detected_language, gpointer userdata) + struct TranslateConvMessage *convmsg = userdata; + const gchar *stored_lang = ""; + const gchar *language_name = NULL; + buddy = purple_find_buddy(convmsg->account, convmsg->sender); + stored_lang = purple_blist_node_get_string((PurpleBlistNode *)buddy, "eionrobb-translate-lang"); + purple_blist_node_set_string((PurpleBlistNode *)buddy, "eionrobb-translate-lang", detected_language); + language_name = get_language_name(detected_language); + if (language_name != NULL) + message = g_strdup_printf("Now translating to %s (auto-detected)", language_name); + purple_conversation_write(convmsg->conv, NULL, message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); + html_text = purple_strdup_withhtml(translated_phrase); + purple_conversation_write(convmsg->conv, convmsg->sender, html_text, convmsg->flags, time(NULL)); + g_free(convmsg->sender); +translate_receiving_im_msg(PurpleAccount *account, char **sender, + char **message, PurpleConversation *conv, + PurpleMessageFlags *flags) + struct TranslateConvMessage *convmsg; + const gchar *stored_lang = "auto"; + const gchar *service_to_use = ""; + buddy = purple_find_buddy(account, *sender); + service_to_use = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/service"); + to_lang = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/locale"); + stored_lang = purple_blist_node_get_string((PurpleBlistNode *)buddy, "eionrobb-translate-lang"); + if (!buddy || !service_to_use || g_str_equal(stored_lang, "none") || g_str_equal(stored_lang, to_lang)) + //Allow the message to go through as per normal + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, *sender); + stripped = purple_markup_strip_html(*message); + convmsg = g_new0(struct TranslateConvMessage, 1); + convmsg->account = account; + convmsg->sender = *sender; + convmsg->flags = *flags; + if (g_str_equal(service_to_use, "google")) + google_translate(stripped, stored_lang, to_lang, translate_receiving_message_cb, convmsg); + } else if (g_str_equal(service_to_use, "bing")) + bing_translate(stripped, stored_lang, to_lang, translate_receiving_message_cb, convmsg); +translate_receiving_chat_msg_cb(const gchar *original_phrase, const gchar *translated_phrase, const gchar *detected_language, gpointer userdata) + struct TranslateConvMessage *convmsg = userdata; + const gchar *stored_lang = ""; + const gchar *language_name = NULL; + chat = purple_blist_find_chat(convmsg->account, convmsg->conv->name); + stored_lang = purple_blist_node_get_string((PurpleBlistNode *)chat, "eionrobb-translate-lang"); + purple_blist_node_set_string((PurpleBlistNode *)chat, "eionrobb-translate-lang", detected_language); + language_name = get_language_name(detected_language); + if (language_name != NULL) + message = g_strdup_printf("Now translating to %s (auto-detected)", language_name); + purple_conversation_write(convmsg->conv, NULL, message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); + html_text = purple_strdup_withhtml(translated_phrase); + purple_conversation_write(convmsg->conv, convmsg->sender, html_text, convmsg->flags, time(NULL)); + g_free(convmsg->sender); +translate_receiving_chat_msg(PurpleAccount *account, char **sender, + char **message, PurpleConversation *conv, + PurpleMessageFlags *flags) + struct TranslateConvMessage *convmsg; + const gchar *stored_lang = "auto"; + const gchar *service_to_use = ""; + chat = purple_blist_find_chat(account, conv->name); + service_to_use = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/service"); + to_lang = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/locale"); + stored_lang = purple_blist_node_get_string((PurpleBlistNode *)chat, "eionrobb-translate-lang"); + if (!chat || !service_to_use || g_str_equal(stored_lang, "none") || g_str_equal(stored_lang, to_lang)) + //Allow the message to go through as per normal + stripped = purple_markup_strip_html(*message); + convmsg = g_new0(struct TranslateConvMessage, 1); + convmsg->account = account; + convmsg->sender = *sender; + convmsg->flags = *flags; + if (g_str_equal(service_to_use, "google")) + google_translate(stripped, stored_lang, to_lang, translate_receiving_chat_msg_cb, convmsg); + } else if (g_str_equal(service_to_use, "bing")) + bing_translate(stripped, stored_lang, to_lang, translate_receiving_chat_msg_cb, convmsg); + // Fake receiving a message to open the conversation window + *message = g_strdup(" "); + *flags |= PURPLE_MESSAGE_INVISIBLE | PURPLE_MESSAGE_NO_LOG; +translate_sending_message_cb(const gchar *original_phrase, const gchar *translated_phrase, const gchar *detected_language, gpointer userdata) + struct TranslateConvMessage *convmsg = userdata; + html_text = purple_strdup_withhtml(translated_phrase); + err = serv_send_im(purple_account_get_connection(convmsg->account), convmsg->sender, html_text, convmsg->flags); + html_text = purple_strdup_withhtml(original_phrase); + purple_conversation_write(convmsg->conv, convmsg->sender, html_text, convmsg->flags, time(NULL)); + purple_signal_emit(purple_conversations_get_handle(), "sent-im-msg", + convmsg->account, convmsg->sender, html_text); + g_free(convmsg->sender); +translate_sending_im_msg(PurpleAccount *account, const char *receiver, char **message) + const gchar *from_lang = ""; + const gchar *service_to_use = ""; + const gchar *to_lang = ""; + struct TranslateConvMessage *convmsg; + from_lang = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/locale"); + service_to_use = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/service"); + buddy = purple_find_buddy(account, receiver); + to_lang = purple_blist_node_get_string((PurpleBlistNode *)buddy, "eionrobb-translate-lang"); + if (!buddy || !service_to_use || !to_lang || g_str_equal(from_lang, to_lang) || g_str_equal(to_lang, "auto")) + // Don't translate this message + stripped = purple_markup_strip_html(*message); + convmsg = g_new0(struct TranslateConvMessage, 1); + convmsg->account = account; + convmsg->sender = g_strdup(receiver); + convmsg->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, receiver, account); + convmsg->flags = PURPLE_MESSAGE_SEND; + if (g_str_equal(service_to_use, "google")) + google_translate(stripped, from_lang, to_lang, translate_sending_message_cb, convmsg); + } else if (g_str_equal(service_to_use, "bing")) + bing_translate(stripped, from_lang, to_lang, translate_sending_message_cb, convmsg); +translate_sending_chat_message_cb(const gchar *original_phrase, const gchar *translated_phrase, const gchar *detected_language, gpointer userdata) + struct TranslateConvMessage *convmsg = userdata; + html_text = purple_strdup_withhtml(translated_phrase); + err = serv_chat_send(purple_account_get_connection(convmsg->account), purple_conv_chat_get_id(PURPLE_CONV_CHAT(convmsg->conv)), html_text, convmsg->flags); + html_text = purple_strdup_withhtml(original_phrase); + // purple_conversation_write(convmsg->conv, convmsg->sender, html_text, convmsg->flags, time(NULL)); + purple_signal_emit(purple_conversations_get_handle(), "sent-chat-msg", + convmsg->account, html_text, + purple_conv_chat_get_id(PURPLE_CONV_CHAT(convmsg->conv))); + g_free(convmsg->sender); +translate_sending_chat_msg(PurpleAccount *account, char **message, int chat_id) + const gchar *from_lang = ""; + const gchar *service_to_use = ""; + const gchar *to_lang = ""; + PurpleChat *chat = NULL; + PurpleConversation *conv; + struct TranslateConvMessage *convmsg; + from_lang = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/locale"); + service_to_use = purple_prefs_get_string("/plugins/core/eionrobb-libpurple-translate/service"); + conv = purple_find_chat(purple_account_get_connection(account), chat_id); + chat = purple_blist_find_chat(account, conv->name); + to_lang = purple_blist_node_get_string((PurpleBlistNode *)chat, "eionrobb-translate-lang"); + if (!chat || !service_to_use || !to_lang || g_str_equal(from_lang, to_lang) || g_str_equal(to_lang, "auto")) + // Don't translate this message + stripped = purple_markup_strip_html(*message); + convmsg = g_new0(struct TranslateConvMessage, 1); + convmsg->account = account; + convmsg->flags = PURPLE_MESSAGE_SEND; + if (g_str_equal(service_to_use, "google")) + google_translate(stripped, from_lang, to_lang, translate_sending_chat_message_cb, convmsg); + } else if (g_str_equal(service_to_use, "bing")) + bing_translate(stripped, from_lang, to_lang, translate_sending_chat_message_cb, convmsg); +translate_action_blist_cb(PurpleBlistNode *node, PurpleKeyValuePair *pair) + PurpleConversation *conv = NULL; + PurpleContact *contact; + purple_blist_node_set_string(node, "eionrobb-translate-lang", NULL); + purple_blist_node_set_string(node, "eionrobb-translate-lang", pair->key); + case PURPLE_BLIST_CHAT_NODE: + chat = (PurpleChat *) node; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, + purple_chat_get_name(chat), + case PURPLE_BLIST_CONTACT_NODE: + contact = (PurpleContact *) node; + node = (PurpleBlistNode *)purple_contact_get_priority_buddy(contact); + //fallthrough intentional + case PURPLE_BLIST_BUDDY_NODE: + buddy = (PurpleBuddy *) node; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, + purple_buddy_get_name(buddy), + purple_buddy_get_account(buddy)); + if (conv != NULL && pair != NULL) + message = g_strdup_printf("Now translating to %s", (const gchar *)pair->value); + purple_conversation_write(conv, NULL, message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); +translate_extended_menu(PurpleBlistNode *node, GList **menu, PurpleCallback callback) + const gchar *stored_lang; + GList *menu_children = NULL; + PurpleMenuAction *action; + PurpleKeyValuePair *pair; + stored_lang = purple_blist_node_get_string(node, "eionrobb-translate-lang"); + action = purple_menu_action_new("Auto", callback, NULL, NULL); + menu_children = g_list_append(menu_children, action); + menu_children = g_list_append(menu_children, NULL); + for(l = supported_languages; l; l = l->next) + pair = (PurpleKeyValuePair *) l->data; + action = purple_menu_action_new(pair->value, callback, pair, NULL); + menu_children = g_list_append(menu_children, action); + // Create the menu for the languages + action = purple_menu_action_new("Translate to...", NULL, NULL, menu_children); + *menu = g_list_append(*menu, action); +translate_blist_extended_menu(PurpleBlistNode *node, GList **menu) + translate_extended_menu(node, menu, (PurpleCallback)translate_action_blist_cb); +translate_action_conv_cb(PurpleConversation *conv, PurpleKeyValuePair *pair) + PurpleBlistNode *node = NULL; + if (conv->type == PURPLE_CONV_TYPE_IM) + node = (PurpleBlistNode *) purple_find_buddy(conv->account, conv->name); + else if (conv->type == PURPLE_CONV_TYPE_CHAT) + node = (PurpleBlistNode *) purple_blist_find_chat(conv->account, conv->name); + translate_action_blist_cb(node, pair); + message = g_strdup_printf("Now translating to %s", (const gchar *)pair->value); + purple_conversation_write(conv, NULL, message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); +translate_conversation_created(PurpleConversation *conv) + PurpleBlistNode *node = NULL; + const gchar *language_key; + const gchar *language_name; + if (conv->type == PURPLE_CONV_TYPE_IM) + node = (PurpleBlistNode *) purple_find_buddy(conv->account, conv->name); + else if (conv->type == PURPLE_CONV_TYPE_CHAT) + node = (PurpleBlistNode *) purple_blist_find_chat(conv->account, conv->name); + language_key = purple_blist_node_get_string(node, "eionrobb-translate-lang"); + if (language_key != NULL) + language_name = get_language_name(language_key); + message = g_strdup_printf("Now translating to %s", language_name); + purple_conversation_write(conv, NULL, message, PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG, time(NULL)); +translate_conv_extended_menu(PurpleConversation *conv, GList **menu) + PurpleBlistNode *node = NULL; + if (conv->type == PURPLE_CONV_TYPE_IM) + node = (PurpleBlistNode *) purple_find_buddy(conv->account, conv->name); + else if (conv->type == PURPLE_CONV_TYPE_CHAT) + node = (PurpleBlistNode *) purple_blist_find_chat(conv->account, conv->name); + translate_extended_menu(node, menu, (PurpleCallback)translate_action_conv_cb); +static PurplePluginPrefFrame * +plugin_config_frame(PurplePlugin *plugin) + PurplePluginPrefFrame *frame; + PurplePluginPref *ppref; + PurpleKeyValuePair *pair; + frame = purple_plugin_pref_frame_new(); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-libpurple-translate/locale", + purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); + for(l = supported_languages; l; l = l->next) + pair = (PurpleKeyValuePair *) l->data; + purple_plugin_pref_add_choice(ppref, pair->value, pair->key); + purple_plugin_pref_frame_add(frame, ppref); + ppref = purple_plugin_pref_new_with_name_and_label( + "/plugins/core/eionrobb-libpurple-translate/service", + purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); + purple_plugin_pref_add_choice(ppref, "Google Translate", "google"); + purple_plugin_pref_add_choice(ppref, "Microsoft Translator", "bing"); + purple_plugin_pref_frame_add(frame, ppref); +init_plugin(PurplePlugin *plugin) + const gchar * const * languages; + languages = g_get_language_names(); + PurpleKeyValuePair *pair; + while((language = languages[i++])) + if (language && strlen(language) == 2) + if (!language || strlen(language) != 2) + purple_prefs_add_none("/plugins/core/eionrobb-libpurple-translate"); + purple_prefs_add_string("/plugins/core/eionrobb-libpurple-translate/locale", language); + purple_prefs_add_string("/plugins/core/eionrobb-libpurple-translate/service", "google"); +#define add_language(label, code) \ + pair = g_new0(PurpleKeyValuePair, 1); \ + pair->key = g_strdup(code); \ + pair->value = g_strdup(label); \ + supported_languages = g_list_append(supported_languages, pair); + add_language("Afrikaans", "af"); + add_language("Albanian", "sq"); + add_language("Arabic", "ar"); + add_language("Armenian", "hy"); + add_language("Azerbaijani", "az"); + add_language("Basque", "eu"); + add_language("Belarusian", "be"); + add_language("Bulgarian", "bg"); + add_language("Catalan", "ca"); + add_language("Chinese (Simplified)", "zh-CN"); + add_language("Chinese (Traditional)", "zh-TW"); + add_language("Croatian", "hr"); + add_language("Czech", "cs"); + add_language("Danish", "da"); + add_language("Dutch", "nl"); + add_language("English", "en"); + add_language("Estonian", "et"); + add_language("Filipino", "tl"); + add_language("Finnish", "fi"); + add_language("French", "fr"); + add_language("Galician", "gl"); + add_language("Georgian", "ka"); + add_language("German", "de"); + add_language("Greek", "el"); + add_language("Haitian Creole", "ht"); + add_language("Hebrew", "iw"); + add_language("Hindi", "hi"); + add_language("Hungarian", "hu"); + add_language("Icelandic", "is"); + add_language("Indonesian", "id"); + add_language("Irish", "ga"); + add_language("Italian", "it"); + add_language("Japanese", "ja"); + add_language("Korean", "ko"); + add_language("Latin", "la"); + add_language("Latvian", "lv"); + add_language("Lithuanian", "lt"); + add_language("Macedonian", "mk"); + add_language("Malay", "ms"); + add_language("Maltese", "mt"); + add_language("Norwegian", "no"); + add_language("Persian", "fa"); + add_language("Polish", "pl"); + add_language("Portuguese", "pt"); + add_language("Romanian", "ro"); + add_language("Russian", "ru"); + add_language("Serbian", "sr"); + add_language("Slovak", "sk"); + add_language("Slovenian", "sl"); + add_language("Spanish", "es"); + add_language("Swahili", "sw"); + add_language("Swedish", "sv"); + add_language("Thai", "th"); + add_language("Turkish", "tr"); + add_language("Ukrainian", "uk"); + add_language("Urdu", "ur"); + add_language("Vietnamese", "vi"); + add_language("Welsh", "cy"); + add_language("Yiddish", "yi"); +plugin_load(PurplePlugin *plugin) + purple_signal_connect(purple_conversations_get_handle(), + "receiving-im-msg", plugin, + PURPLE_CALLBACK(translate_receiving_im_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "sending-im-msg", plugin, + PURPLE_CALLBACK(translate_sending_im_msg), NULL); + purple_signal_connect(purple_blist_get_handle(), + "blist-node-extended-menu", plugin, + PURPLE_CALLBACK(translate_blist_extended_menu), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "blist-node-extended-menu", plugin, + PURPLE_CALLBACK(translate_conv_extended_menu), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "conversation-created", plugin, + PURPLE_CALLBACK(translate_conversation_created), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "receiving-chat-msg", plugin, + PURPLE_CALLBACK(translate_receiving_chat_msg), NULL); + purple_signal_connect(purple_conversations_get_handle(), + "sending-chat-msg", plugin, + PURPLE_CALLBACK(translate_sending_chat_msg), NULL); +plugin_unload(PurplePlugin *plugin) + purple_signal_disconnect(purple_conversations_get_handle(), + "receiving-im-msg", plugin, + PURPLE_CALLBACK(translate_receiving_im_msg)); + purple_signal_disconnect(purple_conversations_get_handle(), + "sending-im-msg", plugin, + PURPLE_CALLBACK(translate_sending_im_msg)); + purple_signal_disconnect(purple_blist_get_handle(), + "blist-node-extended-menu", plugin, + PURPLE_CALLBACK(translate_blist_extended_menu)); + purple_signal_disconnect(purple_conversations_get_handle(), + "blist-node-extended-menu", plugin, + PURPLE_CALLBACK(translate_conv_extended_menu)); + purple_signal_disconnect(purple_conversations_get_handle(), + "conversation-created", plugin, + PURPLE_CALLBACK(translate_conversation_created)); + purple_signal_disconnect(purple_conversations_get_handle(), + "receiving-chat-msg", plugin, + PURPLE_CALLBACK(translate_receiving_chat_msg)); + purple_signal_disconnect(purple_conversations_get_handle(), + "sending-chat-msg", plugin, + PURPLE_CALLBACK(translate_sending_chat_msg)); +static PurplePluginUiInfo prefs_info = { +static PurplePluginInfo info = { + PURPLE_PLUGIN_STANDARD, + PURPLE_PRIORITY_DEFAULT, + "eionrobb-libpurple-translate", + "Translate incoming/outgoing messages", + "Eion Robb <eionrobb@gmail.com>", + "http://purple-translate.googlecode.com/", /* URL */ + plugin_load, /* load */ + plugin_unload, /* unload */ +PURPLE_INIT_PLUGIN(translate, init_plugin, info);