1
0
Fork 0
mirror of https://github.com/Syping/dtranslatebot.git synced 2025-08-21 01:08:13 +02:00

Compare commits

...

53 commits
0.1 ... master

Author SHA1 Message Date
Syping
c76210a30c DPPStaticBundle: update DPP to 10.1.3 and OpenSSL to 3.0.17 2025-08-06 18:34:02 +02:00
Syping
b2da36e576 fix DPP 10.1.0 or newer build errors 2025-08-06 18:33:46 +02:00
Syping
13d384b14e DPPStaticBundle: OpenSSL build remove no-deprecated 2025-03-23 19:39:46 +01:00
Syping
2da43810a1 DPPStaticBundle: update DPP to 10.1.2 and OpenSSL to 3.0.16 2025-03-23 19:15:28 +01:00
Syping
b318df79e1 README.md: update supported translation engines 2024-12-30 16:16:40 +01:00
Syping
d6d22d34a8 remove unnecessary else condition 2024-12-30 16:09:12 +01:00
Syping
24a7b3bc99 reduce duplicate code 2024-12-30 16:06:51 +01:00
Syping
1dc2b2dffa add mozhi support 2024-12-29 16:05:13 +01:00
Syping
691e46a507 add lingvatranslate support 2024-12-29 14:26:05 +01:00
Syping
a42932de12 add --wait-for-translator flag 2024-11-28 16:41:34 +01:00
Syping
6d89aef67c DPPStaticBundle: update DPP to 10.0.35 2024-11-27 15:36:55 +01:00
Syping
500c43d4e6 DPPStaticBundle: update DPP to 10.0.31 and OpenSSL to 3.0.15 2024-10-02 23:36:02 +02:00
Syping
e94fbbf5ac DPPStaticBundle: update DPP to 10.0.30 and OpenSSL to 3.0.14 2024-06-11 04:18:07 +02:00
Syping
f98478bc4d little quality changes 2024-03-26 20:53:10 +01:00
Syping
6bcef831c9 rpmspec: revert add debug package 2024-03-25 11:20:36 +01:00
Syping
420f536c9b make configuration more understandable 2024-03-25 11:15:08 +01:00
Syping
4e71f417d0 rpmspec: add debug package 2024-03-25 10:56:49 +01:00
Syping
b1329aa961 cache available languages and make translator shared 2024-03-25 10:52:01 +01:00
Syping
0e369f5a1d add experimental deepl support 2024-03-23 16:57:54 +01:00
Syping
42755811aa CI: add openSUSE Leap 15.5 2024-03-22 02:37:29 +01:00
Syping
24c14efce2 rpmspec: add suse support 2024-03-20 21:43:08 +01:00
Syping
6764c9c2f3 rpmspec: defining PERL_EXECUTABLE is not needed anymore 2024-03-19 13:48:42 +01:00
Syping
9a5f37929a DPPStaticBundle: get perl executable with find_program 2024-03-17 05:22:22 +01:00
Syping
66a504ac55 systemd: escape working directory 2024-03-17 05:21:33 +01:00
Syping
7d8bbd12ce CI: add Enterprise Linux 2024-03-15 01:45:55 +01:00
Syping
9380dd14ea fix el7 and el9 build errors in RPM SPEC file 2024-03-14 03:30:45 +01:00
Syping
382f029c3b add basic config file to RPM 2024-03-12 10:15:51 +01:00
Syping
6a89203415 systemd-sysusers support and RPMSPEC added 2024-03-12 09:40:58 +01:00
Syping
6f6b5eaa69 fix add_dependencies for WITH_DPP_STATIC_BUNDLE 2024-03-12 06:36:24 +01:00
Syping
fb709b8919 add stub translator for testing purposes 2024-03-11 05:21:01 +01:00
Syping
94429ca718 code style changes and remove some constructors 2024-03-11 04:10:18 +01:00
Syping
de121beaac rename DPPAsExternalProject to DPPStaticBundle
- remove potential DPP AVX dependency
2024-03-11 03:45:48 +01:00
Syping
0dfb2cebe9 CI update and DPPAsExternalProject improvements 2024-03-11 03:24:01 +01:00
Syping
92d9aba8d0 erase empty guilds from memory 2024-03-08 16:56:38 +01:00
Syping
0dbf987ff2 add ci and simplify executable description 2024-03-08 01:25:50 +01:00
Syping
616ce0055b delete channel from settings when no target is left
- converted some pointers to references
- simplified some constructors
2024-03-07 23:30:33 +01:00
Syping
c8239e2517 CMake: fix zlibstatic on Linux issue 2024-03-06 16:42:53 +01:00
Syping
d0139afd26 CMake: fix MSYSTEM check CMake error 2024-03-06 16:30:27 +01:00
Syping
67b4b73400 CMake: fix CMAKE_SYSTEM_VERSION spelling and add CXX passthrough 2024-03-06 16:25:48 +01:00
Syping
6220277305 CMake: msys compatibility added/fixed 2024-03-06 16:20:47 +01:00
Syping
dd2de72f94 ArgumentPassthrough: avoid accidental Cross-Compiling + more fixes 2024-03-06 15:23:11 +01:00
Syping
024400be46 improved external project passthrough of variables 2024-03-05 21:02:05 +01:00
Syping
bb099d644a DPPAsExternalProject: call Perl directly for OpenSSL 2024-03-03 15:17:03 +01:00
Syping
c21ed31889 minor fixes for DPPAsExternalProject 2024-03-02 22:22:05 +01:00
Syping
820593ca2d DPPAsExternalProject: build should work now 2024-03-02 17:42:20 +01:00
Syping
4fe57d1a24 CMake: incomplete DPPAsExternalProject impl 2024-02-29 20:55:33 +01:00
Syping
716a03c43b systemd: change working directory to /var/lib/dtranslatebot 2024-02-27 10:02:32 +01:00
Syping
912ac1eced add systemd service file 2024-02-26 12:01:23 +01:00
Syping
87961fe611 improve code organisation, add .rc file 2024-02-26 11:33:39 +01:00
Syping
5191dcbefc add DTRANSLATEBOT_TOKEN env value, small fixes 2024-02-26 09:41:55 +01:00
Syping
531c24b050 slashcommands: add missing ephemeral flag 2024-02-24 09:15:32 +01:00
Syping
46b80f3bdf message_queue: combine if condition 2024-02-23 11:27:17 +01:00
Syping
ea2c6c460f add /list languages command, minor fixes 2024-02-23 07:14:08 +01:00
46 changed files with 1524 additions and 352 deletions

16
.gitattributes vendored Normal file
View file

@ -0,0 +1,16 @@
* text=auto eol=lf
# Configuration files
*.json text eol=crlf
# Development files
CMakeLists.txt text eol=lf
*.cmake text eol=lf
*.cpp text eol=lf
*.h text eol=lf
# BSD and Linux development files
*.pc.in text eol=lf
# Windows development files
*.rc.in text encoding=cp1252 eol=crlf

