changeset 9478:0efa260e384f

[gaim-migrate @ 10303] " This patch adds the following features for zephyr, in order of usefulness :-) Protocol action (Retrieve subscriptions from server) to list user's subscriptions (chats) in a popup window. Typing notification - (both sending and receiving -- using zephyrs with opcode ping). Correct interpretation of %host% and %canon% entries in .zephyr.subs . These are often used to subscribe to system messages concerning a specific machine (by hostname and canonical hostname, respectively). An updated .todo list." --Arun A Tharuvai committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Wed, 07 Jul 2004 21:56:57 +0000
parents 2c7dcb5851f4
children fababc25f270
files ChangeLog src/protocols/zephyr/.todo src/protocols/zephyr/zephyr.c
diffstat 3 files changed, 416 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Jul 07 19:36:38 2004 +0000
+++ b/ChangeLog	Wed Jul 07 21:56:57 2004 +0000
@@ -7,6 +7,7 @@
 	* Ability to send files from the conversation window (Daniel Atallah)
 	* Parentheses are now displayed around the title and tabs of
 	  conversations from offline accounts or parted chats.
+	* Zephyr typing notification (Arun A Tharuvai)
 
 	Bug Fixes:
 	* The firefox browser option now works with firefox 0.9
--- a/src/protocols/zephyr/.todo	Wed Jul 07 19:36:38 2004 +0000
+++ b/src/protocols/zephyr/.todo	Wed Jul 07 21:56:57 2004 +0000
@@ -1,51 +1,141 @@
-<!-- Automagically generated by the ToDo program on Sat Jun 19 20:54:24 2004 -->
+<!-- Automagically generated by the ToDo program on Mon Jul  5 13:25:51 2004 -->
 <todo version="0.1.17">
     <note priority="veryhigh" time="1036046748">
         God help us.
+        <comment>
+            Yup
+        </comment>
+    </note>
+    <note priority="veryhigh" time="1087756801">
+        clean up code
+        <comment>
+            Being worked on
+        </comment>
     </note>
     <note priority="high" time="1087689758">
         write code to output a WGFILE with a sane name
+        <comment>
+            done
+        </comment>
     </note>
     <note priority="high" time="1087689986">
         get server supplied subscriptions to properly get added as chats
+        <comment>
+            works.
+        </comment>
     </note>
     <note priority="high" time="1087691868">
         zephyr freezes up sometimes (possibly when zlocating, and possibly when krb tickets have expired)? Debug and fix
+        <comment>
+            This is a libzephyr bug. I suppose I could always submit it upstream....
+        </comment>
+    </note>
+    <note priority="high" time="1087758121">
+        Does away actually work for zephyr
+    </note>
+    <note priority="high" time="1088381394">
+        Deal with %host% and %canon, and unsubscriptions (beginning with ! and - ?)
+        <comment>
+            dealt with %host% and %canon%.
+        </comment>
+    </note>
+    <note priority="high" time="1088381529">
+        Code to resubscribe, reload subs from file,  set exposure, zctl ret, zctl new_server, set signature, set display, and other protocol options.
+        <comment>
+            dealt with resubscriptions, "zctl ret".
+        </comment>
+    </note>
+    <note priority="high" time="1088688644">
+        Add zctl ret ("Show what the server thinks you're subscribed to")
+        <comment>
+            Done
+        </comment>
+    </note>
+    <note priority="high" time="1088828649">
+        should pop up errors when subs (with the sub command or zephyr_join_chat) fails
+    </note>
+    <note priority="high" time="1089047538">
+        update the progress bar for zephyr
     </note>
     <note priority="medium" time="1087689774">
         get non-kerberized zephyr working for windows
+        <comment>
+            buggy, untested code exists on hard drive
+        </comment>
     </note>
     <note priority="medium" time="1087689784">
         get kerberized zephyr working for windows
     </note>
     <note priority="medium" time="1087689886">
         get opener and closer tags for @ formatting to only match the correct closing tag, not any closing tag (e.g. [ matches ], and not )
+        <comment>
+            tested, but code needs cleanup
+        </comment>
     </note>
     <note priority="medium" time="1087689920">
         use tzc as a backend
+        <comment>
+            code exists on hard drive but needs lots of work (cleanup, and bug checking)
+        </comment>
     </note>
     <note priority="medium" time="1087690034">
         properly detect when subscriptions get lost
     </note>
     <note priority="medium" time="1087690121">
-        support zcrypt (maybe a plugin would be better?)
+        support zcrypt
+        <comment>
+            Written as a perl plugin (for receiving)
+        </comment>
     </note>
     <note priority="medium" time="1087690133">
         support /commands
+        <comment>
+            /msg (personals), /zl, /inst, /zc, /zi, /zci, /zir, /zcir now supported
+        </comment>
     </note>
     <note priority="medium" time="1087692864">
         look for more memory leaks
     </note>
