changeset 6333:e06e04e44914

[gaim-migrate @ 6832] (20:48:32) Robot101: new IRC plugin y'all (20:48:51) Paco-Paco: The IRC Protocol Plugin that Sucks Less (TM) (20:49:18) Paco-Paco: I think that's what the prpl description field says (20:50:09) LSchiere2: :-) committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Wed, 30 Jul 2003 00:50:29 +0000
parents 392f771bbeb3
children 930708df05dd
files ChangeLog src/protocols/irc/.cvsignore src/protocols/irc/.todo src/protocols/irc/Makefile.am src/protocols/irc/cmds.c src/protocols/irc/irc.c src/protocols/irc/irc.h src/protocols/irc/msgs.c src/protocols/irc/parse.c
diffstat 9 files changed, 2285 insertions(+), 2838 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Jul 29 13:45:49 2003 +0000
+++ b/ChangeLog	Wed Jul 30 00:50:29 2003 +0000
@@ -7,6 +7,7 @@
 	  Voigt, Paul A (darkrain))
 	* Shows "hiptop" icon for AIM buddies using hiptop
 	  devices (Robey Pointer)
+	* New IRC PRPL (Ethan Blanton)
 	* British English "translation" updated (Sam Halliday)
 	* Chinese (Simplified) translation updated (Funda Wang)
 	* Chinese (Traditional) translation updated (Ambrose C. Li)
--- a/src/protocols/irc/.cvsignore	Tue Jul 29 13:45:49 2003 +0000
+++ b/src/protocols/irc/.cvsignore	Wed Jul 30 00:50:29 2003 +0000
@@ -2,7 +2,7 @@
 Makefile
 .deps
 .libs
-irc.lo
+*.lo
 libirc.la
 *.def
 *.lib
--- a/src/protocols/irc/.todo	Tue Jul 29 13:45:49 2003 +0000
+++ b/src/protocols/irc/.todo	Wed Jul 30 00:50:29 2003 +0000
@@ -1,41 +1,60 @@
+<!-- Automagically generated by the ToDo program on Sat Jul 26 19:03:30 2003 -->
 <todo version="0.1.17">
     <note priority="medium" time="1036040668">
         IRC
-        <note priority="high" time="1036041633">
-            option to change nick does not always work right: because gaim continues to compare on the original nick
+        <note priority="high" time="1036041722" done="1058917560">
+            /remove
+        </note>
+        <note priority="high" time="1036041787" done="1058754795">
+            no list of people on /join
+            <comment>
+                This is a convo issue, not an IRC issue
+            </comment>
+        </note>
+        <note priority="medium" time="1036041658" done="1058918372">
+            Error 421, unknown command when going away. sometimes. (has to do with html in away messages?)
+            <comment>
+                I think this was caused by embedded newlines -- fixed, if that's the case.
+            </comment>
         </note>
-        <note priority="high" time="1036041714">
-            fix segfault on attempted send after having been /remove'ed (has this been fixed?)
+        <note priority="medium" time="1036041674" done="1058921563">
+            /names
         </note>
-        <note priority="high" time="1036041787">
-            no list of people on /join
+        <note priority="medium" time="1036041695" done="1058754979">
+            CTCP version generates an autoreply when person is away or idle. the error box used by it also binds the focus (like all other error popups). i'm not sure there is a way around this second issue. (fixed?)
+            <comment>
+                This autoreply is generated by the ircd, nothing we can do about it.
+            </comment>
+        </note>
+        <note priority="medium" time="1058755195" done="1058918104">
+            Allow text to be sent on /query
+        </note>
+        <note priority="veryhigh" time="1058905390">
+            ison
         </note>
         <note priority="high" time="1037675268">
             remember /ignore list in privacy section
         </note>
-        <note priority="medium" time="1036041658">
-            Error 421, unknown command when going away. sometimes. (has to do with html in away messages?)
+        <note priority="high" time="1058843325">
+            /ignore
+        </note>
+        <note priority="high" time="1059247696">
+            /help
         </note>
         <note priority="medium" time="1036041669">
             /ban, /kickban
         </note>
-        <note priority="medium" time="1036041674">
-            /names
+        <note priority="medium" time="1059193670">
+            /list
         </note>
