From a68530cbbfeb17e01fd684b1ef41b960bc173f66 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sat, 22 May 2010 20:45:53 +0000
Subject: Implement XEP-0045 joining, and appropriate error handling.

Resolves: #211

diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index a8a6747..dbf03c9 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -45,7 +45,6 @@ MUCController::MUCController (
 	ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc, presenceOracle, avatarManager, useDelayForLatency, uiEventStream),
 			muc_(new MUC(stanzaChannel, presenceSender, muc)), 
 	nick_(nick) {
-	loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
 	parting_ = false;
 	events_ = uiEventStream;
 	
@@ -53,11 +52,16 @@ MUCController::MUCController (
 	chatWindow_->setRosterModel(roster_);
 	chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this));
 	muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1));
+	muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1));
 	muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1));
 	muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1));
 	muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3));
-	loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
-	loginCheckTimer_->start();
+	muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3));
+	if (timerFactory) {
+		loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS));
+		loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this));
+		loginCheckTimer_->start();
+	}
 
 	muc_->joinAs(nick);
 	chatWindow_->convertToMUC();
@@ -72,21 +76,51 @@ MUCController::~MUCController() {
 	delete muc_;
 	chatWindow_->setRosterModel(NULL);
 	delete roster_;
-	loginCheckTimer_->stop();
+	if (loginCheckTimer_) {
+		loginCheckTimer_->stop();
+	}
 }
 
 void MUCController::handleJoinTimeoutTick() {
-	loginCheckTimer_->stop();
+	receivedActivity();
 	chatWindow_->addSystemMessage("Room " + toJID_.toString() + " is not responding. This operation may never complete");
 }
 
-void MUCController::handleJoinComplete(MUC::JoinResult result) {
-	loginCheckTimer_->stop();
-	if (result == MUC::JoinFailed) {
-		chatWindow_->addErrorMessage("Unable to join this room");
-	} 
+void MUCController::receivedActivity() {
+	if (loginCheckTimer_) {
+		loginCheckTimer_->stop();
+	}
+}
+
+void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) {
+	receivedActivity();
+	String errorMessage = "Unable to join this room";
+	String rejoinNick;
+	if (error) {
+		switch (error->getCondition()) {
+		case ErrorPayload::Conflict: rejoinNick = nick_ + "_"; errorMessage += " as " + nick_ + ", retrying as " + rejoinNick; break;
+		case ErrorPayload::JIDMalformed: errorMessage += ", no nickname specified";break;
+		case ErrorPayload::NotAuthorized: errorMessage += ", a password needed";break;
+		case ErrorPayload::RegistrationRequired: errorMessage += ", only members may join"; break;
+		case ErrorPayload::Forbidden: errorMessage += ", you are banned from the room"; break;
+		case ErrorPayload::ServiceUnavailable: errorMessage += ", the room is full";break;
+		case ErrorPayload::ItemNotFound: errorMessage += ", the room does not exist";break;
+			
+		default: break;
+		}
+	}
+	errorMessage += ".";
+	chatWindow_->addErrorMessage(errorMessage);
+	if (!rejoinNick.isEmpty()) {
+		muc_->joinAs(rejoinNick);
+	}
+}
+
+void MUCController::handleJoinComplete(const String& nick) {
+	receivedActivity();
 	joined_ = true;
-	String joinMessage = "You have joined room " + toJID_.toString() + " as " + nick_;
+	String joinMessage = "You have joined room " + toJID_.toString() + " as " + nick;
+	nick_ = nick;
 	chatWindow_->addSystemMessage(joinMessage);
 }
 
@@ -105,18 +139,76 @@ void MUCController::handleWindowClosed() {
 }
 
 void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
-	JID jid(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()));
-	roster_->addContact(jid, occupant.getNick(), "Occupants");
+	receivedActivity();
+	JID jid(nickToJID(occupant.getNick()));
+	roster_->addContact(jid, occupant.getNick(), roleToGroupName(occupant.getRole()));
+	if (joined_) {
+		String joinString = occupant.getNick() + " has joined the room";
+		MUCOccupant::Role role = occupant.getRole();
+		if (role != MUCOccupant::NoRole && role != MUCOccupant::Participant) {
+			joinString += " as a " + roleToFriendlyName(role);
+
+		}
+		joinString += ".";
+		chatWindow_->addSystemMessage(joinString);
+	}
 	if (avatarManager_ != NULL) {
 		handleAvatarChanged(jid, "dummy");
 	}
 }
 
-void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& /*reason*/) {
+String MUCController::roleToFriendlyName(MUCOccupant::Role role) {
+	switch (role) {
+	case MUCOccupant::Moderator: return "moderator";
+	case MUCOccupant::Participant: return "participant";
+	case MUCOccupant::Visitor: return "visitor";
+	case MUCOccupant::NoRole: return "";
+	}
+	return "";
+}
+
+JID MUCController::nickToJID(const String& nick) {
+	return JID(toJID_.getNode(), toJID_.getDomain(), nick);
+}
+
+void MUCController::preHandleIncomingMessage(boost::shared_ptr<Message>) {
+	/*Buggy implementations never send the status code, so use an incoming message as a hint that joining's done (e.g. the old ejabberd on psi-im.org).*/
+	receivedActivity();
+	joined_ = true;
+}
+
+void MUCController::handleOccupantRoleChanged(const String& nick, const MUCOccupant::Role& newRole, const MUCOccupant::Role& oldRole) {
+	receivedActivity();
+	JID jid(nickToJID(nick));
+	roster_->removeContactFromGroup(jid, roleToGroupName(oldRole));
+	roster_->addContact(jid, nick, roleToGroupName(newRole));
+	chatWindow_->addSystemMessage(nick + " is now a " + roleToFriendlyName(newRole));
+}
+
+String MUCController::roleToGroupName(MUCOccupant::Role role) {
+	String result;
+	switch (role) {
+	case MUCOccupant::Moderator: result = "Moderators"; break;
+	case MUCOccupant::Participant: result = "Participants"; break;
+	case MUCOccupant::Visitor: result = "Visitors"; break;
+	case MUCOccupant::NoRole: result = "Occupants"; break;
+	default: assert(false);
+	}
+	return result;
+}
+
+void MUCController::handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType, const String& reason) {
+	String partMessage = occupant.getNick() + " has left the room";
+	if (!reason.isEmpty()) {
+		partMessage += " (" + reason + ")";
+	}
+	partMessage += ".";
+	chatWindow_->addSystemMessage(partMessage);
 	roster_->removeContact(JID(toJID_.getNode(), toJID_.getDomain(), occupant.getNick()));
 }
 
 void MUCController::handleOccupantPresenceChange(boost::shared_ptr<Presence> presence) {
+	receivedActivity();
 	roster_->applyOnItems(SetPresence(presence, JID::WithResource));
 }
 
diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h
index 9e79835..247a942 100644
--- a/Swift/Controllers/Chat/MUCController.h
+++ b/Swift/Controllers/Chat/MUCController.h
@@ -4,8 +4,7 @@
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_MUCController_H
-#define SWIFTEN_MUCController_H
+#pragma once
 
 #include <boost/shared_ptr.hpp>
 #include <boost/signals.hpp>
