Archived
1
0
Fork 0

Compare commits

...

81 commits

Author SHA1 Message Date
niansa
92c3058485 Update README.md 2021-06-21 21:34:07 +00:00
Nils
6f4d5606a6 Added README 2021-06-21 23:31:07 +02:00
Nils
5db977d9f6 Added opensource command 2021-06-21 23:12:42 +02:00
niansa
73dce15840 Redisabled CI 2021-06-05 17:06:31 +02:00
niansa
e7fe154df3 Try #1 to fix CI 2021-06-05 16:43:45 +02:00
niansa
afcab93421 Updated CDL and CDL DB 2021-06-05 16:40:08 +02:00
niansa
4ab7b39980 Reenabled CI 2021-06-05 16:38:04 +02:00
niansa
6132b8df31 Improvements in warns command 2021-06-05 16:36:43 +02:00
niansa
f31616cbb7 Updated CDL++ and CDL++ Db 2021-05-22 16:34:57 +02:00
niansa
8dca929d80 Update CMakeLists.txt 2021-05-22 14:33:00 +00:00
Nils
c98cfa01ba Forgot to use simpleEmbed() at just one more place 2021-05-21 18:28:14 +02:00
Nils
875578d6ee Use embeds even more 2021-05-21 18:20:40 +02:00
Nils
143a80cba0 Fixed policy warning 2021-05-09 18:10:58 +02:00
Nils
eef1e190a7 Fixed cdlpp.cmake 2021-05-09 18:01:56 +02:00
Nils
c205f9fa94 Allow building without CDL++ installed 2021-05-09 17:55:16 +02:00
Nils
2566265d1d Removed some legacy code 2021-05-09 17:05:41 +02:00
Nils
8329eea3e0 Another fix in CMakeLists.txt 2021-04-29 13:50:40 +02:00
Nils
67506ea201 Fixed CMakeLists.txt once again 2021-04-29 13:36:26 +02:00
Nils
a68b579b16 Added cmake option to toggle eval module 2021-04-29 13:27:52 +02:00
Nils
5f25ab401f Fixed loose cpuid dependency 2021-04-29 13:22:34 +02:00
Nils
44ee2bcb08 Added loose dependency: cpuid 2021-04-29 13:19:14 +02:00
niansa
5e6bf6ccb5 Updated for latest CDL++ 2021-04-09 13:37:48 +02:00
niansa
3afec81b3d Updated CDLPP-DB 2021-04-09 11:52:01 +02:00
niansa
35908dbfcd Documented unwarn command 2021-03-22 13:21:44 +01:00
niansa
13d2479e49 Added unwarn command 2021-03-22 13:18:23 +01:00
niansa
dcf88f4ab0 Disabled .gitlab-ci.yml 2021-03-22 09:06:59 +00:00
niansa
4d17a43c83 Copy member before trying to modify it 2021-03-22 10:04:57 +01:00
niansa
b80b5deb3a Renamed some* commands to nota* 2021-03-19 11:57:24 +01:00
niansa
e07bb7fa55 Added somex commands 2021-03-18 16:47:25 +01:00
niansa
d637773f85 Fixed warn reset on ban 2021-03-12 13:25:33 +01:00
niansa
24f051299a Updated for latest CDL 2021-03-12 13:12:07 +01:00
niansa
bf4c2fae35 Removed duplicate GUILD_BANS intent 2021-03-12 12:42:51 +01:00
niansa
86afdb6a68 Updated CDL-DB 2021-03-12 12:38:58 +01:00
niansa
6b9380a221 Fixed check constraint for WARNS 2021-03-12 12:32:50 +01:00
niansa
17de144df4 Just very minor warning system improvement 2021-03-12 12:13:11 +01:00
niansa
10ff959aee Further fixes 2021-03-12 00:57:36 +01:00
niansa
375f8940a8 Fixed last commit 2021-03-12 00:34:24 +01:00
niansa
ea6a412cf8 Added warn commands 2021-03-12 00:21:44 +01:00
niansa
5be8ee0e21 Use std::filesystem to remove files instead of c api 2021-03-08 08:15:46 +01:00
niansa
35103c5744 Fully clean up evaluation data 2021-03-08 08:07:44 +01:00
niansa
6b31eaa81b Update eval.cpp 2021-03-08 07:00:46 +00:00
niansa
a01d400b23 Added fmt library to CMakeLists.txt 2021-03-08 07:47:01 +01:00
niansa
59fb294826 CMakeLists.txt fixup 2021-03-08 07:45:42 +01:00
niansa
c4db5815b9 Portability improvements 2021-03-08 07:42:34 +01:00
niansa
80e6564a3d Added eval command 2021-03-08 00:19:31 +01:00
niansa
aca3591c39 Use system-wide library instead of using a submodule 2021-02-26 09:36:15 +01:00
niansa
cc19d7c677 Improved 8ball 2021-02-19 13:42:11 +01:00
niansa
0d00969c61 Improved random command 2021-02-19 12:57:03 +01:00
niansa
b611a3df9c Updated CDL 2021-02-19 11:59:05 +01:00
niansa
9e2cfbfcd4 Updated CDL-DB 2021-02-17 18:39:52 +01:00
niansa
939d91e4a4 Updated CDL-DB 2021-02-16 07:40:35 +01:00
niansa
493a5f6b53 Updated CDL-DB 2021-02-16 07:37:43 +01:00
niansa
aed3c7630c Updated CDL and CDL-DB 2021-02-14 17:10:33 +01:00
niansa
fe50130ae3 Fixed currency 2021-02-13 22:30:39 +01:00
niansa
c080055aac Updated CDL 2021-02-13 13:50:30 +01:00
niansa
c95f487278 Updated CDL and CDL-DB 2021-02-13 13:48:23 +01:00
niansa
58bfa3ccca Updated CDL-DB 2021-02-13 09:56:01 +01:00
niansa
977d1ae2f1 Updated CDL DB 2021-02-13 09:25:48 +01:00
niansa
909f20628d Fixed some crashes 2021-02-12 19:06:51 +01:00
niansa
bee04490ee Updated CDL-DB 2021-02-12 16:27:54 +01:00
niansa
2d0c07f03a Fixed accidental globalchat blacklist invertion 2021-02-12 16:05:27 +01:00
niansa
623635ccda Updated CDL DB 2021-02-12 15:34:43 +01:00
niansa
942e3c96a0 Updated CDL and CDL-DB 2021-02-12 15:11:54 +01:00
niansa
f2b1d70400 Updated CDL 2021-02-09 13:15:33 +01:00
niansa
6ae1302daa Updated CDL 2021-02-09 11:08:05 +01:00
niansa
226b1ba9b7 Updated CDL 2021-02-06 18:08:59 +01:00
niansa
159d623a8f Updated CDL 2021-02-06 18:05:40 +01:00
niansa
fda1e17372 Updated CDL 2021-02-05 17:34:26 +01:00
niansa
d9574c3403 Updated CDL 2021-02-05 17:22:52 +01:00
niansa
43635ef4ca Minor code improvements 2021-02-04 14:52:01 +01:00
niansa
c8476f8ea0 Updated CDL 2021-02-03 15:27:42 +01:00
niansa
3f90aa5a69 Added autoroles 2021-01-24 14:16:37 +01:00
niansa
6e9dc5480a Updated CDL DB 2021-01-24 12:47:29 +01:00
niansa
729ed566e1 Updated CDL 2021-01-21 17:31:17 +01:00
niansa
0b039cadfd Marked _end as may be unused 2021-01-19 18:26:51 +01:00
niansa
d439c62e39 Updated CDL 2021-01-19 18:06:22 +01:00
niansa
6318978c60 Updated CDL 2021-01-19 16:57:27 +01:00
niansa
4ea6dbcf73 Fixed bug when not being able to send welcome DM 2021-01-18 15:23:19 +01:00
niansa
36d4d93de0 Updated CDL to fix crash when sending welcome message 2021-01-18 15:20:50 +01:00
niansa
6a3e846844 Updated CDL 2021-01-14 16:08:42 +01:00
niansa
040b876458 Fixed bug in presence 2021-01-14 16:04:33 +01:00
23 changed files with 884 additions and 358 deletions

View file

@ -10,7 +10,7 @@ build:
- cp config.json.example config.json - cp config.json.example config.json
- mkdir build - mkdir build
- cd build - cd build
- cmake .. - cmake .. -DFORCE_SUBMODULE_CDLPP=Yes
- cmake .. -DCMAKE_BUILD_TYPE=Release - cmake .. -DCMAKE_BUILD_TYPE=Release
- make -j"$(nproc)" - make -j"$(nproc)"
- cd .. - cd ..

6
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "lib/cdlpp"]
path = lib/cdlpp
url = https://gitlab.com/niansa/cdlpp.git
[submodule "lib/cdlpp-db"] [submodule "lib/cdlpp-db"]
path = lib/cdlpp-db path = lib/cdlpp-db
url = https://gitlab.com/niansa/cdlpp-db.git url = https://gitlab.com/niansa/cdlpp-db.git
[submodule "lib/cdlpp"]
path = lib/cdlpp
url = https://gitlab.com/niansa/cdlpp.git

View file

