[ADD/WIP] Chat settings

Right now, they are currently unused. Extensions, and Parsee itself
will be able to use those, though.
This commit is contained in:
LDA 2024-10-29 14:44:55 +01:00
commit 064040c18f
17 changed files with 449 additions and 14 deletions

View file

@ -1,7 +1,3 @@
Some dates for Parsee-related events. They mostly serve as LDA's TODOs with Some dates for Parsee-related events. They mostly serve as LDA's TODOs with
a strict deadline: a strict deadline:
- ~September 2024[tomboyish-bridges-adventure]: - Get star-of-hope out by November 8
Get Parsee into the _Phantasmagoria of Bug View_ stage (essentially
v0.0.1 for a public testing) once I can afford `yama`, and start
bridging the Matrix room alongside a shiny XMPP MUC, bridged by
yours truly.

View file

@ -98,10 +98,6 @@ restricted to Parsee admins, with permission from MUC owners, too
be false by default. be false by default.
- Currently, MUC owners may kick Parsee out, with the effect of unlinking the - Currently, MUC owners may kick Parsee out, with the effect of unlinking the
MUC. MUC.
- Rewrite the XMPP command management to actually be aware of context, instead of
being a baked-in X-macro. It could be useful for MUC admins, which may use commands
specifically within the MUC's own context rather than the global Parsee context(for
Parsee admins).
- Look at XEPS-TBD.TXT for XEPs to be done - Look at XEPS-TBD.TXT for XEPs to be done
- Add a MUC server to Parsee, such that it may be able to hook onto it and therefore - Add a MUC server to Parsee, such that it may be able to hook onto it and therefore
support XMPP->Matrix bridging. support XMPP->Matrix bridging.

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-adminify 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee-adminify 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee-adminify - bootstrap an admin to a new Parsee server parsee-adminify - bootstrap an admin to a new Parsee server

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee-aya 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee-aya 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee-aya - generate some nice Ayaya! documentation parsee-aya - generate some nice Ayaya! documentation

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way. ." The last field is the codename, by the way.
." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN ." ALL MANPAGES FOR PARSEE ARE UNDER PUBLIC DOMAIN
.TH parsee 1 "Parsee Utility" "tomboyish-bridges-adventure" .TH parsee 1 "Parsee Utility" "star-of-hope"
.SH NAME .SH NAME
parsee - the jealous XMPP-Matrix bridge parsee - the jealous XMPP-Matrix bridge

View file

@ -222,6 +222,12 @@ Main(Array *args, HashMap *env)
} }
ParseeInitialiseNickTable(); ParseeInitialiseNickTable();
if (verbose >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Initialising affiliation table");
}
ParseeInitialiseAffiliationTable();
conf.port = parsee_conf->port; conf.port = parsee_conf->port;
conf.threads = parsee_conf->http_threads; conf.threads = parsee_conf->http_threads;
conf.maxConnections = conf.threads << 2; conf.maxConnections = conf.threads << 2;
@ -291,6 +297,7 @@ end:
CronStop(cron); CronStop(cron);
CronFree(cron); CronFree(cron);
ParseeFreeData(conf.handlerArgs); ParseeFreeData(conf.handlerArgs);
ParseeDestroyAffiliationTable();
ParseeDestroyNickTable(); ParseeDestroyNickTable();
ParseeDestroyOIDTable(); ParseeDestroyOIDTable();
ParseeDestroyHeadTable(); ParseeDestroyHeadTable();

View file

@ -109,6 +109,7 @@ ParseeExportConfigYAML(Stream *stream)
StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token); StreamPrintf(stream, "hs_token: \"%s\"\n", config->hs_token);
StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart); StreamPrintf(stream, "sender_localpart: \"%s\"\n", config->sender_localpart);
StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n"); StreamPrintf(stream, "protocols: [\"xmpp\", \"jabber\"]\n");
StreamPrintf(stream, "receive_ephemeral: true\n"); /* TODO: Actually use that field */
StreamPrintf(stream, "\n"); StreamPrintf(stream, "\n");
StreamPrintf(stream, "namespaces: \n"); StreamPrintf(stream, "namespaces: \n");
StreamPrintf(stream, " users:\n"); StreamPrintf(stream, " users:\n");

View file

