Browse Source

initial

pull/2054/head
Armano den Boef 3 years ago
parent
commit
ae8dd4ac18
46 changed files with 514 additions and 156 deletions
  1. +1
    -1
      docs/faq/misc/legacy.md
  2. +0
    -2
      docs/faq/toc.yml
  3. +68
    -0
      docs/guides/entities/casting.md
  4. +30
    -27
      docs/guides/entities/glossary.md
  5. BIN
      docs/guides/entities/images/IChannel.png
  6. BIN
      docs/guides/entities/images/IInteraction.png
  7. BIN
      docs/guides/entities/images/IMessage.png
  8. BIN
      docs/guides/entities/images/IUser.png
  9. +41
    -19
      docs/guides/entities/introduction.md
  10. +7
    -0
      docs/guides/entities/samples/casting.cs
  11. +8
    -0
      docs/guides/entities/samples/restentities.cs
  12. +10
    -0
      docs/guides/entities/samples/safety-cast-pass.cs
  13. +9
    -0
      docs/guides/entities/samples/safety-cast-var.cs
  14. +14
    -0
      docs/guides/entities/samples/safety-cast.cs
  15. +0
    -0
      docs/guides/entities/samples/socketentities.cs
  16. +9
    -0
      docs/guides/entities/samples/unboxing.cs
  17. +8
    -0
      docs/guides/toc.yml
  18. +0
    -77
      samples/01_basic_ping_bot/Program.cs
  19. +112
    -0
      samples/BasicBot/Program.cs
  20. +0
    -0
      samples/BasicBot/_BasicBot.csproj
  21. +37
    -0
      samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs
  22. +1
    -1
      samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs
  23. +10
    -1
      samples/InteractionFramework/CommandHandler.cs
  24. +1
    -1
      samples/InteractionFramework/ExampleEnum.cs
  25. +19
    -0
      samples/InteractionFramework/Modules/ComponentModule.cs
  26. +4
    -3
      samples/InteractionFramework/Modules/GeneralModule.cs
  27. +30
    -0
      samples/InteractionFramework/Modules/MessageCommandModule.cs
  28. +51
    -0
      samples/InteractionFramework/Modules/SlashCommandModule.cs
  29. +18
    -0
      samples/InteractionFramework/Modules/UserCommandModule.cs
  30. +4
    -5
      samples/InteractionFramework/Program.cs
  31. +1
    -1
      samples/InteractionFramework/_InteractionFramework.csproj
  32. +1
    -1
      samples/ShardedClient/Modules/PublicModule.cs
  33. +8
    -6
      samples/ShardedClient/Program.cs
  34. +1
    -1
      samples/ShardedClient/Services/CommandHandlingService.cs
  35. +1
    -0
      samples/ShardedClient/_ShardedClient.csproj
  36. +2
    -2
      samples/TextCommandFramework/Modules/PublicModule.cs
  37. +2
    -2
      samples/TextCommandFramework/Program.cs
  38. +1
    -1
      samples/TextCommandFramework/Services/CommandHandlingService.cs
  39. +1
    -1
      samples/TextCommandFramework/Services/PictureService.cs
  40. +1
    -1
      samples/TextCommandFramework/_TextCommandFramework.csproj
  41. +1
    -1
      samples/WebhookClient/Program.cs
  42. +2
    -2
      samples/WebhookClient/_WebhookClient.csproj
  43. +0
    -0
      samples/_idn/Inspector.cs
  44. +0
    -0
      samples/_idn/Program.cs
  45. +0
    -0
      samples/_idn/idn.csproj
  46. +0
    -0
      samples/_idn/logview.ps1

+ 1
- 1
docs/faq/misc/legacy.md View File

@@ -24,6 +24,6 @@ Visit the repo's [release tag] to see the latest public pre-release.

The `DependencyMap` has been replaced with Microsoft's
[DependencyInjection] Abstractions. An example usage can be seen
[here](https://github.com/foxbot/DiscordBotBase/blob/csharp/src/DiscordBot/Program.cs#L36).
[here](https://github.com/Discord-Net-Labs/Discord.Net-Labs/blob/release/3.x/samples/InteractionFramework/Program.cs#L66).

[DependencyInjection]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection

+ 0
- 2
docs/faq/toc.yml View File

@@ -16,7 +16,5 @@
topicUid: FAQ.Commands.Interactions
- name: Dependency Injection
topicUid: FAQ.Commands.DI
- name: Glossary
topicUid: FAQ.Glossary
- name: Legacy or Upgrade
topicUid: FAQ.Legacy

+ 68
- 0
docs/guides/entities/casting.md View File

@@ -0,0 +1,68 @@
---
uid: Guides.Entities.Casting
title: Casting & Unboxing
---

# Casting

Casting can be done in many ways, and is the only method to box and unbox types to/from their base definition.
Casting only works for types that inherit the base type that you want to unbox from.
`IUser` cannot be cast to `IMessage`.

> [!NOTE]
> Interfaces CAN be cast to other interfaces, as long as they inherit eachother.
> The same goes for reverse casting. As long as some entity can be simplified into what it inherits, your cast will pass.

## Boxing

A boxed object is the definition of an object that was simplified (or trimmed) by incoming traffic,
but still owns the data of the originally constructed type. Boxing is an implicit operation.

Through casting, we can **unbox** this type, and access the properties that were unaccessible before.

## Unboxing

Unboxing is the most direct way to access the real definition of an object.
If we want to return a type from its interface, we can unbox it directly.

[!code-csharp[Unboxing](images/unboxing.cs)]

## Regular casting

In 'regular' casting, we use the ` as ` keyword to assign the given type to the object.
If the boxed type can indeed be cast into given type,
it will become said type, and its properties can be accessed.
[!code-csharp[Casting](images/casting.cs)]

> [!WARNING]
> If the type you're casting to is null, a ` NullReferenceException ` will be thrown when its called.
> This makes safety casting much more interesting to use, as it prevents this exception from being thrown.

## Safety casting

Safety casting makes sure that the type you're trying to cast to can never be null, as it passes checks upon calling them.

There are 3 different ways to safety cast an object:

### Basic safety casting:

To safety cast an object, all we need to do is check if it is of the member type in a statement.
If this check fails, it will continue below, making sure we don't try to access null.
[!code-csharp[Base](images/safety-cast.cs)]

### Object declaration:

Here we declare the object we are casting to,
making it so that you can immediately work with its properties without reassigning through regular casting.
[!code-csharp[Declare](images/safety-cast-var.cs)]

### Reverse passage:

In previous examples, we want to let code continue running after the check, or if the check fails.
In this example, the cast will return the entire method (ignoring the latter) upon failure,
and declare the variable for further use into the method:
[!code-csharp[Pass](images/safety-cast-pass.cs)]

> [!NOTE]
> Usage of ` is `, ` not ` and ` as ` is required in cast assignment and/or type checks. ==, != and = are invalid assignment,
> as these operators only apply to initialized objects and not their types.

docs/faq/misc/glossary.md → docs/guides/entities/glossary.md View File

@@ -1,29 +1,19 @@
---
uid: FAQ.Glossary
title: Common Terminologies / Glossary
uid: Guides.Entities.Glossary
title: Glossary & Flowcharts
---

# Glossary
# Entity Types

This is an additional chapter for quick references to various common
types that you may see within Discord.Net. To see more information
regarding each type of object, click on the object to navigate
to our API documentation page where you might find more explanation
about it.
A list of all Discord.Net entities, what they can be cast to and what their properties are.

## Common Types
> [!IMPORTANT]
> All interfaces have the same inheritance tree for both `Socket` and `Rest` entities.
> Entities with that have been marked red are exclusive to the project they source from.

* A **Guild** ([IGuild]) is an isolated collection of users and
channels, and are often referred to as "servers".
- Example: [Discord API](https://discord.gg/jkrBmQR)
* A **Channel** ([IChannel]) represents a generic channel.
- Example: #dotnet_discord-net
- See [Channel Types](#channel-types)
## Channels

[IGuild]: xref:Discord.IGuild
[IChannel]: xref:Discord.IChannel

## Channel Types
![IChannelChart](images/IChannel.png)

### Message Channels
* A **Text Channel** ([ITextChannel]) is a message channel from a Guild.
@@ -45,9 +35,6 @@ holds one or more sub-channels.
* A **Nested Channel** ([INestedChannel]) (2.0+) is a channel that can
exist under a category.

> [!NOTE]
> A Channel ([IChannel]) can be all types of channels.

[INestedChannel]: xref:Discord.INestedChannel
[IGuildChannel]: xref:Discord.IGuildChannel
[IMessageChannel]: xref:Discord.IMessageChannel
@@ -62,17 +49,27 @@ exist under a category.
[IStageChannel]: xref:Discord.IStageChannel
[INewsChannel]: xref:Discord.INewsChannel

## Message Types
## Messages

![IMessageChart](images/IMessage.png)

* A **Rest Followup Message ([RestFollowupMessage]) is a message returned by followup on on an interaction.
* A **Rest Interaction Message ([RestInteractionMessage]) is a message returned by the interactions' original response.
* A **Rest User Message** ([RestUserMessage]) is a message sent over rest, can be any of the above.
* An **User Message** ([IUserMessage]) is a message sent by a user.
* A **System Message** ([ISystemMessage]) is a message sent by Discord itself.
* A **Message** ([IMessage]) can be any of the above.

[RestFollowupMessage]: xref:Discord.Rest.RestFollowupMessage
[RestInteractionMessage]: xref:Discord.Rest.RestInteractionMessage
[RestUserMEssage]: xref:Discord.Rest.RestUserMessage
[IUserMessage]: xref:Discord.IUserMessage
[ISystemMessage]: xref:Discord.ISystemMessage
[IMessage]: xref:Discord.IMessage

## User Types
## Users

![IUserChart](images/IUser.png)

* A **Guild User** ([IGuildUser]) is a user available inside a guild.
* A **Group User** ([IGroupUser]) is a user available inside a group.
@@ -85,7 +82,13 @@ exist under a category.
[ISelfUser]: xref:Discord.ISelfUser
[IUser]: xref:Discord.IUser

## Emoji Types
## Interactions

![IInteractionChart](images/IInteraction.png)

## Other types:

### Emoji

* An **Emote** ([Emote]) is a custom emote from a guild.
- Example: `<:dotnet:232902710280716288>`
@@ -96,7 +99,7 @@ exist under a category.
[Emoji]: xref:Discord.Emoji


## Sticker Types
### Stickers

* A **Sticker** ([ISticker]) is a standard Discord sticker.
* A **Custom Sticker ([ICustomSticker]) is a Guild-unique sticker.
@@ -104,7 +107,7 @@ exist under a category.
[ISticker]: xref:Discord.ISticker
[ICustomSticker]: xref:Discord.ICustomSticker

## Activity Types
### Activity

* A **Game** ([Game]) refers to a user's game activity.
* A **Rich Presence** ([RichGame]) refers to a user's detailed

BIN
docs/guides/entities/images/IChannel.png View File

Before After
Width: 682  |  Height: 882  |  Size: 58 KiB

BIN
docs/guides/entities/images/IInteraction.png View File

Before After
Width: 682  |  Height: 602  |  Size: 37 KiB

BIN
docs/guides/entities/images/IMessage.png View File

Before After
Width: 682  |  Height: 522  |  Size: 36 KiB

BIN
docs/guides/entities/images/IUser.png View File

Before After
Width: 682  |  Height: 522  |  Size: 42 KiB

docs/guides/concepts/entities.md → docs/guides/entities/introduction.md View File

@@ -1,26 +1,41 @@
---
uid: Guides.Concepts.Entities
title: Entities
uid: Guides.Entities.Intro
title: Introduction
---

# Entities in Discord.Net

> [!NOTE]
> This article is written with the Socket variants of entities in mind,
> not the general interfaces or Rest entities.

Discord.Net provides a versatile entity system for navigating the
Discord API.

> [!TIP]
> It is **vital** that you use the proper IDs for an entity when using
> a `GetXXX` method. It is recommended that you enable Discord's
> _developer mode_ to allow easy access to entity IDs, found in
> Settings > Appearance > Advanced. Read more about it in the
> [FAQ](xref:FAQ.Basics.GetStarted) page.

## Inheritance

Due to the nature of the Discord API, some entities are designed with
multiple variants; for example, `SocketUser` and `SocketGuildUser`.
multiple variants; for example, `IUser` and `IGuildUser`.

All models will contain the most detailed version of an entity
possible, even if the type is less detailed.

For example, in the case of the `MessageReceived` event, a
## Socket & REST

REST entities are retrieved over REST, and will be disposed after use.
It is suggested to limit the amount of REST calls as much as possible,
as calls over REST interact with the API, and are thus prone to rate-limits.

- [Learn more about REST](https://restfulapi.net/)

Socket entities are created through the gateway,
most commonly through `DiscordSocketClient` events.
These entities will enter the clients' global cache for later use.

In the case of the `MessageReceived` event, a
`SocketMessage` is passed in with a channel property of type
`SocketMessageChannel`. All messages come from channels capable of
messaging, so this is the only variant of a channel that can cover
@@ -31,7 +46,9 @@ But that doesn't mean a message _can't_ come from a
retrieve information about a guild from a message entity, you will
need to cast its channel object to a `SocketTextChannel`.

You can find out various types of entities in the [Glossary page.](xref:FAQ.Glossary)
> [!NOTE]
> You can find out the inheritance tree & definitions of various entities
> [here](xref:Guides.Entities.Glossary)

## Navigation

@@ -40,26 +57,31 @@ you to easily navigate to an entity's parent or children. As explained
above, you will sometimes need to cast to a more detailed version of
an entity to navigate to its parent.

## Accessing Entities
## Accessing Socket Entities

The most basic forms of entities, `SocketGuild`, `SocketUser`, and
`SocketChannel` can be pulled from the DiscordSocketClient's global
cache, and can be retrieved using the respective `GetXXX` method on
DiscordSocketClient.

> [!TIP]
> It is **vital** that you use the proper IDs for an entity when using
> a `GetXXX` method. It is recommended that you enable Discord's
> _developer mode_ to allow easy access to entity IDs, found in
> Settings > Appearance > Advanced. Read more about it in the
> [FAQ](xref:FAQ.Basics.GetStarted) page.

More detailed versions of entities can be pulled from the basic
entities, e.g., `SocketGuild.GetUser`, which returns a
`SocketGuildUser`, or `SocketGuild.GetChannel`, which returns a
`SocketGuildChannel`. Again, you may need to cast these objects to get
a variant of the type that you need.

## Sample
### Sample

[!code-csharp[Socket Sample](samples/socketentities.cs)]

## Accessing REST Entities

REST entities work almost the same as Socket entities, but are much less frequently used.
To access REST entities, the `DiscordSocketClient`'s `Rest` property is required.
Another option here is to create your own [DiscordRestClient], independent of the Socket gateway.

[DiscordRestClient]: xref:Discord.Rest.DiscordRestClient

### Sample

[!code-csharp[Entity Sample](samples/entities.cs)]
[!code-csharp[Rest Sample](samples/restentities.cs)]

+ 7
- 0
docs/guides/entities/samples/casting.cs View File

@@ -0,0 +1,7 @@
// Say we have an entity, for the simplicity of this example, it will appear from thin air.
IChannel channel;

// If I want this to be an ITextChannel so I can access the properties of a text channel inside of a guild, an approach would be:
ITextChannel textChannel = channel as ITextChannel;

await textChannel.DoSomethingICantWithIChannelAsync();

+ 8
- 0
docs/guides/entities/samples/restentities.cs View File

@@ -0,0 +1,8 @@
// RestUser entities expose the accentcolor and banner of a user.
// This being one of the few use-cases for requesting a RestUser instead of depending on the Socket counterpart.
public static EmbedBuilder WithUserColor(this EmbedBuilder builder, IUser user)
{
var restUser = await _client.Rest.GetUserAsync(user.Id);
return builder.WithColor(restUser.AccentColor ?? Color.Blue);
// The accent color can still be null, so a check for this needs to be done to prevent an exception to be thrown.
}

+ 10
- 0
docs/guides/entities/samples/safety-cast-pass.cs View File

@@ -0,0 +1,10 @@
private void MyFunction(IMessage message)
{
// Here we do the reverse as in the previous examples, and let it continue the code below if it IS an IUserMessage
if (message is not IUserMessage userMessage)
return;

// Because we do the above check inline (dont give the statement a body),
// the code will still declare `userMessage` as available outside of the above statement.
Console.WriteLine(userMessage.Author);
}

+ 9
- 0
docs/guides/entities/samples/safety-cast-var.cs View File

@@ -0,0 +1,9 @@
IUser user;

// Here we can pre-define the actual declaration of said IGuildUser object,
// so we dont need to cast additionally inside of the statement.
if (user is IGuildUser guildUser)
{
Console.WriteLine(guildUser.JoinedAt);
}
// Check failed.

+ 14
- 0
docs/guides/entities/samples/safety-cast.cs View File

@@ -0,0 +1,14 @@
IUser user;

// Here we check if the user is an IGuildUser, if not, let it pass. This ensures its not null.
if (user is IGuildUser)
{
Console.WriteLine("This user is in a guild!");
}
// Check failed.

----------------------------
// Another situation, where we want to get the actual data of said IGuildUser.

----------------------------
// A final situation, where we dont actually need to do anything code-wise when the check does not pass, so we want to simplify it.

docs/guides/concepts/samples/entities.cs → docs/guides/entities/samples/socketentities.cs View File


+ 9
- 0
docs/guides/entities/samples/unboxing.cs View File

@@ -0,0 +1,9 @@
IUser user;

// Here we use inline unboxing to make a call to its member (if available) only once.

// Note that if the entity we're trying to cast to is null, this will throw a NullReferenceException.
Console.WriteLine(((IGuildUser)user).Nickname);

// In case you are certain the entity IS said member, you can also use unboxing to declare variables.
IGuildUser guildUser = (IGuildUser)user;

+ 8
- 0
docs/guides/toc.yml View File

@@ -23,6 +23,14 @@
topicUid: Guides.Concepts.ManageConnections
- name: Entities
topicUid: Guides.Concepts.Entities
- name: Entities
items:
- name: Introduction
topicUid: Guides.Entities.Intro
- name: Casting
topicUid: Guides.Entities.Casting
- name: Glossary & Flowcharts
topicUid: Guides.Entities.Glossary
- name: Working with Text-based Commands
items:
- name: Introduction


+ 0
- 77
samples/01_basic_ping_bot/Program.cs View File

@@ -1,77 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;

namespace _01_basic_ping_bot
{
// This is a minimal, bare-bones example of using Discord.Net
//
// If writing a bot with commands, we recommend using the Discord.Net.Commands
// framework, rather than handling commands yourself, like we do in this sample.
//
// You can find samples of using the command framework:
// - Here, under the 02_commands_framework sample
// - https://github.com/foxbot/DiscordBotBase - a bare-bones bot template
// - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library
class Program
{
private readonly DiscordSocketClient _client;

// Discord.Net heavily utilizes TAP for async, so we create
// an asynchronous context from the beginning.
static void Main(string[] args)
{
new Program().MainAsync().GetAwaiter().GetResult();
}

public Program()
{
// It is recommended to Dispose of a client when you are finished
// using it, at the end of your app's lifetime.
_client = new DiscordSocketClient();

_client.Log += LogAsync;
_client.Ready += ReadyAsync;
_client.MessageReceived += MessageReceivedAsync;
}

public async Task MainAsync()
{
// Tokens should be considered secret data, and never hard-coded.
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
await _client.StartAsync();

// Block the program until it is closed.
await Task.Delay(Timeout.Infinite);
}

private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}

// The Ready event indicates that the client has opened a
// connection and it is now safe to access the cache.
private Task ReadyAsync()
{
Console.WriteLine($"{_client.CurrentUser} is connected!");

return Task.CompletedTask;
}

// This is not the recommended way to write a bot - consider
// reading over the Commands Framework sample.
private async Task MessageReceivedAsync(SocketMessage message)
{
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;

if (message.Content == "!ping")
await message.Channel.SendMessageAsync("pong!");
}
}
}

+ 112
- 0
samples/BasicBot/Program.cs View File

@@ -0,0 +1,112 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;

namespace BasicBot
{
// This is a minimal, bare-bones example of using Discord.Net
//
// If writing a bot with commands/interactions, we recommend using the Discord.Net.Commands/Discord.Net.Interactions
// framework, rather than handling them yourself, like we do in this sample.
//
// You can find samples of using the command framework:
// - Here, under the TextCommandFramework sample
// - At the guides: https://discordnet.dev/guides/text_commands/intro.html
//
// You can find samples of using the interaction framework:
// - Here, under the InteractionFramework sample
// - At the guides: https://discordnet.dev/guides/int_framework/intro.html
class Program
{
// Non-static readonly fields can only be assigned in a constructor.
// If you want to assign it elsewhere, consider removing the readonly keyword.
private readonly DiscordSocketClient _client;

// Discord.Net heavily utilizes TAP for async, so we create
// an asynchronous context from the beginning.
static void Main(string[] args)
=> new Program()
.MainAsync()
.GetAwaiter()
.GetResult();

public Program()
{
// It is recommended to Dispose of a client when you are finished
// using it, at the end of your app's lifetime.
_client = new DiscordSocketClient();

// Subscribing to client events, so that we may receive them whenever they're invoked.
_client.Log += LogAsync;
_client.Ready += ReadyAsync;
_client.MessageReceived += MessageReceivedAsync;
_client.InteractionCreated += InteractionCreatedAsync;
}

public async Task MainAsync()
{
// Tokens should be considered secret data, and never hard-coded.
await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("token"));
// Different approaches to making your token a secret is by putting them in local .json, .yaml, .xml or .txt files, then reading them on startup.

await _client.StartAsync();

// Block the program until it is closed.
await Task.Delay(Timeout.Infinite);
}

private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}

// The Ready event indicates that the client has opened a
// connection and it is now safe to access the cache.
private Task ReadyAsync()
{
Console.WriteLine($"{_client.CurrentUser} is connected!");

return Task.CompletedTask;
}

// This is not the recommended way to write a bot - consider
// reading over the Commands Framework sample.
private async Task MessageReceivedAsync(SocketMessage message)
{
// The bot should never respond to itself.
if (message.Author.Id == _client.CurrentUser.Id)
return;


if (message.Content == "!ping")
{
// Create a new componentbuilder, in which dropdowns & buttons can be created.
var cb = new ComponentBuilder()
.WithButton("Click me!", "unique-id", ButtonStyle.Primary);

// Send a message with content 'pong', including a button.
// This button needs to be build by calling .Build() before being passed into the call.
await message.Channel.SendMessageAsync("pong!", components: cb.Build());
}
}

// For better functionality & a more developer-friendly approach to handling any kind of interaction, refer to:
// https://discordnet.dev/guides/int_framework/intro.html
private async Task InteractionCreatedAsync(SocketInteraction interaction)
{
// safety-casting is the best way to prevent something being cast from being null.
// If this check does not pass, it could not be cast to said type.
if (interaction is SocketMessageComponent component)
{
// Check for the ID created in the button mentioned above.
if (component.Data.CustomId == "unique-id")
await interaction.RespondAsync("Thank you for clicking my button!");

else Console.WriteLine("An ID has been received that has no handler!");
}
}
}
}

samples/01_basic_ping_bot/01_basic_ping_bot.csproj → samples/BasicBot/_BasicBot.csproj View File


+ 37
- 0
samples/InteractionFramework/Attributes/DoUserCheckAttribute.cs View File

@@ -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."));
}
}
}
}

samples/04_interactions_framework/RequireOwnerAttribute.cs → samples/InteractionFramework/Attributes/RequireOwnerAttribute.cs View File

@@ -6,7 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _04_interactions_framework
namespace InteractionFramework.Attributes
{
public class RequireOwnerAttribute : PreconditionAttribute
{

samples/04_interactions_framework/CommandHandler.cs → samples/InteractionFramework/CommandHandler.cs View File

@@ -8,7 +8,7 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace _04_interactions_framework
namespace InteractionFramework
{
public class CommandHandler
{
@@ -27,6 +27,9 @@ namespace _04_interactions_framework
{
// 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;
@@ -37,6 +40,8 @@ namespace _04_interactions_framework
_commands.ComponentCommandExecuted += ComponentCommandExecuted;
}

# region Error Handling

private Task ComponentCommandExecuted (ComponentCommandInfo arg1, Discord.IInteractionContext arg2, IResult arg3)
{
if (!arg3.IsSuccess)
@@ -123,6 +128,9 @@ namespace _04_interactions_framework

return Task.CompletedTask;
}
# endregion

# region Execution

private async Task HandleInteraction (SocketInteraction arg)
{
@@ -142,5 +150,6 @@ namespace _04_interactions_framework
await arg.GetOriginalResponseAsync().ContinueWith(async (msg) => await msg.Result.DeleteAsync());
}
}
# endregion
}
}

samples/04_interactions_framework/ExampleEnum.cs → samples/InteractionFramework/ExampleEnum.cs View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _04_interactions_framework
namespace InteractionFramework
{
public enum ExampleEnum
{

+ 19
- 0
samples/InteractionFramework/Modules/ComponentModule.cs View File

@@ -0,0 +1,19 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using InteractionFramework.Attributes;
using System;
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!");
}

samples/04_interactions_framework/Modules/UtilityModule.cs → samples/InteractionFramework/Modules/GeneralModule.cs View File

@@ -1,16 +1,17 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using InteractionFramework.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace _04_interactions_framework.Modules
namespace InteractionFramework.Modules
{
// Interation modules must be public and inherit from an IInterationModuleBase
public class UtilityModule : InteractionModuleBase<SocketInteractionContext>
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; }
@@ -18,7 +19,7 @@ namespace _04_interactions_framework.Modules
private CommandHandler _handler;

// Constructor injection is also a valid way to access the dependecies
public UtilityModule ( CommandHandler handler )
public GeneralModule(CommandHandler handler)
{
_handler = handler;
}

+ 30
- 0
samples/InteractionFramework/Modules/MessageCommandModule.cs View 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
- 0
samples/InteractionFramework/Modules/SlashCommandModule.cs View 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}");
}
}
}