@ -5,37 +5,57 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(embed.cmake) include(embed.cmake)
include(lib/cdlpp/use.cmake) include(cdlpp.cmake)
include(lib/cdlpp-db/use.cmake)
cdlpp(lib/cdlpp)
cdlppdb(lib/cdlpp-db)
add_compile_definitions(COMPILER_ID="${CMAKE_CXX_COMPILER_ID}") add_compile_definitions(COMPILER_ID="${CMAKE_CXX_COMPILER_ID}")
add_compile_definitions(COMPILER_VERSION="${CMAKE_CXX_COMPILER_VERSION}") add_compile_definitions(COMPILER_VERSION="${CMAKE_CXX_COMPILER_VERSION}")
add_compile_definitions(COMPILER_PLATFORM="${CMAKE_CXX_PLATFORM_ID}") add_compile_definitions(COMPILER_PLATFORM="${CMAKE_CXX_PLATFORM_ID}")
add_executable(tuxiflux add_executable(tuxiflux
${CDLPP_SRC_FILES}
${CDLPPDB_SRC_FILES}
bot/src/cust.cpp bot/src/cust.cpp
bot/src/help_pages.cpp bot/src/help_pages.cpp
bot/modules/useful.cpp bot/modules/useful.cpp
bot/modules/servermanagement.cpp bot/modules/servermanagement.cpp
bot/modules/warns.cpp
bot/modules/misc.cpp bot/modules/misc.cpp
bot/modules/help.cpp bot/modules/help.cpp
bot/modules/money.cpp bot/modules/money.cpp
bot/modules/fun.cpp bot/modules/fun.cpp
bot/modules/eval.cpp
) )
target_include_directories(tuxiflux PRIVATE include_directories(
${CDLPP_INCLUDE_DIRS}
${CDLPPDB_INCLUDE_DIRS}
bot/include/ bot/include/
lib/cdlpp-db/include/
${EMBED_OUTDIR} ${EMBED_OUTDIR}
) )
cdlpp_libs() find_package(cpuid QUIET)
cdlppdb_libs() if (cpuid_FOUND)
target_link_libraries(${PROJECT_NAME} PUBLIC
cpuid::cpuid)
add_compile_definitions(HAS_CPUID)
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(fmt REQUIRED IMPORTED_TARGET fmt)
target_link_libraries(${PROJECT_NAME} PUBLIC
PkgConfig::fmt cdlpp-db)
use_cdlpp(${PROJECT_NAME} lib/cdlpp)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(ENABLE_EVAL "ENABLE_EVAL" ON)
if (${ENABLE_EVAL})
target_link_libraries(${PROJECT_NAME} PUBLIC
dl)
add_compile_definitions(ENABLE_EVAL)
endif()
endif()
configure_file(config.json ./ COPYONLY) configure_file(config.json ./ COPYONLY)
embed(help/*) embed(help/*)
add_subdirectory(lib/cdlpp-db)
add_compile_definitions(WITH_CDLPPDB)

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# Tuxiflux
A feature-filled free and open source Discord bot based on CDL++, Discord++ and Boost ASIO!
This bot is the first big bot written on CDL++, so in case you want to learn how to use that framework... Come on it's an open source project, just read the source code and learn!
## Default prefix
The default prefix is `t#`
## Commands
For a list of commands, this out `help/help_1.txt`!
## Setup
1. Install [CDL++](https://gitlab.com/niansa/cdlpp)
2. Copy `config.json.example` to `config.json` and fill it
3. Clone submodules: `git submodule update --init --depth 1 --recursive`
4. Make and chdir into build directory: `mkdir build && cd build`
5. Configure the project: `cmake ..`
6. Get everything compiled: `make -j$(nproc)`
7. Run the bot: `./tuxiflux`
8. Enjoy!
Hint: the database does not need any special setup. I can recommend [ElephantSQL](https://elephantsql.com) for simple setups.
## Living example
Here's a running instance of Tuxifan ready for daily use: [Discord Bot Invite](https://discordapp.com/api/oauth2/authorize?client_id=788310535799308288&permissions=8&scope=applications.commands%20bot)

View file

@ -1,16 +1,25 @@
#pragma once #pragma once
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <cdlpp/bot.hpp>
#include <cdlpp/cdltypes-incomplete.hpp>
#include "database.hpp" #include "database.hpp"
#include "bot.hpp"
#include "abstract/message.hpp"
extern const std::string default_prefix; extern const std::string default_prefix;
extern std::map<uint64_t, std::string> prefix_cache; extern std::map<uint64_t, std::string> prefix_cache;
void in_main(); void in_main();
void on_message(CMessage msg, std::function<void (CMessage)> cb); void on_message(CDL::CMessage msg, std::function<void (CDL::CMessage)> cb);
void on_error(const std::string& message); void on_error(const std::string& message);
const std::string get_prefix(CChannel channel); void get_prefix(CDL::CChannel channel, std::function<void (const std::string&)> cb);
bool is_globalchat(CChannel channel); bool is_globalchat(CDL::CChannel channel);
constexpr uint32_t color_ok = 0x00ff00,
color_err = 0xff0000,
color_warn = 0xff6600;
inline nlohmann::json simpleEmbed(const std::string& msg, uint32_t color = color_ok) {
return {{"description", msg}, {"color", color}};
}

View file

@ -4,7 +4,8 @@ namespace db_templates {
enum type { enum type {
guild, guild,
user, user,
_end, // Must be last one before _default member,
_end [[maybe_unused]], // Must be last one before _default
_default = guild // Must be last one _default = guild // Must be last one
}; };
} }

View file

@ -1,3 +1,4 @@
#pragma once #pragma once
#include "cust.hpp"
#define NO_SUCH_USER ":warning: I couldn't find the user you've requested. It may not have been cached yet.\n:information_source: Try mentioning the user instead" #define NO_SUCH_USER simpleEmbed(":warning: I couldn't find the user you've requested. It may not have been cached yet.\n:information_source: Try mentioning the user instead", color_err)

View file

@ -1,5 +1,5 @@
#pragma once #pragma once
#include "bot.hpp" #include <cdlpp/bot.hpp>
#include "abstract/channel.hpp" #include <cdlpp/cdltypes-incomplete.hpp>
void send_badusage(CChannel channel, const std::string& command); void send_badusage(CDL::CChannel channel, const std::string& command);

83
bot/modules/eval.cpp Normal file
View file

@ -0,0 +1,83 @@
#ifdef ENABLE_EVAL
#include <string>
#include <filesystem>
#include <cdlpp/cdltypes.hpp>
#include <boost/process.hpp>
#include <dlhandle.hpp>
#include "permassert.hpp"
using namespace std;
using namespace CDL;
class Eval {
static void eval(CMessage msg, CChannel channel, cmdargs& args) {
std::error_code ec;
// Only bot owner may use this
if (msg->author->id != env.settings["owner"]) {
return;
}
// Check args
if (args.empty()) {
return;
}
// Generate code
filesystem::remove("./codef.cpp", ec);
ofstream codef("./codef.cpp");
codef << "#include <cdlpp/cdltypes.hpp>\n"
"#include <dlhandle.hpp>\n"
"extern \"C\"\n"
"void eval_main(CDL::CMessage msg, CDL::CChannel channel) {\n"
"using namespace CDL;"
<< args[0] <<
"}";
codef.close();
// Compile code
channel->start_typing([=] (const bool) {
string libnme = "./codef_"+to_string(msg->id)+".so";
boost::process::async_system(*env.aioc, [=] (boost::system::error_code, int code) {
std::error_code ec;
filesystem::remove("./codef.cpp", ec);
// Check compiler result
if (code != 0) {
channel->send(":warning: Compilation failed");
return;
}
// Run code
try {
auto evalo = new Dlhandle(libnme);
auto fnc = evalo->get<void(CMessage, CChannel)>("eval_main");
remove(libnme.c_str()); // Yep we can safely delete the file; it's still in memory
if (not fnc) {
channel->send(":warning: Failed to find main function");
delete evalo; // Delete evalo directly
} else {
fnc(msg, channel);
// Delete evalo after a minute
/*auto t = new boost::asio::deadline_timer(*env.aioc, boost::posix_time::minutes(1));
t->async_wait([=] (const boost::system::error_code&) {
delete t;
delete evalo
});*/ // We could do this to not have memory leaks but I do not expect the owner to execute that many expressions anyways
}
} catch (Dlhandle::Exception&) {
channel->send(":warning: Loading failed");
}
}, "gcc -std=c++17 ./codef.cpp -shared -fPIC -l cdlpp -o "+libnme);
});
}
public:
Eval() {
using namespace CDL;
// Commands
register_command("eval", eval, 1);
}
};
static Eval eval;
#endif

View file

@ -1,13 +1,13 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <random> #include <random>
#include <cdlpp/cdltypes.hpp>
#include "cust.hpp" #include "cust.hpp"
#include "database.hpp" #include "database.hpp"
#include "generic_msgs.h" #include "generic_msgs.h"
#include "help.hpp" #include "help.hpp"
#include "cdltypes.hpp"
using namespace std; using namespace std;
using namespace CDL;
class Fun { class Fun {
@ -22,25 +22,21 @@ class Fun {
try { try {
min = stoi(args[0]); min = stoi(args[0]);
max = stoi(args[1]); max = stoi(args[1]);
} catch (std::exception&) { } catch (exception&) {
channel->send(":warning: **Both arguments** have to be numbers in range!"); channel->send_embed(simpleEmbed(":warning: **Both arguments** have to be numbers in range!", color_err));
return; return;
} }
// Check numbers // Check numbers
if (min > max) { if (min > max) {
// Swap numbers if they are the wrong way around // Swap numbers if they are the wrong way around
int tmp = max; swap(min, max);
max = min;
min = tmp;
} }
// Initialise generator // Initialise generator
mt19937 gen(msg->id); mt19937 gen(msg->id);
uniform_int_distribution<> distr(min, max); uniform_int_distribution<> distr(min, max);
// Send result // Send result
auto rndno = distr(gen); auto rndno = distr(gen);
channel->send(":game_die:...", [rndno] (CMessage msg) { channel->send_embed(simpleEmbed(":game_die: **"+to_string(rndno)+"**"));
msg->edit(":game_die: **"+to_string(rndno)+"**");
});
} }
static void eightball(CMessage msg, CChannel channel, CDL::cmdargs& args) { static void eightball(CMessage msg, CChannel channel, CDL::cmdargs& args) {
@ -53,13 +49,25 @@ class Fun {
send_badusage(channel, "8ball"); send_badusage(channel, "8ball");
return; return;
} }
// Get "random" answer
mt19937 gen(msg->id);
uniform_int_distribution<> distr(0, static_cast<int>(answers.size() - 1));
// Send answer // Send answer
channel->send(":crystal_ball: "+answers[static_cast<uint64_t>(distr(gen))]); channel->send_embed(simpleEmbed(":crystal_ball: "+answers[msg->id % (answers.size() - 1)]));
} }
static void tpdne(CMessage msg, CChannel channel, CDL::cmdargs&) {
channel->send("https://thispersondoesnotexist.com/image?"+to_string(msg->id));
}
static void tcdne(CMessage msg, CChannel channel, CDL::cmdargs&) {
channel->send("https://thiscatdoesnotexist.com/?"+to_string(msg->id));
}
static void trdne(CMessage msg, CChannel channel, CDL::cmdargs&) {
channel->send("https://thisrentaldoesnotexist.com/img-new/hero.jpg?"+to_string(msg->id));
}
static void thdne(CMessage msg, CChannel channel, CDL::cmdargs&) {
channel->send("https://thishorsedoesnotexist.com/?"+to_string(msg->id));
}
public: public:
@ -68,6 +76,10 @@ public:
// Commands // Commands
register_command("random", random, 2); register_command("random", random, 2);
register_command("8ball", eightball, 1); register_command("8ball", eightball, 1);
register_command("notaperson", tpdne, NO_ARGS);
register_command("notacat", tcdne, NO_ARGS);
register_command("notahorse", thdne, NO_ARGS);
register_command("notarental", trdne, NO_ARGS);
} }
}; };

View file

@ -2,128 +2,133 @@
#include <ostream> #include <ostream>
#include <fstream> #include <fstream>
#include <fmt/format.h> #include <fmt/format.h>
#include "extras.hpp" #include <cdlpp/extras.hpp>
#include <cdlpp/cdltypes.hpp>
#include "cust.hpp" #include "cust.hpp"
#include "help_pages.hpp" #include "help_pages.hpp"
#include "cdltypes.hpp"
using namespace std; using namespace std;
using namespace CDL;
static json syntaxes = json::parse(help_files.find("syntaxes")->second); static json syntaxes = json::parse(help_files.find("syntaxes")->second);
void send_badusage(CChannel channel, const string& command) { void send_badusage(CChannel channel, const string& command) {
// Get prefix
auto prefix = get_prefix(channel);
// Check if any usable syntax is listed to command // Check if any usable syntax is listed to command
if (not syntaxes.contains(command)) { if (not syntaxes.contains(command)) {
channel->send(":warning: Incorrect command usage"); channel->send_embed(simpleEmbed(":warning: Incorrect command usage", color_err));
return; return;
} }
auto syntax = syntaxes[command]; auto syntax = syntaxes[command];
// Check if badusage text exists // Get prefix
if (syntax.contains("badusage_text")) { get_prefix(channel, [=] (const std::string& prefix) {
channel->send(fmt::format(string(syntax["badusage_text"]), prefix+command, string(syntax["usage"]))); // Check if badusage text exists
} if (syntax.contains("badusage_text")) {
// Alternatively send usage channel->send_embed(simpleEmbed(fmt::format(string(syntax["badusage_text"]), prefix+command, string(syntax["usage"])), color_err));
else { }
string x = fmt::format(":warning: Syntax: `{0} {1}`'", prefix+command, string(syntax["usage"])); // Alternatively send usage
channel->send(x); else {
} string x = fmt::format(":warning: Syntax: `{0} {1}`", prefix+command, string(syntax["usage"]));
channel->send_embed(simpleEmbed(x, color_err));
}
});
} }
class Help { class Help {
static void help(CMessage msg, CChannel channel, CDL::cmdargs& args) { static void help(CMessage msg, CChannel channel, CDL::cmdargs& args) {
string help_name; // Get prefix
string helpfile_name; get_prefix(channel, [=] (const std::string& prefix) {
bool help_mainpage = true; string help_name;
bool not_found = false; string helpfile_name;
bool from_template = false; bool help_mainpage = true;
// If no arguments given, default help page bool not_found = false;
if (args.empty()) { bool from_template = false;
help_name = "1"; // If no arguments given, default help page
} else { if (args.empty()) {
help_name = args[0]; help_name = "1";
} } else {
// The name of the file might be equal to the name of the page passed help_name = args[0];
helpfile_name = help_name;
send_help:
// Check if help page is a main page
help_mainpage = extras::is_digits(helpfile_name);
// Find given help page
auto help_file = help_files.find("help_"+helpfile_name);
if (help_file == help_files.end()) {
// Check if notfound page exists
if (not_found) {
// Error page couldn't be found
channel->send(":warning: Error page couldn't be found");
return;
} }
// Check if syntax is listed // The name of the file might be equal to the name of the page passed
if (syntaxes.contains(help_name)) { helpfile_name = help_name;
helpfile_name = "syntaxtmpl"; send_help:
from_template = true; // Check if help page is a main page
help_mainpage = extras::is_digits(helpfile_name);
// Find given help page
auto help_file = help_files.find("help_"+helpfile_name);
if (help_file == help_files.end()) {
// Check if notfound page exists
if (not_found) {
// Error page couldn't be found
channel->send(":warning: Error page couldn't be found");
return;
}
// Check if syntax is listed
if (syntaxes.contains(help_name)) {
helpfile_name = "syntaxtmpl";
from_template = true;
goto send_help;
}
// Send notfound page
helpfile_name = "notfound";
not_found = true;
goto send_help; goto send_help;
} }
// Send notfound page const string &help_text = help_file->second;
helpfile_name = "notfound"; // Send text
not_found = true; json embed;
goto send_help; {
} // Formats in 2 different ways depending on the value of not_foud
const string &help_text = help_file->second; if (not_found) {
// Send text // Page not found
json embed; string syntax_list;
{ // Build list of syntaxes string
// Formats in 2 different ways depending on the value of not_foud if (not syntaxes.empty()) {
if (not_found) { ostringstream syntax_list_builder;
// Page not found syntax_list_builder << "__Command syntax pages available__\n";
string syntax_list; for (const auto& [name, value] : syntaxes.items()) {
// Build list of syntaxes string if (not (value.contains("unlisted") and value["unlisted"].get<bool>())) {
if (not syntaxes.empty()) { syntax_list_builder << " `" << string(name) << "`,";
ostringstream syntax_list_builder; }
syntax_list_builder << "__Command syntax pages available__\n";
for (const auto& [name, value] : syntaxes.items()) {
if (not (value.contains("unlisted") and value["unlisted"].get<bool>())) {
syntax_list_builder << " `" << string(name) << "`,";
} }
syntax_list = syntax_list_builder.str();
syntax_list.pop_back(); // Remove trailing ','
} }
syntax_list = syntax_list_builder.str(); // Generate list of available syntax pages
syntax_list.pop_back(); // Remove trailing ',' embed["description"] = fmt::format(help_text, help_name, syntax_list);
}
// Generate list of available syntax pages
embed["description"] = fmt::format(help_text, help_name, syntax_list);
} else {
// Check if templating is enabled
if (from_template) {
ostringstream examples;
auto syntax = syntaxes[help_name];
// Build examples string
if (syntax.contains("examples")) {
examples << "**Examples:**\n";
for (const auto& example : syntax["examples"].items()) {
examples << fmt::format("• `{0}{1} {2}`\n", get_prefix(channel), help_name, string(example.value()));
}
}
// Apply template
embed["description"] = fmt::format(help_text, help_name, string(syntax["usage"]), examples.str());
} else if (help_mainpage) {
embed["description"] = fmt::format(help_text, get_prefix(channel), extras::get_bot_invite_link(), string(env.settings["supportdiscord"]));
} else { } else {
embed["description"] = help_text; // Check if templating is enabled
if (from_template) {
ostringstream examples;
auto syntax = syntaxes[help_name];
// Build examples string
if (syntax.contains("examples")) {
examples << "**Examples:**\n";
for (const auto& example : syntax["examples"].items()) {
examples << fmt::format("• `{0}{1} {2}`\n", prefix, help_name, string(example.value()));
}
}
// Apply template
embed["description"] = fmt::format(help_text, help_name, string(syntax["usage"]), examples.str());
} else if (help_mainpage) {
embed["description"] = fmt::format(help_text, prefix, extras::get_bot_invite_link(), string(env.settings["supportdiscord"]));
} else {
embed["description"] = help_text;
}
}
// The rest of the embed can differ too
if (help_mainpage) {
embed["thumbnail"]["url"] = extras::get_avatar_url(env.self);
embed["footer"]["icon_url"] = extras::get_avatar_url(msg->author);
embed["footer"]["text"] = "Use "+prefix+"help <command> to see its syntax";
embed["color"] = 0x0000ff; // Blue
} else {
embed["thumbnail"]["url"] = extras::get_avatar_url(msg->author);
} }
} }
// The rest of the embed can differ too channel->send_embed(embed);
if (help_mainpage) { });
embed["thumbnail"]["url"] = extras::get_avatar_url(env.self);
embed["footer"]["icon_url"] = extras::get_avatar_url(msg->author);
embed["footer"]["text"] = "Use "+get_prefix(channel)+"help <command> to see its syntax";
embed["color"] = 0x0000ff; // Blue
} else {
embed["thumbnail"]["url"] = extras::get_avatar_url(msg->author);
}
}
channel->send_embed(embed);
} }

