[ADD/MOD] Cleanup cmd, change Matrix user format

This commit is contained in:
LDA 2024-07-14 20:00:41 +02:00
commit 91d373addb
13 changed files with 217 additions and 77 deletions

View file

@ -35,13 +35,15 @@ TODO
TODO TODO
## TODOS ## TODOS
- Mess with Cytoplasm to make it have support for something like Berkeley DB as - Mess with Cytoplasm to make it have support for something like LMDB as an
an *optional* dependency. This should increase reliability and speed for anyone. *optional* dependency. This should increase reliability and speed for anyone.
- Nesting might be an issue we'll need to deal with. libdb and Berkley DB - Nesting might be an issue we'll need to deal with. libdb and Berkley DB
seem to lack support for them. If we can shove entries at specific indices, seem to lack support for them. If we can shove entries at specific indices,
we _might_ just manage to get some system that can at least emulate that, we _might_ just manage to get some system that can at least emulate that,
and hopefully be reasonably faster than the filesystem, with some added and hopefully be reasonably faster than the filesystem, with some added
reliability. reliability.
- Starting a DM from XMPP to a Matrix user(and also get rid of the '?'-syntax and
use another invalid Matrix char/valid XMPP char ('$'?) for escaped)
- PROPER FUCKING AVATARS - PROPER FUCKING AVATARS
XEP-0084 IS THE WORST PIECE OF SHIT KNOWN TO MAN. If any Jabberbros want to XEP-0084 IS THE WORST PIECE OF SHIT KNOWN TO MAN. If any Jabberbros want to

View file

@ -28,6 +28,9 @@ For future XEPs:
- https://xmpp.org/extensions/xep-0449.html - https://xmpp.org/extensions/xep-0449.html
Stickers are great. Matrix and XMPP somewhat has support for them, so Stickers are great. Matrix and XMPP somewhat has support for them, so
might be a nice-to-have, and also to push over XMPP support. might be a nice-to-have, and also to push over XMPP support.
A minor issue with that is pack management. XMPP requires a pack field
which is used along PEP, it seems, and meanwhile Matrix has ''support''
for packs too, tracking them is between "annoyance" and "yeah, no.".
- https://xmpp.org/extensions/xep-0050.html - https://xmpp.org/extensions/xep-0050.html
Ad-hoc commands that bridge maintainers can deal with XMPP-style are Ad-hoc commands that bridge maintainers can deal with XMPP-style are
also a nice to have. also a nice to have.
@ -46,6 +49,7 @@ THESE I WANT TO SEND THEM A NICE, BRIGHT GIFT:
Not XEPs, but ideas that _needs_ to be added: Not XEPs, but ideas that _needs_ to be added:
- "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d - "GIVE THE PUPPETS APPROPRIATE PLS/ROLES" - Hydro/t4d
"also it [Bifrost] doesn't respect voice either" "also it [Bifrost] doesn't respect voice either"
- Standalone/Static Parsee, ideally as small as it can be(if not as APE).
- https://www.youtube.com/watch?v=InL414iDZmY - https://www.youtube.com/watch?v=InL414iDZmY

View file

