changeset 131:77c90336ba56

Irccd: initial Plugin split, #501
author David Demelier <markand@malikania.fr>
date Thu, 12 May 2016 15:06:11 +0200
parents 483c862b5a52
children 105287c768ba
files lib/irccd/CMakeSources.cmake lib/irccd/config.cpp lib/irccd/js-timer.cpp lib/irccd/plugin-js.cpp lib/irccd/plugin-js.hpp lib/irccd/plugin.cpp lib/irccd/plugin.hpp lib/irccd/service-plugin.cpp lib/irccd/service-plugin.hpp tests/js-timer/main.cpp
diffstat 10 files changed, 1013 insertions(+), 562 deletions(-) [+]
line wrap: on
line diff
--- a/lib/irccd/CMakeSources.cmake	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/CMakeSources.cmake	Thu May 12 15:06:11 2016 +0200
@@ -51,6 +51,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/options.hpp
 	${CMAKE_CURRENT_LIST_DIR}/path.hpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin.hpp
+	${CMAKE_CURRENT_LIST_DIR}/plugin-js.hpp
 	${CMAKE_CURRENT_LIST_DIR}/rule.hpp
 	${CMAKE_CURRENT_LIST_DIR}/server.hpp
 	${CMAKE_CURRENT_LIST_DIR}/server-event.hpp
@@ -126,6 +127,7 @@
 	${CMAKE_CURRENT_LIST_DIR}/options.cpp
 	${CMAKE_CURRENT_LIST_DIR}/path.cpp
 	${CMAKE_CURRENT_LIST_DIR}/plugin.cpp
+	${CMAKE_CURRENT_LIST_DIR}/plugin-js.cpp
 	${CMAKE_CURRENT_LIST_DIR}/rule.cpp
 	${CMAKE_CURRENT_LIST_DIR}/server.cpp
 	${CMAKE_CURRENT_LIST_DIR}/server-event.cpp
--- a/lib/irccd/config.cpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/config.cpp	Thu May 12 15:06:11 2016 +0200
@@ -25,6 +25,7 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "path.hpp"
+#include "plugin-js.hpp"
 #include "rule.hpp"
 #include "server.hpp"
 #include "sysconfig.hpp"
@@ -631,7 +632,7 @@
 				plugins.push_back(Plugin::find(name, findPluginConfig(name)));
 			} else {
 				log::info("plugin {}: trying {}"_format(name, path));
-				plugins.push_back(std::make_shared<Plugin>(name, path, findPluginConfig(name)));
+				plugins.push_back(std::make_shared<JsPlugin>(name, path, findPluginConfig(name)));
 			}
 		} catch (const duk::ErrorInfo &ex) {
 			log::warning("plugin {}: {}"_format(option.key(), ex.what()));
--- a/lib/irccd/js-timer.cpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/js-timer.cpp	Thu May 12 15:06:11 2016 +0200
@@ -20,7 +20,7 @@
 #include <cstdint>
 
 #include "js.hpp"
-#include "plugin.hpp"
+#include "plugin-js.hpp"
 
 namespace irccd {
 
@@ -107,7 +107,7 @@
 	auto timer = std::make_shared<Timer>(static_cast<TimerType>(type), delay);
 
 	/* Add this timer to the underlying plugin */
-	duk::getGlobal<duk::RawPointer<Plugin>>(ctx, "\xff""\xff""plugin")->addTimer(timer);
+	duk::getGlobal<duk::RawPointer<JsPlugin>>(ctx, "\xff""\xff""plugin")->addTimer(timer);
 
 	/* Construct object */
 	duk::construct(ctx, duk::Shared<Timer>{timer});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/plugin-js.cpp	Thu May 12 15:06:11 2016 +0200
@@ -0,0 +1,495 @@
+/*
+ * plugin-js.cpp -- JavaScript plugins for irccd
+ *
+ * Copyright (c) 2013-2016 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 "sysconfig.hpp"
+
+#if defined(HAVE_STAT)
+#  include <sys/stat.h>
+#  include <cerrno>
+#  include <cstring>
+#endif
+
+#include "fs.hpp"
+#include "js-directory.hpp"
+#include "js-elapsed-timer.hpp"
+#include "js-file.hpp"
+#include "js-irccd.hpp"
+#include "js-logger.hpp"
+#include "js-plugin.hpp"
+#include "js-server.hpp"
+#include "js-system.hpp"
+#include "js-timer.hpp"
+#include "js-unicode.hpp"
+#include "js-util.hpp"
+#include "logger.hpp"
+#include "plugin-js.hpp"
+
+namespace irccd {
+
+void JsPlugin::call(const std::string &name, unsigned nargs)
+{
+	duk::getGlobal<void>(m_context, name);
+
+	if (duk::type(m_context, -1) == DUK_TYPE_UNDEFINED) {
+		/* Function not defined, remove the undefined value and all arguments */
+		duk::pop(m_context, nargs + 1);
+	} else {
+		/* Call the function and discard the result */
+		duk::insert(m_context, -nargs - 1);
+
+		if (duk::pcall(m_context, nargs) != 0) {
+			auto error = duk::error(m_context, -1);
+
+			duk::pop(m_context);
+
+			throw error;
+		} else {
+			duk::pop(m_context);
+		}
+	}
+}
+
+void JsPlugin::putVars()
+{
+	duk::StackAssert sa(m_context);
+
+	/* Save a reference to this */
+	duk::putGlobal(m_context, "\xff""\xff""plugin", duk::RawPointer<JsPlugin>{this});
+	duk::putGlobal(m_context, "\xff""\xff""name", name());
+	duk::putGlobal(m_context, "\xff""\xff""path", path());
+}
+
+void JsPlugin::putPath(const std::string &varname, const std::string &append, path::Path type)
+{
+	duk::StackAssert sa(m_context);
+
+	bool found = true;
+	std::string foundpath;
+
+	/*
+	 * Use the first existing directory available.
+	 */
+	for (const std::string &p : path::list(type)) {
+		foundpath = path::clean(p + append);
+
+		if (fs::exists(foundpath)) {
+			found = true;
+			break;
+		}
+	}
+
+	/* Use the system as default */
+	if (!found) {
+		foundpath = path::clean(path::get(type, path::OwnerSystem) + append);
+	}
+
+	duk::getGlobal<void>(m_context, "Irccd");
+	duk::getProperty<void>(m_context, -1, "Plugin");
+	duk::putProperty(m_context, -1, varname, foundpath);
+	duk::pop(m_context, 2);
+}
+
+void JsPlugin::putPaths()
+{
+	duk::StackAssert sa(m_context);
+
+	/*
+	 * dataPath: DATA + plugin/name (e.g ~/.local/share/irccd/plugins/<name>/)
+	 * configPath: CONFIG + plugin/name (e.g ~/.config/irccd/plugin/<name>/)
+	 */
+	putPath("dataPath", "plugin/" + name(), path::PathData);
+	putPath("configPath", "plugin/" + name(), path::PathConfig);
+	putPath("cachePath", "plugin/" + name(), path::PathCache);
+}
+
+void JsPlugin::putConfig(const PluginConfig &config)
+{
+	duk::StackAssert sa(m_context);
+
+	// TODO: override dataPath, configPath, cachePath
+
+	/* Store plugin configuration into Irccd.Plugin.config */
+	duk::getGlobal<void>(m_context, "Irccd");
+	duk::getProperty<void>(m_context, -1, "Plugin");
+	duk::getProperty<void>(m_context, -1, "config");
+
+	if (duk::type(m_context, -1) != DUK_TYPE_OBJECT) {
+		duk::pop(m_context);
+		duk::push(m_context, duk::Object{});
+	}
+
+	for (const auto &pair : config) {
+		duk::putProperty(m_context, -1, pair.first, pair.second);
+	}
+
+	duk::putProperty(m_context, -2, "config");
+	duk::pop(m_context, 2);
+}
+
+JsPlugin::JsPlugin(std::string name, std::string path, const PluginConfig &config)
+	: Plugin(name, path, config)
+{
+	duk::StackAssert sa(m_context);
+
+	/*
+	 * Duktape currently emit useless warnings when a file do
+	 * not exists so we do a homemade access.
+	 */
+#if defined(HAVE_STAT)
+	struct stat st;
+
+	if (::stat(path.c_str(), &st) < 0) {
+		throw std::runtime_error(std::strerror(errno));
+	}
+#endif
+
+	// TODO: change with future modules
+	// Load standard irccd API.
+	loadJsIrccd(m_context);
+	loadJsDirectory(m_context);
+	loadJsElapsedTimer(m_context);
+	loadJsFile(m_context);
+	loadJsLogger(m_context);
+	loadJsPlugin(m_context);
+	loadJsServer(m_context);
+	loadJsSystem(m_context);
+	loadJsTimer(m_context);
+	loadJsUnicode(m_context);
+	loadJsUtil(m_context);
+
+	putVars();
+	putPaths();
+
+	/* Try to load the file (does not call onLoad yet) */
+	if (duk::pevalFile(m_context, path) != 0) {
+		throw duk::error(m_context, -1);
+	}
+
+	duk::pop(m_context);
+
+	/* Initialize user defined options after loading to allow the plugin to define default values */
+	putConfig(config);
+
+	/* Read metadata */
+	duk::getGlobal<void>(m_context, "info");
+
+	if (duk::type(m_context, -1) == DUK_TYPE_OBJECT) {
+		setAuthor(duk::optionalProperty<std::string>(m_context, -1, "author", author()));
+		setLicense(duk::optionalProperty<std::string>(m_context, -1, "license", license()));
+		setSummary(duk::optionalProperty<std::string>(m_context, -1, "summary", summary()));
+		setVersion(duk::optionalProperty<std::string>(m_context, -1, "version", version()));
+	}
+
+	duk::pop(m_context);
+
+	log::debug() << "plugin " << name << ": " << std::endl;
+	log::debug() << "  author:  " << author() << std::endl;
+	log::debug() << "  license: " << license() << std::endl;
+	log::debug() << "  summary: " << summary() << std::endl;
+	log::debug() << "  version: " << version() << std::endl;
+}
+
+JsPlugin::~JsPlugin()
+{
+	for (auto &timer : m_timers) {
+		timer->stop();
+	}
+}
+
+void JsPlugin::addTimer(std::shared_ptr<Timer> timer) noexcept
+{
+	std::weak_ptr<Timer> ptr(timer);
+
+	/*
+	 * These signals are called from the Timer thread and are transmitted to irccd so that it can
+	 * calls appropriate timer functions.
+	 */
+	timer->onSignal.connect([this, ptr] () {
+		auto timer = ptr.lock();
+
+		if (timer) {
+			onTimerSignal(move(timer));
+		}
+	});
+	timer->onEnd.connect([this, ptr] () {
+		auto timer = ptr.lock();
+
+		if (timer) {
+			onTimerEnd(move(timer));
+		}
+	});
+
+	m_timers.insert(move(timer));
+}
+
+void JsPlugin::removeTimer(const std::shared_ptr<Timer> &timer) noexcept
+{
+	duk::StackAssert sa(m_context);
+
+	/* Remove the JavaScript function */
+	duk::push(m_context, duk::Null{});
+	duk::putGlobal(m_context, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
+
+	/* Remove from list */
+	m_timers.erase(timer);
+}
+
+void JsPlugin::onChannelMode(const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &mode,
+			     const std::string &arg)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, mode);
+	duk::push(m_context, arg);
+	call("onChannelMode", 5);
+}
+
+void JsPlugin::onChannelNotice(const std::shared_ptr<Server> &server,
+			       const std::string &origin,
+			       const std::string &channel,
+			       const std::string &notice)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, notice);
+	call("onChannelNotice", 4);
+}
+
+void JsPlugin::onCommand(const std::shared_ptr<Server> &server,
+			 const std::string &origin,
+			 const std::string &channel,
+			 const std::string &message)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, message);
+	call("onCommand", 4);
+}
+
+void JsPlugin::onConnect(const std::shared_ptr<Server> &server)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	call("onConnect", 1);
+}
+
+void JsPlugin::onInvite(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	call("onInvite", 3);
+}
+
+void JsPlugin::onJoin(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	call("onJoin", 3);
+}
+
+void JsPlugin::onKick(const std::shared_ptr<Server> &server,
+		      const std::string &origin,
+		      const std::string &channel,
+		      const std::string &target,
+		      const std::string &reason)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, target);
+	duk::push(m_context, reason);
+	call("onKick", 5);
+}
+
+void JsPlugin::onLoad()
+{
+	duk::StackAssert sa(m_context);
+
+	call("onLoad", 0);
+}
+
+void JsPlugin::onMessage(const std::shared_ptr<Server> &server,
+			 const std::string &origin,
+			 const std::string &channel,
+			 const std::string &message)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, message);
+	call("onMessage", 4);
+}
+
+void JsPlugin::onMe(const std::shared_ptr<Server> &server,
+		    const std::string &origin,
+		    const std::string &channel,
+		    const std::string &message)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, message);
+	call("onMe", 4);
+}
+
+void JsPlugin::onMode(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, mode);
+	call("onMode", 3);
+}
+
+void JsPlugin::onNames(const std::shared_ptr<Server> &server, const std::string &channel, const std::vector<std::string> &names)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, channel);
+	duk::push(m_context, names);
+	call("onNames", 3);
+}
+
+void JsPlugin::onNick(const std::shared_ptr<Server> &server, const std::string &oldnick, const std::string &newnick)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, oldnick);
+	duk::push(m_context, newnick);
+	call("onNick", 3);
+}
+
+void JsPlugin::onNotice(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, notice);
+	call("onNotice", 3);
+}
+
+void JsPlugin::onPart(const std::shared_ptr<Server> &server,
+		      const std::string &origin,
+		      const std::string &channel,
+		      const std::string &reason)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, reason);
+	call("onPart", 4);
+}
+
+void JsPlugin::onQuery(const std::shared_ptr<Server> &server,
+		       const std::string &origin,
+		       const std::string &message)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, message);
+	call("onQuery", 3);
+}
+
+void JsPlugin::onQueryCommand(const std::shared_ptr<Server> &server,
+			      const std::string &origin,
+			      const std::string &message)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, message);
+	call("onQueryCommand", 3);
+}
+
+void JsPlugin::onReload()
+{
+	duk::StackAssert sa(m_context);
+
+	call("onReload");
+}
+
+void JsPlugin::onTopic(const std::shared_ptr<Server> &server,
+		       const std::string &origin,
+		       const std::string &channel,
+		       const std::string &topic)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, origin);
+	duk::push(m_context, channel);
+	duk::push(m_context, topic);
+	call("onTopic", 4);
+}
+
+void JsPlugin::onUnload()
+{
+	duk::StackAssert sa(m_context);
+
+	call("onUnload");
+}
+
+void JsPlugin::onWhois(const std::shared_ptr<Server> &server, const ServerWhois &whois)
+{
+	duk::StackAssert sa(m_context);
+
+	duk::push(m_context, duk::Shared<Server>{server});
+	duk::push(m_context, duk::Object{});
+	duk::putProperty(m_context, -1, "nickname", whois.nick);
+	duk::putProperty(m_context, -1, "username", whois.user);
+	duk::putProperty(m_context, -1, "realname", whois.realname);
+	duk::putProperty(m_context, -1, "host", whois.host);
+	duk::putProperty(m_context, 1, "channels", whois.channels);
+	call("onWhois", 2);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/irccd/plugin-js.hpp	Thu May 12 15:06:11 2016 +0200
@@ -0,0 +1,333 @@
+/*
+ * plugin-js.hpp -- JavaScript plugins for irccd
+ *
+ * Copyright (c) 2013-2016 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_PLUGIN_JS_HPP
+#define IRCCD_PLUGIN_JS_HPP
+
+/**
+ * \file plugin-hs.hpp
+ * \brief JavaScript plugins for irccd.
+ */
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Timers that a plugin owns.
+ */
+using PluginTimers = std::unordered_set<std::shared_ptr<Timer>>;
+
+/**
+ * \brief JavaScript plugins for irccd.
+ */
+class JsPlugin : public Plugin {
+public:
+	// TODO: remove with future modules
+
+	/**
+	 * Signal: onTimerSignal
+	 * ------------------------------------------------
+	 *
+	 * When a timer expires.
+	 *
+	 * Arguments:
+	 * - the timer object
+	 */
+	Signal<std::shared_ptr<Timer>> onTimerSignal;
+
+	/**
+	 * Signal: onTimerEnd
+	 * ------------------------------------------------
+	 *
+	 * When a timer is finished.
+	 *
+	 * Arguments:
+	 * - the timer object
+	 */
+	Signal<std::shared_ptr<Timer>> onTimerEnd;
+
+private:
+	// JavaScript context
+	duk::Context m_context;
+
+	// Plugin info and its timers
+	PluginTimers m_timers;
+
+	// Private helpers
+	void call(const std::string &name, unsigned nargs = 0);
+	void putVars();
+	void putPath(const std::string &varname, const std::string &append, path::Path type);
+	void putPaths();
+	void putConfig(const PluginConfig &config);
+
+public:
+	/**
+	 * Constructor.
+	 *
+	 * \param name the plugin name
+	 * \param path the path to the plugin
+	 * \param config the configuration
+	 */
+	JsPlugin(std::string name, std::string path, const PluginConfig &config = PluginConfig());
+
+	/**
+	 * Close timers.
+	 */
+	~JsPlugin();
+
+	/**
+	 * Add a timer to the plugin.
+	 *
+	 * \param timer the timer to add
+	 */
+	void addTimer(std::shared_ptr<Timer> timer) noexcept;
+
+	/**
+	 * Remove a timer from a plugin.
+	 *
+	 * \param timer
+	 */
+	void removeTimer(const std::shared_ptr<Timer> &timer) noexcept;
+
+	/**
+	 * Access the Duktape context.
+	 *
+	 * \return the context
+	 */
+	inline duk::Context &context() noexcept
+	{
+		return m_context;
+	}
+
+	/**
+	 * On channel message. This event will call onMessage or
+	 * onCommand if the messages starts with the command character
+	 * plus the plugin name.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the message
+	 * \param channel the channel
+	 * \param message the message or command
+	 */
+	void onCommand(const std::shared_ptr<Server> &server,
+		       const std::string &origin,
+		       const std::string &channel,
+		       const std::string &message) override;
+
+	/**
+	 * On successful connection.
+	 *
+	 * \param server the server
+	 */
+	void onConnect(const std::shared_ptr<Server> &server) override;
+
+	/**
+	 * On channel mode.
+	 *
+	 * \param server the server
+	 * \param origin the ouser who has changed the mode
+	 * \param channel the channel
+	 * \param mode the mode
+	 * \param arg the optional mode argument
+	 */
+	void onChannelMode(const std::shared_ptr<Server> &server,
+			   const std::string &origin,
+			   const std::string &channel,
+			   const std::string &mode,
+			   const std::string &arg) override;
+
+	/**
+	 * On a channel notice.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the notice
+	 * \param channel on which channel
+	 * \param notice the message
+	 */
+	void onChannelNotice(const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &notice) override;
+
+	/**
+	 * On invitation.
+	 *
+	 * \param server the server
+	 * \param origin the user who invited you
+	 * \param channel the channel
+	 */
+	void onInvite(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override;
+
+	/**
+	 * On join.
+	 *
+	 * \param server the server
+	 * \param origin the user who joined
+	 * \param channel the channel
+	 */
+	void onJoin(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel) override;
+
+	/**
+	 * On kick.
+	 *
+	 * \param server the server
+	 * \param origin the user who kicked the target
+	 * \param channel the channel
+	 * \param target the kicked target
+	 * \param reason the optional reason
+	 */
+	void onKick(const std::shared_ptr<Server> &server,
+		    const std::string &origin,
+		    const std::string &channel,
+		    const std::string &target,
+		    const std::string &reason) override;
+
+	/**
+	 * On load.
+	 */
+	void onLoad() override;
+
+	/**
+	 * On channel message.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the message
+	 * \param channel the channel
+	 * \param message the message or command
+	 */
+	void onMessage(const std::shared_ptr<Server> &server,
+			       const std::string &origin,
+			       const std::string &channel,
+			       const std::string &message) override;
+
+	/**
+	 * On CTCP Action.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the message
+	 * \param channel the channel (may also be your nickname)
+	 * \param message the message
+	 */
+	void onMe(const std::shared_ptr<Server> &server,
+		  const std::string &origin,
+		  const std::string &channel,
+		  const std::string &message) override;
+
+	/**
+	 * On user mode change.
+	 *
+	 * \param server the server
+	 * \param origin the person who changed the mode
+	 * \param mode the new mode
+	 */
+	void onMode(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode) override;
+
+	/**
+	 * On names listing.
+	 *
+	 * \param server the server
+	 * \param channel the channel
+	 * \param list the list of nicknames
+	 */
+	void onNames(const std::shared_ptr<Server> &server, const std::string &channel, const std::vector<std::string> &list) override;
+
+	/**
+	 * On nick change.
+	 *
+	 * \param server the server
+	 * \param origin the user that changed its nickname
+	 * \param nick the new nickname
+	 */
+	void onNick(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick) override;
+
+	/**
+	 * On user notice.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the notice
+	 * \param notice the notice
+	 */
+	void onNotice(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice) override;
+
+	/**
+	 * On part.
+	 *
+	 * \param server the server
+	 * \param origin the user who left
+	 * \param channel the channel
+	 * \param reason the optional reason
+	 */
+	void onPart(const std::shared_ptr<Server> &server,
+		    const std::string &origin,
+		    const std::string &channel,
+		    const std::string &reason) override;
+
+	/**
+	 * On user query.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the query
+	 * \param message the message
+	 */
+	void onQuery(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) override;
+
+	/**
+	 * On user query command.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the query
+	 * \param message the message
+	 */
+	void onQueryCommand(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message) override;
+
+	/**
+	 * On reload.
+	 */
+	void onReload() override;
+
+	/**
+	 * On topic change.
+	 *
+	 * \param server the server
+	 * \param origin the user who sent the topic
+	 * \param channel the channel
+	 * \param topic the new topic
+	 */
+	void onTopic(const std::shared_ptr<Server> &server,
+		     const std::string &origin,
+		     const std::string &channel,
+		     const std::string &topic) override;
+
+	/**
+	 * On unload.
+	 */
+	void onUnload() override;
+
+	/**
+	 * On whois information.
+	 *
+	 * \param server the server
+	 * \param info the info
+	 */
+	void onWhois(const std::shared_ptr<Server> &server, const ServerWhois &info) override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_JS_HPP
--- a/lib/irccd/plugin.cpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/plugin.cpp	Thu May 12 15:06:11 2016 +0200
@@ -20,29 +20,10 @@
 
 #include <format.h>
 
-#include "sysconfig.hpp"
-
-#if defined(HAVE_STAT)
-#  include <sys/stat.h>
-#  include <cerrno>
-#  include <cstring>
-#endif
-
 #include "fs.hpp"
-#include "js-directory.hpp"
-#include "js-elapsed-timer.hpp"
-#include "js-file.hpp"
-#include "js-irccd.hpp"
-#include "js-logger.hpp"
-#include "js-plugin.hpp"
-#include "js-server.hpp"
-#include "js-system.hpp"
-#include "js-timer.hpp"
-#include "js-unicode.hpp"
-#include "js-util.hpp"
 #include "logger.hpp"
 #include "path.hpp"
-#include "plugin.hpp"
+#include "plugin-js.hpp"
 #include "server.hpp"
 #include "util.hpp"
 
@@ -52,106 +33,6 @@
 
 namespace irccd {
 
-void Plugin::call(const std::string &name, unsigned nargs)
-{
-	duk::getGlobal<void>(m_context, name);
-
-	if (duk::type(m_context, -1) == DUK_TYPE_UNDEFINED) {
-		/* Function not defined, remove the undefined value and all arguments */
-		duk::pop(m_context, nargs + 1);
-	} else {
-		/* Call the function and discard the result */
-		duk::insert(m_context, -nargs - 1);
-
-		if (duk::pcall(m_context, nargs) != 0) {
-			auto error = duk::error(m_context, -1);
-
-			duk::pop(m_context);
-
-			throw error;
-		} else {
-			duk::pop(m_context);
-		}
-	}
-}
-
-void Plugin::putVars()
-{
-	duk::StackAssert sa(m_context);
-
-	/* Save a reference to this */
-	duk::putGlobal(m_context, "\xff""\xff""plugin", duk::RawPointer<Plugin>{this});
-	duk::putGlobal(m_context, "\xff""\xff""name", m_name);
-	duk::putGlobal(m_context, "\xff""\xff""path", m_path);
-}
-
-void Plugin::putPath(const std::string &varname, const std::string &append, path::Path type)
-{
-	duk::StackAssert sa(m_context);
-
-	bool found = true;
-	std::string foundpath;
-
-	/*
-	 * Use the first existing directory available.
-	 */
-	for (const std::string &p : path::list(type)) {
-		foundpath = path::clean(p + append);
-
-		if (fs::exists(foundpath)) {
-			found = true;
-			break;
-		}
-	}
-
-	/* Use the system as default */
-	if (!found) {
-		foundpath = path::clean(path::get(type, path::OwnerSystem) + append);
-	}
-
-	duk::getGlobal<void>(m_context, "Irccd");
-	duk::getProperty<void>(m_context, -1, "Plugin");
-	duk::putProperty(m_context, -1, varname, foundpath);
-	duk::pop(m_context, 2);
-}
-
-void Plugin::putPaths()
-{
-	duk::StackAssert sa(m_context);
-
-	/*
-	 * dataPath: DATA + plugin/name (e.g ~/.local/share/irccd/plugins/<name>/)
-	 * configPath: CONFIG + plugin/name (e.g ~/.config/irccd/plugin/<name>/)
-	 */
-	putPath("dataPath", "plugin/" + m_name, path::PathData);
-	putPath("configPath", "plugin/" + m_name, path::PathConfig);
-	putPath("cachePath", "plugin/" + m_name, path::PathCache);
-}
-
-void Plugin::putConfig(const PluginConfig &config)
-{
-	duk::StackAssert sa(m_context);
-
-	// TODO: override dataPath, configPath, cachePath
-
-	/* Store plugin configuration into Irccd.Plugin.config */
-	duk::getGlobal<void>(m_context, "Irccd");
-	duk::getProperty<void>(m_context, -1, "Plugin");
-	duk::getProperty<void>(m_context, -1, "config");
-
-	if (duk::type(m_context, -1) != DUK_TYPE_OBJECT) {
-		duk::pop(m_context);
-		duk::push(m_context, duk::Object{});
-	}
-
-	for (const auto &pair : config) {
-		duk::putProperty(m_context, -1, pair.first, pair.second);
-	}
-
-	duk::putProperty(m_context, -2, "config");
-	duk::pop(m_context, 2);
-}
-
 std::shared_ptr<Plugin> Plugin::find(const std::string &name, const PluginConfig &config)
 {
 	log::info("plugin {}: searching {}.js"_format(name, name));
@@ -166,7 +47,7 @@
 		log::info("plugin {}: trying {}"_format(name, fullpath));
 
 		try {
-			return std::make_shared<Plugin>(name, fullpath, config);
+			return std::make_shared<JsPlugin>(name, fullpath, config);
 		} catch (const std::exception &ex) {
 			throw std::runtime_error("{}: {}"_format(fullpath, ex.what()));
 		}
@@ -175,343 +56,4 @@
 	throw std::runtime_error("no suitable plugin found");
 }
 
-Plugin::Plugin(std::string name, std::string path, const PluginConfig &config)
-	: m_name(std::move(name))
-	, m_path(std::move(path))
-{
-	duk::StackAssert sa(m_context);
-
-	/*
-	 * Duktape currently emit useless warnings when a file do
-	 * not exists so we do a homemade access.
-	 */
-#if defined(HAVE_STAT)
-	struct stat st;
-
-	if (stat(m_path.c_str(), &st) < 0) {
-		throw std::runtime_error(std::strerror(errno));
-	}
-#endif
-
-#if 0
-	// TODO: not enabled now
-
-	/*
-	 * Store the base path to the plugin, it is required for
-	 * Duktape.modSearch to find external modules and other
-	 * sources.
-	 *
-	 * If path is absolute, the parent is the directory name, otherwise
-	 * we use the current working directory (needed for some tests).
-	 */
-	if (fs::isAbsolute(m_info.path)) {
-		m_info.parent = fs::dirName(m_info.path);
-	} else {
-		m_info.parent = fs::cwd();
-	}
-#endif
-
-	/* Load standard irccd API */
-	loadJsIrccd(m_context);
-	loadJsDirectory(m_context);
-	loadJsElapsedTimer(m_context);
-	loadJsFile(m_context);
-	loadJsLogger(m_context);
-	loadJsPlugin(m_context);
-	loadJsServer(m_context);
-	loadJsSystem(m_context);
-	loadJsTimer(m_context);
-	loadJsUnicode(m_context);
-	loadJsUtil(m_context);
-
-	putVars();
-	putPaths();
-
-	/* Try to load the file (does not call onLoad yet) */
-	if (duk::pevalFile(m_context, m_path) != 0) {
-		throw duk::error(m_context, -1);
-	}
-
-	duk::pop(m_context);
-
-	/* Initialize user defined options after loading to allow the plugin to define default values */
-	putConfig(config);
-
-	/* Read metadata */
-	duk::getGlobal<void>(m_context, "info");
-
-	if (duk::type(m_context, -1) == DUK_TYPE_OBJECT) {
-		m_author = duk::optionalProperty<std::string>(m_context, -1, "author", m_author);
-		m_license = duk::optionalProperty<std::string>(m_context, -1, "license", m_license);
-		m_summary = duk::optionalProperty<std::string>(m_context, -1, "summary", m_summary);
-		m_version = duk::optionalProperty<std::string>(m_context, -1, "version", m_version);
-	}
-
-	duk::pop(m_context);
-
-	log::debug() << "plugin " << m_name << ": " << std::endl;
-	log::debug() << "  author:  " << m_author << std::endl;
-	log::debug() << "  license: " << m_license << std::endl;
-	log::debug() << "  summary: " << m_summary << std::endl;
-	log::debug() << "  version: " << m_version << std::endl;
-}
-
-Plugin::~Plugin()
-{
-	for (auto &timer : m_timers) {
-		timer->stop();
-	}
-}
-
-void Plugin::addTimer(std::shared_ptr<Timer> timer) noexcept
-{
-	std::weak_ptr<Timer> ptr(timer);
-
-	/*
-	 * These signals are called from the Timer thread and are transmitted to irccd so that it can
-	 * calls appropriate timer functions.
-	 */
-	timer->onSignal.connect([this, ptr] () {
-		auto timer = ptr.lock();
-
-		if (timer) {
-			onTimerSignal(move(timer));
-		}
-	});
-	timer->onEnd.connect([this, ptr] () {
-		auto timer = ptr.lock();
-
-		if (timer) {
-			onTimerEnd(move(timer));
-		}
-	});
-
-	m_timers.insert(move(timer));
-}
-
-void Plugin::removeTimer(const std::shared_ptr<Timer> &timer) noexcept
-{
-	duk::StackAssert sa(m_context);
-
-	/* Remove the JavaScript function */
-	duk::push(m_context, duk::Null{});
-	duk::putGlobal(m_context, "\xff""\xff""timer-" + std::to_string(reinterpret_cast<std::intptr_t>(timer.get())));
-
-	/* Remove from list */
-	m_timers.erase(timer);
-}
-
-void Plugin::onChannelMode(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string mode, std::string arg)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(mode));
-	duk::push(m_context, move(arg));
-	call("onChannelMode", 5);
-}
-
-void Plugin::onChannelNotice(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string notice)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(notice));
-	call("onChannelNotice", 4);
-}
-
-void Plugin::onCommand(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(message));
-	call("onCommand", 4);
-}
-
-void Plugin::onConnect(std::shared_ptr<Server> server)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	call("onConnect", 1);
-}
-
-void Plugin::onInvite(std::shared_ptr<Server> server, std::string origin, std::string channel)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	call("onInvite", 3);
-}
-
-void Plugin::onJoin(std::shared_ptr<Server> server, std::string origin, std::string channel)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	call("onJoin", 3);
-}
-
-void Plugin::onKick(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string target, std::string reason)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(target));
-	duk::push(m_context, move(reason));
-	call("onKick", 5);
-}
-
-void Plugin::onLoad()
-{
-	duk::StackAssert sa(m_context);
-
-	call("onLoad", 0);
-}
-
-void Plugin::onMessage(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(message));
-	call("onMessage", 4);
-}
-
-void Plugin::onMe(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(message));
-	call("onMe", 4);
-}
-
-void Plugin::onMode(std::shared_ptr<Server> server, std::string origin, std::string mode)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(mode));
-	call("onMode", 3);
-}
-
-void Plugin::onNames(std::shared_ptr<Server> server, std::string channel, std::vector<std::string> names)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(names));
-	call("onNames", 3);
-}
-
-void Plugin::onNick(std::shared_ptr<Server> server, std::string oldnick, std::string newnick)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(oldnick));
-	duk::push(m_context, move(newnick));
-	call("onNick", 3);
-}
-
-void Plugin::onNotice(std::shared_ptr<Server> server, std::string origin, std::string notice)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(notice));
-	call("onNotice", 3);
-}
-
-void Plugin::onPart(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string reason)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(reason));
-	call("onPart", 4);
-}
-
-void Plugin::onQuery(std::shared_ptr<Server> server, std::string origin, std::string message)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(message));
-	call("onQuery", 3);
-}
-
-void Plugin::onQueryCommand(std::shared_ptr<Server> server, std::string origin, std::string message)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(message));
-	call("onQueryCommand", 3);
-}
-
-void Plugin::onReload()
-{
-	duk::StackAssert sa(m_context);
-
-	call("onReload");
-}
-
-void Plugin::onTopic(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string topic)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, move(origin));
-	duk::push(m_context, move(channel));
-	duk::push(m_context, move(topic));
-	call("onTopic", 4);
-}
-
-void Plugin::onUnload()
-{
-	duk::StackAssert sa(m_context);
-
-	call("onUnload");
-}
-
-void Plugin::onWhois(std::shared_ptr<Server> server, ServerWhois whois)
-{
-	duk::StackAssert sa(m_context);
-
-	duk::push(m_context, duk::Shared<Server>{server});
-	duk::push(m_context, duk::Object{});
-	duk::putProperty(m_context, -1, "nickname", whois.nick);
-	duk::putProperty(m_context, -1, "username", whois.user);
-	duk::putProperty(m_context, -1, "realname", whois.realname);
-	duk::putProperty(m_context, -1, "host", whois.host);
-	duk::putProperty(m_context, 1, "channels", whois.channels);
-	call("onWhois", 2);
-}
-
 } // !irccd