View file

@ -3,13 +3,16 @@
#include <ostream> #include <ostream>
#include <chrono> #include <chrono>
#include <exception> #include <exception>
#ifdef HAS_CPUID
# include <libcpuid.h>
#endif
#include "cust.hpp" #include "cust.hpp"
#include "help.hpp" #include "help.hpp"
#include "generic_msgs.h" #include "generic_msgs.h"
#include "permassert.hpp" #include "permassert.hpp"
#include "cdltypes.hpp" #include <cdlpp/cdltypes.hpp>
using namespace std; using namespace std;
using namespace CDL;
class Misc { class Misc {
@ -17,7 +20,7 @@ class Misc {
timer::time_point startup_time; timer::time_point startup_time;
static void invite(CMessage , CChannel channel, CDL::cmdargs&) { static void invite(CMessage, CChannel channel, CDL::cmdargs&) {
json embed = { json embed = {
{"description", ":speech_balloon: You can invite me using [this]("+extras::get_bot_invite_link()+") link"}, {"description", ":speech_balloon: You can invite me using [this]("+extras::get_bot_invite_link()+") link"},
{"color", 0xffffff} {"color", 0xffffff}
@ -51,18 +54,22 @@ class Misc {
// TODO // TODO
}; };
// Create channel // Create channel
Channel newchannel({}); using namespace Permissions;
{ BaseChannel newchannel = {
newchannel.name = "tuxiflux-globalchat"; .name = "tuxiflux-globalchat",
using namespace Permissions; .overwrites = {
newchannel.overwrites[env.self->id] = { {
env.self->id, env.self->id,
PermissionOverwrite::member, {
VIEW_CHANNEL | SEND_MESSAGES | MANAGE_MESSAGES, .id = env.self->id,
0 .type = PermissionOverwrite::member,
}; .allow = VIEW_CHANNEL | SEND_MESSAGES | MANAGE_MESSAGES,
} .deny = 0
server->create_channel(&newchannel, [channel] (CChannel newchannel) { }
}
}
};
server->create_channel(newchannel, [channel] (CChannel newchannel) {
if (newchannel) { if (newchannel) {
channel->send(":speech_balloon: "+newchannel->get_mention()+" was created and set up. Have fun!"); channel->send(":speech_balloon: "+newchannel->get_mention()+" was created and set up. Have fun!");
} else { } else {
@ -76,30 +83,51 @@ class Misc {
// Get Uptime in hours // Get Uptime in hours
timer::duration uptime = timer::now() - startup_time; timer::duration uptime = timer::now() - startup_time;
auto uptime_hours = uptime.count() / 1000000000 / 60 / 60; auto uptime_hours = uptime.count() / 1000000000 / 60 / 60;
// Get mem usage string // Get prefix
ostringstream mem_use; get_prefix(channel, [=] (const std::string& prefix) {
# ifdef extras_get_mem_supported # ifdef extras_get_mem_supported
// Get mem usage string
ostringstream mem_use;
mem_use << "Memory usage • **" << extras::get_mem::used() << "** MB / **" << extras::get_mem::total() << "** MB\n"; mem_use << "Memory usage • **" << extras::get_mem::used() << "** MB / **" << extras::get_mem::total() << "** MB\n";
# endif # endif
// Build info text # ifdef HAS_CPUID
ostringstream text; // Get CPU data
text << "Online since • **" << uptime_hours << "** hours\n" string cpu_name;
"\n" << if (cpuid_present()) {
mem_use.str() << cpu_raw_data_t cpuid_raw;
"C++ version • **" << __cplusplus << "**\n" cpuid_get_raw_data(&cpuid_raw);
"Boost version • **" BOOST_LIB_VERSION "**\n" cpu_id_t cpuid;
"Compiler • **" COMPILER_ID " " COMPILER_VERSION " (" COMPILER_PLATFORM ")**\n" cpu_identify(&cpuid_raw, &cpuid);
"\n" cpu_name = cpuid.brand_str;
"Servers • **" << cache::guild_cache.size() << "**\n" } else {
"\n" cpu_name = "Unknown";
"Prefix in chat • `" << get_prefix(channel) << '`'; }
// Create embed # endif
json embed = { // Build info text
{"description", text.str()}, ostringstream text;
{"thumbnail", {{"url", extras::get_avatar_url(env.self)}}} text << "Online since • **" << uptime_hours << "** hours\n"
}; "\n" <<
// Send embed # ifdef extras_get_mem_supported
channel->send_embed(embed); mem_use.str() <<
# endif
# ifdef HAS_CPUID
"CPU • **" << cpu_name << "**\n"
# endif
"C++ version • **" << __cplusplus << "**\n"
"Boost version • **" BOOST_LIB_VERSION "**\n"
"Compiler • **" COMPILER_ID " " COMPILER_VERSION " (" COMPILER_PLATFORM ")**\n"
"\n"
"Servers • **" << cache::guild_cache.size() << "**\n"
"\n"
"Prefix in chat • `" << prefix << '`';
// Create embed
json embed = {
{"description", text.str()},
{"thumbnail", {{"url", extras::get_avatar_url(env.self)}}}
};
// Send embed
channel->send_embed(embed);
});
} }
static void gc_autorules(CChannel channel) { static void gc_autorules(CChannel channel) {
@ -145,7 +173,7 @@ class Misc {
return; return;
} }
// Set blacklisted boolean // Set blacklisted boolean
env.db->update(to_dbid(user_id), "GC_BLACKLISTED", newval, db_templates::user); env.db->update(to_dbid(user_id), "GC_BLACKLISTED", newval, nullptr, db_templates::user);
} }
static void restart(CMessage msg, CChannel channel, CDL::cmdargs&) { static void restart(CMessage msg, CChannel channel, CDL::cmdargs&) {
@ -175,12 +203,12 @@ public:
register_command("invite", invite, NO_ARGS); register_command("invite", invite, NO_ARGS);
register_command("setup", setup, 1); register_command("setup", setup, 1);
register_command("gc_blacklist", gc_blacklist, 2); register_command("gc_blacklist", gc_blacklist, 2);
register_command("gc_ban", gc_blacklist, 2);
register_command("restart", restart, NO_ARGS); register_command("restart", restart, NO_ARGS);
register_command("reload", reload, NO_ARGS); register_command("reload", reload, NO_ARGS);
cdl_register_nonstatic_command("about", about, NO_ARGS); cdl_register_nonstatic_command("about", about, NO_ARGS);
// Events // Events
intents::channel_create.push_back(gc_autorules); intents::channel_create.push_back(gc_autorules);
//intents::channel_update.push_back(gc_autorules);
} }
}; };

View file

@ -1,18 +1,18 @@
#include <ostream> #include <ostream>
#include <exception> #include <exception>
#include <ctime> #include <ctime>
#include <cdlpp/cdltypes.hpp>
#include "cust.hpp" #include "cust.hpp"
#include "database.hpp" #include "database.hpp"
#include "generic_msgs.h" #include "generic_msgs.h"
#include "help.hpp" #include "help.hpp"
#include "cdltypes.hpp"
using namespace std; using namespace std;
using namespace CDL;
class Money { class Money {
# define mod_money(user_id, amount) env.db->update(to_dbid(user_id), "BALANCE = BALANCE + ("+amount+")", db_templates::user) # define mod_money(user_id, cb, amount) env.db->update(to_dbid(user_id), "BALANCE = BALANCE + ("+amount+")", cb, db_templates::user)
# define get_money(user_id) env.db->get(to_dbid(user_id), "BALANCE", db_templates::user) # define get_money(user_id, cb) env.db->get<int>(to_dbid(user_id), "BALANCE", cb, db_templates::user)
static void balance(CMessage msg, CChannel channel, CDL::cmdargs& args) { static void balance(CMessage msg, CChannel channel, CDL::cmdargs& args) {
@ -29,15 +29,16 @@ class Money {
} }
// Check if target user == bot // Check if target user == bot
if (user_id == env.self->id) { if (user_id == env.self->id) {
channel->send(":warning: Sadly, I am not allowed to have money :confused:"); channel->send_embed(simpleEmbed(":warning: Sadly, I am not allowed to have money :confused:", color_err));
return; return;
} }
fetch::user(user_id, [channel] (CUser user) { fetch::user(user_id, [channel] (CUser user) {
if (not user) return; if (not user) return;
// Get values // Get values
auto balance = get_money(user->id).as<int32_t>(); get_money(user->id, [=] (auto amount) {
// Send // Send
channel->send("**"+user->get_full_name()+"** currently has **"+to_string(balance)+"** :dollar:"); channel->send_embed(simpleEmbed("**"+user->get_full_name()+"** currently has **"+to_string(amount)+"** :dollar:"));
});
}); });
} }
@ -47,23 +48,30 @@ class Money {
time(&time_raw); time(&time_raw);
tm *time = gmtime(&time_raw); tm *time = gmtime(&time_raw);
// Get last day command was executed // Get last day command was executed
auto last_yday = env.db->get(to_dbid(msg->author->id), "LAST_DAILY", db_templates::user); env.db->get<int>(to_dbid(msg->author->id), "LAST_DAILY", [=] (auto last_daily) {
// Verify that next day was reached // Verify that next day was reached
if (last_yday.as<int>() == time->tm_yday) { if (last_daily == time->tm_yday) {
channel->send(":warning: You can't get your daily money twice a day\n" channel->send_embed(simpleEmbed(":warning: You can't get your daily money twice a day\n"
":information_source: Please note that I'm running in GMT :wink:"); ":information_source: Please note that I'm running in GMT :wink:", color_err));
return; return;
} }
// Update database // Update database
int32_t daily_money = env.settings["daily_money"]; int32_t daily_money = env.settings["daily_money"];
env.db->update(to_dbid(msg->author->id), "LAST_DAILY", time->tm_yday, db_templates::user); env.db->update(to_dbid(msg->author->id), "LAST_DAILY", time->tm_yday, nullptr, db_templates::user);
mod_money(msg->author->id, to_string(daily_money)); mod_money(msg->author->id, [=] (const bool error) {
// Get new balance if (error) {
auto new_value = get_money(msg->author->id).as<int32_t>();
int32_t old_value = new_value - daily_money; } else {
// Send result // Get new balance
channel->send(":dollar: You've received your daily money!\n" get_money(msg->author->id, [=] (auto new_value) {
"**"+to_string(old_value)+"** + **"+to_string(daily_money)+"** = **"+to_string(new_value)+"**"); auto old_value = new_value - daily_money;
// Send result
channel->send_embed(simpleEmbed(":dollar: You've received your daily money!\n"
"**"+to_string(old_value)+"** + **"+to_string(daily_money)+"** = **"+to_string(new_value)+"**"));
});
}
}, to_string(daily_money));
}, db_templates::user);
} }
static void pay(CMessage msg, CChannel channel, CDL::cmdargs& args) { static void pay(CMessage msg, CChannel channel, CDL::cmdargs& args) {
@ -81,10 +89,10 @@ class Money {
} }
// Check target user // Check target user
if (target_user_id == env.self->id) { if (target_user_id == env.self->id) {
channel->send(":warning: Why do you want to give me money?"); channel->send_embed(simpleEmbed(":warning: Why do you want to give me money?", color_err));
return; return;
} else if (target_user_id == msg->author->id) { } else if (target_user_id == msg->author->id) {
channel->send(":warning: That's yourself lol"); channel->send_embed(simpleEmbed(":warning: That's yourself lol", color_err));
return; return;
} }
// Check if specified user ID actually exists and is fetchable // Check if specified user ID actually exists and is fetchable
@ -96,19 +104,19 @@ class Money {
} }
// Check if number is clean // Check if number is clean
if (not extras::is_digits(amount_str)) { if (not extras::is_digits(amount_str)) {
channel->send(":warning: The amount of money you've specified isn't valid :thinking:"); channel->send_embed(simpleEmbed(":warning: The amount of money you've specified isn't valid :thinking:", color_err));
return; return;
} }
// Pay out // Pay out
try { mod_money(msg->author->id, [=] (const bool error) {
mod_money(msg->author->id, string("-")+amount_str); if (error) {
} catch (std::exception&) { channel->send_embed(simpleEmbed(":warning: I wasn't able to pay out... Are you sure you've got enough money?", color_err));
channel->send(":warning: I wasn't able to pay out... Are you sure you've got enough money?"); } else {
return; mod_money(target_user->id, nullptr, amount_str);
} // Report success
mod_money(target_user->id, amount_str); channel->send_embed(simpleEmbed("You've sent **"+amount_str+"** :dollar: to **"+target_user->get_full_name()+"**"));
// Report success }
channel->send("You've sent **"+amount_str+"** :dollar: to **"+target_user->get_full_name()+"**"); }, string("-")+amount_str);
}); });
} }
@ -122,32 +130,29 @@ class Money {
auto insert_str = to_string(insert); auto insert_str = to_string(insert);
// Check if insert is in range // Check if insert is in range
if (not (insert != 0 and insert < 100)){ if (not (insert != 0 and insert < 100)){
channel->send(":warning: **"+insert_str+"** is not > **0** and < **100**!"); channel->send_embed(simpleEmbed(":warning: **"+insert_str+"** is not > **0** and < **100**!", color_err));
return; return;
} }
// Check if user has enough money // Check if user has enough money
if (get_money(msg->author->id).as<int32_t>() < insert) { get_money(msg->author->id, [=] (auto amount) {
channel->send(":warning: Excuse me, but you don't have enough money to do that!"); if (amount < insert) {
return; channel->send_embed(simpleEmbed(":warning: Excuse me, but you don't have enough money to do that!", color_err));
} return;
// Get "random" value }
bool lost = msg->id % 4 == 0; // Get "random" value
if (insert > 10) { bool lost = msg->id % 4 == 0;
lost =! lost; if (insert > 10) {
} lost =! lost;
// Negate insert value if lost }
if (lost) { // Do database transaction
insert = -insert; mod_money(msg->author->id, nullptr, std::string(lost?"-":"")+insert_str);
insert_str.insert(0, 1, '-'); // Report value of won
} if (lost) {
// Do database transaction channel->send_embed(simpleEmbed(":thumbsdown: You've lost **"+insert_str+"** :dollar:", color_warn));
mod_money(msg->author->id, insert_str); } else {
// Report value of won channel->send_embed(simpleEmbed(":thumbsup: You've won **"+insert_str+"** :dollar:", color_ok));
if (lost) { }
channel->send(":thumbsdown: You've lost **"+insert_str+"** :dollar:"); });
} else {
channel->send(":thumbsup: You've won **"+insert_str+"** :dollar:");
}
} }

View file

@ -1,12 +1,12 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <cdlpp/cdltypes.hpp>
#include "database.hpp" #include "database.hpp"
#include "permassert.hpp" #include "permassert.hpp"
#include "help.hpp" #include "help.hpp"
#include "cust.hpp" #include "cust.hpp"
#include "generic_msgs.h" #include "generic_msgs.h"
#include "cdltypes.hpp"
using namespace std; using namespace std;
using namespace CDL;
class Basic { class Basic {
@ -16,7 +16,9 @@ class Basic {
// Check arguments // Check arguments
if (args.empty()) { if (args.empty()) {
// Get prefix // Get prefix
channel->send(":paperclips: The individual prefix for **"+server->name+"** is `"+get_prefix(channel)+'`'); get_prefix(channel, [=] (const std::string& prefix) {
channel->send_embed(simpleEmbed(":paperclips: The individual prefix for **"+server->name+"** is `"+prefix+'`'));
});
return; return;
} }
// Check permissions // Check permissions
@ -33,29 +35,37 @@ class Basic {
prefix_cache[server->id] = newprefix; prefix_cache[server->id] = newprefix;
// Inform about success // Inform about success
if (reset) { if (reset) {
channel->send(":paperclips: Your individual prefix has been reset for your server!"); channel->send_embed(simpleEmbed(":paperclips: Your individual prefix has been reset for your server!"));
} else { } else {
channel->send(":paperclips: Your individual prefix for your server was changed to **"+newprefix+"**."); channel->send_embed(simpleEmbed(":paperclips: Your individual prefix for your server was changed to **"+newprefix+"**."));
} }
} }
inline static string welcomedm_format(CGuild guild, CUser user, const string& message) { inline static string welcomedm_format(CGuild guild, CUser user, const string& message) {
return fmt::format(message, user->username, guild->name); return fmt::format(message, user->username, guild->name);
} }
inline static string welcomedm_get(CGuild guild, CUser user) { static void welcomedm_get(CGuild guild, CUser user, std::function<void (const std::string& str)> cb) {
return welcomedm_format(guild, user, env.db->get(to_dbid(guild->id), "WELCOMEDM", db_templates::guild).as<string>()); env.db->get<string>(to_dbid(guild->id), "WELCOMEDM", [=] (auto welcomedm_str) {
if (not (welcomedm_str.empty() or welcomedm_str == "none")) {
cb(welcomedm_format(guild, user, welcomedm_str));
} else {
cb("none");
}
}, db_templates::guild);
} }
inline static void welcomedm_set(CGuild guild, const string& message) { inline static void welcomedm_set(CGuild guild, const string& message) {
env.db->update(to_dbid(guild->id), "WELCOMEDM", message, db_templates::guild); env.db->update(to_dbid(guild->id), "WELCOMEDM", message, nullptr, db_templates::guild);
} }
inline static void welcomedm_send(CMember member) { inline static void welcomedm_send(CMember member) {
member->get_user([member] (CUser user) { member->get_user([member] (CUser user) {
string welcomedm_msg = welcomedm_get(member->guild, user); welcomedm_get(member->guild, user, [=] (const std::string& welcomedm_msg) {
if (welcomedm_msg != "none") { if (welcomedm_msg != "none") {
user->get_dm([welcomedm_msg] (CChannel channel) { user->get_dm([welcomedm_msg] (CChannel channel) {
channel->send(welcomedm_msg); if (not channel) return;
}); channel->send(welcomedm_msg);
} });
}
});
}); });
} }
static void welcomedm(CMessage msg, CChannel channel, CDL::cmdargs& args) { static void welcomedm(CMessage msg, CChannel channel, CDL::cmdargs& args) {
@ -64,7 +74,9 @@ class Basic {
// Check args // Check args
if (args.empty()) { if (args.empty()) {
// Return current welcomedm // Return current welcomedm
channel->send(welcomedm_get(channel->get_guild(), msg->author)); welcomedm_get(channel->get_guild(), msg->author, [=] (const std::string& welcomedm_msg) {
channel->send(welcomedm_msg);
});
} else { } else {
// Set welcomedm // Set welcomedm
string &newmsg = args[0]; string &newmsg = args[0];
@ -76,7 +88,7 @@ class Basic {
}); });
welcomedm_set(server, newmsg); welcomedm_set(server, newmsg);
} catch (fmt::format_error& e) { } catch (fmt::format_error& e) {
channel->send(":warning: Failed to set welcome DM: "+string(e.what())); channel->send_embed(simpleEmbed(":warning: Failed to set welcome DM: "+string(e.what()), color_err));
} }
} }
} }
@ -102,13 +114,13 @@ class Basic {
auto reason = args[1]; auto reason = args[1];
auto cb = [channel, id_str, id_int, reason, permanent] (const bool error) { auto cb = [channel, id_str, id_int, reason, permanent] (const bool error) {
if (error) { if (error) {
channel->send(":warning: That didn't work. Please make sure I have the permission to ban (Use the `setup check` command) " channel->send_embed(simpleEmbed(":warning: That didn't work. Please make sure I have the permission to ban (Use the `setup check` command) "
"and that the user you've specified actually exists."); "and that the user you've specified actually exists.", color_err));
} else { } else {
// If user is in cache, show its full name; else mention // If user is in cache, show its full name; else mention
auto user = cache::get_user(id_int); auto user = cache::get_user(id_int);
auto identifier = user?user->get_full_name():"<@"+id_str+">"; auto identifier = user?user->get_full_name():"<@"+id_str+">";
channel->send(":smiling_imp: **"+identifier+"** was "+string(permanent?"banned":"kicked")+". (**"+reason+"**)"); channel->send_embed(simpleEmbed(":smiling_imp: **"+identifier+"** was "+string(permanent?"banned":"kicked")+". (**"+reason+"**)"));
} }
}; };
if (permanent) { if (permanent) {
@ -144,9 +156,9 @@ class Basic {
return; return;
} else { } else {
if (res->second->nick.empty()) { if (res->second->nick.empty()) {
channel->send(":information_source: **"+args[0]+"** does not have a nickname"); channel->send_embed(simpleEmbed(":information_source: **"+args[0]+"** does not have a nickname"));
} else { } else {
channel->send(":information_source: The nickname of **"+args[0]+"** is **"+res->second->nick+"**"); channel->send_embed(simpleEmbed(":information_source: The nickname of **"+args[0]+"** is **"+res->second->nick+"**"));
} }
} }
} else { } else {
@ -163,19 +175,95 @@ class Basic {
// Perform change // Perform change
channel->get_guild()->set_nick(user_id, newnick, [args, newnick, channel] (const bool error) { channel->get_guild()->set_nick(user_id, newnick, [args, newnick, channel] (const bool error) {
if (error) { if (error) {
channel->send(":warning: I couldn't change their nickname.\n" channel->send_embed(simpleEmbed(":warning: I couldn't change their nickname.\n"
":information_source: Do I have enough permissions? Is the nick even valid?"); ":information_source: Do I have enough permissions? Is the nick even valid?", color_err));
} else { } else {
if (newnick.empty()) { if (newnick.empty()) {
channel->send(":information_source: The nickname of **"+args[0]+"** was removed."); channel->send_embed(simpleEmbed(":information_source: The nickname of **"+args[0]+"** was removed."));
} else { } else {
channel->send(":information_source: The nickname of **"+args[0]+"** was changed to **"+newnick+"**"); channel->send_embed(simpleEmbed(":information_source: The nickname of **"+args[0]+"** was changed to **"+newnick+"**"));
} }
} }
}); });
} }
} }
static std::vector<Role*> autoroles_get(CGuild guild, const std::string& autoroles_str) {
std::vector<Role*> fres;
if (not autoroles_str.empty()) {
auto autorole_strs = extras::strsplit(autoroles_str, ',');
for (const auto& autorole_str : autorole_strs) {
auto res = guild->roles.find(std::stoul(autorole_str));
if (res != guild->roles.end()) {
if (not res->second->managed) {
fres.push_back(res->second);
}
}
}
}
return fres;
}
static void autoroles_assign(CMember member) {
env.db->get<string>(to_dbid(member->guild->id), "AUTOROLES", [=] (auto autoroles_str) {
auto member_cpy = *member;
// Parse and assign them
auto autoroles = autoroles_get(member_cpy.guild, autoroles_str);
if (not autoroles.empty()) {
for (const auto& autorole : autoroles) {
member_cpy.roles.push_back(autorole->id);
}
member_cpy.commit();
}
});
}
static void autoroles(CMessage msg, CChannel channel, CDL::cmdargs& args) {
gchannelassert(channel);
permassert(msg, channel, Permissions::MANAGE_ROLES);
auto guild = channel->get_guild();
// Check args
if (args.empty()) {
// Send current autoroles
env.db->get<string>(to_dbid(guild->id), "AUTOROLES", [=] (auto autoroles_str) {
auto autoroles = autoroles_get(guild, autoroles_str);
std::ostringstream autoroles_strs;
if (autoroles.empty()) {
autoroles_strs << "**none**";
} else {
for (const auto& autorole : autoroles) {
autoroles_strs << autorole->get_mention() << ", ";
}
}
channel->send_embed({
{"title", "Current autoroles"},
{"description", autoroles_strs.str()}
});
});
} else {
// Set autoroles
std::string autoroles_str;
{
// Compose string
std::ostringstream autoroles_strs;
for (const auto& arg : args) {
autoroles_strs << arg << ',';
}
autoroles_str = autoroles_strs.str();
autoroles_str.pop_back();
}
// Check
std::vector<Role*> autoroles;
try {
autoroles = autoroles_get(guild, autoroles_str);
} catch (std::invalid_argument&) {}
if (autoroles.size() != args.size()) {
channel->send_embed(simpleEmbed(":warning: Not all roles could be located", color_err));
} else {
env.db->update(to_dbid(guild->id), "AUTOROLES", autoroles_str);
channel->send_embed(simpleEmbed("Alright, done!"));
}
}
}
public: public:
Basic() { Basic() {
@ -186,8 +274,10 @@ public:
register_command("ban", ban, 2); register_command("ban", ban, 2);
register_command("kick", kick, 2); register_command("kick", kick, 2);
register_command("nick", nick, 2); register_command("nick", nick, 2);
register_command("autoroles", autoroles, INF_ARGS);
// Events // Events
intents::guild_member_add.push_back(welcomedm_send); intents::guild_member_add.push_back(welcomedm_send);
intents::guild_member_add.push_back(autoroles_assign);
} }
}; };

