changeset 820:9109f19987fb

misc: remove js_ prefix in js namespace
author David Demelier <markand@malikania.fr>
date Thu, 29 Nov 2018 21:28:48 +0100
parents d2737ddd7e36
children 6532414a9cbd
files README.md irccd-test/main.cpp irccd/main.cpp libirccd-js/CMakeLists.txt libirccd-js/irccd/js.hpp libirccd-js/irccd/js/api.cpp libirccd-js/irccd/js/api.hpp libirccd-js/irccd/js/directory_api.cpp libirccd-js/irccd/js/directory_api.hpp libirccd-js/irccd/js/directory_js_api.cpp libirccd-js/irccd/js/directory_js_api.hpp libirccd-js/irccd/js/elapsed_timer_api.cpp libirccd-js/irccd/js/elapsed_timer_api.hpp libirccd-js/irccd/js/elapsed_timer_js_api.cpp libirccd-js/irccd/js/elapsed_timer_js_api.hpp libirccd-js/irccd/js/file_api.cpp libirccd-js/irccd/js/file_api.hpp libirccd-js/irccd/js/file_js_api.cpp libirccd-js/irccd/js/file_js_api.hpp libirccd-js/irccd/js/irccd_api.cpp libirccd-js/irccd/js/irccd_api.hpp libirccd-js/irccd/js/irccd_js_api.cpp libirccd-js/irccd/js/irccd_js_api.hpp libirccd-js/irccd/js/js_api.cpp libirccd-js/irccd/js/js_api.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/logger_api.cpp libirccd-js/irccd/js/logger_api.hpp libirccd-js/irccd/js/logger_js_api.cpp libirccd-js/irccd/js/logger_js_api.hpp libirccd-js/irccd/js/plugin.cpp libirccd-js/irccd/js/plugin.hpp libirccd-js/irccd/js/plugin_api.cpp libirccd-js/irccd/js/plugin_api.hpp libirccd-js/irccd/js/plugin_js_api.cpp libirccd-js/irccd/js/plugin_js_api.hpp libirccd-js/irccd/js/server_api.cpp libirccd-js/irccd/js/server_api.hpp libirccd-js/irccd/js/server_js_api.cpp libirccd-js/irccd/js/server_js_api.hpp libirccd-js/irccd/js/system_api.cpp libirccd-js/irccd/js/system_api.hpp libirccd-js/irccd/js/system_js_api.cpp libirccd-js/irccd/js/system_js_api.hpp libirccd-js/irccd/js/timer_api.cpp libirccd-js/irccd/js/timer_api.hpp libirccd-js/irccd/js/timer_js_api.cpp libirccd-js/irccd/js/timer_js_api.hpp libirccd-js/irccd/js/unicode_api.cpp libirccd-js/irccd/js/unicode_api.hpp libirccd-js/irccd/js/unicode_js_api.cpp libirccd-js/irccd/js/unicode_js_api.hpp libirccd-js/irccd/js/util_api.cpp libirccd-js/irccd/js/util_api.hpp libirccd-js/irccd/js/util_js_api.cpp libirccd-js/irccd/js/util_js_api.hpp libirccd-test/irccd/test/js_fixture.cpp libirccd-test/irccd/test/js_fixture.hpp libirccd-test/irccd/test/js_plugin_fixture.cpp libirccd-test/irccd/test/js_plugin_fixture.hpp tests/src/libirccd-js/js-plugin/main.cpp
diffstat 62 files changed, 6112 insertions(+), 6118 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Mon Nov 26 21:53:27 2018 +0100
+++ b/README.md	Thu Nov 29 21:28:48 2018 +0100
@@ -18,7 +18,7 @@
   - Can connect to multiple servers,
   - Support multiple identities,
   - Can be controlled by sockets and irccdctl,
-  - Runs on Linux, Windows and *BSD,
+  - Runs on Linux, Windows and \*BSD,
   - Extremely well documented,
   - Clean and powerful JavaScript API,
   - Very fast and light.
--- a/irccd-test/main.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/irccd-test/main.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -43,8 +43,8 @@
 #include <irccd/test/debug_server.hpp>
 
 #if defined(IRCCD_HAVE_JS)
-#	include <irccd/js/js_api.hpp>
-#	include <irccd/js/js_plugin.hpp>
+#	include <irccd/js/api.hpp>
+#	include <irccd/js/plugin.hpp>
 #endif
 
 using boost::format;
@@ -59,8 +59,8 @@
 using irccd::daemon::whois_event;
 using irccd::daemon::dynlib_plugin_loader;
 
