org.guifications.plugins.autoprofile_merge: 173815f47a6cde55853103f2189cdd0044457d43

rekkanoryo at guifications.org rekkanoryo at guifications.org
Sun Mar 23 12:55:09 CDT 2008


-----------------------------------------------------------------
Revision: 173815f47a6cde55853103f2189cdd0044457d43
Ancestor: cb62d34f3f8964526dad2f7efef20475f51a45b8
Author: rekkanoryo at guifications.org
Date: 2008-03-23T03:10:31
Branch: org.guifications.plugins.autoprofile_merge

Added files:
        autoprofile/Makefile.am autoprofile/autoaway.c
        autoprofile/autoprofile.c autoprofile/autoprofile.h
        autoprofile/autoreply.c autoprofile/comp_countdownup.c
        autoprofile/comp_executable.c autoprofile/comp_http.c
        autoprofile/comp_logstats.c autoprofile/comp_logstats.h
        autoprofile/comp_logstats_gtk.c autoprofile/comp_quotation.c
        autoprofile/comp_rss.c autoprofile/comp_rss.h
        autoprofile/comp_rss_parser.c autoprofile/comp_rss_xanga.c
        autoprofile/comp_textfile.c autoprofile/comp_timestamp.c
        autoprofile/comp_uptime.c autoprofile/component.c
        autoprofile/component.h autoprofile/gtk_actions.c
        autoprofile/gtk_away_msgs.c autoprofile/gtk_widget.c
        autoprofile/preferences.c autoprofile/sizes.h
        autoprofile/utility.c autoprofile/utility.h
        autoprofile/utility_rfc822.c autoprofile/widget.c
        autoprofile/widget.h
Added directories:
        autoprofile
Modified files:
        configure.ac

ChangeLog: 

Adding Autoprofile to the plugin pack.  This currently does not build.

-----------------------------------------------------------------
This revision's diffstat output:
 autoprofile/Makefile.am         |   60 ++
 autoprofile/autoaway.c          |  145 +++++
 autoprofile/autoprofile.c       |  861 +++++++++++++++++++++++++++++++++
 autoprofile/autoprofile.h       |  112 ++++
 autoprofile/autoreply.c         |  324 ++++++++++++
 autoprofile/comp_countdownup.c  |  434 ++++++++++++++++
 autoprofile/comp_executable.c   |  165 ++++++
 autoprofile/comp_http.c         |  202 +++++++
 autoprofile/comp_logstats.c     | 1039 ++++++++++++++++++++++++++++++++++++++++
 autoprofile/comp_logstats.h     |   28 +
 autoprofile/comp_logstats_gtk.c |  355 +++++++++++++
 autoprofile/comp_quotation.c    |  602 +++++++++++++++++++++++
 autoprofile/comp_rss.c          |  475 ++++++++++++++++++
 autoprofile/comp_rss.h          |   52 ++
 autoprofile/comp_rss_parser.c   |  348 +++++++++++++
 autoprofile/comp_rss_xanga.c    |  117 ++++
 autoprofile/comp_textfile.c     |  264 ++++++++++
 autoprofile/comp_timestamp.c    |  140 +++++
 autoprofile/comp_uptime.c       |   96 +++
 autoprofile/component.c         |   85 +++
 autoprofile/component.h         |   74 ++
 autoprofile/gtk_actions.c       |  341 +++++++++++++
 autoprofile/gtk_away_msgs.c     |  486 ++++++++++++++++++
 autoprofile/gtk_widget.c        |  778 +++++++++++++++++++++++++++++
 autoprofile/preferences.c       |  750 ++++++++++++++++++++++++++++
 autoprofile/sizes.h             |   49 +
 autoprofile/utility.c           |  221 ++++++++
 autoprofile/utility.h           |   41 +
 autoprofile/utility_rfc822.c    |  187 +++++++
 autoprofile/widget.c            |  602 +++++++++++++++++++++++
 autoprofile/widget.h            |   96 +++
 configure.ac                    |    1 
 32 files changed, 9530 insertions(+)