@@ -47,9 +46,15 @@ namespace Swift {
 			void handleOccupantJoined(const MUCOccupant& occupant);
 			void handleOccupantLeft(const MUCOccupant& occupant, MUC::LeavingType type, const String& reason);
 			void handleOccupantPresenceChange(boost::shared_ptr<Presence> presence);
-			void handleJoinComplete(MUC::JoinResult result);
+			void handleOccupantRoleChanged(const String& nick, const MUCOccupant::Role& newRole, const MUCOccupant::Role& oldRole);
+			void handleJoinComplete(const String& nick);
+			void handleJoinFailed(boost::shared_ptr<ErrorPayload> error);
 			void handleJoinTimeoutTick();
-
+			String roleToGroupName(MUCOccupant::Role role);
+			JID nickToJID(const String& nick);
+			String roleToFriendlyName(MUCOccupant::Role role);
+			void receivedActivity();
+			void preHandleIncomingMessage(boost::shared_ptr<Message>);
 		private:
 			MUC* muc_;
 			UIEventStream* events_;
@@ -61,5 +66,4 @@ namespace Swift {
 			boost::shared_ptr<Timer> loginCheckTimer_;
 	};
 }
-#endif
 
diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
index 99b2f90..2204366 100644
--- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp
@@ -64,7 +64,7 @@ public:
 		uiEventStream_ = new UIEventStream();
 		chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>();
 		mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createWindow).With(uiEventStream_).Return(NULL);
-		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true);
+		manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, nickResolver_, presenceOracle_, serverDiscoInfo_, presenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL);
 		avatarManager_ = new MockAvatarManager();
 		manager_->setAvatarManager(avatarManager_);
 	};
diff --git a/Swiften/Elements/MUCPayload.h b/Swiften/Elements/MUCPayload.h
index 5ce682e..4b48b45 100644
--- a/Swiften/Elements/MUCPayload.h
+++ b/Swiften/Elements/MUCPayload.h
@@ -1,17 +1,19 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #pragma once
 
+#include <boost/optional.hpp>
+
+#include "Swiften/JID/JID.h"
+#include "Swiften/Base/String.h"
 #include "Swiften/Elements/Payload.h"
 
 namespace Swift {
 	class MUCPayload : public Payload {
-		public:
-			MUCPayload() {
-			}
+
 	};
 }
diff --git a/Swiften/Elements/MUCUserPayload.h b/Swiften/Elements/MUCUserPayload.h
new file mode 100644
index 0000000..3032b9f
--- /dev/null
+++ b/Swiften/Elements/MUCUserPayload.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include "Swiften/JID/JID.h"
+#include "Swiften/Base/String.h"
+#include "Swiften/Elements/Payload.h"
+#include "Swiften/MUC/MUCOccupant.h"
+
+namespace Swift {
+	class MUCUserPayload : public Payload {
+		public:
+			struct Item {
+				Item() : affiliation(MUCOccupant::NoAffiliation), role(MUCOccupant::NoRole) {}
+				boost::optional<JID> realJID;
+				boost::optional<String> nick;
+				MUCOccupant::Affiliation affiliation;
+				MUCOccupant::Role role;
+			};
+
+			struct StatusCode {
+				StatusCode() {}
+				int code;
+			};
+
+			// struct Password {
+
+			// }
+
+			// struct History {
+
+			// }
+
+			// struct Invite {
+
+			// }
+
+			MUCUserPayload() {
+			}
+
+			void addItem(Item item) {items_.push_back(item);}
+		
+			void addStatusCode(StatusCode code) {statusCodes_.push_back(code);}
+
+			const std::vector<Item> getItems() const {return items_;}
+
+			const std::vector<StatusCode> getStatusCodes() const {return statusCodes_;}
+
+		private:
+			std::vector<Item> items_;
+			std::vector<StatusCode> statusCodes_;
+	};
+}
diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp
index e52eae4..60b81a0 100644
--- a/Swiften/MUC/MUC.cpp
+++ b/Swiften/MUC/MUC.cpp
@@ -1,17 +1,20 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #include "Swiften/MUC/MUC.h"
 
+#include <iostream>
+
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 
 #include "Swiften/Presence/PresenceSender.h"
 #include "Swiften/Client/StanzaChannel.h"
 #include "Swiften/Elements/IQ.h"
