view lib/irccd/js-plugin.c @ 948:21a91311c8ea

cmake: switch back, GNU make is painful
author David Demelier <markand@malikania.fr>
date Sat, 16 Jan 2021 17:58:46 +0100
parents 95201fd9ad88
children ab43ba409f9d
line wrap: on
line source

/*
 * jsapi-plugin.c -- Javascript plugins
 *
 * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>

#include <duktape.h>

#include "channel.h"
#include "event.h"
#include "js-plugin.h"
#include "jsapi-file.h"
#include "jsapi-irccd.h"
#include "jsapi-logger.h"
#include "jsapi-plugin.h"
#include "jsapi-plugin.h"
#include "jsapi-server.h"
#include "jsapi-system.h"
#include "jsapi-timer.h"
#include "jsapi-unicode.h"
#include "log.h"
#include "plugin.h"
#include "util.h"

struct self {
	duk_context *ctx;
	char **options;
	char **templates;
	char **paths;
	char *license;
	char *version;
	char *author;
	char *description;
};

static void
freelist(char **table)
{
	for (char **p = table; *p; ++p)
		free(*p);

	free(table);
}

static char *
metadata(duk_context *ctx, const char *name)
{
	char *ret = NULL;

	duk_get_global_string(ctx, "info");

	if (duk_get_type(ctx, -1) == DUK_TYPE_OBJECT) {
		duk_get_prop_string(ctx, -1, name);

		if (duk_get_type(ctx, -1) == DUK_TYPE_STRING)
			ret = irc_util_strdup(duk_get_string(ctx, -1));

		duk_pop(ctx);
	}

	duk_pop(ctx);

	return ret ? ret : irc_util_strdup("unknown");
}

static void
push_names(duk_context *ctx, const struct irc_channel *ch)
{
	duk_push_array(ctx);

	for (size_t i = 0; i < ch->usersz; ++i) {
		duk_push_string(ctx, ch->users[i].nickname);
		duk_put_prop_index(ctx, -2, i);
	}
}

static const char **
get_table(duk_context *ctx, const char *name, char ***ptable)
{
	char **list;
	size_t listsz;

	duk_get_global_string(ctx, name);

	if (!(listsz = duk_get_length(ctx, -1))) {
		duk_pop(ctx);
		return NULL;
	}

	list = irc_util_calloc(listsz + 1, sizeof (char *));

	duk_enum(ctx, -1, 0);

	for (size_t i = 0; i < listsz && duk_next(ctx, -1, true); ++i) {
		list[i] = irc_util_strdup(duk_to_string(ctx, -2));
		duk_pop_n(ctx, 2);
	}

	duk_pop_n(ctx, 2);

	freelist(*ptable);
	*ptable = list;

	return (const char **)list;
}

static void
set_key_value(duk_context *ctx, const char *table, const char *key, const char *value)
{
	duk_get_global_string(ctx, table);
	duk_push_string(ctx, value);
	duk_put_prop_string(ctx, -2, key);
	duk_pop(ctx);
}

static const char *
get_value(duk_context *ctx, const char *table, const char *key)
{
	const char *ret;

	duk_get_global_string(ctx, table);
	duk_get_prop_string(ctx, -1, key);
	ret = duk_to_string(ctx, -1);
	duk_pop_n(ctx, 2);

	return ret;
}

static void
set_template(struct irc_plugin *plg, const char *key, const char *value)
{
	struct self *js = plg->data;

	set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, key, value);
}

static const char *
get_template(struct irc_plugin *plg, const char *key)
{
	struct self *js = plg->data;

	return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, key);
}

static const char **
get_templates(struct irc_plugin *plg)
{
	struct self *js = plg->data;

	return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_TEMPLATES, &js->templates);
}

static void
set_path(struct irc_plugin *plg, const char *key, const char *value)
{
	struct self *js = plg->data;

	set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, key, value);
}

static const char *
get_path(struct irc_plugin *plg, const char *key)
{
	struct self *js = plg->data;

	return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, key);
}

static const char **
get_paths(struct irc_plugin *plg)
{
	struct self *js = plg->data;

	return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_PATHS, &js->paths);
}

static void
set_option(struct irc_plugin *plg, const char *key, const char *value)
{
	struct self *js = plg->data;

	set_key_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, key, value);
}

static const char *
get_option(struct irc_plugin *plg, const char *key)
{
	struct self *js = plg->data;

	return get_value(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, key);
}

static const char **
get_options(struct irc_plugin *plg)
{
	struct self *js = plg->data;

	return get_table(js->ctx, IRC_JSAPI_PLUGIN_PROP_OPTIONS, &js->options);
}

static void
vcall(struct irc_plugin *plg, const char *function, const char *fmt, va_list ap)
{
	struct self *self = plg->data;
	int nargs = 0;

	duk_get_global_string(self->ctx, function);

	if (!duk_is_function(self->ctx, -1)) {
		duk_pop(self->ctx);
		return;
	}

	for (const char *f = fmt; *f; ++f) {
		void (*push)(duk_context *, void *);

		switch (*f) {
		case 'S':
			irc_jsapi_server_push(self->ctx, va_arg(ap, struct irc_server *));
			break;
		case 's':
			duk_push_string(self->ctx, va_arg(ap, const char *));
			break;
		case 'x':
			push = va_arg(ap, void (*)(duk_context *, void *));
			push(self->ctx, va_arg(ap, void *));
			break;
		default:
			continue;
		}

		++nargs;
	}

	if (duk_pcall(self->ctx, nargs) != 0)
		irc_log_warn("plugin %s: %s\n", duk_to_string(self->ctx, -1));

	duk_pop(self->ctx);
}

static void
call(struct irc_plugin *plg, const char *function, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vcall(plg, function, fmt, ap);
	va_end(ap);
}

static void
handle(struct irc_plugin *plg, const struct irc_event *ev)
{
	switch (ev->type) {
	case IRC_EVENT_CONNECT:
		call(plg, "onConnect", "S", ev->server);
		break;
	case IRC_EVENT_DISCONNECT:
		call(plg, "onDisconnect", "S", ev->server);
		break;
	case IRC_EVENT_INVITE:
		call(plg, "onInvite", "Ssss", ev->server, ev->invite.origin,
		    ev->invite.channel, ev->invite.nickname);
		break;
	case IRC_EVENT_JOIN:
		call(plg, "onJoin", "Sss", ev->server, ev->join.origin,
		    ev->join.channel);
		break;
	case IRC_EVENT_KICK:
		call(plg, "onKick", "Sssss", ev->server, ev->kick.origin, ev->kick.channel,
		    ev->kick.target, ev->kick.reason);
		break;
	case IRC_EVENT_ME:
		call(plg, "onMe", "Ssss", ev->server, ev->me.origin, ev->me.channel,
		    ev->me.message);
		break;
	case IRC_EVENT_MESSAGE:
		call(plg, "onMessage", "Ssss", ev->server, ev->message.origin,
		    ev->message.channel, ev->message.message);
		break;
	case IRC_EVENT_MODE:
		call(plg, "onMode", "Sssssss", ev->server, ev->mode.origin, ev->mode.channel,
		    ev->mode.mode, ev->mode.limit, ev->mode.user, ev->mode.mask);
		break;
	case IRC_EVENT_NAMES:
		call(plg, "onNames", "Ssx", ev->server, ev->names.channel->name,
		    push_names, ev->names.channel);
		break;
	case IRC_EVENT_NICK:
		call(plg, "onNick", "Sss", ev->server, ev->nick.origin, ev->nick.nickname);
		break;
	case IRC_EVENT_NOTICE:
		call(plg, "onNotice", "Ssss", ev->server, ev->notice.origin,
		    ev->notice.channel, ev->notice.message);
		break;
	case IRC_EVENT_PART:
		call(plg, "onPart", "Ssss", ev->server, ev->part.origin, ev->part.channel,
		    ev->part.reason);
		break;
	case IRC_EVENT_TOPIC:
		call(plg, "onTopic", "Ssss", ev->server, ev->topic.origin,
		    ev->topic.channel, ev->topic.topic);
		break;
#if 0
	case IRC_EVENT_WHOIS:
		call(plg, "onWhois", "Sx", ev->topic.server, js_event_whois_push, &ev->whois.whois);
		break;
#endif
	default:
		break;
	}
}

static char *
eat(const char *path)
{
	int fd = -1;
	char *ret = NULL;
	struct stat st;

	if ((fd = open(path, O_RDONLY)) < 0)
		goto err;
	if (fstat(fd, &st) < 0)
		goto err;
	if (!(ret = calloc(1, st.st_size + 1)))
		goto err;
	if (read(fd, ret, st.st_size) != st.st_size)
		goto err;

	close(fd);

	return ret;

err:
	close(fd);
	free(ret);

	return false;
}

static void *
wrap_malloc(void *udata, size_t size)
{
	(void)udata;

	return irc_util_malloc(size);
}

static void *
wrap_realloc(void *udata, void *ptr, size_t size)
{
	(void)udata;

	return irc_util_realloc(ptr, size);
}

static void
wrap_free(void *udata, void *ptr)
{
	(void)udata;

	free(ptr);
}

static bool
init(struct irc_plugin *plg, const char *script)
{
	struct self js = {0};

	/* Load all modules. */
	js.ctx = duk_create_heap(wrap_malloc, wrap_realloc, wrap_free, NULL, NULL);
	irc_jsapi_load(js.ctx);
	irc_jsapi_file_load(js.ctx);
	irc_jsapi_logger_load(js.ctx);
	irc_jsapi_plugin_load(js.ctx, plg);
	irc_jsapi_server_load(js.ctx);
	irc_jsapi_system_load(js.ctx);
	irc_jsapi_timer_load(js.ctx);
	irc_jsapi_unicode_load(js.ctx);

	if (duk_peval_string(js.ctx, script) != 0) {
		irc_log_warn("plugin %s: %s", plg->name, duk_to_string(js.ctx, -1));
		duk_destroy_heap(js.ctx);
		return false;
	}

	plg->license = js.license = metadata(js.ctx, "license");
	plg->version = js.version = metadata(js.ctx, "version");
	plg->author = js.author = metadata(js.ctx, "author");
	plg->description = js.description = metadata(js.ctx, "summary");
	plg->data = irc_util_memdup(&js, sizeof (js));

	return true;
}

