changeset 9130:933a19e3a6b3

[gaim-migrate @ 9908] This puts the core in charge of irc-style /commands, which is way cool. Tim did most of the work, I've just been keeping it in sync with CVS, and slowly adding more commands to jabber. committer: Tailor Script <tailor@pidgin.im>
author Nathan Walp <nwalp@pidgin.im>
date Sun, 30 May 2004 19:34:21 +0000
parents 3e94a77ee0c7
children 4ae763960140
files src/Makefile.am src/cmds.c src/cmds.h src/gtkconv.c src/gtkprefs.c src/protocols/irc/cmds.c src/protocols/irc/irc.c src/protocols/irc/irc.h src/protocols/irc/parse.c src/protocols/jabber/chat.c src/protocols/jabber/chat.h src/protocols/jabber/jabber.c src/protocols/jabber/message.c
diffstat 13 files changed, 739 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile.am	Sun May 30 19:30:14 2004 +0000
+++ b/src/Makefile.am	Sun May 30 19:34:21 2004 +0000
@@ -67,6 +67,8 @@
 	blist.h \
 	buddyicon.c \
 	buddyicon.h \
+	cmds.c \
+	cmds.h \
 	connection.c \
 	connection.h \
 	conversation.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cmds.c	Sun May 30 19:34:21 2004 +0000