View file

@ -1,16 +1,17 @@
#include <string> #include <string>
#include <ostream> #include <ostream>
#include <array> // TODO: remove when no longer needed #include <array> // TODO: remove when no longer needed
#include <cdlpp/cdltypes.hpp>
#include "cust.hpp" #include "cust.hpp"
#include "generic_msgs.h" #include "generic_msgs.h"
#include "help.hpp" #include "help.hpp"
#include "permassert.hpp" #include "permassert.hpp"
#include "cdltypes.hpp"
using namespace std; using namespace std;
using namespace CDL;
class Botinfo { class Useful {
static void serverinfo_roles(CChannel channel) { static void serverinfo_roles(CChannel channel) {
channel->get_guild([channel] (CGuild server) { channel->get_guild([channel] (CGuild server) {
// Get info text // Get info text
@ -66,7 +67,6 @@ class Botinfo {
server->get_bans([args, channel, server, server_owner] (const std::unordered_map<uint64_t, Ban*>& server_bans) { server->get_bans([args, channel, server, server_owner] (const std::unordered_map<uint64_t, Ban*>& server_bans) {
ostringstream server_features; ostringstream server_features;
auto server_avatar_url = extras::get_avatar_url(server); auto server_avatar_url = extras::get_avatar_url(server);
auto server_creation_date = "not yet implemented";
size_t server_members = 0, size_t server_members = 0,
server_bots = 0, server_bots = 0,
server_humans = 0, server_humans = 0,
@ -100,54 +100,53 @@ class Botinfo {
if (server->features.size() == 0) { if (server->features.size() == 0) {
server_features << "**none**"; server_features << "**none**";
} else { } else {
for (auto& feature : server->features) { for (auto feature : server->features) {
server_features << feature_get_nice(feature) << ", "; server_features << feature_get_nice(feature) << ", ";
} }
} }
auto server_featuresstr = server_features.str();
// Get info text // Get info text
string prefix = get_prefix(channel); get_prefix(channel, [=] (const std::string& prefix) {
ostringstream info_text; ostringstream info_text;
info_text << "**Owner**\n" info_text << "**Owner**\n"
"" << server_owner->get_full_name() << " (`" << server_owner->id << "`)\n" "" << server_owner->get_full_name() << " (`" << server_owner->id << "`)\n"
"\n" "\n"
"**Server region**\n" "**Server region**\n"
"" << server->region << "\n" "" << server->region << "\n"
"\n" "\n"
"**Members**\n" "**Members**\n"
"• **" << server_members << "** members, thereof **" << server_bots << "** bots and **" << server_humans << "** humans\n" "• **" << server_members << "** members, thereof **" << server_bots << "** bots and **" << server_humans << "** humans\n"
"• **" << server_bans.size() << "** users banned\n" "• **" << server_bans.size() << "** users banned\n"
"\n" "\n"
"**Channels**\n" "**Channels**\n"
"• **" << server_textchannels << "** text channels\n" "• **" << server_textchannels << "** text channels\n"
"• **" << server_voicechannels << "** voice channels\n" "• **" << server_voicechannels << "** voice channels\n"
"• **" << server_categories << "** categories\n" "• **" << server_categories << "** categories\n"
"\n" "\n"
"**Serverboost**\n" "**Serverboost**\n"
"• Level: **" << server->premium_tier << "**\n" "• Level: **" << server->premium_tier << "**\n"
"• Boosts: **" << server->premium_subscription_count << "**\n" "• Boosts: **" << server->premium_subscription_count << "**\n"
"\n" "\n"
"**Features**\n" "**Features**\n"
"" << server_features.str() << "\n" "" << server_featuresstr << "\n"
"\n" "\n"
"**Server created**\n" "**Emojis**\n"
"" << server_creation_date << "\n" "• Please use `" << prefix << "serverinfo emojis`\n"
"\n" "\n"
"**Emojis**\n" "**Role**\n"
"• Please use `" << prefix << "serverinfo emojis`\n" "• Please use `" << prefix << "serverinfo roles`";
"\n"
"**Role**\n"
"• Please use `" << prefix << "serverinfo roles`";
// Send info text // Send info text
json embed = { json embed = {
{"title", server->name + " (" + to_string(server->id) + ')'}, {"title", server->name + " (" + to_string(server->id) + ')'},
{"description", info_text.str()}, {"description", info_text.str()},
}; };
if (not server_avatar_url.empty()) { if (not server_avatar_url.empty()) {
embed["thumbnail"]["url"] = server_avatar_url; embed["thumbnail"]["url"] = server_avatar_url;
} }
channel->send_embed(embed); channel->send_embed(embed);
});
}); });
}); });
} }
@ -179,17 +178,32 @@ class Botinfo {
} }
} }
static void opensource(CMessage, CChannel channel, CDL::cmdargs&) {
channel->send_embed(simpleEmbed("**Tuxiflux** is [FOSS](https://gitlab.com/niansa/tuxiflux) (**f**ree **o**pen **s**ource **s**oftware).\n"
"This means that you are free to view, modify, and redistribute the source code under the terms of the MIT license.\n"
"\n"
"Make your code open source today!\n"
" - Worried about people stealing the code? If that happens, be proud! Bad code doesn't get stolen, good code does!\n"
" - Your code style is so bad that you don't want anyone to see it? No problem! People might even help you improve it!\n"
" - Show people your open source project! If it's interesting and/or well documented enough, some will help!\n"
" - Help others - without doing anything! People learn a lot just by playing around with other open source projects!\n"
"Btw, [Fosshost](https://fosshost.org) offers free hosting for open source projects that need it!\n"
"\n"
" -> Didn't convince you? Read [this](https://www.codeproject.com/articles/5410/why-open-source) nice article!"));
}
public: public:
Botinfo() { Useful() {
using namespace CDL; using namespace CDL;
// Commands // Commands
register_command("serverinfo", serverinfo, 1); register_command("serverinfo", serverinfo, 1);
register_command("avatar", avatar, 1); register_command("avatar", avatar, 1);
register_command("opensource", opensource, 1);
} }
}; };
static Botinfo botinfo; static Useful botinfo;

165
bot/modules/warns.cpp Normal file
View file

@ -0,0 +1,165 @@
#include <vector>
#include <memory>
#include <cdlpp/cdltypes.hpp>
#include "help.hpp"
#include "generic_msgs.h"
#include "permassert.hpp"
using namespace std;
using namespace CDL;
class Warns {
inline static string get_db_identifier(uint64_t user_id, uint64_t guild_id) {
return to_dbid(user_id)+' '+to_dbid(guild_id);
}
static void warns(CMessage msg, CChannel channel, cmdargs& args) {
auto guild = channel->get_guild();
// Get target member
uint64_t target_user;
if (args.size() == 1) {
target_user = extras::find_user_id(guild, args[0]);
// Check if member was found
if (not target_user) {
channel->send(NO_SUCH_USER);
return;
}
} else {
target_user = msg->author->id;
}
auto identifier = get_db_identifier(target_user, guild->id);
// Get amount of warnings
env.db->get<int>(identifier, "WARNS", [=] (int warns_a) {
// Check if member has any warnings
if (warns_a) {
// Create list of warnings in heap
auto warnings = make_shared<vector<string>>();
warnings->reserve(warns_a);
// Copy amount of warnings to heap
auto hwarns = make_shared<int>(warns_a);
// Define functionm that shows the result
auto show_warns = [=] () {
// Get user
fetch::user(target_user, [=] (CUser target) {
// Check if user could be found
if (not target_user) {
channel->send(NO_SUCH_USER);
return;
}
// Generate string
ostringstream text;
for (const auto& reason : *warnings) {
text << " - " << reason << '\n';
}
// Send as embed
channel->send_embed({
{"title", "Warnings to "+target->get_full_name()},
{"description", text.str()}
});
});
};
// Define reader for next warning
auto next_warn = make_shared<function<void ()>>();
*next_warn = [=] () {
if ((*hwarns)--) {
env.db->get<string>(identifier, "WARN"+to_string(*hwarns), [=] (auto warnstr) {
warnings->push_back(warnstr);
(*next_warn)();
}, db_templates::member);
} else {
// Show warnings
show_warns();
}
};
(*next_warn)();
} else {
channel->send_embed(simpleEmbed(":warning: This user has no warnings!", color_warn));
}
}, db_templates::member);
}
static void warn(CMessage msg, CChannel channel, cmdargs& args) {
auto guild = channel->get_guild();
// Check permissions
permassert(msg, channel, Permissions::BAN_MEMBERS);
// Check arguments
if (args.size() != 2) {
send_badusage(channel, "warn");
return;
}
// Check the mentioned user
auto target_user = extras::find_user_id(guild, args[0]);
// Check if user was found
if (not target_user) {
channel->send(NO_SUCH_USER);
return;
}
// Check how many time the user has been warned already
auto identifier = get_db_identifier(target_user, guild->id);
env.db->get<int>(identifier, "WARNS", [=] (auto warns_a) {
// Increase amount of warns, add warning and check if 3 warnings were reached
if (++warns_a <= 3) {
// Set reason
env.db->update(identifier, "WARN"+to_string(warns_a-1), args[1], [=] (const bool) {
// Update counter
env.db->update(identifier, "WARNS", warns_a, [=] (const bool) {
// Return list of warnings
cmdargs fakeargs = {to_string(target_user)};
warns(nullptr, channel, fakeargs);
// Ban the member if required
if (warns_a == 3) {
guild->ban(target_user, "3 warnings");
channel->send(args[0]+" was **banned**!");
}
}, db_templates::member);
}, db_templates::member);
} else {
channel->send_embed(simpleEmbed(":warning: The user already has 3 warnings!", color_err));
}
}, db_templates::member);
}
static void unwarn(CMessage msg, CChannel channel, cmdargs& args) {
auto guild = channel->get_guild();
// Check permissions
permassert(msg, channel, Permissions::BAN_MEMBERS);
// Check arguments
if (args.size() != 1) {
send_badusage(channel, "unwarn");
return;
}
// Check the mentioned user
auto target_user = extras::find_user_id(guild, args[0]);
// Check if user was found
if (not target_user) {
channel->send(NO_SUCH_USER);
return;
}
// Decrease warn counter
env.db->update(get_db_identifier(target_user, guild->id), "WARNS = WARNS - 1", [=] (const bool error) {
if (not error) {
channel->send_embed(simpleEmbed("This members last warning was removed"));
} else {
channel->send_embed(simpleEmbed(":warning: This member does not yet have any warnings", color_err));
}
}, db_templates::member);
}
public:
Warns() {
register_command("warns", warns, 1);
register_command("warn", warn, 2);
register_command("unwarn", unwarn, 1);
intents::guild_ban_remove.push_back([] (CGuild server, const Ban& ban) {
// Reset warnings for the unbanned user
env.db->update(get_db_identifier(ban.user->id, server->id), "WARNS", 0, nullptr, db_templates::member);
});
}
};
static Warns warns;

