Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slash command service #1733

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d35ba91
Rest features for ApplicationCommands
quinchs Dec 16, 2020
27efb7d
Fixed couple of unfinished properties
quinchs Dec 16, 2020
fae3fbe
Worked on more rest routes for responding to application commands. Wo…
quinchs Dec 17, 2020
c322824
Merge branch 'dev' into Interactions
quinchs Dec 17, 2020
670bd92
Working version of Interactions. All public classes/members now have …
quinchs Dec 18, 2020
07964ac
Merge branch 'Interactions' of https://github.com/quinchs/Discord.Net…
quinchs Dec 18, 2020
a0f9646
Get, modify, and delete Guild/Global commands,
quinchs Dec 19, 2020
09f6f34
Added APPLICATION_COMMAND_DELETE, APPLICATION_COMMAND_UPDATE, and APP…
quinchs Dec 20, 2020
18ffc40
Create application-commands.md
quinchs Dec 21, 2020
21c2a7f
SlashCommandBuilder added
quinchs Dec 27, 2020
ff08f34
Comment updates and explicit conversions for SocketInteractionDataOpt…
quinchs Dec 27, 2020
c5bba76
Merge remote-tracking branch 'origin/SlashCommandService' into SlashC…
quinchs Jan 9, 2021
f3c07fd
Merge remote-tracking branch 'origin/Interactions' into SlashCommandS…
quinchs Jan 9, 2021
f4a8b21
Worked on Slash command service, getting ready for big code grind
quinchs Jan 9, 2021
16f6a4c
Fixed build errors and started an example project for development pur…
george-cosma Feb 10, 2021
f4b3214
Implemented the most basic form of the command service. Does not yet …
george-cosma Feb 11, 2021
4223fa8
Implemented basic parameter recognition and passing them to the method.
george-cosma Feb 14, 2021
50a88e0
Implemented SubCommands and SubCommandGroups properly.
george-cosma Feb 15, 2021
c76fdec
Implemented Global Attribute and added a way to register all commands…
george-cosma Feb 16, 2021
6ba456a
Implemented [Choice( [] , [] )] and [Required] Attributes.
george-cosma Feb 16, 2021
4019d51
Implemented ParameterName for custom names, implemented a public Buil…
george-cosma Feb 17, 2021
af9db9c
Merge pull request #1 from SlenderPlays/SlashCommandServiceV2
george-cosma Feb 18, 2021
3e9b027
Merge pull request #1 from SlenderPlays/SlashCommandService
quinchs Feb 18, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
491 changes: 491 additions & 0 deletions Discord.Net.SlashCommands/Builders/SlashCommandBuilder.cs

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions Discord.Net.SlashCommands/Discord.Net.SlashCommands.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>Discord.Net.SlashCommands</AssemblyName>
<RootNamespace>Discord.SlashCommands</RootNamespace>
<Description>A Discord.Net extension adding support for slash commands.</Description>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">net461;netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard2.0;netstandard2.1</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\src\Discord.Net.Core\Discord.Net.Core.csproj" />
</ItemGroup>

</Project>
30 changes: 29 additions & 1 deletion Discord.Net.sln
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,22 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Analyzers.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Discord.Net.Examples", "src\Discord.Net.Examples\Discord.Net.Examples.csproj", "{47820065-3CFB-401C-ACEA-862BD564A404}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "idn", "samples\idn\idn.csproj", "{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlashCommandsExample", "SlashCommandsExample\SlashCommandsExample.csproj", "{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}"
ProjectSection(ProjectDependencies) = postProject
{6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7} = {6BDEEC08-417B-459F-9CA3-FF8BAB18CAC7}
{9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30} = {9AFAB80E-D2D3-4EDB-B58C-BACA78D1EA30}
{BFC6DC28-0351-4573-926A-D4124244C04F} = {BFC6DC28-0351-4573-926A-D4124244C04F}
{E169E15A-E82C-45BF-8C24-C2CADB7093AA} = {E169E15A-E82C-45BF-8C24-C2CADB7093AA}
{47820065-3CFB-401C-ACEA-862BD564A404} = {47820065-3CFB-401C-ACEA-862BD564A404}
{DBF8B16E-5967-4480-8EDE-15D98A0DF0C4} = {DBF8B16E-5967-4480-8EDE-15D98A0DF0C4}
{FC67057C-E92F-4E1C-98BE-46F839C8AD71} = {FC67057C-E92F-4E1C-98BE-46F839C8AD71}
{91E9E7BD-75C9-4E98-84AA-2C271922E5C2} = {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}
{688FD1D8-7F01-4539-B2E9-F473C5D699C7} = {688FD1D8-7F01-4539-B2E9-F473C5D699C7}
{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C} = {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}
{BBA8E7FB-C834-40DC-822F-B112CB7F0140} = {BBA8E7FB-C834-40DC-822F-B112CB7F0140}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -232,6 +247,18 @@ Global
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x64.Build.0 = Release|Any CPU
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.ActiveCfg = Release|Any CPU
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB}.Release|x86.Build.0 = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|x64.ActiveCfg = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|x64.Build.0 = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|x86.ActiveCfg = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Debug|x86.Build.0 = Debug|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|Any CPU.Build.0 = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|x64.ActiveCfg = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|x64.Build.0 = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|x86.ActiveCfg = Release|Any CPU
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -251,6 +278,7 @@ Global
{FC67057C-E92F-4E1C-98BE-46F839C8AD71} = {C7CF5621-7D36-433B-B337-5A2E3C101A71}
{47820065-3CFB-401C-ACEA-862BD564A404} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
{4A03840B-9EBE-47E3-89AB-E0914DF21AFB} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
{2CB2A016-CCEB-4A67-BC7B-098F114D7C27} = {BB59D5B5-E7B0-4BF4-8F82-D14431B2799B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D2404771-EEC8-45F2-9D71-F3373F6C1495}
Expand Down
155 changes: 155 additions & 0 deletions SlashCommandsExample/DiscordClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using Discord;
using Discord.Commands;
using Discord.SlashCommands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;