--- a/lib/irccd/plugin.hpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/plugin.hpp	Thu May 12 15:06:11 2016 +0200
@@ -46,11 +46,6 @@
 using PluginConfig = std::unordered_map<std::string, std::string>;
 
 /**
- * Timers that a plugin owns.
- */
-using PluginTimers = std::unordered_set<std::shared_ptr<Timer>>;
-
-/**
  * \class Plugin
  * \brief JavaScript plugin
  *
@@ -58,29 +53,6 @@
  * at runtime.
  */
 class Plugin {
-public:
-	/**
-	 * Signal: onTimerSignal
-	 * ------------------------------------------------
-	 *
-	 * When a timer expires.
-	 *
-	 * Arguments:
-	 * - the timer object
-	 */
-	Signal<std::shared_ptr<Timer>> onTimerSignal;
-
-	/**
-	 * Signal: onTimerEnd
-	 * ------------------------------------------------
-	 *
-	 * When a timer is finished.
-	 *
-	 * Arguments:
-	 * - the timer object
-	 */
-	Signal<std::shared_ptr<Timer>> onTimerEnd;
-
 private:
 	// Plugin information
 	std::string m_name;
@@ -92,18 +64,7 @@
 	std::string m_summary{"unknown"};
 	std::string m_version{"unknown"};
 
-	// JavaScript context
-	duk::Context m_context;
-
-	// Plugin info and its timers
-	PluginTimers m_timers;
-
-	// Private helpers
-	void call(const std::string &name, unsigned nargs = 0);
-	void putVars();
-	void putPath(const std::string &varname, const std::string &append, path::Path type);
-	void putPaths();
-	void putConfig(const PluginConfig &config);
+	PluginConfig m_config;
 
 public:
 	/**
@@ -122,12 +83,17 @@
 	 * \param config the plugin configuration
 	 * \throws std::runtime_error on errors
 	 */
-	Plugin(std::string name, std::string path, const PluginConfig &config = PluginConfig());
+	inline Plugin(std::string name, std::string path, PluginConfig config = PluginConfig()) noexcept
+		: m_name(std::move(name))
+		, m_path(std::move(path))
+		, m_config(std::move(config))
+	{
+	}
 
 	/**
 	 * Temporary, close all timers.
 	 */
-	~Plugin();
+	virtual ~Plugin() = default;
 
 	/**
 	 * Get the plugin name.
@@ -231,30 +197,6 @@
 	}
 
 	/**
-	 * Add a timer to the plugin.
-	 *
-	 * \param timer the timer to add
-	 */
-	void addTimer(std::shared_ptr<Timer> timer) noexcept;
-
-	/**
-	 * Remove a timer from a plugin.
-	 *
-	 * \param timer
-	 */
-	void removeTimer(const std::shared_ptr<Timer> &timer) noexcept;
-
-	/**
-	 * Access the Duktape context.
-	 *
-	 * \return the context
-	 */
-	inline duk::Context &context() noexcept
-	{
-		return m_context;
-	}
-
-	/**
 	 * On channel message. This event will call onMessage or
 	 * onCommand if the messages starts with the command character
 	 * plus the plugin name.
@@ -264,14 +206,26 @@
 	 * \param channel the channel
 	 * \param message the message or command
 	 */
-	void onCommand(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message);
+	virtual void onCommand(const std::shared_ptr<Server> &server,
+			       const std::string &origin,
+			       const std::string &channel,
+			       const std::string &message)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)message;
+	}
 
 	/**
 	 * On successful connection.
 	 *
 	 * \param server the server
 	 */
-	void onConnect(std::shared_ptr<Server> server);
+	virtual void onConnect(const std::shared_ptr<Server> &server)
+	{
+		(void)server;
+	}
 
 	/**
 	 * On channel mode.
@@ -282,7 +236,18 @@
 	 * \param mode the mode
 	 * \param arg the optional mode argument
 	 */
-	void onChannelMode(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string mode, std::string arg);
+	virtual void onChannelMode(const std::shared_ptr<Server> &server,
+				   const std::string &origin,
+				   const std::string &channel,
+				   const std::string &mode,
+				   const std::string &arg)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)mode;
+		(void)arg;
+	}
 
 	/**
 	 * On a channel notice.
@@ -292,7 +257,16 @@
 	 * \param channel on which channel
 	 * \param notice the message
 	 */
-	void onChannelNotice(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string notice);
+	virtual void onChannelNotice(const std::shared_ptr<Server> &server,
+				     const std::string &origin,
+				     const std::string &channel,
+				     const std::string &notice)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)notice;
+	}
 
 	/**
 	 * On invitation.
@@ -301,7 +275,12 @@
 	 * \param origin the user who invited you
 	 * \param channel the channel
 	 */
-	void onInvite(std::shared_ptr<Server> server, std::string origin, std::string channel);
+	virtual void onInvite(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+	}
 
 	/**
 	 * On join.
@@ -310,7 +289,12 @@
 	 * \param origin the user who joined
 	 * \param channel the channel
 	 */
