mirror of
https://github.com/Syping/dtranslatebot.git
synced 2026-04-21 14:40:41 +02:00
dtranslatebot: add GTK gui and port messages to log callback system
This commit is contained in:
parent
dfabab2315
commit
84b01d9ae1
27 changed files with 620 additions and 176 deletions
|
|
@ -25,9 +25,11 @@ include(GNUInstallDirs)
|
||||||
set(DTRANSLATEBOT_HEADERS
|
set(DTRANSLATEBOT_HEADERS
|
||||||
src/core/curl_exception.h
|
src/core/curl_exception.h
|
||||||
src/core/database.h
|
src/core/database.h
|
||||||
|
src/core/discord_bot.h
|
||||||
src/core/http_headers.h
|
src/core/http_headers.h
|
||||||
src/core/http_request.h
|
src/core/http_request.h
|
||||||
src/core/http_response.h
|
src/core/http_response.h
|
||||||
|
src/core/log.h
|
||||||
src/core/message_queue.h
|
src/core/message_queue.h
|
||||||
src/core/regex.h
|
src/core/regex.h
|
||||||
src/core/settings.h
|
src/core/settings.h
|
||||||
|
|
@ -44,9 +46,9 @@ set(DTRANSLATEBOT_HEADERS
|
||||||
src/translator/stub/stub.h
|
src/translator/stub/stub.h
|
||||||
)
|
)
|
||||||
set(DTRANSLATEBOT_SOURCES
|
set(DTRANSLATEBOT_SOURCES
|
||||||
src/cli/main.cpp
|
|
||||||
src/core/curl_exception.cpp
|
src/core/curl_exception.cpp
|
||||||
src/core/database.cpp
|
src/core/database.cpp
|
||||||
|
src/core/discord_bot.cpp
|
||||||
src/core/http_headers.cpp
|
src/core/http_headers.cpp
|
||||||
src/core/http_request.cpp
|
src/core/http_request.cpp
|
||||||
src/core/message_queue.cpp
|
src/core/message_queue.cpp
|
||||||
|
|
@ -69,14 +71,12 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
# Boost C++ Libraries
|
# Boost C++ Libraries
|
||||||
option(WITH_BOOST "Build with Boost C++ Libraries" OFF)
|
option(WITH_BOOST "Build with Boost C++ Libraries" OFF)
|
||||||
if (WITH_BOOST)
|
if (WITH_BOOST)
|
||||||
find_package(Boost COMPONENTS regex)
|
find_package(Boost COMPONENTS regex REQUIRED)
|
||||||
if (Boost_regex_FOUND)
|
|
||||||
list(APPEND DTRANSLATEBOT_LIBRARIES
|
list(APPEND DTRANSLATEBOT_LIBRARIES
|
||||||
Boost::regex
|
Boost::regex
|
||||||
)
|
)
|
||||||
set(DTRANSLATEBOT_USE_BOOST_REGEX TRUE)
|
set(DTRANSLATEBOT_USE_BOOST_REGEX TRUE)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
# curl Library
|
# curl Library
|
||||||
find_package(CURL REQUIRED)
|
find_package(CURL REQUIRED)
|
||||||
|
|
@ -89,6 +89,23 @@ else()
|
||||||
find_package(DPP REQUIRED)
|
find_package(DPP REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
option(WITH_GUI "Build with dtranslatebot GUI" OFF)
|
||||||
|
if (WITH_GUI)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
|
||||||
|
list(APPEND DTRANSLATEBOT_HEADERS
|
||||||
|
src/gui/user_interface.h
|
||||||
|
)
|
||||||
|
list(APPEND DTRANSLATEBOT_SOURCES
|
||||||
|
src/gui/main.cpp
|
||||||
|
src/gui/user_interface.cpp
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
list(APPEND DTRANSLATEBOT_SOURCES
|
||||||
|
src/cli/main.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# pthread Support
|
# pthread Support
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|
@ -129,13 +146,27 @@ if (WITH_DPP_STATIC_BUNDLE)
|
||||||
endif()
|
endif()
|
||||||
target_compile_definitions(dtranslatebot PRIVATE
|
target_compile_definitions(dtranslatebot PRIVATE
|
||||||
${DPP_DEFINITIONS}
|
${DPP_DEFINITIONS}
|
||||||
|
$<$<BOOL:${WITH_GUI}>:DTRANSLATEBOT_GUI>
|
||||||
$<$<BOOL:${DTRANSLATEBOT_USE_BOOST_REGEX}>:DTRANSLATEBOT_USE_BOOST_REGEX>
|
$<$<BOOL:${DTRANSLATEBOT_USE_BOOST_REGEX}>:DTRANSLATEBOT_USE_BOOST_REGEX>
|
||||||
)
|
)
|
||||||
if (MSVC AND MSVC_VERSION GREATER_EQUAL 1914)
|
target_compile_options(dtranslatebot PRIVATE
|
||||||
target_compile_options(dtranslatebot PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>)
|
$<$<AND:$<CXX_COMPILER_ID:MSVC>,$<VERSION_GREATER_EQUAL:$<CXX_COMPILER_VERSION>,19.14>,$<COMPILE_LANGUAGE:CXX>>:/Zc:__cplusplus>
|
||||||
endif()
|
$<$<BOOL:${WITH_GUI}>:${GTKMM_CFLAGS}>
|
||||||
target_link_libraries(dtranslatebot PRIVATE ${DTRANSLATEBOT_LIBRARIES} ${DPP_LIBRARIES} CURL::libcurl Threads::Threads)
|
)
|
||||||
target_include_directories(dtranslatebot PRIVATE ${DPP_INCLUDE_DIR})
|
target_link_libraries(dtranslatebot PRIVATE
|
||||||
|
${DTRANSLATEBOT_LIBRARIES}
|
||||||
|
${DPP_LIBRARIES}
|
||||||
|
CURL::libcurl
|
||||||
|
$<$<BOOL:${WITH_GUI}>:${GTKMM_LIBRARIES}>
|
||||||
|
Threads::Threads
|
||||||
|
)
|
||||||
|
target_link_directories(dtranslatebot PRIVATE
|
||||||
|
$<$<BOOL:${WITH_GUI}>:${GTKMM_LIBRARY_DIRS}>
|
||||||
|
)
|
||||||
|
target_include_directories(dtranslatebot PRIVATE
|
||||||
|
${DPP_INCLUDE_DIR}
|
||||||
|
$<$<BOOL:${WITH_GUI}>:${GTKMM_INCLUDE_DIRS}>
|
||||||
|
)
|
||||||
set_target_properties(dtranslatebot PROPERTIES
|
set_target_properties(dtranslatebot PROPERTIES
|
||||||
CXX_STANDARD 17
|
CXX_STANDARD 17
|
||||||
CXX_STANDARD_REQUIRED ON
|
CXX_STANDARD_REQUIRED ON
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,16 @@
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <dpp/cluster.h>
|
|
||||||
#include <dpp/once.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "../core/message_queue.h"
|
#include "../core/discord_bot.h"
|
||||||
#include "../core/settings.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[]) {
|
int main(int argc, char* argv[]) {
|
||||||
bool flag_wait_for_translator = false;
|
bool flag_wait_for_translator = false;
|
||||||
|
|
@ -41,61 +42,27 @@ int main(int argc, char* argv[]) {
|
||||||
return 0;
|
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);
|
CURLcode result = curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
if (result != CURLE_OK) {
|
if (result != CURLE_OK) {
|
||||||
std::cerr << "[Error] Failed to initialise curl" << std::endl;
|
std::cerr << "[Error] Failed to initialise curl" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
std::cout << "[Launch] Processing configuration..." << std::endl;
|
||||||
std::cout << "[Launch] Requesting supported languages..." << std::endl;
|
auto settings = std::make_shared<bot::settings::settings>();
|
||||||
if (!settings.get_translator()->get_languages().empty()) {
|
if (!settings->parse_file(args.at(0), &output_log))
|
||||||
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;
|
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();
|
curl_global_cleanup();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
91
src/core/discord_bot.cpp
Normal file
91
src/core/discord_bot.cpp
Normal 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
56
src/core/discord_bot.h
Normal 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
|
||||||
|
|
@ -27,7 +27,7 @@ namespace bot {
|
||||||
namespace http {
|
namespace http {
|
||||||
class http_request {
|
class http_request {
|
||||||
public:
|
public:
|
||||||
http_request();
|
explicit http_request();
|
||||||
http_request(const http_request&) = delete;
|
http_request(const http_request&) = delete;
|
||||||
http_request& operator=(const http_request&) = delete;
|
http_request& operator=(const http_request&) = delete;
|
||||||
~http_request();
|
~http_request();
|
||||||
|
|
|
||||||
31
src/core/log.h
Normal file
31
src/core/log.h
Normal 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
|
||||||
|
|
@ -27,16 +27,20 @@ void message_queue::add(const message &message)
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
m_queue.push(message);
|
m_queue.push(message);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const message_queue_size_callback &callback : m_callbacks)
|
for (const message_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
callback(m_queue.size());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void message_queue::add(message &&message)
|
void message_queue::add(message &&message)
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
m_queue.push(message);
|
m_queue.push(message);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const message_queue_size_callback &callback : m_callbacks)
|
for (const message_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
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)
|
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()) {
|
if (!m_queue.empty()) {
|
||||||
const message message = m_queue.front();
|
const message message = m_queue.front();
|
||||||
m_queue.pop();
|
m_queue.pop();
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const message_queue_size_callback &callback : m_callbacks)
|
for (const message_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
callback(m_queue.size());
|
||||||
|
#endif
|
||||||
m_mutex.unlock();
|
m_mutex.unlock();
|
||||||
|
|
||||||
auto translator = settings->get_translator();
|
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::this_thread::sleep_for(100ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::queue<message>().swap(m_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
size_t message_queue::size() {
|
size_t message_queue::size() {
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
return m_queue.size();
|
return m_queue.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void message_queue::size_callback_add(const message_queue_size_callback &callback) {
|
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);
|
m_callbacks.push_back(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
void message_queue::size_callback_remove(const message_queue_size_callback &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));
|
m_callbacks.erase(std::remove(m_callbacks.begin(), m_callbacks.end(), callback));
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
#endif
|
||||||
|
|
||||||
void message_queue::terminate()
|
void message_queue::terminate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@
|
||||||
#ifndef MESSAGE_QUEUE_H
|
#ifndef MESSAGE_QUEUE_H
|
||||||
#define MESSAGE_QUEUE_H
|
#define MESSAGE_QUEUE_H
|
||||||
#include <dpp/cluster.h>
|
#include <dpp/cluster.h>
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#endif
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -43,25 +45,34 @@ namespace bot {
|
||||||
std::vector<bot::settings::target> targets;
|
std::vector<bot::settings::target> targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
typedef std::function<void(size_t)> message_queue_size_callback;
|
typedef std::function<void(size_t)> message_queue_size_callback;
|
||||||
|
#endif
|
||||||
typedef std::variant<direct_message, guild_message> message;
|
typedef std::variant<direct_message, guild_message> message;
|
||||||
|
|
||||||
class message_queue {
|
class message_queue {
|
||||||
public:
|
public:
|
||||||
|
message_queue() = default;
|
||||||
|
message_queue(const message_queue&) = delete;
|
||||||
|
message_queue& operator=(const message_queue&) = delete;
|
||||||
void add(const message &message);
|
void add(const message &message);
|
||||||
void add(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_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 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 run(bot::settings::settings *settings, submit_queue *submit_queue);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
size_t size();
|
size_t size();
|
||||||
void size_callback_add(const message_queue_size_callback &callback);
|
void size_callback_add(const message_queue_size_callback &callback);
|
||||||
|
#endif
|
||||||
void terminate();
|
void terminate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_running;
|
bool m_running;
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
std::queue<message> m_queue;
|
std::queue<message> m_queue;
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
std::vector<message_queue_size_callback> m_callbacks;
|
std::vector<message_queue_size_callback> m_callbacks;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* dtranslatebot Discord Translate Bot
|
* 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,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
|
|
@ -24,20 +24,30 @@
|
||||||
#else
|
#else
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#endif
|
#endif
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#define DTRANSLATEBOT_DISCORD_BOT_TOKEN_REGEX "^([\\w-]{24,})\\.([\\w-]{6,})\\.([\\w-]{27,})$"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
|
namespace regex {
|
||||||
#ifdef DTRANSLATEBOT_USE_BOOST_REGEX
|
#ifdef DTRANSLATEBOT_USE_BOOST_REGEX
|
||||||
using boost::regex;
|
using boost::regex;
|
||||||
using boost::regex_match;
|
using boost::regex_match;
|
||||||
using boost::match_results;
|
using boost::match_results;
|
||||||
|
typedef boost::match_results<std::string::const_iterator> smatch;
|
||||||
typedef boost::match_results<std::string_view::const_iterator> svmatch;
|
typedef boost::match_results<std::string_view::const_iterator> svmatch;
|
||||||
#else
|
#else
|
||||||
using std::regex;
|
using std::regex;
|
||||||
using std::regex_match;
|
using std::regex_match;
|
||||||
using std::match_results;
|
using std::match_results;
|
||||||
|
typedef std::match_results<std::string::const_iterator> smatch;
|
||||||
typedef std::match_results<std::string_view::const_iterator> svmatch;
|
typedef std::match_results<std::string_view::const_iterator> svmatch;
|
||||||
#endif
|
#endif
|
||||||
|
inline bool verify_discord_bot_token(const std::string &token) {
|
||||||
|
return regex_match(token, regex(DTRANSLATEBOT_DISCORD_BOT_TOKEN_REGEX));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // REGEX_H
|
#endif // REGEX_H
|
||||||
|
|
|
||||||
|
|
@ -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)
|
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();
|
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++) {
|
for (auto db_guild_id = db_guilds.begin(); db_guild_id != db_guilds.end(); db_guild_id++) {
|
||||||
bool guild_found = false;
|
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++) {
|
for (auto json_preferred_lang = json.begin(); json_preferred_lang != json.end(); json_preferred_lang++) {
|
||||||
if (std::distance(json.begin(), json_preferred_lang) >= 25) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
preferred_langs->push_back(*json_preferred_lang);
|
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");
|
auto json_hostname = json.find("hostname");
|
||||||
if (json_hostname != json.end())
|
if (json_hostname != json.end())
|
||||||
|
|
@ -237,7 +236,7 @@ bool process_server(const dpp::json &json, translator &translator)
|
||||||
|
|
||||||
auto json_url = json.find("url");
|
auto json_url = json.find("url");
|
||||||
if (json_url == json.end()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (translator.hostname.empty())
|
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()) {
|
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;
|
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");
|
auto json_deepl_apiKey = json.find("apiKey");
|
||||||
if (json_deepl_apiKey == json.end()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
translator.apiKey = *json_deepl_apiKey;
|
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") {
|
else if (translator.type == "mozhi") {
|
||||||
if (!process_server(json, translator))
|
if (!process_server(json, translator, log_callback))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
std::string mozhi_engine;
|
std::string mozhi_engine;
|
||||||
|
|
@ -308,25 +307,25 @@ bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::tra
|
||||||
else
|
else
|
||||||
mozhi_engine = "google";
|
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") {
|
else if (translator.type == "libretranslate") {
|
||||||
if (!process_server(json, translator))
|
if (!process_server(json, translator, log_callback))
|
||||||
return false;
|
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") {
|
else if (translator.type == "lingvatranslate") {
|
||||||
if (!process_server(json, translator))
|
if (!process_server(json, translator, log_callback))
|
||||||
return false;
|
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") {
|
else if (translator.type == "stub") {
|
||||||
translator_instance = std::make_shared<bot::translator::stub>();
|
translator_instance = std::make_shared<bot::translator::stub>();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cerr << "[Error] Translator " << translator.type << " is unknown" << std::endl;
|
log_callback("Translator " + translator.type + " is unknown", "Error", true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,19 +558,36 @@ void settings::lock()
|
||||||
m_externallyLockedCount++;
|
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)
|
||||||
{
|
{
|
||||||
try {
|
|
||||||
dpp::json json;
|
dpp::json json;
|
||||||
try {
|
try {
|
||||||
json = dpp::json::parse(data, nullptr, true, true);
|
json = dpp::json::parse(data, nullptr, true, true);
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
log_callback(exception.what(), "Exception", true);
|
||||||
std::cerr << "[Error] Exception while parsing JSON" << std::endl;
|
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;
|
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);
|
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
|
||||||
auto json_token = json.find("token");
|
auto json_token = json.find("token");
|
||||||
if (json_token != json.end())
|
if (json_token != json.end())
|
||||||
|
|
@ -580,7 +596,7 @@ bool settings::parse(const std::string &data, bool initialize)
|
||||||
m_token = token;
|
m_token = token;
|
||||||
|
|
||||||
if (m_token.empty()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -594,14 +610,14 @@ bool settings::parse(const std::string &data, bool initialize)
|
||||||
if (storage_path.empty())
|
if (storage_path.empty())
|
||||||
storage_path = std::filesystem::current_path();
|
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");
|
auto json_translator = json.find("translator");
|
||||||
if (json_translator == json.end()) {
|
if (json_translator == json.end()) {
|
||||||
std::cerr << "[Error] Value translator not found" << std::endl;
|
log_callback("Value translator not found", "Error", true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!process_translator_settings(*json_translator, m_translator))
|
if (!process_translator_settings(*json_translator, m_translator, log_callback))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
auto json_guilds = json.find("guilds");
|
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");
|
auto json_preflangs = json.find("preferred_lang");
|
||||||
if (json_preflangs != json.end() && json_preflangs->is_array())
|
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");
|
auto json_user = json.find("user");
|
||||||
if (json_user != json.end() && json_user->is_object())
|
if (json_user != json.end() && json_user->is_object())
|
||||||
process_user_settings(*json_user, m_avatarSize);
|
process_user_settings(*json_user, m_avatarSize);
|
||||||
|
|
||||||
|
log_callback("Loading database...", "Launch", false);
|
||||||
process_database(m_database, m_guilds, m_users, m_webhookIds);
|
process_database(m_database, m_guilds, m_users, m_webhookIds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
log_callback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
return false;
|
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()
|
void settings::unlock()
|
||||||
{
|
{
|
||||||
m_mutex.unlock();
|
m_mutex.unlock();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* dtranslatebot Discord Translate Bot
|
* 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,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#define SETTINGS_H
|
#define SETTINGS_H
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
|
#include "log.h"
|
||||||
#include "settings_types.h"
|
#include "settings_types.h"
|
||||||
#include "translator.h"
|
#include "translator.h"
|
||||||
|
|
||||||
|
|
@ -62,8 +63,11 @@ namespace bot {
|
||||||
void unlock();
|
void unlock();
|
||||||
|
|
||||||
/* parse functions */
|
/* parse functions */
|
||||||
bool parse(const std::string &data, 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, 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 */
|
/* prevent copies */
|
||||||
settings() = default;
|
settings() = default;
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,20 @@ void submit_queue::add(const translated_message &message)
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
m_queue.push(message);
|
m_queue.push(message);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const submit_queue_size_callback &callback : m_callbacks)
|
for (const submit_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
callback(m_queue.size());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void submit_queue::add(translated_message &&message)
|
void submit_queue::add(translated_message &&message)
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
m_queue.push(message);
|
m_queue.push(message);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const submit_queue_size_callback &callback : m_callbacks)
|
for (const submit_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
callback(m_queue.size());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void submit_queue::run(dpp::cluster *bot)
|
void submit_queue::run(dpp::cluster *bot)
|
||||||
|
|
@ -46,8 +50,10 @@ void submit_queue::run(dpp::cluster *bot)
|
||||||
if (!m_queue.empty()) {
|
if (!m_queue.empty()) {
|
||||||
const translated_message message = m_queue.front();
|
const translated_message message = m_queue.front();
|
||||||
m_queue.pop();
|
m_queue.pop();
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
for (const submit_queue_size_callback &callback : m_callbacks)
|
for (const submit_queue_size_callback &callback : m_callbacks)
|
||||||
callback(m_queue.size());
|
callback(m_queue.size());
|
||||||
|
#endif
|
||||||
m_mutex.unlock();
|
m_mutex.unlock();
|
||||||
|
|
||||||
if (const auto *direct_message = std::get_if<bot::translated_direct_message>(&message)) {
|
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::this_thread::sleep_for(100ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::queue<translated_message>().swap(m_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
size_t submit_queue::size() {
|
size_t submit_queue::size() {
|
||||||
const std::lock_guard<std::mutex> guard(m_mutex);
|
const std::lock_guard<std::mutex> guard(m_mutex);
|
||||||
return m_queue.size();
|
return m_queue.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void submit_queue::size_callback_add(const submit_queue_size_callback &callback) {
|
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);
|
m_callbacks.push_back(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
void submit_queue::size_callback_remove(const submit_queue_size_callback &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));
|
m_callbacks.erase(std::remove(m_callbacks.begin(), m_callbacks.end(), callback));
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
#endif
|
||||||
|
|
||||||
void submit_queue::terminate()
|
void submit_queue::terminate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,15 @@
|
||||||
#define SUBMIT_QUEUE_H
|
#define SUBMIT_QUEUE_H
|
||||||
#include <dpp/cluster.h>
|
#include <dpp/cluster.h>
|
||||||
#include <dpp/webhook.h>
|
#include <dpp/webhook.h>
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
|
#include <functional>
|
||||||
|
#endif
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
struct translated_direct_message {
|
struct translated_direct_message {
|
||||||
|
|
@ -38,23 +43,32 @@ namespace bot {
|
||||||
dpp::webhook webhook;
|
dpp::webhook webhook;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
typedef std::function<void(size_t)> submit_queue_size_callback;
|
typedef std::function<void(size_t)> submit_queue_size_callback;
|
||||||
|
#endif
|
||||||
typedef std::variant<translated_direct_message, translated_guild_message> translated_message;
|
typedef std::variant<translated_direct_message, translated_guild_message> translated_message;
|
||||||
|
|
||||||
class submit_queue {
|
class submit_queue {
|
||||||
public:
|
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(const translated_message &message);
|
||||||
void add(translated_message &&message);
|
void add(translated_message &&message);
|
||||||
void run(dpp::cluster *bot);
|
void run(dpp::cluster *bot);
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
size_t size();
|
size_t size();
|
||||||
void size_callback_add(const submit_queue_size_callback &callback);
|
void size_callback_add(const submit_queue_size_callback &callback);
|
||||||
|
#endif
|
||||||
void terminate();
|
void terminate();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_running;
|
bool m_running;
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
std::queue<translated_message> m_queue;
|
std::queue<translated_message> m_queue;
|
||||||
|
#ifdef DTRANSLATEBOT_GUI
|
||||||
std::vector<submit_queue_size_callback> m_callbacks;
|
std::vector<submit_queue_size_callback> m_callbacks;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,16 +42,16 @@ void bot::webhook_push::run(const bot::translated_guild_message &message, dpp::c
|
||||||
message_v = message_v.substr(1333 + pos);
|
message_v = message_v.substr(1333 + pos);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bot::svmatch match;
|
bot::regex::svmatch match;
|
||||||
if (bot::regex_match(message_eov.begin(), message_eov.end(), match, bot::regex("^.*(\\.|\\?|\\!|\\。)\\s.*$"))) {
|
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));
|
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
|
||||||
message_v = message_v.substr(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));
|
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
|
||||||
message_v = message_v.substr(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));
|
json_body["content"] = message_v.substr(0, 1334 + match.position(1));
|
||||||
message_v = message_v.substr(1334 + match.position(1));
|
message_v = message_v.substr(1334 + match.position(1));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@
|
||||||
using namespace bot::database;
|
using namespace bot::database;
|
||||||
using namespace std::string_literals;
|
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)) {
|
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");
|
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";
|
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);
|
fd = open(lock_file.c_str(), O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
||||||
if (fd == -1) {
|
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());
|
throw std::system_error(errno, std::system_category());
|
||||||
}
|
}
|
||||||
if (fcntl(fd, F_SETLK, &lock) == -1) {
|
if (fcntl(fd, F_SETLK, &lock) == -1) {
|
||||||
close(fd);
|
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());
|
throw std::system_error(errno, std::system_category());
|
||||||
}
|
}
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
const std::filesystem::path lock_file = storage_path / ".lock";
|
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);
|
fh = CreateFileW(lock_file.c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
|
||||||
if (fh == INVALID_HANDLE_VALUE) {
|
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());
|
throw std::system_error(GetLastError(), std::system_category());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -408,7 +408,7 @@ bool file::sync()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -475,7 +475,7 @@ void file::cache_get_channel(dpp::snowflake channel_id, settings::channel &chann
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
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) {
|
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) {
|
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);
|
guilds.push_back(guild_id);
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
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);
|
users.push_back(user_id);
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
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 {
|
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";
|
const std::filesystem::path guild_dir = m_storagePath / "guild";
|
||||||
|
|
@ -657,7 +657,7 @@ void file::sync_cache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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";
|
const std::filesystem::path user_dir = m_storagePath / "user";
|
||||||
|
|
@ -687,6 +687,6 @@ void file::sync_cache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
std::cerr << "[Error] Storage user directory can not be created" << std::endl;
|
m_logCallback("Storage user directory can not be created", "Error", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,13 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
#include "../../core/database.h"
|
#include "../../core/database.h"
|
||||||
|
#include "../../core/log.h"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
namespace database {
|
namespace database {
|
||||||
class file : public database {
|
class file : public database {
|
||||||
public:
|
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();
|
~file();
|
||||||
void add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const bot::settings::target &target) override;
|
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;
|
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::vector<bot::settings::user> m_userCache;
|
||||||
std::mutex m_mutex;
|
std::mutex m_mutex;
|
||||||
std::filesystem::path m_storagePath;
|
std::filesystem::path m_storagePath;
|
||||||
|
bot::log::log_message_callback m_logCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
src/gui/main.cpp
Normal file
31
src/gui/main.cpp
Normal 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
127
src/gui/user_interface.cpp
Normal 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
48
src/gui/user_interface.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,14 +17,13 @@
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#include <dpp/json.h>
|
#include <dpp/json.h>
|
||||||
#include <iostream>
|
|
||||||
#include "deepl.h"
|
#include "deepl.h"
|
||||||
using namespace bot::http;
|
using namespace bot::http;
|
||||||
using namespace bot::translator;
|
using namespace bot::translator;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
deepl::deepl(const std::string &hostname, const std::string 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(apiKey)
|
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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_languages.languages;
|
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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,14 @@
|
||||||
#define TRANSLATOR_DEEPL_H
|
#define TRANSLATOR_DEEPL_H
|
||||||
|
|
||||||
#include "../../core/http_request.h"
|
#include "../../core/http_request.h"
|
||||||
|
#include "../../core/log.h"
|
||||||
#include "../../core/translator.h"
|
#include "../../core/translator.h"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
namespace translator {
|
namespace translator {
|
||||||
class deepl : public translator {
|
class deepl : public translator {
|
||||||
public:
|
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;
|
~deepl() override;
|
||||||
const std::vector<language> get_languages() 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;
|
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;
|
std::string m_hostname;
|
||||||
bot::http::http_request m_http;
|
bot::http::http_request m_http;
|
||||||
supported_languages m_languages;
|
supported_languages m_languages;
|
||||||
|
bot::log::log_message_callback m_logCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,13 @@
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
#include <dpp/json.h>
|
#include <dpp/json.h>
|
||||||
#include <iostream>
|
|
||||||
#include "libretranslate.h"
|
#include "libretranslate.h"
|
||||||
using namespace bot::http;
|
using namespace bot::http;
|
||||||
using namespace bot::translator;
|
using namespace bot::translator;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
libretranslate::libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string 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(apiKey)
|
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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_languages.languages;
|
return m_languages.languages;
|
||||||
|
|
@ -99,7 +98,7 @@ const std::string libretranslate::translate(const std::string &text, const std::
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* dtranslatebot Discord Translate Bot
|
* 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,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "../../core/http_request.h"
|
#include "../../core/http_request.h"
|
||||||
|
#include "../../core/log.h"
|
||||||
#include "../../core/translator.h"
|
#include "../../core/translator.h"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
|
|
@ -28,7 +29,7 @@ namespace bot {
|
||||||
class libretranslate : public translator {
|
class libretranslate : public translator {
|
||||||
|
|
||||||
public:
|
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;
|
~libretranslate() override;
|
||||||
const std::vector<language> get_languages() 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;
|
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;
|
uint16_t m_port;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
bool m_tls;
|
bool m_tls;
|
||||||
|
bot::log::log_message_callback m_logCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,13 @@
|
||||||
|
|
||||||
#include <dpp/json.h>
|
#include <dpp/json.h>
|
||||||
#include <dpp/utility.h>
|
#include <dpp/utility.h>
|
||||||
#include <iostream>
|
|
||||||
#include "lingvatranslate.h"
|
#include "lingvatranslate.h"
|
||||||
using namespace bot::http;
|
using namespace bot::http;
|
||||||
using namespace bot::translator;
|
using namespace bot::translator;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
lingvatranslate::lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool 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_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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_languages.languages;
|
return m_languages.languages;
|
||||||
|
|
@ -92,7 +91,7 @@ const std::string lingvatranslate::translate(const std::string &text, const std:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &exception) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* dtranslatebot Discord Translate Bot
|
* 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,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "../../core/http_request.h"
|
#include "../../core/http_request.h"
|
||||||
|
#include "../../core/log.h"
|
||||||
#include "../../core/translator.h"
|
#include "../../core/translator.h"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
|
|
@ -28,7 +29,7 @@ namespace bot {
|
||||||
class lingvatranslate : public translator {
|
class lingvatranslate : public translator {
|
||||||
|
|
||||||
public:
|
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;
|
~lingvatranslate() override;
|
||||||
const std::vector<language> get_languages() 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;
|
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;
|
uint16_t m_port;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
bool m_tls;
|
bool m_tls;
|
||||||
|
bot::log::log_message_callback m_logCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,13 @@
|
||||||
|
|
||||||
#include <dpp/json.h>
|
#include <dpp/json.h>
|
||||||
#include <dpp/utility.h>
|
#include <dpp/utility.h>
|
||||||
#include <iostream>
|
|
||||||
#include "mozhi.h"
|
#include "mozhi.h"
|
||||||
using namespace bot::http;
|
using namespace bot::http;
|
||||||
using namespace bot::translator;
|
using namespace bot::translator;
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
mozhi::mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &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_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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_languages.languages;
|
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) {
|
catch (const std::exception &exception) {
|
||||||
std::cerr << "[Exception] " << exception.what() << std::endl;
|
m_logCallback(exception.what(), "Exception", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* dtranslatebot Discord Translate Bot
|
* 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,
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
* are permitted provided that the following conditions are met:
|
* are permitted provided that the following conditions are met:
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "../../core/http_request.h"
|
#include "../../core/http_request.h"
|
||||||
|
#include "../../core/log.h"
|
||||||
#include "../../core/translator.h"
|
#include "../../core/translator.h"
|
||||||
|
|
||||||
namespace bot {
|
namespace bot {
|
||||||
|
|
@ -28,7 +29,7 @@ namespace bot {
|
||||||
class mozhi : public translator {
|
class mozhi : public translator {
|
||||||
|
|
||||||
public:
|
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;
|
~mozhi() override;
|
||||||
const std::vector<language> get_languages() 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;
|
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;
|
uint16_t m_port;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
bool m_tls;
|
bool m_tls;
|
||||||
|
bot::log::log_message_callback m_logCallback;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue