From 27d21b371f24272466a2d6a5bf2e2b717ee2d9fc Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sun, 27 Feb 2011 22:45:32 +0000
Subject: A start on Swiftob, a Swiften-based chatbot.


diff --git a/.project b/.project
index 8a42eaa..fcdfcdc 100644
--- a/.project
+++ b/.project
@@ -18,7 +18,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.autoBuildTarget</key>
-					<value>Sluift</value>
+					<value></value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.buildArguments</key>
@@ -29,10 +29,6 @@
 					<value>python</value>
 				</dictionary>
 				<dictionary>
-					<key>org.eclipse.cdt.make.core.buildLocation</key>
-					<value></value>
-				</dictionary>
-				<dictionary>
 					<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
 					<value>-c</value>
 				</dictionary>
@@ -54,7 +50,7 @@
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.fullBuildTarget</key>
-					<value>Sluift</value>
+					<value></value>
 				</dictionary>
 				<dictionary>
 					<key>org.eclipse.cdt.make.core.stopOnError</key>
diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp
index b8c23cd..68a5a86 100644
--- a/Swiften/MUC/MUC.cpp
+++ b/Swiften/MUC/MUC.cpp
@@ -208,6 +208,14 @@ void MUC::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPay
 	}
 }
 
+bool MUC::hasOccupant(const std::string& nick) {
+	return occupants.find(nick) != occupants.end();
+}
+
+MUCOccupant MUC::getOccupant(const std::string& nick) {
+	return occupants.find(nick)->second;
+}
+
 //FIXME: Recognise Topic changes
 
 //TODO: Invites(direct/mediated)
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index ef76a6a..278ef95 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -51,7 +51,9 @@ namespace Swift {
 			void handleIncomingMessage(Message::ref message);
 			/** Expose public so it can be called when e.g. user goes offline */
 			void handleUserLeft(LeavingType);
-
+			/** Get occupant information*/
+			MUCOccupant getOccupant(const std::string& nick);
+			bool hasOccupant(const std::string& nick);
 		public:
 			boost::signal<void (const std::string& /*nick*/)> onJoinComplete;
 			boost::signal<void (ErrorPayload::ref)> onJoinFailed;
diff --git a/Swiftob/Commands.cpp b/Swiftob/Commands.cpp
new file mode 100644
index 0000000..e39d23e
--- /dev/null
+++ b/Swiftob/Commands.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/Commands.h"
+
+#include <iostream>
+#include <boost/bind.hpp>
+
+#include <Swiften/Client/Client.h>
+
+typedef std::pair<std::string, Commands::Command*> NamedCommand;
+
+Commands::Commands(Users* users, Swift::Client* client, Storage* storage, MUCs* mucs) {
+	users_ = users;
+	client_ = client;
+	mucs_ = mucs;
+	storage_ = storage;
+	resetCommands();
+}
+
+void Commands::resetCommands() {
+	foreach (NamedCommand command, commands_) {
+		delete command.second;
+	}
+	commands_.clear();
+	registerCommand("quit", Owner, "Quit the bot", boost::bind(&Commands::handleQuitCommand, this, _1, _2, _3));
+	registerCommand("help", Anyone, "Get help", boost::bind(&Commands::handleHelpCommand, this, _1, _2, _3));
+	registerCommand("join", Owner, "Join a MUC", boost::bind(&Commands::handleJoinCommand, this, _1, _2, _3));
+	registerCommand("part", Owner, "Leave a MUC", boost::bind(&Commands::handlePartCommand, this, _1, _2, _3));
+	registerCommand("rehash", Owner, "Reload scripts", boost::bind(&Commands::handleRehashCommand, this, _1, _2, _3));
+	onReset();
+}
+
+void Commands::registerCommand(const std::string& name, RoleList roles, const std::string& description, boost::function<void(const std::string& /*command*/, const std::string& /*params*/, Swift::Message::ref)> callback) {
+	Command* command = new Command(roles, description);
+	commands_[name] = command;
+	command->onReceived.connect(callback);
+}
+
+bool Commands::hasCommand(const std::string& name) {
+	return commands_.find(name) != commands_.end();
+}
+
+bool Commands::runCommand(const std::string& name, const std::string& params, Swift::Message::ref message) {
+	Users::User::Role userRole = users_->getRoleForSender(message);
+	Command* command = commands_[name];
+	if (roleIn(userRole, command->getAllowedBy())) {
+		command->onReceived(name, params, message);
+		return true;
+	} else {
+		replyTo(message, "You may not run this command", true);
+	}
+	return false;
+}
+
+bool Commands::roleIn(const Users::User::Role userRole, RoleList roleList) {
+	switch (roleList) {
+		case Owner : return userRole == Users::User::Owner;
+		case Anyone : return true;
+	}
+	std::cerr << "Unrecognised role list" << std::endl;
+	return false;
+}
+
+void Commands::handleQuitCommand(const std::string& /*command*/, const std::string& /*params*/, Swift::Message::ref message) {
+	replyTo(message, "Shutting down");
+	std::cout << "Quitting at the behest of " << message->getFrom().toString() << std::endl;
+	exit(0);
+}
+
+void Commands::setRehashError(const std::string& error) {
+	if (!rehashError_.empty()) {
+		rehashError_ += "; ";
+	}
+	rehashError_ += error;
+}
+
+void Commands::handleRehashCommand(const std::string& /*command*/, const std::string& /*params*/, Swift::Message::ref message) {
+	rehashError_ = "";
+	replyTo(message, "Rehashing now.");
+	std::cout << "Rehashing at the behest of " << message->getFrom().toString() << std::endl;
+	resetCommands();
+	if (rehashError_.empty()) {
+		replyTo(message, "Rehash complete");
+	} else {
+		replyTo(message, "I have suffered a tremendous failure: " + rehashError_);
+	}
+}
+
+void Commands::handleJoinCommand(const std::string& /*command*/, const std::string& params, Swift::Message::ref message) {
+	Swift::JID room(params);
+	if (!room.isValid() || !room.getResource().empty() || room.getNode().empty()) {
+		replyTo(message, "Can't join " + room.toString() + ", not a valid room JID.");
+		return;
+	}
+	if (mucs_->contains(room)) {
+		replyTo(message, "I'm already (trying to be?) in " + room.toString() + ".");
+		return;
+	}
+	replyTo(message, "Trying to join " + room.toString() + ".");
+	mucs_->join(room, boost::bind(&Commands::handleJoinCommandSuccess, this, room, message), boost::bind(&Commands::handleJoinCommandFailure, this, room, _1, message));
+}
+
+void Commands::handlePartCommand(const std::string& /*command*/, const std::string& params, Swift::Message::ref message) {
+	Swift::JID room(params);
+	if (!room.isValid() || !room.getResource().empty() || room.getNode().empty()) {
+		replyTo(message, "Can't leave " + room.toString() + ", not a valid room JID.");
+		return;
+	}
+	if (mucs_->contains(room)) {
+		replyTo(message, "I'm not in " + room.toString() + ".");
+		return;
+	}
+	replyTo(message, "Leaving " + room.toString() + ".");
+	mucs_->part(room);
+}
+
+void Commands::handleJoinCommandSuccess(const Swift::JID& room, Swift::Message::ref message) {
+	replyTo(message, "Joined " + room.toString());
+}
+
+void Commands::handleJoinCommandFailure(const Swift::JID& room, const std::string& error, Swift::Message::ref message) {
+	replyTo(message, "Join to " + room.toString() + "failed. " + error);
+}
+
+void Commands::handleHelpCommand(const std::string& /*command*/, const std::string& /*params*/, Swift::Message::ref message) {
+	Users::User::Role userRole = users_->getRoleForSender(message);
+	std::string result("Available commands:");
+	std::string bang = message->getType() == Swift::Message::Groupchat ? "\n!" : "\n";
+	foreach (NamedCommand pair, commands_) {
+		if (roleIn(userRole, pair.second->getAllowedBy())) {
+			result += bang + pair.first + " - " + pair.second->getDescription();
+		}
+	}
+	replyTo(message, result, true);
+}
+
+/**
+ * \param outOfMUC Reply to the sender directly, don't spam MUCs with the reply
+ */
+void Commands::replyTo(Swift::Message::ref source, std::string replyBody, bool outOfMUC) {
+	Swift::Message::ref reply(new Swift::Message());
+	Swift::Message::Type type = source->getType();
+	reply->setType(type);
+	reply->setBody(type == Swift::Message::Groupchat ? source->getFrom().getResource() + ": " + replyBody : replyBody);
+	Swift::JID to = source->getFrom();
+	if (type == Swift::Message::Groupchat) {
+		if (outOfMUC) {
+			reply->setType(Swift::Message::Chat);
+		} else {
+			to = to.toBare();
+		}
+	}
+	reply->setTo(to);
+	if (client_->isAvailable()) {
+		client_->sendMessage(reply);
+	} else {
+		std::cout << "Dropping '" + reply->getBody() + "' -> " + reply->getTo().toString() + " on the floor due to missing connection." << std::endl;
+	}
+}
diff --git a/Swiftob/Commands.h b/Swiftob/Commands.h
new file mode 100644
index 0000000..e11078d
--- /dev/null
+++ b/Swiftob/Commands.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Elements/Message.h>
+
+#include "Swiftob/Users.h"
+#include "Swiftob/MUCs.h"
+
+namespace Swift {
+	class Client;
+}
+
+class Storage;
+class Commands {
+	public:
+		enum RoleList {Anyone, Owner};
+	public:
+		class Command {
+			public:
+				Command(RoleList allowedBy, const std::string& description) : allowedBy_(allowedBy), description_(description) {
+				}
+
+				virtual ~Command() {};
+
+				boost::signal<void (const std::string&, const std::string&, Swift::Message::ref)> onReceived;
+				RoleList getAllowedBy() {return allowedBy_;}
+				std::string getDescription() {return description_;}
+			private:
+				RoleList allowedBy_;
+				std::string description_;
+		};
+
+	public:
+		Commands(Users* users, Swift::Client* client, Storage* storage, MUCs* mucs);
+		bool hasCommand(const std::string&);
+		bool runCommand(const std::string& command, const std::string& params, Swift::Message::ref message);
+		void replyTo(Swift::Message::ref source, std::string replyBody, bool outOfMUC = false);
+		void registerCommand(const std::string& name, RoleList roles, const std::string& description, boost::function<void(const std::string& /*command*/, const std::string& /*params*/, Swift::Message::ref)> callback);
+		void resetCommands();
+		void setRehashError(const std::string& error);
+
+	public:
+		boost::signal<void ()> onReset;
+	private:
+		bool roleIn(const Users::User::Role userRole, RoleList roles);
+		void handleQuitCommand(const std::string& command, const std::string& params, Swift::Message::ref message);
+		void handleHelpCommand(const std::string& command, const std::string& params, Swift::Message::ref message);
+		void handleJoinCommand(const std::string& /*command*/, const std::string& params, Swift::Message::ref message);
+		void handleJoinCommandSuccess(const Swift::JID& room, Swift::Message::ref message);
+		void handleJoinCommandFailure(const Swift::JID& room, const std::string& error, Swift::Message::ref message);
+		void handlePartCommand(const std::string& /*command*/, const std::string& params, Swift::Message::ref message);
+		void handleRehashCommand(const std::string& command, const std::string& params, Swift::Message::ref message);
+	private:
+		std::map<std::string, Command*> commands_;
+		Users* users_;
+		Swift::Client* client_;
+		Storage* storage_;
+		MUCs* mucs_;
+		std::string rehashError_;
+};
+
+
diff --git a/Swiftob/LuaCommands.cpp b/Swiftob/LuaCommands.cpp
new file mode 100644
index 0000000..7be818e
--- /dev/null
+++ b/Swiftob/LuaCommands.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/LuaCommands.h"
+
+#include <boost/bind.hpp>
+#include <vector>
+#include <algorithm>
+
+#include <Swiften/Client/Client.h>
+#include <Swiften/Network/TimerFactory.h>
+
+#include "Swiftob/Commands.h"
+
+#define LUA_COMMANDS "__Lua_Commands"
+#define STORAGE "__Storage"
+
+LuaCommands::LuaCommands(Commands* commands, const std::string& path, Client* client, TimerFactory* timerFactory, MUCs* mucs) : path_(path), scriptsPath_(boost::filesystem::path(path_) / "scripts") {
+	commands_ = commands;
+	client_ = client;
+	timerFactory_ = timerFactory;
+	mucs_ = mucs;
+	commands_->onReset.connect(boost::bind(&LuaCommands::registerCommands, this));
+	registerCommands();
+}
+
+void LuaCommands::registerCommands() {
+	std::cout << "Trying to load all scripts in " << scriptsPath_ << std::endl;
+	if (boost::filesystem::exists(scriptsPath_) && boost::filesystem::is_directory(scriptsPath_)) {
+		std::vector<boost::filesystem::path> files;
+		copy(boost::filesystem::directory_iterator(scriptsPath_), boost::filesystem::directory_iterator(), std::back_inserter(files));
+		foreach (boost::filesystem::path file, files) {
+			if (boost::filesystem::is_regular_file(file) && file.extension() == ".lua") {
+				loadScript(file);
+			}
+		}
+	}
+}
+
+static int l_register_command(lua_State *L) {
+	LuaCommands* commands = NULL;
+	lua_getfield(L, LUA_REGISTRYINDEX, LUA_COMMANDS);
+	commands = static_cast<LuaCommands*>(lua_touserdata(L, -1));
+	lua_pop(L, 1);
+	if (!lua_isfunction(L, 4)) {
+		return luaL_error(L, "register_command callback parameter must be a function");
+	}
+	//luaL_ref callback(lua_to(L, 4));
+	lua_pushvalue(L, 4);
+	int callbackIndex = luaL_ref(L, LUA_REGISTRYINDEX);
+	lua_pop(L, 1);
+
+	if (!lua_isstring(L, 3)) {
+		return luaL_error(L, "register_command description parameter must be a string");
+	}
+	std::string description(lua_tostring(L, 3));
+	lua_pop(L, 1);
+
+	if (!lua_isstring(L, 2)) {
+		return luaL_error(L, "register_command allowed roles parameter must be a string");
+	}
+	std::string roleString(lua_tostring(L, 2));
+	lua_pop(L, 1);
+	Commands::RoleList roleList = Commands::Owner;
+	if (roleString == "Owner") {
+		roleList = Commands::Owner;
+	} else if (roleString == "Anyone") {
+		roleList = Commands::Anyone;
+	} else {
+		return luaL_error(L, "register_command allowed roles parameter has illegal value");
+	}
+	if (!lua_isstring(L, 1)) {
+		return luaL_error(L, "register_command command name parameter must be a string");
+	}
+	std::string name(lua_tostring(L, 1));
+	lua_pop(L, 1);
+	std::cout << "Registering lua command '" << name << "' for '" << roleString << "' with callback index " << callbackIndex << std::endl;
+	commands->getCommands()->registerCommand(name, roleList, description, boost::bind(&LuaCommands::handleLuaCommand, commands, callbackIndex, L, _1, _2, _3));
+
+	return 0;
+}
+
+static std::string luatable_asstring(lua_State *L, const char* key) {
+	lua_getfield(L, -1, key);
+	const char* valueChars = lua_tostring(L, -1);
+	std::string value(valueChars != NULL ? valueChars : "");
+	lua_pop(L, 1);
+	return value;
+}
+
+static int luatable_asint(lua_State *L, const char* key) {
+	lua_getfield(L, -1, key);
+	int value = lua_tointeger(L, -1);
+	lua_pop(L, 1);
+	return value;
+}
+
+static int luatable_asfunction(lua_State *L, const char* key) {
+	lua_getfield(L, -1, key);
+	int callbackIndex = luaL_ref(L, LUA_REGISTRYINDEX);
+	return callbackIndex;
+}
+
+static Message::ref messageFromTable(lua_State *L) {
+	Message::ref message(new Message());
+	message->setFrom(JID(luatable_asstring(L, "from")));
+	message->setBody(luatable_asstring(L, "body"));
+	Message::Type type = Message::Normal;
+	std::string typeString(luatable_asstring(L, "type"));
+	if (typeString == "normal") {
+		type = Message::Normal;
+	} else if (typeString == "chat") {
+		type = Message::Chat;
+	} else if (typeString == "groupchat") {
+		type = Message::Groupchat;
+	} else if (typeString == "error") {
+		type = Message::Error;
+	} else if (typeString == "headline") {
+		type = Message::Headline;
+	} else {
+		return Message::ref();
+	}
+	message->setType(type);
+	return message;
+}
+
+LuaCommands* LuaCommands::commandsFromLua(lua_State *L) {
+	LuaCommands* commands = NULL;
+	lua_getfield(L, LUA_REGISTRYINDEX, LUA_COMMANDS);
+	commands = static_cast<LuaCommands*>(lua_touserdata(L, -1));
+	lua_pop(L, 1);
+	return commands;
+}
+
+Storage* LuaCommands::storageFromLua(lua_State *L) {
+	Storage* storage = NULL;
+	lua_getfield(L, LUA_REGISTRYINDEX, STORAGE);
+	storage = static_cast<Storage*>(lua_touserdata(L, -1));
+	lua_pop(L, 1);
+	return storage;
+}
+
+static int l_reply_to(lua_State *L) {
+	LuaCommands* commands = LuaCommands::commandsFromLua(L);
+
+	if (!lua_isboolean(L, 3) && lua_gettop(L) > 2) {
+		return luaL_error(L, "reply_to parameter 3 must be boolean if present");
+	}
+	bool outOfMUC = lua_toboolean(L, 3);
+	if (lua_gettop(L) == 3) {
+		lua_pop(L, 1);
+	}
+
+	if (!lua_isstring(L, 2)) {
+		return luaL_error(L, "reply_to body parameter must be a string");
+	}
+	std::string body(lua_tostring(L, 2));
+	lua_pop(L, 1);
+
+	if (!lua_istable(L, 1)) {
+		return luaL_error(L, "reply_to message parameter must be a table");
+	}
+	lua_pushvalue(L, 1);
+	Message::ref message(messageFromTable(L));
+	if (!message) {
+		return luaL_error(L, "message parameter invalid");
+	}
+	commands->getCommands()->replyTo(message, body, outOfMUC);
+	lua_pop(L, 1);
+
+	return 0;
+}
+
+static int l_muc_input_to_jid(lua_State *L) {
+	LuaCommands* commands = LuaCommands::commandsFromLua(L);
+	return commands->muc_input_to_jid(L);
+}
+
+int LuaCommands::muc_input_to_jid(lua_State *L) {
+	if (!lua_isstring(L, 2)) {
+		return luaL_error(L, "must pass a string to muc_input_to_jid p2");
+	}
+	std::string source = lua_tostring(L, 2);
+	JID sourceJID(source);
+	lua_pop(L, 1);
+	if (!lua_isstring(L, 1)) {
+		return luaL_error(L, "must pass a string to muc_input_to_jid p1");
+	}
+	std::string input = lua_tostring(L, 1);
+	lua_pop(L, 1);
+	JID result(input);
+	if (mucs_->contains(sourceJID.toBare())) {
+		if (result.isBare() && result.getNode().empty()) {
+			if (mucs_->getMUC(sourceJID.toBare())->hasOccupant(input)) {
+				result = JID(sourceJID.getNode(), sourceJID.getDomain(), input);
+			}
+		}
+	}
+
+	lua_pushstring(L, result.isValid() ? result.toString().c_str() : "");
+	return 1;
+}
+
+void LuaCommands::handleSoftwareVersionResponse(boost::shared_ptr<SoftwareVersion> version, ErrorPayload::ref error, bool timeout, GetSoftwareVersionRequest::ref request, Timer::ref timer, lua_State* L, Callbacks callbacks) {
+	request->onResponse.disconnect_all_slots();
+	timer->onTick.disconnect_all_slots();
+	timer->stop();
+	int callback = callbacks.failure;
+	int stackCount = 0;
+	if (timeout) {
+		callback = callbacks.timeout;
+	} else if (version) {
+		callback = callbacks.success;
+	}
+	lua_rawgeti(L, LUA_REGISTRYINDEX, callback);
+	if (error) {
+		lua_pushstring(L, error->getText().empty() ? "No error text" : error->getText().c_str());
+		stackCount++;
+	}
+	else if (version) {
+		lua_createtable(L, 0, 3);
+		lua_pushstring(L, version->getName().c_str());
+		lua_setfield(L, -2, "name");
+		lua_pushstring(L, version->getVersion().c_str());
+		lua_setfield(L, -2, "version");
+		lua_pushstring(L, version->getOS().c_str());
+		lua_setfield(L, -2, "os");
+		stackCount++;
+	}
+	else {
+		lua_pushliteral(L, "Missing payload");
+		stackCount++;
+	}
+	int result = lua_pcall(L, stackCount, 0, 0);
+	if (result != 0) {
+		std::string error(lua_tostring(L, -1));
+		lua_pop(L, 1);
+		std::cout << error << std::endl;
+		callbacks.erase(L);
+		luaL_error(L, error.c_str());
+	} else {
+		callbacks.erase(L);
+	}
+
+}
+
+static int l_get_software_version(lua_State *L) {
+	LuaCommands* commands = LuaCommands::commandsFromLua(L);
+	return commands->get_software_version(L);
+}
+
+int LuaCommands::get_software_version(lua_State *L) {
+	if (!lua_istable(L, 1)) {
+		return luaL_error(L, "get_software_version requires a table parameter.");
+	}
+	lua_pushvalue(L, 1);
+	JID to(luatable_asstring(L, "to"));
+	if (!to.isValid()) {
+		return luaL_error(L, "invalid JID.");
+	}
+	int timeout = luatable_asint(L, "timeout");
+	if (timeout == 0) {
+		return luaL_error(L, "invalid timeout.");
+	}
+
+	int successCallback = luatable_asfunction(L, "success_callback");
+	int failureCallback = luatable_asfunction(L, "failure_callback");
+	int timeoutCallback = luatable_asfunction(L, "timeout_callback");
+	GetSoftwareVersionRequest::ref request = GetSoftwareVersionRequest::create(to, client_->getIQRouter());
+	Timer::ref timer = timerFactory_->createTimer(timeout * 1000);
+	Callbacks callbacks(successCallback, failureCallback, timeoutCallback);
+	request->onResponse.connect(boost::bind(&LuaCommands::handleSoftwareVersionResponse, this, _1, _2, false, request, timer, L, callbacks));
+	boost::shared_ptr<SoftwareVersion> fakePayload;
+	ErrorPayload::ref fakeError;
+	timer->onTick.connect(boost::bind(&LuaCommands::handleSoftwareVersionResponse, this, fakePayload, fakeError, true, request, timer, L, callbacks));
+	timer->start();
+	request->send();
+	return 1;
+}
+
+static int l_store_setting(lua_State *L) {
+	return LuaCommands::commandsFromLua(L)->store_setting(L);
+}
+
+static int l_get_setting(lua_State *L) {
+	return LuaCommands::commandsFromLua(L)->get_setting(L);
+}
+
+int LuaCommands::store_setting(lua_State *L) {
+	if (!lua_isstring(L, 2) || !lua_isstring(L, 1)) {
+		return luaL_error(L, "both setting and key must be strings");
+	}
+	std::string value(lua_tostring(L, 2));
+	std::string key(lua_tostring(L, 1));
+	lua_pop(L, 2);
+	storageFromLua(L)->saveSetting(key, value);
+	return 0;
+}
+
+int LuaCommands::get_setting(lua_State *L) {
+	if (!lua_isstring(L, 1)) {
+		return luaL_error(L, "key must be a string");
+	}
+	std::string key(lua_tostring(L, 1));
+	lua_pop(L, 1);
+	lua_pushstring(L, storageFromLua(L)->getSetting(key).c_str());
+	return 1;
+
+}
+
+void LuaCommands::handleLuaCommand(int callbackIndex, lua_State* L, const std::string& command, const std::string& params, Swift::Message::ref message) {
+	lua_rawgeti(L, LUA_REGISTRYINDEX, callbackIndex);
+	lua_pushstring(L, command.c_str());
+	lua_pushstring(L, params.c_str());
+	messageOntoStack(message, L);
+	int result = lua_pcall(L, 3, 0, 0);
+	if (result != 0) {
+		std::string error(lua_tostring(L, -1));
+		lua_pop(L, 1);
+		error = "Command '" + command + "' failed: " + error;
+		std::cout << error << std::endl;
+		commands_->replyTo(message, error, false);
+	}
+}
+
+void LuaCommands::messageOntoStack(Swift::Message::ref message, lua_State* L) {
+	lua_createtable(L, 0, 4);
+	std::string typeString;
+	switch (message->getType()) {
+		case Message::Chat : typeString = "chat";break;
+		case Message::Groupchat : typeString = "groupchat";break;
+		case Message::Normal : typeString = "normal";break;
+		case Message::Error : typeString = "error";break;
+		case Message::Headline : typeString = "headline";break;
+	}
+	lua_pushstring(L, typeString.c_str());
+	lua_setfield(L, -2, "type");
+	lua_pushstring(L, message->getFrom().toString().c_str());
+	lua_setfield(L, -2, "from");
+	lua_pushstring(L, message->getFrom().toBare().toString().c_str());
+	lua_setfield(L, -2, "frombare");
+	lua_pushstring(L, message->getTo().toString().c_str());
+	lua_setfield(L, -2, "to");
+	lua_pushstring(L, message->getBody().c_str());
+	lua_setfield(L, -2, "body");
+}
+
+void LuaCommands::loadScript(boost::filesystem::path filePath) {
+	std::cout << "Trying to load file from " << filePath << std::endl;
+	lua_State* lua = lua_open();
+	luaL_openlibs(lua);
+	lua_pushlightuserdata(lua, this);
+	lua_setfield(lua, LUA_REGISTRYINDEX, LUA_COMMANDS);
+	std::string filename(filePath.filename());
+	filename += ".storage";
+	boost::filesystem::path storagePath(boost::filesystem::path(path_) / filename);
+	Storage* storage = new Storage(storagePath);
+	lua_pushlightuserdata(lua, storage);
+	lua_setfield(lua, LUA_REGISTRYINDEX, STORAGE);
+	lua_register(lua, "swiftob_register_command", &l_register_command);
+	lua_register(lua, "swiftob_reply_to", &l_reply_to);
+	lua_register(lua, "swiftob_get_software_version", &l_get_software_version);
+	lua_register(lua, "swiftob_muc_input_to_jid", &l_muc_input_to_jid);
+	lua_register(lua, "swiftob_store_setting", &l_store_setting);
+	lua_register(lua, "swiftob_get_setting", &l_get_setting);
+	int fileLoaded = luaL_dofile(lua, filePath.string().c_str());
+	if (fileLoaded == 0 ) {
+		std::cout << "Loaded" << std::endl;
+	} else {
+		const char* error = lua_tostring(lua, -1);
+		std::cout << "Error: " << error << std::endl;
+		lua_pop(lua, -1);
+	}
+}
diff --git a/Swiftob/LuaCommands.h b/Swiftob/LuaCommands.h
new file mode 100644
index 0000000..dc8e36e
--- /dev/null
+++ b/Swiftob/LuaCommands.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+extern "C" {
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+}
+
+#include <boost/filesystem/fstream.hpp>
+#include <boost/noncopyable.hpp>
+#include "Swiften/Network/NetworkFactories.h"
+#include <Swiften/Elements/SoftwareVersion.h>
+#include <Swiften/Queries/Requests/GetSoftwareVersionRequest.h>
+#include <Swiften/Network/Timer.h>
+
+#include "Swiftob/Commands.h"
+#include "Swiftob/Storage.h"
+
+using namespace Swift;
+/**
+ * Yes, there's an odd naming scheme going on here for methods.
+ * normalCamelCase methods are methods called from C++
+ * lower_case_methods are exposed to Lua through wrappers and
+ * l_lower_case_functions are functions directly exposed (often calling the lower_case_methods).
+ */
+class LuaCommands {
+	public:
+		class LuaCommand : public Commands::Command, boost::noncopyable {
+			public:
+				/** Takes ownership of lua and storage.*/
+				LuaCommand(Commands::RoleList allowedBy, const std::string& description, lua_State* lua, Storage* storage) : Command(allowedBy, description), lua_(lua), storage_(storage) {
+				}
+
+				virtual ~LuaCommand() {
+					lua_close(lua_);
+					delete storage_;
+				};
+
+			private:
+				lua_State* lua_;
+				Storage* storage_;
+		};
+
+		class Callbacks {
+			public:
+				Callbacks(int success, int failure, int timeout) : success(success), failure(failure), timeout(timeout) {};
+				int success;
+				int failure;
+				int timeout;
+				void erase(lua_State *L) {
+					lua_pushnil(L);
+					lua_rawseti(L, LUA_REGISTRYINDEX, success);
+					lua_pushnil(L);
+					lua_rawseti(L, LUA_REGISTRYINDEX, failure);
+					lua_pushnil(L);
+					lua_rawseti(L, LUA_REGISTRYINDEX, timeout);
+				}
+		};
+
+		LuaCommands(Commands* commands, const std::string& path, Client* client, TimerFactory* timerFactory, MUCs* mucs);
+		/* Public but isn't really part of the API */
+		void handleLuaCommand(int callbackIndex, lua_State* L, const std::string& command, const std::string& params, Message::ref message);
+		Commands* getCommands() {return commands_;}
+		int get_software_version(lua_State *L);
+		int muc_input_to_jid(lua_State *L);
+		int store_setting(lua_State *L);
+		int get_setting(lua_State *L);
+		static LuaCommands* commandsFromLua(lua_State *L);
+		static Storage* storageFromLua(lua_State *L);
+	private:
+		void registerCommands();
+		void loadScript(boost::filesystem::path filePath);
+		void messageOntoStack(Message::ref message, lua_State* L);
+		void handleSoftwareVersionResponse(boost::shared_ptr<SoftwareVersion> version, ErrorPayload::ref error, bool timeout, GetSoftwareVersionRequest::ref request, Timer::ref timer, lua_State* L, Callbacks callbacks);
+	private:
+		std::string path_;
+		boost::filesystem::path scriptsPath_;
+		Commands* commands_;
+		MUCs* mucs_;
+		Client* client_;
+		TimerFactory* timerFactory_;
+};
+
+
diff --git a/Swiftob/MUCs.cpp b/Swiftob/MUCs.cpp
new file mode 100644
index 0000000..55bf313
--- /dev/null
+++ b/Swiftob/MUCs.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/MUCs.h"
+
+#include <Swiften/Client/Client.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/MUC/MUCManager.h>
+#include <Swiften/Base/String.h>
+
+#include "Swiftob/Storage.h"
+
+#define MUC_LIST_SETTING "muc_list"
+
+typedef std::pair<JID, MUC::ref> JIDMUCPair;
+
+MUCs::MUCs(Client* client, Storage* storage) : defaultNick_("Kanchil+") {
+	client_ = client;
+	storage_ = storage;
+	client_->onConnected.connect(boost::bind(&MUCs::handleConnected, this));
+}
+
+void MUCs::handleConnected() {
+	foreach (std::string room, String::split(storage_->getSetting(MUC_LIST_SETTING), ' ')) {
+		join(JID(room), boost::bind(&MUCs::handleInitialJoinSuccess, this), boost::bind(&MUCs::handleInitialJoinFailure, this, _1));
+	}
+}
+
+void MUCs::handleInitialJoinSuccess() {
+
+}
+
+void MUCs::handleInitialJoinFailure(const std::string&) {
+
+}
+
+void MUCs::join(const JID& room, boost::signal<void (const std::string&)>::slot_type successCallback, boost::function<void(const std::string& /*reason*/)> failureCallback) {
+	if (contains(room)) {
+		failureCallback("Already in room");
+	}
+	mucs_[room] = client_->getMUCManager()->createMUC(room);
+	mucs_[room]->onJoinComplete.connect(successCallback);
+	mucs_[room]->onJoinFailed.connect(boost::bind(&MUCs::handleJoinFailed, this, room, _1, failureCallback));
+	mucs_[room]->joinWithContextSince(defaultNick_, boost::posix_time::microsec_clock::universal_time());
+	save();
+}
+
+void MUCs::part(const JID& room) {
+	if (!contains(room)) {
+		return;
+	}
+	mucs_[room]->part();
+}
+
+bool MUCs::contains(const JID& room) {
+	return mucs_.find(room) != mucs_.end();
+}
+
+void MUCs::handleJoinFailed(const JID& muc, ErrorPayload::ref error, boost::function<void(const std::string& /*reason*/)> failureCallback) {
+	std::string errorMessage("Couldn't join room");
+	if (error) {
+		switch (error->getCondition()) {
+			case ErrorPayload::Conflict:
+			//	rejoinNick = nick_ + "_";
+				errorMessage += ": Nickname in use";
+			//				errorMessage = str(format("Unable to enter this room as %1%, retrying as %2%") % nick_ % rejoinNick);
+				break;
+			case ErrorPayload::JIDMalformed:
+				errorMessage += ": ";
+				errorMessage += "No nickname specified";
+				break;
+			case ErrorPayload::NotAuthorized:
+				errorMessage += ": ";
+				errorMessage += "A password needed";
+				break;
+			case ErrorPayload::RegistrationRequired:
+				errorMessage += ": ";
+				errorMessage += "Only members may enter";
+				break;
+			case ErrorPayload::Forbidden:
+				errorMessage += ": ";
+				errorMessage += "You are banned from the room";
+				break;
+			case ErrorPayload::ServiceUnavailable:
+				errorMessage += ": ";
+				errorMessage += "The room is full";
+				break;
+			case ErrorPayload::ItemNotFound:
+				errorMessage += ": ";
+				errorMessage += "The room does not exist";
+				break;
+			default:
+				break;
+		}
+		if (!error->getText().empty()) {
+			errorMessage += " - " + error->getText();
+		}
+	}
+	mucs_.erase(muc);
+	failureCallback(errorMessage);
+}
+
+void MUCs::save() {
+	std::string concat;
+	foreach (JIDMUCPair pair, mucs_) {
+		concat += pair.first.toString() + " ";
+	}
+	storage_->saveSetting(MUC_LIST_SETTING, concat);
+}
+
+MUC::ref MUCs::getMUC(const JID& room) {
+	return (mucs_.find(room) != mucs_.end()) ? mucs_[room] : MUC::ref();
+}
diff --git a/Swiftob/MUCs.h b/Swiftob/MUCs.h
new file mode 100644
index 0000000..e727ec2
--- /dev/null
+++ b/Swiftob/MUCs.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/MUC/MUCRegistry.h>
+
+namespace Swift {
+class Client;
+class MUC;
+}
+
+class Storage;
+
+using namespace Swift;
+
+class MUCs {
+	public:
+		MUCs(Client* client, Storage* storage);
+		void join(const JID& room, boost::signal<void (const std::string&)>::slot_type successCallback, boost::function<void(const std::string& /*reason*/)> failureCallback);
+		void part(const JID& room);
+		bool contains(const JID& room);
+		MUC::ref getMUC(const JID& room);
+	private:
+		void handleConnected();
+		void handleJoinFailed(const JID& room, ErrorPayload::ref error, boost::function<void(const std::string& /*reason*/)> failureCallback);
+		void handleInitialJoinSuccess();
+		void handleInitialJoinFailure(const std::string&);
+		void save();
+	private:
+		MUCRegistry registry_;
+		std::map<JID, MUC::ref> mucs_;
+		Client* client_;
+		Storage* storage_;
+		std::string defaultNick_;
+};
+
+
diff --git a/Swiftob/SConscript b/Swiftob/SConscript
new file mode 100644
index 0000000..2139822
--- /dev/null
+++ b/Swiftob/SConscript
@@ -0,0 +1,26 @@
+Import("env")
+
+
+if env["SCONS_STAGE"] == "build":
+	myenv = env.Clone()
+	myenv.MergeFlags(myenv["SWIFTEN_FLAGS"])
+	myenv.MergeFlags(myenv["LIBIDN_FLAGS"])
+	myenv.MergeFlags(myenv["BOOST_FLAGS"])
+	myenv.MergeFlags(myenv["ZLIB_FLAGS"])
+	myenv.MergeFlags(myenv["OPENSSL_FLAGS"])
+	myenv.MergeFlags(myenv.get("SQLITE_FLAGS", {}))
+	myenv.MergeFlags(myenv.get("LIBXML_FLAGS", ""))
+	myenv.MergeFlags(myenv.get("EXPAT_FLAGS", ""))
+	myenv.MergeFlags(myenv["PLATFORM_FLAGS"])
+	myenv.MergeFlags(myenv["LUA_FLAGS"])
+	sources = [
+		"linit.c", # This is horrible!
+		"Swiftob.cpp",
+		"Users.cpp",
+		"Commands.cpp",
+		"MUCs.cpp",
+		"Storage.cpp",
+		"LuaCommands.cpp",
+		"main.cpp"
+	]
+	swiftob = myenv.Program("swiftob", sources)
\ No newline at end of file
diff --git a/Swiftob/Storage.cpp b/Swiftob/Storage.cpp
new file mode 100644
index 0000000..0cf16d7
--- /dev/null
+++ b/Swiftob/Storage.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/Storage.h"
+
+#include <Swiften/Base/String.h>
+#include <Swiften/Base/ByteArray.h>
+#include <Swiften/Base/foreach.h>
+
+typedef std::pair<std::string, std::string> Strings;
+
+Storage::Storage(const std::string& path) : settingsPath_(boost::filesystem::path(path)) {
+	load();
+}
+
+Storage::Storage(const boost::filesystem::path& path) : settingsPath_(path) {
+	load();
+}
+
+void Storage::load() {
+	if (boost::filesystem::exists(settingsPath_)) {
+		Swift::ByteArray data;
+		data.readFromFile(settingsPath_.string());
+		foreach (std::string line, Swift::String::split(data.toString(), '\n')) {
+			std::pair<std::string, std::string> pair = Swift::String::getSplittedAtFirst(line, '\t');
+			settings_[pair.first] = pair.second;
+		}
+	}
+}
+
+void Storage::saveSetting(const std::string& setting, const std::string& value) {
+	settings_[setting] = value;
+	std::string settingsString;
+	foreach(Strings pair, settings_) {
+		settingsString += pair.first + '\t' + pair.second + '\n';
+	}
+	boost::filesystem::ofstream file(settingsPath_);
+	file << settingsString;
+	file.close();
+}
+
+std::string Storage::getSetting(const std::string& setting) {
+	return settings_[setting];
+}
diff --git a/Swiftob/Storage.h b/Swiftob/Storage.h
new file mode 100644
index 0000000..c4181d8
--- /dev/null
+++ b/Swiftob/Storage.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+#include <boost/filesystem/fstream.hpp>
+#include <boost/filesystem/path.hpp>
+
+class Storage {
+	public:
+		Storage(const std::string& path);
+		Storage(const boost::filesystem::path& path);
+		void saveSetting(const std::string& setting, const std::string& value);
+		std::string getSetting(const std::string& setting);
+	private:
+		void load();
+		std::map<std::string, std::string> settings_;
+		std::string path_;
+		boost::filesystem::path settingsPath_;
+};
+
+
diff --git a/Swiftob/Swiftob.cpp b/Swiftob/Swiftob.cpp
new file mode 100644
index 0000000..6c6dad0
--- /dev/null
+++ b/Swiftob/Swiftob.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/Swiftob.h"
+
+#include <string>
+#include <iostream>
+#include <boost/bind.hpp>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Base/String.h>
+#include <Swiften/Presence/PresenceSender.h>
+
+#include "Swiftob/Users.h"
+#include "Swiftob/Storage.h"
+
+
+po::options_description Swiftob::getOptionsDescription() {
+	po::options_description result("Options");
+	result.add_options()
+		("path", po::value<std::string>(), "Configuration folder")
+		("help", "produce help message")
+		("init", "Reset everything (Really, everything, be careful, you only want to use this on first run).")
+		("jid", po::value<std::string>(), "JID to use")
+		("password", po::value<std::string>(), "password")
+		("initial-owner", po::value<std::string>(), "Initial bot owner (JID)")
+		;
+	return result;
+}
+
+Swiftob::Swiftob(const po::variables_map& options) : options_(options), networkFactories_(&eventLoop_), quitting_(false) {
+	std::string path;
+	path = options["path"].as<std::string>();
+	client_ = new Swift::Client(Swift::JID(options["jid"].as<std::string>()), options["password"].as<std::string>(), &networkFactories_);
+	storage_ = new Storage(boost::filesystem::path(path) / "settings.txt");
+	mucs_ = new MUCs(client_, storage_);
+	users_ = new Users(client_, mucs_);
+	commands_ = new Commands(users_, client_, storage_, mucs_);
+	lua_ = new LuaCommands(commands_, path, client_, networkFactories_.getTimerFactory(), mucs_);
+	client_->onConnected.connect(boost::bind(&Swiftob::handleConnected, this));
+	client_->onDisconnected.connect(boost::bind(&Swiftob::handleDisconnected, this, _1));
+	client_->onMessageReceived.connect(boost::bind(&Swiftob::handleMessageReceived, this, _1));
+	if (options_.count("init") > 0) {
+
+	} else if (options_.count("jid") > 0 || options_.count("password") > 0 || options_.count("initial-owner") == 0) {
+		std::cout << "Ignoring initial config options without --initial" << std::endl;
+	}
+	client_->setAlwaysTrustCertificates();
+	client_->setSoftwareVersion("Swiftob", "pregit", "");
+	client_->connect();
+	eventLoop_.run();
+}
+
+void Swiftob::handleConnected() {
+	std::cout << "Connected" << std::endl;
+	if (options_.count("init") > 0) {}{ /* FIXME: Not ready for persistence yet*/
+		users_->clearAll();
+		users_->addUser(Users::User(Swift::JID(options_["initial-owner"].as<std::string>()), Users::User::Owner));
+	}
+	Swift::Presence::ref presence(new Swift::Presence());
+	presence->setStatus("Online and botty");
+	client_->getPresenceSender()->sendPresence(presence);
+}
+
+void Swiftob::handleDisconnected(const boost::optional<Swift::ClientError>& /*error*/) {
+	std::cout << "Disconnected" << std::endl;
+	/* FIXME: check if last connect was more than a minute ago. If so, go ahead and connect, if not then wait a minute before connecting.*/
+	if (quitting_) {
+		eventLoop_.stop();
+	} else {
+		client_->connect();
+	}
+}
+
+void Swiftob::handleMessageReceived(Swift::Message::ref message) {
+	Swift::Message::Type type = message->getType();
+	if (type == Swift::Message::Error || type == Swift::Message::Headline) {
+		std::cout << "Ignoring typed message" << std::endl;
+		return;
+	}
+	std::string body = message->getBody();
+	std::cout << "Got message with body " << body << std::endl;
+	if (body.size() == 0) {
+		std::cout << "Not handling empty body" << std::endl;
+		return;
+	}
+	/*Convert body into !command if it's not a MUC, and it misses the bang*/
+	std::string bangBody(body);
+	if (type != Swift::Message::Groupchat && body[0] != '!') {
+		bangBody = "!" + body;
+	}
+	std::cout << "After banging, body is " << bangBody << std::endl;
+	std::pair<std::string, std::string> split = Swift::String::getSplittedAtFirst(bangBody, ' ');
+	std::string commandName(split.first);
+	commandName = Swift::String::getSplittedAtFirst(commandName, '!').second;
+	/*FIXME: remove leading bang in commandName*/
+	if (commands_->hasCommand(commandName)) {
+		std::cout << "Matched command " << commandName << std::endl;
+		commands_->runCommand(commandName, split.second, message);
+	}
+}
+
+Swiftob::~Swiftob() {
+	delete lua_;
+	delete commands_;
+	delete storage_;
+	delete users_;
+	delete client_;
+}
+
+int Swiftob::exec() {
+	return 0;
+}
+
diff --git a/Swiftob/Swiftob.h b/Swiftob/Swiftob.h
new file mode 100644
index 0000000..45061d0
--- /dev/null
+++ b/Swiftob/Swiftob.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options/options_description.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+#include <Swiften/Base/Platform.h>
+#include <Swiften/EventLoop/SimpleEventLoop.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <Swiften/Client/Client.h>
+#include <Swiften/Network/BoostNetworkFactories.h>
+
+#include "Swiftob/Commands.h"
+#include "Swiftob/LuaCommands.h"
+
+namespace po = boost::program_options;
+
+class Users;
+class Storage;
+class Swiftob {
+	public:
+		Swiftob(const po::variables_map& options);
+		static po::options_description getOptionsDescription();
+		int exec();
+		~Swiftob();
+	private:
+		void handleConnected();
+		void handleDisconnected(const boost::optional<Swift::ClientError>&);
+		void handleMessageReceived(Swift::Message::ref);
+	private:
+		const po::variables_map options_;
+		Swift::SimpleEventLoop eventLoop_;
+		Swift::BoostNetworkFactories networkFactories_;
+		Commands* commands_;
+		LuaCommands* lua_;
+		Storage* storage_;
+		MUCs* mucs_;
+		bool quitting_;
+		Users* users_;
+		Swift::Client* client_;
+};
diff --git a/Swiftob/Users.cpp b/Swiftob/Users.cpp
new file mode 100644
index 0000000..55ba4eb
--- /dev/null
+++ b/Swiftob/Users.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swiftob/Users.h"
+
+#include <iostream>
+
+#include <Swiften/Client/Client.h>
+
+#include "Swiftob/MUCs.h"
+
+Users::Users(Client* client, MUCs* mucs) {
+	client_ = client;
+	mucs_ = mucs;
+}
+
+/* TODO: Store in roster */
+void Users::clearAll() {
+	users_.clear();
+}
+
+void Users::addUser(const User& user) {
+	users_.push_back(user);
+}
+
+Users::User::Role Users::getRoleForSender(Message::ref message) {
+	JID jid = message->getFrom();
+	MUC::ref muc = mucs_->getMUC(message->getFrom().toBare());
+	if (muc && muc->hasOccupant(message->getFrom().getResource())) {
+		MUCOccupant occupant = muc->getOccupant(message->getFrom().getResource());
+		if (occupant.getRealJID()) {
+			jid = occupant.getRealJID().get();
+		}
+	}
+	foreach (User user, users_) {
+		if (user.getJID().equals(jid.toBare(), JID::WithoutResource)) {
+			return user.getRole();
+		}
+	}
+	return User::Unknown;
+}
+
diff --git a/Swiftob/Users.h b/Swiftob/Users.h
new file mode 100644
index 0000000..0acc330
--- /dev/null
+++ b/Swiftob/Users.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/Message.h>
+
+namespace Swift {
+class Client;
+}
+
+class MUCs;
+
+using namespace Swift;
+
+class Users {
+	public:
+		class User {
+			public:
+				/* If you add a role here, edit the role lists in Commands.cpp*/
+				enum Role {Unknown, Owner};
+				User(const JID& jid, Role role) : jid_(jid), role_(role) {}
+				Role getRole() {return role_;}
+				Swift::JID getJID() {return jid_;}
+			private:
+				Swift::JID jid_;
+				Role role_;
+		};
+
+	public:
+		Users(Client* client, MUCs* mucs);
+		void clearAll();
+		void addUser(const User& user);
+		User::Role getRoleForSender(Message::ref message);
+
+	private:
+		std::vector<User> users_;
+		Client* client_;
+		MUCs* mucs_;
+};
+
+
diff --git a/Swiftob/linit.c b/Swiftob/linit.c
new file mode 100644
index 0000000..13c5b09
--- /dev/null
+++ b/Swiftob/linit.c
@@ -0,0 +1 @@
+#include "../3rdParty/Lua/src/linit.c"
diff --git a/Swiftob/main.cpp b/Swiftob/main.cpp
new file mode 100644
index 0000000..9908b45
--- /dev/null
+++ b/Swiftob/main.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2011 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options.hpp>
+#include <boost/version.hpp>
+#include <iostream>
+
+#include "Swiftob.h"
+
+int main(int argc, char* argv[]) {
+	boost::program_options::options_description desc = Swiftob::getOptionsDescription();
+	boost::program_options::variables_map vm;
+	try {
+		boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
+	} catch (boost::program_options::unknown_option option) {
+#if BOOST_VERSION >= 104200
+		std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." <<  std::endl;
+#else
+		std::cout << "Error: " << option.what() << " (continuing)" <<  std::endl;
+#endif
+	}
+	boost::program_options::notify(vm);
+	if (vm.count("help") > 0) {
+		std::cout << desc << "\n";
+		return 1;
+	}
+
+	Swiftob bot(vm);
+	int result = bot.exec();
+
+	return result;
+}
diff --git a/Swiftob/scripts/agenda.lua b/Swiftob/scripts/agenda.lua
new file mode 100644
index 0000000..897b89c
--- /dev/null
+++ b/Swiftob/scripts/agenda.lua
@@ -0,0 +1,94 @@
+agendas = {}
+currents = {}
+
+function full_agenda(from)
+   fullagenda = {}
+   fullagenda[1] = "Roll call"
+   fullagenda[2] = "Agenda bashing"
+   for i, v in ipairs(agendas[from]) do
+      table.insert(fullagenda, v)
+   end
+   table.insert(fullagenda, "Date of next meeting")
+   table.insert(fullagenda, "Any other business")
+   return fullagenda
+end
+
+function agenda_full_command(command, params, message)
+   from = message['frombare']
+   ensure_loaded(from)
+   agenda = agendas[from]
+   fullagenda = full_agenda(from)
+   reply = ""
+   for i, v in ipairs(fullagenda) do
+      reply = reply..i..") "..v.."\n"
+   end
+   reply = reply.."Fini"
+   swiftob_reply_to(message, reply)
+end
+
+function agenda_append_command(command, params, message)
+   from = message['frombare']
+   agenda_append(from, params)
+   agenda_save(from)
+   swiftob_reply_to(message, "Done.")
+end
+
+function agenda_up_command(command, params, message)
+   from = message['frombare']
+   ensure_loaded(from)
+   up = tonumber(params)
+   if up == nil then up = 1 end
+   currents[from] = currents[from] + up
+   if currents[from] <= 0 then currents[from] = 1 end
+   item = full_agenda(from)[currents[from]]
+   if item == nil then item = "Fini." end
+   reply = currents[from]..") "..item
+   swiftob_reply_to(message, reply)
+end
+
+
+function agenda_clear_command(command, params, message)
+   from = message['frombare']
+   agendas[from] = {}
+   agenda_save(from)
+   swiftob_reply_to(message, "Done.")
+end
+
+function agenda_save(from)
+   agenda = agendas[from]
+   swiftob_store_setting("count@@@"..from, #agenda)
+   for i, v in ipairs(agenda) do
+      swiftob_store_setting(i.."@@@"..from, v)
+   end
+end
+
+function ensure_loaded(from)
+   if agendas[from] == nil then
+      agenda_load(from)
+   end
+end
+
+function agenda_load(from)
+   agendas[from] = {}
+   currents[from] = 0
+   num_items = tonumber(swiftob_get_setting("count@@@"..from))
+   if num_items == nil then num_items = 0 end
+   for i = 1, num_items do
+      agenda_append(from, swiftob_get_setting(i.."@@@"..from))
+   end
+end
+
+function agenda_append(from, item)
+   ensure_loaded(from)
+   agenda = agendas[from]
+   table.insert(agenda, item)
+   agendas[from] = agenda
+end
+
+swiftob_register_command("agenda", "Anyone", "print the full agenda", agenda_full_command)
+swiftob_register_command("agendaappend", "Owner", "append an item to the agenda", agenda_append_command)
+swiftob_register_command("agendaclear", "Owner", "clear the agenda", agenda_clear_command)
+swiftob_register_command("agendaup", "Owner", "Moves the current counter by n, and returns the current agenda item", agenda_up_command)
+
+
+
diff --git a/Swiftob/scripts/echo.lua b/Swiftob/scripts/echo.lua
new file mode 100644
index 0000000..7adc2b3
--- /dev/null
+++ b/Swiftob/scripts/echo.lua
@@ -0,0 +1,5 @@
+function echo_message(command, params, message)
+   swiftob_reply_to(message, params)
+end
+
+swiftob_register_command("echo", "Anyone", "What did you say?", echo_message)
\ No newline at end of file
diff --git a/Swiftob/scripts/eval.lua b/Swiftob/scripts/eval.lua
new file mode 100644
index 0000000..c9840dc
--- /dev/null
+++ b/Swiftob/scripts/eval.lua
@@ -0,0 +1,14 @@
+
+function eval_command(command, params, message)
+   assert(loadstring(params))()
+   swiftob_reply_to(message, "done")
+end
+
+function evalr_command(command, params, message)
+   result = assert(loadstring(params))()
+   swiftob_reply_to(message, "" .. result)
+end
+
+swiftob_register_command("eval", "Owner", "Evaluate an expression", eval_command)
+swiftob_register_command("evalr", "Owner", "Evaluate an expression and return the result", evalr_command)
+
diff --git a/Swiftob/scripts/version.lua b/Swiftob/scripts/version.lua
new file mode 100644
index 0000000..40b4e4d
--- /dev/null
+++ b/Swiftob/scripts/version.lua
@@ -0,0 +1,32 @@
+function friendly_version(version) 
+   result = version['name']
+   if version['version'] ~= nil and version['version'] ~= "" then
+      result = result.." version "..version['version']
+   end
+   if version['os'] ~= nil and version['os'] ~= "" then
+      result = result .." on "..version['os']
+   end
+   return result
+end
+
+function version_command(command, params, message)
+   jid = swiftob_muc_input_to_jid(params, message['from'])
+   if jid == nil then
+
+   else 
+      swiftob_get_software_version({
+	to=jid, 
+	timeout=10, 
+	success_callback=function(result)
+			    swiftob_reply_to(message, params.." is running "..friendly_version(result))
+			 end,
+	failure_callback=function(error)
+			    swiftob_reply_to(message, "Error getting version from "..params..": "..error)
+			end,
+	timeout_callback=function()
+			    swiftob_reply_to(message, "Timeout waiting for version from "..params)
+			 end})
+   end
+end
+
+swiftob_register_command("version", "Anyone", "Ask for someone's version", version_command)
\ No newline at end of file
-- 
cgit v0.10.2-6-g49f6