[ADD/WIP] Matrix<->XMPP DMs.

There is still this fun issue with the blocking XML parser leaking
memory on pthread_cancel, I'll try to deal with that later, maybe with a
test stanza.

I also apologise for swearing in the last commit. I promise it won't
happen again, Prosody.
This commit is contained in:
LDA 2024-06-18 20:59:35 +02:00
commit 2cc4b0ed17
11 changed files with 325 additions and 63 deletions

View file

@ -24,7 +24,6 @@ ASVerifyRequest(ParseeHttpArg *arg)
if (!authorisation || strncmp(authorisation, "Bearer ", 7)) if (!authorisation || strncmp(authorisation, "Bearer ", 7))
{ {
Log(LOG_INFO, "Bad auth %s", authorisation);
HttpResponseStatus(arg->ctx, HTTP_FORBIDDEN); HttpResponseStatus(arg->ctx, HTTP_FORBIDDEN);
ret = MatrixCreateError("M_MISSING_TOKEN", "No 'hs_token' given in."); ret = MatrixCreateError("M_MISSING_TOKEN", "No 'hs_token' given in.");
goto end; goto end;
@ -42,7 +41,7 @@ end:
} }
void void
ASAuthenticateRequest(ParseeConfig *data, HttpClientContext *ctx) ASAuthenticateRequest(const ParseeConfig *data, HttpClientContext *ctx)
{ {
char *bearer; char *bearer;
if (!data || !ctx) if (!data || !ctx)
@ -55,7 +54,7 @@ ASAuthenticateRequest(ParseeConfig *data, HttpClientContext *ctx)
Free(bearer); Free(bearer);
} }
bool bool
ASRegisterUser(ParseeConfig *conf, char *user) ASRegisterUser(const ParseeConfig *conf, char *user)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
HashMap *json = NULL; HashMap *json = NULL;
@ -100,7 +99,6 @@ ASPing(const ParseeConfig *conf)
return; return;
} }
Log(LOG_NOTICE, "Pinging...");
path = StrConcat(3, path = StrConcat(3,
"/_matrix/client/v1/appservice/", "/_matrix/client/v1/appservice/",
"Parsee%20XMPP", "Parsee%20XMPP",
@ -118,14 +116,13 @@ ASPing(const ParseeConfig *conf)
JsonFree(json); JsonFree(json);
} }
void void
ASJoin(ParseeConfig *conf, char *id, char *masquerade) ASJoin(const ParseeConfig *conf, char *id, char *masquerade)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
HashMap *json = NULL; HashMap *json = NULL;
char *path; char *path;
if (!conf || !id || !masquerade) if (!conf || !id || !masquerade)
{ {
Log(LOG_ERR, "Bad values %p %p", conf, id);
return; return;
} }
@ -134,7 +131,6 @@ ASJoin(ParseeConfig *conf, char *id, char *masquerade)
"user_id=", masquerade "user_id=", masquerade
); );
Log(LOG_INFO, "%s", path);
ctx = ParseeCreateRequest( ctx = ParseeCreateRequest(
conf, conf,
HTTP_POST, path HTTP_POST, path
@ -148,40 +144,29 @@ ASJoin(ParseeConfig *conf, char *id, char *masquerade)
JsonFree(json); JsonFree(json);
} }
void void
ASSend(ParseeConfig *conf, char *id, char *user, char *type, HashMap *c) ASSend(const ParseeConfig *conf, char *id, char *user, char *type, HashMap *c)
{ {
HttpClientContext *ctx = NULL; HttpClientContext *ctx = NULL;
HashMap *json = NULL, *params_obj;
Stream *s;
char *path, *params; char *path, *params;
char *txn; char *txn;
if (!conf || !id || !type || !c) if (!conf || !id || !type || !user || !c)
{ {
return; return;
} }
txn = StrRandom(16); txn = StrRandom(16);
params_obj = HashMapCreate(); path = StrConcat(9,
if (user)
{
HashMapSet(params_obj, "user_id", user);
}
params = HttpParamEncode(params_obj);
HashMapFree(params_obj);
path = StrConcat(8,
"/_matrix/client/v3/rooms/", "/_matrix/client/v3/rooms/",
id, "/send/", type, "/", txn, "?", id, "/send/", type, "/", txn, "?",
params "user_id=", user
); );
Log(LOG_INFO, "Sending %s", path);
Free(params);
Free(txn); Free(txn);
ctx = ParseeCreateRequest(conf, HTTP_PUT, path); ctx = ParseeCreateRequest(conf, HTTP_PUT, path);
Free(path); Free(path);
json = HashMapCreate();
ASAuthenticateRequest(conf, ctx); ASAuthenticateRequest(conf, ctx);
Log(LOG_INFO, "%d", ParseeSetRequestJSON(ctx, c)); ParseeSetRequestJSON(ctx, c);
HttpClientContextFree(ctx); HttpClientContextFree(ctx);
JsonFree(c); JsonFree(c);
} }

