dtranslatebot: add GTK gui and port messages to log callback system

This commit is contained in:
Syping 2026-04-10 01:34:39 +02:00
parent dfabab2315
commit 84b01d9ae1
27 changed files with 620 additions and 176 deletions

View file

@ -17,15 +17,16 @@
*****************************************************************************/
#include <curl/curl.h>
#include <dpp/cluster.h>
#include <dpp/once.h>
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include "../core/message_queue.h"
#include "../core/discord_bot.h"
#include "../core/settings.h"
#include "../core/slashcommands.h"
using namespace std::chrono_literals;
void output_log(const std::string &message, const std::string &type, bool is_error) {
auto &output = !is_error ? std::cout : std::cerr;
output << "[" << type << "] " << message << std::endl;
}
int main(int argc, char* argv[]) {
bool flag_wait_for_translator = false;
@ -41,60 +42,26 @@ int main(int argc, char* argv[]) {
return 0;
}
std::cout << "[Launch] Processing configuration..." << std::endl;
bot::settings::settings settings;
if (!settings.parse_file(args.at(0)))
return 1;
CURLcode result = curl_global_init(CURL_GLOBAL_DEFAULT);
if (result != CURLE_OK) {
std::cerr << "[Error] Failed to initialise curl" << std::endl;
return 1;
}
for (;;) {
std::cout << "[Launch] Requesting supported languages..." << std::endl;
if (!settings.get_translator()->get_languages().empty()) {
break;
}
else if (flag_wait_for_translator) {
std::this_thread::sleep_for(5000ms);
}
else {
std::cerr << "[Error] Failed to initialise translateable languages" << std::endl;
return 1;
}
std::cout << "[Launch] Processing configuration..." << std::endl;
auto settings = std::make_shared<bot::settings::settings>();
if (!settings->parse_file(args.at(0), &output_log))
return 1;
bot::discord_bot bot;
bot.log_callback_add(&output_log);
try {
bot.run(settings, false, flag_wait_for_translator);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
std::cerr << "[Error] Exception while starting bot" << std::endl;
}
dpp::cluster bot(settings.token(), dpp::i_default_intents | dpp::i_direct_messages | dpp::i_message_content);
bot.on_log([&bot](const dpp::log_t &event) {
std::cerr << "[Log] " << event.message << std::endl;
});
bot::submit_queue submit_queue;
std::thread submit_queue_loop(&bot::submit_queue::run, &submit_queue, &bot);
bot::message_queue message_queue;
std::thread message_queue_loop(&bot::message_queue::run, &message_queue, &settings, &submit_queue);
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<struct register_bot_commands>()) {
bot::slashcommands::register_commands(&bot, &settings);
}
});
std::cout << "[Launch] Starting bot..." << std::endl;
bot.start(dpp::st_wait);
// It's unneccessary, but we choose to exit clean anyway
message_queue.terminate();
message_queue_loop.join();
submit_queue.terminate();
submit_queue_loop.join();
curl_global_cleanup();

91
src/core/discord_bot.cpp Normal file
View file

@ -0,0 +1,91 @@
/*****************************************************************************
* 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 <dpp/once.h>
#include "discord_bot.h"
#include "slashcommands.h"
using namespace bot;
using namespace std::chrono_literals;
void discord_bot::run(std::shared_ptr<bot::settings::settings> settings, bool background, bool wait_for_translator) {
if (m_running)
terminate();
for (;;) {
for (const auto &log_callback : m_log_callbacks)
log_callback("Requesting supported languages...", "Launch", false);
if (!settings->get_translator()->get_languages().empty())
break;
else if (wait_for_translator)
std::this_thread::sleep_for(5000ms);
else
throw std::runtime_error("Failed to initialise translateable languages");
}
m_settings = settings;
m_bot = std::make_unique<dpp::cluster>(m_settings->token(), dpp::i_default_intents | dpp::i_direct_messages | dpp::i_message_content);
m_bot->on_log([=](const dpp::log_t &event) {
for (const auto &log_callback : m_log_callbacks)
log_callback(event.message, "Log", false);
});
m_submit_queue_loop = std::make_unique<std::thread>(&bot::submit_queue::run, &m_submit_queue, m_bot.get());
m_message_queue_loop = std::make_unique<std::thread>(&bot::message_queue::run, &m_message_queue, m_settings.get(), &m_submit_queue);
m_bot->on_message_context_menu(std::bind(&bot::slashcommands::process_message_menu_event, &m_message_queue, m_bot.get(), m_settings.get(), std::placeholders::_1));
m_bot->on_message_create(std::bind(&bot::message_queue::process_guild_message_event, &m_message_queue, m_bot.get(), m_settings.get(), std::placeholders::_1));
m_bot->on_slashcommand(std::bind(&bot::slashcommands::process_command_event, m_bot.get(), m_settings.get(), std::placeholders::_1));
m_bot->on_ready([=]([[maybe_unused]] const dpp::ready_t &event) {
if (dpp::run_once<struct register_bot_commands>())
bot::slashcommands::register_commands(m_bot.get(), m_settings.get());
});
for (const auto &log_callback : m_log_callbacks)
log_callback("Starting bot...", "Launch", false);
m_bot->start(background ? dpp::st_return : dpp::st_wait);
m_running = true;
}
void discord_bot::terminate() {
if (!m_running)
return;
if (std::thread *message_queue_loop = m_message_queue_loop.get()) {
m_message_queue.terminate();
message_queue_loop->join();
m_message_queue_loop.reset();
}
if (std::thread *submit_queue_loop = m_submit_queue_loop.get()) {
m_submit_queue.terminate();
submit_queue_loop->join();
m_submit_queue_loop.reset();
}
m_bot.reset();
m_settings.reset();
m_running = false;
}
bool discord_bot::is_running() {
return m_running;
}
void discord_bot::log_callback_add(const bot::log::log_message_callback &log_callback) {
m_log_callbacks.push_back(log_callback);
}
message_queue* discord_bot::get_message_queue() {
return &m_message_queue;
}
submit_queue* discord_bot::get_submit_queue() {
return &m_submit_queue;
}

56
src/core/discord_bot.h Normal file
View file

@ -0,0 +1,56 @@
/*****************************************************************************
* 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 DISCORD_BOT_H
#define DISCORD_BOT_H
#include <dpp/cluster.h>
#include <memory>
#include "../core/log.h"
#include "../core/message_queue.h"
#include "../core/settings.h"
#include "../core/submit_queue.h"
namespace bot {
class discord_bot {
public:
void run(std::shared_ptr<bot::settings::settings> settings, bool background, bool wait_for_translator = false);
void terminate();
void log_callback_add(const bot::log::log_message_callback &log_callback);
bool is_running();
message_queue* get_message_queue();
submit_queue* get_submit_queue();
/* prevent copies */
explicit discord_bot() = default;
discord_bot(const discord_bot&) = delete;
discord_bot& operator=(const discord_bot&) = delete;
private:
bool m_running;
bot::message_queue m_message_queue;
bot::submit_queue m_submit_queue;
std::shared_ptr<bot::settings::settings> m_settings;
std::unique_ptr<dpp::cluster> m_bot;
std::unique_ptr<std::thread> m_message_queue_loop;
std::unique_ptr<std::thread> m_submit_queue_loop;
std::vector<bot::log::log_message_callback> m_log_callbacks;
};
}
#endif // DISCORD_BOT_H

View file

@ -27,7 +27,7 @@ namespace bot {
namespace http {
class http_request {
public:
http_request();
explicit http_request();
http_request(const http_request&) = delete;
http_request& operator=(const http_request&) = delete;
~http_request();

31
src/core/log.h Normal file
View file

@ -0,0 +1,31 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* 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:
*
* 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 LOG_H
#define LOG_H
#include <functional>
#include <string>
namespace bot {
namespace log {
typedef std::function<void(const std::string&, const std::string&, bool)> log_message_callback;
}
}
#endif // LOG_H

View file

@ -27,16 +27,20 @@ void message_queue::add(const message &message)
{
const std::lock_guard<std::mutex> guard(m_mutex);
m_queue.push(message);
#ifdef DTRANSLATEBOT_GUI
for (const message_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
}
void message_queue::add(message &&message)
{
const std::lock_guard<std::mutex> guard(m_mutex);
m_queue.push(message);
#ifdef DTRANSLATEBOT_GUI
for (const message_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
}
void message_queue::process_direct_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_context_menu_t &event)
@ -105,8 +109,10 @@ void message_queue::run(bot::settings::settings *settings, submit_queue *submit_
if (!m_queue.empty()) {
const message message = m_queue.front();
m_queue.pop();
#ifdef DTRANSLATEBOT_GUI
for (const message_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
m_mutex.unlock();
auto translator = settings->get_translator();
@ -138,24 +144,25 @@ void message_queue::run(bot::settings::settings *settings, submit_queue *submit_
std::this_thread::sleep_for(100ms);
}
}
std::queue<message>().swap(m_queue);
}
#ifdef DTRANSLATEBOT_GUI
size_t message_queue::size() {
const std::lock_guard<std::mutex> guard(m_mutex);
return m_queue.size();
}
void message_queue::size_callback_add(const message_queue_size_callback &callback) {
const std::lock_guard<std::mutex> guard(m_mutex);
m_callbacks.push_back(callback);
}
/*
void message_queue::size_callback_remove(const message_queue_size_callback &callback) {
const std::lock_guard<std::mutex> guard(m_mutex);
m_callbacks.erase(std::remove(m_callbacks.begin(), m_callbacks.end(), callback));
}
*/
#endif
void message_queue::terminate()
{

View file

@ -19,7 +19,9 @@
#ifndef MESSAGE_QUEUE_H
#define MESSAGE_QUEUE_H
#include <dpp/cluster.h>
#ifdef DTRANSLATEBOT_GUI
#include <functional>
#endif
#include <mutex>
#include <queue>
#include <string>
@ -43,25 +45,34 @@ namespace bot {
std::vector<bot::settings::target> targets;
};
#ifdef DTRANSLATEBOT_GUI
typedef std::function<void(size_t)> message_queue_size_callback;
#endif
typedef std::variant<direct_message, guild_message> message;
class message_queue {
public:
message_queue() = default;
message_queue(const message_queue&) = delete;
message_queue& operator=(const message_queue&) = delete;
void add(const message &message);
void add(message &&message);
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);
#ifdef DTRANSLATEBOT_GUI
size_t size();
void size_callback_add(const message_queue_size_callback &callback);
#endif
void terminate();
private:
bool m_running;
std::mutex m_mutex;
std::queue<message> m_queue;
#ifdef DTRANSLATEBOT_GUI
std::vector<message_queue_size_callback> m_callbacks;
#endif
};
}

View file

@ -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:
@ -24,20 +24,30 @@
#else
#include <regex>
#endif
#include <string>
#include <string_view>
#define DTRANSLATEBOT_DISCORD_BOT_TOKEN_REGEX "^([\\w-]{24,})\\.([\\w-]{6,})\\.([\\w-]{27,})$"
namespace bot {
namespace regex {
#ifdef DTRANSLATEBOT_USE_BOOST_REGEX
using boost::regex;
using boost::regex_match;
using boost::match_results;
typedef boost::match_results<std::string_view::const_iterator> svmatch;
using boost::regex;
using boost::regex_match;
using boost::match_results;
typedef boost::match_results<std::string::const_iterator> smatch;
typedef boost::match_results<std::string_view::const_iterator> svmatch;
#else
using std::regex;
using std::regex_match;
using std::match_results;
typedef std::match_results<std::string_view::const_iterator> svmatch;
using std::regex;
using std::regex_match;
using std::match_results;
typedef std::match_results<std::string::const_iterator> smatch;
typedef std::match_results<std::string_view::const_iterator> svmatch;
#endif
inline bool verify_discord_bot_token(const std::string &token) {
return regex_match(token, regex(DTRANSLATEBOT_DISCORD_BOT_TOKEN_REGEX));
}
}
}
#endif // REGEX_H

View file

@ -68,7 +68,6 @@ void process_database_channels(std::shared_ptr<bot::database::database> database
void process_database(std::shared_ptr<bot::database::database> database, std::vector<guild> &guilds, std::vector<user> &users, std::vector<dpp::snowflake> &webhookIds)
{
std::cout << "[Launch] Loading database..." << std::endl;
const std::vector<dpp::snowflake> db_guilds = database->get_guilds();
for (auto db_guild_id = db_guilds.begin(); db_guild_id != db_guilds.end(); db_guild_id++) {
bool guild_found = false;
@ -162,11 +161,11 @@ void process_guild_settings(const dpp::json &json, std::vector<guild> &guilds, s
}
}
void process_preflang_settings(const dpp::json &json, std::vector<std::string> *preferred_langs)
void process_preflang_settings(const dpp::json &json, std::vector<std::string> *preferred_langs, const bot::log::log_message_callback &log_callback)
{
for (auto json_preferred_lang = json.begin(); json_preferred_lang != json.end(); json_preferred_lang++) {
if (std::distance(json.begin(), json_preferred_lang) >= 25) {
std::cerr << "[Error] Value preferred_lang is limited to 25 languages" << std::endl;
log_callback("Value preferred_lang is limited to 25 languages", "Error", true);
break;
}
preferred_langs->push_back(*json_preferred_lang);
@ -217,7 +216,7 @@ void process_server_url(const std::string &url, translator &translator)
}
}
bool process_server(const dpp::json &json, translator &translator)
bool process_server(const dpp::json &json, translator &translator, const bot::log::log_message_callback &log_callback)
{
auto json_hostname = json.find("hostname");
if (json_hostname != json.end())
@ -237,7 +236,7 @@ bool process_server(const dpp::json &json, translator &translator)
auto json_url = json.find("url");
if (json_url == json.end()) {
std::cerr << "[Error] Value url not found in translator object" << std::endl;
log_callback("Value url not found in translator object", "Error", true);
return false;
}
if (translator.hostname.empty())
@ -264,10 +263,10 @@ void process_user_settings(const dpp::json &json, uint16_t &avatar_size)
}
}
bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::translator::translator> &translator_instance)
bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::translator::translator> &translator_instance, const bot::log::log_message_callback &log_callback)
{
if (!json.is_object()) {
std::cerr << "[Error] Value translator needs to be a object" << std::endl;
log_callback("Value translator needs to be a object", "Error", true);
return false;
}
@ -290,15 +289,15 @@ bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::tra
auto json_deepl_apiKey = json.find("apiKey");
if (json_deepl_apiKey == json.end()) {
std::cerr << "[Error] DeepL requires API key for authorization" << std::endl;
log_callback("DeepL requires API key for authorization", "Error", true);
return false;
}
translator.apiKey = *json_deepl_apiKey;
translator_instance = std::make_shared<bot::translator::deepl>(translator.hostname, translator.apiKey);
translator_instance = std::make_shared<bot::translator::deepl>(translator.hostname, translator.apiKey, log_callback);
}
else if (translator.type == "mozhi") {
if (!process_server(json, translator))
if (!process_server(json, translator, log_callback))
return false;
std::string mozhi_engine;
@ -308,25 +307,25 @@ bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::tra
else
mozhi_engine = "google";
translator_instance = std::make_shared<bot::translator::mozhi>(translator.hostname, translator.port, translator.url, translator.tls, mozhi_engine);
translator_instance = std::make_shared<bot::translator::mozhi>(translator.hostname, translator.port, translator.url, translator.tls, mozhi_engine, log_callback);
}
else if (translator.type == "libretranslate") {
if (!process_server(json, translator))
if (!process_server(json, translator, log_callback))
return false;
translator_instance = std::make_shared<bot::translator::libretranslate>(translator.hostname, translator.port, translator.url, translator.tls, translator.apiKey);
translator_instance = std::make_shared<bot::translator::libretranslate>(translator.hostname, translator.port, translator.url, translator.tls, translator.apiKey, log_callback);
}
else if (translator.type == "lingvatranslate") {
if (!process_server(json, translator))
if (!process_server(json, translator, log_callback))
return false;
translator_instance = std::make_shared<bot::translator::lingvatranslate>(translator.hostname, translator.port, translator.url, translator.tls);
translator_instance = std::make_shared<bot::translator::lingvatranslate>(translator.hostname, translator.port, translator.url, translator.tls, log_callback);
}
else if (translator.type == "stub") {
translator_instance = std::make_shared<bot::translator::stub>();
}
else {
std::cerr << "[Error] Translator " << translator.type << " is unknown" << std::endl;
log_callback("Translator " + translator.type + " is unknown", "Error", true);
return false;
}
@ -559,19 +558,36 @@ void settings::lock()
m_externallyLockedCount++;
}
bool settings::parse(const std::string &data, bool initialize)
bool settings::parse(const std::string &data, const bot::log::log_message_callback &log_callback, bool initialize)
{
dpp::json json;
try {
dpp::json json;
try {
json = dpp::json::parse(data, nullptr, true, true);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
std::cerr << "[Error] Exception while parsing JSON" << std::endl;
return false;
}
json = dpp::json::parse(data, nullptr, true, true);
}
catch (const std::exception &exception) {
log_callback(exception.what(), "Exception", true);
log_callback("Exception while parsing JSON", "Error", true);
return false;
}
return process(json, log_callback, initialize);
}
bool settings::parse_file(const std::string &filename, const bot::log::log_message_callback &log_callback, bool initialize)
{
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
if (!ifs.is_open()) {
log_callback("Failed to open JSON configuration file: " + filename, "Error", true);
return false;
}
std::string sdata(std::istreambuf_iterator<char>{ifs}, {});
ifs.close();
return parse(sdata, log_callback, initialize);
}
bool settings::process(const dpp::json &json, const bot::log::log_message_callback &log_callback, bool initialize) {
try {
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
auto json_token = json.find("token");
if (json_token != json.end())
@ -580,7 +596,7 @@ bool settings::parse(const std::string &data, bool initialize)
m_token = token;
if (m_token.empty()) {
std::cerr << "[Error] Discord Bot Token is not configured" << std::endl;
log_callback("Discord Bot Token is not configured", "Error", true);
return false;
}
@ -594,14 +610,14 @@ bool settings::parse(const std::string &data, bool initialize)
if (storage_path.empty())
storage_path = std::filesystem::current_path();
m_database = std::make_shared<bot::database::file>(storage_path);
m_database = std::make_shared<bot::database::file>(storage_path, log_callback);
auto json_translator = json.find("translator");
if (json_translator == json.end()) {
std::cerr << "[Error] Value translator not found" << std::endl;
log_callback("Value translator not found", "Error", true);
return false;
}
if (!process_translator_settings(*json_translator, m_translator))
if (!process_translator_settings(*json_translator, m_translator, log_callback))
return false;
auto json_guilds = json.find("guilds");
@ -610,36 +626,23 @@ bool settings::parse(const std::string &data, bool initialize)
auto json_preflangs = json.find("preferred_lang");
if (json_preflangs != json.end() && json_preflangs->is_array())
process_preflang_settings(*json_preflangs, &m_prefLangs);
process_preflang_settings(*json_preflangs, &m_prefLangs, log_callback);
auto json_user = json.find("user");
if (json_user != json.end() && json_user->is_object())
process_user_settings(*json_user, m_avatarSize);
log_callback("Loading database...", "Launch", false);
process_database(m_database, m_guilds, m_users, m_webhookIds);
return true;
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
log_callback(exception.what(), "Exception", true);
}
return false;
}
bool settings::parse_file(const std::string &filename, bool initialize)
{
std::ifstream ifs(filename, std::ios::in | std::ios::binary);
if (!ifs.is_open()) {
std::cerr << "[Error] Failed to open JSON configuration file located at " << filename << std::endl;
return false;
}
std::string sdata(std::istreambuf_iterator<char>{ifs}, {});
ifs.close();
return parse(sdata, initialize);
}
void settings::unlock()
{
m_mutex.unlock();

View file

@ -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:
@ -20,6 +20,7 @@
#define SETTINGS_H
#include <mutex>
#include "database.h"
#include "log.h"
#include "settings_types.h"
#include "translator.h"
@ -62,8 +63,11 @@ namespace bot {
void unlock();
/* parse functions */
bool parse(const std::string &data, bool initialize = true);
bool parse_file(const std::string &filename, bool initialize = true);
bool parse(const std::string &data, const bot::log::log_message_callback &log_callback, bool initialize = true);
bool parse_file(const std::string &filename, const bot::log::log_message_callback &log_callback, bool initialize = true);
/* process functions */
bool process(const dpp::json &json, const bot::log::log_message_callback &log_callback, bool initialize = true);
/* prevent copies */
settings() = default;

View file

@ -26,16 +26,20 @@ void submit_queue::add(const translated_message &message)
{
const std::lock_guard<std::mutex> guard(m_mutex);
m_queue.push(message);
#ifdef DTRANSLATEBOT_GUI
for (const submit_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
}
void submit_queue::add(translated_message &&message)
{
const std::lock_guard<std::mutex> guard(m_mutex);
m_queue.push(message);
#ifdef DTRANSLATEBOT_GUI
for (const submit_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
}
void submit_queue::run(dpp::cluster *bot)
@ -46,8 +50,10 @@ void submit_queue::run(dpp::cluster *bot)
if (!m_queue.empty()) {
const translated_message message = m_queue.front();
m_queue.pop();
#ifdef DTRANSLATEBOT_GUI
for (const submit_queue_size_callback &callback : m_callbacks)
callback(m_queue.size());
#endif
m_mutex.unlock();
if (const auto *direct_message = std::get_if<bot::translated_direct_message>(&message)) {
@ -64,24 +70,25 @@ void submit_queue::run(dpp::cluster *bot)
std::this_thread::sleep_for(100ms);
}
}
std::queue<translated_message>().swap(m_queue);
}
#ifdef DTRANSLATEBOT_GUI
size_t submit_queue::size() {
const std::lock_guard<std::mutex> guard(m_mutex);
return m_queue.size();
}
void submit_queue::size_callback_add(const submit_queue_size_callback &callback) {
const std::lock_guard<std::mutex> guard(m_mutex);
m_callbacks.push_back(callback);
}
/*
void submit_queue::size_callback_remove(const submit_queue_size_callback &callback) {
const std::lock_guard<std::mutex> guard(m_mutex);
m_callbacks.erase(std::remove(m_callbacks.begin(), m_callbacks.end(), callback));
}
*/
#endif
void submit_queue::terminate()
{

View file

@ -20,10 +20,15 @@
#define SUBMIT_QUEUE_H
#include <dpp/cluster.h>
#include <dpp/webhook.h>
#ifdef DTRANSLATEBOT_GUI
#include <functional>
#endif
#include <mutex>
#include <queue>
#include <string>
#ifdef DTRANSLATEBOT_GUI
#include <vector>
#endif
namespace bot {
struct translated_direct_message {
@ -38,23 +43,32 @@ namespace bot {
dpp::webhook webhook;
};
#ifdef DTRANSLATEBOT_GUI
typedef std::function<void(size_t)> submit_queue_size_callback;
#endif
typedef std::variant<translated_direct_message, translated_guild_message> translated_message;
class submit_queue {
public:
submit_queue() = default;
submit_queue(const submit_queue&) = delete;
submit_queue& operator=(const submit_queue&) = delete;
void add(const translated_message &message);
void add(translated_message &&message);
void run(dpp::cluster *bot);
#ifdef DTRANSLATEBOT_GUI
size_t size();
void size_callback_add(const submit_queue_size_callback &callback);
#endif
void terminate();
private:
bool m_running;
std::mutex m_mutex;
std::queue<translated_message> m_queue;
#ifdef DTRANSLATEBOT_GUI
std::vector<submit_queue_size_callback> m_callbacks;
#endif
};
}

View file

@ -42,16 +42,16 @@ void bot::webhook_push::run(const bot::translated_guild_message &message, dpp::c
message_v = message_v.substr(1333 + pos);
}
else {
bot::svmatch match;
if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\.|\\?|\\!|\\。)\\s.*$"))) {
bot::regex::svmatch match;
if (bot::regex::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex::regex("^.*(\\.|\\?|\\!|\\。)\\s.*$"))) {
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
message_v = message_v.substr(1334 + match.position(1));
}
else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\,)\\s.*$"))) {
else if (bot::regex::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex::regex("^.*(\\,)\\s.*$"))) {
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
message_v = message_v.substr(1334 + match.position(1));
}
else if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*()\\s.*$"))) {
else if (bot::regex::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex::regex("^.*()\\s.*$"))) {
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
message_v = message_v.substr(1334 + match.position(1));
}

View file

@ -31,11 +31,11 @@
using namespace bot::database;
using namespace std::string_literals;
file::file(const std::filesystem::path &storage_path) : m_storagePath(storage_path)
file::file(const std::filesystem::path &storage_path, const log::log_message_callback &log_callback) : m_storagePath(storage_path), m_logCallback(log_callback)
{
std::cout << "[Launch] Checking storage directory..." << std::endl;
m_logCallback("Checking storage directory...", "Launch", false);
if (!std::filesystem::is_directory(storage_path)) {
std::cerr << "[Error] Storage directory " << storage_path << " can not be found" << std::endl;
m_logCallback("[Error] Storage directory " + storage_path.string() + " can not be found", "Error", true);
throw std::runtime_error("Storage directory can not be found");
}
@ -50,19 +50,19 @@ file::file(const std::filesystem::path &storage_path) : m_storagePath(storage_pa
const std::filesystem::path lock_file = storage_path / ".lock";
fd = open(lock_file.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd == -1) {
std::cerr << "[Error] Storage directory " << storage_path << " can not be locked" << std::endl;
m_logCallback("[Error] Storage directory " + storage_path.string() + " can not be locked", "Error", true);
throw std::system_error(errno, std::system_category());
}
if (fcntl(fd, F_SETLK, &lock) == -1) {
close(fd);
std::cerr << "[Error] Storage directory " << storage_path << " can not be locked" << std::endl;
m_logCallback("[Error] Storage directory " + storage_path.string() + " can not be locked", "Error", true);
throw std::system_error(errno, std::system_category());
}
#elif defined(_WIN32)
const std::filesystem::path lock_file = storage_path / ".lock";
fh = CreateFileW(lock_file.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
if (fh == INVALID_HANDLE_VALUE) {
std::cerr << "[Error] Storage directory " << storage_path << " can not be locked" << std::endl;
m_logCallback("[Error] Storage directory " + storage_path.string() + " can not be found", "Error", true);
throw std::system_error(GetLastError(), std::system_category());
}
#endif
@ -408,7 +408,7 @@ bool file::sync()
return true;
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return false;
@ -475,7 +475,7 @@ void file::cache_get_channel(dpp::snowflake channel_id, settings::channel &chann
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
}
@ -504,7 +504,7 @@ void file::cache_get_user(dpp::snowflake user_id, settings::user &user)
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
}
@ -534,7 +534,7 @@ void file::cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> &cha
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
}
@ -555,7 +555,7 @@ void file::list_guilds(std::vector<dpp::snowflake> &guilds)
guilds.push_back(guild_id);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
}
}
@ -580,7 +580,7 @@ void file::list_users(std::vector<dpp::snowflake> &users)
users.push_back(user_id);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
}
}
@ -627,7 +627,7 @@ void file::sync_cache()
}
}
else {
std::cerr << "[Error] Storage channel directory can not be created" << std::endl;
m_logCallback("Storage channel directory can not be created", "Error", true);
}
const std::filesystem::path guild_dir = m_storagePath / "guild";
@ -657,7 +657,7 @@ void file::sync_cache()
}
}
else {
std::cerr << "[Error] Storage guild directory can not be created" << std::endl;
m_logCallback("Storage guild directory can not be created", "Error", true);
}
const std::filesystem::path user_dir = m_storagePath / "user";
@ -687,6 +687,6 @@ void file::sync_cache()
}
}
else {
std::cerr << "[Error] Storage user directory can not be created" << std::endl;
m_logCallback("Storage user directory can not be created", "Error", true);
}
}

