Browse Source

Merge labs 3.x

pull/1923/head
quin lynch 3 years ago
parent
commit
969e882ed9
38 changed files with 291 additions and 168 deletions
  1. +8
    -1
      docs/guides/interactions/application-commands/01-getting-started.md
  2. +7
    -1
      docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md
  3. +17
    -24
      docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md
  4. +5
    -22
      docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md
  5. +7
    -10
      docs/guides/interactions/application-commands/slash-commands/04-parameters.md
  6. +2
    -1
      docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md
  7. +12
    -15
      docs/guides/interactions/application-commands/slash-commands/06-subcommands.md
  8. +14
    -17
      docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md
  9. +7
    -4
      docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md
  10. +5
    -0
      docs/guides/interactions/message-components/01-getting-started.md
  11. +8
    -25
      docs/guides/interactions/message-components/02-responding-to-buttons.md
  12. +5
    -0
      docs/guides/interactions/message-components/03-buttons-in-depth.md
  13. +6
    -1
      docs/guides/interactions/message-components/04-select-menus.md
  14. +37
    -25
      docs/guides/interactions/message-components/05-advanced.md
  15. +21
    -3
      docs/guides/toc.yml
  16. +14
    -4
      src/Discord.Net.Core/CDN.cs
  17. +13
    -1
      src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs
  18. +3
    -2
      src/Discord.Net.Core/Entities/Channels/ChannelType.cs
  19. +5
    -0
      src/Discord.Net.Core/Entities/Roles/RoleProperties.cs
  20. +21
    -0
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  21. +5
    -1
      src/Discord.Net.Core/Entities/Users/UserProperties.cs
  22. +2
    -0
      src/Discord.Net.Rest/API/Common/GuildMember.cs
  23. +2
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs
  24. +3
    -1
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  25. +1
    -1
      src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
  26. +2
    -1
      src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs
  27. +7
    -0
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  28. +4
    -0
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  29. +12
    -0
      src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs
  30. +7
    -0
      src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
  31. +1
    -0
      src/Discord.Net.WebSocket/DiscordShardedClient.cs
  32. +9
    -1
      src/Discord.Net.WebSocket/DiscordSocketClient.cs
  33. +1
    -1
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  34. +2
    -4
      src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs
  35. +1
    -1
      src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs
  36. +6
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  37. +5
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
  38. +4
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs

+ 8
- 1
docs/guides/interactions/application-commands/01-getting-started.md View File

@@ -1,3 +1,9 @@
---
uid: Guides.SlashCommands.Intro
title: Introduction to slash commands
---


# Getting started with application commands.

Welcome! This guide will show you how to use application commands.
@@ -22,4 +28,5 @@ Head over to your discord applications OAuth2 screen and make sure to select the

From there you can then use the link to add your bot to a server.

**Note**: In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild.
> [!NOTE]
> In order for users in your guild to use your slash commands, they need to have the "Use Slash Command" permission on the guild.

+ 7
- 1
docs/guides/interactions/application-commands/context-menu-commands/creating-context-menu-commands.md View File

@@ -1,3 +1,8 @@
---
uid: Guides.ContextCommands.Creating
title: Creating Context Commands
---

# Creating context menu commands.

There are two kinds of Context Menu Commands: User Commands and Message Commands.
@@ -96,4 +101,5 @@ public async Task Client_Ready()

```

**Note**: Application commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register application commands.
> [!NOTE]
> Application commands only need to be created once. They do _not_ have to be 'created' on every startup or connection. The example simple shows creating them in the ready event as it's simpler than creating normal bot commands to register application commands.

+ 17
- 24
docs/guides/interactions/application-commands/context-menu-commands/receiving-context-menu-command-events.md View File

@@ -1,40 +1,33 @@
---
uid: Guides.ContextCommands.Reveiving
title: Receiving Context Commands
---

# Receiving Context Menu events

User commands and Message commands have their own unique objects returned. Different from Slash commands. To get the appropriate object returned, you can use a similar method to the slash commands.
User commands and Message commands have their own unique event just like the other interaction types. For user commands the event is `UserCommandExecuted` and for message commands the event is `MessageCommandExecuted`.

```cs
client.InteractionCreated += InteractionCreatedHandler;
// For message commands
client.MessageCommandExecuted += MessageCommandHandler;

// For user commands
client.UserCommandExecuted += UserCommandHandler;

...

public async Task InteractionCreatedHandler(SocketInteraction arg)
public async Task MessageCommandHandler(SocketMessageCommand arg)
{
if ( arg.Type == InteractionType.ApplicationCommand)
Task.Run(() => ApplicationCommandHandler(arg));
Console.Writeline("Message command received!");
}

