changeset 447:03bb57383cea

Initial Multi-User Chat support This patch adds basic MUC support. We now can: - join an existing room; - create and unlock a room using the /rawxml command; - set our nickname; - send/receive chatgroup messages; - see the members of the room; - leave the room. Chatroom logging is currently disabled, as it could do some unexpected things.
author Mikael Berthe <mikael@lilotux.net>
date Sun, 25 Sep 2005 01:01:44 +0200
parents 9f4e9e9aaf08
children 39a28cb59af3
files mcabber/src/commands.c mcabber/src/compl.h mcabber/src/hooks.c mcabber/src/hooks.h mcabber/src/jabglue.c mcabber/src/jabglue.h mcabber/src/roster.c mcabber/src/roster.h mcabber/src/screen.c
diffstat 9 files changed, 280 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/mcabber/src/commands.c	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/commands.c	Sun Sep 25 01:01:44 2005 +0200
@@ -51,6 +51,7 @@
 static void do_connect(char *arg);
 static void do_disconnect(char *arg);
 static void do_rawxml(char *arg);
+static void do_room(char *arg);
 
 // Global variable for the commands list
 static GSList *Commands;
@@ -92,7 +93,7 @@
           0, &do_move);
   cmd_add("msay", "Send a multi-lines message to the selected buddy",
           COMPL_MULTILINE, 0, &do_msay);
-  //cmd_add("nick");
+  cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room);
   cmd_add("quit", "Exit the software", 0, 0, NULL);
   cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml);
   cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename);
@@ -146,6 +147,11 @@
   compl_add_category_word(COMPL_MULTILINE, "begin");
   compl_add_category_word(COMPL_MULTILINE, "send");
   compl_add_category_word(COMPL_MULTILINE, "verbatim");
+
+  // Room category
+  compl_add_category_word(COMPL_ROOM, "join");
+  compl_add_category_word(COMPL_ROOM, "leave");
+  compl_add_category_word(COMPL_ROOM, "names");
 }
 
 //  expandalias(line)
@@ -231,11 +237,13 @@
     return;
   }
 
-  // local part (UI, logging, etc.)
-  hk_message_out(jid, 0, msg);
+  if (buddy_gettype(BUDDATA(current_buddy)) != ROSTER_TYPE_ROOM) {
+    // local part (UI, logging, etc.)
+    hk_message_out(jid, 0, msg);
+  }
 
   // Network part
-  jb_send_msg(jid, msg);
+  jb_send_msg(jid, msg, buddy_gettype(BUDDATA(current_buddy)));
 }
 
 //  process_command(line)
@@ -547,7 +555,7 @@
   }
 
   bud = BUDDATA(current_buddy);
-  if (!(buddy_gettype(bud) & ROSTER_TYPE_USER)) {
+  if (!(buddy_gettype(bud) & (ROSTER_TYPE_USER|ROSTER_TYPE_ROOM))) {
     scr_LogPrint(LPRINT_NORMAL, "This is not a user");
     return;
   }
@@ -603,7 +611,7 @@
   }
 
   bud = BUDDATA(current_buddy);
-  if (!(buddy_gettype(bud) & ROSTER_TYPE_USER)) {
+  if (!(buddy_gettype(bud) & (ROSTER_TYPE_USER|ROSTER_TYPE_ROOM))) {
     scr_LogPrint(LPRINT_NORMAL, "This is not a user");
     return;
   }
@@ -683,6 +691,7 @@
     }
 
     if (type == ROSTER_TYPE_USER)       typestr = "user";
+    else if (type == ROSTER_TYPE_ROOM)  typestr = "chatroom";
     else if (type == ROSTER_TYPE_AGENT) typestr = "agent";
     snprintf(buffer, 127, "Type: %s", typestr);
     scr_WriteIncomingMessage(jid, buffer, 0, HBB_PREFIX_INFO);
@@ -892,6 +901,83 @@
   }
 }
 
