mbpurple-0.3.0/0000755000175000017500000000000011400373247012715 5ustar somsaksomsakmbpurple-0.3.0/twitgin/0000755000175000017500000000000011400373247014402 5ustar somsaksomsakmbpurple-0.3.0/twitgin/twitgin.c0000644000175000017500000007652711400373247016254 0ustar somsaksomsak/* * Twitgin - A GUI support of libtwitter/microblog-purple for Conversation dialog * Copyright (C) 2008-2010 Chanwit Kaewkasi * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ //#include "internal.h" //#include #include #include #include #include #include #include #include #include #include #include #define TW_MAX_MESSAGE_SIZE 140 #define TW_MAX_MESSAGE_SIZE_TEXT "140" #include "twitter.h" #include "mb_http.h" #include "mb_net.h" #include "mb_util.h" #include "twitpref.h" #define DBGID "twitgin" #define PURPLE_MESSAGE_TWITGIN 0x1000 static PurplePlugin * twitgin_plugin = NULL; MbConfig * _mb_conf = NULL; gchar * twitter_reformat_msg(MbAccount * ta, const TwitterMsg * msg, PurpleConversation * conv); static void twitgin_entry_buffer_on_changed(PidginConversation *gtkconv) { GtkTextIter start; GtkTextIter end; gchar *text = NULL; int size = 0; gchar* size_text = NULL; GtkWidget *size_label = g_object_get_data(G_OBJECT(gtkconv->toolbar), "size_label"); if(size_label != NULL) { gtk_text_buffer_get_iter_at_offset(gtkconv->entry_buffer, &start, 0); gtk_text_buffer_get_iter_at_offset(gtkconv->entry_buffer, &end, 0); gtk_text_iter_forward_to_end(&end); text = gtk_text_buffer_get_text(gtkconv->entry_buffer, &start, &end, FALSE); size = TW_MAX_MESSAGE_SIZE - g_utf8_strlen(text, -1); if(size >= 0) { size_text = g_strdup_printf("%d", size); } else { size_text = g_strdup_printf("%d", size); } gtk_label_set_markup(GTK_LABEL(size_label), size_text); g_free(size_text); } } /* Editable stuff */ //static void twitgin_preinsert_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, gint len, GtkIMHtml *imhtml) { // TODO: // if(strcmp(text,"tw:")==0) { // g_signal_stop_emission_by_name(buffer, "insert-text"); // } //} static void create_twitter_label(PidginConversation *gtkconv) { GtkWidget *label = gtk_label_new(TW_MAX_MESSAGE_SIZE_TEXT); // int id; gtk_box_pack_end(GTK_BOX(gtkconv->toolbar), label, FALSE, FALSE, 0); gtk_widget_show(label); g_object_set_data(G_OBJECT(gtkconv->toolbar), "size_label", label); g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed", G_CALLBACK(twitgin_entry_buffer_on_changed), gtkconv); // g_signal_connect(G_OBJECT(GTK_IMHTML(gtkconv->imhtml)->text_buffer), "insert-text", G_CALLBACK(twitgin_preinsert_cb), gtkconv->imhtml); } static void remove_twitter_label(PidginConversation *gtkconv) { GtkWidget *size_label = NULL; size_label = g_object_get_data(G_OBJECT(gtkconv->toolbar),"size_label"); if (size_label != NULL) { gtk_widget_destroy(size_label); } } static gboolean is_twitter_conversation(PurpleConversation *conv) { purple_debug_info(DBGID, "%s %s\n", __FUNCTION__, conv->account->protocol_id); if(conv->account && conv->account->protocol_id) { return (strncmp(conv->account->protocol_id, "prpl-mbpurple", 13) == 0); } else { return FALSE; } } static void on_conversation_display(PidginConversation *gtkconv) { GtkWidget *size_label = NULL; PurpleConversation *conv = gtkconv->active_conv; if(is_twitter_conversation(conv)) { size_label = g_object_get_data(G_OBJECT(gtkconv->toolbar), "size_label"); if (size_label == NULL) { create_twitter_label(gtkconv); } } } enum { TWITTER_PROTO = 1, IDENTICA_PROTO = 2, }; static gboolean twittgin_uri_handler(const char *proto, const char *cmd_arg, GHashTable *params) { const char * cmd = cmd_arg; char *acct_id = g_hash_table_lookup(params, "account"); PurpleAccount *acct = NULL; MbAccount * ma; PurpleConversation * conv = NULL; PidginConversation * gtkconv; int proto_id = 0; gchar * src = NULL; //const char * decoded_rt; //char * unescaped_rt; purple_debug_info(DBGID, "twittgin_uri_handler\n"); // do not need to test, because the conversation window must be open before one can click // XXX: We need better function to search for the account! if (g_ascii_strcasecmp(proto, "tw") == 0) { proto_id = TWITTER_PROTO; acct = purple_accounts_find(acct_id, "prpl-mbpurple-twitter"); } else if(g_ascii_strcasecmp(proto, "idc") == 0) { proto_id = IDENTICA_PROTO; acct = purple_accounts_find(acct_id, "prpl-mbpurple-identica"); } src = g_hash_table_lookup(params, "src"); if(!src) { purple_debug_info(DBGID, "no src specified\n"); switch(proto_id) { case TWITTER_PROTO : src = "api.twitter.com"; break; case IDENTICA_PROTO : src = "identi.ca"; break; } } purple_debug_info(DBGID, "cmd = %s, src = %s\n", cmd, src); while( (*cmd) == '/' ) { cmd++; } if ( acct && (proto_id > 0) ) { purple_debug_info(DBGID, "found account with libtwitter, proto_id = %d\n", proto_id); ma = (MbAccount *)acct->gc->proto_data; /* tw:rep?to=sender */ if (!g_ascii_strcasecmp(cmd, "reply")) { gchar * sender, *tmp; gchar * name_to_reply; mb_status_t msg_id = 0; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, src, acct); purple_debug_info(DBGID, "conv = %p\n", conv); gtkconv = PIDGIN_CONVERSATION(conv); sender = g_hash_table_lookup(params, "to"); tmp = g_hash_table_lookup(params, "id"); if(tmp) { msg_id = strtoull(tmp, NULL, 10); } purple_debug_info(DBGID, "sender = %s, id = %llu\n", sender, msg_id); if(msg_id > 0) { name_to_reply = g_strdup_printf("@%s ", sender); gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, name_to_reply, -1); gtk_widget_grab_focus(GTK_WIDGET(gtkconv->entry)); g_free(name_to_reply); purple_signal_emit(twitgin_plugin, "twitgin-replying-message", proto, msg_id); } return TRUE; } // retweet hack ! if (!g_ascii_strcasecmp(cmd, "rt")) { /* We obsolete this function to use twitter retweet API instead (06-May-2010) gchar * message, * from, * retweet_message; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, src, acct); purple_debug_info(DBGID, "conv = %p\n", conv); gtkconv = PIDGIN_CONVERSATION(conv); message = g_hash_table_lookup(params, "msg"); from = g_hash_table_lookup(params, "from"); decoded_rt = purple_url_decode(message); unescaped_rt = purple_unescape_html(decoded_rt); retweet_message = g_strdup_printf("rt @%s: %s", from, unescaped_rt); gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, retweet_message, -1); gtk_widget_grab_focus(GTK_WIDGET(gtkconv->entry)); g_free(unescaped_rt); g_free(retweet_message); return TRUE; */ gchar * msg_id; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, src, acct); msg_id = g_hash_table_lookup(params, "id"); twitter_retweet_message(ma, msg_id); purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, g_strdup_printf("message %s is retweeted", msg_id), PURPLE_MESSAGE_SYSTEM, time(NULL)); return TRUE; } // favorite hack ! if (!g_ascii_strcasecmp(cmd, "fav")) { gchar * msg_id; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, src, acct); msg_id = g_hash_table_lookup(params, "id"); twitter_favorite_message(ma, msg_id); purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, g_strdup_printf("message %s is favorited", msg_id), PURPLE_MESSAGE_SYSTEM, time(NULL)); return TRUE; } } return FALSE; } #if ! PURPLE_VERSION_CHECK(2, 6, 0) static PurpleNotifyUiOps twitgin_ops; static void *(*saved_notify_uri)(const char *uri); static void * twitgin_notify_uri(const char *uri) { void * retval = NULL; if( (strncmp(uri,"tw:",3)==0) || (strncmp(uri, "idc:", 4) == 0) ) { purple_debug_info(DBGID, "notify hooked: uri=%s\n", uri); purple_got_protocol_handler_uri(uri); } else { retval = saved_notify_uri(uri); } return retval; } #endif /* * Modify sending message in the same way as receiving message * This should be done only for message generated by self */ gboolean twitgin_on_tweet_send(PurpleAccount * account, const char * who, char ** msg, PurpleConversation * conv, PurpleMessageFlags flags) { MbAccount * ma = account->gc->proto_data; char * retval; TwitterMsg twitter_msg; gchar * username = NULL; // Do not edit msg from these if ((!is_twitter_conversation(conv)) || (flags & PURPLE_MESSAGE_SYSTEM) ) { return FALSE; } if (!(flags & PURPLE_MESSAGE_TWITGIN)) { // Twitter msg not from twitgin -> Do not show if (flags & PURPLE_MESSAGE_SEND) { purple_debug_info(DBGID, "data being displayed = %s, from = %s, flags = %x\n", (*msg), who, flags); purple_debug_info(DBGID, "conv account = %s, name = %s, title = %s\n", purple_account_get_username(conv->account), conv->name, conv->title); purple_debug_info(DBGID, "sending text IM\n"); twitter_msg.id = 0; twitter_msg.avatar_url = NULL; // twitter_msg.from = NULL; //< force the plug-in not displaying own name twitter_get_user_host(ma, &username, NULL); twitter_msg.from = username; twitter_msg.msg_txt = (*msg); twitter_msg.msg_time = time(NULL); twitter_msg.flag = 0; twitter_msg.flag |= TW_MSGFLAG_DOTAG; purple_debug_info(DBGID, "going to modify message\n"); retval = twitter_reformat_msg(ma, &twitter_msg, conv); //< do not reply to myself purple_debug_info(DBGID, "new data = %s\n", retval); purple_conv_im_write(PURPLE_CONV_IM(conv), twitter_msg.from, retval, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_TWITGIN | PURPLE_MESSAGE_RAW | PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NICK, twitter_msg.msg_time); g_free(username); // Discard the message, let the reformat message handle it return TRUE; // g_free(*msg); // (*msg) = retval; // return FALSE; /* } else if( (flags & PURPLE_MESSAGE_SYSTEM) || (flags & PURPLE_MESSAGE_NO_LOG) || (flags & PURPLE_MESSAGE_ERROR)) { return FALSE; */ } else if(flags == PURPLE_MESSAGE_RECV) { // discard only receiving message // Pidgin will free all the message in receiving ends purple_debug_info(DBGID, "flags = %x, received %s\n", flags, (*msg)); return TRUE; } } return FALSE; } /* * Copied from pidgin original code to make the same time format */ gchar * format_datetime(PurpleConversation * conv, time_t mtime) { char * mdate = NULL; gboolean show_date; PidginConversation * gtkconv; gtkconv = PIDGIN_CONVERSATION(conv); if (gtkconv->newday == 0) { struct tm *tm = localtime(&mtime); tm->tm_hour = tm->tm_min = tm->tm_sec = 0; tm->tm_mday++; gtkconv->newday = mktime(tm); } show_date = (mtime >= gtkconv->newday) || (time(NULL) > mtime + 20*60); mdate = purple_signal_emit_return_1(pidgin_conversations_get_handle(), "conversation-timestamp", conv, mtime, show_date); if (mdate == NULL) { struct tm *tm = localtime(&mtime); const char *tmp; if (show_date) tmp = purple_date_format_long(tm); else tmp = purple_time_format(tm); mdate = g_strdup_printf("(%s)", tmp); } return mdate; } /* * Hack the message display, redirect from normal process (on displaying event) and push them back */ void twitgin_on_tweet_recv(MbAccount * ta, gchar * name, TwitterMsg * cur_msg) { PurpleConversation * conv; gchar * fmt_txt = NULL, * tmp = NULL; // Create new conversation if none exist conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, name, ta->account); if (conv == NULL) { conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, ta->account, name); } purple_debug_info(DBGID, "raw text msg = ##%s##\n", cur_msg->msg_txt); // relink message text g_markup_escape_text will not translate " " to   tmp = g_markup_escape_text(cur_msg->msg_txt, strlen(cur_msg->msg_txt)); g_free(cur_msg->msg_txt); cur_msg->msg_txt = tmp; fmt_txt = twitter_reformat_msg(ta, cur_msg, conv); purple_debug_info(DBGID, "fmted text msg = ##%s##\n", fmt_txt); purple_conv_im_write(PURPLE_CONV_IM(conv), cur_msg->from, fmt_txt, PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_TWITGIN | PURPLE_MESSAGE_RAW | PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_NICK, cur_msg->msg_time); g_free(fmt_txt); } static void twitter_update_link(MbAccount * ta, GString * msg, char sym, const char * name) { char * user_name; gboolean user_name_eq_name = FALSE; twitter_get_user_host(ta, &user_name, NULL); purple_debug_info(DBGID, "symbol = %c, name = %s, user_name = %s\n", sym, name, user_name); if(strcmp(name, user_name) == 0) { purple_debug_info(DBGID, "name and username is equal\n"); user_name_eq_name = TRUE; } if(user_name_eq_name) g_string_append_printf(msg, ""); if(strcmp(ta->account->protocol_id, "prpl-mbpurple-twitter") == 0) { if(sym == '@') { g_string_append_printf(msg, "@%s",name,name); } else if(sym == '#') { g_string_append_printf(msg, "#%s",name,name); } } else if(strcmp(ta->account->protocol_id, "prpl-mbpurple-identica") == 0) { if(sym == '@') { g_string_append_printf(msg, "@%s",name,name); } else if(sym == '#') { g_string_append_printf(msg, "#%s",name,name); } } else { g_string_append_printf(msg, "%c%s", sym, name); } if(user_name_eq_name) g_string_append_printf(msg, ""); g_free(user_name); } /** * Build a link to status ID base on protocol number * * @param ma MbAccount in action * @param msg message * @param data generic data */ gchar * twitter_build_status_link(MbAccount * ma, const TwitterMsg * msg, gpointer data) { if(strcmp(ma->account->protocol_id, "prpl-mbpurple-twitter") == 0) { return g_strdup_printf("http://twitter.com/%s/status/%llu", msg->from, (unsigned long long)msg->id); } return NULL; } /* * Reformat text message and makes it looks nicer * * @retval newly allocated buffer of string, need to be freed after used */ char * twitter_reformat_msg(MbAccount * ma, const TwitterMsg * msg, PurpleConversation * conv) { gchar * username = NULL; const gchar * uri_txt = mb_get_uri_txt(ma->account); gchar * fmt_txt = NULL; gchar * linkify_txt = NULL; gchar * fav_txt = NULL; gchar * rt_txt = NULL; gchar * datetime_txt = NULL; gchar * displaying_txt = NULL; GString * output; gchar * src = NULL; gchar * name = NULL, *name_color = NULL; gchar sym, old_char, previous_char; int i = 0, j = 0; gboolean from_eq_username = FALSE; //const char * embed_rt_txt = NULL; gboolean reply_link = purple_prefs_get_bool(TW_PREF_REPLY_LINK); const gchar * account = (const gchar *)purple_account_get_username(ma->account); purple_debug_info(DBGID, "%s\n", __FUNCTION__); twitter_get_user_host(ma, &username, NULL); output = g_string_new(""); // tag for the first thing purple_debug_info(DBGID, "checking for tag\n"); if( (msg->flag & TW_MSGFLAG_DOTAG) && ma->tag ) { purple_debug_info(DBGID, "do the tagging of message, for the tag %s\n", ma->tag); if(ma->tag_pos == MB_TAG_PREFIX) { src = g_strdup_printf("%s %s", ma->tag, msg->msg_txt); } else { src = g_strdup_printf("%s %s", msg->msg_txt, ma->tag); } } else { purple_debug_info(DBGID, "not doing the tagging of message\n"); src = g_strdup(msg->msg_txt); } // color of name purple_debug_info(DBGID, "changing colours\n"); if(msg->from) { if( strcmp(msg->from, username) == 0) { from_eq_username = TRUE; purple_debug_info(DBGID, "self generated message, %s, %s\n", msg->from, username); } } // switch colour for ourself if(from_eq_username) { name_color = g_strdup("darkred"); } else { name_color = g_strdup("darkblue"); } g_string_append_printf(output, "", name_color); // self-filter is not possible now // if(strcmp(msg->from, username) != 0) { uri_txt = mb_get_uri_txt(ma->account); if(reply_link && conv && uri_txt) { if(from_eq_username) { g_string_append_printf(output, ""); } /* purple_debug_info(DBGID, "current output = %s\n", output->str); purple_debug_info(DBGID, "url text = %s\n", mb_get_uri_txt(ta->account)); purple_debug_info(DBGID, "conversation name = %s\n", conv_name); purple_debug_info(DBGID, "from = %s\n", msg->from); purple_debug_info(DBGID, "username = %s\n", username); purple_debug_info(DBGID, "id = %llu\n", msg->id); */ if(msg->id > 0) { #if PURPLE_VERSION_CHECK(2, 6, 0) g_string_append_printf(output, "%s:", uri_txt, conv->name, msg->from, account, msg->id, msg->from); #else g_string_append_printf(output, "%s:", uri_txt, conv->name, msg->from, account, msg->id, msg->from); #endif } else { g_string_append_printf(output, "%s:", msg->from); } if(from_eq_username) { g_string_append_printf(output, ""); } } else { g_string_append_printf(output, "%s:", msg->from); } // } g_string_append_printf(output, " "); g_free(name_color); purple_debug_info(DBGID, "display msg = %s\n", output->str); purple_debug_info(DBGID, "source msg = %s\n", src); // now search message text and look for things to highlight previous_char = src[i]; while(src[i] != '\0') { //purple_debug_info(DBGID, "previous_char = %c, src[i] == %c\n", previous_char, src[i]); if( (i == 0 || isspace(previous_char)) && ((src[i] == '@') || (src[i] == '#')) ) { //purple_debug_info(DBGID, "reformat_msg: sym = %c\n", src[i]); sym = src[i]; // if it's a proper name, extract it i++; j = i; while((src[j] != '\0') && (!isspace(src[j]) && !strchr("!@#$%^&*()-=+[]{};:'\"<>?,./`~", src[j]))) { j++; } if(i == j) { // empty string g_string_append_c(output, sym); continue; } old_char = src[j]; src[j] = '\0'; name = &src[i]; twitter_update_link(ma, output, sym, name); src[j] = old_char; i = j; previous_char = src[i-1]; } else { g_string_append_c(output, src[i]); previous_char = src[i]; i++; } } // end of text formatting g_free(username); g_free(src); fmt_txt = g_string_free(output, FALSE); // // Now formatting all links // // need to manually linkify text since we are going to send RAW message linkify_txt = purple_markup_linkify(fmt_txt); if(uri_txt) { // display favorite link, if enabled if( (msg->id > 0) && purple_prefs_get_bool(TW_PREF_FAV_LINK)) { #if PURPLE_VERSION_CHECK(2, 6, 0) fav_txt = g_strdup_printf(" *", uri_txt, conv->name, account, msg->id); #else fav_txt = g_strdup_printf(" *", uri_txt, conv->name, account, msg->id); #endif } // display rt link, if enabled if( (msg->id > 0) && purple_prefs_get_bool(TW_PREF_RT_LINK) && !msg->is_protected) { // text for retweet url //embed_rt_txt = purple_url_encode(msg->msg_txt); //purple_debug_info(DBGID, "url embed text for retweet = ##%s##\n", embed_rt_txt); #if PURPLE_VERSION_CHECK(2, 6, 0) //rt_txt = g_strdup_printf(" rt", uri_txt, conv->name, account, msg->from, embed_rt_txt); rt_txt = g_strdup_printf(" rt", uri_txt, conv->name, account, msg->id); #else //rt_txt = g_strdup_printf(" rt", uri_txt, conv->name, account, msg->from, embed_rt_txt); rt_txt = g_strdup_printf(" rt", uri_txt, conv->name, account, msg->id); #endif } } if(conv && (msg->msg_time > 0)) { gchar * url = twitter_build_status_link(ma, msg, NULL); //display link to message status in the timestamp if((msg->id > 0) && purple_prefs_get_bool(TW_PREF_MS_LINK) && url) { datetime_txt = g_strdup_printf("%s ", msg->from, msg->id, format_datetime(conv, msg->msg_time)); } else { datetime_txt = g_strdup_printf("%s ", format_datetime(conv, msg->msg_time)); } if(url) g_free(url); } displaying_txt = g_strdup_printf("%s%s%s%s", datetime_txt ? datetime_txt : "", linkify_txt, fav_txt ? fav_txt : "", rt_txt ? rt_txt : ""); if(fav_txt) g_free(fav_txt); if(rt_txt) g_free(rt_txt); if(datetime_txt) g_free(datetime_txt); purple_debug_info(DBGID, "displaying text = ##%s##\n", displaying_txt); g_free(linkify_txt); g_free(fmt_txt); return displaying_txt; } #if PURPLE_VERSION_CHECK(2, 6, 0) static gboolean twitgin_url_clicked_cb(GtkIMHtml * imhtml, GtkIMHtmlLink * link) { const gchar * url = gtk_imhtml_link_get_url(link); purple_debug_info(DBGID, "%s called\n", __FUNCTION__); purple_debug_info(DBGID, "url = %s\n", url); purple_got_protocol_handler_uri(url); return TRUE; } static gboolean twitgin_context_menu(GtkIMHtml * imhtml, GtkIMHtmlLink * link, GtkWidget * menu) { purple_debug_info(DBGID, "%s called\n", __FUNCTION__); // Nothing yet T_T return TRUE; } #endif static gboolean plugin_load(PurplePlugin *plugin) { GList *convs = purple_get_conversations(); void *gtk_conv_handle = pidgin_conversations_get_handle(); PurplePlugin * prpl_plugin; GList * plugins; purple_debug_info(DBGID, "plugin loaded\n"); purple_signal_connect(gtk_conv_handle, "conversation-displayed", plugin, PURPLE_CALLBACK(on_conversation_display), NULL); /* purple_signal_connect(gtk_conv_handle, "conversation-hiding", plugin, PURPLE_CALLBACK(conversation_hiding_cb), NULL); */ while (convs) { PurpleConversation *conv = (PurpleConversation *)convs->data; /* Setup UI and Events */ if (PIDGIN_IS_PIDGIN_CONVERSATION(conv) && is_twitter_conversation(conv)) { create_twitter_label(PIDGIN_CONVERSATION(conv)); } convs = convs->next; } #if PURPLE_VERSION_CHECK(2, 6, 0) gtk_imhtml_class_register_protocol("tw://", twitgin_url_clicked_cb, twitgin_context_menu); gtk_imhtml_class_register_protocol("idc://", twitgin_url_clicked_cb, twitgin_context_menu); #else memcpy(&twitgin_ops, purple_notify_get_ui_ops(), sizeof(PurpleNotifyUiOps)); saved_notify_uri = twitgin_ops.notify_uri; twitgin_ops.notify_uri = twitgin_notify_uri; purple_notify_set_ui_ops(&twitgin_ops); #endif purple_signal_connect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(twittgin_uri_handler), NULL); purple_signal_connect(pidgin_conversations_get_handle(), "displaying-im-msg", plugin, PURPLE_CALLBACK(twitgin_on_tweet_send), NULL); // twitter // handle all mbpurple plug-in plugins = purple_plugins_get_all(); for(; plugins != NULL; plugins = plugins->next) { prpl_plugin = plugins->data; if( (prpl_plugin->info->id != NULL) && (strncmp(prpl_plugin->info->id, "prpl-mbpurple", 13) == 0)) { purple_debug_info(DBGID, "found plug-in %s\n", prpl_plugin->info->id); purple_signal_connect(prpl_plugin, "twitter-message", plugin, PURPLE_CALLBACK(twitgin_on_tweet_recv), NULL); } } return TRUE; } static gboolean plugin_unload(PurplePlugin *plugin) { GList *convs = purple_get_conversations(); purple_debug_info(DBGID, "plugin unloading\n"); #if ! PURPLE_VERSION_CHECK(2, 6, 0) if(twitgin_notify_uri != purple_notify_get_ui_ops()->notify_uri) { purple_debug_info(DBGID, "ui ops changed, cannot unloading\n"); return FALSE; } #endif while (convs) { PurpleConversation *conv = (PurpleConversation *)convs->data; /* Remove label */ if (PIDGIN_IS_PIDGIN_CONVERSATION(conv) && is_twitter_conversation(conv)) { remove_twitter_label(PIDGIN_CONVERSATION(conv)); } convs = convs->next; } #if PURPLE_VERSION_CHECK(2, 6, 0) gtk_imhtml_class_register_protocol("idc://", NULL, NULL); gtk_imhtml_class_register_protocol("tw://", NULL, NULL); #else twitgin_ops.notify_uri = saved_notify_uri; purple_notify_set_ui_ops(&twitgin_ops); purple_signal_disconnect(purple_get_core(), "uri-handler", plugin, PURPLE_CALLBACK(twittgin_uri_handler)); #endif purple_signal_disconnect(purple_conversations_get_handle(), "displaying-im-msg", plugin, PURPLE_CALLBACK(twitgin_on_tweet_send)); purple_signal_disconnect(pidgin_conversations_get_handle(), "twitgin-message", plugin, PURPLE_CALLBACK(twitgin_on_tweet_recv)); purple_debug_info(DBGID, "plugin unloaded\n"); return TRUE; } static PurplePluginPrefFrame * get_plugin_pref_frame(PurplePlugin *plugin) { PurplePluginPrefFrame *frame; PurplePluginPref *ppref; frame = purple_plugin_pref_frame_new(); ppref = purple_plugin_pref_new_with_name_and_label(TW_PREF_REPLY_LINK, _("Enable reply link")); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label(TW_PREF_FAV_LINK, _("Enable favorite link")); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label(TW_PREF_RT_LINK, _("Enable retweet link")); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label(TW_PREF_MS_LINK, _("Enable message status link")); purple_plugin_pref_frame_add(frame, ppref); /* ppref = purple_plugin_pref_new_with_name_and_label(TW_PREF_AVATAR_SIZE, _("Avatar size")); purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); purple_plugin_pref_add_choice(ppref, "Disabled", GINT_TO_POINTER(0)); purple_plugin_pref_add_choice(ppref, "8x8", GINT_TO_POINTER(8)); purple_plugin_pref_add_choice(ppref, "12x12", GINT_TO_POINTER(12)); purple_plugin_pref_add_choice(ppref, "16x16", GINT_TO_POINTER(16)); purple_plugin_pref_add_choice(ppref, "24x24", GINT_TO_POINTER(24)); purple_plugin_pref_add_choice(ppref, "32x32", GINT_TO_POINTER(32)); purple_plugin_pref_add_choice(ppref, "48x48", GINT_TO_POINTER(48)); purple_plugin_pref_frame_add(frame, ppref); */ /* ppref = purple_plugin_pref_new_with_label("integer"); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/int", "integer pref"); purple_plugin_pref_set_bounds(ppref, 0, 255); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/int_choice", "integer choice"); purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); purple_plugin_pref_add_choice(ppref, "One", GINT_TO_POINTER(1)); purple_plugin_pref_add_choice(ppref, "Two", GINT_TO_POINTER(2)); purple_plugin_pref_add_choice(ppref, "Four", GINT_TO_POINTER(4)); purple_plugin_pref_add_choice(ppref, "Eight", GINT_TO_POINTER(8)); purple_plugin_pref_add_choice(ppref, "Sixteen", GINT_TO_POINTER(16)); purple_plugin_pref_add_choice(ppref, "Thirty Two", GINT_TO_POINTER(32)); purple_plugin_pref_add_choice(ppref, "Sixty Four", GINT_TO_POINTER(64)); purple_plugin_pref_add_choice(ppref, "One Hundred Twenty Eight", GINT_TO_POINTER(128)); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_label("string"); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/string", "string pref"); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/masked_string", "masked string"); purple_plugin_pref_set_masked(ppref, TRUE); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/max_string", "string pref\n(max length of 16)"); purple_plugin_pref_set_max_length(ppref, 16); purple_plugin_pref_frame_add(frame, ppref); ppref = purple_plugin_pref_new_with_name_and_label( "/plugins/core/pluginpref_example/string_choice", "string choice"); purple_plugin_pref_set_type(ppref, PURPLE_PLUGIN_PREF_CHOICE); purple_plugin_pref_add_choice(ppref, "red", "red"); purple_plugin_pref_add_choice(ppref, "orange", "orange"); purple_plugin_pref_add_choice(ppref, "yellow", "yellow"); purple_plugin_pref_add_choice(ppref, "green", "green"); purple_plugin_pref_add_choice(ppref, "blue", "blue"); purple_plugin_pref_add_choice(ppref, "purple", "purple"); purple_plugin_pref_frame_add(frame, ppref); */ return frame; } void plugin_destroy(PurplePlugin * plugin) { purple_debug_info("twitgin", "plugin_destroy\n"); purple_signal_unregister(plugin, "twitgin-replying-message"); } static PurplePluginUiInfo prefs_info = { get_plugin_pref_frame, 0, /* page_num (Reserved) */ NULL, /* frame (Reserved) */ /* Padding */ NULL, NULL, NULL, NULL }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, /**< major version */ PURPLE_MINOR_VERSION, /**< minor version */ PURPLE_PLUGIN_STANDARD, /**< type */ PIDGIN_PLUGIN_TYPE, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ PURPLE_PRIORITY_DEFAULT, /**< priority */ "gtktwitgin", /**< id */ "Twitgin", /**< name */ MBPURPLE_VERSION, /**< version */ "Twitter Conversation.", /**< summary */ "Support Microblog-purple " "in the conversation window.", /**< description */ "Chanwit Kaewkasi ", /**< author */ "http://microblog-purple.googlecode.com", /**< homepage */ plugin_load, /**< load */ plugin_unload, /**< unload */ plugin_destroy, /**< destroy */ NULL, /**< ui_info */ NULL, /**< extra_info */ &prefs_info, /**< prefs_info */ NULL, /**< actions */ /* padding */ NULL, NULL, NULL, NULL }; static void plugin_init(PurplePlugin *plugin) { // default plug-in preference purple_prefs_add_none(TW_PREF_PREFIX); purple_prefs_add_bool(TW_PREF_REPLY_LINK, TRUE); purple_prefs_add_bool(TW_PREF_FAV_LINK, TRUE); purple_prefs_add_bool(TW_PREF_RT_LINK, TRUE); purple_prefs_add_bool(TW_PREF_MS_LINK, TRUE); purple_prefs_add_int(TW_PREF_AVATAR_SIZE, 24); purple_signal_register(plugin, "twitgin-replying-message", purple_marshal_POINTER__POINTER_INT64, purple_value_new(PURPLE_TYPE_POINTER), 2, purple_value_new(PURPLE_TYPE_POINTER), // protocol name (tw or idc) purple_value_new(PURPLE_TYPE_INT64) // status ID ); twitgin_plugin = plugin; } PURPLE_INIT_PLUGIN(twitgin, plugin_init, info) mbpurple-0.3.0/twitgin/twitpref.h0000644000175000017500000000250011400373247016414 0ustar somsaksomsak/* Copyright 2008, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * Preference constant for Twitgin */ #ifndef __TWITPREF__ #define __TWITPREF__ #define TW_PREF_PREFIX "/plugins/core/twitgin" #define TW_PREF_REPLY_LINK TW_PREF_PREFIX "/reply_link" #define TW_PREF_FAV_LINK TW_PREF_PREFIX "/fav_link" #define TW_PREF_RT_LINK TW_PREF_PREFIX "/rt_link" #define TW_PREF_MS_LINK TW_PREF_PREFIX "/ms_link" #define TW_PREF_AVATAR_SIZE TW_PREF_PREFIX "/avatar_size" #endif mbpurple-0.3.0/twitgin/Makefile0000644000175000017500000000313211400373247016041 0ustar somsaksomsak# # Microblog protocol plug-in # all: build include ../global.mak TARGETS = twitgin$(PLUGIN_SUFFIX) LD = $(CC) ifeq ($(strip $(IS_WIN32)), 1) TWITGIN_INC_PATHS += -I$(GTK_TOP)/include/gtk-2.0 \ -I$(GTK_TOP)/include/pango-1.0 \ -I$(GTK_TOP)/include/atk-1.0 \ -I$(GTK_TOP)/include/cairo \ -I$(GTK_TOP)/lib/gtk-2.0/include \ -I$(PIDGIN_TOP)/win32 \ -I../microblog/ LIB_PATHS += -L$(GTK_TOP)/lib \ -L$(PURPLE_TOP) \ -L$(PIDGIN_TOP) LIBS = -lgtk-win32-2.0 \ -lglib-2.0 \ -lgdk-win32-2.0 \ -lgobject-2.0 \ -lintl \ -lpurple \ -lpidgin CFLAGS := $(PURPLE_CFLAGS) $(TWITGIN_INC_PATHS) else CFLAGS := $(PURPLE_CFLAGS) $(PIDGIN_CFLAGS) -I../microblog/ LIB_PATHS = LIBS = $(PIDGIN_LIBS) endif TWITGIN_C_SRC = twitgin.c ../microblog/twitter.c ../microblog/tw_util.c ../microblog/mb_net.c ../microblog/mb_http.c ../microblog/mb_util.c ../microblog/mb_cache.c ../microblog/mb_oauth.c TWITGIN_H_SRC = $(TWITGIN_C_SRC:%.c=%.h) TWITGIN_OBJ = $(TWITGIN_C_SRC:%.c=%.o) DISTFILES = twitgin.c twitpref.h Makefile OBJECTS = $(TWITGIN_OBJ) .PHONY: clean install build build: $(TARGETS) install: $(TARGETS) rm -f $(PURPLE_PLUGIN_DIR)/twitgin$(PLUGIN_SUFFIX) install -m 0755 -d $(PURPLE_PLUGIN_DIR) cp twitgin$(PLUGIN_SUFFIX) $(PURPLE_PLUGIN_DIR)/twitgin$(PLUGIN_SUFFIX) uninstall: rm -f $(PURPLE_PLUGIN_DIR)/twitgin$(PLUGIN_SUFFIX) clean: rm -f $(TARGETS) $(OBJECTS) twitgin$(PLUGIN_SUFFIX): $(TWITGIN_OBJ) $(LD) $(LDFLAGS) -shared $(TWITGIN_OBJ) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o twitgin$(PLUGIN_SUFFIX) mbpurple-0.3.0/global.mak0000644000175000017500000000704111400373247014651 0ustar somsaksomsak# # Global header for all makefile # -include ../version.mak # PIDGIN_TREE_TOP is only meaningful on Windows, point it to top directory of Pidgin. IT MUST BE A RELATIVE PATH # It MUST realitve to each subdirectory (microblog, twitgin) PIDGIN_TREE_TOP := ../../pidgin-2.6.6 # For Linux DESTDIR := # Prefix and Libdir is defined below # Is this WIN32? IS_WIN32 = $(shell (uname -a | grep -q -i cygwin) && echo 1 || echo 0) # for override those attributes -include ../local.mak ################## # Windows build section ################## ifeq ($(strip $(IS_WIN32)), 1) # WIN32 # Use makefile and headers supplied by Pidgin PLUGIN_SUFFIX := .dll EXE_SUFFIX := .exe #include global makefile for WIN32 build include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak ## ## INCLUDE PATHS ## INCLUDE_PATHS += -I. \ -I$(GTK_TOP)/include \ -I$(GTK_TOP)/include/glib-2.0 \ -I$(GTK_TOP)/lib/glib-2.0/include \ -I$(PURPLE_TOP) \ -I$(PURPLE_TOP)/win32 \ -I$(PIDGIN_TREE_TOP) \ -I$(PIDGIN_TOP) \ -I$(GTK_TOP)/include/gtk-2.0 \ -I$(GTK_TOP)/include/pango-1.0 \ -I$(GTK_TOP)/include/atk-1.0 \ -I$(GTK_TOP)/include/cairo \ -I$(GTK_TOP)/lib/gtk-2.0/include \ -I$(PIDGIN_TOP)/win32 \ LIBS += -lglib-2.0 \ -lintl \ -lws2_32 \ -lpurple \ $(PIDGIN_TOP)/pidgin$(PLUGIN_SUFFIX) PURPLE_LIBS = -L$(GTK_TOP)/lib -L$(PURPLE_TOP) $(LIBS) PURPLE_CFLAGS = -g -DPURPLE_PLUGINS -DENABLE_NLS -Wall -DMBPURPLE_VERSION=\"$(VERSION)$(SUBVERSION)\" $(INCLUDE_PATHS) PURPLE_PROTOCOL_PIXMAP_DIR = $(PURPLE_INSTALL_DIR)/pixmaps/pidgin/protocols PURPLE_PLUGIN_DIR = $(PURPLE_INSTALL_PLUGINS_DIR) PURPLE_CACERTS_DIR := $(PURPLE_INSTALL_DIR)/ca-certs #include $(PIDGIN_COMMON_RULES) ################## # Linux build is below ################## else IS_PIDGIN = $(shell pkg-config --atleast-version=2.0 pidgin && echo 1 || echo 0) IS_CARRIER = $(shell pkg-config --atleast-version=2.0 carrier && echo 1 || echo 0) ifeq ($(strip $(IS_PIDGIN)), 1) PIDGIN_NAME := pidgin else ifeq ($(strip $(IS_CARRIER)), 1) PIDGIN_NAME := carrier endif endif PREFIX := $(shell pkg-config --variable=prefix $(PIDGIN_NAME) 2> /dev/null || echo /usr) LIBDIR := $(shell pkg-config --variable=libdir $(PIDGIN_NAME) 2> /dev/null || echo $(PREFIX)/lib) # LINUX and others, use pkg-config PURPLE_LIBS = $(shell pkg-config --libs purple) PURPLE_DATAROOT_DIR = $(shell pkg-config --variable=datarootdir purple) PURPLE_CFLAGS = $(CFLAGS) -DPURPLE_PLUGINS -DENABLE_NLS -DMBPURPLE_VERSION=\"$(VERSION)$(SUBVERSION)\" PURPLE_CFLAGS += $(shell pkg-config --cflags purple) PURPLE_CFLAGS += $(shell pkg-config --cflags pidgin) #PURPLE_CFLAGS += -Wall -pthread -I. -g -O2 -pipe -fPIC -DPIC PURPLE_CFLAGS += -Wall -pthread -I. -g -pipe -fPIC -DPIC PLUGIN_SUFFIX := .so EXE_SUFFIX := PURPLE_PROTOCOL_PIXMAP_DIR := $(DESTDIR)$(PREFIX)/share/pixmaps/pidgin/protocols PURPLE_PLUGIN_DIR := $(DESTDIR)$(LIBDIR)/purple-2 PURPLE_CACERTS_DIR := $(DESTDIR)$(PURPLE_DATAROOT_DIR)/purple/ca-certs PIDGIN_LIBS = $(shell pkg-config --libs $(PIDGIN_NAME)) PIDGIN_CFLAGS = $(CFLAGS) -DPIDGIN_PLUGINS -DENABLE_NLS -DMBPURPLE_VERSION=\"$(VERSION)$(SUBVERSION)\" PIDGIN_CFLAGS += $(shell pkg-config --cflags $(PIDGIN_NAME)) #PIDGIN_CFLAGS += -Wall -pthread -I. -g -O2 -pipe -fPIC -DPIC PIDGIN_CFLAGS += -Wall -pthread -I. -g -pipe -fPIC -DPIC LDFLAGS := $(shell (echo $(PIDGIN_CFLAGS) $(PURPLE_CFLAGS) $(OAUTH_CFLAGS) | tr ' ' '\n' | awk '!a[$$0]++' | tr '\n' ' ')) endif dist: $(DISTFILES) mkdir ../$(PACKAGE)-$(VERSION)$(SUBVERSION)/`basename $$PWD` cp -f $(DISTFILES) ../$(PACKAGE)-$(VERSION)$(SUBVERSION)/`basename $$PWD`/ mbpurple-0.3.0/subversion.mak0000644000175000017500000000074211400373247015611 0ustar somsaksomsak# Quick makefile hack to update the sub-version number from the svn release # # Make sure that svn is installed. SVN = $(shell which svn 2> /dev/null) SUBV = \2 ifneq ($(strip $(SVN)),) SUBV = $(shell svn info 2>&1 | grep Revision | awk '{print "svn" $$2}') subversion: -@sed -i 's/\(^SUBVERSION :=\)(.*)/\1 $(SUBV)/' version.mak else subversion: endif # For releases (and non-svn) we just strip the sub-version out release: -@sed -i 's/\(^SUBVERSION :=\).*/\1/' version.mak mbpurple-0.3.0/microblog/0000755000175000017500000000000011400373247014672 5ustar somsaksomsakmbpurple-0.3.0/microblog/mb_oauth.c0000644000175000017500000003075611400373247016647 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * Implementation of OAuth support */ #include #include #include #include #include #include "twitter.h" #include "mb_net.h" #include "mb_http.h" #include "mb_util.h" #include "mb_oauth.h" #define DBGID "mboauth" static const char fixed_headers[] = "User-Agent:" TW_AGENT "\r\n" \ "Accept: */*\r\n" \ "X-Twitter-Client: " TW_AGENT_SOURCE "\r\n" \ "X-Twitter-Client-Version: 0.1\r\n" \ "X-Twitter-Client-Url: " TW_AGENT_DESC_URL "\r\n" \ "Connection: Close\r\n" \ "Pragma: no-cache\r\n"; static MbConnData * mb_oauth_init_connection(MbAccount * ma, int type, const gchar * path, MbHandlerFunc handler, gchar ** full_url); static gchar * mb_oauth_gen_nonce(void); static gchar * mb_oauth_sign_hmac_sha1(const gchar * data, const gchar * key); static gchar * mb_oauth_gen_sigbase(MbHttpData * data, const gchar * url, int type); static gint mb_oauth_request_token_handler(MbConnData * conn_data, gpointer data, const char * error); void mb_oauth_init(struct _MbAccount * ma, const gchar * c_key, const gchar * c_secret) { MbOauth * oauth = &ma->oauth; oauth->c_key = g_strdup(c_key); oauth->c_secret = g_strdup(c_secret); oauth->oauth_token = NULL; oauth->oauth_secret = NULL; oauth->pin = NULL; oauth->ma = ma; // XXX: Should we put this other places instead? srand(time(NULL)); } void mb_oauth_set_token(struct _MbAccount * ma, const gchar * oauth_token, const gchar * oauth_secret) { MbOauth * oauth = &ma->oauth; if(oauth->oauth_token) g_free(oauth->oauth_token); oauth->oauth_token = g_strdup(oauth_token); if(oauth->oauth_secret) g_free(oauth->oauth_secret); oauth->oauth_secret = g_strdup(oauth_secret); } void mb_oauth_set_pin(struct _MbAccount * ma, const gchar * pin) { gchar * tmp, *new; if(ma->oauth.pin) g_free(ma->oauth.pin); if(!pin) { return; } tmp = g_strdup(pin); new = g_strstrip(tmp); ma->oauth.pin = g_strdup(new); g_free(tmp); } void mb_oauth_free(struct _MbAccount * ma) { MbOauth * oauth = &ma->oauth; if(oauth->c_key) g_free(oauth->c_key); if(oauth->c_secret) g_free(oauth->c_secret); if(oauth->oauth_token) g_free(oauth->oauth_token); if(oauth->oauth_secret) g_free(oauth->oauth_secret); if(oauth->pin) g_free(oauth->pin); oauth->c_key = NULL; oauth->c_secret = NULL; oauth->oauth_token = NULL; oauth->oauth_secret = NULL; } /** * Initialize conn_data structure * * @param ma MbAccount in action * @param type type (HTTP_GET or HTTP_POST) * @param path path in URL * @param handler callback when connection established * @param full_url if not NULL, create the full url in this variable (need to be freed) */ static MbConnData * mb_oauth_init_connection(MbAccount * ma, int type, const gchar * path, MbHandlerFunc handler, gchar ** full_url) { MbConnData * conn_data = NULL; gboolean use_https = purple_account_get_bool(ma->account, mc_name(TC_USE_HTTPS), mc_def_bool(TC_USE_HTTPS)); gint retry = purple_account_get_int(ma->account, mc_name(TC_GLOBAL_RETRY), mc_def_int(TC_GLOBAL_RETRY)); gint port; gchar * user = NULL, * host = NULL; if(use_https) { port = TW_HTTPS_PORT; } else { port = TW_HTTP_PORT; } twitter_get_user_host(ma, &user, &host); if(full_url) { (*full_url) = mb_url_unparse(host, 0, path, NULL, use_https); } conn_data = mb_conn_data_new(ma, host, port, handler, use_https); mb_conn_data_set_retry(conn_data, retry); conn_data->request->type = type; if(type == HTTP_POST) { mb_http_data_set_content_type(conn_data->request, "application/x-www-form-urlencoded"); } conn_data->request->port = port; mb_http_data_set_host(conn_data->request, host); mb_http_data_set_path(conn_data->request, path); // XXX: Use global here -> twitter_fixed_headers mb_http_data_set_fixed_headers(conn_data->request, fixed_headers); mb_http_data_set_header(conn_data->request, "Host", host); if(user) g_free(user); if(host) g_free(host); return conn_data; } /** * generate a random string between 15 and 32 chars length * and return a pointer to it. The value needs to be freed by the * caller * * @return zero terminated random string. * @note this function was taken from by liboauth */ static gchar * mb_oauth_gen_nonce(void) { char *nc; const char *chars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; unsigned int max = strlen( chars ); int i, len; len = 15 + floor(rand()*16.0/(double)RAND_MAX); nc = g_malloc(len+1); for(i=0;iparams_len + 1); mb_http_data_encode_param(data, param_str, data->params_len, TRUE); purple_debug_info(DBGID, "final merged param string = %s\n", param_str); encoded_url = g_strdup(purple_url_encode(url)); encoded_param = g_strdup(purple_url_encode(param_str)); g_free(param_str); retval = g_strdup_printf("%s&%s&%s", type_str, encoded_url, encoded_param); g_free(encoded_url); g_free(encoded_param); return retval; } static void _do_oauth(struct _MbAccount * ma, const gchar * path, int type, MbOauthResponse func, gpointer data, MbHandlerFunc handler) { MbConnData * conn_data = NULL; gchar * full_url = NULL; conn_data = mb_oauth_init_connection(ma, type, path, handler, &full_url); mb_oauth_set_http_data(&ma->oauth, conn_data->request, full_url, type); // Set the call back function ma->oauth.response_func = func; // Initiate connection conn_data->handler_data = ma; mb_conn_process_request(conn_data); } static gint mb_oauth_request_token_handler(MbConnData * conn_data, gpointer data, const char * error) { MbAccount * ma = (MbAccount *)data; GList * it; MbHttpParam * param = NULL; gint retval = 0; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); purple_debug_info(DBGID, "got response %s\n", conn_data->response->content->str); if(error) { return -1; } if(conn_data->response->status == HTTP_OK) { purple_debug_info(DBGID, "going to decode the received message\n"); // Decode the parameter first mb_http_data_decode_param_from_content(conn_data->response); purple_debug_info(DBGID, "message decoded\n"); // fill-in all necessary parameter if(ma->oauth.oauth_token != NULL) { g_free(ma->oauth.oauth_token); } if(ma->oauth.oauth_secret != NULL) { g_free(ma->oauth.oauth_secret); } ma->oauth.oauth_token = ma->oauth.oauth_secret = NULL; for(it = g_list_first(conn_data->response->params); it; it = g_list_next(it)) { param = (MbHttpParam *)it->data; if(strcmp(param->key, "oauth_token") == 0) { ma->oauth.oauth_token = g_strdup(param->value); } else if(strcmp(param->key, "oauth_token_secret") == 0) { ma->oauth.oauth_secret = g_strdup(param->value); } if(ma->oauth.oauth_token && ma->oauth.oauth_secret) { break; } } } // no else, leave the error detection to callback function below // now call the user-defined function to get PIN or whatever we need to request for access token // Let the func do the job, since the caller should know the URL if(ma && ma->oauth.response_func) { retval = ma->oauth.response_func(ma, conn_data, data); } purple_debug_info(DBGID, "return value = %d\n", retval); return retval; } void mb_oauth_request_token(struct _MbAccount * ma, const gchar * path, int type, MbOauthResponse func, gpointer data) { if(ma->oauth.oauth_token) g_free(ma->oauth.oauth_token); if(ma->oauth.oauth_secret) g_free(ma->oauth.oauth_secret); ma->oauth.oauth_token = ma->oauth.oauth_secret = NULL; _do_oauth(ma, path, type, func, data, mb_oauth_request_token_handler); } void mb_oauth_request_access(struct _MbAccount * ma, const gchar * path, int type, MbOauthResponse func, gpointer data) { _do_oauth(ma, path, type, func, data, mb_oauth_request_token_handler); } // void mb_oauth_set_http_data(MbOauth * oauth, struct _MbHttpData * http_data, const gchar * full_url, int type) { gchar * nonce = NULL, * sig_base = NULL, * secret = NULL, * signature = NULL; // Attach OAuth data mb_http_data_add_param(http_data, "oauth_consumer_key", oauth->c_key); nonce = mb_oauth_gen_nonce(); mb_http_data_add_param(http_data, "oauth_nonce", nonce); g_free(nonce); mb_http_data_add_param(http_data, "oauth_signature_method", "HMAC-SHA1"); mb_http_data_add_param_ull(http_data, "oauth_timestamp", time(NULL)); mb_http_data_add_param(http_data, "oauth_version", "1.0"); if(oauth->oauth_token && oauth->oauth_secret) { mb_http_data_add_param(http_data, "oauth_token", oauth->oauth_token); } if(oauth->pin) { mb_http_data_add_param(http_data, "oauth_verifier", oauth->pin); } mb_http_data_sort_param(http_data); // Create signature sig_base = mb_oauth_gen_sigbase(http_data, full_url, type); purple_debug_info(DBGID, "got signature base = %s\n", sig_base); secret = g_strdup_printf("%s&%s", oauth->c_secret, oauth->oauth_secret ? oauth->oauth_secret : ""); signature = mb_oauth_sign_hmac_sha1(sig_base, secret); g_free(secret); g_free(sig_base); purple_debug_info(DBGID, "signed signature = %s\n", signature); // Attach to parameter mb_http_data_add_param(http_data, "oauth_signature", signature); g_free(signature); } void mb_oauth_reset_nonce(MbOauth * oauth, struct _MbHttpData * http_data, const gchar * full_url, int type) { gchar * nonce, * secret, * sig_base, * signature; mb_http_data_rm_param(http_data, "oauth_nonce"); mb_http_data_rm_param(http_data, "oauth_signature"); nonce = mb_oauth_gen_nonce(); mb_http_data_add_param(http_data, "oauth_nonce", nonce); g_free(nonce); // Re-Create signature sig_base = mb_oauth_gen_sigbase(http_data, full_url, type); purple_debug_info(DBGID, "got signature base = %s\n", sig_base); secret = g_strdup_printf("%s&%s", oauth->c_secret, oauth->oauth_secret ? oauth->oauth_secret : ""); signature = mb_oauth_sign_hmac_sha1(sig_base, secret); g_free(secret); g_free(sig_base); purple_debug_info(DBGID, "signed signature = %s\n", signature); // Attach to parameter mb_http_data_add_param(http_data, "oauth_signature", signature); g_free(signature); } mbpurple-0.3.0/microblog/mb_cache.h0000644000175000017500000000213711400373247016567 0ustar somsaksomsak/* * Cache user information locally and make it available for other Twitgin part * * Created on: Apr 18, 2010 * Author: somsak */ #ifndef __MBPURPLE_CACHE__ #define __MBPURPLE_CACHE__ #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #ifdef __cplusplus extern "C" { #endif #include #include #include typedef struct { gchar * user_name; //< owner of this cache entry time_t last_update; //< Last cache data update time_t last_use; //< Last use of this data int avatar_img_id; //< imgstore id gchar * avatar_path; //< path name storing this avatar gpointer avatar_data; //< pointer to image buffer, will be freed by imgstore } MbCacheEntry; typedef struct { // Mapping between cache entry and data inside // A mapping between MbAccount and TGCacheGroup GHashTable * data; gboolean fetcher_is_run; int avatar_fetch_max; } MbCache; #ifdef __cplusplus } #endif #endif //< MBPURPLE_CACHE mbpurple-0.3.0/microblog/tw_cmd.c0000644000175000017500000002053611400373247016321 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * Command support for twitter */ #include #include #include "tw_cmd.h" #define DBGID "tw_cmd" typedef struct { const gchar * cmd; const gchar * args; PurpleCmdPriority prio; PurpleCmdFlag flag; TwCmdFunc func; void * data; const gchar * help; } TwCmdEnum ; static PurpleCmdRet tw_cmd_replies(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_refresh(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_refresh_rate(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_caller(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, void * data); static PurpleCmdRet tw_cmd_tag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_btag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_untag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static PurpleCmdRet tw_cmd_set_tag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data, gint position); static PurpleCmdRet tw_cmd_get_user_tweets(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data); static TwCmdEnum tw_cmd_enum[] = { {"replies", "", PURPLE_CMD_P_PRPL, 0, tw_cmd_replies, NULL, "get replies timeline."}, {"refresh", "", PURPLE_CMD_P_PRPL, 0, tw_cmd_refresh, NULL, "refresh tweets now."}, {"refresh_rate", "w", PURPLE_CMD_P_PRPL, 0, tw_cmd_refresh_rate, NULL, "set refresh rate. /refreshrate 120 temporariy change refresh rate to 120 seconds"}, {"tag", "w", PURPLE_CMD_P_PRPL, 0, tw_cmd_tag, NULL, "prepend everything with a specified tag, unset with /untag"}, {"btag", "w", PURPLE_CMD_P_PRPL, 0, tw_cmd_btag, NULL, "append everything with a specified tag, unset with /untag"}, {"untag", "", PURPLE_CMD_P_PRPL, 0, tw_cmd_untag, NULL, "unset already set tag"}, {"get", "w", PURPLE_CMD_P_PRPL, 0, tw_cmd_get_user_tweets, NULL, "get specific user timeline. Use /get to fetch."}, }; PurpleCmdRet tw_cmd_tag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { return tw_cmd_set_tag(conv, cmd, args, error, data, MB_TAG_PREFIX); } PurpleCmdRet tw_cmd_btag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { return tw_cmd_set_tag(conv, cmd, args, error, data, MB_TAG_POSTFIX); } PurpleCmdRet tw_cmd_set_tag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data, gint position) { purple_debug_info(DBGID, "%s called\n", __FUNCTION__); if(data->ma->tag) { g_free(data->ma->tag); } data->ma->tag = g_strdup(args[0]); data->ma->tag_pos = position; return PURPLE_CMD_RET_OK; } PurpleCmdRet tw_cmd_untag(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { MbAccount * ma = data->ma; if(ma->tag) { g_free(ma->tag); ma->tag = NULL; ma->tag_pos = MB_TAG_NONE; } else { serv_got_im(ma->gc, mc_def(TC_FRIENDS_USER), _("no tag is being set"), PURPLE_MESSAGE_SYSTEM, time(NULL)); } return PURPLE_CMD_RET_OK; } PurpleCmdRet tw_cmd_replies(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { const gchar * path; TwitterTimeLineReq * tlr; int count; time_t now; MbAccount * ma = data->ma; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); path = purple_account_get_string(ma->account, mc_name(TC_REPLIES_TIMELINE), mc_def(TC_REPLIES_TIMELINE)); count = 20; //< FIXME: Hardcoded number of count tlr = twitter_new_tlr(path, mc_def(TC_REPLIES_USER), TL_REPLIES, count, _("end reply messages")); tlr->use_since_id = FALSE; time(&now); serv_got_im(ma->gc, tlr->name, _("getting reply messages"), PURPLE_MESSAGE_SYSTEM, now); twitter_fetch_new_messages(ma, tlr); return PURPLE_CMD_RET_OK; } PurpleCmdRet tw_cmd_refresh_rate(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { char * end_ptr = NULL; int new_rate = -1; MbAccount * ma = data->ma; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); new_rate = (int)strtol(args[0], &end_ptr, 10); if( (*end_ptr) == '\0' ) { if(new_rate > 10) { purple_account_set_int(ma->account, mc_name(TC_MSG_REFRESH_RATE), new_rate); return PURPLE_CMD_RET_OK; } else { serv_got_im(ma->gc, mc_def(TC_FRIENDS_USER), _("new rate is too low, must be > 10 seconds"), PURPLE_MESSAGE_SYSTEM, time(NULL)); return PURPLE_CMD_RET_FAILED; } } return PURPLE_CMD_RET_FAILED; } PurpleCmdRet tw_cmd_refresh(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { twitter_fetch_all_new_messages(data->ma); return PURPLE_CMD_RET_OK; } PurpleCmdRet tw_cmd_get_user_tweets(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, TwCmdArg * data) { const gchar * path; TwitterTimeLineReq * tlr; int count; time_t now; MbAccount * ma = data->ma; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); path = purple_account_get_string(ma->account, mc_name(TC_USER_TIMELINE), mc_def(TC_USER_TIMELINE)); count = 20; //< FIXME: Hardcoded number of count // This will spawn another timeline //tlr = twitter_new_tlr(path, mc_def(TC_USER_USER), TL_USER, count, _("end user messages")); // This will fetch user tweets into the same timeline tlr = twitter_new_tlr(path, mc_def(TC_REPLIES_USER), TL_REPLIES, count, _("end user messages")); tlr->use_since_id = FALSE; tlr->screen_name = args[0]; time(&now); serv_got_im(ma->gc, tlr->name, _("getting user messages"), PURPLE_MESSAGE_SYSTEM, now); twitter_fetch_new_messages(ma, tlr); return PURPLE_CMD_RET_OK; } /* * Convenient proxy for calling real function */ PurpleCmdRet tw_cmd_caller(PurpleConversation * conv, const gchar * cmd, gchar ** args, gchar ** error, void * data) { TwCmdArg * tca = data; purple_debug_info(DBGID, "%s called for cmd = %s\n", __FUNCTION__, cmd); tca->ma = conv->account->gc->proto_data; return tca->func(conv, cmd, args, error, tca); } TwCmd * tw_cmd_init(const char * protocol_id) { int i, len = sizeof(tw_cmd_enum)/sizeof(TwCmdEnum); TwCmd * tw_cmd; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); tw_cmd = g_new(TwCmd, 1); tw_cmd->protocol_id = g_strdup(protocol_id); tw_cmd->cmd_id_num = len; tw_cmd->cmd_args = g_malloc0(sizeof(TwCmdArg *) * tw_cmd->cmd_id_num); tw_cmd->cmd_id = g_malloc(sizeof(PurpleCmdId) * tw_cmd->cmd_id_num); for(i = 0; i < len; i++) { tw_cmd->cmd_args[i] = g_new0(TwCmdArg, 1); tw_cmd->cmd_args[i]->func = tw_cmd_enum[i].func; tw_cmd->cmd_args[i]->data = tw_cmd_enum[i].data; tw_cmd->cmd_id[i] = purple_cmd_register(tw_cmd_enum[i].cmd, tw_cmd_enum[i].args, tw_cmd_enum[i].prio, tw_cmd_enum[i].flag | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY, protocol_id, tw_cmd_caller, tw_cmd_enum[i].help, tw_cmd->cmd_args[i] ); purple_debug_info(DBGID, "command %s registered\n", tw_cmd_enum[i].cmd); } return tw_cmd; } void tw_cmd_finalize(TwCmd * tc) { int i; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); for(i = 0; i < tc->cmd_id_num; i++) { purple_cmd_unregister(tc->cmd_id[i]); g_free(tc->cmd_args[i]); } g_free(tc->protocol_id); g_free(tc); } mbpurple-0.3.0/microblog/statusnet16.png0000644000175000017500000000162311400373247017603 0ustar somsaksomsak‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYsòòÎ{ÞtIMEÚÙ~ºëIDAT8Ë•“_hÕeÆŸïû¾çüvÎÎÖ™íOÎÔ"Óêä"k¦x¡¤ QLdTdP!êeÕ…‹XDki[fƒ ܺqÒTÒœ3Í-cÛ±¶sŽ;ö{¿ßû§‹p—eŸ»x¾_°áøepÏ 7ÖÕìPÆÆ Uùµ6ö¯áÝëtÛøÙ©6„C"â´õµÆ®öSØÒ÷3‹EB=ñ¨óbˆ€ÉBõÆÍbeÈ)œëz ¢û§õˆùÝY‘µ$ŸWÚo²†Mê@~Ñ=ØökOέ!Þùj6…LC/ô_¸WÓ!l0"*K.i~ÇÕs|åre T ¡ÁßTZ¾Çħ¹ÂŠ™³SÅtÕ 0W‘À„2¥*ôléäa·—ÈA¦q'ê#«0“Åøôq,T]U.ùíƒÞ'k*¾îކX Z«÷Gºž´ ¤*îsŽÖæ·°*µ ayãàÃéKˆÀW/K¿òº`Ñg"Ð{v^þ²°úæ%¸Ï>Ž 'Î@heÖjB:ѾxS!sÏf|?rÖÚ5Q;+³Æ^ynjè ã™ßÚy¥õÑþì'„tƒyÆXREÄxjñ}U9 .¬µ ßô–ÿîô¸Ìy:¹©Õ銣g7V¤÷oßÚð 1æÑ²Ì!l#é0tñ0\ÖÚ¾¥ÇŒ¬1ìðÃGÞEKWš¶oCqü å®\]+¬‡ÕmÌ %úŠÛ¯M#_þ ž_1çìŒm±â­ë"çˆeÁÂÉoÓ,Qß|#ÏlLÖ¯a äK×Ë,§Ød…KDOU ~™<;ÑÛ £Ü|ûàÔ…Ï|5¼ Ž`,77‰È²ª{kÊqœé¹âLœ˜Ùg¤ùf釨#Kþ¾h2o¾ «=·>U»™þmû{ÂÝÖ#mmSêÇðŸÎ¸9tk¦ò6TŠ6D¶“#† wÀ{G[¡Î($F¸W £fï½zÝÞQÀm>o¹u\ì(ùÞ×kkÊÇÆð¿ùhY:|,sߢþÈâm«Ö»IEND®B`‚mbpurple-0.3.0/microblog/identica48.png0000644000175000017500000000400211400373247017330 0ustar somsaksomsak‰PNG  IHDR00Wù‡sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÚ º0)‚IDAThÞí™{PT÷Ç?¿{wÙeW× 6Øhµ&ÑúˆI£i`œŒØ&錓(­i¦£`3c“Îdø£mÒNR1iÿ4M&“4ŽÓúd¦QC,4ÕøÂ8ÅQYaeß÷×?öáÝ ZDÖš)g†?~çw9÷|Ï9÷¼ÆiœÆéMb¤J ;kVL‘ Ø€DÀ\Gˆ Å…Ÿwq¨<ô˜Š[]¬–3 ZÄp äÇBð×â¢úÁû@eUþ<„ü#°0Ž@^h^•B«úiQƒö?PQ³BR¬ÊË(äjÀŸTEn]_XﺧvÖ䤤ø5 ¿3,¤$ÚHŸ0 S‚ÀCß ¾ÁV<¾~¤”±â>Q…öâú¢÷=ðç½ÂoÔž*ô–7¨ffÚç¡ìgH·æ¢5JÈ «“‹Wð•}/ƒ®ÎØw¼%„akqÑ‘¸„“AðµC–(o1¥³lîf²'¯@eX!ɉ™ÌŸõY™Ñxv;W»#‰xãçRúµñÑhGU lrô!óøÃ¯“cË¿¥òzš˜œEþ‚_19}žmÊ*æYâ @Aækõ—æ®Ç­Ì%‹)es7“hJÕ³Uq€O“ÂÇÔäfO/•ÐÔ”äN/Ôb ±ÆSÕù*P ¿˜9åû £÷úƒSWaP£’Ø¢UyÖ¸hà[ú‹)Ü•à‰É9XLézVš&ä´8…0ë¡’džt×ÂS,SôÇDˆ(ú¢%„ÂX4f1™KD}sc @Á8"}€æÃíqܵð!w·þè•‚¡xe!Ю¿èìk¾;å== ¸:ô¬~!eg\*±K$y¥ó àéð…½£oO_¢F%ØÞQ¯»_SÓ}hW\“´Vgõ6°G@K)È1é…*«óu¡ÊÉ´³jÑoQÄp¹zÙ÷úZ®¿ß€ìñ §¢z÷”•€óvòÊöžFÕeL1ôù6Êa ™„³ÀÉðyjÆÂQ)ïsqäåŸÑ¹í3d·çVö@ð Uå:ÜColÌy£ºi‡É蹤(J—SK;ÿFuÓ;oV7eÝ5y6$GŠPY³b©)3îLyßu[K9[^œAÃ/QÔ„‚€×‹ bÿõ€k7ƒàwûÐTC!ðQhŒ¥nÏšTõ°AçÔ9áF.5eI‰“¢<îõ”$“¾6F4/=Žó|±ov|¥¼uæLÙ²…©Ë—#ŒFº¾ü’“ï¾Kç±cH-Òa?­ÁóÀûšj˜ 캅òH>ôø´%†`'º\O…5ËL›QMÄåéåÆÀ.wþƒk='RÖñ0r1›R „šk='éì9ÃÐÇÍÈ!ÿÍÒ;gkjkIÉÊŠðÒçÌ!§¨ˆ¿¯[ÇÅšš0X#°±>)7ð09Ò[YÌLš`¡ÃáÄáò„Ù6„|É*8I@þÍ D€ú3¿§óF3ýÎ+úÞžÞÁÖá·}^_õG…MÁöíQÊGúëÔTòËËi¯¯ÇãˆÔ›Ùfÿ¦úè9à‰HKb1ñ㥑lN ÓádÏ¿þ͠Ǿ^ê…˜ÌsÏÙ÷ÓÒV‹ÃÙ¥üm×.ƒ~´îˆu˜méÒ[>o5‹ÌE‹¢X@–"ÕDý@e³&“l6 L°`µ˜£B)œ…ž§Ïa¶ Çý@Ïmøeïͩќ‘b¸}³dfÆfÄ$ÍpWfZ“pû‚aÙÞ;@¯3j¼¾b¨¨)0 µ•údjT ɿĥ mBˆeÀ÷€, )´º œô7tµ»d€»€Çƒj2Ý@ßùó±Ær(Fu2icÿyñ*Ç[;H1'pÃéÂí‹Ê`û R X„KÏ€}H"h3½žuO5énvï¨*Ø-„¦ P¤R"W×ÉmÁ—Ú¹CׯӲgßÙ°aXåÛëëé:}ZÏêìžCùp\Þ õÜÞX礻 ¢|÷'…u]#‰õ«ëÂÖŠ2…z¬p( àó’¬99L+(@¨jdGÙÝÜ̧6pG…à Ï{ûú¼ò¬?ôgÉÛp )Ÿ}mõ’Þ1Ýfn *HŸÕ„f­Y㯼BæÂ… \¾Ìžü|ú[£²ÙðCwMãI¤X"Á®Ù*‘^¤ò"°24î^ †¶ö—W‹–9îh¹;zD^ÞŠM óæñLu5m‡óiq±¾ˆ·xPº)Æ«#!u,Ô?€S[‚Å@ä êêÂÙÞεÆF—.ErP ü²$6ñ[¯ßa(©!·¿Z ÂÅ [(>, õ@÷ d`°x!æúoÀJƒ^›ÕâXSiÐÊ €3¦OêÚ÷Ýýw©<ñ²‡£þ༽ ¸® êÊÆt3G* zã°9”2kî±­ri%´(`‘p` \ä›Hoƒùí‘ý\5Nã4NÿôNQ¤éõ#TŸIEND®B`‚mbpurple-0.3.0/microblog/twitter.c0000644000175000017500000010676011400373247016552 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include #include #include "mb_net.h" #include "mb_util.h" #include "mb_cache_util.h" #ifdef _WIN32 # include #else # include # include # include #endif #include "twitter.h" #define DBGID "twitter" #define TW_ACCT_LAST_MSG_ID "twitter_last_msg_id" #define TW_ACCT_SENT_MSG_IDS "twitter_sent_msg_ids" const char * mb_auth_types_str[] = { "mb_oauth", "mb_xauth", "mb_http_basicauth", }; static const char twitter_fixed_headers[] = "User-Agent:" TW_AGENT "\r\n" \ "Accept: */*\r\n" \ "X-Twitter-Client: " TW_AGENT_SOURCE "\r\n" \ "X-Twitter-Client-Version: 0.1\r\n" \ "X-Twitter-Client-Url: " TW_AGENT_DESC_URL "\r\n" \ "Connection: Close\r\n" \ "Pragma: no-cache\r\n"; PurplePlugin * twitgin_plugin = NULL; static MbConnData * twitter_init_connection(MbAccount * ma, gint type, const char * path, MbHandlerFunc handler); static gint twitter_oauth_prepare(MbConnData * conn_data, gpointer data, const char * error); void twitter_request_access(MbAccount * ma); gint twitter_request_authorize(MbAccount * ma, MbConnData * data, gpointer user_data); void twitter_request_authorize_ok_cb(MbAccount * ma, const char * pin); gint twitter_oauth_request_finish(MbAccount * ma, MbConnData * data, gpointer user_data); void twitter_verify_account(MbAccount * ma, gpointer data); gint twitter_verify_authen(MbConnData * conn_data, gpointer data, const char * error); /** * Convenient function to initialize new connection and set necessary value */ // static MbConnData * twitter_init_connection(MbAccount * ma, gint type, const char * path, MbHandlerFunc handler) { MbConnData * conn_data = NULL; gboolean use_https = purple_account_get_bool(ma->account, mc_name(TC_USE_HTTPS), mc_def_bool(TC_USE_HTTPS)); gint retry = purple_account_get_int(ma->account, mc_name(TC_GLOBAL_RETRY), mc_def_int(TC_GLOBAL_RETRY)); gint port; gchar * user_name = NULL, * host = NULL; const char * password; if(use_https) { port = TW_HTTPS_PORT; } else { port = TW_HTTP_PORT; } twitter_get_user_host(ma, &user_name, &host); password = purple_account_get_password(ma->account); conn_data = mb_conn_data_new(ma, host, port, handler, use_https); mb_conn_data_set_retry(conn_data, retry); conn_data->request->type = type; conn_data->request->port = port; mb_http_data_set_host(conn_data->request, host); mb_http_data_set_path(conn_data->request, path); // XXX: Use global here -> twitter_fixed_headers mb_http_data_set_fixed_headers(conn_data->request, twitter_fixed_headers); mb_http_data_set_header(conn_data->request, "Host", host); switch(ma->auth_type) { case MB_OAUTH : case MB_XAUTH : // attach oauth header with this connection if(ma->oauth.oauth_token && ma->oauth.oauth_secret) { conn_data->prepare_handler = twitter_oauth_prepare; conn_data->prepare_handler_data = ma; } break; default : // basic auth is default mb_http_data_set_basicauth(conn_data->request, user_name, password); break; } if(user_name) g_free(user_name); if(host) g_free(host); return conn_data; } static gint twitter_oauth_prepare(MbConnData * conn_data, gpointer data, const char * error) { MbAccount * ma = (MbAccount *)data; gchar * full_url; // error is always NULL here full_url = mb_conn_url_unparse(conn_data); if(conn_data->retry <= 0) { mb_oauth_set_http_data(&ma->oauth, conn_data->request, full_url, conn_data->request->type); } else { mb_oauth_reset_nonce(&ma->oauth, conn_data->request, full_url, conn_data->request->type); } g_free(full_url); return 0; } static TwitterBuddy * twitter_new_buddy() { TwitterBuddy * buddy = g_new(TwitterBuddy, 1); buddy->ma = NULL; buddy->buddy = NULL; buddy->uid = -1; buddy->name = NULL; buddy->status = NULL; buddy->thumb_url = NULL; return buddy; } TwitterTimeLineReq * twitter_new_tlr(const char * path, const char * name, int id, int count, const char * sys_msg) { TwitterTimeLineReq * tlr = g_new(TwitterTimeLineReq, 1); tlr->path = g_strdup(path); tlr->name = g_strdup(name); tlr->count = count; tlr->timeline_id = id; tlr->use_since_id = TRUE; tlr->screen_name = NULL; if(sys_msg) { tlr->sys_msg = g_strdup(sys_msg); } else { tlr->sys_msg = NULL; } return tlr; } void twitter_free_tlr(TwitterTimeLineReq * tlr) { if(tlr->path != NULL) g_free(tlr->path); if(tlr->name != NULL) g_free(tlr->name); if(tlr->sys_msg != NULL) g_free(tlr->sys_msg); g_free(tlr); } GList * twitter_statuses(PurpleAccount *acct) { GList *types = NULL; PurpleStatusType *status; //Online people have a status message and also a date when it was set //status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), "message_date", _("Message changed"), purple_value_new(PURPLE_TYPE_STRING), NULL); status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, NULL, _("Unavailable"), TRUE, TRUE, FALSE); types = g_list_append(types, status); //Offline people dont have messages status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, _("Offline"), TRUE, TRUE, FALSE); types = g_list_append(types, status); return types; } void twitter_buddy_free(PurpleBuddy * buddy) { TwitterBuddy * tbuddy = buddy->proto_data; if(tbuddy) { if(tbuddy->name) g_free(tbuddy->name); if(tbuddy->status) g_free(tbuddy->status); if(tbuddy->thumb_url) g_free(tbuddy->thumb_url); g_free(tbuddy); buddy->proto_data = NULL; } } // Privacy mode skip fetching messages during unavailable state gboolean twitter_skip_fetching_messages(PurpleAccount * acct) { MbAccount * ma = (MbAccount *)acct->gc->proto_data; gboolean privacy_mode = purple_account_get_bool(acct, mc_name(TC_PRIVACY), mc_def_bool(TC_PRIVACY)); gboolean available = purple_status_is_available(purple_account_get_active_status(acct)); if(privacy_mode && !available) { purple_debug_info(DBGID, "Unavailable, skipping fetching due privacy mode\n"); return TRUE; } if(!purple_privacy_check(acct, mc_def(TC_FRIENDS_USER))) { purple_debug_info(DBGID, "Privacy block, skipping fetching due privacy mode\n"); return TRUE; } return FALSE; } // Function to fetch first batch of new message void twitter_fetch_first_new_messages(MbAccount * ma) { TwitterTimeLineReq * tlr; const gchar * tl_path; int count; if(twitter_skip_fetching_messages(ma->account)) { return; } purple_debug_info(DBGID, "%s called\n", __FUNCTION__); tl_path = purple_account_get_string(ma->account, mc_name(TC_FRIENDS_TIMELINE), mc_def(TC_FRIENDS_TIMELINE)); count = purple_account_get_int(ma->account, mc_name(TC_INITIAL_TWEET), mc_def_int(TC_INITIAL_TWEET)); purple_debug_info(DBGID, "count = %d\n", count); tlr = twitter_new_tlr(tl_path, mc_def(TC_FRIENDS_USER), TL_FRIENDS, count, NULL); twitter_fetch_new_messages(ma, tlr); } // Function to fetch all new messages periodically gboolean twitter_fetch_all_new_messages(gpointer data) { MbAccount * ma = data; TwitterTimeLineReq * tlr = NULL; gint i; const gchar * tl_path; if(twitter_skip_fetching_messages(ma->account)) { return TRUE; } for(i = TC_FRIENDS_TIMELINE; i <= TC_USER_TIMELINE; i+=2) { //FIXME: i + 1 is not a very good strategy here if(!purple_find_buddy(ma->account, mc_def(i + 1))) { purple_debug_info(DBGID, "skipping %s\n", tlr->name); continue; } tl_path = purple_account_get_string(ma->account, mc_name(i), mc_def(i)); tlr = twitter_new_tlr(tl_path, mc_def(i + 1), i, TW_STATUS_COUNT_MAX, NULL); purple_debug_info(DBGID, "fetching updates from %s to %s\n", tlr->path, tlr->name); twitter_fetch_new_messages(ma, tlr); } return TRUE; } #if 0 static void twitter_list_sent_id_hash(gpointer key, gpointer value, gpointer user_data) { purple_debug_info(DBGID, "key/value = %s/%s\n", key, value); } #endif // // Decode error message from twitter // char * twitter_decode_error(const char * data) { xmlnode * top = NULL, * error = NULL; gchar * error_str = NULL; top = xmlnode_from_str(data, -1); if(top == NULL) { purple_debug_info(DBGID, "failed to parse XML data from error response\n"); return NULL; } error = xmlnode_get_child(top, "error"); if(error) { error_str = xmlnode_get_data_unescaped(error); } xmlnode_free(top); return error_str; } // // Decode timeline message // GList * twitter_decode_messages(const char * data, time_t * last_msg_time) { GList * retval = NULL; xmlnode * top = NULL, *id_node, *time_node, *status, * text, * user, * user_name, * image_url, * user_is_protected; gchar * from, * msg_txt, * avatar_url = NULL, *xml_str = NULL, * is_protected = NULL; TwitterMsg * cur_msg = NULL; mb_status_t cur_id; time_t msg_time_t; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); top = xmlnode_from_str(data, -1); if(top == NULL) { purple_debug_info(DBGID, "failed to parse XML data\n"); return NULL; } purple_debug_info(DBGID, "successfully parse XML\n"); status = xmlnode_get_child(top, "status"); purple_debug_info(DBGID, "timezone = %ld\n", timezone); while(status) { msg_txt = NULL; from = NULL; xml_str = NULL; //skip = FALSE; // ID id_node = xmlnode_get_child(status, "id"); if(id_node) { xml_str = xmlnode_get_data_unescaped(id_node); } cur_id = strtoull(xml_str, NULL, 10); g_free(xml_str); // time time_node = xmlnode_get_child(status, "created_at"); if(time_node) { xml_str = xmlnode_get_data_unescaped(time_node); } purple_debug_info(DBGID, "msg time = %s\n", xml_str); msg_time_t = mb_mktime(xml_str); if( (*last_msg_time) < msg_time_t) { (*last_msg_time) = msg_time_t; } g_free(xml_str); // message text = xmlnode_get_child(status, "text"); if(text) { msg_txt = xmlnode_get_data_unescaped(text); } // user name user = xmlnode_get_child(status, "user"); if(user) { user_name = xmlnode_get_child(user, "screen_name"); if(user_name) { from = xmlnode_get_data(user_name); } image_url = xmlnode_get_child(user, "profile_image_url"); if(image_url) { avatar_url = xmlnode_get_data(image_url); } user_is_protected = xmlnode_get_child(user, "protected"); if(user_is_protected) { is_protected = xmlnode_get_data(user_is_protected); } } if(from && msg_txt) { cur_msg = g_new(TwitterMsg, 1); purple_debug_info(DBGID, "from = %s, msg = %s\n", from, msg_txt); cur_msg->id = cur_id; cur_msg->from = from; cur_msg->avatar_url = avatar_url; //< actually we don't need this for now cur_msg->msg_time = msg_time_t; if(is_protected && (strcmp(is_protected, "false") == 0) ) { cur_msg->is_protected = FALSE; g_free(is_protected); } else { cur_msg->is_protected = TRUE; } cur_msg->flag = 0; /* if(skip) { cur_msg->flag |= TW_MSGFLAG_SKIP; } */ cur_msg->msg_txt = msg_txt; //purple_debug_info(DBGID, "appending message with id = %llu\n", cur_id); retval = g_list_append(retval, cur_msg); } status = xmlnode_get_next_twin(status); } xmlnode_free(top); return retval; } gint twitter_fetch_new_messages_handler(MbConnData * conn_data, gpointer data, const char * error) { MbAccount * ma = conn_data->ma; const gchar * username; MbHttpData * response = conn_data->response; TwitterTimeLineReq * tlr = data; time_t last_msg_time_t = 0; GList * msg_list = NULL, *it = NULL; TwitterMsg * cur_msg = NULL; gboolean hide_myself; gchar * id_str = NULL, * msg_txt = NULL; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); purple_debug_info(DBGID, "received result from %s\n", tlr->path); if(error) { // we don't want to handle network error return 0; } username = (const gchar *)purple_account_get_username(ma->account); if(response->status == HTTP_MOVED_TEMPORARILY) { // no new messages twitter_free_tlr(tlr); purple_debug_info(DBGID, "no new messages\n"); return 0; } if(response->status != HTTP_OK) { twitter_free_tlr(tlr); if((response->status == HTTP_BAD_REQUEST) || (response->status == HTTP_UNAUTHORIZE)) { // rate limit exceed? if(response->content_len > 0) { gchar * error_str = NULL; error_str = twitter_decode_error(response->content->str); if(ma->gc != NULL) { // XXX: Do we need to use conn_error here? Since we will return 0 below... mb_conn_error(conn_data, PURPLE_CONNECTION_ERROR_OTHER_ERROR, error_str); } g_free(error_str); } return 0; //< return 0 so the request is not being issued again } else { purple_debug_info(DBGID, "something's wrong with the message?, status = %d\n", response->status); return 0; //< should we return -1 instead? } } if(response->content_len == 0) { purple_debug_info(DBGID, "no data to parse\n"); twitter_free_tlr(tlr); return 0; } purple_debug_info(DBGID, "http_data = #%s#\n", response->content->str); msg_list = twitter_decode_messages(response->content->str, &last_msg_time_t); if(msg_list == NULL) { twitter_free_tlr(tlr); return 0; } // reverse the list and append it // only if id > last_msg_id hide_myself = purple_account_get_bool(ma->account, mc_name(TC_HIDE_SELF), mc_def_bool(TC_HIDE_SELF)); msg_list = g_list_reverse(msg_list); for(it = g_list_first(msg_list); it; it = g_list_next(it)) { cur_msg = it->data; purple_debug_info(DBGID, "**twitpocalypse** cur_msg->id = %llu, ma->last_msg_id = %llu\n", cur_msg->id, ma->last_msg_id); if(cur_msg->id > ma->last_msg_id) { ma->last_msg_id = cur_msg->id; mb_account_set_ull(ma->account, TW_ACCT_LAST_MSG_ID, ma->last_msg_id); } id_str = g_strdup_printf("%llu", cur_msg->id); if(!(hide_myself && (g_hash_table_remove(ma->sent_id_hash, id_str) == TRUE))) { msg_txt = g_strdup_printf("%s: %s", cur_msg->from, cur_msg->msg_txt); // we still call serv_got_im here, so purple take the message to the log serv_got_im(ma->gc, tlr->name, msg_txt, PURPLE_MESSAGE_RECV, cur_msg->msg_time); // by handling diaplying-im-msg, the message shouldn't be displayed anymore purple_signal_emit(mc_def(TC_PLUGIN), "twitter-message", ma, tlr->name, cur_msg); g_free(msg_txt); } g_free(id_str); g_free(cur_msg->msg_txt); g_free(cur_msg->from); g_free(cur_msg->avatar_url); g_free(cur_msg); it->data = NULL; } if(ma->last_msg_time < last_msg_time_t) { ma->last_msg_time = last_msg_time_t; } g_list_free(msg_list); if(tlr->sys_msg) { serv_got_im(ma->gc, tlr->name, tlr->sys_msg, PURPLE_MESSAGE_SYSTEM, time(NULL)); } twitter_free_tlr(tlr); return 0; } // // Check for new message periodically // void twitter_fetch_new_messages(MbAccount * ma, TwitterTimeLineReq * tlr) { MbConnData * conn_data; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); conn_data = twitter_init_connection(ma, HTTP_GET, tlr->path, twitter_fetch_new_messages_handler); if(tlr->count > 0) { purple_debug_info(DBGID, "tlr->count = %d\n", tlr->count); mb_http_data_add_param_int(conn_data->request, "count", tlr->count); } if(tlr->use_since_id && (ma->last_msg_id > 0) ) { mb_http_data_add_param_ull(conn_data->request, "since_id", ma->last_msg_id); } if(tlr->screen_name != NULL) { mb_http_data_add_param(conn_data->request, "screen_name", tlr->screen_name); } conn_data->handler_data = tlr; mb_conn_process_request(conn_data); } // // Generate 'fake' buddy list for Twitter // For now, we only add TwFriends, TwUsers, and TwPublic void twitter_get_buddy_list(MbAccount * ma) { PurpleBuddy *buddy; TwitterBuddy *tbuddy; PurpleGroup *twitter_group = NULL; purple_debug_info(DBGID, "buddy list for account %s\n", ma->account->username); //Check if the twitter group already exists twitter_group = purple_find_group(mc_def(TC_USER_GROUP)); // Add timeline as "fake" user // Is TL_FRIENDS already exist? if ( (buddy = purple_find_buddy(ma->account, mc_def(TC_FRIENDS_USER))) == NULL) { purple_debug_info(DBGID, "creating new buddy list for %s\n", mc_def(TC_FRIENDS_USER)); buddy = purple_buddy_new(ma->account, mc_def(TC_FRIENDS_USER), NULL); if (twitter_group == NULL) { purple_debug_info(DBGID, "creating new Twitter group\n"); twitter_group = purple_group_new(mc_def(TC_USER_GROUP)); purple_blist_add_group(twitter_group, NULL); } purple_debug_info(DBGID, "setting protocol-specific buddy information to purplebuddy\n"); if(buddy->proto_data == NULL) { tbuddy = twitter_new_buddy(); buddy->proto_data = tbuddy; tbuddy->buddy = buddy; tbuddy->ma = ma; tbuddy->uid = TL_FRIENDS; tbuddy->name = g_strdup(mc_def(TC_FRIENDS_USER)); } purple_blist_add_buddy(buddy, NULL, twitter_group, NULL); } purple_prpl_got_user_status(ma->account, buddy->name, purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE), NULL); // We'll deal with public and users timeline later } MbAccount * mb_account_new(PurpleAccount * acct) { MbAccount * ma = NULL; const char * auth_type; int i; const gchar * oauth_token = NULL, * oauth_secret = NULL; purple_debug_info(DBGID, "%s\n", __FUNCTION__); ma = g_new(MbAccount, 1); ma->account = acct; ma->gc = acct->gc; ma->state = PURPLE_CONNECTING; ma->timeline_timer = -1; ma->last_msg_id = mb_account_get_ull(acct, TW_ACCT_LAST_MSG_ID, 0); ma->last_msg_time = 0; ma->conn_data_list = NULL; ma->sent_id_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); ma->tag = NULL; ma->tag_pos = MB_TAG_NONE; ma->reply_to_status_id = 0; ma->mb_conf = _mb_conf; // Cache // ma->cache = mb_cache_new(); // Auth Type if(mc_name(TC_AUTH_TYPE)) { // Only twitter has this configuration auth_type = purple_account_get_string(acct, mc_name(TC_AUTH_TYPE), mc_def(TC_AUTH_TYPE)); if(auth_type) { for(i = 0; i < MB_AUTH_MAX; i++) { if(strcmp(mb_auth_types_str[i], auth_type) == 0) { ma->auth_type = i; break; } } } purple_debug_info(DBGID, "auth_type = %d\n", ma->auth_type); } else { // if there's no configuration, then it means the protocol didn't support it // fall back to http basic authentication ma->auth_type = MB_HTTP_BASICAUTH; } // Oauth stuff mb_oauth_init(ma, mc_def(TC_CONSUMER_KEY), mc_def(TC_CONSUMER_SECRET)); oauth_token = purple_account_get_string(ma->account, mc_name(TC_OAUTH_TOKEN), NULL); oauth_secret = purple_account_get_string(ma->account, mc_name(TC_OAUTH_SECRET), NULL); if(oauth_token && oauth_secret && (strlen(oauth_token) > 0) && (strlen(oauth_secret) > 0)) { mb_oauth_set_token(ma, oauth_token, oauth_secret); } acct->gc->proto_data = ma; return ma; } /* static void mb_close_connection(gpointer key, gpointer value, gpointer user_data) { MbConnData *conn_data = value; purple_debug_info(DBGID, "closing each connection\n"); if(conn_data) { purple_debug_info(DBGID, "we have %p -> %p\n", key, value); mb_conn_data_free(conn_data); } } */ static gboolean foreach_remove_expire_idhash(gpointer key, gpointer val, gpointer userdata) { MbAccount * ma = (MbAccount *)userdata; mb_status_t msg_id; msg_id = strtoull(key, NULL, 10); if(ma->last_msg_id >= msg_id) { purple_debug_info(DBGID, "removing %s since it is less than %llu\n", (gchar *)key, ma->last_msg_id); return TRUE; } else { return FALSE; } } void mb_account_free(MbAccount * ma) { guint num_remove; //purple_debug_info(DBGID, "mb_account_free\n"); purple_debug_info(DBGID, "%s\n", __FUNCTION__); // Remove cache // mb_cache_free(ma->cache); ma->mb_conf = NULL; ma->cache = NULL; mb_oauth_free(ma); if(ma->tag) { g_free(ma->tag); ma->tag = NULL; } ma->tag_pos = MB_TAG_NONE; ma->state = PURPLE_DISCONNECTED; if(ma->timeline_timer != -1) { purple_debug_info(DBGID, "removing timer\n"); purple_timeout_remove(ma->timeline_timer); } while(ma->conn_data_list) { purple_debug_info(DBGID, "free-up connection with fetch_url_data = %p\n", ((MbConnData *)ma->conn_data_list->data)->fetch_url_data); mb_conn_data_free(ma->conn_data_list->data); // don't need to delete the list, it will be deleted by conn_data_free eventually } num_remove = g_hash_table_foreach_remove(ma->sent_id_hash, foreach_remove_expire_idhash, ma); purple_debug_info(DBGID, "%u key removed\n", num_remove); mb_account_set_idhash(ma->account, TW_ACCT_SENT_MSG_IDS, ma->sent_id_hash); if(ma->sent_id_hash) { purple_debug_info(DBGID, "destroying sent_id hash\n"); g_hash_table_destroy(ma->sent_id_hash); ma->sent_id_hash = NULL; } ma->account = NULL; ma->gc = NULL; purple_debug_info(DBGID, "free up memory used for microblog account structure\n"); g_free(ma); } void twitter_login(PurpleAccount *acct) { MbAccount *ma = NULL; purple_debug_info(DBGID, "twitter_login\n"); // Create account data ma = mb_account_new(acct); purple_debug_info(DBGID, "creating id hash for sentid\n"); mb_account_get_idhash(acct, TW_ACCT_SENT_MSG_IDS,ma->sent_id_hash); twitter_request_access(ma); // connect to twitgin here purple_debug_info(DBGID, "looking for twitgin\n"); twitgin_plugin = purple_plugins_find_with_id("gtktwitgin"); if(twitgin_plugin) { purple_debug_info(DBGID, "registering twitgin-replying-message signal\n"); purple_signal_connect(twitgin_plugin, "twitgin-replying-message", acct, PURPLE_CALLBACK(twitter_on_replying_message), ma); } } /** * Request for access token, if needed * * @param ma MbAccount in action */ void twitter_request_access(MbAccount * ma) { const gchar * path = NULL; const gchar * oauth_token = NULL, * oauth_secret = NULL; // check if oauth or xauth is already done switch(ma->auth_type) { case MB_OAUTH : // If oauth access request is not done yet, do it now oauth_token = purple_account_get_string(ma->account, mc_name(TC_OAUTH_TOKEN), mc_def(TC_OAUTH_TOKEN)); oauth_secret = purple_account_get_string(ma->account, mc_name(TC_OAUTH_SECRET), mc_def(TC_OAUTH_SECRET)); if(!oauth_token || !oauth_secret || (strlen(oauth_token) <= 0) || (strlen(oauth_secret) <= 0)) { mb_oauth_init(ma, mc_def(TC_CONSUMER_KEY), mc_def(TC_CONSUMER_SECRET)); path = purple_account_get_string(ma->account, mc_name(TC_REQUEST_TOKEN_URL), mc_def(TC_REQUEST_TOKEN_URL)); mb_oauth_request_token(ma, path, HTTP_GET, twitter_request_authorize, NULL); } else { twitter_verify_account(ma, NULL); } break; case MB_XAUTH : // support this later break; default : // default is basic auth, which require no request access twitter_verify_account(ma, NULL); break; } } /* * Redirect user to authorization page and wait for user input */ gint twitter_request_authorize(MbAccount * ma, MbConnData * data, gpointer user_data) { const gchar * request_access_path = NULL; gchar * error_msg = NULL; gchar * user_name, * host, *param = NULL, * full_url; gboolean use_https = FALSE; if( (data->response->status != HTTP_OK) || (!ma->oauth.oauth_token && !ma->oauth.oauth_secret)) { // error if(data->response->content_len > 0) { error_msg = g_strdup(data->response->content->str); } else { error_msg = g_strdup("Unknown error"); } mb_conn_error(data, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, error_msg); g_free(error_msg); return -1; } // process the successfully acquire token request_access_path = purple_account_get_string(ma->account, mc_name(TC_AUTHORIZE_URL), mc_def(TC_AUTHORIZE_URL)); use_https = purple_account_get_bool(ma->account, mc_name(TC_USE_HTTPS), mc_def_bool(TC_USE_HTTPS)); twitter_get_user_host(ma, &user_name, &host); param = g_strdup_printf("oauth_token=%s", ma->oauth.oauth_token); full_url = mb_url_unparse(host, 0, request_access_path, param, use_https); g_free(param); // Open the web browser and redirect user to the authorization page purple_notify_uri((void *)ma->gc, full_url); g_free(full_url); // and wait for user's input for PIN purple_request_input((void *)ma->gc, _("Input your PIN"), _("Please allow " TW_AGENT_SOURCE " to access your account"), _("Please copy the PIN number from the web page"), "", FALSE, FALSE, NULL, _("OK"), G_CALLBACK(twitter_request_authorize_ok_cb), _("Cancel"), NULL, //< We will not handle cancel, user's will have to re-authorize again ma->account, NULL, NULL, ma); g_free(user_name); g_free(host); // Continue in PIN callback return 0; } void twitter_request_authorize_ok_cb(MbAccount * ma, const char * pin) { const gchar * access_token_path = NULL; purple_debug_info(DBGID, "%s called\n", __FUNCTION__); purple_debug_info(DBGID, "got PIN %s\n", pin); // Set the PIN mb_oauth_set_pin(ma, pin); access_token_path = purple_account_get_string(ma->account, mc_name(TC_ACCESS_TOKEN_URL), mc_def(TC_ACCESS_TOKEN_URL)); mb_oauth_request_access(ma, access_token_path, HTTP_POST, twitter_oauth_request_finish, NULL); } /** * Wrapper to call twitter verify account from OAuth realm */ gint twitter_oauth_request_finish(MbAccount * ma, MbConnData * data, gpointer user_data) { if((data->response->status == HTTP_OK) && ma->oauth.oauth_token && ma->oauth.oauth_secret) { if(ma->oauth.pin) { g_free(ma->oauth.pin); ma->oauth.pin = NULL; } purple_account_set_string(ma->account, mc_name(TC_OAUTH_TOKEN), ma->oauth.oauth_token); purple_account_set_string(ma->account, mc_name(TC_OAUTH_SECRET), ma->oauth.oauth_secret); twitter_verify_account(ma, NULL); } else { if(ma->oauth.oauth_token) g_free(ma->oauth.oauth_token); if(ma->oauth.oauth_secret) g_free(ma->oauth.oauth_secret); ma->oauth.oauth_token = ma->oauth.oauth_secret = NULL; purple_connection_error_reason(ma->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Invalid server response"); } return 0; } /** * Verify the account with services * * @param ma MbAccount in action */ void twitter_verify_account(MbAccount * ma, gpointer data) { MbConnData * conn_data = NULL; gchar * path = NULL; path = g_strdup(purple_account_get_string(ma->account, mc_name(TC_VERIFY_PATH), mc_def(TC_VERIFY_PATH))); purple_debug_info(DBGID, "path = %s\n", path); conn_data = twitter_init_connection(ma, HTTP_GET, path, twitter_verify_authen); mb_conn_process_request(conn_data); g_free(path); } /** * Call back for account validity verification * * @param conn_data connection data in progress * @param data MbAccount in action */ gint twitter_verify_authen(MbConnData * conn_data, gpointer data, const char * error) { MbAccount * ma = conn_data->ma; MbHttpData * response = conn_data->response; if(response->content_len > 0) { purple_debug_info(DBGID, "response = %s\n", response->content->str); } if(response->status == HTTP_OK) { gint interval = purple_account_get_int(conn_data->ma->account, mc_name(TC_MSG_REFRESH_RATE), mc_def_int(TC_MSG_REFRESH_RATE)); // extract the username and set it if(response->content_len > 0) { xmlnode * top = NULL, * screen_name = NULL; gchar * screen_name_str = NULL; gchar * user_name = NULL, * host = NULL; top = xmlnode_from_str(response->content->str, -1); if(top != NULL) { screen_name = xmlnode_get_child(top, "screen_name"); if(screen_name) { screen_name_str = xmlnode_get_data_unescaped(screen_name); } } xmlnode_free(top); if(screen_name_str) { purple_debug_info(DBGID, "old username = %s\n", purple_account_get_username(conn_data->ma->account)); twitter_get_user_host(conn_data->ma, &user_name, &host); if(host) { // libpurple host is embed in username, so we need to keep it gchar * tmp; tmp = g_strdup_printf("%s@%s", screen_name_str, host); purple_account_set_username(conn_data->ma->account, tmp); g_free(tmp); } else { purple_account_set_username(conn_data->ma->account, screen_name_str); } g_free(user_name); g_free(host); } else { purple_debug_info(DBGID, "WARNING! will use username in setting instead\n"); } g_free(screen_name_str); } // now prepare for timeline refresher purple_connection_set_state(conn_data->ma->gc, PURPLE_CONNECTED); conn_data->ma->state = PURPLE_CONNECTED; twitter_get_buddy_list(conn_data->ma); purple_debug_info(DBGID, "refresh interval = %d\n", interval); conn_data->ma->timeline_timer = purple_timeout_add_seconds(interval, (GSourceFunc)twitter_fetch_all_new_messages, conn_data->ma); twitter_fetch_first_new_messages(conn_data->ma); return 0; } else { // XXX: Crash at the line below mb_conn_error(conn_data, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "Authentication error"); return -1; } } void twitter_close(PurpleConnection *gc) { MbAccount *ma = gc->proto_data; if(twitgin_plugin) { purple_signal_disconnect(twitgin_plugin, "twitgin-replying-message", ma->account, PURPLE_CALLBACK(twitter_on_replying_message)); } purple_debug_info(DBGID, "twitter_close\n"); if(ma->timeline_timer != -1) { purple_debug_info(DBGID, "removing timer\n"); purple_timeout_remove(ma->timeline_timer); ma->timeline_timer = -1; } //purple_timeout_add(300, (GSourceFunc)twitter_close_timer, ma); mb_account_free(ma); gc->proto_data = NULL; } gint twitter_send_im_handler(MbConnData * conn_data, gpointer data, const char * error) { MbAccount * ma = conn_data->ma; MbHttpData * response = conn_data->response; gchar * id_str = NULL, * who = (gchar *)data; xmlnode * top, *id_node; purple_debug_info(DBGID, "%s\n", __FUNCTION__); if(error) { // don't need to handle network error if(mb_conn_max_retry_reach(conn_data)) { g_free(who); } return -1; } if(response->status != HTTP_OK) { purple_debug_info(DBGID, "http error\n"); if(response->content_len > 0) { purple_debug_info(DBGID, "response = %s\n", response->content->str); } //purple_debug_info(DBGID, "http data = #%s#\n", response->content->str); if(mb_conn_max_retry_reach(conn_data)) { serv_got_im(ma->gc, who, _("error sending status"), PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(who); } return -1; } // We don't need who from this point on g_free(who); if(!purple_account_get_bool(ma->account, mc_name(TC_HIDE_SELF), mc_def_bool(TC_HIDE_SELF))) { return 0; } // Check for returned ID if(response->content->len == 0) { purple_debug_info(DBGID, "can not find http data\n"); return -1; } purple_debug_info(DBGID, "http_data = #%s#\n", response->content->str); // parse response XML top = xmlnode_from_str(response->content->str, -1); if(top == NULL) { purple_debug_info(DBGID, "failed to parse XML data\n"); return -1; } purple_debug_info(DBGID, "successfully parse XML\n"); // ID id_node = xmlnode_get_child(top, "id"); if(id_node) { id_str = xmlnode_get_data_unescaped(id_node); } // save it to account g_hash_table_insert(ma->sent_id_hash, id_str, id_str); //hash_table supposed to free this for use //g_free(id_str); xmlnode_free(top); return 0; } int twitter_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags) { MbAccount * ma = gc->proto_data; MbConnData * conn_data = NULL; gchar * tmp_msg_txt = NULL; gint msg_len; gchar * path; purple_debug_info(DBGID, "%s called, who = %s, message = %s, flag = %d\n", __FUNCTION__, who, message, flags); // prepare message to send tmp_msg_txt = g_strdup(g_strchomp(purple_markup_strip_html(message))); if(ma->tag) { gchar * new_msg_txt; if(ma->tag_pos == MB_TAG_PREFIX) { new_msg_txt = g_strdup_printf("%s %s", ma->tag, tmp_msg_txt); } else { new_msg_txt = g_strdup_printf("%s %s", tmp_msg_txt, ma->tag); } g_free(tmp_msg_txt); tmp_msg_txt = new_msg_txt; } msg_len = strlen(tmp_msg_txt); purple_debug_info(DBGID, "sending message %s\n", tmp_msg_txt); // connection path = g_strdup(purple_account_get_string(ma->account, mc_name(TC_STATUS_UPDATE), mc_def(TC_STATUS_UPDATE))); conn_data = twitter_init_connection(ma, HTTP_POST, path, twitter_send_im_handler); conn_data->handler_data = g_strdup(who); if(ma->reply_to_status_id > 0) { int i; gboolean do_reply = FALSE; // do not add reply tag if the message does not contains @ in the front for(i = 0; i < strlen(message); i++) { if(isspace(message[i])) { continue; } else { if(message[i] == '@') { do_reply = TRUE; } break; } } if(do_reply) { purple_debug_info(DBGID, "setting in_reply_to_status_id = %llu\n", ma->reply_to_status_id); mb_http_data_add_param_ull(conn_data->request, "in_reply_to_status_id", ma->reply_to_status_id); ma->reply_to_status_id = 0; } else { ma->reply_to_status_id = 0; } } // mb_http_data_set_header(conn_data->request, "Content-Type", "application/x-www-form-urlencoded"); mb_http_data_set_content_type(conn_data->request, "application/x-www-form-urlencoded"); mb_http_data_add_param(conn_data->request, "status", tmp_msg_txt); mb_http_data_add_param(conn_data->request, "source", TW_AGENT_SOURCE); /* post_data = g_malloc(TW_MAXBUFF); len = snprintf(post_data, TW_MAXBUFF, "status=%s&source=" TW_AGENT_SOURCE, tmp_msg_txt); mb_http_data_set_content(conn_data->request, post_data, len); */ // g_free(post_data); mb_conn_process_request(conn_data); g_free(path); g_free(tmp_msg_txt); return msg_len; } gchar * twitter_status_text(PurpleBuddy *buddy) { TwitterBuddy * tbuddy = buddy->proto_data; if (tbuddy && tbuddy->status && strlen(tbuddy->status)) return g_strdup(tbuddy->status); return NULL; } // There's no concept of status in TwitterIM for now void twitter_set_status(PurpleAccount *acct, PurpleStatus *status) { const char *msg = purple_status_get_attr_string(status, "message"); purple_debug_info(DBGID, "setting %s's status to %s: %s\n", acct->username, purple_status_get_name(status), msg); } void * twitter_on_replying_message(gchar * proto, mb_status_t msg_id, MbAccount * ma) { purple_debug_info(DBGID, "%s called!\n", __FUNCTION__); purple_debug_info(DBGID, "setting reply_to_id (was %llu) to %llu\n", ma->reply_to_status_id, msg_id); ma->reply_to_status_id = msg_id; return NULL; } /* * Favourite Handler */ void twitter_favorite_message(MbAccount * ma, gchar * msg_id) { // create new connection and call API POST MbConnData * conn_data; gchar * path; path = g_strdup_printf("/favorites/create/%s.xml", msg_id); conn_data = twitter_init_connection(ma, HTTP_POST, path, NULL); mb_conn_process_request(conn_data); g_free(path); } /* * Retweet API Handler */ void twitter_retweet_message(MbAccount * ma, gchar * msg_id) { // create new connection and call API POST MbConnData * conn_data; gchar * path; path = g_strdup_printf("/statuses/retweet/%s.xml", msg_id); conn_data = twitter_init_connection(ma, HTTP_POST, path, NULL); mb_conn_process_request(conn_data); g_free(path); } mbpurple-0.3.0/microblog/statusnet22.png0000644000175000017500000000254711400373247017606 0ustar somsaksomsak‰PNG  IHDRÄ´l;sRGB®ÎébKGDÿÿÿ ½§“ pHYsòòÎ{ÞtIMEÚ-ªºçIDAT8Ë¥•iˆÕUÆŸ³üï½3s½ã¬6i£––¸6S“Ej›‰¸D„Š AÔ‡ÂúVDAMaD¨Ñ‚¥ˆ¨„4£d3¹¤¨i*9n4Î0Ž:û½÷ï9ç¼}05¥/S¿O‡s^Þóò> CdÁÖß ›÷dý{g~¤OÅ\+Mö®zäfŠèœoN O°gŸQx¾2U°¯´(v)RzqÞrxÍÆ£7kåÃÎÓŽH€I‹spÆ8iP¨ù–¢•õÍ aãü%˲VŒ*rpoÒEsÇ`Q_Öÿ"ë´ŽÚoÞ¼¯!wDL–ÝoÙr™6ù)dŒMBœS‘·SúAóúƇ¢m½#XOj­ñZM%¶D‰+°¶­;FDu‚;ä—?ׂ“ë–%G¿ã›þWý(ci­¡µÖô8“Îjø²QEÞòátª·Ó›p%ç‡ôb–À•Œm \3ˆè†cöÍ/u²o®®4¤A†ƒ›h øQ/´VCœÌåùœïsŸÝ§ Jº–†c]ƒŒµ„ÚÌVÌ _3 žqü<#3Ò”´ª·PSý Æ\„’Ä tõžD  ”®Š‚ íé²ï2¨5DS½H{®àóʬq…ÎY][Ž›ëšÜaÖ\"†ú± ¨,žö÷3!íuaÇþ%Hgú‘Íç=*øCøþ³¶à/Ûœ-m»òÄŸ{é®þ6¢È§ÖKV<iˆêŒ!$ìjT¤¦þC bå™zW¯m†žˆ:¹Ÿ[¸ö§·ué{±íÈêHòký}´»BRçÖI©"×–#“wˆÞÂæI¨ÈgÐñºÖ?u¼ifÜ–[Ò—³ÊW€``1áù~ø†Î…eàGíBðqü” `‰Øm¢Boæ W" uHŠúf6m­DÌݨ0•z¸Åõu<{»šâQ~š¢ñØüáÕÆP]¤s0F£²¸Œ]wNdp¡s/N¶mgGâ°1uš½îŸ]6w.&¯ûçMGÙ¬YÉ"t5·°œÖUREæ£øò0P…Ç/nBæ"ÆŽœÁ,´wÀ¹ÎÝ`ŒÀ9ç¬%ÞJPËÃÈE Á¥w/x‡ß}~ÞŸ&ñT{Q¼d“þæ¬ß-/]ÛöîýcŒ!]ÿHÎyLÛ@¤ SÂ@yÞmmS¾ßq(. Ëk;Û‹ãUOWO8›Œ—CpŒ,‘€#R¡@"Í †ùé:nyP-ƒQ€³6 èi 8øA‚0DAi¬™À·{^@"Á™Ñ•Îfk ãÅ“”ÖN&7Ж÷qÁréL߃L˜7AÔTøQ¸ÍRâ?Œ)¨,EÙÔ)è=ÝgΣ $–—ލ¿m¾¶5-³5"Ò,R@ )Ý׃€Æ¡…6æ(½ÉvŒ¦]ý™ñFÀ°¤“MŒˆ-ÓÊ4²¡ìㆯ&À'Å PX±ÛN»—QfÃ=éžÜǵ²vÜšÉö“ã†ÿÁ¦Éã˜É™‰~^s,¾Ö¶.îÚfÉéÖ¡%È('[³?J\·Qö5‡4KN·=šîdʼnp¹4`¬!aÛƒQQÁ˼Ãs%uyÞ¯y™Õǎݼÿ zž~ÈC1àIEND®B`‚mbpurple-0.3.0/microblog/mb_net.h0000644000175000017500000000745311400373247016320 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /* Microblog network processing (mostly for HTTP data) */ #ifndef __MB_NET__ #define __MB_NET__ #include #include "mb_http.h" #include "twitter.h" #ifdef __cplusplus extern "C" { #endif #define MB_NET "mb_net" //typedef TwitterAccount MbAccount; //< for the sake of simplicity for now enum mb_error_action { MB_ERROR_NOACTION = 0, MB_ERROR_RAISE_ERROR = 1, }; // if handler return // 0 - Everything's ok // -1 - Requeue the whole process again struct _MbConnData; typedef gint (*MbHandlerFunc)(struct _MbConnData * , gpointer , const char * error); typedef void (*MbHandlerDataFreeFunc)(gpointer); typedef struct _MbConnData { gchar * host; gint port; MbAccount * ma; gchar * error_message; MbHttpData * request; MbHttpData * response; gint retry; gint max_retry; // Packet preparer, created for OAuth // We will not set prepare_handler back to NULL, so it will be called again when connection is retried // The 3rd argument pass will always be NULL (since no connection has been made yet MbHandlerFunc prepare_handler; gpointer prepare_handler_data; // Connection handler MbHandlerFunc handler; gpointer handler_data; gboolean is_ssl; PurpleUtilFetchUrlData * fetch_url_data; } MbConnData; /* Create new connection data @param ta MbAccount instance @param handler handler function for this request @param is_ssl whether this is SSL or not @return new MbConnData */ extern MbConnData * mb_conn_data_new(MbAccount * ta, const gchar * host, gint port, MbHandlerFunc handler, gboolean is_ssl); /* Free an instance of MbConnData @param conn_data conn_data to free @note connection will be closed if it's still open */ extern void mb_conn_data_free(MbConnData * conn_data); /** * Set maximum number of retry * * @param data MbConnData in action * @param retry number of desired retry */ extern void mb_conn_data_set_retry(MbConnData * data, gint retry); /** * Process the initialize MbConnData * * @param data MbConnData to process */ extern void mb_conn_process_request(MbConnData * data); /** * Call purple_connection_error_reason if this connection was retried more than data->max_retry already * * @param data MbConnData in action * @param error error reason * @param description text description */ extern void mb_conn_error(MbConnData * data, PurpleConnectionError error, const char * description); /** * Create full URL from MbConnData * * @param data MbConnData in action * @return full URL, need to be freed after use */ extern gchar * mb_conn_url_unparse(MbConnData * data); /** * Test if the maximu retry is already reached * * @param data MbConnData in action * @return TRUE if max retry is reached, otherwise false */ extern gboolean mb_conn_max_retry_reach(MbConnData * data); #ifdef __cplusplus } #endif #endif mbpurple-0.3.0/microblog/mb_cache.c0000644000175000017500000001171711400373247016566 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /* * cache.c * * Created on: Apr 18, 2010 * Author: somsak */ #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #ifdef _WIN32 # include #else # include # include # include #endif #include #include "twitter.h" static char cache_base_dir[PATH_MAX] = ""; static void mb_cache_entry_free(gpointer data) { MbCacheEntry * cache_entry = (MbCacheEntry *)data; // unref the image // XXX: Implement later! // free all path reference and user_name g_free(cache_entry->user_name); g_free(cache_entry->avatar_path); } /** * Build a path to the per-user (friend) cache directory * * @param ma MbAccount for the cache * @param user_name friend's user name */ static gchar * build_cache_path(const MbAccount * ma, const gchar * user_name) { gchar * host = NULL, * user = NULL; gchar * retval; // basedir/host/account/username // account may already include host name mb_get_user_host(ma, &user, &host); retval = g_strdup_printf("%s/%s/%s/%s", cache_base_dir, host, user, user_name); g_free(user); g_free(host); return retval; } /* * Read in cache data from file, or ignore it if cache already exists * * @param ma MbAccount entry * @param user_name user name to read cache * @return cache entry, or NULL if none exists */ static MbCacheEntry * read_cache(MbAccount * ma, const gchar * user_name) { MbCacheEntry * cache_entry = NULL; gchar * cache_path = NULL; gchar * cache_avatar_path = NULL; struct stat stat_buf; time_t now; cache_entry = (MbCacheEntry *)g_hash_table_lookup(ma->cache->data, user_name); if(!cache_entry) { // Check if cache file exist, then read in the cache cache_path = build_cache_path(ma, user_name); if(stat(cache_path, &stat_buf) == 0) { // insert new cache entry cache_entry = g_new(MbCacheEntry, 1); cache_entry->avatar_img_id = -1; cache_entry->user_name = g_strdup(user_name); cache_avatar_path = g_strdup_printf("%s/avatar.png", cache_path); // g_hash_table_insert(ma->cache->data, g_strdup(user_name), ); g_free(cache_avatar_path); // And insert this entry } else { purple_build_dir(cache_path, 0700); } } if(cache_path != NULL) g_free(cache_path); return cache_entry; } /** * Return cache base dir */ const char * mb_cache_base_dir(void) { return cache_base_dir; } /** * Initialize cache system */ void mb_cache_init(void) { // Create base dir for all images struct stat stat_buf; const char * user_dir = purple_user_dir(); if(strlen(cache_base_dir) == 0) { snprintf(cache_base_dir, PATH_MAX, "%s/mbpurple", user_dir); } // Check if base dir exists, and create if not if(stat(cache_base_dir, &stat_buf) != 0) { purple_build_dir(cache_base_dir, 0700); } } /** * Create new cache * */ MbCache * mb_cache_new(void) { MbCache * retval = g_new(MbCache, 1); retval->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, mb_cache_entry_free); retval->fetcher_is_run = FALSE; retval->avatar_fetch_max = 20; //< Fetch at most 20 avatars at a time // Initialize cache, if needed return retval; } // Destroy cache void mb_cache_free(MbCache * mb_cache) { // MBAccount is something that we can not touch g_hash_table_destroy(mb_cache->data); g_free(mb_cache); } /** * Insert data to cache * * Insert entry into current cache, thus allocate an image id for the image * Since the protocol plug-in can not use and GTK/GDK function * The task in reading the image data and make it ready will be responsibility * of TwitGin. * * @param ma MbAccount holding the cache * @param entry newly allocated MbCacheEntry */ void mb_cache_insert(MbAccount * ma, MbCacheEntry * entry) { } // There will be no cache removal for now, since all cache must available almost all the time const MbCacheEntry * mb_cache_get(MbCache *tg_cache, MbAccount * ma, const gchar * user_name) { } mbpurple-0.3.0/microblog/mb_http.c0000644000175000017500000007113611400373247016503 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * HTTP data implementation */ #include #include #include #ifdef _WIN32 # include #else # include # include # include #endif #include "mb_http.h" // function below might be static instead static MbHttpParam * mb_http_param_new(void) { MbHttpParam * p = g_new(MbHttpParam, 1); p->key = NULL; p->value = NULL; return p; } static void mb_http_param_free(MbHttpParam * param) { if(param->key) g_free(param->key); if(param->value) g_free(param->value); g_free(param); } static guint mb_strnocase_hash(gconstpointer a) { gint len = strlen(a); gint i; gchar * tmp = g_strdup(a); guint retval; for(i = 0; i < len; i++) { tmp[i] = tolower(tmp[i]); } retval = g_str_hash(tmp); g_free(tmp); return retval; } static gboolean mb_strnocase_equal(gconstpointer a, gconstpointer b) { if(strcasecmp((const gchar *)a, (const gchar *)b) == 0) { return TRUE; } return FALSE; } MbHttpData * mb_http_data_new(void) { MbHttpData * data = g_new(MbHttpData, 1); // URL part, default to HTTP with port 80 data->host = NULL; data->path = NULL; data->proto = MB_HTTP; data->port = 80; data->headers = g_hash_table_new_full(mb_strnocase_hash, mb_strnocase_equal, g_free, g_free); data->headers_len = 0; data->fixed_headers = NULL; data->params = NULL; data->params_len = 0; data->content_type = NULL; data->content = NULL; data->chunked_content = NULL; data->content_len = 0; data->status = -1; data->type = HTTP_GET; //< default is get data->state = MB_HTTP_STATE_INIT; data->packet = NULL; data->cur_packet = NULL; return data; } void mb_http_data_free(MbHttpData * data) { purple_debug_info(MB_HTTPID, "freeing http data\n"); if(data->host) { purple_debug_info(MB_HTTPID, "freeing host\n"); g_free(data->host); } if(data->path) { purple_debug_info(MB_HTTPID, "freeing path\n"); g_free(data->path); } if(data->headers) { purple_debug_info(MB_HTTPID, "freeing header hash table\n"); g_hash_table_destroy(data->headers); } if(data->fixed_headers) { purple_debug_info(MB_HTTPID, "freeing fixed headers\n"); g_free(data->fixed_headers); } data->headers_len = 0; if(data->params) { purple_debug_info(MB_HTTPID, "freeing each parameter\n"); GList * it; MbHttpParam * p; for(it = g_list_first(data->params); it; it = g_list_next(it)) { p = it->data; purple_debug_info(MB_HTTPID, "freeing parameter, %s=%s\n", p->key, p->value); mb_http_param_free(p); } purple_debug_info(MB_HTTPID, "freeing all params\n"); g_list_free(data->params); } if(data->content_type) { g_free(data->content_type); } if(data->content) { purple_debug_info(MB_HTTPID, "freeing request\n"); g_string_free(data->content, TRUE); } if(data->chunked_content) { purple_debug_info(MB_HTTPID, "freeing chunked request\n"); g_string_free(data->chunked_content, TRUE); } if(data->packet) { purple_debug_info(MB_HTTPID, "freeing packet\n"); g_free(data->packet); } purple_debug_info(MB_HTTPID, "freeing self\n"); g_free(data); } /* Always remove entry in hash */ static gboolean hash_remover_always(gpointer key, gpointer value, gpointer data) { return TRUE; } void mb_http_data_truncate(MbHttpData * data) { data->headers_len = 0; data->params_len = 0; data->content_len = 0; data->status = -1; data->state = MB_HTTP_STATE_INIT; if(data->headers) { //g_hash_table_destroy(data->headers); //data->headers = g_hash_table_new_full(mb_strnocase_hash, mb_strnocase_equal, g_free, g_free); g_hash_table_foreach_remove(data->headers, hash_remover_always, NULL); } if(data->fixed_headers) { g_free(data->fixed_headers); data->fixed_headers = NULL; } if(data->params) { GList * it; MbHttpParam * p; for(it = g_list_first(data->params); it; it = g_list_next(it)) { p = it->data; mb_http_param_free(p); } g_list_free(data->params); data->params = NULL; } if(data->content_type) { g_free(data->content_type); data->content_type = NULL; } if(data->content) { g_string_free(data->content, TRUE); data->content = NULL; } if(data->packet) { g_free(data->packet); data->packet = NULL; data->cur_packet = NULL; } } void mb_http_data_set_url(MbHttpData * data, const gchar * url) { gchar * tmp_url = g_strdup(url); gchar * cur_it = NULL, *tmp_it = NULL, *host_and_port = NULL; cur_it = tmp_url; for(;;) { // looking for :// if( (tmp_it = strstr(cur_it, "://")) == NULL) { break; } (*tmp_it) = '\0'; if(strcmp(cur_it, "http") == 0) { data->proto = MB_HTTP; } else if(strcmp(cur_it, "https") == 0) { data->proto = MB_HTTPS; } else { data->proto = MB_PROTO_UNKNOWN; } cur_it = tmp_it + 3; //now looking for host part if( (tmp_it = strchr(cur_it, '/')) == NULL) { break; } (*tmp_it) = '\0'; host_and_port = cur_it; cur_it = tmp_it; //< save cur_it for later use if( (tmp_it = g_strrstr(host_and_port, ":")) == NULL) { // host without port if(data->host) g_free(data->host); data->host = g_strdup(host_and_port); switch(data->proto) { case MB_HTTP : data->port = 80; break; case MB_HTTPS : data->port = 443; break; default : data->port = 80; //< all other protocol assume that it's http, caller should specified port break; } } else { (*tmp_it) = '\0'; if(data->host) g_free(data->host); data->host = g_strdup(host_and_port); data->port = (gint)strtoul(tmp_it + 1, NULL, 10); } // now the path (*cur_it) = '/'; if(data->path) g_free(data->path); data->path = g_strdup(cur_it); break; } g_free(tmp_url); } void mb_http_data_get_url(MbHttpData * data, gchar * url, gint url_len) { gchar proto_str[10]; if(data->proto == MB_HTTP) { strcpy(proto_str, "http"); } else if(data->proto == MB_HTTPS) { strcpy(proto_str, "https"); } else { strcpy(proto_str, "unknown"); } snprintf(url, url_len, "%s://%s:%d%s", proto_str, data->host, data->port, data->path); } void mb_http_data_set_path(MbHttpData * data, const gchar * path) { if(data->path) { g_free(data->path); } data->path = g_strdup(path); } void mb_http_data_set_host(MbHttpData * data, const gchar * host) { if(data->host) { g_free(data->host); } data->host = g_strdup(host); } void mb_http_data_set_content_type(MbHttpData * data, const gchar * type) { if(data->content_type) g_free(data->content_type); data->content_type = g_strdup(type); } void mb_http_data_set_content(MbHttpData * data, const gchar * content, gssize len) { if(data->content) { g_string_truncate(data->content, 0); } else { data->content = g_string_new_len(content, len); } } void mb_http_data_set_header(MbHttpData* data, const gchar * key, const gchar * value) { gint len = strlen(key) + strlen(value) + 10; //< pad ':' and null terminate string and \r\n g_hash_table_insert(data->headers, g_strdup(key), g_strdup(value)); data->headers_len += len; } gchar * mb_http_data_get_header(MbHttpData * data, const gchar * key) { return (gchar *)g_hash_table_lookup(data->headers, key); } void mb_http_data_set_fixed_headers(MbHttpData * data, const gchar * headers) { if(data->fixed_headers) { g_free(data->fixed_headers); } data->fixed_headers = g_strdup(headers); data->headers_len += strlen(data->fixed_headers); } static gint mb_http_data_param_key_pred(gconstpointer a, gconstpointer key) { const MbHttpParam * p = (const MbHttpParam *)a; if(strcmp(p->key, (gchar *)key) == 0) { return 0; } return -1; } void mb_http_data_add_param(MbHttpData * data, const gchar * key, const gchar * value) { MbHttpParam * p = mb_http_param_new(); purple_debug_info(MB_HTTPID, "adding parameter %s = %s\n", key, value); //p->key = g_strdup(purple_url_encode(key)); p->key = g_strdup(key); //p->value = g_strdup(purple_url_encode(value)); p->value = g_strdup(value); data->params = g_list_append(data->params, p); data->params_len += (5*strlen(p->key) + 5*strlen(p->value) + 5); //< length of key + value + "& or ?", x3 in case all character must be url_encode } void mb_http_data_add_param_int(MbHttpData * data, const gchar * key, gint value) { char tmp[100]; snprintf(tmp, sizeof(tmp), "%d", value); mb_http_data_add_param(data, key, tmp); } void mb_http_data_add_param_ull(MbHttpData * data, const gchar * key, unsigned long long value) { char tmp[200]; // Use g_snprintf for maximum compatibility g_snprintf(tmp, sizeof(tmp), "%llu", value); mb_http_data_add_param(data, key, tmp); } const gchar * mb_http_data_find_param(MbHttpData * data, const gchar * key) { GList * retval; MbHttpParam * p; retval = g_list_find_custom(data->params, key, mb_http_data_param_key_pred); if(retval) { p = retval->data; return p->value; } else { return NULL; } } gboolean mb_http_data_rm_param(MbHttpData * data, const gchar * key) { MbHttpParam * p; GList *it = NULL; gboolean retval = FALSE; purple_debug_info(MB_HTTPID, "%s called, key = %s\n", __FUNCTION__, key); it = g_list_first(data->params); while(it) { p = it->data; if(strcmp(p->key, key) == 0) { p = it->data; data->params_len -= (5*strlen(p->key) + 5*strlen(p->value) - 5); mb_http_param_free(p); data->params = g_list_delete_link(data->params, it); it = g_list_first(data->params); retval = TRUE; } else { it = g_list_next(it); } } return retval; } /* // generic utility extern gbool mb_http_data_ok(MbHttpData * data); */ static void mb_http_data_header_assemble(gpointer key, gpointer value, gpointer udata) { MbHttpData * data = udata; gint len; len = sprintf(data->cur_packet, "%s: %s\r\n", (char *)key, (char *)value); data->cur_packet += len; } static int _string_compare_key(gconstpointer a, gconstpointer b) { const MbHttpParam * param_a = (MbHttpParam *)a; const MbHttpParam * param_b = (MbHttpParam *)b; return strcmp(param_a->key, param_b->key); } void mb_http_data_sort_param(MbHttpData * data) { data->params = g_list_sort(data->params, _string_compare_key); } int mb_http_data_encode_param(MbHttpData *data, char * buf, int len, gboolean url_encode) { GList * it; MbHttpParam * p; int cur_len = 0, ret_len = 0; char * cur_buf = buf; purple_debug_info(MB_HTTPID, "%s called, len = %d\n", __FUNCTION__, len); if(data->params) { gchar * encoded_val = NULL; for(it = g_list_first(data->params); it; it = g_list_next(it)) { p = it->data; purple_debug_info(MB_HTTPID, "%s: key = %s, value = %s\n", __FUNCTION__, p->key, p->value); // Only encode value here, so _ in key will not be translated if(url_encode) { encoded_val = g_strdup(purple_url_encode(p->value)); } else { encoded_val = g_strdup(p->value); } ret_len = snprintf(cur_buf, len - cur_len, "%s=%s&", p->key, encoded_val); g_free(encoded_val); purple_debug_info(MB_HTTPID, "len = %d, cur_len = %d, cur_buf = ##%s##\n", len, cur_len, cur_buf); cur_len += ret_len; if(cur_len >= len) { purple_debug_info(MB_HTTPID, "len is too small, len = %d, cur_len = %d\n", len, cur_len); return cur_len; } cur_buf += ret_len; } cur_buf--; (*cur_buf) = '\0'; } purple_debug_info(MB_HTTPID, "final param is %s\n", buf); return (cur_len - 1); } void mb_http_data_decode_param_from_content(MbHttpData *data) { GString * content = NULL; gchar * cur = NULL, * start = NULL, * amp = NULL, * equal = NULL; gchar * key, * val; if(data->content_len > 0) { content = data->content; start = cur = content->str; while( (cur - content->str) < data->content_len) { // Look for & if( (*cur) == '&') { amp = cur; (*amp) = '\0'; if(equal) { (*equal) = '\0'; key = start; val = (equal + 1); // treat every parameter as string mb_http_data_add_param(data, key, val); (*equal) = '='; } (*amp) = '&'; start = amp + 1; } else if ( (*cur) == '=') { equal = cur; } cur++; } } } void mb_http_data_prepare_write(MbHttpData * data) { gchar * cur_packet, * param_content = NULL; gint packet_len, len; if(data->path == NULL) return; // assemble all headers // I don't sure how hash table will behave, so assemple everything should be better packet_len = data->headers_len + data->params_len + strlen(data->path) + 100; //< for \r\n\r\n and GET|POST and other stuff if(data->content) { packet_len += data->content->len; } if(data->packet) g_free(data->packet); data->packet = g_malloc0(packet_len + 1); cur_packet = data->packet; // GET|POST and parameter part if(data->type == HTTP_GET) { len = sprintf(cur_packet, "GET %s", data->path); } else { len = sprintf(cur_packet, "POST %s", data->path); } //printf("cur_packet = %s\n", cur_packet); cur_packet += len; // parameter if(data->params) { if(data->content_type && data->type == HTTP_POST && (strcmp(data->content_type, "application/x-www-form-urlencoded") == 0)) { // Special case // put all parameters in content in this case param_content = g_malloc0(data->params_len + 1); data->content_len = mb_http_data_encode_param(data, param_content, data->params_len, TRUE); // XXX: in this case, abandon what content was g_string_free(data->content, TRUE); data->content = g_string_new(param_content); g_free(param_content); } else { // put all parameters after path (*cur_packet) = '?'; cur_packet++; len = mb_http_data_encode_param(data, cur_packet, packet_len - (cur_packet - data->packet), TRUE); cur_packet += len; } } // Trailing "HTTP/1.1\r\n" (*cur_packet) = ' '; len = sprintf(cur_packet, " HTTP/1.1\r\n"); cur_packet += len; // headers part data->cur_packet = cur_packet; g_hash_table_foreach(data->headers, mb_http_data_header_assemble, data); // Content type (which is actually another header) if(data->content_type) { len = sprintf(data->cur_packet, "Content-Type: %s\r\n", (char *)data->content_type); data->cur_packet += len; } // Fixed headers cur_packet = data->cur_packet; if(data->fixed_headers) { strcpy(cur_packet, data->fixed_headers); cur_packet += strlen(data->fixed_headers); } // content-length, if needed if(data->content) { len = sprintf(cur_packet, "Content-Length: %d\r\n", (int)data->content->len); cur_packet += len; } // end header part len = sprintf(cur_packet, "\r\n"); cur_packet += len; // Content part if(data->content) { /* len = sprintf(cur_packet, "%s", data->content->str); cur_packet += len; */ memcpy(cur_packet, data->content->str, data->content->len); cur_packet += data->content->len; } data->packet_len = cur_packet - data->packet; // reset back to head of packet, ready to transfer data->cur_packet = data->packet; purple_debug_info(MB_HTTPID, "prepared packet = %s\n", data->packet); } void mb_http_data_post_read(MbHttpData * data, const gchar * buf, gint buf_len) { gint packet_len = (buf_len > MB_MAXBUFF) ? buf_len : MB_MAXBUFF; gint cur_pos_len, whole_len; gchar * delim, * cur_pos, *content_start = NULL; gchar * key, *value, *key_value_sep; gboolean continue_to_next_state = FALSE; if(buf_len <= 0) return; switch(data->state) { case MB_HTTP_STATE_INIT : if(data->packet) { g_free(data->packet); } data->packet = g_malloc0(packet_len); data->packet_len = packet_len; data->cur_packet = data->packet; data->state = MB_HTTP_STATE_HEADER; //break; //< purposely move to next step case MB_HTTP_STATE_HEADER : //printf("processing header\n"); // at this state, no data at all, so this should be the very first chunk of data // reallocate buffer if we don't have enough cur_pos_len = data->cur_packet - data->packet; if( (data->packet_len - cur_pos_len) < buf_len) { //printf("reallocate buffer\n"); data->packet_len += (buf_len * 2); data->packet = g_realloc(data->packet, data->packet_len); data->cur_packet = data->packet + cur_pos_len; } memcpy(data->cur_packet, buf, buf_len); whole_len = (data->cur_packet - data->packet) + buf_len; // decipher header cur_pos = data->packet; //printf("initial_cur_pos = #%s#\n", cur_pos); while( (delim = strstr(cur_pos, "\r\n")) != NULL) { if( strncmp(delim, "\r\n\r\n", 4) == 0 ) { // we reach the content now content_start = delim + 4; //printf("found content = %s\n", content_start); } (*delim) = '\0'; //printf("cur_pos = %s\n", cur_pos); if(strncmp(cur_pos, "HTTP/1.0", 7) == 0) { // first line data->status = (gint)strtoul(&cur_pos[9], NULL, 10); //printf("data->status = %d\n", data->status); } else { //Header line if( (key_value_sep = strchr(cur_pos, ':')) != NULL) { //printf("header line\n"); (*key_value_sep) = '\0'; key = cur_pos; value = key_value_sep + 1; key = g_strstrip(key); value = g_strstrip(value); if(strcasecmp(key, "Content-Length") == 0) { data->content_len = (gint)strtoul(value, NULL, 10); } else if (strcasecmp(key, "Transfer-Encoding") == 0) { // Actually I should check for the value // AFAIK, Transfer-Encoding only valid value is chunked // Anyways, this is for identi.ca purple_debug_info(MB_HTTPID, "chunked data transfer\n"); if(data->chunked_content) { g_string_free(data->chunked_content, TRUE); } data->chunked_content = g_string_new(NULL); // Need to goes into CONTENT state to decode the first chunk } mb_http_data_set_header(data, key, value); } else { // invalid header? // do nothing for now purple_debug_info(MB_HTTPID, "an invalid line? line = #%s#", cur_pos); } } if(content_start) { break; } cur_pos = delim + 2; } if(content_start) { // copy the rest of data as content and free everything if(data->content != NULL) { g_string_free(data->content, TRUE); } if(data->chunked_content) { data->chunked_content = g_string_new_len(content_start, whole_len - (content_start - data->packet)); data->content = g_string_new(NULL); continue_to_next_state = TRUE; purple_debug_info(MB_HTTPID, "we'll continue to next state (STATE_CONTENT)\n"); } else { data->content = g_string_new_len(content_start, whole_len - (content_start - data->packet)); } g_free(data->packet); data->cur_packet = data->packet = NULL; data->packet_len = 0; data->state = MB_HTTP_STATE_CONTENT; } else { // copy the rest of string to the beginning if( (cur_pos - data->packet) < whole_len) { gint tmp_len = whole_len - (cur_pos - data->packet); gchar * tmp = g_malloc(tmp_len); memcpy(tmp, cur_pos, tmp_len); memcpy(data->packet, tmp, tmp_len); g_free(tmp); data->cur_packet = data->packet + tmp_len; } } if(!continue_to_next_state) { break; } case MB_HTTP_STATE_CONTENT : if(data->chunked_content) { if(!continue_to_next_state) { //< already have buffer from previous state // Buffer is not already here g_string_append_len(data->chunked_content, buf, buf_len); } // decode the chunked content and put it in content for(;;) { purple_debug_info(MB_HTTPID, "current data in chunked_content = #%s#\n", data->chunked_content->str); cur_pos = strstr(data->chunked_content->str, "\r\n"); if(!cur_pos) { // Content will only be what can be decoded purple_debug_info(MB_HTTPID, "can't find any CRLF\n"); break; } if(cur_pos == data->chunked_content->str) { g_string_erase(data->chunked_content, 0, 2); continue; } (*cur_pos) = '\0'; cur_pos_len = strtoul(data->chunked_content->str, NULL, 16); purple_debug_info(MB_HTTPID, "chunk length = %d, %x\n", cur_pos_len, cur_pos_len); (*cur_pos) = '\r'; if(cur_pos_len == 0) { // we got everything purple_debug_info(MB_HTTPID, "got 0 size chunk, end of message\n"); data->state = MB_HTTP_STATE_FINISHED; data->content_len = data->content->len; break; } if( (data->chunked_content->len - (cur_pos - data->chunked_content->str)) >= cur_pos_len ) { // copy the string to content, then shift the rest of chunked_content purple_debug_info(MB_HTTPID, "appending chunk\n"); g_string_append_len(data->content, cur_pos + 2, cur_pos_len); purple_debug_info(MB_HTTPID, "current content = #%s#\n", data->content->str); g_string_erase(data->chunked_content, 0, (cur_pos + 2 + cur_pos_len) - data->chunked_content->str); } else { // we need more data purple_debug_info(MB_HTTPID, "data is not enough, need more\n"); break; } } } else { g_string_append_len(data->content, buf, buf_len); if(data->content->len >= data->content_len) { data->state = MB_HTTP_STATE_FINISHED; } } break; case MB_HTTP_STATE_FINISHED : break; } } void mb_http_data_set_basicauth(MbHttpData * data, const gchar * user, const gchar * passwd) { gchar * merged_tmp, *encoded_tmp, *value_tmp; gsize authen_len; authen_len = strlen(user) + strlen(passwd) + 1; merged_tmp = g_strdup_printf("%s:%s", user, passwd); encoded_tmp = purple_base64_encode((const guchar *)merged_tmp, authen_len); //g_strlcpy(output, encoded_temp, len); g_free(merged_tmp); value_tmp = g_strdup_printf("Basic %s", encoded_tmp); g_free(encoded_tmp); mb_http_data_set_header(data, "Authorization", value_tmp); g_free(value_tmp); } static gint _do_read(gint fd, PurpleSslConnection * ssl, MbHttpData * data) { gint retval; gchar * buffer; purple_debug_info(MB_HTTPID, "_do_read called\n"); buffer = g_malloc0(MB_MAXBUFF + 1); if(ssl) { retval = purple_ssl_read(ssl, buffer, MB_MAXBUFF); } else { retval = read(fd, buffer, MB_MAXBUFF); } purple_debug_info(MB_HTTPID, "retval = %d\n", retval); purple_debug_info(MB_HTTPID, "buffer = %s\n", buffer); if(retval > 0) { mb_http_data_post_read(data, buffer, retval); } else if(retval == 0) { data->state = MB_HTTP_STATE_FINISHED; if(data->packet) { g_free(data->packet); } } g_free(buffer); purple_debug_info(MB_HTTPID, "before return in _do_read\n"); return retval; } gint mb_http_data_read(gint fd, MbHttpData * data) { return _do_read(fd, NULL, data); } gint mb_http_data_ssl_read(PurpleSslConnection * ssl, MbHttpData * data) { return _do_read(0, ssl, data); } static gint _do_write(gint fd, PurpleSslConnection * ssl, MbHttpData * data) { gint retval, cur_packet_len; purple_debug_info(MB_HTTPID, "preparing HTTP data chunk\n"); if(data->packet == NULL) { mb_http_data_prepare_write(data); } // Do SSL-write, then update cur_packet to proper position. Exit if already exceeding the length purple_debug_info(MB_HTTPID, "writing data %s\n", data->cur_packet); cur_packet_len = data->packet_len - (data->cur_packet - data->packet); if(ssl) { retval = purple_ssl_write(ssl, data->cur_packet, cur_packet_len); } else { retval = write(fd, data->cur_packet, cur_packet_len); } if(retval >= cur_packet_len) { // everything is written purple_debug_info(MB_HTTPID, "we sent all data\n"); data->state = MB_HTTP_STATE_FINISHED; g_free(data->packet); data->cur_packet = data->packet = NULL; data->packet_len = 0; //return retval; } else if( (retval > 0) && (retval < cur_packet_len)) { purple_debug_info(MB_HTTPID, "more data must be sent\n"); data->cur_packet = data->cur_packet + retval; } return retval; } gint mb_http_data_write(gint fd, MbHttpData * data) { return _do_write(fd, NULL, data); } gint mb_http_data_ssl_write(PurpleSslConnection * ssl, MbHttpData * data) { return _do_write(0, ssl, data); } #ifdef UTEST static void print_hash_value(gpointer key, gpointer value, gpointer udata) { printf("key = %s, value = %s\n", (char *)key, (char *)value); } int main(int argc, char * argv[]) { MbHttpData * hdata = NULL; GList * it; char buf[512]; FILE * fp; size_t retval; g_mem_set_vtable(glib_mem_profiler_table); // URL hdata = mb_http_data_new(); mb_http_data_set_url(hdata, "https://twitter.com/statuses/friends_timeline.xml"); printf("URL to set = %s\n", "https://twitter.com/statuses/friends_timeline.xml"); printf("host = %s\n", hdata->host); printf("port = %d\n", hdata->port); printf("proto = %d\n", hdata->proto); printf("path = %s\n", hdata->path); mb_http_data_set_url(hdata, "http://twitter.com/statuses/update.atom"); printf("URL to set = %s\n", "http://twitter.com/statuses/update.atom"); printf("host = %s\n", hdata->host); printf("port = %d\n", hdata->port); printf("proto = %d\n", hdata->proto); printf("path = %s\n", hdata->path); // header mb_http_data_set_header(hdata, "User-Agent", "CURL"); mb_http_data_set_header(hdata, "X-Twitter-Client", "1024"); mb_http_data_set_fixed_headers(hdata, "X-Twitter-Host: 12345678\r\n\ X-Twitter-ABC: 5sadlfjas;dfasdfasdf\r\n"); mb_http_data_set_basicauth(hdata, "user1", "passwd1"); printf("Header %s = %s\n", "User-Agent", mb_http_data_get_header(hdata, "User-Agent")); printf("Header %s = %s\n", "Content-Length", mb_http_data_get_header(hdata, "X-Twitter-Client")); printf("Header %s = %s\n", "XXX", mb_http_data_get_header(hdata, "XXX")); // param mb_http_data_add_param(hdata, "key1", "valuevalue1"); mb_http_data_add_param(hdata, "key2", "valuevalue1 bcadf"); mb_http_data_add_param(hdata, "key1", "valuevalue1"); mb_http_data_add_param_int(hdata, "keyint1", 1000000); printf("Param %s = %s\n", "key1", mb_http_data_find_param(hdata, "key1")); printf("Param %s = %s\n", "key2", mb_http_data_find_param(hdata, "key2")); for(it = g_list_first(hdata->params); it; it = g_list_next(it)) { MbHttpParam * p = it->data; printf("Key = %s, Value = %s\n", p->key, p->value); } printf("Before remove\n"); mb_http_data_rm_param(hdata, "key1"); printf("After remove\n"); for(it = g_list_first(hdata->params); it; it = g_list_next(it)) { MbHttpParam * p = it->data; printf("Key = %s, Value = %s\n", p->key, p->value); } printf("data->path = %s\n", hdata->path); mb_http_data_prepare_write(hdata); printf("data prepared to write\n"); printf("%s\n", hdata->packet); printf("packet_len = %d, strlen = %d\n", hdata->packet_len, strlen(hdata->packet)); // printf("%s\n", buf); mb_http_data_free(hdata); hdata =mb_http_data_new(); fp = fopen("test_input.xml", "r"); while(!feof(fp)) { retval = fread(buf, sizeof(char), sizeof(buf), fp); mb_http_data_post_read(hdata, buf, retval); } fclose(fp); printf("http status = %d\n", hdata->status); g_hash_table_foreach(hdata->headers, print_hash_value, NULL); printf("http content length = %d\n", hdata->content_len); if(hdata->content_len > 0) printf("http content = %s\n", hdata->content->str); // test again, after truncated mb_http_data_truncate(hdata); fp = fopen("test_input.xml", "r"); while(!feof(fp)) { retval = fread(buf, sizeof(char), sizeof(buf), fp); mb_http_data_post_read(hdata, buf, retval); } fclose(fp); printf("http status = %d\n", hdata->status); g_hash_table_foreach(hdata->headers, print_hash_value, NULL); printf("http content length = %d\n", hdata->content_len); if(hdata->content_len > 0) printf("http content = %s\n", hdata->content->str); mb_http_data_free(hdata); g_mem_profile(); return 0; } #endif mbpurple-0.3.0/microblog/dummy_twitterim.c0000644000175000017500000001657111400373247020313 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # include #else # include # include # include #endif static void plugin_init(PurplePlugin *plugin) { } gboolean plugin_load(PurplePlugin *plugin) { return TRUE; } gboolean plugin_unload(PurplePlugin *plugin) { return TRUE; } static const char * dummy_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { return "twitter"; } static void dummy_login(PurpleAccount *acct) { purple_debug_info("dummytw", "dummy_login\n"); purple_notify_info(acct->gc, "Attention please", "Please re-add Twitter protocol", "Twitter plug-in from Microblog-Purple has been undergo important upgrades,which makes it incompatible with the old plug-in\n" "\n" "To upgrade, please REMOVE \"OldTwitterIM\", then RE-ADD \"TwitterIM\" back\n" "\n" "Sorry for any inconvenience, and enjoy your twitter-er experience" ); } static void dummy_close(PurpleConnection *gc) { } static void dummy_buddy_free(PurpleBuddy * buddy) { } static gchar * dummy_status_text(PurpleBuddy *buddy) { return NULL; } static void dummy_set_status(PurpleAccount *acct, PurpleStatus *status) { } static GList * dummy_statuses(PurpleAccount *acct) { GList *types = NULL; PurpleStatusType *status; //Online people have a status message and also a date when it was set status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE); types = g_list_append(types, status); //Offline people dont have messages status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, NULL, _("Offline"), TRUE, TRUE, FALSE); types = g_list_append(types, status); return types; } static PurplePluginProtocolInfo dummy_prpl_info = { /* options */ OPT_PROTO_UNIQUE_CHATNAME, NULL, /* user_splits */ NULL, /* protocol_options */ //NO_BUDDY_ICONS /* icon_spec */ { /* icon_spec, a PurpleBuddyIconSpec */ "png,jpg,gif", /* format */ 0, /* min_width */ 0, /* min_height */ 50, /* max_width */ 50, /* max_height */ 10000, /* max_filesize */ PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */ }, dummy_list_icon, /* list_icon */ NULL, /* list_emblems */ dummy_status_text, /* status_text */ NULL, dummy_statuses, /* status_types */ NULL, /* blist_node_menu */ NULL, /* chat_info */ NULL, /* chat_info_defaults */ dummy_login, /* login */ dummy_close, /* close */ NULL, /* send_im */ NULL, /* set_info */ NULL, NULL, dummy_set_status,/* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ NULL, NULL, /* add_buddies */ NULL, NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL, /* join_chat */ NULL, /* reject chat invite */ NULL, /* get_chat_name */ NULL, /* chat_invite */ NULL, /* chat_leave */ NULL, /* chat_whisper */ NULL, /* chat_send */ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */ dummy_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, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* attention_types */ sizeof(PurplePluginProtocolInfo) /* struct_size */ }; static GList * dummy_actions(PurplePlugin *plugin, gpointer context) { return NULL; } static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /* type */ NULL, /* ui_requirement */ 0, /* flags */ NULL, /* dependencies */ PURPLE_PRIORITY_DEFAULT, /* priority */ "prpl-somsaks-twitter", /* id */ "OldTwitterIM", /* name */ "0.1.3", /* version */ "Twitter data feeder", /* summary */ "Twitter data feeder", /* description */ "Somsak Sriprayoonsakul ", /* author */ "http://microblog-purple.googlecode.com/", /* homepage */ plugin_load, /* load */ plugin_unload, /* unload */ NULL, /* destroy */ NULL, /* ui_info */ &dummy_prpl_info, /* extra_info */ NULL, /* prefs_info */ dummy_actions, /* actions */ NULL, /* padding */ NULL, NULL, NULL }; PURPLE_INIT_PLUGIN(dummy, plugin_init, info); mbpurple-0.3.0/microblog/identica22.png0000644000175000017500000000150411400373247017324 0ustar somsaksomsak‰PNG  IHDRÄ´l;sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÚ :¦÷MRÄIDAT8ËÝ”[H“aÇï¾\ßš)«µ,%V‚±È``,Ñ‘è@‡›]EQ]xWäżÌ À¢ºè+H¥0JФè@k­±ÔœÛdûvx»1é`¹¢›z®^^~ïçáýóÀ¿Vâû‹6ͧJX ”1 g ·;ÒØø‡âVm"û€€ã+& \ìZß=ð[â3šOÍÀ~³QeIÉ:ŠíKI¥Ç v2}ððå+ÿ"Þ\³«%¢nUvµd 2KïË6ž„.hذ«¾[Î$6\¹² à |Ë~#B¡rÙNœÅõäDy>¢ê X1{– gQÅôc ÷‚ZÈÕy‰‘X“ÅdGñS°À<ç˱0?1" Œ'úÉd“?GÇ^“‹$Ð/DæÀ˜&ªÓ}^»b0ùýën`6ª?@±w\Þ\M¼7 r*‚—ìn€Dcg'–„µ X ô[ï}hœ|ksU`6ªds:Ræ0*|}ÎÍMÛ‰÷†YX]Ëë% *Ã}}~ i`‡%a= œžl4wR»¿G´j>UÀû2gU‘Ål§ÿÃr2ƒ]]ˆžŽóéá3GQZ»–Í·n!…tÎ9{ïµæÇít“`ü1ÀÌ®Ú]·ûܳûûÖ^k}kí üôrþ³÷þû T¢È2i€Ù;]$ µ‹ê~6v–£Ñ $©etýe«^.¼u3:ȳµlF¦š't”âk;KÉÅÄTÎ ¹›šjæ?s^®)Äñ~çòà ‘<ðÁ3_Yï¿ušê8±lFfÒ³u–ï*âÞºÐ,QEEî¦êéŽfî/‰ ovA|&F‡.?ûˆkÛcqÃg¡÷´ÌÌ$èéRLú™Û…½éñ?ûîTÓS)OdY_o¥x‹¢ÞfPDÆ€ Q(L QE)‰^“ûðãÔù7Ñ8Û\è_¼7<¸å¦%E¶P”˜ŒŠ*ª! ðPųŒË.[ÿ¢5.ºe#”<'llêü—û:JŸ7l` ƒ5àõ}e ”cìšÎðÈp „Zšc¢–¢‘æ_0Lßtœ¡’­;ûØvàæÏC5µ”iL”€`ˆc+Ôz É EÆ1Gžu,,¢aÀËëß.7õ㫌զÑU)¢™»BdÍ¥Ak Ÿ]uPÃ×7ìAîÒÜAT?¥oA 5²#ïÿé~¸þDÁršcMˆòö»”ßÃÚñØtß×-ØP)ô?S×CpÉ=õzþSÓo0֗㘣D•u2¡»­•=ºcEðžê¹[¬@ŸAŹým8±³Ë€%‚aÂòÞ6œÑSÄD=…'‹hªÂKÇÀ¼‰g²Á0“ 2PŒ¯­vF—“õ=.ÔÈù\ð!€É.´qtZO©ûB ½W5íSõ±6‘ü”ˆêy) ß¹ýl¹mò,„ˆh3s`&ˆ Ûb@J (ÊrççX™P¸£¸ñöÓQž±¤1±‰m\áÁwTÚÚ¿™êØñ|Bê ¢PUˆ* ¯D3Íq¨ÀD¦¸¬³Úý½Z­þ¡4›¼ÂFØâ ù…{õñæ¥P1#Í,o¤ÎWckðìX rB7Ø¢4·ösãùøöh CÌ‘ßpãYàî„4"6– ǵ-ÿB{5ùѤÛ~|3¨kE:Šz:ì³"suL7÷Ùü%÷5›ùùÊÎ=CUÚ£‘¡IËô¨ žæ¸{Ë~l«Ã…Åãû¦ñ«çFçã»@P'G$`ÞuÕYŠ82” TV\#ÑÔ'š{¬ªÎ%®¹è™Fª³æ=T|È!êÊ!È%y~oÛ«":*ËàÅN—#»&óB^÷ïÇþz†=Ó)~¾å®ß°yEËõ º–™F [yüÓÙ…"ŠÈ²mvñ¥Ž§>[k Ó|‹«¶&£Ö *U8i" £š‡r2ÍNÖrÕ»ðqî¢þè©Ý{e•îדïSè=娼«–Œ7îÚ²¿µ^hY¡«ù‰Ô]0mU ââ¢-Õ[ Âif–tvËWfÒa>¤ 4‹>B K±¨ç­èZ‰r¡ÄÎ50QÛm{~#¿„‰òÃýÓð‰._²vrí"ú›âAWs¥¿k‹Í_1…²¨"ó³À™Ð[D†&'SudèR ‘úì±£j•yßÇcƒÊµœÔÎÈ<Ó z‹cõ²Ï¢¯k9 I†c0YX[@¥Ø‹Å}çbAå ¼°÷aµPA Aཛྷ²ð‹DkÃa‰4±`Ò©¾À™¢€eÂ`µˆ®b4“y¹i2õ—#s?T=i. ݪCëÖÀ[.Yðšb‘¾ÍV#š'Ì]ñ›ñ†åW"ŽÊ`2`b1ˆD Àh+õa°g5¶îZ¥Vv Aá½À;1A$0ù{3M䀜B™§ À#&,(ǵ† _›Êü_G†îˆ™ÇH,Ç:VíÐ'?ò¶—,S,A/2J­ TÐìOÉ»°zå³7G¬:-§gí‹qî²ÏàwO `…Að‚P\ø«Û±#°Ö¨n!'¢ñ‘zþ¡JdþÐEÎ÷ÔFeÉäs8ed³v¦h÷ ’¼©Ëµ+ÎpúÍC‡°‘}ÛA)?2„€×ö¿IÔÞ²ôŸ)Z‰ƒ¥Ç_ˆ¡í?ÄLú<¼8 ¢Ð§Š ˜Ù”` Tsj.Èû+pG÷„ó÷mÐ&wR94ˆI ‚ð¢Q L½ç«ÔE }÷]OÏÛÂI*z¨¨ ‚Ï‹úÎk¹Ë1UÜ-—2aIß›1´};ò<´¬h,·”n.é¨L`°,ªÿÐN<|Æþ§å»¤~S7Z¤ÊÌdãÌØ˜Et(0š…ð\×q•éÞZVpl-’;Ï=CÞýð“³J¬èXZÂMª0¨ ½Ô? ýØZ†Ù¢ÝíK‘¦.÷³Z17°æ¨š=º×¯tå´þ¯oÙ|Ÿ®œ|ÆÐÌÄRW)^]Ž“w¶õTº J*  ª’ŽgûÒ<Ü’’|—œìCpþ¶§êx\T‚E`+ B`Žf]ç/ïP­‰Ê ‡nÿ ^·¥}ûYÏÿ‘ûÑdõ/XØþ54BûÄó£ði˜+[”0±-šã ]…Ï©Çe“¹û„¹×eÁ'©+AHÐAhk ˜ ¸Ð„áø˜¡kK‹ÑÈ&ÀÌ­&åÅ,¦Y‹wí ÃTpÎHȯìèoÿò̶)²0[šŒa`¶Ñ AÏ8¤Ó9(áKzëtíµ‚Ÿ¨Š²ó²×»–¿† -öÔÄøô³˜ÝÄcdмoìIĉAXË`žsÁa Šê¶ÅZ›“µ·W’øšúÎ#i€!N,’öm§,AûY+P:q6±°ÜÒ×ÐôðuŸ¯ß­ånµŠ›gþ42çı…Z~l ðìð¡¯ë x¶ì}iû+Íl;Ük „ÄÔ²¢ˆÈF['-ƒgY¹Rˆ¿f\Ä 0Œ5¨œÿFœ°örTNX¶Äå˜Ø²Û¾u=¦} B eC¸6óá"ö^~•6f™C–y乇s;GÖcÿÄS P•—Ø …¨@Äã±m?€“iC`Órc ì4Ä{ cŠhZ™ï³K! &aÁš5Xyíµè8õØb ǰ¥2zV®Äªïü zÞv14¯*¦s¼ê…ÌD²ÌoËR´áШå¨×24Müú‰k0Uß3GâKÍz¼ DD<6ï¼[÷Üf€˜ÀÜ"ÁLPÅ/ícâKHˆP8ùµXö™+` ØX1 f1`kaËeœþ·_D´pЖfUàÌÌMý§YÁi5¢Ôš{q÷Ãëðü¾ÿ† )DÕßyª+%Õƒ*8;g‰H+XCði8^ç ÏOÿÄCõ8D WT€0íD4¨ ¨@DPîù?ÕŒ?ÉÐ-Û·£î=üìw‰i”¯þ›'@Q›F‡J[鎩éÑ.ê9}fa÷I0Í“yVž cGrÞN˜ÀÆòEZ»3Oå";]¸ ð°ïî{ ÞCEp˜„«BEàÓ›î¼ nö¹M •øq«Þý:ô GZ*·ŒO ÿ¶£Üwê`ï‰Ç13e®9ç2ódwÎO Ç(Dí(D°¦€æe.@UûEñ}ò’">“¥ÄtöÁõò}û-@õ¤“^ÜtÏ‚çðÄ÷oÂ3·ß1÷¸óøv4&Óoøím›ñ÷,‡)´A­×bRޛ浟ÞV),èí®,ZØYYÈmÅn$q…¨‚R¡•BÊÉX´ç>£ µzíîf3{4xšTá~UÄ@@=!ÈñVÞ« Ù­1Ô±¹*˜øãª'¿̦•¢C@6=…G¯ÿ6†¾û=ˆonÅ¥í}å݉ôªÃÂþG÷]ŠRÌd‰&0QdöØ1‚®¶&:-²……D\ðÞe©«ïö>ÛÄÆü®­\ñ>‚äToÔz¼Ë?HŒOƱY¢À&ÉÃùÉ“R/=ƒš|ÉÄæsñ&Ä+¢Ø P‰Qè]€þפÚúØûè#hŒŒ#oz¨(Ø0Ïì“ýÛÆ?^ln9bÞúÁýC¥$×¥ E„‚zUmùª¨ªŠ¨ªj Îy ’#ø^sâÙ9×!"WËWèë >´âÉú—õ 9•Þ¬A>e"›½ôéôckÑùB ¾C)b­&ÉQK˜°iÞÐÜR›Á¤Š’Q.D–+ˆ}gá:U\Uzm˜¨Ð0 ؘﳡ·Žíœ‚kú£7ï£kQªzG^w—Ç;$„ÿÕ Ž«nZNʉ!!ªUW‘à[š±¶í†KlÛ¼—oчÇwMSÞ8ü ÔÄ•ž¢ªâ&f¾–ÁRˆÄêËrCsÝÍËz!%¡2Σœê_o}a?sT´‰›qŸ'à‹Ã5ëR?ïäQî(ýª)˜kU5#Š‚7F×>²æå pï]£8ç탈c‚ +íV£†&­/NJà =R(2ä©'ÕVQX®$€èW8⯪×,D$ÑuÜøòßRÀ§o8 +<àúuC¸uùr¸’#—«QE‘sýI£éÞÞL’Ø¢”Ø_ƒ5*h T¹ k6mze®Y6n=í44‘äj½ K#ðãSYž´ÇQÓ©œc,o!Ÿ'%ýäÓO¿ô Í+1ÖlÚ¯FÉkˆ‰_ˆÁ?k³ ñí™í"²‡Õ€Pl«Ñ˜8áç½Å¢*ègæ[£/DŽéŽì•놆PŒ1™qîÄÚÍàÿ#Æ0®™ç÷¯JEV#@{ ¥‘†·w “RW(µÖ~UHÌ0TDdOmæÖ A‹6â>¯:k6mB(ÔÁ‰ª¥A麡!ü¿ÿïêb£"KIEND®B`‚mbpurple-0.3.0/microblog/twitter48.png0000644000175000017500000000625311400373247017264 0ustar somsaksomsak‰PNG  IHDR00Wù‡sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ#‡”&Û +IDAThÞµšK$ÇqÇ‘•UýœÇîZÜÍ—HYäÁ"°-Á0,A'Ãw>ùà‹¿‡¿€¾€6|`’ AaCEŠ2Lðh®H®v—ÃÙyö«º23tȬêìž™ÝYÙîAowWVWGdü#þÿˆZù«?R4 ª¨kà)0e- F„KOXºòCŸpPգ͒à=ZHQ¢F@ 6Oö.SôŠ€ ½Üêÿ²*ŒEŒAB@ƒ,Å¢‚‚«ã—Š ª×+ƒIƵøiÍêÿ ãÛ ©®; Ý[AµähЦ·„àÁVXœï ´`Ê’k¾rÍâ¸Kv¾ˆÈÿSºñ¶…M»W…@%ž,9¡"H³×`Å-#‚б%c+|u§â ö<š{Κ!t‘qzÞn1’-JÃìU×/²éÀÚ¿`ÆÖðÜ à«Û%ÿq¼ä”BWcµ©QÛ'T}vJx}§äÁÌñÉÔuF¹Øv¿áƒ r¸å¦èÚèe¹¤›~)§ÎszâYŒ-oìT|x¼ä˜âlÀà€•áõíŠÇµçÞ¤‰“UX…˜A§0(„G–»Ã’Qi8^z>›5<˜y– ¥Hçøå¨Ñ'郕¢¿œ4TÞØ.ùð´áñL°¡ªP‰Å¨Êé# ˜l£/ÊÂCi„¯_ïñí熚4¼ý`ÊGg Fk.)‘\ŒýóÇ3wÎ<Ûe c°Á”©\œŽ(š¾wræ•A!|çöoß2²f=W%:÷Æv»Ã’øôŒŸ>®Y¥4r©k•gã,Õ#ª7ž¦]0VЦ—‚SÅ’W„LþèKCþä¹!c·='– C[&æËneø³»cfNùਦ0ùsIÑ×K"¢©¦F[µsL›héˆ+ù1wÊ+[%oîõ¸VFãgùèœA8 Êc;V(~«_ðÖõ¿š;ökÏ ‹‚>)/ôüºf‰­*™½ŠQÕ@µõ.ý¥5òÆNÛƒ˜+ …/<, ¢ô&övÜñêVÅó#‹ÓöVùÔ>ÉW6ŒïlZ{ŸÿUY-æõ»­Ö>DlßZ¶²ÝŸe˜"R ­Žì*Ø¥íÒP{åø’úS’ My©sh·ÒYªŠUUT„U.(ª’9+zfõN#8 ‰5“.ñDÇw«‚/õ ¬Èk·FxUj¯,|É\P¦ÃÞ;=#ˆQnöÆœ6Ž-k±…_*.ƒž‡…¦òtUÚbx«€^Á[{}f^ùÛ{'ÌRIR[qA™4 gË‚û“)÷N¦X#‚SeêóšOÏ[˰´ômAi [dUêÉYî;Å¥@ÕU™•ÑN#ù-5¾x¹öŠx×¶JÞÜéóöà =£,œgá<óÆ1uއ†iãñhtÀ Xc0F„ePšºá¤n@e ·ÝêÓ›~Ùè8Mv^- fn•¬8 ðÑRy­v ¸Ù³¼02<šÎP +('iSH|¬H4ZˆŽ´‹1¯c-öA×pè¦>n6T±\–ÍN`MãHttêáF;Etêf?òÎQè& L…J¼­$óÛëµ'Ä× çK`@iTŸ˜ÏÚ79%¨$Þ~!HÚiQPÑÎ.I¤bYUϸ`âH'_ÊY¨ÅñeyúLNI4Þ(ô³\™;eî¡0Ò‹²2>½Úv÷[ÏÒ‡µ(˜Æ7(¸Ôgš ljT¯\BƒFvýr®'Z]øÀÇÓ†ã&0,LÚ»ÖÎøÎt9€¬…ÅH¶¶iOë›$ ›Í­WahV;y•ÊuÃÂk}áZúÒýYÿÌb™5† ! £ˆJײJ’+ÃÛd–UC2¼åU¤UœfñíŒÑ+€¿è@?ýèÌ~´?ãçÇ Fe¹(¬2Ôˆ®!ĶðîÌO8jÃs`Ø!µ-Ýwå5s§v‹gOïÓ&ðÎøÞƒ3¶¬Á«bòÞ¼MÇ„ »VƒduÂ&´r”&€ˆž‹€I •âê90õ{“†ï?šð/ŸO9Zz¶K“®'IíÆ “4`0É>Ûa|ÃXI²V‘ö>²m[…6YW¤O{ÔAyÿ¨æï>;åýÃ9l•E’˜Ù”«ËIÍJ=¹ 2HbääŒ^P…—ôs¾Ñ^×g;Wyôð‡×û¼:.ù§GSÞ~4áÑÜÅT2 ÔHé’蕯ÙܳýtƳŠN>ãÑTF}ˆ¯í³ý\>üEÀáöÀò—/mó×_Þå…QÉÔ‡¬”'aÙšÈW"Ä ‹‚®Ã(u2&1àù$^‡¦Žéƒi`×>=á¥a”.ôÇ7‡LšÀw?:b”*ëW€–N5X“ ±$ìZãÛ’êVÛG)«Rj7vÛ«ò“Se6V`¯¾µSðê Κ~w§ÏÜF5ZJª†)SÒæA¬Bm畈®L1õÃÚ3Oí‘Q!™+?JùðÛ=áö à­Ý>?ÜŸE-&Ù !#Ö.Èy€•Έƒ]!¨ð‹IÃãelׯ—ñ‡f>î¸ÛxM#™«<“¤þxøt"ûJ̉ªH…AÖ¹ªëG™³*›k»ß1ŸP†ÿ:[òù"N¬w¬ð;CÞ…c§Ý„ÂéoðL5w”3¯4)t=#ô‹l3eU:ÉzÓy“1q¦é€8‰¾¿ðüüdÉ,?^ê¾¹]P 7q,²ŠÂÕð 3§ãB¨¤tJ’Í6³u6˜Xò{&»ûÉ‚…SÞ9˜óÚ¸ä7 üþ5K£ðãÏA£Ì5Ï‚«ëi§ðæXx±XåþÜqê½Ä¢“cÈæ]Óª8¥Òšým•½™ãûsnJ^Y†|gÏò\%|px° Ì}ÜY½2·*á[{–;Uܹ‡ Ï{Ç5.ÀÐÆ›Œí=‚Bqs7&ǹá¹621ù“£%U1á/žóüÀb¾6.øÚ¸àá2pÐ(‹pÑúâÑú €—†q.À{Ç5?;]2´&©Hñ¢²º•Õ’`îMÔy’¬¢¡=»§w/8j”?¿;âõ­’ÒVàVe¸Uýæ­¥SøáÁ‚ï=œ3w0NjTºYmT¦¹r°¥÷¨‰ÒÉÃne9q~åD9M3Ò€ðáYÃßüâ”o^ïóë=¾2*¨Ìùæç*Df€ÏkÏ÷÷üà‹9‡µg«4Õ ÖñÜÒ`MÁcGË RP„¾‰÷½þý8dz8Ú6rTDlþóÁ‚wŽjîö-·û7ª‚‘2øBOtývÆÂ+jÏOöëx{}Ü/ù¬€F–~…^éìörŽ˜’ýeàÝÃ9_¿ÞçÕk%ÿ3q¨¬îP¶z •ŒDˆL,¯žÏj¿ÖGt},«»¬ks~]ÍÿEÓNó±«Þ:(¼<,¹5°¼wX³ßvBƒÁÕLgg<mñîÑ‚ßÛÐ/ ÷çžS’ ƒ `Z¤oh‚Åš0d-jlÞ-Öx½5s“3>cjPF…áî¸àv¿àÝÇRͧ ›«ýÃzÎÙ!ûUÅ‘ xÿ¨æ­½>7z– Y5ÚÔ"ÌÞlˆXëòòÿ¡°‘'³÷ùèµh‡dE·ÎªwíXhB!Y2]BæL{‹KOàÐ vqƵã}Fê0£m,U5&x¶¦gØÓÏ™×gœ–\o¶L˜õ±K ‹IIÝÄZX™¼Ó“ "kÏhüšÙzHNÐ,1õœkÍœ~=£§3Ü‚á¿h1‹Þ½<¥IEND®B`‚mbpurple-0.3.0/microblog/twitter.h0000644000175000017500000001436411400373247016555 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com Unless otherwise stated, All Microblog-purple code is released under the GNU General Public License version 3. See COPYING for more information. */ /* Header for twitter-compliant API */ #ifndef __MB_TWITTER__ #define __MB_TWITTER__ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include "mb_cache.h" //< Cache user's information #include "mb_oauth.h" #ifdef __cplusplus extern "C" { #endif #define TW_HOST "twitter.com" #define TW_HTTP_PORT 80 #define TW_HTTPS_PORT 443 #define TW_AGENT "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" #define TW_AGENT_DESC_URL "http://microblog-purple.googlecode.com/files/mb-0.1.xml" #define TW_MAXBUFF 51200 #define TW_MAX_RETRY 3 #define TW_INTERVAL 60 #define TW_STATUS_COUNT_MAX 200 #define TW_INIT_TWEET 15 #define TW_STATUS_TXT_MAX 140 #ifdef MBADIUM #define TW_AGENT_SOURCE "mbadium" #else #define TW_AGENT_SOURCE "mbpidgin" #endif #define TW_FORMAT_BUFFER 2048 #define TW_FORMAT_NAME_MAX 100 enum _TweetTimeLine { TL_FRIENDS = 0, TL_USER = 1, TL_PUBLIC = 2, TL_REPLIES = 3, TL_LAST, }; enum _TweetProxyDataErrorActions { TW_NOACTION = 0, TW_RAISE_ERROR = 1, }; // Hold parameter for statuses request typedef struct _TwitterTimeLineReq { gchar * path; gchar * name; int timeline_id; int count; gboolean use_since_id; gchar * sys_msg; gchar * screen_name; // for /get command to fetch other user TL } TwitterTimeLineReq; extern TwitterTimeLineReq * twitter_new_tlr(const char * path, const char * name, int count, int id, const char * sys_msg); extern void twitter_free_tlr(TwitterTimeLineReq * tlr); /* * Twitter Configuration */ enum _TweetConfig { TC_HIDE_SELF = 0, TC_PLUGIN, TC_PRIVACY, TC_MSG_REFRESH_RATE, TC_INITIAL_TWEET, TC_GLOBAL_RETRY, TC_HOST, TC_USE_HTTPS, TC_STATUS_UPDATE, TC_VERIFY_PATH, TC_FRIENDS_TIMELINE, TC_FRIENDS_USER, TC_PUBLIC_TIMELINE, TC_PUBLIC_USER, TC_USER_TIMELINE, TC_USER_USER, TC_USER_GROUP, TC_REPLIES_TIMELINE, TC_REPLIES_USER, TC_AUTH_TYPE, // OAuth stuff TC_OAUTH_TOKEN, TC_OAUTH_SECRET, TC_CONSUMER_KEY, TC_CONSUMER_SECRET, TC_REQUEST_TOKEN_URL, TC_ACCESS_TOKEN_URL, TC_AUTHORIZE_URL, TC_MAX, }; typedef struct _MbConfig { gchar * conf; //< configuration name gchar * def_str; //< default value to be used gint def_int; gboolean def_bool; } MbConfig; extern MbConfig * _mb_conf; /* Alias for easier usage of these values */ #define mc_name(name) ma->mb_conf[name].conf #define mc_def(name) ma->mb_conf[name].def_str #define mc_def_int(name) ma->mb_conf[name].def_int #define mc_def_bool(name) ma->mb_conf[name].def_bool typedef unsigned long long int mb_status_t; typedef struct _MbAccount { PurpleAccount *account; PurpleConnection *gc; gchar *login_challenge; PurpleConnectionState state; GSList * conn_data_list; guint timeline_timer; mb_status_t last_msg_id; time_t last_msg_time; GHashTable * sent_id_hash; gchar * tag; gint tag_pos; mb_status_t reply_to_status_id; MbCache * cache; gint auth_type; MbConfig * mb_conf; MbOauth oauth; } MbAccount; enum tag_position { MB_TAG_NONE = 0, MB_TAG_PREFIX = 1, MB_TAG_POSTFIX = 2, }; enum auth_types { MB_OAUTH = 0, MB_XAUTH = 1, MB_HTTP_BASICAUTH = 2, MB_AUTH_MAX, }; extern const char * mb_auth_types_str[]; typedef struct _TwitterBuddy { MbAccount *ma; PurpleBuddy *buddy; gint uid; gchar *name; gchar *status; gchar *thumb_url; } TwitterBuddy; #define TW_MSGFLAG_SKIP 0x1 #define TW_MSGFLAG_DOTAG 0x2 typedef struct _TwitterMsg { mb_status_t id; gchar * avatar_url; gchar * from; gchar * msg_txt; time_t msg_time; gint flag; gboolean is_protected; } TwitterMsg; typedef TwitterMsg MbMsg; extern PurplePluginProtocolInfo twitter_prpl_info; extern const char * _TweetTimeLineNames[]; extern const char * _TweetTimeLinePaths[]; extern const char * _TweetTimeLineConfigs[]; /* Microblog function */ extern MbAccount * mb_account_new(PurpleAccount * acct); extern void mb_account_free(MbAccount * ta); /* * Protocol functions */ extern void twitter_set_status(PurpleAccount *acct, PurpleStatus *status); extern GList * twitter_statuses(PurpleAccount *acct); extern gchar * twitter_status_text(PurpleBuddy *buddy); extern void twitter_login(PurpleAccount *acct); extern void twitter_close(PurpleConnection *gc); extern int twitter_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, PurpleMessageFlags flags); extern void twitter_buddy_free(PurpleBuddy * buddy); extern void twitter_get_user_host(const MbAccount * ta, char ** user_name, char ** host); #define mb_get_user_host(a, b, c) twitter_get_user_host(a, b, c) extern void twitter_fetch_new_messages(MbAccount * ta, TwitterTimeLineReq * tlr); extern gboolean twitter_fetch_all_new_messages(gpointer data); extern void * twitter_on_replying_message(gchar * proto, mb_status_t msg_id, MbAccount * ma); extern void twitter_favorite_message(MbAccount * ta, gchar * msg_id); extern void twitter_retweet_message(MbAccount * ta, gchar * msg_id); #ifdef __cplusplus } #endif #endif mbpurple-0.3.0/microblog/mb_cache_util.h0000644000175000017500000000341111400373247017620 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /* * mb_cache_util.h * * Created on: May 8, 2010 * Author: somsak */ #ifndef MB_CACHE_UTIL_H_ #define MB_CACHE_UTIL_H_ #include "twitter.h" #ifdef __cplusplus extern "C" { #endif // Initialize cache system // Currently, this just create directories, so no need for finalize extern void mb_cache_init(void); // Get base dir to cache directory extern const char * mb_cache_base_dir(void); // Create new cache extern MbCache * mb_cache_new(void); // Destroy cache extern void mb_cache_free(MbCache * mb_cache); // Insert data to the cache extern void mb_cache_insert(MbAccount * ma, MbCacheEntry * entry); // There will be no cache removal for now, since all cache must available almost all the time extern const MbCacheEntry * mb_cache_get(const MbAccount * ma, const gchar * user_name); #ifdef __cplusplus } #endif #endif /* MB_CACHE_UTIL_H_ */ mbpurple-0.3.0/microblog/mb_oauth.h0000644000175000017500000000453711400373247016652 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . */ /* * mb_oauth.h * * Created on: May 16, 2010 * Author: somsak */ #ifndef MB_OAUTH_H_ #define MB_OAUTH_H_ #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #ifdef __cplusplus extern "C" { #endif struct _MbAccount; struct _MbConnData; struct _MbHttpData; typedef gint (* MbOauthResponse)(struct _MbAccount * ma, struct _MbConnData * data, gpointer user_data); typedef struct _MbOauth { gchar * c_key; //< Consumer key gchar * c_secret; //< Consumer secret gchar * oauth_token; //< request token gchar * oauth_secret; //< request secret gchar * pin; //< user input PIN MbOauthResponse response_func; struct _MbAccount * ma; gpointer data; } MbOauth; void mb_oauth_init(struct _MbAccount * ma, const gchar * c_key, const gchar * c_secret); void mb_oauth_set_token(struct _MbAccount * ma, const gchar * oauth_token, const gchar * oauth_secret); void mb_oauth_set_pin(struct _MbAccount * ma, const gchar * pin); void mb_oauth_request_token(struct _MbAccount * ma, const gchar * path, int type, MbOauthResponse func, gpointer data); void mb_oauth_request_access(struct _MbAccount * ma, const gchar * path, int type, MbOauthResponse func, gpointer data); void mb_oauth_free(struct _MbAccount * ma); // void mb_oauth_set_http_data(MbOauth * oauth, struct _MbHttpData * http_data, const gchar * full_url, int type); void mb_oauth_reset_nonce(MbOauth * oauth, struct _MbHttpData * http_data, const gchar * full_url, int type); #ifdef __cplusplus } #endif #endif /* MB_OAUTH_H_ */ mbpurple-0.3.0/microblog/twitter22.png0000644000175000017500000000204711400373247017251 0ustar somsaksomsak‰PNG  IHDRÄ´l;sRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ#5Å&¾·§IDAT8Ë}•K‹\EÇçTÝîÉt§§'ÑdŒTT0‚®]×.Ü+"¸ðø9t/ ~!ÀAÅDdq’‰ÌLÏíéÇ}Ô9.êf^I¬KAÝ¢êÇÿžÇÿʧß?pR‹˜¡EUåøþø±µ5 æ ‘HS#"¬VYïZ‘SPy:ÕOˆèqoº¤i*¢·5ãшÕB¹;kN€1«Ÿ'Œ}ô#ôÙ¨\öØÚOÄ FýÈÖ¬!uFQyÿ…!¯Œz8Îíý†÷gÌ“„ûñµ³×$V‚P˜$P%£2Gáƒ+#®­÷Ù³…w.DV£ðÕf‰ÈÐ;ÝÞ…ÄÝ™'âáŽu§/ ^_ëñw‚ÆhÞ÷÷”‡UzbróýÌ1Œhá8 … óÆ©=«1…(\¿x†Ý*†;ü[µü^ÖT–EY7ˆûuMc}ÌE“R;t§B÷þîÆà„Z~™,ùr³dÖ&Ü!¹1oZâÝé«=ekÞ2( ®$'W0Ið[åô$ÃP ¼1^áú…†¯7w©Sb ¬jbÅ]X´‰e2v–àì§ ŽÖJÍíÎ)¼4Œì,+ §'!ªª ª¨ ÚqeÎ,‘ì ý E2! Lj*ŠªTQÕœ`ÑæˆŸ p¹/5É¥B(p¾½?%E‚ *JT”¬TUÉ ¬’³H|1 o­*zªó¶-_Ü™pswIOæFêghE%_¯ÜYteTÙ“Í¢6§rˆªxW—™#Y±HU¡ãÒt¡`ã»ÉI/àê™Èg¯§2çæÎ‚@EŽ%¯ CPe¯6–ÉG¸5w"ƒälŸê8pVJá“‚÷. ùa·Bð ±cànÞ[&îÌÞöøqš([GŸâš³ä”ÉY+EÌ9ÎÊÒ¥Û@„oþœ²*ÎG ^>#¬(âÍk«Êó=áײÆühžQ$»ÚQl”Û-Ÿo–|øâ7 –;ñ´ù Üš¶ÜøkA/(ÉŒ(‚qP/Pq¾([r"Uø¹l¸óGɫÂçV"=•À{v³ªÄO“Š™ù¡°óýÀl±$>Û.ØšL¹rnĸŸK+¨6ÍÖÒ¸_7‡ݳïšæÎ *+š÷ÖbäÁ¼!ÌJb8æ™ò!Ûfô‡B$W¢go ª¸+(ˆv¦î†™“ÌHæ˜)9Ûó%¾·ÃºÀúäé½×IEND®B`‚mbpurple-0.3.0/microblog/tw_util.c0000644000175000017500000000463011400373247016530 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # include #else # include # include # include #endif #include "mb_util.h" #include "twitter.h" #define DBGID "tw_util" void twitter_get_user_host(const MbAccount * ma, char ** user_name, char ** host) { char * at_sign = NULL; purple_debug_info(DBGID, "%s\n", __FUNCTION__); (*user_name) = g_strdup(purple_account_get_username(ma->account)); purple_debug_info(DBGID, "username = ##%s##\n", (*user_name)); if( (at_sign = strrchr(*user_name, '@')) == NULL) { if(host != NULL) { (*host) = g_strdup(purple_account_get_string(ma->account, mc_name(TC_HOST), mc_def(TC_HOST))); purple_debug_info(DBGID, "host (config) = %s\n", (*host)); } } else { (*at_sign) = '\0'; if(host != NULL) { (*host) = g_strdup( at_sign + 1 ); purple_debug_info(DBGID, "host = %s\n", (*host)); } } } mbpurple-0.3.0/microblog/twitter16.png0000644000175000017500000000131511400373247017251 0ustar somsaksomsak‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEØ" ]ÎMIDAT8Ëu’»ŽUW †?ÛkŸû9 —DFJ ÝP€„hÓ¥C‚gà]xŒ¤Œ„Ò„@ AEGZhf¢3ìsξ¬e§ØC€¶eÉÿïË/÷Ÿ¾ gRUˆ*"|×"@º>ÓE´ô¬fST>©à@ øŒ_9A™˜²nZ’™á"|è2#nœŸ°~Â&;OŽv¼Úôß°1˜'¥J‰”EÙ'G°¿šðÛÞ‚¬J%°7KÇ&»•ä ¸¹ „«BŒä×0õíÅ•麎<ÑqùP&ÒO¤aêï«u  d/ÐÒ}ç1B(§†GÕèjx`˜ºïbA yƒgL¿ï@- U¬”ÅÍ3æ·• ¥·ë~x‡žß½×É@)ݯuïQ$¡ùj§‡Ù^²XˆŒpµå&Šª²¿ºZ"¶ùé;°ÜÆU$a_M½¶œåk~‰ºÚ&*N‰éÁλŒf2E!zs–5 ìÞj t†êȬïÄT rTÚÿü%gÎ?í|?`;ÙÂ@ |»KQUêÛÛÉYVpË—½Â@^¦Þ06œþñçüqx¦x 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * * HTTP connection handling for Microblog * */ #ifndef __MB_HTTP__ #define __MB_HTTP__ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #ifdef __cplusplus extern "C" { #endif #define MB_HTTPID "mb_http" enum MbHttpStatus { HTTP_OK = 200, HTTP_MOVED_TEMPORARILY = 304, HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZE = 401, HTTP_NOT_FOUND = 404, }; enum MbHttpRequestType{ HTTP_GET = 1, HTTP_POST = 2, }; enum MbHttpProto { MB_HTTP = 1, MB_HTTPS = 2, MB_PROTO_UNKNOWN = 100, }; enum MbHttpState { MB_HTTP_STATE_INIT = 0, MB_HTTP_STATE_HEADER = 1, MB_HTTP_STATE_CONTENT = 2, MB_HTTP_STATE_FINISHED = 3, }; #define MB_MAXBUFF 10240 typedef struct _MbHttpData { gchar * host; gchar * path; gint port; gint proto; // url = proto://host:port/path // header part GHashTable * headers; gint headers_len; gchar * fixed_headers; // param part GList * params; gint params_len; // content gchar * content_type; GString * content; // Chunked, in case of Transfer-Encoding: chunked GString * chunked_content; gint content_len; // For receiving side, content_len is the size of content, determined by content-length header // For sending side, content_len is never used. gint status; gint type; gint state; gchar * packet; gchar * cur_packet; gint packet_len; } MbHttpData; typedef struct _MbHttpParam { gchar * key; gchar * value; } MbHttpParam; /* Create new MbHttpData @return newly created MbHttpData, use mb_http_data_free to free it afterward */ extern MbHttpData * mb_http_data_new(void); /* Free a MbHttpData @param data MbHttpData to free */ extern void mb_http_data_free(MbHttpData * data); /* Read a Http data from a stream @param fd file descriptor @param data MbHttpData @return number of read bytes */ extern gint mb_http_data_read(gint fd, MbHttpData * data); /* Read a Http data from SSL stream @param ssl purple ssl stream @param data MbHttpData @return number of read bytes */ extern gint mb_http_data_ssl_read(PurpleSslConnection * ssl, MbHttpData * data); /* Write a Http data to a stream @param fd file descriptor @param data MbHttpData @return bytes written */ extern gint mb_http_data_write(gint fd, MbHttpData * data); /* Write a Http data to SSL stream @param ssl purple ssl stream @param data MbHttpData @return bytes written */ extern gint mb_http_data_ssl_write(PurpleSslConnection * ssl, MbHttpData * data); /* Set URL to MbHttpData @param data MbHttpData @param url url to set (ex: https://twitter.com:80/statuses/friends_timeline.xml) */ extern void mb_http_data_set_url(MbHttpData * data, const gchar * url); /* Get URL to MbHttpData @param data MbHttpData @param url output buffer for URL @param url_len length of @a url */ extern void mb_http_data_get_url(MbHttpData * data, gchar * url, gint url_len); /* Set path for current MbHttpData @param data MbHttpData @param path new path to set, if old path exists, it'll be freed first. */ extern void mb_http_data_set_path(MbHttpData * data, const gchar * path); /* Set host for current MbHttpData @param data MbHttpData @param path new host to set, if old path exists, it'll be freed first. */ extern void mb_http_data_set_host(MbHttpData * data, const gchar * host); /* Set content into current content. If content already exist, it'll truncate the string first @param data MbHttpData @param content content to set to data->content */ extern void mb_http_data_set_content(MbHttpData * data, const gchar * content, gssize len); /* Set HTTP Basic authen with specified user/password into header @param data MbHttpData @param user user name @param passwd password */ extern void mb_http_data_set_basicauth(MbHttpData * data, const gchar * user, const gchar * passwd); /* Set/replace a header for HTTP connection to MbHttpData @param data MbHttpData @param key header @param value value of header */ extern void mb_http_data_set_header(MbHttpData* data, const gchar * key, const gchar * value); /* Get current value of current header @param data MbHttpData @param key header to look for @return internal buffer pointed to header string, or NULL if not found */ extern gchar * mb_http_data_get_header(MbHttpData * data, const gchar * key); /* Set a "fixed header" for write (outgoing) stream @param data MbHttpData @param headers header to set @note each line of header MUST ends with \r\n */ extern void mb_http_data_set_fixed_headers(MbHttpData * data, const gchar * headers); /* Add new www-urlencoded parameter to data @param data MbHttpData @param key key of param @param value value of current param */ extern void mb_http_data_add_param(MbHttpData * data, const gchar * key, const gchar * value); /* Add new www-urlencoded parameter to data @param data MbHttpData @param key key of param @param value value of current param */ extern void mb_http_data_add_param_int(MbHttpData * data, const gchar * key, gint value); /* Add new www-urlencoded parameter to data @param data MbHttpData @param key key of param @param value value of current param */ extern void mb_http_data_add_param_ull(MbHttpData * data, const gchar * key, unsigned long long value); /** * Sort parameter list in alphabetical order * * @param data MbHttpData in action */ extern void mb_http_data_sort_param(MbHttpData * data); /** * Encode CGI parameter from a list of params * * Caller should check for the possible length of param from param_len and allocate buf accordingly * * @param data MbHttpData in action * @param buf buffer * @param len maximum length of buffer * @param url_encode if TRUE, do url_encode before creating string, otherwise just leave the value as-is */ extern int mb_http_data_encode_param(MbHttpData *data, char * buf, int len, gboolean url_encode); /** * Decode CGI parameter into a list of params, stored into data->params * * @param data MbHttpData in action */ extern void mb_http_data_decode_param_from_content(MbHttpData *data); /* Look for value of specified parameter @param data MbHttpdata @param key key to look for @return buffer of value of specified key (first occurance) */ extern const gchar * mb_http_data_find_param(MbHttpData * data, const gchar * key); /* Remove a parameter. If multiple instance of key exists, only the first one will be removed. @param data MbHttpData @param key key to remove. @return TRUE if key is founded and removed, FALSE if not found */ extern gboolean mb_http_data_rm_param(MbHttpData * data, const gchar * key); /* Truncate all data and re-initialize everything back to zero @param data MbHttpData */ extern void mb_http_data_truncate(MbHttpData * data); /* Prepare packet for writing to destination data->packet will be ready-to-send gchar * after this call */ extern void mb_http_data_prepare_write(MbHttpData * data); /* Parse red to MbHttpData */ extern void mb_http_data_post_read(MbHttpData * data, const gchar * buf, gint buf_len); /** * Set content type header */ extern void mb_http_data_set_content_type(MbHttpData * data, const gchar * type); #ifdef __cplusplus } #endif #endif mbpurple-0.3.0/microblog/mb_net.c0000644000175000017500000001673611400373247016317 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /* Microblog network processing (mostly for HTTP data) */ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #ifdef _WIN32 # include #else # include # include # include #endif #include #include #include "mb_net.h" // Caller of request retry function static gboolean mb_conn_retry_request(gpointer data); // Fetch URL callback static void mb_conn_fetch_url_cb(PurpleUtilFetchUrlData * url_data, gpointer user_data, const gchar * url_text, gsize len, const gchar * error_message); MbConnData * mb_conn_data_new(MbAccount * ma, const gchar * host, gint port, MbHandlerFunc handler, gboolean is_ssl) { MbConnData * conn_data = NULL; conn_data = g_new(MbConnData, 1); conn_data->host = g_strdup(host); conn_data->port = port; conn_data->ma = ma; conn_data->prepare_handler = NULL; conn_data->prepare_handler_data = NULL; conn_data->handler = handler; conn_data->handler_data = NULL; conn_data->retry = 0; conn_data->max_retry = 0; //conn_data->conn_data = NULL; conn_data->is_ssl = is_ssl; conn_data->request = mb_http_data_new(); conn_data->response = mb_http_data_new(); if(conn_data->is_ssl) { conn_data->request->proto = MB_HTTPS; } else { conn_data->request->proto = MB_HTTP; } conn_data->fetch_url_data = NULL; purple_debug_info(MB_NET, "new: create conn_data = %p\n", conn_data); ma->conn_data_list = g_slist_prepend(ma->conn_data_list, conn_data); purple_debug_info(MB_NET, "registered new connection data with MbAccount\n"); return conn_data; } void mb_conn_data_free(MbConnData * conn_data) { purple_debug_info(MB_NET, "%s: conn_data = %p\n", __FUNCTION__, conn_data); if(conn_data->fetch_url_data) { purple_util_fetch_url_cancel(conn_data->fetch_url_data); } if(conn_data->host) { purple_debug_info(MB_NET, "freeing host name\n"); g_free(conn_data->host); } purple_debug_info(MB_NET, "freeing HTTP data->response\n"); if(conn_data->response) mb_http_data_free(conn_data->response); purple_debug_info(MB_NET, "freeing HTTP data->request\n"); if(conn_data->request) mb_http_data_free(conn_data->request); purple_debug_info(MB_NET, "unregistering conn_data from MbAccount\n"); if(conn_data->ma->conn_data_list) { GSList * list = g_slist_find(conn_data->ma->conn_data_list, conn_data); if(list) { conn_data->ma->conn_data_list = g_slist_delete_link(conn_data->ma->conn_data_list, list); } } purple_debug_info(MB_NET, "freeing self at %p\n", conn_data); g_free(conn_data); } void mb_conn_data_set_retry(MbConnData * data, gint retry) { data->max_retry = retry; } gchar * mb_conn_url_unparse(MbConnData * data) { gchar port_str[20]; if( ((data->port == 80) && !(data->is_ssl)) || ((data->port == 443) && (data->is_ssl))) { port_str[0] = '\0'; } else { snprintf(port_str, 19, ":%hd", data->port); } // parameter is ignored here since we handle header ourself return g_strdup_printf("%s%s%s%s%s", data->is_ssl ? "https://" : "http://", data->host, port_str, (data->request->path[0] == '/') ? "" : "/", data->request->path ); } void mb_conn_fetch_url_cb(PurpleUtilFetchUrlData * url_data, gpointer user_data, const gchar * url_text, gsize len, const gchar * error_message) { MbConnData * conn_data = (MbConnData *)user_data; MbAccount * ma = conn_data->ma; gint retval; purple_debug_info(MB_NET, "%s: url_data = %p\n", __FUNCTION__, url_data); // in whatever situation, url_data should be handled only by libpurple conn_data->fetch_url_data = NULL; if(error_message != NULL) { mb_conn_data_free(conn_data); if(conn_data->handler) { retval = conn_data->handler(conn_data, conn_data->handler_data, error_message); } if(ma->gc != NULL) { purple_connection_error_reason(ma->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); } } else { mb_http_data_post_read(conn_data->response, url_text, len); if(conn_data->handler) { purple_debug_info(MB_NET, "going to call handler\n"); retval = conn_data->handler(conn_data, conn_data->handler_data, NULL); purple_debug_info(MB_NET, "handler returned, retval = %d\n", retval); if(retval == 0) { // Everything's good. Free data structure and go-on with usual works purple_debug_info(MB_NET, "everything's ok, freeing data\n"); mb_conn_data_free(conn_data); } else if(retval == -1) { // Something's wrong. Requeue the whole process conn_data->retry++; if(conn_data->retry <= conn_data->max_retry) { purple_debug_info(MB_NET, "handler return -1, conn_data %p, retry %d, max_retry = %d\n", conn_data, conn_data->retry, conn_data->max_retry); mb_http_data_truncate(conn_data->response); // retry again in 1 second purple_timeout_add_seconds(1, mb_conn_retry_request, conn_data); } else { purple_debug_info(MB_NET, "retry exceed %d > %d\n", conn_data->retry, conn_data->max_retry); mb_conn_data_free(conn_data); } } } } } static gboolean mb_conn_retry_request(gpointer data) { MbConnData * conn_data = (MbConnData *)data; mb_conn_process_request(conn_data); return FALSE; } void mb_conn_process_request(MbConnData * data) { gchar * url; purple_debug_info(MB_NET, "NEW mb_conn_process_request, conn_data = %p\n", data); purple_debug_info(MB_NET, "connecting to %s on port %hd\n", data->host, data->port); if(data->prepare_handler) { data->prepare_handler(data, data->prepare_handler_data, NULL); } url = mb_conn_url_unparse(data); // we manage user_agent by ourself so ignore this completely mb_http_data_prepare_write(data->request); data->fetch_url_data = purple_util_fetch_url_request(url, TRUE, "", TRUE, data->request->packet, TRUE, mb_conn_fetch_url_cb, (gpointer)data); g_free(url); } void mb_conn_error(MbConnData * data, PurpleConnectionError error, const char * description) { if(data->retry >= data->max_retry) { data->ma->state = PURPLE_DISCONNECTED; purple_connection_error_reason(data->ma->gc, error, description); } } gboolean mb_conn_max_retry_reach(MbConnData * data) { return (gboolean)(data->retry >= data->max_retry); } mbpurple-0.3.0/microblog/identica.c0000644000175000017500000003246711400373247016632 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #include #include #include //#include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include #include "mb_net.h" #include "mb_util.h" #ifdef _WIN32 # include #else # include # include # include #endif #include "twitter.h" #ifndef STATUSNET #define LOG_ID "idcim" #define _USER_GROUP "Identi.ca" #define _FRIENDS_USER "identi.ca" #define _PUBLIC_USER "idcpublic" #define _USER_USER "idcuser" #else #define LOG_ID "status.net" #define _FRIENDS_USER "status.net" #define _PUBLIC_USER "Public" #define _USER_USER "Personal" #define _USER_GROUP "Status.net" #endif MbConfig * _mb_conf = NULL; static void plugin_init(PurplePlugin *plugin) { purple_debug_info("twitterim", "plugin_init\n"); purple_debug_info("twitterim", "plugin = %p\n", plugin); purple_signal_register(plugin, "twitter-message", purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, purple_value_new(PURPLE_TYPE_POINTER), // MbAccount ta purple_value_new(PURPLE_TYPE_STRING), // gchar * name purple_value_new(PURPLE_TYPE_POINTER) // TwitterMsg cur_msg ); } static void plugin_destroy(PurplePlugin * plugin) { purple_debug_info("twitterim", "plugin_destroy\n"); purple_signal_unregister(plugin, "twitter-message"); } gboolean plugin_load(PurplePlugin *plugin) { PurpleAccountOption *option; #ifdef STATUSNET PurpleAccountUserSplit * split; #endif PurplePluginInfo *info = plugin->info; PurplePluginProtocolInfo *prpl_info = info->extra_info; purple_debug_info(LOG_ID, "plugin_load\n"); _mb_conf = (MbConfig *)g_malloc0(TC_MAX * sizeof(MbConfig)); // This is just the place to pass pointer to plug-in itself _mb_conf[TC_PLUGIN].conf = NULL; _mb_conf[TC_PLUGIN].def_str = (gchar *)plugin; _mb_conf[TC_HIDE_SELF].conf = g_strdup("hide_myself"); _mb_conf[TC_HIDE_SELF].def_bool = TRUE; option = purple_account_option_bool_new(_("Hide myself in conversation"), _mb_conf[TC_HIDE_SELF].conf, _mb_conf[TC_HIDE_SELF].def_bool); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_MSG_REFRESH_RATE].conf = g_strdup("msg_refresh_rate"); _mb_conf[TC_MSG_REFRESH_RATE].def_int = 60; option = purple_account_option_int_new(_("Message refresh rate (seconds)"), _mb_conf[TC_MSG_REFRESH_RATE].conf, _mb_conf[TC_MSG_REFRESH_RATE].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_INITIAL_TWEET].conf = g_strdup("init_tweet"); _mb_conf[TC_INITIAL_TWEET].def_int = 15; option = purple_account_option_int_new(_("Number of initial tweets"), _mb_conf[TC_INITIAL_TWEET].conf, _mb_conf[TC_INITIAL_TWEET].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_GLOBAL_RETRY].conf = g_strdup("global_retry"); _mb_conf[TC_GLOBAL_RETRY].def_int = 3 ; option = purple_account_option_int_new(_("Maximum number of retry"), _mb_conf[TC_GLOBAL_RETRY].conf, _mb_conf[TC_GLOBAL_RETRY].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); #ifndef STATUSNET _mb_conf[TC_HOST].conf = g_strdup("hostname"); _mb_conf[TC_HOST].def_str = g_strdup("identi.ca"); option = purple_account_option_string_new(_("Identi.ca hostname"), _mb_conf[TC_HOST].conf, _mb_conf[TC_HOST].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); #else split = purple_account_user_split_new(_("Server"), "status.net", '@'); prpl_info->user_splits = g_list_append(prpl_info->user_splits, split); #endif /* * No HTTPS for Identi.ca for now */ _mb_conf[TC_USE_HTTPS].conf = g_strdup("use_https"); _mb_conf[TC_USE_HTTPS].def_bool = FALSE; _mb_conf[TC_STATUS_UPDATE].conf = g_strdup("status_update"); _mb_conf[TC_STATUS_UPDATE].def_str = g_strdup("/api/statuses/update.xml"); option = purple_account_option_string_new(_("Status update path"), _mb_conf[TC_STATUS_UPDATE].conf, _mb_conf[TC_STATUS_UPDATE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_VERIFY_PATH].conf = g_strdup("verify"); _mb_conf[TC_VERIFY_PATH].def_str = g_strdup("/api/account/verify_credentials.xml"); option = purple_account_option_string_new(_("Account verification path"), _mb_conf[TC_VERIFY_PATH].conf, _mb_conf[TC_VERIFY_PATH].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_FRIENDS_TIMELINE].conf = g_strdup("friends_timeline"); _mb_conf[TC_FRIENDS_TIMELINE].def_str = g_strdup("/api/statuses/friends_timeline.xml"); option = purple_account_option_string_new(_("Friends timeline path"), _mb_conf[TC_FRIENDS_TIMELINE].conf, _mb_conf[TC_FRIENDS_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_USER_TIMELINE].conf = g_strdup("user_timeline"); _mb_conf[TC_USER_TIMELINE].def_str = g_strdup("/api/statuses/user_timeline.xml"); option = purple_account_option_string_new(_("User timeline path"), _mb_conf[TC_USER_TIMELINE].conf, _mb_conf[TC_USER_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_PUBLIC_TIMELINE].conf = g_strdup("public_timeline"); _mb_conf[TC_PUBLIC_TIMELINE].def_str = g_strdup("/api/statuses/public_timeline.xml"); option = purple_account_option_string_new(_("Public timeline path"), _mb_conf[TC_PUBLIC_TIMELINE].conf, _mb_conf[TC_PUBLIC_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); // and now for non-option global _mb_conf[TC_FRIENDS_USER].def_str = g_strdup(_FRIENDS_USER); _mb_conf[TC_PUBLIC_USER].def_str = g_strdup(_PUBLIC_USER); _mb_conf[TC_USER_USER].def_str = g_strdup(_USER_USER); _mb_conf[TC_USER_GROUP].def_str = g_strdup(_USER_GROUP); return TRUE; } gboolean plugin_unload(PurplePlugin *plugin) { gint i; purple_debug_info(LOG_ID, "plugin_unload\n"); g_free(_mb_conf[TC_HOST].def_str); g_free(_mb_conf[TC_STATUS_UPDATE].def_str); g_free(_mb_conf[TC_VERIFY_PATH].def_str); g_free(_mb_conf[TC_FRIENDS_TIMELINE].def_str); g_free(_mb_conf[TC_USER_TIMELINE].def_str); g_free(_mb_conf[TC_PUBLIC_TIMELINE].def_str); g_free(_mb_conf[TC_FRIENDS_USER].def_str); g_free(_mb_conf[TC_PUBLIC_USER].def_str); g_free(_mb_conf[TC_USER_USER].def_str); for(i = 0; i < TC_MAX; i++) { if(_mb_conf[i].conf) g_free(_mb_conf[i].conf); } g_free(_mb_conf); return TRUE; } const char * idcim_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { #ifndef STATUSNET return "identica"; #else return "statusnet"; #endif } GList * idcim_actions(PurplePlugin *plugin, gpointer context) { GList *m = NULL; /* PurplePluginAction *act; act = purple_plugin_action_new(_("Set Facebook status..."), idcim_set_status_cb); m = g_list_append(m, act); act = purple_plugin_action_new(_("Search for buddies..."), idcim_search_users); m = g_list_append(m, act); */ return m; } PurplePluginProtocolInfo prpl_info = { /* options */ OPT_PROTO_UNIQUE_CHATNAME, NULL, /* user_splits */ NULL, /* protocol_options */ //NO_BUDDY_ICONS /* icon_spec */ { /* icon_spec, a PurpleBuddyIconSpec */ "png,jpg,gif", /* format */ 0, /* min_width */ 0, /* min_height */ 50, /* max_width */ 50, /* max_height */ 10000, /* max_filesize */ PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */ }, idcim_list_icon, /* list_icon */ NULL, /* list_emblems */ twitter_status_text, /* status_text */ // idcim_tooltip_text,/* tooltip_text */ NULL, twitter_statuses, /* status_types */ NULL, /* blist_node_menu */ NULL, /* chat_info */ NULL, /* chat_info_defaults */ twitter_login, /* login */ twitter_close, /* close */ twitter_send_im, /* send_im */ NULL, /* set_info */ // idcim_send_typing, /* send_typing */ NULL, // idcim_get_info, /* get_info */ NULL, twitter_set_status,/* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ // idcim_add_buddy, /* add_buddy */ NULL, NULL, /* add_buddies */ // idcim_remove_buddy,/* remove_buddy */ NULL, NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL, /* join_chat */ NULL, /* reject chat invite */ NULL, /* get_chat_name */ NULL, /* chat_invite */ NULL, /* chat_leave */ NULL, /* chat_whisper */ NULL, /* chat_send */ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */ twitter_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, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* attention_types */ sizeof(PurplePluginProtocolInfo) /* struct_size */ }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /* type */ NULL, /* ui_requirement */ 0, /* flags */ NULL, /* dependencies */ PURPLE_PRIORITY_DEFAULT, /* priority */ #ifndef STATUSNET "prpl-mbpurple-identica", /* id */ "Identi.ca", /* name */ #else "prpl-mbpurple-laconica", /* id */ //< Keep the old name for compatibility sake "Status.net", /* name */ #endif MBPURPLE_VERSION, /* version */ #ifndef STATUSNET "Identi.ca data feeder", /* summary */ "Identi.ca data feeder", /* description */ #else "Status.net data feeder", /* summary */ "Status.net data feeder", /* description */ #endif "Somsak Sriprayoonsakul ", /* author */ "http://microblog-purple.googlecode.com/", /* homepage */ plugin_load, /* load */ plugin_unload, /* unload */ plugin_destroy, /* destroy */ NULL, /* ui_info */ &prpl_info, /* extra_info */ NULL, /* prefs_info */ idcim_actions, /* actions */ NULL, /* padding */ NULL, NULL, NULL }; #ifndef STATUSNET PURPLE_INIT_PLUGIN(idcim, plugin_init, info); #else PURPLE_INIT_PLUGIN(lcim, plugin_init, info); #endif mbpurple-0.3.0/microblog/mb_util.h0000644000175000017500000000342111400373247016476 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * Utility for microblog plug-in */ #ifndef __MB_UTIL__ #define __MB_UTIL__ #ifdef __cplusplus extern "C" { #endif #include "account.h" extern const char * mb_get_uri_txt(PurpleAccount * pa); extern time_t mb_mktime(char * time_str); extern void mb_account_set_ull(PurpleAccount * account, const char * name, unsigned long long value); extern unsigned long long mb_account_get_ull(PurpleAccount * account, const char * name, unsigned long long default_value); extern void mb_account_set_idhash(PurpleAccount * account, const char * name, GHashTable * id_hash); extern void mb_account_get_idhash(PurpleAccount * account, const char * name, GHashTable * id_hash); extern gchar * mb_url_unparse(const char * host, int port, const char * path, const char * params, gboolean use_https); #ifdef __cplusplus } #endif #endif mbpurple-0.3.0/microblog/mb_util.c0000644000175000017500000002072511400373247016477 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 # include #else # include # include # include #endif #define DBGID "mb_util" static const char * month_abb_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char * wday_abb_names[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", }; time_t mb_mktime(char * time_str) { struct tm msg_time; char * cur, * next, *tmp_cur, *tmp_next, oldval; int counter = 0, tmp_counter = 0, i; int cur_timezone = 0, sign = 1; time_t retval; msg_time.tm_isdst = 0; cur = time_str; next = strchr(cur, ' '); while(next) { oldval = (*next); (*next) = '\0'; switch(counter) { case 0 : // day of week for(i = 0; i < 7; i++) { if(strncasecmp(cur, wday_abb_names[i], 3) == 0) { msg_time.tm_wday = i +1; break; } } break; case 1 : //month name for(i = 0; i < 12; i++) { if(strncasecmp(cur, month_abb_names[i], 3) == 0) { msg_time.tm_mon = i; break; } } break; case 2 : // day of month msg_time.tm_mday = strtoul(cur, NULL, 10); break; case 3 : // HH:MM:SS tmp_cur = cur; tmp_next = strchr(cur, ':'); tmp_counter = 0; while(tmp_next) { switch(tmp_counter) { case 0 : msg_time.tm_hour = strtoul(tmp_cur, NULL, 10); break; case 1 : msg_time.tm_min = strtoul(tmp_cur, NULL, 10); break; } tmp_cur = tmp_next + 1; tmp_next =strchr(tmp_cur, ':'); tmp_counter++; } msg_time.tm_sec = strtoul(tmp_cur, NULL, 10); break; case 4 : // timezone if( (*cur) == '+') { cur++; } else if ( (*cur) == '-') { sign = -1; cur++; } cur_timezone = (int)strtol(cur, NULL, 10); cur_timezone = sign * (cur_timezone / 100) * 60 * 60 + (cur_timezone % 100) * 60; break; } (*next) = oldval; cur = next + 1; next = strchr(cur, ' '); counter++; } // what's left is year msg_time.tm_year = strtoul(cur, NULL, 10) - 1900; //#ifdef UTEST purple_debug_info(DBGID, "msg_time.tm_wday = %d\n", msg_time.tm_wday); purple_debug_info(DBGID, "msg_time.tm_mday = %d\n", msg_time.tm_mday); purple_debug_info(DBGID, "msg_time.tm_mon = %d\n", msg_time.tm_mon); purple_debug_info(DBGID, "msg_time.tm_year = %d\n", msg_time.tm_year); purple_debug_info(DBGID, "msg_time.tm_hour = %d\n", msg_time.tm_hour); purple_debug_info(DBGID, "msg_time.tm_min = %d\n", msg_time.tm_min); purple_debug_info(DBGID, "msg_time.tm_sec = %d\n", msg_time.tm_sec); purple_debug_info(DBGID, "cur_timezone = %d\n", cur_timezone); purple_debug_info(DBGID, "msg_time.tm_isdst = %d\n", msg_time.tm_isdst); purple_debug_info(DBGID, "finished\n"); //#endif #ifndef __WIN32 // Always return GMT time (not sure subtracting is right, but that's ultimately // irrelevant for twitter at least, twitter.com always returns +0000) retval = timegm(&msg_time) - cur_timezone; #else // Seems that on Windows mktime always return the correct date/time according to timezone // Convert back to GMT first then convert to local time // retval = purple_time_build(msg_time.tm_year + 1900, msg_time.tm_mon, msg_time.tm_mday, msg_time.tm_hour, msg_time.tm_min, msg_time.tm_sec); retval = (mktime(&msg_time) - cur_timezone) + wpurple_get_tz_offset(); #endif purple_debug_info(DBGID, "final msg_time = %ld\n", retval); return retval; } const char * mb_get_uri_txt(PurpleAccount * pa) { if (strcmp(pa->protocol_id, "prpl-mbpurple-twitter") == 0) { return "tw"; } else if(strcmp(pa->protocol_id, "prpl-mbpurple-identica") == 0) { return "idc"; } // no support for laconica for now return NULL; } void mb_account_set_ull(PurpleAccount * account, const char * name, unsigned long long value) { gchar * tmp_str; tmp_str = g_strdup_printf("%llu", value); purple_account_set_string(account, name, tmp_str); g_free(tmp_str); } unsigned long long mb_account_get_ull(PurpleAccount * account, const char * name, unsigned long long default_value) { const char * tmp_str; tmp_str = purple_account_get_string(account, name, NULL); if(tmp_str) { return strtoull(tmp_str, NULL, 10); } else { return default_value; } } static void mb_account_foreach_idhash(gpointer key, gpointer val, gpointer userdata) { GString * output = userdata; gchar * str_key = key; if(output->len > 0) { g_string_append_printf(output, ",%s", str_key); purple_debug_info(DBGID, "appending idhash %s\n", str_key); } else { g_string_append(output, str_key); purple_debug_info(DBGID, "setting idhash %s\n", str_key); } } // work around to save a idhash to account // Use , separater void mb_account_set_idhash(PurpleAccount * account, const char * name, GHashTable * id_hash) { GString * output = g_string_new(""); g_hash_table_foreach(id_hash, mb_account_foreach_idhash, output); purple_debug_info(DBGID, "set_idhash output value = %s\n", output->str); // empty string will be set if sent_id_hash is empty purple_account_set_string(account, name, output->str); g_string_free(output, TRUE); } // work around to load a idhash from account // Use , separater void mb_account_get_idhash(PurpleAccount * account, const char * name, GHashTable * id_hash) { const gchar * id_list; gchar * hash_val; gchar ** id_list_str, **tmp; id_list = purple_account_get_string(account, name, NULL); if(id_list && (strlen(id_list) > 0)) { purple_debug_info(DBGID, "got idlist = %s\n", id_list); id_list_str = g_strsplit(id_list, ",", 0); tmp = id_list_str; while( (*tmp) != NULL) { hash_val = g_strdup(*tmp); purple_debug_info(DBGID, "inserting value = %s\n", hash_val); g_hash_table_insert(id_hash, hash_val, hash_val); tmp++; } g_strfreev(id_list_str); } } gchar * mb_url_unparse(const char * host, int port, const char * path, const char * params, gboolean use_https) { gchar * proto = "http://"; if(use_https) { proto = "https://"; } if(port == 0) { return g_strdup_printf("%s%s%s%s%s", proto, host, path, params ? "?" : "", params ? params : ""); } else { return g_strdup_printf("%s%s:%d%s%s%s", proto, host, port, path, params ? "?" : "", params ? params : ""); } } #ifdef UTEST int main(int argc, char * argv[]) { /* time_t msg_time; char * twitter_time = strdup("Wed Jul 23 10:59:53 +0000 2008"); char * cur, * next, *tmp_cur, *tmp_next, oldval; int counter = 0, tmp_counter = 0, i; printf("test time = %s\n", twitter_time); printf("current timezone offset = %ld\n", timezone); printf("current dst offset = %ld\n", daylight); msg_time = mb_mktime(twitter_time); printf("time = %ld\n", msg_time); free(twitter_time); //printf("Converted time = %s\n", asctime(&msg_time)); */ printf("%s", mb_url_unparse("twitter.com", 0, "/oauth/authorize", "a=b&c=d", TRUE)); } #endif mbpurple-0.3.0/microblog/tw_cmd.h0000644000175000017500000000372411400373247016326 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ /** * Command handler for twitter-based protocol */ #ifndef __TW_CMD__ #define __TW_CMD__ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include "twitter.h" #ifdef __cplusplus extern "C" { #endif struct _TwCmdArg; typedef PurpleCmdRet (* TwCmdFunc)(PurpleConversation *, const gchar *, gchar **, gchar **, struct _TwCmdArg *); typedef struct _TwCmdArg { MbAccount * ma; TwCmdFunc func; void * data; } TwCmdArg; typedef struct { char * protocol_id; PurpleCmdId * cmd_id; TwCmdArg ** cmd_args; int cmd_id_num; } TwCmd; extern TwCmd * tw_cmd_init(const char * protocol_id); extern void tw_cmd_finalize(TwCmd * tc); #ifdef __cplusplus } #endif #endif mbpurple-0.3.0/microblog/twitterim.c0000644000175000017500000004203311400373247017070 0ustar somsaksomsak/* Copyright 2008-2010, Somsak Sriprayoonsakul 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 . Some part of the code is copied from facebook-pidgin protocols. For the facebook-pidgin projects, please see http://code.google.com/p/pidgin-facebookchat/. Courtesy to eionrobb at gmail dot com */ #include #include #include #include #include #include #include #include #include #ifndef G_GNUC_NULL_TERMINATED # if __GNUC__ >= 4 # define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) # else # define G_GNUC_NULL_TERMINATED # endif /* __GNUC__ >= 4 */ #endif /* G_GNUC_NULL_TERMINATED */ #include #include #include #include #include #include #include #include #include #include //#include #include "mb_net.h" #include "mb_util.h" #include "tw_cmd.h" #ifdef _WIN32 # include #else # include # include # include #endif #include "twitter.h" #include "mb_cache_util.h" MbConfig * _mb_conf = NULL; static TwCmd * tw_cmd = NULL; PurplePlugin * prpl_plugin = NULL; void twitterim_remove_oauth(PurplePluginAction * action); static void plugin_init(PurplePlugin *plugin) { purple_debug_info("twitterim", "plugin_init\n"); purple_debug_info("twitterim", "plugin = %p\n", plugin); purple_signal_register(plugin, "twitter-message", purple_marshal_VOID__POINTER_POINTER_POINTER, NULL, 3, purple_value_new(PURPLE_TYPE_POINTER), // MbAccount ta purple_value_new(PURPLE_TYPE_STRING), // gchar * name purple_value_new(PURPLE_TYPE_POINTER) // TwitterMsg cur_msg ); mb_cache_init(); } static void plugin_destroy(PurplePlugin * plugin) { purple_debug_info("twitterim", "plugin_destroy\n"); purple_signal_unregister(plugin, "twitter-message"); } gboolean plugin_load(PurplePlugin *plugin) { PurpleAccountOption *option; PurplePluginInfo *info = plugin->info; PurplePluginProtocolInfo *prpl_info = info->extra_info; GList * auth_type_list = NULL; PurpleKeyValuePair * kv; purple_debug_info("twitterim", "plugin_load\n"); _mb_conf = (MbConfig *)g_malloc0(TC_MAX * sizeof(MbConfig)); // This is just the place to pass pointer to plug-in itself _mb_conf[TC_PLUGIN].conf = NULL; _mb_conf[TC_PLUGIN].def_str = (gchar *)plugin; // Authentication types _mb_conf[TC_AUTH_TYPE].conf = g_strdup("twitter_auth_type"); _mb_conf[TC_AUTH_TYPE].def_str = g_strdup(mb_auth_types_str[MB_OAUTH]); kv = g_new(PurpleKeyValuePair, 1); kv->key = g_strdup("OAuth"); kv->value = g_strdup((char *)mb_auth_types_str[MB_OAUTH]); auth_type_list = g_list_append(auth_type_list, kv); kv = g_new(PurpleKeyValuePair, 1); kv->key = g_strdup("XAuth"); kv->value = g_strdup((char *)mb_auth_types_str[MB_XAUTH]); auth_type_list = g_list_append(auth_type_list, kv); kv = g_new(PurpleKeyValuePair, 1); kv->key = g_strdup("HTTP Basic Authentication (old method)"); kv->value = g_strdup((char *)mb_auth_types_str[MB_HTTP_BASICAUTH]); auth_type_list = g_list_append(auth_type_list, kv); option = purple_account_option_list_new(_("Authentication Method"), _mb_conf[TC_AUTH_TYPE].conf, auth_type_list); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); // Hide myself _mb_conf[TC_HIDE_SELF].conf = g_strdup("twitter_hide_myself"); _mb_conf[TC_HIDE_SELF].def_bool = TRUE; option = purple_account_option_bool_new(_("Hide myself in conversation"), _mb_conf[TC_HIDE_SELF].conf, _mb_conf[TC_HIDE_SELF].def_bool); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_PRIVACY].conf = g_strdup("twitter_privacy"); _mb_conf[TC_PRIVACY].def_bool = FALSE; option = purple_account_option_bool_new(_("Not receive messages while unavailable"), _mb_conf[TC_PRIVACY].conf, _mb_conf[TC_PRIVACY].def_bool); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_MSG_REFRESH_RATE].conf = g_strdup("twitter_msg_refresh_rate"); _mb_conf[TC_MSG_REFRESH_RATE].def_int = 60; option = purple_account_option_int_new(_("Message refresh rate (seconds)"), _mb_conf[TC_MSG_REFRESH_RATE].conf, _mb_conf[TC_MSG_REFRESH_RATE].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_INITIAL_TWEET].conf = g_strdup("twitter_init_tweet"); _mb_conf[TC_INITIAL_TWEET].def_int = 15; option = purple_account_option_int_new(_("Number of initial tweets"), _mb_conf[TC_INITIAL_TWEET].conf, _mb_conf[TC_INITIAL_TWEET].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_GLOBAL_RETRY].conf = g_strdup("twitter_global_retry"); _mb_conf[TC_GLOBAL_RETRY].def_int = 3 ; option = purple_account_option_int_new(_("Maximum number of retry"), _mb_conf[TC_GLOBAL_RETRY].conf, _mb_conf[TC_GLOBAL_RETRY].def_int); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_HOST].conf = g_strdup("twitter_hostname"); _mb_conf[TC_HOST].def_str = g_strdup("api.twitter.com"); option = purple_account_option_string_new(_("Hostname"), _mb_conf[TC_HOST].conf, _mb_conf[TC_HOST].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_USE_HTTPS].conf = g_strdup("twitter_use_https"); _mb_conf[TC_USE_HTTPS].def_bool = TRUE; option = purple_account_option_bool_new(_("Use HTTPS"), _mb_conf[TC_USE_HTTPS].conf, _mb_conf[TC_USE_HTTPS].def_bool); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_STATUS_UPDATE].conf = g_strdup("twitter_status_update"); _mb_conf[TC_STATUS_UPDATE].def_str = g_strdup("/1/statuses/update.xml"); option = purple_account_option_string_new(_("Status update path"), _mb_conf[TC_STATUS_UPDATE].conf, _mb_conf[TC_STATUS_UPDATE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_VERIFY_PATH].conf = g_strdup("twitter_verify"); _mb_conf[TC_VERIFY_PATH].def_str = g_strdup("/1/account/verify_credentials.xml"); option = purple_account_option_string_new(_("Account verification path"), _mb_conf[TC_VERIFY_PATH].conf, _mb_conf[TC_VERIFY_PATH].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_FRIENDS_TIMELINE].conf = g_strdup("twitter_friends_timeline"); _mb_conf[TC_FRIENDS_TIMELINE].def_str = g_strdup("/1/statuses/home_timeline.xml"); option = purple_account_option_string_new(_("Home timeline path"), _mb_conf[TC_FRIENDS_TIMELINE].conf, _mb_conf[TC_FRIENDS_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_USER_TIMELINE].conf = g_strdup("twitter_user_timeline"); _mb_conf[TC_USER_TIMELINE].def_str = g_strdup("/1/statuses/user_timeline.xml"); option = purple_account_option_string_new(_("User timeline path"), _mb_conf[TC_USER_TIMELINE].conf, _mb_conf[TC_USER_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_PUBLIC_TIMELINE].conf = g_strdup("twitter_public_timeline"); _mb_conf[TC_PUBLIC_TIMELINE].def_str = g_strdup("/1/statuses/public_timeline.xml"); option = purple_account_option_string_new(_("Public timeline path"), _mb_conf[TC_PUBLIC_TIMELINE].conf, _mb_conf[TC_PUBLIC_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_REPLIES_TIMELINE].conf = g_strdup("twitter_replies_timeline"); _mb_conf[TC_REPLIES_TIMELINE].def_str = g_strdup("/1/statuses/mentions.xml"); option = purple_account_option_string_new(_("Mentions timeline path"), _mb_conf[TC_REPLIES_TIMELINE].conf, _mb_conf[TC_REPLIES_TIMELINE].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); // critical options with no setting page _mb_conf[TC_OAUTH_TOKEN].conf = g_strdup("twitter_oauth_token"); _mb_conf[TC_OAUTH_TOKEN].def_str = NULL; _mb_conf[TC_OAUTH_SECRET].conf = g_strdup("twitter_oauth_secret"); _mb_conf[TC_OAUTH_SECRET].def_str = NULL; // and now for non-option global _mb_conf[TC_FRIENDS_USER].def_str = g_strdup("twitter.com"); _mb_conf[TC_REPLIES_USER].def_str = g_strdup("twitter.com"); _mb_conf[TC_PUBLIC_USER].def_str = g_strdup("twpublic"); _mb_conf[TC_USER_USER].def_str = g_strdup("twuser"); _mb_conf[TC_USER_GROUP].def_str = g_strdup("Twitter"); // OAuth stuff _mb_conf[TC_CONSUMER_KEY].def_str = g_strdup("PCWAdQpyyR12ezp2fVwEhw"); _mb_conf[TC_CONSUMER_SECRET].def_str = g_strdup("EveLmCXJIg2R7BTCpm6OWV8YyX49nI0pxnYXh7JMvDg"); _mb_conf[TC_REQUEST_TOKEN_URL].conf = g_strdup("twitter_oauth_request_token_url"); _mb_conf[TC_REQUEST_TOKEN_URL].def_str = g_strdup("/oauth/request_token"); option = purple_account_option_string_new(_("OAuth request token path"), _mb_conf[TC_REQUEST_TOKEN_URL].conf, _mb_conf[TC_REQUEST_TOKEN_URL].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_ACCESS_TOKEN_URL].conf = g_strdup("twitter_oauth_access_token_url"); _mb_conf[TC_ACCESS_TOKEN_URL].def_str = g_strdup("/oauth/access_token"); option = purple_account_option_string_new(_("OAuth access token path"), _mb_conf[TC_ACCESS_TOKEN_URL].conf, _mb_conf[TC_ACCESS_TOKEN_URL].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); _mb_conf[TC_AUTHORIZE_URL].conf = g_strdup("twitter_oauth_authorize_token_url"); _mb_conf[TC_AUTHORIZE_URL].def_str = g_strdup("/oauth/authorize"); option = purple_account_option_string_new(_("OAuth authorize path"), _mb_conf[TC_AUTHORIZE_URL].conf, _mb_conf[TC_AUTHORIZE_URL].def_str); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); // command support tw_cmd = tw_cmd_init(info->id); return TRUE; } gboolean plugin_unload(PurplePlugin *plugin) { gint i; purple_debug_info("twitterim", "plugin_unload\n"); tw_cmd_finalize(tw_cmd); tw_cmd = NULL; g_free(_mb_conf[TC_CONSUMER_KEY].def_str); g_free(_mb_conf[TC_CONSUMER_SECRET].def_str); g_free(_mb_conf[TC_REQUEST_TOKEN_URL].def_str); g_free(_mb_conf[TC_ACCESS_TOKEN_URL].def_str); g_free(_mb_conf[TC_AUTHORIZE_URL].def_str); g_free(_mb_conf[TC_HOST].def_str); g_free(_mb_conf[TC_STATUS_UPDATE].def_str); g_free(_mb_conf[TC_VERIFY_PATH].def_str); g_free(_mb_conf[TC_FRIENDS_TIMELINE].def_str); g_free(_mb_conf[TC_USER_TIMELINE].def_str); g_free(_mb_conf[TC_PUBLIC_TIMELINE].def_str); g_free(_mb_conf[TC_FRIENDS_USER].def_str); g_free(_mb_conf[TC_PUBLIC_USER].def_str); g_free(_mb_conf[TC_USER_USER].def_str); g_free(_mb_conf[TC_USER_GROUP].def_str); g_free(_mb_conf[TC_AUTH_TYPE].def_str); for(i = 0; i < TC_MAX; i++) { if(_mb_conf[i].conf) { g_free(_mb_conf[i].conf); } } g_free(_mb_conf); return TRUE; } const char * twitterim_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { return "twitter"; } GList * twitterim_actions(PurplePlugin *plugin, gpointer context) { GList *m = NULL; PurplePluginAction *act; act = purple_plugin_action_new(_("Remove access credential (Oauth Token)"), twitterim_remove_oauth); m = g_list_append(m, act); return m; } void twitterim_remove_oauth(PurplePluginAction * action) { PurpleConnection * gc = action->context; purple_account_remove_setting(gc->account, _mb_conf[TC_OAUTH_TOKEN].conf); purple_account_remove_setting(gc->account, _mb_conf[TC_OAUTH_SECRET].conf); purple_notify_formatted(gc->account, _("Access credential removed"), _("Your access credential was removed"), NULL, _("Access credential will be requested again at the next log-in time"), NULL, NULL); } PurplePluginProtocolInfo twitter_prpl_info = { /* options */ OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_REGISTER_NOSCREENNAME, NULL, /* user_splits */ NULL, /* protocol_options */ //NO_BUDDY_ICONS /* icon_spec */ { /* icon_spec, a PurpleBuddyIconSpec */ "png,jpg,gif", /* format */ 0, /* min_width */ 0, /* min_height */ 50, /* max_width */ 50, /* max_height */ 10000, /* max_filesize */ PURPLE_ICON_SCALE_DISPLAY, /* scale_rules */ }, twitterim_list_icon, /* list_icon */ NULL, /* list_emblems */ twitter_status_text, /* status_text */ // twitterim_tooltip_text,/* tooltip_text */ NULL, twitter_statuses, /* status_types */ NULL, /* blist_node_menu */ NULL, /* chat_info */ NULL, /* chat_info_defaults */ twitter_login, /* login */ twitter_close, /* close */ twitter_send_im, /* send_im */ NULL, /* set_info */ // twitterim_send_typing, /* send_typing */ NULL, // twitterim_get_info, /* get_info */ NULL, twitter_set_status,/* set_status */ NULL, /* set_idle */ NULL, /* change_passwd */ // twitterim_add_buddy, /* add_buddy */ NULL, NULL, /* add_buddies */ // twitterim_remove_buddy,/* remove_buddy */ NULL, NULL, /* remove_buddies */ NULL, /* add_permit */ NULL, /* add_deny */ NULL, /* rem_permit */ NULL, /* rem_deny */ NULL, /* set_permit_deny */ NULL, /* join_chat */ NULL, /* reject chat invite */ NULL, /* get_chat_name */ NULL, /* chat_invite */ NULL, /* chat_leave */ NULL, /* chat_whisper */ NULL, /* chat_send */ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */ twitter_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, /* send_file */ NULL, /* new_xfer */ NULL, /* offline_message */ NULL, /* whiteboard_prpl_ops */ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ NULL, /* unregister_user */ NULL, /* send_attention */ NULL, /* attention_types */ sizeof(PurplePluginProtocolInfo) /* struct_size */ }; static PurplePluginInfo info = { PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /* type */ NULL, /* ui_requirement */ 0, /* flags */ NULL, /* dependencies */ PURPLE_PRIORITY_DEFAULT, /* priority */ "prpl-mbpurple-twitter", /* id */ "TwitterIM", /* name */ MBPURPLE_VERSION, /* version */ "Twitter data feeder", /* summary */ "Twitter data feeder", /* description */ "Somsak Sriprayoonsakul ", /* author */ "http://microblog-purple.googlecode.com/", /* homepage */ plugin_load, /* load */ plugin_unload, /* unload */ plugin_destroy, /* destroy */ NULL, /* ui_info */ &twitter_prpl_info, /* extra_info */ NULL, /* prefs_info */ twitterim_actions, /* actions */ NULL, /* padding */ NULL, NULL, NULL }; PURPLE_INIT_PLUGIN(twitterim, plugin_init, info); mbpurple-0.3.0/microblog/Makefile0000644000175000017500000000766611400373247016351 0ustar somsaksomsak# # Microblog protocol plug-in # all: build include ../global.mak TARGETS = liboldtwitter$(PLUGIN_SUFFIX) libtwitter$(PLUGIN_SUFFIX) libidentica$(PLUGIN_SUFFIX) libstatusnet$(PLUGIN_SUFFIX) PRPLS = twitter identica statusnet #PURPLE_CFLAGS = $(shell pkg-config --cflags purple) #PURPLE_LIBS = $(shell pkg-config --libs purple) CFLAGS := $(PURPLE_CFLAGS) LD = $(CC) OLDTWITTER_C_SRC = dummy_twitterim.c OLDTWITTER_OBJ = $(OLDTWITTER_C_SRC:%.c=%.o) TWITTER_C_SRC = twitter.c mb_util.c mb_http.c mb_net.c mb_cache.c twitterim.c tw_util.c tw_cmd.c mb_oauth.c TWITTER_H_SRC = twitter.h mb_util.h mb_http.h mb_net.h tw_cmd.h mb_cache.h mb_oauth.h mb_cache.h mb_cache_util.h TWITTER_IMG = twitter16.png twitter22.png twitter48.png TWITTER_OBJ = $(TWITTER_C_SRC:%.c=%.o) IDENTICA_C_SRC = identica.c mb_util.c mb_http.c mb_net.c mb_cache.c twitter.c tw_util.c mb_oauth.c IDENTICA_H_SRC = $(TWITTER_H_SRC) IDENTICA_IMG = identica16.png identica22.png identica48.png IDENTICA_OBJ = $(IDENTICA_C_SRC:%.c=%.o) statusnet.o: identica.c $(COMPILE.c) $(OUTPUT_OPTION) -DSTATUSNET $< STATUSNET_C_SRC = mb_util.c mb_http.c mb_net.c mb_cache.c twitter.c tw_util.c mb_oauth.c STATUSNET_H_SRC = $(TWITTER_H_SRC) STATUSNET_IMG = statusnet16.png statusnet22.png statusnet48.png STATUSNET_OBJ = $(STATUSNET_C_SRC:%.c=%.o) statusnet.o DISTFILES = $(OLDTWITTER_C_SRC) \ $(TWITTER_C_SRC) $(TWITTER_H_SRC) $(TWITTER_IMG) \ $(IDENTICA_H_SRC) $(IDENTICA_C_SRC) $(IDENTICA_IMG) \ $(STATUSNET_H_SRC) $(STATUSNET_C_SRC) $(STATUSNET_IMG) \ Makefile OBJECTS = $(OLDTWITTER_OBJ) $(TWITTER_OBJ) $(IDENTICA_OBJ) $(STATUSNET_OBJ) .PHONY: all clean install uninstall build: $(TARGETS) install: $(TARGETS) # remove old plug-in rm -f $(PURPLE_PLUGIN_DIR)/liblaconica$(PLUGIN_SUFFIX) for dir in 16 22 48; do \ rm -f $(PURPLE_PROTOCOL_PIXMAP_DIR)/$$dir/laconica.png; \ done # install plug-in for prpl in $(PRPLS); do \ rm -f $(PURPLE_PLUGIN_DIR)/lib$$prpl$(PLUGIN_SUFFIX); \ done install -m 0755 -d $(PURPLE_PLUGIN_DIR) cp liboldtwitter$(PLUGIN_SUFFIX) $(PURPLE_PLUGIN_DIR)/liboldtwitter$(PLUGIN_SUFFIX) for prpl in $(PRPLS); do \ ( cp lib$$prpl$(PLUGIN_SUFFIX) $(PURPLE_PLUGIN_DIR)/lib$$prpl$(PLUGIN_SUFFIX) && \ for dir in 16 22 48; do \ (install -m 0755 -d $(PURPLE_PROTOCOL_PIXMAP_DIR)/$$dir && \ install -m 0644 $$prpl$$dir.png $(PURPLE_PROTOCOL_PIXMAP_DIR)/$$dir/$$prpl.png ) \ done ) \ done uninstall: for prpl in $(PRPLS); do \ rm -f $(PURPLE_PLUGIN_DIR)/lib$$prpl$(PLUGIN_SUFFIX); \ done rm -f $(PURPLE_PLUGIN_DIR)/liboldtwitter$(PLUGIN_SUFFIX) for dir in 16 22 48; do \ rm -f $(PURPLE_PROTOCOL_PIXMAP_DIR)/$$dir/{twitter,identica,statusnet}.png ; \ done clean: rm -f $(TARGETS) $(OBJECTS) liboldtwitter$(PLUGIN_SUFFIX): $(OLDTWITTER_OBJ) $(LD) $(LDFLAGS) -shared $(OLDTWITTER_OBJ) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ libtwitter$(PLUGIN_SUFFIX): $(TWITTER_OBJ) $(LD) $(LDFLAGS) -shared $(TWITTER_OBJ) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ libidentica$(PLUGIN_SUFFIX): $(IDENTICA_OBJ) $(LD) $(LDFLAGS) -shared $(IDENTICA_OBJ) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ libstatusnet$(PLUGIN_SUFFIX): $(STATUSNET_OBJ) $(LD) $(LDFLAGS) -shared $(STATUSNET_OBJ) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ mbchprpl$(EXE_SUFFIX): mbchprpl.o $(CC) $< $(LIB_PATHS) $(LIBS) $(LDFLAGS) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ xml_tester$(EXE_SUFFIX): xml_tester.c $(CC) $< $(LIB_PATHS) $(LIBS) $(LDFLAGS) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ test_mb_http$(EXE_SUFFIX): mb_http.c $(CC) $(CFLAGS) -DUTEST $< $(LIB_PATHS) $(LIBS) $(LDFLAGS) $(PURPLE_LIBS) $(DLL_LD_FLAGS) -o $@ mb_http.o: mb_http.c mb_http.h twitter.h Makefile mb_net.o: mb_net.c mb_net.h mb_http.h twitter.h Makefile mb_util.o: mb_util.c twitter.h Makefile twitter.o: twitter.c mb_net.h mb_http.h twitter.h mb_util.h mb_cache.h mb_oauth.h Makefile mb_cache.o: mb_cache.c twitter.h mb_oauth.o: mb_oauth.c mb_oauth.h twitter.h twitterim.o: twitter.o mb_http.o mb_net.o mb_util.o mb_cache.o mb_oauth.o Makefile identica.o: twitter.o Makefile mbpurple-0.3.0/certs/0000755000175000017500000000000011400373247014035 5ustar somsaksomsakmbpurple-0.3.0/certs/EquifaxSecureGlobaleBusinessCA.pem0000644000175000017500000000170411400373247022521 0ustar somsaksomsak-----BEGIN CERTIFICATE----- MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc 58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv 8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV -----END CERTIFICATE----- mbpurple-0.3.0/certs/Makefile0000644000175000017500000000053511400373247015500 0ustar somsaksomsakall: build include ../global.mak CERTS = EquifaxSecureGlobaleBusinessCA.pem build clean: install : install -d $(PURPLE_CACERTS_DIR) for cert in $(CERTS); do \ install -m 0644 $$cert $(PURPLE_CACERTS_DIR)/$$cert; \ done uninstall : for cert in $(CERTS); do \ rm -f $(PURPLE_CACERTS_DIR)/$$cert; \ done DISTFILES = $(CERTS) Makefile mbpurple-0.3.0/mbpurple.nsi0000644000175000017500000001336011400373247015261 0ustar somsaksomsak; Script based on the Skype4Pidgin, Off-the-Record Messaging and Facebook Chat NSI files SetCompress auto SetCompressor lzma ; todo: SetBrandingImage ; HM NIS Edit Wizard helper defines !define PRODUCT_NAME "pidgin-microblog" ;!define PRODUCT_VERSION "0.1.1" !define PRODUCT_PUBLISHER "Somsak Sriprayoonsakul" !define PRODUCT_WEB_SITE "http://microblog-purple.googlecode.com/" !define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" !define PRODUCT_UNINST_ROOT_KEY "HKLM" ; MUI 1.67 compatible ------ !include "MUI.nsh" ; MUI Settings !define MUI_ABORTWARNING !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" ; Welcome page !insertmacro MUI_PAGE_WELCOME ; License page !insertmacro MUI_PAGE_LICENSE "COPYING" ; Instfiles page !insertmacro MUI_PAGE_INSTFILES !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_TEXT "Run Pidgin" !define MUI_FINISHPAGE_RUN_FUNCTION "RunPidgin" !insertmacro MUI_PAGE_FINISH ; Uninstaller pages !insertmacro MUI_UNPAGE_INSTFILES ; Language files !insertmacro MUI_LANGUAGE "English" !define SHELLFOLDERS "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" ; MUI end ------ Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" OutFile "${PRODUCT_NAME}.exe" Var "PidginDir" ShowInstDetails show ShowUnInstDetails show Section "MainSection" SEC01 ;Check for pidgin installation Call GetPidginInstPath ; Removing deprecated file Delete "$PidginDir\protocols\16\laconica.png" Delete "$PidginDir\protocols\22\laconica.png" Delete "$PidginDir\protocols\48\laconica.png" SetOverwrite try ; Icon SetOutPath "$PidginDir\pixmaps\pidgin" File "/oname=protocols\16\twitter.png" "microblog\twitter16.png" File "/oname=protocols\22\twitter.png" "microblog\twitter22.png" File "/oname=protocols\48\twitter.png" "microblog\twitter48.png" File "/oname=protocols\16\identica.png" "microblog\identica16.png" File "/oname=protocols\22\identica.png" "microblog\identica22.png" File "/oname=protocols\48\identica.png" "microblog\identica48.png" File "/oname=protocols\16\statusnet.png" "microblog\statusnet16.png" File "/oname=protocols\22\statusnet.png" "microblog\statusnet22.png" File "/oname=protocols\48\statusnet.png" "microblog\statusnet48.png" ;CA Certs SetOverwrite try SetOutPath "$PidginDir\ca-certs" File "certs\EquifaxSecureGlobaleBusinessCA.pem" ; main DLL SetOverwrite try copy: ClearErrors ;Delete "$PidginDir\plugins\libtwitter.dll" Delete "$PidginDir\plugins\liblaconica.dll" IfErrors dllbusy SetOutPath "$PidginDir\plugins" File "microblog\libtwitter.dll" File "microblog\libidentica.dll" File "microblog\libstatusnet.dll" File "twitgin\twitgin.dll" ;SetOutPath "$PidginDir" ;File "microblog\mbchprpl.exe" writeUninstaller "$PidginDir\pidgin-microblog-uninst.exe" WriteRegStr "${PRODUCT_UNINST_ROOT_KEY}" "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME} - Microblogging support for Pidgin" WriteRegStr "${PRODUCT_UNINST_ROOT_KEY}" "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" WriteRegStr "${PRODUCT_UNINST_ROOT_KEY}" "${PRODUCT_UNINST_KEY}" "UninstallString" "$PidginDir\pidgin-microblog-uninst.exe" Goto after_copy dllbusy: MessageBox MB_RETRYCANCEL "DLLs are busy. Please close Pidgin (including tray icon) and try again" IDCANCEL cancel Goto copy cancel: Abort "Installation of pidgin-microblog aborted" after_copy: ;Call FixAccount SectionEnd Section "Uninstall" Call un.GetPidginInstPath uninstall: ; certs Delete "$PidginDir\ca-certs\EquifaxSecureGlobaleBusinessCA.pem" ; icons Delete "$PidginDir\protocols\16\twitter.png" Delete "$PidginDir\protocols\22\twitter.png" Delete "$PidginDir\protocols\48\twitter.png" Delete "$PidginDir\protocols\16\identica.png" Delete "$PidginDir\protocols\22\identica.png" Delete "$PidginDir\protocols\48\identica.png" Delete "$PidginDir\protocols\16\statusnet.png" Delete "$PidginDir\protocols\22\statusnet.png" Delete "$PidginDir\protocols\48\statusnet.png" ; main DLLs Delete "$PidginDir\plugins\libtwitter.dll" Delete "$PidginDir\plugins\libidentica.dll" Delete "$PidginDir\plugins\libstatusnet.dll" Delete "$PidginDir\plugins\twitgin.dll" IfErrors dllbusy Delete "$PidginDir\pidgin-microblog-uninst.exe" DeleteRegKey "${PRODUCT_UNINST_ROOT_KEY}" "${PRODUCT_UNINST_KEY}" goto afteruninstall dllbusy: MessageBox MB_RETRYCANCEL "DLLs are busy. Please close Pidgin (including tray icon) and try again" IDCANCEL cancel Goto uninstall cancel: Abort "Uninstall of pidgin-microblog aborted" afteruninstall: SectionEnd Function GetPidginInstPath Push $0 ReadRegStr $0 HKLM "Software\pidgin" "" IfFileExists "$0\pidgin.exe" cont ReadRegStr $0 HKCU "Software\pidgin" "" IfFileExists "$0\pidgin.exe" cont MessageBox MB_OK|MB_ICONINFORMATION "Failed to find Pidgin installation." Abort "Failed to find Pidgin installation. Please install Pidgin first." cont: StrCpy $PidginDir $0 FunctionEnd Function un.GetPidginInstPath Push $0 ReadRegStr $0 HKLM "Software\pidgin" "" IfFileExists "$0\pidgin.exe" cont ReadRegStr $0 HKCU "Software\pidgin" "" IfFileExists "$0\pidgin.exe" cont MessageBox MB_OK|MB_ICONINFORMATION "Failed to find Pidgin installation." Abort "Failed to find Pidgin installation. Please install Pidgin first." cont: StrCpy $PidginDir $0 FunctionEnd Function RunPidgin ExecShell "" "$PidginDir\pidgin.exe" FunctionEnd Function FixAccount ReadRegStr $0 HKCU "${SHELLFOLDERS}" AppData StrCmp $0 "" 0 +2 ReadRegStr $0 HKLM "${SHELLFOLDERS}" "Common AppData" StrCmp $0 "" 0 +2 StrCpy $0 "$WINDIR\Application Data" IfFileExists "$0\Roaming\.purple\accounts.xml" cont cont: ExecWait "$PidginDir\mbchprpl.exe" FunctionEnd mbpurple-0.3.0/COPYING0000644000175000017500000010451311400373247013754 0ustar somsaksomsak GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . mbpurple-0.3.0/version.mak0000644000175000017500000000006311400373247015073 0ustar somsaksomsakPACKAGE := mbpurple VERSION := 0.3.0 SUBVERSION := mbpurple-0.3.0/Makefile0000644000175000017500000000323011400373247014353 0ustar somsaksomsak# # Top-level makefile # include version.mak SUBDIRS = microblog twitgin certs DISTFILES = COPYING global.mak Makefile mbpurple.nsi README.txt version.mak subversion.mak .PHONY: all install clean build distdir default: subversion build build install uninstall clean : for dir in $(SUBDIRS); do \ make -C "$$dir" $@ || exit 1; \ done distdir: rm -rf $(PACKAGE)-$(VERSION)$(SUBVERSION) mkdir $(PACKAGE)-$(VERSION)$(SUBVERSION) for dir in $(SUBDIRS); do \ make -C "$$dir" dist; \ done cp -f $(DISTFILES) $(PACKAGE)-$(VERSION)$(SUBVERSION)/ dist: distdir tar -zcvf $(PACKAGE)-$(VERSION)$(SUBVERSION).tar.gz $(PACKAGE)-$(VERSION)$(SUBVERSION) rm -rf $(PACKAGE)-$(VERSION)$(SUBVERSION) windist: pidgin-microblog-$(VERSION)$(SUBVERSION).exe pidgin-microblog.exe: build mbpurple.nsi makensis /DPRODUCT_VERSION=$(VERSION)$(SUBVERSION) mbpurple.nsi pidgin-microblog-$(VERSION)$(SUBVERSION).exe: pidgin-microblog.exe mv -f $< $@ zipdist: pidgin-microblog-$(VERSION)$(SUBVERSION).zip pidgin-microblog.zip: build rm -rf $(PACKAGE)-$(VERSION)$(SUBVERSION) mkdir $(PACKAGE)-$(VERSION)$(SUBVERSION) PURPLE_PLUGIN_DIR=$(PWD)/$(PACKAGE)-$(VERSION)$(SUBVERSION)/plugins && \ PURPLE_INSTALL_DIR=$(PWD)/$(PACKAGE)-$(VERSION)$(SUBVERSION) && \ mkdir -p $$PURPLE_PLUGIN_DIR && \ for dir in $(SUBDIRS); do \ make -C "$$dir" install PURPLE_INSTALL_DIR=$$PURPLE_INSTALL_DIR PURPLE_PLUGIN_DIR=$$PURPLE_PLUGIN_DIR; \ done zip -r $@ $(PACKAGE)-$(VERSION)$(SUBVERSION) rm -rf $(PACKAGE)-$(VERSION)$(SUBVERSION) pidgin-microblog-$(VERSION)$(SUBVERSION).zip: pidgin-microblog.zip mv -f $< $@ include subversion.makmbpurple-0.3.0/README.txt0000644000175000017500000000012711400373247014413 0ustar somsaksomsakPlease see http://code.google.com/p/microblog-purple/wiki/README for more informations.