-------------- next part --------------
============================================================
--- autoprofile/Makefile.am	01355c1045346405e31ef5920ac7e49bf0c36511
+++ autoprofile/Makefile.am	01355c1045346405e31ef5920ac7e49bf0c36511
@@ -0,0 +1,60 @@
+EXTRA_DIST = \
+	.build \
+	.pidgin-plugin
+
+autoprofiledir = $(PURPLE_LIBDIR)
+
+autoprofile_la_LDFLAGS = -module -avoid-version
+
+if HAVE_PIDGIN
+
+autoprofile_LTLIBRARIES = autoprofile.la
+
+autoprofile_la_SOURCES = \
+	autoaway.c \
+	autoprofile.c \
+	autoprofile.h \
+	autoreply.c \
+	comp_countdownup.c \
+	comp_executable.c \
+	comp_http.c \
+	comp_logstats.c \
+	comp_logstats_gtk.c \
+	comp_logstats.h \
+	component.c \
+	component.h \
+	comp_quotation.c \
+	comp_rss.c \
+	comp_rss.h \
+	comp_rss_parser.c \
+	comp_rss_xanga.c \
+	comp_textfile.c \
+	comp_timestamp.c \
+	comp_uptime.c \
+	gtk_actions.c \
+	gtk_away_msgs.c \
+	gtk_widget.c \
+	Makefile.am \
+	preferences.c \
+	sizes.h \
+	utility.c \
+	utility.h \
+	utility_rfc822.c \
+	widget.c \
+	widget.h
+
+listhandler_la_LIBADD = \
+	$(PIDGIN_LIBS) \
+	$(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
+	-DDATADIR=\"$(PIDGIN_DATADIR)\" \
+	-DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
+	$(DEBUG_CFLAGS) \
+	$(PIDGIN_CFLAGS) \
+	$(GTK_CFLAGS) \
+	$(GLIB_CFLAGS)
+
============================================================
--- autoprofile/autoaway.c	8d7270cbd63aca748e86b4ef6fb9fa9a9d16ce0f
+++ autoprofile/autoaway.c	8d7270cbd63aca748e86b4ef6fb9fa9a9d16ce0f
@@ -0,0 +1,145 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "autoprofile.h"
+#include "idle.h"
+#include "conversation.h"
+
+#define AP_IDLE_CHECK_INTERVAL 5
+
+static guint check_timeout = 0;
+static guint pref_cb = 0;
+static time_t last_active_time = 0;
+
+static gboolean is_idle ()
+{
+  PurpleIdleUiOps *ui_ops;
+  time_t time_idle;
+  const gchar *idle_reporting;
+
+  ui_ops = purple_idle_get_ui_ops ();
+
+  idle_reporting = purple_prefs_get_string ("/core/away/idle_reporting");
+  if (!strcmp (idle_reporting, "system") &&
+      (ui_ops != NULL) && (ui_ops->get_time_idle != NULL)) {
+    time_idle = time (NULL) - last_active_time;
+  } else if (!strcmp (idle_reporting, "gaim")) {
+    time_idle = time (NULL) - last_active_time;
+  } else {
+    time_idle = 0;
+  }
+
+  return (time_idle > 
+           (60 * purple_prefs_get_int("/core/away/mins_before_away")));
+}
+
+static gboolean ap_check_idleness (gpointer data) 
+{
+  gboolean auto_away;
+
+  // ap auto idle
+  // 0    0    0  don't do anything
+  // 0    0    1  ap_use_idleaway ()
+  // 1    0    x  don't do anything, we're already away
+  // 1    1    0  ap_dont_use_idleaway ()
+  // 1    1    1  don't do anything
+
+  if (ap_is_currently_away () && !ap_autoaway_in_use ()) return TRUE;
+  auto_away = purple_prefs_get_bool (
+    "/plugins/gtk/autoprofile/away_when_idle");
+
+  if (is_idle ()) {
+    if (auto_away && !ap_is_currently_away () && !ap_autoaway_in_use ()) {
+      ap_autoaway_enable ();
+    }
+  } else {
+    if (ap_is_currently_away () && ap_autoaway_in_use ()) {
+      ap_autoaway_disable ();
+    }
+  } 
+ 
+  return TRUE;
+}
+
+void ap_autoaway_touch ()
+{
+  time (&last_active_time);
+}
+
+
+static gboolean writing_im_msg_cb (PurpleAccount *account, const char *who,
+  char **message, PurpleConversation *conv, PurpleMessageFlags flags) 
+{
+  ap_autoaway_touch ();
+  ap_check_idleness (NULL);
+  return FALSE;
+}
+
+static void auto_pref_cb (
+  const char *name, PurplePrefType type, gconstpointer val, gpointer data) 
+{
+  if (!purple_prefs_get_bool ("/core/away/away_when_idle")) return;
+
+  purple_notify_error (NULL, NULL,
+    N_("This preference is disabled"), 
+    N_("This preference currently has no effect because AutoProfile is in "
+       "use.  To modify this behavior, use the AutoProfile configuration "
+       "menu."));
+
+  purple_prefs_set_bool ("/core/away/away_when_idle", FALSE);
+}
+
+/*--------------------------------------------------------------------------*
+ * Global functions to start it all                                         *
+ *--------------------------------------------------------------------------*/
+void ap_autoaway_start ()
+{
+  purple_prefs_set_bool ("/core/away/away_when_idle", FALSE);
+
+  check_timeout = purple_timeout_add (AP_IDLE_CHECK_INTERVAL * 1000,
+    ap_check_idleness, NULL);
+  
+  purple_signal_connect (purple_conversations_get_handle (), "writing-im-msg",
+    ap_get_plugin_handle (), PURPLE_CALLBACK(writing_im_msg_cb), NULL);
+
+  pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
+    "/core/away/away_when_idle", auto_pref_cb, NULL);
+
+  ap_autoaway_touch ();
+}
+
+void ap_autoaway_finish ()
+{
+  // Assumes signals are disconnected globally
+        
+  purple_prefs_disconnect_callback (pref_cb);
+  pref_cb = 0;
+
+  if (check_timeout > 0) purple_timeout_remove (check_timeout);
+  check_timeout = 0;
+
+  purple_prefs_set_bool ("/core/away/away_when_idle",
+          purple_prefs_get_bool ("/plugins/gtk/autoprofile/away_when_idle"));
+}
+
+
============================================================
--- autoprofile/autoprofile.c	638632fe58fc8095d4b45970eb6ca1d96d527ea5
+++ autoprofile/autoprofile.c	638632fe58fc8095d4b45970eb6ca1d96d527ea5
@@ -0,0 +1,861 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "autoprofile.h"
+
+#include "version.h"
+#include "savedstatuses.h"
+
+/* General functions */
+static void ap_status_changed (
+  const char *, PurplePrefType, gconstpointer, gpointer);
+static void ap_account_connected (PurpleConnection *);
+
+static void ap_delete_legacy_prefs ();
+
+static void ap_update_queue_start ();
+static void ap_update_queue_finish ();
+
+/*--------------------------------------------------------------------------
+ * GENERAL VARIABLES
+ *------------------------------------------------------------------------*/
+
+static PurplePlugin *plugin_handle = NULL;
+static PurpleSavedStatus *current_ap_status = NULL;
+
+static GStaticMutex update_timeout_mutex = G_STATIC_MUTEX_INIT;
+static GHashTable *update_timeouts = NULL;
+
+static gboolean using_idleaway = FALSE;
+
+static GStaticMutex update_queue_mutex = G_STATIC_MUTEX_INIT;
+static GList *queued_profiles = NULL;
+static guint update_queue_timeout = 0;
+
+/* Functions related to general variables */
+PurplePlugin *ap_get_plugin_handle () { return plugin_handle; }
+
+gboolean ap_is_currently_away () { 
+  return current_ap_status != NULL && 
+         purple_savedstatus_get_type (current_ap_status) == PURPLE_STATUS_AWAY;
+}
+
+/*--------------------------------------------------------------------------
+ * REQUIRED GAIM FUNCTIONS- INFO, INITIALIZATION, UNLOADING
+ *------------------------------------------------------------------------*/
+/* What to do when plugin is loaded */
+static gboolean plugin_load (PurplePlugin *plugin)
+{
+  GList *accounts_pref;
+        
+  ap_debug ("general", "AutoProfile is being loaded");
+
+  plugin_handle = plugin;
+  current_ap_status = purple_savedstatus_new (NULL, PURPLE_STATUS_UNSET);
+  update_timeouts = g_hash_table_new (NULL, NULL);
+
+  ap_delete_legacy_prefs ();
+
+  /* The core autoprofile tracking system */
+  purple_prefs_connect_callback (plugin_handle, "/core/savedstatus/current",
+    ap_status_changed, NULL);
+  purple_signal_connect (purple_connections_get_handle (),
+                       "signed-on", plugin_handle,
+                       PURPLE_CALLBACK (ap_account_connected), NULL);
+
+  ap_component_start (); 
+  ap_gtk_start ();
+
+  accounts_pref = purple_prefs_get_string_list (
+    "/plugins/gtk/autoprofile/profile_accounts");
+  ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (accounts_pref != NULL));
+  free_string_list (accounts_pref);
+  
+  ap_update_after_delay (AP_UPDATE_STATUS);
+  ap_update_after_delay (AP_UPDATE_PROFILE);
+
+  ap_autoaway_start ();
+  ap_autoreply_start ();
+
+  ap_update_queue_start ();
+
+  return TRUE;
+}
+
+/* What to do when plugin is unloaded */
+static gboolean plugin_unload (PurplePlugin *plugin)
+{
+  ap_update_queue_finish ();
+  
+  ap_autoreply_finish ();
+  ap_autoaway_finish (); 
+
+  /* General vars */
+  using_idleaway = FALSE;
+
+  ap_update_stop (AP_UPDATE_STATUS);
+  ap_update_stop (AP_UPDATE_PROFILE);
+
+  /* Disconnect tracking system */
+  purple_signals_disconnect_by_handle (plugin);
+
+  ap_actions_finish ();
+  ap_gtk_finish ();
+  ap_component_finish ();
+
+  g_hash_table_destroy (update_timeouts);
+  return TRUE;
+}
+
+/* General information */
+static PurplePluginInfo info =
+{
+  PURPLE_PLUGIN_MAGIC,
+  PURPLE_MAJOR_VERSION,
+  PURPLE_MINOR_VERSION,
+  PURPLE_PLUGIN_STANDARD,                                   /* type           */
+  PIDGIN_PLUGIN_TYPE,                                   /* ui_requirement */
+  0,                                                      /* flags          */
+  NULL,                                                   /* dependencies   */
+  PURPLE_PRIORITY_DEFAULT,                                  /* priority       */
+
+  N_("gtk-kluge-autoprofile"),                            /* id             */
+  N_("AutoProfile"),                                      /* name           */
+  AUTOPROFILE_VERSION,                                    /* version        */
+  N_("User profile and status message content generator"),/* summary        */
+                                                          /* description    */
+  N_("Allows user to place dynamic text into profiles\n"
+     "and status messages, with the text automatically\n"
+     "updated whenever content changes"),
+                                                          /* author         */
+  N_("Casey Ho <casey at hkn-berkeley-edu>"
+     "\n\t\t\taim:caseyho"),
+  N_("http://autoprofile.sourceforge.net/"),              /* homepage       */
+  plugin_load,                                            /* load           */
+  plugin_unload,                                          /* unload         */
+  NULL,                                                   /* destroy        */
+
+  &ui_info,                                               /* ui_info        */
+  NULL,                                                   /* extra_info     */
+  NULL,                                                   /* prefs_info     */
+  actions                                                 /* actions        */
+};
+
+/*--------------------------------------------------------------------------
+ * CORE FUNCTIONS                                          
+ *------------------------------------------------------------------------*/
+
+static gint get_max_size_status (
+  const PurpleAccount *account, const PurpleStatusPrimitive type) {
+  const char *id;
+
+  if (account == NULL) {
+    switch (type) {
+      case PURPLE_STATUS_AVAILABLE:             return AP_SIZE_AVAILABLE_MAX;
+      case PURPLE_STATUS_AWAY:                  return AP_SIZE_AWAY_MAX;
+      default:                                return AP_SIZE_MAXIMUM;
+    }
+  } else {
+    id = purple_account_get_protocol_id (account);
+
+    switch (type) { 
+      case PURPLE_STATUS_AVAILABLE:
+        if (!strcmp (id, "prpl-oscar"))       return AP_SIZE_AVAILABLE_AIM;
+        else if (!strcmp (id, "prpl-msn"))    return AP_SIZE_AVAILABLE_MSN;
+        else if (!strcmp (id, "prpl-yahoo"))  return AP_SIZE_AVAILABLE_YAHOO;
+        else                                  return AP_SIZE_AVAILABLE_MAX; 
+      case PURPLE_STATUS_AWAY:
+        if (!strcmp (id, "prpl-oscar"))       return AP_SIZE_AWAY_AIM;
+        else                                  return AP_SIZE_AWAY_MAX; 
+      default:
+                                              return AP_SIZE_MAXIMUM;
+    }
+  }
+}
+
+static const char *ap_savedstatus_get_message (
+                const PurpleSavedStatus *status, const PurpleAccount *account)
+{
+  const PurpleSavedStatusSub *substatus;
+        
+  substatus = purple_savedstatus_get_substatus(status, account);
+  if (substatus != NULL) {
+    return purple_savedstatus_substatus_get_message (substatus);
+  } 
+  return purple_savedstatus_get_message (status);
+}
+
+static PurpleStatusPrimitive ap_savedstatus_get_type (
+                const PurpleSavedStatus *status, const PurpleAccount *account)
+{
+  const PurpleSavedStatusSub *substatus;
+
+  substatus = purple_savedstatus_get_substatus(status, account);
+  if (substatus != NULL) {
+    return purple_status_type_get_primitive (
+                      purple_savedstatus_substatus_get_type (substatus));
+  } 
+  return purple_savedstatus_get_type (status);
+}
+
+gchar *ap_get_sample_status_message (PurpleAccount *account) 
+{
+  const PurpleSavedStatus *s;
+  const gchar *message;
+  PurpleStatusPrimitive type;
+
+  s = (using_idleaway? purple_savedstatus_get_idleaway () :
+                       purple_savedstatus_get_current ()); 
+  message = ap_savedstatus_get_message (s, account);
+  type = ap_savedstatus_get_type (s, account);
+  
+  if (!message) return NULL;
+  return ap_generate (message, get_max_size_status (account, type));
+}
+
+/* Generator helper */
+static gchar *ap_process_replacement (const gchar *f) {
+  GString *s;
+  struct widget *w;
+  gchar *result;
+
+  w = ap_widget_find (f);
+  if (w) {
+    result = w->component->generate (w);
+    return result;
+  } else {
+    s = g_string_new ("");
+    g_string_printf (s, "[%s]", f);
+    result = s->str;
+    g_string_free (s, FALSE);
+    return result;
+  }
+}
+
+/* The workhorse generation function! */
+gchar *ap_generate (const gchar *f, gint max_length) {
+  GString *output;
+  gchar *result;
+  gchar *format, *format_start, *percent_start;
+  gchar *replacement;
+  int state;
+
+  output = g_string_new ("");
+  format_start = format = purple_utf8_salvage (f);
+
+  /* When a % has been read (and searching for next %), state is 1
+   * otherwise it is 0
+   */
+  state = 0;
+  percent_start = NULL;
+
+  while (*format) {
+    if (state == 1) {
+      if (*format == '[') {
+        g_string_append_unichar (output, g_utf8_get_char ("["));
+        *format = '\0';
+        g_string_append (output, percent_start);
+        percent_start = format = format+1;        
+      } else if (*format == ']') {
+        *format = '\0';
+        format++;
+        state = 0;
+        replacement = ap_process_replacement (percent_start);
+        percent_start = NULL;
+        g_string_append (output, replacement);
+        free (replacement);
+      } else {
+        format = g_utf8_next_char (format);
+      }
+    } else {
+      if (*format == '\n') {
+        g_string_append (output, "<br>");
+      } else if (*format == '[') {
+        state = 1;
+        percent_start = format+1;
+      } else {
+        g_string_append_unichar (output, g_utf8_get_char (format));
+      }
+      format = g_utf8_next_char (format);
+    }
+  }
+
+  /* Deal with case where final ] not found */
+  if (state == 1) {
+    g_string_append_unichar (output, g_utf8_get_char ("["));
+    g_string_append (output, percent_start);
+  }
+
+  /* Set size limit */
+  g_string_truncate (output, max_length);
+
+  /* Finish up */
+  free (format_start);
+  result = purple_utf8_salvage(output->str);
+  g_string_free (output, TRUE);
+
+  return result;
+}
+
+void ap_account_enable_profile (const PurpleAccount *account, gboolean enable) {
+  GList *original, *new;
+  gboolean original_status;
+
+  gchar *username, *protocol_id;
+
+  GList *node, *tmp;
+  GList *ret = NULL;
+
+  original_status = ap_account_has_profile_enabled (account);
+  if (original_status == enable) {
+    ap_debug_warn ("profile", "New status identical to original, skipping");
+    return;
+  }
+
+  original = purple_prefs_get_string_list (
+    "/plugins/gtk/autoprofile/profile_accounts");
+  username = strdup (purple_account_get_username (account));
+  protocol_id = strdup (purple_account_get_protocol_id (account));
+
+  if (!enable) {
+    /* Remove from the list */
+    ap_debug ("profile", "Disabling profile updates for account");
+
+    while (original) {
+      if (!strcmp (original->data, username) &&
+          !strcmp (original->next->data, protocol_id)) {
+        node = original;
+        tmp = node->next;
+        original = original->next->next;
+        free (node->data);
+        free (tmp->data);
+        g_list_free_1 (node);
+        g_list_free_1 (tmp);
+        free (username);
+        free (protocol_id);
+      } else {
+        node = original;
+        original = original->next->next;
+        node->next->next = ret;
+        ret = node;
+      }
+    }
+
+    new = ret;
+  } else {
+    /* Otherwise add on */
+    GList *ret_start, *ret_end;
+   
+    ap_debug ("profile", "enabling profile updates for account"); 
+  
+    ret_start = (GList *) malloc (sizeof (GList));
+    ret_end = (GList *) malloc (sizeof (GList));
+    ret_start->data = username;
+    ret_start->next = ret_end;
+    ret_end->data = protocol_id;
+    ret_end->next = original;
+
+    new = ret_start;
+  }
+
+  purple_prefs_set_string_list (
+    "/plugins/gtk/autoprofile/profile_accounts", new);
+  ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (new != NULL));
+  
+  free_string_list (new);
+}
+
+gboolean ap_account_has_profile_enabled (const PurpleAccount *account) {
+  GList *accounts_list, *start_list;
+
+  accounts_list = purple_prefs_get_string_list (
+      "/plugins/gtk/autoprofile/profile_accounts");
+
+  start_list = accounts_list;
+
+  /* Search through list of values */
+  while (accounts_list) {
+    // Make sure these things come in pairs
+    if (accounts_list->next == NULL) {
+      ap_debug_error ("is_account_profile_enabled", "invalid account string");
+      free_string_list (start_list);
+      return FALSE;
+    }
+
+    // Check usernames
+    if (!strcmp ((char *) accounts_list->data, account->username)) {
+      // Check protocol
+      if (!strcmp ((char *) accounts_list->next->data, account->protocol_id))
+      {
+        free_string_list (start_list);
+        return TRUE;
+      }
+    } 
+
+    accounts_list = accounts_list->next->next;
+  }
+
+  /* Not found, hence it wasn't enabled */
+  free_string_list (start_list);
+  return FALSE;
+}
+
+/* Profiles: Update every so often */
+static gboolean ap_update_profile () {
+  PurpleAccount *account;
+  const GList *purple_accounts;
+  gboolean account_updated;
+  
+  const char *format; 
+  const char *old_info;
+  char *generated_profile;
+
+  /* Generate the profile text */
+  format = purple_prefs_get_string ("/plugins/gtk/autoprofile/profile");
+
+  if (format == NULL) {
+    ap_debug_error ("general", "profile is null");
+    return FALSE;
+  }
+
+  generated_profile = ap_generate (format, AP_SIZE_PROFILE_MAX);
+
+  // If string is blank, nothing would happen
+  if (*generated_profile == '\0') {
+    free (generated_profile);
+    ap_debug_misc ("general", "empty profile set");
+    generated_profile = strdup (" ");
+  }
+
+  /* Get all accounts and search through each */
+  account_updated = FALSE;
+  for (purple_accounts = purple_accounts_get_all ();
+       purple_accounts != NULL;
+       purple_accounts = purple_accounts->next) {
+    account = (PurpleAccount *)purple_accounts->data;
+    old_info = purple_account_get_user_info (account);
+    
+    /* Check to see if update option set on account */
+    if (ap_account_has_profile_enabled (account) &&
+        (old_info == NULL || strcmp (old_info, generated_profile))) {
+      purple_account_set_user_info (account, generated_profile);
+      account_updated = TRUE;
+      
+      if (purple_account_is_connected (account)) {
+        g_static_mutex_lock (&update_queue_mutex);
+        if (g_list_find (queued_profiles, account) == NULL) {
+          queued_profiles = g_list_append (queued_profiles, account);
+        }
+        g_static_mutex_unlock (&update_queue_mutex);
+      } else {
+        ap_debug_misc ("general", "account not online, not setting profile");
+      }
+    } 
+  }
+
+  if (account_updated) {
+    ap_gtk_add_message (AP_UPDATE_PROFILE, AP_MESSAGE_TYPE_PROFILE, 
+                        generated_profile);
+  }
+  
+  free (generated_profile);
+  return account_updated;
+}
+
+static gboolean ap_update_status () 
+{
+  const PurpleSavedStatus *template_status;  
+  GHashTable *substatus_messages;
+  gchar *new_message, *new_substatus_message;
+  const gchar *sample_message, *old_message;
+  const GList *accounts;
+  gboolean updated;
+  PurpleStatusPrimitive old_type, new_type;
+  const PurpleStatusType *substatus_type;
+  PurpleAccount *account;
+  PurpleSavedStatusSub *substatus;
+
+  template_status = (using_idleaway? purple_savedstatus_get_idleaway () :
+                                     purple_savedstatus_get_current ());
+  updated = FALSE;
+
+  /* If there are substatuses */
+  if (purple_savedstatus_has_substatuses (template_status)) {
+    substatus_messages = g_hash_table_new (NULL, NULL);
+    for (accounts = purple_accounts_get_all (); 
+         accounts != NULL;
+         accounts = accounts->next) 
+    {
+      account = (PurpleAccount *) accounts->data;
+
+      substatus = purple_savedstatus_get_substatus (template_status, account);
+      if (substatus) {
+        new_type = purple_status_type_get_primitive (
+          purple_savedstatus_substatus_get_type (substatus));
+        sample_message = 
+          purple_savedstatus_substatus_get_message (substatus);
+
+        if (sample_message) {
+          new_substatus_message = ap_generate (sample_message, 
+            get_max_size_status (account, new_type));
+        } else {
+          new_substatus_message = NULL;
+        }
+
+        g_hash_table_insert (substatus_messages, account, 
+          new_substatus_message);
+
+        if (!updated) {
+          old_type = ap_savedstatus_get_type (current_ap_status, account);
+          old_message = 
+            ap_savedstatus_get_message (current_ap_status, account);
+
+          if ((old_type != new_type) || 
+              ((old_message == NULL || new_substatus_message == NULL) &&
+               (old_message != new_substatus_message)) ||
+              (old_message != NULL && new_substatus_message != NULL &&
+               strcmp (old_message, new_substatus_message))) 
+          {
+            updated = TRUE;
+          } 
+        }
+      }
+    }
+  } else {
+    substatus_messages = NULL;
+  }
+
+  /* And then the generic main message */
+  sample_message = purple_savedstatus_get_message (template_status);
+  if (sample_message) {
+    new_message = ap_generate (sample_message, get_max_size_status (NULL, 
+                                purple_savedstatus_get_type (template_status)));
+  } else {
+    new_message = NULL;
+  }
+  
+  new_type = purple_savedstatus_get_type (template_status);
+
+  if (!updated) {
+    old_type = purple_savedstatus_get_type (current_ap_status);
+    old_message = purple_savedstatus_get_message (current_ap_status);
+
+    if ((old_type != new_type) || 
+        ((old_message == NULL || new_message == NULL) &&
+         (old_message != new_message)) ||
+        (old_message != NULL && new_message != NULL &&
+         strcmp (old_message, new_message))) 
+    {
+      updated = TRUE;
+    } 
+  }
+
+  if (updated) {
+    APMessageType type;
+    PurpleSavedStatus *new_status;
+
+    new_status = purple_savedstatus_new (NULL, new_type);
+
+    if (new_message) {
+      purple_savedstatus_set_message (new_status, new_message);
+    }
+
+    for (accounts = purple_accounts_get_all ();
+         accounts != NULL;
+         accounts = accounts->next) {
+      account = (PurpleAccount *) accounts->data;
+      substatus = purple_savedstatus_get_substatus (template_status, account);
+
+      if (substatus != NULL) {
+        substatus_type = purple_savedstatus_substatus_get_type (substatus);
+        new_substatus_message = (gchar *) 
+          g_hash_table_lookup (substatus_messages, account);
+        purple_savedstatus_set_substatus (
+          new_status, account, substatus_type, new_substatus_message);
+        free (new_substatus_message);
+      }        
+       
+      purple_savedstatus_activate_for_account (new_status, account); 
+    }
+
+    current_ap_status = new_status;
+
+    if (new_type == PURPLE_STATUS_AVAILABLE) type = AP_MESSAGE_TYPE_AVAILABLE;
+    else if (new_type == PURPLE_STATUS_AWAY) type = AP_MESSAGE_TYPE_AWAY;
+    else                                   type = AP_MESSAGE_TYPE_STATUS;
+
+    ap_gtk_add_message (AP_UPDATE_STATUS, type, new_message);
+  }
+
+  if (new_message) free (new_message);
+  if (substatus_messages) {
+    g_hash_table_destroy (substatus_messages); 
+  }
+
+  ap_update_queueing ();
+
+  return updated;
+}
+
+static gboolean ap_update_cb (gpointer data) {
+  gboolean result;
+  guint timeout;
+  guint delay;
+ 
+  g_static_mutex_lock (&update_timeout_mutex);
+
+  /* Start by removing timeout to self no matter what */
+  timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, data));
+  if (timeout) purple_timeout_remove (timeout);
+
+  /* In future, check here if widget content has changed? */
+
+  switch (GPOINTER_TO_INT (data)) {
+    case AP_UPDATE_STATUS:
+      result = ap_update_status ();
+      break;
+    case AP_UPDATE_PROFILE:
+      result = ap_update_profile ();
+      break;
+    default:
+      result = TRUE;
+  }
+
+  if (!result) {
+    ap_debug ("general", "Content hasn't changed, updating later");
+    delay = AP_SCHEDULE_UPDATE_DELAY;
+  } else {
+    ap_debug ("general", "Content updated");
+    delay = 
+      purple_prefs_get_int ("/plugins/gtk/autoprofile/delay_update") * 1000;
+  }
+  timeout = purple_timeout_add (delay, ap_update_cb, data);
+  g_hash_table_insert (update_timeouts, data, GINT_TO_POINTER (timeout));
+
+  g_static_mutex_unlock (&update_timeout_mutex);
+
+  return FALSE;
+}
+
+void ap_update (APUpdateType type) 
+{
+  ap_update_cb (GINT_TO_POINTER (type));
+}
+
+void ap_update_after_delay (APUpdateType type)
+{
+  guint timeout;
+
+  g_static_mutex_lock (&update_timeout_mutex);
+
+  timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, 
+    GINT_TO_POINTER (type)));
+  if (timeout) purple_timeout_remove (timeout);
+
+  timeout = purple_timeout_add (AP_SCHEDULE_UPDATE_DELAY, ap_update_cb, 
+    GINT_TO_POINTER (type));
+  g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type), 
+    GINT_TO_POINTER (timeout));
+
+  g_static_mutex_unlock (&update_timeout_mutex);
+}
+
+void ap_update_stop (APUpdateType type)
+{
+  guint timeout;
+
+  g_static_mutex_lock (&update_timeout_mutex);
+
+  timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, 
+    GINT_TO_POINTER (type)));
+  if (timeout) purple_timeout_remove (timeout);
+
+  g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type), 0);
+
+  g_static_mutex_unlock (&update_timeout_mutex);
+}
+
+static void ap_account_connected (PurpleConnection *gc) {
+  ap_debug ("general", "Account connection detected");
+  ap_update_after_delay (AP_UPDATE_PROFILE);
+  ap_update_after_delay (AP_UPDATE_STATUS);
+}
+
+void ap_update_queueing () {
+  if (ap_is_currently_away ()) {
+    if (purple_prefs_get_bool(
+      "/plugins/gtk/autoprofile/queue_messages_when_away")) {
+      purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "away");
+    } else {
+      purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never");
+    }
+  }
+}
+
+/* Called whenever current status is changed by Purple's status menu
+   (in buddy list) */
+static void ap_status_changed (
+  const char *name, PurplePrefType type, gconstpointer val, gpointer data) {
+  ap_debug ("general", "Status change detected");
+
+  using_idleaway = FALSE;
+  ap_autoaway_touch ();
+  ap_update (AP_UPDATE_STATUS);
+}
+
+void ap_autoaway_enable () {
+  ap_debug ("idle", "Using idleaway");
+
+  using_idleaway = TRUE;
+  ap_update (AP_UPDATE_STATUS);
+}
+
+void ap_autoaway_disable () {
+  ap_debug ("idle", "Disabling idleaway");
+
+  using_idleaway = FALSE;
+  ap_update (AP_UPDATE_STATUS);
+}
+
+gboolean ap_autoaway_in_use () {
+  return using_idleaway;
+}
+
+static gboolean ap_update_queue (gpointer data)
+{
+  PurpleAccount *account = NULL;
+  PurpleConnection *gc = NULL;
+
+  g_static_mutex_lock (&update_queue_mutex);
+
+  if (queued_profiles != NULL) {
+    account = (PurpleAccount *) queued_profiles->data;
+    queued_profiles = queued_profiles->next;
+  }
+
+  g_static_mutex_unlock (&update_queue_mutex);
+
+  gc = purple_account_get_connection (account);
+  if (gc != NULL) {
+    serv_set_info (gc, purple_account_get_user_info (account));
+  }
+
+  return TRUE;
+}
+
+static void ap_update_queue_start ()
+{
+  update_queue_timeout = purple_timeout_add (2000, ap_update_queue, NULL);
+}
+
+static void ap_update_queue_finish ()
+{
+  purple_timeout_remove (update_queue_timeout);
+  update_queue_timeout = 0;
+}
+/*--------------------------------------------------------------------------* 
+ * Preferences                                                              *
+ *--------------------------------------------------------------------------*/
+static void ap_delete_legacy_prefs () {
+  if (purple_prefs_exists ("/plugins/gtk/autoprofile/tab_number")) {
+    ap_debug ("general", "Deleting legacy preferences");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/components");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/tab_number");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_away");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_profile");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/accounts");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/message_titles");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/message_texts");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/default_profile");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/default_away");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/current_away");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/added_text");
+
+    purple_prefs_remove ("/plugins/gtk/autoprofile/delay_profile");
+    purple_prefs_remove ("/plugins/gtk/autoprofile/delay_away");
+
+    purple_prefs_rename ("/plugins/gtk/autoprofile/text_respond",
+                       "/plugins/gtk/autoprofile/autorespond/text");
+    purple_prefs_rename ("/plugins/gtk/autoprofile/text_trigger",
+                       "/plugins/gtk/autoprofile/autorespond/trigger");
+    purple_prefs_rename ("/plugins/gtk/autoprofile/delay_respond",
+                       "/plugins/gtk/autoprofile/autorespond/delay");
+    purple_prefs_rename ("/plugins/gtk/autoprofile/use_trigger",
+                       "/plugins/gtk/autoprofile/autorespond/enable");
+  }
+}
+
+static void ap_init_preferences () {
+  ap_debug ("general", "Initializing preference defaults if necessary");
+
+  /* Adding the folders */
+  purple_prefs_add_none ("/plugins/gtk");
+  purple_prefs_add_none ("/plugins/gtk/autoprofile");
+  purple_prefs_add_none ("/plugins/gtk/autoprofile/widgets");
+  purple_prefs_add_none ("/plugins/gtk/autoprofile/autorespond");
+
+  /* Behavior-settings */
+  purple_prefs_add_int    ("/plugins/gtk/autoprofile/delay_update", 30);
+  purple_prefs_add_string ("/plugins/gtk/autoprofile/show_summary", "always");
+  purple_prefs_add_bool   ("/plugins/gtk/autoprofile/queue_messages_when_away",
+    FALSE);
+  purple_prefs_add_bool   ("/plugins/gtk/autoprofile/away_when_idle",
+    purple_prefs_get_bool ("/core/away/away_when_idle"));
+
+  /* Auto-response settings */
+  purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/auto_reply",
+    purple_prefs_get_string ("/core/away/auto_reply"));
+  purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/text",
+    _("Say the magic word if you want me to talk more!"));
+  purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/trigger", 
+    _("please"));
+  purple_prefs_add_int  ("/plugins/gtk/autoprofile/autorespond/delay", 2);
+  purple_prefs_add_bool ("/plugins/gtk/autoprofile/autorespond/enable", TRUE);
+
+  /* Profile settings */
+  purple_prefs_add_string_list(
+    "/plugins/gtk/autoprofile/profile_accounts", NULL);
+  purple_prefs_add_string ("/plugins/gtk/autoprofile/profile",
+    _("Get AutoProfile for Purple at <a href=\""
+    "http://autoprofile.sourceforge.net/\">"
+    "autoprofile.sourceforge.net</a><br><br>[Timestamp]"));
+}
+
+/*--------------------------------------------------------------------------* 
+ * Last Call                                                                *
+ *--------------------------------------------------------------------------*/
+static void init_plugin (PurplePlugin *plugin) 
+{ 
+  ap_debug ("general", "Initializing AutoProfile");
+
+  ap_init_preferences ();
+  ap_widget_init ();
+}
+
+PURPLE_INIT_PLUGIN (autoprofile, init_plugin, info)
+
============================================================
--- autoprofile/autoprofile.h	2645e7c203744b3873c426655f56e2d9c3f3479c
+++ autoprofile/autoprofile.h	2645e7c203744b3873c426655f56e2d9c3f3479c
@@ -0,0 +1,112 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#ifndef AUTOPROFILE_H
+#define AUTOPROFILE_H
+
+#define AUTOPROFILE_VERSION "3.00-beta2"
+
+#include "internal.h"
+
+#include "sizes.h"
+#include "widget.h"
+#include "utility.h"
+
+#include "plugin.h"
+#include "gtkplugin.h"
+
+#include "signals.h"
+#include "prefs.h"
+#include "util.h"
+#include "notify.h"
+
+#include "string.h"
+#include "time.h"
+
+#define AP_SCHEDULE_UPDATE_DELAY 3000
+#define AP_GTK_MAX_MESSAGES 50
+
+/* Data types */
+typedef enum
+{
+  AP_MESSAGE_TYPE_OTHER = -1,
+  AP_MESSAGE_TYPE_PROFILE,
+  AP_MESSAGE_TYPE_AWAY,
+  AP_MESSAGE_TYPE_AVAILABLE,
+  AP_MESSAGE_TYPE_STATUS
+} APMessageType;
+
+typedef enum 
+{
+  AP_UPDATE_UNKNOWN = 0,
+  AP_UPDATE_STATUS,
+  AP_UPDATE_PROFILE
+} APUpdateType;
+
+/* Variable access functions */
+PurplePlugin *ap_get_plugin_handle ();
+gboolean ap_is_currently_away ();
+
+void ap_account_enable_profile (const PurpleAccount *, gboolean);
+gboolean ap_account_has_profile_enabled (const PurpleAccount *);
+
+/* Core behavior functions */
+gchar *ap_generate (const char *, gint);
+gchar *ap_get_sample_status_message (PurpleAccount *account);
+void ap_update (APUpdateType);
+void ap_update_after_delay (APUpdateType);
+void ap_update_stop (APUpdateType);
+
+/* Queueing functions */
+void ap_update_queueing ();
+
+/* Auto-away functions */
+void ap_autoaway_start ();
+void ap_autoaway_finish ();
+void ap_autoaway_touch ();
+void ap_autoaway_enable ();
+void ap_autoaway_disable ();
+gboolean ap_autoaway_in_use ();
+
+/* Auto-reply functions */
+void ap_autoreply_start ();
+void ap_autoreply_finish ();
+
+/* Gtk Away Messages */
+void ap_gtk_start ();
+void ap_gtk_finish ();
+void ap_gtk_make_visible ();
+void ap_gtk_add_message (APUpdateType, APMessageType, const gchar *);
+void ap_gtk_set_progress_visible (APUpdateType, gboolean);
+
+/* Gtk Actions */
+GList *actions (PurplePlugin *, gpointer);
+void ap_actions_finish ();
+
+/* Preferences */
+PidginPluginUiInfo ui_info;
+void ap_preferences_display ();
+void ap_gtk_prefs_add_summary_option (GtkWidget *);
+GtkWidget *get_account_page ();
+
+#endif /* #ifndef AUTOPROFILE_H */
============================================================
--- autoprofile/autoreply.c	cd281e6a397afe1b0e094497170e18a7abdc0906
+++ autoprofile/autoreply.c	cd281e6a397afe1b0e094497170e18a7abdc0906
@@ -0,0 +1,324 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "autoprofile.h"
+#include "conversation.h"
+
+#define SECS_BEFORE_RESENDING_AUTORESPONSE 600
+#define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married"
+#define MILLISECS_BEFORE_PROCESSING_MSG 100
+
+static guint pref_cb;
+
+static GSList *last_auto_responses = NULL;
+struct last_auto_response {
+  PurpleConnection *gc;
+  char name[80];
+  time_t sent;
+};
+
+static time_t response_timeout = 0;
+
+/*--------------------------------------------------------------------------*
+ * Auto-response utility functions                                          *
+ *--------------------------------------------------------------------------*/
+static gboolean
+expire_last_auto_responses(gpointer data)
+{
+  GSList *tmp, *cur;
+  struct last_auto_response *lar;
+
+  tmp = last_auto_responses;
+
+  while (tmp) {
+    cur = tmp;
+    tmp = tmp->next;
+    lar = (struct last_auto_response *)cur->data;
+
+    if ((time(NULL) - lar->sent) > SECS_BEFORE_RESENDING_AUTORESPONSE) {
+      last_auto_responses = g_slist_remove(last_auto_responses, lar);
+      g_free(lar);
+    }
+  }
+
+  return FALSE; /* do not run again */
+}
+
+static struct last_auto_response *
+get_last_auto_response(PurpleConnection *gc, const char *name)
+{
+  GSList *tmp;
+  struct last_auto_response *lar;
+
+  /* because we're modifying or creating a lar, schedule the
+   * function to expire them as the pref dictates */
+  purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 5) * 1000, 
+    expire_last_auto_responses, NULL);
+
+  tmp = last_auto_responses;
+
+  while (tmp) {
+    lar = (struct last_auto_response *)tmp->data;
+
+    if (gc == lar->gc && !strncmp(name, lar->name, sizeof(lar->name)))
+      return lar;
+
+    tmp = tmp->next;
+  }
+
+  lar = (struct last_auto_response *)g_new0(struct last_auto_response, 1);
+  g_snprintf(lar->name, sizeof(lar->name), "%s", name);
+  lar->gc = gc;
+  lar->sent = 0;
+  last_auto_responses = g_slist_append(last_auto_responses, lar);
+
+  return lar;
+}
+
+/*--------------------------------------------------------------------------*
+ * Message send/receive general functionality                               *
+ *--------------------------------------------------------------------------*/
+/* Detecting sent message stuff */
+static void sent_im_msg_cb (PurpleAccount *account, const char *receiver,
+                     const char *message) 
+{
+  PurpleConnection *gc;
+  PurplePresence *presence;
+  const gchar *auto_reply_pref;
+
+  gc = purple_account_get_connection (account);
+  presence = purple_account_get_presence (account);
+
+  /*
+   * FIXME - If "only auto-reply when away & idle" is set, then shouldn't
+   * this only reset lar->sent if we're away AND idle?
+   */
+  auto_reply_pref = 
+    purple_prefs_get_string ("/plugins/gtk/autoprofile/autorespond/auto_reply");
+  if ((gc->flags & PURPLE_CONNECTION_AUTO_RESP) &&
+      !purple_presence_is_available(presence) &&
+      strcmp(auto_reply_pref, "never")) 
+  {
+    struct last_auto_response *lar;
+    lar = get_last_auto_response(gc, receiver);
+    lar->sent = time(NULL);
+  }
+}
+
+/* Detecting received message stuff */
+struct received_im_msg {
+  PurpleAccount *account;
+  char *sender;
+  char *message;
+};
+
+static gint process_received_im_msg (gpointer data) 
+{
+  struct received_im_msg *received_im;
+  PurpleAccount *account;
+  char *sender;
+  char *message;
+  PurpleConnection *gc;
+  PurpleConversation *conv;
+
+  received_im = (struct received_im_msg *) data;
+  account = received_im->account;
+  sender = received_im->sender;
+  message = received_im->message;
+  free (data);
+
+  gc = purple_account_get_connection (account);
+
+  /* search for conversation again in case it was created by other handlers */
+  conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, 
+    sender, gc->account);
+  if (conv == NULL)
+    conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
+
+  /*
+   * Don't autorespond if:
+   *
+   *  - it's not supported on this connection
+   *  - we are available
+   *  - or it's disabled
+   *  - or we're not idle and the 'only auto respond if idle' pref
+   *    is set
+   */ 
+  if (gc->flags & PURPLE_CONNECTION_AUTO_RESP)
+  {
+    PurplePresence *presence;
+    PurpleStatus *status;
+    PurpleStatusType *status_type;
+    PurpleStatusPrimitive primitive;
+    const gchar *auto_reply_pref;
+    char *away_msg = NULL;
+
+    auto_reply_pref = purple_prefs_get_string(
+      "/plugins/gtk/autoprofile/autorespond/auto_reply");
+
+    presence = purple_account_get_presence(account);
+    status = purple_presence_get_active_status(presence);
+    status_type = purple_status_get_type(status);
+    primitive = purple_status_type_get_primitive(status_type);
+    if ((primitive == PURPLE_STATUS_AVAILABLE) ||
+        (primitive == PURPLE_STATUS_INVISIBLE) ||
+        (primitive == PURPLE_STATUS_MOBILE) ||
+        !strcmp(auto_reply_pref, "never") ||
+        (!purple_presence_is_idle(presence) && 
+         !strcmp(auto_reply_pref, "awayidle")))
+    {
+      free (sender);
+      free (message);
+      return FALSE;
+    }
+
+    away_msg = ap_get_sample_status_message (account);
+
+    if ((away_msg != NULL) && (*away_msg != '\0')) {
+      struct last_auto_response *lar;
+      gboolean autorespond_enable;
+      time_t now = time(NULL);
+
+      autorespond_enable = purple_prefs_get_bool (
+        "/plugins/gtk/autoprofile/autorespond/enable");
+      /*
+       * This used to be based on the conversation window. But um, if
+       * you went away, and someone sent you a message and got your
+       * auto-response, and then you closed the window, and then they
+       * sent you another one, they'd get the auto-response back too
+       * soon. Besides that, we need to keep track of this even if we've
+       * got a queue. So the rest of this block is just the auto-response,
+       * if necessary.
+       */
+      lar = get_last_auto_response(gc, sender);
+      if ((now - lar->sent) >= SECS_BEFORE_RESENDING_AUTORESPONSE) {
+        lar->sent = now;
+        // Send basic autoresponse
+        serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP);
+        purple_conv_im_write (PURPLE_CONV_IM(conv), NULL, away_msg,
+                            PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP, 
+                            now);
+
+        // Send additional hint if enabled
+        if (autorespond_enable) {
+          const gchar *query = purple_prefs_get_string (
+            "/plugins/gtk/autoprofile/autorespond/text");
+          serv_send_im (gc, sender, query, PURPLE_MESSAGE_AUTO_RESP);
+          purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, query,
+                              PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP,
+                              now); 
+        }
+
+      } else if (autorespond_enable && 
+          difftime (time(NULL), response_timeout) >
+          purple_prefs_get_int ("/plugins/gtk/autoprofile/autorespond/delay")) {
+        gchar *text = purple_markup_strip_html (message);
+        if (match_start (text, purple_prefs_get_string (
+                      "/plugins/gtk/autoprofile/autorespond/trigger")) == 1) {
+          serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP);
+          purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, away_msg,
+                              PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP,
+                              now);       
+
+          response_timeout = time (NULL);
+          ap_debug ("autorespond", "string matched, responding");
+        }
+        free (text);
+      }
+    }
+
+    free (away_msg);
+  }
+
+  free (sender);
+  free (message);
+
+  return FALSE;
+}
+
+static void received_im_msg_cb (PurpleAccount *account, char *sender, 
+        char *message, PurpleConversation *conv, PurpleMessageFlags flags)
+{
+  struct received_im_msg *received_im;
+
+  received_im = 
+    (struct received_im_msg *) malloc (sizeof (struct received_im_msg));
+  received_im->account = account;
+  received_im->sender = strdup (sender);
+  received_im->message = strdup (message);
+
+  purple_timeout_add (MILLISECS_BEFORE_PROCESSING_MSG, process_received_im_msg,
+    received_im);
+}
+
+static void auto_pref_cb (
+  const char *name, PurplePrefType type, gconstpointer val, gpointer data) 
+{
+  if (!strcmp (purple_prefs_get_string ("/core/away/auto_reply"), "never"))
+    return;
+
+  purple_notify_error (NULL, NULL,
+    N_("This preference is disabled"), 
+    N_("This preference currently has no effect because AutoProfile is in "
+       "use.  To modify this behavior, use the AutoProfile configuration "
+       "menu."));
+
+  purple_prefs_set_string ("/core/away/auto_reply", "never");
+}
+
+/*--------------------------------------------------------------------------*
+ * Global functions                                                         *
+ *--------------------------------------------------------------------------*/
+void ap_autoreply_start ()
+{
+  purple_prefs_set_string ("/core/away/auto_reply", "never");
+
+  purple_signal_connect (purple_conversations_get_handle (), "sent-im-msg",
+    ap_get_plugin_handle (), PURPLE_CALLBACK(sent_im_msg_cb), NULL);
+  purple_signal_connect (purple_conversations_get_handle (), "received-im-msg",
+    ap_get_plugin_handle (), PURPLE_CALLBACK(received_im_msg_cb), NULL);
+
+  pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
+    "/core/away/auto_reply", auto_pref_cb, NULL);
+}
+
+void ap_autoreply_finish ()
+{
+  GSList *tmp;
+
+  // Assumes signals are disconnected globally
+  
+  purple_prefs_disconnect_callback (pref_cb);
+  pref_cb = 0;
+
+  purple_prefs_set_string ("/core/away/auto_reply", purple_prefs_get_string (
+    "/plugins/gtk/autoprofile/autorespond/auto_reply"));
+
+  while (last_auto_responses) {
+    tmp = last_auto_responses->next;
+    g_free (last_auto_responses->data);
+    g_slist_free_1 (last_auto_responses);
+    last_auto_responses = tmp;
+  }
+}
+
============================================================
--- autoprofile/comp_countdownup.c	46ebaf0f9305871d403a712c26f4ff46005a4ce2
+++ autoprofile/comp_countdownup.c	46ebaf0f9305871d403a712c26f4ff46005a4ce2
@@ -0,0 +1,434 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "component.h"
+#include "gtkprefs.h"
+#include "utility.h"
+
+static GtkWidget *spin_secs;
+static GtkWidget *spin_mins;
+static GtkWidget *spin_hour;
+static GtkWidget *spin_day;
+static GtkWidget *spin_month;
+static GtkWidget *spin_year;
+
+/* Generate the time! */
+char *count_generate (struct widget *w) 
+{
+  double d_secs, d_mins, d_hours, d_days;
+  char *s_secs, *s_mins, *s_hours, *s_days;
+  double difference;
+  int l, s;
+
+  struct tm *ref_time;
+  char *result;
+
+  ref_time = (struct tm *) malloc (sizeof (struct tm));
+
+  ref_time->tm_sec = ap_prefs_get_int (w, "secs");
+  ref_time->tm_min = ap_prefs_get_int (w, "mins");
+  ref_time->tm_hour = ap_prefs_get_int (w, "hour");
+  ref_time->tm_mday = ap_prefs_get_int (w, "day");
+  ref_time->tm_mon = ap_prefs_get_int (w, "month") - 1;
+  ref_time->tm_year = ap_prefs_get_int (w, "year") - 1900;
+  ref_time->tm_isdst = -1;
+
+  mktime (ref_time);
+
+  if (ap_prefs_get_int (w, "down") == 1)
+    difference = difftime (mktime (ref_time), time(NULL));
+  else
+    difference = difftime (time(NULL), mktime (ref_time));
+
+  if (difference < 0) {
+    d_secs = 0;
+    d_mins = 0;
+    d_hours = 0;
+    d_days = 0;
+  } else {
+    d_mins = floor (difference / 60);
+    d_secs = difference - (d_mins * 60);
+    d_hours = floor (d_mins / 60);
+    d_mins = d_mins - (d_hours * 60);
+    d_days = floor (d_hours / 24);
+    d_hours = d_hours - (d_days * 24);
+  }
+
+  result = (char *)malloc(sizeof (char) * AP_SIZE_MAXIMUM);
+  l = ap_prefs_get_int (w, "large");
+  s = ap_prefs_get_int (w, "small");
+
+  if (l < s) {
+   g_snprintf(result, AP_SIZE_MAXIMUM, 
+      "%.0f days, %.0f hours, %.0f minutes, %.0f seconds",
+      d_days, d_hours, d_mins, d_secs); 
+   free (ref_time);
+   return result;
+  }
+
+  if (l < 3)
+    d_hours = d_hours + (d_days * 24);
+  if (l < 2)
+    d_mins = d_mins + (d_hours * 60);
+  if (l < 1)
+    d_secs = d_secs + (d_mins * 60);
+
+  if (d_days == 1.0)
+    s_days = strdup ("day");
+  else
+    s_days = strdup ("days");
+
+  if (d_hours == 1.0)
+    s_hours = strdup ("hour");
+  else
+    s_hours = strdup ("hours");
+
+  if (d_mins == 1.0)
+    s_mins = strdup ("minute");
+  else
+    s_mins = strdup ("minutes");
+
+  if (d_secs == 1.0)
+    s_secs = strdup ("second");
+  else
+    s_secs = strdup ("seconds");
+
+  switch (l) {
+    case 3:
+      switch (s) {
+        case 3:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
+            d_days, s_days);
+          break;
+        case 2:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
+            d_days, s_days, d_hours, s_hours);
+          break;
+        case 1:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s",
+            d_days, s_days, d_hours, s_hours, d_mins, s_mins);
+          break;
+        case 0:
+          g_snprintf (result, AP_SIZE_MAXIMUM, 
+            "%.0f %s, %.0f %s, %.0f %s, %.0f %s",
+            d_days, s_days, d_hours, s_hours, d_mins, s_mins, d_secs, s_secs);
+          break;
+        default:
+          *result = '\0';
+      }
+      break;
+    case 2:
+      switch (s) {
+        case 2:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
+            d_hours, s_hours);
+          break;
+        case 1:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
+            d_hours, s_hours, d_mins, s_mins);
+          break;
+        case 0:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s",
+            d_hours, s_hours, d_mins, s_mins, d_secs, s_secs);
+          break;
+        default:
+          *result = '\0';
+      }
+      break;
+    case 1:
+      switch (s) {
+        case 1:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
+            d_mins, s_mins);
+          break;
+        case 0:
+          g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
+            d_mins, s_mins, d_secs, s_secs);
+          break;
+        default:
+          *result = '\0';
+      }
+      break;
+    case 0:
+      g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
+        d_secs, s_secs);
+      break;
+    default:
+      *result = '\0';
+  }
+
+  free (s_days);
+  free (s_hours);
+  free (s_mins);
+  free (s_secs);
+  free (ref_time);
+
+  return result;
+}
+
+static void update_year (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "year", value);
+}
+
+static void update_month (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "month", value);
+}
+
+static void update_day (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "day", value);
+}
+
+static void update_hour (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "hour", value);
+}
+
+static void update_mins (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "mins", value);
+}
+
+static void update_secs (GtkSpinButton *spinner, struct widget *w)
+{
+  int value = gtk_spin_button_get_value_as_int (spinner);
+  ap_prefs_set_int (w, "secs", value);
+}
+
+static void set_to_current_time (GtkButton *button, struct widget *w)
+{
+  time_t the_time;
+  struct tm *ref_time;
+
+  the_time = time(NULL);
+  ref_time = ap_localtime(&the_time);
+  ap_prefs_set_int (w, "year",  ref_time->tm_year + 1900);
+  ap_prefs_set_int (w, "month", ref_time->tm_mon + 1);
+  ap_prefs_set_int (w, "day",   ref_time->tm_mday);
+  ap_prefs_set_int (w, "hour",  ref_time->tm_hour);
+  ap_prefs_set_int (w, "mins",  ref_time->tm_min);
+  ap_prefs_set_int (w, "secs",  ref_time->tm_sec);
+  free (ref_time);
+
+  if (spin_secs != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_secs), 
+      ap_prefs_get_int (w, "secs"));
+  }
+  if (spin_mins != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_mins), 
+      ap_prefs_get_int (w, "mins"));
+  } 
+  if (spin_hour != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_hour), 
+      ap_prefs_get_int (w, "hour"));
+  }
+  if (spin_day != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_day), 
+      ap_prefs_get_int (w, "day"));
+  }
+  if (spin_month != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_month), 
+      ap_prefs_get_int (w, "month"));
+  }
+  if (spin_year != NULL) {
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_year), 
+      ap_prefs_get_int (w, "year"));
+  }
+}
+
+GtkWidget *count_menu (struct widget *w)
+{
+  GtkWidget *vbox, *hbox, *big_hbox, *frame;
+  GtkWidget *label, *spinner, *dropbox, *button;
+  GList *options;
+
+  big_hbox = gtk_hbox_new (FALSE, 6);
+  
+  frame = pidgin_make_frame (big_hbox, _("Start/end time"));
+  vbox = gtk_vbox_new (FALSE, 6);
+  gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Year: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (1970, 2035, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "year"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_year), w);
+  spin_year = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Month: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (1, 12, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "month"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_month), w);
+  spin_month = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Day: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (1, 31, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "day"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_day), w);
+  spin_day = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Hour: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (0, 23, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "hour"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_hour), w);
+  spin_hour = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Minutes: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (0, 59, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "mins"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_mins), w);
+  spin_mins = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 12);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  label = gtk_label_new (_("Seconds: "));
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
+  spinner = gtk_spin_button_new_with_range (0, 59, 1);
+  gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), 
+    ap_prefs_get_int (w, "secs"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (update_secs), w);
+  spin_secs = spinner;
+
+  hbox = gtk_hbox_new (FALSE, 12);
+  gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+  button = gtk_button_new_with_label ("Set to current time");
+
+  g_signal_connect (G_OBJECT (button), "clicked",
+                    G_CALLBACK (set_to_current_time), w);
+  gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
+
+  frame = pidgin_make_frame (big_hbox, _("Which way"));
+  vbox = gtk_vbox_new (FALSE, 6);
+  gtk_container_add (GTK_CONTAINER (frame), vbox);
+
+  options = g_list_append (NULL,    (char *) _("Count down to stop date"));
+  options = g_list_append (options, GINT_TO_POINTER(1));
+  options = g_list_append (options, (char *)
+                                      _("Count time since start date"));
+  options = g_list_append (options, GINT_TO_POINTER(0));
+
+  dropbox = ap_prefs_dropdown_from_list (w, vbox, NULL,
+    PURPLE_PREF_INT, "down", options);
+  g_list_free (options);
+
+  options = g_list_append (NULL,    (char *) _("Days"));
+  options = g_list_append (options, GINT_TO_POINTER(3));
+  options = g_list_append (options, (char *) _("Hours"));
+  options = g_list_append (options, GINT_TO_POINTER(2));
+  options = g_list_append (options, (char *) _("Minutes"));
+  options = g_list_append (options, GINT_TO_POINTER(1));
+  options = g_list_append (options, (char *) _("Seconds"));
+  options = g_list_append (options, GINT_TO_POINTER(0));
+
+  dropbox = ap_prefs_dropdown_from_list (w, vbox, 
+    _("Largest units displayed"), PURPLE_PREF_INT, "large", options);
+  dropbox = ap_prefs_dropdown_from_list (w, vbox, 
+    _("Smallest units displayed"), PURPLE_PREF_INT, "small", options);
+  g_list_free (options);
+  
+  return big_hbox;
+}
+
+/* Init prefs */
+void count_init (struct widget *w) {
+  time_t the_time;
+  struct tm *ref_time;
+
+  the_time = time(NULL);
+  ref_time = ap_localtime(&the_time);
+
+  ap_prefs_add_int (w, "down", 1);
+  ap_prefs_add_int (w, "small", 0);
+  ap_prefs_add_int (w, "large", 3);
+  ap_prefs_add_int (w, "year",
+    ref_time->tm_year + 1900);
+  ap_prefs_add_int (w, "month",
+    ref_time->tm_mon + 1);
+  ap_prefs_add_int (w, "day",
+    ref_time->tm_mday);
+  ap_prefs_add_int (w, "hour", 
+    ref_time->tm_hour);
+  ap_prefs_add_int (w, "mins",
+    ref_time->tm_min);
+  ap_prefs_add_int (w, "secs",
+    ref_time->tm_sec);
+  free (ref_time);
+}
+
+struct component count =
+{
+  N_("Countdown timer"),
+  N_("Given a date, shows amount of time until it (or since it)"),
+  "Timer",
+  &count_generate,
+  &count_init,
+  NULL,
+  NULL,
+  NULL,
+  &count_menu
+};
+
============================================================
--- autoprofile/comp_executable.c	c49398a63c6b16ed80e8ff223698cba94fe0fec3
+++ autoprofile/comp_executable.c	c49398a63c6b16ed80e8ff223698cba94fe0fec3
@@ -0,0 +1,165 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "component.h"
+#include "utility.h"
+
+/*---------- EXECUTABLE: STDOUT from a program ----------*/
+static GtkWidget *file_selector;
+static GtkWidget *file_entry;
+
+/* Read file into string and return */
+char *executable_generate (struct widget *w)
+{
+  char *text, *text_start;
+  int max;
+  gboolean exec;
+  GError *return_error;
+  
+  max = ap_prefs_get_int (w, "max_size");
+  exec = g_spawn_command_line_sync (ap_prefs_get_string (w, "command"),
+    &text_start, NULL, NULL, &return_error);
+
+  if (!exec) {
+    /* Excution failed */
+    ap_debug ("executable", "command failed to execute");
+    return strdup (_("[ERROR: command failed to execute]"));
+  }
+
+  if (strlen (text_start) < max)
+    text = text_start + strlen(text_start);
+  else
+    text = text_start + max;
+
+  /* Should back off only if the last item is newline */
+  /* Gets rid of the extra <BR> in output */
+  text--;
+  if (*text != '\n')
+    text++;
+
+  *text = '\0';
+  return text_start;
+}
+
+void executable_filename (GtkWidget *widget, gpointer user_data) {
+  const gchar *selected_filename;
+
+  selected_filename = gtk_file_selection_get_filename (
+    GTK_FILE_SELECTION (file_selector));
+
+  ap_prefs_set_string ((struct widget *) user_data, "command",
+    selected_filename);
+  gtk_entry_set_text (GTK_ENTRY (file_entry), selected_filename);
+}
+
+/* Creates and pops up file selection dialog for fortune file */
+void executable_selection (GtkWidget *widget, struct widget *w) {
+  const char *cur_file;
+  
+  /* Create the selector */
+  file_selector = gtk_file_selection_new (
+    "Select the location of the program");
+
+  cur_file = ap_prefs_get_string (w, "command");
+  if (strlen (cur_file) > 1) {
+    gtk_file_selection_set_filename (
+      GTK_FILE_SELECTION (file_selector), cur_file); 
+  }
+  
+  g_signal_connect (GTK_OBJECT(
+                      GTK_FILE_SELECTION(file_selector)->ok_button),
+                    "clicked", G_CALLBACK (executable_filename), w);
+   			   
+  /* Destroy dialog box when the user clicks button. */
+  g_signal_connect_swapped (GTK_OBJECT(
+      GTK_FILE_SELECTION(file_selector)->ok_button), 
+    "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); 
+
+  g_signal_connect_swapped (GTK_OBJECT (
+      GTK_FILE_SELECTION (file_selector)->cancel_button), 
+    "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector); 
+   
+  /* Display dialog */
+  gtk_widget_show (file_selector);
+}
+
+static gboolean executable_update (GtkWidget *widget, GdkEventFocus *evt, 
+                                gpointer data)
+{
+  ap_prefs_set_string ((struct widget *) data, "command",
+    gtk_entry_get_text (GTK_ENTRY (file_entry)));
+  return FALSE;
+}
+
+/* Create the menu */
+GtkWidget *executable_menu (struct widget *w)
+{
+  GtkWidget *ret = gtk_vbox_new (FALSE, 5);
+  GtkWidget *hbox, *label, *button;
+
+  label = gtk_label_new (
+    _("Specify the command line you wish to execute"));
+  gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+
+  hbox = gtk_hbox_new (FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
+  /* Text entry to type in program name */
+  file_entry = gtk_entry_new ();
+  gtk_box_pack_start (GTK_BOX (hbox), file_entry, FALSE, FALSE, 0);
+  gtk_entry_set_text (GTK_ENTRY (file_entry), 
+                  ap_prefs_get_string (w, "command"));
+  g_signal_connect (G_OBJECT (file_entry), "focus-out-event",
+                    G_CALLBACK (executable_update), w);
+  /* Button to bring up file select dialog */
+  button = gtk_button_new_with_label ("Browse for program");
+  g_signal_connect (G_OBJECT (button), "clicked",
+                    G_CALLBACK (executable_selection), w);
+
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  ap_prefs_labeled_spin_button (w, ret, 
+    _("Max characters to read from output: "), "max_size",
+    1, AP_SIZE_MAXIMUM, NULL);
+  
+  return ret;
+}
+
+void executable_init (struct widget *w) {
+  ap_prefs_add_string (w, "command", "date");
+  ap_prefs_add_int (w, "max_size", 1000);
+}
+
+struct component executable =
+{
+  N_("Command Line"),
+  N_("Reproduces standard output of running a program on the command line"),
+  "Command",
+  &executable_generate,
+  &executable_init,
+  NULL,
+  NULL,
+  NULL,
+  &executable_menu
+};
+
============================================================
--- autoprofile/comp_http.c	8f9e5686d80c9146a87227399302614c56c0d3c5
+++ autoprofile/comp_http.c	8f9e5686d80c9146a87227399302614c56c0d3c5
@@ -0,0 +1,202 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "component.h"
+
+static GHashTable *refresh_timeouts = NULL;
+
+/*---------- HTTP: HTTP requested Data ----------*/
+static void http_response (PurpleUtilFetchUrlData *reuqest_data, gpointer data, const char *c, gsize len, const gchar *error_message)
+{
+  struct widget *w;
+  w = (struct widget *) data;
+
+  // Invalid URL!
+  if (c == NULL) {
+    ap_prefs_set_string (w, "http_data", 
+      _("[AutoProfile error: Invalid URL or no internet connection]"));
+    return;
+  }
+
+  w = (struct widget *) data;
+  ap_prefs_set_string (w, "http_data", c);
+}
+
+static char* http_generate (struct widget *w)
+{
+  const char *result, *url;
+
+  url = ap_prefs_get_string (w, "http_url");
+  if (!url || url[0] == '\0') {
+    return strdup (_("[AutoProfile error: No URL specified]"));
+  }
+
+  result = ap_prefs_get_string (w, "http_data");
+  if (result == NULL) return strdup ("");
+  return strdup (result);
+}
+
+static gboolean http_refresh_update (gpointer user_data)
+{
+  struct widget *w;
+  char *http_url;
+
+  w = (struct widget *) user_data;
+  http_url = strdup (ap_prefs_get_string (w, "http_url"));
+
+  if( http_url && (http_url[0] != '\0') ) {
+    purple_util_fetch_url(http_url, TRUE, NULL, FALSE, http_response, w);
+  } else {
+    ap_prefs_set_string (w, "http_data", "");
+  }
+
+  free (http_url);
+  return TRUE;
+}
+
+static void http_load (struct widget *w)
+{
+  gpointer http_refresh_timeout;
+
+  if (refresh_timeouts == NULL) {
+    refresh_timeouts = g_hash_table_new (NULL, NULL);
+  }
+
+  http_refresh_update (w);
+  http_refresh_timeout = GINT_TO_POINTER (g_timeout_add (
+    ap_prefs_get_int (w, "http_refresh_mins") * 60 * 1000,
+    http_refresh_update, w));
+  g_hash_table_insert (refresh_timeouts, w, http_refresh_timeout);
+}
+
+static void http_unload (struct widget *w)
+{
+  gpointer http_refresh_timeout;
+
+  http_refresh_timeout = g_hash_table_lookup (refresh_timeouts, w);
+  g_source_remove (GPOINTER_TO_INT (http_refresh_timeout));
+  g_hash_table_remove (refresh_timeouts, w);
+}
+
+static void http_init (struct widget *w)
+{
+  ap_prefs_add_string (w, "http_url", "");
+  ap_prefs_add_string (w, "http_data", "");
+  ap_prefs_add_int (w, "http_refresh_mins", 1);
+}
+
+static gboolean http_url_update (GtkWidget *widget, GdkEventFocus *evt, 
+  gpointer data)
+{
+  struct widget *w = (struct widget *) data;
+  ap_prefs_set_string (w, "http_url",
+    gtk_entry_get_text (GTK_ENTRY (widget)));
+
+  return FALSE;
+}
+
+static gboolean http_refresh_mins_update (GtkWidget *widget, gpointer data)
+{
+  struct widget *w;
+  gpointer timeout;
+  int minutes;
+
+  minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
+
+  w = (struct widget *) data;
+  ap_prefs_set_int (w, "http_refresh_mins", minutes);
+
+  // Kill the current timer and run a new one
+  timeout = g_hash_table_lookup (refresh_timeouts, w);
+  g_source_remove (GPOINTER_TO_INT(timeout));
+  timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000,
+    http_refresh_update, w));
+  g_hash_table_replace (refresh_timeouts, w, timeout);
+
+  return FALSE;
+}
+
+static void http_data_update (GtkWidget *w, gpointer data) {
+  http_refresh_update (data);
+}
+
+static GtkWidget *http_menu (struct widget *w)
+{
+  GtkWidget *ret = gtk_vbox_new (FALSE, 5);
+  GtkWidget *label, *hbox, *button, *spinner;
+  GtkWidget *http_url_entry;
+
+  label = gtk_label_new (_("Select URL with source content"));
+  gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
+
+  hbox = gtk_hbox_new (FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
+
+  // URL Entry
+  http_url_entry = gtk_entry_new ();
+  gtk_box_pack_start (GTK_BOX (hbox), http_url_entry, TRUE, TRUE, 0);
+  gtk_entry_set_text (GTK_ENTRY (http_url_entry),
+    ap_prefs_get_string (w, "http_url"));
+  g_signal_connect (G_OBJECT (http_url_entry), "focus-out-event",
+                    G_CALLBACK (http_url_update), w);
+
+  // Update Now!
+  button = gtk_button_new_with_label (_("Fetch page now!"));
+  g_signal_connect (G_OBJECT (button), "clicked",
+                    G_CALLBACK (http_data_update), w);
+
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+
+  hbox = gtk_hbox_new (FALSE, 5);
+  gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
+
+  label = gtk_label_new (_("Delay"));
+  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+  spinner = gtk_spin_button_new_with_range (1, 60, 1);
+  gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
+  gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
+    ap_prefs_get_int (w, "http_refresh_mins"));
+  g_signal_connect (G_OBJECT (spinner), "value-changed",
+                    G_CALLBACK (http_refresh_mins_update), w);
+
+  label = gtk_label_new (_("minutes between page fetches"));
+  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
+
+  return ret;
+}
+
+struct component http =
+{
+  N_("Webpage"),
+  N_("Data fetched from an internet URL using HTTP"),
+  "Webpage",
+  &http_generate,
+  &http_init,
+  &http_load,
+  &http_unload,
+  NULL,
+  &http_menu
+};
+
============================================================
--- autoprofile/comp_logstats.c	a4ad85fdee55de687154715674ea4aaf9b2a7e3e
+++ autoprofile/comp_logstats.c	a4ad85fdee55de687154715674ea4aaf9b2a7e3e
@@ -0,0 +1,1039 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "autoprofile.h"
+#include "log.h"
+#include "account.h"
+#include "conversation.h"
+#include "utility.h"
+#include "util.h"
+
+#include "comp_logstats.h"
+
+struct conversation_time {
+  time_t *start_time;
+  char *name;
+};
+
+/* Represents data about a particular 24 hour period in the logs */
+struct log_date {
+  int year;                   // The year
+  int month;                  // The month
+  int day;                    // The day
+  int received_msgs;          // # msgs received
+  int received_words;         // # words received
+  int sent_msgs;              // # msgs sent
+  int sent_words;             // # words sent
+  GSList *conversation_times; // List of conversation_time pointers
+};
+
+/* List of struct log_dates 
+   This is SORTED by most recent first */
+static GSList *dates = NULL;
+
+/* Hashtable of log_dates */
+static GHashTable *dates_table = NULL;
+
+/* Is the current line part of a message sent or received? */
+static gboolean receiving = FALSE;
+/* Shortcut vars */
+static char *cur_receiver = NULL;
+static char *cur_sender = NULL;
+
+/* Implements GCompareFunc */
+static gint conversation_time_compare (gconstpointer x, gconstpointer y) {
+  const struct conversation_time *a = x;
+  const struct conversation_time *b = y;
+
+  if (difftime (*(a->start_time), *(b->start_time)) == 0.0) {
+    if (!strcmp (a->name, b->name))
+      return 0;
+  }
+
+  return -1;
+}
+
+/* Implements GCompareFunc */
+static gint log_date_compare (gconstpointer x, gconstpointer y)
+{
+  const struct log_date *a = y;
+  const struct log_date *b = x;
+
+  if (a->year == b->year) {
+    if (a->month == b->month) {
+      if (a->day == b->day)
+        return 0;
+      else
+        return a->day - b->day;
+    } else {
+      return a->month - b->month;
+    }
+  } else {
+    return a->year - b->year;
+  }
+}
+
+/* Implements GHashFunc */
+static guint log_date_hash (gconstpointer key)
+{
+  const struct log_date *d = key;
+  return ((d->year * 365) + (d->month * 12) + (d->day));
+}
+
+/* Implements GEqualFunc */
+static gboolean log_date_equal (gconstpointer x, gconstpointer y)
+{
+  const struct log_date *a = y;
+  const struct log_date *b = x;
+
+  if (a->year == b->year &&
+      a->month == b->month &&
+      a->day == b->day) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/* Returns the struct log_date associated with a particular date.
+   Will MODIFY list of dates and insert sorted if not yet created */
+static struct log_date *get_date (int year, int month, int day)
+{
+  struct log_date *cur_date;
+  gpointer *node;
+
+  cur_date = (struct log_date *)malloc(sizeof(struct log_date));
+  cur_date->year = year;
+  cur_date->month = month;
+  cur_date->day = day;
+
+  if ((node = g_hash_table_lookup (dates_table, cur_date))) {
+    free (cur_date);
+    return (struct log_date *)node;
+  } else {
+    g_hash_table_insert (dates_table, cur_date, cur_date);
+    cur_date->received_msgs = 0;
+    cur_date->received_words = 0;
+    cur_date->sent_msgs = 0;
+    cur_date->sent_words = 0;
+    cur_date->conversation_times = NULL;
+    return cur_date;
+  }
+}
+
+/* Like get_date, except specific to the current date */
+static struct log_date *get_today ()
+{
+  time_t the_time;
+  struct tm *cur_time;
+
+  time (&the_time);
+  cur_time = localtime (&the_time);
+
+  return get_date (cur_time->tm_year, cur_time->tm_mon, cur_time->tm_mday);
+}
+
+static int string_word_count (const char *line)
+{
+  int count, state;
+
+  count = 0;
+  state = 0;
+
+  /* If state is 1, currently processing a word */
+  while (*line) {
+    if (state == 0) {
+      if (!isspace (*line))
+        state = 1;
+    } else {
+      if (isspace (*line)) {
+        state = 0;
+        count++;
+      }
+    }
+    line++;
+  }
+
+  if (state == 1)
+    count++;
+
+  return count;
+}
+
+/* Figure out if a person is yourself or someone else */
+static gboolean is_self (PurpleAccount *a, const char *name) {
+  GList *accounts, *aliases, *aliases_start;
+  PurpleAccount *account;
+
+  char *normalized;
+  const char *normalized_alias;
+
+  if (cur_sender && !strcmp (cur_sender, name)) {
+    return TRUE;
+  }
+
+  if (cur_receiver && !strcmp (cur_receiver, name)) {
+    return FALSE;
+  }
+
+  normalized = strdup (purple_normalize (a, name));
+  accounts = purple_accounts_get_all ();
+
+  aliases_start = aliases = purple_prefs_get_string_list (
+    "/plugins/gtk/autoprofile/components/logstat/aliases");
+
+  while (aliases) {
+    normalized_alias = purple_normalize (a, (char *)aliases->data);
+
+    if (!strcmp (normalized, normalized_alias)) {
+      free_string_list (aliases_start);
+      free (normalized);
+
+      if (cur_sender)
+        free (cur_sender);
+      cur_sender = strdup (name);
+
+      return TRUE;
+    }
+
+    aliases = aliases->next;
+  }
+
+  free_string_list (aliases_start);
+
+  while (accounts) {
+    account = (PurpleAccount *)accounts->data;
+    if (!strcmp (normalized, purple_account_get_username (account))) {
+      free (normalized);
+      if (cur_sender)
+        free (cur_sender);
+      cur_sender = strdup (name);
+      return TRUE;
+    }
+    accounts = accounts->next;
+  }
+
+  free (normalized);
+
+  if (cur_receiver)
+    free (cur_receiver);
+  cur_receiver = strdup (name);
+  return FALSE;
+}
+
+/* Parses a line of a conversation */
+static void parse_line (PurpleLog *cur_log, char *l, struct log_date *d)
+{
+  char *cur_line, *cur_line_start;
+  char *name;
+  char *message;
+
+  char *line = l;
+
+  if (strlen (line) > 14 && *line == ' ')
+    line++;
+
+  if (strlen (line) > 13 &&
+      *line == '(' &&
+      isdigit (*(line + 1)) &&
+      isdigit (*(line + 2)) &&
+      *(line + 3) == ':' &&
+      isdigit (*(line + 4)) &&
+      isdigit (*(line + 5)) &&
+      *(line + 6) == ':' &&
+      isdigit (*(line + 7)) &&
+      isdigit (*(line + 8)) &&
+      *(line + 9) == ')' &&
+      isspace (*(line + 10))) {
+    cur_line_start = cur_line = line + 11;
+    while (*cur_line) {
+      if (*cur_line == ':') {
+        *cur_line = '\0';
+        name = cur_line_start;
+        message = ++cur_line;
+
+        receiving = !is_self (cur_log->account, name);
+
+        if (receiving) {
+          d->received_msgs++;
+          d->received_words += string_word_count (message);
+        } else {
+          d->sent_msgs++;
+          d->sent_words += string_word_count (message);
+        }
+
+        return;
+      }
+      cur_line++;
+    }
+
+  }
+
+  if (receiving) {
+    d->received_words += string_word_count (line);
+  } else {
+    d->sent_words += string_word_count (line);
+  }
+}
+
+/* Parses a conversation if hasn't been handled yet */
+static void parse_log (PurpleLog *cur_log)
+{
+  struct log_date *the_date;
+  struct tm *the_time;
+  struct conversation_time *conv_time;
+
+  PurpleLogReadFlags flags;
+  char *content, *cur_content, *cur_content_start, *temp;
+
+  the_time = localtime (&(cur_log->time));
+  the_date = get_date (the_time->tm_year, the_time->tm_mon, the_time->tm_mday);
+
+  /* Check for old log and if no conflicts, add to list */
+  conv_time = (struct conversation_time *)malloc (
+    sizeof (struct conversation_time));
+  conv_time->start_time = (time_t *)malloc (sizeof(time_t));
+  *(conv_time->start_time) = cur_log->time;
+  conv_time->name = strdup (cur_log->name);
+
+  if (g_slist_find_custom (the_date->conversation_times, conv_time, 
+    conversation_time_compare)) {
+    /* We already processed this!  Halt! */
+    free (conv_time->start_time);
+    free (conv_time->name);
+    free (conv_time);
+    return;
+  }
+
+  the_date->conversation_times = g_slist_prepend (the_date->conversation_times,
+    conv_time);
+
+  /* Start rolling the counters! */
+  temp = purple_log_read (cur_log, &flags);
+  if (!strcmp ("html", cur_log->logger->id)) {
+    content = purple_markup_strip_html (temp);
+    free (temp);
+  } else {
+    content = temp;
+  }
+
+  cur_content_start = cur_content = content;
+
+  /* Splits the conversation into lines (each line may not necessarily
+     be a seperate message */
+  while (*cur_content) {
+    if (*cur_content == '\n') {
+      *cur_content = '\0';
+      parse_line (cur_log, cur_content_start, the_date);
+      cur_content_start = cur_content + 1;
+    }
+    cur_content++;
+  }
+
+  parse_line (cur_log, cur_content_start, the_date);
+
+  free (content);
+}
+
+/* Get names of users in logs */
+static GList *logstats_get_names (PurpleLogType type, PurpleAccount *account)
+{
+  GDir *dir;
+  const char *prpl;
+  GList *ret;
+  const char *filename;
+  char *path, *me, *tmp;
+
+  ret = NULL;
+
+  if (type == PURPLE_LOG_CHAT)
+    me = g_strdup_printf ("%s.chat", purple_normalize(account, 
+      purple_account_get_username(account)));
+  else
+    me = g_strdup (purple_normalize(account, 
+      purple_account_get_username(account)));
+
+  /* Get the old logger names */
+  path = g_build_filename(purple_user_dir(), "logs", NULL);
+  if (!(dir = g_dir_open(path, 0, NULL))) {
+    g_free(path);
+    return ret;
+  }
+
+  while ((filename = g_dir_read_name (dir))) {
+    if (purple_str_has_suffix (filename, ".log")) {
+      tmp = strdup (filename);
+      *(tmp + strlen (filename) - 4) = '\0';
+      if (!string_list_find (ret, tmp))
+        ret = g_list_prepend (ret, strdup (tmp));
+      free (tmp);
+    }
+  }
+
+  g_dir_close (dir);
+  g_free (path);
+
+  /* Get the account-specific names */
+  prpl = PURPLE_PLUGIN_PROTOCOL_INFO
+    (purple_find_prpl (purple_account_get_protocol_id(account)))->list_icon(
+      account, NULL);
+
+  path = g_build_filename(purple_user_dir(), "logs", prpl, me, NULL);
+  g_free (me);
+
+  if (!(dir = g_dir_open(path, 0, NULL))) {
+    g_free(path);
+    return ret;
+  }
+
+  while ((filename = g_dir_read_name (dir))) {
+    if (!string_list_find (ret, filename))
+      ret = g_list_prepend (ret, strdup (filename));
+  }
+
+  g_dir_close (dir);
+  g_free (path);
+
+  return ret;
+}
+
+/* On load, reads in all logs and initializes stats database */
+static void logstats_read_logs ()
+{
+  GList *accounts, *logs, *logs_start, *names, *names_start;
+  PurpleLog *cur_log;
+
+  accounts = purple_accounts_get_all();
+
+  ap_debug ("logstats", "parsing log files");
+
+  while (accounts) {
+    names_start = names = logstats_get_names (PURPLE_LOG_IM, 
+      (PurpleAccount *)accounts->data);
+
+    while (names) {
+      logs_start = purple_log_get_logs (PURPLE_LOG_IM, (char *)names->data,
+        (PurpleAccount *)accounts->data);
+      logs = logs_start;
+
+      while (logs) {
+        cur_log = (PurpleLog *)logs->data;
+        parse_log (cur_log);
+        purple_log_free (cur_log);
+        logs = logs->next;
+      }
+
+      g_list_free (logs_start);
+      names = names->next;
+    }
+
+    free_string_list (names_start);
+    accounts = accounts->next;
+  }
+ 
+  /* Cleanup */
+
+  ap_debug ("logstats", "finished parsing log files");
+}
+
+/* Implements GHFunc */
+static void add_element (gpointer key, gpointer value, gpointer data)
+{
+  dates = g_slist_insert_sorted (dates, value, log_date_compare);
+}
+
+/* Updates GList against hashtable */
+static void logstats_update_dates ()
+{
+  g_slist_free (dates);
+  dates = NULL;
+  g_hash_table_foreach (dates_table, add_element, NULL);
+}
+
+/*--------------------- Total calculations -------------------*/
+static int get_total (const char *field)
+{
+  GSList *cur_day;
+  int count;
+  struct log_date *d;
+
+  cur_day = dates;
+  count = 0;
+  while (cur_day) {
+    d = (struct log_date *)cur_day->data;
+    if (!strcmp (field, "received_msgs")) {
+      count += d->received_msgs;
+    } else if (!strcmp (field, "received_words")) {
+      count += d->received_words;
+    } else if (!strcmp (field, "sent_msgs")) {
+      count += d->sent_msgs;
+    } else if (!strcmp (field, "sent_words")) {
+      count += d->sent_words;
+    } else if (!strcmp (field, "num_convos")) {
+      count += g_slist_length (d->conversation_times);
+    } 
+
+    cur_day = cur_day->next;
+  }
+
+  return count;
+}
+
+static int get_recent_total (const char *field, int hours)
+{
+  GSList *cur_day;
+  int count;
+  struct log_date *d;
+  time_t cur_day_time;
+
+  cur_day = dates;
+  count = 0;
+
+  while (cur_day) {
+    d = (struct log_date *)cur_day->data;
+    cur_day_time = purple_time_build (d->year + 1900, d->month + 1, d->day, 
+      0, 0, 0);
+    if (difftime (time (NULL), cur_day_time) > (double) hours * 60.0 * 60.0)
+      break;
+
+    if (!strcmp (field, "received_msgs")) {
+      count += d->received_msgs;
+    } else if (!strcmp (field, "sent_msgs")) {
+      count += d->sent_msgs;
+    } else if (!strcmp (field, "num_convos")) {
+      count += g_slist_length (d->conversation_times);
+    } 
+
+    cur_day = cur_day->next;
+  }
+
+  return count;
+}
+
+static int num_days_since_start ()
+{
+  GSList *first_day;
+  double difference;
+  struct log_date *d;
+
+  first_day = g_slist_last (dates);
+
+  if (!first_day)
+    return 0;
+
+  d = (struct log_date *)first_day->data;
+
+  difference = difftime (
+    time (NULL), purple_time_build (d->year + 1900, d->month + 1, d->day, 
+      0, 0, 0));
+
+  return (int) difference / (60.0 * 60.0 * 24.0); 
+}
+
+static struct log_date *get_max_date (const char *field)
+{
+  struct log_date *max_date, *cur_date;
+  int max_so_far, cur_max;
+  GSList *cur_day;
+
+  max_so_far = 0;
+  max_date = NULL;
+  cur_day = dates;
+
+  while (cur_day) {
+    cur_date = (struct log_date *)cur_day->data;
+    if (!strcmp (field, "conversations")) {
+      cur_max = g_slist_length (cur_date->conversation_times);
+    } else if (!strcmp (field, "received")) {
+      cur_max = cur_date->received_msgs;
+    } else if (!strcmp (field, "sent")) {
+      cur_max = cur_date->sent_msgs;
+    } else if (!strcmp (field, "total")) {
+      cur_max = cur_date->sent_msgs + cur_date->received_msgs;
+    } else {
+      cur_max = 0;
+    }
+
+    if (cur_max >= max_so_far) {
+      max_date = cur_date;
+      max_so_far = cur_max;
+    }
+
+    cur_day = cur_day->next;
+  }
+
+  return max_date;
+}
+
+static char *date_string (const char *field)
+{
+  struct log_date *d;
+  char *output;
+  struct tm *t_struct;
+  time_t t;
+  GSList *last_day;
+
+  last_day = g_slist_last (dates);
+
+  if (!last_day)
+    return NULL;
+
+  if (!strcmp (field, "first")) {
+    d = (struct log_date *) last_day->data;
+  } else {
+    d = get_max_date (field);
+  }
+
+  if (!d)
+    return NULL;
+
+  output = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM);
+  t_struct = (struct tm *)malloc(sizeof(struct tm));
+  t_struct->tm_year = d->year;
+  t_struct->tm_mon = d->month;
+  t_struct->tm_mday = d->day;
+  t_struct->tm_sec = 0;
+  t_struct->tm_min = 0;
+  t_struct->tm_hour = 0;
+  t = mktime (t_struct);
+  free (t_struct);
+  t_struct = localtime (&t);
+
+  strftime (output, AP_SIZE_MAXIMUM - 1, "%a %b %d, %Y", t_struct);
+  return output; 
+}
+
+static int get_max (const char *field)
+{
+  struct log_date *max_date = get_max_date (field);
+
+  if (!max_date)
+    return 0;
+  
+  if (!strcmp (field, "conversations")) {
+    return g_slist_length (max_date->conversation_times);
+  } else if (!strcmp (field, "received")) {
+    return max_date->received_msgs;
+  } else if (!strcmp (field, "sent")) {
+    return max_date->sent_msgs;
+  } else if (!strcmp (field, "total")) {
+    return max_date->sent_msgs + max_date->received_msgs;
+  } else {
+    ap_debug ("logstats", "get-max: invalid paramater");
+    return 0;
+  }
+
+}
+
+
+/*--------------------- Signal handlers ----------------------*/
+static void logstats_received_im (PurpleAccount *account, char *sender,
+                                  char *message, int flags)
+{
+  struct log_date *the_date; 
+
+  the_date = get_today ();
+  the_date->received_msgs++;
+  the_date->received_words += string_word_count (message);
+
+  receiving = TRUE;
+}
+
+static void logstats_sent_im (PurpleAccount *account, const char *receiver,
+                              const char *message)
+{
+  struct log_date *the_date;
+
+  the_date = get_today ();  
+  the_date->sent_msgs++;
+  the_date->sent_words += string_word_count (message);
+
+  receiving = FALSE;
+}
+
+static void logstats_conv_created (PurpleConversation *conv)
+{
+  struct log_date *the_date;
+  struct conversation_time *the_time;
+  
+  if (conv->type == PURPLE_CONV_TYPE_IM) {
+    the_time = malloc (sizeof(struct conversation_time));
+    the_time->name = strdup (conv->name);
+    the_time->start_time = malloc (sizeof(time_t));
+    time (the_time->start_time);
+
+    the_date = get_today ();
+    the_date->conversation_times = g_slist_prepend (
+      the_date->conversation_times, the_time);
+
+    logstats_update_dates ();
+  }
+}
+
+/*--------------------------- Main functions -------------------------*/
+
+/* Component load */
+void logstats_load ()
+{
+  int count;
+  char *msg;
+
+  if (!purple_prefs_get_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled")) {
+    return;
+  }
+
+  /* Initialize database */
+  dates_table = g_hash_table_new (log_date_hash, log_date_equal);
+  logstats_read_logs ();
+  logstats_update_dates ();
+
+  /* Debug */
+  msg = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM);
+  count = get_total ("received_msgs");
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "received msg total is %d", count);
+  ap_debug ("logstats", msg);
+  count = get_total ("sent_msgs");
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "sent msg total is %d", count);
+  ap_debug ("logstats", msg);
+  count = get_total ("received_words");
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "received word total is %d", count);
+  ap_debug ("logstats", msg);
+  count = get_total ("sent_words");
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "sent word total is %d", count);
+  ap_debug ("logstats", msg);
+  count = get_total ("num_convos");
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "num conversations is %d", count);
+  ap_debug ("logstats", msg);
+  count = g_slist_length (dates);
+  g_snprintf (msg, AP_SIZE_MAXIMUM, "num days with conversations is %d", count);
+  ap_debug ("logstats", msg);
+
+  free(msg);
+
+  /* Connect signals */
+  purple_signal_connect (purple_conversations_get_handle (),
+                       "received-im-msg", ap_get_plugin_handle (),
+                       PURPLE_CALLBACK (logstats_received_im), NULL);
+  purple_signal_connect (purple_conversations_get_handle (),
+                       "sent-im-msg", ap_get_plugin_handle (),
+                       PURPLE_CALLBACK (logstats_sent_im), NULL);
+  purple_signal_connect (purple_conversations_get_handle (),
+                       "conversation-created", ap_get_plugin_handle (),
+                       PURPLE_CALLBACK (logstats_conv_created), NULL);
+}
+
+/* Component unload */
+void logstats_unload ()
+{
+  struct log_date *cur_date;
+  struct conversation_time *cur_time;
+  GSList *temp;
+
+  if (!purple_prefs_get_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled")) {
+    return;
+  }
+
+  /* Disconnect signals */
+  purple_signal_disconnect (purple_conversations_get_handle (),
+                          "received-im-msg", ap_get_plugin_handle (),
+                          PURPLE_CALLBACK (logstats_received_im));
+  purple_signal_disconnect (purple_conversations_get_handle (),
+                          "sent-im-msg", ap_get_plugin_handle (),
+                          PURPLE_CALLBACK (logstats_sent_im));
+  purple_signal_disconnect (purple_conversations_get_handle (),
+                          "conversation-created", ap_get_plugin_handle (),
+                          PURPLE_CALLBACK (logstats_conv_created));
+
+  logstats_update_dates ();
+
+  /* Free all the memory */
+  while (dates) {
+    cur_date = (struct log_date *)dates->data;
+    while (cur_date->conversation_times) {
+      temp = cur_date->conversation_times;
+      cur_time = (struct conversation_time *)temp->data;
+      cur_date->conversation_times = temp->next;
+      free (cur_time->start_time);
+      free (cur_time->name);
+      free (cur_time);
+      g_slist_free_1 (temp);
+    }
+    free (cur_date);
+    temp = dates;
+    dates = dates->next; 
+    g_slist_free_1 (temp);
+  }
+
+  if (cur_receiver) {
+    free (cur_receiver);
+    cur_receiver = NULL;
+  }
+  if (cur_sender) {
+    free (cur_sender);
+    cur_sender = NULL;
+  }
+  g_hash_table_destroy (dates_table);
+  dates_table = NULL;
+}
+
+/* Generate the output */
+static char *logstats_generate ()
+{ 
+  char *buf, *output, *date;
+  int state;
+  const char *format;
+
+  if (!purple_prefs_get_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled")) {
+    return NULL;
+  }
+
+  format = purple_prefs_get_string (
+    "/plugins/gtk/autoprofile/components/logstat/format");
+
+  output = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
+  *output = '\0';
+  buf = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
+  *buf = '\0';
+
+  state = 0;
+
+  while (*format) {
+    if (state == 1) {
+      switch (*format) {
+        case '%':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
+          break;
+        case 'R':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_msgs"));
+          break;
+        case 'r':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_words"));
+          break;
+        case 'S':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_msgs"));
+          break;
+        case 's':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_words"));
+          break;
+        case 'T':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, 
+            get_total ("sent_msgs") + get_total ("received_msgs"));
+          break;
+        case 't':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, 
+            get_total ("sent_words") + get_total ("received_words"));
+          break;
+        case 'D':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
+            num_days_since_start ());
+          break;
+        case 'd':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, g_slist_length (dates));
+          break;
+        case 'N':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("num_convos"));
+          break;
+        case 'n':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
+            (double) get_total ("num_convos") / (double) g_slist_length (dates));
+          break;
+        case 'i':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("conversations"));
+          break;
+        case 'I':
+          date = date_string ("conversations");
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
+          free (date);
+          break;
+        case 'j':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("sent"));
+          break;
+        case 'J':
+          date = date_string ("sent");
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
+          free (date);
+          break;
+        case 'k':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("received"));
+          break;
+        case 'K':
+          date = date_string ("received");
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
+          free (date);
+          break;
+        case 'l':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("total"));
+          break;
+        case 'L':
+          date = date_string ("total");
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
+          free (date);
+          break;
+        case 'f':
+          date = date_string ("first");
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
+          free (date);
+          break;
+        case 'u':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("received_words") / (double) get_total ("received_msgs"));
+          break;
+        case 'v':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("sent_words") / (double) get_total ("sent_msgs"));
+          break;
+        case 'w':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) (get_total ("received_words") + get_total ("sent_words")) / (double) (get_total("received_msgs") + get_total ("sent_msgs")));
+          break;
+        case 'U':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("received_msgs") / (double) get_total ("num_convos"));
+          break;
+        case 'V':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("sent_msgs") / (double) get_total ("num_convos"));
+          break;
+        case 'W':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) get_total("num_convos"));
+          break;
+        case 'x':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("received_words") / (double) g_slist_length (dates));
+          break;
+        case 'y':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("sent_words") / (double) g_slist_length (dates));
+          break;
+        case 'z':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            ((double) get_total ("received_words") + (double) get_total ("sent_words")) / (double) g_slist_length (dates));
+          break;
+        case 'X':
+         g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("received_msgs") / (double) g_slist_length (dates));
+          break;
+        case 'Y':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) get_total ("sent_msgs") / (double) g_slist_length (dates));
+          break;
+        case 'Z':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output, 
+            (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) g_slist_length (dates));
+          break;
+        case 'p':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.1f", output,
+            100.0 * (double) g_slist_length (dates) / (double) num_days_since_start ());
+          break;
+        case 'a':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
+            ((struct log_date *) dates->data)->received_msgs);
+          break;
+        case 'b':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
+            ((struct log_date *) dates->data)->sent_msgs);
+          break;
+        case 'c':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
+            g_slist_length (((struct log_date *) dates->data)->conversation_times));
+          break;
+        case 'e':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
+            ((struct log_date *) dates->data)->sent_msgs + ((struct log_date *) dates->data)->received_msgs);
+          break;
+        case 'A':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("received_msgs", 24 * 7));
+          break;
+        case 'B':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("sent_msgs", 24 * 7));
+          break;
+        case 'C':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("num_convos", 24 * 7));
+          break;
+        case 'E':
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, 
+            get_recent_total ("received_msgs", 24 * 7) + get_recent_total ("received_msgs", 24 * 7));
+          break;
+        default:
+          g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
+          break;
+      }
+
+      strcpy (output, buf);
+      format++;
+      state = 0;
+    } else {
+      if (*format == '%') {
+        state = 1;
+      } else {
+        g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
+        strcpy (output, buf);
+      }
+      format++;
+    }
+
+  }
+
+  free (buf);
+  return output;
+}
+
+/* Initialize preferences */
+static void logstats_init ()
+{
+  purple_prefs_add_none ("/plugins/gtk/autoprofile/components/logstat");
+  purple_prefs_add_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE);
+  purple_prefs_add_string (
+    "/plugins/gtk/autoprofile/components/logstat/format", "");
+  purple_prefs_add_string_list (
+    "/plugins/gtk/autoprofile/components/logstat/aliases", NULL);
+}
+
+/* The heart of the component */
+static char *identifiers [7] = { 
+  N_("logs"), 
+  N_("log"), 
+  N_("stat"), 
+  N_("stats"), 
+  N_("logstats"),
+  N_("log statistics"),
+  NULL
+};
+
+struct component logstats =
+{
+  N_("Purple log statistics"),
+  N_("Display various statistics about your message and system logs"),
+  identifiers,
+  &logstats_generate,
+  &logstats_init,
+  &logstats_load,
+  &logstats_unload,
+  NULL,
+  &logstats_prefs
+};
+
============================================================
--- autoprofile/comp_logstats.h	50a352d8577e9c03c3cb9aac4680a4cd589ea8f9
+++ autoprofile/comp_logstats.h	50a352d8577e9c03c3cb9aac4680a4cd589ea8f9
@@ -0,0 +1,28 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+void logstats_load (void);
+void logstats_unload (void);
+GtkWidget *logstats_prefs (void);
+
+
============================================================
--- autoprofile/comp_logstats_gtk.c	ac3dbed2a1ad10693f267467eeaf25f2b5ec7147
+++ autoprofile/comp_logstats_gtk.c	ac3dbed2a1ad10693f267467eeaf25f2b5ec7147
@@ -0,0 +1,355 @@
+/*--------------------------------------------------------------------------*
+ * AUTOPROFILE                                                              *
+ *                                                                          *
+ * A Purple away message and profile manager that supports dynamic text       *
+ *                                                                          *
+ * AutoProfile is the legal property of its developers.  Please refer to    *
+ * the COPYRIGHT file distributed with this source distribution.            *
+ *                                                                          *
+ * This program is free software; you can redistribute it and/or modify     *
+ * it under the terms of the GNU General Public License as published by     *
+ * the Free Software Foundation; either version 2 of the License, or        *
+ * (at your option) any later version.                                      *
+ *                                                                          *
+ * This program is distributed in the hope that it will be useful,          *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
+ * GNU General Public License for more details.                             *
+ *                                                                          *
+ * You should have received a copy of the GNU General Public License        *
+ * along with this program; if not, write to the Free Software              *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA *
+ *--------------------------------------------------------------------------*/
+
+#include "autoprofile.h"
+#include "comp_logstats.h"
+#include "request.h"
+
+GtkWidget *checkbox = NULL;
+
+GtkListStore *alias_list = NULL;
+GtkWidget *alias_view = NULL;
+
+/* General callbacks from main preferences */
+static void logstats_response_cb (GtkDialog *dialog, gint id, 
+  GtkWidget *widget) 
+{
+  purple_prefs_set_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled", TRUE);
+  logstats_load ();
+  gtk_widget_set_sensitive (widget, TRUE);
+
+  gtk_widget_destroy (GTK_WIDGET(dialog));
+}
+
+static void toggle_enable (GtkButton *button, gpointer data)
+{
+  GtkWidget *popup, *vbox, *label;
+  vbox = data;
+
+  if (purple_prefs_get_bool (
+    "/plugins/gtk/autoprofile/components/logstat/enabled")) {
+    logstats_unload ();
+    purple_prefs_set_bool (
+      "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE);
+    gtk_widget_set_sensitive (vbox, FALSE);
+  } else {
+    popup = gtk_dialog_new_with_buttons (
+      "Enable stats for logs", NULL, 0,
+      GTK_STOCK_OK, 42, NULL);
+    g_signal_connect (G_OBJECT(popup), "response",
+      G_CALLBACK(logstats_response_cb), vbox);
+
+    label = gtk_label_new(NULL);
+    gtk_label_set_markup(GTK_LABEL(label), 
+      "\nEnabling this component will have some minor side effects.  Doing so "
+      "will cause Purple to take slightly longer to start up because it must "
+      "parse a large amount of data to gather statistics.  On average, this "
+      "can take slightly over a second for every 100,000 messages in your "
+      "logs.\n\nThe time from when you press the OK button to the time "
+      "when this dialog vanishes is a good approximation of how much extra "
+      "time will elapse before the login screen is shown.\n"
+    );
+    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+    gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label, 
+      FALSE, FALSE, 0);
+
+    gtk_widget_show_all (popup);
+  }
+}
+
+static gboolean logstat_format (GtkWidget *widget, GdkEventFocus *event,
+  gpointer data)
+{
+  purple_prefs_set_string (
+    "/plugins/gtk/autoprofile/components/logstat/format",
+    gtk_entry_get_text (GTK_ENTRY (widget)));
+  return FALSE;
+}
+
+static void new_alias (gpointer data, PurpleRequestFields *fields)
+{
+  GtkTreeIter iter;
+  GList *aliases;
+
+  const char *alias;
+
+  alias = purple_request_fields_get_string (fields, "alias");
+  aliases = purple_prefs_get_string_list (
+    "/plugins/gtk/autoprofile/components/logstat/aliases");
+
+  aliases = g_list_append (aliases, strdup (alias));
+  purple_prefs_set_string_list (
+    "/plugins/gtk/autoprofile/components/logstat/aliases", aliases);
+  free_st