@@ -0,0 +1,316 @@
+/**
+ * @file cmds.c Commands API
+ * @ingroup core
+ *
+ * Copyright (C) 2003-2004 Timothy Ringenbach <omarvo@hotmail.com
+ *
+ * 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 <string.h>
+
+#include "account.h"
+#include "cmds.h"
+
+static GList *cmds = NULL;
+static guint next_id = 1;
+
+typedef struct _GaimCmd {
+	GaimCmdId id;
+	gchar *cmd;
+	gchar *args;
+	GaimCmdPriority priority;
+	GaimCmdFlag flags;
+	gchar *prpl_id;
+	GaimCmdFunc func;
+	gchar *help;
+} GaimCmd;
+
+
+static gint cmds_compare_func(const GaimCmd *a, const GaimCmd *b)
+{
+	if (a->id > b->id)
+		return -1;
+	else if (a->id < b->id)
+		return 1;
+	else return 0;
+}
+
+GaimCmdId *gaim_cmd_register(const gchar *cmd, const gchar *args, GaimCmdPriority p, GaimCmdFlag f,
+                             const gchar *prpl_id, GaimCmdFunc func, const gchar *helpstr)
+{
+	GaimCmdId *id;
+	GaimCmd *c;
+
+	g_return_val_if_fail(cmd != NULL && *cmd != '\0', NULL);
+	g_return_val_if_fail(args != NULL, NULL);
+	g_return_val_if_fail(func != NULL, NULL);
+
+	id = g_new(GaimCmdId, 1);
+	*id = next_id++;
+
+	c = g_new0(GaimCmd, 1);
+	c->id = *id;
+	c->cmd = g_strdup(cmd);
+	c->args = g_strdup(args);
+	c->priority = p;
+	c->flags = f;
+	c->prpl_id = prpl_id ? g_strdup(prpl_id) : NULL;
+	c->func = func;
+	c->help = helpstr ? g_strdup(helpstr) : NULL;
+
+	cmds = g_list_insert_sorted(cmds, c, (GCompareFunc)cmds_compare_func);
+
+	return id;
+}
+
+static void gaim_cmd_free(GaimCmd *c)
+{
+	g_free(c->cmd);
+	g_free(c->args);
+	if (c->prpl_id)
+		g_free(c->prpl_id);
+	if (c->help)
+		g_free(c->help);
+
+	g_free(c);
+}
+
+void gaim_cmd_unregister(GaimCmdId *id)
+{
+	GaimCmd *c;
+	GList *l;
+
+	for (l = cmds; l; l = l->next) {
+		c = l->data;
+
+		if (c->id == *id) {
+			cmds = g_list_remove(cmds, c);
+			gaim_cmd_free(c);
+			g_free(id);
+			return;
+		}
+	}
+}
+
+static gboolean gaim_cmd_parse_args(GaimCmd *cmd, const gchar *s, gchar ***args)
+{
+	int i;
+	const char *end, *cur;
+
+	*args = g_new0(char *, strlen(cmd->args) + 1);
+
+	cur = s;
+
+	for (i = 0; cmd->args[i]; i++) {
+		if (!*cur)
+			return (cmd->flags & GAIM_CMD_FLAG_ALLOW_WRONG_ARGS);
+
+		switch (cmd->args[i]) {
+		case 'w':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			(*args)[i] = g_strndup(cur, end - cur);
+			cur += end - cur;
+			break;
+		case 'W':
+			if (!(end = strchr(cur, ' '))) end = cur + strlen(cur);
+			(*args)[i] = g_strndup(cur, end - cur); /* XXX apply default formatting here */
+			cur += end - cur;
+			break;
+		case 's':
+			(*args)[i] = g_strdup(cur);
+			cur = cur + strlen(cur);
+			break;
+		case 'S':
+			(*args)[i] = g_strdup(cur); /* XXX apply default formatting here */
+			cur = cur + strlen(cur);
+			break;
+		}
+	}
+
+	if (*cur)
+		return (cmd->flags & GAIM_CMD_FLAG_ALLOW_WRONG_ARGS);
+
+	return TRUE;
+}
+
+static void gaim_cmd_free_args(gchar **args)
+{
+	int i;
+
+	for (i = 0; args[i]; i++)
+		g_free(args[i]);
+	g_free(args);
+}
+
+GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmdline, gchar **error)
+{
+	GaimCmd *c;
+	GList *l;
+	gchar *err = NULL;
+	gboolean is_im;
+	gboolean found = FALSE, tried_cmd = FALSE, right_type = FALSE, right_prpl = FALSE;
+	const gchar *prpl_id;
+	gchar **args = NULL;
+	gchar *cmd, *rest;
+	GaimCmdRet ret = GAIM_CMD_RET_CONTINUE;
+
+	error = NULL;
+	prpl_id = gaim_account_get_protocol_id(gaim_conversation_get_account(conv));
+
+	if (gaim_conversation_get_type(conv) == GAIM_CONV_IM)
+		is_im = TRUE;
+	else if (gaim_conversation_get_type(conv) == GAIM_CONV_CHAT)
+		is_im = FALSE;
+	else
+		return GAIM_CMD_STATUS_FAILED;
+
+	rest = strchr(cmdline, ' ');
+	if (rest) {
+		cmd = g_strndup(cmdline, rest - cmdline);
+		rest++;
+	} else {
+		cmd = g_strdup(cmdline);
+		rest = "";
+	}
+
+	for (l = cmds; l; l = l->next) {
+		c = l->data;
+
+		if (strcmp(c->cmd, cmd) != 0)
+			continue;
+
+		found = TRUE;
+
+		if (is_im)
+			if (!(c->flags & GAIM_CMD_FLAG_IM))
+				continue;
+		if (!is_im)
+			if (!(c->flags & GAIM_CMD_FLAG_CHAT))
+				continue;
+
+		right_type = TRUE;
+
+		if ((c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id &&
+		    (strcmp(c->prpl_id, prpl_id) != 0))
+			continue;
+
+		right_prpl = TRUE;
+
+		/* this checks the allow bad args flag for us */
+		if (!gaim_cmd_parse_args(c, rest, &args)) {
+			gaim_cmd_free_args(args);
+			args = NULL;
+			continue;
+		}
+
+		tried_cmd = TRUE;
+		ret = c->func(conv, cmd, args, &err);
+		if (ret == GAIM_CMD_RET_CONTINUE) {
+			if (err)
+				g_free(err);
+			gaim_cmd_free_args(args);
+			args = NULL;
+			continue;
+		} else {
+			break;
+		}
+
+	}
+
+	if (args)
+		gaim_cmd_free_args(args);
+	g_free(cmd);
+
+	if (!found)
+		return GAIM_CMD_STATUS_NOT_FOUND;
+
+	if (!right_type)
+		return GAIM_CMD_STATUS_WRONG_TYPE;
+	if (!right_prpl)
+		return GAIM_CMD_STATUS_WRONG_PRPL;
+	if (!tried_cmd)
+		return GAIM_CMD_STATUS_WRONG_ARGS;
+
+	if (ret == GAIM_CMD_RET_OK) {
+		return GAIM_CMD_STATUS_OK;
+	} else {
+		*error = err;
+		if (ret == GAIM_CMD_RET_CONTINUE)
+			return GAIM_CMD_STATUS_NOT_FOUND;
+		else
+			return GAIM_CMD_STATUS_FAILED;
+	}
+
+}
+
+
+GList *gaim_cmd_list(GaimConversation *conv)
+{
+	GList *ret = NULL;
+	GaimCmd *c;
+	GList *l;
+
+	for (l = cmds; l; l = l->next) {
+		c = l->data;
+
+		if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_IM))
+			if (!(c->flags & GAIM_CMD_FLAG_IM))
+				continue;
+		if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_CHAT))
+			if (!(c->flags & GAIM_CMD_FLAG_CHAT))
+				continue;
+
+		if (conv && (c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id &&
+		    (strcmp(c->prpl_id, gaim_account_get_protocol_id(gaim_conversation_get_account(conv))) != 0))
+			continue;
+
+		ret = g_list_append(ret, c->cmd);
+	}
+
+	return ret;
+}
+
+
+GList *gaim_cmd_help(GaimConversation *conv, const gchar *cmd)
+{
+	GList *ret = NULL;
+	GaimCmd *c;
+	GList *l;
+
+	for (l = cmds; l; l = l->next) {
+		c = l->data;
+
+		if (cmd && (strcmp(cmd, c->cmd) != 0))
+			continue;
+
+		if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_IM))
+			if (!(c->flags & GAIM_CMD_FLAG_IM))
+				continue;
+		if (conv && (gaim_conversation_get_type(conv) == GAIM_CONV_CHAT))
+			if (!(c->flags & GAIM_CMD_FLAG_CHAT))
+				continue;
+
+		if (conv && (c->flags & GAIM_CMD_FLAG_PRPL_ONLY) && c->prpl_id &&
+		    (strcmp(c->prpl_id, gaim_account_get_protocol_id(gaim_conversation_get_account(conv))) != 0))
+			continue;
+
+		ret = g_list_append(ret, c->help);
+	}
+
+	return ret;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cmds.h	Sun May 30 19:34:21 2004 +0000
@@ -0,0 +1,183 @@
+/**
+ * @file cmds.h Commands API
+ * @ingroup core
+ *
+ * Copyright (C) 2003 Timothy Ringenbach <omarvo@hotmail.com
+ *
+ * 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_CMDS_H_
+#define _GAIM_CMDS_H
+
+#include "conversation.h"
+
+/**************************************************************************
+ * @name Structures
+ **************************************************************************/
+/*@{*/
+
+typedef enum   _GaimCmdPriority GaimCmdPriority;
+typedef enum   _GaimCmdFlag     GaimCmdFlag;
+typedef enum _GaimCmdStatus     GaimCmdStatus;
+typedef enum _GaimCmdRet        GaimCmdRet;
+
+enum _GaimCmdStatus {
+	GAIM_CMD_STATUS_OK,
+	GAIM_CMD_STATUS_FAILED,
+	GAIM_CMD_STATUS_NOT_FOUND,
+	GAIM_CMD_STATUS_WRONG_ARGS,
+	GAIM_CMD_STATUS_WRONG_PRPL,
+	GAIM_CMD_STATUS_WRONG_TYPE,
+};
+
+enum _GaimCmdRet {
+	GAIM_CMD_RET_OK,       /**< Everything's okay. Don't look for another command to call. */
+	GAIM_CMD_RET_FAILED,   /**< The command failed, but stop looking.*/
+	GAIM_CMD_RET_CONTINUE, /**< Continue, looking for other commands with the same name to call. */
+};
+
+typedef GaimCmdRet (*GaimCmdFunc)(GaimConversation *, const gchar *cmd, gchar **args, gchar **error);
+typedef guint GaimCmdId;
+
+enum _GaimCmdPriority {
+	GAIM_CMD_P_VERY_LOW = -1000,
+	GAIM_CMD_P_LOW      =     0,
+	GAIM_CMD_P_DEFAULT  =  1000,
+	GAIM_CMD_P_PRPL     =  2000,
+	GAIM_CMD_P_PLUGIN   =  3000,
+	GAIM_CMD_P_ALIAS    =  4000,
+	GAIM_CMD_P_HIGH     =  5000,
+	GAIM_CMD_P_VERYHIGH =  6000,
+};
+
+enum _GaimCmdFlag {
+	GAIM_CMD_FLAG_IM               = 0x01,
+	GAIM_CMD_FLAG_CHAT             = 0x02,
+	GAIM_CMD_FLAG_PRPL_ONLY         = 0x04,
+	GAIM_CMD_FLAG_ALLOW_WRONG_ARGS = 0x08,
+};
+
+
+/*@}*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************
+ * @name Commands API
+ **************************************************************************/
+/*@{*/
+
+/**
+ * Register a new command with the core.
+ *
+ * The command will only happen if commands are enabled,
+ * which is a UI pref. UIs don't have to support commands at all.
+ *
+ * @param cmd The command. This should be a UTF8 (or ASCII) string, with no spaces
+ *            or other white space.
+ * @param args This tells Gaim how to parse the arguments to the command for you.
+ *             If what the user types doesn't match, Gaim will keep looking for another
+ *             command, unless the flag @c GAIM_CMD_FLAG_ALLOW_WRONG_ARGS is passed in f.
+ *             This string contains no whitespace, and uses a single character for each argument.
+ *             The recognized characters are:
+ *               'w' Matches a single word.
+ *               'W' Matches a single word, and applies the default formatting to it.
+ *               's' Matches the rest of the arguments after this point, as a single string.
+ *               'S' Same as 's' but applies the default formatting to the matched string.
+ *             If args is the empty string, then the command accepts no arguments.
+ *             The args passed to callback func will be a @c NULL terminated array of null
+ *             terminated strings, and will always match the number of arguments asked for,
+ *             unless GAIM_CMD_FLAG_ALLOW_WRONG_ARGS is passed.
+ * @param p This is the priority. Higher priority commands will be run first, and usually the
+ *          first command will stop any others from being called.
+ * @param f These are the flags. You need to at least pass one of GAIM_CMD_FLAG_IM or
+ *          GAIM_CMD_FLAG_CHAT (can may pass both) in order for the command to ever actually
+ *          be called.
+ * @param prpl_id This is the prpl's id string. This is only meaningful is the proper flag is set.
+ * @param func This is the function to call when someone enters this command.
+ * @param helpstr This is a string describing how to use the command.
+ * @return A pointer to a GaimCmdId. This is only used for calling gaim_cmd_unregister, which frees it.
+ *         Returns @c NULL on failure.
+ */
+GaimCmdId *gaim_cmd_register(const gchar *cmd, const gchar *args, GaimCmdPriority p, GaimCmdFlag f,
+                             const gchar *prpl_id, GaimCmdFunc func, const gchar *helpstr);
+
+/**
+ * Unregister a command with the core.
+ *
+ * All registered commands must be unregistered, if they're registered by a plugin
+ * or something else that might go away. Normally this is called when the plugin
+ * unloads itself.
+ *
+ * @param id The GaimCmdId to unregister. It is freed after being unregistered.
+ */
+void gaim_cmd_unregister(GaimCmdId *id);
+
+/**
+ * Do a command.
+ *
+ * Normally the UI calls this to perform a command. This might also be useful
+ * if aliases are ever implemented.
+ *
+ * @param conv The conversation the command was typed in.
+ * @param cmd The command the user typed (including all arguments) as a single string.
+ *            The caller doesn't have to do any parsing, except removing the command
+ *            prefix, which the core has no knowledge of. cmd should not contain the
+ *            default formatting, but should contain any user supplied formatting.
+ * @param errormsg If the command failed and errormsg is not NULL, it is filled in with
+ *                 the appropriate error message. It should be freed by the caller with
+ *                 g_free().
+ * @return A GaimCmdStatus indicated if the command succeeded or failed.
+ */
+GaimCmdStatus gaim_cmd_do_command(GaimConversation *conv, const gchar *cmd, gchar **errormsg);
+
+/**
+ * List registered commands.
+ *
+ * Returns a GList (which must be freed by the caller) of all commands
+ * that are valid in the context of conv, or all commands, if conv is
+ * @c NULL. Don't keep this list around past the main loop, or anything else
+ * that might unregister a command, as the char*'s used get freed then.
+ *
+ * @param conv The conversation, or @c NULL.
+ * @return A GList of const char*, which must be freed with g_list_free().
+ */
+GList *gaim_cmd_list(GaimConversation *conv);
+
+/**
+ * Get the help string for a command.
+ *
+ * Returns the help strings for a given command in the form of a GList,
+ * one node for each matching command.
+ *
+ * @param conv The conversation, or @c NULL for no context.
+ * @param cmd The command. No wildcards accepted, but returns help for all
+ *            commands if @c NULL.
+ * @return A GList of const char*s, which is the help string
+ *         for that command.
+ */
+GList *gaim_cmd_help(GaimConversation *conv, const gchar *cmd);
+
+/*@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GAIM_CMDS_H_ */
--- a/src/gtkconv.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/gtkconv.c	Sun May 30 19:34:21 2004 +0000
@@ -36,6 +36,7 @@
 
 #include <gdk/gdkkeysyms.h>
 
