changeset 26785:baa23b7a4d42

merge of '09144f9b78a3f3256423cae57132a43098c065de' and 'cc7bd8014e5aad2cfee2243816c1dbb53f7cd888'
author Paul Aurich <paul@darkrain42.org>
date Sun, 26 Apr 2009 22:55:58 +0000
parents 89f7e9924204 (diff) 5987aeb4b0a3 (current diff)
children f7b409c896cd fef989505ea2 6ab56b3dd836 e0d739a002b4
files
diffstat 10 files changed, 234 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Sun Apr 26 22:54:17 2009 +0000
+++ b/AUTHORS	Sun Apr 26 22:55:58 2009 +0000
@@ -25,6 +25,7 @@
 Bartosz Oler - Developer
 Etan 'deryni' Reisner - Developer
 Tim 'marv' Ringenbach - Developer
+Michael 'Maiku' Ruprecht - Developer, voice and video
 Elliott 'QuLogic' Sales de Andrade - Developer
 Luke 'LSchiere' Schierer - Support
 Megan 'Cae' Schneider - support/QA
--- a/ChangeLog	Sun Apr 26 22:54:17 2009 +0000
+++ b/ChangeLog	Sun Apr 26 22:55:58 2009 +0000
@@ -28,6 +28,7 @@
 	  type 'headline'.
 	* The Ad-Hoc commands associated with our server are now always shown at
 	  login.
+	* Support showing and reporting idle times (XEP-0256)
 
 	IRC:
 	* Correctly handle WHOIS for users who are joined to a large number of
--- a/doc/pidgin.1.in	Sun Apr 26 22:54:17 2009 +0000
+++ b/doc/pidgin.1.in	Sun Apr 26 22:55:58 2009 +0000
@@ -602,7 +602,9 @@
 .br
   Daniel 'datallah' Atallah (developer)
 .br
-  John 'rekkanoryo' Bailey (developer)
+  Paul 'darkrain42' Aurich (developer)
+.br
+  John 'rekkanoryo' Bailey (developer and bugmaster)
 .br
   Ethan 'Paco-Paco' Blanton (developer)
 .br
@@ -632,6 +634,8 @@
 .br
   Tim 'marv' Ringenbach (developer) <\fImarv_sf@users.sf.net\fR>
 .br
+  Michael 'Maiku' Ruprecht (developer, voice and video)
+.br
   Elliott 'QuLogic' Sales de Andrade (developer)
 .br
   Luke 'LSchiere' Schierer (support)
--- a/libpurple/plugins/statenotify.c	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/plugins/statenotify.c	Sun Apr 26 22:55:58 2009 +0000
@@ -71,9 +71,9 @@
                       void *data)
 {
 	if (purple_prefs_get_bool("/plugins/core/statenotify/notify_idle")) {
-		if (idle) {
+		if (idle && !old_idle) {
 			write_status(buddy, _("%s has become idle."));
-		} else {
+		} else if (!idle && old_idle) {
 			write_status(buddy, _("%s is no longer idle."));
 		}
 	}
--- a/libpurple/protocols/jabber/buddy.c	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sun Apr 26 22:55:58 2009 +0000
@@ -98,36 +98,41 @@
 
 	for(l = jb->resources; l; l = l->next)
 	{
-		if(!jbr && !resource) {
-			jbr = l->data;
-		} else if(!resource) {
-			if(((JabberBuddyResource *)l->data)->priority > jbr->priority)
-				jbr = l->data;
-			else if(((JabberBuddyResource *)l->data)->priority == jbr->priority) {
+		JabberBuddyResource *tmp = (JabberBuddyResource *) l->data;
+		if (!jbr && !resource) {
+			jbr = tmp;
+		} else if (!resource) {
+			if (tmp->priority > jbr->priority)
+				jbr = tmp;
+			else if (tmp->priority == jbr->priority) {
 				/* Determine if this resource is more available than the one we've currently chosen */
-				switch(((JabberBuddyResource *)l->data)->state) {
+				switch(tmp->state) {
 					case JABBER_BUDDY_STATE_ONLINE:
 					case JABBER_BUDDY_STATE_CHAT:
 						/* This resource is online/chatty. Prefer to one which isn't either. */
-						if ((jbr->state != JABBER_BUDDY_STATE_ONLINE) && (jbr->state != JABBER_BUDDY_STATE_CHAT))
-							jbr = l->data;
+						if (((jbr->state != JABBER_BUDDY_STATE_ONLINE) && (jbr->state != JABBER_BUDDY_STATE_CHAT))
+							|| (jbr->idle && !tmp->idle)
+							|| (jbr->idle && tmp->idle && tmp->idle > jbr->idle))
+							jbr = tmp;
 						break;
 					case JABBER_BUDDY_STATE_AWAY:
 					case JABBER_BUDDY_STATE_DND:
 						/* This resource is away/dnd. Prefer to one which is extended away, unavailable, or unknown. */
-						if ((jbr->state == JABBER_BUDDY_STATE_XA) || (jbr->state == JABBER_BUDDY_STATE_UNAVAILABLE) ||
+						if (((jbr->state == JABBER_BUDDY_STATE_XA) || (jbr->state == JABBER_BUDDY_STATE_UNAVAILABLE) ||
 							(jbr->state == JABBER_BUDDY_STATE_UNKNOWN) || (jbr->state == JABBER_BUDDY_STATE_ERROR))
-							jbr = l->data;
+							|| (jbr->idle && !tmp->idle)
+							|| (jbr->idle && tmp->idle && tmp->idle > jbr->idle))
+							jbr = tmp;
 						break;
 					case JABBER_BUDDY_STATE_XA:
 						/* This resource is extended away. That's better than unavailable or unknown. */
 						if ((jbr->state == JABBER_BUDDY_STATE_UNAVAILABLE) || (jbr->state == JABBER_BUDDY_STATE_UNKNOWN) || (jbr->state == JABBER_BUDDY_STATE_ERROR))
-							jbr = l->data;
+							jbr = tmp;
 						break;
 					case JABBER_BUDDY_STATE_UNAVAILABLE:
 						/* This resource is unavailable. That's better than unknown. */
 						if ((jbr->state == JABBER_BUDDY_STATE_UNKNOWN) || (jbr->state == JABBER_BUDDY_STATE_ERROR))
-							jbr = l->data;
+							jbr = tmp;
 						break;
 					case JABBER_BUDDY_STATE_UNKNOWN:
 					case JABBER_BUDDY_STATE_ERROR:
@@ -135,9 +140,9 @@
 						break;
 				}
 			}
-		} else if(((JabberBuddyResource *)l->data)->name) {
-			if(!strcmp(((JabberBuddyResource *)l->data)->name, resource)) {
-				jbr = l->data;
+		} else if(tmp->name) {
+			if(!strcmp(tmp->name, resource)) {
+				jbr = tmp;
 				break;
 			}
 		}
@@ -1638,12 +1643,49 @@
 				if(seconds) {
 					char *end = NULL;
 					long sec = strtol(seconds, &end, 10);
-					if(end != seconds) {
+                    JabberBuddy *jb = NULL;
+                    char *resource = NULL;
+                    char *buddy_name = NULL;
+					JabberBuddyResource *jbr = NULL;
+                    
+                    if(end != seconds) {
 						JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
 						if(jbir) {
 							jbir->idle_seconds = sec;
 						}
 					}
+                    /* Update the idle time of the buddy resource, if we got it. 
+                     This will correct the value when a server doesn't mark 
+                     delayed presence and we got the presence when signing on */
+                    jb = jabber_buddy_find(js, from, FALSE);
+                    if (jb) {
+                        resource = jabber_get_resource(from);
+                        buddy_name = jabber_get_bare_jid(from);
+                        /* if the resource already has an idle time set, we
+                         must have gotten it originally from a presence. In
+                         this case we update it. Otherwise don't update it, to
+                         avoid setting an idle and not getting informed about
+                         the resource getting unidle */
+                        if (resource && buddy_name) {
+                            jbr = jabber_buddy_find_resource(jb, resource);
+                            
+                            if (jbr->idle) {
+                                if (sec) {
+                                    jbr->idle = time(NULL) - sec;
+                                } else {
+                                    jbr->idle = 0;
+                                }
+                            
+                                if (jbr == 
+                                    jabber_buddy_find_resource(jb, NULL)) {
+                                    purple_prpl_got_user_idle(js->gc->account, 
+                                        buddy_name, jbr->idle, jbr->idle);
+                                }
+                            }
+                        }
+                        g_free(resource);
+                        g_free(buddy_name);
+                    }
 				}
 			}
 		}
--- a/libpurple/protocols/jabber/buddy.h	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Sun Apr 26 22:55:58 2009 +0000
@@ -69,6 +69,7 @@
 	int priority;
 	JabberBuddyState state;
 	char *status;
+	time_t idle;
 	JabberCapabilities capabilities;
 	char *thread_id;
 	enum {
--- a/libpurple/protocols/jabber/jabber.c	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Apr 26 22:55:58 2009 +0000
@@ -36,6 +36,7 @@
 #include "prpl.h"
 #include "request.h"
 #include "server.h"
+#include "status.h"
 #include "util.h"
 #include "version.h"
 #include "xmlnode.h"
@@ -707,6 +708,7 @@
 	const char *connect_server = purple_account_get_string(account,
 			"connect_server", "");
 	JabberStream *js;
+	PurplePresence *presence;
 	JabberBuddy *my_jb = NULL;
 
 	gc->flags |= PURPLE_CONNECTION_HTML |
@@ -733,6 +735,14 @@
 	js->stun_port = 0;
 	js->stun_query = NULL;
 
+
+	/* if we are idle, set idle-ness on the stream (this could happen if we get
+		disconnected and the reconnects while being idle. I don't think it makes
+		sense to do this when registering a new account... */
+	presence = purple_account_get_presence(account);
+	if (purple_presence_is_idle(presence))
+		js->idle = purple_presence_get_idle_time(presence);
+
 	if(!js->user) {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
@@ -1522,8 +1532,14 @@
 void jabber_idle_set(PurpleConnection *gc, int idle)
 {
 	JabberStream *js = gc->proto_data;
-
+	PurpleAccount *account = purple_connection_get_account(gc);
+	PurpleStatus *status = purple_account_get_active_status(account);
+	
 	js->idle = idle ? time(NULL) - idle : idle;
+	
+	/* send out an updated prescence */
+	purple_debug_info("jabber", "sending updated presence for idle\n");
+	jabber_presence_send(account, status);
 }
 
 static void jabber_blocklist_parse(JabberStream *js, const char *from,
@@ -1721,6 +1737,56 @@
 	return ret;
 }
 
+static void
+jabber_tooltip_add_resource_text(JabberBuddyResource *jbr, 
+	PurpleNotifyUserInfo *user_info, gboolean multiple_resources)
+{
+	char *text = NULL;
+	char *res = NULL;
+	char *label, *value;
+	const char *state;
+
+	if(jbr->status) {
+		char *tmp;
+		text = purple_strreplace(jbr->status, "\n", "<br />\n");
+		tmp = purple_markup_strip_html(text);
+		g_free(text);
+		text = g_markup_escape_text(tmp, -1);
+		g_free(tmp);
+	}
+
+	if(jbr->name)
+		res = g_strdup_printf(" (%s)", jbr->name);
+
+	state = jabber_buddy_state_get_name(jbr->state);
+	if (text != NULL && !purple_utf8_strcasecmp(state, text)) {
+		g_free(text);
+		text = NULL;
+	}
+
+	label = g_strdup_printf("%s%s", _("Status"), (res ? res : ""));
+	value = g_strdup_printf("%s%s%s", state, (text ? ": " : ""), (text ? text : ""));
+
+	purple_notify_user_info_add_pair(user_info, label, value);
+	g_free(label);
+	g_free(value);
+	g_free(text);
+	
+	/* if the resource is idle, show that */
+	/* only show it if there is more than one resource available for
+	the buddy, since the "general" idleness will be shown anyway,
+	this way we can see see the idleness of lower-priority resources */
+	if (jbr->idle && multiple_resources) {
+		gchar *idle_str = 
+			purple_str_seconds_to_string(time(NULL) - jbr->idle);
+		label = g_strdup_printf("%s%s", _("Idle"), (res ? res : ""));
+		purple_notify_user_info_add_pair(user_info, label, idle_str);
+		g_free(idle_str);
+		g_free(label);
+	}
+	g_free(res);
+}
+
 void jabber_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
 {
 	JabberBuddy *jb;
@@ -1744,28 +1810,28 @@
 		const char *sub;
 		GList *l;
 		const char *mood;
-
+		gboolean multiple_resources = 
+			jb->resources && g_list_next(jb->resources);
+		JabberBuddyResource *top_jbr = jabber_buddy_find_resource(jb, NULL);
+
+		/* resource-specific info for the top resource */
+		if (top_jbr) {
+			jabber_tooltip_add_resource_text(top_jbr, user_info, 
+				multiple_resources);
+		}
+
+		for(l=jb->resources; l; l = l->next) {
+			jbr = l->data;
+			/* the remaining resources */
+			if (jbr != top_jbr) {
+				jabber_tooltip_add_resource_text(jbr, user_info,
+					multiple_resources);
+			}
+		}
+		
 		if (full) {
 			PurpleStatus *status;
 
-			if(jb->subscription & JABBER_SUB_FROM) {
-				if(jb->subscription & JABBER_SUB_TO)
-					sub = _("Both");
-				else if(jb->subscription & JABBER_SUB_PENDING)
-					sub = _("From (To pending)");
-				else
-					sub = _("From");
-			} else {
-				if(jb->subscription & JABBER_SUB_TO)
-					sub = _("To");
-				else if(jb->subscription & JABBER_SUB_PENDING)
-					sub = _("None (To pending)");
-				else
-					sub = _("None");
-			}
-
-			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
-
 			status = purple_presence_get_active_status(presence);
 			mood = purple_status_get_attr_string(status, "mood");
 			if(mood != NULL) {
@@ -1790,49 +1856,27 @@
 					g_free(playing);
 				}
 			}
-		}
-
-		for(l=jb->resources; l; l = l->next) {
-			char *text = NULL;
-			char *res = NULL;
-			char *label, *value;
-			const char *state;
-
-			jbr = l->data;
-
-			if(jbr->status) {
-				char *tmp;
-				text = purple_strreplace(jbr->status, "\n", "<br />\n");
-				tmp = purple_markup_strip_html(text);
-				g_free(text);
-				text = g_markup_escape_text(tmp, -1);
-				g_free(tmp);
+
+			if(jb->subscription & JABBER_SUB_FROM) {
+				if(jb->subscription & JABBER_SUB_TO)
+					sub = _("Both");
+				else if(jb->subscription & JABBER_SUB_PENDING)
+					sub = _("From (To pending)");
+				else
+					sub = _("From");
+			} else {
+				if(jb->subscription & JABBER_SUB_TO)
+					sub = _("To");
+				else if(jb->subscription & JABBER_SUB_PENDING)
+					sub = _("None (To pending)");
+				else
+					sub = _("None");
 			}
 
-			if(jbr->name)
-				res = g_strdup_printf(" (%s)", jbr->name);
-
-			state = jabber_buddy_state_get_name(jbr->state);
-			if (text != NULL && !purple_utf8_strcasecmp(state, text)) {
-				g_free(text);
-				text = NULL;
-			}
-
-			label = g_strdup_printf("%s%s",
-							_("Status"), (res ? res : ""));
-			value = g_strdup_printf("%s%s%s",
-							state,
-							(text ? ": " : ""),
-							(text ? text : ""));
-
-			purple_notify_user_info_add_pair(user_info, label, value);
-
-			g_free(label);
-			g_free(value);
-			g_free(text);
-			g_free(res);
-		}
-
+			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
+
+		}	
+		
 		if(!PURPLE_BUDDY_IS_ONLINE(b) && jb->error_msg) {
 			purple_notify_user_info_add_pair(user_info, _("Error"), jb->error_msg);
 		}
--- a/libpurple/protocols/jabber/jabber.h	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Apr 26 22:55:58 2009 +0000
@@ -158,6 +158,7 @@
 	GList *file_transfers;
 
 	time_t idle;
+	time_t old_idle;
 
 	JabberID *user;
 	PurpleConnection *gc;
--- a/libpurple/protocols/jabber/presence.c	Sun Apr 26 22:54:17 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun Apr 26 22:55:58 2009 +0000
@@ -21,6 +21,7 @@
 #include "internal.h"
 
 #include "account.h"
+#include "cipher.h"
 #include "conversation.h"
 #include "debug.h"
 #include "notify.h"
@@ -150,7 +151,8 @@
 					  (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b)))
 	/* check if there are any differences to the <presence> and send them in that case */
 	if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) ||
-		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) {
+		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash) ||
+		js->old_idle != js->idle) {
 		js->allowBuzz = allowBuzz;
 
 		presence = jabber_presence_create_js(js, state, stripped, priority);
@@ -177,6 +179,7 @@
 		js->old_avatarhash = g_strdup(js->avatar_hash);
 		js->old_state = state;
 		js->old_priority = priority;
+		js->old_idle = js->idle;
 	}
 	g_free(stripped);
 
@@ -259,6 +262,16 @@
 		g_free(pstr);
 	}
 
+	/* if we are idle and not offline, include idle */
+	if (js->idle && state != JABBER_BUDDY_STATE_UNAVAILABLE) {
+		xmlnode *query = xmlnode_new_child(presence, "query");
+		gchar seconds[10];
+		g_snprintf(seconds, 10, "%d", (int) (time(NULL) - js->idle));
+		
+		xmlnode_set_namespace(query, "jabber:iq:last");
+		xmlnode_set_attrib(query, "seconds", seconds);
+	}
+	
 	/* JEP-0115 */
 	c = xmlnode_new_child(presence, "c");
 	xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
@@ -354,12 +367,19 @@
 				(( (binval = xmlnode_get_child(photo, "BINVAL")) &&
 				(text = xmlnode_get_data(binval))) ||
 				(text = xmlnode_get_data(photo)))) {
-			char *hash;
+			unsigned char hashval[20];
+			char hash[41], *p;
+			int i;
 
 			data = purple_base64_decode(text, &size);
-			hash = jabber_calculate_data_sha1sum(data, size);
+
+			purple_cipher_digest_region("sha1", data, size,
+					sizeof(hashval), hashval, NULL);
+			p = hash;
+			for(i=0; i<20; i++, p+=2)
+				snprintf(p, 3, "%02x", hashval[i]);
+
 			purple_buddy_icons_set_for_user(js->gc->account, from, data, size, hash);
-			g_free(hash);
 			g_free(text);
 		}
 	}
@@ -425,6 +445,7 @@
 	JabberBuddyResource *jbr = NULL, *found_jbr = NULL;
 	PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
 	gboolean delayed = FALSE;
+	const gchar *stamp = NULL; /* from <delayed/> element */
 	PurpleBuddy *b = NULL;
 	char *buddy_name;
 	JabberBuddyState state = JABBER_BUDDY_STATE_UNKNOWN;
@@ -432,7 +453,8 @@
 	gboolean muc = FALSE;
 	char *avatar_hash = NULL;
 	xmlnode *caps = NULL;
-
+	int idle = 0;
+	
 	if(!(jb = jabber_buddy_find(js, from, TRUE)))
 		return;
 
@@ -508,19 +530,19 @@
 				priority = atoi(p);
 				g_free(p);
 			}
