Parsee/src/XMPPThread/Stanzas/Presence.c
LDA 143bdf0a5a [MOD/FIX] Separate XMPP thread, fix EINVAL issue
threading is good actually. please hmu if you ever trigger the
achievement.
2024-07-17 22:55:25 +02:00

258 lines
7.5 KiB
C

#include "XMPPThread/internal.h"
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Matrix.h>
#include <AS.h>
#include <string.h>
static XMLElement *
CreateVCardRequest(char *from, char *to)
{
XMLElement *vcard_request, *vcard;
char *id;
vcard_request = XMLCreateTag("iq");
XMLAddAttr(vcard_request, "from", from);
XMLAddAttr(vcard_request, "to", to);
XMLAddAttr(vcard_request, "id", (id = StrRandom(16)));
XMLAddAttr(vcard_request, "type", "get");
vcard = XMLCreateTag("vCard");
XMLAddAttr(vcard, "xmlns", "vcard-temp");
XMLAddChild(vcard_request, vcard);
Free(id);
return vcard_request;
}
static UserStatus
GuessStatus(XMLElement *stanza)
{
/* C.F RFC3921: XMPP IM */
XMLElement *show = XMLookForUnique(stanza, "show");
XMLElement *data = show ? ArrayGet(show->children, 0) : NULL;
if (!show || !data)
{
return USER_STATUS_ONLINE;
}
if (StrEquals(data->data, "away") ||
StrEquals(data->data, "xa"))
{
return USER_STATUS_OFFLINE;
}
if (StrEquals(data->data, "chat"))
{
return USER_STATUS_ONLINE;
}
if (StrEquals(data->data, "dnd"))
{
return USER_STATUS_UNAVAILABLE;
}
return USER_STATUS_ONLINE;
}
void
PresenceStanza(ParseeData *args, XMLElement *stanza)
{
#define MUC_USER_NS "http://jabber.org/protocol/muc#user"
XMLElement *user_info;
XMLElement *vc = XMLookForTKV(stanza, "x", "xmlns", "vcard-temp:x:update");
XMLElement *status = XMLookForUnique(stanza, "status");
char *oid = HashMapGet(stanza->attrs, "from");
if ((user_info = XMLookForTKV(stanza, "x", "xmlns", MUC_USER_NS)))
{
XMLElement *item = XMLookForUnique(user_info, "item");
XMLElement *status = XMLookForUnique(user_info, "status");
#define IsStatus(code) (status && \
StrEquals(HashMapGet(status->attrs, "code"), #code))
char *jid = item ? HashMapGet(item->attrs, "jid") : NULL;
char *trim = ParseeTrimJID(jid);
char *from = NULL;
char *type = HashMapGet(stanza->attrs, "type");
char *room = ParseeGetBridgedRoom(args, stanza);
char *decode_from = ParseeLookupJID(oid);
char *real_matrix = ParseeDecodeMXID(decode_from);
char *matrix_user_pl = ParseeEncodeJID(args->config, trim, false);
char *affiliation = HashMapGet(item->attrs, "affiliation");
int power_level = 0;
char *parsee = ParseeMXID(args);
Free(trim);
if (!real_matrix || *real_matrix != '@')
{
Free(real_matrix);
real_matrix = ParseeEncodeJID(args->config, decode_from, false);
}
if (StrEquals(affiliation, "owner"))
{
power_level = 98;
}
else if (StrEquals(affiliation, "admin"))
{
power_level = 50;
}
else if (StrEquals(affiliation, "member"))
{
/* TODO: Reconsider setting the PL if a member, as there's no
* clear reason to do this in some cases. */
power_level = 0;
}
else if (StrEquals(affiliation, "outcast"))
{
power_level = -1;
}
/* Set the user's PL
* TODO: Do NOT change the PL of *real* people nilly-willy.
* In some scenarios, this is really bad behaviour. */
if (room)
{
HashMap *powers = ASGetPL(args->config, room);
HashMap *users = GrabObject(powers, 1, "users");
int64_t level = GrabInteger(powers, 2, "users", matrix_user_pl);
/* I may or may not have fucked up the state hard with this
* lacking any checking. (--gen) */
if (StrEquals(parsee, matrix_user_pl))
{
/* TODO: Give the user an achievement, this is goofy. */
Log(LOG_ERR, "BAD PL DOWNGRADE (%d->%d)", level, power_level);
}
if (users && level != power_level &&
!StrEquals(parsee, matrix_user_pl) &&
matrix_user_pl)
{
JsonValue *val = JsonValueInteger(power_level);
JsonValueFree(JsonSet(users, val, 1, matrix_user_pl));
ASSetPL(args->config, room, powers);
}
else
{
JsonFree(powers);
}
}
if (StrEquals(type, "unavailable") && !StrEquals(parsee, real_matrix))
{
/* If not an MXID, use the Parsee user */
if (IsStatus(301))
{
ASBan(args->config, room, real_matrix);
}
else if (IsStatus(307))
{
ASKick(args->config, room, real_matrix);
}
else if (IsStatus(303))
{
char *nick = HashMapGet(item->attrs, "nick");
ASSetName(args->config, real_matrix, nick);
}
else
{
ASLeave(args->config, room, real_matrix);
}
}
if (jid)
{
ParseePushJIDTable(oid, jid);
}
Free(from);
Free(decode_from);
Free(real_matrix);
Free(matrix_user_pl);
Free(parsee);
Free(room);
}
if (status)
{
XMLElement *status_data = ArrayGet(status->children, 0);
char *decode_from = ParseeLookupJID(oid);
char *trimmed = ParseeTrimJID(decode_from);
char *from_matrix = ParseeEncodeJID(args->config, trimmed, false);
char *status_str = NULL;
if (status_data)
{
status_str = status_data->data;
}
/* TODO: "The server will automatically set a user's presence to
* unavailable if their last active time was over a threshold value
* (e.g. 5 minutes)."
* We _will_ need to manage those cases properly(cronjob?) if we want
* XMPP presences to sync properly */
ASSetStatus(
args->config, from_matrix,
GuessStatus(stanza), status_str
);
Free(decode_from);
Free(from_matrix);
Free(trimmed);
}
if (vc)
{
XMLElement *photo = XMLookForUnique(vc, "photo");
XMLElement *p_dat = photo ? ArrayGet(photo->children, 0) : NULL;
XMLElement *vcard_request;
XMPPComponent *jabber = args->jabber;
char *from;
DbRef *avatars;
HashMap *json;
char *avatar_id;
if (!p_dat)
{
return;
}
avatars = DbLock(args->db, 1, "avatars");
if (!avatars)
{
avatars = DbCreate(args->db, 1, "avatars");
}
json = DbJson(avatars);
avatar_id = GrabString(json, 1, oid);
if (avatar_id && StrEquals(avatar_id, p_dat->data))
{
DbUnlock(args->db, avatars);
return;
}
JsonValueFree(JsonSet(
json, JsonValueString(p_dat->data),
1, oid)
);
DbUnlock(args->db, avatars);
from = ParseeJID(args);
vcard_request = CreateVCardRequest(
from, HashMapGet(stanza->attrs, "from")
);
pthread_mutex_lock(&jabber->write_lock);
XMLEncode(jabber->stream, vcard_request);
StreamFlush(jabber->stream);
pthread_mutex_unlock(&jabber->write_lock);
XMLFreeElement(vcard_request);
Free(from);
}
/* TODO: Sending VCard on presence is slow and stalls the thread */
#undef MUC_USER_NS
}