+#include "cmds.h"
 #include "debug.h"
 #include "imgstore.h"
 #include "log.h"
@@ -334,16 +335,75 @@
 			gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), FALSE);
 	}
 }
+
+static const char *
+gaim_gtk_get_cmd_prefix(void)
+{
+	return "/";
+}
+
 static void
 send_cb(GtkWidget *widget, GaimConversation *conv)
 {
 	GaimGtkConversation *gtkconv;
+	char *cmd;
+	const char *prefix;
 	GaimAccount *account;
 	GaimConnection *gc;
 	char *buf, *clean;
 
 	gtkconv = GAIM_GTK_CONVERSATION(conv);
 	account = gaim_conversation_get_account(conv);
+	prefix = gaim_gtk_get_cmd_prefix();
+
+	if(gaim_prefs_get_bool("/gaim/gtk/conversations/enable_commands")) {
+		cmd = gtk_imhtml_get_text(GTK_IMHTML(gtkconv->entry), NULL, NULL);
+		if(cmd && (strncmp(cmd, prefix, strlen(prefix)) == 0)) {
+			GaimCmdStatus status;
+			char *error, *cmdline;
+
+			cmdline = cmd + strlen(prefix);
+			status = gaim_cmd_do_command(conv, cmdline, &error);
+			g_free(cmd);
+
+			gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry));
+			default_formatize(conv);
+
+			switch(status) {
+				case GAIM_CMD_STATUS_OK:
+					return;
+				case GAIM_CMD_STATUS_NOT_FOUND:
+					gaim_conversation_write(conv, "", _("No such command"),
+							GAIM_MESSAGE_SYSTEM, time(NULL));
+					return;
+				case GAIM_CMD_STATUS_WRONG_ARGS:
+					gaim_conversation_write(conv, "", _("Syntax error"),
+							GAIM_MESSAGE_SYSTEM, time(NULL));
+					return;
+				case GAIM_CMD_STATUS_FAILED:
+					gaim_conversation_write(conv, "", error ? error : _("Command failed"),
+							GAIM_MESSAGE_SYSTEM, time(NULL));
+					if(error)
+						g_free(error);
+					return;
+				case GAIM_CMD_STATUS_WRONG_TYPE:
+					if(gaim_conversation_get_type(conv) == GAIM_CONV_IM)
+						gaim_conversation_write(conv, "", _("That command only works in Chats, not IMs."),
+								GAIM_MESSAGE_SYSTEM, time(NULL));
+					else
+						gaim_conversation_write(conv, "", _("That command only works in IMs, not Chats."),
+								GAIM_MESSAGE_SYSTEM, time(NULL));
+					return;
+				case GAIM_CMD_STATUS_WRONG_PRPL:
+					gaim_conversation_write(conv, "", _("That command doesn't work on this protocol."),
+							GAIM_MESSAGE_SYSTEM, time(NULL));
+					return;
+			}
+		}
+
+		g_free(cmd);
+	}
+
 
 	if (!gaim_account_is_connected(account))
 		return;