static void
load(struct irc_plugin *plg)
{
	call(plg, "onLoad", "");
}

static void
reload(struct irc_plugin *plg)
{
	call(plg, "onReload", "");
}

static void
unload(struct irc_plugin *plg)
{
	call(plg, "onUnload", "");
}

static void
finish(struct irc_plugin *plg)
{
	struct self *self = plg->data;

	if (self->ctx)
		duk_destroy_heap(self->ctx);

	free(self->license);
	free(self->version);
	free(self->author);
	free(self->description);

	memset(self, 0, sizeof (*self));
}

bool
irc_js_plugin_open(struct irc_plugin *plg, const char *path)
{
	assert(plg);
	assert(path);

	char *script = NULL;

	if (!(script = eat(path))) {
		irc_log_warn("plugin: %s", strerror(errno));
		return false;
	}

	if (!(init(plg, script))) {
		free(script);
		return false;
	}

	plg->set_template = set_template;
	plg->get_template = get_template;
	plg->get_templates = get_templates;
	plg->set_path = set_path;
	plg->get_path = get_path;
	plg->get_paths = get_paths;
	plg->set_option = set_option;
	plg->get_option = get_option;
	plg->get_options = get_options;
	plg->load = load;
	plg->reload = reload;
	plg->unload = unload;
	plg->handle = handle;
	plg->finish = finish;

	/* No longer needed. */
	free(script);

	/* If error occured, init() has logged. */
	return plg->data != NULL;
}