45
.github/workflows/linux-rpm.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Linux
on: push
jobs:
Release:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- name: Enterprise Linux 7
version: el7
- name: Enterprise Linux 8
version: el8
- name: Enterprise Linux 9
version: el9
- name: openSUSE Leap 15.5
version: lp155
steps:
- name: Cloning
uses: actions/checkout@v4
- name: Preparing
run: mkdir -m 777 ${{github.workspace}}/rpms
- name: Build RPM
uses: addnab/docker-run-action@v3
with:
image: docker.io/syping/dtranslatebot-build:${{matrix.version}}
options: -v ${{github.workspace}}:/home/rpmbuild/dtranslatebot -v ${{github.workspace}}/rpms:/home/rpmbuild/rpmbuild/RPMS
run: |
VERSION=$(cat dtranslatebot/CMakeLists.txt | grep -oP "project\(dtranslatebot VERSION \K(\S*)(?= LANGUAGES CXX\))")
mkdir -p dtranslatebot-$VERSION
shopt -s extglob
cp -R dtranslatebot/!(rpms|rpmsrc) \
dtranslatebot-$VERSION
tar cfz dtranslatebot-$VERSION.tar.gz dtranslatebot-$VERSION
cp dtranslatebot-$VERSION.tar.gz \
dtranslatebot/rpmsrc/!(*.spec) \
rpmbuild/SOURCES
cp dtranslatebot/rpmsrc/*.spec \
rpmbuild/SPECS
rpmbuild -ba rpmbuild/SPECS/dtranslatebot.spec
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{matrix.name}}
path: |
${{github.workspace}}/rpms/x86_64/*.rpm

43
.github/workflows/windows.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: Windows
on: push
jobs:
Release:
runs-on: windows-latest
env:
BUILD_TYPE: Release
defaults:
run:
shell: msys2 {0}
steps:
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: clang64
update: true
install: >-
git
make
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-cmake
mingw-w64-clang-x86_64-ninja
perl
- name: Cloning
uses: actions/checkout@v4
- name: Configure CMake
run: cmake -B dtranslatebot-build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_EXE_LINKER_FLAGS="-static -lc++" -DWITH_DPP_STATIC_BUNDLE=TRUE -GNinja
- name: Download and build OpenSSL
run: cmake --build dtranslatebot-build --config ${{env.BUILD_TYPE}} --target OpenSSL
- name: Download and build zlib
run: cmake --build dtranslatebot-build --config ${{env.BUILD_TYPE}} --target ZLIB
- name: Download and build DPP
run: cmake --build dtranslatebot-build --config ${{env.BUILD_TYPE}} --target DPP
- name: Build dtranslatebot
run: cmake --build dtranslatebot-build --config ${{env.BUILD_TYPE}}
- name: Install
run: cmake --install dtranslatebot-build --config ${{env.BUILD_TYPE}} --prefix dtranslatebot-install --strip
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Windows
path: |
dtranslatebot-install/bin/dtranslatebot.exe

View file

@ -17,34 +17,43 @@
****************************************************************************]]
cmake_minimum_required(VERSION 3.16)
project(dtranslatebot VERSION 0.1 LANGUAGES CXX)
cmake_policy(VERSION 3.16...3.27)
project(dtranslatebot VERSION 0.2.0 LANGUAGES CXX)
include(GNUInstallDirs)
# dtranslatebot Source files
set(DTRANSLATEBOT_HEADERS
src/database_core.h
src/database_file.h
src/message_queue.h
src/regex.h
src/settings.h
src/settings_types.h
src/slashcommands.h
src/submit_queue.h
src/translator_core.h
src/translator_libretranslate.h
src/webhook_push.h
src/core/database.h
src/core/message_queue.h
src/core/regex.h
src/core/settings.h
src/core/settings_types.h
src/core/slashcommands.h
src/core/submit_queue.h
src/core/translator.h
src/core/webhook_push.h
src/database/file/file.h
src/translator/deepl/deepl.h
src/translator/mozhi/mozhi.h
src/translator/libretranslate/libretranslate.h
src/translator/lingvatranslate/lingvatranslate.h
src/translator/stub/stub.h
)
set(DTRANSLATEBOT_SOURCES
src/database_core.cpp
src/database_file.cpp
src/main.cpp
src/message_queue.cpp
src/settings.cpp
src/slashcommands.cpp
src/submit_queue.cpp
src/translator_core.cpp
src/translator_libretranslate.cpp
src/webhook_push.cpp
src/core/database.cpp
src/core/main.cpp
src/core/message_queue.cpp
src/core/settings.cpp
src/core/slashcommands.cpp
src/core/submit_queue.cpp
src/core/translator.cpp
src/core/webhook_push.cpp
src/database/file/file.cpp
src/translator/deepl/deepl.cpp
src/translator/mozhi/mozhi.cpp
src/translator/libretranslate/libretranslate.cpp
src/translator/lingvatranslate/lingvatranslate.cpp
src/translator/stub/stub.cpp
)
# dtranslatebot Module Path
@ -63,21 +72,59 @@ if (WITH_BOOST)
endif()
# D++ Discord API Library for Bots
find_package(DPP REQUIRED)
option(WITH_DPP_STATIC_BUNDLE "Build with DPP Static Bundle" OFF)
if (WITH_DPP_STATIC_BUNDLE)
include(DPPStaticBundle)
else()
find_package(DPP REQUIRED)
endif()
# pthread Support
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# dtranslatebot Win32 Shared Resources
if (WIN32)
configure_file(src/resources/win32/dtranslatebot.rc.in "${dtranslatebot_BINARY_DIR}/resources/win32/dtranslatebot.rc" @ONLY)
list(APPEND DTRANSLATEBOT_RESOURCES
"${dtranslatebot_BINARY_DIR}/resources/win32/dtranslatebot.rc"
)
endif()
# dtranslatebot systemd Service
if (UNIX AND NOT APPLE)
option(WITH_SYSTEMD "Build with systemd Support" OFF)
if (WITH_SYSTEMD)
find_program(SYSTEMD_ESCAPE_EXECUTABLE NAMES systemd-escape)
if (DEFINED SYSTEMD_ESCAPE_EXECUTABLE)
execute_process(
COMMAND "${SYSTEMD_ESCAPE_EXECUTABLE}" "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/dtranslatebot"
OUTPUT_VARIABLE dtranslatebot_SERVICE_WORKDIR
)
string(STRIP "${dtranslatebot_SERVICE_WORKDIR}" dtranslatebot_SERVICE_WORKDIR)
else()
set(dtranslatebot_SERVICE_WORKDIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/dtranslatebot")
endif()
configure_file(src/systemd/dtranslatebot.service.in "${dtranslatebot_BINARY_DIR}/systemd/service/dtranslatebot.service" @ONLY)
configure_file(src/systemd/dtranslatebot.sysusersd.in "${dtranslatebot_BINARY_DIR}/systemd/sysusers.d/dtranslatebot.conf" @ONLY)
install(FILES "${dtranslatebot_BINARY_DIR}/systemd/service/dtranslatebot.service" DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/systemd/system")
install(FILES "${dtranslatebot_BINARY_DIR}/systemd/sysusers.d/dtranslatebot.conf" DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/sysusers.d")
endif()
endif()
# dtranslatebot Target + Installs
add_executable(dtranslatebot ${DTRANSLATEBOT_HEADERS} ${DTRANSLATEBOT_SOURCES})
add_executable(dtranslatebot ${DTRANSLATEBOT_HEADERS} ${DTRANSLATEBOT_SOURCES} ${DTRANSLATEBOT_RESOURCES})
if (WITH_DPP_STATIC_BUNDLE)
add_dependencies(dtranslatebot DPP)
endif()
target_compile_definitions(dtranslatebot PRIVATE
${DPP_DEFINITIONS}
$<$<BOOL:${DTRANSLATEBOT_USE_BOOST_REGEX}>:DTRANSLATEBOT_USE_BOOST_REGEX>
)
if (MSVC AND MSVC_VERSION GREATER_EQUAL 1914)
target_compile_options(dtranslatebot PRIVATE $<$<COMPILE_LANGUAGE:CXX>:/Zc:__cplusplus>)
endif()
target_link_libraries(dtranslatebot PRIVATE Threads::Threads ${DPP_LIBRARIES} ${DTRANSLATEBOT_LIBRARIES})
target_link_libraries(dtranslatebot PRIVATE ${DTRANSLATEBOT_LIBRARIES} ${DPP_LIBRARIES} Threads::Threads)
target_include_directories(dtranslatebot PRIVATE ${DPP_INCLUDE_DIR})
set_target_properties(dtranslatebot PROPERTIES
CXX_STANDARD 17

View file

@ -2,7 +2,7 @@
Open Source Discord Translation Bot
- Translate incoming channel messages to a Webhook
- Support configuration through slashcommands and JSON
- Support configuration through slash commands and JSON
- Cross-Platform
#### Build Dependencies
@ -10,8 +10,11 @@ Open Source Discord Translation Bot
- Compiler with C++17 Support
- [D++: A C++ Discord API Library for Bots](https://dpp.dev/)
#### Runtime Dependencies
- [LibreTranslate](https://libretranslate.com/)
#### Supported Translation Engines
- [LibreTranslate](https://libretranslate.com/) (Default)
- [Lingva Translate](https://lingva.ml/)
- [Mozhi](https://codeberg.org/aryak/mozhi)
- [DeepL](https://deepl.com/) (Experimental)
#### Build dtranslatebot
@ -23,4 +26,6 @@ sudo cmake --install dtranslatebot-build
```
##### Optional CMake flags
`-DWITH_BOOST=TRUE`
`-DWITH_BOOST=TRUE`
`-DWITH_DPP_STATIC_BUNDLE=TRUE`
`-DWITH_SYSTEMD=TRUE`

View file

@ -0,0 +1,65 @@
#[[**************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
****************************************************************************]]
if (DEFINED CMAKE_BUILD_TYPE)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif()
if (DEFINED CMAKE_C_COMPILER)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}")
list(APPEND CMAKE_PASSTHROUGH_ENV "CC=${CMAKE_C_COMPILER}")
elseif ("$ENV{MSYSTEM}" STREQUAL "CLANG64")
list(APPEND CMAKE_PASSTHROUGH_ENV "CC=clang")
endif()
if (DEFINED CMAKE_C_COMPILER_TARGET)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_C_COMPILER_TARGET=${CMAKE_C_COMPILER_TARGET}")
endif()
if (DEFINED CMAKE_CXX_COMPILER)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}")
list(APPEND CMAKE_PASSTHROUGH_ENV "CXX=${CMAKE_CXX_COMPILER}")
elseif ("$ENV{MSYSTEM}" STREQUAL "CLANG64")
list(APPEND CMAKE_PASSTHROUGH_ENV "CXX=clang++")
endif()
if (DEFINED CMAKE_CXX_COMPILER_TARGET)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_CXX_COMPILER_TARGET=${CMAKE_CXX_COMPILER_TARGET}")
endif()
if (DEFINED CMAKE_RC_COMPILER)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_RC_COMPILER=${CMAKE_RC_COMPILER}")
endif()
if (DEFINED CMAKE_STRIP)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_STRIP=${CMAKE_STRIP}")
endif()
if (DEFINED CMAKE_SYSROOT)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_SYSROOT=${CMAKE_SYSROOT}")
endif()
if (DEFINED CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}")
endif()
if (DEFINED CMAKE_SYSTEM_PROCESSOR AND NOT CMAKE_SYSTEM_PROCESSOR STREQUAL CMAKE_HOST_SYSTEM_PROCESSOR)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}")
endif()
if (DEFINED CMAKE_SYSTEM_VERSION AND NOT CMAKE_SYSTEM_VERSION STREQUAL CMAKE_HOST_SYSTEM_VERSION)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}")
endif()
if (DEFINED CMAKE_TOOLCHAIN_FILE)
list(APPEND CMAKE_PASSTHROUGH_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
endif()
if (DEFINED CMAKE_PASSTHROUGH_ENV)
set(CMAKE_PASSTHROUGH_ENV
"${CMAKE_COMMAND}" -E env ${CMAKE_PASSTHROUGH_ENV}
)
endif()

119
cmake/DPPStaticBundle.cmake Normal file
View file

@ -0,0 +1,119 @@
#[[**************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
****************************************************************************]]
include(ArgumentPassthrough)
# OpenSSL needs to be configured with perl and build with make
find_program(MAKE_EXECUTABLE NAMES make gmake)
if (NOT DEFINED MAKE_EXECUTABLE)
message(SEND_ERROR "make not found")
endif()
find_program(NPROC_EXECUTABLE nproc)
if (DEFINED NPROC_EXECUTABLE)
execute_process(
COMMAND "${NPROC_EXECUTABLE}"
OUTPUT_VARIABLE NPROC
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(MAKE_JOBS_ARG "-j${NPROC}")
endif()
find_program(PERL_EXECUTABLE NAMES perl)
if (NOT DEFINED PERL_EXECUTABLE)
message(SEND_ERROR "perl not found")
endif()
include(ExternalProject)
ExternalProject_Add(ZLIB
URL https://www.zlib.net/zlib-1.3.1.tar.xz
URL_HASH SHA256=38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32
CMAKE_ARGS
-DBUILD_SHARED_LIBS=OFF
${CMAKE_PASSTHROUGH_ARGS}
"-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>"
-DZLIB_BUILD_EXAMPLES=OFF
${ZLIB_CONFIGURE_ARGS}
)
ExternalProject_Get_Property(ZLIB INSTALL_DIR)
set(ZLIB_INSTALL_DIR "${INSTALL_DIR}")
set(OPENSSL_PLATFORM_ARG $<$<BOOL:$ENV{MSYSTEM}>:mingw64>)
ExternalProject_Add(OpenSSL
URL https://github.com/openssl/openssl/releases/download/openssl-3.0.17/openssl-3.0.17.tar.gz
URL_HASH SHA256=dfdd77e4ea1b57ff3a6dbde6b0bdc3f31db5ac99e7fdd4eaf9e1fbb6ec2db8ce
CONFIGURE_COMMAND
${CMAKE_PASSTHROUGH_ENV}
"${PERL_EXECUTABLE}"
"<SOURCE_DIR>/Configure"
"--prefix=<INSTALL_DIR>"
$<$<CONFIG:Debug>:-d>
no-dso
no-dtls
no-engine
no-shared
no-zlib
${OPENSSL_PLATFORM_ARG}
${OPENSSL_CONFIGURE_ARGS}
BUILD_COMMAND "${MAKE_EXECUTABLE}" ${MAKE_JOBS_ARG} build_libs
INSTALL_COMMAND "${MAKE_EXECUTABLE}" ${MAKE_JOBS_ARG} install_dev
)
ExternalProject_Get_Property(OpenSSL INSTALL_DIR)
set(OpenSSL_INSTALL_DIR "${INSTALL_DIR}")
ExternalProject_Add(DPP
URL https://github.com/brainboxdotcc/DPP/archive/refs/tags/v10.1.3.tar.gz
URL_HASH SHA256=a32d94dcd6b23430afff82918234e4e28e0616bd2ddf743c5ab2f1778c5a600b
CMAKE_ARGS
-DAVX_TYPE=AVX0
-DBUILD_SHARED_LIBS=OFF
-DBUILD_VOICE_SUPPORT=OFF
${CMAKE_PASSTHROUGH_ARGS}
"-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>"
-DDPP_BUILD_TEST=OFF
-DDPP_NO_CORO=ON
-DDPP_NO_VCPKG=ON
-DRUN_LDCONFIG=OFF
"-DOpenSSL_ROOT=${OpenSSL_INSTALL_DIR}"
"-DZLIB_ROOT=${ZLIB_INSTALL_DIR}"
${DPP_CONFIGURE_ARGS}
DEPENDS OpenSSL ZLIB
)
ExternalProject_Get_Property(DPP INSTALL_DIR)
set(DPP_INSTALL_DIR "${INSTALL_DIR}")
set(DPP_INCLUDE_DIR "${DPP_INSTALL_DIR}/include")
set(DPP_LIBRARIES
-Wl,-Bstatic
"-L${DPP_INSTALL_DIR}/lib"
"-L${DPP_INSTALL_DIR}/lib64"
dpp
"-L${OpenSSL_INSTALL_DIR}/lib"
"-L${OpenSSL_INSTALL_DIR}/lib64"
ssl
crypto
"-L${ZLIB_INSTALL_DIR}/lib"
"-L${ZLIB_INSTALL_DIR}/lib64"
$<IF:$<BOOL:${WIN32}>,zlibstatic,z>
-Wl,-Bdynamic
)
if (WIN32)
set(DPP_DEFINITIONS DPP_STATIC)
list(APPEND DPP_LIBRARIES
ws2_32
)
endif()

View file

@ -1,4 +1,4 @@
find_path(DPP_INCLUDE_DIR NAMES dpp/dpp.h HINTS ${DPP_ROOT_DIR})
find_library(DPP_LIBRARIES NAMES dpp "libdpp.a" HINTS ${DPP_ROOT_DIR})
find_path(DPP_INCLUDE_DIR NAMES "dpp/dpp.h" HINTS "${DPP_ROOT_DIR}")
find_library(DPP_LIBRARIES NAMES "dpp" "libdpp.a" HINTS "${DPP_ROOT_DIR}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(DPP DEFAULT_MSG DPP_LIBRARIES DPP_INCLUDE_DIR)

View file

@ -1,37 +0,0 @@
{
"guilds": {
"$guild1_id": {
"$channel1_id": {
"source": "en",
"target": "de",
"webhook": "https://discord.com/api/webhooks/$guild1_de_webhook_id/$guild1_de_webhook_token"
},
"$channel2_id": {
"source": "de",
"target": "en",
"webhook": "https://discord.com/api/webhooks/$guild1_en_webhook_id/$guild1_en_webhook_token"
}
},
"My Discord Guild": {
"id": $guild2_id,
"General English": {
"id": $channel3_id,
"source": "en",
"target": {
"de": "https://discord.com/api/webhooks/$guild2_de_webhook_id/$guild2_de_webhook_token",
"fr": "https://discord.com/api/webhooks/$guild2_fr_webhook_id/$guild2_fr_webhook_token"
}
}
}
},
"preferred_lang": ["en", "de", "fr", ...],
"storage": "$working_directory",
"user": {
"avatar_size": 256
},
"token": "$bot_token",
"translator": {
"url": "http://127.0.0.1:5000/",
"apiKey": ""
}
}

View file

@ -1,7 +0,0 @@
{
"token": "",
"translator": {
"url": "http://127.0.0.1:5000/",
"apiKey": ""
}
}

14
rpmsrc/dtranslatebot.json Normal file
View file

@ -0,0 +1,14 @@
{
/*
A DISCORD BOT TOKEN IS NECESSARY TO USE THIS APPLICATION!
You can get a Bot Token from the Discord Developer Portal
by creating a New Application:
https://discord.com/developers/applications
*/
"token": "",
"translator": {
"type": "stub"
}
}

View file

@ -0,0 +1,13 @@
[Unit]
Description=Discord Translation Bot
After=network.target
[Service]
User=dtranslatebot
Group=dtranslatebot
WorkingDirectory=/var/lib/dtranslatebot
ExecStart=/usr/bin/dtranslatebot /etc/dtranslatebot.json
Restart=on-abnormal
[Install]
WantedBy=multi-user.target

96
rpmsrc/dtranslatebot.spec Normal file
View file

@ -0,0 +1,96 @@
%global _lto_cflags %{?_lto_cflags} -ffat-lto-objects
%if 0%{?rhel} && 0%{?rhel} < 8
%global cmake %{?cmake3}
%global cmake_build %{?cmake3_build}
%global cmake_install %{?cmake3_install}
%global cmake_suffix 3
%global toolset_prefix devtoolset-9-
%endif
%if 0%{?rhel} && 0%{?rhel} == 8
%global toolset_prefix gcc-toolset-9-
%endif
%if 0%{?suse_version} && 0%{?suse_version} < 1600
%global toolset_version 9
%endif
Name: dtranslatebot
Version: 0.2.0
Release: 1%{?dist}
Summary: Discord Translation Bot
License: BSD-2-Clause
URL: https://github.com/Syping/%{name}
Source0: %{name}-%{version}.tar.gz
Source1: %{name}.json
Source2: %{name}.service
Source3: %{name}.sysusersd
%if 0%{?fedora} || 0%{?rhel}
BuildRequires: %{?toolset_prefix}annobin
%if 0%{?rhel} && 0%{?rhel} < 9
BuildRequires: epel-rpm-macros-systemd
%endif
%endif
BuildRequires: cmake%{?cmake_suffix}
BuildRequires: %{?toolset_prefix}gcc%{?toolset_version}-c++
BuildRequires: make
BuildRequires: perl(IPC::Cmd)
BuildRequires: systemd-rpm-macros
%systemd_requires
%if 0%{?fedora} || 0%{?rhel}
%sysusers_requires_compat
%endif
%description
dtranslatebot is a Discord Bot which translate incoming Discord messages to Discord webhooks.
%prep
%setup -q
%build
%if 0%{?rhel} && 0%{?rhel} < 8
source /opt/rh/devtoolset-9/enable
%endif
%if 0%{?rhel} && 0%{?rhel} == 8
source /opt/rh/gcc-toolset-9/enable
%endif
%cmake \
-DCMAKE_BUILD_TYPE=Release \
%if 0%{?toolset_version}
-DCMAKE_C_COMPILER=gcc-%{toolset_version} \
-DCMAKE_CXX_COMPILER=g++-%{toolset_version} \
%endif
-DWITH_DPP_STATIC_BUNDLE=TRUE \
-DWITH_SYSTEMD=TRUE
%cmake_build
%install
%cmake_install
mkdir -p %{buildroot}%{_localstatedir}/lib/%{name}
install -p -D -m 0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/%{name}.json
install -p -D -m 0644 %{SOURCE2} %{buildroot}%{_unitdir}/%{name}.service
install -p -D -m 0644 %{SOURCE3} %{buildroot}%{_sysusersdir}/%{name}.conf
%pre
%if 0%{?fedora} || 0%{?rhel}
%sysusers_create_compat %{SOURCE3}
%endif
%if 0%{?suse_version}
%sysusers_create_package %{name} %{SOURCE3}
%endif
%post
%systemd_post %{name}.service
%preun
%systemd_preun %{name}.service
%postun
%systemd_postun_with_restart %{name}.service
%files
%{_bindir}/%{name}
%{_unitdir}/%{name}.service
%{_sysusersdir}/%{name}.conf
%config(noreplace) %attr(0640,root,%{name}) %{_sysconfdir}/%{name}.json
%dir %attr(0750,%{name},%{name}) %{_localstatedir}/lib/%{name}

View file

@ -0,0 +1,2 @@
#Type Name ID GECOS Home directory Shell
u dtranslatebot - "Discord Translation Bot" /var/lib/translatebot -

View file

@ -19,17 +19,9 @@
#ifndef NDEBUG
#include <iostream>
#endif
#include "database_core.h"
#include "database.h"
using namespace bot::database;
database::database()
{
}
database::~database()
{
}
void database::add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const bot::settings::target &target)
{
#ifndef NDEBUG

View file

@ -16,8 +16,8 @@
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef DATABASE_CORE_H
#define DATABASE_CORE_H
#ifndef DATABASE_H
#define DATABASE_H
#include "settings_types.h"
@ -30,8 +30,10 @@ namespace bot {
class database {
public:
explicit database();
virtual ~database();
explicit database() = default;
database(const database&) = delete;
database& operator=(const database&) = delete;
virtual ~database() = default;
virtual void add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const bot::settings::target &target);
virtual void delete_channel(dpp::snowflake guild_id, dpp::snowflake channel_id);
virtual void delete_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &target);
@ -52,4 +54,4 @@ namespace bot {
}
}
#endif // DATABASE_CORE_H
#endif // DATABASE_H

View file

