Compare commits

...

5 commits

Author SHA1 Message Date
LDA
de40106932 Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-21 11:25:08 +01:00
LDA
81edbec1d0 Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-20 22:09:03 +01:00
LDA
e3979e0e6e Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-17 16:25:30 +01:00
LDA
6d7d85662b Merge branch 'master' of https://git.kappach.at/lda/Parsee into janet 2024-11-16 14:36:09 +01:00
LDA
ca87972b3a [ADD/WIP] Push all the Janet changes
This is still unstable(and I still need to design/document the exposed
API)! Do(n't) go and use it!
2024-11-16 14:11:32 +01:00
49 changed files with 3544 additions and 88 deletions

8
.gitignore vendored
View file

@ -24,3 +24,11 @@ tags
!.forgejo
!.forgejo/*
!.forgejo/**
# Janet
janet/*
janet/**
janet
extensions
extensions/*
extensions/**

View file

@ -368,7 +368,7 @@ write_objects(FILE *makefile, str_array_t *sources)
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
char *obj = string_cat("build", strchr(ofl, '/'));
fprintf(makefile, " %s", obj);
free(ofl);
@ -516,7 +516,8 @@ main_build(int argc, char *argv[])
ssize_t nread;
bool with_static = false, with_lmdb = false;
int opt;
str_array_t *sources, *images, *utils, *aya;
str_array_t *sources, *janet = NULL, *images, *utils, *aya;
char *janet_dir = NULL;
if (repo)
{
@ -531,7 +532,7 @@ main_build(int argc, char *argv[])
repo = strdup("N/A");
}
while ((opt = getopt(argc, argv, "sl")) != -1)
while ((opt = getopt(argc, argv, "slJ:")) != -1)
{
switch (opt)
{
@ -542,6 +543,9 @@ main_build(int argc, char *argv[])
with_lmdb = with_static;
with_static = true;
break;
case 'J':
janet_dir = strdup(optarg);
break;
}
}
@ -560,13 +564,22 @@ main_build(int argc, char *argv[])
/* Create all objects */
sources = collect_sources("src", true, ".c");
if (janet_dir)
{
janet = collect_sources(janet_dir, true, ".c");
for (i = 0; i < str_array_len(janet); i++)
{
str_array_add(sources, str_array_get(janet, i));
}
str_array_free(janet);
}
images = collect_sources("etc/media", true, ".png");
fprintf(makefile, "binary:");
write_objects(makefile, sources);
write_images(makefile, images);
fprintf(makefile, "\n\t");
{
fprintf(makefile, "$(CC) -o $(BINARY)");
fprintf(makefile, "$(CC) -lm -o $(BINARY)");
if (with_static)
{
fprintf(makefile, " -static");
@ -579,7 +592,7 @@ main_build(int argc, char *argv[])
fprintf(makefile, " -lm -lpthread -lmbedtls -lmbedx509 -lmbedcrypto -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(LDFLAGS)\n");
fprintf(makefile, " -lCytoplasm -lm $(LDFLAGS)\n");
}
/* Write rules for every source */
@ -587,7 +600,7 @@ main_build(int argc, char *argv[])
{
char *src = str_array_get(sources, i);
char *ofl = string_rep_ext(src, ".c", ".o");
char *obj = string_cat("build", ofl + 3);
char *obj = string_cat("build", strchr(ofl, '/'));
fprintf(makefile, "%s: %s", obj, src);
analyse_dependencies(makefile, src);
@ -605,6 +618,10 @@ main_build(int argc, char *argv[])
str_array_free(s);
fprintf(makefile, "\t$(CC) -c -Isrc -Isrc/include -I$(CYTO_INC) ");
if (janet_dir)
{
fprintf(makefile, "-DJANET -I %s ", janet_dir);
}
fprintf(makefile, "-DVERSION=\"\\\"$(VERSION)\\\"\" ");
fprintf(makefile, "-DNAME=\"\\\"$(NAME)\\\"\" ");
fprintf(makefile, "-DCODE=\"\\\"$(CODE)\\\"\" ");
@ -675,7 +692,7 @@ main_build(int argc, char *argv[])
fprintf(makefile, " -lCytoplasm -lmbedtls -lmbedx509 -lmbedcrypto");
if (with_lmdb) fprintf(makefile, " -llmdb");
}
fprintf(makefile, " -lCytoplasm $(CFLAGS)\n");
fprintf(makefile, " -lCytoplasm -lm $(CFLAGS)\n");
}
free(ofl);
@ -763,6 +780,7 @@ main_build(int argc, char *argv[])
str_array_free(aya);
fflush(makefile);
fclose(makefile);
free(janet_dir);
free(repo);
return EXIT_SUCCESS;
}

View file

@ -12,7 +12,7 @@ CommandHead(CmdSetPL, cmd, argp)
char *user = HashMapGet(cmd->arguments, "user");
char *room = HashMapGet(cmd->arguments, "room");
char *pl_str = HashMapGet(cmd->arguments, "pl");
long pl = strtol(pl_str, NULL, 10);
long pl = pl_str ? strtol(pl_str, NULL, 10) : 0;
HashMap *map;

74
src/Extensions.c Normal file
View file

@ -0,0 +1,74 @@
#ifndef JANET
#include <Extension.h>
#include <stdlib.h>
bool
ExtensionEnabled(void)
{
return false;
}
Extensions *
ExtensionCreateContext(ParseeData *data)
{
(void) data;
return NULL;
}
void
ExtensionDestroyContext(Extensions *ctx)
{
(void) ctx;
}
Extension *
ExtensionLoadBasic(Extensions *ctx, char *id, char *file)
{
(void) file;
(void) ctx;
(void) id;
return NULL;
}
void
ExtensionLoadDir(Extensions *ctx, char *dir)
{
(void) ctx;
(void) dir;
}
bool
ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType type)
{
(void) ctx;
(void) stanza;
return false;
}
bool
ExtensionPushEvent(Extensions *ctx, HashMap *obj)
{
(void) ctx;
(void) obj;
}
void
ExtensionManageCommands(Extensions *ctx, XMLElement *stanza)
{
(void) ctx;
(void) stanza;
}
void
ExtensionRequestCommands(Extensions *ctx)
{
(void) ctx;
}
void
ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza)
{
(void) ctx;
(void) jid;
(void) query;
(void) stanza;
}
void
ExtensionReload(Extensions *ctx, char *id)
{
(void) ctx;
(void) id;
}
#endif

366
src/Extensions/DB.c Normal file
View file

@ -0,0 +1,366 @@
#ifdef JANET
#include "Extensions/Internal.h"
static int JanetDbGet(void *data0, Janet key, Janet *out);
static void JanetMarshalDb(void *data0, JanetMarshalContext *ctx);
static void *JanetUnmarshalDb(JanetMarshalContext *ctx);
static void JanetDbToString(void *data0, JanetBuffer *buf);
static int32_t JanetHashDb(void *data0, size_t size);
static int JanetCloseDb(void *data0, size_t size);
static Janet JanetDbNext(void *data0, Janet next);
static Janet JanetRefClose(int32_t argc, Janet *argv);
static Janet JanetRefGet(int32_t argc, Janet *argv);
static Janet JanetRefSet(int32_t argc, Janet *argv);
static const JanetMethod db_methods[] = {
{ "get", JanetRefGet },
{ "set", JanetRefSet },
{ "close", JanetRefClose },
{ NULL, NULL }
};
const JanetAbstractType janet_db_type = {
.name = "db-ref",
.gc = JanetCloseDb, .gcmark = NULL,
.get = JanetDbGet, .put = NULL,
.marshal = JanetMarshalDb, .unmarshal = JanetUnmarshalDb,
.tostring = JanetDbToString,
.compare = NULL,
.hash = JanetHashDb,
.next = JanetDbNext,
JANET_ATEND_NEXT
};
typedef struct DbAndRef {
Db *database;
DbRef *ref;
bool valid;
} DbAndRef;
static Janet
CreateFromDb(Db *db, DbRef *ref)
{
JanetAbstract *abs;
abs = janet_abstract(&janet_db_type, sizeof(DbAndRef));
((DbAndRef *)(abs))->database = db;
((DbAndRef *)(abs))->ref = ref;
((DbAndRef *)(abs))->valid = true;
return janet_wrap_abstract(abs);
}
static void
CloseFromJanet(Janet v)
{
DbAndRef *indirect = janet_checkabstract(v, &janet_db_type);
if (indirect)
{
indirect->valid = false;
indirect->database = NULL;
indirect->ref = NULL;
}
}
static DbRef *
RefFromJanet(Janet v)
{
DbAndRef *indirect = janet_checkabstract(v, &janet_db_type);
return (indirect && indirect->valid) ? indirect->ref : NULL;
}
static Db *
DbFromJanet(Janet v)
{
DbAndRef *indirect = janet_checkabstract(v, &janet_db_type);
return (indirect && indirect->valid) ? indirect->database : NULL;
}
static int
JanetDbGet(void *data0, Janet key, Janet *out)
{
JanetKeyword kw;
if (!janet_checktype(key, JANET_KEYWORD))
{
return 0;
}
kw = janet_unwrap_keyword(key);
(void) data0;
return janet_getmethod(kw, db_methods, out);
}
static void
JanetMarshalDb(void *data0, JanetMarshalContext *ctx)
{
janet_marshal_abstract(ctx, data0);
}
static void *
JanetUnmarshalDb(JanetMarshalContext *ctx)
{
DbAndRef *data = janet_unmarshal_abstract(ctx, sizeof(DbAndRef));
return data;
}
static int
JanetCloseDb(void *data0, size_t size)
{
DbAndRef *data = data0;
if (data && data->valid)
{
data->valid = false;
DbUnlock(data->database, data->ref);
data->database = NULL;
data->ref = NULL;
}
return 0;
}
static void
JanetDbToString(void *data0, JanetBuffer *buf)
{
janet_buffer_push_cstring(buf, "[" NAME " database reference]");
}
static int32_t
JanetHashDb(void *data0, size_t size)
{
uintptr_t datap = (uintptr_t) data0;
(void) size;
/* Hm. */
return (int32_t) datap;
}
static Janet
JanetDbNext(void *data0, Janet next)
{
(void) data0;
return janet_nextmethod(db_methods, next);
}
static Janet
JanetRefClose(int32_t argc, Janet *argv)
{
Db *db;
DbRef *ref;
janet_fixarity(argc, 1);
if (!(ref = RefFromJanet(argv[0])) ||
!(db = DbFromJanet(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a DbRef *")
);
return janet_wrap_nil();
}
DbUnlock(db, ref);
CloseFromJanet(argv[0]);
return janet_wrap_true();
}
static Janet
JanetRefGet(int32_t argc, Janet *argv)
{
DbRef *ref;
HashMap *json;
janet_fixarity(argc, 1);
if (!(ref = RefFromJanet(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a DbRef *")
);
return janet_wrap_nil();
}
json = DbJson(ref);
return JsonToJanet(json);
}
static Janet
JanetRefSet(int32_t argc, Janet *argv)
{
DbRef *ref;
JanetTable *table;
HashMap *json;
bool ret;
janet_fixarity(argc, 2);
if (!(ref = RefFromJanet(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a DbRef *")
);
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_TABLE))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("object to replace isnt JSON")
);
return janet_wrap_nil();
}
table = janet_unwrap_table(argv[1]);
if (!(json = JanetToJson(table)))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("object to replace isnt JSON")
);
return janet_wrap_nil();
}
ret = DbJsonSet(ref, json);
JsonFree(json);
return janet_wrap_boolean(ret);
}
Janet
JanetDbExists(int32_t argc, Janet *argv)
{
ParseeData *data;
Array *args;
int32_t i;
bool ret;
janet_arity(argc, 2, -1);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a ParseeData *")
);
return janet_wrap_nil();
}
args = ArrayCreate();
for (i = 1; i < argc; i++)
{
char *string = (char *) janet_to_string(argv[i]);
ArrayAdd(args, string);
}
ret = DbExistsArgs(data->db, args);
ArrayFree(args);
return janet_wrap_boolean(ret);
}
Janet
JanetDbList(int32_t argc, Janet *argv)
{
ParseeData *data;
Array *args, *list;
JanetArray *arr;
int32_t i;
janet_arity(argc, 1, -1);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a ParseeData *")
);
return janet_wrap_nil();
}
args = ArrayCreate();
for (i = 1; i < argc; i++)
{
char *string = (char *) janet_to_string(argv[i]);
ArrayAdd(args, string);
}
list = DbListArgs(data->db, args);
arr = janet_array(0);
for (i = 0; i < (int32_t) ArraySize(list); i++)
{
char *ent = ArrayGet(list, i);
janet_array_push(arr, janet_cstringv(ent));
}
ArrayFree(args);
DbListFree(list);
return janet_wrap_array(arr);
}
Janet
JanetDbLock(int32_t argc, Janet *argv)
{
ParseeData *data;
Array *args;
DbRef *ref;
int32_t i;
janet_arity(argc, 2, -1);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a ParseeData *")
);
return janet_wrap_nil();
}
args = ArrayCreate();
for (i = 1; i < argc; i++)
{
char *string = (char *) janet_to_string(argv[i]);
ArrayAdd(args, string);
}
ref = DbLockArgs(data->db, args);
ArrayFree(args);
if (!ref)
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("couldn't lock database")
);
return janet_wrap_nil();
}
return CreateFromDb(data->db, ref);
}
Janet
JanetDbCreate(int32_t argc, Janet *argv)
{
ParseeData *data;
Array *args;
DbRef *ref;
int32_t i;
janet_arity(argc, 2, -1);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("self isnt a ParseeData *")
);
return janet_wrap_nil();
}
args = ArrayCreate();
for (i = 1; i < argc; i++)
{
char *string = (char *) janet_to_string(argv[i]);
ArrayAdd(args, string);
}
ref = DbCreateArgs(data->db, args);
ArrayFree(args);
if (!ref)
{
janet_signalv(
JANET_SIGNAL_ERROR,
janet_cstringv("couldn't create database")
);
return janet_wrap_nil();
}
return CreateFromDb(data->db, ref);
}
#endif