@ -620,3 +620,90 @@ ParseeIsMUCWhitelisted(ParseeData *data, char *muc)
return ret; return ret;
} }
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat)
{
HashMap *ret, *json;
DbRef *ref;
char *key;
JsonValue *value;
if (!data || !chat)
{
return NULL;
}
ref = DbLockIntent(data->db, DB_HINT_READONLY,
3, "chats", chat, "settings"
);
json = DbJson(ref);
if (!ref)
{
return HashMapCreate();
}
ret = HashMapCreate();
while (HashMapIterate(json, &key, (void **) &value))
{
char *str = JsonValueAsString(value);
HashMapSet(ret, key, StrDuplicate(str));
}
DbUnlock(data->db, ref);
return ret;
}
void
ParseeFreeChatSettings(HashMap *settings)
{
char *key;
void *val;
if (!settings)
{
return;
}
while (HashMapIterate(settings, &key, &val))
{
Free(val);
}
HashMapFree(settings);
}
char *
ParseeGetChatSetting(ParseeData *data, char *chat, char *key)
{
HashMap *map;
char *ret;
if (!data || !chat || !key)
{
return NULL;
}
map = ParseeGetChatSettings(data, chat);
ret = StrDuplicate(HashMapGet(map, key));
ParseeFreeChatSettings(map);
return ret;
}
void
ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val)
{
DbRef *ref;
HashMap *json;
if (!data || !chat || !key || !val)
{
return;
}
ref = DbLockIntent(data->db, DB_HINT_WRITE,
3, "chats", chat, "settings"
);
if (!ref)
{
ref = DbCreate(data->db, 3, "chats", chat, "settings");
}
json = DbJson(ref);
JsonValueFree(HashMapSet(json, key, JsonValueString(val)));
DbUnlock(data->db, ref);
return;
}

View file

@ -0,0 +1,106 @@
#include <Parsee.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <pthread.h>
static pthread_mutex_t affi_lock;
static HashMap *affi_table = NULL;
typedef struct XMPPStatus {
char *role;
char *affiliation;
} XMPPStatus;
static XMPPStatus *
CreateStatus(char *role, char *affiliation)
{
XMPPStatus *ret;
if (!role || !affiliation)
{
return NULL;
}
ret = Malloc(sizeof(*ret));
ret->role = StrDuplicate(role);
ret->affiliation = StrDuplicate(affiliation);
return ret;
}
static void
FreeStatus(XMPPStatus *status)
{
if (!status)
{
return;
}
Free(status->affiliation);
Free(status->role);
Free(status);
}
void
ParseeInitialiseAffiliationTable(void)
{
if (affi_table)
{
return;
}
pthread_mutex_init(&affi_lock, NULL);
pthread_mutex_lock(&affi_lock);
affi_table = HashMapCreate();
pthread_mutex_unlock(&affi_lock);
}
void
ParseePushAffiliationTable(char *user, char *affi, char *role)
{
XMPPStatus *status;
if (!user || !affi || !role)
{
return;
}
pthread_mutex_lock(&affi_lock);
status = CreateStatus(role, affi);
FreeStatus(HashMapSet(affi_table, user, status));
pthread_mutex_unlock(&affi_lock);
}
bool
ParseeLookupAffiliation(char *user, char **affiliation, char **role)
{
XMPPStatus *status;
if (!user || !affiliation || !role)
{
return false;
}
pthread_mutex_lock(&affi_lock);
status = HashMapGet(affi_table, user);
*affiliation = StrDuplicate(status ? status->affiliation : NULL);
*role = StrDuplicate(status ? status->role : NULL);
pthread_mutex_unlock(&affi_lock);
return !!status;
}
void
ParseeDestroyAffiliationTable(void)
{
char *key;
void *val;
if (!affi_table)
{
return;
}
pthread_mutex_lock(&affi_lock);
while (HashMapIterate(affi_table, &key, &val))
{
FreeStatus(val);
}
HashMapFree(affi_table);
affi_table = NULL;
pthread_mutex_unlock(&affi_lock);
pthread_mutex_destroy(&affi_lock);
}

View file

@ -233,6 +233,7 @@ ParseeGenerateMTO(char *common_id)
return NULL; return NULL;
} }
/* TODO: Is HttpUrlEncode okay? */
common_id = HttpUrlEncode(common_id); common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", common_id); matrix_to = StrConcat(2, "https://matrix.to/#/", common_id);
Free(common_id); Free(common_id);

117
src/XMPPCommands/MUCKV.c Normal file
View file

