summaryrefslogtreecommitdiff
path: root/bot/src/modules
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2024-05-18 14:48:12 +0500
committerilotterytea <iltsu@alright.party>2024-05-18 14:48:12 +0500
commitd1793df1eda463b10107d41785ad1d7f055ed476 (patch)
treefd3e41c3b4a05924748ae4b762e1ae55a0bc815c /bot/src/modules
parentd7a2de17e9b7931f68b5b4079b1c36866a19d343 (diff)
upd: moved the bot part to a relative subfolder
Diffstat (limited to 'bot/src/modules')
-rw-r--r--bot/src/modules/custom_command.hpp96
-rw-r--r--bot/src/modules/event.hpp145
-rw-r--r--bot/src/modules/help.hpp31
-rw-r--r--bot/src/modules/join.hpp91
-rw-r--r--bot/src/modules/massping.hpp62
-rw-r--r--bot/src/modules/notify.hpp131
-rw-r--r--bot/src/modules/ping.hpp59
-rw-r--r--bot/src/modules/timer.hpp112
8 files changed, 727 insertions, 0 deletions
diff --git a/bot/src/modules/custom_command.hpp b/bot/src/modules/custom_command.hpp
new file mode 100644
index 0000000..50b3692
--- /dev/null
+++ b/bot/src/modules/custom_command.hpp
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../commands/response_error.hpp"
+
+namespace bot {
+ namespace mod {
+ class CustomCommand : public command::Command {
+ std::string get_name() const override { return "scmd"; }
+
+ schemas::PermissionLevel get_permission_level() const override {
+ return schemas::PermissionLevel::MODERATOR;
+ }
+
+ std::vector<std::string> get_subcommand_ids() const override {
+ return {"new", "remove"};
+ }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!request.subcommand_id.has_value()) {
+ throw ResponseException<NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::SUBCOMMAND);
+ }
+
+ const std::string &subcommand_id = request.subcommand_id.value();
+
+ if (!request.message.has_value()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::CommandArgument::NAME);
+ }
+
+ const std::string &message = request.message.value();
+ std::vector<std::string> s = utils::string::split_text(message, ' ');
+
+ std::string name = s[0];
+ s.erase(s.begin());
+
+ pqxx::work work(request.conn);
+ pqxx::result cmds = work.exec(
+ "SELECT id FROM custom_commands WHERE name = '" + name +
+ "' AND channel_id = " + std::to_string(request.channel.get_id()));
+
+ if (subcommand_id == "new") {
+ if (!cmds.empty()) {
+ throw ResponseException<ResponseError::NAMESAKE_CREATION>(
+ request, bundle.localization, name);
+ }
+
+ if (s.empty()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization,
+ command::CommandArgument::MESSAGE);
+ }
+
+ std::string m = utils::string::str(s.begin(), s.end(), ' ');
+
+ work.exec(
+ "INSERT INTO custom_commands(channel_id, name, message) VALUES "
+ "(" +
+ std::to_string(request.channel.get_id()) + ", '" + name +
+ "', '" + m + "')");
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::CustomcommandNew,
+ {name})
+ .value();
+ } else if (subcommand_id == "remove") {
+ if (cmds.empty()) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, name);
+ }
+
+ work.exec("DELETE FROM custom_commands WHERE id = " +
+ std::to_string(cmds[0][0].as<int>()));
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::CustomcommandDelete,
+ {name})
+ .value();
+ }
+
+ throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>(
+ request, bundle.localization);
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/event.hpp b/bot/src/modules/event.hpp
new file mode 100644
index 0000000..4242f07
--- /dev/null
+++ b/bot/src/modules/event.hpp
@@ -0,0 +1,145 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../commands/response_error.hpp"
+#include "../schemas/stream.hpp"
+
+namespace bot {
+ namespace mod {
+ class Event : public command::Command {
+ std::string get_name() const override { return "event"; }
+
+ schemas::PermissionLevel get_permission_level() const override {
+ return schemas::PermissionLevel::MODERATOR;
+ }
+
+ std::vector<std::string> get_subcommand_ids() const override {
+ return {"on", "off"};
+ }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!request.subcommand_id.has_value()) {
+ throw ResponseException<NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::SUBCOMMAND);
+ }
+
+ const std::string &subcommand_id = request.subcommand_id.value();
+
+ if (!request.message.has_value()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::CommandArgument::TARGET);
+ }
+
+ const std::string &message = request.message.value();
+ std::vector<std::string> s = utils::string::split_text(message, ' ');
+
+ std::string target;
+ schemas::EventType type;
+
+ std::vector<std::string> target_and_type =
+ utils::string::split_text(s[0], ':');
+
+ if (target_and_type.size() != 2) {
+ throw ResponseException<ResponseError::INCORRECT_ARGUMENT>(
+ request, bundle.localization, s[0]);
+ }
+
+ s.erase(s.begin());
+
+ target = target_and_type[0];
+ type = schemas::string_to_event_type(target_and_type[1]);
+
+ std::string t = target_and_type[0] + ":" + target_and_type[1];
+
+ auto channels = bundle.helix_client.get_users({target});
+ api::twitch::schemas::User channel;
+
+ if (channels.empty() && type != schemas::EventType::CUSTOM) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, t);
+ }
+
+ pqxx::work work(request.conn);
+ std::string query;
+
+ if (type != schemas::CUSTOM) {
+ channel = channels[0];
+
+ query = "SELECT id FROM events WHERE channel_id = " +
+ std::to_string(request.channel.get_id()) +
+ " AND target_alias_id = " + std::to_string(channel.id) +
+ " AND event_type = " + std::to_string(type);
+ } else {
+ query = "SELECT id FROM events WHERE channel_id = " +
+ std::to_string(request.channel.get_id()) +
+ " AND custom_alias_id = '" + target +
+ "' AND event_type = " + std::to_string(type);
+ }
+
+ pqxx::result event = work.exec(query);
+
+ if (subcommand_id == "on") {
+ if (!event.empty()) {
+ throw ResponseException<ResponseError::NAMESAKE_CREATION>(
+ request, bundle.localization, t);
+ }
+
+ if (s.empty()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization,
+ command::CommandArgument::MESSAGE);
+ }
+
+ std::string m = utils::string::str(s.begin(), s.end(), ' ');
+
+ if (type != schemas::CUSTOM) {
+ query =
+ "INSERT INTO events (channel_id, target_alias_id, "
+ "event_type, "
+ "message) VALUES (" +
+ std::to_string(request.channel.get_id()) + ", " +
+ std::to_string(channel.id) + ", " + std::to_string(type) +
+ ", '" + m + "')";
+ } else {
+ query =
+ "INSERT INTO events (channel_id, custom_alias_id, "
+ "event_type, "
+ "message) VALUES (" +
+ std::to_string(request.channel.get_id()) + ", '" + target +
+ "', " + std::to_string(type) + ", '" + m + "')";
+ }
+
+ work.exec(query);
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::EventOn, {t})
+ .value();
+ } else if (subcommand_id == "off") {
+ if (event.empty()) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, t);
+ }
+
+ work.exec("DELETE FROM events WHERE id = " +
+ std::to_string(event[0][0].as<int>()));
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::EventOff, {t})
+ .value();
+ }
+
+ throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>(
+ request, bundle.localization);
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/help.hpp b/bot/src/modules/help.hpp
new file mode 100644
index 0000000..13af228
--- /dev/null
+++ b/bot/src/modules/help.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../commands/response_error.hpp"
+
+namespace bot {
+ namespace mod {
+ class Help : public command::Command {
+ std::string get_name() const override { return "help"; }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!bundle.configuration.url.help.has_value()) {
+ throw ResponseException<ResponseError::ILLEGAL_COMMAND>(
+ request, bundle.localization);
+ }
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::HelpResponse,
+ {*bundle.configuration.url.help})
+ .value();
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/join.hpp b/bot/src/modules/join.hpp
new file mode 100644
index 0000000..16e8b4a
--- /dev/null
+++ b/bot/src/modules/join.hpp
@@ -0,0 +1,91 @@
+#pragma once
+
+#include <pqxx/pqxx>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../schemas/channel.hpp"
+
+namespace bot {
+ namespace mod {
+ class Join : public command::Command {
+ std::string get_name() const override { return "join"; }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!bundle.configuration.commands.join_allowed) {
+ std::string owner = "";
+
+ if (bundle.configuration.owner.name.has_value()) {
+ owner = " " + bundle.localization
+ .get_formatted_line(
+ request, loc::LineId::MsgOwner,
+ {*bundle.configuration.owner.name})
+ .value();
+ }
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinNotAllowed,
+ {owner})
+ .value();
+ }
+
+ if (!bundle.configuration.commands.join_allow_from_other_chats &&
+ request.channel.get_alias_name() !=
+ bundle.irc_client.get_bot_username()) {
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinFromOtherChat,
+ {bundle.irc_client.get_bot_username()})
+ .value();
+ }
+
+ pqxx::work work(request.conn);
+
+ pqxx::result channels =
+ work.exec("SELECT * FROM channels WHERE alias_id = " +
+ std::to_string(request.user.get_alias_id()));
+
+ if (!channels.empty()) {
+ schemas::Channel channel(channels[0]);
+
+ if (channel.get_opted_out_at().has_value()) {
+ work.exec("UPDATE channels SET opted_out_at = null WHERE id = " +
+ std::to_string(channel.get_id()));
+ work.commit();
+
+ bundle.irc_client.join(channel.get_alias_name());
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinRejoined, {})
+ .value();
+ }
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinAlreadyIn, {})
+ .value();
+ }
+
+ work.exec("INSERT INTO channels(alias_id, alias_name) VALUES (" +
+ std::to_string(request.user.get_alias_id()) + ", '" +
+ request.user.get_alias_name() + "')");
+ work.commit();
+
+ bundle.irc_client.join(request.user.get_alias_name());
+ bundle.irc_client.say(
+ request.user.get_alias_name(),
+ bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinResponseInChat,
+ {})
+ .value());
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::JoinResponse, {})
+ .value();
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/massping.hpp b/bot/src/modules/massping.hpp
new file mode 100644
index 0000000..2957e34
--- /dev/null
+++ b/bot/src/modules/massping.hpp
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+
+namespace bot {
+ namespace mod {
+ class Massping : public command::Command {
+ std::string get_name() const override { return "massping"; }
+
+ schemas::PermissionLevel get_permission_level() const override {
+ return schemas::PermissionLevel::MODERATOR;
+ }
+
+ int get_delay_seconds() const override { return 1; }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ auto chatters = bundle.helix_client.get_chatters(
+ request.channel.get_alias_id(), bundle.irc_client.get_bot_id());
+
+ std::string m;
+
+ if (request.message.has_value()) {
+ m = request.message.value() + " ·";
+ }
+
+ std::string base = "📣 " + m + " ";
+ std::vector<std::string> msgs = {""};
+ int index = 0;
+
+ for (const auto &chatter : chatters) {
+ const std::string &current_msg = msgs.at(index);
+ std::string x = "@" + chatter.login;
+
+ if (base.length() + current_msg.length() + 1 + x.length() >= 500) {
+ index += 1;
+ }
+
+ if (index > msgs.size() - 1) {
+ msgs.push_back(x);
+ } else {
+ msgs[index] = current_msg + " " + x;
+ }
+ }
+
+ std::vector<std::string> msgs2;
+
+ for (const auto &m : msgs) {
+ msgs2.push_back(base + m);
+ }
+
+ return msgs2;
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/notify.hpp b/bot/src/modules/notify.hpp
new file mode 100644
index 0000000..3587e73
--- /dev/null
+++ b/bot/src/modules/notify.hpp
@@ -0,0 +1,131 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../commands/response_error.hpp"
+#include "../schemas/stream.hpp"
+
+namespace bot {
+ namespace mod {
+ class Notify : public command::Command {
+ std::string get_name() const override { return "notify"; }
+
+ std::vector<std::string> get_subcommand_ids() const override {
+ return {"sub", "unsub"};
+ }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!request.subcommand_id.has_value()) {
+ throw ResponseException<NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::SUBCOMMAND);
+ }
+
+ const std::string &subcommand_id = request.subcommand_id.value();
+
+ if (!request.message.has_value()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::CommandArgument::TARGET);
+ }
+
+ const std::string &message = request.message.value();
+ std::vector<std::string> s = utils::string::split_text(message, ' ');
+
+ std::string target;
+ schemas::EventType type;
+
+ std::vector<std::string> target_and_type =
+ utils::string::split_text(s[0], ':');
+
+ if (target_and_type.size() != 2) {
+ throw ResponseException<ResponseError::INCORRECT_ARGUMENT>(
+ request, bundle.localization, s[0]);
+ }
+
+ s.erase(s.begin());
+
+ target = target_and_type[0];
+ type = schemas::string_to_event_type(target_and_type[1]);
+
+ std::string t = target_and_type[0] + ":" + target_and_type[1];
+
+ auto channels = bundle.helix_client.get_users({target});
+ api::twitch::schemas::User channel;
+
+ if (channels.empty() && type != schemas::EventType::CUSTOM) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, t);
+ }
+
+ pqxx::work work(request.conn);
+ std::string query;
+
+ if (type != schemas::CUSTOM) {
+ channel = channels[0];
+
+ query = "SELECT id FROM events WHERE channel_id = " +
+ std::to_string(request.channel.get_id()) +
+ " AND target_alias_id = " + std::to_string(channel.id) +
+ " AND event_type = " + std::to_string(type);
+ } else {
+ query = "SELECT id FROM events WHERE channel_id = " +
+ std::to_string(request.channel.get_id()) +
+ " AND custom_alias_id = '" + target +
+ "' AND event_type = " + std::to_string(type);
+ }
+
+ pqxx::result events = work.exec(query);
+
+ if (events.empty()) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, t);
+ }
+
+ pqxx::row event = events[0];
+
+ pqxx::result subs =
+ work.exec("SELECT id FROM event_subscriptions WHERE event_id = " +
+ std::to_string(event[0].as<int>()) + " AND user_id = " +
+ std::to_string(request.user.get_id()));
+
+ if (subcommand_id == "sub") {
+ if (!subs.empty()) {
+ throw ResponseException<ResponseError::NAMESAKE_CREATION>(
+ request, bundle.localization, t);
+ }
+
+ work.exec(
+ "INSERT INTO event_subscriptions(event_id, user_id) VALUES (" +
+ std::to_string(event[0].as<int>()) + ", " +
+ std::to_string(request.user.get_id()));
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::NotifySub, {t})
+ .value();
+ } else if (subcommand_id == "unsub") {
+ if (subs.empty()) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, t);
+ }
+
+ work.exec("DELETE FROM event_subscriptions WHERE id = " +
+ std::to_string(subs[0][0].as<int>()));
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::NotifyUnsub, {t})
+ .value();
+ }
+
+ throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>(
+ request, bundle.localization);
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/ping.hpp b/bot/src/modules/ping.hpp
new file mode 100644
index 0000000..836917d
--- /dev/null
+++ b/bot/src/modules/ping.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../utils/chrono.hpp"
+
+namespace bot {
+ namespace mod {
+ class Ping : public command::Command {
+ std::string get_name() const override { return "ping"; }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ auto now = std::chrono::steady_clock::now();
+ auto duration = now - START_TIME;
+ auto seconds =
+ std::chrono::duration_cast<std::chrono::seconds>(duration);
+ std::string uptime = utils::chrono::format_timestamp(seconds.count());
+
+ struct rusage usage;
+ getrusage(RUSAGE_SELF, &usage);
+
+ int used_memory = usage.ru_maxrss / 1024;
+
+ std::string cpp_info;
+
+#ifdef __cplusplus
+ cpp_info.append("C++" + std::to_string(__cplusplus).substr(2, 2));
+#endif
+
+#ifdef __VERSION__
+ cpp_info.append(" (gcc " +
+ bot::utils::string::split_text(__VERSION__, ' ')[0] +
+ ")");
+#endif
+
+ if (!cpp_info.empty()) {
+ cpp_info.append(" · ");
+ }
+
+ return bundle.localization
+ .get_formatted_line(
+ request, loc::LineId::PingResponse,
+ {cpp_info, uptime, std::to_string(used_memory)})
+ .value();
+ }
+ };
+ }
+}
diff --git a/bot/src/modules/timer.hpp b/bot/src/modules/timer.hpp
new file mode 100644
index 0000000..36c3982
--- /dev/null
+++ b/bot/src/modules/timer.hpp
@@ -0,0 +1,112 @@
+#pragma once
+
+#include <string>
+#include <variant>
+#include <vector>
+
+#include "../bundle.hpp"
+#include "../commands/command.hpp"
+#include "../commands/response_error.hpp"
+
+namespace bot {
+ namespace mod {
+ class Timer : public command::Command {
+ std::string get_name() const override { return "timer"; }
+
+ schemas::PermissionLevel get_permission_level() const override {
+ return schemas::PermissionLevel::MODERATOR;
+ }
+
+ std::vector<std::string> get_subcommand_ids() const override {
+ return {"new", "remove"};
+ }
+
+ std::variant<std::vector<std::string>, std::string> run(
+ const InstanceBundle &bundle,
+ const command::Request &request) const override {
+ if (!request.subcommand_id.has_value()) {
+ throw ResponseException<NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::SUBCOMMAND);
+ }
+
+ const std::string &subcommand_id = request.subcommand_id.value();
+
+ if (!request.message.has_value()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization, command::CommandArgument::NAME);
+ }
+
+ const std::string &message = request.message.value();
+ std::vector<std::string> s = utils::string::split_text(message, ' ');
+
+ std::string name = s[0];
+ s.erase(s.begin());
+
+ pqxx::work work(request.conn);
+ pqxx::result timers = work.exec(
+ "SELECT id FROM timers WHERE name = '" + name +
+ "' AND channel_id = " + std::to_string(request.channel.get_id()));
+
+ if (subcommand_id == "new") {
+ if (!timers.empty()) {
+ throw ResponseException<ResponseError::NAMESAKE_CREATION>(
+ request, bundle.localization, name);
+ }
+
+ if (s.empty()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization,
+ command::CommandArgument::INTERVAL);
+ }
+
+ int interval_s;
+
+ try {
+ interval_s = std::stoi(s[0]);
+ } catch (std::exception e) {
+ throw ResponseException<ResponseError::INCORRECT_ARGUMENT>(
+ request, bundle.localization, s[0]);
+ }
+
+ s.erase(s.begin());
+
+ if (s.empty()) {
+ throw ResponseException<ResponseError::NOT_ENOUGH_ARGUMENTS>(
+ request, bundle.localization,
+ command::CommandArgument::MESSAGE);
+ }
+
+ std::string m = utils::string::str(s.begin(), s.end(), ' ');
+
+ work.exec(
+ "INSERT INTO timers(channel_id, name, message, interval_sec) "
+ "VALUES "
+ "(" +
+ std::to_string(request.channel.get_id()) + ", '" + name +
+ "', '" + m + "', " + std::to_string(interval_s) + ")");
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::TimerNew, {name})
+ .value();
+ } else if (subcommand_id == "remove") {
+ if (timers.empty()) {
+ throw ResponseException<ResponseError::NOT_FOUND>(
+ request, bundle.localization, name);
+ }
+
+ work.exec("DELETE FROM timers WHERE id = " +
+ std::to_string(timers[0][0].as<int>()));
+ work.commit();
+
+ return bundle.localization
+ .get_formatted_line(request, loc::LineId::TimerDelete, {name})
+ .value();
+ }
+
+ throw ResponseException<ResponseError::SOMETHING_WENT_WRONG>(
+ request, bundle.localization);
+ }
+ };
+ }
+}