+#include "Swiften/Elements/MUCUserPayload.h"
 #include "Swiften/Elements/MUCPayload.h"
 
 namespace Swift {
@@ -25,9 +28,9 @@ MUC::MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID
 //FIXME: discover reserved nickname
 
 void MUC::joinAs(const String &nick) {
-	//FIXME: password
-	//FIXME: history request
-	firstPresenceSeen = false;
+	//TODO: password
+	//TODO: history request
+	joinComplete_ = false;
 
 	ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick);
 
@@ -45,53 +48,73 @@ void MUC::handleIncomingPresence(boost::shared_ptr<Presence> presence) {
 	if (!isFromMUC(presence->getFrom())) {
 		return;
 	}
-
-	if (!firstPresenceSeen) {
+	boost::shared_ptr<MUCUserPayload> mucPayload;
+	foreach (boost::shared_ptr<MUCUserPayload> payload, presence->getPayloads<MUCUserPayload>()) {
+		if (payload->getItems().size() > 0 || payload->getStatusCodes().size() > 0) {
+			mucPayload = payload;
+		}
+	}
+	
+	if (!joinComplete_) {
 		if (presence->getType() == Presence::Error) {
-			onJoinComplete(JoinFailed);
-			//FIXME: parse error element
-			//Wrong password
-			//Members-only
-			//Banned
-			//Nickname-conflict
-			//Max-users
-			//Locked-room
-			
+			String reason;
+			onJoinFailed(presence->getPayload<ErrorPayload>());
 			return;
 		}
-		firstPresenceSeen = true;
-		onJoinComplete(JoinSucceeded);
-		presenceSender->addDirectedPresenceReceiver(ownMUCJID);
 	}
 
 	String nick = presence->getFrom().getResource();
 	if (nick.isEmpty()) {
 		return;
 	}
-	//FIXME: occupant affiliation, role.
-	//FIXME: if status code='110', This is me, stop talking.
-	//FIXME: what's status 210?
+	MUCOccupant::Role role(MUCOccupant::NoRole);
+	MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation);
+	if (mucPayload && mucPayload->getItems().size() > 0) {
+		role = mucPayload->getItems()[0].role;
+		affiliation = mucPayload->getItems()[0].affiliation;
+	}
+
 	//100 is non-anonymous
-	//FIXME: 100 may also be specified in a <message/>
-	//Once I've got my nick (110), everything new is a join
+	//TODO: 100 may also be specified in a <message/>
 	//170 is room logging to http
-	//FIXME: full JIDs
-	//FIXME: Nick changes
+	//TODO: Nick changes
 	if (presence->getType() == Presence::Unavailable) {
 		std::map<String,MUCOccupant>::iterator i = occupants.find(nick);
 		if (i != occupants.end()) {
-			//FIXME: part type
+			//TODO: part type
 			onOccupantLeft(i->second, Part, "");
 			occupants.erase(i);
 		}
 	}
 	else if (presence->getType() == Presence::Available) {
-		std::pair<std::map<String,MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, MUCOccupant(nick)));
+		std::map<String, MUCOccupant>::iterator it = occupants.find(nick);
+		if (it != occupants.end()) {
+			MUCOccupant oldOccupant = it->second;
+			if (oldOccupant.getRole() != role) {
+				onOccupantRoleChanged(nick, role, oldOccupant.getRole());
+			}
+			if (oldOccupant.getAffiliation() != affiliation) {
+				onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation());
+			}
+		}
+		std::pair<std::map<String, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, MUCOccupant(nick, role, affiliation)));
 		if (result.second) {
 			onOccupantJoined(result.first->second);
 		}
 		onOccupantPresenceChange(presence);
 	}