@ -24,22 +24,39 @@
#include "message_queue.h"
#include "settings.h"
#include "slashcommands.h"
using namespace std::chrono_literals;
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cout << "Usage: " << argv[0] << " [json]" << std::endl;
bool flag_wait_for_translator = false;
std::vector<std::string> args;
for (size_t i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--wait-for-translator"))
flag_wait_for_translator = true;
else
args.push_back(argv[i]);
}
if (args.size() != 1) {
std::cout << "Usage: " << argv[0] << " [--wait-for-translator] [json]" << std::endl;
return 0;
}
std::cout << "[Launch] Processing configuration..." << std::endl;
bot::settings::settings settings;
if (!settings.parse_file(argv[1]))
if (!settings.parse_file(args.at(0)))
return 1;
std::cout << "[Launch] Requesting supported languages..." << std::endl;
if (settings.get_translator()->get_languages().empty()) {
std::cerr << "[Error] Failed to initialise translateable languages" << std::endl;
return 2;
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;
}
}
dpp::cluster bot(settings.token(), dpp::i_default_intents | dpp::i_message_content);
@ -55,7 +72,7 @@ int main(int argc, char* argv[]) {
bot.on_message_create(std::bind(&bot::message_queue::process_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](const dpp::ready_t &event) {
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);
}

View file

@ -19,7 +19,7 @@
#include <thread>
#include "message_queue.h"
#include "settings.h"
using namespace bot;
using bot::message_queue;
using namespace std::chrono_literals;
void message_queue::add(const message &message)
@ -36,6 +36,10 @@ void message_queue::add(message &&message)
void message_queue::process_message_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::message_create_t &event)
{
// We check for conditions we want to skip translation for
if (event.msg.author.id == bot->me.id || event.msg.content.empty() || event.msg.has_thread())
return;
if (event.msg.webhook_id) {
const std::lock_guard<bot::settings::settings> guard(*settings);
@ -44,10 +48,6 @@ void message_queue::process_message_event(dpp::cluster *bot, bot::settings::sett
return;
}
// Same as before, just without the involvement of webhooks
if (event.msg.author.id == bot->me.id)
return;
const std::lock_guard<bot::settings::settings> guard(*settings);
if (const bot::settings::channel *channel = settings->get_channel(event.msg.guild_id, event.msg.channel_id)) {
bot::message message;

View file

@ -19,19 +19,23 @@
#include <dpp/json.h>
#include <fstream>
#include <iostream>
#include "database_file.h"
#include "settings.h"
#include "translator_libretranslate.h"
#include "../database/file/file.h"
#include "../translator/deepl/deepl.h"
#include "../translator/mozhi/mozhi.h"
#include "../translator/libretranslate/libretranslate.h"
#include "../translator/lingvatranslate/lingvatranslate.h"
#include "../translator/stub/stub.h"
using namespace bot::settings;
void process_database_channels(std::shared_ptr<bot::database::database> database, bot::settings::guild *guild, std::vector<dpp::snowflake> *webhookIds)
void process_database_channels(std::shared_ptr<bot::database::database> database, bot::settings::guild &guild, std::vector<dpp::snowflake> &webhookIds)
{
const std::vector<dpp::snowflake> db_channels = database->get_channels(guild->id);
const std::vector<dpp::snowflake> db_channels = database->get_channels(guild.id);
for (auto db_channel_id = db_channels.begin(); db_channel_id != db_channels.end(); db_channel_id++) {
bool channel_found = false;
for (auto channel = guild->channel.begin(); channel != guild->channel.end(); channel++) {
for (auto channel = guild.channel.begin(); channel != guild.channel.end(); channel++) {
if (channel->id == *db_channel_id) {
const bot::settings::channel db_channel = database->get_channel(guild->id, channel->id);
const bot::settings::channel db_channel = database->get_channel(guild.id, channel->id);
if (!db_channel.source.empty())
channel->source = db_channel.source;
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++) {
@ -39,14 +43,14 @@ void process_database_channels(std::shared_ptr<bot::database::database> database
for (auto target = channel->targets.begin(); target != channel->targets.end(); target++) {
if (target->target == db_target->target) {
target->webhook = db_target->webhook;
webhookIds->push_back(db_target->webhook.id);
webhookIds.push_back(db_target->webhook.id);
target_found = true;
break;
}
}
if (!target_found) {
channel->targets.push_back(*db_target);
webhookIds->push_back(db_target->webhook.id);
webhookIds.push_back(db_target->webhook.id);
}
}
channel_found = true;
@ -54,23 +58,23 @@ void process_database_channels(std::shared_ptr<bot::database::database> database
}
}
if (!channel_found) {
const bot::settings::channel db_channel = database->get_channel(guild->id, *db_channel_id);
guild->channel.push_back(db_channel);
const bot::settings::channel db_channel = database->get_channel(guild.id, *db_channel_id);
guild.channel.push_back(db_channel);
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++)
webhookIds->push_back(db_target->webhook.id);
webhookIds.push_back(db_target->webhook.id);
}
}
}
void process_database(std::shared_ptr<bot::database::database> database, std::vector<guild> *guilds, std::vector<dpp::snowflake> *webhookIds)
void process_database(std::shared_ptr<bot::database::database> database, std::vector<guild> &guilds, 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;
for (auto guild = guilds->begin(); guild != guilds->end(); guild++) {
for (auto guild = guilds.begin(); guild != guilds.end(); guild++) {
if (guild->id == *db_guild_id) {
process_database_channels(database, &*guild, webhookIds);
process_database_channels(database, *guild, webhookIds);
guild_found = true;
break;
}
@ -78,13 +82,13 @@ void process_database(std::shared_ptr<bot::database::database> database, std::ve
if (!guild_found) {
bot::settings::guild guild;
guild.id = *db_guild_id;
process_database_channels(database, &guild, webhookIds);
guilds->push_back(std::move(guild));
process_database_channels(database, guild, webhookIds);
guilds.push_back(std::move(guild));
}
}
}
void process_guild_settings(const dpp::json &json, std::vector<guild> *guilds, std::vector<dpp::snowflake> *webhookIds)
void process_guild_settings(const dpp::json &json, std::vector<guild> &guilds, std::vector<dpp::snowflake> &webhookIds)
{
for (auto json_guild = json.begin(); json_guild != json.end(); json_guild++) {
if (json_guild->is_object()) {
@ -128,7 +132,7 @@ void process_guild_settings(const dpp::json &json, std::vector<guild> *guilds, s
target target;
target.target = *json_channel_target;
target.webhook = dpp::webhook(json_channel->at("webhook"));
webhookIds->push_back(target.webhook.id);
webhookIds.push_back(target.webhook.id);
channel.targets.push_back(std::move(target));
}
else if (json_channel_target->is_object()) {
@ -136,7 +140,7 @@ void process_guild_settings(const dpp::json &json, std::vector<guild> *guilds, s
target target;
target.target = json_target.key();
target.webhook = dpp::webhook(*json_target);
webhookIds->push_back(target.webhook.id);
webhookIds.push_back(target.webhook.id);
channel.targets.push_back(std::move(target));
}
}
@ -146,7 +150,7 @@ void process_guild_settings(const dpp::json &json, std::vector<guild> *guilds, s
guild.channel.push_back(std::move(channel));
}
}
guilds->push_back(std::move(guild));
guilds.push_back(std::move(guild));
}
}
}
@ -162,103 +166,162 @@ void process_preflang_settings(const dpp::json &json, std::vector<std::string> *
}
}
void process_user_settings(const dpp::json &json, uint16_t *avatar_size)
{
auto json_avatar_size = json.find("avatar_size");
if (json_avatar_size != json.end()) {
*avatar_size = *json_avatar_size;
if (*avatar_size < 16)
*avatar_size = 16;
else if (*avatar_size > 4096)
*avatar_size = 4096;
}
}
void process_url(const std::string &url, translator *translator)
void process_server_url(const std::string &url, translator &translator)
{
std::string_view url_v = url;
if (url_v.substr(0, 7) == "http://") {
translator->tls = false;
if (!translator->port)
translator->port = 80;
translator.tls = false;
if (!translator.port)
translator.port = 80;
url_v = url_v.substr(7);
}
else if (url_v.substr(0, 8) == "https://") {
translator->tls = true;
if (!translator->port)
translator->port = 443;
translator.tls = true;
if (!translator.port)
translator.port = 443;
url_v = url_v.substr(8);
}
else {
translator->tls = false;
if (!translator->port)
translator->port = 80;
translator.tls = false;
if (!translator.port)
translator.port = 80;
}
auto slash_pos = url_v.find_first_of('/');
if (slash_pos != std::string_view::npos) {
translator->url = url_v.substr(slash_pos);
translator.url = url_v.substr(slash_pos);
url_v = url_v.substr(0, slash_pos);
}
else {
translator->url = "/";
translator.url = "/";
url_v = url_v.substr(0, slash_pos);
}
auto colon_pos = url_v.find_first_of(':');
// We don't have IPv6 support here yet
auto colon_pos = url_v.find_last_of(':');
if (colon_pos != std::string_view::npos) {
translator->hostname = url_v.substr(0, colon_pos);
translator.hostname = url_v.substr(0, colon_pos);
const int port = std::stoi(std::string(url_v.substr(colon_pos + 1)));
if (port > 0 && port < 65536)
translator->port = static_cast<uint16_t>(port);
translator.port = static_cast<uint16_t>(port);
else
throw std::invalid_argument("Port is out of range");
}
else {
translator->hostname = url_v;
translator.hostname = url_v;
}
}
bool process_translator_settings(const dpp::json &json, translator *translator)
bool process_server(const dpp::json &json, translator &translator)
{
auto json_hostname = json.find("hostname");
if (json_hostname != json.end())
translator.hostname = *json_hostname;
auto json_tls = json.find("tls");
if (json_tls != json.end())
translator.tls = *json_tls;
else
translator.tls = false;
auto json_port = json.find("port");
if (json_port != json.end())
translator.port = *json_port;
else
translator.port = 0;
auto json_url = json.find("url");
if (json_url == json.end()) {
std::cerr << "[Error] Value url not found in translator object" << std::endl;
return false;
}
if (translator.hostname.empty())
process_server_url(*json_url, translator);
else
translator.url = *json_url;
auto json_apiKey = json.find("apiKey");
if (json_apiKey != json.end())
translator.apiKey = *json_apiKey;
return true;
}
void process_user_settings(const dpp::json &json, uint16_t &avatar_size)
{
auto json_avatar_size = json.find("avatar_size");
if (json_avatar_size != json.end()) {
avatar_size = *json_avatar_size;
if (avatar_size < 16)
avatar_size = 16;
else if (avatar_size > 4096)
avatar_size = 4096;
}
}
bool process_translator_settings(const dpp::json &json, std::shared_ptr<bot::translator::translator> &translator_instance)
{
if (!json.is_object()) {
std::cerr << "[Error] Value translator needs to be a object" << std::endl;
return false;
}
auto json_translate_hostname = json.find("hostname");
if (json_translate_hostname != json.end())
translator->hostname = *json_translate_hostname;
else
translator->hostname = {};
auto json_translate_tls = json.find("tls");
if (json_translate_tls != json.end())
translator->tls = *json_translate_tls;
else
translator->tls = false;
auto json_translate_port = json.find("port");
if (json_translate_port != json.end())
translator->port = *json_translate_port;
else
translator->port = 0;
auto json_translate_url = json.find("url");
if (json_translate_url == json.end()) {
std::cerr << "[Error] Value url not found in translator object" << std::endl;
return false;
}
if (translator->hostname.empty()) {
process_url(*json_translate_url, translator);
bot::settings::translator translator;
auto json_translator_type = json.find("type");
if (json_translator_type != json.end()) {
translator.type = *json_translator_type;
std::transform(translator.type.begin(), translator.type.end(), translator.type.begin(), ::tolower);
}
else {
translator->url = *json_translate_url;
translator.type = "libretranslate";
}
auto json_translate_apiKey = json.find("apiKey");
if (json_translate_apiKey != json.end())
translator->apiKey = *json_translate_apiKey;
else
translator->apiKey.clear();
if (translator.type == "deepl") {
auto json_deepl_hostname = json.find("hostname");
if (json_deepl_hostname != json.end())
translator.hostname = *json_deepl_hostname;
else
translator.hostname = "api-free.deepl.com";
auto json_deepl_apiKey = json.find("apiKey");
if (json_deepl_apiKey == json.end()) {
std::cerr << "[Error] DeepL requires API key for authorization" << std::endl;
return false;
}
translator.apiKey = *json_deepl_apiKey;
translator_instance = std::make_shared<bot::translator::deepl>(translator.hostname, translator.apiKey);
}
else if (translator.type == "mozhi") {
if (!process_server(json, translator))
return false;
std::string mozhi_engine;
auto json_mozhi_engine = json.find("engine");
if (json_mozhi_engine != json.end())
mozhi_engine = *json_mozhi_engine;
else
mozhi_engine = "google";
translator_instance = std::make_shared<bot::translator::mozhi>(translator.hostname, translator.port, translator.url, translator.tls, mozhi_engine);
}
else if (translator.type == "libretranslate") {
if (!process_server(json, translator))
return false;
translator_instance = std::make_shared<bot::translator::libretranslate>(translator.hostname, translator.port, translator.url, translator.tls, translator.apiKey);
}
else if (translator.type == "lingvatranslate") {
if (!process_server(json, translator))
return false;
translator_instance = std::make_shared<bot::translator::lingvatranslate>(translator.hostname, translator.port, translator.url, translator.tls);
}
else if (translator.type == "stub") {
translator_instance = std::make_shared<bot::translator::stub>();
}
else {
std::cerr << "[Error] Translator " << translator.type << " is unknown" << std::endl;
return false;
}
return true;
}
@ -300,6 +363,27 @@ void settings::add_translatebot_webhook(dpp::snowflake webhook_id)
m_webhookIds.push_back(webhook_id);
}
void settings::erase_channel(guild &guild, dpp::snowflake channel_id)
{
for (auto channel = guild.channel.begin(); channel != guild.channel.end(); channel++) {
if (channel->id == channel_id) {
guild.channel.erase(channel);
return;
}
}
}
void settings::erase_guild(dpp::snowflake guild_id)
{
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
for (auto guild = m_guilds.begin(); guild != m_guilds.end(); guild++) {
if (guild->id == guild_id) {
m_guilds.erase(guild);
return;
}
}
}
void settings::erase_translatebot_webhook(dpp::snowflake webhook_id)
{
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
@ -314,9 +398,9 @@ uint16_t settings::avatar_size()
return m_avatarSize;
}
channel* settings::get_channel(guild *guild, dpp::snowflake channel_id)
channel* settings::get_channel(guild &guild, dpp::snowflake channel_id)
{
for (auto channel = guild->channel.begin(); channel != guild->channel.end(); channel++) {
for (auto channel = guild.channel.begin(); channel != guild.channel.end(); channel++) {
if (channel->id == channel_id)
return &*channel;
}
@ -413,10 +497,10 @@ std::shared_ptr<bot::database::database> settings::get_database() const
return m_database;
}
std::unique_ptr<bot::translator::translator> settings::get_translator() const
std::shared_ptr<bot::translator::translator> settings::get_translator() const
{
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
return std::make_unique<bot::translator::libretranslate>(m_translator.hostname, m_translator.port, m_translator.url, m_translator.tls, m_translator.apiKey);
return m_translator;
}
const std::string settings::token() const
@ -446,7 +530,7 @@ bool settings::parse(const std::string &data, bool initialize)
try {
dpp::json json;
try {
json = dpp::json::parse(data);
json = dpp::json::parse(data, nullptr, true, true);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
@ -454,21 +538,24 @@ bool settings::parse(const std::string &data, bool initialize)
return false;
}
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
auto json_token = json.find("token");
if (json_token == json.end()) {
std::cerr << "[Error] Value token not found" << std::endl;
if (json_token != json.end())
m_token = *json_token;
else if (char *token = getenv("DTRANSLATEBOT_TOKEN"))
m_token = token;
if (m_token.empty()) {
std::cerr << "[Error] Discord Bot Token is not configured" << std::endl;
return false;
}
const std::lock_guard<std::recursive_mutex> guard(m_mutex);
m_token = *json_token;
std::filesystem::path storage_path;
auto json_storage = json.find("storage");
if (json_storage != json.end())
storage_path = std::string(*json_storage);
else if (char *storagepath = getenv("DTRANSLATEBOT_STORAGE"))
storage_path = storagepath;
storage_path = storagepath;
if (storage_path.empty())
storage_path = std::filesystem::current_path();
@ -480,14 +567,12 @@ bool settings::parse(const std::string &data, bool initialize)
std::cerr << "[Error] Value translator not found" << std::endl;
return false;
}
if (!process_translator_settings(*json_translator, &m_translator))
if (!process_translator_settings(*json_translator, m_translator))
return false;
m_avatarSize = 256;
auto json_guilds = json.find("guilds");
if (json_guilds != json.end() && json_guilds->is_object())
process_guild_settings(*json_guilds, &m_guilds, &m_webhookIds);
process_guild_settings(*json_guilds, m_guilds, m_webhookIds);
auto json_preflangs = json.find("preferred_lang");
if (json_preflangs != json.end() && json_preflangs->is_array())
@ -495,9 +580,9 @@ bool settings::parse(const std::string &data, bool initialize)
auto json_user = json.find("user");
if (json_user != json.end() && json_user->is_object())
process_user_settings(*json_user, &m_avatarSize);
process_user_settings(*json_user, m_avatarSize);
process_database(m_database, &m_guilds, &m_webhookIds);
process_database(m_database, m_guilds, m_webhookIds);
return true;
}

View file

@ -19,9 +19,9 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include <mutex>
#include "database_core.h"
#include "database.h"
#include "settings_types.h"
#include "translator_core.h"
#include "translator.h"
namespace bot {
namespace settings {
@ -33,11 +33,13 @@ namespace bot {
void add_translatebot_webhook(dpp::snowflake webhook_id);
/* erase functions */
static void erase_channel(guild &guild, dpp::snowflake channel_id);
void erase_guild(dpp::snowflake guild_id);
void erase_translatebot_webhook(dpp::snowflake webhook_id);
/* get functions */
uint16_t avatar_size();
static channel* get_channel(guild *guild, dpp::snowflake channel_id);
static channel* get_channel(guild &guild, dpp::snowflake channel_id);
channel* get_channel(dpp::snowflake guild_id, dpp::snowflake channel_id);
guild* get_guild(dpp::snowflake guild_id);
target* get_target(dpp::snowflake guild_id, dpp::snowflake channel_id, const std::string &target);
@ -45,7 +47,7 @@ namespace bot {
static const target* get_target(const channel *channel, const std::string &target);
const std::vector<std::string> preferred_languages() const;
std::shared_ptr<bot::database::database> get_database() const;
std::unique_ptr<bot::translator::translator> get_translator() const;
std::shared_ptr<bot::translator::translator> get_translator() const;
const std::string token() const;
/* is functions */
@ -59,14 +61,19 @@ namespace bot {
bool parse(const std::string &data, bool initialize = true);
bool parse_file(const std::string &filename, bool initialize = true);
/* prevent copies */
settings() = default;
settings(const settings&) = delete;
settings& operator=(const settings&) = delete;
private:
mutable std::recursive_mutex m_mutex;
size_t m_externallyLockedCount;
uint16_t m_avatarSize;
size_t m_externallyLockedCount = 0;
uint16_t m_avatarSize = 256;
std::shared_ptr<bot::database::database> m_database;
std::vector<guild> m_guilds;
std::vector<std::string> m_prefLangs;
bot::settings::translator m_translator;
std::shared_ptr<bot::translator::translator> m_translator;
std::string m_token;
std::vector<dpp::snowflake> m_webhookIds;
};

View file

@ -41,6 +41,7 @@ namespace bot {
std::vector<bot::settings::channel> channel;
};
struct translator {
std::string type;
std::string hostname;
uint16_t port;
std::string url;

View file

@ -18,19 +18,20 @@
#include <sstream>
#include "slashcommands.h"
using bot::slashcommands;
using namespace std::string_literals;
void bot::slashcommands::process_command_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
void slashcommands::process_command_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
{
if (event.command.get_command_name() == "edit")
bot::slashcommands::process_edit_command(bot, settings, event);
slashcommands::process_edit_command(bot, settings, event);
else if (event.command.get_command_name() == "list")
bot::slashcommands::process_list_command(bot, settings, event);
slashcommands::process_list_command(bot, settings, event);
else if (event.command.get_command_name() == "translate" || event.command.get_command_name() == "translate_pref")
bot::slashcommands::process_translate_command(bot, settings, event);
slashcommands::process_translate_command(bot, settings, event);
}
void bot::slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
void slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
{
try {
dpp::permission user_permissions = event.command.get_resolved_permission(event.command.usr.id);
@ -40,65 +41,84 @@ void bot::slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::
dpp::command_interaction interaction = event.command.get_command_interaction();
if (interaction.options[0].name == "delete") {
const std::lock_guard<bot::settings::settings> guard(*settings);
if (bot::settings::channel *channel = settings->get_channel(event.command.guild_id, event.command.channel_id)) {
const std::string target = std::get<std::string>(event.get_parameter("target"));
if (bot::settings::guild *guild = settings->get_guild(event.command.guild_id)) {
if (bot::settings::channel *channel = settings->get_channel(*guild, event.command.channel_id)) {
const std::string target = std::get<std::string>(event.get_parameter("target"));
std::shared_ptr<bot::database::database> database = settings->get_database();
const bot::settings::channel db_channel = database->get_channel(event.command.guild_id, event.command.channel_id);
auto database = settings->get_database();
const bot::settings::channel db_channel = database->get_channel(event.command.guild_id, event.command.channel_id);
if (db_channel.targets.empty()) {
event.reply(dpp::message("The current channel has no deleteable targets!").set_flags(dpp::m_ephemeral));
}
else if (target == "**") {
std::vector<std::string> targets;
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++) {
targets.push_back(db_target->target);
if (db_channel.targets.empty()) {
event.reply(dpp::message("The current channel has no deleteable targets!").set_flags(dpp::m_ephemeral));
}
for (auto target = channel->targets.begin(); target != channel->targets.end();) {
if (std::find(targets.begin(), targets.end(), target->target) != targets.end()) {
bot->delete_webhook(target->webhook.id, std::bind(&bot::slashcommands::process_deleted_webhook, settings, target->webhook.id, std::placeholders::_1));
target = channel->targets.erase(target);
else if (target == "**") {
std::vector<std::string> targets;
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++) {
targets.push_back(db_target->target);
}
else {
target++;
for (auto target = channel->targets.begin(); target != channel->targets.end();) {
if (std::find(targets.begin(), targets.end(), target->target) != targets.end()) {
bot->delete_webhook(target->webhook.id, std::bind(&slashcommands::process_deleted_webhook, settings, target->webhook.id, std::placeholders::_1));
target = channel->targets.erase(target);
}
else {
target++;
}
}
database->delete_channel(event.command.guild_id, event.command.channel_id);
database->sync();
if (channel->targets.empty()) {
settings->erase_channel(*guild, event.command.channel_id);
if (guild->channel.empty()) {
settings->erase_guild(event.command.guild_id);
}
}
event.reply(dpp::message("Deleteable targets have being deleted!").set_flags(dpp::m_ephemeral));
}
database->delete_channel(event.command.guild_id, event.command.channel_id);
database->sync();
event.reply(dpp::message("Deleteable targets have being deleted!").set_flags(dpp::m_ephemeral));
}
else {
bool target_found = false;
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++) {
if (db_target->target == target) {
target_found = true;
break;
}
}
if (target_found) {
for (auto _target = channel->targets.begin(); _target != channel->targets.end(); _target++) {
if (_target->target == target) {
bot->delete_webhook(_target->webhook.id, std::bind(&bot::slashcommands::process_deleted_webhook, settings, _target->webhook.id, std::placeholders::_1));
channel->targets.erase(_target);
else {
bool target_found = false;
for (auto db_target = db_channel.targets.begin(); db_target != db_channel.targets.end(); db_target++) {
if (db_target->target == target) {
target_found = true;
break;
}
}
if (db_channel.targets.size() == 1)
database->delete_channel(event.command.guild_id, event.command.channel_id);
else
database->delete_channel_target(event.command.guild_id, event.command.channel_id, target);
database->sync();
if (target_found) {
for (auto _target = channel->targets.begin(); _target != channel->targets.end(); _target++) {
if (_target->target == target) {
bot->delete_webhook(_target->webhook.id, std::bind(&slashcommands::process_deleted_webhook, settings, _target->webhook.id, std::placeholders::_1));
channel->targets.erase(_target);
break;
}
}
event.reply(dpp::message("Target have being deleted!").set_flags(dpp::m_ephemeral));
}
else {
event.reply(dpp::message("Target language is not being found or deleteable!").set_flags(dpp::m_ephemeral));
if (db_channel.targets.size() == 1)
database->delete_channel(event.command.guild_id, event.command.channel_id);
else
database->delete_channel_target(event.command.guild_id, event.command.channel_id, target);
database->sync();
if (channel->targets.empty()) {
settings->erase_channel(*guild, event.command.channel_id);
if (guild->channel.empty()) {
settings->erase_guild(event.command.guild_id);
}
}
event.reply(dpp::message("Target have being deleted!").set_flags(dpp::m_ephemeral));
}
else {
event.reply(dpp::message("Target language is not being found or deleteable!").set_flags(dpp::m_ephemeral));
}
}
}
else {
event.reply(dpp::message("The current channel is not being translated!").set_flags(dpp::m_ephemeral));
}
}
else {
event.reply(dpp::message("The current channel is not being translated!").set_flags(dpp::m_ephemeral));
@ -117,13 +137,13 @@ void bot::slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::
source_valid = true;
break;
}
language_codes << " " << language.code;
language_codes << ' ' << language.code;
}
if (source_valid) {
channel->source = source;
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
database->set_channel_source(event.command.guild_id, event.command.channel_id, source);
database->sync();
@ -147,7 +167,7 @@ void bot::slashcommands::process_edit_command(dpp::cluster *bot, bot::settings::
}
}
void bot::slashcommands::process_deleted_webhook(bot::settings::settings *settings, dpp::snowflake webhook_id, const dpp::confirmation_callback_t &callback)
void slashcommands::process_deleted_webhook(bot::settings::settings *settings, dpp::snowflake webhook_id, const dpp::confirmation_callback_t &callback)
{
if (callback.is_error()) {
std::cerr << "[Error] Failed to delete Webhook " << webhook_id << std::endl;
@ -156,7 +176,7 @@ void bot::slashcommands::process_deleted_webhook(bot::settings::settings *settin
settings->erase_translatebot_webhook(webhook_id);
}
void bot::slashcommands::process_list_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
void slashcommands::process_list_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
{
try {
dpp::command_interaction interaction = event.command.get_command_interaction();
@ -171,7 +191,7 @@ void bot::slashcommands::process_list_command(dpp::cluster *bot, bot::settings::
// We want give more information to users who can Manage Webhooks
dpp::permission user_permissions = event.command.get_resolved_permission(event.command.usr.id);
if (user_permissions.has(dpp::p_manage_webhooks)) {
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
const bot::settings::channel db_channel = database->get_channel(event.command.guild_id, event.command.channel_id);
for (auto target = channel->targets.begin(); target != channel->targets.end(); target++) {
@ -217,17 +237,44 @@ void bot::slashcommands::process_list_command(dpp::cluster *bot, bot::settings::
event.reply(dpp::message("The current guild have no translated channel!").set_flags(dpp::m_ephemeral));
}
}
else if (interaction.options[0].name == "languages") {
dpp::permission user_permissions = event.command.get_resolved_permission(event.command.usr.id);
if (user_permissions.has(dpp::p_manage_webhooks)) {
const std::vector<bot::translator::language> languages = settings->get_translator()->get_languages();
std::ostringstream reply_languages;
reply_languages << "**Available Languages**\n";
for (auto language = languages.begin(); language != languages.end();) {
reply_languages << language->name << ": " << language->code;
if (++language != languages.end())
reply_languages << '\n';
}
if (reply_languages.str().length() <= 2000) {
event.reply(dpp::message(reply_languages.str()).set_flags(dpp::m_ephemeral));
}
else {
reply_languages.str({});
reply_languages << "Available Languages:";
for (auto language = languages.begin(); language != languages.end(); language++) {
reply_languages << ' ' << language->code;
}
event.reply(dpp::message(reply_languages.str()).set_flags(dpp::m_ephemeral));
}
}
else {
event.reply(dpp::message("Unauthorized to list available languages!").set_flags(dpp::m_ephemeral));
}
}
else {
throw std::invalid_argument("Option " + interaction.options[0].name + " is not known");
}
}
catch (const std::exception& exception) {
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
event.reply(dpp::message("Exception while processing command:\n"s + exception.what()).set_flags(dpp::m_ephemeral));
}
}
void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
void slashcommands::process_translate_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event)
{
try {
dpp::permission user_permissions = event.command.get_resolved_permission(event.command.usr.id);
@ -241,13 +288,13 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
dpp::command_interaction interaction = event.command.get_command_interaction();
if (interaction.options[0].name == "channel") {
v_target = event.command.get_resolved_channel(
std::get<dpp::snowflake>(event.get_parameter("channel")));
std::get<dpp::snowflake>(event.get_parameter("channel")));
}
else if (interaction.options[0].name == "webhook") {
v_target = dpp::webhook(std::get<std::string>(event.get_parameter("webhook")));
}
const std::vector<bot::translator::language> languages = settings->get_translator()->get_languages();
const auto languages = settings->get_translator()->get_languages();
std::ostringstream language_codes;
bool source_valid = false, target_valid = false;
@ -258,7 +305,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
target_valid = true;
if (source_valid && target_valid)
break;
language_codes << " " << language.code;
language_codes << ' ' << language.code;
}
if (source_valid && target_valid) {
@ -271,7 +318,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
webhook.guild_id = channel->guild_id;
webhook.name = "Translate Bot Webhook <" + std::to_string(event.command.channel_id) + ":" + source + ":" + target + ">";
bot->create_webhook(webhook, std::bind(&bot::slashcommands::process_translate_webhook_new_channel, settings, event, source, target, std::placeholders::_1));
bot->create_webhook(webhook, std::bind(&slashcommands::process_translate_webhook_new_channel, settings, event, source, target, std::placeholders::_1));
}
else if (dpp::webhook *webhook = std::get_if<dpp::webhook>(&v_target)) {
const bot::settings::target s_target = { target, *webhook };
@ -280,7 +327,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
settings->add_channel(s_channel, event.command.guild_id);
settings->add_translatebot_webhook(webhook->id);
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
database->set_channel_source(event.command.guild_id, event.command.channel_id, source);
database->add_channel_target(event.command.guild_id, event.command.channel_id, s_target);
database->sync();
@ -298,7 +345,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
webhook.guild_id = channel->guild_id;
webhook.name = "Translate Bot Webhook <" + std::to_string(event.command.channel_id) + ":" + source + ":" + target + ">";
bot->create_webhook(webhook, std::bind(&bot::slashcommands::process_translate_webhook_add_target, settings, event, target, std::placeholders::_1));
bot->create_webhook(webhook, std::bind(&slashcommands::process_translate_webhook_add_target, settings, event, target, std::placeholders::_1));
}
else if (dpp::webhook *webhook = std::get_if<dpp::webhook>(&v_target)) {
const bot::settings::target s_target = { target, *webhook };
@ -306,7 +353,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
settings->add_target(s_target, event.command.guild_id, event.command.channel_id);
settings->add_translatebot_webhook(webhook->id);
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
database->add_channel_target(event.command.guild_id, event.command.channel_id, s_target);
database->sync();
@ -333,7 +380,7 @@ void bot::slashcommands::process_translate_command(dpp::cluster *bot, bot::setti
}
}
void bot::slashcommands::process_translate_webhook_add_target(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &target, const dpp::confirmation_callback_t &callback)
void slashcommands::process_translate_webhook_add_target(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &target, const dpp::confirmation_callback_t &callback)
{
if (callback.is_error()) {
event.reply(dpp::message("Failed to generate webhook!").set_flags(dpp::m_ephemeral));
@ -347,14 +394,14 @@ void bot::slashcommands::process_translate_webhook_add_target(bot::settings::set
settings->add_target(s_target, event.command.guild_id, event.command.channel_id);
settings->add_translatebot_webhook(webhook.id);
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
database->add_channel_target(event.command.guild_id, event.command.channel_id, s_target);
database->sync();
event.reply(dpp::message("Channel will be now translated!").set_flags(dpp::m_ephemeral));
}
void bot::slashcommands::process_translate_webhook_new_channel(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &source, const std::string &target, const dpp::confirmation_callback_t &callback)
void slashcommands::process_translate_webhook_new_channel(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &source, const std::string &target, const dpp::confirmation_callback_t &callback)
{
if (callback.is_error()) {
event.reply(dpp::message("Failed to generate webhook!").set_flags(dpp::m_ephemeral));
@ -369,7 +416,7 @@ void bot::slashcommands::process_translate_webhook_new_channel(bot::settings::se
settings->add_channel(s_channel, event.command.guild_id);
settings->add_translatebot_webhook(webhook.id);
std::shared_ptr<bot::database::database> database = settings->get_database();
auto database = settings->get_database();
database->set_channel_source(event.command.guild_id, event.command.channel_id, source);
database->add_channel_target(event.command.guild_id, event.command.channel_id, s_target);
database->sync();
@ -377,7 +424,7 @@ void bot::slashcommands::process_translate_webhook_new_channel(bot::settings::se
event.reply(dpp::message("Channel will be now translated!").set_flags(dpp::m_ephemeral));
}
void bot::slashcommands::register_commands(dpp::cluster *bot, bot::settings::settings *settings)
void slashcommands::register_commands(dpp::cluster *bot, bot::settings::settings *settings)
{
settings->lock();
const std::vector<bot::translator::language> languages = settings->get_translator()->get_languages();
@ -387,9 +434,9 @@ void bot::slashcommands::register_commands(dpp::cluster *bot, bot::settings::set
std::vector<dpp::slashcommand> commands;
dpp::command_option source_option(dpp::co_string, "source", "Source language (ISO 639-1)", true);
source_option.set_max_length(static_cast<int64_t>(2)).set_min_length(static_cast<int64_t>(2));
source_option.set_max_length(static_cast<int64_t>(5)).set_min_length(static_cast<int64_t>(2));
dpp::command_option target_option(dpp::co_string, "target", "Target language (ISO 639-1)", true);
target_option.set_max_length(static_cast<int64_t>(2)).set_min_length(static_cast<int64_t>(2));
target_option.set_max_length(static_cast<int64_t>(5)).set_min_length(static_cast<int64_t>(2));
dpp::slashcommand command_edit("edit", "Edit current channel settings", bot->me.id);
command_edit.set_default_permissions(dpp::p_manage_webhooks);
@ -404,8 +451,10 @@ void bot::slashcommands::register_commands(dpp::cluster *bot, bot::settings::set
dpp::slashcommand command_list("list", "List translation settings", bot->me.id);
dpp::command_option channel_list_subcommand(dpp::co_sub_command, "channel", "List current channel translation settings");
dpp::command_option guild_list_subcommand(dpp::co_sub_command, "guild", "List current guild translation settings");
dpp::command_option languages_list_subcommand(dpp::co_sub_command, "languages", "List available languages to translate");
command_list.add_option(channel_list_subcommand);
command_list.add_option(guild_list_subcommand);
command_list.add_option(languages_list_subcommand);
commands.push_back(command_list);
dpp::slashcommand command_translate("translate", "Translate current channel", bot->me.id);

View file

@ -23,16 +23,20 @@
#include "settings.h"
namespace bot {
namespace slashcommands {
extern void process_command_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
extern void process_edit_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
extern void process_deleted_webhook(bot::settings::settings *settings, dpp::snowflake webhook_id, const dpp::confirmation_callback_t &callback);
extern void process_list_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
extern void process_translate_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
extern void process_translate_webhook_add_target(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &target, const dpp::confirmation_callback_t &callback);
extern void process_translate_webhook_new_channel(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &source, const std::string &target, const dpp::confirmation_callback_t &callback);
extern void register_commands(dpp::cluster *bot, bot::settings::settings *settings);
}
class slashcommands {
public:
slashcommands() = delete;
static void process_command_event(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
static void register_commands(dpp::cluster *bot, bot::settings::settings *settings);
private:
static void process_edit_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
static void process_deleted_webhook(bot::settings::settings *settings, dpp::snowflake webhook_id, const dpp::confirmation_callback_t &callback);
static void process_list_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
static void process_translate_command(dpp::cluster *bot, bot::settings::settings *settings, const dpp::slashcommand_t &event);
static void process_translate_webhook_add_target(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &target, const dpp::confirmation_callback_t &callback);
static void process_translate_webhook_new_channel(bot::settings::settings *settings, const dpp::slashcommand_t &event, const std::string &source, const std::string &target, const dpp::confirmation_callback_t &callback);
};
}
#endif // SLASHCOMMANDS_H

View file

@ -19,7 +19,7 @@
#include <thread>
#include "submit_queue.h"
#include "webhook_push.h"
using namespace bot;
using bot::submit_queue;
using namespace std::chrono_literals;
void submit_queue::add(const translated_message &message)

View file

@ -19,17 +19,9 @@
#ifndef NDEBUG
#include <iostream>
#endif
#include "translator_core.h"
#include "translator.h"
using namespace bot::translator;
translator::translator()
{
}
translator::~translator()
{
}
const std::vector<language> translator::get_languages()
{
#ifndef NDEBUG

View file

@ -16,9 +16,10 @@
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef TRANSLATOR_CORE_H
#define TRANSLATOR_CORE_H
#ifndef TRANSLATOR_H
#define TRANSLATOR_H
#include <chrono>
#include <string>
#include <vector>
@ -29,14 +30,21 @@ namespace bot {
std::string name;
};
struct supported_languages {
std::vector<language> languages;
std::chrono::system_clock::time_point query_time;
};
class translator {
public:
explicit translator();
virtual ~translator();
explicit translator() = default;
virtual ~translator() = default;
translator(const translator&) = delete;
translator& operator=(const translator&) = delete;
virtual const std::vector<language> get_languages();
virtual const std::string translate(const std::string &text, const std::string &source, const std::string &target);
};
}
}
#endif // TRANSLATOR_CORE_H
#endif // TRANSLATOR_H

View file

@ -25,10 +25,12 @@ using namespace std::string_view_literals;
void bot::webhook_push::run(const bot::translated_message &message, dpp::cluster *bot)
{
dpp::json json_body = {
{"username"s, message.author},
{"avatar_url"s, message.avatar}
{"username"s, message.author}
};
if (!message.avatar.empty())
json_body["avatar_url"] = message.avatar;
// We will split too long messages into multiple messages
if (message.message.length() > 2000) {
std::string_view message_v = message.message;
@ -73,14 +75,18 @@ void bot::webhook_push::run(const bot::translated_message &message, dpp::cluster
}
}
void webhook_request_completed(std::promise<dpp::http_request_completion_t> &promise, dpp::json &json, const dpp::http_request_completion_t &event)
{
if (event.status != 204)
std::cerr << "[Warning] Webhook push returned unexpected code " << event.status << std::endl;
promise.set_value(event);
}
void bot::webhook_push::push_request(dpp::snowflake webhook_id, const std::string &webhook_token, const std::string &json, dpp::cluster *bot)
{
std::promise<dpp::http_request_completion_t> _p;
std::future<dpp::http_request_completion_t> _f = _p.get_future();
bot->post_rest(API_PATH "/webhooks", std::to_string(webhook_id), dpp::utility::url_encode(webhook_token), dpp::m_post, json, [&bot, &_p](dpp::json &json, const dpp::http_request_completion_t &event) {
if (event.status != 204)
std::cerr << "[Warning] Webhook push returned unexpected code " << event.status << std::endl;
_p.set_value(event);
});
_f.wait();
std::promise<dpp::http_request_completion_t> promise;
std::future<dpp::http_request_completion_t> future = promise.get_future();
bot->post_rest(API_PATH "/webhooks", std::to_string(webhook_id), dpp::utility::url_encode(webhook_token), dpp::m_post, json,
std::bind(&webhook_request_completed, std::ref(promise), std::placeholders::_1, std::placeholders::_2));
future.wait();
}

View file

@ -26,6 +26,7 @@
namespace bot {
class webhook_push {
public:
webhook_push() = delete;
static void run(const bot::translated_message &message, dpp::cluster *bot);
private:

View file

@ -27,7 +27,7 @@
#include <fstream>
#include <iostream>
#include <thread>
#include "database_file.h"
#include "file.h"
using namespace bot::database;
using namespace std::string_literals;
@ -98,7 +98,7 @@ void file::add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
channel.targets.push_back(target);
cache_add_channel(guild_id, channel_id);
guild->channel.push_back(std::move(channel));
@ -107,7 +107,7 @@ void file::add_channel_target(dpp::snowflake guild_id, dpp::snowflake channel_id
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
channel.targets.push_back(target);
cache_add_channel(guild_id, channel_id);
m_dataCache.push_back({ guild_id, { std::move(channel) } });
@ -141,7 +141,7 @@ void file::delete_channel(dpp::snowflake guild_id, dpp::snowflake channel_id)
}
std::vector<dpp::snowflake> channels;
cache_guild(guild_id, &channels);
cache_guild(guild_id, channels);
for (auto channel = channels.begin(); channel != channels.end(); channel++) {
if (*channel == channel_id) {
channels.erase(channel);
@ -175,7 +175,7 @@ void file::delete_channel_target(dpp::snowflake guild_id, dpp::snowflake channel
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
for (auto _target = channel.targets.begin(); _target != channel.targets.end(); _target++) {
if (_target->target == target) {
channel.targets.erase(_target);
@ -188,7 +188,7 @@ void file::delete_channel_target(dpp::snowflake guild_id, dpp::snowflake channel
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
for (auto _target = channel.targets.begin(); _target != channel.targets.end(); _target++) {
if (_target->target == target) {
channel.targets.erase(_target);
@ -266,7 +266,7 @@ bot::settings::channel file::get_channel(dpp::snowflake guild_id, dpp::snowflake
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
return channel;
}
@ -281,7 +281,7 @@ std::vector<dpp::snowflake> file::get_channels(dpp::snowflake guild_id)
}
std::vector<dpp::snowflake> channels;
cache_guild(guild_id, &channels);
cache_guild(guild_id, channels);
return channels;
}
@ -330,7 +330,7 @@ std::vector<dpp::snowflake> file::get_guilds()
{
const std::lock_guard<std::mutex> guard(m_mutex);
std::vector<dpp::snowflake> guilds;
list_guilds(&guilds);
list_guilds(guilds);
return guilds;
}
@ -348,7 +348,7 @@ void file::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
channel.source = source;
cache_add_channel(guild_id, channel_id);
guild->channel.push_back(std::move(channel));
@ -357,7 +357,7 @@ void file::set_channel_source(dpp::snowflake guild_id, dpp::snowflake channel_id
}
bot::settings::channel channel;
cache_get_channel(channel_id, &channel);
cache_get_channel(channel_id, channel);
channel.source = source;
cache_add_channel(guild_id, channel_id);
m_dataCache.push_back({ guild_id, { std::move(channel) } });
@ -390,16 +390,16 @@ void file::cache_add_channel(dpp::snowflake guild_id, dpp::snowflake channel_id)
}
std::vector<dpp::snowflake> channels;
cache_guild(guild_id, &channels);
cache_guild(guild_id, channels);
if (std::find(channels.begin(), channels.end(), channel_id) == channels.end())
channels.push_back(channel_id);
m_channelCache.push_back({ guild_id, std::move(channels) });
}
void file::cache_get_channel(dpp::snowflake channel_id, bot::settings::channel *channel)
void file::cache_get_channel(dpp::snowflake channel_id, settings::channel &channel)
{
channel->id = channel_id;
channel.id = channel_id;
const std::filesystem::path channel_file = m_storagePath / "channel" / (std::to_string(channel_id) + ".json");
@ -418,7 +418,7 @@ void file::cache_get_channel(dpp::snowflake channel_id, bot::settings::channel *
if (json.is_object()) {
auto json_channel_source = json.find("source");
if (json_channel_source != json.end())
channel->source = *json_channel_source;
channel.source = *json_channel_source;
auto json_channel_target = json.find("target");
if (json_channel_target != json.end()) {
@ -433,7 +433,7 @@ void file::cache_get_channel(dpp::snowflake channel_id, bot::settings::channel *
else if (json_target->is_string()) {
target.webhook = dpp::webhook(*json_target);
}
channel->targets.push_back(std::move(target));
channel.targets.push_back(std::move(target));
}
}
}
@ -444,7 +444,7 @@ void file::cache_get_channel(dpp::snowflake channel_id, bot::settings::channel *
}
}
void file::cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> *channels)
void file::cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> &channels)
{
const std::filesystem::path guild_file = m_storagePath / "guild" / (std::to_string(guild_id) + ".json");
@ -463,9 +463,9 @@ void file::cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> *cha
if (json.is_array()) {
for (auto channel = json.begin(); channel != json.end(); channel++) {
if (channel->is_number())
channels->push_back(*channel);
channels.push_back(*channel);
else if (channel->is_string())
channels->push_back(std::stoull(std::string(*channel)));
channels.push_back(std::stoull(std::string(*channel)));
}
}
}
@ -474,7 +474,7 @@ void file::cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> *cha
}
}
void file::list_guilds(std::vector<dpp::snowflake> *guilds)
void file::list_guilds(std::vector<dpp::snowflake> &guilds)
{
const std::filesystem::path guild_dir = m_storagePath / "guild";
@ -488,7 +488,7 @@ void file::list_guilds(std::vector<dpp::snowflake> *guilds)
if (std::all_of(guild_filename.begin(), guild_filename.end(), ::isdigit)) {
try {
dpp::snowflake guild_id = std::stoull(guild_filename);
guilds->push_back(guild_id);
guilds.push_back(guild_id);
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;

View file

@ -26,7 +26,7 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include "database_core.h"
#include "../../core/database.h"
namespace bot {
namespace database {
@ -53,9 +53,9 @@ namespace bot {
private:
void cache_add_channel(dpp::snowflake guild_id, dpp::snowflake channel_id);
void cache_get_channel(dpp::snowflake channel_id, bot::settings::channel *channel);
void cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> *channels);
void list_guilds(std::vector<dpp::snowflake> *guilds);
void cache_get_channel(dpp::snowflake channel_id, bot::settings::channel &channel);
void cache_guild(dpp::snowflake guild_id, std::vector<dpp::snowflake> &channels);
void list_guilds(std::vector<dpp::snowflake> &guilds);
void sync_cache();
#if defined(__unix__)
int fd;

View file

@ -0,0 +1,29 @@
#include <windows.h>
VS_VERSION_INFO VERSIONINFO
FILEVERSION @dtranslatebot_VERSION_MAJOR@, @dtranslatebot_VERSION_MINOR@, @dtranslatebot_VERSION_PATCH@, 0
PRODUCTVERSION @dtranslatebot_VERSION_MAJOR@, @dtranslatebot_VERSION_MINOR@, @dtranslatebot_VERSION_PATCH@, 0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Syping"
VALUE "FileDescription", "Discord Translation Bot"
VALUE "FileVersion", "@dtranslatebot_VERSION@"
VALUE "InternalName", "dtranslatebot"
VALUE "LegalCopyright", "Copyright © 2023-2024 Syping"
VALUE "OriginalFilename", "dtranslatebot.exe"
VALUE "ProductName", "dtranslatebot"
VALUE "ProductVersion", "@dtranslatebot_VERSION@"
END
END
END

View file

@ -0,0 +1,13 @@
[Unit]
Description=Discord Translation Bot
After=network.target
[Service]
User=dtranslatebot
Group=dtranslatebot
WorkingDirectory=@dtranslatebot_SERVICE_WORKDIR@
ExecStart="@CMAKE_INSTALL_FULL_BINDIR@/dtranslatebot" "@CMAKE_INSTALL_FULL_SYSCONFDIR@/dtranslatebot.json"
Restart=on-abnormal
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1 @@
u dtranslatebot - "Discord Translation Bot" "@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/dtranslatebot" -

View file

@ -0,0 +1,116 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#include <dpp/json.h>
#include <dpp/httpsclient.h>
#include "deepl.h"
using namespace bot::translator;
using namespace std::chrono_literals;
using namespace std::string_literals;
deepl::deepl(const std::string &hostname, const std::string apiKey) :
m_hostname(hostname), m_apiKey(apiKey)
{
}
deepl::~deepl()
{
}
const std::vector<language> deepl::get_languages()
{
if (!m_languages.languages.empty()) {
auto current_time = std::chrono::system_clock::now();
auto threshold_time = m_languages.query_time + 24h;
if (current_time <= threshold_time)
return m_languages.languages;
}
try {
dpp::https_client http_request(&m_cluster, m_hostname, 443, "/v2/languages?type=target", "GET", {}, { {"Authorization"s, "DeepL-Auth-Key " + m_apiKey} }, false);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_array()) {
m_languages.languages.clear();
for (auto json_language = response.begin(); json_language != response.end(); json_language++) {
if (json_language->is_object()) {
language language;
auto json_lang_code = json_language->find("language");
if (json_lang_code != json_language->end())
language.code = *json_lang_code;
if (language.code.size() > 2)
std::transform(language.code.begin(), language.code.begin() + 2, language.code.begin(), ::tolower);
else
std::transform(language.code.begin(), language.code.end(), language.code.begin(), ::tolower);
auto json_lang_name = json_language->find("name");
if (json_lang_name != json_language->end())
language.name = *json_lang_name;
if (!language.code.empty() && !language.name.empty())
m_languages.languages.push_back(std::move(language));
}
}
m_languages.query_time = std::chrono::system_clock::now();
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return m_languages.languages;
}
const std::string deepl::translate(const std::string &text, const std::string &source, const std::string &target)
{
const dpp::http_headers http_headers = {
{"Authorization"s, "DeepL-Auth-Key " + m_apiKey},
{"Content-Type"s, "application/json"s}
};
dpp::json json_body = {
{"text"s, { text } },
{"source_lang"s, source},
{"target_lang"s, target},
};
try {
dpp::https_client http_request(&m_cluster, m_hostname, 443, "/v2/translate", "POST", json_body.dump(), http_headers, false);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_object()) {
auto translations = response.find("translations");
if (translations != response.end() && translations->is_array()) {
for (auto translation = translations->begin(); translation != translations->end(); translation++) {
auto tr_text = translation->find("text");
if (tr_text != translation->end())
return *tr_text;
}
}
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return text;
}

View file

@ -0,0 +1,43 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef TRANSLATOR_DEEPL_H
#define TRANSLATOR_DEEPL_H
#include <dpp/cluster.h>
#include "../../core/translator.h"
namespace bot {
namespace translator {
class deepl : public translator {
public:
explicit deepl(const std::string &hostname, const std::string apiKey = {});
~deepl() override;
const std::vector<language> get_languages() override;
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
private:
dpp::cluster m_cluster;
std::string m_apiKey;
std::string m_hostname;
supported_languages m_languages;
};
}
}
#endif // TRANSLATOR_DEEPL_H

View file

@ -18,8 +18,9 @@
#include <dpp/json.h>
#include <dpp/httpsclient.h>
#include "translator_libretranslate.h"
#include "libretranslate.h"
using namespace bot::translator;
using namespace std::chrono_literals;
using namespace std::string_literals;
libretranslate::libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string apiKey) :
@ -33,29 +34,36 @@ libretranslate::~libretranslate()
const std::vector<language> libretranslate::get_languages()
{
std::vector<language> languages;
if (!m_languages.languages.empty()) {
auto current_time = std::chrono::system_clock::now();
auto threshold_time = m_languages.query_time + 24h;
if (current_time <= threshold_time)
return m_languages.languages;
}
try {
dpp::https_client http_request(m_hostname, m_port, m_url + "languages", "GET", {}, {}, !m_tls);
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "languages", "GET", {}, {}, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_array()) {
for (const auto &json_language : response) {
if (json_language.is_object()) {
m_languages.languages.clear();
for (auto json_language = response.begin(); json_language != response.end(); json_language++) {
if (json_language->is_object()) {
language language;
auto json_lang_code = json_language.find("code");
if (json_lang_code != json_language.end())
auto json_lang_code = json_language->find("code");
if (json_lang_code != json_language->end())
language.code = *json_lang_code;
auto json_lang_name = json_language.find("name");
if (json_lang_name != json_language.end())
auto json_lang_name = json_language->find("name");
if (json_lang_name != json_language->end())
language.name = *json_lang_name;
if (!language.code.empty() && !language.name.empty())
languages.push_back(std::move(language));
m_languages.languages.push_back(std::move(language));
}
}
m_languages.query_time = std::chrono::system_clock::now();
}
}
}
@ -63,7 +71,7 @@ const std::vector<language> libretranslate::get_languages()
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return languages;
return m_languages.languages;
}
const std::string libretranslate::translate(const std::string &text, const std::string &source, const std::string &target)
@ -83,7 +91,7 @@ const std::string libretranslate::translate(const std::string &text, const std::
json_body["apiKey"] = m_apiKey;
try {
dpp::https_client http_request(m_hostname, m_port, m_url + "translate", "POST", json_body.dump(), http_headers, !m_tls);
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "translate", "POST", json_body.dump(), http_headers, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_object()) {

View file

@ -20,12 +20,13 @@
#define TRANSLATOR_LIBRETRANSLATE_H
#include <cstdint>
#include <string>
#include "translator_core.h"
#include <dpp/cluster.h>
#include "../../core/translator.h"
namespace bot {
namespace translator {
class libretranslate : public translator {
public:
explicit libretranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string apiKey = {});
~libretranslate() override;
@ -33,8 +34,10 @@ namespace bot {
const std::string translate(const std::string &text, const std::string &source, const std::string &target) override;
private:
dpp::cluster m_cluster;
std::string m_apiKey;
std::string m_hostname;
supported_languages m_languages;
uint16_t m_port;
std::string m_url;
bool m_tls;

View file

@ -0,0 +1,99 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#include <dpp/json.h>
#include <dpp/httpsclient.h>
#include <dpp/utility.h>
#include "lingvatranslate.h"
using namespace bot::translator;
using namespace std::chrono_literals;
using namespace std::string_literals;
lingvatranslate::lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls) :
m_hostname(hostname), m_port(port), m_url(url), m_tls(tls)
{
}
lingvatranslate::~lingvatranslate()
{
}
const std::vector<language> lingvatranslate::get_languages()
{
if (!m_languages.languages.empty()) {
auto current_time = std::chrono::system_clock::now();
auto threshold_time = m_languages.query_time + 24h;
if (current_time <= threshold_time)
return m_languages.languages;
}
try {
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "api/v1/languages/target", "GET", {}, {}, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_object()) {
auto languages = response.find("languages");
if (languages != response.end()) {
m_languages.languages.clear();
for (auto json_language = languages->begin(); json_language != languages->end(); json_language++) {
if (json_language->is_object()) {
language language;
auto json_lang_code = json_language->find("code");
if (json_lang_code != json_language->end())
language.code = *json_lang_code;
auto json_lang_name = json_language->find("name");
if (json_lang_name != json_language->end())
language.name = *json_lang_name;
if (!language.code.empty() && !language.name.empty())
m_languages.languages.push_back(std::move(language));
}
}
m_languages.query_time = std::chrono::system_clock::now();
}
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return m_languages.languages;
}
const std::string lingvatranslate::translate(const std::string &text, const std::string &source, const std::string &target)
{
try {
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "api/v1/" + source + "/" + target + "/" + dpp::utility::url_encode(text), "GET", {}, {}, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_object()) {
auto tr_text = response.find("translation");
if (tr_text != response.end())
return *tr_text;
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return text;
}

View file

@ -0,0 +1,47 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef TRANSLATOR_LINGVATRANSLATE_H
#define TRANSLATOR_LINGVATRANSLATE_H
#include <cstdint>
#include <dpp/cluster.h>
#include "../../core/translator.h"
namespace bot {
namespace translator {
class lingvatranslate : public translator {
public:
explicit lingvatranslate(const std::string &hostname, uint16_t port, const std::string &url, bool tls);
~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;
private:
dpp::cluster m_cluster;
std::string m_hostname;
supported_languages m_languages;
uint16_t m_port;
std::string m_url;
bool m_tls;
};
}
}
#endif // TRANSLATOR_LINGVATRANSLATE_H

View file

@ -0,0 +1,105 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#include <dpp/json.h>
#include <dpp/httpsclient.h>
#include <dpp/utility.h>
#include "mozhi.h"
using namespace bot::translator;
using namespace std::chrono_literals;
using namespace std::string_literals;
mozhi::mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine) :
m_hostname(hostname), m_port(port), m_url(url), m_tls(tls), m_engine(engine)
{
}
mozhi::~mozhi()
{
}
const std::vector<language> mozhi::get_languages()
{
if (!m_languages.languages.empty()) {
auto current_time = std::chrono::system_clock::now();
auto threshold_time = m_languages.query_time + 24h;
if (current_time <= threshold_time)
return m_languages.languages;
}
try {
const std::string parameters = dpp::utility::make_url_parameters({
{"engine"s, m_engine}
});
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "api/target_languages" + parameters, "GET", {}, {}, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_array()) {
m_languages.languages.clear();
for (auto json_language = response.begin(); json_language != response.end(); json_language++) {
if (json_language->is_object()) {
language language;
auto json_lang_code = json_language->find("Id");
if (json_lang_code != json_language->end())
language.code = *json_lang_code;
auto json_lang_name = json_language->find("Name");
if (json_lang_name != json_language->end())
language.name = *json_lang_name;
if (!language.code.empty() && !language.name.empty())
m_languages.languages.push_back(std::move(language));
}
}
m_languages.query_time = std::chrono::system_clock::now();
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return m_languages.languages;
}
const std::string mozhi::translate(const std::string &text, const std::string &source, const std::string &target)
{
try {
const std::string parameters = dpp::utility::make_url_parameters({
{"engine"s, m_engine},
{"from"s, source},
{"to"s, target},
{"text"s, text},
});
dpp::https_client http_request(&m_cluster, m_hostname, m_port, m_url + "api/translate" + parameters, "GET", {}, {}, !m_tls);
if (http_request.get_status() == 200) {
const dpp::json response = dpp::json::parse(http_request.get_content());
if (response.is_object()) {
auto tr_text = response.find("translated-text");
if (tr_text != response.end())
return *tr_text;
}
}
}
catch (const std::exception &exception) {
std::cerr << "[Exception] " << exception.what() << std::endl;
}
return text;
}

View file

@ -0,0 +1,48 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef TRANSLATOR_MOZHI_H
#define TRANSLATOR_MOZHI_H
#include <cstdint>
#include <dpp/cluster.h>
#include "../../core/translator.h"
namespace bot {
namespace translator {
class mozhi : public translator {
public:
explicit mozhi(const std::string &hostname, uint16_t port, const std::string &url, bool tls, const std::string &engine);
~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;
private:
dpp::cluster m_cluster;
std::string m_engine;
std::string m_hostname;
supported_languages m_languages;
uint16_t m_port;
std::string m_url;
bool m_tls;
};
}
}
#endif // TRANSLATOR_MOZHI_H

View file

@ -0,0 +1,39 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#include "stub.h"
using namespace bot::translator;
using namespace std::string_literals;
stub::stub()
{
}
stub::~stub()
{
}
const std::vector<language> stub::get_languages()
{
return { {"a*", "Any Language"} };
}
const std::string stub::translate(const std::string &text, [[maybe_unused]] const std::string &source, [[maybe_unused]] const std::string &target)
{
return text;
}

View file

@ -0,0 +1,36 @@
/*****************************************************************************
* dtranslatebot Discord Translate Bot
* Copyright (C) 2024 Syping
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* This software is provided as-is, no warranties are given to you, we are not
* responsible for anything with use of the software, you are self responsible.
*****************************************************************************/
#ifndef TRANSLATOR_STUB_H
#define TRANSLATOR_STUB_H
#include "../../core/translator.h"
namespace bot {
namespace translator {
class stub : public translator {
public:
explicit stub();
~stub() 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;
};
}
}
#endif // TRANSLATOR_STUB_H