namespace SlashCommandsExample
{
class DiscordClient
{
public static DiscordSocketClient socketClient { get; set; } = new DiscordSocketClient();
public static SlashCommandService _commands { get; set; }
public static IServiceProvider _services { get; set; }

private string botToken = "<YOUR TOKEN HERE>";

public DiscordClient()
{
_commands = new SlashCommandService();
_services = new ServiceCollection()
.AddSingleton(socketClient)
.AddSingleton(_commands)
.BuildServiceProvider();

socketClient.Log += SocketClient_Log;
_commands.Log += SocketClient_Log;
socketClient.InteractionCreated += InteractionHandler;
socketClient.Ready += RegisterCommand;

// This is for dev purposes.
// To avoid the situation in which you accidentally push your bot token to upstream, you can use
// EnviromentVariables to store your key.
botToken = Environment.GetEnvironmentVariable("DiscordSlashCommandsBotToken", EnvironmentVariableTarget.User);
// Uncomment the next line of code to set the environment variable.
// ------------------------------------------------------------------
// | WARNING! |
// | |
// | MAKE SURE TO DELETE YOUR TOKEN AFTER YOU HAVE SET THE VARIABLE |
// | |
// ------------------------------------------------------------------

//Environment.SetEnvironmentVariable("DiscordSlashCommandsBotToken",
// "[YOUR TOKEN GOES HERE DELETE & COMMENT AFTER USE]",
// EnvironmentVariableTarget.User);
}

public async Task RegisterCommand()
{
// Use this to manually register a command for testing.
return;
await socketClient.Rest.CreateGuildCommand(new SlashCommandCreationProperties()
{
Name = "root",
Description = "Root Command",
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "usr",
Description = "User Folder",
Type = ApplicationCommandOptionType.SubCommandGroup,
Options = new List<ApplicationCommandOptionProperties>()
{
// This doesn't work. This is good!
//new ApplicationCommandOptionProperties()
//{
// Name = "strstr",
// Description = "Some random string I guess.",
// Type = ApplicationCommandOptionType.String,
//},
new ApplicationCommandOptionProperties()
{
Name = "zero",
Description = "Zero's Home Folder - COMMAND",
Type = ApplicationCommandOptionType.SubCommand,
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "file",
Description = "the file you want accessed.",
Type = ApplicationCommandOptionType.String
}
}
},
new ApplicationCommandOptionProperties()
{
Name = "johhny",
Description = "Johnny Test's Home Folder - COMMAND",
Type = ApplicationCommandOptionType.SubCommand,
Options = new List<ApplicationCommandOptionProperties>()
{
new ApplicationCommandOptionProperties()
{
Name = "file",
Description = "the file you want accessed.",
Type = ApplicationCommandOptionType.String
}
}
}
}
},
new ApplicationCommandOptionProperties()
{
Name = "random",
Description = "Random things",
Type = ApplicationCommandOptionType.SubCommand
}
}
}, 386658607338618891) ;
}

public async Task RunAsync()
{
await socketClient.LoginAsync(TokenType.Bot, botToken);
await socketClient.StartAsync();

await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
await _commands.RegisterCommandsAsync(socketClient, new List<ulong>()
{
386658607338618891
},
new CommandRegistrationOptions(OldCommandOptions.DELETE_UNUSED,ExistingCommandOptions.OVERWRITE));

// If you would like to register your commands manually use:
//-----------------------------------------//
//
// await _commands.BuildCommands();
//
//-----------------------------------------//
// Though I wouldn't highly recommend it unless you want to do something very specific with them
// such as only registering some commands on only some guilds, or editing them manually.

await Task.Delay(-1);
}