@@ -5815,6 +5875,8 @@
 	gaim_prefs_add_bool("/gaim/gtk/conversations/html_shortcuts", FALSE);
 	gaim_prefs_add_bool("/gaim/gtk/conversations/smiley_shortcuts", FALSE);
 	gaim_prefs_add_bool("/gaim/gtk/conversations/show_formatting_toolbar", TRUE);
+	gaim_prefs_add_bool("/gaim/gtk/conversations/enable_commands", TRUE);
+
 	gaim_prefs_add_string("/gaim/gtk/conversations/placement", "last");
 	gaim_prefs_add_int("/gaim/gtk/conversations/placement_number", 1);
 	gaim_prefs_add_string("/gaim/gtk/conversations/bgcolor", "");
--- a/src/gtkprefs.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/gtkprefs.c	Sun May 30 19:34:21 2004 +0000
@@ -993,6 +993,9 @@
 	gaim_gtk_prefs_checkbox(_("Show a_liases in tabs/titles"),
 			"/core/conversations/use_alias_for_title", vbox);
 
+	gaim_gtk_prefs_checkbox(_("Enable _Commands"),
+			"/gaim/gtk/conversations/enable_commands", vbox);
+
 	vbox = gaim_gtk_make_frame (ret, _("Tab Options"));
 
 	tabs_checkbox = gaim_gtk_prefs_checkbox(_("Show IMs and chats in _tabbed windows"),
--- a/src/protocols/irc/cmds.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/irc/cmds.c	Sun May 30 19:34:21 2004 +0000
@@ -111,13 +111,17 @@
 	g_free(newargs);
 
 	convo = gaim_find_conversation_with_account(target, irc->account);
-	if (convo && gaim_conversation_get_type(convo) == GAIM_CONV_CHAT) {
+	if (convo) {
 		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_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
-				 gaim_connection_get_display_name(gc),
-				 0, action, time(NULL));
+		if (gaim_conversation_get_type(convo) == GAIM_CONV_CHAT)
+			serv_got_chat_in(gc, gaim_conv_chat_get_id(GAIM_CONV_CHAT(convo)),
+			         	 gaim_connection_get_display_name(gc),
+				         0, action, time(NULL));
+		else
+			gaim_conv_im_write(GAIM_CONV_IM(convo), gaim_connection_get_display_name(gc),
+			                  action, 0, time(NULL));
 		g_free(action);
 	}
 
--- a/src/protocols/irc/irc.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/irc/irc.c	Sun May 30 19:34:21 2004 +0000
@@ -321,11 +321,11 @@
 	else
 		args[0] = who;
 	args[1] = what;
-
+#if 0
 	if (*what == '/') {
 		return irc_parse_cmd(irc, who, what + 1);
 	}
-
+#endif
 	irc_cmd_privmsg(irc, "msg", NULL, args);
 	return 1;
 }