+	if (mucPayload && !joinComplete_) {
+		foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
+			if (status.code == 110) {
+				/* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */
+				joinComplete_ = true;
+				ownMUCJID = presence->getFrom();
+				onJoinComplete(getOwnNick());
+				presenceSender->addDirectedPresenceReceiver(ownMUCJID);
+			}
+		}
+	}
+
 }
 
 //FIXME: Recognise Topic changes
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index e306f11..fc691f1 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -25,10 +25,7 @@ namespace Swift {
 	class MUC {
 		public:
 			enum JoinResult { JoinSucceeded, JoinFailed };
-			enum LeavingType { Part };
-			enum Roles {Moderator, Participant, Visitor, NoRole};
-			enum Affiliations {Owner, Admin, Member, Outcast, NoAffiliation};
-			
+			enum LeavingType { Part };			
 
 		public:
 			MUC(StanzaChannel* stanzaChannel, PresenceSender* presenceSender, const JID &muc);
@@ -41,8 +38,11 @@ namespace Swift {
 			void handleIncomingMessage(boost::shared_ptr<Message> message);
 
 		public:
-			boost::signal<void (JoinResult)> onJoinComplete;
+			boost::signal<void (const String& /*nick*/)> onJoinComplete;
+			boost::signal<void (boost::shared_ptr<ErrorPayload>)> onJoinFailed;
 			boost::signal<void (boost::shared_ptr<Presence>)> onOccupantPresenceChange;
+			boost::signal<void (const String&, const MUCOccupant::Role& /*new*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged;
+			boost::signal<void (const String&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged;
 			boost::signal<void (const MUCOccupant&)> onOccupantJoined;
 			boost::signal<void (const MUCOccupant&, LeavingType, const String& /*reason*/)> onOccupantLeft;
 			/* boost::signal<void (const MUCInfo&)> onInfoResult; */
@@ -66,7 +66,7 @@ namespace Swift {
 			StanzaChannel* stanzaChannel;
 			PresenceSender* presenceSender;
 			std::map<String, MUCOccupant> occupants;
-			bool firstPresenceSeen;
+			bool joinComplete_;
 			boost::bsignals::scoped_connection scopedConnection_;
 	};
 }
diff --git a/Swiften/MUC/MUCOccupant.cpp b/Swiften/MUC/MUCOccupant.cpp
index 0bd9787..3e907ab 100644
--- a/Swiften/MUC/MUCOccupant.cpp
+++ b/Swiften/MUC/MUCOccupant.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -8,7 +8,7 @@
 
 namespace Swift {
 
-MUCOccupant::MUCOccupant(const String &nick) : nick_(nick) {
+MUCOccupant::MUCOccupant(const String &nick, Role role, Affiliation affiliation) : nick_(nick), role_(role), affiliation_(affiliation) {
 }
 
 MUCOccupant::~MUCOccupant() {
@@ -18,4 +18,25 @@ String MUCOccupant::getNick() const {
 	return nick_;
 }
 
+MUCOccupant::Role MUCOccupant::getRole() const {
+	return role_;
+}
+
+MUCOccupant::Affiliation MUCOccupant::getAffiliation() const {
+	return affiliation_;
+}
+
+void MUCOccupant::setRealJID(const JID& realJID) {
+	realJID_ = realJID;
+}
+
+void MUCOccupant::setNick(const String& nick) {
+	nick_ = nick;
+}
+
+
+boost::optional<JID> MUCOccupant::getRealJID() const {
+	return realJID_;
+}
+
 }
diff --git a/Swiften/MUC/MUCOccupant.h b/Swiften/MUC/MUCOccupant.h
index 939d634..c9551de 100644
--- a/Swiften/MUC/MUCOccupant.h
+++ b/Swiften/MUC/MUCOccupant.h
@@ -1,27 +1,39 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_MUCOccupant_H
-#define SWIFTEN_MUCOccupant_H
+#pragma once
+
+#include <boost/optional.hpp>
 
 #include "Swiften/Base/String.h"
+#include "Swiften/JID/JID.h"
 
 namespace Swift {
 	class Client;
 
 	class MUCOccupant {
 		public:
-			MUCOccupant(const String &nick);
+			enum Role {Moderator, Participant, Visitor, NoRole};
+			enum Affiliation {Owner, Admin, Member, Outcast, NoAffiliation};
+
+			MUCOccupant(const String &nick, Role role, Affiliation affiliation);
 			~MUCOccupant();
 
 			String getNick() const;
+			Role getRole() const;
+			Affiliation getAffiliation() const;
+			boost::optional<JID> getRealJID() const;
+			void setRealJID(const JID& jid);
+			void setNick(const String& nick);
 
 		private:
 			String nick_;
+			Role role_;
+			Affiliation affiliation_;
+			boost::optional<JID> realJID_;
 	};
 }
 
-#endif
diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
index 8c570cb..1a45ed2 100644
--- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
+++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp
@@ -28,6 +28,7 @@
 #include "Swiften/Parser/PayloadParsers/RawXMLPayloadParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h"
 #include "Swiften/Parser/PayloadParsers/DelayParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h"
 
 using namespace boost;
 
@@ -53,6 +54,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() {
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this)));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory()));
 	factories_.push_back(shared_ptr<PayloadParserFactory>(new DelayParserFactory()));
