diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 64cd59b..480289f 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -19,6 +19,13 @@ jobs: uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest,enable=${{ github.ref_name == 'master' }} - name: Build and push Container id: push uses: docker/build-push-action@v7 diff --git a/src/core/http_request.cpp b/src/core/http_request.cpp index 58d9e0e..7d780b4 100644 --- a/src/core/http_request.cpp +++ b/src/core/http_request.cpp @@ -17,6 +17,7 @@ *****************************************************************************/ #include "http_request.h" +using namespace std::string_literals; http_request::http_request() { instance = curl_easy_init(); @@ -55,7 +56,7 @@ const http_response http_request::post(const std::string &url, const std::string curl_easy_setopt(instance, CURLOPT_URL, url.c_str()); curl_slist *header_slist = nullptr; if (!content_type.empty()) { - curl_slist *new_header_slist = curl_slist_append(header_slist, std::string("Content-Type: " + content_type).c_str()); + curl_slist *new_header_slist = curl_slist_append(header_slist, std::string("Content-Type: "s + content_type).c_str()); if (!new_header_slist) { curl_slist_free_all(header_slist); throw std::bad_alloc(); @@ -87,7 +88,7 @@ const http_response http_request::post(const std::string &url, const std::string } std::string http_request::legacy_url(const std::string &hostname, uint16_t port, const std::string &url, bool tls) { - return (tls ? "https://" : "http://") + hostname + ":" + std::to_string(port) + (url.empty() ? "/" : (url.front() != '/' ? "/" + url : url)); + return (tls ? "https://"s : "http://"s) + hostname + ":"s + std::to_string(port) + (url.empty() ? "/"s : (url.front() != '/' ? "/"s + url : url)); } http_request::~http_request() { diff --git a/src/core/main.cpp b/src/core/main.cpp index 4bb1073..28721a2 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2023-2024 Syping +* Copyright (C) 2023-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -77,7 +77,8 @@ int main(int argc, char* argv[]) { bot::message_queue message_queue; std::thread message_queue_loop(&bot::message_queue::run, &message_queue, &settings, &submit_queue); - bot.on_message_create(std::bind(&bot::message_queue::process_message_event, &message_queue, &bot, &settings, std::placeholders::_1)); + bot.on_message_context_menu(std::bind(&bot::slashcommands::process_message_menu_event, &message_queue, &bot, &settings, std::placeholders::_1)); + bot.on_message_create(std::bind(&bot::message_queue::process_guild_message_event, &message_queue, &bot, &settings, std::placeholders::_1)); bot.on_slashcommand(std::bind(&bot::slashcommands::process_command_event, &bot, &settings, std::placeholders::_1)); bot.on_ready([&bot, &settings]([[maybe_unused]] const dpp::ready_t &event) { if (dpp::run_once()) { diff --git a/src/core/message_queue.cpp b/src/core/message_queue.cpp index a4f1085..56de406 100644 --- a/src/core/message_queue.cpp +++ b/src/core/message_queue.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2023-2024 Syping +* Copyright (C) 2023-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -21,6 +21,7 @@ #include "settings.h" using bot::message_queue; using namespace std::chrono_literals; +using namespace std::string_literals; void message_queue::add(const message &message) { @@ -34,7 +35,30 @@ void message_queue::add(message &&message) m_queue.push(message); } -void message_queue::process_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_create_t &event) +void message_queue::process_direct_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_context_menu_t &event) +{ + try { + // We check for conditions we want to skip translation for + if (event.ctx_message.author.id == bot->me.id || event.ctx_message.content.empty()) { + event.reply(dpp::message("Invalid message").set_flags(dpp::m_ephemeral)); + return; + } + + event.thinking(false); + + bot::direct_message direct_message; + direct_message.event = event; + direct_message.message = event.ctx_message.content; + + add(std::move(direct_message)); + } + catch (const std::exception &exception) { + std::cerr << "[Exception] " << exception.what() << std::endl; + event.reply(dpp::message("Exception while processing command:\n"s + exception.what()).set_flags(dpp::m_ephemeral)); + } +} + +void message_queue::process_guild_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_create_t &event) { // We check for conditions we want to skip translation for if (event.msg.author.id == bot->me.id || event.msg.content.empty() || event.msg.has_thread()) @@ -50,7 +74,7 @@ void message_queue::process_message_event(dpp::cluster *bot, bot::settings::sett const std::lock_guard guard(*settings); if (const bot::settings::channel *channel = settings->get_channel(event.msg.guild_id, event.msg.channel_id)) { - bot::message message; + bot::guild_message message; message.id = event.msg.id; message.author = event.msg.member.get_nickname(); @@ -81,13 +105,21 @@ void message_queue::run(bot::settings::settings *settings, submit_queue *submit_ auto translator = settings->get_translator(); - for (auto target = message.targets.begin(); target != message.targets.end(); target++) { - translated_message tr_message; - tr_message.author = message.author; - tr_message.avatar = message.avatar; - tr_message.message = translator->translate(message.message, message.source, target->target); - tr_message.webhook = target->webhook; - submit_queue->add(std::move(tr_message)); + if (const auto *direct_message = std::get_if(&message)) { + translated_direct_message translated_message; + translated_message.event = direct_message->event; + translated_message.message = translator->translate(direct_message->message, {}, "en"); + submit_queue->add(std::move(translated_message)); + } + else if (const auto *guild_message = std::get_if(&message)) { + for (auto target = guild_message->targets.begin(); target != guild_message->targets.end(); target++) { + translated_guild_message translated_message; + translated_message.author = guild_message->author; + translated_message.avatar = guild_message->avatar; + translated_message.message = translator->translate(guild_message->message, guild_message->source, target->target); + translated_message.webhook = target->webhook; + submit_queue->add(std::move(translated_message)); + } } std::this_thread::yield(); diff --git a/src/core/message_queue.h b/src/core/message_queue.h index 5d3cdde..5d15e5e 100644 --- a/src/core/message_queue.h +++ b/src/core/message_queue.h @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2023-2024 Syping +* Copyright (C) 2023-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -22,12 +22,18 @@ #include #include #include +#include #include #include "settings.h" #include "submit_queue.h" namespace bot { - struct message { + struct direct_message { + dpp::message_context_menu_t event; + std::string message; + }; + + struct guild_message { uint64_t id; std::string author; std::string avatar; @@ -36,11 +42,14 @@ namespace bot { std::vector targets; }; + typedef std::variant message; + class message_queue { public: void add(const message &message); void add(message &&message); - void process_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_create_t &event); + void process_direct_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_context_menu_t &event); + void process_guild_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_create_t &event); void run(bot::settings::settings *settings, submit_queue *submit_queue); void terminate(); diff --git a/src/core/slashcommands.cpp b/src/core/slashcommands.cpp index a55614c..2ae38a9 100644 --- a/src/core/slashcommands.cpp +++ b/src/core/slashcommands.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -27,10 +27,16 @@ void slashcommands::process_command_event(dpp::cluster *bot, bot::settings::sett slashcommands::process_edit_command(bot, settings, event); else if (event.command.get_command_name() == "list") slashcommands::process_list_command(bot, settings, event); - else if (event.command.get_command_name() == "translate" || event.command.get_command_name() == "translate_pref") + else if (event.command.get_command_name() == "translate" || event.command.get_command_name() == "translate pref") slashcommands::process_translate_command(bot, settings, event); } +void slashcommands::process_message_menu_event(bot::message_queue *message_queue, dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_context_menu_t &event) +{ + if (event.command.get_command_name() == "translate message") + message_queue->process_direct_message_event(bot, settings, event); +} + void slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event) { try { @@ -475,7 +481,7 @@ void slashcommands::register_commands(dpp::cluster *bot, bot::settings::settings commands.push_back(command_translate); if (preferred_languages.size() > 1) { - dpp::slashcommand command_translate_pref("translate_pref", "Translate current channel (Preferred languages)", bot->me.id); + dpp::slashcommand command_translate_pref("translate pref", "Translate current channel (Preferred languages)", bot->me.id); command_translate_pref.set_default_permissions(dpp::p_manage_webhooks); dpp::command_option channel_pref_subcommand(dpp::co_sub_command, "channel", "Translate current channel to a channel (Preferred languages)"); dpp::command_option webhook_pref_subcommand(dpp::co_sub_command, "webhook", "Translate current channel to a webhook (Preferred languages)"); @@ -498,5 +504,9 @@ void slashcommands::register_commands(dpp::cluster *bot, bot::settings::settings commands.push_back(command_translate_pref); } + dpp::slashcommand command_translate_message("translate message", dpp::ctxm_message, bot->me.id); + command_translate_message.set_dm_permission(true); + commands.push_back(command_translate_message); + bot->global_bulk_command_create(commands); } diff --git a/src/core/slashcommands.h b/src/core/slashcommands.h index a31d37f..98fe33b 100644 --- a/src/core/slashcommands.h +++ b/src/core/slashcommands.h @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -20,6 +20,7 @@ #define SLASHCOMMANDS_H #include +#include "message_queue.h" #include "settings.h" namespace bot { @@ -27,6 +28,7 @@ namespace bot { public: slashcommands() = delete; static void process_command_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event); + static void process_message_menu_event(bot::message_queue *message_queue, dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_context_menu_t &event); static void register_commands(dpp::cluster *bot, bot::settings::settings *settings); private: diff --git a/src/core/submit_queue.cpp b/src/core/submit_queue.cpp index 12ce874..c39e997 100644 --- a/src/core/submit_queue.cpp +++ b/src/core/submit_queue.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -44,7 +44,12 @@ void submit_queue::run(dpp::cluster *bot) m_queue.pop(); m_mutex.unlock(); - webhook_push::run(message, bot); + if (const auto *direct_message = std::get_if(&message)) { + direct_message->event.edit_original_response(dpp::message(direct_message->message)); + } + else if (const auto *guild_message = std::get_if(&message)) { + webhook_push::run(*guild_message, bot); + } std::this_thread::yield(); } diff --git a/src/core/submit_queue.h b/src/core/submit_queue.h index 2d88546..ffc58b0 100644 --- a/src/core/submit_queue.h +++ b/src/core/submit_queue.h @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -25,13 +25,20 @@ #include namespace bot { - struct translated_message { + struct translated_direct_message { + dpp::message_context_menu_t event; + std::string message; + }; + + struct translated_guild_message { std::string author; std::string avatar; std::string message; dpp::webhook webhook; }; + typedef std::variant translated_message; + class submit_queue { public: void add(const translated_message &message); diff --git a/src/core/webhook_push.cpp b/src/core/webhook_push.cpp index 457ec3e..3d4e567 100644 --- a/src/core/webhook_push.cpp +++ b/src/core/webhook_push.cpp @@ -1,6 +1,6 @@ /***************************************************************************** * dtranslatebot Discord Translate Bot -* Copyright (C) 2024 Syping +* Copyright (C) 2024-2026 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: @@ -22,7 +22,7 @@ using namespace std::string_literals; using namespace std::string_view_literals; -void bot::webhook_push::run(const bot::translated_message &message, dpp::cluster *bot) +void bot::webhook_push::run(const bot::translated_guild_message &message, dpp::cluster *bot) { dpp::json json_body = { {"username"s, message.author} diff --git a/src/core/webhook_push.h b/src/core/webhook_push.h index d4b03f9..ec482eb 100644 --- a/src/core/webhook_push.h +++ b/src/core/webhook_push.h @@ -27,7 +27,7 @@ namespace bot { class webhook_push { public: webhook_push() = delete; - static void run(const bot::translated_message &message, dpp::cluster *bot); + static void run(const bot::translated_guild_message &message, dpp::cluster *bot); private: static void push_request(dpp::snowflake webhook_id, const std::string &webhook_token, const std::string &json, dpp::cluster *bot); diff --git a/src/translator/deepl/deepl.cpp b/src/translator/deepl/deepl.cpp index 6fb73c6..8658fb2 100644 --- a/src/translator/deepl/deepl.cpp +++ b/src/translator/deepl/deepl.cpp @@ -44,7 +44,7 @@ const std::vector deepl::get_languages() try { http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, 443, "/v2/languages?type=target", true), { {"Authorization"s, "DeepL-Auth-Key " + m_apiKey} }); + http_response response = request.get(http_request::legacy_url(m_hostname, 443, "/v2/languages?type=target"s, true), { {"Authorization"s, "DeepL-Auth-Key "s + m_apiKey} }); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_array()) { @@ -90,9 +90,10 @@ const std::string deepl::translate(const std::string &text, const std::string &s dpp::json json_body = { {"text"s, { text } }, - {"source_lang"s, source}, - {"target_lang"s, target}, + {"target_lang"s, target} }; + if (!source.empty()) + json_body["source_lang"] = source; try { http_request request; diff --git a/src/translator/libretranslate/libretranslate.cpp b/src/translator/libretranslate/libretranslate.cpp index 96d6f36..b198268 100644 --- a/src/translator/libretranslate/libretranslate.cpp +++ b/src/translator/libretranslate/libretranslate.cpp @@ -44,7 +44,7 @@ const std::vector libretranslate::get_languages() try { http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "languages", m_tls)); + http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "languages"s, m_tls)); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_array()) { @@ -84,7 +84,7 @@ const std::string libretranslate::translate(const std::string &text, const std:: dpp::json json_body = { {"q"s, text}, - {"source"s, source}, + {"source"s, source.empty() ? "auto"s : source}, {"target"s, target}, {"format"s, "text"s} }; @@ -94,7 +94,7 @@ const std::string libretranslate::translate(const std::string &text, const std:: try { http_request request; - http_response response = request.post(http_request::legacy_url(m_hostname, m_port, m_url + "translate", m_tls), json_body.dump(), "application/json"); + http_response response = request.post(http_request::legacy_url(m_hostname, m_port, m_url + "translate"s, m_tls), json_body.dump(), "application/json"); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_object()) { diff --git a/src/translator/lingvatranslate/lingvatranslate.cpp b/src/translator/lingvatranslate/lingvatranslate.cpp index 2231398..1159f24 100644 --- a/src/translator/lingvatranslate/lingvatranslate.cpp +++ b/src/translator/lingvatranslate/lingvatranslate.cpp @@ -45,7 +45,7 @@ const std::vector lingvatranslate::get_languages() try { http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/languages/target", m_tls)); + http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/languages/target"s, m_tls)); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_object()) { @@ -84,7 +84,7 @@ const std::string lingvatranslate::translate(const std::string &text, const std: { try { http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/" + source + "/" + target + "/" + dpp::utility::url_encode(text), m_tls)); + http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/"s + (source.empty() ? "auto"s : source) + "/"s + target + "/"s + dpp::utility::url_encode(text), m_tls)); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_object()) { diff --git a/src/translator/mozhi/mozhi.cpp b/src/translator/mozhi/mozhi.cpp index f9ffda7..6645985 100644 --- a/src/translator/mozhi/mozhi.cpp +++ b/src/translator/mozhi/mozhi.cpp @@ -48,7 +48,7 @@ const std::vector mozhi::get_languages() {"engine"s, m_engine} }); http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/target_languages", m_tls)); + http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/target_languages"s, m_tls)); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_array()) { @@ -85,12 +85,12 @@ const std::string mozhi::translate(const std::string &text, const std::string &s try { const std::string parameters = dpp::utility::make_url_parameters({ {"engine"s, m_engine}, - {"from"s, source}, + {"from"s, source.empty() ? "auto"s : source}, {"to"s, target}, - {"text"s, text}, + {"text"s, text} }); http_request request; - http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/translate" + parameters, m_tls)); + http_response response = request.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/translate"s + parameters, m_tls)); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_object()) {