@@ -461,11 +461,11 @@
 		gaim_debug(GAIM_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
 		return -EINVAL;
 	}
-
+#if 0
 	if (*what == '/') {
 		return irc_parse_cmd(irc, convo->name, what + 1);
 	}
-
+#endif
 	args[0] = convo->name;
 	args[1] = what;
 
@@ -654,6 +654,8 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
 	_irc_plugin = plugin;
+
+	irc_register_commands();
 }
 
 GAIM_INIT_PLUGIN(irc, _init_plugin, info);
--- a/src/protocols/irc/irc.h	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/irc/irc.h	Sun May 30 19:34:21 2004 +0000
@@ -85,6 +85,7 @@
 char *irc_mirc2html(const char *string);
 char *irc_mirc2txt(const char *string);
 
+void irc_register_commands(void);
 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);
--- a/src/protocols/irc/parse.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/irc/parse.c	Sun May 30 19:34:21 2004 +0000
@@ -26,6 +26,7 @@
 #include "conversation.h"
 #include "notify.h"
 #include "debug.h"
+#include "cmds.h"
 #include "irc.h"
 
 #include <stdio.h>
@@ -135,6 +136,67 @@
 	{ NULL, NULL, NULL }
 };
 
+static GaimCmdRet irc_parse_gaim_cmd(GaimConversation *conv, const gchar *cmd,
+                                        gchar **args, gchar **error)
+{
+	GaimConnection *gc;
+	struct irc_conn *irc;
+	struct _irc_user_cmd *cmdent;
+
+	gc = gaim_conversation_get_gc(conv);
+	if (!gc)
+		return GAIM_CMD_RET_FAILED;
+
+	irc = gc->proto_data;
+
+	if ((cmdent = g_hash_table_lookup(irc->cmds, cmd)) == NULL)
+		return GAIM_CMD_RET_FAILED;
+
+	(cmdent->cb)(irc, cmd, gaim_conversation_get_name(conv), (const char **)args);
+
+	return GAIM_CMD_RET_OK;
+}
+
+static void irc_register_command(struct _irc_user_cmd *c)
+{
+	GaimCmdFlag f;
+	char args[10];
+	char *format;
+	int i;
+
+	f = GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY
+	    | GAIM_CMD_FLAG_ALLOW_WRONG_ARGS;
+
+	format = c->format;
+
+	for (i = 0; (i < (sizeof(args) - 1)) && *format; i++, format++)
+		switch (*format) {
+		case 'v':
+		case 'n':
+		case 'c':
+		case 't':
+			args[i] = 'w';
+			break;
+		case ':':
+		case '*':
+			args[i] = 's';
+			break;
+		}
+
+	args[i] = '\0';
+
+	gaim_cmd_register(c->name, args, GAIM_CMD_P_PRPL, f, "prpl-irc", irc_parse_gaim_cmd,
+	                  _("No help is available at this time for this command."));
+}
+
+void irc_register_commands(void)
+{
+	struct _irc_user_cmd *c;
+
+	for (c = _irc_cmds; c && c->name; c++)
+		irc_register_command(c);
+}
+
 static char *irc_send_convert(struct irc_conn *irc, const char *string)
 {
 	char *utf8;
--- a/src/protocols/jabber/chat.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/jabber/chat.c	Sun May 30 19:34:21 2004 +0000
@@ -103,6 +103,16 @@
 	return chat;
 }
 