public async Task ApplicationCommandHandler(SocketInteraction arg)
public async Task UserCommandHandler(SocketUserCommand arg)
{
switch (arg)
{
case SocketSlashCommand slashCommand:
Console.Writeline("Slash command received!");
break;
case SocketUserCommand userCommand:
Console.Writeline("User command received!")
// userCommand.User = User who ran command.
// userCommand.Data.Member = User who was clicked.
break;
case SocketMessageCommand messageCommand:
Console.Writeline("Message command received!")
// messageCommand.User = User who ran command.
// messageCommand.Data.Message = Message that was clicked.
break;
}
Console.Writeline("User command received!");
}
```

User commands return a SocketUser object, showing the user that was clicked to run the command.
Message commands return a SocketMessage object, showing the message that was clicked to run the command.
User commands contain a SocketUser object called `Member` in their data class, showing the user that was clicked to run the command.
Message commands contain a SocketMessage object called `Message` in their data class, showing the message that was clicked to run the command.

Both return the user who ran the command, the guild (if any), channel, etc.

+ 5
- 22
docs/guides/interactions/application-commands/slash-commands/03-responding-to-slash-commands.md View File

@@ -5,40 +5,25 @@ title: Receiving and Responding to Slash Commands

# Responding to interactions.

Interactions are the base thing sent over by Discord. Slash commands are one of the interaction types. In order to receive a slash command we have to listen to the `InteractionCreated` event. Let's add this to our code.
Interactions are the base thing sent over by Discord. Slash commands are one of the interaction types. We can listen to the `SlashCommandExecuted` event to respond to them. Lets add this to our code:

```cs
client.InteractionCreated += Client_InteractionCreated;
client.SlashCommandExecuted += SlashCommandHandler;

...

private async Task Client_InteractionCreated(SocketInteraction arg)
private async Task SlashCommandHandler(SocketSlashCommand command)
{

}
```

Now that we have the interaction event, let's talk about the `SocketInteraction` argument. The interaction can be cast to either a `SocketSlashCommand` or a `SocketMessageComponent`. In our case, we're trying to use slash commands so let's cast it to a `SocketSlashCommand`.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
{
if(arg is SocketSlashCommand command)
{
// we now have an instance of a SocketSlashCommand named command.
}
}
```

With every type of interaction there is a `Data` field. This is where the relevant information lives about our command that was executed. In our case, `Data` is a `SocketSlashCommandData` instance. In the data class, we can access the name of the command triggered as well as the options if there were any. For this example, we're just going to respond with the name of the command executed.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
private async Task SlashCommandHandler(SocketSlashCommand command)
{
if(arg is SocketSlashCommand command)
{
await command.RespondAsync($"You executed {command.Data.Name}");
}
await command.RespondAsync($"You executed {command.Data.Name}");
}
```

@@ -48,8 +33,6 @@ Let's try this out!

![slash command result](images/slashcommand2.png)

Let's go over the response types quickly, as you would only change them for style points :P

> [!NOTE]
> After receiving an interaction, you must respond to acknowledge it. You can choose to respond with a message immediately using `RespondAsync()` or you can choose to send a deferred response with `DeferAsync()`.
> If choosing a deferred response, the user will see a loading state for the interaction, and you'll have up to 15 minutes to edit the original deferred response using `ModifyOriginalResponseAsync()`. You can read more about response types [here](https://discord.com/developers/docs/interactions/slash-commands#interaction-response)


+ 7
- 10
docs/guides/interactions/application-commands/slash-commands/04-parameters.md View File

@@ -46,7 +46,7 @@ public async Task Client_Ready()
var guildCommand = new SlashCommandBuilder()
.WithName("list-roles")
.WithDescription("Lists all roles of a user.")
.AddOption("user", ApplicationCommandOptionType.User, "The users whos roles you want to be listed", required: true);
.AddOption("user", ApplicationCommandOptionType.User, "The users whos roles you want to be listed", isRequired: true);

try
{
@@ -66,17 +66,14 @@ public async Task Client_Ready()
That seems to be working, now Let's handle the interaction.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
private async Task SlashCommandHandler(SocketSlashCommand command)
{
if(arg is SocketSlashCommand command)
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
}
case "list-roles":
await HandleListRoleCommand(command);
break;
}
}



+ 2
- 1
docs/guides/interactions/application-commands/slash-commands/05-responding-ephemerally.md View File

@@ -7,7 +7,8 @@ title: Ephemeral Responses

What is an ephemeral response? Basically, only the user who executed the command can see the result of it, this is pretty simple to implement.

**Note**: You don't have to run arg.DeferAsync() to capture the interaction, you can use arg.RespondAsync() with a message to capture it, this also follows the ephemeral rule.
> [!NOTE]
> You don't have to run arg.DeferAsync() to capture the interaction, you can use arg.RespondAsync() with a message to capture it, this also follows the ephemeral rule.

When responding with either `FollowupAsync` or `RespondAsync` you can pass in an `ephemeral` property. When setting it to true it will respond ephemerally, false and it will respond non-ephemerally.



+ 12
- 15
docs/guides/interactions/application-commands/slash-commands/06-subcommands.md View File

@@ -86,7 +86,7 @@ public async Task Client_Ready()
.WithName("set")
.WithDescription("Sets the field A")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.String, "the value to set the field ", required: true)
.AddOption("value", ApplicationCommandOptionType.String, "the value to set the field", isRequired: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field A.")
@@ -100,7 +100,7 @@ public async Task Client_Ready()
.WithName("set")
.WithDescription("Sets the field B")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.Integer, "the value to set the fie to.", required: true)
.AddOption("value", ApplicationCommandOptionType.Integer, "the value to set the fie to.", isRequired: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field B.")
@@ -114,7 +114,7 @@ public async Task Client_Ready()
.WithName("set")
.WithDescription("Sets the field C")
.WithType(ApplicationCommandOptionType.SubCommand)
.AddOption("value", ApplicationCommandOptionType.Boolean, "the value to set the fie to.", required: true)
.AddOption("value", ApplicationCommandOptionType.Boolean, "the value to set the fie to.", isRequired: true)
).AddOption(new SlashCommandOptionBuilder()
.WithName("get")
.WithDescription("Gets the value of field C.")
@@ -140,20 +140,17 @@ All that code generates a command that looks like this:
Now that we have our command made, we need to handle the multiple options with this command. So lets add this into our handler:

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
private async Task SlashCommandHandler(SocketSlashCommand command)
{
if(arg is SocketSlashCommand command)
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(command);
break;
}
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(command);
break;
}
}



+ 14
- 17
docs/guides/interactions/application-commands/slash-commands/07-choice-slash-command.md View File

@@ -40,8 +40,8 @@ private async Task Client_Ready()
}
}
```
> **Note:** Your `ApplicationCommandOptionType` specifies which type your choices are, you need to use `ApplicationCommandOptionType.Integer` if choices whos value are numbers and `ApplicationCommandOptionType.String` for string values.
> [!NOTE]
> Your `ApplicationCommandOptionType` specifies which type your choices are, you need to use `ApplicationCommandOptionType.Integer` for choices whos values are whole numbers, `ApplicationCommandOptionType.Number` for choices whos values are doubles, and `ApplicationCommandOptionType.String` for string values.

We have defined 5 choices for the user to pick from, each choice has a value assigned to it. The value can either be a string or an int. In our case we're going to use an int. This is what the command looks like:

@@ -50,23 +50,20 @@ We have defined 5 choices for the user to pick from, each choice has a value ass
Lets add our code for handling the interaction.

