Skip to content

Commit

Permalink
Merge branch 'feature/user-installable-apps'
Browse files Browse the repository at this point in the history
  • Loading branch information
MrPowerGamerBR committed Jul 24, 2024
2 parents d2a3724 + aab24ed commit a58e422
Show file tree
Hide file tree
Showing 132 changed files with 8,400 additions and 713 deletions.
98 changes: 93 additions & 5 deletions src/examples/java/SlashBotExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.Webhook;
import net.dv8tion.jda.api.entities.channel.attribute.IWebhookContainer;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
Expand All @@ -34,6 +38,7 @@

import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static net.dv8tion.jda.api.interactions.commands.OptionType.*;

Expand All @@ -45,7 +50,7 @@ public static void main(String[] args)
.addEventListeners(new SlashBotExample())
.build();

// These commands might take a few minutes to be active after creation/update/delete
// You might need to reload your Discord client if you don't see the commands
CommandListUpdateAction commands = jda.updateCommands();

// Moderation commands with required options
Expand All @@ -56,27 +61,40 @@ public static void main(String[] args)
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days.") // This is optional
.setRequiredRange(0, 7)) // Only allow values between 0 and 7 (inclusive)
.addOptions(new OptionData(STRING, "reason", "The ban reason to use (default: Banned by <user>)")) // optional reason
.setGuildOnly(true) // This way the command can only be executed from a guild, and not the DMs
.setContexts(InteractionContextType.GUILD) // This way the command can only be executed from a guild, and not the DMs
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS)) // Only members with the BAN_MEMBERS permission are going to see this command
);

// Simple reply commands
commands.addCommands(
Commands.slash("echo", "Makes the bot reply to yourself")
.setContexts(InteractionContextType.ALL) // Allow the command to be used anywhere (Bot DMs, Guild, Friend DMs, Group DMs)
.setIntegrationTypes(IntegrationType.ALL) // Allow the command to be installed anywhere (Guilds, Users)
.addOption(STRING, "content", "What the bot should reply to yourself", true) // you can add required options like this too
);

commands.addCommands(
Commands.slash("say", "Makes the bot say what you tell it to")
.setContexts(InteractionContextType.ALL) // Allow the command to be used anywhere (Bot DMs, Guild, Friend DMs, Group DMs)
.setIntegrationTypes(IntegrationType.ALL) // Allow the command to be installed anywhere (Guilds, Users)
.addOption(STRING, "content", "What the bot should say", true) // you can add required options like this too
);

// Commands without any inputs
commands.addCommands(
Commands.slash("leave", "Make the bot leave the server")
.setGuildOnly(true) // this doesn't make sense in DMs
// The default integration types are GUILD_INSTALL.
// Can't use this in DMs, and in guilds the bot isn't in.
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.DISABLED) // only admins should be able to use this command.
);

commands.addCommands(
Commands.slash("prune", "Prune messages from this channel")
.addOption(INTEGER, "amount", "How many messages to prune (Default 100)") // simple optional argument
.setGuildOnly(true)
// The default integration types are GUILD_INSTALL.
// Can't use this in DMs, and in guilds the bot isn't in.
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE))
);

Expand All @@ -98,6 +116,9 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event)
User user = event.getOption("user").getAsUser();
ban(event, user, member);
break;
case "echo":
echo(event, event.getOption("content").getAsString()); // content is required so no null-check here
break;
case "say":
say(event, event.getOption("content").getAsString()); // content is required so no null-check here
break;
Expand Down Expand Up @@ -177,11 +198,78 @@ public void ban(SlashCommandInteractionEvent event, User user, Member member)
.queue(); // execute the entire call chain
}

public void say(SlashCommandInteractionEvent event, String content)
public void echo(SlashCommandInteractionEvent event, String content)
{
event.reply(content).queue(); // This requires no permissions!
}

public void say(SlashCommandInteractionEvent event, String content)
{
event.deferReply().queue();

// To use a webhook, the bot needs to be in the guild,
// reply to the interaction in other cases (detached guilds, DMs, Group DMs).
tryUseWebhook(
event,
// If we can use a webhook
webhook ->
{
// Remove the bot thinking message
event.getHook().deleteOriginal().queue();

webhook.sendMessage(content)
.setUsername(event.getMember().getEffectiveName())
.setAvatarUrl(event.getMember().getEffectiveAvatarUrl())
.queue();
},
// Fallback
() -> event.getHook().sendMessage(content).queue() // This requires no permissions!
);
}