+JabberChat *jabber_chat_find_by_conv(GaimConversation *conv)
+{
+	GaimAccount *account = gaim_conversation_get_account(conv);
+	GaimConnection *gc = gaim_account_get_connection(account);
+	JabberStream *js = gc->proto_data;
+	int id = gaim_conv_chat_get_id(GAIM_CONV_CHAT(conv));
+
+	return jabber_chat_find_by_id(js, id);
+}
+
 void jabber_chat_invite(GaimConnection *gc, int id, const char *msg,
 		const char *name)
 {
--- a/src/protocols/jabber/chat.h	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/jabber/chat.h	Sun May 30 19:34:21 2004 +0000
@@ -49,6 +49,7 @@
 JabberChat *jabber_chat_find(JabberStream *js, const char *room,
 		const char *server);
 JabberChat *jabber_chat_find_by_id(JabberStream *js, int id);
+JabberChat *jabber_chat_find_by_conv(GaimConversation *conv);
 void jabber_chat_destroy(JabberChat *chat);
 void jabber_chat_free(JabberChat *chat);
 gboolean jabber_chat_find_buddy(GaimConversation *conv, const char *name);
--- a/src/protocols/jabber/jabber.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/jabber/jabber.c	Sun May 30 19:34:21 2004 +0000
@@ -23,6 +23,7 @@
 #include "account.h"
 #include "accountopt.h"
 #include "blist.h"
+#include "cmds.h"
 #include "debug.h"
 #include "message.h"
 #include "notify.h"
@@ -1265,6 +1266,75 @@
 	}
 }
 
