diff options
| author | Richard Maudsley <richard.maudsley@isode.com> | 2014-05-13 14:48:10 (GMT) |
|---|---|---|
| committer | Swift Review <review@swift.im> | 2014-05-27 19:53:49 (GMT) |
| commit | 058719296f496b14b906fdee3d74d72a78d9f6a2 (patch) | |
| tree | e15b5e0032b48c924f7f69e20e0b721172a5691d | |
| parent | e5975a6d4809bf05f8c9df724c926bd26fc4a9df (diff) | |
| download | swift-contrib-058719296f496b14b906fdee3d74d72a78d9f6a2.zip swift-contrib-058719296f496b14b906fdee3d74d72a78d9f6a2.tar.bz2 | |
Added Sluift MAM examples. send_mam_query becomes set_mam_query.
Change-Id: I5d81e2476c83a16a8e478656d11d91137b009f3a
| -rw-r--r-- | Sluift/ElementConvertors/MAMQueryConvertor.cpp | 2 | ||||
| -rw-r--r-- | Sluift/Examples/MAMRSM.lua | 23 | ||||
| -rw-r--r-- | Sluift/Examples/MAMRSMPage.lua | 36 | ||||
| -rw-r--r-- | Sluift/Examples/MAMSimple.lua | 19 | ||||
| -rw-r--r-- | Sluift/Examples/MAMSupportedFields.lua | 18 | ||||
| -rw-r--r-- | Sluift/Examples/MAMUser.lua | 25 | ||||
| -rw-r--r-- | Sluift/client.cpp | 29 | ||||
| -rw-r--r-- | Sluift/core.lua | 6 |
8 files changed, 126 insertions, 32 deletions
diff --git a/Sluift/ElementConvertors/MAMQueryConvertor.cpp b/Sluift/ElementConvertors/MAMQueryConvertor.cpp index 7d7224e..cf4f787 100644 --- a/Sluift/ElementConvertors/MAMQueryConvertor.cpp +++ b/Sluift/ElementConvertors/MAMQueryConvertor.cpp @@ -1,75 +1,75 @@ /* * Copyright (c) 2014 Kevin Smith and Remko Tronçon * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #include <boost/numeric/conversion/cast.hpp> #include <boost/smart_ptr/make_shared.hpp> #include <lua.hpp> #include <Sluift/ElementConvertors/MAMQueryConvertor.h> #include <Sluift/LuaElementConvertors.h> #include <Swiften/Elements/Form.h> #include <Swiften/Elements/ResultSet.h> #pragma clang diagnostic ignored "-Wunused-private-field" using namespace Swift; MAMQueryConvertor::MAMQueryConvertor(LuaElementConvertors* convertors) : - GenericLuaElementConvertor<MAMQuery>("mam_query"), + GenericLuaElementConvertor<MAMQuery>("mam"), convertors(convertors) { } MAMQueryConvertor::~MAMQueryConvertor() { } boost::shared_ptr<MAMQuery> MAMQueryConvertor::doConvertFromLua(lua_State* L) { boost::shared_ptr<MAMQuery> result = boost::make_shared<MAMQuery>(); lua_getfield(L, -1, "query_id"); if (lua_isstring(L, -1)) { result->setQueryID(std::string(lua_tostring(L, -1))); } lua_pop(L, 1); lua_getfield(L, -1, "form"); if (!lua_isnil(L, -1)) { boost::shared_ptr<Form> form = boost::dynamic_pointer_cast<Form>(convertors->convertFromLuaUntyped(L, -1, "form")); if (!!form) { result->setForm(form); } } lua_pop(L, 1); lua_getfield(L, -1, "result_set"); if (!lua_isnil(L, -1)) { boost::shared_ptr<ResultSet> resultSet = boost::dynamic_pointer_cast<ResultSet>(convertors->convertFromLuaUntyped(L, -1, "result_set")); if (!!resultSet) { result->setResultSet(resultSet); } } lua_pop(L, 1); return result; } void MAMQueryConvertor::doConvertToLua(lua_State* L, boost::shared_ptr<MAMQuery> payload) { lua_createtable(L, 0, 0); if (payload->getQueryID()) { lua_pushstring(L, (*payload->getQueryID()).c_str()); lua_setfield(L, -2, "query_id"); } if (convertors->convertToLuaUntyped(L, payload->getForm()) > 0) { lua_setfield(L, -2, "form"); } if (convertors->convertToLuaUntyped(L, payload->getResultSet()) > 0) { lua_setfield(L, -2, "result_set"); } } boost::optional<LuaElementConvertor::Documentation> MAMQueryConvertor::getDocumentation() const { return Documentation( "MAMQuery", "This table has the following fields:\n\n" "- `query_id`: string (Optional)\n" "- `form`: string @{Form} (Optional)\n" "- `result_set`: @{ResultSet} (Optional)\n" ); } diff --git a/Sluift/Examples/MAMRSM.lua b/Sluift/Examples/MAMRSM.lua new file mode 100644 index 0000000..c8a1e85 --- /dev/null +++ b/Sluift/Examples/MAMRSM.lua @@ -0,0 +1,23 @@ +-- A query using Result Set Management +-- Usage: ./sluift MAMRSM.lua <jid> <password> <query_dest> <max_results> + +sluift.debug = true + +c = sluift.new_client(arg[1], arg[2]) + +c:connect(); + +query = { + result_set={max_items=arg[4]} +} + +c:set_mam{mam=query, to=arg[3]} + +c:for_each_message(function(e) + if e.payloads[1].tag == 'fin' then return true end + if e.payloads[1]._type == 'mam_result' then + print(e.payloads[1].payload.stanza.payloads[1].text) + end +end) + +c:disconnect() diff --git a/Sluift/Examples/MAMRSMPage.lua b/Sluift/Examples/MAMRSMPage.lua new file mode 100644 index 0000000..cb3307c --- /dev/null +++ b/Sluift/Examples/MAMRSMPage.lua @@ -0,0 +1,36 @@ +-- A page query using Result Set Management +-- Usage: ./sluift MAMRSMPage.lua <jid> <password> <query_dest> <pages> + +sluift.debug = true + +c = sluift.new_client(arg[1], arg[2]) + +c:set_options{compress = false, tls = false} + +c:connect(); + +query = { + result_set={max_items=5} +} + +done = false +page = 0 +while not done and page < tonumber(arg[4]) do + page = page + 1 + c:set_mam{mam=query, to=arg[3]} + c:for_each_message(function(e) + if e.payloads[1].tag == 'fin' then + if e.payloads[2].last_id then + query.result_set.after = e.payloads[2].last_id + else + done = true + end + return true + end + if e.payloads[1]._type == 'mam_result' then + print(e.payloads[1].payload.stanza.payloads[1].text) + end + end) +end + +c:disconnect() diff --git a/Sluift/Examples/MAMSimple.lua b/Sluift/Examples/MAMSimple.lua new file mode 100644 index 0000000..13ab1a0 --- /dev/null +++ b/Sluift/Examples/MAMSimple.lua @@ -0,0 +1,19 @@ +-- Querying the archive for messages +-- Usage: ./sluift MAMSimple.lua <jid> <password> <query_dest> + +sluift.debug = true + +c = sluift.new_client(arg[1], arg[2]) + +c:connect(); + +c:set_mam{mam={}, to=arg[3]} + +c:for_each_message(function(e) + if e.payloads[1].tag == 'fin' then return true end + if e.payloads[1]._type == 'mam_result' then + print(e.payloads[1].payload.stanza.payloads[1].text) + end +end) + +c:disconnect() diff --git a/Sluift/Examples/MAMSupportedFields.lua b/Sluift/Examples/MAMSupportedFields.lua new file mode 100644 index 0000000..0417924 --- /dev/null +++ b/Sluift/Examples/MAMSupportedFields.lua @@ -0,0 +1,18 @@ +-- Retrieving form fields +-- Usage: ./sluift MAMSupportedFields.lua <jid> <password> + +sluift.debug = true + +c = sluift.new_client(arg[1], arg[2]) + +c:connect(); + +mam_result = c:get_mam{} + +for i=1,#mam_result.form.fields do + if mam_result.form.fields[i].type ~= "hidden" then + print("Server supports: " .. mam_result.form.fields[i].name) + end +end + +c:disconnect() diff --git a/Sluift/Examples/MAMUser.lua b/Sluift/Examples/MAMUser.lua new file mode 100644 index 0000000..e4a7c28 --- /dev/null +++ b/Sluift/Examples/MAMUser.lua @@ -0,0 +1,25 @@ +-- Querying for all messages to/from a particular JID +-- Usage: ./sluift MAMUser.lua <jid> <password> <query_dest> <query_jid> + +sluift.debug = true + +c = sluift.new_client(arg[1], arg[2]) + +c:connect(); + +fields = { + with = arg[4] +} + +query_form = sluift.create_form{fields, form_type="urn:xmpp:mam:0"} + +c:set_mam{mam={form=query_form}, to=arg[3]} + +c:for_each_message(function(e) + if e.payloads[1].tag == 'fin' then return true end + if e.payloads[1]._type == 'mam_result' then + print(e.payloads[1].payload.stanza.payloads[1].text) + end +end) + +c:disconnect() diff --git a/Sluift/client.cpp b/Sluift/client.cpp index 8dcd9ae..4b065ab 100644 --- a/Sluift/client.cpp +++ b/Sluift/client.cpp @@ -1,746 +1,719 @@ /* - * Copyright (c) 2013 Remko Tronçon + * Copyright (c) 2013-2014 Remko Tronçon * Licensed under the GNU General Public License. * See the COPYING file for more information. */ #include <boost/lambda/lambda.hpp> #include <boost/lambda/bind.hpp> #include <boost/assign/list_of.hpp> #include <iostream> #include <Sluift/SluiftClient.h> #include <Swiften/JID/JID.h> #include <Swiften/Elements/SoftwareVersion.h> #include <Swiften/Elements/Message.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Elements/RawXMLPayload.h> #include <Swiften/Elements/RosterItemPayload.h> #include <Swiften/Elements/RosterPayload.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/MAMQuery.h> #include <Swiften/Disco/ClientDiscoManager.h> #include <Swiften/Queries/GenericRequest.h> #include <Swiften/Presence/PresenceSender.h> #include <Swiften/Roster/XMPPRoster.h> #include <Swiften/Roster/SetRosterRequest.h> #include <Swiften/Presence/SubscriptionManager.h> #include <Swiften/Roster/XMPPRosterItem.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Queries/Requests/GetSoftwareVersionRequest.h> #include <Sluift/Lua/FunctionRegistration.h> #include <Swiften/Base/foreach.h> #include <Swiften/Base/IDGenerator.h> #include <Sluift/Lua/Check.h> #include <Sluift/Lua/Value.h> #include <Sluift/Lua/Exception.h> #include <Sluift/Lua/LuaUtils.h> #include <Sluift/globals.h> #include <Sluift/ElementConvertors/StanzaConvertor.h> #include <Sluift/ElementConvertors/IQConvertor.h> #include <Sluift/ElementConvertors/PresenceConvertor.h> #include <Sluift/ElementConvertors/MessageConvertor.h> using namespace Swift; namespace lambda = boost::lambda; static inline SluiftClient* getClient(lua_State* L) { return *Lua::checkUserData<SluiftClient>(L, 1); } static inline int getGlobalTimeout(lua_State* L) { lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.moduleLibIndex); lua_getfield(L, -1, "timeout"); int result = boost::numeric_cast<int>(lua_tointeger(L, -1)); lua_pop(L, 2); return result; } static void addPayloadsToTable(lua_State* L, const std::vector<boost::shared_ptr<Payload> >& payloads) { if (!payloads.empty()) { lua_createtable(L, boost::numeric_cast<int>(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<int>(i+1)); } Lua::registerGetByTypeIndex(L, -1); lua_setfield(L, -2, "payloads"); } } static boost::shared_ptr<Payload> getPayload(lua_State* L, int index) { if (lua_type(L, index) == LUA_TTABLE) { return boost::dynamic_pointer_cast<Payload>(Sluift::globals.elementConvertor.convertFromLua(L, index)); } else if (lua_type(L, index) == LUA_TSTRING) { return boost::make_shared<RawXMLPayload>(Lua::checkString(L, index)); } else { return boost::shared_ptr<Payload>(); } } static std::vector< boost::shared_ptr<Payload> > getPayloadsFromTable(lua_State* L, int index) { index = Lua::absoluteOffset(L, index); std::vector< boost::shared_ptr<Payload> > result; lua_getfield(L, index, "payloads"); if (lua_istable(L, -1)) { for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { boost::shared_ptr<Payload> payload = getPayload(L, -1); if (payload) { result.push_back(payload); } } } lua_pop(L, 1); return result; } 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<std::string> hostString = Lua::getStringField(L, 2, "host")) { host = *hostString; } if (boost::optional<int> portInt = Lua::getIntField(L, 2, "port")) { port = *portInt; } } client->connect(host, port); return 0; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, set_trace_enabled, "Enable/disable tracing of the data sent/received.\n\n.", "self\n" "enable a boolean specifying whether to enable/disable tracing", "" ) { getClient(L)->setTraceEnabled(lua_toboolean(L, 1)); 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(getGlobalTimeout(L)); 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", "" ) { Sluift::globals.eventLoop.runOnce(); 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<SoftwareVersion> version = boost::dynamic_pointer_cast<SoftwareVersion>(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<Lua::Value>(item.getJID().toString())) ("name", boost::make_shared<Lua::Value>(item.getName())) ("subscription", boost::make_shared<Lua::Value>(subscription)) ("groups", boost::make_shared<Lua::Value>(std::vector<Lua::Value>(item.getGroups().begin(), item.getGroups().end()))); contactsTable[item.getJID().toString()] = boost::make_shared<Lua::Value>(itemTable); } pushValue(L, contactsTable); Lua::registerTableToString(L, -1); return 1; } SLUIFT_LUA_FUNCTION_WITH_HELP( Client, send_message, "Send a message.", "self\n" "to the JID to send the message to\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" "subject the subject of the MUC room to set\n" "type the type of message to send (`normal`, `chat`, `error`, `groupchat`, `headline`)\n" "payloads payloads to add to the message\n" ) { Sluift::globals.eventLoop.runOnce(); JID to; boost::optional<std::string> body; boost::optional<std::string> subject; std::vector<boost::shared_ptr<Payload> > payloads; 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)) { body = lua_tostring(L, index); ++index; } } if (lua_istable(L, index)) { if (boost::optional<std::string> value = Lua::getStringField(L, index, "to")) { to = *value; } if (boost::optional<std::string> value = Lua::getStringField(L, index, "body")) { body = value; } if (boost::optional<std::string> value = Lua::getStringField(L, index, "type")) { type = MessageConvertor::convertMessageTypeFromString(*value); } if (boost::optional<std::string> value = Lua::getStringField(L, index, "subject")) { subject = value; } payloads = getPayloadsFromTable(L, index); } if (!to.isValid()) { throw Lua::Exception("Missing 'to'"); } if ((!body || body->empty()) && !subject && payloads.empty()) { throw Lua::Exception("Missing any of 'body', 'subject' or 'payloads'"); } Message::ref message = boost::make_shared<Message>(); message->setTo(to); if (body && !body->empty()) { message->setBody(*body); } if (subject) { message->setSubject(*subject); } message->addPayloads(payloads.begin(), payloads.end()); 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" "payloads payloads to add to the presence\n" ) { Sluift::globals.eventLoop.runOnce(); boost::shared_ptr<Presence> presence = boost::make_shared<Presence>(); int index = 2; if (lua_isstring(L, index)) { presence->setStatus(lua_tostring(L, index)); ++index; } if (lua_istable(L, index)) { if (boost::optional<std::string> value = Lua::getStringField(L, index, "to")) { presence->setTo(*value); } if (boost::optional<std::string> value = Lua::getStringField(L, index, "status")) { presence->setStatus(*value); } if (boost::optional<int> value = Lua::getIntField(L, index, "priority")) { presence->setPriority(*value); } if (boost::optional<std::string> value = Lua::getStringField(L, index, "type")) { presence->setType(PresenceConvertor::convertPresenceTypeFromString(*value)); } std::vector< boost::shared_ptr<Payload> > payloads = getPayloadsFromTable(L, index); presence->addPayloads(payloads.begin(), payloads.end()); } 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<std::string> toString = Lua::getStringField(L, 2, "to")) { to = JID(*toString); } int timeout = getGlobalTimeout(L); if (boost::optional<int> timeoutInt = Lua::getIntField(L, 2, "timeout")) { timeout = *timeoutInt; } boost::shared_ptr<Payload> payload; lua_getfield(L, 2, "query"); payload = getPayload(L, -1); lua_pop(L, 1); return client->sendRequest( boost::make_shared< GenericRequest<Payload> >(type, to, payload, client->getClient()->getIQRouter()), timeout).convertToLuaResult(L); } #define DISPATCH_PUBSUB_PAYLOAD(payloadType, container, response) \ else if (boost::shared_ptr<payloadType> p = boost::dynamic_pointer_cast<payloadType>(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<std::string> toString = Lua::getStringField(L, 2, "to")) { to = JID(*toString); } int timeout = getGlobalTimeout(L); if (boost::optional<int> timeoutInt = Lua::getIntField(L, 2, "timeout")) { timeout = *timeoutInt; } IQ::Type type; if (boost::optional<std::string> queryType = Lua::getStringField(L, 2, "type")) { type = IQConvertor::convertIQTypeFromString(*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> payload = getPayload(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<int>(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; } -SLUIFT_LUA_FUNCTION_WITH_HELP( - Client, send_mam_query, - - "Builds and sends a MAM query.\n", - - "self\n" - "mam_query parameters for the query\n" - "jid optional jid to set in the 'to' field of the IQ stanza", - - "See help('MAMQuery') for details." -) { - Lua::checkType(L, 2, LUA_TTABLE); - boost::shared_ptr<MAMQuery> mamQuery = boost::make_shared<MAMQuery>(); - lua_getfield(L, 2, "mam_query"); - if (lua_istable(L, -1)) { - mamQuery = boost::dynamic_pointer_cast<MAMQuery>(Sluift::globals.elementConvertor.convertFromLuaUntyped(L, -1, "mam_query")); - } - JID jid; - lua_getfield(L, 2, "jid"); - if (!lua_isnil(L, -1)) { - jid = JID(lua_tostring(L, -1)); - } - IQRouter *router = getClient(L)->getClient()->getIQRouter(); - router->sendIQ(IQ::createRequest(IQ::Set, jid, IDGenerator().generateID(), mamQuery)); - return 0; -} - static void pushEvent(lua_State* L, const SluiftClient::Event& event) { switch (event.type) { case SluiftClient::Event::MessageType: { Message::ref message = boost::dynamic_pointer_cast<Message>(event.stanza); Lua::Table result = boost::assign::map_list_of ("type", boost::make_shared<Lua::Value>(std::string("message"))) ("from", boost::make_shared<Lua::Value>(message->getFrom().toString())) ("body", boost::make_shared<Lua::Value>(message->getBody())) ("message_type", boost::make_shared<Lua::Value>(MessageConvertor::convertMessageTypeToString(message->getType()))); Lua::pushValue(L, result); addPayloadsToTable(L, message->getPayloads()); Lua::registerTableToString(L, -1); break; } case SluiftClient::Event::PresenceType: { Presence::ref presence = boost::dynamic_pointer_cast<Presence>(event.stanza); Lua::Table result = boost::assign::map_list_of ("type", boost::make_shared<Lua::Value>(std::string("presence"))) ("from", boost::make_shared<Lua::Value>(presence->getFrom().toString())) ("status", boost::make_shared<Lua::Value>(presence->getStatus())) ("presence_type", boost::make_shared<Lua::Value>(PresenceConvertor::convertPresenceTypeToString(presence->getType()))); Lua::pushValue(L, result); addPayloadsToTable(L, presence->getPayloads()); 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 = getGlobalTimeout(L); boost::optional<SluiftClient::Event::Type> type; int condition = 0; if (lua_istable(L, 2)) { if (boost::optional<std::string> 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<int> 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<SluiftClient::Event> 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<int>(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<RosterPayload>(); 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<RosterPayload>(); 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> discoInfo = boost::dynamic_pointer_cast<DiscoInfo>(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; } diff --git a/Sluift/core.lua b/Sluift/core.lua index dfac21a..7487de1 100644 --- a/Sluift/core.lua +++ b/Sluift/core.lua @@ -1,1041 +1,1041 @@ --[[ - Copyright (c) 2013 Remko Tronçon + Copyright (c) 2013-2014 Remko Tronçon Licensed under the GNU General Public License. See the COPYING file for more information. --]] local sluift = select(1, ...) local _G = _G local pairs, ipairs, print, tostring, type, error, assert, next, rawset, xpcall, unpack, io = pairs, ipairs, print, tostring, type, error, assert, next, rawset, xpcall, unpack, io local setmetatable, getmetatable = setmetatable, getmetatable local string = require "string" local table = require "table" local debug = require "debug" _ENV = nil -------------------------------------------------------------------------------- -- Table utility methods -------------------------------------------------------------------------------- local function table_value_tostring(value) local result = tostring(value) if type(value) == 'number' then return result elseif type(value) == 'boolean' then return result elseif type(value) == 'string' then return "'" .. result .. "'" else return '<' .. result .. '>' end end local function table_tostring(table, print_functions, indent, accumulator, history) local INDENT = ' ' local accumulator = accumulator or '' local history = history or {} local indent = indent or '' accumulator = accumulator .. '{' history[table] = true local is_first = true for key, value in pairs(table) do if print_functions or type(value) ~= 'function' then if not is_first then accumulator = accumulator .. ',' end is_first = false accumulator = accumulator .. '\n' .. indent .. INDENT .. '[' .. table_value_tostring(key) .. '] = ' if type(value) == 'table' then if history[value] then accumulator = accumulator .. "..." else accumulator = table_tostring(value, print_functions, indent .. INDENT, accumulator, history) end else accumulator = accumulator .. table_value_tostring(value) end end end history[table] = false if not is_first then accumulator = accumulator .. '\n' .. indent end accumulator = accumulator .. '}' return accumulator end local function register_table_tostring(table, print_functions) if type(table) == 'table' then local metatable = getmetatable(table) if not metatable then metatable = {} setmetatable(table, metatable) end if print_functions then metatable.__tostring = function(table) return table_tostring(table, true) end else metatable.__tostring = table_tostring end end end -- FIXME: Not really a good or efficiant equals, but does the trick for now local function table_equals(t1, t2) return tostring(t1) == tostring(t2) end local function register_table_equals(table) if type(table) == 'table' then local metatable = getmetatable(table) if not metatable then metatable = {} setmetatable(table, metatable) end metatable.__eq = table_equals end end local function merge_tables(...) local result = {} for _, table in ipairs({...}) do for k, v in pairs(table) do result[k] = v end end return result end local function copy(object) if type(object) == 'table' then local copy = {} for key, value in pairs(object) do copy[key] = value end return copy else return object end end local function clear(table) setmetatable(table, nil) for key, value in pairs(table) do rawset(table, key, nil) end end local function trim(string) return string:gsub("^%s*(.-)%s*$", "%1") end local function keys(table) local result = {} for key in pairs(table) do result[#result+1] = key end return result end local function insert_all(table, values) for _, value in pairs(values) do table[#table+1] = value end end -------------------------------------------------------------------------------- -- Help -------------------------------------------------------------------------------- -- Contains help for native methods that we want access to from here local extra_help = {} local help_data = {} local help_classes = {} local help_class_metatables = {} local _H local function get_synopsis(description) return description:gsub("[\n\r].*", "") end local function format_description(text) local result = {} local trim_whitespace for line in (text .. "\n"):gmatch"(.-)\n" do if not trim_whitespace and line:find('[^%s]') then trim_whitespace = line:match("^(%s*)") end if trim_whitespace then line = line:gsub("^" .. trim_whitespace, "") end table.insert(result, line) end return trim(table.concat(result, "\n")) end local function strip_links(text) return text:gsub("(@{(%w*)})", "`%2`") end local function register_help(target, help) assert(target) if not help then help = _H end assert(help) -- Transform description into canonical representation local parameters = {} for _, parameter in pairs(help.parameters or {}) do local parameter_description = parameter[2] if parameter_description and #parameter_description == 0 then parameter_description = nil end if type(parameter) == "table" then parameters[#parameters+1] = { name = parameter[1], description = parameter_description } else parameters[#parameters+1] = { name = parameter } end end local options = {} for option_name, option_description in pairs(help.options or {}) do if type(option_description) == "table" then options[#options+1] = { name = option_description.name, description = option_description.description } else options[#options+1] = { name = option_name, description = option_description } end end local description = format_description(help[1] or help.description or "") local synopsis = get_synopsis(description) if #description == 0 then synopsis = nil description = nil end local data = { description = description, synopsis = synopsis, parameters = parameters, options = options, classes = help.classes } register_table_tostring(data, true) help_data[target] = data end local function register_class_help(class, help) help_classes[#help_classes+1] = class register_help(class, help) end local function register_class_table_help(target, class, help) register_help(target, help) help_class_metatables[class] = target register_class_help(class, help) end _H = { [[ Retrieves the help information from `target`. Returns a table with the following fields: - `description`: the description of `target` - `parameters`: an array of parameters of `target` represented as tables with `name` and `description` fields. - `options`: an array of options (named parameters) of `target` represented as tables with `name` and `description` fields. - `methods`: an array of methods - `fields`: an array of fields ]], parameters = { {"target", "The target to retrieve help of"} } } local function get_help(target) if not target then error("Nil argument or argument missing") end local help = help_data[target] or help_data[getmetatable(target)] or {} -- Collect child methods and fields local children = {} if type(target) == "table" then children = target end local mt if type(target) == "string" then mt = help_class_metatables[target] else mt = getmetatable(target) end if mt and type(mt.__index) == "table" then children = merge_tables(children, mt.__index) end local methods = {} local fields = {} for name, value in pairs(children) do if name:sub(1, 1) ~= "_" then if type(value) == "function" then methods[#methods+1] = { name = name, ref = value } else fields[#fields+1] = { name = name, description = nil } end end end if next(methods) ~= nil then help.methods = methods end if next(fields) ~= nil then help.fields = fields end if next(help) then return help else return nil end end register_help(get_help) _H = { [[ Prints the help of `target`. `target` can be any object. When `target` is a string, prints the help of the class with the given name. ]], parameters = { {"target", "The target to retrieve help of"} } } local function help(target) print() if not target then print("Call `help(target)` to get the help of a specific `target`.") print("`target` can be any object. When `target` is a string, prints") print("the help of the class with the given name.") print() print("For general information about sluift, type:") print(" help(sluift)") print() return end local data = get_help(target) if not data then print("No help available\n") return end -- Collect help of children local methods = {} for _, method in pairs(data.methods or {}) do local description local method_help = get_help(method.ref) if method_help and method_help.description then description = method_help.synopsis end methods[#methods+1] = { name = method.name, description = description } end local fields = copy(data.fields or {}) table.sort(methods, function (a, b) return (a.name or "") < (b.name or "") end) table.sort(fields, function (a, b) return (a.name or "") < (b.name or "") end) local classes = {} for _, class in pairs(data.classes or {}) do classes[#classes+1] = { name = class, description = get_help(class).synopsis } end print(strip_links(data.description) or "(No description available)") for _, p in ipairs({ {"Parameters", data.parameters}, {"Options", data.options}, {"Methods", methods}, {"Fields", fields}, {"Classes", classes}}) do if p[2] and next(p[2]) ~= nil then print() print(p[1] .. ":") for _, parameter in ipairs(p[2]) do if parameter.description then print(" " .. parameter.name .. ": " .. strip_links(parameter.description)) else print(" " .. parameter.name) end end end end print() end register_help(help) -------------------------------------------------------------------------------- -- Utility methods -------------------------------------------------------------------------------- _H = { [[ Perform a shallow copy of `object`. ]], parameters = {{"object", "the object to copy"}} } register_help(copy) _H = { [[ Pretty-print a table ]], parameters = {{"table", "the table to print"}} } local function tprint(table) print(table_tostring(table, true)) end register_help(tprint) local function remove_help_parameters(elements, table) if type(elements) ~= "table" then elements = {elements} end local result = copy(table) for k, v in ipairs(table) do for _, element in ipairs(elements) do if v.name == element then result[k] = nil end end end return result end local function parse_options(unnamed_parameters, arg1, arg2) local options = {} local f if type(arg1) == 'table' then options = arg1 f = arg2 elseif type(arg1) == 'function' then f = arg1 end options.f = f or options.f return copy(options) end local function get_by_type(table, typ) for _, v in ipairs(table) do if v['_type'] == typ then return v end end end local function register_get_by_type_index(table) if type(table) == 'table' then local metatable = getmetatable(table) if not metatable then metatable = {} setmetatable(table, metatable) end metatable.__index = get_by_type end return table end local function call(options) local f = options[1] local result = { xpcall(f, debug.traceback) } if options.finally then options.finally() end if result[1] then table.remove(result, 1) return unpack(result) else error(result[2]) end end local function read_file(file) local f = io.open(file, 'rb') local result = f:read('*all') f:close() return result end _H = { [[ Generate a form table, suitable for PubSubConfiguration and MAMQuery ]], parameters = { {"fields", "The fields that will be converted into a form table"}, {"form_type", "If specified, add a form_type field with this value"}, {"type", "Form type, e.g. 'submit'"} } } local function create_form(...) local options = parse_options({}, ...) local result = { fields = {} } -- FIXME: make nicer when parse_options binds positional arguments to names if options.fields then for var, value in pairs(options.fields) do result.fields[#result.fields+1] = { name = var, value = value } end elseif options[1] then for var, value in pairs(options[1]) do result.fields[#result.fields+1] = { name = var, value = value } end end if options.form_type then result.fields[#result.fields+1] = { name = 'FORM_TYPE', value = options.form_type } end result['type'] = options.type return result end -------------------------------------------------------------------------------- -- Metatables -------------------------------------------------------------------------------- _H = { [[ Client interface ]] } local Client = { _with_prompt = function(client) return client:jid() end } Client.__index = Client register_class_table_help(Client, "Client") _H = { [[ Interface to communicate with a PubSub service ]] } local PubSub = {} PubSub.__index = PubSub register_class_table_help(PubSub, "PubSub") _H = { [[ Interface to communicate with a PubSub node on a service ]] } local PubSubNode = {} PubSubNode.__index = PubSubNode register_class_table_help(PubSubNode, "PubSubNode") -------------------------------------------------------------------------------- -- with -------------------------------------------------------------------------------- local original_G local function with (target, f) -- Dynamic scope if f then with(target) return call{f, finally = function() with() end} end -- No scope if target then if not original_G then original_G = copy(_G) setmetatable(original_G, getmetatable(_G)) clear(_G) end setmetatable(_G, { __index = function(_, key) local value = target[key] if value then if type(value) == 'function' then -- Add 'self' argument to all functions return function(...) return value(target, ...) end else return value end else return original_G[key] end end, __newindex = original_G, _completions = function () local result = {} if type(target) == "table" then insert_all(result, keys(target)) end local mt = getmetatable(target) if mt and type(mt.__index) == 'table' then insert_all(result, keys(mt.__index)) end insert_all(result, keys(original_G)) return result end }) -- Set prompt local prompt = nil -- Try '_with_prompt' in metatable local target_metatable = getmetatable(target) if target_metatable then if type(target_metatable._with_prompt) == "function" then prompt = target_metatable._with_prompt(target) else prompt = target_metatable._with_prompt end end if not prompt then -- Use tostring() local target_string = tostring(target) if string.len(target_string) > 25 then prompt = string.sub(target_string, 0, 22) .. "..." else prompt = target_string end end rawset(_G, "_PROMPT", prompt .. '> ') else -- Reset _G clear(_G) for key, value in pairs(original_G) do _G[key] = value end setmetatable(_G, original_G) end end -------------------------------------------------------------------------------- -- Client -------------------------------------------------------------------------------- extra_help = { ["Client.get_next_event"] = { [[ Returns the next event. ]], parameters = { "self" }, options = { type = "The type of event to return (`message`, `presence`, `pubsub`). When omitted, all event types are returned.", timeout = "The amount of time to wait for events.", ["if"] = "A function to filter events. When this function, called with the event as a parameter, returns true, the event will be returned" } }, ["Client.get"] = { [[ Sends a `get` query. ]], parameters = { "self" }, options = { to = "The JID of the target to send the query to", query = "The query to send", timeout = "The amount of time to wait for the query to finish", } }, ["Client.set"] = { [[ Sends a `set` query. ]], parameters = { "self" }, options = { to = "The JID of the target to send the query to", query = "The query to send.", timeout = "The amount of time to wait for the query to finish.", } }, ["Client.async_connect"] = { [[ Connect to the server asynchronously. This method immediately returns. ]], parameters = { "self" }, options = { host = "The host to connect to. When omitted, is determined by resolving the client JID.", port = "The port to connect to. When omitted, is determined by resolving the client JID." } } } _H = { [[ Connect to the server. This method blocks until the connection has been established. ]], parameters = { "self" }, options = extra_help["Client.async_connect"].options } function Client:connect (...) local options = parse_options({}, ...) local f = options.f self:async_connect(options) self:wait_connected() if f then return call {function() return f(self) end, finally = function() self:disconnect() end} end return true end register_help(Client.connect) _H = { [[ Returns an iterator over all events. This function blocks until `timeout` is reached (or blocks forever if it is omitted). ]], parameters = { "self" }, options = extra_help["Client.get_next_event"].options } function Client:events (options) local function client_events_iterator(s) return s['client']:get_next_event(s['options']) end return client_events_iterator, {client = self, options = options} end register_help(Client.events) _H = { [[ Calls `f` for each event. ]], parameters = { "self" }, options = merge_tables(get_help(Client.events).options, { f = "The functor to call with each event. Required." }) } function Client:for_each_event (...) local options = parse_options({}, ...) if not type(options.f) == 'function' then error('Expected function') end for event in self:events(options) do local result = options.f(event) if result then return result end end end register_help(Client.for_each_event) for method, event_type in pairs({message = 'message', presence = 'presence', pubsub_event = 'pubsub'}) do _H = { "Call `f` for all events of type `" .. event_type .. "`.", parameters = { "self" }, options = remove_help_parameters("type", get_help(Client.for_each_event).options) } Client['for_each_' .. method] = function (client, ...) local options = parse_options({}, ...) options['type'] = event_type return client:for_each_event (options) end register_help(Client['for_each_' .. method]) _H = { "Get the next event of type `" .. event_type .. "`.", parameters = { "self" }, options = remove_help_parameters("type", extra_help["Client.get_next_event"].options) } Client['get_next_' .. method] = function (client, ...) local options = parse_options({}, ...) options['type'] = event_type return client:get_next_event(options) end register_help(Client['get_next_' .. method]) end for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'}) do _H = { "Returns an iterator over all events of type `" .. event_type .. "`.", parameters = { "self" }, options = remove_help_parameters("type", get_help(Client.for_each_event).options) } Client[method] = function (client, ...) local options = parse_options({}, ...) options['type'] = event_type return client:events (options) end register_help(Client[method]) end _H = { [[ Process all pending events ]], parameters = { "self" } } function Client:process_events () for event in self:events{timeout=0} do end end register_help(Client.process_events) -- -- Register get_* and set_* convenience methods for some type of queries -- -- Example usages: -- client:get_software_version{to = 'alice@wonderland.lit'} -- client:set_command{to = 'alice@wonderland.lit', command = { type = 'execute', node = 'uptime' }} -- local get_set_shortcuts = { - get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard'}, - set = {'command'} + get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard', 'mam'}, + set = {'command', 'mam'} } for query_action, query_types in pairs(get_set_shortcuts) do for _, query_type in ipairs(query_types) do _H = { "Sends a `" .. query_action .. "` query of type `" .. query_type .. "`.\n" .. "Apart from the options below, all top level elements of `" .. query_type .. "` can be passed.", parameters = { "self" }, options = remove_help_parameters({"query", "type"}, extra_help["Client.get"].options), } local method = query_action .. '_' .. query_type Client[method] = function (client, options) options = options or {} if type(options) ~= 'table' then error('Invalid options: ' .. options) end options['query'] = merge_tables({_type = query_type}, options[query_type] or {}) return client[query_action](client, options) end register_help(Client[method]) end end _H = { [[ Returns a @{PubSub} object for communicating with the PubSub service at `jid`. ]], parameters = { "self", {"jid", "The JID of the PubSub service"} } } function Client:pubsub (jid) local result = { client = self, jid = jid } setmetatable(result, PubSub) return result end register_help(Client.pubsub) -------------------------------------------------------------------------------- -- PubSub -------------------------------------------------------------------------------- local function process_pubsub_event (event) if event._type == 'pubsub_event_items' then -- Add 'item' shortcut to payload of first item event.item = event.items and event.items[1] and event.items[1].data and event.items[1].data[1] end end function PubSub:list_nodes (options) return self.client:get_disco_items(merge_tables({to = self.jid}, options)) end function PubSub:node (node) local result = { client = self.client, jid = self.jid, node = node } setmetatable(result, PubSubNode) return result end local simple_pubsub_queries = { get_default_configuration = 'pubsub_owner_default', get_subscriptions = 'pubsub_subscriptions', get_affiliations = 'pubsub_affiliations', get_default_subscription_options = 'pubsub_default', } for method, query_type in pairs(simple_pubsub_queries) do PubSub[method] = function (service, options) options = options or {} return service.client:query_pubsub(merge_tables( { type = 'get', to = service.jid, query = { _type = query_type } }, options)) end end for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do PubSub[method] = function (node, ...) local options = parse_options({}, ...) options['if'] = function (event) return event.type == 'pubsub' and event.from == node.jid and event.node == node end return node.client[method](node.client, options) end end -------------------------------------------------------------------------------- -- PubSubNode -------------------------------------------------------------------------------- local function pubsub_node_configuration_to_form(configuration) return create_form{configuration, form_type="http://jabber.org/protocol/pubsub#node_config", type="submit"} end function PubSubNode:list_items (options) return self.client:get_disco_items(merge_tables({to = self.jid, disco_items = { node = self.node }}, options)) end local simple_pubsub_node_queries = { get_configuration = 'pubsub_owner_configure', get_subscriptions = 'pubsub_subscriptions', get_affiliations = 'pubsub_affiliations', get_owner_subscriptions = 'pubsub_owner_subscriptions', get_owner_affiliations = 'pubsub_owner_affiliations', get_default_subscription_options = 'pubsub_default', } for method, query_type in pairs(simple_pubsub_node_queries) do PubSubNode[method] = function (node, options) return node.client:query_pubsub(merge_tables({ type = 'get', to = node.jid, query = { _type = query_type, node = node.node }}, options)) end end function PubSubNode:get_items (...) local options = parse_options({}, ...) local items = options.items or {} if options.maximum_items then items = merge_tables({maximum_items = options.maximum_items}, items) end items = merge_tables({_type = 'pubsub_items', node = self.node}, items) return self.client:query_pubsub(merge_tables({ type = 'get', to = self.jid, query = items}, options)) end function PubSubNode:get_item (...) local options = parse_options({}, ...) if not type(options.id) == 'string' then error('Expected ID') end return self:get_items{items = {{id = options.id}}} end function PubSubNode:create (options) options = options or {} local configure if options['configuration'] then configure = { data = pubsub_node_configuration_to_form(options['configuration']) } end return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_create', node = self.node, configure = configure } }, options)) end function PubSubNode:delete (options) options = options or {} local redirect if options['redirect'] then redirect = {uri = options['redirect']} end return self.client:query_pubsub(merge_tables({ type = 'set', to = self.jid, query = { _type = 'pubsub_owner_delete', node = self.node, redirect = redirect }}, options)) end function PubSubNode:set_configuration(options) options = options or {} local configuration = pubsub_node_configuration_to_form(options['configuration']) return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_owner_configure', node = self.node, data = configuration } }, options)) end function PubSubNode:set_owner_affiliations(...) local options = parse_options({}, ...) return self.client:query_pubsub(merge_tables({ type = 'set', to = self.jid, query = merge_tables({ _type = 'pubsub_owner_affiliations', node = self.node, }, options.affiliations)}, options)) end function PubSubNode:subscribe(...) local options = parse_options({}, ...) local jid = options.jid or sluift.jid.to_bare(self.client:jid()) return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_subscribe', node = self.node, jid = jid } }, options)) end function PubSubNode:unsubscribe(options) options = options or {} return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_unsubscribe', node = self.node, jid = options['jid'], subscription_id = 'subscription_id'} }, options)) end function PubSubNode:get_subscription_options (options) return self.client:query_pubsub(merge_tables( { type = 'get', to = self.jid, query = { _type = 'pubsub_options', node = self.node, jid = options['jid'] } }, options)) end function PubSubNode:publish(...) local options = parse_options({}, ...) local items = options.items or {} if options.item then if type(options.item) == 'string' or options.item._type then items = {{id = options.id, data = { options.item } }} options.id = nil else items = { options.item } end options.item = nil end return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_publish', node = self.node, items = items } }, options)) end function PubSubNode:retract(...) local options = parse_options({}, ...) local items = options.items if options.id then items = {{id = options.id}} end return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_retract', node = self.node, items = items, notify = options['notify'] }}, options)) end function PubSubNode:purge(...) local options = parse_options({}, ...) return self.client:query_pubsub(merge_tables( { type = 'set', to = self.jid, query = { _type = 'pubsub_owner_purge', node = self.node }}, options)) end -- Iterators over events for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do PubSubNode[method] = function (node, ...) local options = parse_options({}, ...) options['if'] = function (event) return event.type == 'pubsub' and event.from == node.jid and event.node == node.node end return node.client[method](node.client, options) end end -------------------------------------------------------------------------------- -- Service discovery -------------------------------------------------------------------------------- local disco = { features = { DISCO_INFO = 'http://jabber.org/protocol/disco#info', COMMANDS = 'http://jabber.org/protocol/commands', USER_LOCATION = 'http://jabber.org/protocol/geoloc', USER_TUNE = 'http://jabber.org/protocol/tune', USER_AVATAR_METADATA = 'urn:xmpp:avatar:metadata', USER_ACTIVITY = 'http://jabber.org/protocol/activity', USER_PROFILE = 'urn:xmpp:tmp:profile' } } -------------------------------------------------------------------------------- _H = nil extra_help['sluift'] = { [[ This module provides methods for XMPP communication. The main entry point of this module is the `new_client` method, which creates a new client for communicating with an XMPP server. ]], classes = help_classes } return { Client = Client, register_help = register_help, register_class_help = register_class_help, register_table_tostring = register_table_tostring, register_table_equals = register_table_equals, register_get_by_type_index = register_get_by_type_index, process_pubsub_event = process_pubsub_event, tprint = tprint, read_file = read_file, disco = disco, get_help = get_help, help = help, extra_help = extra_help, copy = copy, with = with, create_form = create_form } |
Swift