-		} else if(xmlns == NULL) {
-			/* The rest of the cases used to check xmlns individually. */
-			continue;
 		} else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) {
 			/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
 			delayed = TRUE;
-		} else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) {
+			stamp = xmlnode_get_attrib(y, "stamp");
+		} else if(xmlns && !strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) {
 			caps = y; /* store for later, when creating buddy resource */
 		} else if(!strcmp(y->name, "x")) {
-			if(!strcmp(xmlns, "jabber:x:delay")) {
+			const char *xmlns = xmlnode_get_namespace(y);
+			if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
 				/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
 				delayed = TRUE;
-			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
+				stamp = xmlnode_get_attrib(y, "stamp");
+			} else if(xmlns && !strcmp(xmlns, "http://jabber.org/protocol/muc#user")) {
 				xmlnode *z;
 
 				muc = TRUE;
@@ -563,16 +585,33 @@
 							flags |= PURPLE_CBFLAGS_VOICE;
 					}
 				}
-			} else if(!strcmp(xmlns, "vcard-temp:x:update")) {
+			} else if(xmlns && !strcmp(xmlns, "vcard-temp:x:update")) {
 				xmlnode *photo = xmlnode_get_child(y, "photo");
 				if(photo) {
 					g_free(avatar_hash);
 					avatar_hash = xmlnode_get_data(photo);
 				}
 			}