+    <note priority="medium" time="1088196335">
+        Add instance name tabbing support
+    </note>
+    <note priority="medium" time="1088532457">
+        Add support for typing notification (using PING's)
+    </note>
+    <note priority="medium" time="1088814377">
+        Make zlocate be more efficient
+    </note>
+    <note priority="medium" time="1088832146">
+        add zstat support
+    </note>
+    <note priority="medium" time="1089048351">
+        remove unused imhtml buttons (background color, link description)
+    </note>
     <note priority="low" time="1087690041">
-        support multiple zephyr accounts (depends on "use tzc as a backend")
+        support multiple zephyr accounts
+        <comment>
+            Depends on "use tzc as a backend"
+        </comment>
+    </note>
+    <note priority="low" time="1087690240">
+        support gpg
+        <comment>
+            Maybe this should go with gaim-e (gaim-encryption). possibly more useful as a plugin
+        </comment>
     </note>
     <note priority="low" time="1087691895">
         import chats into blist.xml and autojoin on startup?
     </note>
-    <note priority="verylow" time="1087690240">
-        support gpg
-    </note>
     <note priority="verylow" time="1087690286">
         support kzwrite
+        <comment>
+            possibly only useful at MIT. kzwrite might be deprecated
+        </comment>
+    </note>
+    <note priority="verylow" time="1087756756">
+        add xzewd like buddy faces
+        <comment>
+            This may only be useful at MIT. Uses out of protocol mechanisms to get buddy pixmap (hesiod to determine user's homedirectory, and AFS to retrieve the file.
+        </comment>
     </note>
 </todo>
--- a/src/protocols/zephyr/zephyr.c	Wed Jul 07 19:36:38 2004 +0000
+++ b/src/protocols/zephyr/zephyr.c	Wed Jul 07 21:56:57 2004 +0000
@@ -35,18 +35,27 @@
 #include "cmds.h"
 
 #include "zephyr/zephyr.h"
+#include "internal.h"
 
 #include <strings.h>
 
 #define ZEPHYR_FALLBACK_CHARSET "ISO-8859-1"
 
+/* these are deliberately high, since most people don't send multiple "PING"s */
+#define ZEPHYR_TYPING_SEND_TIMEOUT 15
+#define ZEPHYR_TYPING_RECV_TIMEOUT 10
+
 extern Code_t ZGetLocations(ZLocations_t *, int *);
 extern Code_t ZSetLocation(char *);
 extern Code_t ZUnsetLocation();
+extern Code_t ZGetSubscriptions(ZSubscription_t *, int*);
 
 typedef struct _zframe zframe;
 typedef struct _zephyr_triple zephyr_triple;
 
+char ourhost[MAXHOSTNAMELEN];
+char ourhostcanon[MAXHOSTNAMELEN];
+
 /* struct I need for zephyr_to_html */
 struct _zframe {
 	/* true for everything but @color, since inside the parens of that one is
@@ -114,7 +123,7 @@
 static GList *pending_zloc_names = NULL;
 static GSList *subscrips = NULL;
 static int last_id = 0;
-
+unsigned short zephyr_port;
 /* just for debugging */
 static void handle_unknown(ZNotice_t notice)
 {
@@ -154,18 +163,27 @@
 	g_free(zt);
 }
 
-static const char *gaim_zephyr_get_sender()
+static gchar *gaim_zephyr_get_sender()
 {
 	/* will be useful once this plugin can use a backend other
 	   than libzephyr */
-	return ZGetSender();
+	/* XXX add zephyr error reporting */
+	gchar *sender;
+	sender = ZGetSender();
+	if (!sender || !g_ascii_strcasecmp(sender,"")) {
+		sender = "";
+	}
+	return sender;
 }
 
 static const char *gaim_zephyr_get_realm()
 {
 	/* will be useful once this plugin can use a backend other
 	   than libzephyr */
-	return ZGetRealm();
+	gchar *realm=NULL;
+	/* XXX add zephyr error reporting */
+	realm= ZGetRealm();
+	return realm;
 }
 
 /* returns true if zt1 is a subset of zt2.  This function is used to
@@ -265,6 +283,9 @@
 	char *ret;
 
 	len = strlen(message);
+	if (!len) 
+		return g_strdup("");
+
 	ret = g_new0(char, len * 3);
 
 	bzero(ret, len * 3);
@@ -583,6 +604,7 @@
 			char *user;
 			GaimBuddy *b;
 
+			/* XXX add real error reporting */
 			if (ZParseLocations(&notice, NULL, &nlocs, &user) != ZERR_NONE)
 				return;
 
@@ -606,6 +628,7 @@
 					g_string_append_printf(str, _("<br>Hidden or not logged-in"));
 				}
 				for (; nlocs > 0; nlocs--) {
+					/* XXX add real error reporting */
 					ZGetLocations(&locs, &one);
 					g_string_append_printf(str, _("<br>At %s since %s"), locs.host, locs.time);
 				}
@@ -622,22 +645,34 @@
 		GaimConversation *gconv1;
 		GaimConvChat *gcc;
 		char *ptr = notice.z_message + strlen(notice.z_message) + 1;
-		int len = notice.z_message_len - (strlen(notice.z_message) +1);
+		int len; 
 		char *sendertmp = g_strdup_printf("%s", gaim_zephyr_get_sender());
+		int signature_length = strlen(notice.z_message);
+		int message_has_no_body = 0;
 		GaimConvImFlags flags = 0;
-
-		if (len > 0) {
 			gchar *tmpescape;
 
+		/* Need to deal with 0 length  messages to handle typing notification (OPCODE) ping messages */
+		/* One field zephyrs would have caused gaim to crash */
+		if ( (notice.z_message_len == 0) || (signature_length >= notice.z_message_len - 1)) {
+			message_has_no_body = 1;
+			len = 0;
+			gaim_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
+			buf3 = g_strdup("");
+						
+		} else {
+			len =  notice.z_message_len - ( signature_length +1);
+			gaim_debug_info("zephyr","message_size %d %d %d\n",len,notice.z_message_len,signature_length);
 			buf = g_malloc(len + 1);
 			g_snprintf(buf, len + 1, "%s", ptr);
 			g_strchomp(buf);
 			tmpescape = gaim_escape_html(buf);
+			g_free(buf);
 			buf2 = zephyr_to_html(tmpescape);
 			buf3 = zephyr_recv_convert(buf2, strlen(buf2));
 			g_free(buf2);
-			g_free(buf);
 			g_free(tmpescape);
+		}
 
 			if (!g_ascii_strcasecmp(notice.z_class, "MESSAGE") && !g_ascii_strcasecmp(notice.z_class_inst, "PERSONAL") 
                             && !g_ascii_strcasecmp(notice.z_recipient,gaim_zephyr_get_sender())) {
@@ -645,7 +680,12 @@
 				if (!g_ascii_strcasecmp(notice.z_message, "Automated reply:"))
 					flags |= GAIM_CONV_IM_AUTO_RESP;
 				stripped_sender = zephyr_strip_foreign_realm(notice.z_sender);
+
+			if (!g_ascii_strcasecmp(notice.z_opcode,"PING"))
+				serv_got_typing(zgc,stripped_sender,ZEPHYR_TYPING_RECV_TIMEOUT, GAIM_TYPING);
+			else 
 				serv_got_im(zgc, stripped_sender, buf3, flags, time(NULL));
+
 				g_free(stripped_sender);
 			} else {
 				zephyr_triple *zt1, *zt2;
@@ -673,7 +713,6 @@
                                 send_inst = g_strdup_printf("%s %s",sendertmp,notice.z_class_inst);					
                                 send_inst_utf8 = zephyr_recv_convert(send_inst, strlen(send_inst));
                                 if (!send_inst_utf8) {
-                                        fprintf(stderr, "zephyr: send_inst %s became null\n",send_inst);
                                         gaim_debug(GAIM_DEBUG_ERROR, "zephyr","send_inst %s became null\n", send_inst);
                                         send_inst_utf8 = "malformed instance";
                                 }
@@ -704,15 +743,18 @@
 				free_triple(zt1);
 			}
 			g_free(buf3);
-		}
+		
 	}
 }
 
 static gint check_notify(gpointer data)
 {
+	/* XXX add real error reporting */
+
 	while (ZPending()) {
 		ZNotice_t notice;
 		struct sockaddr_in from;
+		/* XXX add real error reporting */
 
 		z_call_r(ZReceiveNotice(&notice, &from));
 
@@ -728,14 +770,14 @@
 			}
 			break;
 		case CLIENTACK:
-			gaim_debug(GAIM_DEBUG_ERROR,"zephyr", "Client ack received\n");
+			gaim_debug_error("zephyr", "Client ack received\n");
 		default:
 			/* we'll just ignore things for now */
 			handle_unknown(notice);
-			gaim_debug(GAIM_DEBUG_ERROR, "zephyr", "Unhandled notice.\n");
+			gaim_debug_error("zephyr", "Unhandled notice.\n");
 			break;
 		}