-        <note priority="medium" time="1036041695">
-            CTCP version generates an autoreply when person is away or idle. the error box used by it also binds the focus (like all other error popups). i'm not sure there is a way around this second issue. (fixed?)
-        </note>
-        <note priority="medium" time="1036041722">
-            multi word remove messages
+        <note priority="medium" time="1059198036">
+            error message on unknown commands (don't just pass to ircd)
         </note>
-        <note priority="medium" time="1036041800">
-            display target of +b and +q (done)?
+        <note priority="medium" time="1059264210">
+            document, document, document
         </note>
-        <note priority="medium" time="1040268884">
-            nick change on signon for in use nick might be broken -- might cause no windows to be created on /join
-        </note>
-        <note priority="low" time="1036041731">
-            auto reply to /msg on away
+        <note priority="low" time="1058921630">
+            fix duplication of names on rejoin (depends on convo API)
         </note>
     </note>
 </todo>
--- a/src/protocols/irc/Makefile.am	Tue Jul 29 13:45:49 2003 +0000
+++ b/src/protocols/irc/Makefile.am	Wed Jul 30 00:50:29 2003 +0000
@@ -1,10 +1,8 @@
-EXTRA_DIST = \
-		PROTOCOL \
-		Makefile.mingw
+EXTRA_DIST = PROTOCOL Makefile.mingw
 
 pkgdir = $(libdir)/gaim
 
-IRCSOURCES = irc.c
+IRCSOURCES = irc.c parse.c cmds.c msgs.c irc.h
 
 AM_CFLAGS = $(st)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/irc/cmds.c	Wed Jul 30 00:50:29 2003 +0000
@@ -0,0 +1,506 @@
+/**
+ * @file cmds.c
+ * 
+ * gaim
+ *
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * 
+ * 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 "internal.h"
+
+#include "conversation.h"
+#include "notify.h"
+#include "debug.h"
+#include "irc.h"
+
+
+static void irc_do_mode(struct irc_conn *irc, const char *target, const char *sign, char **ops);
+
+int irc_cmd_default(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	GaimConversation *convo = gaim_find_conversation_with_account(target, irc->account);
+	char *buf;
+
+	if (!convo)
+		return;
+
+	buf = g_strdup_printf(_("Unknown command: %s"), cmd);
+	if (gaim_conversation_get_type(convo) == GAIM_CONV_IM)
+		gaim_im_write(GAIM_IM(convo), "", buf, -1, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	else
+		gaim_chat_write(GAIM_CHAT(convo), "", buf, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	g_free(buf);
+
+	return 1;
+}
+
+int irc_cmd_away(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf, *message, *cur;
+
+	if (args[0] && strcmp(cmd, "back")) {
+		message = strdup(args[0]);
+		for (cur = message; *cur; cur++) {
+			if (*cur == '\n')
+				*cur = ' ';
+		}
+		buf = irc_format(irc, "v:", "AWAY", message);
+		g_free(message);
+	} else {
+		buf = irc_format(irc, "v", "AWAY");
+	}
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_ctcp_action(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *action, *dst, **newargs;
+	const char *src;
+	GaimConversation *convo;
+
+	if (!args || !args[0] || !gc)
+		return 0;
+
+	action = g_malloc(strlen(args[0]) + 9);
+
+	sprintf(action, "\001ACTION ");
+
+	src = args[0];
+	dst = action + 8;
+	while (*src) {
+		if (*src == '\n') {
+			if (*(src + 1) == '\0') {
+				break;
+			} else {
+				*dst++ = ' ';
+				src++;
+				continue;
+			}
+		}
+		*dst++ = *src++;
+	}
+	*dst++ = '\001';
+	*dst = '\0';
+
+	newargs = g_new0(char *, 2);
+	newargs[0] = g_strdup(target);
+	newargs[1] = action;
+	irc_cmd_privmsg(irc, cmd, target, (const char **)newargs);
+	g_free(newargs[0]);
+	g_free(newargs[1]);
+	g_free(newargs);
+
+	convo = gaim_find_conversation_with_account(target, irc->account);
+	if (convo && gaim_conversation_get_type(convo) == GAIM_CONV_CHAT) {
+		action = g_strdup_printf("/me %s", args[0]);
+		if (action[strlen(action) - 1] == '\n')
+			action[strlen(action) - 1] = '\0';
+		serv_got_chat_in(gc, gaim_chat_get_id(GAIM_CHAT(convo)),
+				 gaim_connection_get_display_name(gc),
+				 0, action, time(NULL));
+		g_free(action);
+	}
+
+	return 1;
+}
+
+int irc_cmd_invite(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0] || !(args[1] || target))
+		return 0;
+
+	buf = irc_format(irc, "vnc", "INVITE", args[0], args[1] ? args[1] : target);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_join(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return 0;
+
+	if (args[1])
+		buf = irc_format(irc, "vcv", "JOIN", args[0], args[1]);
+	else
+		buf = irc_format(irc, "vc", "JOIN", args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_kick(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+	GaimConversation *convo;
+
+	if (!args || !args[0])
+		return 0;
+
+	convo = gaim_find_conversation_with_account(target, irc->account);
+	if (!convo || gaim_conversation_get_type(convo) != GAIM_CONV_CHAT)
+		return;
+
+	if (args[1])
+		buf = irc_format(irc, "vcn:", "KICK", target, args[0], args[1]);
+	else
+		buf = irc_format(irc, "vcn", "KICK", target, args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_mode(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	GaimConnection *gc;
+	char *buf;
+
+	if (!args)
+		return 0;
+
+	if (!strcmp(cmd, "mode")) {
+		if (!args[0] && (*target == '#' || *target == '&'))
+			buf = irc_format(irc, "vc", "MODE", target);
+		else if (args[0] && (*args[0] == '+' || *args[0] == '-'))
+			buf = irc_format(irc, "vcv", "MODE", target, args[0]);
+		else if (args[0])
+			buf = irc_format(irc, "vv", "MODE", args[0]);
+		else
+			return;
+	} else if (!strcmp(cmd, "umode")) {
+		if (!args[0])
+			return;
+		gc = gaim_account_get_connection(irc->account);
+		buf = irc_format(irc, "vnv", "MODE", gaim_connection_get_display_name(gc), args[0]);
+	}
+	irc_send(irc, buf);
+	g_free(buf);
+		
+
+	return 0;
+}
+
+int irc_cmd_names(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args)
+		return 0;
+
+	buf = irc_format(irc, "vc", "NAMES", args[0] ? args[0] : target);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	irc->nameconv = g_strdup(target);
+
+	return 0;
+}
+
+int irc_cmd_nick(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return 0;
+
+	buf = irc_format(irc, "v:", "NICK", args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_op(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char **nicks, **ops, *sign, *mode;
+	int i = 0, used = 0;
+
+	if (!args || !args[0] || !*args[0])
+		return 0;
+
+	if (!strcmp(cmd, "op")) {
+		sign = "+";
+		mode = "o";
+	} else if (!strcmp(cmd, "deop")) {
+		sign = "-";
+		mode = "o";
+	} else if (!strcmp(cmd, "voice")) {
+		sign = "+";
+		mode = "v";
+	} else if (!strcmp(cmd, "devoice")) {
+		sign = "-";
+		mode = "v";
+	} else {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid 'op' command '%s'\n", cmd);
+		return 0;
+	}
+
+	nicks = g_strsplit(args[0], " ", -1);
+
+	for (i = 0; nicks[i]; i++)
+		/* nothing */;
+	ops = g_new0(char *, i * 2 + 1);
+
+	for (i = 0; nicks[i]; i++) {
+		if (!*nicks[i])
+			continue;
+		ops[used++] = mode;
+		ops[used++] = nicks[i];
+	}
+
+	irc_do_mode(irc, target, sign, ops);
+	g_free(ops);
+
+	return;
+}
+
+int irc_cmd_part(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args)
+		return 0;
+
+	if (args[1])
+		buf = irc_format(irc, "vc:", "PART", args[0] ? args[0] : target, args[1]);
+	else
+		buf = irc_format(irc, "vc", "PART", args[0] ? args[0] : target);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_ping(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *stamp;
+	char *buf;
+
+	if (args && args[0]) {
+		if (*args[0] == '#' || *args[0] == '&')
+			return 0;
+		stamp = g_strdup_printf("\001PING %lu\001", time(NULL));
+		buf = irc_format(irc, "vn:", "PRIVMSG", args[0], stamp);
+		g_free(stamp);
+	} else {
+		stamp = g_strdup_printf("%s %d", target, time(NULL));
+		buf = irc_format(irc, "v:", "PING", stamp);
+		g_free(stamp);
+	}
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_privmsg(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	const char *cur, *end;
+	char *msg, *buf;
+
+	if (!args || !args[0] || !args[1])
+		return 0;
+
+	cur = args[1];
+	end = args[1];
+	while (*end && *cur) {
+		end = strchr(cur, '\n');
+		if (!end)
+			end = cur + strlen(cur);
+		msg = g_strndup(cur, end - cur);
+		buf = irc_format(irc, "vt:", "PRIVMSG", args[0], msg);
+		irc_send(irc, buf);
+		g_free(msg);
+		g_free(buf);
+		cur = end + 1;
+	}
+
+	return 0;
+}
+
+int irc_cmd_quit(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	buf = irc_format(irc, "v:", "QUIT", (args && args[0]) ? args[0] : "Download Gaim: " WEBSITE);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_quote(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return 0;
+
+	buf = irc_format(irc, "v", args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_query(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	GaimConversation *convo;
+	GaimConnection *gc;
+
+	if (!args || !args[0])
+		return 0;
+
+	convo = gaim_conversation_new(GAIM_CONV_IM, irc->account, args[0]);
+
+	if (args[1]) {
+		gc = gaim_account_get_connection(irc->account);
+		irc_cmd_privmsg(irc, cmd, target, args);
+		gaim_im_write(GAIM_IM(convo), gaim_connection_get_display_name(gc),
+			      args[1], -1, WFLAG_SEND, time(NULL));
+	}
+
+	return 0;
+}
+
+int irc_cmd_remove(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return 0;
+
+	if (*target != '#' && *target != '&') /* not a channel, punt */
+		return 0;
+
+	if (args[1])
+		buf = irc_format(irc, "vcn:", "REMOVE", target, args[0], args[1]);
+	else
+		buf = irc_format(irc, "vcn", "REMOVE", target, args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_topic(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+	const char *topic;
+	GaimConversation *convo;
+
+	if (!args)
+		return 0;
+
+	convo = gaim_find_conversation_with_account(target, irc->account);
+	if (!convo || gaim_conversation_get_type(convo) != GAIM_CONV_CHAT)
+		return;
+
+	if (!args[0]) {
+		topic = gaim_chat_get_topic (GAIM_CHAT(convo));
+
+		if (topic)
+			buf = g_strdup_printf("current topic is: %s", topic);
+		else
+			buf = g_strdup(_("No topic is set"));
+		gaim_chat_write(GAIM_CHAT(convo), target, buf, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+		g_free(buf);
+
+		return 0;
+	}
+
+	buf = irc_format(irc, "vt:", "TOPIC", target, args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_wallops(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return;
+
+	if (!strcmp(cmd, "wallops"))
+		buf = irc_format(irc, "v:", "WALLOPS", args[0]);
+	else if (!strcmp(cmd, "operwall"))
+		buf = irc_format(irc, "v:", "OPERWALL", args[0]);
+
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return 0;
+}
+
+int irc_cmd_whois(struct irc_conn *irc, const char *cmd, const char *target, const char **args)
+{
+	char *buf;
+
+	if (!args || !args[0])
+		return 0;
+
+	buf = irc_format(irc, "vn", "WHOIS", args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+	irc->whois.nick = g_strdup(args[0]);
+
+	return 0;
+}
+
+static void irc_do_mode(struct irc_conn *irc, const char *target, const char *sign, char **ops)
+{
+	char *buf, mode[5];
+	int i = 0;
+
+	if (!sign)
+		return;
+
+	while (ops[i]) {
+		if (ops[i + 2] && ops[i + 4]) {
+			g_snprintf(mode, sizeof(mode), "%s%s%s%s", sign,
+				   ops[i], ops[i + 2], ops[i + 4]);
+			buf = irc_format(irc, "vcvnnn", "MODE", target, mode,
+					 ops[i + 1], ops[i + 3], ops[i + 5]);
+			i += 6;
+		} else if (ops[i + 2]) {
+			g_snprintf(mode, sizeof(mode), "%s%s%s",
+				   sign, ops[i], ops[i + 2]);
+			buf = irc_format(irc, "vcvnn", "MODE", target, mode,
+					 ops[i + 1], ops[i + 3]);
+			i += 4;
+		} else {
+			g_snprintf(mode, sizeof(mode), "%s%s", sign, ops[i]);
+			buf = irc_format(irc, "vcvn", "MODE", target, mode, ops[i + 1]);
+			i += 2;
+		}
+		irc_send(irc, buf);
+		g_free(buf);
+	}
+}
--- a/src/protocols/irc/irc.c	Tue Jul 29 13:45:49 2003 +0000
+++ b/src/protocols/irc/irc.c	Wed Jul 30 00:50:29 2003 +0000
@@ -1,11 +1,11 @@
-/*
- * gaim - IRC Protocol Plugin
+/**
+ * @file irc.c
+ * 
+ * gaim
  *
- * Copyright (C) 2000-2001, Rob Flynn <rob@tgflinux.com>
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
  * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
- *
- * A large portion of this was copied more or less directly from X-Chat,
- * the world's most rocking IRC client. http://www.xchat.org/
  * 
  * 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
@@ -20,2452 +20,101 @@
  * 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 "internal.h"
 
-#include "account.h"
+#include "plugin.h"
 #include "accountopt.h"
-#include "conversation.h"
-#include "core.h"
-#include "debug.h"
-#include "ft.h"
 #include "multi.h"
-#include "notify.h"
-#include "proxy.h"
 #include "prpl.h"
-#include "request.h"
-#include "server.h"
-#include "util.h"
-
-/* XXX for g_show_info_text(), WEBSITE, etc. */
-#include "gaim.h"
-
-#define IRC_BUF_LEN 4096
-#define PDIWORDS 32
-
-#define DEFAULT_SERVER "irc.freenode.net"
-
-static GaimPlugin *my_protocol = NULL;
-
-#ifndef INET6_ADDRSTRLEN
-#define INET6_ADDRSTRLEN 46
-#endif
+#include "conversation.h"
+#include "debug.h"
+#include "blist.h"
+#include "irc.h"
 
-/* Datastructs */
-struct dcc_chat
-{
-	GaimConnection *gc;	
-	char ip_address[INET6_ADDRSTRLEN];
-	int port;		
-	int fd;			
-	int inpa;		
-	char nick[80];		
-};
-
-struct irc_xfer_data
-{
-	char *ip;
-	int port;
-
-	struct irc_data *idata;
-};
-
-struct irc_data {
-	int fd;
-	gboolean online;
-	guint32 timer;
-
-	char *server;
-
-	char *rxqueue;
-	int rxlen;
-
-	GString *str;
-	int bc;
+static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string);
 
-	char *chantypes;
-	char *chanmodes;
-	char *nickmodes;
-	gboolean six_modes;
-
-	gboolean in_whois;
-	gboolean in_list;
-	GString *liststr;
-	GSList *file_transfers;
-};
-
-/* Prototypes */
-static void irc_start_chat(GaimConnection *gc, const char *who);
-static void irc_ctcp_clientinfo(GaimConnection *gc, const char *who);
-static void irc_ctcp_userinfo(GaimConnection *gc, const char *who);
-static void irc_ctcp_version(GaimConnection *gc, const char *who);
-static void irc_ctcp_ping(GaimConnection *gc, const char *who);
-
-static void irc_send_privmsg(GaimConnection *gc, const char *who, const char *what, gboolean fragment);
-static void irc_send_notice(GaimConnection *gc, char *who, char *what);
-
-static char *irc_send_convert(GaimConnection *gc, const char *string, int maxlen, int *done);
-static char *irc_recv_convert(GaimConnection *gc, char *string);
-static void irc_parse_notice(GaimConnection *gc, char *nick, char *ex,
-                            char *word[], char *word_eol[]);
-static void irc_parse_join(GaimConnection *gc, char *nick,
-                          char *word[], char *word_eol[]);
-static gboolean irc_parse_part(GaimConnection *gc, char *nick, char *cmd,
-                          char *word[], char *word_eol[]);
-static void irc_parse_topic(GaimConnection *gc, char *nick,
-                           char *word[], char *word_eol[]);
-
-static void dcc_chat_cancel(struct dcc_chat *);
+static const char *irc_blist_icon(GaimAccount *a, struct buddy *b);
+static void irc_blist_emblems(struct buddy *b, char **se, char **sw, char **nw, char **ne);
+static GList *irc_away_states(GaimConnection *gc);
+/* static GList *irc_chat_info(GaimConnection *gc); */
+static void irc_login(GaimAccount *account);
+static void irc_login_cb(gpointer data, gint source, GaimInputCondition cond);
+static void irc_close(GaimConnection *gc);
+static int irc_im_send(GaimConnection *gc, const char *who, const char *what, int len, int flags);
+static int irc_chat_send(GaimConnection *gc, int id, const char *what);
+static void irc_chat_join (GaimConnection *gc, GHashTable *data);
+static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond);
 
-/* Global variables */
-GSList *dcc_chat_list = NULL;
+static guint irc_nick_hash(const char *nick);
+static gboolean irc_nick_equal(const char *nick1, const char *nick2);
+static void irc_buddy_free(struct irc_buddy *ib);
 
-struct dcc_chat *
-find_dcc_chat (GaimConnection *gc, const char *nick)
-{
-	GSList *tmp;
-	struct dcc_chat *data;
-	tmp = dcc_chat_list;
-	while (tmp != NULL)
-		{
-			data = (struct dcc_chat *) (tmp)->data;
-			if (data 
-			    && data->nick 
-			    && strcmp (nick, data->nick) == 0
-			    && gc == data->gc)
-				{
-					return data;
-				}
-			tmp = tmp->next;
-		}
-	return NULL;
-}
+static GaimPlugin *_irc_plugin = NULL;
 
-static int 
-irc_write(int fd, char *data, int len)
-{
-	gaim_debug(GAIM_DEBUG_MISC, "irc", "C: %s", data);
-	return write(fd, data, len);
-}
-
-static char *
-irc_send_convert(GaimConnection *gc, const char *string, int maxlen, int *done)
+int irc_send(struct irc_conn *irc, const char *buf)
 {
-	char *converted = g_malloc(maxlen + 1);
-	gchar *inptr = (gchar*)string, *outptr = converted;
-	int inleft = strlen(string), outleft = maxlen;
-	GIConv conv;
-	
-	/* XXX - I think the below line is leaking */
-	conv = g_iconv_open(gaim_account_get_string(gaim_connection_get_account(gc), "charset", "UTF-8") , "UTF-8");
-	if (g_iconv(conv, &inptr, &inleft, &outptr, &outleft) == -1) {
-		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Charset conversion error\n");
-		gaim_debug(GAIM_DEBUG_ERROR, "irc",
-				   "Sending as UTF-8 (this is a hack!)\n");
-		g_free(converted);
-		*done = maxlen;
-		return(g_strndup(string, maxlen));
-	}
-	
-	*done = strlen(string) - inleft;
-	*outptr = '\0';
-	return(converted);
-}
-
-static char *
-irc_recv_convert(GaimConnection *gc, char *string)
-{
-	char *utf8;
-	GError *err = NULL;
-	
-	utf8 = g_convert(string, strlen(string), "UTF-8",
-			 gaim_account_get_string(gaim_connection_get_account(gc), "charset", "UTF-8") , NULL, NULL, &err);
-	if (err) {
-		gaim_debug(GAIM_DEBUG_ERROR, "irc",
-				   "recv conversion error: %s\n", err->message);
-		utf8 = g_strdup(_("(There was an error converting this message.  Check the 'Encoding' option in the Account Editor)"));
-	}
-	
-	return (utf8);
-}
-
-static GaimConversation *
-irc_find_chat(GaimConnection *gc, const char *name)
-{
-	GSList *bcs = gc->buddy_chats;
-
-	while (bcs) {
-		GaimConversation *b = bcs->data;
-		if (!gaim_utf8_strcasecmp(b->name, name))
-			return b;
-		bcs = bcs->next;
-	}
-	return NULL;
-}
-
-static void 
-process_data_init(char *buf, char *cmd, char *word[], char *eol[], gboolean quotes)
-{
-	int wordcount = 2;
-	gboolean space = FALSE;
-	gboolean quote = FALSE;
-	int j = 0;
-
-	word[1] = cmd;
-	eol[1] = buf;
+	if (irc->fd < 0)
+		return -1;
 
-	while (TRUE) {
-		switch (*cmd) {
-		case 0:
-			buf[j] = 0;
-			for (j = wordcount; j < PDIWORDS; j++) {
-				word[j] = "\000\000";
-				eol[j] = "\000\000";
-			}
-			return;
-		case '"':
-			if (!quotes) {
-				space = FALSE;
-				buf[j++] = *cmd;
-				break;
-			}
-			quote = !quote;
-			break;
-		case ' ':
-			if (quote) {
-				space = FALSE;
-				buf[j++] = *cmd;
-				break;
-			}
-			if (space)
-				break;
-			buf[j++] = 0;
-			word[wordcount] = &buf[j];
-			eol[wordcount++] = cmd + 1;
-			if (wordcount == PDIWORDS - 1) {
-				buf[j] = 0;
-				return;
-			}
-			space = TRUE;
-			break;
-		default:
-			space = FALSE;
-			buf[j++] = *cmd;
-		}
-		cmd++;
-	}
-}
-
-static void 
-handle_005(GaimConnection *gc, char *word[], char *word_eol[])
-{
-	int w = 4;
-	struct irc_data *id = gc->proto_data;
-
-	while (w < PDIWORDS && *word[w]) {
-		if (!strncmp(word[w], "MODES=", 5)) {
-			if (atoi(word[w] + 6) >= 6)
-				id->six_modes = TRUE;
-		} else if (!strncmp(word[w], "CHANTYPES=", 10)) {
-			g_free(id->chantypes);
-			id->chantypes = g_strdup(word[w] + 10);
-		} else if (!strncmp(word[w], "CHANMODES=", 10)) {
-			g_free(id->chanmodes);
-			id->chanmodes = g_strdup(word[w] + 10);
-		} else if (!strncmp(word[w], "PREFIX=", 7)) {
-			char *pre = strchr(word[w] + 7, ')');
-			if (pre) {
-				*pre = 0;
-				g_free(id->nickmodes);
-				id->nickmodes = g_strdup(word[w] + 8);
-			}
-		}
-		w++;
-	}
+	/* gaim_debug(GAIM_DEBUG_MISC, "irc", "sent: %s", buf); */
+	return write(irc->fd, buf, strlen(buf));
 }
 
-static const char *irc_colors[] = {
-		"#000000", "#ffffff", "#000066", "#006600",
-		"#ff0000", "#660000", "#660066", "#666600",
-		"#cccc00", "#33cc33", "#00acac", "#00ccac",
-		"#0000ff", "#cc00cc", "#666666", "#00ccac"
-};
-
-#define int_to_col(c) (irc_colors[(((c)<0 || (c)> 15)?0:c)])
-
-static GString *
-encode_html(char *msg)
-{
-	GString *str = g_string_new("");
-	char *cur = msg, *end = msg;
-	gboolean bold = FALSE, underline = FALSE, italics = FALSE;
-
-	while ((end = strchr(cur, '<'))) {
-		*end = 0;
-		str = g_string_append(str, cur);
-		cur = ++end;
-		if (!g_ascii_strncasecmp(cur, "B>", 2)) {
-			if (!bold) {
-				bold = TRUE;
-				str = g_string_append_c(str, '\2');
-			}
-			cur = cur + 2;
-		} else if (!g_ascii_strncasecmp(cur, "I>", 2)) { /* use bold for italics too */
-			if (!italics) {
-				italics = TRUE;
-				str = g_string_append_c(str, '\2');
-			}
-			cur = cur + 2;
-		} else if (!g_ascii_strncasecmp(cur, "U>", 2)) {
-			if (!underline) {
-				underline = TRUE;
-				str = g_string_append_c(str, '\37');
-			}
-			cur = cur + 2;
-		}  else if (!g_ascii_strncasecmp(cur, "/B>", 3)) {
-			if (bold) {
-				bold = FALSE;
-				str = g_string_append_c(str, '\2');
-			}
-			cur = cur + 3;
-		}  else if (!g_ascii_strncasecmp(cur, "/I>", 3)) {
-			if (italics) {
-				italics = FALSE;
-				str = g_string_append_c(str, '\2');
-			}
-			cur = cur + 3;
-		}  else if (!g_ascii_strncasecmp(cur, "/U>", 3)) {
-			if (underline) {
-				underline = FALSE;
-				str = g_string_append_c(str, '\37');
-			}
-			cur = cur + 3;
-		}  else {
-			str = g_string_append_c(str, '<');
-		}
-
-	}
-	str = g_string_append(str, cur);
-	return str;
-}
-
-static GString *
-decode_html(char *msg)
+/* XXX I don't like messing directly with these buddies */
+gboolean irc_blist_timeout(struct irc_conn *irc)
 {
-	GString /* oo la la */ *str = g_string_new("");
-	char *cur = msg, *end = msg;
-	gboolean bold = FALSE, underline = FALSE, fg = FALSE, bg = FALSE;
-	int fore, back;
-	while (*end) {
-		switch (*end) {
-		case 02: /* ^B */
-			*end = 0;
-			str = g_string_append(str, cur);
-			if (bold)
-				str = g_string_append(str, "</B>");
-			else
-				str = g_string_append(str, "<B>");
-			bold = !bold;
-			cur = end + 1;
-			break;
-		case 03: /* ^C */
-			*end++ = 0;
-			str = g_string_append(str, cur);
-			fore = back = -1;
-			if (isdigit(*end)) {
-				fore = *end++ - '0';
-				if (isdigit(*end)) {
-					fore *= 10;
-					fore += *end++ - '0';
-				}
-				if (*end == ',' && isdigit(end[1])) {
-					end++;
-					back = *end++ - '0';
-					if (isdigit(*end)) {
-						back *= 10;
-						back += *end++ - '0';
-					}
-				}
-			}
-			if (fore == -1) {
-				if (fg)
-					str = g_string_append(str, "</FONT>");
-				if (bg)
-					str = g_string_append(str, "</FONT>");
-				fg = bg = FALSE;
-			} else {
-				fore %= 16;
-				if (fg)
-					str = g_string_append(str, "</FONT>");
-				if (back != -1) {
-					if (bg)
-						str = g_string_append(str, "</FONT>");
-					back %= 16;
-					str = g_string_append(str, "<FONT BACK=");
-					str = g_string_append(str, int_to_col(back));
-					str = g_string_append_c(str, '>');
-					bg = TRUE;
-				}
-				str = g_string_append(str, "<FONT COLOR=");
-				str = g_string_append(str, int_to_col(fore));
-				str = g_string_append_c(str, '>');
-				fg = TRUE;
-			}
-			cur = end--;
-			break;
-		case 017: /* ^O */
-			if (!bold && !underline && !fg && !bg)
-				break;
-			*end = 0;
-			str = g_string_append(str, cur);
-			if (bold)
-				str = g_string_append(str, "</B>");
-			if (underline)
-				str = g_string_append(str, "</U>");
-			if (fg)
-				str = g_string_append(str, "</FONT>");
-			if (bg)
-				str = g_string_append(str, "</FONT>");
-			bold = underline = fg = bg = FALSE;
-			cur = end + 1;
-			break;
-		case 037: /* ^_ */
-			*end = 0;
-			str = g_string_append(str, cur);
-			if (underline)
-				str = g_string_append(str, "</U>");
-			else
-				str = g_string_append(str, "<U>");
-			underline = !underline;
-			cur = end + 1;
-			break;
-		}
-		end++;
-	}
-	if (*cur)
-		str = g_string_append(str, cur);
-	return str;
-}
-
-static void 
-irc_got_im(GaimConnection *gc, char *who, char *what, int flags, time_t t)
-{
-	char *utf8 = irc_recv_convert(gc, what);
-	GString *str = decode_html(utf8);
-	serv_got_im(gc, who, str->str, flags, t, -1);
-	g_string_free(str, TRUE);
-	g_free(utf8);
-}
+	GString *string = g_string_sized_new(512);
+	char *list, *buf;
 
-static void 
-dcc_chat_cancel(struct dcc_chat *);
-
-void
-dcc_chat_in (gpointer data, gint source, GaimInputCondition condition)
-{
-	struct dcc_chat *chat = data;
-	gchar buffer[IRC_BUF_LEN];
-	gchar buf[128];
-	int n = 0;
-	GaimConversation *convo;
-	gaim_debug(GAIM_DEBUG_MISC, "irc", "THIS IS TOO MUCH EFFORT\n");
-	n = read (chat->fd, buffer, IRC_BUF_LEN);
-	if (n > 0) {
-
-		buffer[n] = 0;
-		g_strstrip(buffer);
-
-		/* Convert to HTML */
-		if (strlen(buffer)) {
-			gaim_debug(GAIM_DEBUG_INFO, "irc",
-					   "DCC Message from: %s\n", chat->nick);
-			irc_got_im(chat->gc, chat->nick, buffer, 0, 
-				   time(NULL));
-		}
-	}	
-	else	{
-		g_snprintf (buf, sizeof buf, _("DCC Chat with %s closed"),
-			    chat->nick);
-		convo = gaim_conversation_new(GAIM_CONV_IM, chat->gc->account,
-									  chat->nick);
-		gaim_conversation_write(convo, NULL, buf, -1, WFLAG_SYSTEM,
-								time(NULL));
-		dcc_chat_cancel (chat);
-	}
-}
-
-void 
-irc_read_dcc_ack (gpointer data, gint source, GaimInputCondition condition) {
-	/* Read ACK Here */
-
-}
-
-void 
-dcc_send_callback (gpointer data, gint source, GaimInputCondition condition) {
-#if 0
-	struct irc_file_transfer *ift = data;
-	struct sockaddr_in addr;
-	int len = sizeof(addr);
-		
-	addr.sin_family = AF_INET;
-	addr.sin_port = htons(ift->port);
-	addr.sin_addr.s_addr = INADDR_ANY;
-
-	ift->fd = accept(ift->fd, (struct sockaddr *)&addr, &len);
-	if (!ift->fd) {
-		/* FIXME: Handle this gracefully XXX */
-		printf("Something bad happened here, bubba!\n");
-		return;
-	}
-		
-	/*	ift->awatcher = gaim_input_add(ift->fd, GAIM_INPUT_READ, irc_read_dcc_ack, ift); */
-		
-	if (transfer_out_do(ift->xfer, ift->fd, 0)) {
-		gaim_input_remove(ift->watcher);
-		ift->watcher = 0;
-	}
-#endif
-}
-
-void 
-dcc_chat_callback (gpointer data, gint source, GaimInputCondition condition) {
-	struct dcc_chat *chat = data;
-	GaimConversation *convo;
-	char buf[IRC_BUF_LEN];
-
-	convo = gaim_conversation_new(GAIM_CONV_IM, chat->gc->account, chat->nick);
+	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_append, (gpointer)string);
 
-	chat->fd = source;
-	g_snprintf (buf, sizeof buf,
-		    _("DCC Chat with %s established"),
-		    chat->nick);
-	gaim_conversation_write(convo, NULL, buf, -1, WFLAG_SYSTEM, time(NULL));
-	gaim_debug(GAIM_DEBUG_INFO, "irc",
-			   "Chat with %s established\n", chat->nick);
-	dcc_chat_list =  g_slist_append (dcc_chat_list, chat);
-	gaim_input_remove(chat->inpa);
-	chat->inpa = gaim_input_add(source, GAIM_INPUT_READ, dcc_chat_in, chat);
-}
-
-static void 
-irc_got_chat_in(GaimConnection *gc, int id, char *who, int whisper, char *msg, time_t t)
-{
-	char *utf8 = irc_recv_convert(gc, msg);
-	GString *str = decode_html(utf8);
-	serv_got_chat_in(gc, id, who, whisper, str->str, t);
-	g_string_free(str, TRUE);
-	g_free(utf8);
-}
-
-static void 
-handle_list(GaimConnection *gc, char *list)
-{
-	struct irc_data *id = gc->proto_data;
-	char *tmp;
-	GaimBlistNode *gnode, *bnode;
-
-	tmp = g_utf8_strdown(list, -1);
-
-	id->str = g_string_append_c(id->str, ' ');
-	id->str = g_string_append(id->str, tmp);
-	id->bc--;
-	g_free(tmp);
-	if (id->bc)
-		return;
-
-
-	for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
-		if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for(bnode = gnode->child; bnode; bnode = bnode->next) {
-			struct buddy *b = (struct buddy *)bnode;
-			if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-				continue;
-			if(b->account->gc == gc) {
-				char *tmp = g_utf8_strdown(b->name, -1);
-				char *x, *l;
-				x = strstr(id->str->str, tmp);
-				l = x + strlen(b->name);
-				if (x && (*l != ' ' && *l != 0))
-					x = 0;
-				if (!GAIM_BUDDY_IS_ONLINE(b) && x)
-					serv_got_update(gc, b->name, 1, 0, 0, 0, 0);
-				else if (GAIM_BUDDY_IS_ONLINE(b) && !x)
-					serv_got_update(gc, b->name, 0, 0, 0, 0, 0);
-				g_free(tmp);
-			}
-		}
-	}
-	g_string_free(id->str, TRUE);
-	id->str = g_string_new("");
-}
-
-static gboolean 
-irc_request_buddy_update(gpointer data)
-{
-	GaimConnection *gc = data;
-	struct irc_data *id = gc->proto_data;
-	char buf[500];
-	int n = g_snprintf(buf, sizeof(buf), "ISON");
-	gboolean found = FALSE;
-
-	GaimBlistNode *gnode, *bnode;
-
-	if (id->bc)
+	list = g_string_free(string, FALSE);
+	if (!list || !strlen(list)) {
+		g_free(list);
 		return TRUE;
-
-	for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
-		if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
-			continue;
-		for(bnode = gnode->child; bnode; bnode = bnode->next) {
-			struct buddy *b = (struct buddy *)bnode;
-			if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-				continue;
-			if(b->account->gc == gc) {
-				if (n + strlen(b->name) + 2 > sizeof(buf)) {
-					g_snprintf(buf + n, sizeof(buf) - n, "\r\n");
-					irc_write(id->fd, buf, n);
-					id->bc++;
-					n = g_snprintf(buf, sizeof(buf), "ISON");
-				}
-				n += g_snprintf(buf + n, sizeof(buf) - n, " %s", b->name);
-
-				found = TRUE;
-			}
-		}
 	}
 
-	if (found) {
-		g_snprintf(buf + n, sizeof(buf) - n, "\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-		id->bc++;
-	}
+	buf = irc_format(irc, "v:", "ISON", list);
+	g_free(list);
+	irc_send(irc, buf);
+	g_free(buf);
 
 	return TRUE;
 }
 
-static void 
-handle_names(GaimConnection *gc, char *chan, char *names)
-{
-	GaimConversation *c = irc_find_chat(gc, chan);
-	GaimChat *chat;
-	char **buf, **tmp;
-
-	if (!c) return;
-	if (*names == ':') names++;
-
-	chat = GAIM_CHAT(c);
-
-	buf = g_strsplit(names, " ", -1);
-
-	for (tmp = buf; *tmp; tmp++)
-		gaim_chat_add_user(chat, *tmp, NULL);
-
-	g_strfreev(buf);
-}
-
-static void 
-handle_notopic(GaimConnection *gc, char *text)
-{
-	GaimConversation *c;
-
-	if ((c = irc_find_chat(gc, text))) {
-		char buf[IRC_BUF_LEN];
-
-		g_snprintf(buf, sizeof(buf), _("No topic is set"));
-
-		gaim_chat_set_topic(GAIM_CHAT(c), NULL, buf);
-	}
-}
-
-static void 
-handle_topic(GaimConnection *gc, char *text)
-{
-	GaimConversation *c;
-	char *po = strchr(text, ' '), *buf;
-
-	if (!po)
-		return;
-
-	*po = 0;
-	po += 2;
-
-	if ((c = irc_find_chat(gc, text))) {
-                po = irc_recv_convert(gc, po);
-		gaim_chat_set_topic(GAIM_CHAT(c), NULL, po);
-		buf = g_strdup_printf(_("<B>%s has changed the topic to: %s</B>"), text, po);
-		gaim_conversation_write(c, NULL, buf, -1, WFLAG_SYSTEM, time(NULL));
-                g_free(buf);
-                g_free(po);
-	}
-}
-
-static gboolean 
-mode_has_arg(GaimConnection *gc, char sign, char mode)
-{
-	struct irc_data *id = gc->proto_data;
-	char *cm = id->chanmodes;
-	int type = 0;
-
-	if (strchr(id->nickmodes, mode))
-		return TRUE;
-
-	while (*cm) {
-		if (*cm == ',')
-			type++;
-		else if (*cm == mode) {
-			switch (type) {
-			case 0:
-			case 1:
-				return TRUE;
-			case 2:
-				if (sign == '+')
-					return TRUE;
-			case 3:
-				return FALSE;
-			}
-		}
-		cm++;
-	}
-
-	return FALSE;
-}
-
-static void 
-irc_chan_mode(GaimConnection *gc, char *room, char sign, char mode, char *argstr, char *who)
-{
-	GaimConversation *c = irc_find_chat(gc, room);
-	char buf[IRC_BUF_LEN];
-	char *nick = g_strndup(who, strchr(who, '!') - who);
-
-	g_snprintf(buf, sizeof(buf), _("-:- mode/%s [%c%c %s] by %s"), 
-		   room, sign, mode, strlen(argstr) ? argstr : "",
-		   nick);
-	g_free(nick);
-
-	gaim_conversation_write(c, NULL, buf, -1, WFLAG_SYSTEM, time(NULL));
-}
-
-static void 
-irc_user_mode(GaimConnection *gc, char *room, char sign, char mode, char *nick)
+static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string)
 {
-	GaimConversation *c = irc_find_chat(gc, room);
-	GList *r;
-
-	if (mode != 'o' && mode != 'v' && mode != 'h')
-		return;
-
-	if (!c)
-		return;
-
-	r = gaim_chat_get_users(GAIM_CHAT(c));
-	while (r) {
-		gboolean op = FALSE, halfop = FALSE, voice = FALSE;
-		char *who = r->data;
-
-		if (*who == '@') {
-			op = TRUE;
-			who++;
-		}
-
-		if (*who == '%') {
-			halfop = TRUE;
-			who++;
-		}
-
-		if (*who == '+') {
-			voice = TRUE;
-			who++;
-		}
-
-		if (!strcmp(who, nick)) {
-			char *tmp, buf[IRC_BUF_LEN];
-
-			if (mode == 'o') {
-				if (sign == '-')
-					op = FALSE;
-				else
-					op = TRUE;
-			}
-
-			if (mode == 'h') {
-				if (sign == '-')
-					halfop = FALSE;
-				else
-					halfop = TRUE;
-			}
-
-			if (mode == 'v') {
-				if (sign == '-')
-					voice = FALSE;
-				else
-					voice = TRUE;
-			}
-
-			tmp = g_strdup(r->data);
-			g_snprintf(buf, sizeof(buf), "%s%s%s",
-					   (op ? "@" : (halfop ? "%" : "")),
-					   voice ? "+" : "", nick);
-			gaim_chat_rename_user(GAIM_CHAT(c), tmp, buf);
-			g_free(tmp);
-			return;
-		}
-		r = r->next;
-	}
-}
-
-static void 
-handle_mode(GaimConnection *gc, char *word[], char *word_eol[], gboolean n324)
-{
-	struct irc_data *id = gc->proto_data;
-	int offset = n324 ? 4 : 3;
-	char *chan = word[offset];
-	GaimConversation *c = irc_find_chat(gc, chan);
-	char *modes = word[offset + 1];
-	int len = strlen(word_eol[offset]) - 1;
-	char sign = *modes++;
-	int arg = 1;
-	char *argstr;
-	char *who = word[1];
-
-	if (!c)
-		return;
-
-	if (word_eol[offset][len] == ' ')
-		word_eol[offset][len] = 0;
-
-	while (TRUE) {
-		switch (*modes) {
-		case 0:
-			return;
-		case '+':
-		case '-':
-			sign = *modes;
-			break;
-		default:
-			if (mode_has_arg(gc, sign, *modes))
-				argstr = word[++arg + offset];
-			else
-				argstr = "";
-			if (strchr(id->nickmodes, *modes))
-				irc_user_mode(gc, chan, sign, *modes, argstr);
-			else if (strchr(who, '!'))				
-				irc_chan_mode(gc, chan, sign, *modes, argstr, who);
-		}
-		modes++;
-	}
-}
-
-static void
-handle_version(GaimConnection *gc, char *word[], char *word_eol[], int num)
-{
-	struct irc_data *id = gc->proto_data;
-	GString *str;
-
-	id->liststr = g_string_new("");
-
-	id->liststr = g_string_append(id->liststr, "<b>Version: </b>");
-	id->liststr = g_string_append(id->liststr, word_eol[4]);
-
-	str = decode_html(id->liststr->str);
-	g_show_info_text(gc, NULL, 2, str->str, NULL);
-	g_string_free(str, TRUE);
-	g_string_free(id->liststr, TRUE);
-	id->liststr = NULL;
-}
-
-static void 
-handle_who(GaimConnection *gc, char *word[], char *word_eol[], int num)
-{
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-
-	if (!id->in_whois) {
-		id->in_whois = TRUE;
-		id->liststr = g_string_new("");
-	}
-
-	switch (num) {
-	case 352:
-		g_snprintf(buf, sizeof(buf), "<b>%s</b> (%s@%s): %s<br>", 
-			   word[8], word[5], word[6], word_eol[11]);
-		id->liststr = g_string_append(id->liststr, buf);
-		break;
-	}
-}
-
-/* Handle our whois stuff here.  You know what, I have a sore throat.  You know
- * what I think about that? I'm not too pleased with it.  Perhaps I should take
- * some medicine, or perhaps I should go to bed? Blah!! */
-
-static void 
-handle_whois(GaimConnection *gc, char *word[], char *word_eol[], int num)
-{
-	struct irc_data *id = gc->proto_data;
-	char tmp[1024];
-
-	if (!id->in_whois) {
-		id->in_whois = TRUE;
-		id->liststr = g_string_new("");
-	} else {
-		/* I can't decide if we should have one break or two */
-		id->liststr = g_string_append(id->liststr, "<BR>");
-		id->in_whois = TRUE;
-	}
-
-	switch (num) {
-	case 311:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s: </b>", _("User"));
-		id->liststr = g_string_append(id->liststr, tmp);
-		break;
-	case 312:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s: </b>", _("Server"));
-		id->liststr = g_string_append(id->liststr, tmp);
-		break;
-	case 313:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s:</b> %s ", _("IRC Operator"), word[4]);
-		id->liststr = g_string_append(id->liststr, tmp);
-		break;
-	case 314:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s: </b><b>%s</b> (%s@%s) %s",
-			   _("User"), word[4], word[5], word[6], word_eol[8]);
-		id->liststr = g_string_append(id->liststr, tmp);
-		return;
-	case 317:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s: </b>", _("Idle Time"));
-		id->liststr = g_string_append(id->liststr, tmp);
-		break;
-	case 319:
-		g_snprintf(tmp, sizeof(tmp), "<b>%s: </b>", _("Channels"));
-		id->liststr = g_string_append(id->liststr, tmp);
-		break;
-	/* Numeric 320 is used by the freenode irc network for showing 
-	 * that a user is identified to services (Jason Straw <misato@wopn.org>)*/
-	case 320:
-		g_snprintf(tmp, sizeof(tmp), _("%s is an Identified User"), word[4]);
-		id->liststr = g_string_append(id->liststr, tmp);
-		return;
-	default:
-		break;
-	}
-
-	if (word_eol[5][0] == ':')
-		id->liststr = g_string_append(id->liststr, word_eol[5] + 1);
-	/* Nicer idle time output, by jonas@birme.se */
-	else if (isdigit(word_eol[5][0])) {
-		time_t idle = atol(word_eol[5]);
-		time_t signon = atol(strchr(word_eol[5], ' '));
-				
-		g_snprintf(tmp, sizeof(tmp), 
-			   _("%ld seconds [signon: %s]"), (idle / 1000), ctime(&signon));
-		id->liststr = g_string_append(id->liststr, tmp);
-	}
-	else
-		id->liststr = g_string_append(id->liststr, word_eol[5]);
-}
-
-static void 
-handle_roomlist(GaimConnection *gc, char *word[], char *word_eol[])
-{
-	struct irc_data *id = gc->proto_data;
-
-	if (!id->in_list) {
-		id->in_list = TRUE;
-		id->liststr = g_string_new("");
-	} else {
-		id->liststr = g_string_append(id->liststr, "<BR>");
-		id->in_list = TRUE;
-	}
-
-	id->liststr = g_string_append(id->liststr, word_eol[4]);
-}
-
-static void 
-irc_change_nick(void *a, const char *b) {
-	GaimConnection *gc = a;
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];	
-	g_snprintf(buf, sizeof(buf), "NICK %s\r\n", b);
-	irc_write(id->fd, buf, strlen(buf));
-	gaim_connection_set_display_name(gc, b);
+	ib->flag = FALSE;
+	g_string_append_printf(string, "%s ", name);
 }
 
-static void 
-process_numeric(GaimConnection *gc, char *word[], char *word_eol[])
-{
-	const char *displayname = gaim_connection_get_display_name(gc);
-	struct irc_data *id = gc->proto_data;
-	char *text = word_eol[3];
-	int n = atoi(word[2]);
-	char tmp[1024];
-
-	if (!g_ascii_strncasecmp(displayname, text, strlen(displayname)))
-		text += strlen(displayname) + 1;
-	if (*text == ':')
-		text++;
-
-	/* RPL_ and ERR_ */
-	switch (n) {
-	case 4:
-		if (!strncmp(word[5], "u2.10", 5))
-			id->six_modes = TRUE;
-		else
-			id->six_modes = FALSE;
-		break;
-	case 5:
-		handle_005(gc, word, word_eol);
-		break;
-	case 301: /* RPL_AWAY */
-		if (id->in_whois) {
-			g_snprintf(tmp, sizeof(tmp), "<BR><b>%s: </b>", _("Away"));
-			id->liststr = g_string_append(id->liststr, tmp);
-
-			if (word_eol[5][0] == ':')
-				id->liststr = g_string_append(id->liststr, word_eol[5] + 1);
-			else
-				id->liststr = g_string_append(id->liststr, word_eol[5]);
-		} else
-			irc_got_im(gc, word[4], word_eol[5], IM_FLAG_AWAY, time(NULL));
-		break;
-	case 303: /* RPL_ISON */
-		handle_list(gc, &word_eol[4][1]);
-		break;
-	case 311: /* RPL_WHOISUSER */
-	case 312: /* RPL_WHOISSERVER */
-	case 313: /* RPL_WHOISOPERATOR */
-	case 314: /* RPL_WHOWASUSER */
-	case 317: /* RPL_WHOISIDLE */
-	case 319: /* RPL_WHOISCHANNELS */
-	case 320: /* FreeNode Identified */
-		handle_whois(gc, word, word_eol, n);
-		break;
-	case 322: /* RPL_LIST */
-		handle_roomlist(gc, word, word_eol);
-		break;
-	case 315: /* RPL_ENDOFWHO */
-	case 318: /* RPL_ENDOFWHOIS */
-	case 323: /* RPL_LISTEND */
-	case 369: /* RPL_ENDOFWHOWAS */
-		if ((id->in_whois || id->in_list) && id->liststr) {
-			GString *str = decode_html(id->liststr->str);
-			g_show_info_text(gc, NULL, 2, str->str, NULL);
-			g_string_free(str, TRUE);
-			g_string_free(id->liststr, TRUE);
-			id->liststr = NULL;
-			id->in_whois = FALSE;
-			id->in_list = FALSE;
-		}
-		break;
-	case 324: /* RPL_CHANNELMODEIS */
-		handle_mode(gc, word, word_eol, TRUE);
-		break;
-	case 331: /* RPL_NOTOPIC */
-		handle_notopic(gc, text);
-		break;
-	case 332: /* RPL_TOPIC */
-		handle_topic(gc, text);
-		break;
-	case 351: /* RPL_VERSION */
-		handle_version(gc, word, word_eol, n);
-		break;
-	case 352: /* RPL_WHOREPLY */
-		handle_who(gc, word, word_eol, n);
-		break;
-	case 353: /* RPL_NAMREPLY */
-		handle_names(gc, word[5], word_eol[6]);
-		break;
-	case 376: /* RPL_ENDOFMOTD */
-		irc_request_buddy_update(gc);
-		break;
-	case 382: /* RPL_REHASHING */
-		gaim_notify_error(gc, NULL, _("Rehashing server"), _("IRC Operator"));
-		break;
-	case 401: /* ERR_NOSUCHNICK */
-		gaim_notify_error(gc, NULL, _("No such nick/channel"), _("IRC Error"));
-		break;
-	case 402: /* ERR_NOSUCHSERVER */
-		gaim_notify_error(gc, NULL, _("No such server"), _("IRC Error"));
-		break;
-	case 422: /* ERR_NOMOTD */
-		break;  /* drop it - bringing up dialog for NOMOTD is annoying */
-	case 431: /* ERR_NONICKNAMEGIVEN */
-		gaim_notify_error(gc, NULL, _("No nickname given"), _("IRC Error"));
-		break;
-	case 481: /* ERR_NOPRIVILEGES */
-		gaim_notify_error(gc, NULL, _("You're not an IRC operator!"),
-						  _("IRC Error"));
-		break;		
-	case 433:
-		gaim_request_input(gc, NULL, _("That nick is already in use.  "
-									   "Please enter a new nick"),
-						   NULL, gaim_connection_get_display_name(gc),
-						   FALSE, FALSE,
-						   _("OK"), G_CALLBACK(irc_change_nick),
-						   _("Cancel"), NULL, gc);
-		break;
-	default:
-		/* Other error messages */
-		if (n > 400 && n < 502) {
-			char errmsg[IRC_BUF_LEN];
-			char *errmsg1 = strrchr(text, ':');
-
-			g_snprintf(errmsg, sizeof(errmsg), "IRC Error %d", n);
-
-			if (errmsg) {
-				gaim_notify_error(gc, NULL, errmsg,
-								  (errmsg1 ? errmsg1 + 1 : NULL));
-			}
-		}
-
-		break;
-	}
-}
-
-static gboolean 
-is_channel(GaimConnection *gc, const char *name)
-{
-	struct irc_data *id = gc->proto_data;
-	if (strchr(id->chantypes, *name))
-		return TRUE;
-	return FALSE;
-}
-
-static void 
-irc_rem_chat_bud(GaimConnection *gc, char *nick, GaimConversation *b, char *reason)
-{
-
-	GaimChat *chat;
-
-	if (b) {
-		GList *r;
-
-		chat = GAIM_CHAT(b);
-
-		r = gaim_chat_get_users(chat);
-
-		while (r) {
-			char *who = r->data;
-			if (*who == '@')
-				who++;
-			if (*who == '%')
-				who++;
-			if (*who == '+')
-				who++;
-			if (!gaim_utf8_strcasecmp(who, nick)) {
-				gaim_chat_remove_user(chat, who, reason);
-				break;
-			}
-			r = r->next;
-		}
-	} else {
-		GSList *bcs = gc->buddy_chats;
-		while (bcs) {
-			GaimConversation *bc = bcs->data;
-			irc_rem_chat_bud(gc, nick, bc, reason);
-			bcs = bcs->next;
-		}
-	}
-}
-
-static void 
-irc_change_name(GaimConnection *gc, char *old, char *new)
-{
-	GSList *bcs = gc->buddy_chats;
-	char buf[IRC_BUF_LEN];
-
-	while (bcs) {
-		GaimConversation *b = bcs->data;
-		GaimChat *chat;
-		GList *r;
-
-		chat = GAIM_CHAT(b);
-
-		r = gaim_chat_get_users(chat);
-
-		while (r) {
-			char *who = r->data;
-			int n = 0;
-			if (*who == '@')
-				buf[n++] = *who++;
-			if (*who == '%')
-				buf[n++] = *who++;
-			if (*who == '+')
-				buf[n++] = *who++;
-			g_snprintf(buf + n, sizeof(buf) - n, "%s", new);
-			if (!strcmp(who, old)) {
-				char *tmp = g_strdup(r->data);
-				gaim_chat_rename_user(chat, tmp, buf);
-				r = gaim_chat_get_users(chat);
-				g_free(tmp);
-				break;
-			} else
-				r = r->next;
-		}
-		bcs = bcs->next;
-	}
-}
-
-static void 
-handle_privmsg(GaimConnection *gc, char *to, char *nick, char *msg)
-{
-	if (is_channel(gc, to)) {
-		GaimConversation *c = irc_find_chat(gc, to);
-		if (!c)
-			return;
-		irc_got_chat_in(gc, gaim_chat_get_id(GAIM_CHAT(c)),
-						nick, 0, msg, time(NULL));
-	} else {
-		char *tmp = g_malloc(strlen(nick) + 2);
-		g_snprintf(tmp, strlen(nick) + 2, "@%s", nick);
-		if (gaim_find_conversation(tmp))
-			irc_got_im(gc, tmp, msg, 0, time(NULL));
-		else {
-			*tmp = '+';
-			if (gaim_find_conversation(tmp))
-				irc_got_im(gc, tmp, msg, 0, time(NULL));
-			else
-				irc_got_im(gc, nick, msg, 0, time(NULL));
-		}
-		g_free(tmp);
-	}
-}
-
-static void 
-dcc_chat_init(struct dcc_chat *data) {
-	if (g_list_find(gaim_connections_get_all(), data->gc)) {
-		gaim_proxy_connect(data->gc->account, data->ip_address, data->port, dcc_chat_callback, data);
-	} else {
-		g_free(data);
-	}
-}
-
-static void 
-dcc_chat_cancel(struct dcc_chat *data){
-	if (g_list_find(gaim_connections_get_all(), data->gc) && find_dcc_chat(data->gc, data->nick)) {
-		dcc_chat_list = g_slist_remove(dcc_chat_list, data); 
-		gaim_input_remove (data->inpa);
-		close (data->fd);
-	}
-	g_free(data);
-}
-
-static void 
-irc_convo_closed(GaimConnection *gc, const char *who)
+static const char *irc_blist_icon(GaimAccount *a, struct buddy *b)
 {
-	struct dcc_chat *dchat = find_dcc_chat(gc, who);
-	if (!dchat)
-		return;
-
-	dcc_chat_cancel(dchat);
-}
-
-static void
-irc_xfer_init(GaimXfer *xfer)
-{
-	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
-
-	gaim_xfer_start(xfer, -1, data->ip, data->port);
-}
-
-static void
-irc_xfer_end(GaimXfer *xfer)
-{
-	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
-
-	data->idata->file_transfers = g_slist_remove(data->idata->file_transfers,
-												 xfer);
-
-	g_free(data);
-	xfer->data = NULL;
-}
-
-static void
-irc_xfer_cancel_send(GaimXfer *xfer)
-{
-	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
-
-	data->idata->file_transfers = g_slist_remove(data->idata->file_transfers,
-												 xfer);
-
-	g_free(data);
-	xfer->data = NULL;
-}
-
-static void
-irc_xfer_cancel_recv(GaimXfer *xfer)
-{
-	struct irc_xfer_data *data = (struct irc_xfer_data *)xfer->data;
-
-	data->idata->file_transfers = g_slist_remove(data->idata->file_transfers,
-												 xfer);
-
-	g_free(data);
-	xfer->data = NULL;
-}
-
-static void
-irc_xfer_ack(GaimXfer *xfer, const char *buffer, size_t size)
-{
-	guint32 pos;
-
-	pos = htonl(gaim_xfer_get_bytes_sent(xfer));
-
-	write(xfer->fd, (char *)&pos, 4);
-}
-
-/* NOTE: This was taken from irssi. Thanks irssi! */
-
-static gboolean
-is_numeric(const char *str, char end_char)
-{
-	g_return_val_if_fail(str != NULL, FALSE);
-
-	if (*str == '\0' || *str == end_char)
-		return FALSE;
-
-	while (*str != '\0' && *str != end_char) {
-		if (*str < '0' || *str > '9')
-			return FALSE;
-
-		str++;
-	}
-
-	return TRUE;
-}
-
-#define get_params_match(params, pos) \
-	(is_numeric(params[pos], '\0') && \
-	is_numeric(params[(pos)+1], '\0') && atol(params[(pos)+1]) < 65536 && \
-	is_numeric(params[(pos)+2], '\0'))
-
-/* Return number of parameters in `params' that belong to file name.
-   Normally it's paramcount-3, but I don't think anything forbids of
-   adding some extension where there could be more parameters after
-   file size.
-
-   MIRC sends filenames with spaces quoted ("file name"), but I'd rather
-   not trust that entirely either. At least some clients that don't really
-   understand the problem with spaces in file names sends the file name
-   without any quotes. */
-static int
-get_file_params_count(char **params, int paramcount)
-{
-	int pos, best;
-
-	if (*params[0] == '"') {
-		/* quoted file name? */
-		for (pos = 0; pos < paramcount - 3; pos++) {
-			if (params[pos][strlen(params[pos]) - 1] == '"' &&
-				get_params_match(params, pos + 1)) {
-
-				return pos + 1;
-			}
-		}
-	}
-
-	best = paramcount - 3;
-
-	for (pos = paramcount - 3; pos > 0; pos--) {
-		if (get_params_match(params, pos))
-			best = pos;
-	}
-
-	return best;
-}
-
-static void 
-handle_ctcp(GaimConnection *gc, char *to, char *nick,
-			char *msg, char *word[], char *word_eol[])
-{
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-	char out[IRC_BUF_LEN];
-
-	if (!g_ascii_strncasecmp(msg, "VERSION", 7)) {
-		g_snprintf(buf, sizeof(buf), "\001VERSION Gaim " VERSION ": The Penguin Pimpin' "
-			   "Multi-protocol Messaging Client: " WEBSITE "\001");
-		irc_send_notice (gc, nick, buf);
-		g_snprintf(out, sizeof(out), ">> CTCP VERSION requested from %s", nick);
-		gaim_notify_info(gc, NULL, out, _("IRC CTCP info"));
-	}
-	else if (!g_ascii_strncasecmp(msg, "CLIENTINFO", 10)) {
-		g_snprintf(buf, sizeof(buf), "\001CLIENTINFO USERINFO CLIENTINFO VERSION\001");
-		irc_send_notice (gc, nick, buf);
-		g_snprintf(out, sizeof(out), ">> CTCP CLIENTINFO requested from %s", nick);
-		gaim_notify_info(gc, NULL, out, _("IRC CTCP info"));
-	}
-	else if (!g_ascii_strncasecmp(msg, "USERINFO", 8)) {
-		g_snprintf(buf, sizeof(buf), "\001USERINFO Alias: %s\001", gc->account->alias);
-		irc_send_notice (gc, nick, buf);
-		g_snprintf(out, sizeof(out), ">> CTCP USERINFO requested from %s", nick);
-		gaim_notify_info(gc, NULL, out, _("IRC CTCP info"));
-	}
-	else if (!g_ascii_strncasecmp(msg, "ACTION", 6)) {
-		char *po = strchr(msg + 6, 1);
-		char *tmp;
-		if (po) *po = 0;
-		tmp = g_strconcat("/me", msg + 6, NULL);
-		handle_privmsg(gc, to, nick, tmp);
-		g_free(tmp);
-	}
-	else if (!g_ascii_strncasecmp(msg, "PING", 4)) {
-		g_snprintf(buf, sizeof(buf), "\001%s\001", msg);
-		irc_send_notice (gc, nick, buf);
-		g_snprintf(out, sizeof(out), ">> CTCP PING requested from %s", nick);
-		gaim_notify_info(gc, NULL, out, _("IRC CTCP info"));
-	}
-	else if (!g_ascii_strncasecmp(msg, "DCC CHAT", 8)) {
-		char **chat_args = g_strsplit(msg, " ", 5);
-		char ask[1024];
-		struct dcc_chat *dccchat = g_new0(struct dcc_chat, 1);
-		dccchat->gc = gc;	
-		g_snprintf(dccchat->ip_address, sizeof(dccchat->ip_address), chat_args[3]);	
-		dccchat->port=atoi(chat_args[4]);		
-		g_snprintf(dccchat->nick, sizeof(dccchat->nick), nick);	
-		g_snprintf(ask, sizeof(ask), _("%s would like to establish a DCC chat"), nick);
-
-		gaim_request_action(gc, NULL, ask,
-							_("This requires a direct connection to be "
-							  "established between the two computers.  "
-							  "Messages sent will not pass through the "
-							  "IRC server"), 0, dccchat, 2,
-							_("Connect"), G_CALLBACK(dcc_chat_init),
-							_("Cancel"), G_CALLBACK(dcc_chat_cancel));
-	}
-	else if (!g_ascii_strncasecmp(msg, "DCC SEND", 8)) {
-		GaimXfer *xfer;
-		char **send_args;
-		char *ip, *filename;
-		struct irc_xfer_data *xfer_data;
-		size_t size;
-		int param_count, file_params, len;
-		int port;
-
-		/* Okay, this is ugly, but should get us past "DCC SEND" */
-		msg = strstr(msg, "DCC SEND");
-		msg = strchr(msg, ' ') + 1;
-		msg = strchr(msg, ' ') + 1;
-
-		/* SEND <file name> <address> <port> <size> [...] */
-		send_args = g_strsplit(msg, " ", -1);
-
-		for (param_count = 0; send_args[param_count] != NULL; param_count++)
-			;
-
-		if (param_count < 4) {
-			char buf[IRC_BUF_LEN];
-
-			g_snprintf(buf, sizeof(buf),
-					   _("Received an invalid file send request from %s."),
-					   nick);
-
-			gaim_notify_error(gc, NULL, buf, _("IRC Error"));
-
-			return;
-		}
-
-		file_params = get_file_params_count(send_args, param_count);
-
-		/* send_args[paramcount - 1][strlen(send_args[5])-1] = 0; */
-
-		/* Give these better names. */
-		ip       = send_args[file_params];
-		port     = atoi(send_args[file_params + 1]);
-		size     = atoi(send_args[file_params + 2]);
-
-		send_args[file_params] = NULL;
-
-		filename = g_strjoinv(" ", send_args);
-
-		g_strfreev(send_args);
-
-		len = strlen(filename);
-
-		if (len > 1 && *filename == '"' && filename[len - 1] == '"') {
-			/* "file name" - MIRC sends filenames with spaces like this */
-			filename[len - 1] = '\0';
-			g_memmove(filename, filename + 1, len);
-		}
-
-		/* Setup the IRC-specific transfer data. */
-		xfer_data = g_malloc0(sizeof(struct irc_xfer_data));
-		xfer_data->ip    = ip;
-		xfer_data->port  = port;
-		xfer_data->idata = id;
-
-		/* Build the file transfer handle. */
-		xfer = gaim_xfer_new(gc->account, GAIM_XFER_RECEIVE, nick);
-		xfer->data = xfer_data;
-
-		/* Set the info about the incoming file. */
-		gaim_xfer_set_filename(xfer, filename);
-		gaim_xfer_set_size(xfer, size);
-
-		g_free(filename);
-
-		/* Setup our I/O op functions. */
-		gaim_xfer_set_init_fnc(xfer,        irc_xfer_init);
-		gaim_xfer_set_end_fnc(xfer,         irc_xfer_end);
-		gaim_xfer_set_cancel_send_fnc(xfer, irc_xfer_cancel_send);
-		gaim_xfer_set_cancel_recv_fnc(xfer, irc_xfer_cancel_recv);
-		gaim_xfer_set_ack_fnc(xfer,         irc_xfer_ack);
-
-		/* Keep track of this transfer for later. */
-		id->file_transfers = g_slist_append(id->file_transfers, xfer);
-
-		/* Now perform the request! */
-		gaim_xfer_request(xfer);
-	}
+	return "irc";
 }
 
-static gboolean 
-irc_parse(GaimConnection *gc, char *buf)
-{
-	struct irc_data *idata = gc->proto_data;
-	gchar outbuf[IRC_BUF_LEN];
-	char *word[PDIWORDS], *word_eol[PDIWORDS];
-	char pdibuf[522];
-	char *ex, ip[128], nick[128];
-	char *cmd;
-
-	/* Check for errors */
-	
-	if (*buf != ':') {
-		if (!strncmp(buf, "NOTICE ", 7))
-			buf += 7;
-		if (!strncmp(buf, "PING ", 5)) {
-			int r = FALSE;
-			g_snprintf(outbuf, sizeof(outbuf), "PONG %s\r\n", buf + 5);
-			if (irc_write(idata->fd, outbuf, strlen(outbuf)) < 0) {
-				gaim_connection_error(gc, _("Unable to write"));
-				r = TRUE;
-			}
-			return r;
-		}
-		/* XXX doesn't handle ERROR */
-		return FALSE;
-	}
-
-	if (!idata->online) {
-		/* Now lets sign ourselves on */
-		gaim_connection_set_state(gc, GAIM_CONNECTED);
-		serv_finish_login(gc);
-
-		/* we don't call this now because otherwise some IRC servers might not like us */
-		idata->timer = g_timeout_add(20000, irc_request_buddy_update, gc);
-		idata->online = TRUE;
-	}
-
-	buf++;
-
-	process_data_init(pdibuf, buf, word, word_eol, FALSE);
-
-	if (atoi(word[2])) {
-		if (*word_eol[3])
-			process_numeric(gc, word, word_eol);
-		return FALSE;
-	}
-
-	cmd = word[2];
-
-	ex = strchr(pdibuf, '!');
-	if (!ex) {
-		strncpy(ip, pdibuf, sizeof(ip));
-		ip[sizeof(ip)-1] = 0;
-		strncpy(nick, pdibuf, sizeof(nick));
-		nick[sizeof(nick)-1] = 0;
-	} else {
-		strncpy(ip, ex + 1, sizeof(ip));
-		ip[sizeof(ip)-1] = 0;
-		strncpy(nick, pdibuf, sizeof(nick));
-		nick[sizeof(nick)-1] = 0;
-		if ((ex - pdibuf) < sizeof (nick))
-			nick[ex - pdibuf] = 0; /* cut the buffer at the '!' */
-	}
-
-	if (!strcmp(cmd, "INVITE")) {
-		GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal,
-				g_free, g_free);
-
-		g_hash_table_replace(components, g_strdup("channel"), g_strdup(word[4]));
-
-		serv_got_chat_invite(gc, word[4] + 1, nick, NULL, components);
-	} else if (!strcmp(cmd, "JOIN")) {
-		irc_parse_join(gc, nick, word, word_eol);
-	} else if (!strcmp(cmd, "KICK")) {
-		if (!strcmp(gaim_connection_get_display_name(gc), word[4])) {
-			GaimConversation *c = irc_find_chat(gc, word[3]);
-			if (!c)
-				return FALSE;
-			gc->buddy_chats = g_slist_remove(gc->buddy_chats, c);
-			gaim_conversation_set_account(c, NULL);
-			g_snprintf(outbuf, sizeof(outbuf), _("You have been kicked from %s: %s"),
-				   word[3], *word_eol[5] == ':' ? word_eol[5] + 1 : word_eol[5]);
-			gaim_notify_error(gc, NULL, outbuf, _("IRC Error"));
-		} else {
-			char *reason = *word_eol[5] == ':' ? word_eol[5] + 1 : word_eol[5];
-			char *msg = g_strdup_printf(_("Kicked by %s: %s"), nick, reason);
-			GaimConversation *c = irc_find_chat(gc, word[3]);
-			irc_rem_chat_bud(gc, word[4], c, msg);
-			g_free(msg);
-		}
-	} else if (!strcmp(cmd, "KILL")) { /* */
-	} else if (!strcmp(cmd, "MODE")) {
-		handle_mode(gc, word, word_eol, FALSE);
-	} else if (!strcmp(cmd, "NICK")) {
-		char *new = *word_eol[3] == ':' ? word_eol[3] + 1 : word_eol[3];
-		if (!strcmp(gaim_connection_get_display_name(gc), nick))
-			gaim_connection_set_display_name(gc, new);
-		irc_change_name(gc, nick, new);
-	} else if (!strcmp(cmd, "NOTICE")) {
-		irc_parse_notice(gc, nick, ex, word, word_eol);
-	} else if (!strcmp(cmd, "PART")) {
-		if (!irc_parse_part(gc, nick, cmd, word, word_eol))
-			return FALSE;
-	} else if (!strcmp(cmd, "PRIVMSG")) {
-		char *to, *msg;
-		if (!*word[3])
-			return FALSE;
-		to = word[3];
-		msg = *word_eol[4] == ':' ? word_eol[4] + 1 : word_eol[4];
-		if (msg[0] == 1 && msg[strlen (msg) - 1] == 1) { /* ctcp */
-			if (!g_ascii_strncasecmp(msg + 1, "DCC ", 4))
-				process_data_init(pdibuf, buf, word, word_eol, TRUE);
-			handle_ctcp(gc, to, nick, msg + 1, word, word_eol);
-		} else {
-			handle_privmsg(gc, to, nick, msg);
-		}
-	} else if (!strcmp(cmd, "PONG")) { /* */
-	} else if (!strcmp(cmd, "QUIT")) {
-		irc_rem_chat_bud(gc, nick, irc_find_chat(gc, word[3]), *word_eol[3] == ':' ? word_eol[3] + 1 : word_eol[3]);
-	} else if (!strcmp(cmd, "TOPIC")) {
-		irc_parse_topic(gc, nick, word, word_eol);
-	} else if (!strcmp(cmd, "WALLOPS")) { /* Don't know if a dialog box is the right way? */
-		char *msg = strrchr(word_eol[0], ':');
-		if (msg)
-			gaim_notify_error(gc, NULL, msg+1, _("IRC Operator"));
-	}
-
-	return FALSE;
-}
-
-/* CTCP by jonas@birme.se */
-static void
-irc_parse_notice(GaimConnection *gc, char *nick, char *ex,
-                 char *word[], char *word_eol[])
-{
-	char buf[IRC_BUF_LEN];
-
-	if (!g_ascii_strcasecmp(word[4], ":\001CLIENTINFO")) {
-		char *p = g_strrstr(word_eol[5], "\001");
-		*p = 0;
-		g_snprintf(buf, sizeof(buf), "CTCP Answer: %s", word_eol[5]);
-		gaim_notify_info(gc, NULL, buf, _("CTCP ClientInfo"));
-
-	} else if (!g_ascii_strcasecmp(word[4], ":\001USERINFO")) {
-		char *p = g_strrstr(word_eol[5], "\001");
-		*p = 0;
-		g_snprintf(buf, sizeof(buf), "CTCP Answer: %s", word_eol[5]);
-		gaim_notify_info(gc, NULL, buf, _("CTCP UserInfo"));
-
-	} else if (!g_ascii_strcasecmp(word[4], ":\001VERSION")) {
-		char *p = g_strrstr(word_eol[5], "\001");
-		*p = 0;
-		g_snprintf(buf, sizeof(buf), "CTCP Answer: %s", word_eol[5]);
-		gaim_notify_info(gc, NULL, buf, _("CTCP Version"));
-
-	} else if (!g_ascii_strcasecmp(word[4], ":\001PING")) {
-		char *p = g_strrstr(word_eol[5], "\001");
-		struct timeval ping_time;
-		struct timeval now;
-		gchar **vector;
-
-		if (p)
-			*p = 0;
-
-		vector = g_strsplit(word_eol[5], ".", 2);
-
-		if (gettimeofday(&now, NULL) == 0 && vector != NULL) {
-			if (vector[1] && now.tv_usec - atol(vector[1]) < 0) {
-				ping_time.tv_sec = now.tv_sec - atol(vector[0]) - 1;
-				ping_time.tv_usec = now.tv_usec - atol(vector[1]) + 1000000;
-
-			} else {
-				ping_time.tv_sec = now.tv_sec - atol(vector[0]);
-				if(vector[1])
-					ping_time.tv_usec = now.tv_usec - atol(vector[1]);
-			}
-
-			g_snprintf(buf, sizeof(buf),
-					   "CTCP Ping reply from %s: %lu.%.03lu seconds",
-					   nick, ping_time.tv_sec, (ping_time.tv_usec/1000));
-
-			gaim_notify_info(gc, NULL, buf, _("CTCP Ping"));
-			g_strfreev(vector);
-		}
-	} else {
-		if (*word_eol[4] == ':') word_eol[4]++;
-		if (ex)
-			irc_got_im(gc, nick, word_eol[4], 0, time(NULL));
-	}
-}
-
-static void
-irc_parse_join(GaimConnection *gc, char *nick,
-               char *word[], char *word_eol[])
+static void irc_blist_emblems(struct buddy *b, char **se, char **sw, char **nw, char **ne)
 {
-	char *chan = *word[3] == ':' ? word[3] + 1 : word[3];
-	static int id = 1;
-	GaimConversation *c;
-	char *hostmask, *p;
-
-	if (!gaim_utf8_strcasecmp(gaim_connection_get_display_name(gc), nick)) {
-		serv_got_joined_chat(gc, id++, chan);
-	} else {
-		c = irc_find_chat(gc, chan);
-		if (c) {
-			hostmask = g_strdup(word[1]);
-			p = strchr(hostmask, '!');
-			if (p) {
-				char *pend = strchr(p, ' ');
-				if (pend) {
-					*pend = 0;
-				}
-
-				gaim_chat_add_user(GAIM_CHAT(c), nick, p + 1);
-
-				g_free(hostmask);
-			}
-		}
-	}
-}
-	
-static void
-irc_parse_topic(GaimConnection *gc, char *nick,
-                char *word[], char *word_eol[])
-{
-	GaimConversation *c = irc_find_chat(gc, word[3]);
-	char *topic = irc_recv_convert(gc, *word_eol[4] == ':' ? word_eol[4] + 1 : word_eol[4]);
-	char buf[IRC_BUF_LEN];
-
-	if (c) {
-		gaim_chat_set_topic(GAIM_CHAT(c), nick, topic);
-		g_snprintf(buf, sizeof(buf),
-				   _("<B>%s has changed the topic to: %s</B>"), nick, topic);
-
-		gaim_conversation_write(c, NULL, buf, -1, WFLAG_SYSTEM, time(NULL));
-	}
-	g_free(topic);
-}
-
-static gboolean
-irc_parse_part(GaimConnection *gc, char *nick, char *cmd,
-               char *word[], char *word_eol[])
-{
-	char *chan = cmd + 5;
-	GaimConversation *c;
-	GaimChat *chat;
-	char *reason = word_eol[4];
-	GList *r;
-
-	if (*chan == ':')
-		chan++;
-	if (*reason == ':')
-		reason++;
-	if (!(c = irc_find_chat(gc, chan)))
-		return FALSE;
-	if (!strcmp(nick, gaim_connection_get_display_name(gc))) {
-		serv_got_chat_left(gc, gaim_chat_get_id(GAIM_CHAT(c)));
-		return FALSE;
-	}
-
-	chat = GAIM_CHAT(c);
-
-	r = gaim_chat_get_users(GAIM_CHAT(c));
-
-	while (r) {
-		char *who = r->data;
-		if (*who == '@')
-			who++;
-		if (*who == '%')
-			who++;
-		if (*who == '+')
-			who++;
-		if (!gaim_utf8_strcasecmp(who, nick)) {
-			gaim_chat_remove_user(chat, who, reason);
-			break;
-		}
-		r = r->next;
-	}
-	return TRUE;
-}
-
-static void 
-irc_callback(gpointer data, gint source, GaimInputCondition condition)
-{
-	GaimConnection *gc = data;
-	struct irc_data *idata = gc->proto_data;
-	int i = 0;
-	gchar buf[1024];
-	gboolean off;
-
-	i = read(idata->fd, buf, 1024);
-	if (i <= 0) {
-		gaim_connection_error(gc, _("Read error"));
-		return;
-	}
-
-	idata->rxqueue = g_realloc(idata->rxqueue, i + idata->rxlen + 1);
-	memcpy(idata->rxqueue + idata->rxlen, buf, i);
-	idata->rxlen += i;
-	idata->rxqueue[idata->rxlen] = 0;
-
-	while (1) {
-		char *d, *e;
-		int len;
-
-		if (!idata->rxqueue || ((e = strchr(idata->rxqueue, '\n')) == NULL))
-			return;
-
-		len = e - idata->rxqueue + 1;
-		d = g_strndup(idata->rxqueue, len);
-		g_strchomp(d);
-		gaim_debug(GAIM_DEBUG_MISC, "irc", "S: %s\n", d);
-
-		/* REMOVE ME BEFORE SUBMIT! */
-		/*fprintf(stderr, "IRC S: %s\n", d);*/
-
-		idata->rxlen -= len;
-		if (idata->rxlen) {
-			char *tmp = g_strdup(e + 1);
-			g_free(idata->rxqueue);
-			idata->rxqueue = tmp;
-		} else {
-			g_free(idata->rxqueue);
-			idata->rxqueue = NULL;
-		}
-
-		off = irc_parse(gc, d);
-
-		g_free(d);
-
-		if (off)
-			return;
-	}
-}
-
-static void 
-irc_login_callback(gpointer data, gint source, GaimInputCondition condition)
-{
-	GaimConnection *gc = data;
-	GaimAccount *account = gaim_connection_get_account(gc);
-	struct irc_data *idata;
-	char hostname[256];
-	char buf[IRC_BUF_LEN];
-	char *test;
-	const char *alias;
-	const char *charset = gaim_account_get_string(account, "charset", "UTF-8");
-	GError *err = NULL;
-
-	idata = gc->proto_data;
-
-	if (source < 0) {
-		gaim_connection_error(gc, _("Write error"));
-		return;
-	}
-	idata->fd = source;
-	
-	/* Try a quick conversion to see if the specified encoding is OK */
-	test = g_convert("test", strlen("test"), charset,
-			 "UTF-8", NULL, NULL, &err);
-	if (err) {
-		gaim_debug(GAIM_DEBUG_ERROR, "irc",
-			   "Couldn't initialize %s for IRC charset conversion, using ISO-8859-1\n",
-			   charset);
-		gaim_account_set_string(account, "charset", "UTF-8");
-	}
-	
-	g_free(test);
-	
-	gethostname(hostname, sizeof(hostname) - 1);
-	hostname[sizeof(hostname) - 1] = 0;
-
-	if (!*hostname)
-		g_snprintf(hostname, sizeof(hostname), "localhost");
-
-	if (gaim_account_get_password(account) != NULL) {
-		g_snprintf(buf, sizeof(buf), "PASS %s\r\n",
-				   gaim_account_get_password(account));
-
-		if (irc_write(idata->fd, buf, strlen(buf)) < 0) {
-			gaim_connection_error(gc, _("Write error"));
-			return;
-		}
-	}
-
-	alias = gaim_account_get_alias(account);
-
-	g_snprintf(buf, sizeof(buf), "USER %s %s %s :%s\r\n",
-		   g_get_user_name(), hostname,
-		   idata->server,
-		   (alias == NULL ? "gaim" : alias));
-
-	if (irc_write(idata->fd, buf, strlen(buf)) < 0) {
-		gaim_connection_error(gc, _("Write error"));
-		return;
-	}
-
-	g_snprintf(buf, sizeof(buf), "NICK %s\r\n",
-			   gaim_connection_get_display_name(gc));
-
-	if (irc_write(idata->fd, buf, strlen(buf)) < 0) {
-		gaim_connection_error(gc, _("Write error"));
-		return;
-	}
-
-	gc->inpa = gaim_input_add(idata->fd, GAIM_INPUT_READ, irc_callback, gc);
-}
-
-static void 
-irc_login(GaimAccount *account)
-{
-	const char *username = gaim_account_get_username(account);
-	char buf[IRC_BUF_LEN];
-	int rc;
-
-	GaimConnection *gc;
-	struct irc_data *idata;
-	char **parts;
-
-	gc = gaim_account_get_connection(account);
-	idata = gc->proto_data = g_new0(struct irc_data, 1);
-
-	parts = g_strsplit(username, "@", 2);
-	gaim_connection_set_display_name(gc, parts[0]);
-	idata->server = g_strdup(parts[1]);
-	g_strfreev(parts);
-
-	g_snprintf(buf, sizeof(buf), _("Signon: %s"), username);
-	gaim_connection_update_progress(gc, buf, 1, 2);
-
-	idata->chantypes = g_strdup("#&!+");
-	idata->chanmodes = g_strdup("beI,k,lnt");
-	idata->nickmodes = g_strdup("ohv");
-	idata->str = g_string_new("");
-	idata->fd = -1;
-
-	rc = gaim_proxy_connect(account, idata->server,
-			   gaim_account_get_int(account, "port", 6667),
-			   irc_login_callback, gc);
-	
-	if (rc != 0) {
-		gaim_connection_error(gc, _("Unable to create socket"));
-		return;
-	}
+	if (b->present == GAIM_BUDDY_OFFLINE)
+		*se = "offline";
 }
 
-static void 
-irc_close(GaimConnection *gc)
-{
-	struct irc_data *idata = (struct irc_data *)gc->proto_data;
-
-	gchar buf[IRC_BUF_LEN];
-
-	if (idata->str->len > 0) {
-		g_snprintf(buf, sizeof(buf), "QUIT :%s\r\n", idata->str->str);
-	} else {
-		g_snprintf(buf, sizeof(buf), 
-			   "QUIT :Download Gaim [%s]\r\n", WEBSITE);
-	}
-	irc_write(idata->fd, buf, strlen(buf));
-
-	if (idata->rxqueue)
-		g_free(idata->rxqueue);
-
-	idata->rxqueue = NULL;
-	idata->rxlen = 0;
-
-	/* Kill any existing transfers */
-	while (idata->file_transfers) {
-		GaimXfer *xfer;
-
-		xfer = (GaimXfer *)idata->file_transfers->data;
-
-		gaim_xfer_end(xfer);
-		gaim_xfer_destroy(xfer);
-
-		idata->file_transfers = idata->file_transfers->next;
-	}
-	idata->file_transfers = NULL;
-
-
-	g_free(idata->chantypes);
-	g_free(idata->chanmodes);
-	g_free(idata->nickmodes);
-
-	g_string_free(idata->str, TRUE);
-	if (idata->liststr)
-		g_string_free(idata->liststr, TRUE);
-
-	if (idata->timer)
-		g_source_remove(idata->timer);
-
-	if (gc->inpa)
-		gaim_input_remove(gc->inpa);
-
-	close(idata->fd);
-	g_free(gc->proto_data);
-}
-
-static void 
-set_mode_3(GaimConnection *gc, const char *who, int sign, int mode,
-		       int start, int end, char *word[])
+static GList *irc_away_states(GaimConnection *gc)
 {
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-	int left;
-	int i = start;
-
-	while (1) {
-		left = end - i;
-		switch (left) {
-		case 0:
-			return;
-		case 1:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c %s\r\n",
-				   who, sign, mode, word[i]);
-			i += 1;
-			break;
-		case 2:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c %s %s\r\n",
-				   who, sign, mode, mode, word[i], word[i + 1]);
-			i += 2;
-			break;
-		default:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c %s %s %s\r\n",
-				   who, sign, mode, mode, mode,
-				   word[i], word[i + 1], word[i + 2]);
-			i += 2;
-			break;
-		}
-		irc_write(id->fd, buf, strlen(buf));
-		if (left < 3)
-			return;
-	}
-}
-
-static void 
-set_mode_6(GaimConnection *gc, const char *who, int sign, int mode,
-		       int start, int end, char *word[])
-{
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-	int left;
-	int i = start;
-
-	while (1) {
-		left = end - i;
-		switch (left) {
-		case 0:
-			return;
-		case 1:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c %s\r\n",
-				   who, sign, mode, word[i]);
-			i += 1;
-			break;
-		case 2:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c %s %s\r\n",
-				   who, sign, mode, mode, word[i], word[i + 1]);
-			i += 2;
-			break;
-		case 3:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c %s %s %s\r\n",
-				   who, sign, mode, mode, mode,
-				   word[i], word[i + 1], word[i + 2]);
-			i += 3;
-			break;
-		case 4:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c %s %s %s %s\r\n",
-				   who, sign, mode, mode, mode, mode,
-				   word[i], word[i + 1], word[i + 2], word[i + 3]);
-			i += 4;
-			break;
-		case 5:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c%c %s %s %s %s %s\r\n",
-				   who, sign, mode, mode, mode, mode, mode,
-				   word[i], word[i + 1], word[i + 2],
-				   word[i + 3], word[i + 4]);
-			i += 5;
-			break;
-		default:
-			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c%c%c %s %s %s %s %s %s\r\n",
-				   who, sign, mode, mode, mode, mode, mode, mode,
-				   word[i], word[i + 1], word[i + 2],
-				   word[i + 3], word[i + 4], word[i + 5]);
-			i += 6;
-			break;
-		}
-		irc_write(id->fd, buf, strlen(buf));
-		if (left < 6)
-			return;
-	}
-}
-
-static void 
-set_mode(GaimConnection *gc, const char *who, int sign, int mode, char *word[])
-{
-	struct irc_data *id = gc->proto_data;
-	int i = 2;
-
-	while (1) {
-		if (!*word[i]) {
-			if (i == 2)
-				return;
-			if (id->six_modes)
-				set_mode_6(gc, who, sign, mode, 2, i, word);
-			else
-				set_mode_3(gc, who, sign, mode, 2, i, word);
-			return;
-		}
-		i++;
-	}
-}
-
-static void 
-set_chan_mode(GaimConnection *gc, const char *chan, const char *mode_str)
-{
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-
-	if ((mode_str[0] == '-') || (mode_str[0] == '+')) {
-		g_snprintf(buf, sizeof(buf), "MODE %s %s\r\n", chan, mode_str);
-		irc_write(id->fd, buf, strlen(buf));
-	}
+	return g_list_append(NULL, (gpointer)GAIM_AWAY_CUSTOM);
 }
 