private async Task InteractionHandler(SocketInteraction arg)
{
if(arg.Type == InteractionType.ApplicationCommand)
{
await _commands.ExecuteAsync(arg);
}
}

private Task SocketClient_Log(LogMessage arg)
{
Console.WriteLine("[Discord] " + arg.ToString());
return Task.CompletedTask;
}
}
}
104 changes: 104 additions & 0 deletions SlashCommandsExample/Modules/DevModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Discord.Commands;
using Discord.Commands.SlashCommands.Types;
using Discord.SlashCommands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace SlashCommandsExample.Modules
{
// You can make the whole module Global
//[Global]
public class DevModule : SlashCommandModule<SocketInteraction>
{
[SlashCommand("ping", "Ping the bot to see if it's alive!")]
[Global]
public async Task PingAsync()
{
await Reply(":white_check_mark: **Bot Online**");
}

[SlashCommand("echo", "I'll repeate everything you said to me, word for word.")]
public async Task EchoAsync(
[Description("The message you want repetead")]
[Required]
string message)
{
await Reply($"{Interaction.Member?.Nickname ?? Interaction.Member?.Username} told me to say this: \r\n{message}");
}

[SlashCommand("overload","Just hit me with every type of data you got, man!")]
public async Task OverloadAsync(
[ParameterName("var1")]
bool? boolean,
[ParameterName("var2")]
int? integer,
[ParameterName("var3")]
string myString,
SocketGuildChannel channel,
SocketGuildUser user,
SocketRole role
)
{
await Reply($"You gave me:\r\n {boolean}, {integer}, {myString}, <#{channel?.Id}>, {user?.Mention}, {role?.Mention}");

}

[SlashCommand("stats","Get the stats from Game(tm) for players or teams.")]
public async Task GetStatsAsync(
[Required]
[Choice("XBOX","xbox")]
[Choice("PlayStation","ps")]
[Choice("PC","pc")]
string platform,
[Choice("Player",1)]
[Choice("Team",2)]
int searchType
)
{
await Reply($"Well I got this: {platform}, {searchType}");
}

[CommandGroup("root")]
//[Global]
public class DevModule_Root : SlashCommandModule<SocketInteraction>
{
[SlashCommand("rng", "Gives you a random number from this \"machine\"")]
public async Task RNGAsync()
{
var rand = new Random();
await Reply(rand.Next(0, 101).ToString());
}

[CommandGroup("usr")]
public class DevModule_Root_Usr : SlashCommandModule<SocketInteraction>
{
[SlashCommand("zero", "Gives you a file from user zero from this \"machine\"")]
public async Task ZeroAsync([Description("The file you want.")] string file)
{
await Reply($"You don't have permissiont to access {file} from user \"zero\".");
}
[SlashCommand("johnny", "Gives you a file from user Johnny Test from this \"machine\"")]
public async Task JohnnyAsync([Description("The file you want.")] string file)
{
await Reply($"You don't have permissiont to access {file} from user \"johnny\".");
}
}
}
}
}
/*
The base way of defining a command using the regular command service:

public class PingModule : ModuleBase<SocketCommandContext>
{
[Command("ping")]
[Summary("Pong! Check if the bot is alive.")]
public async Task PingAsync()
{
await ReplyAsync(":white_check_mark: **Bot Online**");
}
}
*/
20 changes: 20 additions & 0 deletions SlashCommandsExample/Modules/InvalidModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Discord.SlashCommands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Text;

namespace SlashCommandsExample.Modules
{
// Doesn't inherit from SlashCommandModule
public class InvalidDefinition : Object
{
// commands
}

// Isn't public
class PrivateDefinition : SlashCommandModule<SocketInteraction>
{
// commands
}
}
26 changes: 26 additions & 0 deletions SlashCommandsExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* This project, is at this moment used for testing and debugging the new and experimental Slash Commands.
* After all testing has been done, and the project is ready to be integrated into the main Discord.Net ecosystem
* this project should be re-made into one that could be used as an example usage of the new Slash Command Service.
*/
using System;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.SlashCommands;
using Discord.WebSocket;

namespace SlashCommandsExample
{
class Program
{
static void Main(string[] args)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can declare Main as Task Main(string[] args) and simply await your RunAsync() call to avoid mixing synchronous and asynchronous code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter here, as they both ultimately compile down to the same IL internally; at least, as far as this example is concerned.

{
Console.WriteLine("Hello World!");

DiscordClient discordClient = new DiscordClient();
// This could instead be handled in another thread, if for whatever reason you want to continue execution in the main Thread.
discordClient.RunAsync().GetAwaiter().GetResult();
}
}
}
7 changes: 7 additions & 0 deletions SlashCommandsExample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"profiles": {
"SlashCommandsExample": {
"commandName": "Project"
}
}
}
Loading