Parsee/src/MatrixEventHandler.c
LDA ffc0679493 [MOD/WIP] Kill off the stanza sending function
The stanza sender is dead. All hail the Stanza Builder.
2024-08-13 20:24:32 +02:00

445 lines
12 KiB
C

#include <Parsee.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <string.h>
#include <stdlib.h>
#include <StanzaBuilder.h>
#include <Matrix.h>
#include <AS.h>
static void
ParseeMemberHandler(ParseeData *data, HashMap *event)
{
char *state_key = GrabString(event, 1, "state_key");
char *membership = GrabString(event, 2, "content", "membership");
char *room_id = GrabString(event, 1, "room_id");
char *sender = GrabString(event, 1, "sender");
char *chat_id;
char *local = data->config->sender_localpart;
const ParseeConfig *conf = data->config;
if (StrEquals(membership, "invite") && ParseeIsPuppet(conf, state_key))
{
DbRef *ref = NULL;
HashMap *json;
char *jid;
bool direct = GrabBoolean(event, 2, "content", "is_direct");
bool bot = !strncmp(sender + 1, local, strlen(local));
Free(ASJoin(conf, room_id, state_key));
jid = ParseeDecodeLocalJID(conf, state_key);
if (direct && !bot)
{
ref = DbCreate(data->db, 3, "rooms", room_id, "data");
json = DbJson(ref);
HashMapSet(json, "is_direct", JsonValueBoolean(direct && !bot));
HashMapSet(json, "xmpp_user", JsonValueString(jid));
DbUnlock(data->db, ref);
ParseePushDMRoom(data, sender, jid, room_id);
}
if (jid)
{
Free(jid);
}
}
else if (StrEquals(membership, "join") && !ParseeIsPuppet(conf, state_key))
{
char *jid = ParseeEncodeMXID(state_key);
chat_id = ParseeGetFromRoomID(data, room_id);
if (chat_id)
{
char *muc = ParseeGetMUCID(data, chat_id);
char *rev = StrConcat(2, muc, "/parsee");
XMPPJoinMUC(data->jabber, jid, rev);
Free(rev);
Free(muc);
/* TODO: XEP-0084 magic to advertise a new avatar if possible. */
}
Free(jid);
Free(chat_id);
}
else if ((StrEquals(membership, "leave") ||
StrEquals(membership, "ban"))
&& !ParseeIsPuppet(conf, state_key))
{
/* TODO: Manage bans on their own, rather than a spicy leave.
* Then again, this could cause desync issues. */
XMPPComponent *jabber = data->jabber;
char *jid = ParseeEncodeMXID(state_key);
char *name = NULL, *rev = NULL, *muc_id = NULL;
char *reason = GrabString(event, 2, "content", "reason");
/* Try to find the chat ID */
chat_id = ParseeGetFromRoomID(data, room_id);
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
goto end;
}
/* TODO: Check the name's validity */
name = ASGetName(data->config, room_id, state_key);
rev = StrConcat(4, muc_id, "/", name, "[p]");
XMPPLeaveMUC(jabber, jid, rev, reason);
end:
Free(chat_id);
Free(muc_id);
Free(name);
Free(rev);
Free(jid);
}
}
static void
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 *sender = GrabString(event, 1, "sender");
char *profile = ParseeMXID(data);
Command *cmd = NULL;
ParseeCmdArg arg = {
.data = data,
.event = event
};
if (StrEquals(msgtype, "m.notice"))
{
Free(profile);
return;
}
if (*body != '!')
{
/* All commands are to be marked with a ! */
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("Please enter a valid command")
));
Free(profile);
return;
}
if (!ParseeIsAdmin(data, sender))
{
Free(ASSend(
data->config, id, profile,
"m.room.message",
MatrixCreateNotice("You are not authorised to do this.")
));
Free(profile);
return;
}
body++;
cmd = CommandParse(body);
RouteCommand(data->handler, cmd, &arg);
Free(profile);
CommandFree(cmd);
}
static const char *
GetXMPPInformation(ParseeData *data, HashMap *event, char **from, char **to)
{
const char *type = NULL;
char *room_id = GrabString(event, 1, "room_id");
char *matrix_sender = GrabString(event, 1, "sender");
char *chat_id = NULL, *muc_id = NULL;
char *user;
DbRef *room_data;
HashMap *data_json;
XMPPComponent *jabber = data ? data->jabber : NULL;
bool direct = false;
if (!data || !event || !from || !to)
{
return NULL;
}
*from = NULL;
*to = NULL;
chat_id = ParseeGetFromRoomID(data, room_id);
room_data = DbLockIntent(data->db,
DB_HINT_READONLY,
3, "rooms", room_id, "data"
);
data_json = DbJson(room_data);
direct = GrabBoolean(data_json, 1, "is_direct");
type = direct ? "chat" : "groupchat";
user = GrabString(data_json, 1, "xmpp_user");
*from = ParseeEncodeMXID(matrix_sender);
if (direct)
{
*to = StrDuplicate(user);
Free(chat_id);
}
else
{
char *matrix_name, *muc_join_as;
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
/* muc_id is already implied to be freed by this point */
Free(*from);
*from = NULL;
DbUnlock(data->db, room_data);
return NULL;
}
matrix_name = ASGetName(data->config, room_id, matrix_sender);
muc_join_as = StrConcat(4, muc_id, "/", matrix_name, "[p]");
/* TODO: Manage name conflicts. That would have been an easy
* task(try the original one, and use a counter if it fails),
* but that'd involve modifying the rest of the code, which
* I'm not doing at 01:39 ... */
XMPPJoinMUC(jabber, *from, muc_join_as);
*to = muc_id;
Free(matrix_name);
Free(muc_join_as);
}
Free(chat_id);
DbUnlock(data->db, room_data);
return type;
}
static void
ParseeMessageHandler(ParseeData *data, HashMap *event)
{
XMPPComponent *jabber = data->jabber;
StanzaBuilder *builder;
DbRef *ref = NULL;
HashMap *json;
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;
char *reply_id = MatrixGetReply(event);
char *xepd = ParseeXMPPify(event);
char *type, *user, *xmppified_user = NULL, *to = NULL;
char *unauth = NULL;
char *origin_id = NULL, *stanza = NULL;
char *sender = NULL;
char *unedited_id = MatrixGetEdit(event);
char *url = GrabString(event, 2, "content", "url");
bool direct = false;
if (ParseeIsPuppet(data->config, m_sender) ||
ParseeManageBan(data, m_sender, id))
{
Free(reply_id);
Free(xepd);
Free(unedited_id);
return;
}
chat_id = ParseeGetFromRoomID(data, id);
/* TODO: This ref should be marked as read-only,
* as LMDB doesn't seem to like having two concurrent RW
* transactions running. */
ref = DbLockIntent(data->db, DB_HINT_READONLY, 3, "rooms", id, "data");
json = DbJson(ref);
direct = JsonValueAsBoolean(HashMapGet(json, "is_direct"));
if (!direct && !chat_id)
{
ParseeBotHandler(data, event);
DbUnlock(data->db, ref);
ref = NULL;
Free(chat_id);
Free(reply_id);
Free(xepd);
Free(unedited_id);
return;
}
type = direct ? "chat" : "groupchat";
user = GrabString(json, 1, "xmpp_user");
unauth = ParseeToUnauth(data, url);
if (direct)
{
xmppified_user = ParseeEncodeMXID(m_sender);
to = StrDuplicate(user);
Free(chat_id);
}
else
{
char *name, *rev;
/* Try to find the chat ID */
muc_id = ParseeGetMUCID(data, chat_id);
if (!chat_id)
{
goto end;
}
xmppified_user = ParseeEncodeMXID(m_sender);
/* TODO: Check the name's validity.
* Is there a good way to check for that that isn't
* just "await on join and try again?" */
name = ASGetName(data->config, id, m_sender);
rev = StrConcat(4, muc_id, "/", name, "[p]");
XMPPJoinMUC(jabber, xmppified_user, rev);
to = muc_id;
Free(name);
Free(rev);
}
if (reply_id)
{
/* TODO: Monocles chat DM users HATE this trick!
* Replies don't work there. Go figure why. */
if (!ParseeGetStanzaInfo(data, chat_id, reply_id, &stanza, &sender))
{
ParseeGetDMStanzaInfo(data, id, reply_id, &stanza, &sender);
}
}
else if (unedited_id)
{
if (!ParseeGetOrigin(data, chat_id, unedited_id, &origin_id))
{
ParseeGetDMOrigin(data, id, unedited_id, &origin_id);
}
}
if (direct && sender)
{
char *sndr_tmp = sender;
sender = ParseeTrimJID(sender);
Free(sndr_tmp);
}
{
char *xmpp_ident = StrRandom(32);
builder = CreateStanzaBuilder(xmppified_user, to, xmpp_ident);
SetStanzaType(builder, type);
SetStanzaBody(builder, xepd ? xepd : body);
SetStanzaReply(builder, stanza, sender);
SetStanzaLink(builder, unauth);
SetStanzaEdit(builder, origin_id);
SetStanzaXParsee(builder, event);
WriteoutStanza(builder, jabber);
DestroyStanzaBuilder(builder);
if (direct)
{
ParseePushDMStanza(
data, id, NULL,
xmpp_ident, ev_id,
xmppified_user
);
}
Free(xmpp_ident);
}
end:
Free(origin_id);
Free(xmppified_user);
Free(chat_id);
Free(to);
Free(reply_id);
Free(xepd);
Free(stanza);
Free(sender);
Free(unauth);
Free(unedited_id);
DbUnlock(data->db, ref);
ref = NULL;
}
void
ParseeEventHandler(ParseeData *data, HashMap *event)
{
char *event_type, *event_id, *room_id, *sender;
char *parsee = ParseeMXID(data);
event_id = GrabString(event, 1, "event_id");
event_type = GrabString(event, 1, "type");
room_id = GrabString(event, 1, "room_id");
sender = GrabString(event, 1, "sender");
if (!data || !event || ParseeIsPuppet(data->config, sender))
{
Free(parsee);
return;
}
ParseePushHeadTable(room_id, event_id);
if (StrEquals(event_type, "m.room.member"))
{
ParseeMemberHandler(data, event);
Free(parsee);
return;
}
else if (StrEquals(event_type, "m.room.message") ||
StrEquals(event_type, "m.sticker")) /* TODO: Actual sticker
* support here... */
{
ParseeMessageHandler(data, event);
Free(parsee);
return;
}
else if (StrEquals(event_type, "m.room.redaction") &&
!StrEquals(sender, parsee))
{
char *from, *to;
char *redacted = GrabString(event, 1, "redacts");
char *redacted_stanza = NULL;
char *chat_id;
XMPPComponent *jabber = data->jabber;
const char *type = GetXMPPInformation(data, event, &from, &to);
chat_id = ParseeGetFromRoomID(data, room_id);
if (!ParseeGetOrigin(data, chat_id, redacted, &redacted_stanza))
{
ParseeGetDMOrigin(data, room_id, redacted, &redacted_stanza);
}
/* Some clients don't support retractions *at all*, which smell.
* This therefore serves as a fallback, just in case that fails.
*
* TODO: BAD IDEA. XMPP CLIENTS, FUCKING IMPLEMENT IT YOU LAZY
* FUCKTARDS.
*/
XMPPRetract(jabber, from, to, (char *) type, redacted_stanza);
Free(redacted_stanza);
Free(chat_id);
Free(from);
Free(to);
}
Free(parsee);
}