222
src/Extensions/Data.c Normal file
View file

@ -0,0 +1,222 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include "Extensions/Functions.h"
static int JanetDataGet(void *data0, Janet key, Janet *out);
static void JanetMarshalData(void *data0, JanetMarshalContext *ctx);
static void *JanetUnmarshalData(JanetMarshalContext *ctx);
static void JanetDataToString(void *data0, JanetBuffer *buf);
static int32_t JanetHashData(void *data0, size_t size);
static Janet JanetDataNext(void *data0, Janet next);
static Janet JanetShimMXC(int32_t argc, Janet *argv);
static Janet JanetMTO(int32_t argc, Janet *argv);
static Janet JanetIsAdmin(int32_t argc, Janet *argv);
static Janet JanetUptime(int32_t argc, Janet *argv);
static Janet JanetGlobalBan(int32_t argc, Janet *argv);
static const JanetMethod data_methods[] = {
/* Matrix */
{ "matrix/invite", JanetMatrixInvite },
{ "matrix/find", JanetMatrixFind },
{ "matrix/send", JanetMatrixSend },
{ "matrix/shim-mxc", JanetShimMXC },
{ "matrix/matrix-to", JanetMTO },
/* Bridge */
{ "bridge/get", JanetGetKey },
{ "bridge/set", JanetSetKey },
{ "bridge/stanza-info", JanetStanzaInfo },
/* XMPP */
{ "xmpp/write", JanetWriteStanza },
/* Database */
{ "db/lock", JanetDbLock },
{ "db/exists?", JanetDbExists },
{ "db/create", JanetDbCreate },
{ "db/list", JanetDbList },
/* General */
{ "admin?", JanetIsAdmin },
{ "uptime", JanetUptime },
{ "global-ban", JanetGlobalBan },
{ NULL, NULL }
};
const JanetAbstractType janet_data_type = {
.name = "parsee-ctx",
.gc = NULL, .gcmark = NULL,
.get = JanetDataGet, .put = NULL,
.marshal = JanetMarshalData, .unmarshal = JanetUnmarshalData,
.tostring = JanetDataToString,
.compare = NULL,
.hash = JanetHashData,
.next = JanetDataNext,
JANET_ATEND_NEXT
};
ParseeData *
GetParseeData(Janet v)
{
ParseeData **indirect = janet_checkabstract(v, &janet_data_type);
return indirect ? *indirect : NULL;
}
Janet
FromParseeData(ParseeData *data)
{
JanetAbstract abs;
if (!data)
{
return janet_wrap_nil();
}
abs = janet_abstract(&janet_data_type, sizeof(ParseeData *));
*((ParseeData **) abs) = data;
return janet_wrap_abstract(abs);
}
static int
JanetDataGet(void *data0, Janet key, Janet *out)
{
JanetKeyword kw;
if (!janet_checktype(key, JANET_KEYWORD))
{
return 0;
}
kw = janet_unwrap_keyword(key);
(void) data0;
return janet_getmethod(kw, data_methods, out);
}
static void
JanetMarshalData(void *data0, JanetMarshalContext *ctx)
{
janet_marshal_abstract(ctx, data0);
}
static void *
JanetUnmarshalData(JanetMarshalContext *ctx)
{
ParseeData **data = janet_unmarshal_abstract(ctx, sizeof(ParseeData *));
return data ? *data : NULL;
}
static void
JanetDataToString(void *data0, JanetBuffer *buf)
{
janet_buffer_push_cstring(buf, "[" NAME " context type]");
}
static int32_t
JanetHashData(void *data0, size_t size)
{
uintptr_t datap = (uintptr_t) data0;
(void) size;
/* We're casting down, but in a context where there is really
* just one ParseeData, is that really such a crime? */
return (int32_t) datap;
}
static Janet
JanetDataNext(void *data0, Janet next)
{
(void) data0;
return janet_nextmethod(data_methods, next);
}
static Janet
JanetIsAdmin(int32_t argc, Janet *argv)
{
ParseeData *data;
char *str;
janet_fixarity(argc, 2);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
str = (char *) janet_to_string(argv[1]);
return janet_wrap_boolean(ParseeIsAdmin(data, str));
}
static Janet
JanetShimMXC(int32_t argc, Janet *argv)
{
ParseeData *data;
Janet v;
char *mxc, *shimmed;
janet_fixarity(argc, 2);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
mxc = (char *) janet_to_string(argv[1]);
shimmed = ParseeToUnauth(data, mxc);
if (!shimmed)
{
return janet_wrap_nil();
}
v = janet_cstringv(shimmed);
Free(shimmed);
return v;
}
static Janet
JanetMTO(int32_t argc, Janet *argv)
{
ParseeData *data;
Janet v;
char *common, *mto;
janet_fixarity(argc, 2);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
common = (char *) janet_to_string(argv[1]);
mto = ParseeGenerateMTO(common);
if (!mto)
{
return janet_wrap_nil();
}
v = janet_cstringv(mto);
Free(mto);
(void) data;
return v;
}
static Janet
JanetUptime(int32_t argc, Janet *argv)
{
ParseeData *data;
janet_fixarity(argc, 1);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
(void) data;
return janet_wrap_number(ParseeUptime()/1000.);
}
static Janet
JanetGlobalBan(int32_t argc, Janet *argv)
{
ParseeData *data;
const char *user, *reason = NULL;
janet_arity(argc, 2, 3);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
if (!(user = janet_getcstring(argv, 1)))
{
return janet_wrap_nil();
}
if (argc == 3 && janet_checktype(argv[2], JANET_STRING))
{
reason = janet_getcstring(argv, 2);
}
ParseeGlobalBan(data, (char *) user, (char *) reason);
return janet_wrap_true();
}
#endif

207
src/Extensions/Functions.c Normal file
View file

@ -0,0 +1,207 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include "Extensions/Functions.h"
Janet
JanetMilliseconds(int32_t argc, Janet *argv)
{
janet_fixarity(argc, 0);
return janet_wrap_number(UtilTsMillis()/1000.);
}
Janet
JanetCytoVersion(int32_t argc, Janet *argv)
{
janet_fixarity(argc, 0);
return janet_cstringv(CytoplasmGetVersionStr());
}
static const JanetReg functions[] = {
/* Cytoplasm */
{
"cyto/version", JanetCytoVersion,
"(cyto/version)\n\n"
"Returns the Cytoplasm version as a string."
},
{
"cyto/ts", JanetMilliseconds,
"(cyto/ts)\n\n"
"Returns the timestamp as seconds since 01-01-1970."
},
/* XML */
{
"xml/data", JanetCreateXMLData,
"(xml/data [PARENT?] [TEXT])\n\n"
"Creates a data XML element with freeform text."
},
{
"xml/tag", JanetCreateXMLTag,
"(xml/tag [PARENT?] [NAME])\n\n"
"Creates a simple XML tag with a name"
},
/* JSON */
{
"json/parse", JanetParseJSON,
"(json/parse [INPUT:buffer]) -> table|NIL\n\n"
"Parses a JSON buffer into a table."
},
/* HTTP */
{
"http/request", JanetHttpRequest,
"(http/request "
"[METHOD:string] "
"[URL:string] "
"[HEADERS:table?] "
"[DATA:buffer])\n\n"
"Creates and sends an HTTP request, and returns an HTTP response"
},
{NULL, NULL, NULL}
};
void
SetupBasicExtensionContext(Extension *ext, JanetTable *env)
{
char *parsee;
/* Constants */
janet_def(ext->env,
"parsee/ctx", FromParseeData(ext->source->data),
"The Parsee context used, which maps directly to a ParseeData "
"structure pointer in C99land. Used in various functions."
);
parsee = ParseeJID(ext->source->data);
janet_def(ext->env,
"parsee/jid", janet_cstringv(parsee),
"The Parsee bot's Jabber ID as a string"
);
Free(parsee);
parsee = ParseeMXID(ext->source->data);
janet_def(ext->env,
"parsee/mxid", janet_cstringv(parsee),
"The Parsee bot's MXID as a string"
);
Free(parsee);
janet_def(ext->env,
"parsee/id", janet_cstringv(ext->id),
"The extension's 'identifier'(reverse DNS) notation."
);
/* Functions */
janet_cfuns(env, "parsee", functions);
/* "Why bother with C99?" */
#define CheckType(var, type, str) \
"(if (not= (type " #var ") :" #type ") (error `" str "`))"
janet_dostring(ext->env,
"(defn xml/get-attr[xml key] "
CheckType(xml, table, "XML is not a table")
CheckType(key, string, "KEY is not a string")
"(get (get xml :xml-attrs) key) "
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xml/set-attr[xml key val] "
CheckType(xml, table, "XML is not a table")
CheckType(key, string, "KEY is not a string")
CheckType(val, string, "VAL is not a string")
"(put (get xml :xml-attrs) key val) "
/* Just to make sure this function returns NIL */
"nil"
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/create-note [type msg] "
"(def note (xml/tag `note`)) "
"(def mesg (xml/data note msg)) "
"(xml/set-attr note `type` type) "
"note"
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/create-form [] "
"(def x (xml/tag `x`)) "
"(xml/set-attr x `xmlns` `jabber:x:data`) "
"x"
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/set-form-title [x title] "
"(def title-xml (xml/tag x `title`)) "
"(def title-txt (xml/data title-xml title)) "
"title-xml"
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/generate-table [x fields elems] "
CheckType(x, table, "Form is not a table")
CheckType(fields, table, "Fields is not a table")
CheckType(elems, array, "Elems is not an array")
"(def reported (xml/tag x `reported`)) "
"(loop [[id name] :pairs fields] "
"(def field (xml/tag reported `field`)) "
"(xml/set-attr field `var` id) "
"(xml/set-attr field `label` name) "
") "
"(loop [entry :in elems] "
CheckType(entry, table, "Entry is not a table.")
"(def xml-entry (xml/tag x `item`)) "
"(loop [[id val] :pairs entry] "
"(def xml-field (xml/tag xml-entry `field`)) "
"(def xml-value (xml/tag xml-field `value`)) "
"(xml/data xml-value val) "
"(xml/set-attr xml-field `var` id) "
") "
") "
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/find-tag [xml tag] "
"(var ret nil) "
"(loop [xml-field :in (get xml :xml-children)] "
"(if (= (get xml-field :xml-name) tag) (do"
"(set ret xml-field) "
"(break)"
")) "
") "
"ret "
")"
, "embed:" __FILE__, NULL
);
janet_dostring(ext->env,
"(defn xmppcmd/get-field [submit key] "
CheckType(submit, table, "Input is not a table")
"(var value nil) "
"(loop [xml-field :in (get submit :xml-children)] "
"(if (and (= (get xml-field :xml-name) `field`) (= (get (get xml-field :xml-attrs) `var`) key)) (do"
"(set value (xmppcmd/find-tag xml-field `value`)) "
"(set value (get (get (get value :xml-children) 0) :xml-data)) "
"(break)"
")) "
") "
"value "
") "
, "embed:" __FILE__, NULL
);
#undef CheckType
}
#endif

View file

@ -0,0 +1,34 @@
#ifdef JANET
#include <janet.h>
#define Bind(name) Janet Janet##name(int32_t argc, Janet *argv)
Bind(Milliseconds);
Bind(CytoVersion);
Bind(WriteStanza);
Bind(CreateXMLData);
Bind(CreateXMLTag);
Bind(SetXMLAttr);
Bind(ParseJSON);
Bind(MatrixInvite);
Bind(MatrixFind);
Bind(MatrixSend);
Bind(StanzaInfo);
Bind(GetKey);
Bind(SetKey);
Bind(DbLock);
Bind(DbCreate);
Bind(DbList);
Bind(DbExists);
Bind(HttpRequest);
#endif

258
src/Extensions/Http.c Normal file
View file