-
+		/* XXX add real error reporting */
 		ZFreeNotice(&notice);
 	}
 
@@ -766,6 +808,7 @@
 					const char *chk;
 
 					chk = local_zephyr_normalize(b->name);
+					/* XXX add real error reporting */
 					/* doesn't matter if this fails or not; we'll just move on to the next one */
 					ZRequestLocations(chk, &ald, UNACKED, ZAUTH);
 					g_free(ald.user);
@@ -780,6 +823,7 @@
 
 static char *get_exposure_level()
 {
+	/* XXX add real error reporting */
 	char *exposure = ZGetVariable("exposure");
 
 	if (!exposure)
@@ -807,12 +851,32 @@
 	g_strchomp(str);
 }
 
+static void zephyr_inithosts()
+{
+	/* XXX This code may not be Win32 clean */
+	struct hostent *hent;
+	
+	if (gethostname(ourhost, sizeof(ourhost)-1) == -1) {
+		gaim_debug(GAIM_DEBUG_ERROR, "zephyr", "unable to retrieve hostname, %%host%% and %%canon%% will be wrong in subscriptions and have been set to unknown\n");
+		g_stpcpy(ourhost,"unknown");
+		g_stpcpy(ourhostcanon,"unknown");
+		return;
+	}
+	
+	if (!(hent = gethostbyname(ourhost))) {
+		gaim_debug(GAIM_DEBUG_ERROR,"zephyr", "unable to resolve hostname, %%canon%% will be wrong in subscriptions.and has been set to the value of %%host%%, %s\n",ourhost);
+		g_stpcpy(ourhostcanon,ourhost);
+		return;
+	}
+	g_stpcpy(ourhostcanon,hent->h_name);
+	return;
+}
+
 static void process_zsubs()
 {
         /* Loads zephyr chats "(subscriptions) from ~/.zephyr.subs, and 
            registers (subscribes to) them on the server */
 
-        /* XXX write subscriptions into the buddy list somehow "a Zephyr Chats group?"*/
         /* XXX deal with unsubscriptions */
         /* XXX deal with punts */
 
@@ -820,12 +884,15 @@
 	gchar *fname;
 	gchar buff[BUFSIZ];
 
+	zephyr_inithosts();
 	fname = g_strdup_printf("%s/.zephyr.subs", gaim_home_dir());
 	f = fopen(fname, "r");
 	if (f) {
 		char **triple;
 		ZSubscription_t sub;
 		char *recip;
+		char *z_class;
+		char *z_instance;
 
 		while (fgets(buff, BUFSIZ, f)) {
 			strip_comments(buff);
@@ -867,10 +934,37 @@
 					}
 					g_free(tmp);
 					sub.zsub_recipient = recip;
+
+					if (!g_ascii_strcasecmp(triple[0],"%host%")) {
+						z_class = g_strdup(ourhost);
+					} else if (!g_ascii_strcasecmp(triple[0],"%canon%")) {
+						z_class = g_strdup(ourhostcanon);
+					} else {
+						z_class = g_strdup(triple[0]);
+					}
+					sub.zsub_class = z_class;
+					
+					if (!g_ascii_strcasecmp(triple[1],"%host%")) {
+						z_instance = g_strdup(ourhost);
+					} else if (!g_ascii_strcasecmp(triple[1],"%canon%")) {
+						z_instance = g_strdup(ourhostcanon);
+					} else {
+						z_instance = g_strdup(triple[1]);
+					}
+					sub.zsub_classinst = z_instance;
+
+					/* There should be some sort of error report listing classes that couldn't be subbed to.
+					   Not important right now though */
+
 					if (ZSubscribeTo(&sub, 1, 0) != ZERR_NONE) {
+
 						gaim_debug(GAIM_DEBUG_ERROR, "zephyr", "Couldn't subscribe to %s, %s, %s\n", sub.zsub_class, sub.zsub_classinst, sub.zsub_recipient);
 					}
-					subscrips = g_slist_append(subscrips, new_triple(triple[0], triple[1], recip));
+
+					subscrips = g_slist_append(subscrips, new_triple(sub.zsub_class,sub.zsub_classinst,sub.zsub_recipient));
+					/*					  g_hash_table_destroy(sub_hash_table); */
+					g_free(z_instance);
+					g_free(z_class);
 					g_free(recip);
 				}
 				g_strfreev(triple);
@@ -911,6 +1005,7 @@
 {
 	ZSubscription_t sub;
 
+	/* This needs to fixed to deal with multiple accounts somehow */
 	if (zgc) {
 		gaim_notify_error(account->gc, NULL,
 						  _("Already logged in with Zephyr"), _("Because Zephyr uses your system username, you " "are unable to have multiple accounts on it " "when logged in as the same user."));
@@ -918,11 +1013,13 @@
 	}
 
 	zgc = gaim_account_get_connection(account);
-	zgc->flags |= GAIM_CONNECTION_HTML;
-	gaim_connection_update_progress(zgc, _("Connecting"), 0, 2);
+	zgc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_URLDESC;
+	gaim_connection_update_progress(zgc, _("Connecting"), 0, 8);
+
+	/* XXX z_call_s should actually try to report the com_err determined error */
 
 	z_call_s(ZInitialize(), "Couldn't initialize zephyr");
-	z_call_s(ZOpenPort(NULL), "Couldn't open port");
+	z_call_s(ZOpenPort(&zephyr_port), "Couldn't open port");
 	z_call_s(ZSetLocation((char *)
 						  gaim_account_get_string(zgc->account, "exposure_level", EXPOSE_REALMVIS)), "Couldn't set location");
 
@@ -930,9 +1027,12 @@
 	sub.zsub_classinst = "PERSONAL";
 	sub.zsub_recipient = (char *)gaim_zephyr_get_sender();
 
-	/* we don't care if this fails. i'm lying right now. */
 	if (ZSubscribeTo(&sub, 1, 0) != ZERR_NONE) {
-		gaim_debug(GAIM_DEBUG_ERROR, "zephyr", "Couldn't subscribe to messages!\n");
+		/* XXX don't translate this yet. It could be written better */
+		/* XXX error messages could be handled with more detail */
+		gaim_notify_error(account->gc, NULL,
+				  "Unable to subscribe to messages", "Unable to subscribe to initial messages");
+		return;
 	}
 
 	gaim_connection_set_state(zgc, GAIM_CONNECTED);
@@ -968,19 +1068,45 @@
 	}
 
 	while (s) {
+		char *zclass, *zinst, *zrecip;
 		zt = s->data;
 		triple = g_strsplit(zt->name, ",", 3);
-		if (triple[2] != NULL) {
-			if (!g_ascii_strcasecmp(triple[2], "")) {
-				fprintf(fd, "%s,%s,*\n", triple[0], triple[1]);
-			} else if (!g_ascii_strcasecmp(triple[2], gaim_zephyr_get_sender())) {
-				fprintf(fd, "%s,%s,%%me%%\n", triple[0], triple[1]);
+		
+		/* deal with classes */
+		if (!g_ascii_strcasecmp(triple[0],ourhost)) {
+			zclass = g_strdup("%host%");;
+		} else if (!g_ascii_strcasecmp(triple[0],ourhostcanon)) {
+			zclass = g_strdup("%canon%");;
+		} else {
+			zclass = g_strdup(triple[0]);
+		}
+
+		/* deal with instances */
+
+		if (!g_ascii_strcasecmp(triple[1],ourhost)) {
+			zinst = g_strdup("%host%");;
+		} else if (!g_ascii_strcasecmp(triple[1],ourhostcanon)) {
+			zinst = g_strdup("%canon%");;
 			} else {
-				fprintf(fd, "%s\n", zt->name);
+			zinst = g_strdup(triple[1]);
 			}
+
+		/* deal with recipients */
+		if (triple[2] == NULL) {
+			zrecip = g_strdup("*");
+		} else if (!g_ascii_strcasecmp(triple[2],"")){
+			zrecip = g_strdup("*");
+		} else if (!g_ascii_strcasecmp(triple[2], gaim_zephyr_get_sender())) {
+			zrecip = g_strdup("%me%");
 		} else {
-			fprintf(fd, "%s,%s,*\n", triple[0], triple[1]);
+			zrecip = g_strdup(triple[2]);
 		}
+
+		fprintf(fd, "%s,%s,%s\n",zclass,zinst,zrecip);
+		 
+		g_free(zclass);
+		g_free(zinst);
+		g_free(zrecip);
 		g_free(triple);
 		s = s->next;
 	}
@@ -1072,12 +1198,12 @@
 }
 
 static int zephyr_send_message(char* zclass, char* instance, char* recipient, const char *im, 
-                               const char *sig) ;
+			       const char *sig, char *opcode) ;
 
 const char * zephyr_get_signature()
 {
-        const char * sig;
-        sig = ZGetVariable("zwrite-signature");
+	/* XXX add zephyr error reporting */
+	const char * sig =ZGetVariable("zwrite-signature");
         if (!sig) {
                 sig = g_get_real_name();
         }
@@ -1111,7 +1237,7 @@
 	else
 		recipient = local_zephyr_normalize(zt->recipient);
 
-        zephyr_send_message(zt->class,inst,recipient,im,sig);
+	zephyr_send_message(zt->class,inst,recipient,im,sig,"");
         /*        g_free(inst); */
         /*        g_free(recipient);  */
 	return 0;
@@ -1127,13 +1253,13 @@
 	else {
 		sig = zephyr_get_signature();
 	}
-        zephyr_send_message("MESSAGE","PERSONAL",local_zephyr_normalize(who),im,sig);
+	zephyr_send_message("MESSAGE","PERSONAL",local_zephyr_normalize(who),im,sig,"");
 
 	return 1; 
 }
 
 static int zephyr_send_message(char* zclass, char* instance, char* recipient, const char *im, 
-                               const char *sig) 
+			       const char *sig, char *opcode) 
 {
         char *html_buf;
         char *html_buf2;
@@ -1156,11 +1282,18 @@
         notice.z_default_format = "Class $class, Instance $instance:\n" "To: @bold($recipient) at $time $date\n" "From: @bold($1) <$sender>\n\n$2";
 	notice.z_message_len = strlen(html_buf2) + strlen(sig) + 2;
 	notice.z_message = buf;
+	notice.z_opcode = g_strdup(opcode);
+	gaim_debug_info("zephyr","About to send notice");
+	if (! ZSendNotice(&notice, ZAUTH) == ZERR_NONE) {
+		/* XXX handle errors here */
+                return 0;
+	}
+	gaim_debug_info("zephyr","notice sent");
+	g_free(buf);
+
 	g_free(html_buf2);
 	g_free(html_buf);
 
-	ZSendNotice(&notice, ZAUTH);
-	g_free(buf);
         return 1;
 }
 
