diff plugins/tcl/tcl_cmds.c @ 6694:2d2f04c5c7d2

[gaim-migrate @ 7220] Sean probably won't think this is contact support. This is in fact a Tcl script plugin loader. That's probably what he'll think it is. committer: Tailor Script <tailor@pidgin.im>
author Ethan Blanton <elb@pidgin.im>
date Tue, 02 Sep 2003 03:34:37 +0000
parents
children 57161e3abbb5
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/tcl/tcl_cmds.c	Tue Sep 02 03:34:37 2003 +0000
@@ -0,0 +1,913 @@
+/**
+ * @file tcl_cmds.c Commands for the Gaim Tcl plugin bindings
+ *
+ * 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 <tcl.h>
+
+#include "internal.h"
+#include "conversation.h"
+#include "connection.h"
+#include "account.h"
+#include "server.h"
+#include "notify.h"
+#include "debug.h"
+#include "prefs.h"
+#include "core.h"
+
+#include "tcl_gaim.h"
+
+static gboolean tcl_validate_account(GaimAccount *account, Tcl_Interp *interp);
+static gboolean tcl_validate_gc(GaimConnection *gc);
+
+static gboolean tcl_validate_account(GaimAccount *account, Tcl_Interp *interp)
+{
+	GList *cur;
+	for (cur = gaim_accounts_get_all(); cur != NULL; cur = g_list_next(cur)) {
+		if (account == cur->data)
+			return TRUE;
+	}
+	if (interp != NULL)
+		Tcl_SetStringObj(Tcl_GetObjResult(interp), "invalid account", -1);
+	return FALSE;
+}
+
+static gboolean tcl_validate_conversation(GaimConversation *convo, Tcl_Interp *interp)
+{
+	GList *cur;
+
+	for (cur = gaim_get_conversations(); cur != NULL; cur = g_list_next(cur)) {
+		if (convo == cur->data)
+			return TRUE;
+	}
+	if (interp != NULL)
+		Tcl_SetStringObj(Tcl_GetObjResult(interp), "invalid account", -1);
+	return FALSE;
+}
+
+static gboolean tcl_validate_gc(GaimConnection *gc)
+{
+	GList *cur;
+	for (cur = gaim_connections_get_all(); cur != NULL; cur = g_list_next(cur)) {
+		if (gc == cur->data)
+			return TRUE;
+	}
+	return FALSE;
+}
+
+int tcl_cmd_account(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *result = Tcl_GetObjResult(interp), *list, *elem;
+	char *cmds[] = { "alias", "connect", "connection", "disconnect", "find", 
+			 "handle", "isconnected", "list", "protocol", "username",
+			 NULL };
+	enum { CMD_ACCOUNT_ALIAS, CMD_ACCOUNT_CONNECT, CMD_ACCOUNT_CONNECTION,
+	       CMD_ACCOUNT_DISCONNECT, CMD_ACCOUNT_FIND, CMD_ACCOUNT_HANDLE,
+	       CMD_ACCOUNT_ISCONNECTED, CMD_ACCOUNT_LIST, CMD_ACCOUNT_PROTOCOL,
+	       CMD_ACCOUNT_USERNAME } cmd;
+	char *listopts[] = { "-all", "-online", NULL };
+	enum { CMD_ACCOUNTLIST_ALL, CMD_ACCOUNTLIST_ONLINE } listopt;
+	const char *alias;
+	GList *cur;
+	GaimAccount *account;
+	int error;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	switch (cmd) {
+	case CMD_ACCOUNT_ALIAS:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		alias = gaim_account_get_alias(account);
+		Tcl_SetStringObj(result, alias ? (char *)alias : "", -1);
+		break;
+	case CMD_ACCOUNT_CONNECT:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		if (gaim_account_is_connected(account))
+			Tcl_SetIntObj(result, (int)gaim_account_get_connection(account));
+		else
+			Tcl_SetIntObj(result, (int)gaim_account_connect(account));
+		break;
+	case CMD_ACCOUNT_CONNECTION:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		Tcl_SetIntObj(result, (int)gaim_account_get_connection(account));
+		break;
+	case CMD_ACCOUNT_DISCONNECT:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		gaim_account_disconnect(account);
+		break;
+	case CMD_ACCOUNT_FIND:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "username protocol");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_accounts_find_with_prpl_id(Tcl_GetString(objv[2]),
+									   Tcl_GetString(objv[3])));
+		break;
+	case CMD_ACCOUNT_HANDLE:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_accounts_get_handle());
+		break;
+	case CMD_ACCOUNT_ISCONNECTED:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		Tcl_SetBooleanObj(result, gaim_account_is_connected(account));
+		break;
+	case CMD_ACCOUNT_LIST:
+		listopt = CMD_ACCOUNTLIST_ALL;
+		if (objc > 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "?option?");
+			return TCL_ERROR;
+		}
+		if (objc == 3) {
+			if ((error = Tcl_GetIndexFromObj(interp, objv[2], listopts, "option", 0, (int *)&listopt)) != TCL_OK)
+				return error;
+		}
+		list = Tcl_NewListObj(0, NULL);
+		for (cur = gaim_accounts_get_all(); cur != NULL; cur = g_list_next(cur)) {
+			account = cur->data;
+			if (listopt == CMD_ACCOUNTLIST_ONLINE && !gaim_account_is_connected(account))
+				continue;
+			elem = Tcl_NewIntObj((int)account);
+			Tcl_ListObjAppendElement(interp, list, elem);
+		}
+		Tcl_SetObjResult(interp, list);
+		break;
+	case CMD_ACCOUNT_PROTOCOL:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		Tcl_SetStringObj(result, (char *)gaim_account_get_protocol_id(account), -1);
+		break;
+	case CMD_ACCOUNT_USERNAME:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "account");
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account);
+		if (error || !tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		Tcl_SetStringObj(result, (char *)gaim_account_get_username(account), -1);
+		break;
+	}
+
+	return TCL_OK;
+}
+
+static GaimBlistNode *tcl_list_to_buddy(Tcl_Interp *interp, int count, Tcl_Obj **elems)
+{
+	GaimBlistNode *node = NULL;
+	GaimAccount *account;
+	char *name;
+	char *type;
+
+	if (count < 3) {
+		Tcl_SetStringObj(Tcl_GetObjResult(interp), "list too short", -1);
+		return NULL;
+	}
+
+	type = Tcl_GetString(elems[0]);
+	name = Tcl_GetString(elems[1]);
+	if (Tcl_GetIntFromObj(interp, elems[2], (int *)&account) != TCL_OK)
+		return NULL;
+	if (!tcl_validate_account(account, interp))
+		return NULL;
+
+	if (!strcmp(type, "buddy")) {
+		node = (GaimBlistNode *)gaim_find_buddy(account, name);
+	} else if (!strcmp(type, "group")) {
+		node = (GaimBlistNode *)gaim_blist_find_chat(account, name);
+	}
+
+	return node;
+}
+
+int tcl_cmd_buddy(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *list, *tclgroup, *tclgrouplist, *tclbud, **elems, *result;
+	char *cmds[] = { "alias", "handle", "info", "list", NULL };
+	enum { CMD_BUDDY_ALIAS, CMD_BUDDY_HANDLE, CMD_BUDDY_INFO, CMD_BUDDY_LIST } cmd;
+	struct gaim_buddy_list *blist;
+	GaimBlistNode *node, *gnode;
+	GaimAccount *account;
+	struct buddy *bnode;
+	struct chat *cnode;
+	int error, all = 0, count;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	result = Tcl_GetObjResult(interp);
+
+	switch (cmd) {
+	case CMD_BUDDY_ALIAS:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "buddy");
+			return TCL_ERROR;
+		}
+		if ((error = Tcl_ListObjGetElements(interp, objv[2], &count, &elems)) != TCL_OK)
+			return error;
+		if ((node = tcl_list_to_buddy(interp, count, elems)) == NULL)
+			return TCL_ERROR;
+		if (node->type == GAIM_BLIST_CHAT_NODE)
+			Tcl_SetStringObj(result, ((struct chat *)node)->alias, -1);
+		else if (node->type == GAIM_BLIST_BUDDY_NODE)
+			Tcl_SetStringObj(result, gaim_get_buddy_alias((struct buddy *)node), -1);
+		return TCL_OK;
+		break;
+	case CMD_BUDDY_HANDLE:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_blist_get_handle());
+		break;
+	case CMD_BUDDY_INFO:
+		if (objc != 3 && objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "( buddy | account username )");
+			return TCL_ERROR;
+		}
+		if (objc == 3) {
+			if ((error = Tcl_ListObjGetElements(interp, objv[2], &count, &elems)) != TCL_OK)
+				return error;
+			if (count < 3) {
+				Tcl_SetStringObj(result, "buddy too short", -1);
+				return TCL_ERROR;
+			}
+			if (strcmp("buddy", Tcl_GetString(elems[0]))) {
+				Tcl_SetStringObj(result, "invalid buddy", -1);
+				return TCL_ERROR;
+			}
+			if ((error = Tcl_GetIntFromObj(interp, elems[2], (int *)&account)) != TCL_OK)
+				return TCL_ERROR;
+			if (!tcl_validate_account(account, interp))
+				return TCL_ERROR;
+			serv_get_info(gaim_account_get_connection(account), Tcl_GetString(elems[1]));
+		} else {
+			if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&account)) != TCL_OK)
+				return error;
+			if (!tcl_validate_account(account, interp))
+				return TCL_ERROR;
+			serv_get_info(gaim_account_get_connection(account), Tcl_GetString(objv[3]));
+		}
+		break;
+	case CMD_BUDDY_LIST:
+		if (objc == 3) {
+			if (!strcmp("-all", Tcl_GetString(objv[2]))) {
+				all = 1;
+			} else {
+				Tcl_SetStringObj(result, "", -1);
+				Tcl_AppendStringsToObj(result, "unknown option: ", Tcl_GetString(objv[2]), NULL);
+				return TCL_ERROR;
+			}
+		}
+		list = Tcl_NewListObj(0, NULL);
+		blist = gaim_get_blist();
+		for (gnode = blist->root; gnode != NULL; gnode = gnode->next) {
+			tclgroup = Tcl_NewListObj(0, NULL);
+			Tcl_ListObjAppendElement(interp, tclgroup, Tcl_NewStringObj("group", -1));
+			Tcl_ListObjAppendElement(interp, tclgroup,
+						 Tcl_NewStringObj(((struct group *)gnode)->name, -1));
+			tclgrouplist = Tcl_NewListObj(0, NULL);
+			for (node = gnode->child; node != NULL; node = node->next) {
+				switch (node->type) {
+				case GAIM_BLIST_BUDDY_NODE:
+					bnode = (struct buddy *)node;
+					if (!all && !gaim_account_is_connected(bnode->account))
+						continue;
+					tclbud = Tcl_NewListObj(0, NULL);
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("buddy", -1));
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(bnode->name, -1));
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewIntObj((int)bnode->account));
+					break;
+				case GAIM_BLIST_CHAT_NODE:
+					cnode = (struct chat *)node;
+					if (!all && !gaim_account_is_connected(cnode->account))
+						continue;
+					tclbud = Tcl_NewListObj(0, NULL);
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj("chat", -1));
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewStringObj(cnode->alias, -1));
+					Tcl_ListObjAppendElement(interp, tclbud, Tcl_NewIntObj((int)cnode->account));
+					break;
+				default:
+					continue;
+				}
+				Tcl_ListObjAppendElement(interp, tclgrouplist, tclbud);
+			}
+			Tcl_ListObjAppendElement(interp, tclgroup, tclgrouplist);
+			Tcl_ListObjAppendElement(interp, list, tclgroup);
+		}
+		Tcl_SetObjResult(interp, list);
+		break;
+	}
+
+	return TCL_OK;
+}
+
+int tcl_cmd_connection(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *result = Tcl_GetObjResult(interp), *list, *elem;
+	char *cmds[] = { "account", "handle", "list", NULL };
+	enum { CMD_CONN_ACCOUNT, CMD_CONN_HANDLE, CMD_CONN_LIST } cmd;
+	int error;
+	GList *cur;
+	GaimConnection *gc;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	switch (cmd) {
+	case CMD_CONN_ACCOUNT:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, _("gc"));
+			return TCL_ERROR;
+		}
+		error = Tcl_GetIntFromObj(interp, objv[2], (int *)&gc);
+		if (error || !tcl_validate_gc(gc)) {
+			Tcl_SetStringObj(result, "invalid gc", -1);
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_connection_get_account(gc));
+		break;
+	case CMD_CONN_HANDLE:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_connections_get_handle());
+		break;
+	case CMD_CONN_LIST:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		list = Tcl_NewListObj(0, NULL);
+		for (cur = gaim_connections_get_all(); cur != NULL; cur = g_list_next(cur)) {
+			elem = Tcl_NewIntObj((int)cur->data);
+			Tcl_ListObjAppendElement(interp, list, elem);
+		}
+		Tcl_SetObjResult(interp, list);
+		break;
+	}
+
+	return TCL_OK;
+}
+
+int tcl_cmd_conversation(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *list, *elem, *result = Tcl_GetObjResult(interp);
+	char *cmds[] = { "find", "handle", "list", "new", "write", NULL };
+	enum { CMD_CONV_FIND, CMD_CONV_HANDLE, CMD_CONV_LIST, CMD_CONV_NEW, CMD_CONV_WRITE } cmd;
+	char *styles[] = { "send", "recv", "system", NULL };
+	enum { CMD_CONV_WRITE_SEND, CMD_CONV_WRITE_RECV, CMD_CONV_WRITE_SYSTEM } style;
+	char *findopts[] = { "-account", NULL };
+	enum { CMD_CONV_FIND_ACCOUNT } findopt;
+	char *newopts[] = { "-chat", "-im" };
+	enum { CMD_CONV_NEW_CHAT, CMD_CONV_NEW_IM } newopt;
+	GaimConversation *convo;
+	GaimAccount *account;
+	GaimConversationType type;
+	GList *cur;
+	char *opt, *from, *what;
+	int error, argsused, flags;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	switch (cmd) {
+	case CMD_CONV_FIND:
+		if (objc < 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "?options? name");
+			return TCL_ERROR;
+		}
+		argsused = 2;
+		account = NULL;
+		while (argsused < objc) {
+			opt = Tcl_GetString(objv[argsused]);
+			if (*opt == '-') {
+				if ((error = Tcl_GetIndexFromObj(interp, objv[argsused], findopts,
+								 "option", 0, (int *)&findopt)) != TCL_OK)
+					return error;
+				argsused++;
+				switch (findopt) {
+				case CMD_CONV_FIND_ACCOUNT:
+					if (argsused == objc - 1) {
+						Tcl_SetStringObj(result, "-account requires an argument", -1);
+						return TCL_ERROR;
+					}
+					if ((error = Tcl_GetIntFromObj(interp, objv[argsused],
+								       (int *)&account)) != TCL_OK)
+						return error;
+					if (!tcl_validate_account(account, interp))
+						return TCL_ERROR;
+					argsused++;
+					break;
+				}
+			} else {
+				break;
+			}
+		}
+		if (objc - argsused != 1) {
+			Tcl_WrongNumArgs(interp, 2, objv, "?options? name");
+			return error;
+		}
+		if (account != NULL) {
+			convo = gaim_find_conversation_with_account(Tcl_GetString(objv[argsused]), account);
+		} else {
+			convo = gaim_find_conversation(Tcl_GetString(objv[argsused]));
+		}
+		Tcl_SetIntObj(result, (int)convo);
+		break;
+	case CMD_CONV_HANDLE:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_conversations_get_handle());
+		break;
+	case CMD_CONV_LIST:
+		list = Tcl_NewListObj(0, NULL);
+		for (cur = gaim_get_conversations(); cur != NULL; cur = g_list_next(cur)) {
+			elem = Tcl_NewIntObj((int)cur->data);
+			Tcl_ListObjAppendElement(interp, list, elem);
+		}
+		Tcl_SetObjResult(interp, list);
+		break;
+	case CMD_CONV_NEW:
+		if (objc < 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "?options? account name");
+			return TCL_ERROR;
+		}
+		argsused = 2;
+		type = GAIM_CONV_IM;
+		while (argsused < objc) {
+			opt = Tcl_GetString(objv[argsused]);
+			if (*opt == '-') {
+				if ((error = Tcl_GetIndexFromObj(interp, objv[argsused], newopts,
+								 "option", 0, (int *)&newopt)) != TCL_OK)
+					return error;
+				argsused++;
+				switch (newopt) {
+				case CMD_CONV_NEW_CHAT:
+					type = GAIM_CONV_CHAT;
+					break;
+				case CMD_CONV_NEW_IM:
+					type = GAIM_CONV_IM;
+					break;
+				}
+			} else {
+				break;
+			}
+		}
+		if (objc - argsused != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "?options? account name");
+			return TCL_ERROR;
+		}
+		if ((error = Tcl_GetIntFromObj(interp, objv[argsused++], (int *)&account)) != TCL_OK)
+			return error;
+		if (!tcl_validate_account(account, interp))
+			return TCL_ERROR;
+		convo = gaim_conversation_new(type, account, Tcl_GetString(objv[argsused]));
+		Tcl_SetIntObj(result, (int)convo);
+		break;
+	case CMD_CONV_WRITE:
+		if (objc != 6) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation style from what");
+			return TCL_ERROR;
+		}
+		if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&convo)) != TCL_OK)
+			return error;
+		if ((error = Tcl_GetIndexFromObj(interp, objv[3], styles, "style", 0, (int *)&style)) != TCL_OK)
+			return error;
+		if (!tcl_validate_conversation(convo, interp))
+			return TCL_ERROR;
+		from = Tcl_GetString(objv[4]);
+		what = Tcl_GetString(objv[5]);
+		
+		switch (style) {
+		case CMD_CONV_WRITE_SEND:
+			flags = GAIM_MESSAGE_SEND;
+			break;
+		case CMD_CONV_WRITE_RECV:
+			flags = GAIM_MESSAGE_RECV;
+			break;
+		case CMD_CONV_WRITE_SYSTEM:
+			flags = GAIM_MESSAGE_SYSTEM;
+			break;
+		}
+		if (gaim_conversation_get_type(convo) == GAIM_CONV_CHAT)
+			gaim_chat_write(GAIM_CHAT(convo), from, what, flags, time(NULL));
+		else
+			gaim_im_write(GAIM_IM(convo), from, what, -1, flags, time(NULL));
+		break;
+	}
+
+	return TCL_OK;
+}
+
+int tcl_cmd_core(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *result = Tcl_GetObjResult(interp);
+	char *cmds[] = { "handle", "quit", NULL };
+	enum { CMD_CORE_HANDLE, CMD_CORE_QUIT } cmd;
+	int error;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	switch (cmd) {
+	case CMD_CORE_HANDLE:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		Tcl_SetIntObj(result, (int)gaim_get_core());
+		break;
+	case CMD_CORE_QUIT:
+		if (objc != 2) {
+			Tcl_WrongNumArgs(interp, 2, objv, "");
+			return TCL_ERROR;
+		}
+		gaim_core_quit();
+		break;
+	}
+
+	return TCL_OK;
+}
+
+int tcl_cmd_debug(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	char *category, *message;
+	int lev;
+	char *levels[] = { "-misc", "-info", "-warning", "-error", NULL };
+	GaimDebugLevel levelind[] = { GAIM_DEBUG_MISC, GAIM_DEBUG_INFO, GAIM_DEBUG_WARNING, GAIM_DEBUG_ERROR };
+	int error;
+
+	if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 1, objv, "level category message");
+		return TCL_ERROR;
+	}
+
+	error = Tcl_GetIndexFromObj(interp, objv[1], levels, "debug level", 0, &lev);
+	if (error != TCL_OK)
+		return error;
+
+	category = Tcl_GetString(objv[2]);
+	message = Tcl_GetString(objv[3]);
+
+	gaim_debug(levelind[lev], category, "%s\n", message);
+
+	return TCL_OK;
+}
+
+int tcl_cmd_notify(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	int error, type;
+	char *opts[] = { "-error", "-warning", "-info", NULL };
+	GaimNotifyMsgType optind[] = { GAIM_NOTIFY_MSG_ERROR, GAIM_NOTIFY_MSG_WARNING, GAIM_NOTIFY_MSG_INFO };
+	char *title, *msg1, *msg2;
+
+	if (objc < 4 || objc > 5) {
+		Tcl_WrongNumArgs(interp, 1, objv, "?type? title primary secondary");
+		return TCL_ERROR;
+	}
+
+	if (objc == 4) {
+		title = Tcl_GetString(objv[1]);
+		msg1 = Tcl_GetString(objv[2]);
+		msg2 = Tcl_GetString(objv[3]);
+	} else {
+		error = Tcl_GetIndexFromObj(interp, objv[1], opts, "message type", 0, &type);
+		if (error != TCL_OK)
+			return error;
+		title = Tcl_GetString(objv[2]);
+		msg1 = Tcl_GetString(objv[3]);
+		msg2 = Tcl_GetString(objv[4]);
+	}
+
+	gaim_notify_message(_tcl_plugin, optind[type], title, msg1, msg2, NULL, NULL);
+
+	return TCL_OK;
+}
+
+int tcl_cmd_prefs(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	Tcl_Obj *result, *list, *elem, **elems;
+	char *cmds[] = { "get", "set", "type", NULL };
+	enum { CMD_PREFS_GET, CMD_PREFS_SET, CMD_PREFS_TYPE } cmd;
+	/* char *types[] = { "none", "boolean", "int", "string", "stringlist", NULL }; */
+	/* enum { TCL_PREFS_NONE, TCL_PREFS_BOOL, TCL_PREFS_INT, TCL_PREFS_STRING, TCL_PREFS_STRINGLIST } type; */
+	GaimPrefType preftype;
+	GList *cur;
+	int error, intval, nelem, i;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	result = Tcl_GetObjResult(interp);
+	switch (cmd) {
+	case CMD_PREFS_GET:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 1, objv, "path");
+			return TCL_ERROR;
+		}
+		preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+		switch (preftype) {
+		case GAIM_PREF_NONE:
+			Tcl_SetStringObj(result, "pref type none", -1);
+			return TCL_ERROR;
+			break;
+		case GAIM_PREF_BOOLEAN:
+			Tcl_SetBooleanObj(result, gaim_prefs_get_bool(Tcl_GetString(objv[2])));
+			break;
+		case GAIM_PREF_INT:
+			Tcl_SetIntObj(result, gaim_prefs_get_int(Tcl_GetString(objv[2])));
+			break;
+		case GAIM_PREF_STRING:
+			Tcl_SetStringObj(result, (char *)gaim_prefs_get_string(Tcl_GetString(objv[2])), -1);
+			break;
+		case GAIM_PREF_STRING_LIST:
+			cur = gaim_prefs_get_string_list(Tcl_GetString(objv[2]));
+			list = Tcl_NewListObj(0, NULL);
+			while (cur != NULL) {
+				elem = Tcl_NewStringObj((char *)cur->data, -1);
+				Tcl_ListObjAppendElement(interp, list, elem);
+				cur = g_list_next(cur);
+			}
+			Tcl_SetObjResult(interp, list);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+			Tcl_SetStringObj(result, "unknown pref type", -1);
+			return TCL_ERROR;
+		}
+		break;
+	case CMD_PREFS_SET:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 1, objv, "path value");
+			return TCL_ERROR;
+		}
+		preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+		switch (preftype) {
+		case GAIM_PREF_NONE:
+			Tcl_SetStringObj(result, "bad path or pref type none", -1);
+			return TCL_ERROR;
+			break;
+		case GAIM_PREF_BOOLEAN:
+			if ((error = Tcl_GetBooleanFromObj(interp, objv[3], &intval)) != TCL_OK)
+				return error;
+			gaim_prefs_set_bool(Tcl_GetString(objv[2]), intval);
+			break;
+		case GAIM_PREF_INT:
+			if ((error = Tcl_GetIntFromObj(interp, objv[3], &intval)) != TCL_OK)
+				return error;
+			gaim_prefs_set_int(Tcl_GetString(objv[2]), intval);
+			break;
+		case GAIM_PREF_STRING:
+			gaim_prefs_set_string(Tcl_GetString(objv[2]), Tcl_GetString(objv[3]));
+			break;
+		case GAIM_PREF_STRING_LIST:
+			if ((error = Tcl_ListObjGetElements(interp, objv[3], &nelem, &elems)) != TCL_OK)
+				return error;
+			cur = NULL;
+			for (i = 0; i < nelem; i++) {
+				cur = g_list_append(cur, (gpointer)Tcl_GetString(elems[i]));
+			}
+			gaim_prefs_set_string_list(Tcl_GetString(objv[2]), cur);
+			g_list_free(cur);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+			return TCL_ERROR;
+		}
+		break;
+	case CMD_PREFS_TYPE:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 1, objv, "path");
+			return TCL_ERROR;
+		}
+		preftype = gaim_prefs_get_type(Tcl_GetString(objv[2]));
+		switch (preftype) {
+		case GAIM_PREF_NONE:
+			Tcl_SetStringObj(result, "none", -1);
+			break;
+		case GAIM_PREF_BOOLEAN:
+			Tcl_SetStringObj(result, "boolean", -1);
+			break;
+		case GAIM_PREF_INT:
+			Tcl_SetStringObj(result, "int", -1);
+			break;
+		case GAIM_PREF_STRING:
+			Tcl_SetStringObj(result, "string", -1);
+			break;
+		case GAIM_PREF_STRING_LIST:
+			Tcl_SetStringObj(result, "stringlist", -1);
+			break;
+		default:
+			gaim_debug(GAIM_DEBUG_ERROR, "tcl", "tcl does not know about pref type %d\n", preftype);
+			Tcl_SetStringObj(result, "unknown", -1);
+		}
+		break;
+	}
+
+	return TCL_OK;
+}
+
+int tcl_cmd_send_im(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	GaimConnection *gc;
+	char *who, *text;
+	int error;
+	Tcl_Obj *result;
+
+	if (objc != 4) {
+		Tcl_WrongNumArgs(interp, 1, objv, "gc who text");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIntFromObj(interp, objv[1], (int *)&gc)) != TCL_OK)
+		return error;
+	if (!tcl_validate_gc(gc)) {
+		result = Tcl_GetObjResult(interp);
+		Tcl_SetStringObj(result, "invalid gc", -1);
+		return TCL_ERROR;
+	}
+
+	who = Tcl_GetString(objv[2]);
+	text = Tcl_GetString(objv[3]);
+
+	serv_send_im(gc, who, text, -1, 0);
+
+	return TCL_OK;
+}
+
+int tcl_cmd_signal(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	char *cmds[] = { "connect", "disconnect", NULL };
+	enum { CMD_SIGNAL_CONNECT, CMD_SIGNAL_DISCONNECT } cmd;
+	struct tcl_signal_handler *handler;
+	Tcl_Obj **elems, *result = Tcl_GetObjResult(interp);
+	void *instance;
+	int error, nelems, i;
+
+	if (objc < 2) {
+		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
+		return TCL_ERROR;
+	}
+
+	if ((error = Tcl_GetIndexFromObj(interp, objv[1], cmds, "subcommand", 0, (int *)&cmd)) != TCL_OK)
+		return error;
+
+	switch (cmd) {
+	case CMD_SIGNAL_CONNECT:
+		if (objc != 6) {
+			Tcl_WrongNumArgs(interp, 2, objv, "instance signal args proc");
+			return TCL_ERROR;
+		}
+		if ((error = Tcl_ListObjGetElements(interp, objv[4], &nelems, &elems)) != TCL_OK)
+			return error;
+		handler = g_new0(struct tcl_signal_handler, 1);
+		if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&handler->instance)) != TCL_OK) {
+			g_free(handler);
+			return error;
+		}
+		handler->signal = g_strdup(Tcl_GetString(objv[3]));
+		if (nelems) {
+			handler->argnames = g_new0(char *, nelems);
+			for (i = 0; i < nelems; i++) {
+				handler->argnames[i] = g_strdup(Tcl_GetString(elems[i]));
+			}
+		}
+		handler->nnames = nelems;
+		handler->proc = Tcl_NewStringObj("namespace eval ::gaim::_callback { ", -1);
+		Tcl_AppendStringsToObj(handler->proc, Tcl_GetString(objv[5]), " }", NULL);
+		Tcl_IncrRefCount(handler->proc);
+		handler->interp = interp;
+		if (!tcl_signal_connect(handler)) {
+			tcl_signal_handler_free(handler);
+			Tcl_SetIntObj(result, 1);
+		} else {
+			Tcl_SetIntObj(result, 0);
+		}
+		break;
+	case CMD_SIGNAL_DISCONNECT:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "signal");
+			return TCL_ERROR;
+		}
+		if ((error = Tcl_GetIntFromObj(interp, objv[2], (int *)&instance)) != TCL_OK)
+			return error;
+		tcl_signal_disconnect(instance, Tcl_GetString(objv[3]), interp);
+		break;
+	}
+
+	return TCL_OK;
+}
+
+static gboolean unload_self(gpointer data)
+{
+	GaimPlugin *plugin = data;
+	gaim_plugin_unload(plugin);
+	return FALSE;
+}
+
+int tcl_cmd_unload(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
+{
+	GaimPlugin *plugin;
+	if (objc != 1) {
+		Tcl_WrongNumArgs(interp, 1, objv, "");
+		return TCL_ERROR;
+	}
+
+	if ((plugin = tcl_interp_get_plugin(interp)) == NULL) {
+		/* This isn't exactly OK, but heh.  What do you do? */
+		return TCL_OK;
+	}
+	/* We can't unload immediately, but we can unload at the first 
+	 * known safe opportunity. */
+	g_idle_add(unload_self, (gpointer)plugin);
+
+	return TCL_OK;
+}