-using irccd::js::js_plugin_loader;
-using irccd::js::js_api;
+using irccd::js::plugin_loader;
+using irccd::js::api;
 
 namespace irccd::test {
 
@@ -645,9 +645,9 @@
 	daemon->plugins().add_loader(std::make_unique<dynlib_plugin_loader>());
 
 #if defined(IRCCD_HAVE_JS)
-	auto loader = std::make_unique<js_plugin_loader>(*daemon);
+	auto loader = std::make_unique<plugin_loader>(*daemon);
 
-	for (const auto& f : js_api::registry())
+	for (const auto& f : api::registry())
 		loader->get_modules().push_back(f());
 
 	daemon->plugins().add_loader(std::move(loader));
--- a/irccd/main.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/irccd/main.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -35,8 +35,8 @@
 #include <irccd/daemon/transport_service.hpp>
 
 #if defined(IRCCD_HAVE_JS)
-#	include <irccd/js/js_api.hpp>
-#	include <irccd/js/js_plugin.hpp>
+#	include <irccd/js/api.hpp>
+#	include <irccd/js/plugin.hpp>
 #endif
 
 namespace irccd::daemon {
@@ -185,9 +185,9 @@
 	instance->plugins().add_loader(std::make_unique<dynlib_plugin_loader>());
 
 #if defined(IRCCD_HAVE_JS)
-	auto loader = std::make_unique<js::js_plugin_loader>(*instance);
+	auto loader = std::make_unique<js::plugin_loader>(*instance);
 
-	for (const auto& f : js::js_api::registry())
+	for (const auto& f : js::api::registry())
 		loader->get_modules().push_back(f());
 
 	instance->plugins().add_loader(std::move(loader));
--- a/libirccd-js/CMakeLists.txt	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-js/CMakeLists.txt	Thu Nov 29 21:28:48 2018 +0100
@@ -22,50 +22,49 @@
 
 set(
 	SOURCES
-	${libirccd-js_SOURCE_DIR}/irccd/js.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/directory_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/directory_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/duk.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/duk.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/file_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/file_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/irccd_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/irccd_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/logger_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/logger_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/plugin_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/plugin_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/server_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/server_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/system_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/system_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/timer_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/timer_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/unicode_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/unicode_js_api.hpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/util_js_api.cpp
-	${libirccd-js_SOURCE_DIR}/irccd/js/util_js_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/directory_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/directory_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/duk.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/duk.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/elapsed_timer_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/elapsed_timer_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/file_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/file_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/irccd_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/irccd_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/plugin.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/plugin.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/logger_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/logger_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/plugin_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/plugin_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/server_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/server_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/system_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/system_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/timer_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/timer_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/unicode.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/unicode.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/unicode_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/unicode_api.hpp
+	${PROJECT_SOURCE_DIR}/irccd/js/util_api.cpp
+	${PROJECT_SOURCE_DIR}/irccd/js/util_api.hpp
 )
 
 irccd_define_library(
 	TARGET libirccd-js
 	EXPORT
-	HEADERS ${libirccd-js_SOURCE_DIR}/irccd/
-	SOURCES
-		${SOURCES}
+	HEADERS ${PROJECT_SOURCE_DIR}/irccd/
+	SOURCES ${SOURCES}
 	LIBRARIES
 		Boost::timer
 		libduktape
 		libirccd-daemon
 	PUBLIC_INCLUDES
-		$<BUILD_INTERFACE:${libirccd-js_SOURCE_DIR}>
+		$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>
 
 )
--- a/libirccd-js/irccd/js.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-js/irccd/js.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -26,20 +26,20 @@
 
 #include "sysconfig.hpp"
 
-#include "js/file_js_api.hpp"
+#include "js/api.hpp"
+#include "js/directory_api.hpp"
+#include "js/duk.hpp"
+#include "js/elapsed_timer_api.hpp"
+#include "js/file_api.hpp"
+#include "js/irccd_api.hpp"
+#include "js/logger_api.hpp"
+#include "js/plugin.hpp"
+#include "js/plugin_api.hpp"
+#include "js/server_api.hpp"
+#include "js/system_api.hpp"
+#include "js/timer_api.hpp"
 #include "js/unicode.hpp"
-#include "js/directory_js_api.hpp"
-#include "js/js_plugin.hpp"
-#include "js/js_api.hpp"
-#include "js/timer_js_api.hpp"
-#include "js/unicode_js_api.hpp"
-#include "js/util_js_api.hpp"
-#include "js/duk.hpp"
-#include "js/elapsed_timer_js_api.hpp"
-#include "js/system_js_api.hpp"
-#include "js/logger_js_api.hpp"
-#include "js/irccd_js_api.hpp"
-#include "js/plugin_js_api.hpp"
-#include "js/server_js_api.hpp"
+#include "js/unicode_api.hpp"
+#include "js/util_api.hpp"
 
 #endif // !IRCCD_JS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,65 @@
+/*
+ * api.cpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2018 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 "directory_api.hpp"
+#include "elapsed_timer_api.hpp"
+#include "file_api.hpp"
+#include "irccd_api.hpp"
+#include "logger_api.hpp"
+#include "plugin_api.hpp"
+#include "server_api.hpp"
+#include "system_api.hpp"
+#include "timer_api.hpp"
+#include "unicode_api.hpp"
+#include "util_api.hpp"
+
+namespace irccd::js {
+
+namespace {
+
+template <typename T>
+auto bind() noexcept -> api::constructor
+{
+	return [] () noexcept {
+		return std::make_unique<T>();
+	};
+}
+
+} // !namespace
+
+auto api::registry() noexcept -> const std::vector<constructor>&
+{
+	static const std::vector<constructor> list {
+		// Irccd API must be loaded first.
+		bind<irccd_api>(),
+		bind<directory_api>(),
+		bind<elapsed_timer_api>(),
+		bind<file_api>(),
+		bind<logger_api>(),
+		bind<plugin_api>(),
+		bind<server_api>(),
+		bind<system_api>(),
+		bind<timer_api>(),
+		bind<unicode_api>(),
+		bind<util_api>()
+	};
+
+	return list;
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,92 @@
+/*
+ * api.hpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_API_HPP
+#define IRCCD_JS_API_HPP
+
+/**
+ * \file api.hpp
+ * \brief Javascript API module.
+ */
+
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "duk.hpp"
+
+namespace irccd {
+
+namespace daemon {
+
+class bot;
+
+} // !daemon
+
+namespace js {
+
+class plugin;
+
+/**
+ * \ingroup js-api
+ * \brief Javascript API module.
+ */
+class api {
+public:
+	/**
+	 * \brief Command constructor factory.
+	 */
+	using constructor = std::function<std::unique_ptr<api> ()>;
+
+	/**
+	 * \brief Registry of all commands.
+	 */
+	static auto registry() noexcept -> const std::vector<constructor>&;
+
+	/**
+	 * Default constructor.
+	 */
+	api() noexcept = default;
+
+	/**
+	 * Virtual destructor defaulted.
+	 */
+	virtual ~api() noexcept = default;
+
+	/**
+	 * Get the module name.
+	 *
+	 * \return the name
+	 */
+	virtual auto get_name() const noexcept -> std::string_view = 0;
+
+	/**
+	 * Load the module into the Javascript plugin.
+	 *
+	 * \param bot the irccd instance
+	 * \param plugin the plugin
+	 */
+	virtual void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) = 0;
+};
+
+} // !js
+
+} // !irccd
+
+#endif // !IRCCD_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,406 @@
+/*
+ * directory_api.cpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2018 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 <boost/predef/os.h>
+
+#include <irccd/sysconfig.hpp>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <fstream>
+#include <regex>
+#include <stdexcept>
+#include <string>
+
+#include <irccd/fs_util.hpp>
+
+#include "directory_api.hpp"
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+
+namespace fs = boost::filesystem;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ wrap
+
+/*
+ * Wrap the function and raise appropriate error in case of need.
+ */
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+	try {
+		return handler();
+	} catch (const boost::system::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+// {{{ path
+
+/*
+ * Get the path associated with the directory object as this.
+ */
+auto path(duk_context* ctx) -> std::string
+{
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, "path");
+
+	if (duk_get_type(ctx, -1) != DUK_TYPE_STRING)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Directory object");
+
+	const auto ret = duk::get<std::string>(ctx, -1);
+
+	if (ret.empty())
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "directory object has empty path");
+
+	duk_pop_n(ctx, 2);
+
+	return ret;
+}
+
+// }}}
+
+// {{{ find
+
+/*
+ * Generic find function for:
+ *
+ * - Irccd.Directory.find
+ * - Irccd.Directory.prototype.find
+ *
+ * The pattern_index is the argument where to test if the argument is a regex or
+ * a string.
+ */
+auto find(duk_context* ctx, std::string base, bool recursive, int pattern_index) -> duk_ret_t
+{
+	/*
+	 * Helper for checking if it's a valid RegExp object.
+	 */
+	const auto is_regex = [&] {
+		duk_get_global_string(ctx, "RegExp");
+		const auto result = duk_instanceof(ctx, pattern_index, -1);
+		duk_pop(ctx);
+
+		return result;
+	};
+
+	/*
+	 * Helper for getting regex source.
+	 */
+	const auto pattern = [&] {
+		duk_get_prop_string(ctx, pattern_index, "source");
+		const auto pattern = duk_to_string(ctx, -1);
+		duk_pop(ctx);
+
+		return pattern;
+	};
+
+	std::string path;
+
+	if (duk_is_string(ctx, pattern_index))
+		path = fs_util::find(base, duk::get<std::string>(ctx, pattern_index), recursive);
+	else if (is_regex())
+		path = fs_util::find(base, pattern(), recursive);
+	else
+		throw duk::type_error("pattern must be a string or a regex expression");
+
+	if (path.empty())
+		return 0;
+
+	return duk::push(ctx, path);
+}
+
+// }}}
+
+// {{{ remove
+
+/*
+ * Generic remove function for:
+ *
+ * - Irccd.Directory.remove
+ * - Irccd.Directory.prototype.remove
+ */
+auto remove(const std::string& path, bool recursive) -> duk_ret_t
+{
+	if (!boost::filesystem::is_directory(path))
+		throw std::system_error(make_error_code(std::errc::invalid_argument));
+
+	if (!recursive)
+		boost::filesystem::remove(path);
+	else
+		boost::filesystem::remove_all(path);
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Directory.prototype.find
+
+/*
+ * Method: Irccd.Directory.prototype.find(pattern, recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of Irccd.Directory.find(path, pattern, recursive) but the path is
+ * taken from the directory object.
+ *
+ * Arguments:
+ *   - pattern, the regular expression or file name,
+ *   - recursive, set to true to search recursively (default: false).
+ * Returns:
+ *   The path to the file or undefined if not found.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_prototype_find(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return find(ctx, path(ctx), duk::get<bool>(ctx, 1), 0);
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Directory.prototype.remove
+
+/*
+ * Method: Irccd.Directory.prototype.remove(recursive)
+ * --------------------------------------------------------
+ *
+ * Synonym of Directory.remove(recursive) but the path is taken from the
+ * directory object.
+ *
+ * Arguments:
+ *   - recursive, recursively or not (default: false).
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_prototype_remove(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return remove(path(ctx), duk::get<bool>(ctx, 0));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Directory [constructor]
+
+/*
+ * Function: Irccd.Directory(path) [constructor]
+ * --------------------------------------------------------
+ *
+ * Opens and read the directory at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_constructor(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		if (!duk_is_constructor_call(ctx))
+			return 0;
+
+		const auto path = duk::require<std::string>(ctx, 0);
+
+		if (!boost::filesystem::is_directory(path))
+			throw std::system_error(make_error_code(std::errc::invalid_argument));
+
+		duk_push_this(ctx);
+
+		// 'entries' property.
+		duk_push_string(ctx, "entries");
+		duk_push_array(ctx);
+
+		unsigned i = 0;
+		for (const auto& entry : boost::filesystem::directory_iterator(path)) {
+			duk_push_object(ctx);
+			duk::push(ctx, entry.path().filename().string());
+			duk_put_prop_string(ctx, -2, "name");
+			duk_push_int(ctx, entry.status().type());
+			duk_put_prop_string(ctx, -2, "type");
+			duk_put_prop_index(ctx, -2, i++);
+		}
+
+		duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+
+		// 'path' property.
+		duk::push(ctx, "path");
+		duk::push(ctx, path);
+		duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Directory.find
+
+/*
+ * Function: Irccd.Directory.find(path, pattern, recursive)
+ * --------------------------------------------------------
+ *
+ * Find an entry by a pattern or a regular expression.
+ *
+ * Arguments:
+ *   - path, the base path,
+ *   - pattern, the regular expression or file name,
+ *   - recursive, set to true to search recursively (default: false).
+ * Returns:
+ *   The path to the file or undefined on errors or not found.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_find(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return find(ctx, duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 2), 1);
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Directory.remove
+
+/*
+ * Function: Irccd.Directory.remove(path, recursive)
+ * --------------------------------------------------------
+ *
+ * Remove the directory optionally recursively.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ *   - recursive, recursively or not (default: false).
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_remove(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return remove(duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 1));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Directory.mkdir
+
+/*
+ * Function: Irccd.Directory.mkdir(path, mode = 0700)
+ * --------------------------------------------------------
+ *
+ * Create a directory specified by path. It will create needed subdirectories
+ * just like you have invoked mkdir -p.
+ *
+ * Arguments:
+ *   - path, the path to the directory,
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Directory_mkdir(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		boost::filesystem::create_directories(duk::require<std::string>(ctx, 0));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+	{ "find",               Directory_prototype_find,       DUK_VARARGS     },
+	{ "remove",             Directory_prototype_remove,     1               },
+	{ nullptr,              nullptr,                        0               }
+};
+
+const duk_function_list_entry functions[] = {
+	{ "find",               Directory_find,                 DUK_VARARGS     },
+	{ "mkdir",              Directory_mkdir,                DUK_VARARGS     },
+	{ "remove",             Directory_remove,               DUK_VARARGS     },
+	{ nullptr,              nullptr,                        0               }
+};
+
+const duk_number_list_entry constants[] = {
+	{ "TypeFile",           static_cast<int>(fs::regular_file)              },
+	{ "TypeDir",            static_cast<int>(fs::directory_file)            },
+	{ "TypeLink",           static_cast<int>(fs::symlink_file)              },
+	{ "TypeBlock",          static_cast<int>(fs::block_file)                },
+	{ "TypeCharacter",      static_cast<int>(fs::character_file)            },
+	{ "TypeFifo",           static_cast<int>(fs::fifo_file)	                },
+	{ "TypeSocket",         static_cast<int>(fs::socket_file)               },
+	{ "TypeUnknown",	static_cast<int>(fs::type_unknown)              },
+	{ nullptr,              0                                               }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ directory_api
+
+auto directory_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Directory";
+}
+
+void directory_api::load(daemon::bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_c_function(plugin->get_context(), Directory_constructor, 2);
+	duk_put_number_list(plugin->get_context(), -1, constants);
+	duk_put_function_list(plugin->get_context(), -1, functions);
+
+#if BOOST_OS_WINDOWS
+	duk_push_string(plugin->get_context(), "\\");
+#else
+	duk_push_string(plugin->get_context(), "/");
+#endif
+
+	duk_put_prop_string(plugin->get_context(), -2, "separator");
+
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, methods);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "Directory");
+	duk_pop(plugin->get_context());
+}
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/directory_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * directory_api.hpp -- Irccd.Directory API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_DIRECTORY_API_HPP
+#define IRCCD_JS_DIRECTORY_API_HPP
+
+/**
+ * \file directory_api.hpp
+ * \brief Irccd.Directory Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Irccd.Directory Javascript API.
+ * \ingroup js-api
+ */
+class directory_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_DIRECTORY_API_HPP
--- a/libirccd-js/irccd/js/directory_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,406 +0,0 @@
-/*
- * directory_js_api.cpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2018 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 <boost/predef/os.h>
-
-#include <irccd/sysconfig.hpp>
-
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <fstream>
-#include <regex>
-#include <stdexcept>
-#include <string>
-
-#include <irccd/fs_util.hpp>
-
-#include "directory_js_api.hpp"
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-
-namespace fs = boost::filesystem;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ wrap
-
-/*
- * Wrap the function and raise appropriate error in case of need.
- */
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
-{
-	try {
-		return handler();
-	} catch (const boost::system::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-// {{{ path
-
-/*
- * Get the path associated with the directory object as this.
- */
-auto path(duk_context* ctx) -> std::string
-{
-	duk_push_this(ctx);
-	duk_get_prop_string(ctx, -1, "path");
-
-	if (duk_get_type(ctx, -1) != DUK_TYPE_STRING)
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Directory object");
-
-	const auto ret = duk::get<std::string>(ctx, -1);
-
-	if (ret.empty())
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "directory object has empty path");
-
-	duk_pop_n(ctx, 2);
-
-	return ret;
-}
-
-// }}}
-
-// {{{ find
-
-/*
- * Generic find function for:
- *
- * - Irccd.Directory.find
- * - Irccd.Directory.prototype.find
- *
- * The pattern_index is the argument where to test if the argument is a regex or
- * a string.
- */
-auto find(duk_context* ctx, std::string base, bool recursive, int pattern_index) -> duk_ret_t
-{
-	/*
-	 * Helper for checking if it's a valid RegExp object.
-	 */
-	const auto is_regex = [&] {
-		duk_get_global_string(ctx, "RegExp");
-		const auto result = duk_instanceof(ctx, pattern_index, -1);
-		duk_pop(ctx);
-
-		return result;
-	};
-
-	/*
-	 * Helper for getting regex source.
-	 */
-	const auto pattern = [&] {
-		duk_get_prop_string(ctx, pattern_index, "source");
-		const auto pattern = duk_to_string(ctx, -1);
-		duk_pop(ctx);
-
-		return pattern;
-	};
-
-	std::string path;
-
-	if (duk_is_string(ctx, pattern_index))
-		path = fs_util::find(base, duk::get<std::string>(ctx, pattern_index), recursive);
-	else if (is_regex())
-		path = fs_util::find(base, pattern(), recursive);
-	else
-		throw duk::type_error("pattern must be a string or a regex expression");
-
-	if (path.empty())
-		return 0;
-
-	return duk::push(ctx, path);
-}
-
-// }}}
-
-// {{{ remove
-
-/*
- * Generic remove function for:
- *
- * - Irccd.Directory.remove
- * - Irccd.Directory.prototype.remove
- */
-auto remove(const std::string& path, bool recursive) -> duk_ret_t
-{
-	if (!boost::filesystem::is_directory(path))
-		throw std::system_error(make_error_code(std::errc::invalid_argument));
-
-	if (!recursive)
-		boost::filesystem::remove(path);
-	else
-		boost::filesystem::remove_all(path);
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Directory.prototype.find
-
-/*
- * Method: Irccd.Directory.prototype.find(pattern, recursive)
- * --------------------------------------------------------
- *
- * Synonym of Irccd.Directory.find(path, pattern, recursive) but the path is
- * taken from the directory object.
- *
- * Arguments:
- *   - pattern, the regular expression or file name,
- *   - recursive, set to true to search recursively (default: false).
- * Returns:
- *   The path to the file or undefined if not found.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_prototype_find(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return find(ctx, path(ctx), duk::get<bool>(ctx, 1), 0);
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Directory.prototype.remove
-
-/*
- * Method: Irccd.Directory.prototype.remove(recursive)
- * --------------------------------------------------------
- *
- * Synonym of Directory.remove(recursive) but the path is taken from the
- * directory object.
- *
- * Arguments:
- *   - recursive, recursively or not (default: false).
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_prototype_remove(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return remove(path(ctx), duk::get<bool>(ctx, 0));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Directory [constructor]
-
-/*
- * Function: Irccd.Directory(path) [constructor]
- * --------------------------------------------------------
- *
- * Opens and read the directory at the specified path.
- *
- * Arguments:
- *   - path, the path to the directory,
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_constructor(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		if (!duk_is_constructor_call(ctx))
-			return 0;
-
-		const auto path = duk::require<std::string>(ctx, 0);
-
-		if (!boost::filesystem::is_directory(path))
-			throw std::system_error(make_error_code(std::errc::invalid_argument));
-
-		duk_push_this(ctx);
-
-		// 'entries' property.
-		duk_push_string(ctx, "entries");
-		duk_push_array(ctx);
-
-		unsigned i = 0;
-		for (const auto& entry : boost::filesystem::directory_iterator(path)) {
-			duk_push_object(ctx);
-			duk::push(ctx, entry.path().filename().string());
-			duk_put_prop_string(ctx, -2, "name");
-			duk_push_int(ctx, entry.status().type());
-			duk_put_prop_string(ctx, -2, "type");
-			duk_put_prop_index(ctx, -2, i++);
-		}
-
-		duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-
-		// 'path' property.
-		duk::push(ctx, "path");
-		duk::push(ctx, path);
-		duk_def_prop(ctx, -3, DUK_DEFPROP_ENUMERABLE | DUK_DEFPROP_HAVE_VALUE);
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Directory.find
-
-/*
- * Function: Irccd.Directory.find(path, pattern, recursive)
- * --------------------------------------------------------
- *
- * Find an entry by a pattern or a regular expression.
- *
- * Arguments:
- *   - path, the base path,
- *   - pattern, the regular expression or file name,
- *   - recursive, set to true to search recursively (default: false).
- * Returns:
- *   The path to the file or undefined on errors or not found.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_find(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return find(ctx, duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 2), 1);
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Directory.remove
-
-/*
- * Function: Irccd.Directory.remove(path, recursive)
- * --------------------------------------------------------
- *
- * Remove the directory optionally recursively.
- *
- * Arguments:
- *   - path, the path to the directory,
- *   - recursive, recursively or not (default: false).
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_remove(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return remove(duk::require<std::string>(ctx, 0), duk::get<bool>(ctx, 1));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Directory.mkdir
-
-/*
- * Function: Irccd.Directory.mkdir(path, mode = 0700)
- * --------------------------------------------------------
- *
- * Create a directory specified by path. It will create needed subdirectories
- * just like you have invoked mkdir -p.
- *
- * Arguments:
- *   - path, the path to the directory,
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Directory_mkdir(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		boost::filesystem::create_directories(duk::require<std::string>(ctx, 0));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ definitions
-
-const duk_function_list_entry methods[] = {
-	{ "find",               Directory_prototype_find,       DUK_VARARGS     },
-	{ "remove",             Directory_prototype_remove,     1               },
-	{ nullptr,              nullptr,                        0               }
-};
-
-const duk_function_list_entry functions[] = {
-	{ "find",               Directory_find,                 DUK_VARARGS     },
-	{ "mkdir",              Directory_mkdir,                DUK_VARARGS     },
-	{ "remove",             Directory_remove,               DUK_VARARGS     },
-	{ nullptr,              nullptr,                        0               }
-};
-
-const duk_number_list_entry constants[] = {
-	{ "TypeFile",           static_cast<int>(fs::regular_file)              },
-	{ "TypeDir",            static_cast<int>(fs::directory_file)            },
-	{ "TypeLink",           static_cast<int>(fs::symlink_file)              },
-	{ "TypeBlock",          static_cast<int>(fs::block_file)                },
-	{ "TypeCharacter",      static_cast<int>(fs::character_file)            },
-	{ "TypeFifo",           static_cast<int>(fs::fifo_file)	                },
-	{ "TypeSocket",         static_cast<int>(fs::socket_file)               },
-	{ "TypeUnknown",	static_cast<int>(fs::type_unknown)              },
-	{ nullptr,              0                                               }
-};
-
-// }}}
-
-} // !namespace
-
-// {{{ directory_js_api
-
-auto directory_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Directory";
-}
-
-void directory_js_api::load(daemon::bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_c_function(plugin->get_context(), Directory_constructor, 2);
-	duk_put_number_list(plugin->get_context(), -1, constants);
-	duk_put_function_list(plugin->get_context(), -1, functions);
-
-#if BOOST_OS_WINDOWS
-	duk_push_string(plugin->get_context(), "\\");
-#else
-	duk_push_string(plugin->get_context(), "/");
-#endif
-
-	duk_put_prop_string(plugin->get_context(), -2, "separator");
-
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, methods);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "Directory");
-	duk_pop(plugin->get_context());
-}
-
-// }}}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/directory_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * directory_js_api.hpp -- Irccd.Directory API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_DIRECTORY_JS_API_HPP
-#define IRCCD_JS_DIRECTORY_JS_API_HPP
-
-/**
- * \file directory_js_api.hpp
- * \brief Irccd.Directory Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \brief Irccd.Directory Javascript API.
- * \ingroup js-api
- */
-class directory_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_DIRECTORY_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,182 @@
+/*
+ * elapsed_timer_api.cpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2018 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 <boost/timer/timer.hpp>
+
+#include "elapsed_timer_api.hpp"
+#include "plugin.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.ElapsedTimer"));
+
+// {{{ self
+
+auto self(duk_context* ctx) -> boost::timer::cpu_timer*
+{
+	duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	const auto ptr = static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an ElapsedTimer object");
+
+	return ptr;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.pause
+
+/*
+ * Method: ElapsedTimer.prototype.pause
+ * ------------------------------------------------------------------
+ *
+ * Pause the timer, without resetting the current elapsed time stored.
+ */
+auto ElapsedTimer_prototype_pause(duk_context* ctx) -> duk_ret_t
+{
+	self(ctx)->stop();
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.restart
+
+/*
+ * Method: Irccd.ElapsedTimer.prototype.restart
+ * ------------------------------------------------------------------
+ *
+ * Restart the timer without resetting the current elapsed time.
+ */
+auto ElapsedTimer_prototype_restart(duk_context* ctx) -> duk_ret_t
+{
+	self(ctx)->resume();
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer.prototype.elapsed
+
+/*
+ * Method: ElapsedTimer.prototype.elapsed
+ * ------------------------------------------------------------------
+ *
+ * Get the number of elapsed milliseconds.
+ *
+ * Returns:
+ *   The time elapsed.
+ */
+auto ElapsedTimer_prototype_elapsed(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
+
+	return 1;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer [constructor]
+
+/*
+ * Function: Irccd.ElapsedTimer [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new ElapsedTimer object.
+ */
+auto ElapsedTimer_constructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_this(ctx);
+	duk_push_pointer(ctx, new boost::timer::cpu_timer);
+	duk_put_prop_string(ctx, -2, signature.data());
+	duk_pop(ctx);
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ElapsedTimer [destructor]
+
+/*
+ * Function: Irccd.ElapsedTimer [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto ElapsedTimer_destructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_get_prop_string(ctx, 0, signature.data());
+	delete static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, signature.data());
+
+	return 0;
+}
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+	{ "elapsed",    ElapsedTimer_prototype_elapsed, 0 },
+	{ "pause",      ElapsedTimer_prototype_pause,   0 },
+	{ "restart",    ElapsedTimer_prototype_restart, 0 },
+	{ nullptr,      nullptr,                        0 }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ elapsed_timer_api
+
+auto elapsed_timer_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.ElapsedTimer";
+}
+
+void elapsed_timer_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_c_function(plugin->get_context(), ElapsedTimer_constructor, 0);
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, methods);
+	duk_push_c_function(plugin->get_context(), ElapsedTimer_destructor, 1);
+	duk_set_finalizer(plugin->get_context(), -2);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "ElapsedTimer");
+	duk_pop(plugin->get_context());
+}
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/elapsed_timer_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * elapsed_timer_api.hpp -- Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_ELAPSED_TIMER_API_HPP
+#define IRCCD_JS_ELAPSED_TIMER_API_HPP
+
+/**
+ * \file elapsed_timer_api.hpp
+ * \brief Irccd.ElapsedTimer Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.ElapsedTimer Javascript API.
+ */
+class elapsed_timer_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_ELAPSED_TIMER_API_HPP
--- a/libirccd-js/irccd/js/elapsed_timer_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,182 +0,0 @@
-/*
- * elapsed_timer_js_api.cpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2018 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 <boost/timer/timer.hpp>
-
-#include "elapsed_timer_js_api.hpp"
-#include "js_plugin.hpp"
-
-using irccd::daemon::bot;
-
-namespace irccd::js {
-
-namespace {
-
-const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.ElapsedTimer"));
-
-// {{{ self
-
-auto self(duk_context* ctx) -> boost::timer::cpu_timer*
-{
-	duk::stack_guard sa(ctx);
-
-	duk_push_this(ctx);
-	duk_get_prop_string(ctx, -1, signature.data());
-	const auto ptr = static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
-	duk_pop_2(ctx);
-
-	if (!ptr)
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not an ElapsedTimer object");
-
-	return ptr;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.pause
-
-/*
- * Method: ElapsedTimer.prototype.pause
- * ------------------------------------------------------------------
- *
- * Pause the timer, without resetting the current elapsed time stored.
- */
-auto ElapsedTimer_prototype_pause(duk_context* ctx) -> duk_ret_t
-{
-	self(ctx)->stop();
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.restart
-
-/*
- * Method: Irccd.ElapsedTimer.prototype.restart
- * ------------------------------------------------------------------
- *
- * Restart the timer without resetting the current elapsed time.
- */
-auto ElapsedTimer_prototype_restart(duk_context* ctx) -> duk_ret_t
-{
-	self(ctx)->resume();
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer.prototype.elapsed
-
-/*
- * Method: ElapsedTimer.prototype.elapsed
- * ------------------------------------------------------------------
- *
- * Get the number of elapsed milliseconds.
- *
- * Returns:
- *   The time elapsed.
- */
-auto ElapsedTimer_prototype_elapsed(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_uint(ctx, self(ctx)->elapsed().wall / 1000000LL);
-
-	return 1;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer [constructor]
-
-/*
- * Function: Irccd.ElapsedTimer [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new ElapsedTimer object.
- */
-auto ElapsedTimer_constructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_this(ctx);
-	duk_push_pointer(ctx, new boost::timer::cpu_timer);
-	duk_put_prop_string(ctx, -2, signature.data());
-	duk_pop(ctx);
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ElapsedTimer [destructor]
-
-/*
- * Function: Irccd.ElapsedTimer [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-auto ElapsedTimer_destructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_get_prop_string(ctx, 0, signature.data());
-	delete static_cast<boost::timer::cpu_timer*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-	duk_del_prop_string(ctx, 0, signature.data());
-
-	return 0;
-}
-
-// }}}
-
-// {{{ definitions
-
-const duk_function_list_entry methods[] = {
-	{ "elapsed",    ElapsedTimer_prototype_elapsed, 0 },
-	{ "pause",      ElapsedTimer_prototype_pause,   0 },
-	{ "restart",    ElapsedTimer_prototype_restart, 0 },
-	{ nullptr,      nullptr,                        0 }
-};
-
-// }}}
-
-} // !namespace
-
-// {{{ elapsed_timer_js_api
-
-auto elapsed_timer_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.ElapsedTimer";
-}
-
-void elapsed_timer_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_c_function(plugin->get_context(), ElapsedTimer_constructor, 0);
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, methods);
-	duk_push_c_function(plugin->get_context(), ElapsedTimer_destructor, 1);
-	duk_set_finalizer(plugin->get_context(), -2);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "ElapsedTimer");
-	duk_pop(plugin->get_context());
-}
-
-// }}}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/elapsed_timer_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * elapsed_timer_js_api.hpp -- Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
-#define IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
-
-/**
- * \file elapsed_timer_js_api.hpp
- * \brief Irccd.ElapsedTimer Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.ElapsedTimer Javascript API.
- */
-class elapsed_timer_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_ELAPSED_TIMER_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/file_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,807 @@
+/*
+ * file_api.cpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2018 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 <algorithm>
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <vector>
+
+#include <boost/filesystem.hpp>
+
+#include <irccd/fs_util.hpp>
+
+#include "file_api.hpp"
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.File"));
+const std::string_view prototype(DUK_HIDDEN_SYMBOL("Irccd.File.prototype"));
+
+// {{{ clear_crlf
+
+auto clear_crlf(std::string input) noexcept -> std::string
+{
+	if (input.length() > 0 && input.back() == '\r')
+		input.pop_back();
+
+	return input;
+}
+
+// }}}
+
+// {{{ from_errno
+
+auto from_errno() noexcept -> std::system_error
+{
+	return std::system_error(make_error_code(static_cast<std::errc>(errno)));
+}
+
+// }}}
+
+// {{{ self
+
+auto self(duk_context* ctx) -> std::shared_ptr<file>
+{
+	duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	auto ptr = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
+
+	return *ptr;
+}
+
+// }}}
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+	try {
+		return handler();
+	} catch (const boost::system::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.basename
+
+/*
+ * Method: Irccd.File.prototype.basename()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.basename(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The base name.
+ */
+auto File_prototype_basename(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, fs_util::base_name(self(ctx)->get_path()));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.close
+
+/*
+ * Method: Irccd.File.prototype.close()
+ * --------------------------------------------------------
+ *
+ * Force close of the file, automatically called when object is collected.
+ */
+auto File_prototype_close(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		self(ctx)->close();
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.dirname
+
+/*
+ * Method: Irccd.File.prototype.dirname()
+ * --------------------------------------------------------
+ *
+ * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
+ *
+ * Returns:
+ *   The directory name.
+ */
+auto File_prototype_dirname(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, fs_util::dir_name(self(ctx)->get_path()));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.lines
+
+/*
+ * Method: Irccd.File.prototype.lines()
+ * --------------------------------------------------------
+ *
+ * Read all lines and return an array.
+ *
+ * Returns:
+ *   An array with all lines.
+ * Throws
+ *   - Any exception on error.
+ */
+auto File_prototype_lines(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		duk_push_array(ctx);
+
+		std::FILE* fp = self(ctx)->get_handle();
+		std::string buffer;
+		std::array<char, 128> data;
+		std::int32_t i = 0;
+
+		while (std::fgets(&data[0], data.size(), fp) != nullptr) {
+			buffer += data.data();
+
+			const auto pos = buffer.find('\n');
+
+			if (pos != std::string::npos) {
+				duk::push(ctx, clear_crlf(buffer.substr(0, pos)));
+				duk_put_prop_index(ctx, -2, i++);
+
+				buffer.erase(0, pos + 1);
+			}
+		}
+
+		// Maybe an error in the stream.
+		if (std::ferror(fp))
+			throw from_errno();
+
+		// Missing '\n' in end of file.
+		if (!buffer.empty()) {
+			duk::push(ctx, clear_crlf(buffer));
+			duk_put_prop_index(ctx, -2, i++);
+		}
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.read
+
+/*
+ * Method: Irccd.File.prototype.read(amount)
+ * --------------------------------------------------------
+ *
+ * Read the specified amount of characters or the whole file.
+ *
+ * Arguments:
+ *   - amount, the amount of characters or -1 to read all (Optional, default: -1).
+ * Returns:
+ *   The string.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_read(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		const auto fp = self(ctx)->get_handle();
+		const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
+
+		if (amount == 0 || !fp)
+			return 0;
+
+		std::string data;
+		std::size_t total = 0;
+
+		if (amount < 0) {
+			std::array<char, 128> buffer;
+			std::size_t nread;
+
+			while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) {
+				if (std::ferror(fp))
+					throw from_errno();
+
+				std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
+				total += nread;
+			}
+		} else {
+			data.resize(static_cast<std::size_t>(amount));
+			total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp);
+
+			if (std::ferror(fp))
+				throw from_errno();
+
+			data.resize(total);
+		}
+
+		return duk::push(ctx, data);
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.readline
+
+/*
+ * Method: Irccd.File.prototype.readline()
+ * --------------------------------------------------------
+ *
+ * Read the next line available.
+ *
+ * Returns:
+ *   The next line or undefined if eof.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_readline(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto fp = self(ctx)->get_handle();
+
+		if (fp == nullptr || std::feof(fp))
+			return 0;
+
+		std::string result;
+
+		for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
+			result += (char)ch;
+		if (std::ferror(fp))
+			throw from_errno();
+
+		return duk::push(ctx, clear_crlf(result));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.remove
+
+/*
+ * Method: Irccd.File.prototype.remove()
+ * --------------------------------------------------------
+ *
+ * Synonym of Irccd.File.prototype.remove(path) but with the path from the file.
+ *
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_remove(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		boost::filesystem::remove(self(ctx)->get_path());
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.seek
+
+/*
+ * Method: Irccd.File.prototype.seek(type, amount)
+ * --------------------------------------------------------
+ *
+ * Sets the position in the file.
+ *
+ * Arguments:
+ *   - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet),
+ *   - amount, the new offset.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_seek(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto fp = self(ctx)->get_handle();
+		auto type = duk_require_int(ctx, 0);
+		auto amount = duk_require_int(ctx, 1);
+
+		if (fp != nullptr && std::fseek(fp, amount, type) != 0)
+			throw from_errno();
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.stat
+
+#if defined(IRCCD_HAVE_STAT)
+
+/*
+ * Method: Irccd.File.prototype.stat() [optional]
+ * --------------------------------------------------------
+ *
+ * Synonym of File.stat(path) but with the path from the file.
+ *
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_stat(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto file = self(ctx);
+		struct stat st;
+
+		if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
+			throw from_errno();
+
+		duk::push(ctx, st);
+
+		return 1;
+	});
+}
+
+#endif // !IRCCD_HAVE_STAT
+
+// }}}
+
+// {{{ Irccd.File.prototype.tell
+
+/*
+ * Method: Irccd.File.prototype.tell()
+ * --------------------------------------------------------
+ *
+ * Get the actual position in the file.
+ *
+ * Returns:
+ *   The position.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_tell(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto fp = self(ctx)->get_handle();
+		long pos;
+
+		if (fp == nullptr)
+			return 0;
+
+		if ((pos = std::ftell(fp)) == -1L)
+			throw from_errno();
+
+		duk_push_int(ctx, pos);
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.prototype.write
+
+/*
+ * Method: Irccd.File.prototype.write(data)
+ * --------------------------------------------------------
+ *
+ * Write some characters to the file.
+ *
+ * Arguments:
+ *   - data, the character to write.
+ * Returns:
+ *   The number of bytes written.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_prototype_write(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto fp = self(ctx)->get_handle();
+		auto data = duk::require<std::string>(ctx, 0);
+
+		if (fp == nullptr)
+			return 0;
+
+		const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
+
+		if (std::ferror(fp))
+			throw from_errno();
+
+		duk_push_uint(ctx, nwritten);
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File [constructor]
+
+/*
+ * Function: Irccd.File(path, mode) [constructor]
+ * --------------------------------------------------------
+ *
+ * Open a file specified by path with the specified mode.
+ *
+ * Arguments:
+ *   - path, the path to the file,
+ *   - mode, the mode string.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_constructor(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		if (!duk_is_constructor_call(ctx))
+			return 0;
+
+		const auto path = duk::require<std::string>(ctx, 0);
+		const auto mode = duk::require<std::string>(ctx, 1);
+
+		duk_push_this(ctx);
+		duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode)));
+		duk_put_prop_string(ctx, -2, signature.data());
+		duk_pop(ctx);
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File [destructor]
+
+/*
+ * Function: Irccd.File() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto File_destructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_get_prop_string(ctx, 0, signature.data());
+	delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, signature.data());
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.File.basename
+
+/*
+ * Function: Irccd.File.basename(path)
+ * --------------------------------------------------------
+ *
+ * duk_ret_turn the file basename as specified in `basename(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The base name.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_basename(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.dirname
+
+/*
+ * Function: Irccd.File.dirname(path)
+ * --------------------------------------------------------
+ *
+ * duk_ret_turn the file directory name as specified in `dirname(3)` C function.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_dirname(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.exists
+
+/*
+ * Function: Irccd.File.exists(path)
+ * --------------------------------------------------------
+ *
+ * Check if the file exists.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   True if exists.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_exists(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.remove
+
+/*
+ * Function Irccd.File.remove(path)
+ * --------------------------------------------------------
+ *
+ * Remove the file at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_remove(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		boost::filesystem::remove(duk::require<std::string>(ctx, 0));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.File.stat
+
+#if defined(IRCCD_HAVE_STAT)
+
+/*
+ * Function Irccd.File.stat(path) [optional]
+ * --------------------------------------------------------
+ *
+ * Get file information at the specified path.
+ *
+ * Arguments:
+ *   - path, the path to the file.
+ * Returns:
+ *   The stat information.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto File_stat(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		struct stat st;
+
+		if (::stat(duk_require_string(ctx, 0), &st) < 0)
+			throw from_errno();
+
+		return duk::push(ctx, st);
+	});
+}
+
+#endif // !IRCCD_HAVE_STAT
+
+// }}}
+
+// {{{ definitions
+
+const duk_function_list_entry methods[] = {
+	{ "basename",   File_prototype_basename,        0 },
+	{ "close",      File_prototype_close,           0 },
+	{ "dirname",    File_prototype_dirname,         0 },
+	{ "lines",      File_prototype_lines,           0 },
+	{ "read",       File_prototype_read,            1 },
+	{ "readline",   File_prototype_readline,        0 },
+	{ "remove",     File_prototype_remove,          0 },
+	{ "seek",       File_prototype_seek,            2 },
+#if defined(IRCCD_HAVE_STAT)
+	{ "stat",       File_prototype_stat,            0 },
+#endif
+	{ "tell",       File_prototype_tell,            0 },
+	{ "write",      File_prototype_write,           1 },
+	{ nullptr,      nullptr,                        0 }
+};
+
+const duk_function_list_entry functions[] = {
+	{ "basename",   File_basename,                  1 },
+	{ "dirname",    File_dirname,                   1 },
+	{ "exists",     File_exists,                    1 },
+	{ "remove",     File_remove,                    1 },
+#if defined(IRCCD_HAVE_STAT)
+	{ "stat",       File_stat,                      1 },
+#endif
+	{ nullptr,      nullptr,                        0 }
+};
+
+const duk_number_list_entry constants[] = {
+	{ "SeekCur",    SEEK_CUR                          },
+	{ "SeekEnd",    SEEK_END                          },
+	{ "SeekSet",    SEEK_SET                          },
+	{ nullptr,      0                                 }
+};
+
+// }}}
+
+} // !namespace
+
+// {{{ file_api
+
+auto file_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.File";
+}
+
+void file_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_c_function(plugin->get_context(), File_constructor, 2);
+	duk_put_number_list(plugin->get_context(), -1, constants);
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, methods);
+	duk_push_c_function(plugin->get_context(), File_destructor, 1);
+	duk_set_finalizer(plugin->get_context(), -2);
+	duk_dup(plugin->get_context(), -1);
+	duk_put_global_string(plugin->get_context(), prototype.data());
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "File");
+	duk_pop(plugin->get_context());
+}
+
+// }}}
+
+// {{{ duk::type_traits<std::shared_ptr<file>>
+
+using file_traits = duk::type_traits<std::shared_ptr<file>>;
+
+void file_traits::push(duk_context* ctx, std::shared_ptr<file> fp)
+{
+	assert(ctx);
+	assert(fp);
+
+	duk::stack_guard sa(ctx, 1);
+
+	duk_push_object(ctx);
+	duk_push_pointer(ctx, new std::shared_ptr<file>(std::move(fp)));
+	duk_put_prop_string(ctx, -2, signature.data());
+	duk_get_global_string(ctx, prototype.data());
+	duk_set_prototype(ctx, -2);
+}
+
+auto file_traits::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>
+{
+	if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
+
+	duk_get_prop_string(ctx, index, signature.data());
+	const auto fp = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return *fp;
+}
+
+// }}}
+
+// {{{ duk::type_traits<struct stat>
+
+#if defined(IRCCD_HAVE_STAT)
+
+void duk::type_traits<struct stat>::push(duk_context* ctx, const struct stat& st)
+{
+	duk::stack_guard sa(ctx, 1);
+
+	duk_push_object(ctx);
+
+#if defined(IRCCD_HAVE_STAT_ST_ATIME)
+	duk_push_int(ctx, st.st_atime);
+	duk_put_prop_string(ctx, -2, "atime");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_BLKSIZE)
+	duk_push_int(ctx, st.st_blksize);
+	duk_put_prop_string(ctx, -2, "blksize");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_BLOCKS)
+	duk_push_int(ctx, st.st_blocks);
+	duk_put_prop_string(ctx, -2, "blocks");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_CTIME)
+	duk_push_int(ctx, st.st_ctime);
+	duk_put_prop_string(ctx, -2, "ctime");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_DEV)
+	duk_push_int(ctx, st.st_dev);
+	duk_put_prop_string(ctx, -2, "dev");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_GID)
+	duk_push_int(ctx, st.st_gid);
+	duk_put_prop_string(ctx, -2, "gid");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_INO)
+	duk_push_int(ctx, st.st_ino);
+	duk_put_prop_string(ctx, -2, "ino");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_MODE)
+	duk_push_int(ctx, st.st_mode);
+	duk_put_prop_string(ctx, -2, "mode");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_MTIME)
+	duk_push_int(ctx, st.st_mtime);
+	duk_put_prop_string(ctx, -2, "mtime");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_NLINK)
+	duk_push_int(ctx, st.st_nlink);
+	duk_put_prop_string(ctx, -2, "nlink");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_RDEV)
+	duk_push_int(ctx, st.st_rdev);
+	duk_put_prop_string(ctx, -2, "rdev");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_SIZE)
+	duk_push_int(ctx, st.st_size);
+	duk_put_prop_string(ctx, -2, "size");
+#endif
+#if defined(IRCCD_HAVE_STAT_ST_UID)
+	duk_push_int(ctx, st.st_uid);
+	duk_put_prop_string(ctx, -2, "uid");
+#endif
+}
+
+#endif // !IRCCD_HAVE_STAT
+
+// }}}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/file_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,211 @@
+/*
+ * file_api.hpp -- Irccd.File API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_FILE_API_HPP
+#define IRCCD_JS_FILE_API_HPP
+
+/**
+ * \file file_api.hpp
+ * \brief Irccd.File Javascript API.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(IRCCD_HAVE_STAT)
+#   include <sys/types.h>
+#   include <sys/stat.h>
+#endif
+
+#include <cassert>
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <functional>
+#include <stdexcept>
+#include <string>
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \brief Object for Javascript to perform I/O.
+ *
+ * This class can be constructed to Javascript.
+ *
+ * It is used in:
+ *
+ * - Irccd.File [constructor]
+ * - Irccd.System.popen (optional)
+ */
+class file {
+private:
+	file(const file&) = delete;
+	file& operator=(const file&) = delete;
+
+	file(file&&) = delete;
+	file& operator=(file&&) = delete;
+
+private:
+	std::string path_;
+	std::FILE* stream_;
+	std::function<void (std::FILE*)> destructor_;
+
+public:
+	/**
+	 * Construct a file specified by path
+	 *
+	 * \param path the path
+	 * \param mode the mode string (for std::fopen)
+	 * \throw std::runtime_error on failures
+	 */
+	inline file(std::string path, const std::string& mode)
+		: path_(std::move(path))
+		, destructor_([] (std::FILE* fp) { std::fclose(fp); })
+	{
+		if ((stream_ = std::fopen(path_.c_str(), mode.c_str())) == nullptr)
+			throw std::runtime_error(std::strerror(errno));
+	}
+
+	/**
+	 * Construct a file from a already created FILE pointer (e.g. popen).
+	 *
+	 * The class takes ownership of fp and will close it.
+	 *
+	 * \pre destructor must not be null
+	 * \param fp the file pointer
+	 * \param destructor the function to close fp (e.g. std::fclose)
+	 */
+	inline file(std::FILE* fp, std::function<void (std::FILE*)> destructor) noexcept
+		: stream_(fp)
+		, destructor_(std::move(destructor))
+	{
+		assert(destructor_ != nullptr);
+	}
+
+	/**
+	 * Closes the file.
+	 */
+	virtual ~file() noexcept
+	{
+		close();
+	}
+
+	/**
+	 * Get the path.
+	 *
+	 * \return the path
+	 * \warning empty when constructed from the FILE constructor
+	 */
+	inline const std::string& get_path() const noexcept
+	{
+		return path_;
+	}
+
+	/**
+	 * Get the handle.
+	 *
+	 * \return the handle or nullptr if the stream was closed
+	 */
+	inline std::FILE* get_handle() noexcept
+	{
+		return stream_;
+	}
+
+	/**
+	 * Force close, can be safely called multiple times.
+	 */
+	inline void close() noexcept
+	{
+		if (stream_) {
+			destructor_(stream_);
+			stream_ = nullptr;
+		}
+	}
+};
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.File Javascript API.
+ */
+class file_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialization for generic file type as shared_ptr.
+ *
+ * Supports push, require.
+ */
+template <>
+struct type_traits<std::shared_ptr<file>> {
+	/**
+	 * Push a file.
+	 *
+	 * \pre fp != nullptr
+	 * \param ctx the the context
+	 * \param fp the file
+	 */
+	static void push(duk_context* ctx, std::shared_ptr<file> fp);
+
+	/**
+	 * Require a file. Raises a JavaScript error if not a File.
+	 *
+	 * \param ctx the context
+	 * \param index the index
+	 * \return the file pointer
+	 */
+	static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>;
+};
+
+#if defined(IRCCD_HAVE_STAT)
+
+/**
+ * \brief Specialization for struct stat.
+ *
+ * Supports push.
+ */
+template <>
+struct type_traits<struct stat> {
+	/**
+	 * Push the stat information to the stack as Javascript object.
+	 *
+	 * \param ctx the context
+	 * \param st the stat structure
+	 */
+	static void push(duk_context* ctx, const struct stat& st);
+};
+
+#endif // !IRCCD_HAVE_STAT
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_FILE_API_HPP
--- a/libirccd-js/irccd/js/file_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,807 +0,0 @@
-/*
- * file_js_api.cpp -- Irccd.File API
- *
- * Copyright (c) 2013-2018 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 <algorithm>
-#include <array>
-#include <cassert>
-#include <iterator>
-#include <vector>
-
-#include <boost/filesystem.hpp>
-
-#include <irccd/fs_util.hpp>
-
-#include "file_js_api.hpp"
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-
-using irccd::daemon::bot;
-
-namespace irccd::js {
-
-namespace {
-
-const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.File"));
-const std::string_view prototype(DUK_HIDDEN_SYMBOL("Irccd.File.prototype"));
-
-// {{{ clear_crlf
-
-auto clear_crlf(std::string input) noexcept -> std::string
-{
-	if (input.length() > 0 && input.back() == '\r')
-		input.pop_back();
-
-	return input;
-}
-
-// }}}
-
-// {{{ from_errno
-
-auto from_errno() noexcept -> std::system_error
-{
-	return std::system_error(make_error_code(static_cast<std::errc>(errno)));
-}
-
-// }}}
-
-// {{{ self
-
-auto self(duk_context* ctx) -> std::shared_ptr<file>
-{
-	duk::stack_guard sa(ctx);
-
-	duk_push_this(ctx);
-	duk_get_prop_string(ctx, -1, signature.data());
-	auto ptr = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-	duk_pop_2(ctx);
-
-	if (!ptr)
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
-
-	return *ptr;
-}
-
-// }}}
-
-// {{{ wrap
-
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
-{
-	try {
-		return handler();
-	} catch (const boost::system::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.basename
-
-/*
- * Method: Irccd.File.prototype.basename()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.basename(path)` but with the path from the file.
- *
- * Returns:
- *   The base name.
- */
-auto File_prototype_basename(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, fs_util::base_name(self(ctx)->get_path()));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.close
-
-/*
- * Method: Irccd.File.prototype.close()
- * --------------------------------------------------------
- *
- * Force close of the file, automatically called when object is collected.
- */
-auto File_prototype_close(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		self(ctx)->close();
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.dirname
-
-/*
- * Method: Irccd.File.prototype.dirname()
- * --------------------------------------------------------
- *
- * Synonym of `Irccd.File.dirname(path)` but with the path from the file.
- *
- * Returns:
- *   The directory name.
- */
-auto File_prototype_dirname(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, fs_util::dir_name(self(ctx)->get_path()));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.lines
-
-/*
- * Method: Irccd.File.prototype.lines()
- * --------------------------------------------------------
- *
- * Read all lines and return an array.
- *
- * Returns:
- *   An array with all lines.
- * Throws
- *   - Any exception on error.
- */
-auto File_prototype_lines(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		duk_push_array(ctx);
-
-		std::FILE* fp = self(ctx)->get_handle();
-		std::string buffer;
-		std::array<char, 128> data;
-		std::int32_t i = 0;
-
-		while (std::fgets(&data[0], data.size(), fp) != nullptr) {
-			buffer += data.data();
-
-			const auto pos = buffer.find('\n');
-
-			if (pos != std::string::npos) {
-				duk::push(ctx, clear_crlf(buffer.substr(0, pos)));
-				duk_put_prop_index(ctx, -2, i++);
-
-				buffer.erase(0, pos + 1);
-			}
-		}
-
-		// Maybe an error in the stream.
-		if (std::ferror(fp))
-			throw from_errno();
-
-		// Missing '\n' in end of file.
-		if (!buffer.empty()) {
-			duk::push(ctx, clear_crlf(buffer));
-			duk_put_prop_index(ctx, -2, i++);
-		}
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.read
-
-/*
- * Method: Irccd.File.prototype.read(amount)
- * --------------------------------------------------------
- *
- * Read the specified amount of characters or the whole file.
- *
- * Arguments:
- *   - amount, the amount of characters or -1 to read all (Optional, default: -1).
- * Returns:
- *   The string.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_read(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		const auto fp = self(ctx)->get_handle();
-		const auto amount = duk_is_number(ctx, 0) ? duk_get_int(ctx, 0) : -1;
-
-		if (amount == 0 || !fp)
-			return 0;
-
-		std::string data;
-		std::size_t total = 0;
-
-		if (amount < 0) {
-			std::array<char, 128> buffer;
-			std::size_t nread;
-
-			while ((nread = std::fread(&buffer[0], sizeof (buffer[0]), buffer.size(), fp)) > 0) {
-				if (std::ferror(fp))
-					throw from_errno();
-
-				std::copy(buffer.begin(), buffer.begin() + nread, std::back_inserter(data));
-				total += nread;
-			}
-		} else {
-			data.resize(static_cast<std::size_t>(amount));
-			total = std::fread(&data[0], sizeof (data[0]), static_cast<std::size_t>(amount), fp);
-
-			if (std::ferror(fp))
-				throw from_errno();
-
-			data.resize(total);
-		}
-
-		return duk::push(ctx, data);
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.readline
-
-/*
- * Method: Irccd.File.prototype.readline()
- * --------------------------------------------------------
- *
- * Read the next line available.
- *
- * Returns:
- *   The next line or undefined if eof.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_readline(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto fp = self(ctx)->get_handle();
-
-		if (fp == nullptr || std::feof(fp))
-			return 0;
-
-		std::string result;
-
-		for (int ch; (ch = std::fgetc(fp)) != EOF && ch != '\n'; )
-			result += (char)ch;
-		if (std::ferror(fp))
-			throw from_errno();
-
-		return duk::push(ctx, clear_crlf(result));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.remove
-
-/*
- * Method: Irccd.File.prototype.remove()
- * --------------------------------------------------------
- *
- * Synonym of Irccd.File.prototype.remove(path) but with the path from the file.
- *
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_remove(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		boost::filesystem::remove(self(ctx)->get_path());
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.seek
-
-/*
- * Method: Irccd.File.prototype.seek(type, amount)
- * --------------------------------------------------------
- *
- * Sets the position in the file.
- *
- * Arguments:
- *   - type, the type of setting (File.SeekSet, File.SeekCur, File.SeekSet),
- *   - amount, the new offset.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_seek(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto fp = self(ctx)->get_handle();
-		auto type = duk_require_int(ctx, 0);
-		auto amount = duk_require_int(ctx, 1);
-
-		if (fp != nullptr && std::fseek(fp, amount, type) != 0)
-			throw from_errno();
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.stat
-
-#if defined(IRCCD_HAVE_STAT)
-
-/*
- * Method: Irccd.File.prototype.stat() [optional]
- * --------------------------------------------------------
- *
- * Synonym of File.stat(path) but with the path from the file.
- *
- * Returns:
- *   The stat information.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_stat(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto file = self(ctx);
-		struct stat st;
-
-		if (file->get_handle() == nullptr && ::stat(file->get_path().c_str(), &st) < 0)
-			throw from_errno();
-
-		duk::push(ctx, st);
-
-		return 1;
-	});
-}
-
-#endif // !IRCCD_HAVE_STAT
-
-// }}}
-
-// {{{ Irccd.File.prototype.tell
-
-/*
- * Method: Irccd.File.prototype.tell()
- * --------------------------------------------------------
- *
- * Get the actual position in the file.
- *
- * Returns:
- *   The position.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_tell(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto fp = self(ctx)->get_handle();
-		long pos;
-
-		if (fp == nullptr)
-			return 0;
-
-		if ((pos = std::ftell(fp)) == -1L)
-			throw from_errno();
-
-		duk_push_int(ctx, pos);
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.prototype.write
-
-/*
- * Method: Irccd.File.prototype.write(data)
- * --------------------------------------------------------
- *
- * Write some characters to the file.
- *
- * Arguments:
- *   - data, the character to write.
- * Returns:
- *   The number of bytes written.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_prototype_write(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto fp = self(ctx)->get_handle();
-		auto data = duk::require<std::string>(ctx, 0);
-
-		if (fp == nullptr)
-			return 0;
-
-		const auto nwritten = std::fwrite(data.c_str(), 1, data.length(), fp);
-
-		if (std::ferror(fp))
-			throw from_errno();
-
-		duk_push_uint(ctx, nwritten);
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File [constructor]
-
-/*
- * Function: Irccd.File(path, mode) [constructor]
- * --------------------------------------------------------
- *
- * Open a file specified by path with the specified mode.
- *
- * Arguments:
- *   - path, the path to the file,
- *   - mode, the mode string.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_constructor(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		if (!duk_is_constructor_call(ctx))
-			return 0;
-
-		const auto path = duk::require<std::string>(ctx, 0);
-		const auto mode = duk::require<std::string>(ctx, 1);
-
-		duk_push_this(ctx);
-		duk_push_pointer(ctx, new std::shared_ptr<file>(new file(path, mode)));
-		duk_put_prop_string(ctx, -2, signature.data());
-		duk_pop(ctx);
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File [destructor]
-
-/*
- * Function: Irccd.File() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-auto File_destructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_get_prop_string(ctx, 0, signature.data());
-	delete static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-	duk_del_prop_string(ctx, 0, signature.data());
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.File.basename
-
-/*
- * Function: Irccd.File.basename(path)
- * --------------------------------------------------------
- *
- * duk_ret_turn the file basename as specified in `basename(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The base name.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_basename(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, fs_util::base_name(duk_require_string(ctx, 0)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.dirname
-
-/*
- * Function: Irccd.File.dirname(path)
- * --------------------------------------------------------
- *
- * duk_ret_turn the file directory name as specified in `dirname(3)` C function.
- *
- * Arguments:
- *   - path, the path to the file.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_dirname(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, fs_util::dir_name(duk_require_string(ctx, 0)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.exists
-
-/*
- * Function: Irccd.File.exists(path)
- * --------------------------------------------------------
- *
- * Check if the file exists.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   True if exists.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_exists(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, boost::filesystem::exists(duk_require_string(ctx, 0)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.remove
-
-/*
- * Function Irccd.File.remove(path)
- * --------------------------------------------------------
- *
- * Remove the file at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_remove(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		boost::filesystem::remove(duk::require<std::string>(ctx, 0));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.File.stat
-
-#if defined(IRCCD_HAVE_STAT)
-
-/*
- * Function Irccd.File.stat(path) [optional]
- * --------------------------------------------------------
- *
- * Get file information at the specified path.
- *
- * Arguments:
- *   - path, the path to the file.
- * Returns:
- *   The stat information.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto File_stat(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		struct stat st;
-
-		if (::stat(duk_require_string(ctx, 0), &st) < 0)
-			throw from_errno();
-
-		return duk::push(ctx, st);
-	});
-}
-
-#endif // !IRCCD_HAVE_STAT
-
-// }}}
-
-// {{{ definitions
-
-const duk_function_list_entry methods[] = {
-	{ "basename",   File_prototype_basename,        0 },
-	{ "close",      File_prototype_close,           0 },
-	{ "dirname",    File_prototype_dirname,         0 },
-	{ "lines",      File_prototype_lines,           0 },
-	{ "read",       File_prototype_read,            1 },
-	{ "readline",   File_prototype_readline,        0 },
-	{ "remove",     File_prototype_remove,          0 },
-	{ "seek",       File_prototype_seek,            2 },
-#if defined(IRCCD_HAVE_STAT)
-	{ "stat",       File_prototype_stat,            0 },
-#endif
-	{ "tell",       File_prototype_tell,            0 },
-	{ "write",      File_prototype_write,           1 },
-	{ nullptr,      nullptr,                        0 }
-};
-
-const duk_function_list_entry functions[] = {
-	{ "basename",   File_basename,                  1 },
-	{ "dirname",    File_dirname,                   1 },
-	{ "exists",     File_exists,                    1 },
-	{ "remove",     File_remove,                    1 },
-#if defined(IRCCD_HAVE_STAT)
-	{ "stat",       File_stat,                      1 },
-#endif
-	{ nullptr,      nullptr,                        0 }
-};
-
-const duk_number_list_entry constants[] = {
-	{ "SeekCur",    SEEK_CUR                          },
-	{ "SeekEnd",    SEEK_END                          },
-	{ "SeekSet",    SEEK_SET                          },
-	{ nullptr,      0                                 }
-};
-
-// }}}
-
-} // !namespace
-
-// {{{ file_js_api
-
-auto file_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.File";
-}
-
-void file_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_c_function(plugin->get_context(), File_constructor, 2);
-	duk_put_number_list(plugin->get_context(), -1, constants);
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, methods);
-	duk_push_c_function(plugin->get_context(), File_destructor, 1);
-	duk_set_finalizer(plugin->get_context(), -2);
-	duk_dup(plugin->get_context(), -1);
-	duk_put_global_string(plugin->get_context(), prototype.data());
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "File");
-	duk_pop(plugin->get_context());
-}
-
-// }}}
-
-// {{{ duk::type_traits<std::shared_ptr<file>>
-
-using file_traits = duk::type_traits<std::shared_ptr<file>>;
-
-void file_traits::push(duk_context* ctx, std::shared_ptr<file> fp)
-{
-	assert(ctx);
-	assert(fp);
-
-	duk::stack_guard sa(ctx, 1);
-
-	duk_push_object(ctx);
-	duk_push_pointer(ctx, new std::shared_ptr<file>(std::move(fp)));
-	duk_put_prop_string(ctx, -2, signature.data());
-	duk_get_global_string(ctx, prototype.data());
-	duk_set_prototype(ctx, -2);
-}
-
-auto file_traits::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>
-{
-	if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a File object");
-
-	duk_get_prop_string(ctx, index, signature.data());
-	const auto fp = static_cast<std::shared_ptr<file>*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-
-	return *fp;
-}
-
-// }}}
-
-// {{{ duk::type_traits<struct stat>
-
-#if defined(IRCCD_HAVE_STAT)
-
-void duk::type_traits<struct stat>::push(duk_context* ctx, const struct stat& st)
-{
-	duk::stack_guard sa(ctx, 1);
-
-	duk_push_object(ctx);
-
-#if defined(IRCCD_HAVE_STAT_ST_ATIME)
-	duk_push_int(ctx, st.st_atime);
-	duk_put_prop_string(ctx, -2, "atime");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_BLKSIZE)
-	duk_push_int(ctx, st.st_blksize);
-	duk_put_prop_string(ctx, -2, "blksize");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_BLOCKS)
-	duk_push_int(ctx, st.st_blocks);
-	duk_put_prop_string(ctx, -2, "blocks");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_CTIME)
-	duk_push_int(ctx, st.st_ctime);
-	duk_put_prop_string(ctx, -2, "ctime");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_DEV)
-	duk_push_int(ctx, st.st_dev);
-	duk_put_prop_string(ctx, -2, "dev");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_GID)
-	duk_push_int(ctx, st.st_gid);
-	duk_put_prop_string(ctx, -2, "gid");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_INO)
-	duk_push_int(ctx, st.st_ino);
-	duk_put_prop_string(ctx, -2, "ino");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_MODE)
-	duk_push_int(ctx, st.st_mode);
-	duk_put_prop_string(ctx, -2, "mode");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_MTIME)
-	duk_push_int(ctx, st.st_mtime);
-	duk_put_prop_string(ctx, -2, "mtime");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_NLINK)
-	duk_push_int(ctx, st.st_nlink);
-	duk_put_prop_string(ctx, -2, "nlink");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_RDEV)
-	duk_push_int(ctx, st.st_rdev);
-	duk_put_prop_string(ctx, -2, "rdev");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_SIZE)
-	duk_push_int(ctx, st.st_size);
-	duk_put_prop_string(ctx, -2, "size");
-#endif
-#if defined(IRCCD_HAVE_STAT_ST_UID)
-	duk_push_int(ctx, st.st_uid);
-	duk_put_prop_string(ctx, -2, "uid");
-#endif
-}
-
-#endif // !IRCCD_HAVE_STAT
-
-// }}}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/file_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-/*
- * file_js_api.hpp -- Irccd.File API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_FILE_JS_API_HPP
-#define IRCCD_JS_FILE_JS_API_HPP
-
-/**
- * \file file_js_api.hpp
- * \brief Irccd.File Javascript API.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(IRCCD_HAVE_STAT)
-#   include <sys/types.h>
-#   include <sys/stat.h>
-#endif
-
-#include <cassert>
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-#include <functional>
-#include <stdexcept>
-#include <string>
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \brief Object for Javascript to perform I/O.
- *
- * This class can be constructed to Javascript.
- *
- * It is used in:
- *
- * - Irccd.File [constructor]
- * - Irccd.System.popen (optional)
- */
-class file {
-private:
-	file(const file&) = delete;
-	file& operator=(const file&) = delete;
-
-	file(file&&) = delete;
-	file& operator=(file&&) = delete;
-
-private:
-	std::string path_;
-	std::FILE* stream_;
-	std::function<void (std::FILE*)> destructor_;
-
-public:
-	/**
-	 * Construct a file specified by path
-	 *
-	 * \param path the path
-	 * \param mode the mode string (for std::fopen)
-	 * \throw std::runtime_error on failures
-	 */
-	inline file(std::string path, const std::string& mode)
-		: path_(std::move(path))
-		, destructor_([] (std::FILE* fp) { std::fclose(fp); })
-	{
-		if ((stream_ = std::fopen(path_.c_str(), mode.c_str())) == nullptr)
-			throw std::runtime_error(std::strerror(errno));
-	}
-
-	/**
-	 * Construct a file from a already created FILE pointer (e.g. popen).
-	 *
-	 * The class takes ownership of fp and will close it.
-	 *
-	 * \pre destructor must not be null
-	 * \param fp the file pointer
-	 * \param destructor the function to close fp (e.g. std::fclose)
-	 */
-	inline file(std::FILE* fp, std::function<void (std::FILE*)> destructor) noexcept
-		: stream_(fp)
-		, destructor_(std::move(destructor))
-	{
-		assert(destructor_ != nullptr);
-	}
-
-	/**
-	 * Closes the file.
-	 */
-	virtual ~file() noexcept
-	{
-		close();
-	}
-
-	/**
-	 * Get the path.
-	 *
-	 * \return the path
-	 * \warning empty when constructed from the FILE constructor
-	 */
-	inline const std::string& get_path() const noexcept
-	{
-		return path_;
-	}
-
-	/**
-	 * Get the handle.
-	 *
-	 * \return the handle or nullptr if the stream was closed
-	 */
-	inline std::FILE* get_handle() noexcept
-	{
-		return stream_;
-	}
-
-	/**
-	 * Force close, can be safely called multiple times.
-	 */
-	inline void close() noexcept
-	{
-		if (stream_) {
-			destructor_(stream_);
-			stream_ = nullptr;
-		}
-	}
-};
-
-/**
- * \ingroup js-api
- * \brief Irccd.File Javascript API.
- */
-class file_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-namespace duk {
-
-/**
- * \brief Specialization for generic file type as shared_ptr.
- *
- * Supports push, require.
- */
-template <>
-struct type_traits<std::shared_ptr<file>> {
-	/**
-	 * Push a file.
-	 *
-	 * \pre fp != nullptr
-	 * \param ctx the the context
-	 * \param fp the file
-	 */
-	static void push(duk_context* ctx, std::shared_ptr<file> fp);
-
-	/**
-	 * Require a file. Raises a JavaScript error if not a File.
-	 *
-	 * \param ctx the context
-	 * \param index the index
-	 * \return the file pointer
-	 */
-	static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<file>;
-};
-
-#if defined(IRCCD_HAVE_STAT)
-
-/**
- * \brief Specialization for struct stat.
- *
- * Supports push.
- */
-template <>
-struct type_traits<struct stat> {
-	/**
-	 * Push the stat information to the stack as Javascript object.
-	 *
-	 * \param ctx the context
-	 * \param st the stat structure
-	 */
-	static void push(duk_context* ctx, const struct stat& st);
-};
-
-#endif // !IRCCD_HAVE_STAT
-
-} // !duk
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_FILE_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,246 @@
+/*
+ * irccd_api.cpp -- Irccd API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
+
+#include <cerrno>
+#include <string>
+#include <unordered_map>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ do_raise
+
+template <typename Error>
+void do_raise(duk_context* ctx, const Error& ex)
+{
+	duk::stack_guard sa(ctx, 1);
+
+	duk_get_global_string(ctx, "Irccd");
+	duk_get_prop_string(ctx, -1, "SystemError");
+	duk_remove(ctx, -2);
+	duk::push(ctx, ex.code().value());
+	duk::push(ctx, ex.code().message());
+	duk_new(ctx, 2);
+
+	(void)duk_throw(ctx);
+}
+
+// }}}
+
+// {{{ Irccd.SystemError [constructor]
+
+auto SystemError_constructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_this(ctx);
+	duk_push_int(ctx, duk_require_int(ctx, 0));
+	duk_put_prop_string(ctx, -2, "errno");
+	duk_push_string(ctx, duk_require_string(ctx, 1));
+	duk_put_prop_string(ctx, -2, "message");
+	duk_push_string(ctx, "SystemError");
+	duk_put_prop_string(ctx, -2, "name");
+	duk_pop(ctx);
+
+	return 0;
+}
+
+// }}}
+
+// {{{ definitions
+
+const std::unordered_map<std::string, int> errors{
+	{ "E2BIG",              E2BIG           },
+	{ "EACCES",             EACCES          },
+	{ "EADDRINUSE",         EADDRINUSE      },
+	{ "EADDRNOTAVAIL",      EADDRNOTAVAIL   },
+	{ "EAFNOSUPPORT",       EAFNOSUPPORT    },
+	{ "EAGAIN",             EAGAIN          },
+	{ "EALREADY",           EALREADY        },
+	{ "EBADF",              EBADF           },
+#if defined(EBADMSG)
+	{ "EBADMSG",            EBADMSG         },
+#endif
+	{ "EBUSY",              EBUSY           },
+	{ "ECANCELED",          ECANCELED       },
+	{ "ECHILD",             ECHILD          },
+	{ "ECONNABORTED",       ECONNABORTED    },
+	{ "ECONNREFUSED",       ECONNREFUSED    },
+	{ "ECONNRESET",         ECONNRESET      },
+	{ "EDEADLK",            EDEADLK         },
+	{ "EDESTADDRREQ",       EDESTADDRREQ    },
+	{ "EDOM",               EDOM            },
+	{ "EEXIST",             EEXIST          },
+	{ "EFAULT",             EFAULT          },
+	{ "EFBIG",              EFBIG           },
+	{ "EHOSTUNREACH",       EHOSTUNREACH    },
+#if defined(EIDRM)
+	{ "EIDRM",              EIDRM           },
+#endif
+	{ "EILSEQ",             EILSEQ          },
+	{ "EINPROGRESS",        EINPROGRESS     },
+	{ "EINTR",              EINTR           },
+	{ "EINVAL",             EINVAL          },
+	{ "EIO",                EIO             },
+	{ "EISCONN",            EISCONN         },
+	{ "EISDIR",             EISDIR          },
+	{ "ELOOP",              ELOOP           },
+	{ "EMFILE",             EMFILE          },
+	{ "EMLINK",             EMLINK          },
+	{ "EMSGSIZE",           EMSGSIZE        },
+	{ "ENAMETOOLONG",       ENAMETOOLONG    },
+	{ "ENETDOWN",           ENETDOWN        },
+	{ "ENETRESET",          ENETRESET       },
+	{ "ENETUNREACH",        ENETUNREACH     },
+	{ "ENFILE",             ENFILE          },
+	{ "ENOBUFS",            ENOBUFS         },
+#if defined(ENODATA)
+	{ "ENODATA",            ENODATA         },
+#endif
+	{ "ENODEV",             ENODEV          },
+	{ "ENOENT",             ENOENT          },
+	{ "ENOEXEC",            ENOEXEC         },
+	{ "ENOLCK",             ENOLCK          },
+#if defined(ENOLINK)
+	{ "ENOLINK",            ENOLINK         },
+#endif
+	{ "ENOMEM",             ENOMEM          },
+#if defined(ENOMSG)
+	{ "ENOMSG",             ENOMSG          },
+#endif
+	{ "ENOPROTOOPT",        ENOPROTOOPT     },
+	{ "ENOSPC",             ENOSPC          },
+#if defined(ENOSR)
+	{ "ENOSR",              ENOSR           },
+#endif
+#if defined(ENOSTR)
+	{ "ENOSTR",             ENOSTR          },
+#endif
+	{ "ENOSYS",             ENOSYS          },
+	{ "ENOTCONN",           ENOTCONN        },
+	{ "ENOTDIR",            ENOTDIR         },
+	{ "ENOTEMPTY",          ENOTEMPTY       },
+#if defined(ENOTRECOVERABLE)
+	{ "ENOTRECOVERABLE",    ENOTRECOVERABLE },
+#endif
+	{ "ENOTSOCK",           ENOTSOCK        },
+	{ "ENOTSUP",            ENOTSUP         },
+	{ "ENOTTY",             ENOTTY          },
+	{ "ENXIO",              ENXIO           },
+	{ "EOPNOTSUPP",         EOPNOTSUPP      },
+	{ "EOVERFLOW",          EOVERFLOW       },
+	{ "EOWNERDEAD",         EOWNERDEAD      },
+	{ "EPERM",              EPERM           },
+	{ "EPIPE",              EPIPE           },
+	{ "EPROTO",             EPROTO          },
+	{ "EPROTONOSUPPORT",    EPROTONOSUPPORT },
+	{ "EPROTOTYPE",         EPROTOTYPE      },
+	{ "ERANGE",             ERANGE          },
+	{ "EROFS",              EROFS           },
+	{ "ESPIPE",             ESPIPE          },
+	{ "ESRCH",              ESRCH           },
+#if defined(ETIME)
+	{ "ETIME",              ETIME           },
+#endif
+	{ "ETIMEDOUT",          ETIMEDOUT       },
+#if defined(ETXTBSY)
+	{ "ETXTBSY",            ETXTBSY         },
+#endif
+	{ "EWOULDBLOCK",        EWOULDBLOCK     },
+	{ "EXDEV",              EXDEV           }
+};
+
+// }}}
+
+} // !namespace
+
+void duk::type_traits<std::system_error>::raise(duk_context* ctx, const std::system_error& ex)
+{
+	do_raise(ctx, ex);
+}
+
+void duk::type_traits<boost::system::system_error>::raise(duk_context* ctx, const boost::system::system_error& ex)
+{
+	do_raise(ctx, ex);
+}
+
+auto irccd_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd";
+}
+
+void irccd_api::load(bot& bot, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	// irccd.
+	duk_push_object(plugin->get_context());
+
+	// Version.
+	duk_push_object(plugin->get_context());
+	duk::push(plugin->get_context(), IRCCD_VERSION_MAJOR);
+	duk_put_prop_string(plugin->get_context(), -2, "major");
+	duk::push(plugin->get_context(), IRCCD_VERSION_MINOR);
+	duk_put_prop_string(plugin->get_context(), -2, "minor");
+	duk::push(plugin->get_context(), IRCCD_VERSION_PATCH);
+	duk_put_prop_string(plugin->get_context(), -2, "patch");
+	duk_put_prop_string(plugin->get_context(), -2, "version");
+
+	// Create the system_error that inherits from Error.
+	duk_push_c_function(plugin->get_context(), SystemError_constructor, 2);
+
+	// Put errno codes into the irccd.system_error object.
+	for (const auto& [k, v] : errors) {
+		duk_push_int(plugin->get_context(), v);
+		duk_put_prop_string(plugin->get_context(), -2, k.c_str());
+	}
+
+	duk_push_object(plugin->get_context());
+	duk_get_global_string(plugin->get_context(), "Error");
+	duk_get_prop_string(plugin->get_context(), -1, "prototype");
+	duk_remove(plugin->get_context(), -2);
+	duk_set_prototype(plugin->get_context(), -2);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "SystemError");
+
+	// Set irccd as global.
+	duk_put_global_string(plugin->get_context(), "Irccd");
+
+	// Store global instance.
+	duk_push_pointer(plugin->get_context(), &bot);
+	duk_put_global_string(plugin->get_context(), DUK_HIDDEN_SYMBOL("irccd-ref"));
+}
+
+auto duk::type_traits<bot>::self(duk_context *ctx) -> bot&
+{
+	duk::stack_guard sa(ctx);
+
+	duk_get_global_string(ctx, DUK_HIDDEN_SYMBOL("irccd-ref"));
+	const auto ptr = static_cast<bot*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return *ptr;
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/irccd_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,109 @@
+/*
+ * irccd_api.hpp -- Irccd API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_IRCCD_API_HPP
+#define IRCCD_JS_IRCCD_API_HPP
+
+/**
+ * \file irccd_api.hpp
+ * \brief irccd Javascript API.
+ */
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+#include <system_error>
+
+#include <boost/system/system_error.hpp>
+
+#include "api.hpp"
+
+namespace irccd::daemon {
+
+class bot;
+
+} // !irccd
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd Javascript API.
+ */
+class irccd_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialize dukx_type_traits for bot.
+ */
+template <>
+struct type_traits<daemon::bot> {
+	/**
+	 * Get irccd instance stored in this context.
+	 *
+	 * \param ctx the context
+	 * \return the irccd reference
+	 */
+	static auto self(duk_context* ctx) -> daemon::bot&;
+};
+
+/**
+ * \brief Specialize dukx_type_traits for boost::system::system_error.
+ */
+template <>
+struct type_traits<std::system_error> {
+	/**
+	 * Raise an Irccd.SystemError.
+	 *
+	 * \param ctx the context
+	 * \param ex the exception
+	 */
+	static void raise(duk_context* ctx, const std::system_error& ex);
+};
+
+/**
+ * \brief Specialize dukx_type_traits for boost::system::system_error.
+ */
+template <>
+struct type_traits<boost::system::system_error> {
+	/**
+	 * Raise an Irccd.SystemError.
+	 *
+	 * \param ctx the context
+	 * \param ex the exception
+	 */
+	static void raise(duk_context* ctx, const boost::system::system_error& ex);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_IRCCD_API_HPP
--- a/libirccd-js/irccd/js/irccd_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,246 +0,0 @@
-/*
- * irccd_js_api.cpp -- Irccd API
- *
- * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
-
-#include <cerrno>
-#include <string>
-#include <unordered_map>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-
-using irccd::daemon::bot;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ do_raise
-
-template <typename Error>
-void do_raise(duk_context* ctx, const Error& ex)
-{
-	duk::stack_guard sa(ctx, 1);
-
-	duk_get_global_string(ctx, "Irccd");
-	duk_get_prop_string(ctx, -1, "SystemError");
-	duk_remove(ctx, -2);
-	duk::push(ctx, ex.code().value());
-	duk::push(ctx, ex.code().message());
-	duk_new(ctx, 2);
-
-	(void)duk_throw(ctx);
-}
-
-// }}}
-
-// {{{ Irccd.SystemError [constructor]
-
-auto SystemError_constructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_this(ctx);
-	duk_push_int(ctx, duk_require_int(ctx, 0));
-	duk_put_prop_string(ctx, -2, "errno");
-	duk_push_string(ctx, duk_require_string(ctx, 1));
-	duk_put_prop_string(ctx, -2, "message");
-	duk_push_string(ctx, "SystemError");
-	duk_put_prop_string(ctx, -2, "name");
-	duk_pop(ctx);
-
-	return 0;
-}
-
-// }}}
-
-// {{{ definitions
-
-const std::unordered_map<std::string, int> errors{
-	{ "E2BIG",              E2BIG           },
-	{ "EACCES",             EACCES          },
-	{ "EADDRINUSE",         EADDRINUSE      },
-	{ "EADDRNOTAVAIL",      EADDRNOTAVAIL   },
-	{ "EAFNOSUPPORT",       EAFNOSUPPORT    },
-	{ "EAGAIN",             EAGAIN          },
-	{ "EALREADY",           EALREADY        },
-	{ "EBADF",              EBADF           },
-#if defined(EBADMSG)
-	{ "EBADMSG",            EBADMSG         },
-#endif
-	{ "EBUSY",              EBUSY           },
-	{ "ECANCELED",          ECANCELED       },
-	{ "ECHILD",             ECHILD          },
-	{ "ECONNABORTED",       ECONNABORTED    },
-	{ "ECONNREFUSED",       ECONNREFUSED    },
-	{ "ECONNRESET",         ECONNRESET      },
-	{ "EDEADLK",            EDEADLK         },
-	{ "EDESTADDRREQ",       EDESTADDRREQ    },
-	{ "EDOM",               EDOM            },
-	{ "EEXIST",             EEXIST          },
-	{ "EFAULT",             EFAULT          },
-	{ "EFBIG",              EFBIG           },
-	{ "EHOSTUNREACH",       EHOSTUNREACH    },
-#if defined(EIDRM)
-	{ "EIDRM",              EIDRM           },
-#endif
-	{ "EILSEQ",             EILSEQ          },
-	{ "EINPROGRESS",        EINPROGRESS     },
-	{ "EINTR",              EINTR           },
-	{ "EINVAL",             EINVAL          },
-	{ "EIO",                EIO             },
-	{ "EISCONN",            EISCONN         },
-	{ "EISDIR",             EISDIR          },
-	{ "ELOOP",              ELOOP           },
-	{ "EMFILE",             EMFILE          },
-	{ "EMLINK",             EMLINK          },
-	{ "EMSGSIZE",           EMSGSIZE        },
-	{ "ENAMETOOLONG",       ENAMETOOLONG    },
-	{ "ENETDOWN",           ENETDOWN        },
-	{ "ENETRESET",          ENETRESET       },
-	{ "ENETUNREACH",        ENETUNREACH     },
-	{ "ENFILE",             ENFILE          },
-	{ "ENOBUFS",            ENOBUFS         },
-#if defined(ENODATA)
-	{ "ENODATA",            ENODATA         },
-#endif
-	{ "ENODEV",             ENODEV          },
-	{ "ENOENT",             ENOENT          },
-	{ "ENOEXEC",            ENOEXEC         },
-	{ "ENOLCK",             ENOLCK          },
-#if defined(ENOLINK)
-	{ "ENOLINK",            ENOLINK         },
-#endif
-	{ "ENOMEM",             ENOMEM          },
-#if defined(ENOMSG)
-	{ "ENOMSG",             ENOMSG          },
-#endif
-	{ "ENOPROTOOPT",        ENOPROTOOPT     },
-	{ "ENOSPC",             ENOSPC          },
-#if defined(ENOSR)
-	{ "ENOSR",              ENOSR           },
-#endif
-#if defined(ENOSTR)
-	{ "ENOSTR",             ENOSTR          },
-#endif
-	{ "ENOSYS",             ENOSYS          },
-	{ "ENOTCONN",           ENOTCONN        },
-	{ "ENOTDIR",            ENOTDIR         },
-	{ "ENOTEMPTY",          ENOTEMPTY       },
-#if defined(ENOTRECOVERABLE)
-	{ "ENOTRECOVERABLE",    ENOTRECOVERABLE },
-#endif
-	{ "ENOTSOCK",           ENOTSOCK        },
-	{ "ENOTSUP",            ENOTSUP         },
-	{ "ENOTTY",             ENOTTY          },
-	{ "ENXIO",              ENXIO           },
-	{ "EOPNOTSUPP",         EOPNOTSUPP      },
-	{ "EOVERFLOW",          EOVERFLOW       },
-	{ "EOWNERDEAD",         EOWNERDEAD      },
-	{ "EPERM",              EPERM           },
-	{ "EPIPE",              EPIPE           },
-	{ "EPROTO",             EPROTO          },
-	{ "EPROTONOSUPPORT",    EPROTONOSUPPORT },
-	{ "EPROTOTYPE",         EPROTOTYPE      },
-	{ "ERANGE",             ERANGE          },
-	{ "EROFS",              EROFS           },
-	{ "ESPIPE",             ESPIPE          },
-	{ "ESRCH",              ESRCH           },
-#if defined(ETIME)
-	{ "ETIME",              ETIME           },
-#endif
-	{ "ETIMEDOUT",          ETIMEDOUT       },
-#if defined(ETXTBSY)
-	{ "ETXTBSY",            ETXTBSY         },
-#endif
-	{ "EWOULDBLOCK",        EWOULDBLOCK     },
-	{ "EXDEV",              EXDEV           }
-};
-
-// }}}
-
-} // !namespace
-
-void duk::type_traits<std::system_error>::raise(duk_context* ctx, const std::system_error& ex)
-{
-	do_raise(ctx, ex);
-}
-
-void duk::type_traits<boost::system::system_error>::raise(duk_context* ctx, const boost::system::system_error& ex)
-{
-	do_raise(ctx, ex);
-}
-
-auto irccd_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd";
-}
-
-void irccd_js_api::load(bot& bot, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	// irccd.
-	duk_push_object(plugin->get_context());
-
-	// Version.
-	duk_push_object(plugin->get_context());
-	duk::push(plugin->get_context(), IRCCD_VERSION_MAJOR);
-	duk_put_prop_string(plugin->get_context(), -2, "major");
-	duk::push(plugin->get_context(), IRCCD_VERSION_MINOR);
-	duk_put_prop_string(plugin->get_context(), -2, "minor");
-	duk::push(plugin->get_context(), IRCCD_VERSION_PATCH);
-	duk_put_prop_string(plugin->get_context(), -2, "patch");
-	duk_put_prop_string(plugin->get_context(), -2, "version");
-
-	// Create the system_error that inherits from Error.
-	duk_push_c_function(plugin->get_context(), SystemError_constructor, 2);
-
-	// Put errno codes into the irccd.system_error object.
-	for (const auto& [k, v] : errors) {
-		duk_push_int(plugin->get_context(), v);
-		duk_put_prop_string(plugin->get_context(), -2, k.c_str());
-	}
-
-	duk_push_object(plugin->get_context());
-	duk_get_global_string(plugin->get_context(), "Error");
-	duk_get_prop_string(plugin->get_context(), -1, "prototype");
-	duk_remove(plugin->get_context(), -2);
-	duk_set_prototype(plugin->get_context(), -2);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "SystemError");
-
-	// Set irccd as global.
-	duk_put_global_string(plugin->get_context(), "Irccd");
-
-	// Store global instance.
-	duk_push_pointer(plugin->get_context(), &bot);
-	duk_put_global_string(plugin->get_context(), DUK_HIDDEN_SYMBOL("irccd-ref"));
-}
-
-auto duk::type_traits<bot>::self(duk_context *ctx) -> bot&
-{
-	duk::stack_guard sa(ctx);
-
-	duk_get_global_string(ctx, DUK_HIDDEN_SYMBOL("irccd-ref"));
-	const auto ptr = static_cast<bot*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-
-	return *ptr;
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/irccd_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-/*
- * irccd_js_api.hpp -- Irccd API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_IRCCD_JS_API_HPP
-#define IRCCD_JS_IRCCD_JS_API_HPP
-
-/**
- * \file irccd_js_api.hpp
- * \brief irccd Javascript API.
- */
-
-#include <cerrno>
-#include <cstring>
-#include <string>
-#include <system_error>
-
-#include <boost/system/system_error.hpp>
-
-#include "js_api.hpp"
-
-namespace irccd::daemon {
-
-class bot;
-
-} // !irccd
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd Javascript API.
- */
-class irccd_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-namespace duk {
-
-/**
- * \brief Specialize dukx_type_traits for bot.
- */
-template <>
-struct type_traits<daemon::bot> {
-	/**
-	 * Get irccd instance stored in this context.
-	 *
-	 * \param ctx the context
-	 * \return the irccd reference
-	 */
-	static auto self(duk_context* ctx) -> daemon::bot&;
-};
-
-/**
- * \brief Specialize dukx_type_traits for boost::system::system_error.
- */
-template <>
-struct type_traits<std::system_error> {
-	/**
-	 * Raise an Irccd.SystemError.
-	 *
-	 * \param ctx the context
-	 * \param ex the exception
-	 */
-	static void raise(duk_context* ctx, const std::system_error& ex);
-};
-
-/**
- * \brief Specialize dukx_type_traits for boost::system::system_error.
- */
-template <>
-struct type_traits<boost::system::system_error> {
-	/**
-	 * Raise an Irccd.SystemError.
-	 *
-	 * \param ctx the context
-	 * \param ex the exception
-	 */
-	static void raise(duk_context* ctx, const boost::system::system_error& ex);
-};
-
-} // !duk
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_IRCCD_JS_API_HPP
--- a/libirccd-js/irccd/js/js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +0,0 @@
-/*
- * js_api.cpp -- Javascript API module
- *
- * Copyright (c) 2013-2018 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 "directory_js_api.hpp"
-#include "elapsed_timer_js_api.hpp"
-#include "file_js_api.hpp"
-#include "irccd_js_api.hpp"
-#include "logger_js_api.hpp"
-#include "plugin_js_api.hpp"
-#include "server_js_api.hpp"
-#include "system_js_api.hpp"
-#include "timer_js_api.hpp"
-#include "unicode_js_api.hpp"
-#include "util_js_api.hpp"
-
-namespace irccd::js {
-
-namespace {
-
-template <typename T>
-auto bind() noexcept -> js_api::constructor
-{
-	return [] () noexcept {
-		return std::make_unique<T>();
-	};
-}
-
-} // !namespace
-
-auto js_api::registry() noexcept -> const std::vector<constructor>&
-{
-	static const std::vector<constructor> list {
-		// Irccd API must be loaded first.
-		bind<irccd_js_api>(),
-		bind<directory_js_api>(),
-		bind<elapsed_timer_js_api>(),
-		bind<file_js_api>(),
-		bind<logger_js_api>(),
-		bind<plugin_js_api>(),
-		bind<server_js_api>(),
-		bind<system_js_api>(),
-		bind<timer_js_api>(),
-		bind<unicode_js_api>(),
-		bind<util_js_api>()
-	};
-
-	return list;
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,92 +0,0 @@
-/*
- * js_api.hpp -- Javascript API module
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_JS_API_HPP
-#define IRCCD_JS_JS_API_HPP
-
-/**
- * \file js_api.hpp
- * \brief Javascript API module.
- */
-
-#include <functional>
-#include <memory>
-#include <string_view>
-#include <vector>
-
-#include "duk.hpp"
-
-namespace irccd {
-
-namespace daemon {
-
-class bot;
-
-} // !daemon
-
-namespace js {
-
-class js_plugin;
-
-/**
- * \ingroup js-api
- * \brief Javascript API module.
- */
-class js_api {
-public:
-	/**
-	 * \brief Command constructor factory.
-	 */
-	using constructor = std::function<std::unique_ptr<js_api> ()>;
-
-	/**
-	 * \brief Registry of all commands.
-	 */
-	static auto registry() noexcept -> const std::vector<constructor>&;
-
-	/**
-	 * Default constructor.
-	 */
-	js_api() noexcept = default;
-
-	/**
-	 * Virtual destructor defaulted.
-	 */
-	virtual ~js_api() noexcept = default;
-
-	/**
-	 * Get the module name.
-	 *
-	 * \return the name
-	 */
-	virtual auto get_name() const noexcept -> std::string_view = 0;
-
-	/**
-	 * Load the module into the Javascript plugin.
-	 *
-	 * \param bot the irccd instance
-	 * \param plugin the plugin
-	 */
-	virtual void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) = 0;
-};
-
-} // !js
-
-} // !irccd
-
-#endif // !IRCCD_JS_JS_API_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,382 +0,0 @@
-/*
- * js_plugin.cpp -- Javascript plugins for irccd
- *
- * Copyright (c) 2013-2018 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 <cstring>
-#include <cerrno>
-#include <fstream>
-#include <iterator>
-#include <stdexcept>
-
-#include <irccd/daemon/bot.hpp>
-
-#include "js_api.hpp"
-#include "js_plugin.hpp"
-#include "server_js_api.hpp"
-
-using irccd::daemon::bot;
-using irccd::daemon::connect_event;
-using irccd::daemon::disconnect_event;
-using irccd::daemon::invite_event;
-using irccd::daemon::join_event;
-using irccd::daemon::kick_event;
-using irccd::daemon::me_event;
-using irccd::daemon::message_event;
-using irccd::daemon::mode_event;
-using irccd::daemon::names_event;
-using irccd::daemon::nick_event;
-using irccd::daemon::notice_event;
-using irccd::daemon::part_event;
-using irccd::daemon::plugin;
-using irccd::daemon::plugin_error;
-using irccd::daemon::topic_event;
-using irccd::daemon::whois_event;
-using irccd::daemon::whois_info;
-
-namespace irccd::js {
-
-namespace {
-
-auto get_metadata(duk::context& ctx, std::string_view name) -> std::string_view
-{
-	std::string_view ret("unknown");
-
-	duk::stack_guard guard(ctx);
-	duk_get_global_string(ctx, "info");
-
-	if (duk_get_type(ctx, -1) == DUK_TYPE_OBJECT) {
-		duk_get_prop_string(ctx, -1, name.data());
-
-		if (duk_get_type(ctx, -1) == DUK_TYPE_STRING)
-			ret = duk_get_string(ctx, -1);
-
-		duk_pop(ctx);
-	}
-
-	duk_pop(ctx);
-
-	return ret;
-}
-
-auto get_table(duk::context& ctx, std::string_view name) -> plugin::map
-{
-	plugin::map result;
-
-	duk::stack_guard sa(ctx);
-	duk_get_global_string(ctx, name.data());
-	duk_enum(ctx, -1, 0);
-
-	while (duk_next(ctx, -1, true)) {
-		result.emplace(duk_to_string(ctx, -2), duk_to_string(ctx, -1));
-		duk_pop_n(ctx, 2);
-	}
-
-	duk_pop_n(ctx, 2);
-
-	return result;
-}
-
-void set_table(duk::context& ctx, std::string_view name, const plugin::map& vars)
-{
-	duk::stack_guard sa(ctx);
-	duk_get_global_string(ctx, name.data());
-
-	for (const auto& pair : vars) {
-		duk::push(ctx, pair.second);
-		duk_put_prop_string(ctx, -2, pair.first.c_str());
-	}
-
-	duk_pop(ctx);
-}
-
-} // !namespace
-
-void js_plugin::push() noexcept
-{
-}
-
-template <typename Value, typename... Args>
-void js_plugin::push(Value&& value, Args&&... args)
-{
-	duk::push(context_, std::forward<Value>(value));
-	push(std::forward<Args>(args)...);
-}
-
-template <typename... Args>
-void js_plugin::call(const std::string& func, Args&&... args)
-{
-	duk::stack_guard sa(context_);
-
-	duk_get_global_string(context_, func.c_str());
-
-	if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED) {
-		duk_pop(context_);
-		return;
-	}
-
-	push(std::forward<Args>(args)...);
-
-	if (duk_pcall(context_, sizeof... (Args)) != 0)
-		throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
-
-	duk_pop(context_);
-}
-
-js_plugin::js_plugin(std::string id, std::string path)
-	: plugin(std::move(id))
-	, path_(path)
-{
-	duk::stack_guard sa(context_);
-
-	/*
-	 * Create two special tables for configuration and formats, they are
-	 * referenced later as
-	 *
-	 *   - Irccd.Plugin.config
-	 *   - Irccd.Plugin.format
-	 *   - Irccd.Plugin.paths
-	 *
-	 * In js_plugin_module.cpp.
-	 */
-	duk_push_object(context_);
-	duk_put_global_string(context_, config_property.data());
-	duk_push_object(context_);
-	duk_put_global_string(context_, format_property.data());
-	duk_push_object(context_);
-	duk_put_global_string(context_, paths_property.data());
-
-	duk_push_pointer(context_, this);
-	duk_put_global_string(context_, DUK_HIDDEN_SYMBOL("plugin"));
-	duk::push(context_, path);
-	duk_put_global_string(context_, DUK_HIDDEN_SYMBOL("path"));
-}
-
-auto js_plugin::get_context() noexcept -> duk::context&
-{
-	return context_;
-}
-
-auto js_plugin::get_name() const noexcept -> std::string_view
-{
-	return get_metadata(context_, "name");
-}
-
-auto js_plugin::get_author() const noexcept -> std::string_view
-{
-	return get_metadata(context_, "author");
-}
-
-auto js_plugin::get_license() const noexcept -> std::string_view
-{
-	return get_metadata(context_, "license");
-}
-
-auto js_plugin::get_summary() const noexcept -> std::string_view
-{
-	return get_metadata(context_, "summary");
-}
-
-auto js_plugin::get_version() const noexcept -> std::string_view
-{
-	return get_metadata(context_, "version");
-}
-
-auto js_plugin::get_options() const -> map
-{
-	return get_table(context_, config_property);
-}
-
-void js_plugin::set_options(const map& map)
-{
-	set_table(context_, config_property, map);
-}
-
-auto js_plugin::get_formats() const -> map
-{
-	return get_table(context_, format_property);
-}
-
-void js_plugin::set_formats(const map& map)
-{
-	set_table(context_, format_property, map);
-}
-
-auto js_plugin::get_paths() const -> map
-{
-	return get_table(context_, paths_property);
-}
-
-void js_plugin::set_paths(const map& map)
-{
-	set_table(context_, paths_property, map);
-}
-
-void js_plugin::open()
-{
-	std::ifstream input(path_);
-
-	if (!input)
-		throw plugin_error(plugin_error::exec_error, get_name(), std::strerror(errno));
-
-	std::string data(
-		std::istreambuf_iterator<char>(input.rdbuf()),
-		std::istreambuf_iterator<char>()
-	);
-
-	if (duk_peval_string(context_, data.c_str()))
-		throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
-}
-
-void js_plugin::handle_command(bot&, const message_event& event)
-{
-	call("onCommand", event.server, event.origin, event.channel, event.message);
-}
-
-void js_plugin::handle_connect(bot&, const connect_event& event)
-{
-	call("onConnect", event.server);
-}
-
-void js_plugin::handle_disconnect(bot&, const disconnect_event& event)
-{
-	call("onDisconnect", event.server);
-}
-
-void js_plugin::handle_invite(bot&, const invite_event& event)
-{
-	call("onInvite", event.server, event.origin, event.channel);
-}
-
-void js_plugin::handle_join(bot&, const join_event& event)
-{
-	call("onJoin", event.server, event.origin, event.channel);
-}
-
-void js_plugin::handle_kick(bot&, const kick_event& event)
-{
-	call("onKick", event.server, event.origin, event.channel, event.target, event.reason);
-}
-
-void js_plugin::handle_load(bot&)
-{
-	call("onLoad");
-}
-
-void js_plugin::handle_message(bot&, const message_event& event)
-{
-	call("onMessage", event.server, event.origin, event.channel, event.message);
-}
-
-void js_plugin::handle_me(bot&, const me_event& event)
-{
-	call("onMe", event.server, event.origin, event.channel, event.message);
-}
-
-void js_plugin::handle_mode(bot&, const mode_event& event)
-{
-	call("onMode", event.server, event.origin, event.channel, event.mode,
-		event.limit, event.user, event.mask);
-}
-
-void js_plugin::handle_names(bot&, const names_event& event)
-{
-	call("onNames", event.server, event.channel, event.names);
-}
-
-void js_plugin::handle_nick(bot&, const nick_event& event)
-{
-	call("onNick", event.server, event.origin, event.nickname);
-}
-
-void js_plugin::handle_notice(bot&, const notice_event& event)
-{
-	call("onNotice", event.server, event.origin, event.channel, event.message);
-}
-
-void js_plugin::handle_part(bot&, const part_event& event)
-{
-	call("onPart", event.server, event.origin, event.channel, event.reason);
-}
-
-void js_plugin::handle_reload(bot&)
-{
-	call("onReload");
-}
-
-void js_plugin::handle_topic(bot&, const topic_event& event)
-{
-	call("onTopic", event.server, event.origin, event.channel, event.topic);
-}
-
-void js_plugin::handle_unload(bot&)
-{
-	call("onUnload");
-}
-
-void js_plugin::handle_whois(bot&, const whois_event& event)
-{
-	call("onWhois", event.server, event.whois);
-}
-
-js_plugin_loader::js_plugin_loader(bot& bot,
-                                   std::vector<std::string> directories,
-                                   std::vector<std::string> extensions) noexcept
-	: plugin_loader(std::move(directories), std::move(extensions))
-	, bot_(bot)
-{
-}
-
-js_plugin_loader::~js_plugin_loader() noexcept = default;
-
-auto js_plugin_loader::get_modules() const noexcept -> const modules&
-{
-	return modules_;
-}
-
-auto js_plugin_loader::get_modules() noexcept -> modules&
-{
-	return modules_;
-}
-
-auto js_plugin_loader::open(std::string_view id, std::string_view path) -> std::shared_ptr<plugin>
-{
-	auto plugin = std::make_shared<js_plugin>(std::string(id), std::string(path));
-
-	for (const auto& mod : modules_)
-		mod->load(bot_, plugin);
-
-	plugin->open();
-
-	return plugin;
-}
-
-void duk::type_traits<whois_info>::push(duk_context* ctx, const whois_info& whois)
-{
-	duk_push_object(ctx);
-	duk::push(ctx, whois.nick);
-	duk_put_prop_string(ctx, -2, "nickname");
-	duk::push(ctx, whois.user);
-	duk_put_prop_string(ctx, -2, "username");
-	duk::push(ctx, whois.realname);
-	duk_put_prop_string(ctx, -2, "realname");
-	duk::push(ctx, whois.hostname);
-	duk_put_prop_string(ctx, -2, "hostname");
-	duk::push(ctx, whois.channels);
-	duk_put_prop_string(ctx, -2, "channels");
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/js_plugin.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,315 +0,0 @@
-/*
- * js_plugin.hpp -- JavaScript plugins for irccd
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_PLUGIN_HPP
-#define IRCCD_JS_PLUGIN_HPP
-
-/**
- * \file js_plugin.hpp
- * \brief JavaScript plugins for irccd.
- */
-
-#include <irccd/daemon/plugin.hpp>
-#include <irccd/daemon/server.hpp>
-
-#include "duk.hpp"
-
-/**
- * \brief Javascript namespace
- */
-namespace irccd::js {
-
-class js_api;
-
-/**
- * \ingroup js
- * \ingroup daemon-plugins
- * \brief JavaScript plugins for irccd.
- */
-class js_plugin : public daemon::plugin {
-public:
-	/**
-	 * Global property where to read/write plugin configuration (object).
-	 */
-	static inline const std::string_view config_property{DUK_HIDDEN_SYMBOL("config")};
-
-	/**
-	 * Global property where to read/write plugin formats (object).
-	 */
-	static inline const std::string_view format_property{DUK_HIDDEN_SYMBOL("formats")};
-
-	/**
-	 * Global property where paths are defined (object).
-	 */
-	static inline const std::string_view paths_property{DUK_HIDDEN_SYMBOL("paths")};
-
-private:
-	// JavaScript context.
-	mutable duk::context context_;
-
-	// Path to Javascript script file.
-	std::string path_;
-
-	void push() noexcept;
-
-	template <typename Value, typename... Args>
-	void push(Value&& value, Args&&... args);
-
-	template <typename... Args>
-	void call(const std::string&, Args&&... args);
-
-public:
-	/**
-	 * Constructor.
-	 *
-	 * \param id the plugin id
-	 * \param path the path to the plugin
-	 */
-	js_plugin(std::string id, std::string path);
-
-	/**
-	 * Access the Duktape context.
-	 *
-	 * \return the context
-	 */
-	auto get_context() noexcept -> duk::context&;
-
-	/**
-	 * Open the script file associated.
-	 */
-	void open();
-
-	/**
-	 * \copydoc daemon::plugin::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc daemon::plugin::get_author
-	 */
-	auto get_author() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc daemon::plugin::get_license
-	 */
-	auto get_license() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc daemon::plugin::get_summary
-	 */
-	auto get_summary() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc daemon::plugin::get_version
-	 */
-	auto get_version() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc daemon::plugin::get_options
-	 */
-	auto get_options() const -> map override;
-
-	/**
-	 * \copydoc daemon::plugin::set_options
-	 */
-	void set_options(const map& map) override;
-
-	/**
-	 * \copydoc daemon::plugin::get_formats
-	 */
-	auto get_formats() const -> map override;
-
-	/**
-	 * \copydoc daemon::plugin::set_formats
-	 */
-	void set_formats(const map& map) override;
-
-	/**
-	 * \copydoc daemon::plugin::get_paths
-	 */
-	auto get_paths() const -> map override;
-
-	/**
-	 * \copydoc daemon::plugin::set_paths
-	 */
-	void set_paths(const map& map) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_command
-	 */
-	void handle_command(daemon::bot& bot, const daemon::message_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_connect
-	 */
-	void handle_connect(daemon::bot& bot, const daemon::connect_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_disconnect
-	 */
-	void handle_disconnect(daemon::bot& bot, const daemon::disconnect_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_invite
-	 */
-	void handle_invite(daemon::bot& bot, const daemon::invite_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_join
-	 */
-	void handle_join(daemon::bot& bot, const daemon::join_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_kick
-	 */
-	void handle_kick(daemon::bot& bot, const daemon::kick_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_load
-	 */
-	void handle_load(daemon::bot& bot) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_message
-	 */
-	void handle_message(daemon::bot& bot, const daemon::message_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_me
-	 */
-	void handle_me(daemon::bot& bot, const daemon::me_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_mode
-	 */
-	void handle_mode(daemon::bot& bot, const daemon::mode_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_names
-	 */
-	void handle_names(daemon::bot& bot, const daemon::names_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_nick
-	 */
-	void handle_nick(daemon::bot& bot, const daemon::nick_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_notice
-	 */
-	void handle_notice(daemon::bot& bot, const daemon::notice_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_part
-	 */
-	void handle_part(daemon::bot& bot, const daemon::part_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_reload
-	 */
-	void handle_reload(daemon::bot& bot) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_topic
-	 */
-	void handle_topic(daemon::bot& bot, const daemon::topic_event& event) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_unload
-	 */
-	void handle_unload(daemon::bot& bot) override;
-
-	/**
-	 * \copydoc daemon::plugin::handle_whois
-	 */
-	void handle_whois(daemon::bot& bot, const daemon::whois_event& event) override;
-};
-
-/**
- * \ingroup plugins
- * \brief Implementation for searching Javascript plugins.
- */
-class js_plugin_loader : public daemon::plugin_loader {
-public:
-	/**
-	 * \brief The list of Javascript API modules.
-	 */
-	using modules = std::vector<std::unique_ptr<js_api>>;
-
-private:
-	daemon::bot& bot_;
-	modules modules_;
-
-public:
-	/**
-	 * Constructor.
-	 *
-	 * \param bot the irccd instance
-	 * \param directories directories to search
-	 * \param extensions extensions to search
-	 */
-	js_plugin_loader(daemon::bot& bot,
-	                 std::vector<std::string> directories = {},
-	                 std::vector<std::string> extensions = {".js"}) noexcept;
-
-	/**
-	 * Destructor defaulted.
-	 */
-	~js_plugin_loader() noexcept;
-
-	/**
-	 * Get the list of modules.
-	 *
-	 * \return the modules
-	 */
-	auto get_modules() const noexcept -> const modules&;
-
-	/**
-	 * Overloaded function.
-	 *
-	 * \return the modules
-	 */
-	auto get_modules() noexcept -> modules&;
-
-	/**
-	 * \copydoc daemon::plugin_loader::open
-	 */
-	auto open(std::string_view id, std::string_view file) -> std::shared_ptr<daemon::plugin>;
-};
-
-namespace duk {
-
-/**
- * \brief Specialization for type_traits<whois_info>
- */
-template <>
-struct type_traits<daemon::whois_info> : public std::true_type {
-	/**
-	 * Push a whois_info.
-	 *
-	 * \param ctx the Duktape context
-	 * \param who the information
-	 */
-	static void push(duk_context* ctx, const daemon::whois_info& who);
-};
-
-} // !duk
-
-} // !irccd::js
-
-#endif // !IRCCD_PLUGIN_JS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/logger_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,149 @@
+/*
+ * logger_api.cpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/daemon/bot.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "logger_api.hpp"
+#include "plugin_api.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ print
+
+auto print(duk_context* ctx, unsigned level) -> duk_ret_t
+{
+	assert(level <= 2);
+
+	try {
+		auto& sink = duk::type_traits<bot>::self(ctx).get_log();
+		auto& self = duk::type_traits<plugin>::self(ctx);
+
+		switch (level) {
+		case 0:
+			sink.debug<daemon::plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+			break;
+		case 1:
+			sink.info<daemon::plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+			break;
+		default:
+			sink.warning<daemon::plugin>(self) << duk_require_string(ctx, 0) << std::endl;
+			break;
+		}
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Logger.info
+
+/*
+ * Function: Irccd.Logger.info(message)
+ * --------------------------------------------------------
+ *
+ * Write a verbose message.
+ *
+ * Arguments:
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_info(duk_context* ctx) -> duk_ret_t
+{
+	return print(ctx, 1);
+}
+
+// }}}
+
+// {{{ Irccd.Logger.warning
+
+/*
+ * Function: Irccd.Logger.warning(message)
+ * --------------------------------------------------------
+ *
+ * Write a warning message.
+ *
+ * Arguments:
+ *   - message, the warning.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_warning(duk_context* ctx) -> duk_ret_t
+{
+	return print(ctx, 2);
+}
+
+// }}}
+
+// {{{ Irccd.Logger.debug
+
+/*
+ * Function: Irccd.Logger.debug(message)
+ * --------------------------------------------------------
+ *
+ * Write a debug message, only shown if irccd is compiled in debug.
+ *
+ * Arguments:
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.SystemError on errors
+ */
+auto Logger_debug(duk_context* ctx) -> duk_ret_t
+{
+	return print(ctx, 0);
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+	{ "info",       Logger_info,    1 },
+	{ "warning",    Logger_warning, 1 },
+	{ "debug",      Logger_debug,   1 },
+	{ nullptr,      nullptr,        0 }
+};
+
+} // !namespace
+
+auto logger_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Logger";
+}
+
+void logger_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_put_prop_string(plugin->get_context(), -2, "Logger");
+	duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/logger_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * logger_api.hpp -- Irccd.Logger API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_LOGGER_API_HPP
+#define IRCCD_JS_LOGGER_API_HPP
+
+/**
+ * \file logger_api.hpp
+ * \brief Irccd.Logger Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief irccd.Logger Javascript API.
+ */
+class logger_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_LOGGER_API_HPP
--- a/libirccd-js/irccd/js/logger_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,150 +0,0 @@
-/*
- * logger_js_api.cpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/bot.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "logger_js_api.hpp"
-#include "plugin_js_api.hpp"
-
-using irccd::daemon::bot;
-using irccd::daemon::plugin;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ print
-
-auto print(duk_context* ctx, unsigned level) -> duk_ret_t
-{
-	assert(level <= 2);
-
-	try {
-		auto& sink = duk::type_traits<bot>::self(ctx).get_log();
-		auto& self = duk::type_traits<js_plugin>::self(ctx);
-
-		switch (level) {
-		case 0:
-			sink.debug<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
-			break;
-		case 1:
-			sink.info<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
-			break;
-		default:
-			sink.warning<plugin>(self) << duk_require_string(ctx, 0) << std::endl;
-			break;
-		}
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Logger.info
-
-/*
- * Function: Irccd.Logger.info(message)
- * --------------------------------------------------------
- *
- * Write a verbose message.
- *
- * Arguments:
- *   - message, the message.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Logger_info(duk_context* ctx) -> duk_ret_t
-{
-	return print(ctx, 1);
-}
-
-// }}}
-
-// {{{ Irccd.Logger.warning
-
-/*
- * Function: Irccd.Logger.warning(message)
- * --------------------------------------------------------
- *
- * Write a warning message.
- *
- * Arguments:
- *   - message, the warning.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Logger_warning(duk_context* ctx) -> duk_ret_t
-{
-	return print(ctx, 2);
-}
-
-// }}}
-
-// {{{ Irccd.Logger.debug
-
-/*
- * Function: Irccd.Logger.debug(message)
- * --------------------------------------------------------
- *
- * Write a debug message, only shown if irccd is compiled in debug.
- *
- * Arguments:
- *   - message, the message.
- * Throws:
- *   - Irccd.SystemError on errors
- */
-auto Logger_debug(duk_context* ctx) -> duk_ret_t
-{
-	return print(ctx, 0);
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-	{ "info",       Logger_info,    1 },
-	{ "warning",    Logger_warning, 1 },
-	{ "debug",      Logger_debug,   1 },
-	{ nullptr,      nullptr,        0 }
-};
-
-} // !namespace
-
-auto logger_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Logger";
-}
-
-void logger_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_put_prop_string(plugin->get_context(), -2, "Logger");
-	duk_pop(plugin->get_context());
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/logger_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * logger_js_api.hpp -- Irccd.Logger API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_LOGGER_JS_API_HPP
-#define IRCCD_JS_LOGGER_JS_API_HPP
-
-/**
- * \file logger_js_api.hpp
- * \brief Irccd.Logger Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief irccd.Logger Javascript API.
- */
-class logger_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_LOGGER_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,382 @@
+/*
+ * plugin.cpp -- Javascript plugins for irccd
+ *
+ * Copyright (c) 2013-2018 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 <cstring>
+#include <cerrno>
+#include <fstream>
+#include <iterator>
+#include <stdexcept>
+
+#include <irccd/daemon/bot.hpp>
+
+#include "api.hpp"
+#include "plugin.hpp"
+#include "server_api.hpp"
+
+using irccd::daemon::bot;
+using irccd::daemon::connect_event;
+using irccd::daemon::disconnect_event;
+using irccd::daemon::invite_event;
+using irccd::daemon::join_event;
+using irccd::daemon::kick_event;
+using irccd::daemon::me_event;
+using irccd::daemon::message_event;
+using irccd::daemon::mode_event;
+using irccd::daemon::names_event;
+using irccd::daemon::nick_event;
+using irccd::daemon::notice_event;
+using irccd::daemon::part_event;
+using irccd::daemon::plugin;
+using irccd::daemon::plugin_error;
+using irccd::daemon::topic_event;
+using irccd::daemon::whois_event;
+using irccd::daemon::whois_info;
+
+namespace irccd::js {
+
+namespace {
+
+auto get_metadata(duk::context& ctx, std::string_view name) -> std::string_view
+{
+	std::string_view ret("unknown");
+
+	duk::stack_guard guard(ctx);
+	duk_get_global_string(ctx, "info");
+
+	if (duk_get_type(ctx, -1) == DUK_TYPE_OBJECT) {
+		duk_get_prop_string(ctx, -1, name.data());
+
+		if (duk_get_type(ctx, -1) == DUK_TYPE_STRING)
+			ret = duk_get_string(ctx, -1);
+
+		duk_pop(ctx);
+	}
+
+	duk_pop(ctx);
+
+	return ret;
+}
+
+auto get_table(duk::context& ctx, std::string_view name) -> plugin::map
+{
+	plugin::map result;
+
+	duk::stack_guard sa(ctx);
+	duk_get_global_string(ctx, name.data());
+	duk_enum(ctx, -1, 0);
+
+	while (duk_next(ctx, -1, true)) {
+		result.emplace(duk_to_string(ctx, -2), duk_to_string(ctx, -1));
+		duk_pop_n(ctx, 2);
+	}
+
+	duk_pop_n(ctx, 2);
+
+	return result;
+}
+
+void set_table(duk::context& ctx, std::string_view name, const plugin::map& vars)
+{
+	duk::stack_guard sa(ctx);
+	duk_get_global_string(ctx, name.data());
+
+	for (const auto& pair : vars) {
+		duk::push(ctx, pair.second);
+		duk_put_prop_string(ctx, -2, pair.first.c_str());
+	}
+
+	duk_pop(ctx);
+}
+
+} // !namespace
+
+void plugin::push() noexcept
+{
+}
+
+template <typename Value, typename... Args>
+void plugin::push(Value&& value, Args&&... args)
+{
+	duk::push(context_, std::forward<Value>(value));
+	push(std::forward<Args>(args)...);
+}
+
+template <typename... Args>
+void plugin::call(const std::string& func, Args&&... args)
+{
+	duk::stack_guard sa(context_);
+
+	duk_get_global_string(context_, func.c_str());
+
+	if (duk_get_type(context_, -1) == DUK_TYPE_UNDEFINED) {
+		duk_pop(context_);
+		return;
+	}
+
+	push(std::forward<Args>(args)...);
+
+	if (duk_pcall(context_, sizeof... (Args)) != 0)
+		throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
+
+	duk_pop(context_);
+}
+
+plugin::plugin(std::string id, std::string path)
+	: daemon::plugin(std::move(id))
+	, path_(path)
+{
+	duk::stack_guard sa(context_);
+
+	/*
+	 * Create two special tables for configuration and formats, they are
+	 * referenced later as
+	 *
+	 *   - Irccd.Plugin.config
+	 *   - Irccd.Plugin.format
+	 *   - Irccd.Plugin.paths
+	 *
+	 * In plugin_module.cpp.
+	 */
+	duk_push_object(context_);
+	duk_put_global_string(context_, config_property.data());
+	duk_push_object(context_);
+	duk_put_global_string(context_, format_property.data());
+	duk_push_object(context_);
+	duk_put_global_string(context_, paths_property.data());
+
+	duk_push_pointer(context_, this);
+	duk_put_global_string(context_, DUK_HIDDEN_SYMBOL("plugin"));
+	duk::push(context_, path);
+	duk_put_global_string(context_, DUK_HIDDEN_SYMBOL("path"));
+}
+
+auto plugin::get_context() noexcept -> duk::context&
+{
+	return context_;
+}
+
+auto plugin::get_name() const noexcept -> std::string_view
+{
+	return get_metadata(context_, "name");
+}
+
+auto plugin::get_author() const noexcept -> std::string_view
+{
+	return get_metadata(context_, "author");
+}
+
+auto plugin::get_license() const noexcept -> std::string_view
+{
+	return get_metadata(context_, "license");
+}
+
+auto plugin::get_summary() const noexcept -> std::string_view
+{
+	return get_metadata(context_, "summary");
+}
+
+auto plugin::get_version() const noexcept -> std::string_view
+{
+	return get_metadata(context_, "version");
+}
+
+auto plugin::get_options() const -> map
+{
+	return get_table(context_, config_property);
+}
+
+void plugin::set_options(const map& map)
+{
+	set_table(context_, config_property, map);
+}
+
+auto plugin::get_formats() const -> map
+{
+	return get_table(context_, format_property);
+}
+
+void plugin::set_formats(const map& map)
+{
+	set_table(context_, format_property, map);
+}
+
+auto plugin::get_paths() const -> map
+{
+	return get_table(context_, paths_property);
+}
+
+void plugin::set_paths(const map& map)
+{
+	set_table(context_, paths_property, map);
+}
+
+void plugin::open()
+{
+	std::ifstream input(path_);
+
+	if (!input)
+		throw plugin_error(plugin_error::exec_error, get_name(), std::strerror(errno));
+
+	std::string data(
+		std::istreambuf_iterator<char>(input.rdbuf()),
+		std::istreambuf_iterator<char>()
+	);
+
+	if (duk_peval_string(context_, data.c_str()))
+		throw plugin_error(plugin_error::exec_error, get_name(), duk::get_stack(context_, -1).get_stack());
+}
+
+void plugin::handle_command(bot&, const message_event& event)
+{
+	call("onCommand", event.server, event.origin, event.channel, event.message);
+}
+
+void plugin::handle_connect(bot&, const connect_event& event)
+{
+	call("onConnect", event.server);
+}
+
+void plugin::handle_disconnect(bot&, const disconnect_event& event)
+{
+	call("onDisconnect", event.server);
+}
+
+void plugin::handle_invite(bot&, const invite_event& event)
+{
+	call("onInvite", event.server, event.origin, event.channel);
+}
+
+void plugin::handle_join(bot&, const join_event& event)
+{
+	call("onJoin", event.server, event.origin, event.channel);
+}
+
+void plugin::handle_kick(bot&, const kick_event& event)
+{
+	call("onKick", event.server, event.origin, event.channel, event.target, event.reason);
+}
+
+void plugin::handle_load(bot&)
+{
+	call("onLoad");
+}
+
+void plugin::handle_message(bot&, const message_event& event)
+{
+	call("onMessage", event.server, event.origin, event.channel, event.message);
+}
+
+void plugin::handle_me(bot&, const me_event& event)
+{
+	call("onMe", event.server, event.origin, event.channel, event.message);
+}
+
+void plugin::handle_mode(bot&, const mode_event& event)
+{
+	call("onMode", event.server, event.origin, event.channel, event.mode,
+		event.limit, event.user, event.mask);
+}
+
+void plugin::handle_names(bot&, const names_event& event)
+{
+	call("onNames", event.server, event.channel, event.names);
+}
+
+void plugin::handle_nick(bot&, const nick_event& event)
+{
+	call("onNick", event.server, event.origin, event.nickname);
+}
+
+void plugin::handle_notice(bot&, const notice_event& event)
+{
+	call("onNotice", event.server, event.origin, event.channel, event.message);
+}
+
+void plugin::handle_part(bot&, const part_event& event)
+{
+	call("onPart", event.server, event.origin, event.channel, event.reason);
+}
+
+void plugin::handle_reload(bot&)
+{
+	call("onReload");
+}
+
+void plugin::handle_topic(bot&, const topic_event& event)
+{
+	call("onTopic", event.server, event.origin, event.channel, event.topic);
+}
+
+void plugin::handle_unload(bot&)
+{
+	call("onUnload");
+}
+
+void plugin::handle_whois(bot&, const whois_event& event)
+{
+	call("onWhois", event.server, event.whois);
+}
+
+plugin_loader::plugin_loader(bot& bot,
+                             std::vector<std::string> directories,
+                             std::vector<std::string> extensions) noexcept
+	: daemon::plugin_loader(std::move(directories), std::move(extensions))
+	, bot_(bot)
+{
+}
+
+plugin_loader::~plugin_loader() noexcept = default;
+
+auto plugin_loader::get_modules() const noexcept -> const modules&
+{
+	return modules_;
+}
+
+auto plugin_loader::get_modules() noexcept -> modules&
+{
+	return modules_;
+}
+
+auto plugin_loader::open(std::string_view id, std::string_view path) -> std::shared_ptr<daemon::plugin>
+{
+	auto plg = std::make_shared<plugin>(std::string(id), std::string(path));
+
+	for (const auto& mod : modules_)
+		mod->load(bot_, plg);
+
+	plg->open();
+
+	return plg;
+}
+
+void duk::type_traits<whois_info>::push(duk_context* ctx, const whois_info& whois)
+{
+	duk_push_object(ctx);
+	duk::push(ctx, whois.nick);
+	duk_put_prop_string(ctx, -2, "nickname");
+	duk::push(ctx, whois.user);
+	duk_put_prop_string(ctx, -2, "username");
+	duk::push(ctx, whois.realname);
+	duk_put_prop_string(ctx, -2, "realname");
+	duk::push(ctx, whois.hostname);
+	duk_put_prop_string(ctx, -2, "hostname");
+	duk::push(ctx, whois.channels);
+	duk_put_prop_string(ctx, -2, "channels");
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,315 @@
+/*
+ * plugin.hpp -- JavaScript plugins for irccd
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_PLUGIN_HPP
+#define IRCCD_JS_PLUGIN_HPP
+
+/**
+ * \file plugin.hpp
+ * \brief JavaScript plugins for irccd.
+ */
+
+#include <irccd/daemon/plugin.hpp>
+#include <irccd/daemon/server.hpp>
+
+#include "duk.hpp"
+
+/**
+ * \brief Javascript namespace
+ */
+namespace irccd::js {
+
+class api;
+
+/**
+ * \ingroup js
+ * \ingroup daemon-plugins
+ * \brief JavaScript plugins for irccd.
+ */
+class plugin : public daemon::plugin {
+public:
+	/**
+	 * Global property where to read/write plugin configuration (object).
+	 */
+	static inline const std::string_view config_property{DUK_HIDDEN_SYMBOL("config")};
+
+	/**
+	 * Global property where to read/write plugin formats (object).
+	 */
+	static inline const std::string_view format_property{DUK_HIDDEN_SYMBOL("formats")};
+
+	/**
+	 * Global property where paths are defined (object).
+	 */
+	static inline const std::string_view paths_property{DUK_HIDDEN_SYMBOL("paths")};
+
+private:
+	// JavaScript context.
+	mutable duk::context context_;
+
+	// Path to Javascript script file.
+	std::string path_;
+
+	void push() noexcept;
+
+	template <typename Value, typename... Args>
+	void push(Value&& value, Args&&... args);
+
+	template <typename... Args>
+	void call(const std::string&, Args&&... args);
+
+public:
+	/**
+	 * Constructor.
+	 *
+	 * \param id the plugin id
+	 * \param path the path to the plugin
+	 */
+	plugin(std::string id, std::string path);
+
+	/**
+	 * Access the Duktape context.
+	 *
+	 * \return the context
+	 */
+	auto get_context() noexcept -> duk::context&;
+
+	/**
+	 * Open the script file associated.
+	 */
+	void open();
+
+	/**
+	 * \copydoc daemon::plugin::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc daemon::plugin::get_author
+	 */
+	auto get_author() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc daemon::plugin::get_license
+	 */
+	auto get_license() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc daemon::plugin::get_summary
+	 */
+	auto get_summary() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc daemon::plugin::get_version
+	 */
+	auto get_version() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc daemon::plugin::get_options
+	 */
+	auto get_options() const -> map override;
+
+	/**
+	 * \copydoc daemon::plugin::set_options
+	 */
+	void set_options(const map& map) override;
+
+	/**
+	 * \copydoc daemon::plugin::get_formats
+	 */
+	auto get_formats() const -> map override;
+
+	/**
+	 * \copydoc daemon::plugin::set_formats
+	 */
+	void set_formats(const map& map) override;
+
+	/**
+	 * \copydoc daemon::plugin::get_paths
+	 */
+	auto get_paths() const -> map override;
+
+	/**
+	 * \copydoc daemon::plugin::set_paths
+	 */
+	void set_paths(const map& map) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_command
+	 */
+	void handle_command(daemon::bot& bot, const daemon::message_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_connect
+	 */
+	void handle_connect(daemon::bot& bot, const daemon::connect_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_disconnect
+	 */
+	void handle_disconnect(daemon::bot& bot, const daemon::disconnect_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_invite
+	 */
+	void handle_invite(daemon::bot& bot, const daemon::invite_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_join
+	 */
+	void handle_join(daemon::bot& bot, const daemon::join_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_kick
+	 */
+	void handle_kick(daemon::bot& bot, const daemon::kick_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_load
+	 */
+	void handle_load(daemon::bot& bot) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_message
+	 */
+	void handle_message(daemon::bot& bot, const daemon::message_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_me
+	 */
+	void handle_me(daemon::bot& bot, const daemon::me_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_mode
+	 */
+	void handle_mode(daemon::bot& bot, const daemon::mode_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_names
+	 */
+	void handle_names(daemon::bot& bot, const daemon::names_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_nick
+	 */
+	void handle_nick(daemon::bot& bot, const daemon::nick_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_notice
+	 */
+	void handle_notice(daemon::bot& bot, const daemon::notice_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_part
+	 */
+	void handle_part(daemon::bot& bot, const daemon::part_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_reload
+	 */
+	void handle_reload(daemon::bot& bot) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_topic
+	 */
+	void handle_topic(daemon::bot& bot, const daemon::topic_event& event) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_unload
+	 */
+	void handle_unload(daemon::bot& bot) override;
+
+	/**
+	 * \copydoc daemon::plugin::handle_whois
+	 */
+	void handle_whois(daemon::bot& bot, const daemon::whois_event& event) override;
+};
+
+/**
+ * \ingroup plugins
+ * \brief Implementation for searching Javascript plugins.
+ */
+class plugin_loader : public daemon::plugin_loader {
+public:
+	/**
+	 * \brief The list of Javascript API modules.
+	 */
+	using modules = std::vector<std::unique_ptr<api>>;
+
+private:
+	daemon::bot& bot_;
+	modules modules_;
+
+public:
+	/**
+	 * Constructor.
+	 *
+	 * \param bot the irccd instance
+	 * \param directories directories to search
+	 * \param extensions extensions to search
+	 */
+	plugin_loader(daemon::bot& bot,
+	              std::vector<std::string> directories = {},
+	              std::vector<std::string> extensions = {".js"}) noexcept;
+
+	/**
+	 * Destructor defaulted.
+	 */
+	~plugin_loader() noexcept;
+
+	/**
+	 * Get the list of modules.
+	 *
+	 * \return the modules
+	 */
+	auto get_modules() const noexcept -> const modules&;
+
+	/**
+	 * Overloaded function.
+	 *
+	 * \return the modules
+	 */
+	auto get_modules() noexcept -> modules&;
+
+	/**
+	 * \copydoc daemon::plugin_loader::open
+	 */
+	auto open(std::string_view id, std::string_view file) -> std::shared_ptr<daemon::plugin>;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialization for type_traits<whois_info>
+ */
+template <>
+struct type_traits<daemon::whois_info> : public std::true_type {
+	/**
+	 * Push a whois_info.
+	 *
+	 * \param ctx the Duktape context
+	 * \param who the information
+	 */
+	static void push(duk_context* ctx, const daemon::whois_info& who);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_PLUGIN_JS_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,461 @@
+/*
+ * plugin_api.cpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/daemon/bot.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "plugin_api.hpp"
+
+using irccd::daemon::bot;
+using irccd::daemon::plugin_error;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Plugin"));
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_idx_t
+{
+	try {
+		return handler();
+	} catch (const plugin_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+/*
+ * set
+ * ------------------------------------------------------------------
+ *
+ * This setter is used to replace the Irccd.Plugin.(config|format|paths)
+ * property when the plugin assign a new one.
+ *
+ * Because the plugin configuration always has higher priority, when a new
+ * object is assigned to 'config' or to the 'format' property, the plugin
+ * configuration is merged to the assigned one, adding or replacing any values.
+ *
+ * Example:
+ *
+ * Plugin 'xyz' does:
+ *
+ * Irccd.Plugin.config = {
+ *	  mode: "simple",
+ *	  level: "123"
+ * };
+ *
+ * The user configuration is:
+ *
+ * [plugin.xyz]
+ * mode = "hard"
+ * path = "/var"
+ *
+ * The final user table looks like this:
+ *
+ * Irccd.Plugin.Config = {
+ *	  mode: "hard",
+ *	  level: "123",
+ *	  path: "/var"
+ * };
+ */
+auto set(duk_context* ctx, std::string_view name) -> duk_ret_t
+{
+	if (!duk_is_object(ctx, 0))
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.data());
+
+	// Merge old table with new one.
+	duk_get_global_string(ctx, name.data());
+	duk_enum(ctx, -1, 0);
+
+	while (duk_next(ctx, -1, true))
+		duk_put_prop(ctx, 0);
+
+	// Pop enum and old table.
+	duk_pop_2(ctx);
+
+	// Replace the old table with the new assigned one.
+	duk_put_global_string(ctx, name.data());
+
+	return 0;
+}
+
+/*
+ * get
+ * ------------------------------------------------------------------
+ *
+ * Get the Irccd.Plugin.(config|format|paths) property.
+ */
+auto get(duk_context* ctx, std::string_view name) -> duk_ret_t
+{
+	duk_get_global_string(ctx, name.data());
+
+	return 1;
+}
+
+/*
+ * set_config
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.config property.
+ */
+auto set_config(duk_context* ctx) -> duk_ret_t
+{
+	return set(ctx, plugin::config_property);
+}
+
+/*
+ * get_config
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.config property.
+ */
+auto get_config(duk_context* ctx) -> duk_ret_t
+{
+	return get(ctx, plugin::config_property);
+}
+
+/*
+ * set_format
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.format property.
+ */
+auto set_format(duk_context* ctx) -> duk_ret_t
+{
+	return set(ctx, plugin::format_property);
+}
+
+/*
+ * get_format
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.format property.
+ */
+auto get_format(duk_context* ctx) -> duk_ret_t
+{
+	return get(ctx, plugin::format_property);
+}
+
+/*
+ * set_paths
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.Plugin.format property.
+ */
+auto set_paths(duk_context* ctx) -> duk_ret_t
+{
+	return set(ctx, plugin::paths_property);
+}
+
+/*
+ * get_paths
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.Plugin.format property.
+ */
+auto get_paths(duk_context* ctx) -> duk_ret_t
+{
+	return get(ctx, plugin::paths_property);
+}
+
+// {{{ Irccd.Plugin.info
+
+/*
+ * Function: Irccd.Plugin.info([name])
+ * ------------------------------------------------------------------
+ *
+ * Get information about a plugin.
+ *
+ * The returned object as the following properties:
+ *
+ * - name: (string) the plugin identifier,
+ * - author: (string) the author,
+ * - license: (string) the license,
+ * - summary: (string) a short description,
+ * - version: (string) the version
+ *
+ * Arguments:
+ *   - name, the plugin identifier, if not specified the current plugin is
+ *	 selected.
+ * Returns:
+ *   The plugin information or undefined if the plugin was not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Plugin_info(duk_context* ctx) -> duk_idx_t
+{
+	return wrap(ctx, [&] {
+		daemon::plugin* plg;
+
+		if (duk_get_top(ctx) >= 1)
+			plg = duk::type_traits<bot>::self(ctx).plugins().get(duk_require_string(ctx, 0)).get();
+		else
+			plg = std::addressof(duk::type_traits<plugin>::self(ctx));
+
+		if (!plg)
+			return 0;
+
+		duk_push_object(ctx);
+		duk::push(ctx, plg->get_name());
+		duk_put_prop_string(ctx, -2, "name");
+		duk::push(ctx, plg->get_author());
+		duk_put_prop_string(ctx, -2, "author");
+		duk::push(ctx, plg->get_license());
+		duk_put_prop_string(ctx, -2, "license");
+		duk::push(ctx, plg->get_summary());
+		duk_put_prop_string(ctx, -2, "summary");
+		duk::push(ctx, plg->get_version());
+		duk_put_prop_string(ctx, -2, "version");
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.list
+
+/*
+ * Function: Irccd.Plugin.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the list of plugins, the array returned contains all plugin names.
+ *
+ * Returns:
+ *   The list of all plugin names.
+ */
+auto Plugin_list(duk_context* ctx) -> duk_idx_t
+{
+	int i = 0;
+
+	duk_push_array(ctx);
+
+	for (const auto& plg : duk::type_traits<bot>::self(ctx).plugins().list()) {
+		duk::push(ctx, plg->get_id());
+		duk_put_prop_index(ctx, -2, i++);
+	}
+
+	return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.load
+
+/*
+ * Function: Irccd.Plugin.load(name)
+ * ------------------------------------------------------------------
+ *
+ * Load a plugin by name. This function will search through the standard
+ * directories.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_load(duk_context* ctx) -> duk_idx_t
+{
+	return wrap(ctx, [&] {
+		duk::type_traits<bot>::self(ctx).plugins().load(
+			duk::require<std::string_view>(ctx, 0), "");
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.reload
+
+/*
+ * Function: Irccd.Plugin.reload(name)
+ * ------------------------------------------------------------------
+ *
+ * Reload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_reload(duk_context* ctx) -> duk_idx_t
+{
+	return wrap(ctx, [&] {
+		duk::type_traits<bot>::self(ctx).plugins().reload(duk::require<std::string>(ctx, 0));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Plugin.unload
+
+/*
+ * Function: Irccd.Plugin.unload(name)
+ * ------------------------------------------------------------------
+ *
+ * Unload a plugin by name.
+ *
+ * Arguments:
+ *   - name, the plugin identifier.
+ * Throws:
+ *   - Irccd.PluginError on plugin related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Plugin_unload(duk_context* ctx) -> duk_idx_t
+{
+	return wrap(ctx, [&] {
+		duk::type_traits<bot>::self(ctx).plugins().unload(duk::require<std::string>(ctx, 0));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.PluginError [constructor]
+
+/*
+ * Function: Irccd.PluginError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.PluginError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+auto PluginError_constructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_this(ctx);
+	duk_push_int(ctx, duk_require_int(ctx, 0));
+	duk_put_prop_string(ctx, -2, "code");
+	duk_push_string(ctx, duk_require_string(ctx, 1));
+	duk_put_prop_string(ctx, -2, "message");
+	duk_push_string(ctx, "PluginError");
+	duk_put_prop_string(ctx, -2, "name");
+	duk_pop(ctx);
+
+	return 0;
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+	{ "info",       Plugin_info,    DUK_VARARGS     },
+	{ "list",       Plugin_list,    0               },
+	{ "load",       Plugin_load,    1               },
+	{ "reload",     Plugin_reload,  1               },
+	{ "unload",     Plugin_unload,  1               },
+	{ nullptr,      nullptr,        0               }
+};
+
+} // !namespace
+
+auto plugin_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Plugin";
+}
+
+void plugin_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	// Store plugin.
+	duk_push_pointer(plugin->get_context(), plugin.get());
+	duk_put_global_string(plugin->get_context(), signature.data());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, functions);
+
+	// 'config' property.
+	duk_push_string(plugin->get_context(), "config");
+	duk_push_c_function(plugin->get_context(), get_config, 0);
+	duk_push_c_function(plugin->get_context(), set_config, 1);
+	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+	// 'format' property.
+	duk_push_string(plugin->get_context(), "format");
+	duk_push_c_function(plugin->get_context(), get_format, 0);
+	duk_push_c_function(plugin->get_context(), set_format, 1);
+	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+	// 'format' property.
+	duk_push_string(plugin->get_context(), "paths");
+	duk_push_c_function(plugin->get_context(), get_paths, 0);
+	duk_push_c_function(plugin->get_context(), set_paths, 1);
+	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
+	// PluginError function.
+	duk_push_c_function(plugin->get_context(), PluginError_constructor, 2);
+	duk_push_object(plugin->get_context());
+	duk_get_global_string(plugin->get_context(), "Error");
+	duk_get_prop_string(plugin->get_context(), -1, "prototype");
+	duk_remove(plugin->get_context(), -2);
+	duk_set_prototype(plugin->get_context(), -2);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "PluginError");
+
+	duk_put_prop_string(plugin->get_context(), -2, "Plugin");
+	duk_pop(plugin->get_context());
+}
+
+namespace duk {
+
+auto type_traits<plugin>::self(duk_context* ctx) -> plugin&
+{
+	duk::stack_guard sa(ctx);
+
+	duk_get_global_string(ctx, signature.data());
+	auto plg = static_cast<plugin*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return *plg;
+}
+
+void type_traits<plugin_error>::raise(duk_context* ctx, const plugin_error& ex)
+{
+	duk::stack_guard sa(ctx, 1);
+
+	duk_get_global_string(ctx, "Irccd");
+	duk_get_prop_string(ctx, -1, "PluginError");
+	duk_remove(ctx, -2);
+	duk::push(ctx, ex.code().value());
+	duk::push(ctx, ex.code().message());
+	duk_new(ctx, 2);
+
+	(void)duk_throw(ctx);
+}
+
+} // !duk
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/plugin_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,83 @@
+/*
+ * plugin_api.hpp -- Irccd.Plugin API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_PLUGIN_API_HPP
+#define IRCCD_JS_PLUGIN_API_HPP
+
+/**
+ * \file plugin_api.hpp
+ * \brief Irccd.Plugin Javascript API.
+ */
+
+#include "api.hpp"
+#include "plugin.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.Plugin Javascript API.
+ */
+class plugin_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialize dukx_type_traits for plugin.
+ */
+template <>
+struct type_traits<plugin> {
+	/**
+	 * Access the plugin stored in this context.
+	 *
+	 * \param ctx the context
+	 * \return the plugin
+	 */
+	static auto self(duk_context* ctx) -> plugin&;
+};
+
+/**
+ * \brief Specialization for plugin_error.
+ */
+template <>
+struct type_traits<daemon::plugin_error> {
+	/**
+	 * Raise a plugin_error.
+	 *
+	 * \param ctx the context
+	 * \param error the error
+	 */
+	static void raise(duk_context* ctx, const daemon::plugin_error& error);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_PLUGIN_API_HPP
--- a/libirccd-js/irccd/js/plugin_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,462 +0,0 @@
-/*
- * plugin_js_api.cpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2018 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 <irccd/daemon/bot.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "plugin_js_api.hpp"
-
-using irccd::daemon::bot;
-using irccd::daemon::plugin_error;
-using irccd::daemon::plugin;
-
-namespace irccd::js {
-
-namespace {
-
-const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Plugin"));
-
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_idx_t
-{
-	try {
-		return handler();
-	} catch (const plugin_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-/*
- * set
- * ------------------------------------------------------------------
- *
- * This setter is used to replace the Irccd.Plugin.(config|format|paths)
- * property when the plugin assign a new one.
- *
- * Because the plugin configuration always has higher priority, when a new
- * object is assigned to 'config' or to the 'format' property, the plugin
- * configuration is merged to the assigned one, adding or replacing any values.
- *
- * Example:
- *
- * Plugin 'xyz' does:
- *
- * Irccd.Plugin.config = {
- *	  mode: "simple",
- *	  level: "123"
- * };
- *
- * The user configuration is:
- *
- * [plugin.xyz]
- * mode = "hard"
- * path = "/var"
- *
- * The final user table looks like this:
- *
- * Irccd.Plugin.Config = {
- *	  mode: "hard",
- *	  level: "123",
- *	  path: "/var"
- * };
- */
-auto set(duk_context* ctx, std::string_view name) -> duk_ret_t
-{
-	if (!duk_is_object(ctx, 0))
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "'%s' property must be object", name.data());
-
-	// Merge old table with new one.
-	duk_get_global_string(ctx, name.data());
-	duk_enum(ctx, -1, 0);
-
-	while (duk_next(ctx, -1, true))
-		duk_put_prop(ctx, 0);
-
-	// Pop enum and old table.
-	duk_pop_2(ctx);
-
-	// Replace the old table with the new assigned one.
-	duk_put_global_string(ctx, name.data());
-
-	return 0;
-}
-
-/*
- * get
- * ------------------------------------------------------------------
- *
- * Get the Irccd.Plugin.(config|format|paths) property.
- */
-auto get(duk_context* ctx, std::string_view name) -> duk_ret_t
-{
-	duk_get_global_string(ctx, name.data());
-
-	return 1;
-}
-
-/*
- * set_config
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.config property.
- */
-auto set_config(duk_context* ctx) -> duk_ret_t
-{
-	return set(ctx, js_plugin::config_property);
-}
-
-/*
- * get_config
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.config property.
- */
-auto get_config(duk_context* ctx) -> duk_ret_t
-{
-	return get(ctx, js_plugin::config_property);
-}
-
-/*
- * set_format
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.format property.
- */
-auto set_format(duk_context* ctx) -> duk_ret_t
-{
-	return set(ctx, js_plugin::format_property);
-}
-
-/*
- * get_format
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.format property.
- */
-auto get_format(duk_context* ctx) -> duk_ret_t
-{
-	return get(ctx, js_plugin::format_property);
-}
-
-/*
- * set_paths
- * ------------------------------------------------------------------
- *
- * Wrap setter for Irccd.Plugin.format property.
- */
-auto set_paths(duk_context* ctx) -> duk_ret_t
-{
-	return set(ctx, js_plugin::paths_property);
-}
-
-/*
- * get_paths
- * ------------------------------------------------------------------
- *
- * Wrap getter for Irccd.Plugin.format property.
- */
-auto get_paths(duk_context* ctx) -> duk_ret_t
-{
-	return get(ctx, js_plugin::paths_property);
-}
-
-// {{{ Irccd.Plugin.info
-
-/*
- * Function: Irccd.Plugin.info([name])
- * ------------------------------------------------------------------
- *
- * Get information about a plugin.
- *
- * The returned object as the following properties:
- *
- * - name: (string) the plugin identifier,
- * - author: (string) the author,
- * - license: (string) the license,
- * - summary: (string) a short description,
- * - version: (string) the version
- *
- * Arguments:
- *   - name, the plugin identifier, if not specified the current plugin is
- *	 selected.
- * Returns:
- *   The plugin information or undefined if the plugin was not found.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Plugin_info(duk_context* ctx) -> duk_idx_t
-{
-	return wrap(ctx, [&] {
-		plugin* plugin;
-
-		if (duk_get_top(ctx) >= 1)
-			plugin = duk::type_traits<bot>::self(ctx).plugins().get(duk_require_string(ctx, 0)).get();
-		else
-			plugin = std::addressof(duk::type_traits<js_plugin>::self(ctx));
-
-		if (!plugin)
-			return 0;
-
-		duk_push_object(ctx);
-		duk::push(ctx, plugin->get_name());
-		duk_put_prop_string(ctx, -2, "name");
-		duk::push(ctx, plugin->get_author());
-		duk_put_prop_string(ctx, -2, "author");
-		duk::push(ctx, plugin->get_license());
-		duk_put_prop_string(ctx, -2, "license");
-		duk::push(ctx, plugin->get_summary());
-		duk_put_prop_string(ctx, -2, "summary");
-		duk::push(ctx, plugin->get_version());
-		duk_put_prop_string(ctx, -2, "version");
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.list
-
-/*
- * Function: Irccd.Plugin.list()
- * ------------------------------------------------------------------
- *
- * Get the list of plugins, the array returned contains all plugin names.
- *
- * Returns:
- *   The list of all plugin names.
- */
-auto Plugin_list(duk_context* ctx) -> duk_idx_t
-{
-	int i = 0;
-
-	duk_push_array(ctx);
-
-	for (const auto& plg : duk::type_traits<bot>::self(ctx).plugins().list()) {
-		duk::push(ctx, plg->get_id());
-		duk_put_prop_index(ctx, -2, i++);
-	}
-
-	return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.load
-
-/*
- * Function: Irccd.Plugin.load(name)
- * ------------------------------------------------------------------
- *
- * Load a plugin by name. This function will search through the standard
- * directories.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Plugin_load(duk_context* ctx) -> duk_idx_t
-{
-	return wrap(ctx, [&] {
-		duk::type_traits<bot>::self(ctx).plugins().load(
-			duk::require<std::string_view>(ctx, 0), "");
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.reload
-
-/*
- * Function: Irccd.Plugin.reload(name)
- * ------------------------------------------------------------------
- *
- * Reload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Plugin_reload(duk_context* ctx) -> duk_idx_t
-{
-	return wrap(ctx, [&] {
-		duk::type_traits<bot>::self(ctx).plugins().reload(duk::require<std::string>(ctx, 0));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Plugin.unload
-
-/*
- * Function: Irccd.Plugin.unload(name)
- * ------------------------------------------------------------------
- *
- * Unload a plugin by name.
- *
- * Arguments:
- *   - name, the plugin identifier.
- * Throws:
- *   - Irccd.PluginError on plugin related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Plugin_unload(duk_context* ctx) -> duk_idx_t
-{
-	return wrap(ctx, [&] {
-		duk::type_traits<bot>::self(ctx).plugins().unload(duk::require<std::string>(ctx, 0));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.PluginError [constructor]
-
-/*
- * Function: Irccd.PluginError(code, message)
- * ------------------------------------------------------------------
- *
- * Create an Irccd.PluginError object.
- *
- * Arguments:
- *   - code, the error code,
- *   - message, the error message.
- */
-auto PluginError_constructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_this(ctx);
-	duk_push_int(ctx, duk_require_int(ctx, 0));
-	duk_put_prop_string(ctx, -2, "code");
-	duk_push_string(ctx, duk_require_string(ctx, 1));
-	duk_put_prop_string(ctx, -2, "message");
-	duk_push_string(ctx, "PluginError");
-	duk_put_prop_string(ctx, -2, "name");
-	duk_pop(ctx);
-
-	return 0;
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-	{ "info",       Plugin_info,    DUK_VARARGS     },
-	{ "list",       Plugin_list,    0               },
-	{ "load",       Plugin_load,    1               },
-	{ "reload",     Plugin_reload,  1               },
-	{ "unload",     Plugin_unload,  1               },
-	{ nullptr,      nullptr,        0               }
-};
-
-} // !namespace
-
-auto plugin_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Plugin";
-}
-
-void plugin_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	// Store plugin.
-	duk_push_pointer(plugin->get_context(), plugin.get());
-	duk_put_global_string(plugin->get_context(), signature.data());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, functions);
-
-	// 'config' property.
-	duk_push_string(plugin->get_context(), "config");
-	duk_push_c_function(plugin->get_context(), get_config, 0);
-	duk_push_c_function(plugin->get_context(), set_config, 1);
-	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-	// 'format' property.
-	duk_push_string(plugin->get_context(), "format");
-	duk_push_c_function(plugin->get_context(), get_format, 0);
-	duk_push_c_function(plugin->get_context(), set_format, 1);
-	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-	// 'format' property.
-	duk_push_string(plugin->get_context(), "paths");
-	duk_push_c_function(plugin->get_context(), get_paths, 0);
-	duk_push_c_function(plugin->get_context(), set_paths, 1);
-	duk_def_prop(plugin->get_context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
-
-	// PluginError function.
-	duk_push_c_function(plugin->get_context(), PluginError_constructor, 2);
-	duk_push_object(plugin->get_context());
-	duk_get_global_string(plugin->get_context(), "Error");
-	duk_get_prop_string(plugin->get_context(), -1, "prototype");
-	duk_remove(plugin->get_context(), -2);
-	duk_set_prototype(plugin->get_context(), -2);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "PluginError");
-
-	duk_put_prop_string(plugin->get_context(), -2, "Plugin");
-	duk_pop(plugin->get_context());
-}
-
-namespace duk {
-
-auto type_traits<js_plugin>::self(duk_context* ctx) -> js_plugin&
-{
-	duk::stack_guard sa(ctx);
-
-	duk_get_global_string(ctx, signature.data());
-	auto plugin = static_cast<js_plugin*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-
-	return *plugin;
-}
-
-void type_traits<plugin_error>::raise(duk_context* ctx, const plugin_error& ex)
-{
-	duk::stack_guard sa(ctx, 1);
-
-	duk_get_global_string(ctx, "Irccd");
-	duk_get_prop_string(ctx, -1, "PluginError");
-	duk_remove(ctx, -2);
-	duk::push(ctx, ex.code().value());
-	duk::push(ctx, ex.code().message());
-	duk_new(ctx, 2);
-
-	(void)duk_throw(ctx);
-}
-
-} // !duk
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/plugin_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/*
- * plugin_js_api.hpp -- Irccd.Plugin API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_PLUGIN_JS_API_HPP
-#define IRCCD_JS_PLUGIN_JS_API_HPP
-
-/**
- * \file plugin_js_api.hpp
- * \brief Irccd.Plugin Javascript API.
- */
-
-#include "js_api.hpp"
-#include "js_plugin.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.Plugin Javascript API.
- */
-class plugin_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-namespace duk {
-
-/**
- * \brief Specialize dukx_type_traits for plugin.
- */
-template <>
-struct type_traits<js_plugin> {
-	/**
-	 * Access the plugin stored in this context.
-	 *
-	 * \param ctx the context
-	 * \return the plugin
-	 */
-	static auto self(duk_context* ctx) -> js_plugin&;
-};
-
-/**
- * \brief Specialization for plugin_error.
- */
-template <>
-struct type_traits<daemon::plugin_error> {
-	/**
-	 * Raise a plugin_error.
-	 *
-	 * \param ctx the context
-	 * \param error the error
-	 */
-	static void raise(duk_context* ctx, const daemon::plugin_error& error);
-};
-
-} // !duk
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_PLUGIN_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,898 @@
+/*
+ * server_api.cpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2018 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 <cassert>
+#include <sstream>
+#include <unordered_map>
+
+#include <irccd/daemon/bot.hpp>
+#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/server_util.hpp>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "server_api.hpp"
+
+using irccd::daemon::bot;
+using irccd::daemon::server;
+using irccd::daemon::server_error;
+using irccd::daemon::server_util::from_json;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Server"));
+const std::string_view prototype(DUK_HIDDEN_SYMBOL("Irccd.Server.prototype"));
+
+auto self(duk_context* ctx) -> std::shared_ptr<server>
+{
+	duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
+
+	return *static_cast<std::shared_ptr<server>*>(ptr);
+}
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+	try {
+		return handler(ctx);
+	} catch (const server_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// {{{ Irccd.Server.prototype.info
+
+/*
+ * Method: Irccd.Server.prototype.info()
+ * ------------------------------------------------------------------
+ *
+ * Get the server information as an object containing the following properties:
+ *
+ * name: the server unique name
+ * host: the host name
+ * port: the port number
+ * ssl: true if using ssl
+ * sslVerify: true if ssl was verified
+ * channels: an array of all channels
+ */
+auto Server_prototype_info(duk_context* ctx) -> duk_ret_t
+{
+	const auto server = self(ctx);
+	const auto& channels = server->get_channels();
+
+	duk_push_object(ctx);
+	duk::push(ctx, server->get_id());
+	duk_put_prop_string(ctx, -2, "name");
+	duk::push(ctx, server->get_hostname());
+	duk_put_prop_string(ctx, -2, "hostname");
+	duk_push_int(ctx, server->get_port());
+	duk_put_prop_string(ctx, -2, "port");
+	duk_push_boolean(ctx, (server->get_options() & server::options::ssl) == server::options::ssl);
+	duk_put_prop_string(ctx, -2, "ssl");
+	duk::push(ctx, server->get_command_char());
+	duk_put_prop_string(ctx, -2, "commandChar");
+	duk::push(ctx, server->get_realname());
+	duk_put_prop_string(ctx, -2, "realname");
+	duk::push(ctx, server->get_nickname());
+	duk_put_prop_string(ctx, -2, "nickname");
+	duk::push(ctx, server->get_username());
+	duk_put_prop_string(ctx, -2, "username");
+	duk::push(ctx, std::vector<std::string>(channels.begin(), channels.end()));
+	duk_put_prop_string(ctx, -2, "channels");
+
+	return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.invite
+
+/*
+ * Method: Irccd.Server.prototype.invite(target, channel)
+ * ------------------------------------------------------------------
+ *
+ * Invite someone to a channel.
+ *
+ * Arguments:
+ *   - target, the target to invite,
+ *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_invite(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+		auto channel = duk::require<std::string>(ctx, 1);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->invite(std::move(target), std::move(channel));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.isSelf
+
+/*
+ * Method: Irccd.Server.prototype.isSelf(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Arguments:
+ *   - nickname, the nickname to check.
+ * Returns:
+ *   True if the nickname targets this server.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_prototype_isSelf(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		return duk::push(ctx, self(ctx)->is_self(duk::require<std::string>(ctx, 0)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.join
+
+/*
+ * Method: Irccd.Server.prototype.join(channel, password = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Join a channel with an optional password.
+ *
+ * Arguments:
+ *   - channel, the channel to join,
+ *   - password, the password or undefined to not use.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_join(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto channel = duk::require<std::string>(ctx, 0);
+		auto password = duk::get<std::string>(ctx, 1);
+
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->join(std::move(channel), std::move(password));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.kick
+
+/*
+ * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Kick someone from a channel.
+ *
+ * Arguments:
+ *   - target, the target to kick,
+ *   - channel, the channel,
+ *   - reason, the optional reason or undefined to not set.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_kick(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+		auto channel = duk::require<std::string>(ctx, 1);
+		auto reason = duk::get<std::string>(ctx, 2);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->kick(std::move(target), std::move(channel), std::move(reason));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.me
+
+/*
+ * Method: Irccd.Server.prototype.me(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a CTCP Action.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_me(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+		auto message = duk::get<std::string>(ctx, 1);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+
+		self(ctx)->me(std::move(target), std::move(message));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.message
+
+/*
+ * Method: Irccd.Server.prototype.message(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a message.
+ *
+ * Arguments:
+ *   - target, the target or a channel,
+ *   - message, the message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_message(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+		auto message = duk::get<std::string>(ctx, 1);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+
+		self(ctx)->message(std::move(target), std::move(message));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.mode
+
+/*
+ * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask)
+ * ------------------------------------------------------------------
+ *
+ * Change your mode.
+ *
+ * Arguments:
+ *   - mode, the new mode.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_mode(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto channel = duk::require<std::string>(ctx, 0);
+		auto mode = duk::require<std::string>(ctx, 1);
+		auto limit = duk::get<std::string>(ctx, 2);
+		auto user = duk::get<std::string>(ctx, 3);
+		auto mask = duk::get<std::string>(ctx, 4);
+
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+		if (mode.empty())
+			throw server_error(server_error::invalid_mode);
+
+		self(ctx)->mode(
+			std::move(channel),
+			std::move(mode),
+			std::move(limit),
+			std::move(user),
+			std::move(mask)
+		);
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.names
+
+/*
+ * Method: Irccd.Server.prototype.names(channel)
+ * ------------------------------------------------------------------
+ *
+ * Get the list of names from a channel.
+ *
+ * Arguments:
+ *   - channel, the channel.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_names(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto channel = duk::require<std::string>(ctx, 0);
+
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->names(std::move(channel));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.nick
+
+/*
+ * Method: Irccd.Server.prototype.nick(nickname)
+ * ------------------------------------------------------------------
+ *
+ * Change the nickname.
+ *
+ * Arguments:
+ *   - nickname, the nickname.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_nick(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto nickname = duk::require<std::string>(ctx, 0);
+
+		if (nickname.empty())
+			throw server_error(server_error::invalid_nickname);
+
+		self(ctx)->set_nickname(std::move(nickname));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.notice
+
+/*
+ * Method: Irccd.Server.prototype.notice(target, message)
+ * ------------------------------------------------------------------
+ *
+ * Send a private notice.
+ *
+ * Arguments:
+ *   - target, the target,
+ *   - message, the notice message.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_notice(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+		auto message = duk::get<std::string>(ctx, 1);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+
+		self(ctx)->notice(std::move(target), std::move(message));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.part
+
+/*
+ * Method: Irccd.Server.prototype.part(channel, reason = undefined)
+ * ------------------------------------------------------------------
+ *
+ * Leave a channel.
+ *
+ * Arguments:
+ *   - channel, the channel to leave,
+ *   - reason, the optional reason, keep undefined for portability.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_part(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto channel = duk::require<std::string>(ctx, 0);
+		auto reason = duk::get<std::string>(ctx, 1);
+
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->part(std::move(channel), std::move(reason));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.send
+
+/*
+ * Method: Irccd.Server.prototype.send(raw)
+ * ------------------------------------------------------------------
+ *
+ * Send a raw message to the IRC server.
+ *
+ * Arguments:
+ *   - raw, the raw message (without terminators).
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_send(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto raw = duk::require<std::string>(ctx, 0);
+
+		if (raw.empty())
+			throw server_error(server_error::invalid_message);
+
+		self(ctx)->send(std::move(raw));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.topic
+
+/*
+ * Method: Server.prototype.topic(channel, topic)
+ * ------------------------------------------------------------------
+ *
+ * Change a channel topic.
+ *
+ * Arguments:
+ *   - channel, the channel,
+ *   - topic, the new topic.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_topic(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto channel = duk::require<std::string>(ctx, 0);
+		auto topic = duk::get<std::string>(ctx, 1);
+
+		if (channel.empty())
+			throw server_error(server_error::invalid_channel);
+
+		self(ctx)->topic(std::move(channel), std::move(topic));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.whois
+
+/*
+ * Method: Irccd.Server.prototype.whois(target)
+ * ------------------------------------------------------------------
+ *
+ * Get whois information.
+ *
+ * Arguments:
+ *   - target, the target.
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_prototype_whois(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto target = duk::require<std::string>(ctx, 0);
+
+		if (target.empty())
+			throw server_error(server_error::invalid_nickname);
+
+		self(ctx)->whois(std::move(target));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.prototype.toString
+
+/*
+ * Method: Irccd.Server.prototype.toString()
+ * ------------------------------------------------------------------
+ *
+ * Convert the object to std::string, convenience for adding the object
+ * as property key.
+ *
+ * Returns:
+ *   The server name (unique).
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_prototype_toString(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		duk::push(ctx, self(ctx)->get_id());
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server [constructor]
+
+/*
+ * Function: Irccd.Server(params) [constructor]
+ * ------------------------------------------------------------------
+ *
+ * Construct a new server.
+ *
+ * Params must be filled with the following properties:
+ *
+ * name: the name,
+ * hostname: the hostname,
+ * ipv6: true to use ipv6,      (Optional: default false)
+ * port: the port number,       (Optional: default 6667)
+ * password: the password,      (Optional: default none)
+ * channels: array of channels  (Optiona: default empty)
+ * ssl: true to use ssl,        (Optional: default false)
+ * nickname: "nickname",        (Optional, default: irccd)
+ * username: "user name",       (Optional, default: irccd)
+ * realname: "real name",       (Optional, default: IRC Client Daemon)
+ * commandChar: "!",            (Optional, the command char, default: "!")
+ *
+ * Arguments:
+ *   - params, the server properties
+ * Throws:
+ *   - Irccd.ServerError on server related errors,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Server_constructor(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		if (!duk_is_constructor_call(ctx))
+			return 0;
+
+		duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+
+		auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
+		auto s = from_json(duk::type_traits<bot>::self(ctx).get_service(), json);
+
+		duk_push_this(ctx);
+		duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
+		duk_put_prop_string(ctx, -2, signature.data());
+		duk_pop(ctx);
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server [destructor]
+
+/*
+ * Function: Irccd.Server() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Delete the property.
+ */
+auto Server_destructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_get_prop_string(ctx, 0, signature.data());
+	delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+	duk_del_prop_string(ctx, 0, signature.data());
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Server.add
+
+/*
+ * Function: Irccd.Server.add(s)
+ * ------------------------------------------------------------------
+ *
+ * Register a new server to the irccd instance.
+ *
+ * Arguments:
+ *   - s, the server to add.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_add(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		duk::type_traits<bot>::self(ctx).servers().add(
+			duk::require<std::shared_ptr<server>>(ctx, 0));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.find
+
+/*
+ * Function: Irccd.Server.find(name)
+ * ------------------------------------------------------------------
+ *
+ * Find a server by name.
+ *
+ * Arguments:
+ *   - name, the server name
+ * Returns:
+ *   The server object or undefined if not found.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Server_find(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [] (auto ctx) {
+		auto id = duk::require<std::string>(ctx, 0);
+		auto server = duk::type_traits<bot>::self(ctx).servers().get(id);
+
+		if (!server)
+			return 0;
+
+		duk::push(ctx, server);
+
+		return 1;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Server.list
+
+/*
+ * Function: Irccd.Server.list()
+ * ------------------------------------------------------------------
+ *
+ * Get the map of all loaded servers.
+ *
+ * Returns:
+ *   An object with string-to-servers pairs.
+ */
+auto Server_list(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_object(ctx);
+
+	for (const auto& server : duk::type_traits<bot>::self(ctx).servers().list()) {
+		duk::push(ctx, server);
+		duk_put_prop_string(ctx, -2, server->get_id().c_str());
+	}
+
+	return 1;
+}
+
+// }}}
+
+// {{{ Irccd.Server.remove
+
+/*
+ * Function: Irccd.Server.remove(name)
+ * ------------------------------------------------------------------
+ *
+ * Remove a server from the irccd instance. You can pass the server object since
+ * it's coercible to a string.
+ *
+ * Arguments:
+ *   - name the server name.
+ */
+auto Server_remove(duk_context* ctx) -> duk_ret_t
+{
+	duk::type_traits<bot>::self(ctx).servers().remove(duk_require_string(ctx, 0));
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.ServerError
+
+/*
+ * Function: Irccd.ServerError(code, message)
+ * ------------------------------------------------------------------
+ *
+ * Create an Irccd.ServerError object.
+ *
+ * Arguments:
+ *   - code, the error code,
+ *   - message, the error message.
+ */
+auto ServerError_constructor(duk_context* ctx) -> duk_ret_t
+{
+	duk_push_this(ctx);
+	duk_push_int(ctx, duk_require_int(ctx, 0));
+	duk_put_prop_string(ctx, -2, "code");
+	duk_push_string(ctx, duk_require_string(ctx, 1));
+	duk_put_prop_string(ctx, -2, "message");
+	duk_push_string(ctx, "ServerError");
+	duk_put_prop_string(ctx, -2, "name");
+	duk_pop(ctx);
+
+	return 0;
+}
+
+// }}}
+
+const duk_function_list_entry methods[] = {
+	{ "info",       Server_prototype_info,          0               },
+	{ "invite",     Server_prototype_invite,        2               },
+	{ "isSelf",     Server_prototype_isSelf,        1               },
+	{ "join",       Server_prototype_join,          DUK_VARARGS     },
+	{ "kick",       Server_prototype_kick,          DUK_VARARGS     },
+	{ "me",         Server_prototype_me,            2               },
+	{ "message",    Server_prototype_message,       2               },
+	{ "mode",       Server_prototype_mode,          1               },
+	{ "names",      Server_prototype_names,         1               },
+	{ "nick",       Server_prototype_nick,          1               },
+	{ "notice",     Server_prototype_notice,        2               },
+	{ "part",       Server_prototype_part,          DUK_VARARGS     },
+	{ "send",       Server_prototype_send,          1               },
+	{ "topic",      Server_prototype_topic,         2               },
+	{ "toString",   Server_prototype_toString,      0               },
+	{ "whois",      Server_prototype_whois,         1               },
+	{ nullptr,      nullptr,                        0               }
+};
+
+const duk_function_list_entry functions[] = {
+	{ "add",        Server_add,                     1               },
+	{ "find",       Server_find,                    1               },
+	{ "list",       Server_list,                    0               },
+	{ "remove",     Server_remove,                  1               },
+	{ nullptr,      nullptr,                        0               }
+};
+
+} // !namespace
+
+auto server_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Server";
+}
+
+void server_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+
+	// ServerError function.
+	duk_push_c_function(plugin->get_context(), ServerError_constructor, 2);
+	duk_push_object(plugin->get_context());
+	duk_get_global_string(plugin->get_context(), "Error");
+	duk_get_prop_string(plugin->get_context(), -1, "prototype");
+	duk_remove(plugin->get_context(), -2);
+	duk_set_prototype(plugin->get_context(), -2);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "ServerError");
+
+	// Server constructor.
+	duk_push_c_function(plugin->get_context(), Server_constructor, 1);
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, methods);
+	duk_push_c_function(plugin->get_context(), Server_destructor, 1);
+	duk_set_finalizer(plugin->get_context(), -2);
+	duk_dup_top(plugin->get_context());
+	duk_put_global_string(plugin->get_context(), prototype.data());
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "Server");
+	duk_pop(plugin->get_context());
+}
+
+namespace duk {
+
+void type_traits<std::shared_ptr<server>>::push(duk_context* ctx, std::shared_ptr<server> server)
+{
+	assert(ctx);
+	assert(server);
+
+	duk::stack_guard sa(ctx, 1);
+
+	duk_push_object(ctx);
+	duk_push_pointer(ctx, new std::shared_ptr<class server>(std::move(server)));
+	duk_put_prop_string(ctx, -2, signature.data());
+	duk_get_global_string(ctx, prototype.data());
+	duk_set_prototype(ctx, -2);
+}
+
+auto type_traits<std::shared_ptr<server>>::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<server>
+{
+	if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
+
+	duk_get_prop_string(ctx, index, signature.data());
+	auto file = *static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	return file;
+}
+
+void type_traits<server_error>::raise(duk_context* ctx, const server_error& ex)
+{
+	duk::stack_guard sa(ctx, 1);
+
+	duk_get_global_string(ctx, "Irccd");
+	duk_get_prop_string(ctx, -1, "ServerError");
+	duk_remove(ctx, -2);
+	duk::push(ctx, ex.code().value());
+	duk::push(ctx, ex.code().message());
+	duk_new(ctx, 2);
+
+	(void)duk_throw(ctx);
+}
+
+} // !duk
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/server_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,96 @@
+/*
+ * server_api.hpp -- Irccd.Server API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_SERVER_API_HPP
+#define IRCCD_JS_SERVER_API_HPP
+
+/**
+ * \file server_api.hpp
+ * \brief Irccd.Server Javascript API.
+ */
+
+#include <irccd/daemon/server.hpp>
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.Server Javascript API.
+ */
+class server_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+namespace duk {
+
+/**
+ * \brief Specialization for servers as shared_ptr.
+ *
+ * Supports push, require.
+ */
+template <>
+struct type_traits<std::shared_ptr<daemon::server>> {
+	/**
+	 * Push a server.
+	 *
+	 * \pre server != nullptr
+	 * \param ctx the context
+	 * \param server the server
+	 */
+	static void push(duk_context* ctx, std::shared_ptr<daemon::server> server);
+
+	/**
+	 * Require a server. Raise a Javascript error if not a Server.
+	 *
+	 * \param ctx the context
+	 * \param index the index
+	 * \return the server
+	 */
+	static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<daemon::server>;
+};
+
+/**
+ * \brief Specialization for server_error.
+ */
+template <>
+struct type_traits<daemon::server_error> {
+	/**
+	 * Raise a server_error.
+	 *
+	 * \param ctx the context
+	 * \param error the error
+	 */
+	static void raise(duk_context* ctx, const daemon::server_error& error);
+};
+
+} // !duk
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_SERVER_API_HPP
--- a/libirccd-js/irccd/js/server_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,898 +0,0 @@
-/*
- * server_js_api.cpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2018 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 <cassert>
-#include <sstream>
-#include <unordered_map>
-
-#include <irccd/daemon/bot.hpp>
-#include <irccd/daemon/server_service.hpp>
-#include <irccd/daemon/server_util.hpp>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "server_js_api.hpp"
-
-using irccd::daemon::bot;
-using irccd::daemon::server;
-using irccd::daemon::server_error;
-using irccd::daemon::server_util::from_json;
-
-namespace irccd::js {
-
-namespace {
-
-const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Server"));
-const std::string_view prototype(DUK_HIDDEN_SYMBOL("Irccd.Server.prototype"));
-
-auto self(duk_context* ctx) -> std::shared_ptr<server>
-{
-	duk::stack_guard sa(ctx);
-
-	duk_push_this(ctx);
-	duk_get_prop_string(ctx, -1, signature.data());
-	auto ptr = duk_to_pointer(ctx, -1);
-	duk_pop_2(ctx);
-
-	if (!ptr)
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
-
-	return *static_cast<std::shared_ptr<server>*>(ptr);
-}
-
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
-{
-	try {
-		return handler(ctx);
-	} catch (const server_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// {{{ Irccd.Server.prototype.info
-
-/*
- * Method: Irccd.Server.prototype.info()
- * ------------------------------------------------------------------
- *
- * Get the server information as an object containing the following properties:
- *
- * name: the server unique name
- * host: the host name
- * port: the port number
- * ssl: true if using ssl
- * sslVerify: true if ssl was verified
- * channels: an array of all channels
- */
-auto Server_prototype_info(duk_context* ctx) -> duk_ret_t
-{
-	const auto server = self(ctx);
-	const auto& channels = server->get_channels();
-
-	duk_push_object(ctx);
-	duk::push(ctx, server->get_id());
-	duk_put_prop_string(ctx, -2, "name");
-	duk::push(ctx, server->get_hostname());
-	duk_put_prop_string(ctx, -2, "hostname");
-	duk_push_int(ctx, server->get_port());
-	duk_put_prop_string(ctx, -2, "port");
-	duk_push_boolean(ctx, (server->get_options() & server::options::ssl) == server::options::ssl);
-	duk_put_prop_string(ctx, -2, "ssl");
-	duk::push(ctx, server->get_command_char());
-	duk_put_prop_string(ctx, -2, "commandChar");
-	duk::push(ctx, server->get_realname());
-	duk_put_prop_string(ctx, -2, "realname");
-	duk::push(ctx, server->get_nickname());
-	duk_put_prop_string(ctx, -2, "nickname");
-	duk::push(ctx, server->get_username());
-	duk_put_prop_string(ctx, -2, "username");
-	duk::push(ctx, std::vector<std::string>(channels.begin(), channels.end()));
-	duk_put_prop_string(ctx, -2, "channels");
-
-	return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.invite
-
-/*
- * Method: Irccd.Server.prototype.invite(target, channel)
- * ------------------------------------------------------------------
- *
- * Invite someone to a channel.
- *
- * Arguments:
- *   - target, the target to invite,
- *   - channel, the channel.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_invite(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-		auto channel = duk::require<std::string>(ctx, 1);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->invite(std::move(target), std::move(channel));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.isSelf
-
-/*
- * Method: Irccd.Server.prototype.isSelf(nickname)
- * ------------------------------------------------------------------
- *
- * Arguments:
- *   - nickname, the nickname to check.
- * Returns:
- *   True if the nickname targets this server.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Server_prototype_isSelf(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		return duk::push(ctx, self(ctx)->is_self(duk::require<std::string>(ctx, 0)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.join
-
-/*
- * Method: Irccd.Server.prototype.join(channel, password = undefined)
- * ------------------------------------------------------------------
- *
- * Join a channel with an optional password.
- *
- * Arguments:
- *   - channel, the channel to join,
- *   - password, the password or undefined to not use.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_join(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto channel = duk::require<std::string>(ctx, 0);
-		auto password = duk::get<std::string>(ctx, 1);
-
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->join(std::move(channel), std::move(password));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.kick
-
-/*
- * Method: Irccd.Server.prototype.kick(target, channel, reason = undefined)
- * ------------------------------------------------------------------
- *
- * Kick someone from a channel.
- *
- * Arguments:
- *   - target, the target to kick,
- *   - channel, the channel,
- *   - reason, the optional reason or undefined to not set.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_kick(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-		auto channel = duk::require<std::string>(ctx, 1);
-		auto reason = duk::get<std::string>(ctx, 2);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->kick(std::move(target), std::move(channel), std::move(reason));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.me
-
-/*
- * Method: Irccd.Server.prototype.me(target, message)
- * ------------------------------------------------------------------
- *
- * Send a CTCP Action.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_me(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-		auto message = duk::get<std::string>(ctx, 1);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-
-		self(ctx)->me(std::move(target), std::move(message));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.message
-
-/*
- * Method: Irccd.Server.prototype.message(target, message)
- * ------------------------------------------------------------------
- *
- * Send a message.
- *
- * Arguments:
- *   - target, the target or a channel,
- *   - message, the message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_message(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-		auto message = duk::get<std::string>(ctx, 1);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-
-		self(ctx)->message(std::move(target), std::move(message));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.mode
-
-/*
- * Method: Irccd.Server.prototype.mode(channel, mode, limit, user, mask)
- * ------------------------------------------------------------------
- *
- * Change your mode.
- *
- * Arguments:
- *   - mode, the new mode.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_mode(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto channel = duk::require<std::string>(ctx, 0);
-		auto mode = duk::require<std::string>(ctx, 1);
-		auto limit = duk::get<std::string>(ctx, 2);
-		auto user = duk::get<std::string>(ctx, 3);
-		auto mask = duk::get<std::string>(ctx, 4);
-
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-		if (mode.empty())
-			throw server_error(server_error::invalid_mode);
-
-		self(ctx)->mode(
-			std::move(channel),
-			std::move(mode),
-			std::move(limit),
-			std::move(user),
-			std::move(mask)
-		);
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.names
-
-/*
- * Method: Irccd.Server.prototype.names(channel)
- * ------------------------------------------------------------------
- *
- * Get the list of names from a channel.
- *
- * Arguments:
- *   - channel, the channel.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_names(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto channel = duk::require<std::string>(ctx, 0);
-
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->names(std::move(channel));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.nick
-
-/*
- * Method: Irccd.Server.prototype.nick(nickname)
- * ------------------------------------------------------------------
- *
- * Change the nickname.
- *
- * Arguments:
- *   - nickname, the nickname.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_nick(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto nickname = duk::require<std::string>(ctx, 0);
-
-		if (nickname.empty())
-			throw server_error(server_error::invalid_nickname);
-
-		self(ctx)->set_nickname(std::move(nickname));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.notice
-
-/*
- * Method: Irccd.Server.prototype.notice(target, message)
- * ------------------------------------------------------------------
- *
- * Send a private notice.
- *
- * Arguments:
- *   - target, the target,
- *   - message, the notice message.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_notice(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-		auto message = duk::get<std::string>(ctx, 1);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-
-		self(ctx)->notice(std::move(target), std::move(message));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.part
-
-/*
- * Method: Irccd.Server.prototype.part(channel, reason = undefined)
- * ------------------------------------------------------------------
- *
- * Leave a channel.
- *
- * Arguments:
- *   - channel, the channel to leave,
- *   - reason, the optional reason, keep undefined for portability.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_part(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto channel = duk::require<std::string>(ctx, 0);
-		auto reason = duk::get<std::string>(ctx, 1);
-
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->part(std::move(channel), std::move(reason));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.send
-
-/*
- * Method: Irccd.Server.prototype.send(raw)
- * ------------------------------------------------------------------
- *
- * Send a raw message to the IRC server.
- *
- * Arguments:
- *   - raw, the raw message (without terminators).
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_send(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto raw = duk::require<std::string>(ctx, 0);
-
-		if (raw.empty())
-			throw server_error(server_error::invalid_message);
-
-		self(ctx)->send(std::move(raw));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.topic
-
-/*
- * Method: Server.prototype.topic(channel, topic)
- * ------------------------------------------------------------------
- *
- * Change a channel topic.
- *
- * Arguments:
- *   - channel, the channel,
- *   - topic, the new topic.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_topic(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto channel = duk::require<std::string>(ctx, 0);
-		auto topic = duk::get<std::string>(ctx, 1);
-
-		if (channel.empty())
-			throw server_error(server_error::invalid_channel);
-
-		self(ctx)->topic(std::move(channel), std::move(topic));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.whois
-
-/*
- * Method: Irccd.Server.prototype.whois(target)
- * ------------------------------------------------------------------
- *
- * Get whois information.
- *
- * Arguments:
- *   - target, the target.
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_prototype_whois(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto target = duk::require<std::string>(ctx, 0);
-
-		if (target.empty())
-			throw server_error(server_error::invalid_nickname);
-
-		self(ctx)->whois(std::move(target));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.prototype.toString
-
-/*
- * Method: Irccd.Server.prototype.toString()
- * ------------------------------------------------------------------
- *
- * Convert the object to std::string, convenience for adding the object
- * as property key.
- *
- * Returns:
- *   The server name (unique).
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Server_prototype_toString(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		duk::push(ctx, self(ctx)->get_id());
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server [constructor]
-
-/*
- * Function: Irccd.Server(params) [constructor]
- * ------------------------------------------------------------------
- *
- * Construct a new server.
- *
- * Params must be filled with the following properties:
- *
- * name: the name,
- * hostname: the hostname,
- * ipv6: true to use ipv6,      (Optional: default false)
- * port: the port number,       (Optional: default 6667)
- * password: the password,      (Optional: default none)
- * channels: array of channels  (Optiona: default empty)
- * ssl: true to use ssl,        (Optional: default false)
- * nickname: "nickname",        (Optional, default: irccd)
- * username: "user name",       (Optional, default: irccd)
- * realname: "real name",       (Optional, default: IRC Client Daemon)
- * commandChar: "!",            (Optional, the command char, default: "!")
- *
- * Arguments:
- *   - params, the server properties
- * Throws:
- *   - Irccd.ServerError on server related errors,
- *   - Irccd.SystemError on other errors.
- */
-auto Server_constructor(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		if (!duk_is_constructor_call(ctx))
-			return 0;
-
-		duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
-
-		auto json = nlohmann::json::parse(duk_json_encode(ctx, 0));
-		auto s = from_json(duk::type_traits<bot>::self(ctx).get_service(), json);
-
-		duk_push_this(ctx);
-		duk_push_pointer(ctx, new std::shared_ptr<server>(std::move(s)));
-		duk_put_prop_string(ctx, -2, signature.data());
-		duk_pop(ctx);
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server [destructor]
-
-/*
- * Function: Irccd.Server() [destructor]
- * ------------------------------------------------------------------
- *
- * Delete the property.
- */
-auto Server_destructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_get_prop_string(ctx, 0, signature.data());
-	delete static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-	duk_del_prop_string(ctx, 0, signature.data());
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Server.add
-
-/*
- * Function: Irccd.Server.add(s)
- * ------------------------------------------------------------------
- *
- * Register a new server to the irccd instance.
- *
- * Arguments:
- *   - s, the server to add.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Server_add(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		duk::type_traits<bot>::self(ctx).servers().add(
-			duk::require<std::shared_ptr<server>>(ctx, 0));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.find
-
-/*
- * Function: Irccd.Server.find(name)
- * ------------------------------------------------------------------
- *
- * Find a server by name.
- *
- * Arguments:
- *   - name, the server name
- * Returns:
- *   The server object or undefined if not found.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Server_find(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [] (auto ctx) {
-		auto id = duk::require<std::string>(ctx, 0);
-		auto server = duk::type_traits<bot>::self(ctx).servers().get(id);
-
-		if (!server)
-			return 0;
-
-		duk::push(ctx, server);
-
-		return 1;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Server.list
-
-/*
- * Function: Irccd.Server.list()
- * ------------------------------------------------------------------
- *
- * Get the map of all loaded servers.
- *
- * Returns:
- *   An object with string-to-servers pairs.
- */
-auto Server_list(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_object(ctx);
-
-	for (const auto& server : duk::type_traits<bot>::self(ctx).servers().list()) {
-		duk::push(ctx, server);
-		duk_put_prop_string(ctx, -2, server->get_id().c_str());
-	}
-
-	return 1;
-}
-
-// }}}
-
-// {{{ Irccd.Server.remove
-
-/*
- * Function: Irccd.Server.remove(name)
- * ------------------------------------------------------------------
- *
- * Remove a server from the irccd instance. You can pass the server object since
- * it's coercible to a string.
- *
- * Arguments:
- *   - name the server name.
- */
-auto Server_remove(duk_context* ctx) -> duk_ret_t
-{
-	duk::type_traits<bot>::self(ctx).servers().remove(duk_require_string(ctx, 0));
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.ServerError
-
-/*
- * Function: Irccd.ServerError(code, message)
- * ------------------------------------------------------------------
- *
- * Create an Irccd.ServerError object.
- *
- * Arguments:
- *   - code, the error code,
- *   - message, the error message.
- */
-auto ServerError_constructor(duk_context* ctx) -> duk_ret_t
-{
-	duk_push_this(ctx);
-	duk_push_int(ctx, duk_require_int(ctx, 0));
-	duk_put_prop_string(ctx, -2, "code");
-	duk_push_string(ctx, duk_require_string(ctx, 1));
-	duk_put_prop_string(ctx, -2, "message");
-	duk_push_string(ctx, "ServerError");
-	duk_put_prop_string(ctx, -2, "name");
-	duk_pop(ctx);
-
-	return 0;
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-	{ "info",       Server_prototype_info,          0               },
-	{ "invite",     Server_prototype_invite,        2               },
-	{ "isSelf",     Server_prototype_isSelf,        1               },
-	{ "join",       Server_prototype_join,          DUK_VARARGS     },
-	{ "kick",       Server_prototype_kick,          DUK_VARARGS     },
-	{ "me",         Server_prototype_me,            2               },
-	{ "message",    Server_prototype_message,       2               },
-	{ "mode",       Server_prototype_mode,          1               },
-	{ "names",      Server_prototype_names,         1               },
-	{ "nick",       Server_prototype_nick,          1               },
-	{ "notice",     Server_prototype_notice,        2               },
-	{ "part",       Server_prototype_part,          DUK_VARARGS     },
-	{ "send",       Server_prototype_send,          1               },
-	{ "topic",      Server_prototype_topic,         2               },
-	{ "toString",   Server_prototype_toString,      0               },
-	{ "whois",      Server_prototype_whois,         1               },
-	{ nullptr,      nullptr,                        0               }
-};
-
-const duk_function_list_entry functions[] = {
-	{ "add",        Server_add,                     1               },
-	{ "find",       Server_find,                    1               },
-	{ "list",       Server_list,                    0               },
-	{ "remove",     Server_remove,                  1               },
-	{ nullptr,      nullptr,                        0               }
-};
-
-} // !namespace
-
-auto server_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Server";
-}
-
-void server_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-
-	// ServerError function.
-	duk_push_c_function(plugin->get_context(), ServerError_constructor, 2);
-	duk_push_object(plugin->get_context());
-	duk_get_global_string(plugin->get_context(), "Error");
-	duk_get_prop_string(plugin->get_context(), -1, "prototype");
-	duk_remove(plugin->get_context(), -2);
-	duk_set_prototype(plugin->get_context(), -2);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "ServerError");
-
-	// Server constructor.
-	duk_push_c_function(plugin->get_context(), Server_constructor, 1);
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, methods);
-	duk_push_c_function(plugin->get_context(), Server_destructor, 1);
-	duk_set_finalizer(plugin->get_context(), -2);
-	duk_dup_top(plugin->get_context());
-	duk_put_global_string(plugin->get_context(), prototype.data());
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "Server");
-	duk_pop(plugin->get_context());
-}
-
-namespace duk {
-
-void type_traits<std::shared_ptr<server>>::push(duk_context* ctx, std::shared_ptr<server> server)
-{
-	assert(ctx);
-	assert(server);
-
-	duk::stack_guard sa(ctx, 1);
-
-	duk_push_object(ctx);
-	duk_push_pointer(ctx, new std::shared_ptr<class server>(std::move(server)));
-	duk_put_prop_string(ctx, -2, signature.data());
-	duk_get_global_string(ctx, prototype.data());
-	duk_set_prototype(ctx, -2);
-}
-
-auto type_traits<std::shared_ptr<server>>::require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<server>
-{
-	if (!duk_is_object(ctx, index) || !duk_has_prop_string(ctx, index, signature.data()))
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Server object");
-
-	duk_get_prop_string(ctx, index, signature.data());
-	auto file = *static_cast<std::shared_ptr<server>*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-
-	return file;
-}
-
-void type_traits<server_error>::raise(duk_context* ctx, const server_error& ex)
-{
-	duk::stack_guard sa(ctx, 1);
-
-	duk_get_global_string(ctx, "Irccd");
-	duk_get_prop_string(ctx, -1, "ServerError");
-	duk_remove(ctx, -2);
-	duk::push(ctx, ex.code().value());
-	duk::push(ctx, ex.code().message());
-	duk_new(ctx, 2);
-
-	(void)duk_throw(ctx);
-}
-
-} // !duk
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/server_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-/*
- * server_js_api.hpp -- Irccd.Server API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_SERVER_JS_API_HPP
-#define IRCCD_JS_SERVER_JS_API_HPP
-
-/**
- * \file server_js_api.hpp
- * \brief Irccd.Server Javascript API.
- */
-
-#include <irccd/daemon/server.hpp>
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.Server Javascript API.
- */
-class server_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-namespace duk {
-
-/**
- * \brief Specialization for servers as shared_ptr.
- *
- * Supports push, require.
- */
-template <>
-struct type_traits<std::shared_ptr<daemon::server>> {
-	/**
-	 * Push a server.
-	 *
-	 * \pre server != nullptr
-	 * \param ctx the context
-	 * \param server the server
-	 */
-	static void push(duk_context* ctx, std::shared_ptr<daemon::server> server);
-
-	/**
-	 * Require a server. Raise a Javascript error if not a Server.
-	 *
-	 * \param ctx the context
-	 * \param index the index
-	 * \return the server
-	 */
-	static auto require(duk_context* ctx, duk_idx_t index) -> std::shared_ptr<daemon::server>;
-};
-
-/**
- * \brief Specialization for server_error.
- */
-template <>
-struct type_traits<daemon::server_error> {
-	/**
-	 * Raise a server_error.
-	 *
-	 * \param ctx the context
-	 * \param error the error
-	 */
-	static void raise(duk_context* ctx, const daemon::server_error& error);
-};
-
-} // !duk
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_SERVER_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,332 @@
+/*
+ * system_api.cpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
+
+#include <chrono>
+#include <cstdlib>
+#include <thread>
+
+#if defined(IRCCD_HAVE_POPEN)
+#  include <cstdio>
+#endif
+
+#include <irccd/system.hpp>
+
+#include "file_api.hpp"
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "system_api.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+	try {
+		return handler();
+	} catch (const std::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.System.env
+
+/*
+ * Function: Irccd.System.env(key)
+ * ------------------------------------------------------------------
+ *
+ * Get an environment system variable.
+ *
+ * Arguments:
+ *   - key, the environment variable.
+ * Returns:
+ *   The value.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_env(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, sys::env(duk::get<std::string>(ctx, 0)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.exec
+
+/*
+ * Function: Irccd.System.exec(cmd)
+ * ------------------------------------------------------------------
+ *
+ * Execute a system command.
+ *
+ * Arguments:
+ *   - cmd, the command to execute.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_exec(duk_context* ctx) -> duk_ret_t
+{
+	std::system(duk_require_string(ctx, 0));
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.System.home
+
+/*
+ * Function: Irccd.System.home()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system user's home.
+ *
+ * Returns:
+ *   The user home directory.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_home(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, sys::home());
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.name
+
+/*
+ * Function: Irccd.System.name()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system name.
+ *
+ * Returns:
+ *   The system name.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_name(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, sys::name());
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.popen
+
+#if defined(IRCCD_HAVE_POPEN)
+
+/*
+ * Function: Irccd.System.popen(cmd, mode) [optional]
+ * ------------------------------------------------------------------
+ *
+ * Wrapper for popen(3) if the function is available.
+ *
+ * Arguments:
+ *   - cmd, the command to execute,
+ *   - mode, the mode (e.g. "r").
+ * Returns:
+ *   A irccd.File object.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_popen(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
+
+		if (fp == nullptr)
+			throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
+
+		return duk::push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
+	});
+}
+
+#endif // !IRCCD_HAVE_POPEN
+
+// }}}
+
+// {{{ Icrcd.System.sleep
+
+/*
+ * Function: Irccd.System.sleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in seconds.
+ *
+ * Arguments:
+ *   - delay, the delay in seconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_sleep(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.ticks
+
+/*
+ * Function: Irccd.System.ticks()
+ * ------------------------------------------------------------------
+ *
+ * Get the number of milliseconds since irccd was started.
+ *
+ * Returns:
+ *   The number of milliseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_ticks(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push<unsigned>(ctx, sys::ticks());
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.usleep
+
+/*
+ * Function: Irccd.System.usleep(delay)
+ * ------------------------------------------------------------------
+ *
+ * Sleep the main loop for the specific delay in microseconds.
+ *
+ * Arguments:
+ *   - delay, the delay in microseconds.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_usleep(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
+
+		return 0;
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.uptime
+
+/*
+ * Function: Irccd.System.uptime()
+ * ------------------------------------------------------------------
+ *
+ * Get the system uptime.
+ *
+ * Returns:
+ *   The system uptime.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_uptime(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push<unsigned>(ctx, sys::uptime());
+	});
+}
+
+// }}}
+
+// {{{ Irccd.System.version
+
+/*
+ * Function: Irccd.System.version()
+ * ------------------------------------------------------------------
+ *
+ * Get the operating system version.
+ *
+ * Returns:
+ *   The system version.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto System_version(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, sys::version());
+	});
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+	{ "env",        System_env,     1 },
+	{ "exec",       System_exec,    1 },
+	{ "home",       System_home,    0 },
+	{ "name",       System_name,    0 },
+#if defined(IRCCD_HAVE_POPEN)
+	{ "popen",      System_popen,   2 },
+#endif
+	{ "sleep",      System_sleep,   1 },
+	{ "ticks",      System_ticks,   0 },
+	{ "uptime",     System_uptime,  0 },
+	{ "usleep",     System_usleep,  1 },
+	{ "version",    System_version, 0 },
+	{ nullptr,      nullptr,        0 }
+};
+
+} // !namespace
+
+auto system_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.System";
+}
+
+void system_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_put_prop_string(plugin->get_context(), -2, "System");
+	duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/system_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * system_api.hpp -- Irccd.System API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_SYSTEM_API_HPP
+#define IRCCD_JS_SYSTEM_API_HPP
+
+/**
+ * \file system_api.hpp
+ * \brief Irccd.System Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.System Javascript API.
+ */
+class system_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_SYSTEM_API_HPP
--- a/libirccd-js/irccd/js/system_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,332 +0,0 @@
-/*
- * system_js_api.cpp -- Irccd.System API
- *
- * Copyright (c) 2013-2018 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 <irccd/sysconfig.hpp>
-
-#include <chrono>
-#include <cstdlib>
-#include <thread>
-
-#if defined(IRCCD_HAVE_POPEN)
-#  include <cstdio>
-#endif
-
-#include <irccd/system.hpp>
-
-#include "file_js_api.hpp"
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "system_js_api.hpp"
-
-using irccd::daemon::bot;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ wrap
-
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
-{
-	try {
-		return handler();
-	} catch (const std::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.System.env
-
-/*
- * Function: Irccd.System.env(key)
- * ------------------------------------------------------------------
- *
- * Get an environment system variable.
- *
- * Arguments:
- *   - key, the environment variable.
- * Returns:
- *   The value.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_env(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, sys::env(duk::get<std::string>(ctx, 0)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.exec
-
-/*
- * Function: Irccd.System.exec(cmd)
- * ------------------------------------------------------------------
- *
- * Execute a system command.
- *
- * Arguments:
- *   - cmd, the command to execute.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_exec(duk_context* ctx) -> duk_ret_t
-{
-	std::system(duk_require_string(ctx, 0));
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.System.home
-
-/*
- * Function: Irccd.System.home()
- * ------------------------------------------------------------------
- *
- * Get the operating system user's home.
- *
- * Returns:
- *   The user home directory.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_home(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, sys::home());
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.name
-
-/*
- * Function: Irccd.System.name()
- * ------------------------------------------------------------------
- *
- * Get the operating system name.
- *
- * Returns:
- *   The system name.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_name(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, sys::name());
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.popen
-
-#if defined(IRCCD_HAVE_POPEN)
-
-/*
- * Function: Irccd.System.popen(cmd, mode) [optional]
- * ------------------------------------------------------------------
- *
- * Wrapper for popen(3) if the function is available.
- *
- * Arguments:
- *   - cmd, the command to execute,
- *   - mode, the mode (e.g. "r").
- * Returns:
- *   A irccd.File object.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_popen(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		auto fp = ::popen(duk_require_string(ctx, 0), duk_require_string(ctx, 1));
-
-		if (fp == nullptr)
-			throw std::system_error(make_error_code(static_cast<std::errc>(errno)));
-
-		return duk::push(ctx, std::make_shared<file>(fp, [] (auto fp) { ::pclose(fp); }));
-	});
-}
-
-#endif // !IRCCD_HAVE_POPEN
-
-// }}}
-
-// {{{ Icrcd.System.sleep
-
-/*
- * Function: Irccd.System.sleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in seconds.
- *
- * Arguments:
- *   - delay, the delay in seconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_sleep(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		std::this_thread::sleep_for(std::chrono::seconds(duk_get_int(ctx, 0)));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.ticks
-
-/*
- * Function: Irccd.System.ticks()
- * ------------------------------------------------------------------
- *
- * Get the number of milliseconds since irccd was started.
- *
- * Returns:
- *   The number of milliseconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_ticks(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push<unsigned>(ctx, sys::ticks());
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.usleep
-
-/*
- * Function: Irccd.System.usleep(delay)
- * ------------------------------------------------------------------
- *
- * Sleep the main loop for the specific delay in microseconds.
- *
- * Arguments:
- *   - delay, the delay in microseconds.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_usleep(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		std::this_thread::sleep_for(std::chrono::microseconds(duk_get_int(ctx, 0)));
-
-		return 0;
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.uptime
-
-/*
- * Function: Irccd.System.uptime()
- * ------------------------------------------------------------------
- *
- * Get the system uptime.
- *
- * Returns:
- *   The system uptime.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_uptime(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push<unsigned>(ctx, sys::uptime());
-	});
-}
-
-// }}}
-
-// {{{ Irccd.System.version
-
-/*
- * Function: Irccd.System.version()
- * ------------------------------------------------------------------
- *
- * Get the operating system version.
- *
- * Returns:
- *   The system version.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto System_version(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, sys::version());
-	});
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-	{ "env",        System_env,     1 },
-	{ "exec",       System_exec,    1 },
-	{ "home",       System_home,    0 },
-	{ "name",       System_name,    0 },
-#if defined(IRCCD_HAVE_POPEN)
-	{ "popen",      System_popen,   2 },
-#endif
-	{ "sleep",      System_sleep,   1 },
-	{ "ticks",      System_ticks,   0 },
-	{ "uptime",     System_uptime,  0 },
-	{ "usleep",     System_usleep,  1 },
-	{ "version",    System_version, 0 },
-	{ nullptr,      nullptr,        0 }
-};
-
-} // !namespace
-
-auto system_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.System";
-}
-
-void system_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_put_prop_string(plugin->get_context(), -2, "System");
-	duk_pop(plugin->get_context());
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/system_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * system_js_api.hpp -- Irccd.System API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_SYSTEM_JS_API_HPP
-#define IRCCD_JS_SYSTEM_JS_API_HPP
-
-/**
- * \file system_js_api.hpp
- * \brief Irccd.System Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.System Javascript API.
- */
-class system_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_SYSTEM_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,316 @@
+/*
+ * timer_api.cpp -- Irccd.timer API
+ *
+ * Copyright (c) 2013-2018 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 <boost/asio.hpp>
+
+#include <irccd/daemon/bot.hpp>
+#include <irccd/daemon/logger.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "plugin_api.hpp"
+#include "timer_api.hpp"
+
+namespace asio = boost::asio;
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Timer"));
+const std::string_view table(DUK_HIDDEN_SYMBOL("Irccd.Timer.callbacks"));
+
+// {{{ timer
+
+class timer : public std::enable_shared_from_this<timer> {
+public:
+	enum class type {
+		single,
+		repeat
+	};
+
+private:
+	boost::asio::deadline_timer handle_;
+	plugin& plugin_;
+
+	std::string key_;
+	type type_;
+	int delay_;
+
+	bool is_running_{false};
+	bool is_waiting_{false};
+
+	void handle();
+
+public:
+	timer(boost::asio::io_service&, plugin&, type, int);
+
+	auto key() const noexcept -> const std::string&;
+
+	void start();
+
+	void stop();
+};
+
+void timer::handle()
+{
+	duk::stack_guard sa(plugin_.get_context());
+
+	duk_push_global_stash(plugin_.get_context());
+	duk_get_prop_string(plugin_.get_context(), -1, table.data());
+	duk_remove(plugin_.get_context(), -2);
+	duk_get_prop_string(plugin_.get_context(), -1, key_.c_str());
+	duk_remove(plugin_.get_context(), -2);
+
+	if (duk_pcall(plugin_.get_context(), 0)) {
+		auto& log = duk::type_traits<bot>::self(plugin_.get_context()).get_log();
+
+		log.warning(static_cast<const daemon::plugin&>(plugin_)) << "timer error:" << std::endl;
+		log.warning(static_cast<const daemon::plugin&>(plugin_)) << "  " << duk::get_stack(plugin_.get_context(), -1).what() << std::endl;
+	} else
+		duk_pop(plugin_.get_context());
+}
+
+timer::timer(boost::asio::io_service& service, plugin& plugin, type type, int delay)
+	: handle_(service)
+	, plugin_(plugin)
+	, type_(type)
+	, delay_(delay)
+{
+}
+
+auto timer::key() const noexcept -> const std::string&
+{
+	return key_;
+}
+
+void timer::start()
+{
+	if (is_waiting_)
+		return;
+
+	is_running_ = is_waiting_ = true;
+
+	handle_.expires_from_now(boost::posix_time::milliseconds(delay_));
+	handle_.async_wait([this] (auto code) {
+		is_waiting_ = false;
+
+		if (code) {
+			is_running_ = false;
+			return;
+		}
+
+		handle();
+
+		if (is_running_ && type_ == type::repeat)
+			start();
+	});
+}
+
+void timer::stop()
+{
+	if (is_running_) {
+		handle_.cancel();
+		is_running_ = false;
+	}
+}
+
+// }}}
+
+// {{{ self
+
+auto self(duk_context* ctx) -> timer*
+{
+	duk::stack_guard sa(ctx);
+
+	duk_push_this(ctx);
+	duk_get_prop_string(ctx, -1, signature.data());
+	auto ptr = duk_to_pointer(ctx, -1);
+	duk_pop_2(ctx);
+
+	if (!ptr)
+		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object");
+
+	return static_cast<timer*>(ptr);
+}
+
+// }}}
+
+// {{{ Irccd.Timer.prototype.start
+
+/*
+ * Method: Irccd.Timer.prototype.start()
+ * --------------------------------------------------------
+ *
+ * Start the timer. If the timer is already started the method is a no-op.
+ */
+auto Timer_prototype_start(duk_context* ctx) -> duk_ret_t
+{
+	self(ctx)->start();
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer.prototype.stop
+
+/*
+ * Method: Irccd.Timer.prototype.stop()
+ * --------------------------------------------------------
+ *
+ * Stop the timer.
+ */
+auto Timer_prototype_stop(duk_context* ctx) -> duk_ret_t
+{
+	self(ctx)->stop();
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer [destructor]
+
+/*
+ * Function: Irccd.Timer() [destructor]
+ * ------------------------------------------------------------------
+ *
+ * Deleter the timer.
+ */
+auto Timer_destructor(duk_context* ctx) -> duk_ret_t
+{
+	duk::stack_guard sa(ctx);
+
+	// Get timer from this.
+	duk_get_prop_string(ctx, 0, signature.data());
+	auto ptr = static_cast<timer*>(duk_to_pointer(ctx, -1));
+	duk_pop(ctx);
+
+	// Remove callback from timer table.
+	duk_push_global_stash(ctx);
+	duk_get_prop_string(ctx, -1, table.data());
+	duk_remove(ctx, -2);
+	duk_del_prop_string(ctx, -1, ptr->key().c_str());
+	duk_pop(ctx);
+
+	delete ptr;
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Timer [constructor]
+
+/*
+ * Function: Irccd.Timer(type, delay, callback) [constructor]
+ * --------------------------------------------------------
+ *
+ * Create a new timer object.
+ *
+ * Arguments:
+ *   - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat),
+ *   - delay, the interval in milliseconds,
+ *   - callback, the function to call.
+ */
+auto Timer_constructor(duk_context* ctx) -> duk_ret_t
+{
+	if (!duk_is_constructor_call(ctx))
+		return 0;
+
+	try {
+		// Check parameters.
+		const auto type = duk_require_int(ctx, 0);
+		const auto delay = duk_require_int(ctx, 1);
+
+		if (type < static_cast<int>(timer::type::single) || type > static_cast<int>(timer::type::repeat))
+			duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
+		if (delay < 0)
+			duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
+		if (!duk_is_callable(ctx, 2))
+			duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
+
+		auto& plg = duk::type_traits<plugin>::self(ctx);
+		auto& daemon = duk::type_traits<bot>::self(ctx);
+		auto object = new timer(daemon.get_service(), plg, static_cast<timer::type>(type), delay);
+
+		duk_push_this(ctx);
+		duk_push_pointer(ctx, object);
+		duk_put_prop_string(ctx, -2, signature.data());
+		duk_push_c_function(ctx, Timer_destructor, 1);
+		duk_set_finalizer(ctx, -2);
+		duk_pop(ctx);
+
+		// Store the function in a table to be called later.
+		duk_push_global_stash(ctx);
+		duk_get_prop_string(ctx, -1, table.data());
+		duk_remove(ctx, -2);
+		duk_dup(ctx, 2);
+		duk_put_prop_string(ctx, -2, object->key().c_str());
+		duk_pop(ctx);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+const duk_function_list_entry methods[] = {
+	{ "start",      Timer_prototype_start,  0               },
+	{ "stop",       Timer_prototype_stop,   0               },
+	{ nullptr,      nullptr,                0               }
+};
+
+const duk_number_list_entry constants[] = {
+	{ "Single",     static_cast<int>(timer::type::single)   },
+	{ "Repeat",     static_cast<int>(timer::type::repeat)   },
+	{ nullptr,      0                                       }
+};
+
+} // !namespace
+
+auto timer_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Timer";
+}
+
+void timer_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_c_function(plugin->get_context(), Timer_constructor, 3);
+	duk_put_number_list(plugin->get_context(), -1, constants);
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, methods);
+	duk_put_prop_string(plugin->get_context(), -2, "prototype");
+	duk_put_prop_string(plugin->get_context(), -2, "Timer");
+	duk_pop(plugin->get_context());
+	duk_push_global_stash(plugin->get_context());
+	duk_push_object(plugin->get_context());
+	duk_put_prop_string(plugin->get_context(), -2, table.data());
+	duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/timer_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * timer_api.hpp -- Irccd.Timer API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_TIMER_API_HPP
+#define IRCCD_JS_TIMER_API_HPP
+
+/**
+ * \file timer_api.hpp
+ * \brief Irccd.Timer Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.Timer Javascript API.
+ */
+class timer_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_TIMER_API_HPP
--- a/libirccd-js/irccd/js/timer_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,317 +0,0 @@
-/*
- * timer_js_api.cpp -- Irccd.timer API
- *
- * Copyright (c) 2013-2018 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 <boost/asio.hpp>
-
-#include <irccd/daemon/bot.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/plugin_service.hpp>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "plugin_js_api.hpp"
-#include "timer_js_api.hpp"
-
-namespace asio = boost::asio;
-
-using irccd::daemon::bot;
-using irccd::daemon::plugin;
-
-namespace irccd::js {
-
-namespace {
-
-const std::string_view signature(DUK_HIDDEN_SYMBOL("Irccd.Timer"));
-const std::string_view table(DUK_HIDDEN_SYMBOL("Irccd.Timer.callbacks"));
-
-// {{{ timer
-
-class timer : public std::enable_shared_from_this<timer> {
-public:
-	enum class type {
-		single,
-		repeat
-	};
-
-private:
-	boost::asio::deadline_timer handle_;
-	js_plugin& plugin_;
-
-	std::string key_;
-	type type_;
-	int delay_;
-
-	bool is_running_{false};
-	bool is_waiting_{false};
-
-	void handle();
-
-public:
-	timer(boost::asio::io_service&, js_plugin&, type, int);
-
-	auto key() const noexcept -> const std::string&;
-
-	void start();
-
-	void stop();
-};
-
-void timer::handle()
-{
-	duk::stack_guard sa(plugin_.get_context());
-
-	duk_push_global_stash(plugin_.get_context());
-	duk_get_prop_string(plugin_.get_context(), -1, table.data());
-	duk_remove(plugin_.get_context(), -2);
-	duk_get_prop_string(plugin_.get_context(), -1, key_.c_str());
-	duk_remove(plugin_.get_context(), -2);
-
-	if (duk_pcall(plugin_.get_context(), 0)) {
-		auto& log = duk::type_traits<bot>::self(plugin_.get_context()).get_log();
-
-		log.warning(static_cast<const plugin&>(plugin_)) << "timer error:" << std::endl;
-		log.warning(static_cast<const plugin&>(plugin_)) << "  " << duk::get_stack(plugin_.get_context(), -1).what() << std::endl;
-	} else
-		duk_pop(plugin_.get_context());
-}
-
-timer::timer(boost::asio::io_service& service, js_plugin& plugin, type type, int delay)
-	: handle_(service)
-	, plugin_(plugin)
-	, type_(type)
-	, delay_(delay)
-{
-}
-
-auto timer::key() const noexcept -> const std::string&
-{
-	return key_;
-}
-
-void timer::start()
-{
-	if (is_waiting_)
-		return;
-
-	is_running_ = is_waiting_ = true;
-
-	handle_.expires_from_now(boost::posix_time::milliseconds(delay_));
-	handle_.async_wait([this] (auto code) {
-		is_waiting_ = false;
-
-		if (code) {
-			is_running_ = false;
-			return;
-		}
-
-		handle();
-
-		if (is_running_ && type_ == type::repeat)
-			start();
-	});
-}
-
-void timer::stop()
-{
-	if (is_running_) {
-		handle_.cancel();
-		is_running_ = false;
-	}
-}
-
-// }}}
-
-// {{{ self
-
-auto self(duk_context* ctx) -> timer*
-{
-	duk::stack_guard sa(ctx);
-
-	duk_push_this(ctx);
-	duk_get_prop_string(ctx, -1, signature.data());
-	auto ptr = duk_to_pointer(ctx, -1);
-	duk_pop_2(ctx);
-
-	if (!ptr)
-		duk_error(ctx, DUK_ERR_TYPE_ERROR, "not a Timer object");
-
-	return static_cast<timer*>(ptr);
-}
-
-// }}}
-
-// {{{ Irccd.Timer.prototype.start
-
-/*
- * Method: Irccd.Timer.prototype.start()
- * --------------------------------------------------------
- *
- * Start the timer. If the timer is already started the method is a no-op.
- */
-auto Timer_prototype_start(duk_context* ctx) -> duk_ret_t
-{
-	self(ctx)->start();
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer.prototype.stop
-
-/*
- * Method: Irccd.Timer.prototype.stop()
- * --------------------------------------------------------
- *
- * Stop the timer.
- */
-auto Timer_prototype_stop(duk_context* ctx) -> duk_ret_t
-{
-	self(ctx)->stop();
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer [destructor]
-
-/*
- * Function: Irccd.Timer() [destructor]
- * ------------------------------------------------------------------
- *
- * Deleter the timer.
- */
-auto Timer_destructor(duk_context* ctx) -> duk_ret_t
-{
-	duk::stack_guard sa(ctx);
-
-	// Get timer from this.
-	duk_get_prop_string(ctx, 0, signature.data());
-	auto ptr = static_cast<timer*>(duk_to_pointer(ctx, -1));
-	duk_pop(ctx);
-
-	// Remove callback from timer table.
-	duk_push_global_stash(ctx);
-	duk_get_prop_string(ctx, -1, table.data());
-	duk_remove(ctx, -2);
-	duk_del_prop_string(ctx, -1, ptr->key().c_str());
-	duk_pop(ctx);
-
-	delete ptr;
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Timer [constructor]
-
-/*
- * Function: Irccd.Timer(type, delay, callback) [constructor]
- * --------------------------------------------------------
- *
- * Create a new timer object.
- *
- * Arguments:
- *   - type, the type of timer (Irccd.Timer.Single or Irccd.Timer.Repeat),
- *   - delay, the interval in milliseconds,
- *   - callback, the function to call.
- */
-auto Timer_constructor(duk_context* ctx) -> duk_ret_t
-{
-	if (!duk_is_constructor_call(ctx))
-		return 0;
-
-	try {
-		// Check parameters.
-		const auto type = duk_require_int(ctx, 0);
-		const auto delay = duk_require_int(ctx, 1);
-
-		if (type < static_cast<int>(timer::type::single) || type > static_cast<int>(timer::type::repeat))
-			duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid timer type");
-		if (delay < 0)
-			duk_error(ctx, DUK_ERR_TYPE_ERROR, "negative delay given");
-		if (!duk_is_callable(ctx, 2))
-			duk_error(ctx, DUK_ERR_TYPE_ERROR, "missing callback function");
-
-		auto& plugin = duk::type_traits<js_plugin>::self(ctx);
-		auto& daemon = duk::type_traits<bot>::self(ctx);
-		auto object = new timer(daemon.get_service(), plugin, static_cast<timer::type>(type), delay);
-
-		duk_push_this(ctx);
-		duk_push_pointer(ctx, object);
-		duk_put_prop_string(ctx, -2, signature.data());
-		duk_push_c_function(ctx, Timer_destructor, 1);
-		duk_set_finalizer(ctx, -2);
-		duk_pop(ctx);
-
-		// Store the function in a table to be called later.
-		duk_push_global_stash(ctx);
-		duk_get_prop_string(ctx, -1, table.data());
-		duk_remove(ctx, -2);
-		duk_dup(ctx, 2);
-		duk_put_prop_string(ctx, -2, object->key().c_str());
-		duk_pop(ctx);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-const duk_function_list_entry methods[] = {
-	{ "start",      Timer_prototype_start,  0               },
-	{ "stop",       Timer_prototype_stop,   0               },
-	{ nullptr,      nullptr,                0               }
-};
-
-const duk_number_list_entry constants[] = {
-	{ "Single",     static_cast<int>(timer::type::single)   },
-	{ "Repeat",     static_cast<int>(timer::type::repeat)   },
-	{ nullptr,      0                                       }
-};
-
-} // !namespace
-
-auto timer_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Timer";
-}
-
-void timer_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_c_function(plugin->get_context(), Timer_constructor, 3);
-	duk_put_number_list(plugin->get_context(), -1, constants);
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, methods);
-	duk_put_prop_string(plugin->get_context(), -2, "prototype");
-	duk_put_prop_string(plugin->get_context(), -2, "Timer");
-	duk_pop(plugin->get_context());
-	duk_push_global_stash(plugin->get_context());
-	duk_push_object(plugin->get_context());
-	duk_put_prop_string(plugin->get_context(), -2, table.data());
-	duk_pop(plugin->get_context());
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/timer_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * timer_js_api.hpp -- Irccd.Timer API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_TIMER_JS_API_HPP
-#define IRCCD_JS_TIMER_JS_API_HPP
-
-/**
- * \file timer_js_api.hpp
- * \brief Irccd.Timer Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.Timer Javascript API.
- */
-class timer_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_TIMER_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,165 @@
+/*
+ * unicode_api.cpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2018 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 "plugin.hpp"
+#include "unicode.hpp"
+#include "unicode_api.hpp"
+
+using irccd::daemon::bot;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ Irccd.Unicode.isDigit
+
+/*
+ * Function: Irccd.Unicode.isDigit(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the digit category.
+ */
+auto Unicode_isDigit(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isLetter
+
+/*
+ * Function: Irccd.Unicode.isLetter(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the letter category.
+ */
+auto Unicode_isLetter(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isLower
+
+/*
+ * Function: Irccd.Unicode.isLower(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is lower case.
+ */
+auto Unicode_isLower(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::islower(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isSpace
+
+/*
+ * Function: Irccd.Unicode.isSpace(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is in the space category.
+ */
+auto Unicode_isSpace(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::isspace(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isTitle
+
+/*
+ * Function: Irccd.Unicode.isTitle(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is title case.
+ */
+auto Unicode_isTitle(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::istitle(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+// {{{ Irccd.Unicode.isUpper
+
+/*
+ * Function: Irccd.Unicode.isUpper(code)
+ * --------------------------------------------------------
+ *
+ * Arguments:
+ *   - code, the code point.
+ * Returns:
+ *   True if the code is upper case.
+ */
+auto Unicode_isUpper(duk_context* ctx) noexcept -> duk_ret_t
+{
+	return duk::push(ctx, unicode::isupper(duk_get_int(ctx, 0)));
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+	{ "isDigit",            Unicode_isDigit,        1 },
+	{ "isLetter",           Unicode_isLetter,       1 },
+	{ "isLower",            Unicode_isLower,        1 },
+	{ "isSpace",            Unicode_isSpace,        1 },
+	{ "isTitle",            Unicode_isTitle,        1 },
+	{ "isUpper",            Unicode_isUpper,        1 },
+	{ nullptr,              nullptr,                0 }
+};
+
+} // !namespace
+
+auto unicode_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Unicode";
+}
+
+void unicode_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_put_prop_string(plugin->get_context(), -2, "Unicode");
+	duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/unicode_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * unicode_api.hpp -- Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_UNICODE_API_HPP
+#define IRCCD_JS_UNICODE_API_HPP
+
+/**
+ * \file unicode_api.hpp
+ * \brief Irccd.Unicode Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.Unicode Javascript API.
+ */
+class unicode_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_UNICODE_API_HPP
--- a/libirccd-js/irccd/js/unicode_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-/*
- * unicode_js_api.cpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2018 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 "js_plugin.hpp"
-#include "unicode.hpp"
-#include "unicode_js_api.hpp"
-
-using irccd::daemon::bot;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ Irccd.Unicode.isDigit
-
-/*
- * Function: Irccd.Unicode.isDigit(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the digit category.
- */
-auto Unicode_isDigit(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::isdigit(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isLetter
-
-/*
- * Function: Irccd.Unicode.isLetter(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the letter category.
- */
-auto Unicode_isLetter(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::isalpha(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isLower
-
-/*
- * Function: Irccd.Unicode.isLower(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is lower case.
- */
-auto Unicode_isLower(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::islower(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isSpace
-
-/*
- * Function: Irccd.Unicode.isSpace(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is in the space category.
- */
-auto Unicode_isSpace(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::isspace(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isTitle
-
-/*
- * Function: Irccd.Unicode.isTitle(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is title case.
- */
-auto Unicode_isTitle(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::istitle(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-// {{{ Irccd.Unicode.isUpper
-
-/*
- * Function: Irccd.Unicode.isUpper(code)
- * --------------------------------------------------------
- *
- * Arguments:
- *   - code, the code point.
- * Returns:
- *   True if the code is upper case.
- */
-auto Unicode_isUpper(duk_context* ctx) noexcept -> duk_ret_t
-{
-	return duk::push(ctx, unicode::isupper(duk_get_int(ctx, 0)));
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-	{ "isDigit",            Unicode_isDigit,        1 },
-	{ "isLetter",           Unicode_isLetter,       1 },
-	{ "isLower",            Unicode_isLower,        1 },
-	{ "isSpace",            Unicode_isSpace,        1 },
-	{ "isTitle",            Unicode_isTitle,        1 },
-	{ "isUpper",            Unicode_isUpper,        1 },
-	{ nullptr,              nullptr,                0 }
-};
-
-} // !namespace
-
-auto unicode_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Unicode";
-}
-
-void unicode_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_put_prop_string(plugin->get_context(), -2, "Unicode");
-	duk_pop(plugin->get_context());
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/unicode_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * unicode_js_api.hpp -- Irccd.Unicode API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_UNICODE_JS_API_HPP
-#define IRCCD_JS_UNICODE_JS_API_HPP
-
-/**
- * \file unicode_js_api.hpp
- * \brief Irccd.Unicode Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.Unicode Javascript API.
- */
-class unicode_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_UNICODE_JS_API_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/util_api.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,354 @@
+/*
+ * util_api.cpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2018 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 <climits>
+
+#include <irccd/string_util.hpp>
+
+#include "irccd_api.hpp"
+#include "plugin.hpp"
+#include "util_api.hpp"
+
+using irccd::daemon::bot;
+using irccd::daemon::irc::user;
+
+namespace irccd::js {
+
+namespace {
+
+// {{{ subst
+
+/*
+ * Read parameters for Irccd.Util.format function, the object is defined as
+ * following:
+ *
+ * {
+ *   date: the date object
+ *   flags: the flags (not implemented yet)
+ *   field1: a field to substitute in #{} pattern
+ *   field2: a field to substitute in #{} pattern
+ *   fieldn: ...
+ * }
+ */
+auto subst(duk_context* ctx, int index) -> string_util::subst
+{
+	string_util::subst params;
+
+	if (!duk_is_object(ctx, index))
+		return params;
+
+	duk_enum(ctx, index, 0);
+
+	while (duk_next(ctx, -1, true)) {
+		if (duk::get<std::string>(ctx, -2) == "date")
+			params.time = static_cast<time_t>(duk_get_number(ctx, -1) / 1000);
+		else
+			params.keywords.insert({
+				duk::get<std::string>(ctx, -2),
+				duk::get<std::string>(ctx, -1)
+			});
+
+		duk_pop_n(ctx, 2);
+	}
+
+	return params;
+}
+
+// }}}
+
+// {{{ split
+
+/*
+ * split (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Extract individual tokens in array or a whole string as a std:::vector.
+ */
+auto split(duk_context* ctx) -> std::vector<std::string>
+{
+	duk_require_type_mask(ctx, 0, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING);
+
+	std::vector<std::string> result;
+	std::string pattern = " \t\n";
+
+	if (duk_is_string(ctx, 0))
+		result = string_util::split(duk::get<std::string>(ctx, 0), pattern);
+	else if (duk_is_array(ctx, 0)) {
+		duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);
+
+		while (duk_next(ctx, -1, 1)) {
+			// Split individual tokens as array if spaces are found.
+			const auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
+
+			result.insert(result.end(), tmp.begin(), tmp.end());
+			duk_pop_2(ctx);
+		}
+	}
+
+	return result;
+}
+
+// }}}
+
+// {{{ limit
+
+/*
+ * limit (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Get the maxl/maxc argument.
+ *
+ * The argument value is the default and also used as the result returned.
+ */
+auto limit(duk_context* ctx, int index, const char* name, int value) -> int
+{
+	if (duk_get_top(ctx) < index || !duk_is_number(ctx, index))
+		return value;
+
+	value = duk_to_int(ctx, index);
+
+	if (value <= 0)
+		duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name);
+
+	return value;
+}
+
+// }}}
+
+// {{{ lines
+
+/*
+ * lines (for Irccd.Util.cut)
+ * ------------------------------------------------------------------
+ *
+ * Build a list of lines.
+ *
+ * Several cases possible:
+ *
+ *   - s is the current line
+ *   - abc is the token to add
+ *
+ * s   = ""				 (new line)
+ * s  -> "abc"
+ *
+ * s   = "hello world"	  (enough room)
+ * s  -> "hello world abc"
+ *
+ * s   = "hello world"	  (not enough room: maxc is smaller)
+ * s+1 = "abc"
+ */
+auto lines(duk_context* ctx, const std::vector<std::string>& tokens, int maxc) -> std::vector<std::string>
+{
+	std::vector<std::string> result{""};
+
+	for (const auto& s : tokens) {
+		if (s.length() > static_cast<std::size_t>(maxc))
+			duk_error(ctx, DUK_ERR_RANGE_ERROR, "word '%s' could not fit in maxc limit (%d)", s.c_str(), maxc);
+
+		// Compute the length required (prepend a space if needed)
+		auto required = s.length() + (result.back().empty() ? 0 : 1);
+
+		if (result.back().length() + required > static_cast<std::size_t>(maxc))
+			result.push_back(s);
+		else {
+			if (!result.back().empty())
+				result.back() += ' ';
+
+			result.back() += s;
+		}
+	}
+
+	return result;
+}
+
+// }}}
+
+// {{{ wrap
+
+template <typename Handler>
+auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
+{
+	try {
+		return handler();
+	} catch (const std::system_error& ex) {
+		duk::raise(ctx, ex);
+	} catch (const std::exception& ex) {
+		duk::raise(ctx, ex);
+	}
+
+	return 0;
+}
+
+// }}}
+
+// {{{ Irccd.Util.cut
+
+/*
+ * Function: Irccd.Util.cut(data, maxc, maxl)
+ * --------------------------------------------------------
+ *
+ * Cut a piece of data into several lines.
+ *
+ * The argument data is a string or a list of strings. In any case, all strings
+ * are first splitted by spaces and trimmed. This ensure that useless
+ * whitespaces are discarded.
+ *
+ * The argument maxc controls the maximum of characters allowed per line, it can
+ * be a positive integer. If undefined is given, a default of 72 is used.
+ *
+ * The argument maxl controls the maximum of lines allowed. It can be a positive
+ * integer or undefined for an infinite list.
+ *
+ * If maxl is used as a limit and the data can not fit within the bounds,
+ * undefined is returned.
+ *
+ * An empty list may be returned if empty strings were found.
+ *
+ * Arguments:
+ *   - data, a string or an array of strings,
+ *   - maxc, max number of colums (Optional, default: 72),
+ *   - maxl, max number of lines (Optional, default: undefined).
+ * Returns:
+ *   A list of strings ready to be sent or undefined if the data is too big.
+ * Throws:
+ *   - RangeError if maxl or maxc are negative numbers,
+ *   - RangeError if one word length was bigger than maxc,
+ *   - TypeError if data is not a string or a list of strings,
+ *   - Irccd.SystemError on other errors.
+ */
+auto Util_cut(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		const auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
+		const auto maxl = limit(ctx, 2, "maxl", INT_MAX);
+
+		if (list.size() > static_cast<std::size_t>(maxl))
+			return 0;
+
+		// Empty list but lines() returns at least one.
+		if (list.size() == 1 && list[0].empty()) {
+			duk_push_array(ctx);
+			return 1;
+		}
+
+		return duk::push(ctx, list);
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Util.format
+
+/*
+ * Function: Irccd.Util.format(text, parameters)
+ * --------------------------------------------------------
+ *
+ * Format a string with templates.
+ *
+ * Arguments:
+ *   - input, the text to update,
+ *   - params, the parameters.
+ * Returns:
+ *   The converted text.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_format(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, string_util::format(duk::get<std::string>(ctx, 0), subst(ctx, 1)));
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Util.splituser
+
+/*
+ * Function: Irccd.Util.splituser(ident)
+ * --------------------------------------------------------
+ *
+ * Return the nickname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The nickname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_splituser(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, user::parse(duk::require<std::string>(ctx, 0)).nick);
+	});
+}
+
+// }}}
+
+// {{{ Irccd.Util.splithost
+
+/*
+ * Function: Irccd.Util.splithost(ident)
+ * --------------------------------------------------------
+ *
+ * Return the hostname part from a full username.
+ *
+ * Arguments:
+ *   - ident, the full identity.
+ * Returns:
+ *   The hostname.
+ * Throws:
+ *   - Irccd.SystemError on errors.
+ */
+auto Util_splithost(duk_context* ctx) -> duk_ret_t
+{
+	return wrap(ctx, [&] {
+		return duk::push(ctx, user::parse(duk::require<std::string>(ctx, 0)).host);
+	});
+}
+
+// }}}
+
+const duk_function_list_entry functions[] = {
+	{ "cut",        Util_cut,       DUK_VARARGS     },
+	{ "format",     Util_format,    DUK_VARARGS     },
+	{ "splituser",  Util_splituser, 1               },
+	{ "splithost",  Util_splithost, 1               },
+	{ nullptr,      nullptr,        0               }
+};
+
+} // !namespace
+
+auto util_api::get_name() const noexcept -> std::string_view
+{
+	return "Irccd.Util";
+}
+
+void util_api::load(bot&, std::shared_ptr<plugin> plugin)
+{
+	duk::stack_guard sa(plugin->get_context());
+
+	duk_get_global_string(plugin->get_context(), "Irccd");
+	duk_push_object(plugin->get_context());
+	duk_put_function_list(plugin->get_context(), -1, functions);
+	duk_put_prop_string(plugin->get_context(), -2, "Util");
+	duk_pop(plugin->get_context());
+}
+
+} // !irccd::js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/util_api.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -0,0 +1,50 @@
+/*
+ * util_api.hpp -- Irccd.Util API
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_JS_UTIL_API_HPP
+#define IRCCD_JS_UTIL_API_HPP
+
+/**
+ * \file util_api.hpp
+ * \brief Irccd.Util Javascript API.
+ */
+
+#include "api.hpp"
+
+namespace irccd::js {
+
+/**
+ * \ingroup js-api
+ * \brief Irccd.Util Javascript API.
+ */
+class util_api : public api {
+public:
+	/**
+	 * \copydoc api::get_name
+	 */
+	auto get_name() const noexcept -> std::string_view override;
+
+	/**
+	 * \copydoc api::load
+	 */
+	void load(daemon::bot& bot, std::shared_ptr<plugin> plugin) override;
+};
+
+} // !irccd::js
+
+#endif // !IRCCD_JS_UTIL_API_HPP
--- a/libirccd-js/irccd/js/util_js_api.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,354 +0,0 @@
-/*
- * util_js_api.cpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2018 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 <climits>
-
-#include <irccd/string_util.hpp>
-
-#include "irccd_js_api.hpp"
-#include "js_plugin.hpp"
-#include "util_js_api.hpp"
-
-using irccd::daemon::bot;
-using irccd::daemon::irc::user;
-
-namespace irccd::js {
-
-namespace {
-
-// {{{ subst
-
-/*
- * Read parameters for Irccd.Util.format function, the object is defined as
- * following:
- *
- * {
- *   date: the date object
- *   flags: the flags (not implemented yet)
- *   field1: a field to substitute in #{} pattern
- *   field2: a field to substitute in #{} pattern
- *   fieldn: ...
- * }
- */
-auto subst(duk_context* ctx, int index) -> string_util::subst
-{
-	string_util::subst params;
-
-	if (!duk_is_object(ctx, index))
-		return params;
-
-	duk_enum(ctx, index, 0);
-
-	while (duk_next(ctx, -1, true)) {
-		if (duk::get<std::string>(ctx, -2) == "date")
-			params.time = static_cast<time_t>(duk_get_number(ctx, -1) / 1000);
-		else
-			params.keywords.insert({
-				duk::get<std::string>(ctx, -2),
-				duk::get<std::string>(ctx, -1)
-			});
-
-		duk_pop_n(ctx, 2);
-	}
-
-	return params;
-}
-
-// }}}
-
-// {{{ split
-
-/*
- * split (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Extract individual tokens in array or a whole string as a std:::vector.
- */
-auto split(duk_context* ctx) -> std::vector<std::string>
-{
-	duk_require_type_mask(ctx, 0, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_STRING);
-
-	std::vector<std::string> result;
-	std::string pattern = " \t\n";
-
-	if (duk_is_string(ctx, 0))
-		result = string_util::split(duk::get<std::string>(ctx, 0), pattern);
-	else if (duk_is_array(ctx, 0)) {
-		duk_enum(ctx, 0, DUK_ENUM_ARRAY_INDICES_ONLY);
-
-		while (duk_next(ctx, -1, 1)) {
-			// Split individual tokens as array if spaces are found.
-			const auto tmp = string_util::split(duk_to_string(ctx, -1), pattern);
-
-			result.insert(result.end(), tmp.begin(), tmp.end());
-			duk_pop_2(ctx);
-		}
-	}
-
-	return result;
-}
-
-// }}}
-
-// {{{ limit
-
-/*
- * limit (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Get the maxl/maxc argument.
- *
- * The argument value is the default and also used as the result returned.
- */
-auto limit(duk_context* ctx, int index, const char* name, int value) -> int
-{
-	if (duk_get_top(ctx) < index || !duk_is_number(ctx, index))
-		return value;
-
-	value = duk_to_int(ctx, index);
-
-	if (value <= 0)
-		duk_error(ctx, DUK_ERR_RANGE_ERROR, "argument %d (%s) must be positive", index, name);
-
-	return value;
-}
-
-// }}}
-
-// {{{ lines
-
-/*
- * lines (for Irccd.Util.cut)
- * ------------------------------------------------------------------
- *
- * Build a list of lines.
- *
- * Several cases possible:
- *
- *   - s is the current line
- *   - abc is the token to add
- *
- * s   = ""				 (new line)
- * s  -> "abc"
- *
- * s   = "hello world"	  (enough room)
- * s  -> "hello world abc"
- *
- * s   = "hello world"	  (not enough room: maxc is smaller)
- * s+1 = "abc"
- */
-auto lines(duk_context* ctx, const std::vector<std::string>& tokens, int maxc) -> std::vector<std::string>
-{
-	std::vector<std::string> result{""};
-
-	for (const auto& s : tokens) {
-		if (s.length() > static_cast<std::size_t>(maxc))
-			duk_error(ctx, DUK_ERR_RANGE_ERROR, "word '%s' could not fit in maxc limit (%d)", s.c_str(), maxc);
-
-		// Compute the length required (prepend a space if needed)
-		auto required = s.length() + (result.back().empty() ? 0 : 1);
-
-		if (result.back().length() + required > static_cast<std::size_t>(maxc))
-			result.push_back(s);
-		else {
-			if (!result.back().empty())
-				result.back() += ' ';
-
-			result.back() += s;
-		}
-	}
-
-	return result;
-}
-
-// }}}
-
-// {{{ wrap
-
-template <typename Handler>
-auto wrap(duk_context* ctx, Handler handler) -> duk_ret_t
-{
-	try {
-		return handler();
-	} catch (const std::system_error& ex) {
-		duk::raise(ctx, ex);
-	} catch (const std::exception& ex) {
-		duk::raise(ctx, ex);
-	}
-
-	return 0;
-}
-
-// }}}
-
-// {{{ Irccd.Util.cut
-
-/*
- * Function: Irccd.Util.cut(data, maxc, maxl)
- * --------------------------------------------------------
- *
- * Cut a piece of data into several lines.
- *
- * The argument data is a string or a list of strings. In any case, all strings
- * are first splitted by spaces and trimmed. This ensure that useless
- * whitespaces are discarded.
- *
- * The argument maxc controls the maximum of characters allowed per line, it can
- * be a positive integer. If undefined is given, a default of 72 is used.
- *
- * The argument maxl controls the maximum of lines allowed. It can be a positive
- * integer or undefined for an infinite list.
- *
- * If maxl is used as a limit and the data can not fit within the bounds,
- * undefined is returned.
- *
- * An empty list may be returned if empty strings were found.
- *
- * Arguments:
- *   - data, a string or an array of strings,
- *   - maxc, max number of colums (Optional, default: 72),
- *   - maxl, max number of lines (Optional, default: undefined).
- * Returns:
- *   A list of strings ready to be sent or undefined if the data is too big.
- * Throws:
- *   - RangeError if maxl or maxc are negative numbers,
- *   - RangeError if one word length was bigger than maxc,
- *   - TypeError if data is not a string or a list of strings,
- *   - Irccd.SystemError on other errors.
- */
-auto Util_cut(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		const auto list = lines(ctx, split(ctx), limit(ctx, 1, "maxc", 72));
-		const auto maxl = limit(ctx, 2, "maxl", INT_MAX);
-
-		if (list.size() > static_cast<std::size_t>(maxl))
-			return 0;
-
-		// Empty list but lines() returns at least one.
-		if (list.size() == 1 && list[0].empty()) {
-			duk_push_array(ctx);
-			return 1;
-		}
-
-		return duk::push(ctx, list);
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Util.format
-
-/*
- * Function: Irccd.Util.format(text, parameters)
- * --------------------------------------------------------
- *
- * Format a string with templates.
- *
- * Arguments:
- *   - input, the text to update,
- *   - params, the parameters.
- * Returns:
- *   The converted text.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Util_format(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, string_util::format(duk::get<std::string>(ctx, 0), subst(ctx, 1)));
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Util.splituser
-
-/*
- * Function: Irccd.Util.splituser(ident)
- * --------------------------------------------------------
- *
- * Return the nickname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The nickname.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Util_splituser(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, user::parse(duk::require<std::string>(ctx, 0)).nick);
-	});
-}
-
-// }}}
-
-// {{{ Irccd.Util.splithost
-
-/*
- * Function: Irccd.Util.splithost(ident)
- * --------------------------------------------------------
- *
- * Return the hostname part from a full username.
- *
- * Arguments:
- *   - ident, the full identity.
- * Returns:
- *   The hostname.
- * Throws:
- *   - Irccd.SystemError on errors.
- */
-auto Util_splithost(duk_context* ctx) -> duk_ret_t
-{
-	return wrap(ctx, [&] {
-		return duk::push(ctx, user::parse(duk::require<std::string>(ctx, 0)).host);
-	});
-}
-
-// }}}
-
-const duk_function_list_entry functions[] = {
-	{ "cut",        Util_cut,       DUK_VARARGS     },
-	{ "format",     Util_format,    DUK_VARARGS     },
-	{ "splituser",  Util_splituser, 1               },
-	{ "splithost",  Util_splithost, 1               },
-	{ nullptr,      nullptr,        0               }
-};
-
-} // !namespace
-
-auto util_js_api::get_name() const noexcept -> std::string_view
-{
-	return "Irccd.Util";
-}
-
-void util_js_api::load(bot&, std::shared_ptr<js_plugin> plugin)
-{
-	duk::stack_guard sa(plugin->get_context());
-
-	duk_get_global_string(plugin->get_context(), "Irccd");
-	duk_push_object(plugin->get_context());
-	duk_put_function_list(plugin->get_context(), -1, functions);
-	duk_put_prop_string(plugin->get_context(), -2, "Util");
-	duk_pop(plugin->get_context());
-}
-
-} // !irccd::js
--- a/libirccd-js/irccd/js/util_js_api.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-/*
- * util_js_api.hpp -- Irccd.Util API
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_JS_UTIL_JS_API_HPP
-#define IRCCD_JS_UTIL_JS_API_HPP
-
-/**
- * \file util_js_api.hpp
- * \brief Irccd.Util Javascript API.
- */
-
-#include "js_api.hpp"
-
-namespace irccd::js {
-
-/**
- * \ingroup js-api
- * \brief Irccd.Util Javascript API.
- */
-class util_js_api : public js_api {
-public:
-	/**
-	 * \copydoc js_api::get_name
-	 */
-	auto get_name() const noexcept -> std::string_view override;
-
-	/**
-	 * \copydoc js_api::load
-	 */
-	void load(daemon::bot& bot, std::shared_ptr<js_plugin> plugin) override;
-};
-
-} // !irccd::js
-
-#endif // !IRCCD_JS_UTIL_JS_API_HPP
--- a/libirccd-test/irccd/test/js_fixture.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-test/irccd/test/js_fixture.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -21,9 +21,9 @@
 namespace irccd::test {
 
 js_fixture::js_fixture(const std::string& path)
-	: plugin_(new js::js_plugin("test", path))
+	: plugin_(new js::plugin("test", path))
 {
-	for (const auto& f : js::js_api::registry())
+	for (const auto& f : js::api::registry())
 		f()->load(bot_, plugin_);
 
 	if (!path.empty())
--- a/libirccd-test/irccd/test/js_fixture.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-test/irccd/test/js_fixture.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -26,10 +26,10 @@
 
 #include "irccd_fixture.hpp"
 
-#include <irccd/js/js_plugin.hpp>
-#include <irccd/js/js_api.hpp>
+#include <irccd/js/plugin.hpp>
+#include <irccd/js/api.hpp>
 
-#include <irccd/js/irccd_js_api.hpp>
+#include <irccd/js/irccd_api.hpp>
 
 namespace irccd::test {
 
@@ -41,7 +41,7 @@
 	/**
 	 * \brief Javascript plugin.
 	 */
-	std::shared_ptr<js::js_plugin> plugin_;
+	std::shared_ptr<js::plugin> plugin_;
 
 	/**
 	 * Constructor.
--- a/libirccd-test/irccd/test/js_plugin_fixture.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-test/irccd/test/js_plugin_fixture.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -1,5 +1,5 @@
 /*
- * js_plugin_fixture.cpp -- test fixture helper for Javascript plugins
+ * plugin_fixture.cpp -- test fixture helper for Javascript plugins
  *
  * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
  *
@@ -22,22 +22,22 @@
 #include <irccd/daemon/plugin_service.hpp>
 #include <irccd/daemon/server_service.hpp>
 
-#include <irccd/js/directory_js_api.hpp>
-#include <irccd/js/elapsed_timer_js_api.hpp>
-#include <irccd/js/file_js_api.hpp>
-#include <irccd/js/irccd_js_api.hpp>
-#include <irccd/js/js_plugin.hpp>
-#include <irccd/js/logger_js_api.hpp>
-#include <irccd/js/plugin_js_api.hpp>
-#include <irccd/js/server_js_api.hpp>
-#include <irccd/js/system_js_api.hpp>
-#include <irccd/js/timer_js_api.hpp>
-#include <irccd/js/unicode_js_api.hpp>
-#include <irccd/js/util_js_api.hpp>
+#include <irccd/js/directory_api.hpp>
+#include <irccd/js/elapsed_timer_api.hpp>
+#include <irccd/js/file_api.hpp>
+#include <irccd/js/irccd_api.hpp>
+#include <irccd/js/plugin.hpp>
+#include <irccd/js/logger_api.hpp>
+#include <irccd/js/plugin_api.hpp>
+#include <irccd/js/server_api.hpp>
+#include <irccd/js/system_api.hpp>
+#include <irccd/js/timer_api.hpp>
+#include <irccd/js/unicode_api.hpp>
+#include <irccd/js/util_api.hpp>
 
 #include "js_plugin_fixture.hpp"
 
-using irccd::js::js_plugin;
+using irccd::js::plugin;
 
 using irccd::daemon::logger::silent_sink;
 
@@ -46,7 +46,7 @@
 js_plugin_fixture::js_plugin_fixture(std::string path)
 	: server_(std::make_shared<mock_server>(service_, "test", "local"))
 {
-	plugin_ = std::make_unique<js_plugin>("test", std::move(path));
+	plugin_ = std::make_unique<plugin>("test", std::move(path));
 
 	bot_.set_log(std::make_unique<silent_sink>());
 	bot_.get_log().set_verbose(false);
@@ -57,7 +57,7 @@
 	server_->set_nickname("irccd");
 	server_->clear();
 
-	for (const auto& f : js::js_api::registry())
+	for (const auto& f : js::api::registry())
 		f()->load(bot_, plugin_);
 
 	plugin_->open();
--- a/libirccd-test/irccd/test/js_plugin_fixture.hpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/libirccd-test/irccd/test/js_plugin_fixture.hpp	Thu Nov 29 21:28:48 2018 +0100
@@ -26,7 +26,7 @@
 
 #include <irccd/daemon/bot.hpp>
 
-#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/plugin.hpp>
 
 #include "mock_server.hpp"
 
@@ -41,7 +41,7 @@
 protected:
 	boost::asio::io_service service_;               //!< The I/O service.
 	daemon::bot bot_{service_};                     //!< The irccd instance.
-	std::shared_ptr<js::js_plugin> plugin_;         //!< The plugin to test.
+	std::shared_ptr<js::plugin> plugin_;            //!< The plugin to test.
 	std::shared_ptr<mock_server> server_;           //!< A mock server.
 
 public:
--- a/tests/src/libirccd-js/js-plugin/main.cpp	Mon Nov 26 21:53:27 2018 +0100
+++ b/tests/src/libirccd-js/js-plugin/main.cpp	Thu Nov 29 21:28:48 2018 +0100
@@ -21,17 +21,15 @@
 
 #include <irccd/daemon/plugin_service.hpp>
 
-#include <irccd/js/js_api.hpp>
-#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/api.hpp>
+#include <irccd/js/plugin.hpp>
 
 #include <irccd/test/irccd_fixture.hpp>
 
-using irccd::daemon::plugin;
-
 using irccd::test::irccd_fixture;
 
-using irccd::js::js_plugin;
-using irccd::js::js_api;
+using irccd::js::plugin;
+using irccd::js::api;
 
 namespace irccd {
 
@@ -39,13 +37,13 @@
 
 class js_plugin_fixture : public irccd_fixture {
 protected:
-	std::shared_ptr<js_plugin> plugin_;
+	std::shared_ptr<plugin> plugin_;
 
 	void load(const std::string& path)
 	{
-		plugin_ = std::make_unique<js_plugin>("test", path);
+		plugin_ = std::make_unique<plugin>("test", path);
 
-		for (const auto& f : js_api::registry())
+		for (const auto& f : api::registry())
 			f()->load(bot_, plugin_);
 
 		plugin_->open();
@@ -103,15 +101,15 @@
 
 class js_plugin_loader_fixture : public irccd_fixture {
 protected:
-	std::shared_ptr<plugin> plugin_;
+	std::shared_ptr<daemon::plugin> plugin_;
 
 	js_plugin_loader_fixture()
 	{
 		bot_.set_config(config(CMAKE_CURRENT_SOURCE_DIR "/irccd.conf"));
 
-		auto loader = std::make_unique<js::js_plugin_loader>(bot_);
+		auto loader = std::make_unique<js::plugin_loader>(bot_);
 
-		for (const auto& f : js::js_api::registry())
+		for (const auto& f : js::api::registry())
 			loader->get_modules().push_back(f());
 
 		bot_.plugins().add_loader(std::move(loader));