+	factories_.push_back(shared_ptr<PayloadParserFactory>(new MUCUserPayloadParserFactory()));
 	foreach(shared_ptr<PayloadParserFactory> factory, factories_) {
 		addFactory(factory.get());
 	}
diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp
new file mode 100644
index 0000000..5bb0e68
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include "Swiften/MUC/MUCOccupant.h"
+
+#include <cassert>
+#include <iostream>
+
+namespace Swift {
+
+MUCUserPayloadParser::MUCUserPayloadParser() : level(TopLevel) {
+}
+
+void MUCUserPayloadParser::handleStartElement(const String& element, const String&, const AttributeMap& attributes) {
+	if (level == ItemLevel) {
+		if (element == "item") {
+			MUCUserPayload::Item item;
+			String affiliation = attributes.getAttribute("affiliation");
+			String role = attributes.getAttribute("role");
+			String nick = attributes.getAttribute("nick");
+			String jid = attributes.getAttribute("jid");
+			item.affiliation = parseAffiliation(affiliation);
+			item.role = parseRole(role);
+			if (!jid.isEmpty()) {
+				item.realJID = JID(jid);
+			}
+			if (!nick.isEmpty()) {
+				item.nick = nick;
+			}
+			getPayloadInternal()->addItem(item);
+		} else if (element == "status") {
+			MUCUserPayload::StatusCode status;
+			try {
+				status.code = boost::lexical_cast<int>(attributes.getAttribute("code").getUTF8Data());
+				getPayloadInternal()->addStatusCode(status);
+			} catch (boost::bad_lexical_cast&) {
+			}
+		}
+	}
+	++level;
+}
+
+MUCOccupant::Role MUCUserPayloadParser::parseRole(const String& roleString) const {
+	if (roleString == "moderator") {
+		return MUCOccupant::Moderator;
+	}
+	if (roleString == "participant") {
+		return MUCOccupant::Participant;
+	}
+	if (roleString == "visitor") {
+		return MUCOccupant::Visitor;
+	}
+	if (roleString == "none") {
+		return MUCOccupant::NoRole;
+	}
+	return MUCOccupant::NoRole;
+}
+
+MUCOccupant::Affiliation MUCUserPayloadParser::parseAffiliation(const String& affiliationString) const {
+	if (affiliationString == "owner") {
+		return MUCOccupant::Owner;
+	}
+	if (affiliationString == "admin") {
+		return MUCOccupant::Admin;
+	}
+	if (affiliationString == "member") {
+		return MUCOccupant::Member;
+	}
+	if (affiliationString == "outcast") {
+		return MUCOccupant::Outcast;
+	}
+	if (affiliationString == "none") {
+		return MUCOccupant::NoAffiliation;
+	}
+	return MUCOccupant::NoAffiliation;
+}
+
+
+void MUCUserPayloadParser::handleEndElement(const String& /*element*/, const String&) {
+	--level;
+}
+
+void MUCUserPayloadParser::handleCharacterData(const String& /*data*/) {
+
+}
+
+}
diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h
new file mode 100644
index 0000000..01c7de1
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include "Swiften/Elements/MUCUserPayload.h"
+#include "Swiften/Parser/GenericPayloadParser.h"
+
+namespace Swift {
+	class MUCUserPayloadParser : public GenericPayloadParser<MUCUserPayload> {
+		public:
+			MUCUserPayloadParser();
+
+			virtual void handleStartElement(const String& element, const String&, const AttributeMap& attributes);
+			virtual void handleEndElement(const String& element, const String&);
+			virtual void handleCharacterData(const String& data);
+			MUCOccupant::Role parseRole(const String& itemString) const;
+			MUCOccupant::Affiliation parseAffiliation(const String& statusString) const;
+		private:
+			enum Level { 
+				TopLevel = 0, 
+				ItemLevel = 1
+			};
+			int level;
+	};
+}
diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h
new file mode 100644
index 0000000..3946ece
--- /dev/null
+++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Parser/GenericPayloadParserFactory.h"
+#include "Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h"
+
+namespace Swift {
+	class MUCUserPayloadParserFactory : public GenericPayloadParserFactory<MUCUserPayloadParser> {
+		public:
+			MUCUserPayloadParserFactory() : GenericPayloadParserFactory<MUCUserPayloadParser>("x", "http://jabber.org/protocol/muc#user") {}
+	};
+}
diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript
index 77f50be..430d827 100644
--- a/Swiften/Parser/SConscript
+++ b/Swiften/Parser/SConscript
@@ -37,6 +37,7 @@ sources = [
 		"PayloadParsers/VCardParser.cpp",
 		"PayloadParsers/VCardUpdateParser.cpp",
 		"PayloadParsers/DelayParser.cpp",
+		"PayloadParsers/MUCUserPayloadParser.cpp",
 		"PlatformXMLParserFactory.cpp",
 		"PresenceParser.cpp",
 		"SerializingParser.cpp",
diff --git a/Swiften/SConscript b/Swiften/SConscript
index 4d25dc5..f8bcc8c 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -74,6 +74,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Serializer/PayloadSerializers/ErrorSerializer.cpp",
 			"Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp",
 			"Serializer/PayloadSerializers/MUCPayloadSerializer.cpp",
+			"Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp",
 			"Serializer/PayloadSerializers/ResourceBindSerializer.cpp",
 			"Serializer/PayloadSerializers/RosterSerializer.cpp",
 			"Serializer/PayloadSerializers/SecurityLabelSerializer.cpp",
diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
index caf1d9c..e407876 100644
--- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
+++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp
@@ -13,6 +13,7 @@
 #include "Swiften/Serializer/PayloadSerializers/ErrorSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/RosterSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h"
+#include "Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/StatusSerializer.h"
 #include "Swiften/Serializer/PayloadSerializers/StatusShowSerializer.h"
diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp
index 9630014..36aa47f 100644
--- a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.cpp
@@ -1,19 +1,21 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #include "Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h"
 
+#include "Swiften/Serializer/XML/XMLElement.h"
+
 namespace Swift {
 
 MUCPayloadSerializer::MUCPayloadSerializer() : GenericPayloadSerializer<MUCPayload>() {
 }
 
-String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload>)  const {
-	String result("<x xmlns='http://jabber.org/protocol/muc'/>");
-	return result;
+String MUCPayloadSerializer::serializePayload(boost::shared_ptr<MUCPayload> payload)  const {
+	XMLElement mucElement("x", "http://jabber.org/protocol/muc");
+	return mucElement.serialize();
 }
 
 }
diff --git a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h
index b015980..cd7f107 100644
--- a/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h
+++ b/Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h
@@ -1,11 +1,10 @@
 /*
- * Copyright (c) 2010 Remko Tronçon
+ * Copyright (c) 2010 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
-#ifndef SWIFTEN_MUCPayloadSerializer_H
-#define SWIFTEN_MUCPayloadSerializer_H
+#pragma once
 
 #include "Swiften/Serializer/GenericPayloadSerializer.h"
 #include "Swiften/Elements/MUCPayload.h"
@@ -14,9 +13,7 @@ namespace Swift {
 	class MUCPayloadSerializer : public GenericPayloadSerializer<MUCPayload> {
 		public:
 			MUCPayloadSerializer();
-
 			virtual String serializePayload(boost::shared_ptr<MUCPayload> version)  const;
 	};
 }
 
-#endif
diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp
new file mode 100644
index 0000000..f4732ea
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h"
+
+#include <sstream>
+
+#include <boost/shared_ptr.hpp>
+
+#include "Swiften/Base/foreach.h"
+#include "Swiften/Serializer/XML/XMLElement.h"
+#include "Swiften/Serializer/XML/XMLTextNode.h"
+
+
+namespace Swift {
+
+MUCUserPayloadSerializer::MUCUserPayloadSerializer() : GenericPayloadSerializer<MUCUserPayload>() {
+}
+
+String MUCUserPayloadSerializer::serializePayload(boost::shared_ptr<MUCUserPayload> payload)  const {
+	XMLElement mucElement("x", "http://jabber.org/protocol/muc");
+	foreach (const MUCUserPayload::StatusCode statusCode, payload->getStatusCodes()) {
+		boost::shared_ptr<XMLElement> statusElement(new XMLElement("status"));
+		std::ostringstream code;
+		code << statusCode.code;
+		statusElement->setAttribute("code", code.str());
+		mucElement.addNode(statusElement);
+	}
+	foreach (const MUCUserPayload::Item item, payload->getItems()) {
+		boost::shared_ptr<XMLElement> itemElement(new XMLElement("item"));
+		itemElement->setAttribute("affiliation", affiliationToString(item.affiliation));
+		itemElement->setAttribute("role", roleToString(item.role));
+		if (item.realJID) {
+			itemElement->setAttribute("jid", item.realJID.get());
+		}
+		if (item.nick) {
+			itemElement->setAttribute("nick", item.nick.get());
+		}
+		mucElement.addNode(itemElement);
+	}
+	return mucElement.serialize();
+}
+
+String MUCUserPayloadSerializer::affiliationToString(MUCOccupant::Affiliation affiliation) const {
+	String result;
+	switch (affiliation) {
+	case MUCOccupant::Owner: result = "owner"; break;
+	case MUCOccupant::Admin: result = "admin"; break;
+	case MUCOccupant::Member: result = "member"; break;
+	case MUCOccupant::Outcast: result = "outcast"; break;
+	case MUCOccupant::NoAffiliation: result = "none"; break;
+	default: assert(false);
+	}
+	return result;
+}
+
+String MUCUserPayloadSerializer::roleToString(MUCOccupant::Role role) const {
+	String result;
+	switch (role) {
+	case MUCOccupant::Moderator: result = "moderator"; break;
+	case MUCOccupant::NoRole: result = "none"; break;
+	case MUCOccupant::Participant: result = "participant"; break;
+	case MUCOccupant::Visitor: result = "visitor"; break;
+	default: assert(false);
+	}
+	return result;
+
+}
+
+
+}
diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h
new file mode 100644
index 0000000..bad21c9
--- /dev/null
+++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Serializer/GenericPayloadSerializer.h"
+#include "Swiften/Elements/MUCUserPayload.h"
+
+namespace Swift {
+	class MUCUserPayloadSerializer : public GenericPayloadSerializer<MUCUserPayload> {
+		public:
+			MUCUserPayloadSerializer();
+			String affiliationToString(MUCOccupant::Affiliation affiliation) const;
+			String roleToString(MUCOccupant::Role role) const;
+
+			virtual String serializePayload(boost::shared_ptr<MUCUserPayload> version)  const;
+	};
+}
+
-- 
cgit v0.10.2-6-g49f6