@ -0,0 +1,258 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include "Extensions/Functions.h"
#include <Cytoplasm/Uri.h>
#include <Cytoplasm/HttpClient.h>
static int JanetHttpGet(void *http0, Janet key, Janet *out);
static void JanetMarshalHttp(void *http0, JanetMarshalContext *ctx);
static void *JanetUnmarshalHttp(JanetMarshalContext *ctx);
static void JanetHttpToString(void *http0, JanetBuffer *buf);
static int32_t JanetHashHttp(void *http0, size_t size);
static Janet JanetHttpNext(void *http0, Janet next);
static int JanetCloseHttp(void *data0, size_t size);
static Janet JanetHttpCode(int32_t argc, Janet *argv);
static Janet JanetHttpHeaders(int32_t argc, Janet *argv);
static Janet JanetHttpResponse(int32_t argc, Janet *argv);
static const JanetMethod http_methods[] = {
{ "code", JanetHttpCode },
{ "headers", JanetHttpHeaders },
{ "response", JanetHttpResponse },
{ NULL, NULL }
};
typedef struct HttpStruct {
int code;
HashMap *headers;
uint8_t *data;
int32_t size;
} HttpStruct;
const JanetAbstractType janet_http_type = {
.name = "http-response",
.gc = JanetCloseHttp, .gcmark = NULL,
.get = JanetHttpGet, .put = NULL,
.marshal = JanetMarshalHttp, .unmarshal = JanetUnmarshalHttp,
.tostring = JanetHttpToString,
.compare = NULL,
.hash = JanetHashHttp,
.next = JanetHttpNext,
JANET_ATEND_NEXT
};
Janet
JanetHttpRequest(int32_t argc, Janet *argv)
{
const char *method_str = NULL;
const char *path_str = NULL;
JanetTable *headers = NULL;
JanetAbstract *abs = NULL;
JanetBuffer *data = NULL;
HttpClientContext *ctx;
HashMap *raw_headers;
char *header, *value;
HttpStruct *http;
int code, ch;
int32_t i;
Uri *url;
janet_arity(argc, 2, 4);
if (!(method_str = janet_getcstring(argv, 0)) ||
!(path_str = janet_getcstring(argv, 1)))
{
return janet_wrap_nil();
}
if (argc >= 3)
{
headers = janet_gettable(argv, 2);
}
if (argc >= 4)
{
data = janet_getbuffer(argv, 3);
}
url = UriParse(path_str);
if (!url ||
(!StrEquals(url->proto, "http") && !StrEquals(url->proto, "https")))
{
UriFree(url);
return janet_wrap_nil();
}
if (!url->port || StrEquals(url->proto, "http"))
{
url->port = 80;
}
if (!url->port || StrEquals(url->proto, "https"))
{
url->port = 443;
}
ctx = HttpRequest(
HttpRequestMethodFromString(method_str),
StrEquals(url->proto, "https") ? HTTP_FLAG_TLS : HTTP_FLAG_NONE,
url->port, url->host, url->path
);
UriFree(url);
if (!ctx)
{
return janet_wrap_nil();
}
for (i = 0; headers && i < headers->capacity; i++)
{
JanetKV kv = headers->data[i];
char *key, *value;
if (!janet_checktype(kv.key, JANET_STRING))
{
continue;
}
key = (char *) janet_unwrap_string(kv.key);
value = (char *) janet_to_string(kv.value);
HttpRequestHeader(ctx, key, value);
}
if (data)
{
char *len_str = StrInt((int) data->count);
HttpRequestHeader(ctx, "Content-Length", len_str);
Free(len_str);
}
HttpRequestSendHeaders(ctx);
if (data)
{
for (i = 0; i < data->count; i++)
{
uint8_t byte = data->data[i];
StreamPutc(HttpClientStream(ctx), byte);
}
}
code = HttpRequestSend(ctx);
raw_headers = HttpResponseHeaders(ctx);
abs = janet_abstract(&janet_http_type, sizeof(HttpStruct));
http = (HttpStruct *) abs;
http->code = code;
http->headers = HashMapCreate();
http->size = 0;
http->data = NULL;
while (HashMapIterate(raw_headers, &header, (void **) &value))
{
HashMapSet(http->headers, header, StrDuplicate(value));
}
while ((ch = StreamGetc(HttpClientStream(ctx))) != EOF)
{
http->data = Realloc(http->data, ++(http->size));
http->data[http->size-1] = ch;
}
HttpClientContextFree(ctx);
return janet_wrap_abstract(abs);
}
static int
JanetHttpGet(void *http0, Janet key, Janet *out)
{
JanetKeyword kw;
if (!janet_checktype(key, JANET_KEYWORD))
{
return 0;
}
kw = janet_unwrap_keyword(key);
(void) http0;
return janet_getmethod(kw, http_methods, out);
}
static void
JanetMarshalHttp(void *http0, JanetMarshalContext *ctx)
{
janet_marshal_abstract(ctx, http0);
}
static void *
JanetUnmarshalHttp(JanetMarshalContext *ctx)
{
HttpStruct *http = janet_unmarshal_abstract(ctx, sizeof(HttpStruct));
return http;
}
static void
JanetHttpToString(void *http0, JanetBuffer *buf)
{
janet_buffer_push_cstring(buf, "[" NAME " HTTP object]");
}
static int32_t
JanetHashHttp(void *http0, size_t size)
{
uintptr_t httpp = (uintptr_t) http0;
(void) size;
return (int32_t) httpp;
}
static Janet
JanetHttpNext(void *http0, Janet next)
{
(void) http0;
return janet_nextmethod(http_methods, next);
}
static int
JanetCloseHttp(void *data0, size_t size)
{
HttpStruct *http = data0;
char *key;
void *val;
while (HashMapIterate(http->headers, &key, &val))
{
Free(val);
}
HashMapFree(http->headers);
Free(http->data);
return 0;
}
static Janet
JanetHttpCode(int32_t argc, Janet *argv)
{
JanetAbstract *abs;
HttpStruct *http;
janet_fixarity(argc, 1);
abs = janet_getabstract(argv, 0, &janet_http_type);
if (!abs)
{
return janet_wrap_nil();
}
http = (HttpStruct *) abs;
return janet_wrap_integer(http->code);
}
static Janet
JanetHttpHeaders(int32_t argc, Janet *argv)
{
return janet_wrap_nil();
}
static Janet
JanetHttpResponse(int32_t argc, Janet *argv)
{
JanetBuffer *buffer;
JanetAbstract *abs;
HttpStruct *http;
janet_fixarity(argc, 1);
abs = janet_getabstract(argv, 0, &janet_http_type);
if (!abs)
{
return janet_wrap_nil();
}
http = (HttpStruct *) abs;
buffer = janet_buffer(http->size + 1);
janet_buffer_push_bytes(buffer, (const uint8_t *) http->data, http->size);
return janet_wrap_buffer(buffer);
}
#endif

98
src/Extensions/Internal.h Normal file
View file

@ -0,0 +1,98 @@
#ifndef PARSEE_IEXTENSIONS_H
#define PARSEE_IEXTENSIONS_H
#ifdef JANET
#include <Extension.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <XMPPCommand.h>
#include <Parsee.h>
#include <XML.h>
#include <janet.h>
#include <pthread.h>
#include <stdlib.h>
#define JANET_STANZA_PUSH 0
#define JANET_REQUEST_XMPPCMD 1
#define JANET_REQUEST_XMPPCMD_A 2
#define JANET_REQUEST_XMPPCMD_F 3
struct Extensions {
HashMap *extensions;
size_t count;
ParseeData *data;
pthread_mutex_t mtx;
};
struct Extension {
Extensions *source;
JanetTable *env;
JanetVM *vm;
char *id;
uint8_t *bytes;
size_t len;
char *file;
JanetFiber *stanzas;
pthread_t thr;
volatile bool halted;
XMPPCommandManager *manager;
JanetTable *functions;
};
/** Creates a Janet table from an XML element. For all sensical
* usecases, set in to NULL.
* -----------------------
* Returns: a wrapped Janet table[janet] */
Janet ExtensionsFromXML(JanetArray *in, XMLElement *e);
/** Takes a Janet table and tries to encode it as XML.
* ----------------
* Returns: an XML element[HEAP] | NULL */
XMLElement * ExtensionsToXML(JanetTable *t);
/** Creates a Janet table from a JSON object.
* ----------
* Returns: a wrapped Janet table[janet] | NULL */
Janet JsonToJanet(HashMap *obj);
/** Takes a Janet table and tries to encode it as JSON.
* ----------------
* Returns: a JSON object[HEAP] | NULL */
HashMap * JanetToJson(JanetTable *table);
/** Configures some basic context for the embedded Janet environment.
* ---------------------------
* Returns: NOTHING
* Modifies: env */
void SetupBasicExtensionContext(Extension *ext, JanetTable *env);
/** Tries to get a ParseeData from a Janet value(see 'parsee/ctx').
* ------------------
* Returns: the data object | NULL */
ParseeData * GetParseeData(Janet v);
/** Generates a Janet value from a ParseeData.
* ------------
* See-Also; GetParseeData */
Janet FromParseeData(ParseeData *data);
bool
TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out);
#endif
#endif

170
src/Extensions/JSON.c Normal file
View file

@ -0,0 +1,170 @@
#ifdef JANET
#include <janet.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Json.h>
#include <Cytoplasm/Str.h>
#include <StringStream.h>
#include <XML.h>
Janet JsonToJanet(HashMap *obj);
static Janet
JsonValueToJanet(JsonValue *val)
{
Janet jVal;
switch (JsonValueType(val))
{
case JSON_STRING:
jVal = janet_cstringv(JsonValueAsString(val));
break;
case JSON_NULL:
jVal = janet_wrap_nil();
break;
case JSON_FLOAT:
jVal = janet_wrap_number(JsonValueAsFloat(val));
break;
case JSON_INTEGER:
jVal = janet_wrap_s64(JsonValueAsInteger(val));
break;
case JSON_BOOLEAN:
jVal = janet_wrap_boolean(JsonValueAsBoolean(val));
break;
case JSON_OBJECT:
jVal = JsonToJanet(JsonValueAsObject(val));
break;
case JSON_ARRAY:
{
JanetArray *arr = janet_array(0);
Array *jArr = JsonValueAsArray(val);
size_t i, len;
for (i = 0, len = ArraySize(jArr); i < len; i++)
{
janet_array_push(arr, JsonValueToJanet(ArrayGet(jArr, i)));
}
jVal = janet_wrap_array(arr);
}
break;
}
return jVal;
}
Janet
JsonToJanet(HashMap *obj)
{
JanetTable *table;
char *key;
JsonValue *val;
if (!obj)
{
return janet_wrap_nil();
}
table = janet_table(0);
while (HashMapIterate(obj, &key, (void **) &val))
{
Janet jVal = JsonValueToJanet(val);
janet_table_put(table, janet_cstringv(key), jVal);
}
return janet_wrap_table(table);
}
HashMap * JanetToJson(JanetTable *table);
static JsonValue *
JanetToJsonValue(Janet janet)
{
JsonValue *ret = NULL;
if (janet_type(janet) == JANET_STRING)
{
ret = JsonValueString((char *) janet_unwrap_string(janet));
}
else if (janet_type(janet) == JANET_BOOLEAN)
{
ret = JsonValueBoolean(janet_unwrap_boolean(janet));
}
else if (janet_type(janet) == JANET_NIL)
{
ret = JsonValueNull();
}
else if (janet_type(janet) == JANET_ARRAY)
{
JanetArray *arr = janet_unwrap_array(janet);
Array *jArr = ArrayCreate();
size_t i;
for (i = 0; i < arr->count; i++)
{
Janet child = arr->data[i];
ArrayAdd(jArr, JanetToJsonValue(child));
}
ret = JsonValueArray(jArr);
}
else if (janet_type(janet) == JANET_TABLE)
{
ret = JsonValueObject(JanetToJson(janet_unwrap_table(janet)));
}
else if (janet_is_int(janet))
{
ret = JsonValueInteger(janet_unwrap_s64(janet));
}
else if (janet_type(janet) == JANET_NUMBER)
{
ret = JsonValueFloat(janet_unwrap_number(janet));
}
return ret;
}
HashMap *
JanetToJson(JanetTable *table)
{
HashMap *ret;
size_t i;
if (!table)
{
return NULL;
}
ret = HashMapCreate();
for (i = 0; i < table->capacity; i++)
{
JanetKV *kv = &table->data[i];
char *key;
if (!janet_checktype(kv->key, JANET_STRING))
{
continue;
}
key = (char *) janet_unwrap_string(kv->key);
JsonValueFree(HashMapSet(ret, key, JanetToJsonValue(kv->value)));
}
return ret;
}
Janet
JanetParseJSON(int32_t argc, Janet *argv)
{
JanetBuffer *buf = NULL;
Stream *buffer_stream;
HashMap *json;
Janet ret;
janet_fixarity(argc, 1);
if (!(buf = janet_getbuffer(argv, 0)))
{
return janet_wrap_nil();
}
buffer_stream = StrStreamReaderN((char *) buf->data, buf->count);
json = JsonDecode(buffer_stream);
StreamClose(buffer_stream);
ret = JsonToJanet(json);
JsonFree(json);
return ret;
}
#endif

30
src/Extensions/Jabber.c Normal file
View file

@ -0,0 +1,30 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include "Extensions/Functions.h"
Janet
JanetWriteStanza(int32_t argc, Janet *argv)
{
XMLElement *xml;
ParseeData *data;
janet_fixarity(argc, 2);
if (janet_type(argv[0]) != JANET_POINTER)
{
return janet_wrap_nil();
}
if (janet_type(argv[1]) != JANET_TABLE)
{
return janet_wrap_nil();
}
data = janet_unwrap_pointer(argv[0]);
xml = ExtensionsToXML(janet_unwrap_table(argv[1]));
XMPPSendStanza(data->jabber, xml, data->config->max_stanza_size);
XMLFreeElement(xml);
return janet_wrap_nil();
}
#endif

304
src/Extensions/Janet.c Normal file
View file

@ -0,0 +1,304 @@
#ifdef JANET
#include <Extension.h>
#include <stdlib.h>
#include <janet.h>
#include <Cytoplasm/Cytoplasm.h>
#include <Cytoplasm/HashMap.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Stream.h>
#include <Cytoplasm/Util.h>
#include <Cytoplasm/Str.h>
#include <Cytoplasm/Log.h>
#include <Parsee.h>
#include <XML.h>
#include <pthread.h>
#include <dirent.h>
#include "Extensions/Internal.h"
bool
ExtensionEnabled(void)
{
return true;
}
Extensions *
ExtensionCreateContext(ParseeData *data)
{
Extensions *ctx = Malloc(sizeof(*ctx));
ctx->extensions = HashMapCreate();
ctx->data = data;
ctx->count = 0;
pthread_mutex_init(&ctx->mtx, NULL);
return ctx;
}
void
ExtensionDestroyContext(Extensions *ctx)
{
char *key;
Extension *ext;
if (!ctx)
{
return;
}
while (HashMapIterate(ctx->extensions, &key, (void **) &ext))
{
/* TODO */
ext->halted = true;
janet_loop1_interrupt(ext->vm);
pthread_join(ext->thr, NULL);
XMPPFreeManager(ext->manager);
Free(ext->bytes);
Free(ext->file);
Free(ext->id);
Free(ext);
}
pthread_mutex_destroy(&ctx->mtx);
HashMapFree(ctx->extensions);
Free(ctx);
}
Janet
GetGlobal(Extension *ext, char *key)
{
Janet v;
v = janet_table_get(ext->env, janet_csymbolv(key));
if (janet_type(v) == JANET_NIL)
{
return janet_wrap_nil();
}
return janet_table_get(janet_unwrap_table(v), janet_ckeywordv("value"));
}
bool
TryCall(Extension *ext, char *func, int32_t argc, Janet *argv, Janet *out)
{
Janet v = GetGlobal(ext, func);
JanetFiber *fib = NULL;
JanetSignal sig = JANET_SIGNAL_OK;
if (janet_type(v) == JANET_FUNCTION)
{
sig = janet_pcall(
janet_unwrap_function(v),
argc, argv,
out, &fib
);
}
if (sig != JANET_SIGNAL_OK)
{
janet_stacktrace(fib, *out);
}
return sig == JANET_SIGNAL_OK;
}
static void *
ExtensionLoop(void *p)
{
Extension *ext = p;
Janet f_table;
janet_init();
ext->env = janet_core_env(NULL);
ext->functions = janet_table(0);
ext->vm = janet_local_vm();
f_table = janet_wrap_table(ext->functions);
janet_gcroot(f_table);
/* Load the Janet environment */
SetupBasicExtensionContext(ext, ext->env);
janet_dobytes(ext->env, ext->bytes, ext->len, ext->file, NULL);
while (!ext->halted)
{
janet_loop();
UtilSleepMillis(1);
}
janet_gcunroot(f_table);
//janet_table_deinit(ext->env);
janet_deinit();
return NULL;
}
void
ExtensionReload(Extensions *ctx, char *id)
{
Extension *ext;
Log(LOG_INFO, "ctx=%p id=%s", ctx, id);
if (!ctx || !id)
{
return;
}
pthread_mutex_lock(&ctx->mtx);
Log(LOG_INFO, "Reloading %s", id);
ext = HashMapGet(ctx->extensions, id);
if (!ext)
{
pthread_mutex_unlock(&ctx->mtx);
Log(LOG_INFO, ":(");
return;
}
ext->halted = true;
janet_loop1_interrupt(ext->vm);
pthread_join(ext->thr, NULL);
XMPPFreeManager(ext->manager);
Free(ext->bytes);
Free(ext->id);
HashMapDelete(ctx->extensions, id);
ExtensionLoadBasic(ctx, id, ext->file);
Free(ext->file);
Free(ext);
pthread_mutex_unlock(&ctx->mtx);
}
Extension *
ExtensionLoadBasic(Extensions *ctx, char *id, char *file)
{
Extension *ext;
Stream *stream;
uint8_t *bytes = NULL;
size_t len = 0;
int c;
if (!ctx || !id || !file)
{
return NULL;
}
if (!(stream = StreamOpen(file, "r")))
{
return NULL;
}
while ((c = StreamGetc(stream)) != EOF)
{
bytes = Realloc(bytes, len + 1);
bytes[len++] = c;
}
StreamClose(stream);
ext = Malloc(sizeof(*ext));
/* will be set by the thread */
ext->vm = NULL;
ext->stanzas = NULL;
ext->file = StrDuplicate(file);
ext->id = StrDuplicate(id);
ext->bytes = bytes;
ext->source = ctx;
ext->len = len;
ext->halted = false;
ext->manager = XMPPCreateManager(ext);
HashMapSet(ctx->extensions, id, ext);
pthread_create(
&ext->thr,
NULL,
ExtensionLoop,
ext
);
return ext;
}
static bool
EndsWith(char *str, const char *ext)
{
size_t len_str, len_ext;
if (!str || !ext)
{
return false;
}
len_str = strlen(str);
len_ext = strlen(ext);
if (len_ext > len_str)
{
return false;
}
return StrEquals(str + (len_str - len_ext), ext);
}
void
ExtensionLoadDir(Extensions *ctx, char *dir)
{
struct dirent *ent;
DIR *handle;
if (!ctx || !dir)
{
return;
}
if (!(handle = opendir(dir)))
{
return;
}
while ((ent = readdir(handle)))
{
const char *extension = ".janet";
char *name = ent->d_name;
char *actual_path;
char *id = name;
if (!EndsWith(name, extension))
{
continue;
}
id = StrDuplicate(id);
actual_path = StrConcat(3, dir, "/", name);
id[strlen(name) - strlen(extension)] = '\0';
ExtensionLoadBasic(ctx, id, actual_path);
Free(actual_path);
Free(id);
}
closedir(handle);
}
void
ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza)
{
size_t i;
char *key;
Extension *val;
if (!ctx || !jid || !query || !stanza)
{
return;
}
pthread_mutex_lock(&ctx->mtx);
for (i = 0; IterateReentrant(ctx->extensions, key, val, i); )
{
XMPPShoveCommandList(val->manager, jid, query, stanza);
}
pthread_mutex_unlock(&ctx->mtx);
}
void
ExtensionManageCommands(Extensions *ctx, XMLElement *stanza)
{
size_t i;
char *key;
Extension *val;
if (!ctx || !stanza)
{
return;
}
pthread_mutex_lock(&ctx->mtx);
for (i = 0; IterateReentrant(ctx->extensions, key, val, i); )
{
XMPPManageCommand(val->manager, stanza, ctx->data);
}
pthread_mutex_unlock(&ctx->mtx);
}
#endif

86
src/Extensions/KV.c Normal file
View file

@ -0,0 +1,86 @@
#ifdef JANET
#include "Extensions/Internal.h"
Janet
JanetGetKey(int32_t argc, Janet *argv)
{
ParseeData *data;
char *chat_id = NULL;
char *key = NULL, *val = NULL;
Janet ret;
janet_fixarity(argc, 3);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_STRING))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[2], JANET_STRING))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string"));
return janet_wrap_nil();
}
if (!(data = janet_unwrap_pointer(argv[0])))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is NULL.."));
return janet_wrap_nil();
}
chat_id = (char *) janet_unwrap_string(argv[1]);
key = (char *) janet_unwrap_string(argv[2]);
if ((val = ParseeGetChatSetting(data, chat_id, key)))
{
ret = janet_cstringv(val);
Free(val);
}
else
{
ret = janet_wrap_nil();
}
return ret;
}
Janet
JanetSetKey(int32_t argc, Janet *argv)
{
ParseeData *data;
char *chat_id = NULL;
char *key = NULL, *val = NULL;
janet_fixarity(argc, 4);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_STRING))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CHAT ID is not a string"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[2], JANET_STRING))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("KEY is not a string"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[3], JANET_STRING))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("VAL is not a string"));
return janet_wrap_nil();
}
chat_id = (char *) janet_unwrap_string(argv[1]);
key = (char *) janet_unwrap_string(argv[2]);
val = (char *) janet_unwrap_string(argv[3]);
ParseeSetChatSetting(data, chat_id, key, val);
return janet_wrap_nil();
}
#endif

149
src/Extensions/Matrix.c Normal file
View file

@ -0,0 +1,149 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include <AS.h>
Janet
JanetMatrixInvite(int32_t argc, Janet *argv)
{
ParseeData *data;
char *invitee, *room;
janet_fixarity(argc, 3);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
if (janet_type(argv[1]) != JANET_STRING)
{
return janet_wrap_nil();
}
if (janet_type(argv[2]) != JANET_STRING)
{
return janet_wrap_nil();
}
invitee = (char *) janet_unwrap_string(argv[1]);
room = (char *) janet_unwrap_string(argv[2]);
if (!data)
{
return janet_wrap_nil();
}
ASInvite(data->config, room, invitee);
return janet_wrap_nil();
}
Janet
JanetMatrixFind(int32_t argc, Janet *argv)
{
ParseeData *data;
char *id, *room;
Janet returned;
HashMap *e;
janet_fixarity(argc, 3);
if (!(data = GetParseeData(argv[0])))
{
return janet_wrap_nil();
}
if (janet_type(argv[1]) != JANET_STRING)
{
return janet_wrap_nil();
}
if (janet_type(argv[2]) != JANET_STRING)
{
return janet_wrap_nil();
}
room = (char *) janet_unwrap_string(argv[1]);
id = (char *) janet_unwrap_string(argv[2]);
if (!data)
{
return janet_wrap_nil();
}
if (!(e = ASFind(data->config, room, id)))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("couldnt find event"));
return janet_wrap_nil();
}
returned = JsonToJanet(e);
JsonFree(e);
return returned;
}
Janet
JanetMatrixSend(int32_t argc, Janet *argv)
{
ParseeData *data;
char *from, *room, *type, *id;
JanetTable *json;
Janet returned;
HashMap *e;
janet_fixarity(argc, 5);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr.")
);
return janet_wrap_nil();
}
if (janet_type(argv[1]) != JANET_STRING)
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("FROM is not a string.")
);
return janet_wrap_nil();
}
if (janet_type(argv[2]) != JANET_STRING)
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("ROOM is not a string.")
);
return janet_wrap_nil();
}
if (janet_type(argv[3]) != JANET_STRING)
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("TYPE is not a string.")
);
return janet_wrap_nil();
}
if (janet_type(argv[4]) != JANET_TABLE)
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("JSON is not a table.")
);
return janet_wrap_nil();
}
from = (char *) janet_unwrap_string(argv[1]);
room = (char *) janet_unwrap_string(argv[2]);
type = (char *) janet_unwrap_string(argv[3]);
json = janet_unwrap_table(argv[4]);
if (!data)
{
return janet_wrap_nil();
}
if (!(e = JanetToJson(json)))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("JSON is not valid JSON.")
);
return janet_wrap_nil();
}
if (!(id = ASSend(data->config, room, from, type, e, 0)))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Could not send event")
);
return janet_wrap_nil();
}
returned = janet_cstringv(id);
Free(id);
return returned;
}
#endif

View file

@ -0,0 +1,89 @@
#ifdef JANET
#include "Extensions/Internal.h"
typedef struct ExtensionEventReply {
pthread_mutex_t lock;
pthread_cond_t cond;
HashMap *obj;
Extension *ext;
bool reply, ack;
} ExtensionEventReply;
static void
OnEventResponse(JanetEVGenericMessage msg)
{
ExtensionEventReply *reply = msg.argp;
Extension *ext = reply->ext;
Janet args[1] = {
JsonToJanet(reply->obj),
};
Janet ret;
if (TryCall(ext, "on-event", 1, args, &ret))
{
pthread_mutex_lock(&reply->lock);
reply->ack = true;
reply->reply = janet_truthy(ret);
pthread_cond_signal(&reply->cond);
pthread_mutex_unlock(&reply->lock);
return;
}
pthread_mutex_lock(&reply->lock);
reply->ack = true;
reply->reply = false;
pthread_cond_signal(&reply->cond);
pthread_mutex_unlock(&reply->lock);
}
bool
ExtensionPushEvent(Extensions *ctx, HashMap *obj)
{
bool veto = false;
size_t i;
char *key;
Extension *val;
if (!ctx || !obj)
{
return false;
}
pthread_mutex_lock(&ctx->mtx);
for (i = 0; IterateReentrant(ctx->extensions, key, val, i); )
{
ExtensionEventReply *reply = Malloc(sizeof(*reply));
JanetEVGenericMessage msg = {
/* We except a veto boolean. */
.tag = JANET_STANZA_PUSH,
};
pthread_mutex_init(&reply->lock, NULL);
pthread_cond_init(&reply->cond, NULL);
reply->ext = val;
reply->reply = false;
reply->ack = false;
reply->obj = obj;
msg.argp = reply;
janet_ev_post_event(val->vm, OnEventResponse, msg);
pthread_mutex_lock(&reply->lock);
while (!reply->ack)
{
pthread_cond_wait(&reply->cond, &reply->lock);
}
pthread_mutex_unlock(&reply->lock);
pthread_mutex_destroy(&reply->lock);
pthread_cond_destroy(&reply->cond);
if (reply->reply)
{
veto = true;
}
Free(reply);
}
pthread_mutex_unlock(&ctx->mtx);
return veto;
}
#endif

View file