@@ -1207,6 +1340,8 @@
 	if (ZRequestLocations(normalized_who, &ald, UNACKED, ZAUTH) == ZERR_NONE) {
 		pending_zloc_names = g_list_append(pending_zloc_names,
 				g_strdup(normalized_who));
+	} else {
+		/* XXX deal with errors somehow */
 	}
 }
 
@@ -1218,10 +1353,14 @@
 	}
 
 	if (!g_ascii_strcasecmp(state, _("Hidden"))) {
+		/* XXX handle errors */
 		ZSetLocation(EXPOSE_OPSTAFF);
 		gc->away = g_strdup("");
-	} else if (!g_ascii_strcasecmp(state, _("Online")))
+	} 
+	else if (!g_ascii_strcasecmp(state, _("Online"))) {
+		/* XXX handle errors */
 		ZSetLocation(get_exposure_level());
+	}
 	else /* state is GAIM_AWAY_CUSTOM */ if (msg)
 		gc->away = g_strdup(msg);
 }
@@ -1263,6 +1402,15 @@
 	return m;
 }
 
+/* Called when the server notifies us a message couldn't get sent */
+
+static void zephyr_subscribe_failed(ZSubscription_t *sub)
+{
+	gchar* subscribe_failed = g_strdup_printf(_("Attempt to subscribe to %s,%s,%s failed"), sub->zsub_class, sub->zsub_classinst,sub->zsub_recipient);
+	gaim_notify_error(zgc,"", subscribe_failed, NULL);
+	g_free(subscribe_failed);
+}
+
 static void zephyr_join_chat(GaimConnection * gc, GHashTable * data)
 {
 	ZSubscription_t sub;
@@ -1275,10 +1423,23 @@
 	instname = g_hash_table_lookup(data, "instance");
 	recip = g_hash_table_lookup(data, "recipient");
 
+		
 	if (!classname)
 		return;
+
+	if (!g_ascii_strcasecmp(classname,"%host%"))
+	    classname = g_strdup(ourhost);
+	if (!g_ascii_strcasecmp(classname,"%canon%")) 
+	    classname = g_strdup(ourhostcanon);
+
 	if (!instname || !strlen(instname))
 		instname = "*";
+
+	if (!g_ascii_strcasecmp(instname,"%host%"))
+	    instname = g_strdup(ourhost);
+	if (!g_ascii_strcasecmp(instname,"%canon%")) 
+	    instname = g_strdup(ourhostcanon);
+
 	if (!recip || (*recip == '*'))
 		recip = "";
 	if (!g_ascii_strcasecmp(recip, "%me%"))
@@ -1303,6 +1464,8 @@
 	sub.zsub_recipient = zt1->recipient;
         
 	if (ZSubscribeTo(&sub, 1, 0) != ZERR_NONE) {
+		/* XXX output better subscription information */
+		zephyr_subscribe_failed(&sub);
 		free_triple(zt1);
 		return;
 	}
@@ -1326,11 +1489,56 @@
 	}
 }
 
