Mercurial > irccd
view libirccd/irccd/daemon/command.cpp @ 804:d55a64c6586b
irccdctl: unify CLI output, closes #928 @1h
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 14 Nov 2018 14:07:05 +0100 |
parents | 1a6152af0866 |
children |
line wrap: on
line source
/* * command.cpp -- remote command * * 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/string_util.hpp> #include "command.hpp" #include "irccd.hpp" #include "plugin.hpp" #include "plugin_service.hpp" #include "rule.hpp" #include "rule_service.hpp" #include "rule_util.hpp" #include "server.hpp" #include "server_service.hpp" #include "server_util.hpp" #include "transport_client.hpp" using namespace std::string_literals; namespace irccd { namespace { void exec_set(transport_client& client, plugin& plugin, const nlohmann::json& args) { assert(args.count("value") > 0); const auto var = args.find("variable"); const auto value = args.find("value"); if (var == args.end() || !var->is_string()) throw irccd_error(irccd_error::error::incomplete_message); if (value == args.end() || !value->is_string()) throw irccd_error(irccd_error::error::incomplete_message); auto config = plugin.get_options(); config[*var] = *value; plugin.set_options(config); client.success("plugin-config"); } void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args) { auto variables = nlohmann::json::object(); auto var = args.find("variable"); if (var != args.end() && var->is_string()) variables[var->get<std::string>()] = plugin.get_options()[*var]; else for (const auto& pair : plugin.get_options()) variables[pair.first] = pair.second; /* * Don't put all variables into the response, put them into a sub * property 'variables' instead. * * It's easier for the client to iterate over all. */ client.write({ { "command", "plugin-config" }, { "variables", variables } }); } template <typename T> auto bind() noexcept -> command::constructor { return [] () noexcept { return std::make_unique<T>(); }; } } // !namespace const std::vector<command::constructor> command::registry{ bind<plugin_config_command>(), bind<plugin_info_command>(), bind<plugin_list_command>(), bind<plugin_load_command>(), bind<plugin_reload_command>(), bind<plugin_unload_command>(), bind<rule_add_command>(), bind<rule_edit_command>(), bind<rule_info_command>(), bind<rule_info_command>(), bind<rule_list_command>(), bind<rule_move_command>(), bind<rule_remove_command>(), bind<server_connect_command>(), bind<server_disconnect_command>(), bind<server_info_command>(), bind<server_invite_command>(), bind<server_join_command>(), bind<server_kick_command>(), bind<server_list_command>(), bind<server_me_command>(), bind<server_message_command>(), bind<server_mode_command>(), bind<server_nick_command>(), bind<server_notice_command>(), bind<server_part_command>(), bind<server_reconnect_command>(), bind<server_topic_command>(), }; // {{{ plugin_config_command auto plugin_config_command::get_name() const noexcept -> std::string_view { return "plugin-config"; } void plugin_config_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("plugin"); if (!id || !string_util::is_identifier(*id)) throw plugin_error(plugin_error::invalid_identifier); const auto plugin = irccd.plugins().require(*id); if (args.count("value") > 0) exec_set(client, *plugin, args); else exec_get(client, *plugin, args); } // }}} // {{{ plugin_info_command auto plugin_info_command::get_name() const noexcept -> std::string_view { return "plugin-info"; } void plugin_info_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("plugin"); if (!id || !string_util::is_identifier(*id)) throw plugin_error(plugin_error::invalid_identifier); const auto plugin = irccd.plugins().require(*id); client.write({ { "command", "plugin-info" }, { "author", std::string(plugin->get_author()) }, { "license", std::string(plugin->get_license()) }, { "summary", std::string(plugin->get_summary()) }, { "version", std::string(plugin->get_version()) } }); } // }}} // {{{ plugin_list_command auto plugin_list_command::get_name() const noexcept -> std::string_view { return "plugin-list"; } void plugin_list_command::exec(irccd& irccd, transport_client& client, const document&) { auto list = nlohmann::json::array(); for (const auto& plg : irccd.plugins().list()) list += plg->get_id(); client.write({ { "command", "plugin-list" }, { "list", list } }); } // }}} // {{{ plugin_load_command auto plugin_load_command::get_name() const noexcept -> std::string_view { return "plugin-load"; } void plugin_load_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("plugin"); if (!id || !string_util::is_identifier(*id)) throw plugin_error(plugin_error::invalid_identifier); irccd.plugins().load(*id, ""); client.success("plugin-load"); } // }}} // {{{ plugin_reload_command auto plugin_reload_command::get_name() const noexcept -> std::string_view { return "plugin-reload"; } void plugin_reload_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("plugin"); if (!id || !string_util::is_identifier(*id)) throw plugin_error(plugin_error::invalid_identifier); irccd.plugins().reload(*id); client.success("plugin-reload"); } // }}} // {{{ plugin_unload_command auto plugin_unload_command::get_name() const noexcept -> std::string_view { return "plugin-unload"; } void plugin_unload_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("plugin"); if (!id || !string_util::is_identifier(*id)) throw plugin_error(plugin_error::invalid_identifier); irccd.plugins().unload(*id); client.success("plugin-unload"); } // }}} // {{{ rule_add_command auto rule_add_command::get_name() const noexcept -> std::string_view { return "rule-add"; } void rule_add_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto index = args.optional<unsigned>("index", irccd.rules().list().size()); if (!index || *index > irccd.rules().list().size()) throw rule_error(rule_error::error::invalid_index); irccd.rules().insert(rule_util::from_json(args), *index); client.success("rule-add"); } // }}} // {{{ rule_edit_command auto rule_edit_command::get_name() const noexcept -> std::string_view { return "rule-edit"; } void rule_edit_command::exec(irccd& irccd, transport_client& client, const document& args) { static const auto updateset = [] (auto& set, auto args, const auto& key) { for (const auto& v : args["remove-"s + key]) { if (v.is_string()) set.erase(v.template get<std::string>()); } for (const auto& v : args["add-"s + key]) { if (v.is_string()) set.insert(v.template get<std::string>()); } }; const auto index = args.get<unsigned>("index"); if (!index) throw rule_error(rule_error::invalid_index); // Create a copy to avoid incomplete edition in case of errors. auto rule = irccd.rules().require(*index); updateset(rule.channels, args, "channels"); updateset(rule.events, args, "events"); updateset(rule.plugins, args, "plugins"); updateset(rule.servers, args, "servers"); auto action = args.find("action"); if (action != args.end()) { if (!action->is_string()) throw rule_error(rule_error::error::invalid_action); if (action->get<std::string>() == "accept") rule.action = rule::action_type::accept; else if (action->get<std::string>() == "drop") rule.action = rule::action_type::drop; else throw rule_error(rule_error::invalid_action); } // All done, sync the rule. irccd.rules().require(*index) = rule; client.success("rule-edit"); } // }}} // {{{ rule_info_command auto rule_info_command::get_name() const noexcept -> std::string_view { return "rule-info"; } void rule_info_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto index = args.get<unsigned>("index"); if (!index) throw rule_error(rule_error::invalid_index); auto json = rule_util::to_json(irccd.rules().require(*index)); json.push_back({"command", "rule-info"}); client.write(std::move(json)); } // }}} // {{{ rule_list_command auto rule_list_command::get_name() const noexcept -> std::string_view { return "rule-list"; } void rule_list_command::exec(irccd& irccd, transport_client& client, const document&) { auto array = nlohmann::json::array(); for (const auto& rule : irccd.rules().list()) array.push_back(rule_util::to_json(rule)); client.write({ { "command", "rule-list" }, { "list", std::move(array) } }); } // }}} // {{{ rule_move_command auto rule_move_command::get_name() const noexcept -> std::string_view { return "rule-move"; } void rule_move_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto from = args.get<unsigned>("from"); const auto to = args.get<unsigned>("to"); if (!from || !to) throw rule_error(rule_error::invalid_index); /* * Examples of moves * -------------------------------------------------------------- * * Before: [0] [1] [2] * * from = 0 * to = 2 * * After: [1] [2] [0] * * -------------------------------------------------------------- * * Before: [0] [1] [2] * * from = 2 * to = 0 * * After: [2] [0] [1] * * -------------------------------------------------------------- * * Before: [0] [1] [2] * * from = 0 * to = 123 * * After: [1] [2] [0] */ // Ignore dumb input. if (*from == *to) { client.success("rule-move"); return; } if (*from >= irccd.rules().list().size()) throw rule_error(rule_error::error::invalid_index); const auto save = irccd.rules().list()[*from]; irccd.rules().remove(*from); irccd.rules().insert(save, *to > irccd.rules().list().size() ? irccd.rules().list().size() : *to); client.success("rule-move"); } // }}} // {{{ rule_remove_command auto rule_remove_command::get_name() const noexcept -> std::string_view { return "rule-remove"; } void rule_remove_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto index = args.get<unsigned>("index"); if (!index || *index >= irccd.rules().list().size()) throw rule_error(rule_error::invalid_index); irccd.rules().remove(*index); client.success("rule-remove"); } // }}} // {{{ server_connect_command auto server_connect_command::get_name() const noexcept -> std::string_view { return "server-connect"; } void server_connect_command::exec(irccd& irccd, transport_client& client, const document& args) { auto server = server_util::from_json(irccd.get_service(), args); if (irccd.servers().has(server->get_id())) throw server_error(server_error::already_exists); irccd.servers().add(std::move(server)); client.success("server-connect"); } // }}} // {{{ server_disconnect_command auto server_disconnect_command::get_name() const noexcept -> std::string_view { return "server-disconnect"; } void server_disconnect_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto it = args.find("server"); if (it == args.end()) irccd.servers().clear(); else { if (!it->is_string() || !string_util::is_identifier(it->get<std::string>())) throw server_error(server_error::invalid_identifier); const auto name = it->get<std::string>(); irccd.servers().require(name); irccd.servers().remove(name); } client.success("server-disconnect"); } // }}} // {{{ server_info_command auto server_info_command::get_name() const noexcept -> std::string_view { return "server-info"; } void server_info_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); const auto server = irccd.servers().require(*id); // Construct the JSON response. auto response = document::object(); // General stuff. response.push_back({"command", "server-info"}); response.push_back({"name", server->get_id()}); response.push_back({"hostname", server->get_hostname()}); response.push_back({"port", server->get_port()}); response.push_back({"nickname", server->get_nickname()}); response.push_back({"username", server->get_username()}); response.push_back({"realname", server->get_realname()}); response.push_back({"channels", server->get_channels()}); // Optional stuff. response.push_back({"ipv4", static_cast<bool>(server->get_options() & server::options::ipv4)}); response.push_back({"ipv6", static_cast<bool>(server->get_options() & server::options::ipv6)}); response.push_back({"ssl", static_cast<bool>(server->get_options() & server::options::ssl)}); client.write(response); } // }}} // {{{ server_invite_command auto server_invite_command::get_name() const noexcept -> std::string_view { return "server-invite"; } void server_invite_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto target = args.get<std::string>("target"); const auto channel = args.get<std::string>("channel"); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!target || target->empty()) throw server_error(server_error::invalid_nickname); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); irccd.servers().require(*id)->invite(*target, *channel); client.success("server-invite"); } // }}} // {{{ server_join_command auto server_join_command::get_name() const noexcept -> std::string_view { return "server-join"; } void server_join_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("channel"); const auto password = args.optional<std::string>("password", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!password) throw server_error(server_error::invalid_password); irccd.servers().require(*id)->join(*channel, *password); client.success("server-join"); } // }}} // {{{ server_kick_command auto server_kick_command::get_name() const noexcept -> std::string_view { return "server-kick"; } void server_kick_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto target = args.get<std::string>("target"); const auto channel = args.get<std::string>("channel"); const auto reason = args.optional<std::string>("reason", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!target || target->empty()) throw server_error(server_error::invalid_nickname); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!reason) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->kick(*target, *channel, *reason); client.success("server-kick"); } // }}} // {{{ server_list_command auto server_list_command::get_name() const noexcept -> std::string_view { return "server-list"; } void server_list_command::exec(irccd& irccd, transport_client& client, const document&) { auto json = nlohmann::json::object(); auto list = nlohmann::json::array(); for (const auto& server : irccd.servers().list()) list.push_back(server->get_id()); client.write({ { "command", "server-list" }, { "list", std::move(list) } }); } // }}} // {{{ server_me_command auto server_me_command::get_name() const noexcept -> std::string_view { return "server-me"; } void server_me_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("target"); const auto message = args.optional<std::string>("message", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!message) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->me(*channel, *message); client.success("server-me"); } // }}} // {{{ server_message_command auto server_message_command::get_name() const noexcept -> std::string_view { return "server-message"; } void server_message_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("target"); const auto message = args.optional<std::string>("message", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!message) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->message(*channel, *message); client.success("server-message"); } // }}} // {{{ server_mode_command auto server_mode_command::get_name() const noexcept -> std::string_view { return "server-mode"; } void server_mode_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("channel"); const auto mode = args.get<std::string>("mode"); const auto limit = args.optional<std::string>("limit", ""); const auto user = args.optional<std::string>("user", ""); const auto mask = args.optional<std::string>("mask", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!mode || mode->empty()) throw server_error(server_error::invalid_mode); if (!limit || !user || !mask) throw server_error(server_error::invalid_mode); irccd.servers().require(*id)->mode(*channel, *mode, *limit, *user, *mask); client.success("server-mode"); } // }}} // {{{ server_nick_command auto server_nick_command::get_name() const noexcept -> std::string_view { return "server-nick"; } void server_nick_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto nick = args.get<std::string>("nickname"); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!nick || nick->empty()) throw server_error(server_error::invalid_nickname); irccd.servers().require(*id)->set_nickname(*nick); client.success("server-nick"); } // }}} // {{{ server_notice_command auto server_notice_command::get_name() const noexcept -> std::string_view { return "server-notice"; } void server_notice_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("target"); const auto message = args.optional<std::string>("message", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!message) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->notice(*channel, *message); client.success("server-notice"); } // }}} // {{{ server_part_command auto server_part_command::get_name() const noexcept -> std::string_view { return "server-part"; } void server_part_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("channel"); const auto reason = args.optional<std::string>("reason", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!reason) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->part(*channel, *reason); client.success("server-part"); } // }}} // {{{ server_reconnect_command auto server_reconnect_command::get_name() const noexcept -> std::string_view { return "server-reconnect"; } void server_reconnect_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto it = args.find("server"); if (it == args.end()) irccd.servers().reconnect(); else { if (!it->is_string() || !string_util::is_identifier(it->get<std::string>())) throw server_error(server_error::invalid_identifier); irccd.servers().reconnect(it->get<std::string>()); } client.success("server-reconnect"); } // }}} // {{{ server_topic_command auto server_topic_command::get_name() const noexcept -> std::string_view { return "server-topic"; } void server_topic_command::exec(irccd& irccd, transport_client& client, const document& args) { const auto id = args.get<std::string>("server"); const auto channel = args.get<std::string>("channel"); const auto topic = args.optional<std::string>("topic", ""); if (!id || !string_util::is_identifier(*id)) throw server_error(server_error::invalid_identifier); if (!channel || channel->empty()) throw server_error(server_error::invalid_channel); if (!topic) throw server_error(server_error::invalid_message); irccd.servers().require(*id)->topic(*channel, *topic); client.success("server-topic"); } // }}} } // !irccd