View file

@ -27,12 +27,13 @@
#include <windows.h>
#endif
#include "../../core/database.h"
#include "../../core/log.h"
namespace bot {
namespace database {
class file : public database {
public:
explicit file(const std::filesystem::path &storage_path);
explicit file(const std::filesystem::path &storage_path, const bot::log::log_message_callback &log_callback);
~file();
void add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const bot::settings::target &target) override;
void delete_channel(dpp::snowflake guild_id, dpp::snowflake channel_id) override;
@ -72,6 +73,7 @@ namespace bot {
std::vector<bot::settings::user> m_userCache;
std::mutex m_mutex;
std::filesystem::path m_storagePath;
bot::log::log_message_callback m_logCallback;
};
}
}

31
src/gui/main.cpp Normal file
View file

@ -0,0 +1,31 @@
/*****************************************************************************
* 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 <curl/curl.h>
#include <gtkmm/application.h>
#include "user_interface.h"
using namespace bot::gui;
int main(int argc, char *argv[])
{
CURLcode result = curl_global_init(CURL_GLOBAL_DEFAULT);
if (result != CURLE_OK)
return -1;
auto app = Gtk::Application::create("de.syping.dtranslatebot");
return app->make_window_and_run<user_interface>(argc, argv);
}

127
src/gui/user_interface.cpp Normal file
View file

@ -0,0 +1,127 @@
/*****************************************************************************
* 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 <gtkmm/alertdialog.h>
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/editable.h>
#include <gtkmm/label.h>
#include <gtkmm/passwordentry.h>
#include <gtkmm/textview.h>
#include <memory>
#include "../core/regex.h"
#include "user_interface.h"
using namespace bot::gui;
user_interface::user_interface()
{
set_title("dtranslatebot");
set_default_size(500, 0);
set_resizable(false);
auto vertical_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
vertical_box->set_margin(6);
vertical_box->set_spacing(6);
auto token_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
token_box->set_spacing(6);
vertical_box->append(*token_box);
auto token_label = Gtk::make_managed<Gtk::Label>("Discord Bot Token:");
token_box->append(*token_label);
m_token_entry = Gtk::make_managed<Gtk::PasswordEntry>();
m_token_entry->set_show_peek_icon(true);
m_token_entry->set_hexpand(true);
token_box->append(*m_token_entry);
auto log_textview = Gtk::make_managed<Gtk::TextView>();
log_textview->set_size_request(-1, 300);
log_textview->set_editable(false);
log_textview->set_monospace(true);
log_textview->set_wrap_mode(Gtk::WrapMode::WORD_CHAR);
m_log = log_textview->get_buffer();
vertical_box->append(*log_textview);
auto button_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
button_box->set_spacing(6);
button_box->set_halign(Gtk::Align::CENTER);
vertical_box->append(*button_box);
m_start_button = Gtk::make_managed<Gtk::Button>("Start");
m_start_button->set_size_request(80, -1);
m_start_button->set_sensitive(false);
m_start_button->signal_clicked().connect(sigc::mem_fun(*this, &user_interface::run));
button_box->append(*m_start_button);
m_stop_button = Gtk::make_managed<Gtk::Button>("Stop");
m_stop_button->set_size_request(80, -1);
m_stop_button->set_sensitive(false);
m_stop_button->signal_clicked().connect(sigc::mem_fun(*this, &user_interface::terminate));
button_box->append(*m_stop_button);
m_token_entry->signal_changed().connect([=]{
bool token_valid = bot::regex::verify_discord_bot_token(m_token_entry->get_text());
m_start_button->set_sensitive(!m_bot.is_running() ? token_valid : false);
});
set_child(*vertical_box);
m_log_callback = std::bind(&user_interface::log_append, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
m_bot.log_callback_add(m_log_callback);
m_log_dispatcher.connect([=](){
const std::lock_guard<std::mutex> guard(m_log_buffer_mutex);
m_log->insert(m_log->end(), m_log->begin() != m_log->end() ? m_log_buffer : m_log_buffer.substr(1));
m_log_buffer.clear();
});
}
void user_interface::log_append(const std::string &message, const std::string &type, bool is_error) {
const std::lock_guard<std::mutex> guard(m_log_buffer_mutex);
m_log_buffer.append("\n[" + type + "] " + message);
m_log_dispatcher.emit();
}
void user_interface::run() {
auto settings = std::make_shared<bot::settings::settings>();
if (settings->process({{"token", m_token_entry->get_text()}, {"translator", {{"type", "stub"}}}}, m_log_callback)) {
try {
m_bot.run(settings, true, false);
m_start_button->set_sensitive(false);
m_stop_button->set_sensitive(true);
}
catch (const std::exception &exception) {
auto alert_dialog = Gtk::AlertDialog::create(exception.what());
alert_dialog->set_modal(true);
alert_dialog->show(*this);
}
}
else {
auto alert_dialog = Gtk::AlertDialog::create("Failed to process settings");
alert_dialog->set_modal(true);
alert_dialog->show(*this);
}
}
void user_interface::terminate() {
log_append("Stopping bot...", "Launch");
m_bot.terminate();
bool token_valid = bot::regex::verify_discord_bot_token(m_token_entry->get_text());
m_start_button->set_sensitive(token_valid);
m_stop_button->set_sensitive(false);
}

48
src/gui/user_interface.h Normal file
View file

@ -0,0 +1,48 @@
/*****************************************************************************
* 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 <glibmm/dispatcher.h>
#include <gtkmm/button.h>
#include <gtkmm/passwordentry.h>
#include <gtkmm/textbuffer.h>
#include <gtkmm/window.h>
#include <mutex>
#include "../core/discord_bot.h"
namespace bot {
namespace gui {
class user_interface : public Gtk::Window {
public:
explicit user_interface();
void log_append(const std::string &message, const std::string &type = "Log", bool is_error = false);
void run();
void terminate();
private:
bot::discord_bot m_bot;
Glib::RefPtr<Gtk::TextBuffer> m_log;
Gtk::Button* m_start_button;
Gtk::Button* m_stop_button;
Gtk::PasswordEntry* m_token_entry;
std::string m_log_buffer;
Glib::Dispatcher m_log_dispatcher;
std::mutex m_log_buffer_mutex;
bot::log::log_message_callback m_log_callback;
};
}
}

View file

@ -17,14 +17,13 @@
*****************************************************************************/
#include <dpp/json.h>
#include <iostream>
#include "deepl.h"
using namespace bot::http;
using namespace bot::translator;
using namespace std::chrono_literals;
deepl::deepl(const std::string &hostname, const std::string apiKey) :
m_hostname(hostname), m_apiKey(apiKey)
deepl::deepl(const std::string &hostname, const std::string &api_key, const bot::log::log_message_callback &log_callback) :
m_hostname(hostname), m_apiKey(api_key), m_logCallback(log_callback)
{
}
@ -97,7 +96,7 @@ const std::vector<language> deepl::get_languages()
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return m_languages.languages;
@ -130,7 +129,7 @@ const std::string deepl::translate(const std::string &text, const std::string &s
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return text;

View file

@ -20,13 +20,14 @@
#define TRANSLATOR_DEEPL_H
#include "../../core/http_request.h"
#include "../../core/log.h"
#include "../../core/translator.h"
namespace bot {
namespace translator {
class deepl : public translator {
public:
explicit deepl(const std::string &hostname, const std::string apiKey = {});
explicit deepl(const std::string &hostname, const std::string &api_key, const bot::log::log_message_callback &log_callback);
~deepl() override;
const std::vector<language> get_languages() override;
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
@ -36,6 +37,7 @@ namespace bot {
std::string m_hostname;
bot::http::http_request m_http;
supported_languages m_languages;
bot::log::log_message_callback m_logCallback;
};
}
}

View file

@ -17,14 +17,13 @@
*****************************************************************************/
#include <dpp/json.h>
#include <iostream>
#include "libretranslate.h"
using namespace bot::http;
using namespace bot::translator;
using namespace std::chrono_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)
libretranslate::libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &api_key, const bot::log::log_message_callback &log_callback) :
m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_apiKey(api_key), m_logCallback(log_callback)
{
}
@ -68,7 +67,7 @@ const std::vector<language> libretranslate::get_languages()
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return m_languages.languages;
@ -99,7 +98,7 @@ const std::string libretranslate::translate(const std::string &text, const std::
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return text;

View file

@ -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 <cstdint>
#include "../../core/http_request.h"
#include "../../core/log.h"
#include "../../core/translator.h"
namespace bot {
@ -28,7 +29,7 @@ namespace bot {
class libretranslate : public translator {
public:
explicit libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string apiKey = {});
explicit libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &api_key, const bot::log::log_message_callback &log_callback);
~libretranslate() override;
const std::vector<language> get_languages() override;
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
@ -41,6 +42,7 @@ namespace bot {
uint16_t m_port;
std::string m_url;
bool m_tls;
bot::log::log_message_callback m_logCallback;
};
}
}