View file

@ -3,11 +3,12 @@
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <cdlpp/cdltypes.hpp>
#include "database.hpp" #include "database.hpp"
#include "db_templates.hpp" #include "db_templates.hpp"
#include "cdltypes.hpp"
#include "cust.hpp" #include "cust.hpp"
using namespace std; using namespace std;
using namespace CDL;
const string default_prefix = "t#"; const string default_prefix = "t#";
std::map<uint64_t, std::string> prefix_cache; std::map<uint64_t, std::string> prefix_cache;
@ -18,12 +19,17 @@ static const std::vector<std::string> get_table_tmpl(db_templates::type type) {
switch (type) { switch (type) {
case guild: case guild:
return {"PREFIX TEXT ", return {"PREFIX TEXT ",
"WELCOMEDM TEXT "}; "WELCOMEDM TEXT ",
"AUTOROLES TEXT "};
case user: case user:
return {"BALANCE INT CHECK(BALANCE > -1) ", return {"BALANCE INT CHECK(BALANCE > -1) ",
"LAST_DAILY INT ", "LAST_DAILY INT ",
"GC_BLACKLISTED BOOLEAN "}; "GC_BLACKLISTED BOOLEAN "};
case _end: break; case member:
return {"WARN0 TEXT ",
"WARN1 TEXT ",
"WARN2 TEXT ",
"WARNS INT CHECK(WARNS >= 0) "};
} }
return {}; return {};
} }
@ -33,15 +39,22 @@ static const std::map<std::string, std::string> get_record_tmpl(db_templates::ty
case guild: case guild:
return { return {
{"PREFIX", default_prefix}, {"PREFIX", default_prefix},
{"WELCOMEDM", "none"} {"WELCOMEDM", ""},
{"AUTOROLES", ""}
}; };
case user: case user:
return { return {
{"BALANCE", env.settings.contains("start_money")?std::to_string(env.settings["start_money"].get<uint32_t>()):"0"}, {"BALANCE", env.settings.contains("start_money")?std::to_string(env.settings["start_money"].get<uint32_t>()):"0"},
{"LAST_DAILY", "-1"}, {"LAST_DAILY", "-1"},
{"GC_BLACKLISTED", "false"} {"GC_BLACKLISTED", "false"},
}; };
case _end: break; case member:
return {
{"WARN0", ""},
{"WARN1", ""},
{"WARN2", ""},
{"WARNS", "0"},
};
} }
return {}; return {};
} }
@ -51,21 +64,22 @@ void in_main() {
env.db = new Database(get_table_tmpl, get_record_tmpl); env.db = new Database(get_table_tmpl, get_record_tmpl);
} }
const std::string get_prefix(CChannel channel) { void get_prefix(CChannel channel, std::function<void (const std::string&)> cb) {
auto server_id = channel->guild_id; auto server_id = channel->guild_id;
if (not server_id) { if (not server_id) {
return default_prefix; cb(default_prefix);
} else { } else {
// Try cache // Try cache
auto res = prefix_cache.find(server_id); auto res = prefix_cache.find(server_id);
if (res != prefix_cache.end()) { if (res != prefix_cache.end()) {
// Cache entry found, return // Cache entry found, return
return res->second; cb(res->second);
} else { } else {
// Cache entry not found, fetch and create // Cache entry not found, fetch and create
auto prefix = env.db->get(to_dbid(server_id), "prefix").as<std::string>(); env.db->get<std::string>(to_dbid(server_id), "prefix", [=] (auto prefix) {
prefix_cache[server_id] = prefix; prefix_cache[server_id] = prefix;
return prefix; cb(prefix);
});
} }
} }
} }
@ -88,39 +102,46 @@ void on_message(CMessage msg, std::function<void (CMessage)> cb) {
cb(msg); cb(msg);
return; return;
} }
// Check if message has no content or user is blacklisted // Check if message has no content
if (msg->content.empty() or env.db->get(to_dbid(msg->author->id), "GC_BLACKLISTED", db_templates::user).as<bool>()) { if (msg->content.empty()) {
msg->remove(); msg->remove();
return; return;
} }
// Generate embed // Check if user is blacklisted
nlohmann::json embed; env.db->get<bool>(to_dbid(msg->author->id), "GC_BLACKLISTED", [=] (auto blacklisted) {
{ if (blacklisted) {
auto &user = msg->author; msg->remove();
embed["description"] = msg->content; return;
embed["author"]["name"] = user->get_full_name(); }
embed["author"]["icon_url"] = extras::get_avatar_url(user); // Generate embed
embed["footer"]["text"] = "Server • "+server->name+" | ID • "+to_string(user->id); nlohmann::json embed;
// Get color {
uint32_t color = 0x696969; // Grey auto &user = msg->author;
for (const auto& teammember : env.settings["team"].items()) { embed["description"] = msg->content;
if (teammember.value() == user->id) { embed["author"]["name"] = user->get_full_name();
// User is team member embed["author"]["icon_url"] = extras::get_avatar_url(user);
color = 0x40e0d0; // Turquoise embed["footer"]["text"] = "Server • "+server->name+" | ID • "+to_string(user->id);
// Get color
uint32_t color = 0x696969; // Grey
for (const auto& teammember : env.settings["team"].items()) {
if (teammember.value() == user->id) {
// User is team member
color = 0x40e0d0; // Turquoise
}
}
embed["color"] = color;
}
// Delete original message
msg->remove();
// Distribute message around servers
for (const auto& [guild_id, guild] : cache::guild_cache) {
for (const auto& [channel_id, channel] : guild->channels) {
if (is_globalchat(channel)) {
channel->send_embed(embed);
}
} }
} }
embed["color"] = color; }, db_templates::user);
}
// Delete original message
msg->remove();
// Distribute message around servers
for (const auto& [guild_id, guild] : cache::guild_cache) {
for (const auto& [channel_id, channel] : guild->channels) {
if (is_globalchat(channel)) {
channel->send_embed(embed);
}
}
}
}); });
}); });
} }
@ -141,8 +162,6 @@ void on_error(const std::string& message) {
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
using namespace CDL;
// Set handlers // Set handlers
handlers::get_prefix = get_prefix; handlers::get_prefix = get_prefix;
handlers::in_main = in_main; handlers::in_main = in_main;
@ -154,17 +173,20 @@ int main(int argc, char **argv) {
auto scroller = new PresenceScroller(); auto scroller = new PresenceScroller();
scroller->presences.push_back([] () { scroller->presences.push_back([] () {
Presence fres; Presence fres;
fres.activity.type = ActivityType::playing; fres.activities.push_back(Activity(
fres.activity.text = "on "+std::to_string(cache::guild_cache.size())+" servers"; "on "+std::to_string(cache::guild_cache.size())+" servers",
ActivityType::playing
));
return fres; return fres;
}); });
scroller->presences.push_back([] () { scroller->presences.push_back([] () {
Presence fres; Presence fres;
fres.activity.type = ActivityType::listening; fres.activities.push_back(Activity(
fres.activity.text = "to "+std::to_string(cache::user_cache.size())+" users"; std::to_string(cache::user_cache.size())+" users",
ActivityType::listening
));
return fres; return fres;
}); });
}); });
// Set cache props // Set cache props