+static GaimCmdRet jabber_cmd_chat_config(GaimConversation *conv,
+		const char *cmd, char **args, char **error)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	jabber_chat_request_room_configure(chat);
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_register(GaimConversation *conv,
+		const char *cmd, char **args, char **error)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	jabber_chat_register(chat);
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_topic(GaimConversation *conv,
+		const char *cmd, char **args, char **error)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	jabber_chat_change_topic(chat, args ? args[0] : NULL);
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_nick(GaimConversation *conv,
+		const char *cmd, char **args, char **error)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+
+	if(!args || !args[0])
+		return GAIM_CMD_RET_FAILED;
+
+	jabber_chat_change_nick(chat, args[0]);
+	return GAIM_CMD_RET_OK;
+}
+
+static GaimCmdRet jabber_cmd_chat_part(GaimConversation *conv,
+		const char *cmd, char **args, char **error)
+{
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
+	jabber_chat_part(chat, args ? args[0] : NULL);
+	return GAIM_CMD_RET_OK;
+}
+
+static void jabber_register_commands(void)
+{
+	gaim_cmd_register("config", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY, "prpl-jabber",
+			jabber_cmd_chat_config, _("Configure a chat room"));
+	gaim_cmd_register("configure", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY, "prpl-jabber",
+			jabber_cmd_chat_config, _("Configure a chat room"));
+	gaim_cmd_register("nick", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY, "prpl-jabber",
+			jabber_cmd_chat_nick, _("Change your nickname"));
+	gaim_cmd_register("part", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+			jabber_cmd_chat_part, _("Leave the room"));
+	gaim_cmd_register("register", "", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY, "prpl-jabber",
+			jabber_cmd_chat_register, _("Register with a chat room"));
+	/* XXX: there needs to be a core /topic cmd, methinks */
+	gaim_cmd_register("topic", "s", GAIM_CMD_P_PRPL,
+			GAIM_CMD_FLAG_CHAT | GAIM_CMD_FLAG_PRPL_ONLY |
+			GAIM_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
+			jabber_cmd_chat_topic, _("View or change the topic"));
+}
+
 static GaimPluginPrefFrame *
 get_plugin_pref_frame(GaimPlugin *plugin) {
 	GaimPluginPrefFrame *frame;
@@ -1413,6 +1483,8 @@
 
 	gaim_prefs_add_none("/plugins/prpl/jabber");
 	gaim_prefs_add_bool("/plugins/prpl/jabber/hide_os", FALSE);
+
+	jabber_register_commands();
 }
 
 GAIM_INIT_PLUGIN(jabber, init_plugin, info);
--- a/src/protocols/jabber/message.c	Sun May 30 19:30:14 2004 +0000
+++ b/src/protocols/jabber/message.c	Sun May 30 19:34:21 2004 +0000
@@ -482,7 +482,7 @@
 	JabberChat *chat;
 	JabberMessage *jm;
 	JabberStream *js;
-	char *buf, *body, *xhtml;
+	char *buf;
 
 	if(!msg || !gc)
 		return 0;
@@ -493,42 +493,23 @@
 	if(!chat)
 		return 0;
 
+	jm = g_new0(JabberMessage, 1);
+	jm->js = gc->proto_data;
+	jm->type = JABBER_MESSAGE_GROUPCHAT;
+	jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
+
 	buf = g_strdup_printf("<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html>", msg);
-	gaim_markup_html_to_xhtml(buf, &xhtml, &body);
+	gaim_markup_html_to_xhtml(buf, &jm->xhtml, &jm->body);
 	g_free(buf);
 
-	if(!strcmp(body, "/configure") || !strcmp(body, "/config")) {
-		jabber_chat_request_room_configure(chat);
-	} else if(!strcmp(body, "/register")) {
-		jabber_chat_register(chat);
-	} else if(!strncmp(body, "/topic", 6)) {
-		jabber_chat_change_topic(chat, strlen(body) > 7 ? body+7 : NULL);
-	} else if(!strncmp(body, "/nick", 5)) {
-		if(strlen(body) > 6)
-			jabber_chat_change_nick(chat, body+6);
-	} else if(!strncmp(body, "/part", 5)) {
-		jabber_chat_part(chat, strlen(body) > 6 ? body+6 : NULL);
-	} else {
-		jm = g_new0(JabberMessage, 1);
-		jm->js = gc->proto_data;
-		jm->type = JABBER_MESSAGE_GROUPCHAT;
-		jm->to = g_strdup_printf("%s@%s", chat->room, chat->server);
-
-
-		if(chat->xhtml)
-			jm->xhtml = xhtml;
-		else
-			g_free(xhtml);
-
-		jm->body = body;
-
-		jabber_message_send(jm);
-		jabber_message_free(jm);
-		return 1;
+	if(!chat->xhtml) {
+		g_free(jm->xhtml);
+		jm->xhtml = NULL;
 	}
 
-	g_free(body);
-	g_free(xhtml);
+	jabber_message_send(jm);
+	jabber_message_free(jm);
+
 	return 1;
 }