+static void do_room(char *arg)
+{
+  gpointer bud;
+
+  if (!arg || (!*arg)) {
+    scr_LogPrint(LPRINT_NORMAL, "Missing parameter");
+    return;
+  }
+
+  bud = BUDDATA(current_buddy);
+
+  if (!strncasecmp(arg, "join", 4))  {
+    GSList *roster_usr;
+    char *roomname, *nick, *roomid;
+
+    arg += 4;
+    if (*arg++ != ' ') {
+      scr_LogPrint(LPRINT_NORMAL, "Wrong or missing parameter");
+      return;
+    }
+    for (; *arg && *arg == ' '; arg++)
+      ;
+
+    roomname = g_strdup(arg);
+    nick = strchr(roomname, ' ');
+    if (!nick) {
+      scr_LogPrint(LPRINT_NORMAL, "Missing parameter (nickname)");
+      g_free(roomname);
+      return;
+    }
+
+    *nick++ = 0;
+    while (*nick && *nick == ' ')
+      nick++;
+    if (!*nick) {
+      scr_LogPrint(LPRINT_NORMAL, "Missing parameter (nickname)");
+      g_free(roomname);
+      return;
+    }
+    // room syntax: "room@server/nick"
+    // FIXME: check roomid is a jid
+    roomid = g_strdup_printf("%s/%s", roomname, nick);
+    jb_room_join(roomid);
+    roster_usr = roster_add_user(roomname, NULL, NULL, ROSTER_TYPE_ROOM);
+    if (roster_usr)
+      buddy_setnickname(roster_usr->data, nick);
+    g_free(roomname);
+    g_free(roomid);
+    buddylist_build();
+    update_roster = TRUE;
+  } else if (!strncasecmp(arg, "leave", 5))  {
+    char *roomid;
+    arg += 5;
+    for (; *arg && *arg == ' '; arg++)
+      ;
+    if (!(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) {
+      scr_LogPrint(LPRINT_NORMAL, "This isn't a chatroom");
+      return;
+    }
+    roomid = g_strdup_printf("%s/%s", buddy_getjid(bud),
+                             buddy_getnickname(bud));
+    jb_setstatus(offline, roomid, arg);
+    g_free(roomid);
+    buddy_setnickname(bud, NULL);
+    buddy_del_all_resources(bud);
+    scr_LogPrint(LPRINT_NORMAL, "You have left %s", buddy_getjid(bud));
+  } else if (!strcasecmp(arg, "names"))  {
+    if (!(buddy_gettype(bud) & ROSTER_TYPE_ROOM)) {
+      scr_LogPrint(LPRINT_NORMAL, "This isn't a chatroom");
+      return;
+    }
+    do_info(NULL);
+  } else {
+    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+  }
+}
+
 static void do_connect(char *arg)
 {
   mcabber_connect();
--- a/mcabber/src/compl.h	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/compl.h	Sun Sep 25 01:01:44 2005 +0200
@@ -14,6 +14,7 @@
 #define COMPL_GROUP       (1<<9)
 #define COMPL_GROUPNAME   (1<<10)
 #define COMPL_MULTILINE   (1<<11)
+#define COMPL_ROOM        (1<<12)
 
 void    compl_add_category_word(guint, const char *command);
 void    compl_del_category_word(guint categ, const char *word);
--- a/mcabber/src/hooks.c	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/hooks.c	Sun Sep 25 01:01:44 2005 +0200
@@ -30,33 +30,59 @@
 
 static char *extcmd;
 
-inline void hk_message_in(const char *jid, time_t timestamp, const char *msg,
-                          const char *type)
+inline void hk_message_in(const char *jid, const char *resname,
+                          time_t timestamp, const char *msg, const char *type)
 {
   int new_guy = FALSE;
-  int message_flags;
+  int is_groupchat = FALSE;
+  int message_flags = 0;
+  guint rtype = ROSTER_TYPE_USER;
+  char *wmsg;
+  GSList *roster_usr;
+
+  if (type && !strcmp(type, "groupchat")) {
+    rtype = ROSTER_TYPE_ROOM;
+    is_groupchat = TRUE;
+    if (!resname) {
+      message_flags = HBB_PREFIX_INFO;
+      resname = "";
+    }
+    wmsg = g_strdup_printf("<%s> %s", resname, msg);
+  } else {
+    wmsg = (char*) msg;
+  }
 
   // If this user isn't in the roster, we add it
-  if (!roster_exists(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
-    roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER);
+  roster_usr = roster_find(jid, jidsearch,
+                           rtype|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
+  if (!roster_usr) {
+    roster_add_user(jid, NULL, NULL, rtype);
     new_guy = TRUE;
+  } else {
+    if (buddy_gettype(roster_usr->data) == ROSTER_TYPE_ROOM)
+      is_groupchat = TRUE;
   }
 
   if (type && !strcmp(type, "error")) {
     message_flags = HBB_PREFIX_ERR | HBB_PREFIX_IN;
     scr_LogPrint(LPRINT_LOGNORM, "Error message received from <%s>", jid);
-  } else
-    message_flags = 0;
+  }
 
   // Note: the hlog_write should not be called first, because in some
   // cases scr_WriteIncomingMessage() will load the history and we'd
   // have the message twice...
-  scr_WriteIncomingMessage(jid, msg, timestamp, message_flags);
+  scr_WriteIncomingMessage(jid, wmsg, timestamp, message_flags);
+
   // We don't log the message if it is an error message
-  if (!(message_flags & HBB_PREFIX_ERR))
-    hlog_write_message(jid, timestamp, FALSE, msg);
+  // or if it is a groupchat message
+  // XXX We could use an option here to know if we should write GC messages...
+  if (!is_groupchat && !(message_flags & HBB_PREFIX_ERR))
+    hlog_write_message(jid, timestamp, FALSE, wmsg);
+
   // External command
-  hk_ext_cmd(jid, 'M', 'R', NULL);
+  if (!is_groupchat)
+    hk_ext_cmd(jid, 'M', 'R', NULL);
+
   // We need to rebuild the list if the sender is unknown or
   // if the sender is offline/invisible and hide_offline_buddies is set
   if (new_guy ||
@@ -66,6 +92,8 @@
     buddylist_build();
     update_roster = TRUE;
   }
+
+  if (rtype == ROSTER_TYPE_ROOM) g_free(wmsg);
 }
 
 inline void hk_message_out(const char *jid, time_t timestamp, const char *msg)
--- a/mcabber/src/hooks.h	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/hooks.h	Sun Sep 25 01:01:44 2005 +0200
@@ -5,8 +5,8 @@
 #include "jabglue.h"
 
 
-inline void hk_message_in(const char *jid, time_t timestamp, const char *msg,
-                          const char *type);
+inline void hk_message_in(const char *jid, const char *resname,
+                          time_t timestamp, const char *msg, const char *type);
 inline void hk_message_out(const char *jid, time_t timestamp, const char *msg);
 inline void hk_statuschange(const char *jid, const char *resname, gchar prio,
                             time_t timestamp, enum imstatus status,
--- a/mcabber/src/jabglue.c	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/jabglue.c	Sun Sep 25 01:01:44 2005 +0200
@@ -333,10 +333,17 @@
   mystatus = st;
 }
 
-void jb_send_msg(const char *jid, const char *text)
+void jb_send_msg(const char *jid, const char *text, int type)
 {
+  gchar *strtype;
   gchar *buffer = to_utf8(text);
-  xmlnode x = jutil_msgnew(TMSG_CHAT, (char*)jid, 0, (char*)buffer);
+
+  if (type == ROSTER_TYPE_ROOM)
+    strtype = TMSG_GROUPCHAT;
+  else
+    strtype = TMSG_CHAT;
+
+  xmlnode x = jutil_msgnew(strtype, (char*)jid, 0, (char*)buffer);
   jab_send(jc, x);
   xmlnode_free(x);
   g_free(buffer);
@@ -462,6 +469,26 @@
   g_free(cleanjid);
 }
 
+// Join a MUC room
+// room syntax: "room@server/nick"
+void jb_room_join(const char *room)
+{
+  xmlnode x, y;
+
+  if (!online) return;
+  if (!room)   return;
+
+  x = jutil_presnew(JPACKET__UNKNOWN, 0, 0);
+  xmlnode_put_attrib(x, "from", jid_full(jc->user));
+  xmlnode_put_attrib(x, "to", room);
+  y = xmlnode_insert_tag(x, "x");
+  xmlnode_put_attrib(y, "xmlns", "http://jabber.org/protocol/muc");
+
+  jab_send(jc, x);
+  xmlnode_free(x);
+  jb_reset_keepalive();
+}
+
 void postlogin()
 {
   //int i;
@@ -571,6 +598,7 @@
         const char *enc, time_t timestamp)
 {
   char *jid;
+  const char *rname;
   gchar *buffer = from_utf8(body);
 
   jid = jidtodisp(from);
@@ -585,18 +613,9 @@
     return;
   }
 
-  /*
-  //char *u, *h, *r;
-  //jidsplit(from, &u, &h, &r);
-  // Maybe we should remember the resource?
-  if (r)
-    scr_LogPrint(LPRINT_NORMAL,
-                 "There is an extra part in message (resource?): %s", r);
-  //scr_LogPrint(LPRINT_NORMAL, "Msg from <%s>, type=%s",
-  //             jidtodisp(from), type);
-  */
-
-  hk_message_in(jid, timestamp, buffer, type);
+  rname = strchr(from, '/');
+  if (rname) rname++;
+  hk_message_in(jid, rname, timestamp, buffer, type);
   g_free(jid);
   g_free(buffer);
 }
@@ -727,7 +746,7 @@
 
 void packethandler(jconn conn, jpacket packet)
 {
-  char *p, *r;
+  char *p, *r, *s;
   const char *m, *rname;
   xmlnode x, y;
   char *from=NULL, *type=NULL, *body=NULL, *enc=NULL;
@@ -906,7 +925,28 @@
 
             }
           }
+        } else if (!strcmp(type, "get")) {
+          p = xmlnode_get_attrib(packet->x, "id");
+          if (p) {
+            xmlnode z;
+
+            id = p;
+            x = xmlnode_new_tag("iq");
+            xmlnode_put_attrib(x, "type", "result");
+            xmlnode_put_attrib(x, "to", from);
+            xmlnode_put_attrib(x, "id", id);
+            xmlnode_put_attrib(x, "type", "error");
+            y = xmlnode_insert_tag(x, "error");
+            xmlnode_put_attrib(y, "code", "503");
+            xmlnode_put_attrib(y, "type", "cancel");
+            z = xmlnode_insert_tag(y, "feature-not-implemented");
+            xmlnode_put_attrib(z, "xmlns",
+                               "urn:ietf:params:xml:ns:xmpp-stanzas");
+            jab_send(conn, x);
+            xmlnode_free(x);
+          }
         } else if (!strcmp(type, "set")) {
+          /* FIXME: send error */
         } else if (!strcmp(type, "error")) {
           if ((x = xmlnode_get_tag(packet->x, "error")) != NULL)
             display_server_error(x);
@@ -940,19 +980,43 @@
           ust = offline;
 
         if ((x = xmlnode_get_tag(packet->x, "status")) != NULL)
-          p = from_utf8(xmlnode_get_data(x));
+          s = from_utf8(xmlnode_get_data(x));
         else
-          p = NULL;
+          s = NULL;
 
         // Call hk_statuschange() if status has changed or if the
         // status message is different
         rname = strchr(from, '/');
         if (rname) rname++;
+
+        // Check for MUC presence packet
+        // There can be multiple <x> tags!!
+        x = xmlnode_get_firstchild(packet->x);
+        for ( ; x; x = xmlnode_get_nextsibling(x)) {
+          if ((p = xmlnode_get_name(x)) && !strcmp(p, "x"))
+            if ((p = xmlnode_get_attrib(x, "xmlns")) &&
+                !strncasecmp(p, "http://jabber.org/protocol/muc", 30))
+              break;
+        }
+        if (x) {    // This is a MUC presence message
+          roster_add_user(r, NULL, NULL, ROSTER_TYPE_ROOM);
+
+          if (rname)
+            roster_setstatus(r, rname, bpprio, ust, NULL);
+          else
+            scr_LogPrint(LPRINT_LOGNORM, "MUC DBG: no rname!"); /* DBG */
+
+          buddylist_build();
+          scr_DrawRoster();
+          break;
+        }
+
+        // Not a MUC message, so this is a regular buddy...
         m = roster_getstatusmsg(r, rname);
-        if ((ust != roster_getstatus(r, rname)) || (p && (!m || strcmp(p, m))))
-          hk_statuschange(r, rname, bpprio, 0, ust, p);
+        if ((ust != roster_getstatus(r, rname)) || (s && (!m || strcmp(s, m))))
+          hk_statuschange(r, rname, bpprio, 0, ust, s);
         g_free(r);
-        if (p) g_free(p);
+        if (s) g_free(s);
         break;
 
     case JPACKET_S10N:
--- a/mcabber/src/jabglue.h	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/jabglue.h	Sun Sep 25 01:01:44 2005 +0200
@@ -47,11 +47,12 @@
 void jb_updatebuddy(const char *jid, const char *name, const char *group);
 inline enum imstatus jb_getstatus();
 void jb_setstatus(enum imstatus st, const char *recipient, const char *msg);
-void jb_send_msg(const char *, const char *);
+void jb_send_msg(const char *jid, const char *text, int type);
 void jb_send_raw(const char *str);
 void jb_keepalive();
 inline void jb_reset_keepalive();
 void jb_set_keepalive_delay(unsigned int delay);
 inline void jb_set_priority(unsigned int priority);
+void jb_room_join(const char *room);
 
 #endif /* __JABGLUE_H__ */
--- a/mcabber/src/roster.c	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/roster.c	Sun Sep 25 01:01:44 2005 +0200
@@ -44,7 +44,7 @@
   guint type;
   enum subscr subscription;
   GSList *resource;
-  res *cur_res;
+  gchar *nickname; // For groupchats
   guint flags;
   // list: user -> points to his group; group -> points to its users list
   GSList *list;
@@ -197,7 +197,8 @@
   if (!jidname) return NULL;
 
   if (!roster_type)
-    roster_type = ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_GROUP;
+    roster_type = ROSTER_TYPE_USER | ROSTER_TYPE_ROOM |
+                  ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP;
 
   sample.type = roster_type;
   if (type == jidsearch) {
@@ -249,7 +250,9 @@
   roster *my_group;
   GSList *slist;
 
-  if ((type != ROSTER_TYPE_USER) && (type != ROSTER_TYPE_AGENT)) {
+  if ((type != ROSTER_TYPE_USER) &&
+      (type != ROSTER_TYPE_ROOM) &&
+      (type != ROSTER_TYPE_AGENT)) {
     // XXX Error message?
     return NULL;
   }
@@ -258,7 +261,7 @@
   if (!group)  group = "";
 
   // #1 Check this user doesn't already exist
-  slist = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  slist = roster_find(jid, jidsearch, type|ROSTER_TYPE_AGENT);
   if (slist) return slist;
   // #2 add group if necessary
   slist = roster_add_group(group);
@@ -276,7 +279,7 @@
     roster_usr->name = g_strdup(str);
     g_free(str);
   }
-  roster_usr->type  = type; //ROSTER_TYPE_USER;
+  roster_usr->type  = type;
   roster_usr->list  = slist;    // (my_group SList element)
   // #4 Insert node (sorted)
   my_group->list = g_slist_insert_sorted(my_group->list, roster_usr,
@@ -292,7 +295,8 @@
   roster *roster_usr;
   GSList *node;
 
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM);
   if (sl_user == NULL)
     return;
   roster_usr = (roster*)sl_user->data;
@@ -304,6 +308,7 @@
   // Let's free memory (jid, name, status message)
   if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
   if (roster_usr->name)       g_free((gchar*)roster_usr->name);
+  if (roster_usr->nickname)   g_free((gchar*)roster_usr->nickname);
   free_all_resources(&roster_usr->resource);
   g_free(roster_usr);
 
@@ -341,6 +346,7 @@
       // Free name and jid
       if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
       if (roster_usr->name)       g_free((gchar*)roster_usr->name);
+      if (roster_usr->nickname)   g_free((gchar*)roster_usr->nickname);
       free_all_resources(&roster_usr->resource);
       g_free(roster_usr);
       sl_usr = g_slist_next(sl_usr);
@@ -371,7 +377,8 @@
   roster *roster_usr;
   res *p_res;
 
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
   // If we can't find it, we add it
   if (sl_user == NULL)
     sl_user = roster_add_user(jid, NULL, NULL, ROSTER_TYPE_USER);
@@ -405,7 +412,8 @@
   GSList *sl_user;
   roster *roster_usr;
 
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
   if (sl_user == NULL)
     return;
 
@@ -425,7 +433,8 @@
   GSList *sl_user;
   roster *roster_usr, *roster_grp;
 
-  sl_user = roster_find(jid, jidsearch, ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
+  sl_user = roster_find(jid, jidsearch,
+                        ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT);
   if (sl_user == NULL)
     return;
 
@@ -527,14 +536,6 @@
   return roster_usr->type;
 }
 
-inline guint roster_exists(const char *jidname, enum findwhat type,
-        guint roster_type)
-{
-  if (roster_find(jidname, type, roster_type))
-    return TRUE;
-  return FALSE;
-}
-
 
 /* ### BuddyList functions ### */
 
@@ -693,6 +694,7 @@
   // Free old buddy
   if (roster_usr->jid)        g_free((gchar*)roster_usr->jid);
   if (roster_usr->name)       g_free((gchar*)roster_usr->name);
+  if (roster_usr->nickname)   g_free((gchar*)roster_usr->nickname);
   free_all_resources(&roster_usr->resource);
   g_free(roster_usr);
 
@@ -740,6 +742,26 @@
   return roster_usr->name;
 }
 
+void buddy_setnickname(gpointer rosterdata, char *newname)
+{
+  roster *roster_usr = rosterdata;
+
+  if (!roster_usr->type & ROSTER_TYPE_ROOM) return;
+
+  if (roster_usr->nickname) {
+    g_free((gchar*)roster_usr->nickname);
+    roster_usr->nickname = NULL;
+  }
+  if (newname)
+    roster_usr->nickname = g_strdup(newname);
+}
+
+const char *buddy_getnickname(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+  return roster_usr->nickname;
+}
+
 //  buddy_getgroupname()
 // Returns a pointer on buddy's group name.
 const char *buddy_getgroupname(gpointer rosterdata)
@@ -813,6 +835,18 @@
   return reslist;
 }
 
