mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 19:55:10 +00:00
[ADD/WIP] Basic XEP-0393 support.
Still needs lots of work. Did I fail to mention I _hate_ HTML?
This commit is contained in:
parent
94cba7acf0
commit
771c3271ad
10 changed files with 473 additions and 9 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <StringStream.h>
|
||||
#include <Parsee.h>
|
||||
#include <XMPP.h>
|
||||
#include <XML.h>
|
||||
|
|
@ -29,6 +30,7 @@ Main(void)
|
|||
Stream *yaml;
|
||||
Cron *cron = NULL;
|
||||
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
Log(LOG_INFO,
|
||||
"%s - v%s (Cytoplasm %s)",
|
||||
NAME, VERSION, CytoplasmGetVersionStr()
|
||||
|
|
@ -66,7 +68,6 @@ Main(void)
|
|||
ASRegisterUser(parsee_conf, parsee_conf->sender_localpart);
|
||||
|
||||
|
||||
memset(&conf, 0, sizeof(conf));
|
||||
conf.port = parsee_conf->port;
|
||||
conf.threads = 4;
|
||||
conf.maxConnections = 32;
|
||||
|
|
|
|||
|
|
@ -73,12 +73,14 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
char *sender = GrabString(event, 1, "sender");
|
||||
char *chat_id, *muc_id, *jid;
|
||||
char *reply_id = MatrixGetReply(event);
|
||||
char *xepd = ParseeXMPPify(event);
|
||||
|
||||
bool direct = false;
|
||||
|
||||
if (ParseeIsPuppet(data->config, sender))
|
||||
{
|
||||
Free(reply_id);
|
||||
Free(xepd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +103,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
|
||||
Free(local);
|
||||
Free(reply_id);
|
||||
Free(xepd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +113,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
if (!chat_id)
|
||||
{
|
||||
Free(reply_id);
|
||||
Free(xepd);
|
||||
return;
|
||||
}
|
||||
jid = ParseeEncodeMXID(sender);
|
||||
|
|
@ -125,7 +129,11 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
Log(LOG_INFO, "Replying to %s by %s", stanza, sender);
|
||||
}
|
||||
XMPPJoinMUC(jabber, jid, rev);
|
||||
XMPPSendPlain(jabber, jid, muc_id, body, "groupchat", stanza, sender);
|
||||
XMPPSendPlain(
|
||||
jabber, jid, muc_id,
|
||||
xepd ? xepd : body, "groupchat",
|
||||
stanza, sender
|
||||
);
|
||||
Free(rev);
|
||||
Free(name);
|
||||
Free(stanza);
|
||||
|
|
@ -135,6 +143,7 @@ ParseeMessageHandler(ParseeData *data, HashMap *event)
|
|||
Free(muc_id);
|
||||
Free(jid);
|
||||
Free(reply_id);
|
||||
Free(xepd);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -132,3 +132,156 @@ ParseeFindDatastart(char *data)
|
|||
|
||||
return (int) (startline - data);
|
||||
}
|
||||
|
||||
#include <StringStream.h>
|
||||
|
||||
static char *
|
||||
XMPPifyElement(XMLElement *elem)
|
||||
{
|
||||
char *xepd = NULL, *tmp = NULL;
|
||||
|
||||
size_t i;
|
||||
XMLElement *child;
|
||||
char *subxep;
|
||||
#define Concat(strp) tmp = xepd; \
|
||||
xepd = StrConcat(2, xepd, strp); \
|
||||
Free(tmp)
|
||||
switch (elem->type)
|
||||
{
|
||||
case XML_ELEMENT_DATA:
|
||||
Concat(elem->data);
|
||||
break;
|
||||
case XML_ELEMENT_TAG:
|
||||
if (StrEquals(elem->name, "b") || StrEquals(elem->name, "strong"))
|
||||
{
|
||||
Concat("*");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
Concat("*");
|
||||
}
|
||||
else if (StrEquals(elem->name, "em"))
|
||||
{
|
||||
Concat("_");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
Concat("_");
|
||||
}
|
||||
else if (StrEquals(elem->name, "code"))
|
||||
{
|
||||
Concat("`");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
Concat("`");
|
||||
}
|
||||
else if (StrEquals(elem->name, "blockquote"))
|
||||
{
|
||||
Concat(">");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
Concat("\n");
|
||||
}
|
||||
else if (StrEquals(elem->name, "br"))
|
||||
{
|
||||
Concat("\n");
|
||||
/* HTML fucking SUCKS */
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
}
|
||||
else if (StrEquals(elem->name, "a"))
|
||||
{
|
||||
char *href = HashMapGet(elem->attrs, "href");
|
||||
Concat("(");
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
Concat(" points to ");
|
||||
Concat(href);
|
||||
Concat(" )");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; i < ArraySize(elem->children); i++)
|
||||
{
|
||||
child = ArrayGet(elem->children, i);
|
||||
subxep = XMPPifyElement(child);
|
||||
|
||||
Concat(subxep);
|
||||
Free(subxep);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return xepd;
|
||||
}
|
||||
char *
|
||||
ParseeXMPPify(HashMap *event)
|
||||
{
|
||||
char *cntr, *type, *format, *html;
|
||||
char *xepd = NULL, *tmp;
|
||||
XMLElement *elem;
|
||||
if (!event)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check if it is a message event. */
|
||||
type = JsonValueAsString(HashMapGet(event, "type"));
|
||||
if (!StrEquals(type, "m.room.message"))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
format = JsonValueAsString(JsonGet(event, 2, "content", "format"));
|
||||
if (!StrEquals(format, "org.matrix.custom.html"))
|
||||
{
|
||||
/* Settle for the raw body instead. */
|
||||
char *body = JsonValueAsString(JsonGet(event, 2, "content", "body"));
|
||||
return StrDuplicate(body);
|
||||
}
|
||||
|
||||
html = JsonValueAsString(JsonGet(event, 2, "content", "formatted_body"));
|
||||
html = StrConcat(3, "<html>", html, "</html>");
|
||||
elem = XMLDecode(StrStreamReader(html), true);
|
||||
|
||||
xepd = XMPPifyElement(elem);
|
||||
|
||||
XMLFreeElement(elem);
|
||||
Free(html);
|
||||
|
||||
return xepd;
|
||||
}
|
||||
|
|
|
|||
131
src/Streams/Reader.c
Normal file
131
src/Streams/Reader.c
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#include <StringStream.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Stream.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Io.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct ReaderCookie {
|
||||
size_t length;
|
||||
size_t offset;
|
||||
char *buffer;
|
||||
} ReaderCookie;
|
||||
|
||||
#define Remaining() (cook->length - cook->offset)
|
||||
|
||||
static ssize_t
|
||||
ReadStreamReader(void *coop, void *to, size_t n)
|
||||
{
|
||||
ReaderCookie *cook = coop;
|
||||
size_t remaining;
|
||||
if (!cook)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
remaining = Remaining();
|
||||
if (n > remaining)
|
||||
{
|
||||
memcpy(to, cook->buffer + cook->offset, remaining);
|
||||
cook->offset = cook->length;
|
||||
return remaining;
|
||||
}
|
||||
|
||||
memcpy(to, cook->buffer + cook->offset, n);
|
||||
cook->offset += n;
|
||||
return n;
|
||||
}
|
||||
static ssize_t
|
||||
WriteStreamReader(void *coop, void *from, size_t n)
|
||||
{
|
||||
/* Writing to a stream reader is silly. */
|
||||
return 0;
|
||||
}
|
||||
static off_t
|
||||
SeekStreamReader(void *coop, off_t mag, int sgn)
|
||||
{
|
||||
ReaderCookie *cook = coop;
|
||||
if (!cook)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (sgn)
|
||||
{
|
||||
case SEEK_SET:
|
||||
if (mag > cook->length)
|
||||
{
|
||||
cook->offset = cook->length;
|
||||
return 0;
|
||||
}
|
||||
else if (mag < 0)
|
||||
{
|
||||
cook->offset = 0;
|
||||
return 0;
|
||||
}
|
||||
cook->offset = mag;
|
||||
return 0;
|
||||
case SEEK_CUR:
|
||||
cook->offset += mag;
|
||||
if (cook->offset > cook->length)
|
||||
{
|
||||
cook->offset = cook->length;
|
||||
}
|
||||
else if (cook->offset < 0)
|
||||
{
|
||||
cook->offset = 0;
|
||||
}
|
||||
return 0;
|
||||
case SEEK_END:
|
||||
cook->offset += cook->length + mag;
|
||||
if (cook->offset > cook->length)
|
||||
{
|
||||
cook->offset = cook->length;
|
||||
}
|
||||
else if (cook->offset < 0)
|
||||
{
|
||||
cook->offset = 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
CloseStreamReader(void *coop)
|
||||
{
|
||||
/* Nothing to free as of now. */
|
||||
if (coop)
|
||||
{
|
||||
Free(coop);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const static IoFunctions Functions = {
|
||||
.read = ReadStreamReader,
|
||||
.seek = SeekStreamReader,
|
||||
.write = WriteStreamReader,
|
||||
.close = CloseStreamReader,
|
||||
};
|
||||
|
||||
Stream *
|
||||
StrStreamReader(char *buffer)
|
||||
{
|
||||
Io *raw_io;
|
||||
ReaderCookie *cookie;
|
||||
if (!buffer)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cookie = Malloc(sizeof(*cookie));
|
||||
cookie->buffer = buffer;
|
||||
cookie->length = strlen(buffer);
|
||||
cookie->offset = 0;
|
||||
raw_io = IoCreate(cookie, Functions);
|
||||
return StreamIo(raw_io);
|
||||
}
|
||||
64
src/Streams/Writer.c
Normal file
64
src/Streams/Writer.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include <StringStream.h>
|
||||
|
||||
#include <Cytoplasm/Memory.h>
|
||||
#include <Cytoplasm/Stream.h>
|
||||
#include <Cytoplasm/Str.h>
|
||||
#include <Cytoplasm/Io.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static ssize_t
|
||||
ReadStreamWriter(void *coop, void *to, size_t n)
|
||||
{
|
||||
/* Reading from a stream writer is silly. */
|
||||
return 0;
|
||||
}
|
||||
static ssize_t
|
||||
WriteStreamWriter(void *coop, void *from, size_t n)
|
||||
{
|
||||
char **cook = coop;
|
||||
char *tmp = NULL;
|
||||
char *str = Malloc(n + 1);
|
||||
|
||||
memcpy(str, from, n);
|
||||
str[n] = '\0';
|
||||
|
||||
tmp = *cook;
|
||||
*cook = StrConcat(2, *cook, str);
|
||||
Free(tmp);
|
||||
Free(str);
|
||||
return 0;
|
||||
}
|
||||
static off_t
|
||||
SeekStreamWriter(void *coop, off_t mag, int sgn)
|
||||
{
|
||||
/* TODO: Seeking would be useful, though not supported yet. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
CloseStreamWriter(void *coop)
|
||||
{
|
||||
/* Nothing to free as of now. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
const static IoFunctions Functions = {
|
||||
.read = ReadStreamWriter,
|
||||
.seek = SeekStreamWriter,
|
||||
.write = WriteStreamWriter,
|
||||
.close = CloseStreamWriter,
|
||||
};
|
||||
|
||||
Stream *
|
||||
StrStreamWriter(char **buffer)
|
||||
{
|
||||
Io *raw_io;
|
||||
if (!buffer)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
raw_io = IoCreate(buffer, Functions);
|
||||
return StreamIo(raw_io);
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ XMLDecode(Stream *stream, bool autofree)
|
|||
{
|
||||
continue;
|
||||
}
|
||||
XMLFreeElement(e);
|
||||
//XMLFreeElement(e);
|
||||
}
|
||||
ArrayFree(stack);
|
||||
XMLFreeLexer(lexer);
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ XMLCrank(XMLexer *lexer)
|
|||
return NULL;
|
||||
}
|
||||
event = XMLCreateRelax(lexer);
|
||||
//Log(LOG_INFO, "A %d", lexer->state);
|
||||
|
||||
switch (lexer->state)
|
||||
{
|
||||
|
|
@ -213,13 +214,16 @@ XMLCrank(XMLexer *lexer)
|
|||
break;
|
||||
case XML_STATE_ATTR:
|
||||
attrname = XMLParseName(lexer);
|
||||
//Log(LOG_INFO, "A %d %s", lexer->state, attrname);
|
||||
if (!attrname)
|
||||
{
|
||||
/* TODO: Throw error */
|
||||
}
|
||||
XMLPushElement(lexer, attrname);
|
||||
|
||||
//Log(LOG_INFO, "Reading props...");
|
||||
props = XMLReadProps(lexer);
|
||||
//Log(LOG_INFO, "Read props!");
|
||||
|
||||
XMLSkipSpace(lexer);
|
||||
if (XMLookahead(lexer, "/>", true))
|
||||
|
|
@ -516,6 +520,7 @@ XMLReadProps(XMLexer *lexer)
|
|||
/* A lack of name is totally excepted */
|
||||
break;
|
||||
}
|
||||
//Log(LOG_INFO, "K=%s...", name);
|
||||
|
||||
value = StrDuplicate("");
|
||||
if (XMLookahead(lexer, "=", true))
|
||||
|
|
@ -719,18 +724,23 @@ XMLParseAttQuote(XMLexer *lexer)
|
|||
|
||||
while ((c = XMLGetc(lexer)))
|
||||
{
|
||||
//Log(LOG_INFO, "E1=%c", c);
|
||||
if (c == '&')
|
||||
{
|
||||
char *code = NULL;
|
||||
int c2;
|
||||
int p2 = XMLInitialiseBuffer(lexer);
|
||||
while ((c2 = XMLGetc(lexer)))
|
||||
while ((c2 = XMLGetc(lexer)) && c2 != EOF)
|
||||
{
|
||||
if (c2 == ';')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c2 != ';')
|
||||
{
|
||||
XMLReset(lexer, p2);
|
||||
}
|
||||
}
|
||||
else if (!IsNormalQ(c))
|
||||
{
|
||||
|
|
@ -759,18 +769,23 @@ XMLParseAttDouble(XMLexer *lexer)
|
|||
|
||||
while ((c = XMLGetc(lexer)))
|
||||
{
|
||||
//Log(LOG_INFO, "E2=%c", c);
|
||||
if (c == '&')
|
||||
{
|
||||
char *code = NULL;
|
||||
int c2;
|
||||
int p2 = XMLInitialiseBuffer(lexer);
|
||||
while ((c2 = XMLGetc(lexer)))
|
||||
while ((c2 = XMLGetc(lexer)) && c2 != EOF)
|
||||
{
|
||||
if (c2 == ';')
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c2 != ';')
|
||||
{
|
||||
XMLReset(lexer, p2);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (!IsNormalD(c))
|
||||
|
|
|
|||
|
|
@ -124,6 +124,83 @@ MessageStanza(ParseeData *args, XMLElement *stanza)
|
|||
return true;
|
||||
}
|
||||
|
||||
#define DISCO "http://jabber.org/protocol/disco#info"
|
||||
static void
|
||||
IQDiscoGet(ParseeData *args, XMPPComponent *jabber, XMLElement *stanza)
|
||||
{
|
||||
char *from, *to, *id;
|
||||
XMLElement *iq_reply, *query;
|
||||
|
||||
from = HashMapGet(stanza->attrs, "from");
|
||||
to = HashMapGet(stanza->attrs, "to");
|
||||
id = HashMapGet(stanza->attrs, "id");
|
||||
|
||||
/* Generate an IQ reply with discovery information */
|
||||
iq_reply = XMLCreateTag("iq");
|
||||
XMLAddAttr(iq_reply, "to", from);
|
||||
XMLAddAttr(iq_reply, "from", to);
|
||||
XMLAddAttr(iq_reply, "type", "result");
|
||||
XMLAddAttr(iq_reply, "id", id);
|
||||
{
|
||||
query = XMLCreateTag("query");
|
||||
XMLAddAttr(query, "xmlns", DISCO);
|
||||
{
|
||||
XMLElement *feature;
|
||||
#define AdvertiseSimple(f) do \
|
||||
{ \
|
||||
feature = XMLCreateTag("feature"); \
|
||||
XMLAddAttr(feature, "var", f); \
|
||||
XMLAddChild(query, feature); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
AdvertiseSimple("urn:xmpp:reply:0");
|
||||
AdvertiseSimple("urn:xmpp:styling:0");
|
||||
|
||||
AdvertiseSimple("urn:parsee:x-parsee:0");
|
||||
AdvertiseSimple("urn:parsee:jealousy:0");
|
||||
/* TODO: Advertise more things */
|
||||
#undef AdvertiseSimple
|
||||
}
|
||||
XMLAddChild(iq_reply, query);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&jabber->write_lock);
|
||||
XMLEncode(jabber->stream, iq_reply);
|
||||
pthread_mutex_unlock(&jabber->write_lock);
|
||||
|
||||
XMLFreeElement(iq_reply);
|
||||
}
|
||||
static void
|
||||
IQGet(ParseeData *args, XMLElement *stanza)
|
||||
{
|
||||
XMPPComponent *jabber = args->jabber;
|
||||
if (XMLookForTKV(stanza, "query", "xmlns", DISCO))
|
||||
{
|
||||
IQDiscoGet(args, jabber, stanza);
|
||||
}
|
||||
|
||||
}
|
||||
#undef DISCO
|
||||
static void
|
||||
IQStanza(ParseeData *args, XMLElement *stanza)
|
||||
{
|
||||
char *type;
|
||||
type = HashMapGet(stanza->attrs, "type");
|
||||
#define OnType(ctyp, callback) do \
|
||||
{ \
|
||||
if (StrEquals(type, #ctyp)) \
|
||||
{ \
|
||||
callback(args, stanza); \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
OnType(get, IQGet);
|
||||
#undef OnType
|
||||
}
|
||||
|
||||
void *
|
||||
ParseeXMPPThread(void *argp)
|
||||
{
|
||||
|
|
@ -177,10 +254,7 @@ ParseeXMPPThread(void *argp)
|
|||
}
|
||||
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.");
|
||||
IQStanza(args, stanza);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -163,4 +163,8 @@ extern void ParseeCleanup(void *data);
|
|||
|
||||
/* Finds the offset of the first line without a '>' at its start. */
|
||||
extern int ParseeFindDatastart(char *data);
|
||||
|
||||
|
||||
/* XMPP-ifies a message event to XEP-0393 if possible. */
|
||||
extern char * ParseeXMPPify(HashMap *event);
|
||||
#endif
|
||||
|
|
|
|||
13
src/include/StringStream.h
Normal file
13
src/include/StringStream.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef PARSEE_STRSTREAM_H
|
||||
#define PARSEE_STRSTREAM_H
|
||||
|
||||
#include <Cytoplasm/Stream.h>
|
||||
|
||||
/* Creates a string stream writer. The referenced buffer must be in the heap,
|
||||
* or NULL. */
|
||||
extern Stream * StrStreamWriter(char **buffer);
|
||||
|
||||
/* Creates a string stream reader. The referenced buffer may be everywhere. */
|
||||
extern Stream * StrStreamReader(char *buffer);
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue