stage changes

This commit is contained in:
L-Nafaryus 2024-03-14 13:06:52 +05:00
commit ab3a1cb6b1
Signed by: L-Nafaryus
GPG Key ID: 582F8B0866B294A1
13 changed files with 613 additions and 0 deletions

5
.clang-format Normal file
View File

@ -0,0 +1,5 @@
---
BasedOnStyle: LLVM
IndentWidth: 4
---
Language: Cpp

6
.containerignore Normal file
View File

@ -0,0 +1,6 @@
**
!lib/**
!CMakeLists.txt
!main.cc
!include.hh
!extern.cc

118
.gitignore vendored Normal file
View File

@ -0,0 +1,118 @@
build*/
.floo
token.dat
cmake-build-*/
scratch.txt
env
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Created by https://www.gitignore.io/api/jetbrains+all
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### JetBrains+all Patch ###
# Ignores the whole idea folder
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
.idea/
# End of https://www.gitignore.io/api/jetbrains+all
# Created by https://www.gitignore.io/api/visualstudiocode
# Edit at https://www.gitignore.io/?templates=visualstudiocode
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# End of https://www.gitignore.io/api/visualstudiocode
*.log

59
CMakeLists.txt Normal file
View File

@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.16)
project(discord-oscuro LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")
include(cmake/macros.cmake)
include(cmake/CPM.cmake)
CPMAddPackage("gh:DiscordPP/discordpp#daf04ea")
CPMAddPackage("gh:DiscordPP/rest-simpleweb#4cf911b")
CPMAddPackage("gh:DiscordPP/websocket-simpleweb#9082991")
CPMAddPackage("gh:DiscordPP/plugin-native#238b7e8")
CPMAddPackage("gh:DiscordPP/plugin-overload#8862ac5")
CPMAddPackage("gh:DiscordPP/plugin-responder#3c2c485")
CPMAddPackage("gh:DiscordPP/plugin-interactionhandler#df07fd3")
CPMAddPackage("gh:DiscordPP/plugin-ratelimit#2c8cb34")
CPMAddPackage("gh:libcpr/cpr#1.10.4")
set(DISCORDPP_USE_BOOST OFF CACHE BOOL "Override option" FORCE)
CREATE_DISCORDPP_DEFINITIONS()
CREATE_DISCORDPP_INCLUDE()
set(THREADS_PREFER_PTHREAD_FLAG ON)
if(${DISCORDPP_USE_BOOST})
find_package(Boost 1.71.0 REQUIRED system date_time)
endif()
find_package(Threads REQUIRED)
find_package(OpenSSL REQUIRED)
add_executable(${PROJECT_NAME}
source/main.cpp
)
#target_precompile_headers(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/source/main.hpp)
target_link_libraries(${PROJECT_NAME}
${Boost_LIBRARIES}
Threads::Threads
${OPENSSL_LIBRARIES}
discordpp
discordpp-rest-simpleweb
discordpp-websocket-simpleweb
discordpp-plugin-native
discordpp-plugin-overload
discordpp-plugin-responder
discordpp-plugin-interactionhandler
discordpp-plugin-ratelimit
cpr::cpr
)
copy_file("env")

29
Containerfile Normal file
View File

@ -0,0 +1,29 @@
FROM alpine:latest AS build
RUN apk update
RUN apk add --no-cache \
build-base \
cmake \
samurai \
boost-dev \
git \
openssl-dev
WORKDIR /echo_bot
COPY . .
RUN cmake -B build -G Ninja .
RUN cmake --build build
FROM alpine:latest
RUN apk update
RUN apk add --no-cache \
libgcc \
libstdc++ \
libc6-compat \
openssl-dev
WORKDIR /echo_bot
COPY --from=build /echo_bot/build/echo_bot .
CMD source /run/secrets/discord-oscuro-env; ./echo_bot

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 George Kusayko <L-Nafaryus>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Oscuro - Discord Bot (c++)
## Note
- Create `env` file:
```sh
export BOT_TOKEN="Bot ....."
```
# License
**oscuro** is licensed under the [MIT License](LICENSE).

23
cmake/CPM.cmake Normal file
View File

@ -0,0 +1,23 @@
set(CPM_DOWNLOAD_VERSION 0.38.1)
if(CPM_SOURCE_CACHE)
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE} ABSOLUTE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION}))
message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}")
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION}
)
endif()
include(${CPM_DOWNLOAD_LOCATION})

8
cmake/macros.cmake Normal file
View File

@ -0,0 +1,8 @@
function(copy_file FILE)
if (EXISTS ${CMAKE_SOURCE_DIR}/${FILE})
configure_file(${FILE} ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
elseif (EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${FILE})
endif ()
endfunction()

22
source/bot.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <discordpp/bot.hh>
#include <discordpp/rest-simpleweb.hh>
#include <discordpp/websocket-simpleweb.hh>
#include <discordpp/plugin-native.hh>
#include <discordpp/plugin-overload.hh>
#include <discordpp/plugin-responder.hh>
#include <discordpp/plugin-interactionhandler.hh>
#include <discordpp/plugin-ratelimit.hh>
using Bot = discordpp::PluginRateLimit
<discordpp::PluginInteractionHandler
<discordpp::PluginResponder
<discordpp::PluginOverload
<discordpp::PluginNative
<discordpp::WebsocketSimpleWeb
<discordpp::RestSimpleWeb
<discordpp::Bot>>>>>>>;
namespace dpp = discordpp;

19
source/commands.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <functional>
#include <nlohmann/json.hpp>
#include "bot.hpp"
using json = nlohmann::json;
using event = std::function<void(const json)>;
auto help(const std::shared_ptr<Bot>& bot)
{
return [&bot](const dpp::MessageCreateEvent& msg)
{
};
}

218
source/main.cpp Normal file
View File

@ -0,0 +1,218 @@
#include <fstream>
#include <iostream>
#include <regex>
#ifdef ASIO_STANDALONE
#include <asio.hpp>
#else
#include <boost/asio.hpp>
namespace asio = boost::asio;
#endif
#include "bot.hpp"
using json = nlohmann::json;
#include <cpr/cpr.h>
#include "utils.hpp"
#include "commands.hpp"
int main() {
dpp::log::filter = dpp::log::debug;
dpp::log::out = &std::cerr;
std::cout << "Starting bot ..." << std::endl;
std::string token = getToken();
if (token.empty()) {
std::cerr << "Failed to read token from environment. Exiting ..." << std::endl;
exit(1);
}
dpp::User self;
auto bot = std::make_shared<Bot>();
bot->debugUnhandled = true;
bot->intents = dpp::intents::NONE | dpp::intents::GUILD_MESSAGES;
bot->handlers.insert({
"READY",
[&self](dpp::ReadyEvent ready){ self = *ready.user; }
});
bot->prefix = "/";
bot->respond("test", help(bot));
bot->respond("help", "Mention me and I'll echo your message back!");
bot->respond("about", [&bot](dpp::MessageCreateEvent msg) {
std::ostringstream content;
content << "Sure thing, "
<< *(msg.member->nick ? msg.author->username : msg.member->nick)
<< "!\n"
<< "I'm a simple bot meant to demonstrate the "
"Discord++ library.\n"
<< "You can learn more about Discord++ at "
"https://discord.gg/VHAyrvspCx";
bot->createMessage()
->channel_id(*msg.channel_id)
->content(content.str())
->run();
});
bot->respond("lookatthis", [&bot](dpp::MessageCreateEvent msg) {
std::ifstream ifs("image.jpg", std::ios::binary);
if (!ifs) {
std::cerr << "Couldn't load file 'image.jpg'!\n";
return;
}
ifs.seekg(0, std::ios::end);
std::ifstream::pos_type fileSize = ifs.tellg();
ifs.seekg(0, std::ios::beg);
auto file = std::make_shared<std::string>(fileSize, '\0');
ifs.read(file->data(), fileSize);
bot->createMessage()
->channel_id(*msg.channel_id)
->content("Look at this photograph")
->filename("image.jpg")
->filetype("image/jpg")
->file(file)
->run();
});
bot->respond("notacat", [&bot](dpp::MessageCreateEvent msg)
{
cpr::Response res = cpr::Get(cpr::Url{"https://cataas.com/cat"});
std::cout << "Fetching a cat: " << res.status_code << ", " << res.header["content-type"] << std::endl;
bot->createMessage()
->channel_id(*msg.channel_id)
->filename("cat.jpg")
->filetype(res.header["content-type"])
->file(res.text)
->run();
});
bot->respond("channelinfo", [&bot](dpp::MessageCreateEvent msg) {
bot->getChannel()
->channel_id(*msg.channel_id)
->onRead([&bot, msg](bool error, json res) {
bot->createMessage()
->channel_id(*msg.channel_id)
->content("```json\n" + res["body"].dump(4) + "\n```")
->run();
})
->run();
});
bot->respond("register", [&bot, &self](dpp::MessageCreateEvent msg) {
if (*msg.author->id == 272712928074006528) {
bot->createGuildApplicationCommand()
->application_id(*self.id)
->guild_id(*msg.guild_id)
->name("echo")
->description("Echoes what you say")
->options({dpp::ApplicationCommandOption(
dpp::ApplicationCommandOptionType::STRING,
std::string("message"), dpp::omitted, std::string("The message to echo"),
dpp::omitted, true)})
->command_type(dpp::ApplicationCommandType::CHAT_INPUT)
->onRead([](bool error, json res) {
std::cout << res.dump(4) << std::endl;
})
->run();
}
bot->createGuildApplicationCommand()
->application_id(*self.id)
->guild_id(*msg.guild_id)
->name("lookatthis")
->description("Help information")
->options({dpp::ApplicationCommandOption(
dpp::ApplicationCommandOptionType::STRING,
std::string("message"), dpp::omitted, std::string("The message to echo"),
dpp::omitted, true)})
->command_type(dpp::ApplicationCommandType::CHAT_INPUT)
->onRead([](bool error, json res) {
std::cout << res.dump(4) << std::endl;
})
->run();
});
bot->interactionHandlers.insert(
{1102290596896460850, [&bot](dpp::Interaction msg) {
bot->createResponse()
->interaction_id(*msg.id)
->interaction_token(*msg.token)
->interaction_type(
dpp::InteractionCallbackType::CHANNEL_MESSAGE_WITH_SOURCE)
->data({{
"content",
*std::get<dpp::ApplicationCommandData>(*msg.data).options->at(0).value
}})
->run();
}});
// Create handler for the MESSAGE_CREATE payload, this receives all messages
// sent that the bot can see.
bot->handlers.insert(
{"MESSAGE_CREATE", [&bot, &self](const dpp::MessageCreateEvent msg) {
// Ignore messages from other bots
if (msg.webhook_id || (msg.author->bot && *msg.author->bot)) {
return;
}
// Scan through mentions in the message for self
bool mentioned = false;
for (const dpp::User &mention : *msg.mentions) {
mentioned = mentioned || (*mention.id == *self.id);
}
if (mentioned) {
// Identify and remove mentions of self from the message
std::stringstream content;
content << "чё тебе надо, ";//*msg.content;
/*unsigned int oldlength, length = content.length();
do {
oldlength = length;
content = std::regex_replace(
content,
std::regex(R"(<@!?)" + std::to_string(*self.id) +
R"(> ?)"),
"");
length = content.length();
} while (oldlength > length);
*/
// Get the target user's display name
std::string name = *(msg.member->nick ? msg.member->nick
: msg.author->username);
content << name << "?";
//std::cout << "Echoing " << name << '\n';
// Echo the created message
bot->createMessage()
->channel_id(*msg.channel_id)
->content(content.str())
->run();
// Set status to Playing "with [author]"
/*bot->send(3,
{{"game", {{"name", "with " + name}, {"type", 0}}},
{"status", "online"},
{"afk", false},
{"since", "null"}});*/
}
}});
auto asioCtx = std::make_shared<asio::io_context>();
bot->initBot(9, token, asioCtx);
bot->run();
return 0;
}

74
source/utils.hpp Normal file
View File

@ -0,0 +1,74 @@
#pragma once
#include <fstream>
#include <iostream>
/*/
* Source: https://stackoverflow.com/a/6089413/1526048
/*/
std::istream &safeGetline(std::istream& is, std::string& str)
{
str.clear();
// The characters in the stream are read one-by-one using a std::streambuf.
// That is faster than reading them one-by-one using the std::istream.
// Code that uses streambuf this way must be guarded by a sentry object.
// The sentry object performs various tasks,
// such as thread synchronization and updating the stream state.
std::istream::sentry se(is, true);
std::streambuf *sb = is.rdbuf();
for (;;)
{
int c = sb->sbumpc();
switch (c)
{
case '\n':
return is;
case '\r':
if (sb->sgetc() == '\n') {
sb->sbumpc();
}
return is;
case std::streambuf::traits_type::eof():
// Also handle the case when the last line has no line ending
if (str.empty())
is.setstate(std::ios::eofbit);
return is;
default:
str += (char)c;
}
}
}
std::string getToken(const std::string& filename = "")
{
std::string token;
char const *env = std::getenv("BOT_TOKEN");
if (env != nullptr)
{
token = std::string(env);
}
else
{
std::ifstream tokenFile(filename);
if (!tokenFile)
return "";
safeGetline(tokenFile, token);
tokenFile.close();
}
return token;
}