-	void onJoin(std::shared_ptr<Server> server, std::string origin, std::string channel);
+	virtual void onJoin(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &channel)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+	}
 
 	/**
 	 * On kick.
@@ -321,12 +305,25 @@
 	 * \param target the kicked target
 	 * \param reason the optional reason
 	 */
-	void onKick(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string target, std::string reason);
+	virtual void onKick(const std::shared_ptr<Server> &server,
+			    const std::string &origin,
+			    const std::string &channel,
+			    const std::string &target,
+			    const std::string &reason)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)target;
+		(void)reason;
+	}
 
 	/**
 	 * On load.
 	 */
-	void onLoad();
+	virtual void onLoad()
+	{
+	}
 
 	/**
 	 * On channel message.
@@ -336,7 +333,16 @@
 	 * \param channel the channel
 	 * \param message the message or command
 	 */
-	void onMessage(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message);
+	virtual void onMessage(const std::shared_ptr<Server> &server,
+			       const std::string &origin,
+			       const std::string &channel,
+			       const std::string &message)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)message;
+	}
 
 	/**
 	 * On CTCP Action.
@@ -346,7 +352,16 @@
 	 * \param channel the channel (may also be your nickname)
 	 * \param message the message
 	 */
-	void onMe(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string message);
+	virtual void onMe(const std::shared_ptr<Server> &server,
+			  const std::string &origin,
+			  const std::string &channel,
+			  const std::string &message)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)message;
+	}
 
 	/**
 	 * On user mode change.
@@ -355,7 +370,12 @@
 	 * \param origin the person who changed the mode
 	 * \param mode the new mode
 	 */