@ -22,6 +22,13 @@ static HttpServer *server = NULL;
static pthread_t xmpp_thr; static pthread_t xmpp_thr;
static XMPPComponent *jabber = NULL; static XMPPComponent *jabber = NULL;
static volatile uint64_t start;
uint64_t
ParseeUptime(void)
{
return UtilTsMillis() - start;
}
int int
Main(void) Main(void)
{ {
@ -30,6 +37,8 @@ Main(void)
Stream *yaml; Stream *yaml;
Cron *cron = NULL; Cron *cron = NULL;
start = UtilTsMillis();
memset(&conf, 0, sizeof(conf)); memset(&conf, 0, sizeof(conf));
Log(LOG_INFO, Log(LOG_INFO,
"%s - v%s (Cytoplasm %s)", "%s - v%s (Cytoplasm %s)",

View file

@ -60,6 +60,8 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
XMPPJoinMUC(data->jabber, jid, rev); XMPPJoinMUC(data->jabber, jid, rev);
Free(rev); Free(rev);
Free(muc); Free(muc);
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */
} }
Free(jid); Free(jid);
Free(chat_id); Free(chat_id);

View file

@ -824,3 +824,40 @@ ParseeManageBan(ParseeData *data, char *user, char *room)
return banned; return banned;
} }
char *
ParseeStringifyDate(uint64_t millis)
{
uint64_t rest = millis;
uint64_t hours, minutes, seconds;
char *hs, *ms, *ss, *out;
hours = rest / (1 HOURS);
rest = rest % (1 HOURS);
minutes = rest / (1 MINUTES);
rest = rest % (1 MINUTES);
seconds = rest / (1 SECONDS);
hs = StrInt(hours);
ms = StrInt(minutes);
ss = StrInt(seconds);
out = StrConcat(8,
hours ? hs : "",
hours ? " hours" : "",
(hours && minutes) ? ", " : "",
minutes ? ms : "",
minutes ? " minutes" : "",
(minutes && seconds) ? ", " : "",
seconds ? ss : "",
seconds ? " seconds" : ""
);
Free(hs);
Free(ms);
Free(ss);
return out;
}

View file