private void tryUseWebhook(SlashCommandInteractionEvent event, Consumer<Webhook> webhookCallback, Runnable noWebhookCallback)
{
// If the bot isn't in the guild, fallback to a normal reply
if (!event.hasFullGuild())
{
noWebhookCallback.run();
return;
}

// In case the channel doesn't support webhooks, fallback to a normal reply
if (!(event.getGuildChannel() instanceof IWebhookContainer))
{
noWebhookCallback.run();
return;
}

// If we don't have permissions to create a webhook, fallback to a normal reply
final IWebhookContainer webhookContainer = (IWebhookContainer) event.getGuildChannel();
// Make sure to take the permission overrides into account by supplying the channel!
if (!event.getGuild().getSelfMember().hasPermission(webhookContainer, Permission.MANAGE_WEBHOOKS))
{
noWebhookCallback.run();
return;
}

// We can use webhooks! Try to find an existing one, or create one
webhookContainer.retrieveWebhooks().queue(webhooks ->
{
// Try to find an existing webhook, one which we own
for (Webhook webhook : webhooks)
{
if (event.getJDA().getSelfUser().equals(webhook.getOwnerAsUser()))
{
webhookCallback.accept(webhook);
return;
}
}

// No webhook found, create one and pass it to the callback
webhookContainer.createWebhook("/say webhook").queue(webhookCallback);
});
}

public void leave(SlashCommandInteractionEvent event)
{
if (!event.getMember().hasPermission(Permission.KICK_MEMBERS))
Expand Down
60 changes: 54 additions & 6 deletions src/main/java/net/dv8tion/jda/api/entities/ApplicationInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.utils.ImageProxy;
import net.dv8tion.jda.internal.utils.Checks;
import org.jetbrains.annotations.Unmodifiable;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.*;

/**
* Represents a Discord Application from its bot's point of view.
Expand Down Expand Up @@ -116,7 +114,7 @@ default ImageProxy getIcon()

/**
* Configures the required scopes applied to the {@link #getInviteUrl(Permission...)} and similar methods.
* <br>To use slash commands you must add {@code "applications.commands"} to these scopes. The scope {@code "bot"} is always applied.
* <br>The scope {@code "bot"} is always applied.
*
* @param scopes
* The scopes to use with {@link #getInviteUrl(Permission...)} and the likes
Expand All @@ -135,7 +133,7 @@ default ApplicationInfo setRequiredScopes(@Nonnull String... scopes)

/**
* Configures the required scopes applied to the {@link #getInviteUrl(Permission...)} and similar methods.
* <br>To use slash commands you must add {@code "applications.commands"} to these scopes. The scope {@code "bot"} is always applied.
* <br>The scope {@code "bot"} is always applied.
*
* @param scopes
* The scopes to use with {@link #getInviteUrl(Permission...)} and the likes
Expand Down Expand Up @@ -408,6 +406,56 @@ default EnumSet<Flag> getFlags()
*/
long getFlagsRaw();

/**
* The configurations for each {@link IntegrationType} set on the application.
*
* @return The configurations for each integration type
*/
@Nonnull
Map<IntegrationType, IntegrationTypeConfiguration> getIntegrationTypesConfig();

/**
* Configuration of a single {@link IntegrationType}.
*
* @see ApplicationInfo#getIntegrationTypesConfig()
*/
interface IntegrationTypeConfiguration
{
/**
* The OAuth2 install parameters for the default in-app authorization link.
* <br>When a user invites your application in the Discord app, these will be the parameters of the invite url.
*
* @return The OAuth2 install parameters for the default in-app authorization link
*/
@Nullable
InstallParameters getInstallParameters();
}

/**
* OAuth2 install parameter for the default in-app authorization link.
*
* @see IntegrationTypeConfiguration#getInstallParameters()
*/
interface InstallParameters
{
/**
* Gets the required scopes granted to the bot when invited.
*
* @return The required scopes granted to the bot when invited
*/
@Nonnull
List<String> getScopes();

/**
* Gets the permissions your bot asks for when invited.
* <br><b>Note:</b> Users can choose to disable permissions before and after inviting your bot.
*
* @return The permissions your bot asks for when invited
*/
@Nonnull
Set<Permission> getPermissions();
}

/**
* Flag constants corresponding to the <a href="https://discord.com/developers/docs/resources/application#application-object-application-flags" target="_blank">Discord Enum</a>
*
Expand Down
Loading

0 comments on commit a58e422

Please sign in to comment.