@ -0,0 +1,98 @@
#ifdef JANET
#include "Extensions/Internal.h"
typedef struct ExtensionStanzaReply {
pthread_mutex_t lock;
pthread_cond_t cond;
XMLElement *stanza;
Extension *ext;
bool reply, ack;
char *type;
} ExtensionStanzaReply;
static void
OnStanzaResponse(JanetEVGenericMessage msg)
{
ExtensionStanzaReply *reply = msg.argp;
Extension *ext = reply->ext;
Janet args[2] = {
ExtensionsFromXML(NULL, reply->stanza),
janet_cstringv(reply->type)
};
Janet ret;
if (TryCall(ext, "on-stanza", 2, args, &ret))
{
pthread_mutex_lock(&reply->lock);
reply->ack = true;
reply->reply = janet_truthy(ret);
pthread_cond_signal(&reply->cond);
pthread_mutex_unlock(&reply->lock);
return;
}
pthread_mutex_lock(&reply->lock);
reply->ack = true;
reply->reply = false;
pthread_cond_signal(&reply->cond);
pthread_mutex_unlock(&reply->lock);
}
bool
ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t)
{
bool veto = false;
size_t i;
char *table[STANZA_TYPE_COUNT] = {
"raw",
"message",
"iq",
"presence"
};
char *key;
Extension *val;
if (!ctx || !stanza)
{
return false;
}
pthread_mutex_lock(&ctx->mtx);
for (i = 0; IterateReentrant(ctx->extensions, key, val, i); )
{
ExtensionStanzaReply *reply = Malloc(sizeof(*reply));
JanetEVGenericMessage msg = {
/* We except a veto boolean. */
.tag = JANET_STANZA_PUSH,
};
pthread_mutex_init(&reply->lock, NULL);
pthread_cond_init(&reply->cond, NULL);
reply->ext = val;
reply->reply = false;
reply->ack = false;
reply->type = table[t];
reply->stanza = stanza;
msg.argp = reply;
janet_ev_post_event(val->vm, OnStanzaResponse, msg);
pthread_mutex_lock(&reply->lock);
while (!reply->ack)
{
pthread_cond_wait(&reply->cond, &reply->lock);
}
pthread_mutex_unlock(&reply->lock);
pthread_mutex_destroy(&reply->lock);
pthread_cond_destroy(&reply->cond);
if (reply->reply)
{
veto = true;
}
Free(reply);
}
pthread_mutex_unlock(&ctx->mtx);
return veto;
}
#endif

View file

@ -0,0 +1,580 @@
#ifdef JANET
#include "Extensions/Internal.h"
#include <XMPPCommand.h>
#include <XMPPFormTool.h>
typedef struct ExtensionCommandReply {
pthread_mutex_t lock;
pthread_cond_t cond;
XMLElement *form;
char *from;
char *node;
XMLElement *out;
XMPPCommand *cmd;
Extension *ext;
bool ack;
} ExtensionCommandReply;
static void OnCommandResponse(JanetEVGenericMessage msg);
static int JanetCmdGet(void *data0, Janet key, Janet *out);
static void JanetMarshalCmd(void *data0, JanetMarshalContext *ctx);
static void *JanetUnmarshalCmd(JanetMarshalContext *ctx);
static void JanetCmdToString(void *data0, JanetBuffer *buf);
static int32_t JanetHashCmd(void *data0, size_t size);
static Janet JanetCmdNext(void *data0, Janet next);
static Janet JanetCmdCreateText(int32_t argc, Janet *argv);
static Janet JanetCmdCreateBool(int32_t argc, Janet *argv);
static Janet JanetCmdCreateFixed(int32_t argc, Janet *argv);
static Janet JanetCmdSetTitle(int32_t argc, Janet *argv);
static const JanetMethod cmd_methods[] = {
{ "create-text", JanetCmdCreateText },
{ "create-bool", JanetCmdCreateBool },
{ "create-fixed", JanetCmdCreateFixed },
{ "set-title", JanetCmdSetTitle },
{ NULL, NULL }
};
const JanetAbstractType janet_cmd_type = {
.name = "parsee-xmppcmd",
.gc = NULL, .gcmark = NULL,
.get = JanetCmdGet, .put = NULL,
.marshal = JanetMarshalCmd, .unmarshal = JanetUnmarshalCmd,
.tostring = JanetCmdToString,
.compare = NULL,
.hash = JanetHashCmd,
.next = JanetCmdNext,
JANET_ATEND_NEXT
};
static XMPPCommand *
JanetToCommand(Janet v)
{
XMPPCommand **indirect = janet_checkabstract(v, &janet_cmd_type);
return indirect ? *indirect : NULL;
}
static Janet
CommandToJanet(XMPPCommand *cmd)
{
JanetAbstract abs;
if (!cmd)
{
return janet_wrap_nil();
}
abs = janet_abstract(&janet_cmd_type, sizeof(XMPPCommand *));
*((XMPPCommand **) abs) = cmd;
return janet_wrap_abstract(abs);
}
static void
PutFunction(JanetTable *functions, char *node, char *kind, JanetFunction *f)
{
JanetTable *kinds;
Janet kindsv;
if (!functions || !node || !kind || !f)
{
return;
}
kindsv = janet_table_get(functions, janet_cstringv(node));
if (janet_checktype(kindsv, JANET_NIL))
{
kinds = janet_table(0);
janet_table_put(
functions,
janet_cstringv(node), janet_wrap_table(kinds)
);
}
else
{
kinds = janet_unwrap_table(kindsv);
}
janet_table_put(kinds, janet_cstringv(kind), janet_wrap_function(f));
}
static JanetFunction *
FetchFunction(JanetTable *functions, char *node, char *kind)
{
Janet f_tablev, fv;
JanetTable *ftable;
if (!functions || !node || !kind)
{
return NULL;
}
f_tablev = janet_table_get(
functions,
janet_cstringv(node)
);
if (!janet_checktype(f_tablev, JANET_TABLE))
{
return NULL;
}
ftable = janet_unwrap_table(f_tablev);
fv = janet_table_get(
ftable,
janet_cstringv(kind)
);
if (!janet_checktype(fv, JANET_FUNCTION))
{
return NULL;
}
return janet_unwrap_function(fv);
}
extern void
JanetFormCb(XMPPCommandManager *manager, XMPPCommand *cmd, char *from, char *node)
{
Extension *ext = XMPPGetManagerCookie(manager);
ExtensionCommandReply *reply;
reply = Malloc(sizeof(*reply));
JanetEVGenericMessage msg = {
.tag = JANET_REQUEST_XMPPCMD_F
};
pthread_mutex_init(&reply->lock, NULL);
pthread_cond_init(&reply->cond, NULL);
reply->ext = ext;
reply->node = node;
reply->from = from;
reply->ack = false;
reply->cmd = cmd;
msg.argp = reply;
janet_ev_post_event(ext->vm, OnCommandResponse, msg);
pthread_mutex_lock(&reply->lock);
while (!reply->ack)
{
pthread_cond_wait(&reply->cond, &reply->lock);
}
pthread_mutex_unlock(&reply->lock);
pthread_mutex_destroy(&reply->lock);
pthread_cond_destroy(&reply->cond);
Free(reply);
}
static void
JanetCmdCb(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
Extension *ext = XMPPGetManagerCookie(m);
ExtensionCommandReply *reply;
reply = Malloc(sizeof(*reply));
JanetEVGenericMessage msg = {
.tag = JANET_REQUEST_XMPPCMD_A
};
pthread_mutex_init(&reply->lock, NULL);
pthread_cond_init(&reply->cond, NULL);
reply->ext = ext;
reply->node = node;
reply->form = form;
reply->from = from;
reply->ack = false;
reply->out = out;
msg.argp = reply;
janet_ev_post_event(ext->vm, OnCommandResponse, msg);
pthread_mutex_lock(&reply->lock);
while (!reply->ack)
{
pthread_cond_wait(&reply->cond, &reply->lock);
}
pthread_mutex_unlock(&reply->lock);
pthread_mutex_destroy(&reply->lock);
pthread_cond_destroy(&reply->cond);
Free(reply);
}
static Janet
JanetRegisterCommand(int32_t argc, Janet *argv)
{
JanetTable *self;
JanetFunction *func;
JanetFunction *form;
char *node, *name;
Janet v;
Extension *ext;
XMPPCommand *cmd;
janet_arity(argc, 4, 5);
if (!janet_checktype(argv[0], JANET_TABLE) ||
!janet_checktype(argv[1], JANET_STRING) ||
!janet_checktype(argv[2], JANET_STRING) ||
!janet_checktype(argv[3], JANET_FUNCTION) ||
(argc == 5 && !janet_checktype(argv[4], JANET_FUNCTION)))
{
return janet_wrap_nil();
}
self = janet_unwrap_table(argv[0]);
node = (char *) janet_unwrap_string(argv[1]);
name = (char *) janet_unwrap_string(argv[2]);
func = janet_unwrap_function(argv[3]);
form = argc == 5 ? janet_unwrap_function(argv[4]) : NULL;
v = janet_table_get(self, janet_ckeywordv("ptr"));
if (!janet_checktype(v, JANET_POINTER))
{
return janet_wrap_nil();
}
ext = janet_unwrap_pointer(v);
PutFunction(ext->functions, node, "act", func);
PutFunction(ext->functions, node, "form", form);
cmd = XMPPBasicCmd(XMPPCMD_ADMINS, node, name, JanetCmdCb);
if (form)
{
XMPPCmdOptionsCreator(cmd, JanetFormCb);
}
XMPPRegisterCommand(ext->manager, cmd);
return janet_wrap_nil();
}
static void
OnCommandResponse(JanetEVGenericMessage msg)
{
ExtensionCommandReply *reply = msg.argp;
Extension *ext = reply->ext;
Janet ret, in;
if (msg.tag == JANET_REQUEST_XMPPCMD)
{
JanetTable *cmd = janet_table(0);
janet_table_put(cmd,
janet_ckeywordv("ptr"),
janet_wrap_pointer(reply->ext)
);
janet_table_put(cmd,
janet_ckeywordv("register"),
janet_wrap_cfunction(JanetRegisterCommand)
);
in = janet_wrap_table(cmd);
TryCall(ext, "on-xmppcmd", 1, &in, &ret);
}
else if (msg.tag == JANET_REQUEST_XMPPCMD_F)
{
Janet in[2] = {
janet_cstringv(reply->from),
CommandToJanet(reply->cmd)
};
JanetFiber *fib = NULL;
JanetFunction *func;
JanetSignal sig;
func = FetchFunction(reply->ext->functions, reply->node, "form");
if (func)
{
sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib);
if (sig != JANET_SIGNAL_OK)
{
janet_stacktrace(fib, ret);
}
}
}
else if (msg.tag == JANET_REQUEST_XMPPCMD_A)
{
JanetFunction *func;
func = FetchFunction(reply->ext->functions, reply->node, "act");
if (func)
{
Janet in[2] = {
janet_cstringv(reply->from),
ExtensionsFromXML(NULL, reply->form)
};
JanetFiber *fib = NULL;
JanetSignal sig;
XMLElement *out = reply->out;
sig = janet_pcall(func, sizeof(in)/sizeof(*in), in, &ret, &fib);
if (sig != JANET_SIGNAL_OK)
{
janet_stacktrace(fib, ret);
SetNote("error", "Internal error in Janet extension.");
}
if (janet_checktype(ret, JANET_ARRAY))
{
JanetArray *arr = janet_unwrap_array(ret);
size_t i, added;
for (i = 0, added = 0; i < arr->count; i++)
{
Janet v = arr->data[i];
JanetTable *source;
XMLElement *child = NULL;
if (!reply->out || !janet_checktype(v, JANET_TABLE))
{
continue;
}
source = janet_unwrap_table(v);
child = ExtensionsToXML(source);
XMLAddChild(reply->out, child);
if (child)
{
added++;
}
}
if (added == 0)
{
SetNote("error", "No values returned in Janet extension.");
}
}
else
{
SetNote("error", "Invalid output in Janet extension.");
}
}
}
pthread_mutex_lock(&reply->lock);
reply->ack = true;
pthread_cond_signal(&reply->cond);
pthread_mutex_unlock(&reply->lock);
}
void
ExtensionRequestCommands(Extensions *ctx)
{
size_t i;
char *key;
Extension *val;
if (!ctx)
{
return;
}
pthread_mutex_lock(&ctx->mtx);
for (i = 0; IterateReentrant(ctx->extensions, key, val, i); )
{
ExtensionCommandReply *reply = Malloc(sizeof(*reply));
JanetEVGenericMessage msg = {
.tag = JANET_REQUEST_XMPPCMD
};
pthread_mutex_init(&reply->lock, NULL);
pthread_cond_init(&reply->cond, NULL);
reply->ext = val;
reply->ack = false;
msg.argp = reply;
janet_ev_post_event(val->vm, OnCommandResponse, msg);
pthread_mutex_lock(&reply->lock);
while (!reply->ack)
{
pthread_cond_wait(&reply->cond, &reply->lock);
}
pthread_mutex_unlock(&reply->lock);
pthread_mutex_destroy(&reply->lock);
pthread_cond_destroy(&reply->cond);
Free(reply);
}
pthread_mutex_unlock(&ctx->mtx);
}
static int
JanetCmdGet(void *data0, Janet key, Janet *out)
{
JanetKeyword kw;
if (!janet_checktype(key, JANET_KEYWORD))
{
return 0;
}
kw = janet_unwrap_keyword(key);
(void) data0;
return janet_getmethod(kw, cmd_methods, out);
}
static void
JanetMarshalCmd(void *data0, JanetMarshalContext *ctx)
{
janet_marshal_abstract(ctx, data0);
}
static void *
JanetUnmarshalCmd(JanetMarshalContext *ctx)
{
XMPPCommand **data = janet_unmarshal_abstract(ctx, sizeof(XMPPCommand *));
return data ? *data : NULL;
}
static void
JanetCmdToString(void *data0, JanetBuffer *buf)
{
janet_buffer_push_cstring(buf, "[" NAME " XMPP command type]");
}
static int32_t
JanetHashCmd(void *data0, size_t size)
{
uintptr_t datap = (uintptr_t) data0;
(void) size;
/* We're casting down, but in a context where there is really
* just one ParseeData, is that really such a crime? */
return (int32_t) datap;
}
static Janet
JanetCmdNext(void *data0, Janet next)
{
(void) data0;
return janet_nextmethod(cmd_methods, next);
}
static Janet
JanetCmdCreateText(int32_t argc, Janet *argv)
{
XMPPCommand *cmd;
XMPPOption *opt;
bool req;
char *id, *def, *desc;
janet_arity(argc, 4, 5);
if (!(cmd = JanetToCommand(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command")
);
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_BOOLEAN) ||
!janet_checktype(argv[2], JANET_STRING) ||
!janet_checktype(argv[3], JANET_STRING) ||
((argc == 5) && !janet_checktype(argv[4], JANET_STRING)))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types")
);
return janet_wrap_nil();
}
req = janet_unwrap_boolean(argv[1]);
id = (char *) janet_unwrap_string(argv[2]);
def = (char *) janet_unwrap_string(argv[3]);
desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL;
opt = XMPPCreateText(req, id, def);
XMPPSetDescription(opt, desc);
XMPPAddOption(cmd, opt);
return janet_wrap_nil();
}
static Janet
JanetCmdCreateBool(int32_t argc, Janet *argv)
{
XMPPCommand *cmd;
XMPPOption *opt;
bool req, def;
char *id, *desc;
janet_arity(argc, 4, 5);
if (!(cmd = JanetToCommand(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command")
);
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_BOOLEAN) ||
!janet_checktype(argv[2], JANET_STRING) ||
!janet_checktype(argv[3], JANET_BOOLEAN) ||
((argc == 5) && !janet_checktype(argv[4], JANET_STRING)))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types")
);
return janet_wrap_nil();
}
req = janet_unwrap_boolean(argv[1]);
id = (char *) janet_unwrap_string(argv[2]);
def = janet_unwrap_boolean(argv[3]);
desc = argc == 5 ? (char *) janet_unwrap_string(argv[4]) : NULL;
opt = XMPPCreateBool(req, id, def);
XMPPSetDescription(opt, desc);
XMPPAddOption(cmd, opt);
return janet_wrap_nil();
}
static Janet
JanetCmdCreateFixed(int32_t argc, Janet *argv)
{
XMPPCommand *cmd;
XMPPOption *opt;
char *id, *desc;
janet_fixarity(argc, 3);
if (!(cmd = JanetToCommand(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command")
);
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_STRING) ||
!janet_checktype(argv[2], JANET_STRING))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types")
);
return janet_wrap_nil();
}
id = (char *) janet_unwrap_string(argv[1]);
desc = (char *) janet_unwrap_string(argv[2]);
opt = XMPPCreateFixed(id, desc);
XMPPSetDescription(opt, desc);
XMPPAddOption(cmd, opt);
return janet_wrap_nil();
}
static Janet
JanetCmdSetTitle(int32_t argc, Janet *argv)
{
XMPPCommand *cmd;
char *title;
janet_fixarity(argc, 2);
if (!(cmd = JanetToCommand(argv[0])))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Object is not a command")
);
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_STRING))
{
janet_signalv(
JANET_SIGNAL_ERROR, janet_cstringv("Invalid argument types")
);
return janet_wrap_nil();
}
title = (char *) janet_unwrap_string(argv[1]);
XMPPSetFormTitle(cmd, title);
return janet_wrap_nil();
}
#endif

