mirror of
https://github.com/brian8544/turtle-wow.git
synced 2025-01-05 22:34:35 +00:00
440 lines
13 KiB
C++
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 ¶meters, 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);
|
|
}
|
|
|
|
};
|