View file

@ -18,14 +18,13 @@
#include <dpp/json.h>
#include <dpp/utility.h>
#include <iostream>
#include "lingvatranslate.h"
using namespace bot::http;
using namespace bot::translator;
using namespace std::chrono_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)
lingvatranslate::lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const bot::log::log_message_callback &log_callback) :
m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_logCallback(log_callback)
{
}
@ -72,7 +71,7 @@ const std::vector<language> lingvatranslate::get_languages()
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return m_languages.languages;
@ -92,7 +91,7 @@ const std::string lingvatranslate::translate(const std::string &text, const std:
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return text;

View file

@ -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:
@ -21,6 +21,7 @@
#include <cstdint>
#include "../../core/http_request.h"
#include "../../core/log.h"
#include "../../core/translator.h"
namespace bot {
@ -28,7 +29,7 @@ namespace bot {
class lingvatranslate : public translator {
public:
explicit lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls);
explicit lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const bot::log::log_message_callback &log_callback);
~lingvatranslate() override;
const std::vector<language> get_languages() override;
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
@ -40,6 +41,7 @@ namespace bot {
uint16_t m_port;
std::string m_url;
bool m_tls;
bot::log::log_message_callback m_logCallback;
};
}
}

View file

@ -18,14 +18,13 @@
#include <dpp/json.h>
#include <dpp/utility.h>
#include <iostream>
#include "mozhi.h"
using namespace bot::http;
using namespace bot::translator;
using namespace std::chrono_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)
mozhi::mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine, const bot::log::log_message_callback &log_callback) :
m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_engine(engine), m_logCallback(log_callback)
{
}
@ -72,7 +71,7 @@ const std::vector<language> mozhi::get_languages()
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return m_languages.languages;
@ -98,7 +97,7 @@ const std::string mozhi::translate(const std::string &text, const std::string &s
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
m_logCallback(exception.what(), "Exception", true);
}
return text;

View file

@ -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:
@ -21,6 +21,7 @@
#include <cstdint>
#include "../../core/http_request.h"
#include "../../core/log.h"
#include "../../core/translator.h"
namespace bot {
@ -28,7 +29,7 @@ namespace bot {
class mozhi : public translator {
public:
explicit mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine);
explicit mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine, const bot::log::log_message_callback &log_callback);
~mozhi() override;
const std::vector<language> get_languages() override;
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
@ -41,6 +42,7 @@ namespace bot {
uint16_t m_port;
std::string m_url;
bool m_tls;
bot::log::log_message_callback m_logCallback;
};
}
}