+//  buddy_del_all_resources()
+// Remove all resources from the specified buddy
+void buddy_del_all_resources(gpointer rosterdata)
+{
+  roster *roster_usr = rosterdata;
+
+  while (roster_usr->resource) {
+    res *r = roster_usr->resource->data;
+    del_resource(roster_usr, r->name);
+  }
+}
+
 //  buddy_setflags()
 // Set one or several flags to value (TRUE/FALSE)
 void buddy_setflags(gpointer rosterdata, guint flags, guint value)
@@ -873,7 +907,7 @@
         if ((bname) && (*bname))
           list = g_slist_append(list, g_strdup(bname));
       }
-    } else { // ROSTER_TYPE_USER (jid)
+    } else { // ROSTER_TYPE_USER (jid) (or agent, or chatroom...)
         const char *bjid = buddy_getjid(BUDDATA(buddy));
         if (bjid)
           list = g_slist_append(list, g_strdup(bjid));
--- a/mcabber/src/roster.h	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/roster.h	Sun Sep 25 01:01:44 2005 +0200
@@ -28,6 +28,7 @@
 #define ROSTER_TYPE_USER    1
 #define ROSTER_TYPE_GROUP   2
 #define ROSTER_TYPE_AGENT   4
+#define ROSTER_TYPE_ROOM    8
 
 // Flags:
 #define ROSTER_FLAG_MSG     1   // Message not read
@@ -48,6 +49,7 @@
 GSList *roster_add_group(const char *name);
 GSList *roster_add_user(const char *jid, const char *name, const char *group,
         guint type);
+GSList *roster_find(const char *jidname, enum findwhat type, guint roster_type);
 void    roster_del_user(const char *jid);
 void    roster_free(void);
 void    roster_setstatus(const char *jid, const char *resname, gchar prio,
@@ -58,8 +60,6 @@
 enum imstatus roster_getstatus(const char *jid, const char *resname);
 const char   *roster_getstatusmsg(const char *jid, const char *resname);
 guint   roster_gettype(const char *jid);
-inline guint roster_exists(const char *jidname, enum findwhat type,
-                           guint roster_type);
 
 void    buddylist_build(void);
 void    buddy_hide_group(gpointer rosterdata, int hide);
@@ -68,6 +68,8 @@
 const char *buddy_getjid(gpointer rosterdata);
 void        buddy_setname(gpointer rosterdata, char *newname);
 const char *buddy_getname(gpointer rosterdata);
+void        buddy_setnickname(gpointer rosterdata, char *newname);
+const char *buddy_getnickname(gpointer rosterdata);
 guint   buddy_gettype(gpointer rosterdata);
 void    buddy_setgroup(gpointer rosterdata, char *newgroupname);
 const char *buddy_getgroupname(gpointer rosterdata);
@@ -76,6 +78,7 @@
 const char *buddy_getstatusmsg(gpointer rosterdata, const char *resname);
 gchar   buddy_getresourceprio(gpointer rosterdata, const char *resname);
 GSList *buddy_getresources(gpointer rosterdata);
+void    buddy_del_all_resources(gpointer rosterdata);
 void    buddy_setflags(gpointer rosterdata, guint flags, guint value);
 guint   buddy_getflags(gpointer rosterdata);
 GList  *buddy_search(char *string);
--- a/mcabber/src/screen.c	Sun Sep 25 00:44:11 2005 +0200
+++ b/mcabber/src/screen.c	Sun Sep 25 01:01:44 2005 +0200
@@ -720,6 +720,7 @@
     enum imstatus budstate;
     unsigned short ismsg = buddy_getflags(BUDDATA(buddy)) & ROSTER_FLAG_MSG;
     unsigned short isgrp = buddy_gettype(BUDDATA(buddy)) & ROSTER_TYPE_GROUP;
+    unsigned short ismuc = buddy_gettype(BUDDATA(buddy)) & ROSTER_TYPE_ROOM;
     unsigned short ishid = buddy_getflags(BUDDATA(buddy)) & ROSTER_FLAG_HIDE;
 
     if (rOffset > 0) {
@@ -749,6 +750,8 @@
         wattrset(rosterWnd, COLOR_PAIR(COLOR_BD_DES));
     }
 
+    if (ismuc) status = 'C';
+
     strncpy(name, buddy_getname(BUDDATA(buddy)), ROSTER_WIDTH-7);
     if (isgrp) {
       char *sep;