summaryrefslogtreecommitdiffstats
path: root/Sluift
diff options
context:
space:
mode:
Diffstat (limited to 'Sluift')
-rw-r--r--Sluift/ElementConvertors/MAMQueryConvertor.cpp2
-rw-r--r--Sluift/Examples/MAMRSM.lua23
-rw-r--r--Sluift/Examples/MAMRSMPage.lua36
-rw-r--r--Sluift/Examples/MAMSimple.lua19
-rw-r--r--Sluift/Examples/MAMSupportedFields.lua18
-rw-r--r--Sluift/Examples/MAMUser.lua25
-rw-r--r--Sluift/client.cpp29
-rw-r--r--Sluift/core.lua6
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
}