Mercurial > irccd
changeset 618:5afc0b3a9ad8
Plugin joke: brand new plugin, closes #609 @2h
The new joke plugin offers a convenient registry of jokes that are displayed in
a random and unique order.
It keeps track of displayed jokes per channel/server pairs.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 19 Dec 2017 22:02:12 +0100 |
parents | 241583937af0 |
children | a2ece4ed9f5d |
files | CHANGES.md plugins/joke/joke.js plugins/joke/joke.md tests/src/plugins/CMakeLists.txt tests/src/plugins/joke/CMakeLists.txt tests/src/plugins/joke/jokes-empty.json tests/src/plugins/joke/jokes-invalid.json tests/src/plugins/joke/jokes-not-array.json tests/src/plugins/joke/jokes-toobig.json tests/src/plugins/joke/jokes.json tests/src/plugins/joke/main.cpp |
diffstat | 11 files changed, 411 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGES.md Tue Dec 19 20:22:31 2017 +0100 +++ b/CHANGES.md Tue Dec 19 22:02:12 2017 +0100 @@ -33,6 +33,10 @@ (#594), (#595), (#681), (#697), - The libircclient has been replaced by a simple homemade library (#581). +Plugins: + + - Introduce brand new joke plugin (#609). + irccd 2.2.0 2017-09-26 ----------------------
--- a/plugins/joke/joke.js Tue Dec 19 20:22:31 2017 +0100 +++ b/plugins/joke/joke.js Tue Dec 19 22:02:12 2017 +0100 @@ -95,36 +95,62 @@ try { var file = new File(path, "r"); + var data = JSON.parse(file.read()); } catch (e) { throw Error(path + ": " + e.message); } - var data = JSON.parse(file.read()); - if (!data || !data.length) throw Error(path + ": no jokes found"); + // Ensure that jokes only contain strings. var jokes = data.filter(function (joke) { - return joke && joke.length <= Plugin.config["max-list-lines"]; + if (!joke || joke.length == 0 || joke.length > parseInt(Plugin.config["max-list-lines"])) + return false; + + for (var i = 0; i < joke.length; ++i) + if (typeof (joke[i]) !== "string") + return false; + + return true; }); - if (!jokes) + if (!jokes || jokes.length === 0) throw Error(path + ": empty jokes"); return jokes; } +/** + * Convert a pair server/channel into a unique identifier. + * + * \return channel@server + */ function id(server, channel) { return channel + "@" + server.toString(); } +/** + * Show the joke in the specified channel. + * + * \warning this function does not check for max-list-lines parameter + * \param server the server object + * \param channel the channel string + * \param joke the joke array (array of strings) + */ function show(server, channel, joke) { for (var l = 0; l < joke.length; ++l) server.message(channel, joke[l]); } +/** + * Remove the joke from the table. + * + * \param i the server/channel identifier + * \param index the joke index + */ function remove(i, index) { table[i].splice(index, 1); @@ -160,3 +186,9 @@ show(server, channel, table[i][index]); remove(i, index); } + +function onReload() +{ + // This will force reload of jokes on next onCommand. + table = {}; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/joke/joke.md Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,85 @@ +--- +title: "Joke plugin" +header: "Joke plugin" +guide: yes +--- + +The plugin **joke** is a convenient command to display jokes in a random order +without displaying always the same. + +It loads jokes per channel/server pair and display a unique joke each time it is +invoked. + +# Installation + +The plugin **joke** is distributed with irccd. To enable it add the following to +your `plugins` section: + +```ini +[plugins] +joke = "" +``` + +## Usage + +The plugin **joke** requires a database of jokes file, it consists of a plain +JSON file of array of array of strings. + +Example of **jokes.json** file: + +```javascript +[ + [ + "Tip to generate a good random password:", + "Ask a Windows user to quit vim." + ], + [ + "Have you tried turning it off and on again?" + ] +] +``` + +This file contains two jokes, the first one will be printed on two lines while +the second only has one. + +Then, invoke the plugin: + +```nohighlight +markand: !joke +irccd: Have you tried turning it off and on again? +markand: !joke +irccd: Tip to generate a good random password: +irccd: Ask a Windows user to quit vim. +``` + +## Configuration + +The following options are available under the `[plugin.history]` section: + + - **file**: (string) path to the JSON jokes files (Optional: defaults to data + directory/jokes.json) + +### Keywords supported + +The following keywords are supported: + +| Parameter | Keywords | +|-----------|-----------------| +| **file** | channel, server | + +Warning: if you use keywords in the **file** parameter, you won't have a default + joke database anymore. + +## Formats + +The **joke** plugin supports the following formats in `[format.joke]` section: + + - **error**: (string) format when an internal error occured. + +### Keywords supported + +The following keywords are supported: + +| Format | Keywords | +|-----------|-----------------------------------| +| **error** | channel, nickname, origin, server |
--- a/tests/src/plugins/CMakeLists.txt Tue Dec 19 20:22:31 2017 +0100 +++ b/tests/src/plugins/CMakeLists.txt Tue Dec 19 22:02:12 2017 +0100 @@ -20,5 +20,6 @@ add_subdirectory(auth) add_subdirectory(hangman) add_subdirectory(history) +add_subdirectory(joke) add_subdirectory(logger) add_subdirectory(plugin)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/CMakeLists.txt Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,32 @@ +# +# CMakeLists.txt -- CMake build system for irccd +# +# Copyright (c) 2013-2017 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. +# + +irccd_define_test( + NAME plugin-joke + SOURCES + main.cpp + jokes-empty.json + jokes-invalid.json + jokes.json + jokes-not-array.json + jokes-toobig.json + LIBRARIES libirccd + FLAGS + PLUGIN_NAME="joke" + PLUGIN_PATH="${CMAKE_SOURCE_DIR}/plugins/joke/joke.js" +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/jokes-empty.json Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,9 @@ +[ + [ + ], + [ + ], + [ + false + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/jokes-invalid.json Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,12 @@ +[ + [ + ], + [ + 1234, + true, + "still hav a string though" + ], + [ + "a" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/jokes-not-array.json Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,3 @@ +{ + "reason": "this is not a valid jokes database" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/jokes-toobig.json Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,15 @@ +[ + [ + "xxx", + "xxx", + "xxx" + ], + [ + "a" + ], + [ + "yyy", + "yyy", + "yyy" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/jokes.json Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,9 @@ +[ + [ + "aaa" + ], + [ + "bbbb", + "bbbb" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/src/plugins/joke/main.cpp Tue Dec 19 22:02:12 2017 +0100 @@ -0,0 +1,205 @@ +/* + * main.cpp -- test joke plugin + * + * Copyright (c) 2013-2017 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. + */ + +#define BOOST_TEST_MODULE "Joke plugin" +#include <boost/test/unit_test.hpp> + +#include <irccd/test/plugin_test.hpp> + +namespace irccd { + +class joke_test : public plugin_test { +public: + joke_test() + : plugin_test(PLUGIN_NAME, PLUGIN_PATH) + { + plugin_->set_formats({ + { "error", "error=#{server}:#{channel}:#{origin}:#{nickname}" } + }); + } + + void load(plugin_config config = {}) + { + // Add file if not there. + if (config.count("file") == 0) + config.emplace("file", CMAKE_CURRENT_SOURCE_DIR "/jokes.json"); + + plugin_->set_config(config); + plugin_->on_load(irccd_); + } +}; + +BOOST_FIXTURE_TEST_SUITE(joke_test_suite, joke_test) + +BOOST_AUTO_TEST_CASE(simple) +{ + /* + * Jokes.json have two jokes. + * + * aaa + * + * And + * + * bbbb + * bbbb + */ + std::unordered_map<std::string, int> said{ + { "aaa", 0 }, + { "bbbb", 0 } + }; + + load(); + + auto call = [&] () { + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + // "bbbb" is two lines. + if (cmd["message"] == "bbbb") { + auto first = server_->cqueue().front(); + + BOOST_TEST(first["command"].template get<std::string>() == "message"); + BOOST_TEST(first["target"].template get<std::string>() == "#joke"); + BOOST_TEST(first["message"].template get<std::string>() == "bbbb"); + } else + BOOST_TEST(cmd["message"].template get<std::string>() == "aaa"); + + said[cmd["message"].template get<std::string>()] += 1; + server_->cqueue().clear(); + }; + + call(); + call(); + + BOOST_TEST(said.size() == 2U); + BOOST_TEST(said["aaa"] == 1U); + BOOST_TEST(said["bbbb"] == 1U); +} + +BOOST_AUTO_TEST_CASE(toobig) +{ + // xxx and yyy are both 3-lines which we disallow. only a must be said. + load({ + { "file", CMAKE_CURRENT_SOURCE_DIR "/jokes-toobig.json" }, + { "max-list-lines", "2" } + }); + + std::unordered_map<std::string, int> said{ + { "a", 0 } + }; + + auto call = [&] () { + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + BOOST_TEST(cmd["command"].template get<std::string>() == "message"); + BOOST_TEST(cmd["target"].template get<std::string>() == "#joke"); + BOOST_TEST(cmd["message"].template get<std::string>() == "a"); + + said[cmd["message"].template get<std::string>()] += 1; + server_->cqueue().clear(); + }; + + call(); + call(); + call(); + + BOOST_TEST(said.size() == 1U); + BOOST_TEST(said["a"] == 3U); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + // Only a is the valid joke in this file. + load({ + { "file", CMAKE_CURRENT_SOURCE_DIR "/jokes-invalid.json" }, + }); + + std::unordered_map<std::string, int> said{ + { "a", 0 } + }; + + auto call = [&] () { + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + BOOST_TEST(cmd["command"].template get<std::string>() == "message"); + BOOST_TEST(cmd["target"].template get<std::string>() == "#joke"); + BOOST_TEST(cmd["message"].template get<std::string>() == "a"); + + server_->cqueue().clear(); + said[cmd["message"].template get<std::string>()] += 1; + }; + + call(); + call(); + call(); + + BOOST_TEST(said.size() == 1U); + BOOST_TEST(said["a"] == 3U); +} + +BOOST_AUTO_TEST_SUITE(errors) + +BOOST_AUTO_TEST_CASE(not_found) +{ + load({{"file", "doesnotexist.json"}}); + + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + BOOST_TEST(cmd["command"].get<std::string>() == "message"); + BOOST_TEST(cmd["target"].get<std::string>() == "#joke"); + BOOST_TEST(cmd["message"].get<std::string>() == "error=test:#joke:jean!jean@localhost:jean"); +} + +BOOST_AUTO_TEST_CASE(not_array) +{ + load({{"file", CMAKE_CURRENT_SOURCE_DIR "/jokes-not-array.json"}}); + + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + BOOST_TEST(cmd["command"].get<std::string>() == "message"); + BOOST_TEST(cmd["target"].get<std::string>() == "#joke"); + BOOST_TEST(cmd["message"].get<std::string>() == "error=test:#joke:jean!jean@localhost:jean"); +} + +BOOST_AUTO_TEST_CASE(empty) +{ + load({{"file", CMAKE_CURRENT_SOURCE_DIR "/jokes-empty.json"}}); + + plugin_->on_command(irccd_, {server_, "jean!jean@localhost", "#joke", ""}); + + auto cmd = server_->cqueue().back(); + + BOOST_TEST(cmd["command"].get<std::string>() == "message"); + BOOST_TEST(cmd["target"].get<std::string>() == "#joke"); + BOOST_TEST(cmd["message"].get<std::string>() == "error=test:#joke:jean!jean@localhost:jean"); +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() + +} // !irccd