+static GaimChat *zephyr_find_blist_chat(GaimAccount *account, const char *name)
+{
+	GaimBlistNode *gnode, *cnode;
+
+	/* XXX needs to be %host%,%canon%, and %me% clean */
+	for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
+		for(cnode = gnode->child; cnode; cnode = cnode->next) {
+			GaimChat *chat = (GaimChat*)cnode;
+			char *zclass, *inst, *recip;
+			char** triple;
+			if(!GAIM_BLIST_NODE_IS_CHAT(cnode))
+				continue;
+			if(chat->account !=account)
+				continue;
+			if(!(zclass = g_hash_table_lookup(chat->components, "class")))
+				continue;
+			if(!(inst = g_hash_table_lookup(chat->components, "instance")))
+				inst = g_strdup("");
+			if(!(recip = g_hash_table_lookup(chat->components, "recipient")))
+				recip = g_strdup("");
+			triple = g_strsplit(name,",",3);
+			if (!g_ascii_strcasecmp(triple[0],zclass) && !g_ascii_strcasecmp(triple[1],inst) && !g_ascii_strcasecmp(triple[2],recip))
+				return chat;
+			
+		}
+	}
+	return NULL;
+}
 static const char *zephyr_list_icon(GaimAccount * a, GaimBuddy * b)
 {
 	return "zephyr";
 }
 
+static int zephyr_send_typing(GaimConnection *gc, const char *who, int typing) {
+	gchar *recipient;
+	if (!typing)
+		return 0;
+	/* XXX We probably should care if this fails. Or maybe we don't want to */
+	if (!who) {
+		gaim_debug_info("zephyr", "who is null\n");
+		recipient = local_zephyr_normalize("");
+	} else {
+		recipient = local_zephyr_normalize(who);
+	}
+
+	gaim_debug_info("zephyr","about to send typing notification to %s",recipient);
+	zephyr_send_message("MESSAGE","PERSONAL",recipient,"","","PING");
+	gaim_debug_info("zephyr","sent typing notification");
+	return ZEPHYR_TYPING_SEND_TIMEOUT;
+}
 
 
 
@@ -1358,12 +1566,16 @@
                                       const char *cmd, char **args, char **error)
 {
         char *recipient;
+        
         if (!g_ascii_strcasecmp(args[0],"*"))
-                recipient = local_zephyr_normalize("");
+                return GAIM_CMD_RET_FAILED;  /* "*" is not a valid argument */
         else 
                 recipient = local_zephyr_normalize(args[0]);
 
-        if (zephyr_send_message("MESSAGE","PERSONAL",recipient,args[1],zephyr_get_signature()))
+        if (strlen(recipient) < 1)
+                return GAIM_CMD_RET_FAILED; /* a null recipient is a chat message, not an IM */
+
+	if (zephyr_send_message("MESSAGE","PERSONAL",recipient,args[1],zephyr_get_signature(),""))
                 return GAIM_CMD_RET_OK;
         else 
                 return GAIM_CMD_RET_FAILED;
@@ -1406,7 +1618,7 @@
                                               const char *cmd, char **args, char **error)
 {
         /* args = instance, message */
-        if (zephyr_send_message("message",args[0],"",args[1],zephyr_get_signature())) 
+	if ( zephyr_send_message("message",args[0],"",args[1],zephyr_get_signature(),""))
                 return GAIM_CMD_RET_OK;
         else 
                 return GAIM_CMD_RET_FAILED;
@@ -1416,7 +1628,7 @@
                                               const char *cmd, char **args, char **error)
 {
         /* args = class, instance, message */
-        if ( zephyr_send_message(args[0],args[1],"",args[2],zephyr_get_signature()))
+	if ( zephyr_send_message(args[0],args[1],"",args[2],zephyr_get_signature(),""))
                 return GAIM_CMD_RET_OK;
         else 
                 return GAIM_CMD_RET_FAILED;
@@ -1426,7 +1638,7 @@
                                               const char *cmd, char **args, char **error)
 {
         /* args = class, instance, recipient, message */
-        if ( zephyr_send_message(args[0],args[1],args[2],args[3],zephyr_get_signature())) 
+	if ( zephyr_send_message(args[0],args[1],args[2],args[3],zephyr_get_signature(),"")) 
                 return GAIM_CMD_RET_OK;
         else
                 return GAIM_CMD_RET_FAILED;
@@ -1436,7 +1648,7 @@
                                               const char *cmd, char **args, char **error)
 {
         /* args = instance, recipient, message */
-        if ( zephyr_send_message("message",args[0],args[1],args[2],zephyr_get_signature())) 
+	if ( zephyr_send_message("message",args[0],args[1],args[2],zephyr_get_signature(),"")) 
                 return GAIM_CMD_RET_OK;
         else
                 return GAIM_CMD_RET_FAILED;
@@ -1446,7 +1658,7 @@
                                               const char *cmd, char **args, char **error)
 {
         /* args = class, message */
-        if ( zephyr_send_message(args[0],"PERSONAL","",args[1],zephyr_get_signature()))
+	if ( zephyr_send_message(args[0],"PERSONAL","",args[1],zephyr_get_signature(),"")) 
                 return GAIM_CMD_RET_OK;
         else 
                 return GAIM_CMD_RET_FAILED;
@@ -1530,8 +1742,8 @@
                 zst.zsub_class = zt->class;
                 zst.zsub_classinst = zt->instance;
                 zst.zsub_recipient = zt->recipient;
-                ZSubscribeTo(&zst, 1, 0);
                 /* XXX We really should care if this fails */
+		ZSubscribeTo(&zst, 1, 0);
                 s = s->next;
         }
         /* XXX handle unsubscriptions */