@ -39,22 +39,6 @@ ParseeIsPuppet(const ParseeConfig *conf, char *user)
* room. */ * room. */
return flag; return flag;
} }
bool
ParseeIsJabberPuppet(const ParseeConfig *config, char *jid)
{
char *resource;
bool ret;
if (!config || !jid)
{
return false;
}
resource = ParseeGetResource(jid);
ret = *resource == '%';
Free(resource);
return ret;
}
char * char *
ParseeDecodeJID(char *str, char term) ParseeDecodeJID(char *str, char term)
{ {
@ -155,6 +139,7 @@ ParseeDecodeLocalMUC(const ParseeConfig *c, char *alias)
return ParseeDecodeJID(data_start, ':'); return ParseeDecodeJID(data_start, ':');
} }
static const char *HEX = "0123456789abcdef";
char * char *
ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim) ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
{ {
@ -168,7 +153,6 @@ ParseeEncodeJID(const ParseeConfig *c, char *jid, bool trim)
ret = StrConcat(2, c->namespace_base, "_l_"); ret = StrConcat(2, c->namespace_base, "_l_");
for (i = 0; i < strlen(jid); i++) for (i = 0; i < strlen(jid); i++)
{ {
const char *HEX = "0123456789abcdef";
char cpy = jid[i]; char cpy = jid[i];
char cs[4] = { 0 }; char cs[4] = { 0 };
cs[0] = cpy; cs[0] = cpy;
@ -237,60 +221,75 @@ char *
ParseeEncodeMXID(char *mxid) ParseeEncodeMXID(char *mxid)
{ {
char *ret; char *ret;
size_t i; size_t i, j;
if (!mxid) if (!mxid)
{ {
return NULL; return NULL;
} }
ret = Malloc(strlen(mxid) + 1);
for (i = 0; i < strlen(mxid) + 1; i++) /* Worst case scenario of 3-bytes the char */
ret = Malloc(strlen(mxid) * 3 + 1);
for (i = 0, j = 0; i < strlen(mxid); i++)
{ {
char src = mxid[i]; char src = mxid[i];
/* TODO: More robust system */ /* TODO: More robust system */
if (src == '@') if (src <= 0x20 ||
(src == '"' || src == '&' ||
src == '\'' || src == '/' ||
src == ':' || src == '<' ||
src == '>' || src == '@' ||
src == 0x7F))
{ {
src = '%'; ret[j++] = '?';
ret[j++] = HEX[(src & 0xF0) >> 4];
ret[j++] = HEX[(src & 0x0F) >> 0];
continue;
} }
else if (src == ':') ret[j++] = src;
{
src = '=';
}
ret[i] = src;
} }
ret[j] = '\0';
return ret; return ret;
} }
char * char *
ParseeDecodeMXID(char *mxid) ParseeDecodeMXID(char *mxid)
{ {
char *ret; char *out = NULL;
size_t i;
if (!mxid) if (!mxid)
{ {
return NULL; return NULL;
} }
ret = Malloc(strlen(mxid) + 1); #define Okay(c) ((c) && (c != '@'))
for (i = 0; i < strlen(mxid) + 1; i++) while (Okay(*mxid))
{ {
char src = mxid[i]; char c = *mxid;
if (src == '%') char buf[3] = { 0 };
char *tmp;
if (c == '?' && Okay(*(mxid + 1)) && Okay(*(mxid + 2)))
{ {
src = '@'; mxid++;
}
else if (src == '=')
{
src = ':';
}
else if (src == '@')
{
ret[i] = '\0';
break;
}
ret[i] = src;
}
return ret; memcpy(buf, mxid, 2);
buf[0] = strtol(buf, NULL, 16);
buf[1] = '\0';
tmp = StrConcat(2, out, buf);
Free(out);
out = tmp;
mxid += 2;
continue;
}
memcpy(buf, mxid, 1);
tmp = StrConcat(2, out, buf);
Free(out);
out = tmp;
mxid++;
}
#undef Okay
return out;
} }
char * char *
ParseeGetDMID(char *mxid, char *jid) ParseeGetDMID(char *mxid, char *jid)

View file

@ -8,6 +8,10 @@ struct XMPPCommand {
XMPPCmdCallback callback; XMPPCmdCallback callback;
char *node, *name; char *node, *name;
char *form_instruction;
char *form_title;
/* TODO: On-the-fly generation of options */
Array *options; Array *options;
}; };
@ -25,12 +29,34 @@ XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct)
cmd->callback = callback_funct; cmd->callback = callback_funct;
cmd->node = StrDuplicate(node); cmd->node = StrDuplicate(node);
cmd->name = StrDuplicate(name); cmd->name = StrDuplicate(name);
cmd->form_instruction = NULL;
cmd->form_title = NULL;
/* No options -> no form required */ /* No options -> no form required */
cmd->options = NULL; cmd->options = NULL;
return cmd; return cmd;
} }
void
XMPPSetFormTitle(XMPPCommand *cmd, char *title)
{
if (!cmd)
{
return;
}
Free(cmd->form_title);
cmd->form_title = StrDuplicate(title);
}
void
XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction)
{
if (!cmd)
{
return;
}
Free(cmd->form_instruction);
cmd->form_instruction = StrDuplicate(instruction);
}
void void
XMPPFreeCommand(XMPPCommand *cmd) XMPPFreeCommand(XMPPCommand *cmd)
{ {
@ -47,6 +73,8 @@ XMPPFreeCommand(XMPPCommand *cmd)
} }
ArrayFree(cmd->options); ArrayFree(cmd->options);
XMPPSetFormInstruction(cmd, NULL);
XMPPSetFormTitle(cmd, NULL);
Free(cmd->node); Free(cmd->node);
Free(cmd->name); Free(cmd->name);
Free(cmd); Free(cmd);
@ -80,6 +108,25 @@ XMPPFormifyCommand(XMPPCommand *cmd)
x = XMLCreateTag("x"); x = XMLCreateTag("x");
XMLAddAttr(x, "xmlns", "jabber:x:data"); XMLAddAttr(x, "xmlns", "jabber:x:data");
XMLAddAttr(x, "type", "form"); XMLAddAttr(x, "type", "form");
if (cmd->form_title)
{
XMLElement *instr_xml, *instr_txt;
instr_xml = XMLCreateTag("title");
instr_txt = XMLCreateText(cmd->form_title);
XMLAddChild(instr_xml, instr_txt);
XMLAddChild(x, instr_xml);
}
if (cmd->form_instruction)
{
XMLElement *instr_xml, *instr_txt;
instr_xml = XMLCreateTag("instructions");
instr_txt = XMLCreateText(cmd->form_instruction);
XMLAddChild(instr_xml, instr_txt);
XMLAddChild(x, instr_xml);
}
/* TODO: Other fields */ /* TODO: Other fields */
for (i = 0; i < ArraySize(cmd->options); i++) for (i = 0; i < ArraySize(cmd->options); i++)
@ -117,23 +164,6 @@ XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLEleme
cmd->callback(m, from, form, to); cmd->callback(m, from, form, to);
} }
void
XMPPShoveOptions(XMPPCommand *cmd, XMLElement *form)
{
size_t i;
if (!cmd || !form)
{
return;
}
for (i = 0; i < ArraySize(cmd->options); i++)
{
XMPPOption *opt = ArrayGet(cmd->options, i);
XMLElement *xml = XMPPOptionToXML(opt);
XMLAddChild(form, xml);
}
}
bool bool
XMPPVerifyForm(XMPPCommand *cmd, XMLElement *form) XMPPVerifyForm(XMPPCommand *cmd, XMLElement *form)
{ {
@ -157,7 +187,7 @@ XMPPVerifyForm(XMPPCommand *cmd, XMLElement *form)
/* Required field not found; die. */ /* Required field not found; die. */
return false; return false;
} }
/* TODO */ /* TODO: Verify type, array membership, uniqueness, etc... */
} }
} }

