mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 13:45:10 +00:00
[FIX/WIP] Plumbing, -Werror, death to hitmen
Suspend killers are now no more. Except the actual killers to be gone eventually. Plumbing is also very basic as of now, but it "works".
This commit is contained in:
parent
1f658ece76
commit
bb836789b2
23 changed files with 310 additions and 165 deletions
4
Makefile
4
Makefile
|
|
@ -19,8 +19,8 @@ SOURCE=src
|
|||
OBJECT=build
|
||||
INCLUDES=src/include
|
||||
CC=cc
|
||||
CFLAGS=-I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -O3
|
||||
LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -Wl,--export-dynamic -O3
|
||||
CFLAGS=-I$(INCLUDES) -I$(CYTO_INC) -DNAME="\"$(NAME)\"" -DVERSION="\"$(VERSION)\"" -DREPOSITORY=\"$(REPOSITORY)\" -O3 -g -ggdb -Wall -Werror
|
||||
LDFLAGS=-L $(CYTO_LIB) -lCytoplasm -Wl,--export-dynamic -O3 -g -ggdb
|
||||
BINARY=parsee
|
||||
# ============================ Compilation =================================
|
||||
SRC_FILES:=$(shell find $(SOURCE) -name '*.c')
|
||||
|
|
|
|||
|
|
@ -42,8 +42,11 @@ TODO
|
|||
- Look at XEPS-TBD.TXT for XEPs to be done
|
||||
- Achievements
|
||||
### Why?
|
||||
> "[...] and it [BoVeX] has an achievement system, because another thing I don't like is when software will not acknowledge that you've reached an obscure error state, essentially outsmarting it."
|
||||
- Tom Murphy VII
|
||||
> "[...] and it [BoVeX] has an achievement system, because another thing I don't like is
|
||||
> when software will not acknowledge that you've reached an obscure error state, essentially
|
||||
> outsmarting it."
|
||||
|
||||
- [Tom Murphy VII](https://tom7.org)
|
||||
|
||||
## DONATING/CONTRIBUTING
|
||||
If you know things about XMPP or Matrix, yet aren't familiar with C99, or just
|
||||
|
|
|
|||
42
src/AS.c
42
src/AS.c
|
|
@ -222,19 +222,33 @@ ASKick(const ParseeConfig *conf, char *id, char *banned)
|
|||
HttpClientContextFree(ctx);
|
||||
JsonFree(json);
|
||||
}
|
||||
void
|
||||
char *
|
||||
ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
|
||||
{
|
||||
HttpClientContext *ctx = NULL;
|
||||
HashMap *json = NULL;
|
||||
char *path;
|
||||
if (!conf || !id || !masquerade)
|
||||
char *path, *ret;
|
||||
if (!conf || !id)
|
||||
{
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!masquerade)
|
||||
{
|
||||
char *raw = StrConcat(4,
|
||||
"@", conf->sender_localpart,
|
||||
":", conf->homeserver_host
|
||||
);
|
||||
masquerade = HttpUrlEncode(raw);
|
||||
Free(raw);
|
||||
}
|
||||
else
|
||||
{
|
||||
masquerade = HttpUrlEncode(masquerade);
|
||||
}
|
||||
id = HttpUrlEncode(id);
|
||||
path = StrConcat(5,
|
||||
"/_matrix/client/v3/rooms/", id, "/join?",
|
||||
"/_matrix/client/v3/join/", id, "?",
|
||||
"user_id=", masquerade
|
||||
);
|
||||
|
||||
|
|
@ -246,15 +260,23 @@ ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
|
|||
json = HashMapCreate();
|
||||
ASAuthenticateRequest(conf, ctx);
|
||||
ParseeSetRequestJSON(ctx, json);
|
||||
JsonFree(json);
|
||||
|
||||
json = JsonDecode(HttpClientStream(ctx));
|
||||
ret = StrDuplicate(GrabString(json, 1, "room_id"));
|
||||
JsonFree(json);
|
||||
|
||||
HttpClientContextFree(ctx);
|
||||
JsonFree(json);
|
||||
Free(masquerade);
|
||||
Free(id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
void
|
||||
ASSetState(const ParseeConfig *conf, char *id, char *type, char *key, char *mask, HashMap *state)
|
||||
{
|
||||
HttpClientContext *ctx = NULL;
|
||||
char *path, *params;
|
||||
char *path;
|
||||
if (!conf || !id || !type || !mask || !state)
|
||||
{
|
||||
JsonFree(state);
|
||||
|
|
@ -278,7 +300,7 @@ char *
|
|||
ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
|
||||
{
|
||||
HttpClientContext *ctx = NULL;
|
||||
char *path, *params;
|
||||
char *path;
|
||||
char *txn, *ret;
|
||||
HashMap *reply;
|
||||
if (!conf || !id || !type || !user || !c)
|
||||
|
|
@ -626,7 +648,6 @@ ASReupload(const ParseeConfig *c, char *from, char **mime)
|
|||
HttpClientContext *ctx;
|
||||
unsigned short port;
|
||||
int size = 0, flags = HTTP_FLAG_NONE;
|
||||
int i;
|
||||
char *ret, *content_len;
|
||||
|
||||
if (!c || !from)
|
||||
|
|
@ -685,7 +706,7 @@ ASType(const ParseeConfig *c, char *user, char *room, bool status)
|
|||
{
|
||||
HttpClientContext *ctx = NULL;
|
||||
HashMap *json;
|
||||
char *path, *full;
|
||||
char *path;
|
||||
if (!c || !user || !room)
|
||||
{
|
||||
return;
|
||||
|
|
@ -760,7 +781,6 @@ ASSetUserConfig(const ParseeConfig *c, char *user, char *key, HashMap *map)
|
|||
{
|
||||
|
||||
HttpClientContext *ctx = NULL;
|
||||
HashMap *json;
|
||||
char *path;
|
||||
if (!c || !key || !map)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <Matrix.h>
|
||||
#include <Bot.h>
|
||||
#include <AS.h>
|
||||
|
||||
CommandHead(CmdBanUser, cmd, argp)
|
||||
|
|
@ -13,32 +14,21 @@ CommandHead(CmdBanUser, cmd, argp)
|
|||
HashMap *event = args->event;
|
||||
char *user = HashMapGet(cmd->arguments, "user");
|
||||
char *room = HashMapGet(cmd->arguments, "room");
|
||||
char *msg;
|
||||
char *msgtype = GrabString(event, 2, "content", "msgtype");
|
||||
char *body = GrabString(event, 2, "content", "body");
|
||||
char *id = GrabString(event, 1, "room_id");
|
||||
char *ev_id = GrabString(event, 1, "event_id");
|
||||
char *sender = GrabString(event, 1, "sender");
|
||||
char *profile = StrConcat(4,
|
||||
"@", data->config->sender_localpart,
|
||||
":", data->config->homeserver_host
|
||||
);
|
||||
|
||||
BotInitialise();
|
||||
|
||||
if (!user || !room)
|
||||
{
|
||||
Free(profile);
|
||||
BotDestroy();
|
||||
return;
|
||||
}
|
||||
ASBan(data->config, room, user);
|
||||
|
||||
msg = StrConcat(3, "Banning '", user, "'...");
|
||||
Free(ASSend(
|
||||
data->config, id, profile,
|
||||
"m.room.message",
|
||||
MatrixCreateNotice(msg)
|
||||
));
|
||||
Free(msg);
|
||||
Free(profile);
|
||||
ReplySprintf("Banning %s from '%s'...",
|
||||
user, room
|
||||
);
|
||||
|
||||
BotDestroy();
|
||||
}
|
||||
CommandHead(CmdNoFlyList, cmd, argp)
|
||||
{
|
||||
|
|
@ -46,30 +36,19 @@ CommandHead(CmdNoFlyList, cmd, argp)
|
|||
ParseeData *data = args->data;
|
||||
HashMap *event = args->event;
|
||||
char *user = HashMapGet(cmd->arguments, "user");
|
||||
char *msg;
|
||||
char *msgtype = GrabString(event, 2, "content", "msgtype");
|
||||
char *body = GrabString(event, 2, "content", "body");
|
||||
char *id = GrabString(event, 1, "room_id");
|
||||
char *ev_id = GrabString(event, 1, "event_id");
|
||||
char *sender = GrabString(event, 1, "sender");
|
||||
char *profile = StrConcat(4,
|
||||
"@", data->config->sender_localpart,
|
||||
":", data->config->homeserver_host
|
||||
);
|
||||
char *reason = HashMapGet(cmd->arguments, "reason");
|
||||
BotInitialise();
|
||||
|
||||
if (!user)
|
||||
{
|
||||
Free(profile);
|
||||
BotDestroy();
|
||||
return;
|
||||
}
|
||||
|
||||
msg = StrConcat(3, "Banning '", user, "'...");
|
||||
Free(ASSend(
|
||||
data->config, id, profile,
|
||||
"m.room.message",
|
||||
MatrixCreateNotice(msg)
|
||||
));
|
||||
Free(msg);
|
||||
ParseeGlobalBan(data, user);
|
||||
Free(profile);
|
||||
ReplySprintf("Banning %s for '%s'",
|
||||
user, reason ? reason : "[no reason specified]"
|
||||
);
|
||||
ParseeGlobalBan(data, user, reason);
|
||||
|
||||
BotDestroy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ CommandHead(CmdHelp, cmd, argp)
|
|||
{
|
||||
ParseeCmdArg *args = argp;
|
||||
ParseeData *data = args->data;
|
||||
HashMap *json, *event = args->event;
|
||||
HashMap *event = args->event;
|
||||
BotInitialise();
|
||||
|
||||
ReplyBasic("Commands: ");
|
||||
|
|
|
|||
89
src/Commands/Plumb.c
Normal file
89
src/Commands/Plumb.c
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include <Routes.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <Matrix.h>
|
||||
#include <Bot.h>
|
||||
#include <AS.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
CommandHead(CmdPlumb, cmd, argp)
|
||||
{
|
||||
ParseeCmdArg *args = argp;
|
||||
ParseeData *data = args->data;
|
||||
HashMap *event = args->event;
|
||||
char *muc = NULL, *room = NULL, *chat_id = NULL;
|
||||
char *room_id = NULL;
|
||||
MUCInfo info = { .exists = false };
|
||||
|
||||
BotInitialise();
|
||||
|
||||
BotRequired(muc);
|
||||
BotRequired(room);
|
||||
|
||||
/* Check MUC viability */
|
||||
Log(LOG_INFO, "BAR1");
|
||||
if (ParseeManageBan(args->data, muc, NULL))
|
||||
{
|
||||
ReplySprintf("MUC '%s' is not allowed on this bridge.", muc);
|
||||
Log(LOG_INFO, "BAR1F");
|
||||
goto end;
|
||||
}
|
||||
Log(LOG_INFO, "BAR2");
|
||||
if (!XMPPQueryMUC(args->data->jabber, muc, &info))
|
||||
{
|
||||
ReplySprintf("MUC '%s' does not exist.", muc);
|
||||
Log(LOG_INFO, "BAR2F");
|
||||
goto end;
|
||||
}
|
||||
Log(LOG_INFO, "BAR3");
|
||||
if ((chat_id = ParseeGetFromMUCID(args->data, muc)))
|
||||
{
|
||||
Free(chat_id);
|
||||
chat_id = NULL;
|
||||
ReplySprintf("MUC '%s' is already mapped.", muc);
|
||||
Log(LOG_INFO, "BAR3F");
|
||||
goto end;
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "FOO");
|
||||
|
||||
/* Check room viability */
|
||||
room_id = ASJoin(args->data->config, room, NULL);
|
||||
if (!room_id)
|
||||
{
|
||||
ReplySprintf("Room '%s' does not exist.", room);
|
||||
Log(LOG_INFO, "FOO2");
|
||||
goto end;
|
||||
}
|
||||
Log(LOG_INFO, "FOO3");
|
||||
if ((chat_id = ParseeGetFromRoomID(args->data, room_id)))
|
||||
{
|
||||
Free(chat_id);
|
||||
chat_id = NULL;
|
||||
ReplySprintf("Room '%s' is already mapped.", room);
|
||||
Log(LOG_INFO, "FOO4");
|
||||
goto end;
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "FOO5");
|
||||
chat_id = ParseePushMUC(args->data, room_id, muc);
|
||||
if (chat_id)
|
||||
{
|
||||
char *rev = StrConcat(2, muc, "/parsee");
|
||||
XMPPJoinMUC(args->data->jabber, "parsee", rev);
|
||||
|
||||
Free(rev);
|
||||
}
|
||||
Log(LOG_INFO, "FOO6");
|
||||
|
||||
ReplySprintf("Plumbed '%s'", muc);
|
||||
end:
|
||||
Log(LOG_INFO, "End.");
|
||||
BotDestroy();
|
||||
Free(chat_id);
|
||||
Free(room_id);
|
||||
XMPPFreeMUCInfo(info);
|
||||
}
|
||||
|
|
@ -12,8 +12,7 @@ CommandHead(CmdStats, cmd, argp)
|
|||
{
|
||||
ParseeCmdArg *args = argp;
|
||||
ParseeData *data = args->data;
|
||||
HashMap *json, *event = args->event;
|
||||
char *msg;
|
||||
HashMap *event = args->event;
|
||||
size_t alloc = MemoryAllocated();
|
||||
size_t kb = alloc >> 10;
|
||||
size_t mb = kb >> 10;
|
||||
|
|
@ -30,7 +29,7 @@ CommandHead(CmdStats, cmd, argp)
|
|||
);
|
||||
|
||||
ReplySprintf("- Memory used: %d%s (reported by Cytoplasm)",
|
||||
min, unit
|
||||
(int) min, unit
|
||||
);
|
||||
ReplySprintf("- Source code and licensing information: %s",
|
||||
REPOSITORY
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ CommandHead(CmdUnlinkMUC, cmd, argp)
|
|||
HashMap *json, *event = args->event, *mucs;
|
||||
DbRef *ref;
|
||||
char *muc = NULL, *chat_id = NULL, *room = NULL;
|
||||
JsonValue *val;
|
||||
|
||||
BotInitialise();
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@ static HttpServer *server = NULL;
|
|||
static pthread_t xmpp_thr;
|
||||
static XMPPComponent *jabber = NULL;
|
||||
|
||||
extern int ParseeFindDatastart(char *data);
|
||||
int
|
||||
Main(void)
|
||||
{
|
||||
HttpServerConfig conf;
|
||||
ParseeData *data = NULL;
|
||||
const ParseeConfig *parsee_conf;
|
||||
Stream *yaml;
|
||||
Cron *cron = NULL;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
|
|||
char *jid;
|
||||
bool direct = GrabBoolean(event, 2, "content", "is_direct");
|
||||
bool bot = !strncmp(sender + 1, local, strlen(local));
|
||||
ASJoin(conf, room_id, state_key);
|
||||
Free(ASJoin(conf, room_id, state_key));
|
||||
|
||||
jid = ParseeDecodeLocalJID(conf, state_key);
|
||||
|
||||
|
|
@ -70,7 +70,6 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
|
|||
char *msgtype = GrabString(event, 2, "content", "msgtype");
|
||||
char *body = GrabString(event, 2, "content", "body");
|
||||
char *id = GrabString(event, 1, "room_id");
|
||||
char *ev_id = GrabString(event, 1, "event_id");
|
||||
char *sender = GrabString(event, 1, "sender");
|
||||
char *profile = StrConcat(4,
|
||||
"@", data->config->sender_localpart,
|
||||
|
|
@ -117,7 +116,7 @@ ParseeBotHandler(ParseeData *data, HashMap *event)
|
|||
|
||||
RouteCommand(data->handler, cmd, &arg);
|
||||
}
|
||||
end:
|
||||
|
||||
Free(profile);
|
||||
CommandFree(cmd);
|
||||
}
|
||||
|
|
@ -128,16 +127,14 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
DbRef *ref = NULL;
|
||||
HashMap *json;
|
||||
|
||||
char *msgtype = GrabString(event, 2, "content", "msgtype");
|
||||
char *body = GrabString(event, 2, "content", "body");
|
||||
char *id = GrabString(event, 1, "room_id");
|
||||
char *ev_id = GrabString(event, 1, "event_id");
|
||||
char *m_sender = GrabString(event, 1, "sender");
|
||||
char *chat_id, *muc_id, *jid;
|
||||
char *chat_id, *muc_id;
|
||||
char *reply_id = MatrixGetReply(event);
|
||||
char *xepd = ParseeXMPPify(event);
|
||||
char *cmd_lp = data->config->sender_localpart;
|
||||
char *type, *user, *xmppified_user = NULL, *to;
|
||||
char *type, *user, *xmppified_user = NULL, *to = NULL;
|
||||
char *unauth = NULL;
|
||||
char *origin_id = NULL, *stanza = NULL;
|
||||
char *sender = NULL;
|
||||
|
|
@ -189,7 +186,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
}
|
||||
else
|
||||
{
|
||||
char *name, *rev, *stanza;
|
||||
char *name, *rev;
|
||||
/* Try to find the chat ID */
|
||||
muc_id = ParseeGetMUCID(data, chat_id);
|
||||
if (!chat_id)
|
||||
|
|
|
|||
|
|
@ -369,14 +369,16 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
|
|||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return xepd;
|
||||
}
|
||||
char *
|
||||
ParseeXMPPify(HashMap *event)
|
||||
{
|
||||
char *cntr, *type, *format, *html;
|
||||
char *xepd = NULL, *tmp;
|
||||
char *type, *format, *html;
|
||||
char *xepd = NULL;
|
||||
XMLElement *elem;
|
||||
|
||||
XMPPFlags flags;
|
||||
|
|
@ -756,10 +758,10 @@ end:
|
|||
|
||||
}
|
||||
void
|
||||
ParseeGlobalBan(ParseeData *data, char *user)
|
||||
ParseeGlobalBan(ParseeData *data, char *user, char *reason)
|
||||
{
|
||||
DbRef *ref;
|
||||
HashMap *j;
|
||||
HashMap *j, *obj;
|
||||
if (!data || !user)
|
||||
{
|
||||
return;
|
||||
|
|
@ -773,7 +775,13 @@ ParseeGlobalBan(ParseeData *data, char *user)
|
|||
|
||||
j = DbJson(ref);
|
||||
|
||||
JsonValueFree(HashMapSet(j, user, JsonValueObject(HashMapCreate())));
|
||||
obj = HashMapCreate();
|
||||
if (reason)
|
||||
{
|
||||
HashMapSet(obj, "reason", JsonValueString(reason));
|
||||
}
|
||||
HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis()));
|
||||
JsonValueFree(HashMapSet(j, user, JsonValueObject(obj)));
|
||||
|
||||
DbUnlock(data->db, ref);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ ParseeSendPresence(ParseeData *data)
|
|||
DbRef *ref;
|
||||
HashMap *j, *mucs;
|
||||
JsonValue *val;
|
||||
char *chatid = NULL, *muc;
|
||||
char *muc;
|
||||
if (!data)
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ RouteHead(RouteUserAck, arr, argp)
|
|||
ParseeHttpArg *args = argp;
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
Array *events;
|
||||
size_t i;
|
||||
|
||||
char *user = ArrayGet(arr, 0);
|
||||
response = ASVerifyRequest(args);
|
||||
|
|
@ -44,8 +42,6 @@ RouteHead(RouteRoomAck, arr, argp)
|
|||
ParseeHttpArg *args = argp;
|
||||
HashMap *request = NULL;
|
||||
HashMap *response = NULL;
|
||||
Array *events;
|
||||
size_t i;
|
||||
MUCInfo info = { .exists = false };
|
||||
|
||||
char *room = ArrayGet(arr, 0), *muc = NULL, *id = NULL;
|
||||
|
|
@ -119,6 +115,16 @@ RouteHead(RouteRoomAck, arr, argp)
|
|||
/* Creates a mapping */
|
||||
chatid = ParseePushMUC(args->data, id, muc);
|
||||
|
||||
/* Invite the Parsee listener, so that we can have some
|
||||
* events right up */
|
||||
{
|
||||
char *rev = StrConcat(2, muc, "/parsee");
|
||||
Log(LOG_NOTICE, "Sending presence to %s", rev);
|
||||
XMPPJoinMUC(args->data->jabber, "parsee", rev);
|
||||
|
||||
Free(rev);
|
||||
}
|
||||
|
||||
response = HashMapCreate();
|
||||
end:
|
||||
if (chatid)
|
||||
|
|
|
|||
|
|
@ -14,10 +14,6 @@ static XMPPComponent *jabber = NULL;
|
|||
static void
|
||||
SignalHandler(int signal)
|
||||
{
|
||||
size_t i;
|
||||
XMLElement *message, *body, *data;
|
||||
char *from;
|
||||
|
||||
switch (signal)
|
||||
{
|
||||
case SIGPIPE:
|
||||
|
|
@ -32,6 +28,8 @@ SignalHandler(int signal)
|
|||
return;
|
||||
}
|
||||
/* Create a loopback stanza, forcing the thread to die */
|
||||
|
||||
/* TODO: Better way to break out. */
|
||||
Log(LOG_INFO, "Killing thread...");
|
||||
XMPPKillThread(jabber, "killer");
|
||||
pthread_join(xmpp_thr, NULL);
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ struct XMLexer {
|
|||
static bool XMLookahead(XMLexer *lexer, const char *str, bool skip);
|
||||
|
||||
/* Parses an XML "name" */
|
||||
static bool XMLIsStart(XMLexer *lexer);
|
||||
static char * XMLParseName(XMLexer *lexer);
|
||||
static bool XMLSkipSpace(XMLexer *lexer);
|
||||
static char * XMLParseAttValue(XMLexer *lexer);
|
||||
|
|
@ -122,11 +121,8 @@ XMLEvent *
|
|||
XMLCrank(XMLexer *lexer)
|
||||
{
|
||||
XMLEvent *event = NULL;
|
||||
char c;
|
||||
int ch;
|
||||
char *attrname;
|
||||
HashMap *props;
|
||||
char *key, *val;
|
||||
char cbuf[2] = { 0 };
|
||||
char *tmp;
|
||||
if (!lexer)
|
||||
|
|
@ -259,6 +255,9 @@ XMLCrank(XMLexer *lexer)
|
|||
XMLFreeEvent(event);
|
||||
event = XMLCreateEnd(lexer, attrname);
|
||||
break;
|
||||
default:
|
||||
/* TODO */
|
||||
break;
|
||||
}
|
||||
/* TODO: Crank our XML parser. */
|
||||
return event;
|
||||
|
|
@ -608,7 +607,7 @@ static char *
|
|||
XMLDecodeString(char *s)
|
||||
{
|
||||
char *ret = NULL, *tmp;
|
||||
char c, cs[2] = { 0 };
|
||||
char cs[2] = { 0 };
|
||||
|
||||
while (*s)
|
||||
{
|
||||
|
|
@ -724,10 +723,9 @@ XMLParseAttQuote(XMLexer *lexer)
|
|||
|
||||
while ((c = XMLGetc(lexer)))
|
||||
{
|
||||
//Log(LOG_INFO, "E1=%c", c);
|
||||
if (c == '&')
|
||||
{
|
||||
char *code = NULL;
|
||||
//char *code = NULL;
|
||||
int c2;
|
||||
int p2 = XMLInitialiseBuffer(lexer);
|
||||
int j = 0;
|
||||
|
|
@ -774,7 +772,6 @@ XMLParseAttDouble(XMLexer *lexer)
|
|||
//Log(LOG_INFO, "E2=%c", c);
|
||||
if (c == '&')
|
||||
{
|
||||
char *code = NULL;
|
||||
int c2;
|
||||
int p2 = XMLInitialiseBuffer(lexer);
|
||||
int j = 0;
|
||||
|
|
@ -811,7 +808,7 @@ XMLParseAttDouble(XMLexer *lexer)
|
|||
static char *
|
||||
XMLParseAttValue(XMLexer *lexer)
|
||||
{
|
||||
ssize_t point = XMLInitialiseBuffer(lexer);
|
||||
XMLInitialiseBuffer(lexer);
|
||||
|
||||
if (XMLookahead(lexer, "'", true))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ XMPPInitialiseCompStream(char *host, int port)
|
|||
comp->stream = stream;
|
||||
pthread_mutex_init(&comp->write_lock, NULL);
|
||||
|
||||
(void) error;
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
|
|||
char *uuid, *from;
|
||||
if (!jabber || !muc)
|
||||
{
|
||||
Log(LOG_WARNING, ":(");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -32,25 +33,19 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
|
|||
|
||||
XMLAddChild(iq_query, query);
|
||||
Free(from);
|
||||
Free(uuid);
|
||||
|
||||
/* Pause the XMPP thread */
|
||||
XMPPKillThread(jabber, "suspend");
|
||||
UtilSleepMillis(500);
|
||||
{
|
||||
XMLElement *identity;
|
||||
/* -- WE ARE ON OUR OWN HERE WITH STANZAS -- */
|
||||
XMLEncode(jabber->stream, iq_query);
|
||||
StreamFlush(jabber->stream);
|
||||
XMLFreeElement(iq_query);
|
||||
|
||||
/* Except an IQ reply */
|
||||
iq_query = XMLDecode(jabber->stream, false);
|
||||
/* TODO: I've spotted presence requests spawning there. */
|
||||
iq_query = ParseeAwaitStanza(uuid);
|
||||
Free(uuid);
|
||||
if (!iq_query || !StrEquals(iq_query->name, "iq"))
|
||||
{
|
||||
XMLFreeElement(iq_query);
|
||||
ParseeWakeupThread();
|
||||
pthread_mutex_unlock(&jabber->write_lock);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -62,7 +57,6 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
|
|||
"conference"))
|
||||
{
|
||||
XMLFreeElement(iq_query);
|
||||
ParseeWakeupThread();
|
||||
pthread_mutex_unlock(&jabber->write_lock);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -79,9 +73,7 @@ XMPPQueryMUC(XMPPComponent *jabber, char *muc, MUCInfo *out)
|
|||
XMLFreeElement(iq_query);
|
||||
}
|
||||
}
|
||||
/* Wake it up once we're done */
|
||||
pthread_mutex_unlock(&jabber->write_lock);
|
||||
ParseeWakeupThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ XMPPJoinMUC(XMPPComponent *comp, char *fr, char *muc)
|
|||
void
|
||||
XMPPKillThread(XMPPComponent *jabber, char *killer)
|
||||
{
|
||||
XMLElement *message, *body, *data;
|
||||
XMLElement *message, *body;
|
||||
char *from;
|
||||
|
||||
if (!jabber || !killer)
|
||||
|
|
|
|||
160
src/XMPPThread.c
160
src/XMPPThread.c
|
|
@ -49,8 +49,8 @@ typedef struct XMPPIdentity {
|
|||
static int
|
||||
ICollate(unsigned char *cata, unsigned char *catb)
|
||||
{
|
||||
size_t al = cata ? strlen(cata) : 0;
|
||||
size_t bl = catb ? strlen(catb) : 0;
|
||||
size_t al = cata ? strlen((char *) cata) : 0;
|
||||
size_t bl = catb ? strlen((char *) catb) : 0;
|
||||
|
||||
if (!al && !bl)
|
||||
{
|
||||
|
|
@ -89,8 +89,8 @@ IdentitySort(void *idap, void *idbp)
|
|||
{
|
||||
XMPPIdentity *ida = idap;
|
||||
XMPPIdentity *idb = idbp;
|
||||
unsigned char *cata = ida->category;
|
||||
unsigned char *catb = idb->category;
|
||||
unsigned char *cata = (unsigned char *) ida->category;
|
||||
unsigned char *catb = (unsigned char *) idb->category;
|
||||
|
||||
return ICollate(cata, catb);
|
||||
}
|
||||
|
|
@ -144,7 +144,7 @@ XMPPGenerateVer(void)
|
|||
|
||||
Sha = Sha1(S);
|
||||
Free(S);
|
||||
S = Base64Encode(Sha, 20);
|
||||
S = Base64Encode((const char *) Sha, 20);
|
||||
Free(Sha);
|
||||
|
||||
ArrayFree(features);
|
||||
|
|
@ -159,17 +159,6 @@ XMPPGenerateVer(void)
|
|||
return S;
|
||||
}
|
||||
|
||||
static pthread_mutex_t cond_var_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
void
|
||||
ParseeWakeupThread(void)
|
||||
{
|
||||
pthread_mutex_lock(&cond_var_lock);
|
||||
pthread_cond_signal(&cond_var);
|
||||
pthread_mutex_unlock(&cond_var_lock);
|
||||
}
|
||||
|
||||
static char *
|
||||
ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza)
|
||||
{
|
||||
|
|
@ -222,7 +211,6 @@ ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza)
|
|||
XMLElement *reactions = XMLookForTKV(stanza,
|
||||
"reactions", "xmlns", "urn:xmpp:reactions:0"
|
||||
);
|
||||
char *from = (HashMapGet(stanza->attrs, "from"));
|
||||
char *reacted_id = reactions ? HashMapGet(reactions->attrs, "id") : NULL;
|
||||
|
||||
return ParseeGetEventFromID(data, stanza, reacted_id);
|
||||
|
|
@ -237,7 +225,6 @@ ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event)
|
|||
|
||||
char *id_str = HashMapGet(stanza->attrs, "id");
|
||||
char *s_id_str = XMPPGetStanzaID(stanza);
|
||||
char *o_id_str = XMPPGetOriginID(stanza);
|
||||
|
||||
if (!chat_id)
|
||||
{
|
||||
|
|
@ -389,6 +376,8 @@ ManageProfileItem(ParseeData *args, XMLElement *item, XMLElement *stanza, XMPPTh
|
|||
pthread_mutex_unlock(&jabber->write_lock);
|
||||
|
||||
XMLFreeElement(request);
|
||||
|
||||
(void) url; /* TODO */
|
||||
}
|
||||
|
||||
end:
|
||||
|
|
@ -406,7 +395,7 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|||
XMLElement *event = NULL;
|
||||
|
||||
char *to, *room, *from, *from_matrix, *decode_from;
|
||||
char *chat_id = NULL, *mroom_id = NULL;
|
||||
char *mroom_id = NULL;
|
||||
size_t i;
|
||||
|
||||
to = NULL;
|
||||
|
|
@ -506,9 +495,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|||
{
|
||||
char *res = ParseeGetResource(from);
|
||||
char *encoded = ParseeEncodeJID(args->config, decode_from, false);
|
||||
char *s_id_str = XMPPGetStanzaID(stanza);
|
||||
char *o_id_str = XMPPGetOriginID(stanza);
|
||||
char *id_str = HashMapGet(stanza->attrs, "id");
|
||||
char *event_id = NULL;
|
||||
char *replaced = XMPPGetReplacedID(stanza);
|
||||
char *reply_to = XMPPGetReply(stanza);
|
||||
|
|
@ -548,7 +534,7 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|||
ASSetName(args->config, encoded, res);
|
||||
}
|
||||
ASInvite(args->config, mroom_id, encoded);
|
||||
ASJoin(args->config, mroom_id, encoded);
|
||||
Free(ASJoin(args->config, mroom_id, encoded));
|
||||
|
||||
/* Check if it is a media link */
|
||||
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
|
||||
|
|
@ -574,7 +560,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|||
}
|
||||
else if (reactions)
|
||||
{
|
||||
char *reacted_id = HashMapGet(reactions->attrs, "id");
|
||||
Array *react_child = reactions->children;
|
||||
size_t reacts = ArraySize(react_child);
|
||||
event_id = ParseeGetReactedEvent(args, stanza);
|
||||
|
|
@ -642,8 +627,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
|
|||
XMLElement *parsee = XMLookForUnique(stanza, "x-parsee");
|
||||
XMLElement *event = XMLookForUnique(parsee, "event-id");
|
||||
XMLElement *e_d = ArrayGet(event ? event->children : NULL, 0);
|
||||
char *s_id_str = XMPPGetStanzaID(stanza);
|
||||
char *id_str = HashMapGet(stanza->attrs, "id");
|
||||
|
||||
ParseePushAllStanza(args, stanza, e_d->data);
|
||||
}
|
||||
|
|
@ -752,7 +735,6 @@ TrimBase64(char *b64)
|
|||
static void
|
||||
IQResult(ParseeData *args, XMLElement *stanza)
|
||||
{
|
||||
XMPPComponent *jabber = args->jabber;
|
||||
XMLElement *vcard = XMLookForTKV(stanza, "vCard", "xmlns", "vcard-temp");
|
||||
|
||||
XMLElement *event = XMLookForTKV(stanza, "pubsub",
|
||||
|
|
@ -776,7 +758,7 @@ IQResult(ParseeData *args, XMLElement *stanza)
|
|||
char *id = HashMapGet(item->attrs, "id");
|
||||
char *from = HashMapGet(stanza->attrs, "from");
|
||||
char *base64;
|
||||
unsigned char *bdata;
|
||||
char *bdata;
|
||||
size_t length, b64len;
|
||||
Stream *datastream;
|
||||
char *mxc, *from_matrix, *jid;
|
||||
|
|
@ -804,8 +786,8 @@ IQResult(ParseeData *args, XMLElement *stanza)
|
|||
b64len = base64 ? strlen(base64) : 0;
|
||||
length = Base64DecodedSize(base64, b64len);
|
||||
|
||||
/* TODO: Bound checks! */
|
||||
bdata = Base64Decode(base64, b64len);
|
||||
/* TODO: Bound checks for a size limit. */
|
||||
bdata = (char *) Base64Decode(base64, b64len);
|
||||
datastream = StrStreamReaderN(bdata, length);
|
||||
mxc = ASUpload(args->config, datastream, length);
|
||||
|
||||
|
|
@ -837,13 +819,16 @@ IQResult(ParseeData *args, XMLElement *stanza)
|
|||
if (nickname)
|
||||
{
|
||||
XMLElement *data = ArrayGet(nickname->children, 0);
|
||||
/* TODO: Use the nickname for something.
|
||||
* And the rest of the vCard, somewhere. */
|
||||
(void) data;
|
||||
}
|
||||
if (photo)
|
||||
{
|
||||
XMLElement *binval = XMLookForUnique(photo, "BINVAL");
|
||||
XMLElement *data = ArrayGet(binval->children, 0);
|
||||
char *base64;
|
||||
unsigned char *bdata;
|
||||
char *bdata;
|
||||
size_t length, b64len;
|
||||
Stream *datastream;
|
||||
char *mxc, *from_matrix, *jid;
|
||||
|
|
@ -885,7 +870,7 @@ IQGet(ParseeData *args, XMLElement *stanza)
|
|||
else if (XMLookForTKV(stanza, "query", "xmlns", "jabber:iq:version"))
|
||||
{
|
||||
XMLElement *iq_reply, *query;
|
||||
XMLElement *name, *version, *val;
|
||||
XMLElement *name, *version;
|
||||
char *from = HashMapGet(stanza->attrs, "from");
|
||||
char *to = HashMapGet(stanza->attrs, "to");
|
||||
char *id = HashMapGet(stanza->attrs, "id");
|
||||
|
|
@ -980,24 +965,22 @@ PresenceStanza(ParseeData *args, XMLElement *stanza)
|
|||
if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS)))
|
||||
{
|
||||
XMLElement *item = XMLookForUnique(user_info, "item");
|
||||
XMPPComponent *jabber = args->jabber;
|
||||
char *jid = item ? HashMapGet(item->attrs, "jid") : NULL;
|
||||
char *from, *best = jid ? jid : oid;
|
||||
char *from;
|
||||
char *type = HashMapGet(stanza->attrs, "type");
|
||||
|
||||
if (StrEquals(type, "unavailable"))
|
||||
{
|
||||
/* TODO: Treat as a ban if the role is outcast */
|
||||
/* TODO: Treat as a ban if the role is outcast.
|
||||
* Modify the code to be more accurate later. */
|
||||
char *room = ParseeGetBridgedRoom(args, stanza);
|
||||
char *decode_from = ParseeLookupJID(oid);
|
||||
char *from_matrix = ParseeDecodeMXID(decode_from);
|
||||
char *affiliation = HashMapGet(item->attrs, "affiliation");
|
||||
|
||||
if (!from_matrix || *from_matrix != '@')
|
||||
{
|
||||
Free(from_matrix);
|
||||
from_matrix = ParseeEncodeJID(args->config, oid, true);
|
||||
}
|
||||
|
||||
Log(LOG_INFO, "Acting on %s", from_matrix);
|
||||
Log(LOG_INFO, "OID=%s DF=%s", oid, decode_from);
|
||||
|
||||
if (StrEquals(affiliation, "outcast"))
|
||||
{
|
||||
|
|
@ -1097,11 +1080,11 @@ XMPPDispatcher(void *argp)
|
|||
{
|
||||
XMPPThread *thread = argp;
|
||||
ParseeData *args = thread->info->args;
|
||||
XMPPComponent *jabber = thread->info->jabber;
|
||||
|
||||
while (thread->info->running)
|
||||
{
|
||||
XMLElement *stanza = RetrieveStanza(thread);
|
||||
|
||||
if (!stanza)
|
||||
{
|
||||
UtilSleepMillis(1);
|
||||
|
|
@ -1116,7 +1099,6 @@ XMPPDispatcher(void *argp)
|
|||
}
|
||||
else if (StrEquals(stanza->name, "message"))
|
||||
{
|
||||
size_t i;
|
||||
if (!MessageStanza(args, stanza, thread))
|
||||
{
|
||||
continue;
|
||||
|
|
@ -1135,8 +1117,19 @@ XMPPDispatcher(void *argp)
|
|||
}
|
||||
XMLFreeElement(stanza);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct XMPPAwait {
|
||||
pthread_mutex_t cond_lock;
|
||||
pthread_cond_t condition;
|
||||
|
||||
XMLElement *stanza;
|
||||
} XMPPAwait;
|
||||
static pthread_mutex_t await_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static HashMap *await_table = NULL;
|
||||
|
||||
void *
|
||||
ParseeXMPPThread(void *argp)
|
||||
{
|
||||
|
|
@ -1144,9 +1137,10 @@ ParseeXMPPThread(void *argp)
|
|||
XMPPComponent *jabber = args->jabber;
|
||||
XMLElement *stanza = NULL;
|
||||
XMPPThreadInfo info;
|
||||
pthread_mutex_t stanzas_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
size_t i, j = 0;
|
||||
size_t i;
|
||||
|
||||
/* Initialise the await table */
|
||||
await_table = HashMapCreate();
|
||||
|
||||
/* Initialise the FIFO */
|
||||
info.stanzas = ArrayCreate();
|
||||
|
|
@ -1174,9 +1168,7 @@ ParseeXMPPThread(void *argp)
|
|||
|
||||
while (true)
|
||||
{
|
||||
char *to, *room, *from, *from_matrix;
|
||||
char *chat_id, *mroom_id;
|
||||
ssize_t cntr;
|
||||
char *from, *id;
|
||||
|
||||
stanza = XMLDecode(jabber->stream, false);
|
||||
if (!stanza)
|
||||
|
|
@ -1184,25 +1176,37 @@ ParseeXMPPThread(void *argp)
|
|||
continue;
|
||||
}
|
||||
|
||||
id = HashMapGet(stanza->attrs, "id");
|
||||
if (id)
|
||||
{
|
||||
XMPPAwait *await;
|
||||
/* Lock out the table to see if we're awaiting. */
|
||||
pthread_mutex_lock(&await_lock);
|
||||
if ((await = HashMapGet(await_table, id)))
|
||||
{
|
||||
await->stanza = stanza;
|
||||
pthread_mutex_lock(&await->cond_lock);
|
||||
pthread_cond_signal(&await->condition);
|
||||
pthread_mutex_unlock(&await->cond_lock);
|
||||
|
||||
HashMapDelete(await_table, id);
|
||||
|
||||
pthread_mutex_unlock(&await_lock);
|
||||
continue;
|
||||
}
|
||||
pthread_mutex_unlock(&await_lock);
|
||||
}
|
||||
|
||||
|
||||
if (StrEquals(stanza->name, "message") && XMPPIsKiller(stanza))
|
||||
{
|
||||
const char *killer = "killer";
|
||||
const char *suspend="suspend";
|
||||
from = HashMapGet(stanza->attrs, "from");
|
||||
if (!strncmp(from, killer, strlen(killer)))
|
||||
{
|
||||
XMLFreeElement(stanza);
|
||||
break;
|
||||
}
|
||||
else if (!strncmp(from, suspend, strlen(suspend)))
|
||||
{
|
||||
/* TODO */
|
||||
XMLFreeElement(stanza);
|
||||
pthread_mutex_lock(&cond_var_lock);
|
||||
pthread_cond_wait(&cond_var, &cond_var_lock);
|
||||
pthread_mutex_unlock(&cond_var_lock);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
PushStanza(&info, stanza);
|
||||
|
|
@ -1222,6 +1226,48 @@ ParseeXMPPThread(void *argp)
|
|||
}
|
||||
ArrayFree(info.stanzas);
|
||||
|
||||
HashMapFree(await_table);
|
||||
|
||||
pthread_mutex_destroy(&info.lock);
|
||||
return NULL;
|
||||
}
|
||||
XMLElement *
|
||||
ParseeAwaitStanza(char *identifier)
|
||||
{
|
||||
XMPPAwait awa, *await;
|
||||
XMLElement *stanza;
|
||||
if (!identifier)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO: Pthreads HATE me using Malloc here, so I'm abusing stackspace.
|
||||
* Not *too much* of a problem, just a weird oddity. If anyone has a clue
|
||||
* on why that happens (at least on ARM64 with a Pi4 running Debian), let
|
||||
* me know! */
|
||||
await = &awa;
|
||||
pthread_mutex_lock(&await_lock);
|
||||
|
||||
pthread_cond_init(&await->condition, NULL);
|
||||
pthread_mutex_init(&await->cond_lock, NULL);
|
||||
await->stanza = NULL;
|
||||
|
||||
HashMapSet(await_table, identifier, await);
|
||||
pthread_mutex_unlock(&await_lock);
|
||||
|
||||
pthread_mutex_lock(&await->cond_lock);
|
||||
while (!await->stanza)
|
||||
{
|
||||
pthread_cond_wait(&await->condition, &await->cond_lock);
|
||||
}
|
||||
|
||||
stanza = await->stanza;
|
||||
pthread_mutex_lock(&await_lock);
|
||||
pthread_mutex_unlock(&await_lock);
|
||||
|
||||
pthread_mutex_unlock(&await->cond_lock);
|
||||
|
||||
pthread_cond_destroy(&await->condition);
|
||||
pthread_mutex_destroy(&await->cond_lock);
|
||||
return stanza;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ extern void ASPing(const ParseeConfig *);
|
|||
|
||||
/* Joins a room from an ID and a given user we want to masquerade
|
||||
* as. */
|
||||
extern void ASJoin(const ParseeConfig *, char *, char *);
|
||||
extern char * ASJoin(const ParseeConfig *, char *, char *);
|
||||
|
||||
/* Bans from a room a specific user */
|
||||
extern void ASBan(const ParseeConfig *, char *, char *);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,12 @@
|
|||
); \
|
||||
char *id = GrabString(event, 1, "room_id")
|
||||
|
||||
#define BotRequired(prop) prop = HashMapGet(cmd->arguments, #prop); \
|
||||
if (!prop) \
|
||||
{ \
|
||||
ReplyBasic("Field `" #prop "` REQUIRED."); \
|
||||
}
|
||||
|
||||
#define BotDestroy() Free(profile)
|
||||
|
||||
#define ReplyBasic(rep) do \
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ extern void ParseeRequest(HttpServerContext *, void *);
|
|||
/* A pthread callback used for listening to a component */
|
||||
extern void * ParseeXMPPThread(void *data);
|
||||
|
||||
/* Wakes up the XMPP thread from a "suspend" killer stanza */
|
||||
extern void ParseeWakeupThread(void);
|
||||
/* Wait for a specific stanza with an ID */
|
||||
extern XMLElement * ParseeAwaitStanza(char *identifier);
|
||||
|
||||
/* Finds the room a DM is associated to, from a Matrix user and a Jabber
|
||||
* ID. */
|
||||
|
|
@ -204,7 +204,7 @@ extern void ParseeDestroyJIDTable(void);
|
|||
|
||||
/* Globally bans a Matrix user from ever interacting with Parsee, and bans
|
||||
* them from bridged rooms where the bot has administrator. */
|
||||
extern void ParseeGlobalBan(ParseeData *, char *user);
|
||||
extern void ParseeGlobalBan(ParseeData *, char *user, char *reason);
|
||||
|
||||
/* Verifies if a user was globally banned. If so, then apply actions to the
|
||||
* room ID */
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ typedef struct ParseeCmdArg {
|
|||
"Sets the power level to send a specific event " \
|
||||
"in a Parsee room" \
|
||||
) \
|
||||
X_COMMAND( \
|
||||
"plumb-muc", CmdPlumb, \
|
||||
"Plumbs an existing Matrix room to a MUC." \
|
||||
"You'll need to give the Parsee bot enough " \
|
||||
"privileges, however. " \
|
||||
) \
|
||||
X_COMMAND( \
|
||||
"help", CmdHelp, \
|
||||
"Shows the command list" \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue