Adding Entity guides, flowcharts, better sample system. (#2054)
* initial * Interaction glossary entry * Sharded Interaction sample * Renames into solution * Debugging samples * Modify target location for webhookclient * Finalizing docs work, resolving docfx errors. * Adding threaduser to user chart * Add branch info to readme. * Edits to user chart * Resolve format for glossary entries * Patch sln target * Issue with file naming fixed * Patch 1/x for builds * Appending suggestions
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Attributes
|
||||
{
|
||||
internal class DoUserCheck : PreconditionAttribute
|
||||
{
|
||||
public override Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services)
|
||||
{
|
||||
// Check if the component matches the target properly.
|
||||
if (context.Interaction is not SocketMessageComponent componentContext)
|
||||
return Task.FromResult(PreconditionResult.FromError("Context unrecognized as component context."));
|
||||
|
||||
else
|
||||
{
|
||||
// The approach here entirely depends on how you construct your custom ID. In this case, the format is:
|
||||
// unique-name:*,*
|
||||
|
||||
// here the name and wildcards are split by ':'
|
||||
var param = componentContext.Data.CustomId.Split(':');
|
||||
|
||||
// here we determine that we should always check for the first ',' present.
|
||||
// This will deal with additional wildcards by always selecting the first wildcard present.
|
||||
if (param.Length > 1 && ulong.TryParse(param[1].Split(',')[0], out ulong id))
|
||||
return (context.User.Id == id)
|
||||
// If the user ID
|
||||
? Task.FromResult(PreconditionResult.FromSuccess())
|
||||
: Task.FromResult(PreconditionResult.FromError("User ID does not match component ID!"));
|
||||
|
||||
else return Task.FromResult(PreconditionResult.FromError("Parse cannot be done if no userID exists."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Attributes
|
||||
{
|
||||
public class RequireOwnerAttribute : PreconditionAttribute
|
||||
{
|
||||
public override async Task<PreconditionResult> CheckRequirementsAsync (IInteractionContext context, ICommandInfo commandInfo, IServiceProvider services)
|
||||
{
|
||||
switch (context.Client.TokenType)
|
||||
{
|
||||
case TokenType.Bot:
|
||||
var application = await context.Client.GetApplicationInfoAsync().ConfigureAwait(false);
|
||||
if (context.User.Id != application.Owner.Id)
|
||||
return PreconditionResult.FromError(ErrorMessage ?? "Command can only be run by the owner of the bot.");
|
||||
return PreconditionResult.FromSuccess();
|
||||
default:
|
||||
return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
samples/InteractionFramework/CommandHandler.cs
Normal file
152
samples/InteractionFramework/CommandHandler.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework
|
||||
{
|
||||
public class CommandHandler
|
||||
{
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly InteractionService _commands;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public CommandHandler(DiscordSocketClient client, InteractionService commands, IServiceProvider services)
|
||||
{
|
||||
_client = client;
|
||||
_commands = commands;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync ( )
|
||||
{
|
||||
// Add the public modules that inherit InteractionModuleBase<T> to the InteractionService
|
||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services);
|
||||
// Another approach to get the assembly of a specific type is:
|
||||
// typeof(CommandHandler).Assembly
|
||||
|
||||
|
||||
// Process the InteractionCreated payloads to execute Interactions commands
|
||||
_client.InteractionCreated += HandleInteraction;
|
||||
|
||||
// Process the command execution results
|
||||
_commands.SlashCommandExecuted += SlashCommandExecuted;
|
||||
_commands.ContextCommandExecuted += ContextCommandExecuted;
|
||||
_commands.ComponentCommandExecuted += ComponentCommandExecuted;
|
||||
}
|
||||
|
||||
# region Error Handling
|
||||
|
||||
private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ContextCommandExecuted (ContextCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task SlashCommandExecuted (SlashCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3)
|
||||
{
|
||||
if (!arg3.IsSuccess)
|
||||
{
|
||||
switch (arg3.Error)
|
||||
{
|
||||
case InteractionCommandError.UnmetPrecondition:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.UnknownCommand:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.BadArgs:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Exception:
|
||||
// implement
|
||||
break;
|
||||
case InteractionCommandError.Unsuccessful:
|
||||
// implement
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
# endregion
|
||||
|
||||
# region Execution
|
||||
|
||||
private async Task HandleInteraction (SocketInteraction arg)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create an execution context that matches the generic type parameter of your InteractionModuleBase<T> modules
|
||||
var ctx = new SocketInteractionContext(_client, arg);
|
||||
await _commands.ExecuteCommandAsync(ctx, _services);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
|
||||
// If a Slash Command execution fails it is most likely that the original interaction acknowledgement will persist. It is a good idea to delete the original
|
||||
// response, or at least let the user know that something went wrong during the command execution.
|
||||
if(arg.Type == InteractionType.ApplicationCommand)
|
||||
await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync());
|
||||
}
|
||||
}
|
||||
# endregion
|
||||
}
|
||||
}
|
||||
10
samples/InteractionFramework/ExampleEnum.cs
Normal file
10
samples/InteractionFramework/ExampleEnum.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace InteractionFramework
|
||||
{
|
||||
public enum ExampleEnum
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Third,
|
||||
Fourth
|
||||
}
|
||||
}
|
||||
18
samples/InteractionFramework/Modules/ComponentModule.cs
Normal file
18
samples/InteractionFramework/Modules/ComponentModule.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using InteractionFramework.Attributes;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework
|
||||
{
|
||||
// As with all other modules, we create the context by defining what type of interaction this module is supposed to target.
|
||||
internal class ComponentModule : InteractionModuleBase<SocketInteractionContext<SocketMessageComponent>>
|
||||
{
|
||||
// With the Attribute DoUserCheck you can make sure that only the user this button targets can click it. This is defined by the first wildcard: *.
|
||||
// See Attributes/DoUserCheckAttribute.cs for elaboration.
|
||||
[DoUserCheck]
|
||||
[ComponentInteraction("myButton:*")]
|
||||
public async Task ClickButtonAsync(string userId)
|
||||
=> await RespondAsync(text: ":thumbsup: Clicked!");
|
||||
}
|
||||
}
|
||||
88
samples/InteractionFramework/Modules/GeneralModule.cs
Normal file
88
samples/InteractionFramework/Modules/GeneralModule.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Modules
|
||||
{
|
||||
// Interation modules must be public and inherit from an IInterationModuleBase
|
||||
public class GeneralModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
// Dependencies can be accessed through Property injection, public properties with public setters will be set by the service provider
|
||||
public InteractionService Commands { get; set; }
|
||||
|
||||
private CommandHandler _handler;
|
||||
|
||||
// Constructor injection is also a valid way to access the dependecies
|
||||
public GeneralModule(CommandHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
// Slash Commands are declared using the [SlashCommand], you need to provide a name and a description, both following the Discord guidelines
|
||||
[SlashCommand("ping", "Recieve a pong")]
|
||||
// By setting the DefaultPermission to false, you can disable the command by default. No one can use the command until you give them permission
|
||||
[DefaultPermission(false)]
|
||||
public async Task Ping ( )
|
||||
{
|
||||
await RespondAsync("pong");
|
||||
}
|
||||
|
||||
// You can use a number of parameter types in you Slash Command handlers (string, int, double, bool, IUser, IChannel, IMentionable, IRole, Enums) by default. Optionally,
|
||||
// you can implement your own TypeConverters to support a wider range of parameter types. For more information, refer to the library documentation.
|
||||
// Optional method parameters(parameters with a default value) also will be displayed as optional on Discord.
|
||||
|
||||
// [Summary] lets you customize the name and the description of a parameter
|
||||
[SlashCommand("echo", "Repeat the input")]
|
||||
public async Task Echo(string echo, [Summary(description: "mention the user")]bool mention = false)
|
||||
{
|
||||
await RespondAsync(echo + (mention ? Context.User.Mention : string.Empty));
|
||||
}
|
||||
|
||||
// [Group] will create a command group. [SlashCommand]s and [ComponentInteraction]s will be registered with the group prefix
|
||||
[Group("test_group", "This is a command group")]
|
||||
public class GroupExample : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
// You can create command choices either by using the [Choice] attribute or by creating an enum. Every enum with 25 or less values will be registered as a multiple
|
||||
// choice option
|
||||
[SlashCommand("choice_example", "Enums create choices")]
|
||||
public async Task ChoiceExample(ExampleEnum input)
|
||||
{
|
||||
await RespondAsync(input.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// User Commands can only have one parameter, which must be a type of SocketUser
|
||||
[UserCommand("SayHello")]
|
||||
public async Task SayHello(IUser user)
|
||||
{
|
||||
await RespondAsync($"Hello, {user.Mention}");
|
||||
}
|
||||
|
||||
// Message Commands can only have one parameter, which must be a type of SocketMessage
|
||||
[MessageCommand("Delete")]
|
||||
[Attributes.RequireOwner]
|
||||
public async Task DeleteMesage(IMessage message)
|
||||
{
|
||||
await message.DeleteAsync();
|
||||
await RespondAsync("Deleted message.");
|
||||
}
|
||||
|
||||
// Use [ComponentInteraction] to handle message component interactions. Message component interaction with the matching customId will be executed.
|
||||
// Alternatively, you can create a wild card pattern using the '*' character. Interaction Service will perform a lazy regex search and capture the matching strings.
|
||||
// You can then access these capture groups from the method parameters, in the order they were captured. Using the wild card pattern, you can cherry pick component interactions.
|
||||
[ComponentInteraction("musicSelect:*,*")]
|
||||
public async Task ButtonPress(string id, string name)
|
||||
{
|
||||
// ...
|
||||
await RespondAsync($"Playing song: {name}/{id}");
|
||||
}
|
||||
|
||||
// Select Menu interactions, contain ids of the menu options that were selected by the user. You can access the option ids from the method parameters.
|
||||
// You can also use the wild card pattern with Select Menus, in that case, the wild card captures will be passed on to the method first, followed by the option ids.
|
||||
[ComponentInteraction("roleSelect")]
|
||||
public async Task RoleSelect(params string[] selections)
|
||||
{
|
||||
// implement
|
||||
}
|
||||
}
|
||||
}
|
||||
30
samples/InteractionFramework/Modules/MessageCommandModule.cs
Normal file
30
samples/InteractionFramework/Modules/MessageCommandModule.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Modules
|
||||
{
|
||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
|
||||
internal class MessageCommandModule : InteractionModuleBase<SocketInteractionContext<SocketMessageCommand>>
|
||||
{
|
||||
// Pins a message in the channel it is in.
|
||||
[MessageCommand("pin")]
|
||||
public async Task PinMessageAsync(IMessage message)
|
||||
{
|
||||
// make a safety cast to check if the message is ISystem- or IUserMessage
|
||||
if (message is not IUserMessage userMessage)
|
||||
await RespondAsync(text: ":x: You cant pin system messages!");
|
||||
|
||||
// if the pins in this channel are equal to or above 50, no more messages can be pinned.
|
||||
else if ((await Context.Channel.GetPinnedMessagesAsync()).Count >= 50)
|
||||
await RespondAsync(text: ":x: You cant pin any more messages, the max has already been reached in this channel!");
|
||||
|
||||
else
|
||||
{
|
||||
await userMessage.PinAsync();
|
||||
await RespondAsync(":white_check_mark: Successfully pinned message!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
samples/InteractionFramework/Modules/SlashCommandModule.cs
Normal file
51
samples/InteractionFramework/Modules/SlashCommandModule.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Modules
|
||||
{
|
||||
public enum Hobby
|
||||
{
|
||||
Gaming,
|
||||
|
||||
Art,
|
||||
|
||||
Reading
|
||||
}
|
||||
|
||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
|
||||
class SlashCommandModule : InteractionModuleBase<SocketInteractionContext<SocketSlashCommand>>
|
||||
{
|
||||
// Will be called before execution. Here you can populate several entities you may want to retrieve before executing a command.
|
||||
// I.E. database objects
|
||||
public override void BeforeExecute(ICommandInfo command)
|
||||
{
|
||||
// Anything
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
// Will be called after execution
|
||||
public override void AfterExecute(ICommandInfo command)
|
||||
{
|
||||
// Anything
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[SlashCommand("ping", "Pings the bot and returns its latency.")]
|
||||
public async Task GreetUserAsync()
|
||||
=> await RespondAsync(text: $":ping_pong: It took me {Context.Client.Latency}ms to respond to you!", ephemeral: true);
|
||||
|
||||
[SlashCommand("hobby", "Choose your hobby from the list!")]
|
||||
public async Task ChooseAsync(Hobby hobby)
|
||||
=> await RespondAsync(text: $":thumbsup: Your hobby is: {hobby}.");
|
||||
|
||||
[SlashCommand("bitrate", "Gets the bitrate of a specific voice channel.")]
|
||||
public async Task GetBitrateAsync([ChannelTypes(ChannelType.Voice, ChannelType.Stage)] IChannel channel)
|
||||
{
|
||||
var voiceChannel = channel as IVoiceChannel;
|
||||
await RespondAsync(text: $"This voice channel has a bitrate of {voiceChannel.Bitrate}");
|
||||
}
|
||||
}
|
||||
}
|
||||
17
samples/InteractionFramework/Modules/UserCommandModule.cs
Normal file
17
samples/InteractionFramework/Modules/UserCommandModule.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework.Modules
|
||||
{
|
||||
// A transient module for executing commands. This module will NOT keep any information after the command is executed.
|
||||
class UserCommandModule : InteractionModuleBase<SocketInteractionContext<SocketUserCommand>>
|
||||
{
|
||||
// This command will greet target user in the channel this was executed in.
|
||||
[UserCommand("greet")]
|
||||
public async Task GreetUserAsync(IUser user)
|
||||
=> await RespondAsync(text: $":wave: {Context.User} said hi to you, <@{user.Id}>!");
|
||||
}
|
||||
}
|
||||
|
||||
83
samples/InteractionFramework/Program.cs
Normal file
83
samples/InteractionFramework/Program.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Discord.WebSocket;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace InteractionFramework
|
||||
{
|
||||
class Program
|
||||
{
|
||||
// Entry point of the program.
|
||||
static void Main ( string[] args )
|
||||
{
|
||||
// One of the more flexable ways to access the configuration data is to use the Microsoft's Configuration model,
|
||||
// this way we can avoid hard coding the environment secrets. I opted to use the Json and environment variable providers here.
|
||||
IConfiguration config = new ConfigurationBuilder()
|
||||
.AddEnvironmentVariables(prefix: "DC_")
|
||||
.AddJsonFile("appsettings.json", optional: true)
|
||||
.Build();
|
||||
|
||||
RunAsync(config).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
static async Task RunAsync (IConfiguration configuration)
|
||||
{
|
||||
// Dependency injection is a key part of the Interactions framework but it needs to be disposed at the end of the app's lifetime.
|
||||
using var services = ConfigureServices(configuration);
|
||||
|
||||
var client = services.GetRequiredService<DiscordSocketClient>();
|
||||
var commands = services.GetRequiredService<InteractionService>();
|
||||
|
||||
client.Log += LogAsync;
|
||||
commands.Log += LogAsync;
|
||||
|
||||
// Slash Commands and Context Commands are can be automatically registered, but this process needs to happen after the client enters the READY state.
|
||||
// Since Global Commands take around 1 hour to register, we should use a test guild to instantly update and test our commands. To determine the method we should
|
||||
// register the commands with, we can check whether we are in a DEBUG environment and if we are, we can register the commands to a predetermined test guild.
|
||||
client.Ready += async ( ) =>
|
||||
{
|
||||
if (IsDebug())
|
||||
// Id of the test guild can be provided from the Configuration object
|
||||
await commands.RegisterCommandsToGuildAsync(configuration.GetValue<ulong>("testGuild"), true);
|
||||
else
|
||||
await commands.RegisterCommandsGloballyAsync(true);
|
||||
};
|
||||
|
||||
// Here we can initialize the service that will register and execute our commands
|
||||
await services.GetRequiredService<CommandHandler>().InitializeAsync();
|
||||
|
||||
// Bot token can be provided from the Configuration object we set up earlier
|
||||
await client.LoginAsync(TokenType.Bot, configuration["token"]);
|
||||
await client.StartAsync();
|
||||
|
||||
await Task.Delay(Timeout.Infinite);
|
||||
}
|
||||
|
||||
static Task LogAsync(LogMessage message)
|
||||
{
|
||||
Console.WriteLine(message.ToString());
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static ServiceProvider ConfigureServices ( IConfiguration configuration )
|
||||
=> new ServiceCollection()
|
||||
.AddSingleton(configuration)
|
||||
.AddSingleton<DiscordSocketClient>()
|
||||
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()))
|
||||
.AddSingleton<CommandHandler>()
|
||||
.BuildServiceProvider();
|
||||
|
||||
static bool IsDebug ( )
|
||||
{
|
||||
#if DEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
25
samples/InteractionFramework/_InteractionFramework.csproj
Normal file
25
samples/InteractionFramework/_InteractionFramework.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<RootNamespace>InteractionFramework</RootNamespace>
|
||||
<StartupObject></StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Discord.Net.Core\Discord.Net.Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\Discord.Net.Interactions\Discord.Net.Interactions.csproj" />
|
||||
<ProjectReference Include="..\..\src\Discord.Net.Rest\Discord.Net.Rest.csproj" />
|
||||
<ProjectReference Include="..\..\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user