diff --git a/CMakeLists.txt b/CMakeLists.txt index c9da687..453b3c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,13 +18,12 @@ cmake_minimum_required(VERSION 3.16) cmake_policy(VERSION 3.16...3.27) -project(dtranslatebot VERSION 0.3.3 LANGUAGES CXX) +project(dtranslatebot VERSION 0.3.1 LANGUAGES CXX) include(GNUInstallDirs) # dtranslatebot Source files set(DTRANSLATEBOT_HEADERS src/core/database.h - src/core/http_headers.h src/core/http_request.h src/core/http_response.h src/core/message_queue.h @@ -44,7 +43,6 @@ set(DTRANSLATEBOT_HEADERS ) set(DTRANSLATEBOT_SOURCES src/core/database.cpp - src/core/http_headers.cpp src/core/http_request.cpp src/core/main.cpp src/core/message_queue.cpp diff --git a/README.md b/README.md index 79e1ebc..8e68dab 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Open Source Discord Translation Bot - [LibreTranslate](https://libretranslate.com/) (Default) - [Lingva Translate](https://lingva.ml/) - [Mozhi](https://codeberg.org/aryak/mozhi) -- [DeepL](https://deepl.com/) +- [DeepL](https://deepl.com/) (Experimental) #### Build dtranslatebot diff --git a/rpmsrc/dtranslatebot.spec b/rpmsrc/dtranslatebot.spec index 0338a06..2c2c894 100644 --- a/rpmsrc/dtranslatebot.spec +++ b/rpmsrc/dtranslatebot.spec @@ -15,7 +15,7 @@ %endif Name: dtranslatebot -Version: 0.3.3 +Version: 0.3.1 Release: 1%{?dist} Summary: Discord Translation Bot License: BSD-2-Clause diff --git a/src/core/http_headers.cpp b/src/core/http_headers.cpp deleted file mode 100644 index c118d1f..0000000 --- a/src/core/http_headers.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/***************************************************************************** -* dtranslatebot Discord Translate Bot -* Copyright (C) 2026 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 "http_headers.h" -using namespace bot::http; - -http_headers::http_headers() { - instance = nullptr; -} - -http_headers::http_headers(const http_headers &headers) { - instance = copy_from(headers.data()); -} - -http_headers::http_headers(const std::string &field, const std::string &value) { - instance = nullptr; - add(field, value); -} - -http_headers::http_headers(const http_header &header) { - instance = nullptr; - add(header); -} - -http_headers::http_headers(const std::initializer_list &headers) { - instance = nullptr; - try { - add(headers); - } - catch (const std::bad_alloc &exception) { - curl_slist_free_all(instance); - throw; - } -} - -http_headers::http_headers(const std::vector &headers) { - instance = nullptr; - try { - add(headers); - } - catch (const std::bad_alloc &exception) { - curl_slist_free_all(instance); - throw; - } -} - -http_headers::~http_headers() { - curl_slist_free_all(instance); -} - -http_headers& http_headers::operator=(const curl_slist *other) { - if (this->data() == other) - return *this; - if (curl_slist *headers = copy_from(other)) { - curl_slist_free_all(instance); - instance = headers; - } - return *this; -} - -http_headers& http_headers::operator=(const http_headers &other) { - if (this == &other) - return *this; - if (curl_slist *headers = copy_from(other.data())) { - curl_slist_free_all(instance); - instance = headers; - } - return *this; -} - -void http_headers::add(const std::string &field, const std::string &value) { - const std::string header = field + ": " + value; - curl_slist *headers = curl_slist_append(instance, header.c_str()); - if (!headers) - throw std::bad_alloc(); - instance = headers; -} - -void http_headers::add(const http_header &header) { - add(header.first, header.second); -} - -void http_headers::add(const std::initializer_list &headers) { - for (const auto &header : headers) - add(header); -} - -void http_headers::add(const std::vector &headers) { - for (const auto &header : headers) - add(header); -} - -void http_headers::remove(const std::string &field) { - const std::string header = field + ":"; - curl_slist *headers = curl_slist_append(instance, header.c_str()); - if (!headers) - throw std::bad_alloc(); - instance = headers; -} - -void http_headers::remove(const std::vector &fields) { - for (const auto &field : fields) - remove(field); -} - -const curl_slist* http_headers::data() const { - return instance; -} - -curl_slist* http_headers::copy_from(const curl_slist *headers) { - curl_slist *instance = nullptr; - for (const curl_slist *i = headers; i; i = i->next) { - curl_slist *headers = curl_slist_append(instance, i->data); - if (!headers) { - curl_slist_free_all(instance); - throw std::bad_alloc(); - } - instance = headers; - } - return instance; -} diff --git a/src/core/http_headers.h b/src/core/http_headers.h deleted file mode 100644 index 7e488cd..0000000 --- a/src/core/http_headers.h +++ /dev/null @@ -1,57 +0,0 @@ -/***************************************************************************** -* dtranslatebot Discord Translate Bot -* Copyright (C) 2026 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 HTTP_HEADERS_H -#define HTTP_HEADERS_H -#include -#include -#include -#include -#include - -namespace bot { - namespace http { - typedef std::pair http_header; - - class http_headers { - public: - http_headers(); - http_headers(const http_headers &headers); - http_headers(const std::string &field, const std::string &value); - http_headers(const http_header &header); - http_headers(const std::initializer_list &headers); - http_headers(const std::vector &headers); - ~http_headers(); - http_headers& operator=(const curl_slist *headers); - http_headers& operator=(const http_headers &headers); - void add(const std::string &field, const std::string &value); - void add(const http_header &header); - void add(const std::initializer_list &headers); - void add(const std::vector &headers); - void remove(const std::string &field); - void remove(const std::vector &fields); - const curl_slist* data() const; - - private: - static curl_slist* copy_from(const curl_slist *headers); - curl_slist *instance; - }; - } -} - -#endif // HTTP_HEADERS_H diff --git a/src/core/http_request.cpp b/src/core/http_request.cpp index dde7872..58d9e0e 100644 --- a/src/core/http_request.cpp +++ b/src/core/http_request.cpp @@ -17,7 +17,6 @@ *****************************************************************************/ #include "http_request.h" -using namespace bot::http; http_request::http_request() { instance = curl_easy_init(); @@ -25,23 +24,56 @@ http_request::http_request() { throw std::bad_alloc(); } -const http_response http_request::get(const std::string &url, const http_headers &headers) { +const http_response http_request::get(const std::string &url, const dpp::http_headers &headers) { http_response response; curl_easy_setopt(instance, CURLOPT_URL, url.c_str()); - curl_easy_setopt(instance, CURLOPT_HTTPHEADER, headers.data()); + curl_slist *header_slist = nullptr; + if (!headers.empty()) { + for (const auto &header : headers) { + curl_slist *new_header_slist = curl_slist_append(header_slist, std::string(header.first + ": " + header.second).c_str()); + if (!new_header_slist) { + curl_slist_free_all(header_slist); + throw std::bad_alloc(); + } + header_slist = new_header_slist; + } + } + if (!header_slist) + curl_easy_setopt(instance, CURLOPT_HTTPHEADER, header_slist); curl_easy_setopt(instance, CURLOPT_WRITEDATA, &response.content); curl_easy_setopt(instance, CURLOPT_WRITEFUNCTION, &writer); CURLcode result = curl_easy_perform(instance); if (result == CURLE_OK) curl_easy_getinfo(instance, CURLINFO_RESPONSE_CODE, &response.status); curl_easy_reset(instance); + curl_slist_free_all(header_slist); return response; } -const http_response http_request::post(const std::string &url, const std::string &content, const http_headers &headers) { +const http_response http_request::post(const std::string &url, const std::string &content, const std::string &content_type, const dpp::http_headers &headers) { http_response response; curl_easy_setopt(instance, CURLOPT_URL, url.c_str()); - curl_easy_setopt(instance, CURLOPT_HTTPHEADER, headers.data()); + 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()); + if (!new_header_slist) { + curl_slist_free_all(header_slist); + throw std::bad_alloc(); + } + header_slist = new_header_slist; + } + if (!headers.empty()) { + for (const auto &header : headers) { + curl_slist *new_header_slist = curl_slist_append(header_slist, std::string(header.first + ": " + header.second).c_str()); + if (!new_header_slist) { + curl_slist_free_all(header_slist); + throw std::bad_alloc(); + } + header_slist = new_header_slist; + } + } + if (!header_slist) + curl_easy_setopt(instance, CURLOPT_HTTPHEADER, header_slist); curl_easy_setopt(instance, CURLOPT_POSTFIELDS, content.data()); curl_easy_setopt(instance, CURLOPT_POSTFIELDSIZE, content.size()); curl_easy_setopt(instance, CURLOPT_WRITEDATA, &response.content); @@ -50,6 +82,7 @@ const http_response http_request::post(const std::string &url, const std::string if (result == CURLE_OK) curl_easy_getinfo(instance, CURLINFO_RESPONSE_CODE, &response.status); curl_easy_reset(instance); + curl_slist_free_all(header_slist); return response; } diff --git a/src/core/http_request.h b/src/core/http_request.h index 104b5f4..e9bb387 100644 --- a/src/core/http_request.h +++ b/src/core/http_request.h @@ -20,26 +20,21 @@ #define HTTP_REQUEST_H #include #include -#include "http_headers.h" +#include #include "http_response.h" -namespace bot { - namespace http { - class http_request { - public: - http_request(); - http_request(const http_request&) = delete; - http_request& operator=(const http_request&) = delete; - ~http_request(); - const http_response get(const std::string &url, const http_headers &headers = {}); - const http_response post(const std::string &url, const std::string &content, const http_headers &headers = {}); - static std::string legacy_url(const std::string &hostname, uint16_t port, const std::string &url, bool tls); +class http_request +{ +public: + http_request(); + ~http_request(); + const http_response get(const std::string &url, const dpp::http_headers &headers = {}); + const http_response post(const std::string &url, const std::string &content, const std::string &content_type, const dpp::http_headers &headers = {}); + static std::string legacy_url(const std::string &hostname, uint16_t port, const std::string &url, bool tls); - private: - static size_t writer(char *source, size_t size, size_t nmemb, std::string *target); - CURL *instance; - }; - } -} +private: + static size_t writer(char *source, size_t size, size_t nmemb, std::string *target); + CURL *instance; +}; #endif // HTTP_REQUEST_H diff --git a/src/core/http_response.h b/src/core/http_response.h index e4cc06a..2364e13 100644 --- a/src/core/http_response.h +++ b/src/core/http_response.h @@ -20,13 +20,10 @@ #define HTTP_RESPONSE_H #include -namespace bot { - namespace http { - struct http_response { - std::string content; - long status; - }; - } -} +struct http_response +{ + std::string content; + long status; +}; #endif // HTTP_RESPONSE_H diff --git a/src/translator/deepl/deepl.cpp b/src/translator/deepl/deepl.cpp index a8b8047..8658fb2 100644 --- a/src/translator/deepl/deepl.cpp +++ b/src/translator/deepl/deepl.cpp @@ -17,11 +17,12 @@ *****************************************************************************/ #include -#include +#include +#include "../../core/http_request.h" #include "deepl.h" -using namespace bot::http; using namespace bot::translator; using namespace std::chrono_literals; +using namespace std::string_literals; deepl::deepl(const std::string &hostname, const std::string apiKey) : m_hostname(hostname), m_apiKey(apiKey) @@ -42,8 +43,8 @@ const std::vector deepl::get_languages() } try { - http_response response = m_http.get(http_request::legacy_url(m_hostname, 443, "/v2/languages?type=target", true), - {{"Authorization", "DeepL-Auth-Key " + m_apiKey}}); + http_request request; + 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()) { @@ -56,16 +57,10 @@ const std::vector deepl::get_languages() if (json_lang_code != json_language->end()) language.code = *json_lang_code; - if (language.code.size() == 5) { + if (language.code.size() > 2) std::transform(language.code.begin(), language.code.begin() + 2, language.code.begin(), ::tolower); - } - else if (language.code.size() > 5) { - std::transform(language.code.begin(), language.code.begin() + 2, language.code.begin(), ::tolower); - std::transform(language.code.begin() + 4, language.code.end(), language.code.begin() + 4, ::tolower); - } - else { + else 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()) @@ -75,23 +70,6 @@ const std::vector deepl::get_languages() m_languages.languages.push_back(std::move(language)); } } - // Improving DeepL compatibility - if (std::find_if(m_languages.languages.begin(), m_languages.languages.end(), [](language language) { - return language.code == "en"; - }) == m_languages.languages.end()) { - language english; - english.code = "en"; - english.name = "English (Default)"; - m_languages.languages.push_back(english); - } - if (std::find_if(m_languages.languages.begin(), m_languages.languages.end(), [](language language) { - return language.code == "pt"; - }) == m_languages.languages.end()) { - language portuguese; - portuguese.code = "pt"; - portuguese.name = "Portuguese (Default)"; - m_languages.languages.push_back(portuguese); - } m_languages.query_time = std::chrono::system_clock::now(); } } @@ -105,16 +83,21 @@ const std::vector deepl::get_languages() const std::string deepl::translate(const std::string &text, const std::string &source, const std::string &target) { - dpp::json json_body = { - {"text", { text } }, - {"target_lang", target == "en" ? "en-US" : target == "pt" ? "pt-PT" : target} + const dpp::http_headers http_headers = { + {"Authorization"s, "DeepL-Auth-Key " + m_apiKey}, + {"Content-Type"s, "application/json"s} }; - if (!source.empty() && source != "auto") - json_body["source_lang"] = source.length() > 2 ? source.substr(0, 2) : source; + + dpp::json json_body = { + {"text"s, { text } }, + {"target_lang"s, target} + }; + if (!source.empty()) + json_body["source_lang"] = source; try { - http_response response = m_http.post(http_request::legacy_url(m_hostname, 443, "/v2/translate", true), json_body.dump(), - {{"Authorization", "DeepL-Auth-Key " + m_apiKey}, {"Content-Type", "application/json"}}); + http_request request; + http_response response = request.post(http_request::legacy_url(m_hostname, 443, "/v2/translate", true), json_body.dump(), "application/json", { {"Authorization"s, "DeepL-Auth-Key " + m_apiKey} }); if (response.status == 200) { const dpp::json json_response = dpp::json::parse(response.content); if (json_response.is_object()) { diff --git a/src/translator/deepl/deepl.h b/src/translator/deepl/deepl.h index 0e07aa6..6fd90f9 100644 --- a/src/translator/deepl/deepl.h +++ b/src/translator/deepl/deepl.h @@ -19,7 +19,7 @@ #ifndef TRANSLATOR_DEEPL_H #define TRANSLATOR_DEEPL_H -#include "../../core/http_request.h" +#include #include "../../core/translator.h" namespace bot { @@ -34,7 +34,6 @@ namespace bot { private: std::string m_apiKey; std::string m_hostname; - bot::http::http_request m_http; supported_languages m_languages; }; } diff --git a/src/translator/libretranslate/libretranslate.cpp b/src/translator/libretranslate/libretranslate.cpp index 8cc6926..b198268 100644 --- a/src/translator/libretranslate/libretranslate.cpp +++ b/src/translator/libretranslate/libretranslate.cpp @@ -17,11 +17,12 @@ *****************************************************************************/ #include -#include +#include +#include "../../core/http_request.h" #include "libretranslate.h" -using namespace bot::http; using namespace bot::translator; using namespace std::chrono_literals; +using namespace std::string_literals; libretranslate::libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string apiKey) : m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_apiKey(apiKey) @@ -42,7 +43,8 @@ const std::vector libretranslate::get_languages() } try { - http_response response = m_http.get(http_request::legacy_url(m_hostname, m_port, m_url + "languages", m_tls)); + http_request request; + 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()) { @@ -76,19 +78,23 @@ const std::vector libretranslate::get_languages() const std::string libretranslate::translate(const std::string &text, const std::string &source, const std::string &target) { + const dpp::http_headers http_headers = { + {"Content-Type"s, "application/json"s} + }; + dpp::json json_body = { - {"q", text}, - {"source", source.empty() ? "auto" : source}, - {"target", target}, - {"format", "text"} + {"q"s, text}, + {"source"s, source.empty() ? "auto"s : source}, + {"target"s, target}, + {"format"s, "text"s} }; if (!m_apiKey.empty()) json_body["apiKey"] = m_apiKey; try { - http_response response = m_http.post(http_request::legacy_url(m_hostname, m_port, m_url + "translate", m_tls), json_body.dump(), - {{"Content-Type", "application/json"}}); + http_request request; + 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/libretranslate/libretranslate.h b/src/translator/libretranslate/libretranslate.h index d0237ec..04c0439 100644 --- a/src/translator/libretranslate/libretranslate.h +++ b/src/translator/libretranslate/libretranslate.h @@ -20,7 +20,7 @@ #define TRANSLATOR_LIBRETRANSLATE_H #include -#include "../../core/http_request.h" +#include #include "../../core/translator.h" namespace bot { @@ -36,7 +36,6 @@ namespace bot { private: std::string m_apiKey; std::string m_hostname; - bot::http::http_request m_http; supported_languages m_languages; uint16_t m_port; std::string m_url; diff --git a/src/translator/lingvatranslate/lingvatranslate.cpp b/src/translator/lingvatranslate/lingvatranslate.cpp index e9eaad1..1159f24 100644 --- a/src/translator/lingvatranslate/lingvatranslate.cpp +++ b/src/translator/lingvatranslate/lingvatranslate.cpp @@ -17,12 +17,13 @@ *****************************************************************************/ #include +#include #include -#include +#include "../../core/http_request.h" #include "lingvatranslate.h" -using namespace bot::http; using namespace bot::translator; using namespace std::chrono_literals; +using namespace std::string_literals; lingvatranslate::lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls) : m_hostname(hostname), m_port(port), m_url(url), m_tls(tls) @@ -43,7 +44,8 @@ const std::vector lingvatranslate::get_languages() } try { - http_response response = m_http.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/languages/target", m_tls)); + http_request request; + 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()) { @@ -81,7 +83,8 @@ const std::vector lingvatranslate::get_languages() const std::string lingvatranslate::translate(const std::string &text, const std::string &source, const std::string &target) { try { - http_response response = m_http.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/v1/" + (source.empty() ? "auto" : source) + "/" + target + "/" + dpp::utility::url_encode(text), m_tls)); + http_request request; + 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/lingvatranslate/lingvatranslate.h b/src/translator/lingvatranslate/lingvatranslate.h index c18b67b..cf65739 100644 --- a/src/translator/lingvatranslate/lingvatranslate.h +++ b/src/translator/lingvatranslate/lingvatranslate.h @@ -20,7 +20,7 @@ #define TRANSLATOR_LINGVATRANSLATE_H #include -#include "../../core/http_request.h" +#include #include "../../core/translator.h" namespace bot { @@ -35,7 +35,6 @@ namespace bot { private: std::string m_hostname; - bot::http::http_request m_http; supported_languages m_languages; uint16_t m_port; std::string m_url; diff --git a/src/translator/mozhi/mozhi.cpp b/src/translator/mozhi/mozhi.cpp index 5231113..6645985 100644 --- a/src/translator/mozhi/mozhi.cpp +++ b/src/translator/mozhi/mozhi.cpp @@ -17,12 +17,13 @@ *****************************************************************************/ #include +#include #include -#include +#include "../../core/http_request.h" #include "mozhi.h" -using namespace bot::http; using namespace bot::translator; using namespace std::chrono_literals; +using namespace std::string_literals; mozhi::mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine) : m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_engine(engine) @@ -44,9 +45,10 @@ const std::vector mozhi::get_languages() try { const std::string parameters = dpp::utility::make_url_parameters({ - {"engine", m_engine} + {"engine"s, m_engine} }); - http_response response = m_http.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/target_languages", m_tls)); + http_request request; + 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()) { @@ -82,12 +84,13 @@ const std::string mozhi::translate(const std::string &text, const std::string &s { try { const std::string parameters = dpp::utility::make_url_parameters({ - {"engine", m_engine}, - {"from", source.empty() ? "auto" : source}, - {"to", target}, - {"text", text} + {"engine"s, m_engine}, + {"from"s, source.empty() ? "auto"s : source}, + {"to"s, target}, + {"text"s, text} }); - http_response response = m_http.get(http_request::legacy_url(m_hostname, m_port, m_url + "api/translate" + parameters, m_tls)); + http_request request; + 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()) { diff --git a/src/translator/mozhi/mozhi.h b/src/translator/mozhi/mozhi.h index fb40aa4..1602096 100644 --- a/src/translator/mozhi/mozhi.h +++ b/src/translator/mozhi/mozhi.h @@ -20,7 +20,7 @@ #define TRANSLATOR_MOZHI_H #include -#include "../../core/http_request.h" +#include #include "../../core/translator.h" namespace bot { @@ -36,7 +36,6 @@ namespace bot { private: std::string m_engine; std::string m_hostname; - bot::http::http_request m_http; supported_languages m_languages; uint16_t m_port; std::string m_url;