/* * Copyright (c) 2013 Remko Tronçon * Licensed under the GNU General Public License. * See the COPYING file for more information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Swift; namespace lambda = boost::lambda; static inline SluiftClient* getClient(lua_State* L) { return *Lua::checkUserData(L, 1); } SLUIFT_LUA_FUNCTION(Client, async_connect) { SluiftClient* client = getClient(L); std::string host = client->getOptions().manualHostname; int port = client->getOptions().manualPort; if (lua_istable(L, 2)) { if (boost::optional hostString = Lua::getStringField(L, 2, "host")) { host = *hostString; } if (boost::optional portInt = Lua::getIntField(L, 2, "port")) { port = *portInt; } } client->connect(host, port); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, wait_connected, "Block until the client is connected.\n\nThis is useful after an `async_connect`.", "self", "" ) { getClient(L)->waitConnected(); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, is_connected, "Checks whether this client is still connected.\n\nReturns a boolean.", "self\n", "" ) { lua_pushboolean(L, getClient(L)->isConnected()); return 1; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, disconnect, "Disconnect from the server", "self\n", "" ) { getClient(L)->disconnect(); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, set_version, "Sets the published version of this client.", "self", "name the name of the client software\n" "version the version identifier of this client\n" "os the OS this client is running on\n" ) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); if (boost::shared_ptr version = boost::dynamic_pointer_cast(Sluift::globals.elementConvertor.convertFromLuaUntyped(L, 2, "software_version"))) { client->setSoftwareVersion(version->getName(), version->getVersion(), version->getOS()); } return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, get_contacts, "Returns a table of all the contacts in the contact list.", "self\n", "" ) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); Lua::Table contactsTable; foreach(const XMPPRosterItem& item, client->getRoster()) { std::string subscription; switch(item.getSubscription()) { case RosterItemPayload::None: subscription = "none"; break; case RosterItemPayload::To: subscription = "to"; break; case RosterItemPayload::From: subscription = "from"; break; case RosterItemPayload::Both: subscription = "both"; break; case RosterItemPayload::Remove: subscription = "remove"; break; } Lua::Table itemTable = boost::assign::map_list_of ("jid", boost::make_shared(item.getJID().toString())) ("name", boost::make_shared(item.getName())) ("subscription", boost::make_shared(subscription)) ("groups", boost::make_shared(std::vector(item.getGroups().begin(), item.getGroups().end()))); contactsTable[item.getJID().toString()] = boost::make_shared(itemTable); } pushValue(L, contactsTable); Lua::registerTableToString(L, -1); return 1; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, send_message, "Send a message.", "self\n" "body the body of the message. Can alternatively be specified using the `body` option\n", "to the JID to send the message to\n" "body the body of the message\n" "type the type of message to send (`normal`, `chat`, `error`, `groupchat`, `headline`)\n" ) { Sluift::globals.eventLoop.runOnce(); JID to; std::string body; int index = 2; Message::Type type = Message::Chat; if (lua_isstring(L, index)) { to = std::string(lua_tostring(L, index)); ++index; if (!lua_isstring(L, index)) { std::string body = lua_tostring(L, index); ++index; } } if (lua_istable(L, index)) { if (boost::optional value = Lua::getStringField(L, index, "to")) { to = *value; } if (boost::optional value = Lua::getStringField(L, index, "body")) { body = *value; } if (boost::optional value = Lua::getStringField(L, index, "type")) { if (*value == "normal") { type = Message::Normal; } else if (*value == "chat") { type = Message::Chat; } else if (*value == "error") { type = Message::Error; } else if (*value == "groupchat") { type = Message::Groupchat; } else if (*value == "headline") { type = Message::Headline; } } } if (!to.isValid()) { throw Lua::Exception("Missing 'to'"); } if (body.empty()) { throw Lua::Exception("Missing 'body'"); } Message::ref message = boost::make_shared(); message->setTo(to); message->setBody(body); message->setType(type); getClient(L)->getClient()->sendMessage(message); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, send_presence, "Send presence.", "self\n" "body the text of the presence. Can alternatively be specified using the `status` option\n", "to the JID to send the message to\n" "status the text of the presence\n" "priority the priority of the presence\n" "type the type of message to send (`available`, `error`, `probe`, `subscribe`, `subscribed`, `unavailable`, `unsubscribe`, `unsubscribed`)\n" ) { Sluift::globals.eventLoop.runOnce(); boost::shared_ptr presence = boost::make_shared(); int index = 2; if (lua_isstring(L, index)) { presence->setStatus(lua_tostring(L, index)); ++index; } if (lua_istable(L, index)) { if (boost::optional value = Lua::getStringField(L, index, "to")) { presence->setTo(*value); } if (boost::optional value = Lua::getStringField(L, index, "status")) { presence->setStatus(*value); } if (boost::optional value = Lua::getIntField(L, index, "priority")) { presence->setPriority(*value); } if (boost::optional value = Lua::getStringField(L, index, "type")) { if (*value == "available") { presence->setType(Presence::Available); } else if (*value == "error") { presence->setType(Presence::Error); } else if (*value == "probe") { presence->setType(Presence::Probe); } else if (*value == "subscribe") { presence->setType(Presence::Subscribe); } else if (*value == "subscribed") { presence->setType(Presence::Subscribed); } else if (*value == "unavailable") { presence->setType(Presence::Unavailable); } else if (*value == "unsubscribe") { presence->setType(Presence::Unsubscribe); } else if (*value == "unsubscribed") { presence->setType(Presence::Unsubscribed); } } } getClient(L)->getClient()->getPresenceSender()->sendPresence(presence); lua_pushvalue(L, 1); return 0; } static int sendQuery(lua_State* L, IQ::Type type) { SluiftClient* client = getClient(L); JID to; if (boost::optional toString = Lua::getStringField(L, 2, "to")) { to = JID(*toString); } int timeout = Sluift::globals.timeout; if (boost::optional timeoutInt = Lua::getIntField(L, 2, "timeout")) { timeout = *timeoutInt; } boost::shared_ptr payload; lua_getfield(L, 2, "query"); if (lua_type(L, -1) == LUA_TTABLE) { payload = Sluift::globals.elementConvertor.convertFromLua(L, -1); } else if (lua_type(L, -1) == LUA_TSTRING) { payload = boost::make_shared(Lua::checkString(L, -1)); } lua_pop(L, 1); return client->sendRequest( boost::make_shared< GenericRequest >(type, to, payload, client->getClient()->getIQRouter()), timeout).convertToLuaResult(L); } #define DISPATCH_PUBSUB_PAYLOAD(payloadType, container, response) \ else if (boost::shared_ptr p = boost::dynamic_pointer_cast(payload)) { \ return client->sendPubSubRequest(type, to, p, timeout).convertToLuaResult(L); \ } SLUIFT_LUA_FUNCTION(Client, query_pubsub) { SluiftClient* client = getClient(L); JID to; if (boost::optional toString = Lua::getStringField(L, 2, "to")) { to = JID(*toString); } int timeout = Sluift::globals.timeout; if (boost::optional timeoutInt = Lua::getIntField(L, 2, "timeout")) { timeout = *timeoutInt; } IQ::Type type; if (boost::optional queryType = Lua::getStringField(L, 2, "type")) { if (*queryType == "get") { type = IQ::Get; } else if (*queryType == "set") { type = IQ::Set; } else { throw Lua::Exception("Illegal query type: '" + *queryType + "'"); } } else { throw Lua::Exception("Missing query type"); } lua_getfield(L, 2, "query"); if (!lua_istable(L, -1)) { throw Lua::Exception("Missing/incorrect query"); } boost::shared_ptr payload = Sluift::globals.elementConvertor.convertFromLua(L, -1); if (false) { } SWIFTEN_PUBSUB_FOREACH_PUBSUB_PAYLOAD_TYPE(DISPATCH_PUBSUB_PAYLOAD) else { throw Lua::Exception("Incorrect PubSub payload"); } } SLUIFT_LUA_FUNCTION(Client, get) { return sendQuery(L, IQ::Get); } SLUIFT_LUA_FUNCTION(Client, set) { return sendQuery(L, IQ::Set); } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, send, "Sends a raw string", "self\n" "data the string to send\n", "" ) { Sluift::globals.eventLoop.runOnce(); getClient(L)->getClient()->sendData(std::string(Lua::checkString(L, 2))); lua_pushvalue(L, 1); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, set_options, "Sets the connection options of this client.", "self", "host The host to connect to. When omitted, is determined from resolving the JID domain.\n" "port The port to connect to. When omitted, is determined from resolving the JID domain.\n" "ack Request acknowledgements\n" "compress Use stream compression when available\n" "tls Use TLS when available\n" "bosh_url Connect using the specified BOSH URL\n" "allow_plain_without_tls Allow PLAIN authentication without a TLS encrypted connection\n" ) { SluiftClient* client = getClient(L); Lua::checkType(L, 2, LUA_TTABLE); lua_getfield(L, 2, "host"); if (!lua_isnil(L, -1)) { client->getOptions().manualHostname = lua_tostring(L, -1); } lua_getfield(L, 2, "port"); if (!lua_isnil(L, -1)) { client->getOptions().manualPort = boost::numeric_cast(lua_tointeger(L, -1)); } lua_getfield(L, 2, "ack"); if (!lua_isnil(L, -1)) { client->getOptions().useAcks = lua_toboolean(L, -1); } lua_getfield(L, 2, "compress"); if (!lua_isnil(L, -1)) { client->getOptions().useStreamCompression = lua_toboolean(L, -1); } lua_getfield(L, 2, "tls"); if (!lua_isnil(L, -1)) { bool useTLS = lua_toboolean(L, -1); client->getOptions().useTLS = (useTLS ? ClientOptions::UseTLSWhenAvailable : ClientOptions::NeverUseTLS); } lua_getfield(L, 2, "bosh_url"); if (!lua_isnil(L, -1)) { client->getOptions().boshURL = URL::fromString(lua_tostring(L, -1)); } lua_getfield(L, 2, "allow_plain_without_tls"); if (!lua_isnil(L, -1)) { client->getOptions().allowPLAINWithoutTLS = lua_toboolean(L, -1); } lua_pushvalue(L, 1); return 0; } static std::string convertPresenceTypeToString(Presence::Type type) { std::string result; switch (type) { case Presence::Available: result = "available"; break; case Presence::Error: result = "error"; break; case Presence::Probe: result = "probe"; break; case Presence::Subscribe: result = "subscribe"; break; case Presence::Subscribed: result = "subscribed"; break; case Presence::Unavailable: result = "unavailable"; break; case Presence::Unsubscribe: result = "unsubscribe"; break; case Presence::Unsubscribed: result = "unsubscribed"; break; } return result; } static std::string convertMessageTypeToString(Message::Type type) { std::string result; switch (type) { case Message::Normal: result = "normal"; break; case Message::Chat: result = "chat"; break; case Message::Error: result = "error"; break; case Message::Groupchat: result = "groupchat"; break; case Message::Headline: result = "headline"; break; } return result; } static void pushEvent(lua_State* L, const SluiftClient::Event& event) { switch (event.type) { case SluiftClient::Event::MessageType: { Message::ref message = boost::dynamic_pointer_cast(event.stanza); Lua::Table result = boost::assign::map_list_of ("type", boost::make_shared(std::string("message"))) ("from", boost::make_shared(message->getFrom().toString())) ("body", boost::make_shared(message->getBody())) ("message_type", boost::make_shared(convertMessageTypeToString(message->getType()))); Lua::pushValue(L, result); Lua::registerTableToString(L, -1); break; } case SluiftClient::Event::PresenceType: { Presence::ref presence = boost::dynamic_pointer_cast(event.stanza); Lua::Table result = boost::assign::map_list_of ("type", boost::make_shared(std::string("presence"))) ("from", boost::make_shared(presence->getFrom().toString())) ("status", boost::make_shared(presence->getStatus())) ("presence_type", boost::make_shared(convertPresenceTypeToString(presence->getType()))); Lua::pushValue(L, result); if (!presence->getPayloads().empty()) { const std::vector > payloads = presence->getPayloads(); lua_createtable(L, boost::numeric_cast(payloads.size()), 0); for (size_t i = 0; i < payloads.size(); ++i) { Sluift::globals.elementConvertor.convertToLua(L, payloads[i]); lua_rawseti(L, -2, boost::numeric_cast(i+1)); } Lua::registerGetByTypeIndex(L, -1); lua_setfield(L, -2, "payloads"); } Lua::registerTableToString(L, -1); break; } case SluiftClient::Event::PubSubEventType: { Sluift::globals.elementConvertor.convertToLua(L, event.pubsubEvent); lua_pushstring(L, "pubsub"); lua_setfield(L, -2, "type"); lua_pushstring(L, event.from.toString().c_str()); lua_setfield(L, -2, "from"); lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex); lua_getfield(L, -1, "process_pubsub_event"); lua_pushvalue(L, -3); lua_call(L, 1, 0); lua_pop(L, 1); } } } struct CallUnaryLuaPredicateOnEvent { CallUnaryLuaPredicateOnEvent(lua_State* L, int index) : L(L), index(index) { } bool operator()(const SluiftClient::Event& event) { lua_pushvalue(L, index); pushEvent(L, event); if (lua_pcall(L, 1, 1, 0) != 0) { throw Lua::Exception(lua_tostring(L, -1)); } bool result = lua_toboolean(L, -1); lua_pop(L, 1); return result; } lua_State* L; int index; }; SLUIFT_LUA_FUNCTION(Client, get_next_event) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); int timeout = Sluift::globals.timeout; boost::optional type; int condition = 0; if (lua_istable(L, 2)) { if (boost::optional typeString = Lua::getStringField(L, 2, "type")) { if (*typeString == "message") { type = SluiftClient::Event::MessageType; } else if (*typeString == "presence") { type = SluiftClient::Event::PresenceType; } else if (*typeString == "pubsub") { type = SluiftClient::Event::PubSubEventType; } } if (boost::optional timeoutInt = Lua::getIntField(L, 2, "timeout")) { timeout = *timeoutInt; } lua_getfield(L, 2, "if"); if (lua_isfunction(L, -1)) { condition = Lua::absoluteOffset(L, -1); } } boost::optional event; if (condition) { event = client->getNextEvent(timeout, CallUnaryLuaPredicateOnEvent(L, condition)); } else if (type) { event = client->getNextEvent( timeout, lambda::bind(&SluiftClient::Event::type, lambda::_1) == *type); } else { event = client->getNextEvent(timeout); } if (event) { pushEvent(L, *event); } else { lua_pushnil(L); } return 1; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, add_contact, "Add a contact to the contact list.", "self\n", "jid The JID of the contact to add\n" "name The name to use in the contact list\n" "groups An array of group names to add the contact to\n") { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); RosterItemPayload item; if (lua_type(L, 2) == LUA_TTABLE) { lua_getfield(L, 2, "jid"); const char* rawJID = lua_tostring(L, -1); if (rawJID) { item.setJID(std::string(rawJID)); } lua_getfield(L, 2, "name"); const char* rawName = lua_tostring(L, -1); if (rawName) { item.setName(rawName); } lua_getfield(L, 2, "groups"); if (!lua_isnil(L, -1)) { if (lua_type(L, -1) == LUA_TTABLE) { for (size_t i = 1; i <= lua_objlen(L, -1); ++i) { lua_rawgeti(L, -1, boost::numeric_cast(i)); const char* rawGroup = lua_tostring(L, -1); if (rawGroup) { item.addGroup(rawGroup); } lua_pop(L, 1); } } else { throw Lua::Exception("Groups should be a table"); } } } else { item.setJID(Lua::checkString(L, 2)); } client->getRoster(); if (!client->getClient()->getRoster()->containsJID(item.getJID())) { RosterPayload::ref roster = boost::make_shared(); roster->addItem(item); Sluift::Response response = client->sendVoidRequest( SetRosterRequest::create(roster, client->getClient()->getIQRouter()), -1); if (response.error) { return response.convertToLuaResult(L); } } client->getClient()->getSubscriptionManager()->requestSubscription(item.getJID()); lua_pushboolean(L, true); return 1; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, remove_contact, "Remove a contact from the contact list.", "self\n" "jid the JID of the contact to remove\n", "" ) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); JID jid(Lua::checkString(L, 2)); RosterPayload::ref roster = boost::make_shared(); roster->addItem(RosterItemPayload(JID(Lua::checkString(L, 2)), "", RosterItemPayload::Remove)); return client->sendVoidRequest( SetRosterRequest::create(roster, client->getClient()->getIQRouter()), -1).convertToLuaResult(L); } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, confirm_subscription, "Confirm subscription of a contact.", "self\n" "jid the JID of the contact to confirm the subscription of\n", "" ) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); JID jid(Lua::checkString(L, 2)); client->getClient()->getSubscriptionManager()->confirmSubscription(jid); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, cancel_subscription, "Cancel the subscription of a contact.", "self\n" "jid the JID of the contact to cancel the subscription of\n", "" ) { Sluift::globals.eventLoop.runOnce(); SluiftClient* client = getClient(L); JID jid(Lua::checkString(L, 2)); client->getClient()->getSubscriptionManager()->cancelSubscription(jid); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, set_disco_info, "Sets the service discovery information for this client", "self\n" "disco_info A structured representation of the service discovery information\n", "" ) { SluiftClient* client = getClient(L); if (!lua_istable(L, 2)) { throw Lua::Exception("Missing disco info"); } if (boost::shared_ptr discoInfo = boost::dynamic_pointer_cast(Sluift::globals.elementConvertor.convertFromLuaUntyped(L, 2, "disco_info"))) { client->getClient()->getDiscoManager()->setDiscoInfo(*discoInfo); } else { throw Lua::Exception("Illegal disco info"); } return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, set_caps_node, "Sets the caps node of this client", "self\n" "node The caps node (e.g. 'http://swift.im/sluift')\n", "" ) { SluiftClient* client = getClient(L); std::string node(Lua::checkString(L, 2)); client->getClient()->getDiscoManager()->setCapsNode(Lua::checkString(L, 2)); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, jid, "Returns the JID of this client", "self\n", "" ) { SluiftClient* client = getClient(L); lua_pushstring(L, client->getClient()->getJID().toString().c_str()); return 1; } SLUIFT_LUA_FUNCTION(Client, __gc) { SluiftClient* client = getClient(L); delete client; return 0; }