19
cdlpp.cmake Normal file
View file

@ -0,0 +1,19 @@
function(use_cdlpp target cdlpp_src_dir)
find_package(PkgConfig REQUIRED)
pkg_check_modules(cdlpp IMPORTED_TARGET cdlpp)
option(FORCE_SUBMODULE_CDLPP "Use CDLPP from submodule even if installed in system" No)
if(cdlpp_FOUND AND NOT FORCE_SUBMODULE_CDLPP)
set(CDLPP_LIB PkgConfig::cdlpp)
else()
execute_process(
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND git submodule update --init --recursive --depth 1
)
add_subdirectory(${cdlpp_src_dir})
set(CDLPP_LIB "cdlpp")
endif()
target_link_libraries(${PROJECT_NAME} PUBLIC
${CDLPP_LIB})
endfunction()

View file

@ -6,6 +6,7 @@
"team": [ "team": [
<team member IDs as integers> <team member IDs as integers>
], ],
"owner": <owner user ID>,
"start_money": <how much initial money the users receive; ommit to set to 0>, "start_money": <how much initial money the users receive; ommit to set to 0>,
"daily_money": <how much daily money the users can receive; ommit to disable daily money> "daily_money": <how much daily money the users can receive; ommit to disable daily money>
} }

View file

@ -2,16 +2,16 @@ Page **1/1**
• You can call up a page with `{0}help [page]` • You can call up a page with `{0}help [page]`
:gear: **Useful** :gear: **Useful**
• `serverinfo`, `avatar` • `serverinfo`, `avatar`, `opensource`
:dollar: **Money** :dollar: **Money**
• `balance/bal`, `pay`, `daily`, `bet` • `balance/bal`, `pay`, `daily`, `bet`
:tools: **Servermanagement** :tools: **Servermanagement**
• `kick`, `ban`, `prefix`, `nick`, `welcomedm` • `kick`, `ban`, `warn`, `unwarn`, `warns`, `prefix`, `nick`, `welcomedm`, `autoroles`
:smile: **Fun** :smile: **Fun**
• `random`, `8ball` • `random`, `8ball`, `notaperson`, `notacat`, `notahorse`, `notarental`
:speech_balloon: **Misc** :speech_balloon: **Misc**
• `about`, `setup`, `invite` • `about`, `setup`, `invite`

View file

@ -38,6 +38,26 @@
"@EvilOne Scammer" "@EvilOne Scammer"
] ]
}, },
"warn": {
"badusage_text": ":warning: I need to know who to warn.",
"usage": "<@member (mention)> <reason>",
"examples": [
"@EvilOne Scammer"
]
},
"unwarn": {
"badusage_text": ":warning: I need to know who to unwarn.",
"usage": "<@member (mention)>",
"examples": [
"@GoodOne"
]
},
"warns": {
"usage": "<@member (mention)>",
"examples": [
"@tuxifan"
]
},
"balance": { "balance": {
"usage": "[user]", "usage": "[user]",
"examples": [ "examples": [
@ -87,5 +107,11 @@
"examples": [ "examples": [
"**Welcome {0} to {1}!** ..." "**Welcome {0} to {1}!** ..."
] ]
},
"autoroles": {
"usage": "[roles IDs]",
"examples": [
"802881774598356992 802881832017723403"
]
} }
} }

@ -1 +1 @@
Subproject commit a104ec0a69f8bc7b80f3ae703e7851d8bbb14e01 Subproject commit 1ffb9581d40547bee82e193691c3eedb84746347

@ -1 +1 @@
Subproject commit bb4a7f27ca79cbedc3d49e5cb28922f33b1fd5b0 Subproject commit ac425fd277f5e7c20951d102e391c229cbe499b8