Parsee/src/Parsee/Data.c
LDA cb0e77e7a4 [MOD/WIP] Mess a bit with the XEP-0393 parser
It took a comical amount of time for me to do that LMAO
2024-08-01 10:31:59 +02:00

896 lines
22 KiB
C

#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Log.h>
#include <Cytoplasm/Str.h>
#include <string.h>
#include <stdlib.h>
#include <Routes.h>
#include <Glob.h>
#include <AS.h>
ParseeData *
ParseeInitData(XMPPComponent *comp)
{
ParseeData *data;
if (!ParseeConfigGet())
{
return NULL;
}
data = Malloc(sizeof(*data));
data->config = ParseeConfigGet();
data->router = HttpRouterCreate();
data->jabber = comp;
data->handler = CommandCreateRouter();
data->db = DbOpen(data->config->db_path, 0);
#define X_ROUTE(path, func) do {\
if (!HttpRouterAdd(data->router, path, func))\
{\
Log(LOG_ERR, "Can't register %s", path);\
}\
}\
while (0);
ROUTES
#undef X_ROUTE
#define X_COMMAND(path,n,d) CommandAddCommand(data->handler, path, n);
COMMANDS
#undef X_COMMAND
return data;
}
void
ParseeFreeData(ParseeData *data)
{
if (!data)
{
return;
}
XMPPEndCompStream(data->jabber);
DbClose(data->db);
HttpRouterFree(data->router);
CommandFreeRouter(data->handler);
Free(data);
}
void
ParseeCleanup(void *datp)
{
ParseeData *data = datp;
Array *chats;
char *chat;
size_t i;
uint64_t ts = UtilTsMillis();
Log(LOG_NOTICE, "Cleaning up...");
chats = DbList(data->db, 1, "chats");
for (i = 0; i < ArraySize(chats); i++)
{
DbRef *ref;
HashMap *json, *stanzas, *events, *ids;
char *stanza, *event, *id;
JsonValue *val;
Array *to_delete;
size_t j;
chat = ArrayGet(chats, i);
ref = DbLock(data->db, 2, "chats", chat);
json = DbJson(ref);
#define CleanupField(field, timeout, threshold) do \
{ \
size_t cleaned = 0; \
field##s = JsonValueAsObject(HashMapGet(json, #field"s")); \
to_delete = ArrayCreate(); \
while (HashMapIterate(field##s, &field, (void **) &val)) \
{ \
HashMap *obj = JsonValueAsObject(val); \
uint64_t age = JsonValueAsInteger(HashMapGet(obj, "age")); \
uint64_t dur = ts - age; \
\
if ((dur > (timeout))) \
{ \
ArrayAdd(to_delete, StrDuplicate(field)); \
cleaned++; \
} \
} \
\
for (j = 0; j < ArraySize(to_delete); j++) \
{ \
field = ArrayGet(to_delete, j); \
if (cleaned > threshold) \
{ \
JsonValueFree(HashMapDelete(field##s, field)); \
} \
Free(field); \
} \
ArrayFree(to_delete); \
} \
while (0)
CleanupField(stanza, 30 MINUTES, 50);
CleanupField(event, 30 MINUTES, 50);
CleanupField(id, 30 MINUTES, 50);
DbUnlock(data->db, ref);
}
DbListFree(chats);
/* TODO */
chats = DbList(data->db, 1, "rooms");
for (i = 0; i < ArraySize(chats); i++)
{
DbRef *ref;
HashMap *json, *stanzas, *events, *ids;
char *stanza, *event, *id;
JsonValue *val;
Array *to_delete;
size_t j;
chat = ArrayGet(chats, i);
ref = DbLock(data->db, 3, "rooms", chat, "data");
json = DbJson(ref);
#define CleanupField(field, timeout, threshold) do \
{ \
size_t cleaned = 0; \
field##s = JsonValueAsObject(HashMapGet(json, #field"s")); \
to_delete = ArrayCreate(); \
while (HashMapIterate(field##s, &field, (void **) &val)) \
{ \
HashMap *obj = JsonValueAsObject(val); \
uint64_t age = JsonValueAsInteger(HashMapGet(obj, "age")); \
uint64_t dur = ts - age; \
\
if ((dur > (timeout))) \
{ \
ArrayAdd(to_delete, StrDuplicate(field)); \
cleaned++; \
} \
} \
\
for (j = 0; j < ArraySize(to_delete); j++) \
{ \
field = ArrayGet(to_delete, j); \
if (cleaned > threshold) \
{ \
JsonValueFree(HashMapDelete(field##s, field)); \
} \
Free(field); \
} \
ArrayFree(to_delete); \
} \
while (0)
CleanupField(stanza, 3 HOURS, 50);
CleanupField(event, 3 HOURS, 50);
CleanupField(id, 3 HOURS, 50);
DbUnlock(data->db, ref);
}
DbListFree(chats);
}
int
ParseeFindDatastart(char *data)
{
char *startline;
bool found = false;
if (!data)
{
return 0;
}
startline = data;
while (startline)
{
char *endline = strchr(startline, '\n');
if (*startline != '>')
{
found = true;
break;
}
startline = endline ? endline + 1 : NULL;
}
if (!found)
{
return 0;
}
return (int) (startline - data);
}
#include <StringStream.h>
typedef struct XMPPFlags {
bool quote;
} XMPPFlags;
static char *
XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
{
char *xepd = NULL, *tmp = NULL;
size_t i;
XMLElement *child;
char *reply_id = JsonValueAsString(
JsonGet(event, 4,
"content", "m.relates_to", "m.in_reply_to", "event_id"
));
char *room_id = JsonValueAsString(HashMapGet(event, "room_id"));
HashMap *referenced;
char *subxep;
#define Concat(strp) do \
{ \
size_t cidx; \
size_t len = strp ? strlen(strp) : 0; \
for (cidx = 0; cidx < len; cidx++) \
{ \
char cch[2] = { strp[cidx], 0 }; \
char nch = *cch ? strp[cidx+1] : '\0'; \
bool c = *cch == '\n' && nch != '>'; \
if (c && flags.quote) \
{ \
tmp = xepd; \
xepd = StrConcat(2, xepd, "\n>"); \
Free(tmp); \
continue; \
} \
tmp = xepd; \
xepd = StrConcat(2, xepd, cch); \
Free(tmp); \
} \
} \
while (0)
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(event, child, flags);
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(event, child, flags);
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(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("`");
}
else if (StrEquals(elem->name, "mx-reply"))
{
char *str;
referenced = ASFind(ParseeConfigGet(), room_id, reply_id);
str = JsonValueAsString(
JsonGet(referenced, 2, "content", "body")
);
if (!str)
{
JsonFree(referenced);
return xepd;
}
Concat(">");
flags.quote = true;
Concat(str);
flags.quote = false;
Concat("\n");
JsonFree(referenced);
}
else if (StrEquals(elem->name, "blockquote"))
{
Concat(">");
flags.quote = true;
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
subxep = XMPPifyElement(event, child, flags);
Concat(subxep);
Free(subxep);
}
flags.quote = false;
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(event, child, flags);
Concat(subxep);
Free(subxep);
}
Concat("\n");
}
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(event, child, flags);
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(event, child, flags);
Concat(subxep);
Free(subxep);
}
}
break;
default:
break;
}
return xepd;
}
char *
ParseeXMPPify(HashMap *event)
{
char *type, *format, *html;
char *xepd = NULL;
XMLElement *elem;
XMPPFlags flags;
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 = XMLCDecode(StrStreamReader(html), true, true);
flags.quote = false;
xepd = XMPPifyElement(event, elem, flags);
XMLFreeElement(elem);
Free(html);
return xepd;
}
void
ParseePushDMStanza(ParseeData *data, char *room_id, char *stanza_id, char *id, char *ev, char *sender)
{
DbRef *ref;
HashMap *j;
HashMap *stanzas, *obj, *events, *ids;
bool new_stanzas = false, new_events = false;
bool new_ids = false;
uint64_t age = UtilTsMillis();
if (!data || !room_id || !ev || !sender)
{
return;
}
ref = DbLock(data->db, 3, "rooms", room_id, "data");
if (!ref)
{
return;
}
j = DbJson(ref);
if (stanza_id)
{
stanzas = JsonValueAsObject(HashMapGet(j, "stanzas"));
if (!stanzas)
{
stanzas = HashMapCreate();
new_stanzas = true;
}
obj = HashMapCreate();
HashMapSet(obj, "age", JsonValueInteger(age));
HashMapSet(obj, "event", JsonValueString(ev));
JsonValueFree(HashMapSet(stanzas, stanza_id, JsonValueObject(obj)));
if (new_stanzas)
{
HashMapSet(j, "stanzas", JsonValueObject(stanzas));
}
}
events = JsonValueAsObject(HashMapGet(j, "events"));
if (!events)
{
events = HashMapCreate();
new_events = true;
}
obj = HashMapCreate();
HashMapSet(obj, "stanza", JsonValueString(stanza_id));
HashMapSet(obj, "origin", JsonValueString(id));
HashMapSet(obj, "sender", JsonValueString(sender));
HashMapSet(obj, "age", JsonValueInteger(age));
JsonValueFree(HashMapSet(events, ev, JsonValueObject(obj)));
if (new_events)
{
HashMapSet(j, "events", JsonValueObject(events));
}
if (id)
{
ids = JsonValueAsObject(HashMapGet(j, "ids"));
if (!ids)
{
ids = HashMapCreate();
new_ids = true;
}
obj = HashMapCreate();
HashMapSet(obj, "stanza", JsonValueString(stanza_id));
HashMapSet(obj, "event", JsonValueString(ev));
HashMapSet(obj, "sender", JsonValueString(sender));
HashMapSet(obj, "age", JsonValueInteger(age));
JsonValueFree(HashMapSet(ids, id, JsonValueObject(obj)));
if (new_ids)
{
HashMapSet(j, "ids", JsonValueObject(ids));
}
}
DbUnlock(data->db, ref);
}
void
ParseePushStanza(ParseeData *data, char *chat_id, char *stanza_id, char *id, char *ev, char *sender)
{
DbRef *ref;
HashMap *j;
HashMap *stanzas, *obj, *events, *ids;
bool new_stanzas = false, new_events = false;
bool new_ids = false;
uint64_t age = UtilTsMillis();
if (!data || !chat_id || !stanza_id || !ev || !sender)
{
return;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (!ref)
{
return;
}
stanzas = JsonValueAsObject(HashMapGet(j, "stanzas"));
if (!stanzas)
{
stanzas = HashMapCreate();
new_stanzas = true;
}
obj = HashMapCreate();
HashMapSet(obj, "age", JsonValueInteger(age));
HashMapSet(obj, "event", JsonValueString(ev));
JsonValueFree(HashMapSet(stanzas, stanza_id, JsonValueObject(obj)));
if (new_stanzas)
{
HashMapSet(j, "stanzas", JsonValueObject(stanzas));
}
events = JsonValueAsObject(HashMapGet(j, "events"));
if (!events)
{
events = HashMapCreate();
new_events = true;
}
obj = HashMapCreate();
HashMapSet(obj, "stanza", JsonValueString(stanza_id));
HashMapSet(obj, "origin", JsonValueString(id));
HashMapSet(obj, "sender", JsonValueString(sender));
HashMapSet(obj, "age", JsonValueInteger(age));
JsonValueFree(HashMapSet(events, ev, JsonValueObject(obj)));
if (new_events)
{
HashMapSet(j, "events", JsonValueObject(events));
}
if (id)
{
ids = JsonValueAsObject(HashMapGet(j, "ids"));
if (!ids)
{
ids = HashMapCreate();
new_ids = true;
}
obj = HashMapCreate();
HashMapSet(obj, "stanza", JsonValueString(stanza_id));
HashMapSet(obj, "event", JsonValueString(ev));
HashMapSet(obj, "sender", JsonValueString(sender));
HashMapSet(obj, "age", JsonValueInteger(age));
JsonValueFree(HashMapSet(ids, id, JsonValueObject(obj)));
if (new_ids)
{
HashMapSet(j, "ids", JsonValueObject(ids));
}
}
DbUnlock(data->db, ref);
}
bool
ParseeVerifyDMStanza(ParseeData *data, char *room_id, char *id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *stanzas = NULL;
bool ret = true;
if (!data || !room_id || !id)
{
return true;
}
ref = DbLock(data->db, 3, "rooms", room_id, "data");
j = DbJson(ref);
if (!ref)
{
goto end;
}
stanzas = JsonValueAsObject(HashMapGet(j, "ids"));
if (!stanzas)
{
goto end;
}
ret = !HashMapGet(stanzas, id);
end:
DbUnlock(data->db, ref);
return ret;
}
bool
ParseeVerifyStanza(ParseeData *data, char *chat_id, char *stanza_id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *stanzas = NULL;
bool ret = true;
if (!data || !chat_id || !stanza_id)
{
return true;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (!ref)
{
goto end;
}
stanzas = JsonValueAsObject(HashMapGet(j, "stanzas"));
if (!stanzas)
{
goto end;
}
ret = !HashMapGet(stanzas, stanza_id);
end:
DbUnlock(data->db, ref);
return ret;
}
char *
ParseeEventFromID(ParseeData *data, char *chat_id, char *id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *ids = NULL;
char *ret = NULL;
if (!data || !chat_id || !id)
{
return NULL;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (!ref)
{
goto end;
}
ids = JsonValueAsObject(HashMapGet(j, "ids"));
if (!ids)
{
goto end;
}
ret = JsonValueAsString(JsonGet(ids, 2, id, "event"));
ret = StrDuplicate(ret);
end:
DbUnlock(data->db, ref);
return ret;
}
char *
ParseeDMEventFromID(ParseeData *data, char *room_id, char *id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *ids = NULL;
char *ret = NULL;
if (!data || !room_id || !id)
{
return NULL;
}
ref = DbLock(data->db, 3, "rooms", room_id, "data");
j = DbJson(ref);
if (!ref)
{
goto end;
}
ids = JsonValueAsObject(HashMapGet(j, "ids"));
if (!ids)
{
goto end;
}
ret = JsonValueAsString(JsonGet(ids, 2, id, "event"));
ret = StrDuplicate(ret);
end:
DbUnlock(data->db, ref);
return ret;
}
char *
ParseeEventFromSID(ParseeData *data, char *chat_id, char *id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *ids = NULL;
char *ret = NULL;
if (!data || !chat_id || !id)
{
return NULL;
}
ref = DbLock(data->db, 2, "chats", chat_id);
j = DbJson(ref);
if (!ref)
{
goto end;
}
ids = JsonValueAsObject(HashMapGet(j, "stanzas"));
if (!ids)
{
goto end;
}
ret = JsonValueAsString(JsonGet(ids, 2, id, "event"));
ret = StrDuplicate(ret);
end:
DbUnlock(data->db, ref);
return ret;
}
char *
ParseeDMEventFromSID(ParseeData *data, char *room_id, char *id)
{
DbRef *ref = NULL;
HashMap *j = NULL;
HashMap *ids = NULL;
char *ret = NULL;
if (!data || !room_id || !id)
{
return NULL;
}
ref = DbLock(data->db, 3, "rooms", room_id, "data");
j = DbJson(ref);
if (!ref)
{
goto end;
}
ids = JsonValueAsObject(HashMapGet(j, "stanzas"));
if (!ids)
{
goto end;
}
ret = JsonValueAsString(JsonGet(ids, 2, id, "event"));
ret = StrDuplicate(ret);
end:
DbUnlock(data->db, ref);
return ret;
}
void
ParseeGlobalBan(ParseeData *data, char *glob, char *reason)
{
DbRef *ref;
HashMap *j, *obj;
if (!data || !glob)
{
return;
}
ref = DbLock(data->db, 1, "global_bans");
if (!ref)
{
ref = DbCreate(data->db, 1, "global_bans");
}
j = DbJson(ref);
obj = HashMapCreate();
if (reason)
{
HashMapSet(obj, "reason", JsonValueString(reason));
}
HashMapSet(obj, "date", JsonValueInteger(UtilTsMillis()));
JsonValueFree(HashMapSet(j, glob, JsonValueObject(obj)));
DbUnlock(data->db, ref);
}
bool
ParseeManageBan(ParseeData *data, char *user, char *room)
{
DbRef *ref;
HashMap *j;
char *key;
JsonValue *val;
bool banned = false , matches = false;
if (!data || !user)
{
return false;
}
ref = DbLock(data->db, 1, "global_bans");
j = DbJson(ref);
while (HashMapIterate(j, &key, (void **) &val))
{
HashMap *obj = JsonValueAsObject(val);
if (matches)
{
continue;
}
if (GlobMatches(key, user))
{
banned = true;
matches = true;
if (room)
{
/* TODO: Use the object to set the reason */
ASBan(data->config, room, user);
(void) obj;
}
}
}
DbUnlock(data->db, ref);
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;
}
void
ParseeAchievement(const char *func, const char *msg, bool die)
{
Log(LOG_ERR, "=========== Achievement GET! ===========");
Log(LOG_ERR, "%s: %s.", func, msg);
Log(LOG_ERR, "THIS IS, LET'S SAY, NOT GOOD, AND A SIGN OF ");
Log(LOG_ERR, "A PROGRAMMER ERROR. PLEASE KILLALL -9 PARSEE.");
Log(LOG_ERR, "");
Log(LOG_ERR, "YOU, HOWEVER, GET TO WIN AN AWARD FOR THIS.");
Log(LOG_ERR, "I AM SIMPLY JEALOUS OF YOU GETTING SUCH A ");
Log(LOG_ERR, "GOOD ERROR MESSAGE.");
Log(LOG_ERR, "=========== Achievement GET! ===========");
if (die)
{
abort();
}
}
char *
ParseeGenerateMTO(char *common_id)
{
char *matrix_to;
if (!common_id)
{
return NULL;
}
common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", common_id);
Free(common_id);
return matrix_to;
}