-	void onMode(std::shared_ptr<Server> server, std::string origin, std::string mode);
+	virtual void onMode(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &mode)
+	{
+		(void)server;
+		(void)origin;
+		(void)mode;
+	}
 
 	/**
 	 * On names listing.
@@ -364,7 +384,12 @@
 	 * \param channel the channel
 	 * \param list the list of nicknames
 	 */
-	void onNames(std::shared_ptr<Server> server, std::string channel, std::vector<std::string> list);
+	virtual void onNames(const std::shared_ptr<Server> &server, const std::string &channel, const std::vector<std::string> &list)
+	{
+		(void)server;
+		(void)channel;
+		(void)list;
+	}
 
 	/**
 	 * On nick change.
@@ -373,7 +398,12 @@
 	 * \param origin the user that changed its nickname
 	 * \param nick the new nickname
 	 */
-	void onNick(std::shared_ptr<Server> server, std::string origin, std::string nick);
+	virtual void onNick(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &nick)
+	{
+		(void)server;
+		(void)origin;
+		(void)nick;
+	}
 
 	/**
 	 * On user notice.
@@ -382,7 +412,12 @@
 	 * \param origin the user who sent the notice
 	 * \param notice the notice
 	 */
-	void onNotice(std::shared_ptr<Server> server, std::string origin, std::string notice);
+	virtual void onNotice(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &notice)
+	{
+		(void)server;
+		(void)origin;
+		(void)notice;
+	}
 
 	/**
 	 * On part.
@@ -392,7 +427,16 @@
 	 * \param channel the channel
 	 * \param reason the optional reason
 	 */
-	void onPart(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string reason);
+	virtual void onPart(const std::shared_ptr<Server> &server,
+			    const std::string &origin,
+			    const std::string &channel,
+			    const std::string &reason)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)reason;
+	}
 
 	/**
 	 * On user query.
@@ -401,7 +445,12 @@
 	 * \param origin the user who sent the query
 	 * \param message the message
 	 */
-	void onQuery(std::shared_ptr<Server> server, std::string origin, std::string message);
+	virtual void onQuery(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message)
+	{
+		(void)server;
+		(void)origin;
+		(void)message;
+	}
 
 	/**
 	 * On user query command.
@@ -410,12 +459,19 @@
 	 * \param origin the user who sent the query
 	 * \param message the message
 	 */
-	void onQueryCommand(std::shared_ptr<Server> server, std::string origin, std::string message);
+	virtual void onQueryCommand(const std::shared_ptr<Server> &server, const std::string &origin, const std::string &message)
+	{
+		(void)server;
+		(void)origin;
+		(void)message;
+	}
 
 	/**
 	 * On reload.
 	 */
-	void onReload();
+	virtual void onReload()
+	{
+	}
 
 	/**
 	 * On topic change.
@@ -425,12 +481,23 @@
 	 * \param channel the channel
 	 * \param topic the new topic
 	 */
-	void onTopic(std::shared_ptr<Server> server, std::string origin, std::string channel, std::string topic);
+	virtual void onTopic(const std::shared_ptr<Server> &server,
+			     const std::string &origin,
+			     const std::string &channel,
+			     const std::string &topic)
+	{
+		(void)server;
+		(void)origin;
+		(void)channel;
+		(void)topic;
+	}
 
 	/**
 	 * On unload.
 	 */
-	void onUnload();
+	virtual void onUnload()
+	{
+	}
 
 	/**
 	 * On whois information.
@@ -438,7 +505,11 @@
 	 * \param server the server
 	 * \param info the info
 	 */
-	void onWhois(std::shared_ptr<Server> server, ServerWhois info);
+	virtual void onWhois(const std::shared_ptr<Server> &server, const ServerWhois &info)
+	{
+		(void)server;
+		(void)info;
+	}
 };
 
 } // !irccd
--- a/lib/irccd/service-plugin.cpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/service-plugin.cpp	Thu May 12 15:06:11 2016 +0200
@@ -25,6 +25,7 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "plugin.hpp"
+#include "plugin-js.hpp"
 #include "service-plugin.hpp"
 
 using namespace fmt::literals;
@@ -71,13 +72,18 @@
 {
 	using namespace std::placeholders;
 
-	std::weak_ptr<Plugin> ptr(plugin);
+	// TODO: REMOVE WHEN WE GET THE JAVASCRIPT MODULES
+	std::shared_ptr<JsPlugin> jsp = std::dynamic_pointer_cast<JsPlugin>(plugin);
+
+	if (jsp) {
+		std::weak_ptr<JsPlugin> ptr(jsp);
 
-	plugin->onTimerSignal.connect(std::bind(&PluginService::handleTimerSignal, this, ptr, _1));
-	plugin->onTimerEnd.connect(std::bind(&PluginService::handleTimerEnd, this, ptr, _1));
+		jsp->onTimerSignal.connect(std::bind(&PluginService::handleTimerSignal, this, ptr, _1));
+		jsp->onTimerEnd.connect(std::bind(&PluginService::handleTimerEnd, this, ptr, _1));
 
-	// Store reference to irccd.
-	duk::putGlobal(plugin->context(), "\xff""\xff""irccd", duk::RawPointer<Irccd>{&m_irccd});
+		// Store reference to irccd.
+		duk::putGlobal(jsp->context(), "\xff""\xff""irccd", duk::RawPointer<Irccd>{&m_irccd});
+	}
 
 	// Initial load now.
 	try {
@@ -152,7 +158,7 @@
 	}
 }
 
-void PluginService::handleTimerSignal(std::weak_ptr<Plugin> ptr, std::shared_ptr<Timer> timer)
+void PluginService::handleTimerSignal(std::weak_ptr<JsPlugin> ptr, std::shared_ptr<Timer> timer)
 {
 	m_irccd.post([this, ptr, timer] (Irccd &) {
 		auto plugin = ptr.lock();
@@ -175,7 +181,7 @@
 	});
 }
 
-void PluginService::handleTimerEnd(std::weak_ptr<Plugin> ptr, std::shared_ptr<Timer> timer)
+void PluginService::handleTimerEnd(std::weak_ptr<JsPlugin> ptr, std::shared_ptr<Timer> timer)
 {
 	m_irccd.post([this, ptr, timer] (Irccd &) {
 		auto plugin = ptr.lock();
--- a/lib/irccd/service-plugin.hpp	Wed May 11 21:09:10 2016 +0200
+++ b/lib/irccd/service-plugin.hpp	Thu May 12 15:06:11 2016 +0200
@@ -31,6 +31,7 @@
 
 class Irccd;
 class Plugin;
+class JsPlugin;
 class Timer;
 
 /**
@@ -42,8 +43,8 @@
 	std::vector<std::shared_ptr<Plugin>> m_plugins;
 
 	// TODO: get rid of this with future JavaScript modules.
-	void handleTimerSignal(std::weak_ptr<Plugin>, std::shared_ptr<Timer>);
-	void handleTimerEnd(std::weak_ptr<Plugin>, std::shared_ptr<Timer>);
+	void handleTimerSignal(std::weak_ptr<JsPlugin>, std::shared_ptr<Timer>);
+	void handleTimerEnd(std::weak_ptr<JsPlugin>, std::shared_ptr<Timer>);
 
 public:
 	/**
--- a/tests/js-timer/main.cpp	Wed May 11 21:09:10 2016 +0200
+++ b/tests/js-timer/main.cpp	Thu May 12 15:06:11 2016 +0200
@@ -21,7 +21,7 @@
 #include <irccd/elapsed-timer.hpp>
 #include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
-#include <irccd/plugin.hpp>
+#include <irccd/plugin-js.hpp>
 #include <irccd/service-plugin.hpp>
 #include <irccd/system.hpp>
 
@@ -32,7 +32,7 @@
 	Irccd irccd;
 	ElapsedTimer timer;
 
-	auto plugin = std::make_shared<Plugin>("timer", IRCCD_TESTS_DIRECTORY "/timer-single.js");
+	auto plugin = std::make_shared<JsPlugin>("timer", IRCCD_TESTS_DIRECTORY "/timer-single.js");
 
 	irccd.pluginService().add(plugin);
 
@@ -49,7 +49,7 @@
 	Irccd irccd;
 	ElapsedTimer timer;
 
-	auto plugin = std::make_shared<Plugin>("timer", IRCCD_TESTS_DIRECTORY "/timer-repeat.js");
+	auto plugin = std::make_shared<JsPlugin>("timer", IRCCD_TESTS_DIRECTORY "/timer-repeat.js");
 
 	irccd.pluginService().add(plugin);