```cs
private async Task Client_InteractionCreated(SocketInteraction arg)
private async Task SlashCommandHandler(SocketSlashCommand command)
{
if(arg is SocketSlashCommand command)
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
// Let's add a switch statement for the command name so we can handle multiple commands in one event.
switch(command.Data.Name)
{
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(HandleFeedbackCommand);
break;
case "feedback":
await HandleFeedbackCommand(command);
break;
}
case "list-roles":
await HandleListRoleCommand(command);
break;
case "settings":
await HandleSettingsCommand(command);
break;
case "feedback":
await HandleFeedbackCommand(command);
break;
}
}



+ 7
- 4
docs/guides/interactions/application-commands/slash-commands/08-bulk-overwrite-of-global-slash-commands.md View File

@@ -6,9 +6,11 @@ title: Slash Command Bulk Overwrites
If you have too many global commands then you might want to consider using the bulk overwrite function.

```cs
public async Task Client_Ready() {
public async Task Client_Ready()
{
List<ApplicationCommandProperties> applicationCommandProperties = new();
try {
try
{
// Simple help slash command.
SlashCommandBuilder globalCommandHelp = new SlashCommandBuilder();
globalCommandHelp.WithName("help");
@@ -28,10 +30,11 @@ public async Task Client_Ready() {
applicationCommandProperties.Add(globalCommandAddFamily.Build());

await _client.BulkOverwriteGlobalApplicationCommandsAsync(applicationCommandProperties.ToArray());
} catch (ApplicationCommandException exception) {
}
catch (ApplicationCommandException exception)
{
var json = JsonConvert.SerializeObject(exception.Error, Formatting.Indented);
Console.WriteLine(json);
}
Console.WriteLine("Client Ready: Finished");
}
```

+ 5
- 0
docs/guides/interactions/message-components/01-getting-started.md View File

@@ -1,3 +1,8 @@
---
uid: Guides.MessageComponents.GettingStarted
title: Getting Started with Components
---

# Message Components

Message components are a framework for adding interactive elements to a message your app or bot sends. They're accessible, customizable, and easy to use.


+ 8
- 25
docs/guides/interactions/message-components/02-responding-to-buttons.md View File

@@ -1,30 +1,23 @@
---
uid: Guides.MessageComponents.Responding
title: Responding to Components
---

# Responding to button clicks

Responding to buttons is pretty simple, there are a couple ways of doing it and we can cover both.

### Method 1: Hooking the InteractionCreated Event

We can hook the `InteractionCreated` event since button clicks are a form of interactions:
We can hook the `ButtonExecuted` event for button type interactions:

```cs
client.IntreactionCreated += MyInteractionHandler;
client.ButtonExecuted += MyButtonHandler;
```

Now, lets write our handler.

```cs
public async Task MyInteractionHandler(SocketInteraction arg)
{
// first we check the type of the interaction, this can be done with a switch statement
switch(arg)
{
case SocketMessageComponent component:
// we now have a variable defined as 'component' which contains our component data, lets pass it to a different handler.

break;
}
}

public async Task MyButtonHandler(SocketMessageComponent component)
{
// We can now check for our custom id
@@ -41,14 +34,4 @@ public async Task MyButtonHandler(SocketMessageComponent component)

Running it and clicking the button:

![](Images/image2.png)

### Method 2: Hooking the ButtonExecuted Event

This method skips the first switch statement because the `ButtonExecuted` event is only fired when a button is clicked, meaning we dont have to check the type of the interaction.

```cs
client.ButtonExecuted += MyButtonHandler;
```

The rest of the code is the same and produces the same result.
![](Images/image2.png)

+ 5
- 0
docs/guides/interactions/message-components/03-buttons-in-depth.md View File

@@ -1,3 +1,8 @@
---
uid: Guides.MessageComponents.Buttons
title: Buttons in Depth
---

# Buttons in depth

There are many changes you can make to buttons, lets take a look at the parameters in the `WithButton` function"


+ 6
- 1
docs/guides/interactions/message-components/04-select-menus.md View File

@@ -1,3 +1,8 @@
---
uid: Guides.MessageComponents.SelectMenus
title: Select Menus
---

# Select menus

Select menus allow users to select from a range of options, this can be quite useful with configuration commands etc.
@@ -48,7 +53,7 @@ And opening the menu we see:

![](Images/image5.png)

Lets handle the selection of an option, as before we can hook the `InteractionCreated` event and check the type ourself but for this example im just going to use the `SelectMenuExecuted` event
Lets handle the selection of an option, We can hook the `SelectMenuExecuted` event to handle our select menu:

```cs
client.SelectMenuExecuted += MyMenuHandler;


+ 37
- 25
docs/guides/interactions/message-components/05-advanced.md View File

@@ -1,3 +1,8 @@
---
uid: Guides.MessageComponents.Advanced
title: Advanced Concepts
---

# Advanced

Lets say you have some components on an ephemeral slash command, and you want to modify the message that the button is on. The issue with this is that ephemeral messages are not stored and can not be get via rest or other means.
@@ -45,31 +50,38 @@ break;
Now, let's listen to the select menu executed event and add a case for `select-1`

```cs
switch (arg.Data.CustomId)
client.SelectMenuExecuted += SelectMenuHandler;

...

public async Task SelectMenuHandler(SocketMessageComponent arg)
{
case "select-1":
var value = arg.Data.Values.First();
var menu = new SelectMenuBuilder()
{
CustomId = "select-1",
Placeholder = $"{(arg.Message.Components.First().Components.First() as SelectMenu).Options.FirstOrDefault(x => x.Value == value).Label}",
MaxValues = 1,
MinValues = 1,
Disabled = true
};

menu.AddOption("Meh", "1", "Its not gaming.")
.AddOption("Ish", "2", "Some would say that this is gaming.")
.AddOption("Moderate", "3", "It could pass as gaming")
.AddOption("Confirmed", "4", "We are gaming")
.AddOption("Excellent", "5", "It is renowned as gaming nation wide", new Emoji("🔥"));

// We use UpdateAsync to update the message and its original content and components.
await arg.UpdateAsync(x =>
{
x.Content = $"Thank you {arg.User.Mention} for rating us {value}/5 on the gaming scale";
x.Components = new ComponentBuilder().WithSelectMenu(menu).Build();
});
break;
switch (arg.Data.CustomId)
{
case "select-1":
var value = arg.Data.Values.First();
var menu = new SelectMenuBuilder()
{
CustomId = "select-1",
Placeholder = $"{(arg.Message.Components.First().Components.First() as SelectMenu).Options.FirstOrDefault(x => x.Value == value).Label}",
MaxValues = 1,
MinValues = 1,
Disabled = true
};
menu.AddOption("Meh", "1", "Its not gaming.")
.AddOption("Ish", "2", "Some would say that this is gaming.")
.AddOption("Moderate", "3", "It could pass as gaming")
.AddOption("Confirmed", "4", "We are gaming")
.AddOption("Excellent", "5", "It is renowned as gaming nation wide", new Emoji("🔥"));
// We use UpdateAsync to update the message and its original content and components.
await arg.UpdateAsync(x =>
{
x.Content = $"Thank you {arg.User.Mention} for rating us {value}/5 on the gaming scale";
x.Components = new ComponentBuilder().WithSelectMenu(menu).Build();
});
break;
}
}
```

+ 21
- 3
docs/guides/toc.yml View File

@@ -35,10 +35,10 @@
topicUid: Guides.Commands.DI
- name: Post-execution Handling
topicUid: Guides.Commands.PostExecution
- name: Working with Interactions
- name: Working with Slash commands
items:
- name: Introduction
topicUid: Guides.Interactions.Intro
topicUid: Guides.SlashCommands.Intro
- name: Creating slash commands
topicUid: Guides.SlashCommands.Creating
- name: Receiving and responding to slash commands
@@ -51,8 +51,26 @@
topicUid: Guides.SlashCommands.SubCommand
- name: Slash command choices
topicUid: Guides.SlashCommands.Choices
- name: Slash ommands Bulk Overwrites
- name: Slash commands Bulk Overwrites
topicUid: Guides.SlashCommands.BulkOverwrite
- name: Working with Context commands
items:
- name: Creating Context Commands
topicUid: Guides.ContextCommands.Creating
- name: Receiving Context Commands
topicUid: Guides.ContextCommands.Reveiving
- name: Working with Message Components
items:
- name: Getting started
topicUid: Guides.MessageComponents.GettingStarted
- name: Responding to Components
topicUid: Guides.MessageComponents.Responding
- name: Buttons in depth
topicUid: Guides.MessageComponents.Buttons
- name: Select menus
topicUid: Guides.MessageComponents.SelectMenus
- name: Advanced Concepts
topicUid: Guides.MessageComponents.Advanced
- name: Emoji
topicUid: Guides.Emoji
- name: Voice


+ 14
- 4
src/Discord.Net.Core/CDN.cs View File

@@ -47,6 +47,14 @@ namespace Discord
return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}";
}

public static string GetGuildUserAvatarUrl(ulong userId, ulong guildId, string avatarId, ushort size, ImageFormat format)
{
if (avatarId == null)
return null;
string extension = FormatToExtension(format, avatarId);
return $"{DiscordConfig.CDNUrl}guilds/{guildId}/users/{userId}/avatars/{avatarId}.{extension}?size={size}";
}

/// <summary>
/// Returns a user banner URL.
/// </summary>
@@ -131,15 +139,17 @@ namespace Discord
/// </summary>
/// <param name="guildId">The guild snowflake identifier.</param>
/// <param name="bannerId">The banner image identifier.</param>
/// <param name="format">The format to return.</param>
/// <param name="size">The size of the image to return in horizontal pixels. This can be any power of two between 16 and 2048 inclusive.</param>
/// <returns>
/// A URL pointing to the guild's banner image.
/// </returns>
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ushort? size = null)
public static string GetGuildBannerUrl(ulong guildId, string bannerId, ImageFormat format, ushort? size = null)
{
if (!string.IsNullOrEmpty(bannerId))
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.jpg" + (size.HasValue ? $"?size={size}" : string.Empty);
return null;
if (string.IsNullOrEmpty(bannerId))
return null;
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{guildId}/{bannerId}.{extension}" + (size.HasValue ? $"?size={size}" : string.Empty);
}
/// <summary>
/// Returns an emoji URL.


+ 13
- 1
src/Discord.Net.Core/Entities/Activities/ActivityProperties.cs View File

@@ -33,6 +33,18 @@ namespace Discord
/// <summary>
/// Indicates that a user can play this song.
/// </summary>
Play = 0b100000
Play = 0b100000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel with friends.
/// </summary>
PARTY_PRIVACY_FRIENDS = 0b1000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
PARTY_PRIVACY_VOICE_CHANNEL = 0b10000000,
/// <summary>
/// Indicates that a user is playing an activity in a voice channel.
/// </summary>
EMBEDDED = 0b10000000
}
}

+ 3
- 2
src/Discord.Net.Core/Entities/Channels/ChannelType.cs View File

@@ -24,7 +24,8 @@ namespace Discord
/// <summary> The channel is a private temporary thread channel under a text channel. </summary>
PrivateThread = 12,
/// <summary> The channel is a stage voice channel. </summary>
Stage = 13

Stage = 13,
/// <summary> The channel is a guild directory used in hub servers. (Unreleased)</summary>
GuildDirectory = 14
}
}

+ 5
- 0
src/Discord.Net.Core/Entities/Roles/RoleProperties.cs View File

@@ -50,6 +50,11 @@ namespace Discord
/// This value may not be set if the role is an @everyone role.
/// </remarks>
public Optional<bool> Hoist { get; set; }

/// <summary>
/// Gets or sets the icon of the role.
/// </summary>
public Optional<Image> Icon { get; set; }
/// <summary>
/// Gets or sets whether or not this role can be mentioned.
/// </summary>


+ 21
- 0
src/Discord.Net.Core/Entities/Users/IGuildUser.cs View File

@@ -25,6 +25,13 @@ namespace Discord
/// </returns>
string Nickname { get; }
/// <summary>
/// Gets the guild specific avatar for this users.
/// </summary>
/// <returns>
/// The users guild avatar hash if they have one; otherwise <see langword="null"/>.
/// </returns>
string GuildAvatarId { get; }
/// <summary>
/// Gets the guild-level permissions for this user.
/// </summary>
/// <returns>
@@ -96,6 +103,20 @@ namespace Discord
/// </returns>
ChannelPermissions GetPermissions(IGuildChannel channel);

/// <summary>
/// Gets the guild avatar URL for this user.
/// </summary>
/// <remarks>
/// This property retrieves a URL for this guild user's guild specific avatar. In event that the user does not have a valid guild avatar
/// (i.e. their avatar identifier is not set), this method will return <c>null</c>.
/// </remarks>
/// <param name="format">The format to return.</param>
/// <param name="size">The size of the image to return in. This can be any power of two between 16 and 2048.
/// </param>
/// <returns>
/// A string representing the user's avatar URL; <c>null</c> if the user does not have an avatar in place.
/// </returns>
string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
/// <summary>
/// Kicks this user from this guild.
/// </summary>


+ 5
- 1
src/Discord.Net.Core/Entities/Users/UserProperties.cs View File

@@ -61,9 +61,13 @@ namespace Discord
/// Flag given to users that developed bots and early verified their accounts.
/// </summary>
EarlyVerifiedBotDeveloper = 1 << 17,
/// <summary>
/// <summary>
/// Flag given to users that are discord certified moderators who has give discord's exam.
/// </summary>
DiscordCertifiedModerator = 1 << 18,
/// <summary>
/// Flag given to bots that use only outgoing webhooks, exclusively.
/// </summary>
BotHTTPInteractions = 1 << 19,
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/GuildMember.cs View File

@@ -9,6 +9,8 @@ namespace Discord.API
public User User { get; set; }
[JsonProperty("nick")]
public Optional<string> Nick { get; set; }
[JsonProperty("avatar")]
public Optional<string> Avatar { get; set; }
[JsonProperty("roles")]
public Optional<ulong[]> Roles { get; set; }
[JsonProperty("joined_at")]


+ 2
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs View File

@@ -13,6 +13,8 @@ namespace Discord.API.Rest
public Optional<uint> Color { get; set; }
[JsonProperty("hoist")]
public Optional<bool> Hoist { get; set; }
[JsonProperty("icon")]
public Optional<Image> Icon { get; set; }
[JsonProperty("mentionable")]
public Optional<bool> Mentionable { get; set; }
}


+ 3
- 1
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -373,7 +373,9 @@ namespace Discord.API
Preconditions.NotNull(args, nameof(args));
Preconditions.AtLeast(args.Position, 0, nameof(args.Position));
Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name));
Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name));

if(args.Name.IsSpecified)
Preconditions.LessThan(args.Name.Value.Length, 100, nameof(args.Name));

options = RequestOptions.CreateOrClone(options);



+ 1
- 1
src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs View File

@@ -102,7 +102,7 @@ namespace Discord.Rest
/// <inheritdoc />
public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId);
/// <inheritdoc />
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId);
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId, ImageFormat.Auto);

/// <summary>
/// Gets the built-in role containing all users in this guild.


+ 2
- 1
src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs View File

@@ -24,7 +24,8 @@ namespace Discord.Rest
Hoist = args.Hoist,
Mentionable = args.Mentionable,
Name = args.Name,
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create<string>()
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create<string>(),
Icon = args.Icon.IsSpecified ? args.Icon.Value.ToModel() : Optional<API.Image>.Unspecified
};
var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false);



+ 7
- 0
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -21,6 +21,8 @@ namespace Discord.Rest

/// <inheritdoc />
public string Nickname { get; private set; }
/// <inheritdoc/>
public string GuildAvatarId { get; private set; }
internal IGuild Guild { get; private set; }
/// <inheritdoc />
public bool IsDeafened { get; private set; }
@@ -80,6 +82,8 @@ namespace Discord.Rest
_joinedAtTicks = model.JoinedAt.Value.UtcTicks;
if (model.Nick.IsSpecified)
Nickname = model.Nick.Value;
if (model.Avatar.IsSpecified)
GuildAvatarId = model.Avatar.Value;
if (model.Deaf.IsSpecified)
IsDeafened = model.Deaf.Value;
if (model.Mute.IsSpecified)
@@ -156,6 +160,9 @@ namespace Discord.Rest
var guildPerms = GuildPermissions;
return new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, guildPerms.RawValue));
}

public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetGuildUserAvatarUrl(Id, GuildId, GuildAvatarId, size, format);
#endregion

#region IGuildUser


+ 4
- 0
src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs View File

@@ -54,6 +54,10 @@ namespace Discord.Rest
/// <inheritdoc />
string IGuildUser.Nickname => null;
/// <inheritdoc />
string IGuildUser.GuildAvatarId => null;
/// <inheritdoc />
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null;
/// <inheritdoc />
bool? IGuildUser.IsPending => null;
/// <inheritdoc />
int IGuildUser.Hierarchy => 0;


+ 12
- 0
src/Discord.Net.WebSocket/API/Gateway/GuildJoinRequestDeleteEvent.cs View File

@@ -0,0 +1,12 @@
using Newtonsoft.Json;

namespace Discord.API.Gateway
{
internal class GuildJoinRequestDeleteEvent
{
[JsonProperty("user_id")]
public ulong UserId { get; set; }
[JsonProperty("guild_id")]
public ulong GuildId { get; set; }
}
}

+ 7
- 0
src/Discord.Net.WebSocket/BaseSocketClient.Events.cs View File

@@ -335,6 +335,13 @@ namespace Discord.WebSocket
remove { _guildUpdatedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<SocketGuild, SocketGuild, Task>> _guildUpdatedEvent = new AsyncEvent<Func<SocketGuild, SocketGuild, Task>>();
/// <summary>Fired when a user leaves without agreeing to the member screening </summary>
public event Func<ulong, ulong, Task> GuildJoinRequestDeleted
{
add { _guildJoinRequestDeletedEvent.Add(value); }
remove { _guildJoinRequestDeletedEvent.Remove(value); }
}
internal readonly AsyncEvent<Func<ulong, ulong, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent<Func<ulong, ulong, Task>>();
#endregion

#region Users


+ 1
- 0
src/Discord.Net.WebSocket/DiscordShardedClient.cs View File

@@ -485,6 +485,7 @@ namespace Discord.WebSocket
client.GuildStickerCreated += (sticker) => _guildStickerCreated.InvokeAsync(sticker);
client.GuildStickerDeleted += (sticker) => _guildStickerDeleted.InvokeAsync(sticker);
client.GuildStickerUpdated += (before, after) => _guildStickerUpdated.InvokeAsync(before, after);
client.GuildJoinRequestDeleted += (userId, guildId) => _guildJoinRequestDeletedEvent.InvokeAsync(userId, guildId);
}
#endregion



+ 9
- 1
src/Discord.Net.WebSocket/DiscordSocketClient.cs View File

@@ -74,7 +74,7 @@ namespace Discord.WebSocket
internal WebSocketProvider WebSocketProvider { get; private set; }
internal bool AlwaysDownloadUsers { get; private set; }
internal int? HandlerTimeout { get; private set; }
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient;
internal new DiscordSocketApiClient ApiClient => base.ApiClient;
/// <inheritdoc />
public override IReadOnlyCollection<SocketGuild> Guilds => State.Guilds;
/// <inheritdoc/>
@@ -1004,6 +1004,14 @@ namespace Discord.WebSocket
}
}
break;
case "GUILD_JOIN_REQUEST_DELETE":
{
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_JOIN_REQUEST_DELETE)").ConfigureAwait(false);

var data = (payload as JToken).ToObject<GuildJoinRequestDeleteEvent>(_serializer);
await TimedInvokeAsync(_guildJoinRequestDeletedEvent, nameof(GuildJoinRequestDeleted), data.UserId, data.GuildId).ConfigureAwait(false);
}
break;
#endregion

#region Channels


+ 1
- 1
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -138,7 +138,7 @@ namespace Discord.WebSocket
/// <inheritdoc />
public string DiscoverySplashUrl => CDN.GetGuildDiscoverySplashUrl(Id, DiscoverySplashId);
/// <inheritdoc />
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId);
public string BannerUrl => CDN.GetGuildBannerUrl(Id, BannerId, ImageFormat.Auto);
/// <summary> Indicates whether the client has all the members downloaded to the local guild cache. </summary>
public bool HasAllMembers => MemberCount <= DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted;
/// <summary> Indicates whether the guild cache is synced to this guild. </summary>


+ 2
- 4
src/Discord.Net.WebSocket/Entities/Interaction/Slash Commands/SocketAutocompleteInteractionData.cs View File

@@ -60,14 +60,12 @@ namespace Discord.WebSocket
{
var options = new List<AutocompleteOption>();

options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.GetValueOrDefault(false)));

if (model.Options.IsSpecified)
{
options.AddRange(model.Options.Value.SelectMany(GetOptions));
}
else if(model.Focused.IsSpecified)
{
options.Add(new AutocompleteOption(model.Type, model.Name, model.Value.GetValueOrDefault(null), model.Focused.Value));
}

return options;
}


+ 1
- 1
src/Discord.Net.WebSocket/Entities/Interaction/SocketBaseCommand/SocketCommandBase.cs View File

@@ -178,8 +178,8 @@ namespace Discord.WebSocket
/// <inheritdoc/>
public override async Task<RestFollowupMessage> FollowupWithFileAsync(
Stream fileStream,
string fileName,
string text = null,
string fileName = null,
Embed[] embeds = null,
bool isTTS = false,
bool ephemeral = false,


+ 6
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -30,7 +30,8 @@ namespace Discord.WebSocket
public SocketGuild Guild { get; }
/// <inheritdoc />
public string Nickname { get; private set; }

/// <inheritdoc/>
public string GuildAvatarId { get; private set; }
/// <inheritdoc />
public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } }
/// <inheritdoc />
@@ -154,6 +155,8 @@ namespace Discord.WebSocket
_joinedAtTicks = model.JoinedAt.Value.UtcTicks;
if (model.Nick.IsSpecified)
Nickname = model.Nick.Value;
if (model.Avatar.IsSpecified)
GuildAvatarId = model.Avatar.Value;
if (model.Roles.IsSpecified)
UpdateRoles(model.Roles.Value);
if (model.PremiumSince.IsSpecified)
@@ -218,6 +221,8 @@ namespace Discord.WebSocket
/// <inheritdoc />
public ChannelPermissions GetPermissions(IGuildChannel channel)
=> new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue));
public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetGuildUserAvatarUrl(Id, Guild.Id, GuildAvatarId, size, format);

private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)";
internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser;


+ 5
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs View File

@@ -52,6 +52,9 @@ namespace Discord.WebSocket
get => GuildUser.AvatarId;
internal set => GuildUser.AvatarId = value;
}
/// <inheritdoc/>
public string GuildAvatarId
=> GuildUser.GuildAvatarId;

/// <inheritdoc/>
public override string BannerId
@@ -205,6 +208,8 @@ namespace Discord.WebSocket
/// <inheritdoc/>
IReadOnlyCollection<ulong> IGuildUser.RoleIds => GuildUser.Roles.Select(x => x.Id).ToImmutableArray();

string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => GuildUser.GetGuildAvatarUrl(format, size);

internal override SocketGlobalUser GlobalUser => GuildUser.GlobalUser;

internal override SocketPresence Presence { get => GuildUser.Presence; set => GuildUser.Presence = value; }


+ 4
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -81,6 +81,10 @@ namespace Discord.WebSocket
/// <inheritdoc />
string IGuildUser.Nickname => null;
/// <inheritdoc />
string IGuildUser.GuildAvatarId => null;
/// <inheritdoc />
string IGuildUser.GetGuildAvatarUrl(ImageFormat format, ushort size) => null;
/// <inheritdoc />
DateTimeOffset? IGuildUser.PremiumSince => null;
/// <inheritdoc />
bool? IGuildUser.IsPending => null;


Loading…
Cancel
Save