diff --git a/src/commands/close_cmd.cpp b/src/commands/close_cmd.cpp index 4dcf845..5b07074 100644 --- a/src/commands/close_cmd.cpp +++ b/src/commands/close_cmd.cpp @@ -1,5 +1,6 @@ -#include "commands.h" +#include "commands.h" #include "../globals/globals.h" +#include "../ticket_registry.h" #include #include @@ -39,22 +40,22 @@ void cmd::closeCommand(dpp::cluster& bot, const dpp::slashcommand_t& event) } else if (event.command.channel.parent_id == globals::category::ticketId) { - event.reply(dpp::message("Closed ticket!")); - bot.channel_get(event.command.channel.id, [&bot, event](const dpp::confirmation_callback_t& callback) { - if (!callback.is_error()) + if (callback.is_error()) { - dpp::channel ticketChannel = std::get(callback.value); - std::vector overwrites = ticketChannel.permission_overwrites; + event.reply(dpp::message("Unable to find this ticket.").set_flags(dpp::m_ephemeral)); + return; + } - for (const auto& overwrite : overwrites) - { - if (overwrite.type == dpp::overwrite_type::ot_member) - { - bot.channel_edit_permissions(ticketChannel, overwrite.id, 0, dpp::p_view_channel, true); - } - } + const auto ticketChannel = std::get(callback.value); + for (const auto& overwrite : ticketChannel.permission_overwrites) + { + if (overwrite.type == dpp::overwrite_type::ot_member) + bot.channel_edit_permissions(ticketChannel, overwrite.id, 0, dpp::p_view_channel, true); } + + tickets::registry.removeByChannel(event.command.guild_id, ticketChannel.id); + event.reply(dpp::message("Closed ticket!")); }); } else @@ -62,3 +63,30 @@ void cmd::closeCommand(dpp::cluster& bot, const dpp::slashcommand_t& event) event.reply(dpp::message("This command only operates within tickets and threads.").set_flags(dpp::m_ephemeral)); } } + +void cmd::closeTicketButton(dpp::cluster& bot, const dpp::button_click_t& event) +{ + if (event.command.channel.parent_id != globals::category::ticketId) + { + event.reply(dpp::message("This button only works inside a ticket.").set_flags(dpp::m_ephemeral)); + return; + } + + bot.channel_get(event.command.channel.id, [&bot, event](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) + { + event.reply(dpp::message("Unable to find this ticket.").set_flags(dpp::m_ephemeral)); + return; + } + + const auto ticketChannel = std::get(callback.value); + for (const auto& overwrite : ticketChannel.permission_overwrites) + { + if (overwrite.type == dpp::overwrite_type::ot_member) + bot.channel_edit_permissions(ticketChannel, overwrite.id, 0, dpp::p_view_channel, true); + } + + tickets::registry.removeByChannel(event.command.guild_id, ticketChannel.id); + event.reply(dpp::message("Closed ticket!")); + }); +} diff --git a/src/commands/commands.h b/src/commands/commands.h index edfc965..0690430 100644 --- a/src/commands/commands.h +++ b/src/commands/commands.h @@ -29,6 +29,13 @@ namespace cmd */ void closeCommand(dpp::cluster& bot, const dpp::slashcommand_t& event); + /** + * @brief Closes a ticket from its message button + * @param bot cluster + * @param event button click event + */ + void closeTicketButton(dpp::cluster& bot, const dpp::button_click_t& event); + /** * @brief Opens a ticket * @param bot cluster diff --git a/src/commands/ticket_cmd.cpp b/src/commands/ticket_cmd.cpp index 7546cf1..1f1f27d 100644 --- a/src/commands/ticket_cmd.cpp +++ b/src/commands/ticket_cmd.cpp @@ -1,8 +1,20 @@ #include "commands.h" #include "../globals/globals.h" +#include "../ticket_registry.h" void cmd::ticketCommand(dpp::cluster& bot, const dpp::slashcommand_t& event) { + const auto guildId = event.command.guild_id; + const auto userId = event.command.get_issuing_user().id; + + if (const auto existingTicket = tickets::registry.find(guildId, userId)) + { + event.reply(dpp::message( + "You already have an open ticket: <#" + existingTicket->str() + ">") + .set_flags(dpp::m_ephemeral)); + return; + } + dpp::message message(event.command.channel_id, "Creating ticket..."); event.reply(message.set_flags(dpp::m_ephemeral)); @@ -14,10 +26,10 @@ void cmd::ticketCommand(dpp::cluster& bot, const dpp::slashcommand_t& event) dpp::channel ticketChannel = dpp::channel() .set_name(event.command.get_issuing_user().username) .set_type(dpp::CHANNEL_TEXT) - .set_guild_id(event.command.guild_id) + .set_guild_id(guildId) .set_parent_id(globals::category::ticketId) - .set_permission_overwrite(event.command.guild_id, dpp::overwrite_type::ot_role, 0, dpp::p_view_channel) - .set_permission_overwrite(event.command.get_issuing_user().id, dpp::overwrite_type::ot_member, ticketPerms, 0) + .set_permission_overwrite(guildId, dpp::overwrite_type::ot_role, 0, dpp::p_view_channel) + .set_permission_overwrite(userId, dpp::overwrite_type::ot_member, ticketPerms, 0) .set_permission_overwrite(globals::role::staffId, dpp::overwrite_type::ot_role, ticketPerms, 0); const dpp::command_interaction cmdData = event.command.get_command_interaction(); @@ -30,18 +42,36 @@ void cmd::ticketCommand(dpp::cluster& bot, const dpp::slashcommand_t& event) } } - bot.channel_create(ticketChannel, [&bot, event](const dpp::confirmation_callback_t& callback) { - if (!callback.is_error()) + bot.channel_create(ticketChannel, [&bot, event, guildId, userId](const dpp::confirmation_callback_t& callback) { + if (callback.is_error()) { - const auto ticketChannel = std::get(callback.value); - const auto pingMessage = dpp::message(ticketChannel.id, event.command.get_issuing_user().get_mention() + " opened this ticket."); - bot.message_create(pingMessage); - - event.edit_response("Ticket " + ticketChannel.get_mention() + " created!"); + event.edit_response("Failed to create ticket channel."); + return; } - else + + const auto createdChannel = std::get(callback.value); + if (!tickets::registry.add(guildId, userId, createdChannel.id)) { - event.edit_response("Failed to create ticket channel!"); + bot.channel_delete(createdChannel.id); + event.edit_response("You already have an open ticket."); + return; } + + const auto pingMessage = dpp::message( + createdChannel.id, + event.command.get_issuing_user().get_mention() + " opened this ticket."); + + dpp::message ticketMessage = pingMessage; + ticketMessage.add_component( + dpp::component().add_component( + dpp::component() + .set_label("Close ticket") + .set_type(dpp::cot_button) + .set_style(dpp::cos_danger) + .set_id("closeTicket"))); + + bot.message_create(ticketMessage); + + event.edit_response("Ticket " + createdChannel.get_mention() + " created!"); }); } diff --git a/src/main.cpp b/src/main.cpp index c1bacad..af8f9aa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,7 +84,9 @@ int main() }); bot.on_button_click([&bot](const dpp::button_click_t& event) { - if (event.custom_id == "delSuggestion") + if (event.custom_id == "closeTicket") + cmd::closeTicketButton(bot, event); + else if (event.custom_id == "delSuggestion") utils::suggestion::deleteSuggestion(bot, event); else if (event.custom_id == "editSuggestion") utils::suggestion::editSuggestion(bot, event); diff --git a/src/ticket_registry.cpp b/src/ticket_registry.cpp new file mode 100644 index 0000000..7838011 --- /dev/null +++ b/src/ticket_registry.cpp @@ -0,0 +1,47 @@ +#include "ticket_registry.h" + +#include + +namespace tickets +{ + std::optional Registry::find( + const dpp::snowflake guildId, + const dpp::snowflake userId) const + { + std::scoped_lock lock(mutex); + + const auto ticket = tickets.find({guildId, userId}); + + if (ticket == tickets.end()) + return std::nullopt; + + return ticket->second; + } + + bool Registry::add( + const dpp::snowflake guildId, + const dpp::snowflake userId, + const dpp::snowflake channelId) + { + std::scoped_lock lock(mutex); + + return tickets.emplace( + Key{guildId, userId}, + channelId).second; + } + + void Registry::removeByChannel( + const dpp::snowflake guildId, + const dpp::snowflake channelId) + { + std::scoped_lock lock(mutex); + + std::erase_if(tickets, [guildId, channelId](const auto& ticket) { + return ticket.first.guildId == guildId + && ticket.second == channelId; + }); + } + + Registry registry; +} + diff --git a/src/ticket_registry.h b/src/ticket_registry.h new file mode 100644 index 0000000..b25a0a3 --- /dev/null +++ b/src/ticket_registry.h @@ -0,0 +1,69 @@ +#ifndef TICKET_REGISTRY_H +#define TICKET_REGISTRY_H + +#include + +#include +#include +#include +#include +#include + +namespace tickets +{ + class Registry + { + public: + std::optional find( + dpp::snowflake guildId, + dpp::snowflake userId) const; + + bool add( + dpp::snowflake guildId, + dpp::snowflake userId, + dpp::snowflake channelId); + + void removeByChannel( + dpp::snowflake guildId, + dpp::snowflake channelId); + + private: + struct Key + { + dpp::snowflake guildId{}; + dpp::snowflake userId{}; + + bool operator==(const Key& other) const + { + return guildId == other.guildId + && userId == other.userId; + } + }; + + struct KeyHash + { + std::size_t operator()(const Key& key) const + { + const auto guildHash = + std::hash{}( + static_cast(key.guildId)); + + const auto userHash = + std::hash{}( + static_cast(key.userId)); + + return guildHash + ^ (userHash + 0x9e3779b9 + + (guildHash << 6) + + (guildHash >> 2)); + } + }; + + mutable std::mutex mutex; + std::unordered_map tickets; + }; + + extern Registry registry; +} + +#endif // TICKET_REGISTRY_H diff --git a/src/utils/suggestion/suggestion.cpp b/src/utils/suggestion/suggestion.cpp index e85514a..0515fe6 100644 --- a/src/utils/suggestion/suggestion.cpp +++ b/src/utils/suggestion/suggestion.cpp @@ -1,6 +1,34 @@ #include "suggestion.h" #include "../../globals/globals.h" +#include +#include +#include + +namespace +{ + constexpr std::string_view authorFooterPrefix = "author_id:"; + + std::optional getSuggestionAuthorId(const dpp::message& message) + { + if (message.embeds.empty() || !message.embeds.front().footer) + return std::nullopt; + + const auto& footer = message.embeds.front().footer; + if (!footer->text.starts_with(authorFooterPrefix)) + return std::nullopt; + + try + { + return dpp::snowflake(footer->text.substr(authorFooterPrefix.size())); + } + catch (const std::exception&) + { + return std::nullopt; + } + } +} + void utils::suggestion::createSuggestion(dpp::cluster& bot, const dpp::message_create_t& event) { dpp::user user = event.msg.author; @@ -27,6 +55,7 @@ void utils::suggestion::createSuggestion(dpp::cluster& bot, const dpp::message_c .set_color(globals::color::defaultColor) .set_title("Suggestion") .set_author(user.format_username(), "", user.get_avatar_url()) + .set_footer("author_id:" + user.id.str()) .set_description(event.msg.content); dpp::message msg(event.msg.channel_id, result); @@ -86,10 +115,9 @@ void utils::suggestion::createSuggestion(dpp::cluster& bot, const dpp::message_c void utils::suggestion::deleteSuggestion(dpp::cluster& bot, const dpp::button_click_t& event) { - std::string clicker = event.command.get_issuing_user().format_username(); - std::string originalAuthor = event.command.msg.embeds[0].author->name; + const auto originalAuthorId = getSuggestionAuthorId(event.command.msg); - if (clicker == originalAuthor) + if (originalAuthorId && event.command.get_issuing_user().id == *originalAuthorId) bot.message_delete(event.command.msg.id, event.command.msg.channel_id); else event.reply(dpp::message("You can only delete your own suggestions.").set_flags(dpp::m_ephemeral)); @@ -97,10 +125,9 @@ void utils::suggestion::deleteSuggestion(dpp::cluster& bot, const dpp::button_cl void utils::suggestion::editSuggestion(dpp::cluster& bot, const dpp::button_click_t& event) { - std::string clicker = event.command.get_issuing_user().format_username(); - std::string originalAuthor = event.command.msg.embeds[0].author->name; + const auto originalAuthorId = getSuggestionAuthorId(event.command.msg); - if (clicker == originalAuthor) + if (originalAuthorId && event.command.get_issuing_user().id == *originalAuthorId) { dpp::interaction_modal_response modal("editModal", "Edit suggestion");