turtle-wow-source-kinda/Dumps/Source Code/16 - Development_server/patch_1172/dep/dpp/commandhandler.cpp
Brian Oost a1d5bb70b2 Init
2024-08-06 18:06:40 +02:00

440 lines
13 KiB
C++

/************************************************************************************
*
* D++, A Lightweight C++ library for Discord
*
* Copyright 2021 Craig Edwards and D++ contributors
* (https://github.com/brainboxdotcc/DPP/graphs/contributors)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
************************************************************************************/
#include <dpp/commandhandler.h>
#include <dpp/cluster.h>
#include <dpp/exception.h>
#include <dpp/stringops.h>
#include <sstream>
namespace dpp {
param_info::param_info(parameter_type t, bool o, const std::string &d, const std::map<command_value, std::string> &opts) : type(t), optional(o), description(d), choices(opts)
{
}
command_source::command_source(const struct message_create_t& event) : guild_id(event.msg.guild_id), channel_id(event.msg.channel_id), command_id(0), issuer(event.msg.author), message_event(event), interaction_event(std::nullopt)
{
}
command_source::command_source(const struct interaction_create_t& event) : guild_id(event.command.guild_id), channel_id(event.command.channel_id), command_id(event.command.id), command_token(event.command.token), issuer(event.command.usr), message_event(std::nullopt), interaction_event(event)
{
}
commandhandler::commandhandler(cluster* o, bool auto_hook_events, snowflake application_id) : slash_commands_enabled(false), owner(o), app_id(application_id)
{
if (!application_id && o->me.id) {
app_id = o->me.id;
}
if (auto_hook_events) {
interactions = o->on_slashcommand([this](const dpp::slashcommand_t &event) {
this->route(event);
});
messages = o->on_message_create([this](const dpp::message_create_t & event) {
this->route(event);
});
} else {
interactions = messages = 0;
}
}
commandhandler& commandhandler::set_owner(cluster* o)
{
owner = o;
return *this;
}
commandhandler::~commandhandler()
{
if (messages && interactions) {
owner->on_message_create.detach(messages);
owner->on_slashcommand.detach(interactions);
}
}
commandhandler& commandhandler::add_prefix(const std::string &prefix)
{
prefixes.emplace_back(prefix);
if (prefix == "/") {
/* Register existing slash commands */
slash_commands_enabled = true;
}
return *this;
}
commandhandler& commandhandler::add_command(const std::string &command, const parameter_registration_t &parameters, command_handler handler, const std::string &description, snowflake guild_id)
{
command_info_t i;
i.func = handler;
i.guild_id = guild_id;
i.parameters = parameters;
commands[lowercase(command)] = i;
if (slash_commands_enabled) {
if (this->app_id.empty()) {
if (owner->me.id.empty()) {
throw dpp::logic_exception("Command handler not ready (i don't know my application ID)");
} else {
this->app_id = owner->me.id;
}
}
dpp::slashcommand newcommand;
/* Create a new global command on ready event */
newcommand.set_name(lowercase(command)).set_description(description).set_application_id(this->app_id);
for (auto& parameter : parameters) {
command_option_type cot = co_string;
switch (parameter.second.type) {
case pt_boolean:
cot = co_boolean;
break;
case pt_integer:
cot = co_integer;
break;
case pt_string:
cot = co_string;
break;
case pt_user:
cot = co_user;
break;
case pt_role:
cot = co_role;
break;
case pt_channel:
cot = co_channel;
break;
case pt_double:
cot = co_number;
break;
}
command_option opt(cot, parameter.first, parameter.second.description, !parameter.second.optional);
if (!parameter.second.choices.empty()) {
for (auto& c : parameter.second.choices) {
opt.add_choice(dpp::command_option_choice(c.second, c.first));
}
}
newcommand.add_option(opt);
}
/* Register the command */
if (guild_id) {
if (bulk_registration_list_guild.find(guild_id) == bulk_registration_list_guild.end()) {
bulk_registration_list_guild[guild_id] = {};
}
bulk_registration_list_guild[guild_id].emplace_back(newcommand);
} else {
bulk_registration_list_global.emplace_back(newcommand);
}
}
return *this;
}
commandhandler& commandhandler::register_commands()
{
for(auto & guild_commands : bulk_registration_list_guild) {
owner->guild_bulk_command_create(guild_commands.second, guild_commands.first, [guild_commands, this](const dpp::confirmation_callback_t &callback) {
if (callback.is_error()) {
this->owner->log(dpp::ll_error, "Failed to register guild slash commands for guild id '" + std::to_string(guild_commands.first) + "': " + callback.http_info.body);
}
});
}
owner->global_bulk_command_create(bulk_registration_list_global, [this](const dpp::confirmation_callback_t &callback) {
if (callback.is_error()) {
this->owner->log(dpp::ll_error, "Failed to register global slash commands: " + callback.http_info.body);
}
});
return *this;
}
bool commandhandler::string_has_prefix(std::string &str)
{
for (auto& p : prefixes) {
size_t prefix_length = utility::utf8len(p);
if (utility::utf8substr(str, 0, prefix_length) == p) {
str.erase(str.begin(), str.begin() + prefix_length);
return true;
}
}
return false;
}
/* Note that message based command routing relies on cache to resolve ping types (e.g. user, channel ping).
* There isn't really a way around this for many things because there is no 'resolved' member for it.
* We only get resolved information for the user issuing the command.
*/
void commandhandler::route(const struct dpp::message_create_t& event)
{
std::string msg_content = event.msg.content;
if (string_has_prefix(msg_content)) {
/* Put the string into stringstream to parse parameters at spaces.
* We use stringstream as it handles multiple spaces etc nicely.
*/
std::stringstream ss(msg_content);
std::string command;
ss >> command;
/* Prefixed command, the prefix was removed */
auto found_cmd = commands.find(lowercase(command));
if (found_cmd != commands.end()) {
/* Filter out guild specific commands that are not for the current guild */
if (found_cmd->second.guild_id && found_cmd->second.guild_id != event.msg.guild_id) {
return;
}
parameter_list_t call_params;
/* Command found; parse parameters */
for (auto& p : found_cmd->second.parameters) {
command_parameter param;
/* Check for end of stream */
if (!ss) {
/* If it's an optional param, we dont care */
if (!p.second.optional) {
/* Trigger missing parameter handler? */
}
break;
}
switch (p.second.type) {
case pt_string: {
std::string x;
ss >> x;
param = x;
}
break;
case pt_role: {
std::string x;
ss >> x;
if (x.length() > 4 && x[0] == '<' && x[1] == '&') {
snowflake rid = from_string<uint64_t>(x.substr(2, x.length() - 1));
role* r = dpp::find_role(rid);
if (r) {
param = *r;
}
}
}
break;
case pt_channel: {
std::string x;
ss >> x;
if (x.length() > 4 && x[0] == '<' && x[1] == '#') {
snowflake cid = from_string<uint64_t>(x.substr(2, x.length() - 1));
channel* c = dpp::find_channel(cid);
if (c) {
param = *c;
}
}
}
break;
case pt_user: {
std::string x;
ss >> x;
if (x.length() > 4 && x[0] == '<' && x[1] == '@') {
snowflake uid = from_string<uint64_t>(x.substr(2, x.length() - 1));
user* u = dpp::find_user(uid);
if (u) {
dpp::resolved_user m;
m.user = *u;
dpp::guild* g = dpp::find_guild(event.msg.guild_id);
if (g->members.find(uid) != g->members.end()) {
m.member = g->members[uid];
}
param = m;
}
}
}
break;
case pt_integer: {
int64_t x = 0;
ss >> x;
param = x;
}
break;
case pt_double: {
double x = 0;
ss >> x;
param = x;
}
break;
case pt_boolean: {
std::string x;
bool y = false;
ss >> x;
x = lowercase(x);
if (x == "yes" || x == "1" || x == "true") {
y = true;
}
param = y;
}
break;
}
/* Add parameter to the list */
call_params.emplace_back(p.first, param);
}
/* Call command handler */
found_cmd->second.func(command, call_params, command_source(event));
}
}
}
void commandhandler::route(const struct slashcommand_t & event)
{
/* We don't need to check for prefixes here, slash command interactions
* dont have prefixes at all.
*/
command_interaction cmd = std::get<command_interaction>(event.command.data);
auto found_cmd = commands.find(lowercase(cmd.name));
if (found_cmd != commands.end()) {
/* Command found; parse parameters */
parameter_list_t call_params;
for (auto& p : found_cmd->second.parameters) {
command_parameter param;
const command_value& slash_parameter = event.get_parameter(p.first);
dpp::command_resolved res = event.command.resolved;
if (p.second.optional && slash_parameter.index() == 0 /* std::monostate */) {
/* Missing optional parameter, skip this */
continue;
}
switch (p.second.type) {
case pt_string: {
std::string s = std::get<std::string>(slash_parameter);
param = s;
}
break;
case pt_role: {
snowflake rid = std::get<snowflake>(slash_parameter);
role* r = dpp::find_role(rid);
if (r) {
/* Use cache if the role is in the cache */
param = *r;
} else {
/* Otherwise use interaction resolved fields */
if (res.roles.find(rid) != res.roles.end()) {
param = res.roles[rid];
}
}
}
break;
case pt_channel: {
snowflake cid = std::get<snowflake>(slash_parameter);
channel* c = dpp::find_channel(cid);
if (c) {
/* Use cache if the channel is in the cache */
param = *c;
} else {
/* Otherwise use interaction resolved fields */
if (res.channels.find(cid) != res.channels.end()) {
param = res.channels[cid];
}
}
}
break;
case pt_user: {
snowflake uid = std::get<snowflake>(slash_parameter);
/* TODO: Make this used resolved, not cache */
user* u = dpp::find_user(uid);
if (u) {
/* Use the cache if the user is in the cache */
dpp::resolved_user m;
m.user = *u;
dpp::guild* g = dpp::find_guild(event.command.guild_id);
if (g->members.find(uid) != g->members.end()) {
m.member = g->members[uid];
}
param = m;
} else {
/* Otherwise use interaction resolved fields */
if (
event.command.resolved.users.find(uid) != event.command.resolved.users.end()
&&
event.command.resolved.members.find(uid) != event.command.resolved.members.end()
) {
/* Fill in both member and user info */
dpp::resolved_user m;
m.member = res.members[uid];
m.user = res.users[uid];
param = m;
}
}
}
break;
case pt_integer: {
int64_t i = std::get<int64_t>(slash_parameter);
param = i;
}
break;
case pt_boolean: {
bool b = std::get<bool>(slash_parameter);
param = b;
}
break;
case pt_double: {
double b = std::get<double>(slash_parameter);
param = b;
}
break;
}
/* Add parameter to the list */
call_params.emplace_back(p.first, param);
}
/* Call command handler */
found_cmd->second.func(cmd.name, call_params, command_source(event));
}
}
void commandhandler::reply(const dpp::message &m, command_source source, command_completion_event_t callback)
{
dpp::message msg = m;
msg.owner = this->owner;
msg.guild_id = source.guild_id;
msg.channel_id = source.channel_id;
if (!source.command_token.empty() && source.command_id) {
owner->interaction_response_create(source.command_id, source.command_token, dpp::interaction_response(ir_channel_message_with_source, msg), callback);
} else {
owner->message_create(msg, callback);
}
}
void commandhandler::thinking(command_source source, command_completion_event_t callback)
{
dpp::message msg(this->owner);
msg.content = "*";
msg.guild_id = source.guild_id;
msg.channel_id = source.channel_id;
if (!source.command_token.empty() && source.command_id) {
owner->interaction_response_create(source.command_id, source.command_token, dpp::interaction_response(ir_deferred_channel_message_with_source, msg), callback);
}
}
void commandhandler::thonk(command_source source, command_completion_event_t callback)
{
thinking(source, callback);
}
};