View file

@ -0,0 +1,98 @@
#ifdef JANET
#include "Extensions/Internal.h"
Janet
JanetStanzaInfo(int32_t argc, Janet *argv)
{
ParseeData *data;
XMLElement *stanza;
JanetTable *ret;
char *trimmed = NULL;
char *chat_id = NULL;
char *from = NULL;
char *room = NULL;
char *mxid = NULL;
janet_fixarity(argc, 2);
if (!(data = GetParseeData(argv[0])))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("CTX is not a ptr"));
return janet_wrap_nil();
}
if (!janet_checktype(argv[1], JANET_TABLE))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not a table"));
return janet_wrap_nil();
}
if (!(stanza = ExtensionsToXML(janet_unwrap_table(argv[1]))))
{
janet_signalv(JANET_SIGNAL_ERROR, janet_cstringv("STANZA is not XML."));
return janet_wrap_nil();
}
from = HashMapGet(stanza->attrs, "from");
trimmed = ParseeTrimJID(from);
chat_id = ParseeGetFromMUCID(data, trimmed);
ret = janet_table(0);
janet_table_put(ret,
janet_ckeywordv("in-muc"),
janet_wrap_boolean(ParseeIsMUC(data, trimmed))
);
janet_table_put(ret,
janet_ckeywordv("in-dm"),
janet_wrap_boolean(StrEquals(HashMapGet(stanza->attrs, "type"), "chat"))
);
if (chat_id && trimmed)
{
room = ParseeGetRoomID(data, chat_id);
janet_table_put(ret,
janet_ckeywordv("muc-jid"), janet_cstringv(trimmed)
);
janet_table_put(ret,
janet_ckeywordv("id"), janet_cstringv(chat_id)
);
}
else
{
char *to = ParseeDecodeMXID(HashMapGet(stanza->attrs, "to"));
char *dmid = ParseeGetDMID(to, from);
room = ParseeFindDMRoom(data, to, from);
if (dmid)
{
janet_table_put(ret,
janet_ckeywordv("id"), janet_cstringv(dmid)
);
}
Free(dmid);
Free(to);
}
if (room)
{
janet_table_put(ret,
janet_ckeywordv("room-id"), janet_cstringv(room)
);
}
if ((mxid = ParseeGetBridgedUser(data, stanza)))
{
janet_table_put(ret,
janet_ckeywordv("bridged-sender"), janet_cstringv(mxid)
);
}
Free(trimmed);
Free(chat_id);
Free(room);
Free(mxid);
XMLFreeElement(stanza);
return janet_wrap_table(ret);
}
#endif

357
src/Extensions/XML.c Normal file
View file

@ -0,0 +1,357 @@
#ifdef JANET
#include <janet.h>
#include <Cytoplasm/Memory.h>
#include <Cytoplasm/Str.h>
#include <StringStream.h>
#include <XML.h>
static Janet XMLToString(int32_t argc, Janet *argv);
static Janet JanetXMLAddAttr(int32_t argc, Janet *argv);
static void InitialiseBasicXML(JanetTable *table, char *type);
XMLElement *
ExtensionsToXML(JanetTable *t)
{
XMLElement *ret = NULL;
char *type = NULL, *name, *data;
JanetArray *children;
JanetTable *attrs;
int32_t i;
Janet v;
if (!t)
{
return NULL;
}
#define ChkType(ent, type) (janet_type((v = janet_table_get(t, janet_ckeywordv(ent)))) == type)
if (!ChkType("xml-type", JANET_STRING))
{
return NULL;
}
type = (char *) janet_unwrap_string(v);
if (StrEquals(type, "data"))
{
if (!ChkType("xml-data", JANET_STRING))
{
return NULL;
}
data = (char *) janet_unwrap_string(v);
ret = XMLCreateText(data);
}
else if (StrEquals(type, "tag"))
{
if (!ChkType("xml-name", JANET_STRING))
{
return NULL;
}
name = (char *) janet_unwrap_string(v);
if (!ChkType("xml-attrs", JANET_TABLE))
{
return NULL;
}
ret = XMLCreateTag(name);
attrs = janet_unwrap_table(v);
for (i = 0; i < attrs->capacity; i++)
{
JanetKV *kv = &attrs->data[i];
if (!janet_checktype(kv->key, JANET_STRING) ||
!janet_checktype(kv->value, JANET_STRING))
{
continue;
}
XMLAddAttr(ret,
(char *) janet_unwrap_string(kv->key),
(char *) janet_unwrap_string(kv->value)
);
}
if (!ChkType("xml-children", JANET_ARRAY))
{
return ret;
}
children = janet_unwrap_array(v);
for (i = 0; i < children->capacity; i++)
{
Janet cJanet = children->data[i];
XMLElement *cXML = NULL;
if (!janet_checktype(cJanet, JANET_TABLE))
{
continue;
}
cXML = ExtensionsToXML(janet_unwrap_table(cJanet));
XMLAddChild(ret, cXML);
}
}
return ret;
}
Janet
ExtensionsFromXML(JanetArray *in, XMLElement *e)
{
JanetArray *children = NULL;
JanetTable *table;
size_t i, len;
char *type = NULL;
if (!e)
{
return janet_wrap_nil();
}
table = janet_table(0);
switch (e->type)
{
case XML_ELEMENT_TAG:
type = "tag";
break;
case XML_ELEMENT_DATA:
case XML_ELEMENT_CDATA:
type = "data";
break;
case XML_ELEMENT_PI:
type = "pi";
break;
case XML_ELEMENT_UNKNOWN:
type = "unknown";
break;
}
InitialiseBasicXML(table, type);
if (e->type == XML_ELEMENT_DATA || e->type == XML_ELEMENT_PI)
{
janet_table_put(table,
janet_ckeywordv("xml-data"), janet_cstringv(e->data)
);
}
else if (e->type == XML_ELEMENT_TAG)
{
char *attr, *val;
JanetTable *attrs;
children = janet_array(ArraySize(e->children));
janet_table_put(table,
janet_ckeywordv("xml-name"), janet_cstringv(e->name)
);
for (i = 0, len = ArraySize(e->children); i < len; i++)
{
ExtensionsFromXML(children, ArrayGet(e->children, i));
}
janet_table_put(table,
janet_ckeywordv("xml-children"), janet_wrap_array(children)
);
attrs = janet_table(0);
while (HashMapIterate(e->attrs, &attr, (void **) &val))
{
janet_table_put(attrs,
janet_cstringv(attr), janet_cstringv(val)
);
}
janet_table_put(table,
janet_ckeywordv("xml-attrs"), janet_wrap_table(attrs)
);
}
if (children && in)
{
janet_array_push(in, janet_wrap_table(table));
}
return janet_wrap_table(table);
}
static Janet
JanetXMLAddAttr(int32_t argc, Janet *argv)
{
Janet self_v, key_v, val_v, attr_v;
JanetTable *self, *attrs;
char *key, *val;
janet_fixarity(argc, 3);
if (!janet_checktype((self_v = argv[0]), JANET_TABLE))
{
return janet_wrap_nil();
}
if (!janet_checktype((key_v = argv[1]), JANET_STRING))
{
return janet_wrap_nil();
}
if (!janet_checktype((val_v = argv[2]), JANET_STRING))
{
return janet_wrap_nil();
}
key = (char *) janet_unwrap_string(key_v);
val = (char *) janet_unwrap_string(val_v);
self = janet_unwrap_table(self_v);
attr_v = janet_table_get(self, janet_ckeywordv("xml-attrs"));
if (!janet_checktype(attr_v, JANET_TABLE))
{
return janet_wrap_nil();
}
attrs = janet_unwrap_table(attr_v);
janet_table_put(attrs, janet_cstringv(key), janet_cstringv(val));
return janet_wrap_nil();
}
static Janet
XMLToString(int32_t argc, Janet *argv)
{
XMLElement *xml;
Stream *fake;
Janet ret;
char *buf = NULL;
janet_fixarity(argc, 1);
if (!janet_checktype(argv[0], JANET_TABLE))
{
return janet_wrap_nil();
}
xml = ExtensionsToXML(janet_gettable(argv, 0));
if (!xml)
{
return janet_wrap_nil();
}
fake = StrStreamWriter(&buf);
XMLEncode(fake, xml);
StreamFlush(fake);
StreamClose(fake);
XMLFreeElement(xml);
ret = janet_cstringv(buf);
Free(buf);
return ret;
}
Janet
JanetCreateXMLData(int32_t argc, Janet *argv)
{
Janet parent_v, text_v;
JanetTable *parent;
JanetTable *ret;
char *text;
janet_arity(argc, 1, 2);
if (argc == 1)
{
text_v = argv[0];
parent_v = janet_wrap_nil();
}
else
{
parent_v = argv[0];
text_v = argv[1];
}
if (janet_type(text_v) != JANET_STRING)
{
return janet_wrap_nil();
}
text = (char *) janet_unwrap_string(text_v);
ret = janet_table(0);
InitialiseBasicXML(ret, "data");
janet_table_put(ret,
janet_ckeywordv("xml-data"), janet_cstringv(text)
);
if (janet_type(parent_v) == JANET_TABLE)
{
JanetArray *children;
Janet children_v;
parent = janet_unwrap_table(parent_v);
children_v = janet_table_get(parent, janet_ckeywordv("xml-children"));
if (janet_type(children_v) != JANET_ARRAY)
{
goto end;
}
children = janet_unwrap_array(children_v);
janet_array_push(children, janet_wrap_table(ret));
}
end:
return janet_wrap_table(ret);
}
Janet
JanetCreateXMLTag(int32_t argc, Janet *argv)
{
Janet parent_v, text_v;
JanetTable *parent;
JanetTable *ret;
char *text;
janet_arity(argc, 1, 2);
if (argc == 1)
{
text_v = argv[0];
parent_v = janet_wrap_nil();
}
else
{
parent_v = argv[0];
text_v = argv[1];
}
if (janet_type(text_v) != JANET_STRING)
{
return janet_wrap_nil();
}
text = (char *) janet_unwrap_string(text_v);
ret = janet_table(0);
InitialiseBasicXML(ret, "tag");
janet_table_put(ret,
janet_ckeywordv("xml-name"), janet_cstringv(text)
);
if (janet_type(parent_v) == JANET_TABLE)
{
JanetArray *children;
Janet children_v;
parent = janet_unwrap_table(parent_v);
children_v = janet_table_get(parent, janet_ckeywordv("xml-children"));
if (janet_type(children_v) != JANET_ARRAY)
{
goto end;
}
children = janet_unwrap_array(children_v);
janet_array_push(children, janet_wrap_table(ret));
}
end:
return janet_wrap_table(ret);
}
void
InitialiseBasicXML(JanetTable *table, char *type)
{
janet_table_put(table,
janet_ckeywordv("stringify"), janet_wrap_cfunction(XMLToString)
);
janet_table_put(table,
janet_ckeywordv("set-attr"), janet_wrap_cfunction(JanetXMLAddAttr)
);
janet_table_put(table,
janet_ckeywordv("xml-type"), janet_cstringv(type)
);
if (StrEquals(type, "tag"))
{
janet_table_put(table,
janet_ckeywordv("xml-attrs"), janet_wrap_table(janet_table(0))
);
janet_table_put(table,
janet_ckeywordv("xml-children"), janet_wrap_array(janet_array(0))
);
}
}
#endif