+		} else if (!strcmp(y->name, "query") && 
+			!strcmp(xmlnode_get_namespace(y), "jabber:iq:last")) {
+			/* resource has specified idle */
+			const gchar *seconds = xmlnode_get_attrib(y, "seconds");
+			if (seconds) {
+				/* we may need to take "delayed" into account here */
+				idle = atoi(seconds);
+			}
 		}
 	}
 
+	if (idle && delayed && stamp) {
+		/* if we have a delayed presence, we need to add the delay to the idle
+		 value */
+		time_t offset = time(NULL) - purple_str_to_time(stamp, TRUE, NULL, NULL,
+			NULL);
+		purple_debug_info("jabber", "got delay %s yielding %ld s offset\n",
+			stamp, offset);
+		idle += offset; 
+	}
 
 	if(jid->node && (chat = jabber_chat_find(js, jid->node, jid->domain))) {
 		static int i = 1;
@@ -747,6 +786,12 @@
 		} else {
 			jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
 					state, status);
+			if (idle) {
+				jbr->idle = time(NULL) - idle;
+			} else {
+				jbr->idle = 0;
+			}
+			
 			if(caps) {
 				const char *node = xmlnode_get_attrib(caps,"node");
 				const char *ver = xmlnode_get_attrib(caps,"ver");
@@ -765,6 +810,7 @@
 		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
 			jabber_google_presence_incoming(js, buddy_name, found_jbr);
 			purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(found_jbr->state), "priority", found_jbr->priority, "message", found_jbr->status, NULL);
+			purple_prpl_got_user_idle(js->gc->account, buddy_name, found_jbr->idle, found_jbr->idle);
 		} else {
 			purple_prpl_got_user_status(js->gc->account, buddy_name, "offline", status ? "message" : NULL, status, NULL);
 		}
--- a/pidgin/gtkdialogs.c	Sun Apr 26 22:54:17 2009 +0000
+++ b/pidgin/gtkdialogs.c	Sun Apr 26 22:55:58 2009 +0000
@@ -91,6 +91,7 @@
 	{"Bartosz Oler",		NULL, NULL},
 	{"Etan 'deryni' Reisner",       NULL, NULL},
 	{"Tim 'marv' Ringenbach",		NULL, NULL},
+	{"Michael 'Maiku' Ruprecht",	N_("voice and video"), NULL},
 	{"Elliott 'QuLogic' Sales de Andrade",	NULL,	NULL},
 	{"Luke 'LSchiere' Schierer",	N_("support"), "lschiere@users.sf.net"},
 	{"Evan Schoenberg",		NULL, NULL},