@ -0,0 +1,117 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *muc;
char *key, *val;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Setting MUC properties requires the 'owner' affiliation.");
Free(affiliation);
return;
}
GetFieldValue(key, "key", form);
GetFieldValue(val, "val", form);
if (!key || !val)
{
SetNote("error", "No keys or no value given.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
ParseeSetChatSetting(data, chat_id, key, val);
SetNote("info", "Set key-value pair!");
end:
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}
void
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
char *chat_id = NULL;
char *muc = NULL;
XMLElement *x;
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
HashMap *settings = NULL;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Getting MUC roperties requires the 'owner' affiliation.");
goto end;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
settings = ParseeGetChatSettings(data, chat_id);
x = XMLCreateTag("x");
title = XMLCreateTag("title");
SetTitle(x, "MUC/room settings");
XMLAddChild(x, title);
XMLAddAttr(x, "xmlns", "jabber:x:data");
XMLAddAttr(x, "type", "result");
{
char *key, *val;
reported = XMLCreateTag("reported");
XMLAddChild(x, reported);
/* Report */
Report("key", "Setting's key");
Report("val", "Setting's value");
/* Set */
while (HashMapIterate(settings, &key, (void **) &val))
{
BeginItem();
SetField("key", key);
SetField("val", val);
EndItem();
}
}
XMLAddChild(out, x);
end:
ParseeFreeChatSettings(settings);
Free(affiliation);
Free(chat_id);
Free(muc);
(void) form;
}

View file

@ -0,0 +1,67 @@
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPFormTool.h>
#include <XMPPCommand.h>
#include <Matrix.h>
#include <Parsee.h>
#include <XMPP.h>
#include <XML.h>
#include <AS.h>
void
MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
char *chat_id;
char *parsee;
char *room;
char *muc;
ParseeLookupAffiliation(from, &affiliation, &role);
Free(role);
if (!StrEquals(affiliation, "owner"))
{
SetNote("error", "Unlinking a MUC requires the 'owner' affiliation.");
Free(affiliation);
return;
}
muc = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, muc);
room = ParseeGetRoomID(data, chat_id);
if (!chat_id)
{
SetNote("error", "Couldn't fetch chat ID.");
Free(muc);
return;
}
ParseeUnlinkRoom(data, chat_id);
parsee = ParseeMXID(data);
Free(ASSend(
data->config, room, parsee,
"m.room.message",
MatrixCreateNotice("This room has been unlinked."),
0
));
ASLeave(data->config, room, parsee);
XMPPLeaveMUC(data->jabber, "parsee", muc, "Unlinked by MUC admin.");
/* Setting an error here won't work, as we're communicating through
* the MUC, which we *left*. I guess we can try to defer the leave. */
SetNote("info", "Unlinked MUC.");
Free(affiliation);
Free(chat_id);
Free(parsee);
Free(room);
Free(muc);
(void) form;
}

View file

@ -189,6 +189,7 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
time = UtilTsMillis(); time = UtilTsMillis();
rectime = time; rectime = time;
from = HashMapGet(stanza->attrs, "from"); from = HashMapGet(stanza->attrs, "from");
if (ParseeManageBan(args, from, NULL)) if (ParseeManageBan(args, from, NULL))
{ {
@ -202,7 +203,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
return false; return false;
} }
if (ServerHasXEP421(args, from)) if (ServerHasXEP421(args, from))
{ {
XMLElement *occupant = XMLookForTKV( XMLElement *occupant = XMLookForTKV(

View file

@ -174,6 +174,7 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{ {
ParseePushJIDTable(oid, jid); ParseePushJIDTable(oid, jid);
} }
ParseePushAffiliationTable(oid, affiliation, role);
decode_from = ParseeLookupJID(oid); decode_from = ParseeLookupJID(oid);
real_matrix = ParseeDecodeMXID(decode_from); real_matrix = ParseeDecodeMXID(decode_from);

View file

@ -276,6 +276,44 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id);
/* Finds the MUC JID from a chat ID */ /* Finds the MUC JID from a chat ID */
extern char * ParseeGetMUCID(ParseeData *, char *chat_id); extern char * ParseeGetMUCID(ParseeData *, char *chat_id);
/** Fetches a configuration value from a key in a chat(given a Chat ID),
* as a string or NULL. Keys are to be stored like Java packages(reveres DNS).
* Parsee has the right over any key with the <code>'p.'</code> prefix.
* -----------------------------------
* Returns: a valid string[HEAP] | NULL
* Modifies: NOTHING
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting */
extern char *
ParseeGetChatSetting(ParseeData *data, char *chat, char *key);
/** Fetches the entire configuration in a chat(given a Chat ID), as an hashmap
* of strings.
* -----------------------------------
* Returns: a valid string[HEAP] | NULL
* Modifies: NOTHING
* Thrasher: ParseeFreeChatSettings
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeSetChatSetting, ParseeGetChatSetting */
extern HashMap *
ParseeGetChatSettings(ParseeData *data, char *chat);
/** Destroys memory allocated from a call to {ParseeGetChatSettings}.
* -----------------------
* Returns: NOTHING
* Modifies: {settings}
* Thrashes: {settings}
* See-Also: ParseeGetChatSettings */
extern void
ParseeFreeChatSettings(HashMap *settings);
/** Replaces a configuration key-value pair within the chat's context, which
* can be read with {ParseeGetChatSetting}.
* -------------------------------------
* Returns: NOTHING
* Modifies: the chat context
* See-Also: ParseeGetFromMUCID, ParseeGetFromRoomID, ParseeGetChatSetting */
extern void
ParseeSetChatSetting(ParseeData *data, char *chat, char *key, char *val);
/* Pushes a stanza ID to a chat ID */ /* Pushes a stanza ID to a chat ID */
extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushStanza(ParseeData *, char *chat_id, char *stanza_id, char *origin_id, char *event, char *sender);
extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender); extern void ParseePushDMStanza(ParseeData *, char *room_id, char *stanza_id, char *origin_id, char *event, char *sender);
@ -327,6 +365,11 @@ extern void ParseePushOIDTable(char *muc, char *occupant);
extern char *ParseeLookupOID(char *muc); extern char *ParseeLookupOID(char *muc);
extern void ParseeDestroyOIDTable(void); extern void ParseeDestroyOIDTable(void);
extern void ParseeInitialiseAffiliationTable(void);
extern void ParseePushAffiliationTable(char *user, char *affiliation, char *role);
extern bool ParseeLookupAffiliation(char *muc, char **affiliation, char **role);
extern void ParseeDestroyAffiliationTable(void);
extern void ParseeInitialiseJIDTable(void); extern void ParseeInitialiseJIDTable(void);
extern void ParseePushJIDTable(char *muc, char *bare); extern void ParseePushJIDTable(char *muc, char *bare);
extern char *ParseeLookupJID(char *muc); extern char *ParseeLookupJID(char *muc);

View file

@ -45,5 +45,18 @@ typedef enum XMPPCommandFlags {
XMPP_COMMAND(WhitelistCallback, XMPPCMD_ADMINS, "wl", "Get Parsee's chat whitelist", {}) \ XMPP_COMMAND(WhitelistCallback, XMPPCMD_ADMINS, "wl", "Get Parsee's chat whitelist", {}) \
XMPP_COMMAND(MUCInformationID, XMPPCMD_MUC, "muc-info-id", "Get bridged Matrix room ID", {}) \ XMPP_COMMAND(MUCInformationID, XMPPCMD_MUC, "muc-info-id", "Get bridged Matrix room ID", {}) \
XMPP_COMMAND(MUCInformationCID, XMPPCMD_MUC, "muc-info-cid", "Get MUC's internal ID", {}) \ XMPP_COMMAND(MUCInformationCID, XMPPCMD_MUC, "muc-info-cid", "Get MUC's internal ID", {}) \
XMPP_COMMAND(MUCUnlink, XMPPCMD_MUC, "muc-unlink", "Unlinks MUC", {}) \
XMPP_COMMAND(MUCSetKey, XMPPCMD_MUC, "muc-set-key", "Sets a key within the MUC/room's context", { \
XMPPOption *key = XMPPCreateText(true, "key", ""); \
XMPPOption *val = XMPPCreateText(true, "val", ""); \
XMPPSetDescription(key, "Key"); \
XMPPSetDescription(val, "Value"); \
XMPPAddOption(cmd, key); \
XMPPAddOption(cmd, val); \
\
XMPPSetFormTitle(cmd, "Set a key-value pair"); \
XMPPSetFormInstruction(cmd, "Replace a key with a specific value"); \
}) \
XMPP_COMMAND(MUCGetKeys, XMPPCMD_MUC, "muc-get-keys", "Get all key-values in the MUC/room.", {}) \
XMPPCOMMANDS XMPPCOMMANDS

View file

@ -113,7 +113,7 @@ Main(Array *args, HashMap *env)
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
Log(LOG_ERR, "%s: couldn't open DB '%s'", exec, db_path); Log(LOG_ERR, "%s: couldn't open config '%s' or couldnt edit DB", exec, db_path);
(void) env; (void) env;
return EXIT_FAILURE; return EXIT_FAILURE;
} }