View file

@ -252,7 +252,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
{ {
XMLElement *actions = XMLCreateTag("actions"); XMLElement *actions = XMLCreateTag("actions");
XMLElement *completed = XMLCreateTag("complete"); XMLElement *completed = XMLCreateTag("complete");
XMLElement *x = XMLCreateTag("x"); XMLElement *x;
XMLAddChild(actions, completed); XMLAddChild(actions, completed);
session_id = XMPPRegisterSession(m, to, from, node); session_id = XMPPRegisterSession(m, to, from, node);
@ -271,9 +271,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddChild(command_xml, actions); XMLAddChild(command_xml, actions);
XMLAddChild(iq, command_xml); XMLAddChild(iq, command_xml);
XMLAddAttr(x, "xmlns", "jabber:x:data"); x = XMPPFormifyCommand(cmd);
XMLAddAttr(x, "type", "form");
XMPPShoveOptions(cmd, x);
XMLAddChild(command_xml, x); XMLAddChild(command_xml, x);
pthread_mutex_lock(&jabber->write_lock); pthread_mutex_lock(&jabber->write_lock);
@ -286,6 +284,11 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
} }
/* Doesn't need to be freed, as it lives with m. */ /* Doesn't need to be freed, as it lives with m. */
/* TODO: I've noticed memory leakages around here with a
* StrDuplicate'd session ID. I think StrDuplicate should
* just "rat out" the original source in cases of leaks
* like this, maybe with extra data beyond the NULL-terminator */
session_id = XMPPRegisterSession(m, to, from, node); session_id = XMPPRegisterSession(m, to, from, node);
/* No forms, we good. */ /* No forms, we good. */

View file

@ -175,6 +175,8 @@ XMPPOptionToXML(XMPPOption *opt)
data = XMLCreateText(opt->desc); data = XMLCreateText(opt->desc);
XMLAddChild(desc, data); XMLAddChild(desc, data);
XMLAddChild(elem, desc); XMLAddChild(elem, desc);
XMLAddAttr(elem, "label", opt->desc);
} }
if (opt->type == XMPP_OPTION_LIST) if (opt->type == XMPP_OPTION_LIST)

View file

@ -0,0 +1,32 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
void
CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");
Free(trimmed);
return;
}
Free(trimmed);
ParseeCleanup(data);
/* TODO: Cleanup old sessions? */
SetNote("info", "Parsee data was sucessfully cleant up.");
}

View file