@@ -1547,6 +1759,41 @@
 }
 
 
+static void zephyr_action_get_subs_from_server(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	gchar *title;
+	int retval, nsubs, one,i;
+	ZSubscription_t subs;
+	GString* subout = g_string_new("Subscription list<br>");
+
+	title = g_strdup_printf("Server subscriptions for %s", gaim_zephyr_get_sender());
+	
+	if (zephyr_port == 0) {
+		gaim_debug(GAIM_DEBUG_ERROR,"zephyr", "error while retrieving port");
+		return;
+	} 
+	if ((retval = ZRetrieveSubscriptions(zephyr_port,&nsubs)) != ZERR_NONE) {
+		/* XXX better error handling */
+		gaim_debug(GAIM_DEBUG_ERROR,"zephyr", "error while retrieving subscriptions from server");
+		return;
+	}
+	g_string_append_printf(subout,"Subscription list <br>");
+	for(i=0;i<nsubs;i++) {
+		one = 1;
+		if ((retval = ZGetSubscriptions(&subs,&one)) != ZERR_NONE) {
+			/* XXX better error handling */
+			gaim_debug(GAIM_DEBUG_ERROR,"zephyr", "error while retrieving individual subscription");
+			return;
+		}
+		g_string_append_printf(subout, "Class %s Instance %s Recipient %s<br>",
+				       subs.zsub_class, subs.zsub_classinst,
+				       subs.zsub_recipient);
+	}
+	gaim_notify_formatted(gc, title, title, NULL,  subout->str, NULL, NULL);
+}
+
+
 static GList *zephyr_actions(GaimPlugin *plugin, gpointer context)
 {
 	GList *list = NULL;
@@ -1555,6 +1802,9 @@
 	act = gaim_plugin_action_new(_("Resubscribe"), zephyr_action_resubscribe);
 	list = g_list_append(list, act);
 
+	act = gaim_plugin_action_new(_("Retrieve subscriptions from server"), zephyr_action_get_subs_from_server);
+	list = g_list_append(list,act);
+
 	return list;
 }
 