+ 18
- 0
samples/InteractionFramework/Modules/UserCommandModule.cs View File

@@ -0,0 +1,18 @@
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using System;
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}>!");
}
}


samples/04_interactions_framework/Program.cs → samples/InteractionFramework/Program.cs View File

@@ -8,10 +8,11 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace _04_interactions_framework
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,
@@ -24,7 +25,7 @@ namespace _04_interactions_framework
RunAsync(config).GetAwaiter().GetResult();
}

static async Task RunAsync (IConfiguration configuration )
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);
@@ -64,14 +65,12 @@ namespace _04_interactions_framework
}

static ServiceProvider ConfigureServices ( IConfiguration configuration )
{
return new ServiceCollection()
=> new ServiceCollection()
.AddSingleton(configuration)
.AddSingleton<DiscordSocketClient>()
.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()))
.AddSingleton<CommandHandler>()
.BuildServiceProvider();
}

static bool IsDebug ( )
{

samples/04_interactions_framework/04_interactions_framework.csproj → samples/InteractionFramework/_InteractionFramework.csproj View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_04_interactions_framework</RootNamespace>
<RootNamespace>InteractionFramework</RootNamespace>
<StartupObject></StartupObject>
</PropertyGroup>


samples/03_sharded_client/Modules/PublicModule.cs → samples/ShardedClient/Modules/PublicModule.cs View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using Discord.Commands;

namespace _03_sharded_client.Modules
namespace ShardedClient.Modules
{
// Remember to make your module reference the ShardedCommandContext
public class PublicModule : ModuleBase<ShardedCommandContext>

samples/03_sharded_client/Program.cs → samples/ShardedClient/Program.cs View File

@@ -1,13 +1,13 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using _03_sharded_client.Services;
using ShardedClient.Services;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using Microsoft.Extensions.DependencyInjection;

namespace _03_sharded_client
namespace ShardedClient
{
// This is a minimal example of using Discord.Net's Sharded Client
// The provided DiscordShardedClient class simplifies having multiple
@@ -15,7 +15,11 @@ namespace _03_sharded_client
class Program
{
static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
=> new Program()
.MainAsync()
.GetAwaiter()
.GetResult();

public async Task MainAsync()
{
// You specify the amount of shards you'd like to have with the
@@ -51,13 +55,11 @@ namespace _03_sharded_client
}

private ServiceProvider ConfigureServices(DiscordSocketConfig config)
{
return new ServiceCollection()
=> new ServiceCollection()
.AddSingleton(new DiscordShardedClient(config))
.AddSingleton<CommandService>()
.AddSingleton<CommandHandlingService>()
.BuildServiceProvider();
}


private Task ReadyAsync(DiscordSocketClient shard)

samples/03_sharded_client/Services/CommandHandlingService.cs → samples/ShardedClient/Services/CommandHandlingService.cs View File

@@ -6,7 +6,7 @@ using Discord;
using Discord.Commands;
using Discord.WebSocket;

namespace _03_sharded_client.Services
namespace ShardedClient.Services
{
public class CommandHandlingService
{

samples/02_commands_framework/02_commands_framework.csproj → samples/ShardedClient/_ShardedClient.csproj View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>ShardedClient</RootNamespace>
</PropertyGroup>

<ItemGroup>

samples/02_commands_framework/Modules/PublicModule.cs → samples/TextCommandFramework/Modules/PublicModule.cs View File

@@ -2,9 +2,9 @@ using System.IO;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using _02_commands_framework.Services;
using TextCommandFramework.Services;

namespace _02_commands_framework.Modules
namespace TextCommandFramework.Modules
{
// Modules must be public and inherit from an IModuleBase
public class PublicModule : ModuleBase<SocketCommandContext>

samples/02_commands_framework/Program.cs → samples/TextCommandFramework/Program.cs View File

@@ -6,9 +6,9 @@ using Microsoft.Extensions.DependencyInjection;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
using _02_commands_framework.Services;
using TextCommandFramework.Services;

namespace _02_commands_framework
namespace TextCommandFramework
{
// This is a minimal example of using Discord.Net's command
// framework - by no means does it show everything the framework

samples/02_commands_framework/Services/CommandHandlingService.cs → samples/TextCommandFramework/Services/CommandHandlingService.cs View File

@@ -6,7 +6,7 @@ using Discord;
using Discord.Commands;
using Discord.WebSocket;

namespace _02_commands_framework.Services
namespace TextCommandFramework.Services
{
public class CommandHandlingService
{

samples/02_commands_framework/Services/PictureService.cs → samples/TextCommandFramework/Services/PictureService.cs View File

@@ -2,7 +2,7 @@ using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

namespace _02_commands_framework.Services
namespace TextCommandFramework.Services
{
public class PictureService
{

samples/03_sharded_client/03_sharded_client.csproj → samples/TextCommandFramework/_TextCommandFramework.csproj View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>_03_sharded_client</RootNamespace>
<RootNamespace>TextCommandFramework</RootNamespace>
</PropertyGroup>

<ItemGroup>

samples/04_webhook_client/Program.cs → samples/WebhookClient/Program.cs View File

@@ -2,7 +2,7 @@ using Discord;
using Discord.Webhook;
using System.Threading.Tasks;

namespace _04_webhook_client
namespace WebHookClient
{
// This is a minimal example of using Discord.Net's Webhook Client
// Webhooks are send-only components of Discord that allow you to make a POST request

samples/04_webhook_client/04_webhook_client.csproj → samples/WebhookClient/_WebhookClient.csproj View File

@@ -2,8 +2,8 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>_04_webhook_client</RootNamespace>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>WebHookClient</RootNamespace>
</PropertyGroup>

<ItemGroup>

samples/idn/Inspector.cs → samples/_idn/Inspector.cs View File


samples/idn/Program.cs → samples/_idn/Program.cs View File


samples/idn/idn.csproj → samples/_idn/idn.csproj View File


samples/idn/logview.ps1 → samples/_idn/logview.ps1 View File


Loading…
Cancel
Save