-static int 
-handle_command(GaimConnection *gc, const char *who, const char *in_what)
+static GList *irc_buddy_menu(GaimConnection *gc, const char *who)
 {
-	char buf[IRC_BUF_LEN];
-	char pdibuf[IRC_BUF_LEN];
-	char *word[PDIWORDS], *word_eol[PDIWORDS];
-	char *tmp = g_strdup(in_what);
-	GString *str = encode_html(tmp);
-	char *intl;
-	int len;
-	struct dcc_chat *dccchat = find_dcc_chat(gc, who);
-	struct irc_data *id = gc->proto_data;
-	char *what = str->str;
-
-	g_free(tmp);
-
-	if (*what != '/') {
-		if (dccchat) {
-			intl = irc_send_convert(gc, what, sizeof(buf), &len);
-			g_snprintf(buf, sizeof(buf), "%s\r\n", intl);
-			g_free(intl);
-			irc_write(dccchat->fd, buf, strlen(buf));
-			g_string_free(str, TRUE);
-			return 1;
-		}
-		irc_send_privmsg (gc, who, what, TRUE);
-		g_string_free(str, TRUE);
-		return 1;
-	}
-	
-	process_data_init(pdibuf, what + 1, word, word_eol, TRUE);
-	g_string_free(str, FALSE);
-	if (!g_ascii_strcasecmp(pdibuf, "ME")) {
-		if (dccchat) {
-			intl = irc_send_convert(gc, word_eol[2], sizeof(buf), &len);
-			g_snprintf(buf, sizeof(buf), "\001ACTION %s\001\r\n", intl);
-			g_free(intl);
-			irc_write(dccchat->fd, buf, strlen(buf));
-			g_free(what);
-			return 1;
-		}
-		g_snprintf(buf, sizeof(buf), "\001ACTION %s\001", word_eol[2]);
-		irc_send_privmsg (gc, who, buf, FALSE);
-		g_free(what);
-		return 1;
-	} else if (!g_ascii_strcasecmp(pdibuf, "INVITE")) {
-		char buf[IRC_BUF_LEN];
-		g_snprintf(buf, sizeof(buf), "INVITE %s\r\n", word_eol[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "TOPIC")) {
-		if (!*word_eol[2]) {
-			GaimConversation *c;
-			GaimChat *chat;
-
-			c = irc_find_chat(gc, who);
-			chat = GAIM_CHAT(c);
-
-			g_snprintf(buf, sizeof(buf), _("Topic for %s is %s"),
-					   who, (gaim_chat_get_topic(chat)
-							 ? gaim_chat_get_topic(chat)
-							 : "(no topic set)"));
+	struct irc_conn *irc = gc->proto_data;
+	struct proto_buddy_menu *pbm;
 
-			gaim_conversation_write(c, NULL, buf, -1,
-									WFLAG_SYSTEM | WFLAG_NOLOG, time(NULL));
-		} else {
-			/* This could be too long */
-			intl = irc_send_convert(gc, word_eol[2], sizeof(buf), &len);
-			g_snprintf(buf, sizeof(buf), "TOPIC %s :%s\r\n", who, intl);
-			g_free(intl);
-			irc_write(id->fd, buf, strlen(buf));
-		}
-	} else if (!g_ascii_strcasecmp(pdibuf, "NICK")) {
-		if (!*word_eol[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		g_snprintf(buf, sizeof(buf), "NICK %s\r\n", word_eol[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "OP")) {
-		set_mode(gc, who, '+', 'o', word);
-	} else if (!g_ascii_strcasecmp(pdibuf, "DEOP")) {
-		set_mode(gc, who, '-', 'o', word);
-	} else if (!g_ascii_strcasecmp(pdibuf, "VOICE")) {
-		set_mode(gc, who, '+', 'v', word);
-	} else if (!g_ascii_strcasecmp(pdibuf, "DEVOICE")) {
-		set_mode(gc, who, '-', 'v', word);
-	} else if (!g_ascii_strcasecmp(pdibuf, "MODE")) {
-		set_chan_mode(gc, who, word_eol[2]);
-	} else if (!g_ascii_strcasecmp(pdibuf, "QUOTE")) {
-		if (!*word_eol[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		g_snprintf(buf, sizeof(buf), "%s\r\n", word_eol[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "SAY")) {
-		if (!*word_eol[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		irc_send_privmsg (gc, who, word_eol[2], TRUE);
-		return 1;
-	} else if (!g_ascii_strcasecmp(pdibuf, "MSG")) {
-		if (!*word[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		if (!*word_eol[3]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		irc_send_privmsg (gc, word[2], word_eol[3], TRUE);
-	} else if (!g_ascii_strcasecmp(pdibuf, "KICK")) {
-		if (!*word[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		if (*word_eol[3]) {
-			intl = irc_send_convert(gc, word_eol[3], sizeof(buf), &len);
-			g_snprintf(buf, sizeof(buf), "KICK %s %s :%s\r\n", who, word[2], intl);
-			g_free(intl);
-		} else
-			g_snprintf(buf, sizeof(buf), "KICK %s %s\r\n", who, word[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "JOIN") || !g_ascii_strcasecmp(pdibuf, "J")) {
-		if (!*word[2]) {
-			g_free(what);
-			return -EINVAL;
-		}
-		if (*word[3])
-			g_snprintf(buf, sizeof(buf), "JOIN %s %s\r\n", word[2], word[3]);
-		else
-			g_snprintf(buf, sizeof(buf), "JOIN %s\r\n", word[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "PART")) {
-		const char *chan = *word[2] ? word[2] : who;
-		char *reason = word_eol[3];
-		GaimConversation *c;
-		if (!is_channel(gc, chan)) {
-			g_free(what);
-			return -EINVAL;
-		}
-		c = irc_find_chat(gc, chan);
-		if (*reason) {
-			intl = irc_send_convert(gc, reason, sizeof(buf), &len);
-			g_snprintf(buf, sizeof(buf), "PART %s :%s\r\n", chan, intl);
-			g_free(intl);
-		} else
-			g_snprintf(buf, sizeof(buf), "PART %s\r\n", chan);
-		irc_write(id->fd, buf, strlen(buf));
-		if (c) {
-			gc->buddy_chats = g_slist_remove(gc->buddy_chats, c);
-			gaim_conversation_set_account(c, NULL);
-			g_snprintf(buf, sizeof(buf), _("You have left %s"), chan);
-			gaim_notify_info(gc, NULL, buf, _("IRC Part"));
-		}
-	} else if (!g_ascii_strcasecmp(pdibuf, "WHOIS")) {
-		g_snprintf(buf, sizeof(buf), "WHOIS %s\r\n", word_eol[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "WHOWAS")) {
-		g_snprintf(buf, sizeof(buf), "WHOWAS %s\r\n", word_eol[2]);
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "LIST")) {
-		g_snprintf(buf, sizeof(buf), "LIST\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "QUIT")) {
-		char *reason = word_eol[2];
-		id->str = g_string_insert(id->str, 0, reason);
-		gaim_core_quit();
-	} else if (!g_ascii_strcasecmp(pdibuf, "VERSION")) {
-		g_snprintf(buf, sizeof(buf), "VERSION\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "W")) {
-		g_snprintf(buf, sizeof(buf), "WHO *\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "REHASH")) {
-		g_snprintf(buf, sizeof(buf), "REHASH\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "RESTART")) {
-		g_snprintf(buf, sizeof(buf), "RESTART\r\n");
-		irc_write(id->fd, buf, strlen(buf));
-	} else if (!g_ascii_strcasecmp(pdibuf, "CTCP")) {
-		if (!g_ascii_strcasecmp(word[2], "CLIENTINFO")) {
-			if (word[3])
-				irc_ctcp_clientinfo(gc, word[3]);
-		} else if (!g_ascii_strcasecmp(word[2], "USERINFO")) {
-			if (word[3])
-				irc_ctcp_userinfo(gc, word[3]);
-		} else if (!g_ascii_strcasecmp(word[2], "VERSION")) {
-			if (word[3])
-				irc_ctcp_version(gc, word[3]);
-
-		} else if (!g_ascii_strcasecmp(word[2], "PING")) {
-			if (word[3])
-				irc_ctcp_ping(gc, word[3]);
-		}
-	} else if (!g_ascii_strcasecmp(pdibuf, "DCC")) {
-		GaimConversation *c = NULL;
-		if (!g_ascii_strcasecmp(word[2], "CHAT")) {
-			if (word[3])
-				irc_start_chat(gc, word[3]);
-			
-			if (is_channel(gc, who)) {
-				c = irc_find_chat(gc, who);
-			} else {
-				c = gaim_find_conversation(who);
-			}
-			if (c) {
-				gaim_conversation_write(c, NULL,
-										_("<I>Requesting DCC CHAT</I>"),
-										-1, WFLAG_SYSTEM, time(NULL));
-			}
-		}
-	} else if (!g_ascii_strcasecmp(pdibuf, "HELP")) {
-		GaimConversation *c = NULL;
-		if (is_channel(gc, who)) {
-			c = irc_find_chat(gc, who);
-		} else {
-			c = gaim_find_conversation(who);
-		}
-		if (!c) {
-			g_free(what);
-			return -EINVAL;
-		}
-		if (!g_ascii_strcasecmp(word[2], "OPER")) {
-			gaim_conversation_write(c, NULL,
-				_("<B>Operator commands:<BR>"
-				  "REHASH RESTART</B>"),
-				-1, WFLAG_NOLOG, time(NULL));
-		} else if (!g_ascii_strcasecmp(word[2], "CTCP")) {
-			gaim_conversation_write(c, NULL,
-			_("<B>CTCP commands:<BR>"
-			  "CLIENTINFO <nick><BR>"
-			  "USERINFO <nick><BR>"
-			  "VERSION <nick><BR>"
-			  "PING <nick></B><BR>"),
-			-1, WFLAG_NOLOG, time(NULL));
-		} else if (!g_ascii_strcasecmp(word[2], "DCC")) {
-			gaim_conversation_write(c, NULL,
-				_("<B>DCC commands:<BR>"
-				  "CHAT <nick></B>"),
-				-1, WFLAG_NOLOG, time(NULL));
-		} else {
-			gaim_conversation_write(c, NULL,
-				_("<B>Currently supported commands:<BR>"
-				  "WHOIS INVITE NICK LIST<BR>"
-				  "JOIN PART TOPIC KICK<BR>"
-				  "OP DEOP VOICE DEVOICE<BR>"
-				  "ME MSG QUOTE SAY QUIT<BR>"
-				  "MODE VERSION W WHOWAS<BR>"
-				  "Type /HELP OPER for operator commands<BR>"
-				  "Type /HELP CTCP for CTCP commands<BR>"
-				  "Type /HELP DCC for DCC commands"),
-				-1, WFLAG_NOLOG, time(NULL));
-		}
-	} else {
-		GaimConversation *c = NULL;
-		if (is_channel(gc, who)) {
-			c = irc_find_chat(gc, who);
-		} else {
-			c = gaim_find_conversation(who);
-		}
-		if (!c) {
-			g_free(what);
-			return -EINVAL;
-		}
-
-		gaim_conversation_write(c, NULL, _("<B>Unknown command</B>"),
-								-1, WFLAG_NOLOG, time(NULL));
-	}
-	g_free(what);
-	return 0;
+	return NULL;
 }
 
-static int 
-send_msg(GaimConnection *gc, const char *who, const char *what)
-{
-	char *cr = strchr(what, '\n');
-	if (cr) {
-		int ret = 0;
-		while (TRUE) {
-			if (cr)
-				*cr = 0;
-			ret = handle_command(gc, who, what);
-			if (!cr)
-				break;
-			what = cr + 1;
-			if (!*what)
-				break;
-			*cr = '\n';
-			cr = strchr(what, '\n');
-		}
-		return ret;
-	} else
-		return handle_command(gc, who, what);
-}
-
-static void 
-irc_chat_invite(GaimConnection *gc, int idn, const char *message, const char *name) {
-	char buf[IRC_BUF_LEN]; 
-	struct irc_data *id = gc->proto_data;
-	GaimConversation *c = gaim_find_chat(gc, idn);
-	g_snprintf(buf, sizeof(buf), "INVITE %s %s\r\n", name, c->name);
-	irc_write(id->fd, buf, strlen(buf));
-}
-
-static int 
-irc_send_im(GaimConnection *gc, const char *who, const char *what, int len, int flags)
-{
-	if (*who == '@' || *who == '%' || *who == '+')
-		return send_msg(gc, who + 1, what);
-	return send_msg(gc, who, what);
-}
-
-/* IRC doesn't have a buddy list, but we can still figure out who's online with ISON */
-static void 
-irc_add_buddy(GaimConnection *gc, const char *who) {}
-static void 
-irc_remove_buddy(GaimConnection *gc, const char *who, const char *group) {}
-
-static GList *
-irc_chat_info(GaimConnection *gc)
+static GList *irc_chat_join_info(GaimConnection *gc)
 {
 	GList *m = NULL;
 	struct proto_chat_entry *pce;
@@ -2478,448 +127,294 @@
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Password:");
 	pce->identifier = "password";
-	pce->secret = TRUE;
 	m = g_list_append(m, pce);
 
 	return m;
 }
 
-static void
-irc_join_chat(GaimConnection *gc, GHashTable *data)
+static void irc_login(GaimAccount *account)
 {
-	struct irc_data *id = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-	char *name, *pass;
+	GaimConnection *gc;
+	struct irc_conn *irc;
+	char *buf, **userparts;
+	const char *username = gaim_account_get_username(account);
+	int err;
+
+	gc = gaim_account_get_connection(account);
+
+	gc->flags |= OPT_CONN_AUTO_RESP;
+
+	gc->proto_data = irc = g_new0(struct irc_conn, 1);
+	irc->account = account;
+
+	userparts = g_strsplit(username, "@", 2);
+	gaim_connection_set_display_name(gc, userparts[0]);
+	irc->server = g_strdup(userparts[1]);
+	g_strfreev(userparts);
 
-	if (!data)
+	irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal, 
+					     NULL, (GDestroyNotify)irc_buddy_free);
+	irc->cmds = g_hash_table_new(g_str_hash, g_str_equal);
+	irc_cmd_table_build(irc);
+	irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
+	irc_msg_table_build(irc);
+
+	buf = g_strdup_printf(_("Signon: %s"), username);
+	gaim_connection_update_progress(gc, buf, 1, 2);
+	g_free(buf);
+
+	err = gaim_proxy_connect(account, irc->server, 
+				 gaim_account_get_int(account, "port", IRC_DEFAULT_PORT),
+				 irc_login_cb, gc);
+
+	if (err || !account->gc) {
+		gaim_connection_error(gc, _("Couldn't create socket"));
 		return;
-
-	name = g_hash_table_lookup(data, "channel");
-	pass = g_hash_table_lookup(data, "password");
-	if (pass) {
-		g_snprintf(buf, sizeof(buf), "JOIN %s %s\r\n", name, pass);
-	} else
-		g_snprintf(buf, sizeof(buf), "JOIN %s\r\n", name);
-	irc_write(id->fd, buf, strlen(buf));
+	}
 }
 
-static void 
-irc_chat_leave(GaimConnection *gc, int id)
+static void irc_login_cb(gpointer data, gint source, GaimInputCondition cond)
 {
-	struct irc_data *idata = gc->proto_data;
-	GaimConversation *c = gaim_find_chat(gc, id);
-	char buf[IRC_BUF_LEN];
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+	char hostname[256];
+	char *buf;
+	GList *connections = gaim_connections_get_all();
+
+	if (source < 0)
+		return;
+
+	if (!g_list_find(connections, gc)) {
+		close(source);
+		return;
+	}
+
+	irc->fd = source;
 
-	if (!c) return;
+	if (gc->account->password && *gc->account->password) {
+		buf = irc_format(irc, "vv", "PASS", gc->account->password);
+		if (irc_send(irc, buf) < 0) {
+			gaim_connection_error(gc, "Error sending password");
+			return;
+		}
+		g_free(buf);
+	}
 
-	g_snprintf(buf, sizeof(buf), "PART %s\r\n", c->name);
-	irc_write(idata->fd, buf, strlen(buf));
+	gethostname(hostname, sizeof(hostname));
+	hostname[sizeof(hostname) - 1] = '\0';
+	buf = irc_format(irc, "vvvv:", "USER", g_get_user_name(), hostname, irc->server,
+			      gc->account->alias && *gc->account->alias ? gc->account->alias : IRC_DEFAULT_ALIAS);
+	if (irc_send(irc, buf) < 0) {
+		gaim_connection_error(gc, "Error registering with server");
+		return;
+	}
+	g_free(buf);
+	buf = irc_format(irc, "vn", "NICK", gaim_connection_get_display_name(gc));
+	if (irc_send(irc, buf) < 0) {
+		gaim_connection_error(gc, "Error sending nickname");
+		return;
+	}
+	g_free(buf);
+
+	gc->inpa = gaim_input_add(irc->fd, GAIM_INPUT_READ, irc_input_cb, gc);
 }
 
-static int 
-irc_chat_send(GaimConnection *gc, int id, const char *what)
+static void irc_close(GaimConnection *gc)
 {
-	GaimConversation *c = gaim_find_chat(gc, id);
-	if (!c)
-		return -EINVAL;
-	if (send_msg(gc, c->name, what) > 0)
-		serv_got_chat_in(gc, gaim_chat_get_id(GAIM_CHAT(c)),
-			gaim_connection_get_display_name(gc), 0, what, time(NULL));
-	return 0;
+	struct irc_conn *irc = gc->proto_data;
+
+	irc_cmd_quit(irc, "quit", NULL, NULL);
+
+	if (gc->inpa)
+		g_source_remove(gc->inpa);
+
+	g_free(irc->inbuf);
+	close(irc->fd);
+	if (irc->timer)
+		g_source_remove(irc->timer);
+	g_hash_table_destroy(irc->cmds);
+	g_hash_table_destroy(irc->msgs);
+	if (irc->motd)
+		g_string_free(irc->motd, TRUE);
+	g_free(irc->server);
+	g_free(irc);
 }
 
-static GList *
-irc_away_states(GaimConnection *gc)
+static int irc_im_send(GaimConnection *gc, const char *who, const char *what, int len, int flags)
 {
-	return g_list_append(NULL, GAIM_AWAY_CUSTOM);
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[2];
+
+	if (*who == '@' || *who == '%' || *who == '+')
+		args[0] = who + 1;
+	else
+		args[0] = who;
+	args[1] = what;
+
+	if (*what == '/') {
+		return irc_parse_cmd(irc, who, what + 1);
+	}
+
+	irc_cmd_privmsg(irc, "msg", NULL, args);
+	return 1;
 }
 
-static void 
-irc_set_away(GaimConnection *gc, const char *state, const char *msg)
+static void irc_get_info(GaimConnection *gc, const char *who)
 {
-	struct irc_data *idata = gc->proto_data;
-	char buf[IRC_BUF_LEN];
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[1];
+	args[0] = who;
+	irc_cmd_whois(irc, "whois", NULL, args);
+}
+
+static void irc_set_away(GaimConnection *gc, const char *state, const char *msg)
+{
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[1];
 
 	if (gc->away) {
 		g_free(gc->away);
 		gc->away = NULL;
 	}
 
-	if (msg) {
-		g_snprintf(buf, sizeof(buf), "AWAY :%s\r\n", msg);
+	if (msg)
 		gc->away = g_strdup(msg);
-	} else
-		g_snprintf(buf, sizeof(buf), "AWAY\r\n");
-
-	irc_write(idata->fd, buf, strlen(buf));
-}
 
-static const char *
-irc_list_icon(GaimAccount *a, struct buddy *b)
-{
-	return "irc";
-}
-
-static void irc_list_emblems(struct buddy *b, char **se, char **sw, char **nw, char **ne)
-{
-	if (b->present == GAIM_BUDDY_OFFLINE)
-		*se = "offline";
+	args[0] = msg;
+	irc_cmd_away(irc, "away", NULL, args);
 }
 
-static void 
-dcc_chat_connected(gpointer data, gint source, GdkInputCondition condition)
+static void irc_add_buddy(GaimConnection *gc, const char *who)
 {
-	struct dcc_chat *chat = data;
-	GaimConversation *convo;
-	char buf[128];
-	struct sockaddr_in addr;
-	int addrlen = sizeof (addr);
-	addr.sin_family = AF_INET;
-	addr.sin_port = htons (chat->port);
-	addr.sin_addr.s_addr = INADDR_ANY;
-	chat->fd = accept (chat->fd, (struct sockaddr *) (&addr), &addrlen);
-	if (!chat->fd) {
-		dcc_chat_cancel (chat);
-		convo = gaim_conversation_new(GAIM_CONV_IM, chat->gc->account,
-									  chat->nick);
-		g_snprintf (buf, sizeof buf, _("DCC Chat with %s closed"),
-			    chat->nick);
-		gaim_conversation_write(convo, NULL, buf, -1,
-								WFLAG_SYSTEM, time(NULL));
-		return;
-	}
-	chat->inpa =
-		gaim_input_add (chat->fd, GAIM_INPUT_READ, dcc_chat_in, chat);
-	convo = gaim_conversation_new(GAIM_CONV_IM, chat->gc->account, chat->nick);
-	g_snprintf (buf, sizeof buf, _("DCC Chat with %s established"),
-				chat->nick);
-	gaim_conversation_write(convo, NULL, buf, -1, WFLAG_SYSTEM, time(NULL));
-	gaim_debug(GAIM_DEBUG_INFO, "irc",
-			   "Chat with %s established\n", chat->nick);
-	dcc_chat_list = g_slist_append (dcc_chat_list, chat);
+	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
+	struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
+	ib->name = g_strdup(who);
+	g_hash_table_insert(irc->buddies, ib->name, ib);
 }
-#if 0
-static void 
-irc_ask_send_file(GaimConnection *gc, char *destsn) {
-	struct irc_data *id = (struct irc_data *)gc->proto_data;
-	struct irc_file_transfer *ift = g_new0(struct irc_file_transfer, 1);
-	char *localip = (char *)malloc(12);
 
-	if (getlocalip(localip) == -1) {
-		free(localip);
-		return;
-	} 
-
-	ift->type = IFT_SENDFILE_OUT;
-	ift->sn = g_strdup(destsn);
-	ift->gc = gc;
-	snprintf(ift->ip, sizeof(ift->ip), "%s", localip);
-	id->file_transfers = g_slist_append(id->file_transfers, ift);
-
-	ift->xfer = transfer_out_add(gc, ift->sn);
+static void irc_remove_buddy(GaimConnection *gc, const char *who, const char *group)
+{
+	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
+	g_hash_table_remove(irc->buddies, who);
 }
 
-static struct 
-irc_file_transfer *find_ift_by_xfer(GaimConnection *gc,
-									struct file_transfer *xfer) {
-		
-	GSList *g = ((struct irc_data *)gc->proto_data)->file_transfers;
-	struct irc_file_transfer *f = NULL;
 
-	while (g) {
-		f = (struct irc_file_transfer *)g->data;
-		if (f->xfer == xfer)
-			break;
-		g = g->next;
-		f = NULL;
+static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+	char *cur, *end;
+	int len;
+
+	if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) {
+		irc->inbuflen += IRC_INITIAL_BUFSIZE;
+		irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
 	}
 
-	return f;
-}
+	if ((len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1)) < 0) {
+		gaim_connection_error(gc, "Read error");
+		return;
+	}
+	irc->inbufused += len;
+	irc->inbuf[irc->inbufused] = '\0';
 
-static void 
-irc_file_transfer_data_chunk(GaimConnection *gc, struct file_transfer *xfer, const char *data, int len) {
-	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
-	guint32 pos;
-	
-	ift->cur += len;
-	pos = htonl(ift->cur);
-	write(ift->fd, (char *)&pos, 4);
-
-	// FIXME: You should check to verify that they received the data when
-	// you are sending a file ...
+	for (cur = irc->inbuf; cur < irc->inbuf + irc->inbufused && (end = strstr(cur, "\r\n")); cur = end + 2) {
+		*end = '\0';
+		irc_parse_msg(irc, cur);
+	}
+	if (cur != irc->inbuf + irc->inbufused) { /* leftover */
+		irc->inbufused -= (cur - irc->inbuf);
+		memmove(irc->inbuf, cur, irc->inbufused);
+	} else {
+		irc->inbufused = 0;
+	}
 }
 
-static void 
-irc_file_transfer_cancel (GaimConnection *gc, struct file_transfer *xfer) {
-	struct irc_data *id = (struct irc_data *)gc->proto_data;
-	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
-
-	printf("Our shit got canceled, yo!\n");
-
-	/* Remove the FT from our list of transfers */
-	id->file_transfers = g_slist_remove(id->file_transfers, ift);
-
-	gaim_input_remove(ift->watcher);
-
-	/* Close our FT because we're done */
-	close(ift->fd);
-
-	g_free(ift->sn);
-	g_free(ift->name);
-
-	g_free(ift);
-}
-
-static void 
-irc_file_transfer_done(GaimConnection *gc, struct file_transfer *xfer) {
-	struct irc_data *id = (struct irc_data *)gc->proto_data;
-	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
-
-
-	printf("Our shit be done, yo.\n");
-	
-	/* Remove the FT from our list of transfers */
-	id->file_transfers = g_slist_remove(id->file_transfers, ift);
-
-	gaim_input_remove(ift->watcher);
+static void irc_chat_join (GaimConnection *gc, GHashTable *data)
+{
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[2];
 
-	/* Close our FT because we're done */
-	close(ift->fd);
-
-	g_free(ift->sn);
-	g_free(ift->name);
-
-	g_free(ift);
-}
-
-static void 
-irc_file_transfer_out (GaimConnection *gc, struct file_transfer *xfer, const char *name, int totfiles, int totsize) {
-	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
-	struct sockaddr_in addr;
-	char buf[IRC_BUF_LEN];
-	int len;
-	
-	
-	ift->fd = socket (AF_INET, SOCK_STREAM, 0);
-	addr.sin_family = AF_INET;
-	addr.sin_port = 0;
-	addr.sin_addr.s_addr = INADDR_ANY;
-	bind (ift->fd, (struct sockaddr *) &addr, sizeof(addr));
-	listen(ift->fd, 1);
-
-	len = sizeof(addr);
-	getsockname (ift->fd, (struct sockaddr *) &addr, &len);
-
-	ift->port = ntohs(addr.sin_port);
-
-	ift->watcher = gaim_input_add (ift->fd, GAIM_INPUT_READ, dcc_send_callback, ift);
-	printf("watcher is %d\n", ift->watcher);
-
-	snprintf(buf, sizeof(buf), "\001DCC SEND %s %s %d %d\001\n", name, ift->ip, ift->port, totsize);
-	printf("Trying: %s\n", buf);
-	irc_send_im (gc, ift->sn, buf, -1, 0);
+	args[0] = g_hash_table_lookup(data, "channel");
+	args[1] = g_hash_table_lookup(data, "password");
+	irc_cmd_join(irc, "join", NULL, args);
 }
 
-static void 
-irc_file_transfer_in(GaimConnection *gc,
-				 struct file_transfer *xfer, int offset) {
-
-	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
-
-	ift->xfer = xfer;
-	gaim_proxy_connect(gc->account, ift->ip, ift->port, dcc_recv_callback, ift);
-}
-#endif
-
-static void 
-irc_ctcp_clientinfo(GaimConnection *gc, const char *who)
+static void irc_chat_invite(GaimConnection *gc, int id, const char *message, const char *name) 
 {
-	char buf[IRC_BUF_LEN];
-
-	snprintf (buf, sizeof buf, "\001CLIENTINFO\001");
-	irc_send_privmsg(gc, who, buf, FALSE);
-}
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
 
-static void 
-irc_ctcp_userinfo(GaimConnection *gc, const char *who)
-{
-	char buf[IRC_BUF_LEN];
-
-	snprintf (buf, sizeof buf, "\001USERINFO\001");
-	irc_send_privmsg(gc, who, buf, FALSE);
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
+		return;
+	}
+	args[0] = name;
+	args[1] = gaim_conversation_get_name(convo);
+	irc_cmd_invite(irc, "invite", gaim_conversation_get_name(convo), args);
 }
 
-static void 
-irc_ctcp_version(GaimConnection *gc, const char *who)
+
+static void irc_chat_leave (GaimConnection *gc, int id)
 {
-	char buf[IRC_BUF_LEN];
-
-	snprintf (buf, sizeof buf, "\001VERSION\001");
-	irc_send_privmsg(gc, who, buf, FALSE);
-}
-
-static void 
-irc_ctcp_ping(GaimConnection *gc, const char *who)
-{
-	char buf[IRC_BUF_LEN];
-	struct timeval now;
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
 
-	gettimeofday(&now, NULL);
-	g_snprintf (buf, sizeof(buf), "\001PING %lu.%.03lu\001", now.tv_sec,
-			now.tv_usec/1000);
-	irc_send_privmsg(gc, who, buf, FALSE);
-}
+	if (!convo)
+		return;
 
-static void 
-irc_send_notice(GaimConnection *gc, char *who, char *what)
-{
-	char buf[IRC_BUF_LEN], *intl;
-	struct irc_data *id = gc->proto_data;
-	int len;
-	
-	intl = irc_send_convert(gc, what, 501, &len);
-	g_snprintf(buf, sizeof(buf), "NOTICE %s :%s\r\n", who, intl);
-	g_free(intl);
-	irc_write(id->fd, buf, strlen(buf));
+	args[0] = gaim_conversation_get_name(convo);
+	args[1] = NULL;
+	irc_cmd_part(irc, "part", gaim_conversation_get_name(convo), args);
+	serv_got_chat_left(gc, id);
 }
 
-/* Don't call this guy with fragment = 1 for anything but straight
- * up privmsgs.  (no CTCP/whatever)  It's still dangerous for CTCPs
- * (it might not include the trailing \001), but I think this behavior
- * is generally better than not fragmenting at all on lots of our
- * packets. */
-/* From RFC2812:
- * IRC messages are always lines of characters terminated with a CR-LF
- * (Carriage Return - Line Feed) pair, and these messages SHALL NOT
- * exceed 512 characters in length, counting all characters including
- * the trailing CR-LF. Thus, there are 510 characters maximum allowed
- * for the command and its parameters. */
-/* So apparently that includes all the inter-server crap, which is up
- * to NINETY-THREE chars on dancer, which seems to be a pretty liberal
- * ircd.  My rough calculation for now is ":<nick>!~<user>@<host> ",
- * where <host> is a max of an (uncalculated) 63 chars.  Thanks to
- * trelane and #freenode for giving a hand here. */
-static void 
-irc_send_privmsg(GaimConnection *gc, const char *who, const char *what, gboolean fragment)
+static int irc_chat_send(GaimConnection *gc, int id, const char *what)
 {
-	char buf[IRC_BUF_LEN], *intl;
-	struct irc_data *id = gc->proto_data;
-	/* 512 - 12 (for PRIVMSG" "" :""\r\n") - namelen - nicklen - 68 */
-	int nicklen = (gc->account->alias && strlen(gc->account->alias)) ? strlen(gc->account->alias) : 4;
-	int max = 444 - strlen(who) - strlen(g_get_user_name()) - nicklen;
-	
-	int len;
-	
-	do {
-		/* the \001 on CTCPs may cause a problem here for some 
-		 * charsets, but probably not ones people use for IRC. */
-		intl = irc_send_convert(gc, what, max, &len);
-		g_snprintf(buf, sizeof(buf), "PRIVMSG %s :%s\r\n", who, intl);
-		g_free(intl);
-		irc_write(id->fd, buf, strlen(buf));
-		what += len;
-	} while (fragment && strlen(what));
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
+
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
+		return -EINVAL;
+	}
+
+	if (*what == '/') {
+		return irc_parse_cmd(irc, convo->name, what + 1);
+	}
+
+	args[0] = convo->name;
+	args[1] = what;
+
+	irc_cmd_privmsg(irc, "msg", NULL, args);
+	serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, what, time(NULL));
+	return 0;
 }
 
-static void 
-irc_start_chat(GaimConnection *gc, const char *who) {
-	struct dcc_chat *chat;
-	int len;
-	struct sockaddr_in addr;
-	char buf[IRC_BUF_LEN];
-	const char *ip;
-	
-	/* Create a socket */
-	chat = g_new0 (struct dcc_chat, 1);
-	chat->fd = socket (AF_INET, SOCK_STREAM, 0);
-	chat->gc = gc;
-	g_snprintf (chat->nick, sizeof (chat->nick), "%s", who);
-	if (chat->fd < 0)  {
-		dcc_chat_cancel (chat);
-		return;
-	}
-	addr.sin_family = AF_INET;
-	addr.sin_port = 0;
-	addr.sin_addr.s_addr = INADDR_ANY;
-	bind (chat->fd, (struct sockaddr *) &addr, sizeof (addr));
-	listen (chat->fd, 1);
-	len = sizeof (addr);
-	getsockname (chat->fd, (struct sockaddr *) &addr, &len);
-	chat->port = ntohs (addr.sin_port);
+static guint irc_nick_hash(const char *nick)
+{
+	char *lc;
+	guint bucket;
 
-	ip = gaim_xfers_get_ip_for_account(gaim_connection_get_account(gc));
-	strncpy(chat->ip_address, ip, INET6_ADDRSTRLEN);
+	lc = g_utf8_strdown(nick, -1);
+	bucket = g_str_hash(lc);
+	g_free(lc);
 
-	chat->inpa =
-		gaim_input_add (chat->fd, GAIM_INPUT_READ, dcc_chat_connected,
-				chat);
-	g_snprintf (buf, sizeof buf, "\001DCC CHAT chat %s %d\001\n",
-		    chat->ip_address, chat->port);
-	irc_send_im (gc, who, buf, -1, 0);
+	return bucket;
 }
 
-static void 
-irc_get_info(GaimConnection *gc, const char *who)
+static gboolean irc_nick_equal(const char *nick1, const char *nick2)
 {
-	struct irc_data *idata = gc->proto_data;
-	char buf[IRC_BUF_LEN];
-
-	if (*who == '@')
-		who++;
-	if (*who == '%')
-		who++;
-	if (*who == '+')
-		who++;
-
-	g_snprintf(buf, sizeof(buf), "WHOIS %s\r\n", who);
-	irc_write(idata->fd, buf, strlen(buf));
+	return (gaim_utf8_strcasecmp(nick1, nick2) == 0);
 }
 
-static GList *
-irc_buddy_menu(GaimConnection *gc, const char *who)
+static void irc_buddy_free(struct irc_buddy *ib)
 {
-	GList *m = NULL;
-	struct proto_buddy_menu *pbm;
-
-	pbm = g_new0(struct proto_buddy_menu, 1);
-	pbm->label = _("DCC Chat");
-	pbm->callback = irc_start_chat;
-	pbm->gc = gc;
-	m = g_list_append(m, pbm);
-	/*
-	  pbm = g_new0(struct proto_buddy_menu, 1);
-	  pbm->label = _("DCC Send");
-	  pbm->callback = irc_ask_send_file;
-	  pbm->gc = gc;
-	  m = g_list_append(m, pbm);
-	*/
-	
-	pbm = g_new0(struct proto_buddy_menu, 1);
-	pbm->label = _("CTCP ClientInfo");
-	pbm->callback = irc_ctcp_clientinfo;
-	pbm->gc = gc;
-	m = g_list_append(m, pbm);
-	
-	pbm = g_new0(struct proto_buddy_menu, 1);
-	pbm->label = _("CTCP UserInfo");
-	pbm->callback = irc_ctcp_userinfo;
-	pbm->gc = gc;
-	m = g_list_append(m, pbm);
-
-	pbm = g_new0(struct proto_buddy_menu, 1);
-	pbm->label = _("CTCP Version");
-	pbm->callback = irc_ctcp_version;
-	pbm->gc = gc;
-	m = g_list_append(m, pbm);
-
-	pbm = g_new0(struct proto_buddy_menu, 1);
-	pbm->label = _("CTCP Ping");
-	pbm->callback = irc_ctcp_ping;
-	pbm->gc = gc;
-	m = g_list_append(m, pbm);
-
-	return m;
+	g_free(ib->name);
+	g_free(ib);
 }
 
 static GaimPluginProtocolInfo prpl_info =
@@ -2928,17 +423,17 @@
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
 	NULL,
 	NULL,
-	irc_list_icon,
-	irc_list_emblems,
+	irc_blist_icon,
+	irc_blist_emblems,
 	NULL,
 	NULL,
 	irc_away_states,
 	NULL,
-	irc_buddy_menu,
-	irc_chat_info,
+	NULL, /*irc_buddy_menu,*/
+	irc_chat_join_info,
 	irc_login,
 	irc_close,
-	irc_send_im,
+	irc_im_send,
 	NULL,
 	NULL,
 	irc_get_info,
@@ -2959,7 +454,7 @@
 	NULL,
 	NULL,
 	NULL,
-	irc_join_chat,
+	irc_chat_join,
 	irc_chat_invite,
 	irc_chat_leave,
 	NULL,
@@ -2972,7 +467,7 @@
 	NULL,
 	NULL,
 	NULL,
-	irc_convo_closed,
+	NULL, /*irc_convo_closed,*/
 	NULL
 };
 
@@ -2988,10 +483,8 @@
 	"prpl-irc",                                       /**< id             */
 	"IRC",                                            /**< name           */
 	VERSION,                                          /**< version        */
-	                                                  /**  summary        */
-	N_("IRC Protocol Plugin"),
-	                                                  /**  description    */
-	N_("IRC Protocol Plugin"),
+	N_("IRC Protocol Plugin"),                        /**  summary        */
+	N_("The IRC Protocol Plugin that Sucks Less"),    /**  description    */
 	NULL,                                             /**< author         */
 	WEBSITE,                                          /**< homepage       */
 
@@ -3003,26 +496,21 @@
 	&prpl_info                                        /**< extra_info     */
 };
 
-static void
-init_plugin(GaimPlugin *plugin)
+static void _init_plugin(GaimPlugin *plugin)
 {
 	GaimAccountUserSplit *split;
 	GaimAccountOption *option;
 
-	split = gaim_account_user_split_new(_("Server"), DEFAULT_SERVER, '@');
+	split = gaim_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@');
 	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
 
-
-	option = gaim_account_option_int_new(_("Port"), "port", 6667);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-											   option);
+	option = gaim_account_option_int_new(_("Port:"), "port", IRC_DEFAULT_PORT);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-	option = gaim_account_option_string_new(_("Encoding"), "charset",
-											"ISO-8859-1");
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-											   option);
+	option = gaim_account_option_string_new(_("Encoding"), "encoding", IRC_DEFAULT_CHARSET);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-	my_protocol = plugin;
+	_irc_plugin = plugin;
 }
 
-GAIM_INIT_PLUGIN(irc, init_plugin, info);
+GAIM_INIT_PLUGIN(irc, _init_plugin, info);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/irc/irc.h	Wed Jul 30 00:50:29 2003 +0000
@@ -0,0 +1,148 @@
+/**
+ * @file irc.h
+ * 
+ * gaim
+ *
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * 
+ * 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 _GAIM_IRC_H
+#define _GAIM_IRC_H
+
+#include <glib.h>
+
+#include "multi.h"
+
+#define IRC_DEFAULT_SERVER "irc.freenode.net"
+#define IRC_DEFAULT_PORT 6667
+
+#define IRC_DEFAULT_CHARSET "UTF-8"
+#define IRC_DEFAULT_ALIAS "gaim"
+
+#define IRC_INITIAL_BUFSIZE 1024
+
+enum { IRC_USEROPT_SERVER, IRC_USEROPT_PORT, IRC_USEROPT_CHARSET };
+enum irc_state { IRC_STATE_NEW, IRC_STATE_ESTABLISHED };
+
+struct irc_conn {
+	GaimAccount *account;
+	GHashTable *msgs;
+	GHashTable *cmds;
+	char *server;
+	int fd;
+	guint timer;
+	GHashTable *buddies;
+
+	char *inbuf;
+	int inbuflen;
+	int inbufused;
+
+	GString *motd;
+	GString *names;
+	char *nameconv;
+	struct _whois {
+		char *nick;
+		char *away;
+		char *userhost;
+		char *name;
+		char *server;
+		char *serverinfo;
+		char *channels;
+		int ircop;
+		int identified;
+		int idle;
+		time_t signon;
+	} whois;
+};
+
+struct irc_buddy {
+	char *name;
+	gboolean online;
+	gboolean flag;
+};
+
+typedef int (*IRCCmdCallback) (struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+
+int irc_send(struct irc_conn *irc, const char *buf);
+gboolean irc_blist_timeout(struct irc_conn *irc);
+
+char *irc_mirc2html(const char *string);
+
+void irc_msg_table_build(struct irc_conn *irc);
+void irc_parse_msg(struct irc_conn *irc, char *input);
+int irc_parse_cmd(struct irc_conn *irc, const char *target, const char *cmdstr);
+char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice);
+char *irc_format(struct irc_conn *irc, const char *format, ...);
+
+void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_away(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_badmode(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_banned(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_chanmode(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_endwhois(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_endmotd(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_invite(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_inviteonly(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_ison(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_join(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_kick(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_mode(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_motd(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_names(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_nick(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_nickused(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_nonick(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_nosend(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_notice(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_notinchan(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_notop(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_part(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_ping(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_pong(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_privmsg(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_quit(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_topic(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_unknown(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_wallops(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_whois(struct irc_conn *irc, const char *name, const char *from, char **args);
+
+void irc_msg_ignore(struct irc_conn *irc, const char *name, const char *from, char **args);
+
+void irc_cmd_table_build(struct irc_conn *irc);
+
+int irc_cmd_default(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_away(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_ctcp_action(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_invite(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_join(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_kick(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_mode(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_names(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_nick(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_op(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_privmsg(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_part(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_ping(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_quit(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_quote(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_query(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_remove(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_topic(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_wallops(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+int irc_cmd_whois(struct irc_conn *irc, const char *cmd, const char *target, const char **args);
+
+#endif /* _GAIM_IRC_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/irc/msgs.c	Wed Jul 30 00:50:29 2003 +0000
@@ -0,0 +1,790 @@
+/**
+ * @file msgs.c
+ * 
+ * gaim
+ *
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * 
+ * 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 "internal.h"
+
+/* XXX g_show_info_text */
+#include "gaim.h"
+
+#include "conversation.h"
+#include "blist.h"
+#include "notify.h"
+#include "util.h"
+#include "debug.h"
+#include "irc.h"
+
+#include <stdio.h>
+
+static char *irc_mask_nick(const char *mask);
+static char *irc_mask_userhost(const char *mask);
+static void irc_chat_remove_buddy(GaimConversation *convo, char *data[2]);
+static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc);
+
+static char *irc_mask_nick(const char *mask)
+{
+	char *end, *buf;
+
+	end = strchr(mask, '!');
+	if (!end)
+		buf = g_strdup(mask);
+	else
+		buf = g_strndup(mask, end - mask);
+
+	return buf;
+}
+
+static char *irc_mask_userhost(const char *mask)
+{
+	return g_strdup(strchr(mask, '!') + 1);
+}
+
+static void irc_chat_remove_buddy(GaimConversation *convo, char *data[2])
+{
+	GList *users = gaim_chat_get_users(GAIM_CHAT(convo));
+	char *message = g_strdup_printf("quit: %s", data[1]);
+
+	if (g_list_find_custom(users, data[0], (GCompareFunc)(strcmp)))
+		gaim_chat_remove_user(GAIM_CHAT(convo), data[0], message);
+
+	g_free(message);
+}
+
+void irc_msg_default(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	gaim_debug(GAIM_DEBUG_INFO, "irc", "Unrecognized message: %s\n", args[0]);
+}
+
+void irc_msg_away(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+
+	if (!args || !args[1])
+		return;
+
+	if (irc->whois.nick && !gaim_utf8_strcasecmp(irc->whois.nick, args[1])) {
+		/* We're doing a whois, show this in the whois dialog */
+		irc_msg_whois(irc, name, from, args);
+		return;
+	}
+
+	gc = gaim_account_get_connection(irc->account);
+	if (gc)
+		serv_got_im(gc, args[1], args[2], IM_FLAG_AWAY, time(NULL), -1);
+}
+
+void irc_msg_badmode(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+
+	if (!args || !args[1] || !gc)
+		return;
+
+	gaim_notify_error(gc, NULL, _("Bad mode"), args[1]);
+}
+
+void irc_msg_banned(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *buf;
+
+	if (!args || !args[1] || !gc)
+		return;
+
+	buf = g_strdup_printf(_("You are banned from %s."), args[1]);
+	gaim_notify_error(gc, _("Banned"), _("Banned"), buf);
+	g_free(buf);
+}
+
+void irc_msg_chanmode(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConversation *convo;
+	char *buf;
+
+	if (!args || !args[1] || !args[2])
+		return;
+
+	convo = gaim_find_conversation_with_account(args[1], irc->account);
+	if (!convo)	/* XXX punt on channels we are not in for now */
+		return;
+
+	buf = g_strdup_printf("mode for %s: %s %s", args[1], args[2], args[3] ? args[3] : "");
+	gaim_chat_write(GAIM_CHAT(convo), "", buf, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	g_free(buf);
+
+	return;
+}
+
+void irc_msg_whois(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	if (!irc->whois.nick) {
+		gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unexpected WHOIS reply for %s\n", args[1]);
+		return;
+	}
+
+	if (gaim_utf8_strcasecmp(irc->whois.nick, args[1])) {
+		gaim_debug(GAIM_DEBUG_WARNING, "irc", "Got WHOIS reply for %s while waiting for %s\n", args[1], irc->whois.nick);
+		return;
+	}
+
+	if (!strcmp(name, "301")) {
+		irc->whois.away = g_strdup(args[2]);
+	} else if (!strcmp(name, "311")) {
+		irc->whois.userhost = g_strdup_printf("%s@%s", args[2], args[3]);
+		irc->whois.name = g_strdup(args[5]);
+	} else if (!strcmp(name, "312")) {
+		irc->whois.server = g_strdup(args[2]);
+		irc->whois.serverinfo = g_strdup(args[3]);
+	} else if (!strcmp(name, "313")) {
+		irc->whois.ircop = 1;
+	} else if (!strcmp(name, "317")) {
+		irc->whois.idle = atoi(args[2]);
+		if (args[3])
+			irc->whois.signon = (time_t)atoi(args[3]);
+	} else if (!strcmp(name, "319")) {
+		irc->whois.channels = g_strdup(args[2]);
+	} else if (!strcmp(name, "320")) {
+		irc->whois.identified = 1;
+	}
+}
+
+void irc_msg_endwhois(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+	GString *info;
+	char *str;
+
+	if (!irc->whois.nick) {
+		gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unexpected End of WHOIS for %s\n", args[1]);
+		return;
+	}
+	if (gaim_utf8_strcasecmp(irc->whois.nick, args[1])) {
+		gaim_debug(GAIM_DEBUG_WARNING, "irc", "Received end of WHOIS for %s, expecting %s\n", args[1], irc->whois.nick);
+		return;
+	}
+
+	info = g_string_new("");
+	g_string_append_printf(info, "<b>%s:</b> %s%s%s<br>", _("Nick"), args[1],
+			       irc->whois.ircop ? _(" <i>(ircop)</i>") : "",
+			       irc->whois.identified ? _(" <i>(identified)</i>") : "");
+	if (irc->whois.away) {
+		g_string_append_printf(info, "<b>%s:</b> %s<br>", _("Away"), irc->whois.away);
+		g_free(irc->whois.away);
+	}
+	if (irc->whois.userhost) {
+		g_string_append_printf(info, "<b>%s:</b> %s<br>", _("Username"), irc->whois.userhost);
+		g_string_append_printf(info, "<b>%s:</b> %s<br>", _("Realname"), irc->whois.name);
+		g_free(irc->whois.userhost);
+		g_free(irc->whois.name);
+	}
+	if (irc->whois.server) {
+		g_string_append_printf(info, "<b>%s:</b> %s (%s)<br>", _("Server"), irc->whois.server, irc->whois.serverinfo);
+		g_free(irc->whois.server);
+		g_free(irc->whois.serverinfo);
+	}
+	if (irc->whois.channels) {
+		g_string_append_printf(info, "<b>%s:</b> %s<br>", _("Currently on"), irc->whois.channels);
+		g_free(irc->whois.channels);
+	}
+	if (irc->whois.idle) {
+		g_string_append_printf(info, _("<b>Idle for:</b> %d days, %02d:%02d:%02d<br>"),
+				       irc->whois.idle / 86400, (irc->whois.idle % 86400) / 3600,
+				       (irc->whois.idle % 3600) / 60, irc->whois.idle % 60);
+		g_string_append_printf(info, "<b>%s:</b> %s", _("Online since"), ctime(&irc->whois.signon));
+	}
+	if (!strcmp(irc->whois.nick, "Paco-Paco")) {
+		g_string_append_printf(info, _("<br><b>Defining adjective:</b> Glorious<br>"));
+	}
+
+	gc = gaim_account_get_connection(irc->account);
+	str = g_string_free(info, FALSE);
+	g_show_info_text(gc, irc->whois.nick, 2, str, NULL);
+	g_free(str);
+	memset(&irc->whois, 0, sizeof(irc->whois));
+}
+
+void irc_msg_topic(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *chan, *topic, *msg, *nick;
+	GaimConversation *convo;
+
+	if (!strcmp(name, "topic")) {
+		chan = args[0];
+		topic = args[1];
+	} else {
+		chan = args[1];
+		topic = args[2];
+	}
+
+	convo = gaim_find_conversation_with_account(chan, irc->account);
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got a topic for %s, which doesn't exist\n", chan);
+	}
+	gaim_chat_set_topic(GAIM_CHAT(convo), NULL, topic);
+	/* If this is an interactive update, print it out */
+	if (!strcmp(name, "topic")) {
+		nick = irc_mask_nick(from);
+		msg = g_strdup_printf(_("%s has changed the topic to: %s"), nick, topic);
+		g_free(nick);
+		gaim_chat_write(GAIM_CHAT(convo), from, msg, WFLAG_SYSTEM, time(NULL));
+		g_free(msg);
+	} else {
+		msg = g_strdup_printf(_("The topic for %s is: %s"), chan, topic);
+		gaim_chat_write(GAIM_CHAT(convo), "", msg, WFLAG_SYSTEM, time(NULL));
+		g_free(msg);
+	}
+}
+
+void irc_msg_unknown(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *buf;
+
+	if (!args || !args[1] || !gc)
+		return;
+
+	buf = g_strdup_printf(_("Unknown message '%s'"), args[1]);
+	gaim_notify_error(gc, _("Unknown message"), buf, _("Gaim has sent a message the IRC server did not understand."));
+	g_free(buf);
+}
+
+void irc_msg_names(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *names, *cur, *end, *tmp, *msg;
+	GaimConversation *convo;
+
+	if (!strcmp(name, "366")) {
+		convo = gaim_find_conversation_with_account(irc->nameconv ? irc->nameconv : args[1], irc->account);
+		if (!convo) {
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got a NAMES list for %s, which doesn't exist\n", args[2]);
+			g_string_free(irc->names, TRUE);
+			irc->names = NULL;
+			g_free(irc->nameconv);
+			irc->nameconv = NULL;
+			return;
+		}
+
+		names = cur = g_string_free(irc->names, FALSE);
+		irc->names = NULL;
+		if (irc->nameconv) {
+			msg = g_strdup_printf("Users on %s: %s", args[1], names);
+			if (gaim_conversation_get_type(convo) == GAIM_CONV_CHAT)
+				gaim_chat_write(GAIM_CHAT(convo), "", msg, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+			else
+				gaim_im_write(GAIM_IM(convo), "", msg, -1, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+			g_free(msg);
+			g_free(irc->nameconv);
+			irc->nameconv = NULL;
+		} else {
+			while (*cur) {
+				end = strchr(cur, ' ');
+				if (!end)
+					end = cur + strlen(cur);
+				if (*cur == '@' || *cur == '%' || *cur == '+')
+					cur++;
+				tmp = g_strndup(cur, end - cur);
+				gaim_chat_add_user(GAIM_CHAT(convo), tmp, NULL);
+				g_free(tmp);
+				cur = end;
+				if (*cur)
+					cur++;
+			}
+		}
+		g_free(names);
+	} else {
+		if (!irc->names)
+			irc->names = g_string_new("");
+
+		irc->names = g_string_append(irc->names, args[3]);
+	}
+}
+
+void irc_msg_motd(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+	if (!strcmp(name, "375")) {
+		gc = gaim_account_get_connection(irc->account);
+		if (gc)
+			gaim_connection_set_display_name(gc, args[0]);
+	}
+
+	if (!irc->motd)
+		irc->motd = g_string_new("");
+
+	g_string_append_printf(irc->motd, "%s<br>", args[1]);
+}
+
+void irc_msg_endmotd(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+
+	gc = gaim_account_get_connection(irc->account);
+	if (!gc)
+		return;
+
+	gaim_connection_set_state(gc, GAIM_CONNECTED);
+
+	irc_blist_timeout(irc);
+	irc->timer = g_timeout_add(45000, (GSourceFunc)irc_blist_timeout, (gpointer)irc);
+}
+
+void irc_msg_nonick(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+	GaimConversation *convo;
+
+	convo = gaim_find_conversation_with_account(args[1], irc->account);
+	if (convo) {
+		if (gaim_conversation_get_type(convo) == GAIM_CONV_CHAT) /* does this happen? */
+			gaim_chat_write(GAIM_CHAT(convo), args[1], _("no such channel"),
+					WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+		else
+			gaim_im_write(GAIM_IM(convo), args[1], _("User is not logged in"), -1,
+				      WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	} else {
+		if ((gc = gaim_account_get_connection(irc->account)) == NULL)
+			return;
+		gaim_notify_error(gc, NULL, _("No such nick or channel"), args[1]);
+	}
+
+	if (irc->whois.nick && !gaim_utf8_strcasecmp(irc->whois.nick, args[1])) {
+		g_free(irc->whois.nick);
+		irc->whois.nick = NULL;
+	}
+}
+
+void irc_msg_nosend(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc;
+	GaimConversation *convo;
+
+	convo = gaim_find_conversation_with_account(args[1], irc->account);
+	if (convo) {
+		gaim_chat_write(GAIM_CHAT(convo), args[1], args[2], WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	} else {
+		if ((gc = gaim_account_get_connection(irc->account)) == NULL)
+			return;
+		gaim_notify_error(gc, NULL, _("Could not send"), args[2]);
+	}
+}
+
+void irc_msg_notinchan(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConversation *convo = gaim_find_conversation_with_account(args[1], irc->account);
+
+	gaim_debug(GAIM_DEBUG_INFO, "irc", "We're apparently not in %s, but tried to use it\n", args[1]);
+	if (convo) {
+		/*g_slist_remove(irc->gc->buddy_chats, convo);
+		  gaim_conversation_set_account(convo, NULL);*/
+		gaim_chat_write(GAIM_CHAT(convo), args[1], args[2], WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	}
+}
+
+void irc_msg_notop(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConversation *convo;
+
+	if (!args || !args[1] || !args[2])
+		return;
+
+	convo = gaim_find_conversation_with_account(args[1], irc->account);
+	if (!convo)
+		return;
+
+	gaim_chat_write(GAIM_CHAT(convo), "", args[2], WFLAG_SYSTEM, time(NULL));
+}
+
+void irc_msg_invite(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	char *nick = irc_mask_nick(from);
+
+	if (!args || !args[1] || !gc) {
+		g_free(nick);
+		g_hash_table_destroy(components);
+		return;
+	}
+
+	g_hash_table_insert(components, strdup("channel"), strdup(args[1]));
+
+	serv_got_chat_invite(gc, args[1], nick, NULL, components);
+	g_free(nick);
+}
+
+void irc_msg_inviteonly(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *buf;
+
+	if (!args || !args[1] || !gc)
+		return;
+
+	buf = g_strdup_printf(_("Joining %s requires an invitation."), args[1]);
+	gaim_notify_error(gc, _("Invitation only"), _("Invitation only"), buf);
+	g_free(buf);
+}
+
+void irc_msg_ison(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char **nicks;
+	struct irc_buddy *ib;
+	int i;
+
+	if (!args || !args[1])
+		return;
+
+	nicks = g_strsplit(args[1], " ", -1);
+
+	for (i = 0; nicks[i]; i++) {
+		if ((ib = g_hash_table_lookup(irc->buddies, (gconstpointer)nicks[i])) == NULL) {
+			continue;
+		}
+		ib->flag = TRUE;
+	}
+
+	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_status, (gpointer)irc);
+}
+
+static void irc_buddy_status(char *name, struct irc_buddy *ib, struct irc_conn *irc)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	struct buddy *buddy = gaim_find_buddy(irc->account, name);
+
+	if (!gc || !buddy)
+		return;
+
+	if (ib->online && !ib->flag) {
+		serv_got_update(gc, buddy->name, 0, 0, 0, 0, 0);
+		ib->online = FALSE;
+	}
+
+	if (!ib->online && ib->flag) {
+		serv_got_update(gc, buddy->name, 1, 0, 0, 0, 0);
+		ib->online = TRUE;
+	}
+}
+
+void irc_msg_join(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GaimConversation *convo;
+	char *nick = irc_mask_nick(from), *userhost;
+	static int id = 1;
+
+	if (!gc) {
+		g_free(nick);
+		return;
+	}
+
+	if (!gaim_utf8_strcasecmp(nick, gaim_connection_get_display_name(gc))) {
+		/* We are joining a channel for the first time */
+		serv_got_joined_chat(gc, id++, args[0]);
+		g_free(nick);
+		return;
+	}
+
+	convo = gaim_find_conversation_with_account(args[0], irc->account);
+	if (convo == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "JOIN for %s failed\n", args[0]);
+		g_free(nick);
+		return;
+	}
+
+	userhost = irc_mask_userhost(from);
+	gaim_chat_add_user(GAIM_CHAT(convo), nick, userhost);
+	g_free(userhost);
+	g_free(nick);
+}
+
+void irc_msg_kick(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GaimConversation *convo = gaim_find_conversation_with_account(args[0], irc->account);
+	char *nick = irc_mask_nick(from), *buf;
+
+	if (!gc) {
+		g_free(nick);
+		return;
+	}
+
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Recieved a KICK for unknown channel %s\n", args[0]);
+		g_free(nick);
+		return;
+	}
+
+	if (!gaim_utf8_strcasecmp(gaim_connection_get_display_name(gc), args[1])) {
+		buf = g_strdup_printf(_("You have been kicked by %s: (%s)"), nick, args[2]);
+		gaim_chat_write(GAIM_CHAT(convo), args[0], buf, WFLAG_SYSTEM, time(NULL));
+		g_free(buf);
+		/*g_slist_remove(irc->gc->buddy_chats, convo);
+		  gaim_conversation_set_account(convo, NULL);*/
+		/*g_list_free(gaim_chat_get_users(GAIM_CHAT(convo)));
+		  gaim_chat_set_users(GAIM_CHAT(convo), NULL);*/
+	} else {
+		buf = g_strdup_printf(_("Kicked by %s (%s)"), nick, args[2]);
+		gaim_chat_remove_user(GAIM_CHAT(convo), args[1], buf);
+		g_free(buf);
+	}
+
+	g_free(nick);
+	return;
+}
+
+void irc_msg_mode(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConversation *convo;
+	char *nick = irc_mask_nick(from), *buf;
+
+	if (*args[0] == '#' || *args[0] == '&') {	/* Channel	*/
+		convo = gaim_find_conversation_with_account(args[0], irc->account);
+		if (!convo) {
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "MODE received for %s, which we are not in\n", args[0]);
+			g_free(nick);
+			return;
+		}
+		buf = g_strdup_printf(_("mode (%s %s) by %s"), args[1], args[2] ? args[2] : "", nick);
+		gaim_chat_write(GAIM_CHAT(convo), args[0], buf, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+		g_free(buf);
+	} else {					/* User		*/
+	}
+	g_free(nick);
+}
+
+void irc_msg_nick(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GSList *chats;
+	char *nick = irc_mask_nick(from);
+
+	if (!gc) {
+		g_free(nick);
+		return;
+	}
+	chats = gc->buddy_chats;
+
+	if (!gaim_utf8_strcasecmp(nick, gaim_connection_get_display_name(gc))) {
+		gaim_connection_set_display_name(gc, args[0]);
+	}
+
+	while (chats) {
+		GaimChat *chat = GAIM_CHAT(chats->data);
+		GList *users = gaim_chat_get_users(chat);
+
+		while (users) {
+			char *user = users->data;
+
+			if (!strcmp(nick, user)) {
+				gaim_chat_rename_user(chat, user, args[0]);
+				users = gaim_chat_get_users(chat);
+				break;
+			}
+			users = users->next;
+		}
+		chats = chats->next;
+	}
+	g_free(nick);
+}
+
+void irc_msg_nickused(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *newnick, *buf, *end;
+
+	if (!args || !args[1])
+		return;
+
+	newnick = strdup(args[1]);
+	end = newnick + strlen(newnick) - 1;
+	/* try three fallbacks */
+	if (*end == 2) *end = '3';
+	else if (*end == 1) *end = '2';
+	else *end = '1';
+
+	buf = irc_format(irc, "vn", "NICK", newnick);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+void irc_msg_notice(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *newargs[2];
+
+	newargs[0] = " notice ";	/* The spaces are magic, leave 'em in! */
+	newargs[1] = args[1];
+	irc_msg_privmsg(irc, name, from, newargs);
+}
+
+void irc_msg_part(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GaimConversation *convo;
+	char *nick, *msg;
+
+	if (!args || !args[0] || !args[1] || !gc)
+		return;
+
+	convo = gaim_find_conversation_with_account(args[0], irc->account);
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_INFO, "irc", "Got a PART on %s, which doesn't exist -- probably closed\n", args[0]);
+		return;
+	}
+
+	nick = irc_mask_nick(from);
+	if (!gaim_utf8_strcasecmp(nick, gaim_connection_get_display_name(gc))) {
+		msg = g_strdup_printf(_("You have parted the channel%s%s"), *args[1] ? ": " : "", args[1]);
+		gaim_chat_write(GAIM_CHAT(convo), args[0], msg, WFLAG_SYSTEM, time(NULL));
+		g_free(msg);
+	} else {
+		gaim_chat_remove_user(GAIM_CHAT(convo), nick, args[1]);
+	}
+	g_free(nick);
+}
+
+void irc_msg_ping(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	char *buf;
+	if (!args || !args[0])
+		return;
+
+	buf = irc_format(irc, "v:", "PONG", args[0]);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+void irc_msg_pong(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConversation *convo;
+	GaimConnection *gc;
+	char **parts, *msg;
+	time_t oldstamp;
+
+	if (!args || !args[1])
+		return;
+
+	parts = g_strsplit(args[1], " ", 2);
+
+	if (!parts[0] || !parts[1]) {
+		g_strfreev(parts);
+		return;
+	}
+
+	if (sscanf(parts[1], "%lu", &oldstamp) != 1) {
+		msg = g_strdup(_("Error: invalid PONG from server"));
+	} else {
+		msg = g_strdup_printf(_("PING reply -- Lag: %d seconds"), time(NULL) - oldstamp);
+	}
+
+	convo = gaim_find_conversation_with_account(parts[0], irc->account);
+	g_strfreev(parts);
+	if (convo) {
+		if (gaim_conversation_get_type (convo) == GAIM_CONV_CHAT)
+			gaim_chat_write(GAIM_CHAT(convo), "PONG", msg, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+		else
+			gaim_im_write(GAIM_IM(convo), "PONG", msg, -1, WFLAG_SYSTEM|WFLAG_NOLOG, time(NULL));
+	} else {
+		gc = gaim_account_get_connection(irc->account);
+		if (!gc) {
+			g_free(msg);
+			return;
+		}
+		gaim_notify_info(gc, NULL, "PONG", msg);
+	}
+	g_free(msg);
+}
+
+void irc_msg_privmsg(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	GaimConversation *convo;
+	char *nick = irc_mask_nick(from), *tmp, *msg;
+	int notice = 0;
+
+	if (!args || !args[0] || !args[1] || !gc) {
+		g_free(nick);
+		return;
+	}
+
+	notice = !strcmp(args[0], " notice ");
+	tmp = irc_parse_ctcp(irc, nick, args[0], args[1], notice);
+	if (!tmp) {
+		g_free(nick);
+		return;
+	}
+	msg = irc_mirc2html(tmp);
+	g_free(tmp);
+	if (notice) {
+		tmp = g_strdup_printf("(notice) %s", msg);
+		g_free(msg);
+		msg = tmp;
+	}
+
+	if (!gaim_utf8_strcasecmp(args[0], gaim_connection_get_display_name(gc))) {
+		serv_got_im(gc, nick, msg, 0, time(NULL), -1);
+	} else if (notice) {
+		serv_got_im(gc, nick, msg, 0, time(NULL), -1);
+	} else {
+		convo = gaim_find_conversation_with_account(args[0], irc->account);
+		if (convo)
+			serv_got_chat_in(gc, gaim_chat_get_id(GAIM_CHAT(convo)), nick, 0, msg, time(NULL));
+		else
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got a PRIVMSG on %s, which does not exist\n", args[0]);
+	}
+	g_free(msg);
+	g_free(nick);
+}
+
+void irc_msg_quit(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *data[2];
+
+	if (!args || !args[0] || !gc)
+		return;
+
+	data[0] = irc_mask_nick(from);
+	data[1] = args[0];
+	/* XXX this should have an API, I shouldn't grab this directly */
+	g_slist_foreach(gc->buddy_chats, (GFunc)irc_chat_remove_buddy, data);
+	g_free(data[0]);
+
+	return;
+}
+
+void irc_msg_wallops(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	GaimConnection *gc = gaim_account_get_connection(irc->account);
+	char *nick, *msg;
+
+	if (!args || !args[0] || !gc)
+		return;
+
+	nick = irc_mask_nick(from);
+	msg = g_strdup_printf (_("Wallops from %s"), nick);
+	g_free(nick);
+	gaim_notify_info(gc, NULL, msg, args[0]);
+	g_free(msg);
+}
+
+void irc_msg_ignore(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/irc/parse.c	Wed Jul 30 00:50:29 2003 +0000
@@ -0,0 +1,497 @@
+/**
+ * @file parse.c
+ * 
+ * gaim
+ *
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * 
+ * 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 "internal.h"
+
+#include "accountopt.h"
+#include "conversation.h"
+#include "notify.h"
+#include "debug.h"
+#include "irc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+static char *irc_send_convert(struct irc_conn *irc, const char *string);
+static char *irc_recv_convert(struct irc_conn *irc, const char *string);
+
+char *irc_mirc2html(const char *string);
+
+static void irc_parse_error_cb(struct irc_conn *irc, char *input);
+static void irc_default_cb(struct irc_conn *irc, char *input);
+
+static char *irc_mirc_colors[16] = {
+	"white", "black", "blue", "dark green", "red", "brown", "purple",
+		"orange", "yellow", "green", "teal", "cyan", "light blue",
+		"pink", "grey", "light grey" };
+
+/*typedef void (*IRCMsgCallback)(struct irc_conn *irc, char *from, char *name, char **args);*/
+static struct _irc_msg {
+	char *name;
+	char *format;
+	void (*cb)(struct irc_conn *irc, const char *name, const char *from, char **args);
+} _irc_msgs[] = {
+	{ "301", "nn:", irc_msg_away },		/* User is away			*/
+	{ "303", "n:", irc_msg_ison },		/* ISON reply			*/
+	{ "311", "nnvvv:", irc_msg_whois },	/* Whois user			*/
+	{ "312", "nnv:", irc_msg_whois },	/* Whois server			*/
+	{ "313", "nn:", irc_msg_whois },	/* Whois ircop			*/
+	{ "317", "nnvv", irc_msg_whois },	/* Whois idle			*/
+	{ "318", "nt:", irc_msg_endwhois },	/* End of WHOIS			*/
+	{ "319", "nn:", irc_msg_whois },	/* Whois channels		*/
+	{ "320", "nn:", irc_msg_whois },	/* Whois (fn ident)		*/
+	{ "324", "ncv:", irc_msg_chanmode },	/* Channel modes		*/
+	{ "331", "nc:",	irc_msg_topic },	/* No channel topic		*/
+	{ "332", "nc:", irc_msg_topic },	/* Channel topic		*/
+	{ "333", "*", irc_msg_ignore },		/* Topic setter stuff		*/
+	{ "353", "nvc:", irc_msg_names },	/* Names list			*/
+	{ "366", "nc:", irc_msg_names },	/* End of names			*/
+	{ "372", "n:", irc_msg_motd },		/* MOTD				*/
+	{ "375", "n:", irc_msg_motd },		/* Start MOTD			*/
+	{ "376", "n:", irc_msg_endmotd },	/* End of MOTD			*/
+	{ "401", "nt:", irc_msg_nonick },	/* No such nick/chan		*/
+	{ "404", "nt:", irc_msg_nosend },	/* Cannot send to chan		*/
+	{ "421", "nv:", irc_msg_unknown },	/* Unknown command		*/
+	{ "433", "vn:", irc_msg_nickused },	/* Nickname already in use	*/
+	{ "442", "nc:", irc_msg_notinchan },	/* Not in channel		*/
+	{ "473", "nc:", irc_msg_inviteonly },	/* Tried to join invite-only	*/
+	{ "474", "nc:", irc_msg_banned },	/* Banned from channel		*/
+	{ "482", "nc:", irc_msg_notop },	/* Need to be op to do that	*/
+	{ "501", "n:", irc_msg_badmode },	/* Unknown mode flag		*/
+	{ "invite", "n:", irc_msg_invite },	/* Invited			*/
+	{ "join", ":", irc_msg_join },		/* Joined a channel		*/
+	{ "kick", "cn:", irc_msg_kick },	/* KICK				*/
+	{ "mode", "tv:", irc_msg_mode },	/* MODE for channel		*/
+	{ "nick", ":", irc_msg_nick },		/* Nick change			*/
+	{ "notice", "t:", irc_msg_notice },	/* NOTICE recv			*/
+	{ "part", "c:", irc_msg_part },		/* Parted a channel		*/
+	{ "ping", ":", irc_msg_ping },		/* Received PING from server	*/
+	{ "pong", "v:", irc_msg_pong },		/* Received PONG from server	*/
+	{ "privmsg", "t:", irc_msg_privmsg },	/* Received private message	*/
+	{ "topic", "c:", irc_msg_topic },	/* TOPIC command		*/
+	{ "quit", ":", irc_msg_quit },		/* QUIT notice			*/
+	{ "wallops", ":", irc_msg_wallops },	/* WALLOPS command		*/
+	{ NULL, NULL, NULL }
+};
+
+static struct _irc_user_cmd {
+	char *name;
+	char *format;
+	IRCCmdCallback cb;
+} _irc_cmds[] = {
+	{ "away", ":", irc_cmd_away },
+	{ "deop", ":", irc_cmd_op },
+	{ "devoice", ":", irc_cmd_op },
+	{ "invite", ":", irc_cmd_invite },
+	{ "j", "cv", irc_cmd_join },
+	{ "join", "cv", irc_cmd_join },
+	{ "kick", "n:", irc_cmd_kick },
+	{ "me", ":", irc_cmd_ctcp_action },
+	{ "mode", ":", irc_cmd_mode },
+	{ "msg", "t:", irc_cmd_privmsg },
+	{ "names", "c", irc_cmd_names },
+	{ "nick", "n", irc_cmd_nick },
+	{ "op", ":", irc_cmd_op },
+	{ "operwall", ":", irc_cmd_wallops },
+	{ "part", "c:", irc_cmd_part },
+	{ "ping", "n", irc_cmd_ping },
+	{ "query", "n:", irc_cmd_query },
+	{ "quit", ":", irc_cmd_quit },
+	{ "quote", "*", irc_cmd_quote },
+	{ "remove", "n:", irc_cmd_remove },
+	{ "topic", ":", irc_cmd_topic },
+	{ "umode", ":", irc_cmd_mode },
+	{ "voice", ":", irc_cmd_op },
+	{ "wallops", ":", irc_cmd_wallops },
+	{ "whois", "n", irc_cmd_whois },
+	{ NULL, NULL }
+};
+
+static char *irc_send_convert(struct irc_conn *irc, const char *string)
+{
+	char *utf8;
+	GError *err = NULL;
+	
+	utf8 = g_convert(string, strlen(string), 
+			 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET),
+			 "UTF-8", NULL, NULL, &err);
+	if (err) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "send conversion error: %s\n", err->message);
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Sending raw, which probably isn't right\n");
+		utf8 = g_strdup(string);
+	}
+	
+	return utf8;
+}
+
+static char *irc_recv_convert(struct irc_conn *irc, const char *string)
+{
+	char *utf8;
+	GError *err = NULL;
+	
+	utf8 = g_convert(string, strlen(string), "UTF-8",
+			 gaim_account_get_string(irc->account, "encoding", IRC_DEFAULT_CHARSET),
+			 NULL, NULL, &err);
+	if (err) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "recv conversion error: %s\n", err->message);
+		utf8 = g_strdup(_("(There was an error converting this message.  Check the 'Encoding' option in the Account Editor)"));
+	}
+	
+	return utf8;
+}
+
+/* XXX tag closings are not necessarily correctly nested here!  If we
+ *     get a ^O or reach the end of the string and there are open
+ *     tags, they are closed in a fixed order ... this means, for
+ *     example, you might see <FONT COLOR="blue">some text <B>with
+ *     various attributes</FONT></B> (notice that B and FONT overlap
+ *     and are not cleanly nested).  This is imminently fixable but
+ *     I am not fixing it right now.
+ */
+char *irc_mirc2html(const char *string)
+{
+	const char *cur, *end;
+	char fg[3] = "\0\0", bg[3] = "\0\0";
+	int fgnum, bgnum;
+	int font = 0, bold = 0;
+	GString *decoded = g_string_sized_new(strlen(string));
+
+	cur = string;
+	do {
+		end = strpbrk(cur, "\002\003\007\017\026");
+
+		decoded = g_string_append_len(decoded, cur, end ? end - cur : strlen(cur));
+		cur = end ? end : cur + strlen(cur);
+
+		switch (*cur) {
+		case '\002':
+			cur++;
+			if (!bold) {
+				decoded = g_string_append(decoded, "<B>");
+				bold = TRUE;
+			} else {
+				decoded = g_string_append(decoded, "</B>");
+				bold = FALSE;
+			}
+			break;
+		case '\003':
+			cur++;
+			fg[0] = fg[1] = bg[0] = bg[1] = '\0';
+			if (isdigit(*cur))
+				fg[0] = *cur++;
+			if (isdigit(*cur))
+				fg[1] = *cur++;
+			if (*cur == ',') {
+				cur++;
+				if (isdigit(*cur))
+					bg[0] = *cur++;
+				if (isdigit(*cur))
+					bg[1] = *cur++;
+			}
+			if (font) {
+				decoded = g_string_append(decoded, "</FONT>");
+				font = FALSE;
+			}
+
+			if (fg[0]) {
+				fgnum = atoi(fg);
+				if (fgnum < 0 || fgnum > 15)
+					continue;
+				font = TRUE;
+				g_string_append_printf(decoded, "<FONT COLOR=\"%s\"", irc_mirc_colors[fgnum]);
+				if (bg[0]) {
+					bgnum = atoi(bg);
+					if (bgnum >= 0 && bgnum < 16)
+						g_string_append_printf(decoded, " BACK=\"%s\"", irc_mirc_colors[bgnum]);
+				}
+				decoded = g_string_append_c(decoded, '>');
+			}
+			break;
+		case '\007':
+		case '\026':
+			cur++;
+			break;
+		case '\017':
+			cur++;
+			/* fallthrough */
+		case '\000':
+			if (bold)
+				decoded = g_string_append(decoded, "</BOLD>");
+			if (font)
+				decoded = g_string_append(decoded, "</FONT>");
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "Unexpected mIRC formatting character %d\n", *cur);
+		}
+	} while (*cur);
+
+	return g_string_free(decoded, FALSE);
+}
+
+char *irc_parse_ctcp(struct irc_conn *irc, const char *from, const char *to, const char *msg, int notice)
+{
+	GaimConnection *gc;
+	const char *cur = msg + 1;
+	char *buf, *ctcp;
+	time_t timestamp;
+
+	if (msg[0] != '\001' || msg[strlen(msg) - 1] != '\001')
+		return g_strdup(msg);
+
+	if (!strncmp(cur, "ACTION ", 7)) {
+		cur += 7;
+		buf = g_strdup_printf("/me %s", cur);
+		buf[strlen(buf) - 1] = '\0';
+		return buf;
+	} else if (!strncmp(cur, "PING ", 5)) {
+		if (notice) { /* reply */
+			sscanf(cur, "PING %lu", &timestamp);
+			gc = gaim_account_get_connection(irc->account);
+			if (!gc)
+				return NULL;
+			buf = g_strdup_printf(_("Reply time from %s: %d seconds"), from, time(NULL) - timestamp);
+			gaim_notify_info(gc, _("PONG"), _("CTCP PING reply"), buf);
+			g_free(buf);
+			return NULL;
+		} else {
+			buf = irc_format(irc, "vt:", "NOTICE", from, msg);
+			irc_send(irc, buf);
+			g_free(buf);
+			gc = gaim_account_get_connection(irc->account);
+		}
+	} else if (!strncmp(cur, "VERSION", 7) && !notice) {
+		buf = irc_format(irc, "vt:", "NOTICE", from, "\001VERSION Gaim IRC\001");
+		irc_send(irc, buf);
+		g_free(buf);
+	}
+
+	ctcp = g_strdup(msg + 1);
+	ctcp[strlen(ctcp) - 1] = '\0';
+	buf = g_strdup_printf("Received CTCP '%s' (to %s) from %s", ctcp, to, from);
+	g_free(ctcp);
+	return buf;
+}
+
+void irc_msg_table_build(struct irc_conn *irc)
+{
+	int i;
+
+	if (!irc || !irc->msgs) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a message table on a bogus structure\n");
+		return;
+	}
+
+	for (i = 0; _irc_msgs[i].name; i++) {
+		g_hash_table_insert(irc->msgs, (gpointer)_irc_msgs[i].name, (gpointer)&_irc_msgs[i]);
+	}
+}
+
+void irc_cmd_table_build(struct irc_conn *irc)
+{
+	int i;
+
+	if (!irc || !irc->cmds) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Attempt to build a command table on a bogus structure\n");
+		return;
+	}
+
+	for (i = 0; _irc_cmds[i].name ; i++) {
+		g_hash_table_insert(irc->cmds, (gpointer)_irc_cmds[i].name, (gpointer)&_irc_cmds[i]);
+	}
+}
+
+char *irc_format(struct irc_conn *irc, const char *format, ...)
+{
+	GString *string = g_string_new("");
+	char *tok, *tmp;
+	const char *cur;
+	va_list ap;
+
+	va_start(ap, format);
+	for (cur = format; *cur; cur++) {
+		if (cur != format)
+			g_string_append_c(string, ' ');
+
+		tok = va_arg(ap, char *);
+		switch (*cur) {
+		case 'v':
+			g_string_append(string, tok);
+			break;
+		case ':':
+			g_string_append_c(string, ':');
+			/* no break! */
+		case 't':
+		case 'n':
+		case 'c':
+			tmp = irc_send_convert(irc, tok);
+			g_string_append(string, tmp);
+			g_free(tmp);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "Invalid format character '%c'\n", *cur);
+			break;
+		}
+	}
+	va_end(ap);
+	g_string_append(string, "\r\n");
+	return (g_string_free(string, FALSE));
+}
+
+void irc_parse_msg(struct irc_conn *irc, char *input)
+{
+	struct _irc_msg *msgent;
+	char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg;
+	int i;
+
+	if (!strncmp(input, "PING ", 5)) {
+		msg = irc_format(irc, "vv", "PONG", input + 5);
+		irc_send(irc, msg);
+		g_free(msg);
+		return;
+	} else if (!strncmp(input, "ERROR ", 6)) {
+		gaim_connection_error(gaim_account_get_connection(irc->account), _("Disconnected"));
+		return;
+	}
+
+	if (input[0] != ':' || (cur = strchr(input, ' ')) == NULL) {
+		irc_parse_error_cb(irc, input);
+		return;
+	}
+
+	from = g_strndup(&input[1], cur - &input[1]);
+	cur++;
+	end = strchr(cur, ' ');
+	if (!end)
+		end = cur + strlen(cur);
+
+	tmp = g_strndup(cur, end - cur);
+	msgname = g_ascii_strdown(tmp, -1);
+	g_free(tmp);
+
+	if ((msgent = g_hash_table_lookup(irc->msgs, msgname)) == NULL) {
+		irc_msg_default(irc, "", from, &input);
+		g_free(msgname);
+		g_free(from);
+		return;
+	}
+	g_free(msgname);
+
+	args = g_new0(char *, strlen(msgent->format));
+	for (cur = end, fmt = msgent->format, i = 0; fmt[i] && *cur++; i++) {
+		switch (fmt[i]) {
+		case 'v':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			args[i] = g_strndup(cur, end - cur);
+			cur += end - cur;
+			break;
+		case 't':
+		case 'n':
+		case 'c':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			tmp = g_strndup(cur, end - cur);
+			args[i] = irc_recv_convert(irc, tmp);
+			g_free(tmp);
+			cur += end - cur;
+			break;
+		case ':':
+			if (*cur == ':') cur++;
+			args[i] = irc_recv_convert(irc, cur);
+			cur = cur + strlen(cur);
+			break;
+		case '*':
+			args[i] = g_strdup(cur);
+			cur = cur + strlen(cur);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid message format character '%c'\n", fmt[i]);
+			break;
+		}
+	}
+	(msgent->cb)(irc, msgent->name, from, args);
+	for (i = 0; i < strlen(msgent->format); i++) {
+		g_free(args[i]);
+	}
+	g_free(args);
+	g_free(from);
+}
+
+int irc_parse_cmd(struct irc_conn *irc, const char *target, const char *cmdstr)
+{
+	const char *cur, *end, *fmt; 
+	char *tmp, *cmd, **args;
+	struct _irc_user_cmd *cmdent;
+	int i, ret;
+
+	cur = cmdstr;
+	end = strchr(cmdstr, ' ');
+	if (!end)
+		end = cur + strlen(cur);
+
+	tmp = g_strndup(cur, end - cur);
+	cmd = g_utf8_strdown(tmp, -1);
+	g_free(tmp);
+
+	if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL) {
+		ret = irc_cmd_default(irc, cmd, target, &cmdstr);
+		g_free(cmd);
+		return ret;
+	}
+
+	args = g_new0(char *, strlen(cmdent->format));
+	for (cur = end, fmt = cmdent->format, i = 0; fmt[i] && *cur++; i++) {
+		switch (fmt[i]) {
+		case 'v':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			args[i] = g_strndup(cur, end - cur);
+			cur += end - cur;
+			break;
+		case 't':
+		case 'n':
+		case 'c':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			args[i] = g_strndup(cur, end - cur);
+			cur += end - cur;
+			break;
+		case ':':
+		case '*':
+			args[i] = g_strdup(cur);
+			cur = cur + strlen(cur);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "irc", "invalid command format character '%c'\n", fmt[i]);
+			break;
+		}
+	}
+	ret = (cmdent->cb)(irc, cmd, target, (const char **)args);
+	for (i = 0; i < strlen(cmdent->format); i++)
+		g_free(args[i]);
+	g_free(args);
+
+	g_free(cmd);
+	return ret;
+}
+
+static void irc_parse_error_cb(struct irc_conn *irc, char *input)
+{
+	gaim_debug(GAIM_DEBUG_WARNING, "irc", "Unrecognized string: %s\n", input);
+}