View file

@ -22,6 +22,9 @@ ParseeRequest(HttpServerContext *ctx, void *argp)
/* Basic headers */
HttpResponseStatus(ctx, HTTP_OK);
HttpResponseHeader(ctx, "Server", NAME "/v" VERSION);
#ifdef PLATFORM_WINDOWS
HttpResponseHeader(ctx, "X-Powered-By", "some weirdows");
#endif
HttpResponseHeader(ctx, "Connection", "close");
arg.data = data;

View file

@ -18,6 +18,11 @@
#include <XMPP.h>
#include <AS.h>
#ifdef JANET
#include <janet.h>
#endif
#include <Extension.h>
static HttpServer *server = NULL;
static pthread_t xmpp_thr;
static XMPPComponent *jabber = NULL;
@ -99,6 +104,8 @@ Main(Array *args, HashMap *env)
int http = 8;
int verbose = 0;
Extensions *ext_ctx = NULL;
start = UtilTsMillis();
memset(&conf, 0, sizeof(conf));
@ -110,6 +117,9 @@ Main(Array *args, HashMap *env)
Log(LOG_INFO, "=======================");
Log(LOG_INFO, "(C)opyright 2024 LDA and other contributors");
Log(LOG_INFO, "(This program is free software, see LICENSE.)");
#ifdef JANET
Log(LOG_INFO, "**Built with Janet!**");
#endif
#ifdef PLATFORM_IPHONE
Log(LOG_WARNING, "Wait. Are you running this on an iPhone?");
@ -269,6 +279,10 @@ Main(Array *args, HashMap *env)
Log(LOG_DEBUG, "Verbosity level: %d", verbose);
((ParseeData *) conf.handlerArgs)->verbosity = verbose;
ext_ctx = ExtensionCreateContext(conf.handlerArgs);
((ParseeData *) conf.handlerArgs)->exts = ext_ctx;
ExtensionLoadDir(ext_ctx, parsee_conf->extensions);
Log(LOG_NOTICE, "Setting up local Matrix user...");
if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart))
{
@ -332,6 +346,8 @@ end:
ParseeDestroyHeadTable();
ParseeDestroyJIDTable();
ExtensionDestroyContext(ext_ctx);
(void) env;
return 0;
}

View file

@ -63,6 +63,8 @@ ParseeConfigLoad(char *conf)
CopyToStr(component_host, "component_host");
CopyToStr(shared_comp_secret, "shared_secret");
CopyToInt(max_stanza_size, "max_stanza_size");
CopyToStr(extensions, "extensions");
if (!config->max_stanza_size)
{
/* Standard XMPP "minimum" maximum */
@ -140,6 +142,7 @@ ParseeConfigFree(void)
Free(config->namespace_base);
Free(config->media_base);
Free(config->listen_as);
Free(config->extensions);
Free(config);
}
const ParseeConfig *

View file

@ -726,3 +726,13 @@ ParseeIsMediaEnabled(ParseeData *data, char *chat_id)
return ret;
}
bool
ParseeIsMUC(ParseeData *data, char *jid)
{
if (!data)
{
return false;
}
return XMPPQueryMUC(data->jabber, jid, NULL);
}

View file

@ -153,7 +153,6 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
char *href = HashMapGet(elem->attrs, "href");
/* TODO: Check if the element here is a Matrix.TO
* pointing to a Parsee user. */
Concat("(");
for (i = 0; i < ArraySize(elem->children); i++)
{
child = ArrayGet(elem->children, i);
@ -162,9 +161,9 @@ XMPPifyElement(HashMap *event, XMLElement *elem, XMPPFlags flags)
Concat(subxep);
Free(subxep);
}
Concat(" points to ");
Concat("< ");
Concat(href);
Concat(" )");
Concat(" >");
}
else
{

View file

@ -34,6 +34,10 @@ RouteHead(RouteTxns, arr, argp)
for (i = 0; i < ArraySize(events); i++)
{
HashMap *event = JsonValueAsObject(ArrayGet(events, i));
if (ExtensionPushEvent(args->data->exts, event))
{
continue;
}
ParseeEventHandler(args->data, event);
}

View file

@ -4,20 +4,10 @@
#include <Cytoplasm/Array.h>
#include <Cytoplasm/Str.h>
struct XMPPCommand {
XMPPCmdCallback callback;
char *node, *name;
char *form_instruction;
char *form_title;
/* TODO: On-the-fly generation of options */
Array *options;
XMPPOptionWriter otf;
};
#include "XMPPCommand/Internal.h"
XMPPCommand *
XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct)
XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback callback_funct)
{
XMPPCommand *cmd;
@ -32,6 +22,7 @@ XMPPBasicCmd(char *node, char *name, XMPPCmdCallback callback_funct)
cmd->name = StrDuplicate(name);
cmd->form_instruction = NULL;
cmd->form_title = NULL;
cmd->flags = flags;
/* No options -> no form required */
cmd->options = NULL;
@ -127,7 +118,7 @@ XMPPFormifyCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
ArrayFree(cmd->options);
cmd->options = NULL;
cmd->otf(m, cmd, from);
cmd->otf(m, cmd, from, cmd->node);
}
if (!cmd->options)
{
@ -184,13 +175,13 @@ XMPPCommandRequiresForm(XMPPCommand *cmd)
return cmd ? (cmd->otf || !!cmd->options) : false;
}
void
XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form)
XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node)
{
if (!m || !cmd || !from || !to)
if (!m || !cmd || !from || !to || !node)
{
return;
}
cmd->callback(m, from, form, to);
cmd->callback(m, from, form, to, node);
}
bool

View file

@ -0,0 +1,17 @@
#include <XMPPCommand.h>
#include <Cytoplasm/Array.h>
struct XMPPCommand {
XMPPCmdCallback callback;
char *node, *name;
char *form_instruction;
char *form_title;
/* TODO: On-the-fly generation of options */
Array *options;
XMPPOptionWriter otf;
XMPPCommandFlags flags;
};

View file

@ -8,6 +8,8 @@
#include <pthread.h>
#include "XMPPCommand/Internal.h"
typedef struct XMPPSession {
char *identifier;
@ -323,7 +325,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddAttr(command_xml, "node", node);
XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_id);
XMPPExecuteCommand(m, cmd, from, command_xml, NULL);
XMPPExecuteCommand(m, cmd, from, command_xml, NULL, node);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
@ -363,7 +365,7 @@ XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeData *data)
XMLAddAttr(command_xml, "node", node);
XMLAddAttr(command_xml, "status", "completed");
XMLAddAttr(command_xml, "sessionid", session_given);
XMPPExecuteCommand(m, cmd, from, command_xml, x_form);
XMPPExecuteCommand(m, cmd, from, command_xml, x_form, node);
XMLAddChild(iq, command_xml);
XMPPSendStanza(jabber, iq, data->config->max_stanza_size);
@ -385,3 +387,15 @@ XMPPGetManagerCookie(XMPPCommandManager *manager)
{
return manager ? manager->cookie : NULL;
}
XMPPCommandFlags
XMPPGetCommandFlags(XMPPCommandManager *m, char *id)
{
XMPPCommand *cmd;
if (!m || !id)
{
return XMPPCMD_ALL;
}
cmd = HashMapGet(m->commands, id);
return cmd ? cmd->flags : XMPPCMD_ALL;
}

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -23,6 +23,8 @@ AddAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
GetFieldValue(glob, "glob", form);
(void) node;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -21,6 +21,8 @@ AddNoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
GetFieldValue(entity, "entity", form);
GetFieldValue(reason, "reason", form);
(void) node;
if (!ParseeIsAdmin(data, trimmed))
{
SetNote("error", "User is not authorised to execute command.");

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
size_t i;
@ -20,6 +20,8 @@ AdminsCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLElement *title;
XMLElement *reported, *item, *field, *value, *txt;
(void) node;
x = XMLCreateTag("x");
title = XMLCreateTag("title");

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -31,4 +31,5 @@ CleanCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
SetNote("info", "Parsee data was sucessfully cleant up.");
(void) form;
(void) node;
}

View file

@ -31,7 +31,7 @@ DelAdmin(Array *admin_list, char *glob)
return removed;
}
void
DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -84,9 +84,11 @@ DelAdminCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
}
SetNote("info", "Sucessfully removed admins");
/* TODO */
(void) node;
}
void
FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
DbRef *admins;
@ -120,4 +122,6 @@ FormDelAdminCallback(XMPPCommandManager *m, XMPPCommand *cmd, char *from)
DbUnlock(data->db, admins);
XMPPAddOption(cmd, admin_opt);
(void) node;
}

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
@ -29,9 +29,10 @@ MUCInformationID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement
Free(room_id);
Free(chat_id);
(void) form;
(void) node;
}
void
MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *muc = ParseeTrimJID(from);
@ -46,4 +47,5 @@ MUCInformationCID(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen
Free(msg);
Free(chat_id);
(void) form;
(void) node;
}

View file

@ -15,7 +15,7 @@
#include <AS.h>
void
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
MUCSetKey(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
@ -51,9 +51,10 @@ end:
Free(chat_id);
Free(muc);
(void) form;
(void) node;
}
void
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
MUCGetKeys(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation = NULL, *role = NULL;
@ -114,4 +115,5 @@ end:
Free(chat_id);
Free(muc);
(void) form;
(void) node;
}

View file

@ -14,7 +14,7 @@
#include <AS.h>
void
MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *affiliation, *role;
@ -64,4 +64,5 @@ MUCUnlink(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
Free(room);
Free(muc);
(void) form;
(void) node;
}

View file

@ -14,7 +14,7 @@
#define TITLE "Parsee global bans"
void
NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -89,4 +89,5 @@ NoflyCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *o
}
(void) form;
(void) node;
}

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
char *trimmed = ParseeTrimJID(from);
size_t alloc = MemoryAllocated();
@ -69,5 +69,6 @@ StatusCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *
XMLAddChild(out, x);
(void) form;
(void) node;
(void) m;
}

View file

@ -12,7 +12,7 @@
#include <XML.h>
void
ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -35,9 +35,10 @@ ClearWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLE
SetNote("info", "Parsee whitelist was removed.");
(void) form;
(void) node;
}
void
AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -79,9 +80,10 @@ AddWhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLEle
DbUnlock(data->db, ref);
SetNote("info", "Server successfully whitelisted.");
(void) node;
}
void
WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out)
WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElement *out, char *node)
{
ParseeData *data = XMPPGetManagerCookie(m);
char *trimmed = ParseeTrimJID(from);
@ -139,4 +141,5 @@ WhitelistCallback(XMPPCommandManager *m, char *from, XMLElement *form, XMLElemen
}
(void) form;
(void) node;
}

View file

@ -94,15 +94,13 @@ PEPVCardEvent(PEPManager *m, XMLElement *stanza, XMLElement *item)
AddTextField(vcard, "fn", name);
AddTextField(vcard, "nickname", mxid);
AddURIField(vcard, "url", REPOSITORY);
AddURIField(vcard, "url", "https://kappach.at/parsee");
AddURIField(vcard, "url", "https://matrix.org");
AddURIField(vcard, "url", m_to);
AddTextField(
vcard,
"note",
"This is a bridged Matrix user, from Parsee."
"This is a bridged Matrix user, from " NAME "."
);
Free(mxid);
Free(name);

View file