@@ -1563,35 +1813,35 @@
 static GaimPluginProtocolInfo prpl_info = {
 	GAIM_PRPL_API_VERSION,
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_NO_PASSWORD,
-	NULL,					/* user_splits */
-	NULL,					/* protocol_options */
-	NO_BUDDY_ICONS,			/* icon_spec */
-	zephyr_list_icon,		/* list_icon */
-	NULL,					/* list_emblems */
-	NULL,					/* status_text */
-	NULL,					/* tooltip_text */
+	NULL, /* ??? user_splits */
+	NULL,  /* ??? protocol_options */
+	NO_BUDDY_ICONS,
+	zephyr_list_icon,
+	NULL, /* ??? list_emblems */
+	NULL, /* ??? status_text */
+	NULL, /* ??? tooltip_text */
 	zephyr_away_states,		/* away_states */
-	NULL,					/* blist_node_menu */
+	NULL, /* ??? blist_node_menu - probably all useful actions are already handled*/
 	zephyr_chat_info,		/* chat_info */
 	zephyr_login,			/* login */
 	zephyr_close,			/* close */
 	zephyr_send_im,			/* send_im */
-	NULL,					/* set_info */
-	NULL,					/* send_typing */
+	NULL,	/* XXX set info (Location?) */
+	zephyr_send_typing,  /* send_typing */
 	zephyr_zloc,			/* get_info */
-	zephyr_set_away,		/* set_away */
-	NULL,					/* set_idle */
-	NULL,					/* change_passwd */
+	zephyr_set_away,  /* XXX set_away need to fix */
+	NULL, /* ??? set idle */
+	NULL, /* change password */
 	NULL,					/* add_buddy */
 	NULL,					/* add_buddies */
 	NULL,					/* remove_buddy */
 	NULL,					/* remove_buddies */
-	NULL,					/* add_permit */
-	NULL,					/* add_deny */
-	NULL,					/* rem_permit */
-	NULL,					/* rem_deny */
-	NULL,					/* set_permit_deny */
-	NULL,					/* warn */
+	NULL, /* add_permit -- not useful, since anyone can zephyr you by default*/
+	NULL, /* XXX add deny  -- maybe put an auto "I'm ignoring your zephyrs*/
+	NULL, /* remove_permit -- not useful, see add permit */
+	NULL, /* XXX remove deny -- remove above deny, */
+	NULL, /* ??? set permit deny */
+	NULL,  /* --- warn  */
 	zephyr_join_chat,		/* join_chat */
 	NULL,					/* reject_chat */
 	NULL,					/* chat_invite */
@@ -1600,22 +1850,22 @@
 	zephyr_chat_send,		/* chat_send */
 	NULL,					/* keepalive */
 	NULL,					/* register_user */
-	NULL,					/* get_cb_info */
+	NULL, /* XXX get_cb_info get chat buddy info */
 	NULL,					/* get_cb_away */
 	NULL,					/* alias_buddy */
 	NULL,					/* group_buddy */
 	NULL,					/* rename_group */
-	NULL,					/* buddy_free */
+	NULL, /* ??? buddy_free */
 	NULL,					/* convo_closed */
 	zephyr_normalize,		/* normalize */
-	NULL,					/* set_buddy_icon */
+	NULL, /* XXX set_buddy_icon */
 	NULL,					/* remove_group */
-	NULL,					/* get_cb_real_name */
+	NULL, /* XXX get_cb_real_name */
 	zephyr_chat_set_topic,	/* set_chat_topic */
-	NULL,					/* find_blist_chat */
+	zephyr_find_blist_chat, /* find_blist_chat */
 	NULL,					/* roomlist_get_list */
 	NULL,					/* roomlist_cancel */
-	NULL,					/* roomlist_expand_category */
+	NULL,  /* roomlist_expand_category */
 	NULL,					/* can_receive_file */
 	NULL					/* send_file */
 };