mirror of
https://forge.fsky.io/lda/Parsee.git
synced 2026-03-13 13:45:10 +00:00
474 lines
12 KiB
C
474 lines
12 KiB
C
#include <Parsee.h>
|
|
|
|
#include <Cytoplasm/Memory.h>
|
|
#include <Cytoplasm/Json.h>
|
|
#include <Cytoplasm/Str.h>
|
|
#include <Cytoplasm/Log.h>
|
|
#include <Cytoplasm/Sha.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <StanzaBuilder.h>
|
|
#include <Matrix.h>
|
|
#include <AS.h>
|
|
|
|
static void
|
|
JoinMUC(ParseeData *data, HashMap *event, char *jid, char *muc, char *name)
|
|
{
|
|
char *sender = GrabString(event, 1, "sender");
|
|
|
|
char *rev = StrConcat(3, muc, "/", name);
|
|
int nonce = 0;
|
|
|
|
while (!XMPPJoinMUC(data->jabber, jid, rev, true) && nonce < 20)
|
|
{
|
|
char *nonce_str = StrInt(nonce);
|
|
char *input = StrConcat(4, sender, name, data->id, nonce_str);
|
|
unsigned char *digest = Sha256(input);
|
|
char *hex = ShaToHex(digest);
|
|
|
|
if (strlen(hex) >= 8)
|
|
{
|
|
hex[8] = '\0';
|
|
}
|
|
|
|
Free(rev);
|
|
rev = StrConcat(6, muc, "/", name, "[", hex, "]");
|
|
nonce++;
|
|
|
|
Free(nonce_str);
|
|
Free(digest);
|
|
Free(input);
|
|
Free(hex);
|
|
}
|
|
Free(rev);
|
|
}
|
|
|
|
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 *name = ASGetName(data->config, room_id, state_key);
|
|
|
|
JoinMUC(data, event, jid, muc, name);
|
|
Free(name);
|
|
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;
|
|
|
|
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_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);
|
|
|
|
/* 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 ... */
|
|
JoinMUC(data, event, *from, muc_id, matrix_name);
|
|
|
|
*to = muc_id;
|
|
|
|
Free(matrix_name);
|
|
}
|
|
|
|
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");
|
|
char *encoded_from = NULL;
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
encoded_from = ParseeEncodeMXID(m_sender);
|
|
xmppified_user = StrConcat(3,
|
|
encoded_from, "@", jabber->host
|
|
);
|
|
if (direct)
|
|
{
|
|
to = StrDuplicate(user);
|
|
|
|
Free(chat_id);
|
|
}
|
|
else
|
|
{
|
|
char *name;
|
|
/* Try to find the chat ID */
|
|
muc_id = ParseeGetMUCID(data, chat_id);
|
|
if (!chat_id)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
JoinMUC(data, event, encoded_from, muc_id, name);
|
|
|
|
to = muc_id;
|
|
|
|
Free(name);
|
|
}
|
|
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, unauth ? unauth : (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);
|
|
Free(encoded_from);
|
|
|
|
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);
|
|
}
|
|
|