View file

@ -61,7 +61,7 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
} }
HttpClientContext * HttpClientContext *
ParseeCreateRequest(ParseeConfig *conf, HttpRequestMethod meth, char *path) ParseeCreateRequest(const ParseeConfig *conf, HttpRequestMethod meth, char *path)
{ {
HttpClientContext *ctx; HttpClientContext *ctx;
if (!conf || !path) if (!conf || !path)

View file

@ -1,6 +1,7 @@
#include <Cytoplasm/HttpServer.h> #include <Cytoplasm/HttpServer.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Log.h>
#include <pthread.h>
#include <string.h> #include <string.h>
#include <signal.h> #include <signal.h>
@ -10,6 +11,7 @@
#include <AS.h> #include <AS.h>
static HttpServer *server = NULL; static HttpServer *server = NULL;
static pthread_t xmpp_thr;
static void static void
SignalHandler(int signal) SignalHandler(int signal)
{ {
@ -26,6 +28,7 @@ SignalHandler(int signal)
return; return;
} }
HttpServerStop(server); HttpServerStop(server);
pthread_cancel(xmpp_thr);
break; break;
} }
} }
@ -59,7 +62,6 @@ Main(void)
parsee_conf->shared_comp_secret parsee_conf->shared_comp_secret
); );
} }
Log(LOG_INFO, "HS token: %s", parsee_conf->hs_token);
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart); ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
@ -70,6 +72,12 @@ Main(void)
conf.handlerArgs = ParseeInitData(jabber); conf.handlerArgs = ParseeInitData(jabber);
conf.handler = ParseeRequest; conf.handler = ParseeRequest;
if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs))
{
Log(LOG_ERR, "Couldn't start XMPP listener thread.");
/* TODO: Die */
}
sigAction.sa_handler = SignalHandler; sigAction.sa_handler = SignalHandler;
sigfillset(&sigAction.sa_mask); sigfillset(&sigAction.sa_mask);
sigAction.sa_flags = SA_RESTART; sigAction.sa_flags = SA_RESTART;

View file

@ -15,6 +15,7 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
char *state_key = GrabString(event, 1, "state_key"); char *state_key = GrabString(event, 1, "state_key");
char *membership = GrabString(event, 2, "content", "membership"); char *membership = GrabString(event, 2, "content", "membership");
char *room_id = GrabString(event, 1, "room_id"); char *room_id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender");
const ParseeConfig *conf = data->config; const ParseeConfig *conf = data->config;
Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership); Log(LOG_INFO, "Membership '%s'->'%s'", state_key, membership);
@ -23,28 +24,22 @@ ParseeMemberHandler(ParseeData *data, HashMap *event)
DbRef *ref; DbRef *ref;
HashMap *json; HashMap *json;
char *jid; char *jid;
Log(LOG_INFO, "Looks like %s was invited to %s",
state_key,
room_id
);
ASJoin(conf, room_id, state_key); ASJoin(conf, room_id, state_key);
ref = DbCreate(data->db, 3, "rooms", room_id, "data");
json = DbJson(ref);
Log(LOG_INFO, "Grabbing JID");
jid = ParseeDecodeLocalJID(conf, state_key); jid = ParseeDecodeLocalJID(conf, state_key);
Log(LOG_INFO, "JID: %s", jid); ref = DbCreate(data->db, 3, "rooms", room_id, "data");
json = DbJson(ref);
HashMapSet(json, "is_direct", JsonValueBoolean(true)); HashMapSet(json, "is_direct", JsonValueBoolean(true));
HashMapSet(json, "xmpp_user", JsonValueString(jid)); HashMapSet(json, "xmpp_user", JsonValueString(jid));
DbUnlock(data->db, ref);
ParseePushDMRoom(data, sender, jid, room_id);
/* Pretend everything is a dm, ignoring is_direct */ /* Pretend everything is a dm, ignoring is_direct */
if (jid) if (jid)
{ {
Free(jid); Free(jid);
} }
DbUnlock(data->db, ref);
} }
} }
static void static void
@ -59,9 +54,16 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
char *id = GrabString(event, 1, "room_id"); char *id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender"); char *sender = GrabString(event, 1, "sender");
if (ParseeIsPuppet(data->config, sender))
{
Log(LOG_INFO, "Do not bridge the puppet");
return;
}
ref = DbLock(data->db, 3, "rooms", id, "data"); ref = DbLock(data->db, 3, "rooms", id, "data");
json = DbJson(ref); json = DbJson(ref);
if (JsonValueAsBoolean(HashMapGet(json, "is_direct"))) if (JsonValueAsBoolean(HashMapGet(json, "is_direct")))
{ {
char *user = GrabString(json, 1, "xmpp_user"); char *user = GrabString(json, 1, "xmpp_user");
@ -85,11 +87,6 @@ ParseeEventHandler(ParseeData *data, HashMap *event)
return; return;
} }
Log(LOG_INFO, "Event by '%s', with type '%s'",
JsonValueAsString(HashMapGet(event, "sender")),
JsonValueAsString(HashMapGet(event, "type"))
);
event_type = GrabString(event, 1, "type"); event_type = GrabString(event, 1, "type");
if (StrEquals(event_type, "m.room.member")) if (StrEquals(event_type, "m.room.member"))
{ {

View file

@ -1,9 +1,13 @@
#include <Parsee.h> #include <Parsee.h>
#include <Cytoplasm/Memory.h> #include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h> #include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h> #include <Cytoplasm/Sha.h>
#include <string.h> #include <string.h>
#include <stdlib.h>
#include <ctype.h>
bool bool
ParseeIsPuppet(const ParseeConfig *conf, char *user) ParseeIsPuppet(const ParseeConfig *conf, char *user)
@ -99,6 +103,54 @@ ParseeDecodeLocalJID(const ParseeConfig *c, char *mxid)
/* Until the ':', data_start now is an encoded JID */ /* Until the ':', data_start now is an encoded JID */
return DecodeJID(data_start, ':'); return DecodeJID(data_start, ':');
} }
char *
ParseeEncodeJID(const ParseeConfig *c, char *jid)
{
char *ret, *tmp;
size_t i;
if (!c || !jid)
{
return NULL;
}
ret = StrConcat(2, c->namespace_base, "_l_");
for (i = 0; i < strlen(jid); i++)
{
const char *HEX = "0123456789abcdef";
char cpy = jid[i];
char cs[4] = { 0 };
cs[0] = cpy;
if (*cs == '/')
{
/* RID: Break everything and die. */
break;
}
if (islower(*cs) || isalpha(*cs) || *cs == '_' ||
*cs == '=' || *cs == '-' || *cs == '/' ||
*cs == '+' || *cs == '.')
{
tmp = ret;
ret = StrConcat(2, ret, cs);
Free(tmp);
continue;
}
/* Hex encode everything */
cs[0] = '=';
cs[1] = HEX[(cpy >> 4) & 0xF];
cs[2] = HEX[(cpy >> 0) & 0xF];
tmp = ret;
ret = StrConcat(2, ret, cs);
Free(tmp);
}
tmp = ret;
ret = StrConcat(4, "@", ret, ":", c->homeserver_host);
Free(tmp);
return ret;
}
char * char *
ParseeGetLocal(char *mxid) ParseeGetLocal(char *mxid)
{ {
@ -129,6 +181,8 @@ ParseeGetLocal(char *mxid)
return cpy; return cpy;
} }
/* TODO: More robust system */
char * char *
ParseeEncodeMXID(char *mxid) ParseeEncodeMXID(char *mxid)
{ {
@ -155,3 +209,119 @@ ParseeEncodeMXID(char *mxid)
return ret; return ret;
} }
char *
ParseeDecodeMXID(char *mxid)
{
char *ret;
size_t i;
if (!mxid)
{
return NULL;
}
ret = Malloc(strlen(mxid) + 1);
for (i = 0; i < strlen(mxid) + 1; i++)
{
char src = mxid[i];
if (src == '%')
{
src = '@';
}
else if (src == '=')
{
src = ':';
}
else if (src == '@')
{
ret[i] = '\0';
break;
}
ret[i] = src;
}
return ret;
}
char *
ParseeGetDMID(char *mxid, char *jid)
{
char *concat, *sha;
unsigned char *raw;
if (!mxid || !jid)
{
return NULL;
}
jid = ParseeTrimJID(jid);
concat = StrConcat(2, mxid, jid);
raw = Sha1(concat);
sha = ShaToHex(raw);
Free(concat);
Free(raw);
Free(jid);
return sha;
}
char *
ParseeFindDMRoom(ParseeData *data, char *mxid, char *jid)
{
DbRef *ref;
HashMap *j;
char *room, *dmid;
if (!data || !mxid || !jid)
{
return NULL;
}
dmid = ParseeGetDMID(mxid, jid);
ref = DbLock(data->db, 2, "users", dmid);
j = DbJson(ref);
room = StrDuplicate(JsonValueAsString(HashMapGet(j, "room")));
DbUnlock(data->db, ref);
Free(dmid);
return room;
}
void
ParseePushDMRoom(ParseeData *d, char *mxid, char *jid, char *r)
{
DbRef *ref;
HashMap *j;
char *dmid;
if (!d || !mxid || !jid || !r)
{
return;
}
dmid = ParseeGetDMID(mxid, jid);
ref = DbCreate(d->db, 2, "users", dmid);
j = DbJson(ref);
HashMapSet(j, "room", JsonValueString(r));
DbUnlock(d->db, ref);
Free(dmid);
return;
}
char *
ParseeTrimJID(char *jid)
{
char *ret;
size_t i;
if (!jid)
{
return NULL;
}
ret = StrDuplicate(jid);
for (i = 0; i < strlen(ret); i++)
{
if (ret[i] == '/')
{
ret[i] = '\0';
break;
}
}
return ret;
}