@ -65,6 +65,16 @@ XMPPDispatcher(void *argp)
Log(LOG_DEBUG, "Received stanza='%s'.", stanza->name);
}
if (ExtensionPushStanza(args->exts, stanza, STANZA_RAW))
{
if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Stanza was vetoed by an extension.");
}
XMLFreeElement(stanza);
continue;
}
if (StrEquals(stanza->name, "presence"))
{
PresenceStanza(args, stanza, thread);
@ -130,6 +140,7 @@ bool
XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
{
ParseeData *args = XMPPGetManagerCookie(m);
XMPPCommandFlags flags;
char *trimmed_from;
char *from;
char *chat_id;
@ -144,28 +155,24 @@ XMPPCommandFilter(XMPPCommandManager *m, char *id, XMLElement *stanza)
is_muc = !!(chat_id = ParseeGetFromMUCID(args, trimmed_from));
Free(trimmed_from);
Free(chat_id);
#define XMPP_COMMAND(f,l,n,t,s) \
if (StrEquals(n, id)) \
{ \
if (l == XMPPCMD_ALL) \
{ \
return true; \
} \
else if (l == XMPPCMD_MUC) \
{ \
return is_muc; \
} \
else if (l == XMPPCMD_ADMINS) \
{ \
bool is_admin; \
trimmed_from = ParseeTrimJID(from); \
is_admin = ParseeIsAdmin(args, trimmed_from); \
Free(trimmed_from); \
return is_admin; \
} \
flags = XMPPGetCommandFlags(m, id);
if (flags == XMPPCMD_ALL)
{
return true;
}
else if (flags == XMPPCMD_MUC)
{
return is_muc;
}
else if (flags == XMPPCMD_ADMINS)
{
bool is_admin;
trimmed_from = ParseeTrimJID(from);
is_admin = ParseeIsAdmin(args, trimmed_from);
Free(trimmed_from);
return is_admin;
}
XMPPCOMMANDS
#undef XMPP_COMMAND
return false;
}
@ -191,13 +198,14 @@ ParseeXMPPThread(void *argp)
XMPPCommand *cmd;
#define XMPP_COMMAND(f,l,n,t,s) \
cmd = XMPPBasicCmd( \
n, t, f \
l, n, t, f \
); \
s \
XMPPRegisterCommand(info.m, cmd);
XMPPCOMMANDS
#undef XMPP_COMMAND
}
ExtensionRequestCommands(args->exts);
info.pep_manager = CreatePEPManager(args, &info);
{
PEPManagerAddEvent(

View file

@ -421,10 +421,16 @@ IQGet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
char *parsee_muc_jid = StrConcat(3, trimmed, "/", "parsee");
XMLAddAttr(q, "xmlns", "http://jabber.org/protocol/disco#items");
XMLAddAttr(q, "node", "http://jabber.org/protocol/commands");
XMPPShoveCommandList(thr->info->m,
IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza
);
ExtensionShoveCommands(
args->exts,
IsInMUC(args, from) ? parsee_muc_jid : to,
q, stanza
);
XMLAddChild(iq_reply, q);
Free(parsee_muc_jid);
}
@ -611,6 +617,7 @@ IQSet(ParseeData *args, XMLElement *stanza, XMPPThread *thr)
{
XMPPCommandManager *manager = thr->info->m;
XMPPManageCommand(manager, stanza, args);
ExtensionManageCommands(args->exts, stanza);
}
#undef DISCO

View file

@ -411,7 +411,17 @@ end_error:
/* Check if it is a media link */
oob = XMLookForTKV(stanza, "x", "xmlns", "jabber:x:oob");
if (oob && data && media_enabled)
/* TODO: This may be way too late. I think a way to listen to *all*
* stanzas may be worthwhile. */
if (ExtensionPushStanza(args->exts, stanza, STANZA_MESSAGE))
{
//if (args->verbosity >= PARSEE_VERBOSE_COMICAL)
{
Log(LOG_DEBUG, "Stanza was vetoed by an extension.");
}
}
else if (oob && data && media_enabled)
{
char *mxc, *mime = NULL;
HashMap *content = NULL;

View file

@ -68,7 +68,6 @@ int IdentitySort(void *idap, void *idbp);
char * ParseeGetBridgedRoom(ParseeData *data, XMLElement *stanza);
char * ParseeGetEventFromID(ParseeData *data, XMLElement *stanza, char *id);
char * ParseeGetReactedEvent(ParseeData *data, XMLElement *stanza);
void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event);
bool ParseeVerifyAllStanza(ParseeData *args, XMLElement *stanza);
HashMap * ShoveStanza(HashMap *content, XMLElement *stanza);
@ -82,9 +81,6 @@ void PresenceStanza(ParseeData *args, XMLElement *stanza, XMPPThread *thr);
bool ServerHasXEP421(ParseeData *data, char *from);
char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force);
#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL)
PEPManager * CreatePEPManager(ParseeData *data, void *cookie);
void * PEPManagerCookie(PEPManager *manager);
void PEPManagerAddEvent(PEPManager *manager, char *node, PEPEvent event);

81
src/include/Extension.h Normal file
View file

@ -0,0 +1,81 @@
#ifndef PARSEE_EXTENSION_H
#define PARSEE_EXTENSION_H
#include <stdbool.h>
#include <Cytoplasm/Json.h>
#include <XML.h>
typedef struct ParseeData ParseeData;
typedef struct Extensions Extensions;
typedef struct Extension Extension;
typedef enum StanzaType {
STANZA_RAW = 0,
STANZA_MESSAGE = 1,
STANZA_IQ = 2,
STANZA_PRESENCE = 3,
STANZA_TYPE_COUNT
} StanzaType;
/** Verifies if extensions are enabled with the Parsee binary.
* Functions here <strong>WILL</strong> fail if this function
* returns false!
* -------------------
* Returns: true IFF extensions are enabled */
extern bool ExtensionEnabled(void);
/* Creates an extension context. */
extern Extensions * ExtensionCreateContext(struct ParseeData *data);
/** Destroys the entire extension context, and all of its extensions.
* -------------------------------------
* Returns: NOTHING
* Thrashes: ctx */
extern void ExtensionDestroyContext(Extensions *ctx);
/** Loads a basic extension from a simple Janet file.
* ----------------------------
* Returns: An extension handle[ctx] | NULL */
extern Extension * ExtensionLoadBasic(Extensions *ctx, char *id, char *file);
/** Reloads an extension from an ID.
* ---------------------------
* Returns: NOTHING */
extern void ExtensionReload(Extensions *ctx, char *id);
/** Loads extensions from a directory. The directory's Janet files are loaded
* (with the part before the .janet extension denoting the identifier).
* -----------------------------------
* Returns: NOTHING */
extern void ExtensionLoadDir(Extensions *ctx, char *dir);
/** Broadcasts a stanza received to all extensions(by calling the
* on-stanza function).
* -----------------------------------------
* Returns: NOTHING
* Modifies: the extensions */
extern bool ExtensionPushStanza(Extensions *ctx, XMLElement *stanza, StanzaType t);
/** Broadcasts a Matrix event to all extensions, akin to ExtensionPushStanza.
* -----------
* Returns: NOTHING
* Modifies: the extensions */
extern bool ExtensionPushEvent(Extensions *ctx, HashMap *obj);
/** Calls "on-xmpp-cmd" on extensions' Janet side, with no arguments.
* ----------
* Returns: NOTHING
* Modifies: the extensions */
extern void ExtensionRequestCommands(Extensions *ctx);
extern void
ExtensionShoveCommands(Extensions *ctx, char *jid, XMLElement *query, XMLElement *stanza);
extern void
ExtensionManageCommands(Extensions *ctx, XMLElement *stanza);
#endif

View file

@ -15,6 +15,7 @@ typedef struct ParseeData ParseeData;
#include <pthread.h>
#include <Extension.h>
#include <Command.h>
#include <XMPP.h>
@ -50,6 +51,7 @@ typedef struct ParseeConfig {
/* ------- DB -------- */
char *db_path;
size_t db_size;
char *extensions;
/* - COMMAND-LINE FLAGS - */
int xmpp_threads, http_threads;
@ -72,8 +74,10 @@ typedef struct ParseeData {
pthread_mutex_t oidl;
/* If Parsee was intentionally halted */
bool halted;
volatile bool halted;
pthread_mutex_t halt_lock;
Extensions *exts;
} ParseeData;
typedef struct Argument {
@ -93,6 +97,8 @@ typedef struct Argument {
#define GrabObject(obj, ...) JsonValueAsObject(JsonGet(obj, __VA_ARGS__))
#define GrabArray(obj, ...) JsonValueAsArray(JsonGet(obj, __VA_ARGS__))
#define IterateReentrant(h, k, v, i) HashMapIterateReentrant(h, &k, (void **) &v, &i)
/* Milliseconds to UNIT macros, to be used like 30 SECONDS and 1 MINUTES
* in timestamps */
#define SECONDS * 1000
@ -121,6 +127,7 @@ extern const char *parsee_ascii[PARSEE_ASCII_LINES];
* Modifies: the logger output */
extern void ParseePrintASCII(void);
/**
* Checks if two versions of Parsee can be considered "compatible".
* This is mainly used for things like database operations. TODO:
@ -276,6 +283,12 @@ extern char * ParseeGetRoomID(ParseeData *, char *chat_id);
/* Finds the MUC JID from a chat ID */
extern char * ParseeGetMUCID(ParseeData *, char *chat_id);
/** Verifies if a JID maps to a chatroom through Service Discovery.
* ----------------
* Returns: whenever the JID is a real MUC
* Modifies: the XMPP stream */
extern bool ParseeIsMUC(ParseeData *data, char *jid);
/** 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.
@ -321,6 +334,14 @@ ParseeIsMediaEnabled(ParseeData *data, char *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);
/** Automatically pushes the link between a stanza and a bridged Matrix event.
* It behaves like {ParseePushStanza} and {ParseePushDMStanza}, but the routing
* is done automatically.
* ----------------------------
* Returns: NOTHING
* Modifies: the database */
extern void ParseePushAllStanza(ParseeData *args, XMLElement *stanza, char *event);
/* Checks if a stanza is not duplicated in a chat ID */
extern bool ParseeVerifyStanza(ParseeData *, char *chat_id, char *stanza_id);
@ -488,4 +509,8 @@ extern void ParseeUnlinkRoom(ParseeData *data, char *chat_id);
* Modifies: NOTHING */
extern bool ParseeIsMUCWhitelisted(ParseeData *data, char *muc);
/* TODO */
extern char * ParseeGetBridgedUserI(ParseeData *data, XMLElement *stanza, char *force);
#define ParseeGetBridgedUser(data, stanza) ParseeGetBridgedUserI(data, stanza, NULL)
#endif

View file

@ -1,3 +1,4 @@
typedef struct XMPPCommandManager XMPPCommandManager;
#ifndef PARSEE_XMPPCOMMAND_H
#define PARSEE_XMPPCOMMAND_H
@ -7,11 +8,16 @@
#include <Parsee.h>
#include <XML.h>
typedef struct XMPPCommandManager XMPPCommandManager;
typedef enum XMPPCommandFlags {
XMPPCMD_ALL = 0,
XMPPCMD_MUC , /* Only for MUCs */
XMPPCMD_ADMINS /* Only for administrators */
} XMPPCommandFlags;
typedef struct XMPPCommand XMPPCommand;
typedef struct XMPPOption XMPPOption;
typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *);
typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *);
typedef void (*XMPPCmdCallback)(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *);
typedef void (*XMPPOptionWriter)(XMPPCommandManager *, XMPPCommand *, char *, char *);
typedef bool (*XMPPCmdFilter)(XMPPCommandManager *, char *id, XMLElement *stanza);
/** Creates a simple XMPP command manager, which routes commands
@ -39,7 +45,7 @@ XMPPManagerSetFilter(XMPPCommandManager *manager, XMPPCmdFilter filter);
* Modifies: NOTHING
* See-Also: XMPPRegisterCommand */
extern XMPPCommand *
XMPPBasicCmd(char *node, char *name, XMPPCmdCallback cb);
XMPPBasicCmd(XMPPCommandFlags flags, char *node, char *name, XMPPCmdCallback cb);
extern void
XMPPCmdOptionsCreator(XMPPCommand *cmd, XMPPOptionWriter writer);
extern void XMPPAddOption(XMPPCommand *cmd, XMPPOption *opt);
@ -49,7 +55,7 @@ extern char * XMPPGetCommandDesc(XMPPCommand *cmd);
extern void XMPPSetFormInstruction(XMPPCommand *cmd, char *instruction);
extern void XMPPSetFormTitle(XMPPCommand *cmd, char *title);
extern bool XMPPCommandRequiresForm(XMPPCommand *cmd);
extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form);
extern void XMPPExecuteCommand(XMPPCommandManager *m, XMPPCommand *cmd, char *from, XMLElement *to, XMLElement *form, char *node);
/** Create a basic option.
* -----------------------------------------------------
@ -91,6 +97,12 @@ extern void XMPPRegisterCommand(XMPPCommandManager *m, XMPPCommand *cmd);
* See-Also: XMPPCreateManager */
extern void XMPPShoveCommandList(XMPPCommandManager *m, char *jid, XMLElement *p, XMLElement *s);
/** Returns the flags associated to a command.
* ----------------
* Returns: some flags
* Modifies: NOTHING */
extern XMPPCommandFlags XMPPGetCommandFlags(XMPPCommandManager *m, char *id);
/** Destroys all memory related to the command {manager}.
* -----------------------------------------------------
* Returns: NOTHING
@ -111,11 +123,11 @@ extern bool XMPPManageCommand(XMPPCommandManager *m, XMLElement *stanza, ParseeD
#define XMPP_COMMAND(f,l,n,t,s) \
extern void \
f(XMPPCommandManager *, char *, XMLElement *, XMLElement *); \
f(XMPPCommandManager *, char *, XMLElement *, XMLElement *, char *); \
/* This symbol might not exist. We do, however, not care, as
* it is not always done. */ \
extern void \
Form##f(XMPPCommandManager *, XMPPCommand *, char *); \
Form##f(XMPPCommandManager *, XMPPCommand *, char *, char *); \
#include "XMPPCommands.x.h"

View file

@ -1,9 +1,4 @@
/* C X-macro file */
typedef enum XMPPCommandFlags {
XMPPCMD_ALL = 0,
XMPPCMD_MUC , /* Only for MUCs */
XMPPCMD_ADMINS /* Only for administrators */
} XMPPCommandFlags;
#define XMPPCOMMANDS \
XMPP_COMMAND(StatusCallback, XMPPCMD_ALL, "stats", "Get Parsee statistics", {}) \
XMPP_COMMAND(CleanCallback, XMPPCMD_ADMINS, "clean", "Cleanup temporary Parsee data", {}) \