@ -22,6 +22,7 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
size_t gb = mb >> 10; size_t gb = mb >> 10;
size_t min = gb ? gb : (mb ? mb : (kb ? kb : alloc)); size_t min = gb ? gb : (mb ? mb : (kb ? kb : alloc));
char *unit = gb ? "GB" : (mb ? "MB" : (kb ? "KB" : "B")); char *unit = gb ? "GB" : (mb ? "MB" : (kb ? "KB" : "B"));
XMLElement *x; XMLElement *x;
XMLElement *title, *txt; XMLElement *title, *txt;
@ -52,6 +53,7 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
/* Report */ /* Report */
Report("mem-alloc", "Heap allocated with Cytoplasm"); Report("mem-alloc", "Heap allocated with Cytoplasm");
Report("xml-congest", "Unprocessed stanzas(congestion)"); Report("xml-congest", "Unprocessed stanzas(congestion)");
Report("uptime", "Parsee uptime");
/* Set */ /* Set */
BeginItem(); BeginItem();
@ -59,13 +61,16 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
char *min_str = StrInt(min); char *min_str = StrInt(min);
char *congest = StrInt(ParseeCongestion()); char *congest = StrInt(ParseeCongestion());
char *alloc = StrConcat(3, min_str, " ", unit); char *alloc = StrConcat(3, min_str, " ", unit);
char *up = ParseeStringifyDate(ParseeUptime());
SetField("mem-alloc", alloc); SetField("mem-alloc", alloc);
SetField("xml-congest", congest); SetField("xml-congest", congest);
SetField("uptime", up);
Free(congest); Free(congest);
Free(min_str); Free(min_str);
Free(alloc); Free(alloc);
Free(up);
} }
EndItem(); EndItem();
} }

View file

