mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 13:45:10 +00:00
[ADD/MOD] XMPP->Matrix media bridge, small cleanup
This commit is contained in:
parent
4de7227ee7
commit
42d69226f0
14 changed files with 327 additions and 54 deletions
105
src/AS.c
105
src/AS.c
|
|
@ -3,8 +3,10 @@
|
|||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/Uri.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <Matrix.h>
|
||||
|
||||
|
|
@ -305,11 +307,11 @@ ASGetName(const ParseeConfig *c, char *room, char *user)
|
|||
|
||||
reply = JsonDecode(HttpClientStream(ctx));
|
||||
|
||||
JsonFree(reply);
|
||||
ret = StrDuplicate(
|
||||
JsonValueAsString(HashMapGet(reply, "displayname"))
|
||||
);
|
||||
HttpClientContextFree(ctx);
|
||||
JsonFree(reply);
|
||||
Free(path);
|
||||
|
||||
if (!ret)
|
||||
|
|
@ -321,3 +323,104 @@ ASGetName(const ParseeConfig *c, char *room, char *user)
|
|||
|
||||
return NULL;
|
||||
}
|
||||
char *
|
||||
ASUpload(const ParseeConfig *c, Stream *from, unsigned int size)
|
||||
{
|
||||
char *size_str, *path, *ret, *user;
|
||||
int i;
|
||||
HttpClientContext *ctx;
|
||||
HashMap *reply;
|
||||
if (!c || !from)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_str = StrInt(size);
|
||||
user = StrConcat(4, "@",c->sender_localpart,":",c->homeserver_host);
|
||||
path = StrConcat(2,
|
||||
"/_matrix/media/v3/upload?user_id=", user
|
||||
);
|
||||
ctx = ParseeCreateRequest(c, HTTP_POST, path);
|
||||
ASAuthenticateRequest(c, ctx);
|
||||
if (size)
|
||||
{
|
||||
HttpRequestHeader(ctx, "Content-Length", size_str);
|
||||
}
|
||||
HttpRequestSendHeaders(ctx);
|
||||
|
||||
StreamCopy(from, HttpClientStream(ctx));
|
||||
HttpRequestSend(ctx);
|
||||
|
||||
reply = JsonDecode(HttpClientStream(ctx));
|
||||
ret = StrDuplicate(
|
||||
JsonValueAsString(HashMapGet(reply, "content_uri"))
|
||||
);
|
||||
HttpClientContextFree(ctx);
|
||||
JsonFree(reply);
|
||||
Free(size_str);
|
||||
Free(path);
|
||||
Free(user);
|
||||
|
||||
return ret;
|
||||
}
|
||||
char *
|
||||
ASReupload(const ParseeConfig *c, char *from, char **mime)
|
||||
{
|
||||
Uri *uri;
|
||||
HttpClientContext *ctx;
|
||||
unsigned short port;
|
||||
int size = 0, flags = HTTP_FLAG_NONE;
|
||||
int i;
|
||||
char *ret, *content_len;
|
||||
|
||||
if (!c || !from)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uri = UriParse(from);
|
||||
if (!uri)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (uri->port)
|
||||
{
|
||||
port = uri->port;
|
||||
}
|
||||
else if (StrEquals(uri->proto, "https"))
|
||||
{
|
||||
port = 443;
|
||||
}
|
||||
else
|
||||
{
|
||||
port = 80;
|
||||
}
|
||||
|
||||
if (StrEquals(uri->proto, "https"))
|
||||
{
|
||||
flags |= HTTP_FLAG_TLS;
|
||||
}
|
||||
|
||||
ctx = HttpRequest(
|
||||
HTTP_GET, flags, port, uri->host, uri->path
|
||||
);
|
||||
HttpRequestSendHeaders(ctx);
|
||||
HttpRequestSend(ctx);
|
||||
|
||||
if (mime)
|
||||
{
|
||||
*mime = HashMapGet(HttpResponseHeaders(ctx), "content-type");
|
||||
*mime = StrDuplicate(*mime);
|
||||
}
|
||||
|
||||
content_len = HashMapGet(HttpResponseHeaders(ctx), "content-length");
|
||||
if (content_len)
|
||||
{
|
||||
size = strtol(content_len, NULL, 10);
|
||||
}
|
||||
ret = ASUpload(c, HttpClientStream(ctx), size);
|
||||
|
||||
HttpClientContextFree(ctx);
|
||||
UriFree(uri);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
53
src/Events.c
53
src/Events.c
|
|
@ -1,5 +1,10 @@
|
|||
#include <Matrix.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
HashMap *
|
||||
MatrixCreateNotice(char *body)
|
||||
{
|
||||
|
|
@ -61,3 +66,51 @@ MatrixCreateNickChange(char *nick)
|
|||
|
||||
return map;
|
||||
}
|
||||
HashMap *
|
||||
MatrixCreateMedia(char *mxc, char *body, char *mime)
|
||||
{
|
||||
HashMap *map;
|
||||
char *mime_type = NULL, *matrix_type = NULL;
|
||||
if (!mxc || !body)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
matrix_type = "m.file";
|
||||
if (mime)
|
||||
{
|
||||
size_t i;
|
||||
mime_type = StrDuplicate(mime);
|
||||
for (i = 0; i < strlen(mime); i++)
|
||||
{
|
||||
if (mime_type[i] == '/')
|
||||
{
|
||||
mime_type[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (StrEquals(mime_type, "image"))
|
||||
{
|
||||
matrix_type = "m.image";
|
||||
}
|
||||
else if (StrEquals(mime_type, "video"))
|
||||
{
|
||||
matrix_type = "m.video";
|
||||
}
|
||||
else if (StrEquals(mime_type, "audio"))
|
||||
{
|
||||
matrix_type = "m.audio";
|
||||
}
|
||||
|
||||
Free(mime_type);
|
||||
}
|
||||
|
||||
map = HashMapCreate();
|
||||
HashMapSet(map, "msgtype", JsonValueString(matrix_type));
|
||||
HashMapSet(map, "mimetype", JsonValueString(mime));
|
||||
HashMapSet(map, "body", JsonValueString(body));
|
||||
HashMapSet(map, "url", JsonValueString(mxc));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <Cytoplasm/Cytoplasm.h>
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Util.h>
|
||||
#include <Cytoplasm/Cron.h>
|
||||
#include <Cytoplasm/Log.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ Main(void)
|
|||
ParseeData *data = NULL;
|
||||
const ParseeConfig *parsee_conf;
|
||||
Stream *yaml;
|
||||
Cron *cron = NULL;
|
||||
|
||||
Log(LOG_INFO,
|
||||
"%s - v%s (Cytoplasm %s)",
|
||||
|
|
@ -63,6 +65,7 @@ Main(void)
|
|||
Log(LOG_NOTICE, "Setting up local Matrix user...");
|
||||
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
|
||||
|
||||
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
conf.port = parsee_conf->port;
|
||||
conf.threads = 4;
|
||||
|
|
@ -70,6 +73,10 @@ Main(void)
|
|||
conf.handlerArgs = ParseeInitData(jabber);
|
||||
conf.handler = ParseeRequest;
|
||||
|
||||
Log(LOG_NOTICE, "Starting up local cronjobs...");
|
||||
cron = CronCreate( 10 SECONDS );
|
||||
CronEvery(cron, 30 MINUTES, ParseeCleanup, conf.handlerArgs);
|
||||
|
||||
Log(LOG_NOTICE, "Creating XMPP listener thread...");
|
||||
if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs))
|
||||
{
|
||||
|
|
@ -98,6 +105,8 @@ end:
|
|||
Log(LOG_INFO, "Exiting...");
|
||||
HttpServerFree(server);
|
||||
ParseeConfigFree();
|
||||
CronStop(cron);
|
||||
CronFree(cron);
|
||||
ParseeFreeData(conf.handlerArgs);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,3 +44,12 @@ ParseeFreeData(ParseeData *data)
|
|||
HttpRouterFree(data->router);
|
||||
Free(data);
|
||||
}
|
||||
|
||||
void
|
||||
ParseeCleanup(void *datp)
|
||||
{
|
||||
ParseeData *data = datp;
|
||||
|
||||
Log(LOG_NOTICE, "Cleaning up...");
|
||||
/* TODO */
|
||||
}
|
||||
|
|
@ -131,3 +131,26 @@ XMLookForUnique(XMLElement *parent, char *tag)
|
|||
|
||||
return NULL;
|
||||
}
|
||||
XMLElement *
|
||||
XMLookForTKV(XMLElement *parent, char *tag, char *k, char *v)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!parent || !tag || !k || !v)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(parent->children); i++)
|
||||
{
|
||||
XMLElement *child = ArrayGet(parent->children, i);
|
||||
HashMap *attrs = child->attrs;
|
||||
char *value = HashMapGet(attrs, k);
|
||||
if (StrEquals(child->name, tag) && StrEquals(value, v))
|
||||
{
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
XMLElement *
|
||||
XMLDecode(Stream *stream, bool autofree)
|
||||
{
|
||||
/* TODO: Use the existing SAX parser to decode everything */
|
||||
#define push(x) ArrayAdd(stack, x)
|
||||
#define pop(x) ArrayDelete(stack, ArraySize(stack) - 1)
|
||||
#define peek(x) ArrayGet(stack, ArraySize(stack) - 1)
|
||||
|
|
@ -14,6 +13,7 @@ XMLDecode(Stream *stream, bool autofree)
|
|||
XMLElement *ret = NULL;
|
||||
XMLElement *top;
|
||||
char *key, *val;
|
||||
size_t i;
|
||||
|
||||
Array *stack;
|
||||
|
||||
|
|
@ -85,6 +85,15 @@ XMLDecode(Stream *stream, bool autofree)
|
|||
event = NULL;
|
||||
}
|
||||
XMLFreeEvent(event);
|
||||
for (i = 0; i < ArraySize(stack); i++)
|
||||
{
|
||||
XMLElement *e = ArrayGet(stack, i);
|
||||
if (e == ret)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
XMLFreeElement(e);
|
||||
}
|
||||
ArrayFree(stack);
|
||||
XMLFreeLexer(lexer);
|
||||
return ret;
|
||||
|
|
@ -158,8 +167,6 @@ XMLEncodeTag(Stream *stream, XMLElement *element)
|
|||
void
|
||||
XMLEncode(Stream *stream, XMLElement *element)
|
||||
{
|
||||
/* TODO: Write the entire XML element. This shouldn't be
|
||||
* too hard. */
|
||||
if (!stream || !element)
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -160,6 +160,9 @@ XMPPAuthenticateCompStream(XMPPComponent *comp, char *shared)
|
|||
!StrEquals(ev->element, "handshake"))
|
||||
{
|
||||
Log(LOG_ERR, "Excepted empty handshake reply, got nonsense.");
|
||||
Log(LOG_ERR, "Another service (possibly Parsee) may have taken over.");
|
||||
Log(LOG_ERR, "");
|
||||
Log(LOG_ERR, "Simply jealous of that other service...");
|
||||
Free(stream_id);
|
||||
Free(handshake);
|
||||
XMLFreeEvent(ev);
|
||||
|
|
|
|||
149
src/XMPPThread.c
149
src/XMPPThread.c
|
|
@ -33,6 +33,95 @@ ParseeDMHandler(char *room, char *from, XMLElement *data, const ParseeConfig *c)
|
|||
);
|
||||
}
|
||||
|
||||
static bool
|
||||
MessageStanza(ParseeData *args, XMLElement *stanza)
|
||||
{
|
||||
XMPPComponent *jabber = args->jabber;
|
||||
|
||||
XMLElement *body = NULL;
|
||||
XMLElement *data = NULL;
|
||||
XMLElement *stanza_id = NULL;
|
||||
|
||||
char *to, *room, *from, *from_matrix;
|
||||
char *chat_id, *mroom_id;
|
||||
size_t i;
|
||||
body = XMLookForUnique(stanza, "body");
|
||||
if (!body)
|
||||
{
|
||||
XMLFreeElement(stanza);
|
||||
return false;
|
||||
}
|
||||
stanza_id = XMLookForUnique(stanza, "stanza-id");
|
||||
|
||||
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
|
||||
from = HashMapGet(stanza->attrs, "from");
|
||||
from_matrix = ParseeEncodeJID(args->config, from, true);
|
||||
room = ParseeFindDMRoom(args, to, from);
|
||||
data = ArrayGet(body->children, 0);
|
||||
|
||||
/* TODO: Consider having rich messages. */
|
||||
chat_id = ParseeGetFromMUCID(args, from);
|
||||
mroom_id = ParseeGetRoomID(args, chat_id);
|
||||
if (room)
|
||||
{
|
||||
ParseeDMHandler(room, from_matrix, data, args->config);
|
||||
}
|
||||
if (mroom_id && !XMPPIsParseeStanza(stanza))
|
||||
{
|
||||
char *res = ParseeGetResource(from);
|
||||
char *encoded = ParseeEncodeJID(args->config, from, false);
|
||||
char *s_id_str = HashMapGet(stanza_id->attrs, "id");
|
||||
|
||||
/* TODO: Create smarter puppet names */
|
||||
if (ParseeVerifyStanza(args, chat_id, s_id_str))
|
||||
{
|
||||
XMLElement *oob, *oob_data;
|
||||
|
||||
ASRegisterUser(args->config, encoded);
|
||||
ASSetName(args->config, encoded, res);
|
||||
ASJoin(args->config, mroom_id, encoded);
|
||||
|
||||
/* Check if it is a media link */
|
||||
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
|
||||
if (oob)
|
||||
{
|
||||
char *mxc, *mime = NULL;
|
||||
HashMap *content = NULL;
|
||||
oob_data = XMLookForUnique(oob, "url");
|
||||
oob_data = ArrayGet(oob_data->children, 0);
|
||||
|
||||
mxc = ASReupload(args->config, oob_data->data, &mime);
|
||||
content = MatrixCreateMedia(mxc, data->data, mime);
|
||||
|
||||
ASSend(
|
||||
args->config, mroom_id, encoded,
|
||||
"m.room.message", content
|
||||
);
|
||||
Free(mime);
|
||||
Free(mxc);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSend(
|
||||
args->config, mroom_id, encoded,
|
||||
"m.room.message", MatrixCreateMessage(data->data)
|
||||
);
|
||||
}
|
||||
ParseePushStanza(args, chat_id, s_id_str);
|
||||
}
|
||||
|
||||
Free(res);
|
||||
Free(encoded);
|
||||
}
|
||||
Free(chat_id);
|
||||
Free(mroom_id);
|
||||
Free(from_matrix);
|
||||
Free(room);
|
||||
Free(to);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void *
|
||||
ParseeXMPPThread(void *argp)
|
||||
{
|
||||
|
|
@ -41,9 +130,6 @@ ParseeXMPPThread(void *argp)
|
|||
XMLElement *stanza = NULL;
|
||||
while (true)
|
||||
{
|
||||
XMLElement *body = NULL;
|
||||
XMLElement *data = NULL;
|
||||
XMLElement *stanza_id = NULL;
|
||||
char *to, *room, *from, *from_matrix;
|
||||
char *chat_id, *mroom_id;
|
||||
|
||||
|
|
@ -55,6 +141,7 @@ ParseeXMPPThread(void *argp)
|
|||
|
||||
if (StrEquals(stanza->name, "presence"))
|
||||
{
|
||||
/* TODO: Manage presence */
|
||||
XMLFreeElement(stanza);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -80,56 +167,18 @@ ParseeXMPPThread(void *argp)
|
|||
continue;
|
||||
}
|
||||
}
|
||||
body = XMLookForUnique(stanza, "body");
|
||||
if (!body)
|
||||
|
||||
if (!MessageStanza(args, stanza))
|
||||
{
|
||||
XMLFreeElement(stanza);
|
||||
continue;
|
||||
}
|
||||
stanza_id = XMLookForUnique(stanza, "stanza-id");
|
||||
|
||||
to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
|
||||
from = HashMapGet(stanza->attrs, "from");
|
||||
from_matrix = ParseeEncodeJID(args->config, from, true);
|
||||
room = ParseeFindDMRoom(args, to, from);
|
||||
data = ArrayGet(body->children, 0);
|
||||
|
||||
/* TODO: Consider having rich messages, and move that out
|
||||
* to separate functions */
|
||||
chat_id = ParseeGetFromMUCID(args, from);
|
||||
mroom_id = ParseeGetRoomID(args, chat_id);
|
||||
if (room)
|
||||
{
|
||||
ParseeDMHandler(room, from_matrix, data, args->config);
|
||||
}
|
||||
if (mroom_id && !XMPPIsParseeStanza(stanza))
|
||||
{
|
||||
char *res = ParseeGetResource(from);
|
||||
char *encoded = ParseeEncodeJID(args->config, from, false);
|
||||
char *s_id_str = HashMapGet(stanza_id->attrs, "id");
|
||||
|
||||
/* TODO: Create smarter puppet names, and send presence
|
||||
* in every room at Parsee's bootup. */
|
||||
if (ParseeVerifyStanza(args, chat_id, s_id_str))
|
||||
{
|
||||
ASRegisterUser(args->config, encoded);
|
||||
ASSetName(args->config, encoded, res);
|
||||
ASJoin(args->config, mroom_id, encoded);
|
||||
ASSend(
|
||||
args->config, mroom_id, encoded,
|
||||
"m.room.message", MatrixCreateMessage(data->data)
|
||||
);
|
||||
ParseePushStanza(args, chat_id, s_id_str);
|
||||
}
|
||||
|
||||
Free(res);
|
||||
Free(encoded);
|
||||
}
|
||||
Free(chat_id);
|
||||
Free(mroom_id);
|
||||
Free(from_matrix);
|
||||
Free(room);
|
||||
Free(to);
|
||||
}
|
||||
else if (StrEquals(stanza->name, "iq"))
|
||||
{
|
||||
/* TODO: Ought to manage info/query requests */
|
||||
Log(LOG_WARNING, "Unimplemented 'iq' stanza.");
|
||||
Log(LOG_WARNING, "Odds are, this is the programmer's fault");
|
||||
Log(LOG_WARNING, "and this needs to be implemented later on.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,4 +43,10 @@ extern void ASSetName(const ParseeConfig *c, char *user, char *name);
|
|||
/* Returns the user's name in a room, or a copy of the MXID itself, to be
|
||||
* Free'd. */
|
||||
extern char * ASGetName(const ParseeConfig *c, char *room, char *user);
|
||||
|
||||
/* Uploads data to Matrix to be used later */
|
||||
extern char * ASUpload(const ParseeConfig *c, Stream *from, unsigned int size);
|
||||
|
||||
/* Reuploads a HTTP URL to Matrix, with an optional MIME type returned. */
|
||||
extern char * ASReupload(const ParseeConfig *c, char *from, char **mime);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ extern HashMap * MatrixCreateNotice(char *body);
|
|||
/* Creates the content for a normal message. */
|
||||
extern HashMap * MatrixCreateMessage(char *body);
|
||||
|
||||
/* Creates the content for a media file. */
|
||||
extern HashMap * MatrixCreateMedia(char *mxc, char *body, char *mime);
|
||||
|
||||
/* Creates the content for a m.room.name state event */
|
||||
extern HashMap * MatrixCreateNameState(char *name);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ typedef struct ParseeData {
|
|||
Db *db;
|
||||
} ParseeData;
|
||||
|
||||
#define SECONDS * 1000
|
||||
#define MINUTES * 60 SECONDS
|
||||
#define HOURS * 60 MINUTES
|
||||
|
||||
/* Initialises a Parsee config from scratch, and writes to it
|
||||
* as JSON in the CWD. */
|
||||
extern void ParseeConfigInit(void);
|
||||
|
|
@ -150,4 +154,7 @@ extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id);
|
|||
extern void ParseeSendPresence(ParseeData *);
|
||||
|
||||
extern bool ParseeInitialiseSignals(HttpServer *, pthread_t, XMPPComponent *);
|
||||
|
||||
/* Job used to cleanup Parsee data that isn't necessary anymore. */
|
||||
extern void ParseeCleanup(void *data);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ extern void XMLAddAttr(XMLElement *element, char *key, char *val);
|
|||
extern void XMLAddChild(XMLElement *element, XMLElement *child);
|
||||
|
||||
extern XMLElement * XMLookForUnique(XMLElement *parent, char *tag);
|
||||
extern XMLElement * XMLookForTKV(XMLElement *parent, char *tag, char *k, char *v);
|
||||
|
||||
/* Frees an XML element properly. */
|
||||
extern void XMLFreeElement(XMLElement *element);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue