[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
parent 55ac682d26
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
a strict deadline:
- ~September 2024[tomboyish-bridges-adventure]:
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.
- Get star-of-hope out by November 8

View file

@ -98,10 +98,6 @@ restricted to Parsee admins, with permission from MUC owners, too
be false by default.
- Currently, MUC owners may kick Parsee out, with the effect of unlinking the
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
- Add a MUC server to Parsee, such that it may be able to hook onto it and therefore
support XMPP->Matrix bridging.

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way.
." 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
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.
." 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
parsee-aya - generate some nice Ayaya! documentation

View file

@ -1,6 +1,6 @@
." The last field is the codename, by the way.
." 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
parsee - the jealous XMPP-Matrix bridge

View file

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

View file

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

View file

@ -620,3 +620,90 @@ ParseeIsMUCWhitelisted(ParseeData *data, char *muc)
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;
}
/* TODO: Is HttpUrlEncode okay? */
common_id = HttpUrlEncode(common_id);
matrix_to = StrConcat(2, "https://matrix.to/#/", 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();
rectime = time;
from = HashMapGet(stanza->attrs, "from");
if (ParseeManageBan(args, from, NULL))
{
@ -202,7 +203,6 @@ MessageStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
return false;
}
if (ServerHasXEP421(args, from))
{
XMLElement *occupant = XMLookForTKV(

View file

@ -174,6 +174,7 @@ PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
ParseePushJIDTable(oid, jid);
}
ParseePushAffiliationTable(oid, affiliation, role);
decode_from = ParseeLookupJID(oid);
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 */
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 */
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);
@ -327,6 +365,11 @@ extern void ParseePushOIDTable(char *muc, char *occupant);
extern char *ParseeLookupOID(char *muc);
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 ParseePushJIDTable(char *muc, char *bare);
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(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(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

View file

@ -113,7 +113,7 @@ Main(Array *args, HashMap *env)
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;
return EXIT_FAILURE;
}