@ -53,7 +53,7 @@ typedef struct ParseeData {
#define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__)) #define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__))
#define GrabArray(obj, ...) JsonValueAsArray(JsonGet(obj, __VA_ARGS__)) #define GrabArray(obj, ...) JsonValueAsArray(JsonGet(obj, __VA_ARGS__))
/* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTE /* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTES
* in timestamps */ * in timestamps */
#define SECONDS * 1000 #define SECONDS * 1000
#define MINUTES * 60 SECONDS #define MINUTES * 60 SECONDS
@ -90,9 +90,6 @@ extern void ParseeEventHandler(ParseeData *, HashMap *);
/* Verifies if a user is a Parsee puppet user. */ /* Verifies if a user is a Parsee puppet user. */
extern bool ParseeIsPuppet(const ParseeConfig *, char *); extern bool ParseeIsPuppet(const ParseeConfig *, char *);
/* Verifies if a user is a Parsee puppet user on XMPP. */
extern bool ParseeIsJabberPuppet(const ParseeConfig *, char *);
/* Decodes a local JID for a user into a string. */ /* Decodes a local JID for a user into a string. */
extern char * ParseeDecodeLocalJID(const ParseeConfig *, char *); extern char * ParseeDecodeLocalJID(const ParseeConfig *, char *);
@ -230,4 +227,10 @@ extern bool ParseeVerifyDMStanza(ParseeData *data, char *room_id, char *id);
/* Checks if any user is an admin */ /* Checks if any user is an admin */
extern bool ParseeIsAdmin(ParseeData *data, char *user); extern bool ParseeIsAdmin(ParseeData *data, char *user);
/* Measures Parsee's overall uptime */
extern uint64_t ParseeUptime(void);
/* Turns a date into a nice "X minutes, Y seconds" string */
extern char * ParseeStringifyDate(uint64_t millis);
#endif #endif

View file

@ -31,6 +31,8 @@ extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt);
extern XMLElement * XMPPFormifyCommand(XMPPCommand *cmd); extern XMLElement * XMPPFormifyCommand(XMPPCommand *cmd);
extern char * XMPPGetCommandNode(XMPPCommand *cmd); extern char * XMPPGetCommandNode(XMPPCommand *cmd);
extern char * XMPPGetCommandDesc(XMPPCommand *cmd); extern char * XMPPGetCommandDesc(XMPPCommand *cmd);
extern void XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction);
extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title);
extern bool XMPPCommandRequiresForm(XMPPCommand *cmd); extern bool XMPPCommandRequiresForm(XMPPCommand *cmd);
extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form); extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form);
@ -51,7 +53,6 @@ extern bool XMPPIsOptionRequired(XMPPOption *opt);
extern char * XMPPOptionVar(XMPPOption *opt); extern char * XMPPOptionVar(XMPPOption *opt);
extern XMLElement * XMPPOptionToXML(XMPPOption *opt); extern XMLElement * XMPPOptionToXML(XMPPOption *opt);
extern void XMPPShoveOptions(XMPPCommand *cmd, XMLElement *form);
extern void XMPPFreeOption(XMPPOption *opt); extern void XMPPFreeOption(XMPPOption *opt);
extern void XMPPFreeCommand(XMPPCommand *cmd); extern void XMPPFreeCommand(XMPPCommand *cmd);
@ -92,11 +93,16 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD
/* --------------------------------- COMMANDS --------------------------------- */ /* --------------------------------- COMMANDS --------------------------------- */
#define XMPPCOMMANDS \ #define XMPPCOMMANDS \
XMPP_COMMAND(StatusCallback, "stats", "Get Parsee statistics", {}) \ XMPP_COMMAND(StatusCallback, "stats", "Get Parsee statistics", {}) \
XMPP_COMMAND(CleanCallback, "clean", "Cleanup temporary Parsee data", {}) \
XMPP_COMMAND(AdminsCallback, "admin", "Get Parsee admin list", {}) \ XMPP_COMMAND(AdminsCallback, "admin", "Get Parsee admin list", {}) \
XMPP_COMMAND(NoflyCallback, "nofly", "Get Parsee nofly list", {}) \
XMPP_COMMAND(AddAdminCallback, "add-admin", "Adds glob as admin", { \ XMPP_COMMAND(AddAdminCallback, "add-admin", "Adds glob as admin", { \
XMPPOption *glob = XMPPCreateText(true, "glob", ""); \ XMPPOption *glob = XMPPCreateText(true, "glob", ""); \
XMPPSetDescription(glob, "Glob pattern to set as admin"); \ XMPPSetDescription(glob, "Glob pattern to set as admin"); \
XMPPAddOption(cmd, glob); \ XMPPAddOption(cmd, glob); \
\
XMPPSetFormTitle(cmd, "Admin addition form"); \
XMPPSetFormInstruction(cmd, "Select a glob pattern to add as an admin"); \
}) \ }) \
XMPP_COMMAND(AddNoflyCallback, "add-nofly", "Adds user to nofly", { \ XMPP_COMMAND(AddNoflyCallback, "add-nofly", "Adds user to nofly", { \
XMPPOption *entity = XMPPCreateText(true, "entity", ""); \ XMPPOption *entity = XMPPCreateText(true, "entity", ""); \
@ -105,10 +111,16 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD
XMPPAddOption(cmd, entity); \ XMPPAddOption(cmd, entity); \
XMPPSetDescription(reason, "Reason for the no-fly"); \ XMPPSetDescription(reason, "Reason for the no-fly"); \
XMPPAddOption(cmd, reason); \ XMPPAddOption(cmd, reason); \
\
XMPPSetFormTitle(cmd, "No-fly addition form"); \
XMPPSetFormInstruction(cmd, "Select a glob pattern to add to the nofly"); \
}) \ }) \
XMPP_COMMAND(NoflyCallback, "nofly", "Get Parsee nofly list", {})
#define XMPP_COMMAND(f,n,t,s) extern void f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); #define XMPP_COMMAND(f,n,t,s) \
extern void \
f(XMPPCommandManager *, char *, XMLElement *, XMLElement *);
XMPPCOMMANDS XMPPCOMMANDS
#undef XMPP_COMMAND #undef XMPP_COMMAND
#endif #endif