View file

@ -13,11 +13,9 @@ RouteHead(RouteTxns, arr, argp)
Array *events; Array *events;
size_t i; size_t i;
Log(LOG_INFO, "Caught one!");
response = ASVerifyRequest(args); response = ASVerifyRequest(args);
if (response) if (response)
{ {
Log(LOG_INFO, ":(");
goto end; goto end;
} }
if (HttpRequestMethodGet(args->ctx) != HTTP_PUT) if (HttpRequestMethodGet(args->ctx) != HTTP_PUT)
@ -30,7 +28,6 @@ RouteHead(RouteTxns, arr, argp)
goto end; goto end;
} }
Log(LOG_INFO, "Caught a decent one!");
RequestJSON(); RequestJSON();
/* TODO: Do a thing with these. */ /* TODO: Do a thing with these. */
@ -44,7 +41,6 @@ RouteHead(RouteTxns, arr, argp)
/* TODO: Store TXN ID somewhere so that we can commit /* TODO: Store TXN ID somewhere so that we can commit
* "Epic Matrix Idempotency" */ * "Epic Matrix Idempotency" */
Log(LOG_INFO, "OK!");
response = HashMapCreate(); response = HashMapCreate();
end: end:
JsonFree(request); JsonFree(request);

View file

@ -1,9 +1,5 @@
#include <XML.h> #include <XML.h>
#include <Cytoplasm/Log.h>
/* TODO: The rest of all that. */
XMLElement * XMLElement *
XMLDecode(Stream *stream, bool autofree) XMLDecode(Stream *stream, bool autofree)
{ {
@ -27,12 +23,12 @@ XMLDecode(Stream *stream, bool autofree)
stack = ArrayCreate(); stack = ArrayCreate();
while ((event = XMLCrank(lexer)) && (!ret || ArraySize(stack))) while ((event = XMLCrank(lexer)) && (!ret || ArraySize(stack)))
{ {
bool flag = false;
switch (event->type) switch (event->type)
{ {
case XML_LEXER_STARTELEM: case XML_LEXER_STARTELEM:
/* Create a new element that will populated. */ /* Create a new element that will populated. */
top = XMLCreateTag(event->element); top = XMLCreateTag(event->element);
Log(LOG_INFO, "<%s>", top->name);
XMLAddChild(peek(), top); XMLAddChild(peek(), top);
while (HashMapIterate(event->attrs, &key, (void **) &val)) while (HashMapIterate(event->attrs, &key, (void **) &val))
{ {
@ -50,7 +46,6 @@ XMLDecode(Stream *stream, bool autofree)
/* Create a new element that will populated. */ /* Create a new element that will populated. */
top = XMLCreateTag(event->element); top = XMLCreateTag(event->element);
XMLAddChild(peek(), top); XMLAddChild(peek(), top);
Log(LOG_INFO, "<%s />", top->name);
while (HashMapIterate(event->attrs, &key, (void **) &val)) while (HashMapIterate(event->attrs, &key, (void **) &val))
{ {
XMLAddAttr(top, key, val); XMLAddAttr(top, key, val);
@ -65,11 +60,25 @@ XMLDecode(Stream *stream, bool autofree)
/* Fallthrough */ /* Fallthrough */
case XML_LEXER_ENDELEM: case XML_LEXER_ENDELEM:
/* Pop out an element out of the DOM. */ /* Pop out an element out of the DOM. */
Log(LOG_INFO, "</>");
pop(); pop();
if (!ArraySize(stack))
{
XMLFreeEvent(event);
event = NULL;
flag = true;
break;
}
break;
case XML_LEXER_DATA:
top = XMLCreateText(event->data);
XMLAddChild(peek(), top);
break; break;
default: break; default: break;
} }
if (flag)
{
break;
}
XMLFreeEvent(event); XMLFreeEvent(event);
event = NULL; event = NULL;
} }

View file

@ -658,8 +658,6 @@ XMLParseAttQuote(XMLexer *lexer)
point = XMLInitialiseBuffer(lexer); point = XMLInitialiseBuffer(lexer);
/* TODO: My Prosody is actually trolling me.
* GIVE ME A FUCKING STREAM ID YOU RETARD. */
while ((c = XMLGetc(lexer))) while ((c = XMLGetc(lexer)))
{ {
if (!IsNormalQ(c)) if (!IsNormalQ(c))

75
src/XMPPThread.c Normal file
View file

@ -0,0 +1,75 @@
#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <Matrix.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void *
ParseeXMPPThread(void *argp)
{
ParseeData *args = argp;
XMPPComponent *jabber = args->jabber;
XMLElement *stanza = NULL;
while (true)
{
/* TODO: pthread cancelation points */
XMLElement *body = NULL;
XMLElement *data = NULL;
char *to, *room, *from, *from_matrix;
/* Decoding XML is blocking, which will cause memory issues when we
* want to murder this thread.
* We could ping the server, and die on a "response" stanza, however.
*/
stanza = XMLDecode(jabber->stream, false);
if (!stanza)
{
ASSend(
args->config, "!jdHnoKgHCm51ujbw:ari.lt", "_parsee_bridge",
"m.room.message", MatrixCreateNotice("Hi.")
);
continue;
}
if (StrEquals(stanza->name, "message"))
{
size_t i;
for (i = 0; i < ArraySize(stanza->children); i++)
{
XMLElement *child = ArrayGet(stanza->children, i);
if (StrEquals(child->name, "body"))
{
body = child;
break;
}
}
if (!body)
{
XMLFreeElement(stanza);
continue;
}
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
from = HashMapGet(stanza->attrs, "from");
from_matrix = ParseeEncodeJID(args->config, from);
room = ParseeFindDMRoom(args, to, from);
data = ArrayGet(body->children, 0);
/* TODO: Manage that out. $[to] corresponds to a Matrix
* user, which we need to find the */
ASSend(
args->config, room, from_matrix,
"m.room.message", MatrixCreateNotice(data->data)
);
Free(from_matrix);
Free(room);
Free(to);
}
XMLFreeElement(stanza);
}
return NULL;
}

View file

@ -13,21 +13,21 @@ extern HashMap * ASVerifyRequest(ParseeHttpArg *);
/* Authenticates a request with the correct as_token. /* Authenticates a request with the correct as_token.
* It does not send the request, however. */ * It does not send the request, however. */
extern void ASAuthenticateRequest(ParseeConfig *, HttpClientContext *); extern void ASAuthenticateRequest(const ParseeConfig *, HttpClientContext *);
/* Registers an user through the Application Service API. Returns /* Registers an user through the Application Service API. Returns
* true if the user was created. */ * true if the user was created. */
extern bool ASRegisterUser(ParseeConfig *, char *); extern bool ASRegisterUser(const ParseeConfig *, char *);
/* Pings the homeserver to get attention. */ /* Pings the homeserver to get attention. */
extern void ASPing(const ParseeConfig *); extern void ASPing(const ParseeConfig *);
/* Joins a room from an ID and a given user we want to masquerade /* Joins a room from an ID and a given user we want to masquerade
* as. */ * as. */
extern void ASJoin(ParseeConfig *, char *, char *); extern void ASJoin(const ParseeConfig *, char *, char *);
/* Sends a message event with a specific type and body. /* Sends a message event with a specific type and body.
* Said body is freed during the function's execution. */ * Said body is freed during the function's execution. */
extern void ASSend(ParseeConfig *, char *, char *, char *, HashMap *); extern void ASSend(const ParseeConfig *, char *, char *, char *, HashMap *);
#endif #endif

View file

@ -62,10 +62,7 @@ extern void ParseeConfigFree(void);
extern ParseeData * ParseeInitData(XMPPComponent *); extern ParseeData * ParseeInitData(XMPPComponent *);
extern void ParseeFreeData(ParseeData *); extern void ParseeFreeData(ParseeData *);
/* HTTP server handler for Parsee, takes in a config. */ extern HttpClientContext * ParseeCreateRequest(const ParseeConfig *, HttpRequestMethod, char *);
extern void ParseeRequest(HttpServerContext *, void *);
extern HttpClientContext * ParseeCreateRequest(ParseeConfig *, HttpRequestMethod, char *);
/* Sends headers, and writes the JSON object. */ /* Sends headers, and writes the JSON object. */
extern HttpStatus ParseeSetRequestJSON(HttpClientContext *, HashMap *); extern HttpStatus ParseeSetRequestJSON(HttpClientContext *, HashMap *);
@ -79,9 +76,36 @@ extern bool ParseeIsPuppet(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 *);
/* Encodes a JID into a Parsee localpart */
extern char * ParseeEncodeJID(const ParseeConfig *, char *);
/* Gets the localpart of a MXID */ /* Gets the localpart of a MXID */
extern char * ParseeGetLocal(char *); extern char * ParseeGetLocal(char *);
/* Encodes an MXID to a valid Jabber ID head */ /* Encodes an MXID to a valid Jabber ID head */
extern char * ParseeEncodeMXID(char *); extern char * ParseeEncodeMXID(char *);
/* Decodes a Jabber ID head into a valid MXID */
extern char * ParseeDecodeMXID(char *);
/* HTTP server handler for Parsee, takes in a config. */
extern void ParseeRequest(HttpServerContext *, void *);
/* A pthread callback used for listening to a component */
extern void * ParseeXMPPThread(void *data);
/* Finds the room a DM is associated to, from a Matrix user and a Jabber
* ID. */
extern char * ParseeFindDMRoom(ParseeData *data, char *mxid, char *jid);
/* Gets a DM ID from a Matrix and Jabber user */
extern char * ParseeGetDMID(char *mxid, char *jid);
/* Creates a DM mapping in the database */
extern void ParseePushDMRoom(ParseeData *, char *mxid, char *jid, char *r);
/* Trims the component from a JID */
extern char * ParseeTrimJID(char *jid);
#endif #endif