From 0e369f5a1d20c1a5567dd375916a34b314fa00dd Mon Sep 17 00:00:00 2001 From: Syping Date: Sat, 23 Mar 2024 16:57:54 +0100 Subject: [PATCH] add experimental deepl support --- CMakeLists.txt | 2 + src/core/settings.cpp | 21 ++++++- src/core/settings_types.h | 1 + src/core/slashcommands.cpp | 4 +- src/translator/deepl/deepl.cpp | 104 +++++++++++++++++++++++++++++++++ src/translator/deepl/deepl.h | 40 +++++++++++++ 6 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 src/translator/deepl/deepl.cpp create mode 100644 src/translator/deepl/deepl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 178606b..5c169ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ set(DTRANSLATEBOT_HEADERS src/core/translator.h src/core/webhook_push.h src/database/file/file.h + src/translator/deepl/deepl.h src/translator/libretranslate/libretranslate.h src/translator/stub/stub.h ) @@ -46,6 +47,7 @@ set(DTRANSLATEBOT_SOURCES src/core/translator.cpp src/core/webhook_push.cpp src/database/file/file.cpp + src/translator/deepl/deepl.cpp src/translator/libretranslate/libretranslate.cpp src/translator/stub/stub.cpp ) diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 46a9af5..ee0cbd9 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -21,6 +21,7 @@ #include #include "settings.h" #include "../database/file/file.h" +#include "../translator/deepl/deepl.h" #include "../translator/libretranslate/libretranslate.h" #include "../translator/stub/stub.h" using namespace bot::settings; @@ -233,7 +234,23 @@ bool process_translator_settings(const dpp::json &json, translator &translator) std::transform(translator_type.begin(), translator_type.end(), translator_type.begin(), ::tolower); } - if (translator_type == "libretranslate") { + if (translator_type == "deepl") { + translator.type = TRANSLATOR_DEEPL; + + auto json_deepl_hostname = json.find("hostname"); + if (json_deepl_hostname != json.end()) + translator.hostname = *json_deepl_hostname; + else + translator.hostname = "api-free.deepl.com"; + + auto json_deepl_apiKey = json.find("apiKey"); + if (json_deepl_apiKey == json.end()) { + std::cerr << "[Error] DeepL requires API key for authorization" << std::endl; + return false; + } + translator.apiKey = *json_deepl_apiKey; + } + else if (translator_type == "libretranslate") { translator.type = TRANSLATOR_LIBRETRANSLATE; auto json_lt_hostname = json.find("hostname"); @@ -459,6 +476,8 @@ std::unique_ptr settings::get_translator() const const std::lock_guard guard(m_mutex); switch (m_translator.type) { + case TRANSLATOR_DEEPL: + return std::make_unique(m_translator.hostname, m_translator.apiKey); case TRANSLATOR_LIBRETRANSLATE: return std::make_unique(m_translator.hostname, m_translator.port, m_translator.url, m_translator.tls, m_translator.apiKey); case TRANSLATOR_STUB: diff --git a/src/core/settings_types.h b/src/core/settings_types.h index 923be1c..3eed401 100644 --- a/src/core/settings_types.h +++ b/src/core/settings_types.h @@ -41,6 +41,7 @@ namespace bot { std::vector channel; }; enum translator_type { + TRANSLATOR_DEEPL, TRANSLATOR_LIBRETRANSLATE, TRANSLATOR_STUB }; diff --git a/src/core/slashcommands.cpp b/src/core/slashcommands.cpp index b1bbc21..a55614c 100644 --- a/src/core/slashcommands.cpp +++ b/src/core/slashcommands.cpp @@ -434,9 +434,9 @@ void slashcommands::register_commands(dpp::cluster *bot, bot::settings::settings std::vector commands; dpp::command_option source_option(dpp::co_string, "source", "Source language (ISO 639-1)", true); - source_option.set_max_length(static_cast(2)).set_min_length(static_cast(2)); + source_option.set_max_length(static_cast(5)).set_min_length(static_cast(2)); dpp::command_option target_option(dpp::co_string, "target", "Target language (ISO 639-1)", true); - target_option.set_max_length(static_cast(2)).set_min_length(static_cast(2)); + target_option.set_max_length(static_cast(5)).set_min_length(static_cast(2)); dpp::slashcommand command_edit("edit", "Edit current channel settings", bot->me.id); command_edit.set_default_permissions(dpp::p_manage_webhooks); diff --git a/src/translator/deepl/deepl.cpp b/src/translator/deepl/deepl.cpp new file mode 100644 index 0000000..43d9c76 --- /dev/null +++ b/src/translator/deepl/deepl.cpp @@ -0,0 +1,104 @@ +/***************************************************************************** +* dtranslatebot Discord Translate Bot +* Copyright (C) 2024 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#include +#include +#include "deepl.h" +using namespace bot::translator; +using namespace std::string_literals; + +deepl::deepl(const std::string &hostname, const std::string apiKey) : + m_hostname(hostname), m_apiKey(apiKey) +{ +} + +deepl::~deepl() +{ +} + +const std::vector deepl::get_languages() +{ + std::vector languages; + + try { + dpp::https_client http_request(m_hostname, 443, "/v2/languages?type=target", "GET", {}, { {"Authorization"s, "DeepL-Auth-Key " + m_apiKey} }, false); + if (http_request.get_status() == 200) { + const dpp::json response = dpp::json::parse(http_request.get_content()); + if (response.is_array()) { + for (const auto &json_language : response) { + if (json_language.is_object()) { + language language; + + auto json_lang_code = json_language.find("language"); + if (json_lang_code != json_language.end()) + language.code = *json_lang_code; + + std::transform(language.code.begin(), language.code.end(), language.code.begin(), ::tolower); + + auto json_lang_name = json_language.find("name"); + if (json_lang_name != json_language.end()) + language.name = *json_lang_name; + + if (!language.code.empty() && !language.name.empty()) + languages.push_back(std::move(language)); + } + } + } + } + } + catch (const std::exception &exception) { + std::cerr << "[Exception] " << exception.what() << std::endl; + } + + return languages; +} + +const std::string deepl::translate(const std::string &text, const std::string &source, const std::string &target) +{ + const dpp::http_headers http_headers = { + {"Authorization"s, "DeepL-Auth-Key "s + m_apiKey}, + {"Content-Type"s, "application/json"s} + }; + + dpp::json json_body = { + {"text"s, { text } }, + {"target_lang"s, target}, + }; + + try { + dpp::https_client http_request(m_hostname, 443, "/v2/translate", "POST", json_body.dump(), http_headers, false); + if (http_request.get_status() == 200) { + const dpp::json response = dpp::json::parse(http_request.get_content()); + if (response.is_object()) { + auto translations = response.find("translations"); + if (translations != response.end() && translations->is_array()) { + for (auto translation = translations->begin(); translation != translations->end(); translation++) { + auto tr_text = translation->find("text"); + if (tr_text != translation->end()) + return *tr_text; + } + } + } + } + } + catch (const std::exception &exception) { + std::cerr << "[Exception] " << exception.what() << std::endl; + } + + return text; +} diff --git a/src/translator/deepl/deepl.h b/src/translator/deepl/deepl.h new file mode 100644 index 0000000..a33031e --- /dev/null +++ b/src/translator/deepl/deepl.h @@ -0,0 +1,40 @@ +/***************************************************************************** +* dtranslatebot Discord Translate Bot +* Copyright (C) 2024 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#ifndef TRANSLATOR_DEEPL_H +#define TRANSLATOR_DEEPL_H + +#include "../../core/translator.h" + +namespace bot { + namespace translator { + class deepl : public translator { + public: + explicit deepl(const std::string &hostname, const std::string apiKey = {}); + ~deepl() override; + const std::vector get_languages() override; + const std::string translate(const std::string &text, const std::string &source, const std::string &target) override; + + private: + std::string m_apiKey; + std::string m_hostname; + }; + } +} + +#endif // TRANSLATOR_DEEPL_H