* guild events initial * sharded events * Add new gateway intents and fix bugs * More work on new changes to guild events * Update guild scheduled events * Added events to extended guild and add event start event * Update preconditions * Implement breaking changes guild guild events. Add guild event permissions * Update tests and change privacy level requirements * Update summaries and add docs for guild eventspull/1923/head
@@ -0,0 +1,31 @@ | |||||
--- | |||||
uid: Guides.GuildEvents.Creating | |||||
title: Creating Guild Events | |||||
--- | |||||
# Creating guild events | |||||
You can create new guild events by using the `CreateEventAsync` function on a guild. | |||||
### Parameters | |||||
| Name | Type | Summary | | |||||
| ------------- | --------------------------------- | ---------------------------------------------------------------------------- | | |||||
| name | `string` | Sets the name of the event. | | |||||
| startTime | `DateTimeOffset` | Sets the start time of the event. | | |||||
| type | `GuildScheduledEventType` | Sets the type of the event. | | |||||
| privacyLevel? | `GuildScheduledEventPrivacyLevel` | Sets the privacy level of the event | | |||||
| description? | `string` | Sets the description of the event. | | |||||
| endTime? | `DateTimeOffset?` | Sets the end time of the event. | | |||||
| channelId? | `ulong?` | Sets the channel id of the event, only valid on stage or voice channel types | | |||||
| location? | `string` | Sets the location of the event, only valid on external types | | |||||
Lets create a basic test event. | |||||
```cs | |||||
var guild = client.GetGuild(guildId); | |||||
var guildEvent = await guild.CreateEventAsync("test event", DateTimeOffset.UtcNow.AddDays(1), GuildScheduledEventType.External, endTime: DateTimeOffset.UtcNow.AddDays(2), location: "Space"); | |||||
``` | |||||
This code will create an event that lasts a day and starts tomorrow. It will be an external event thats in space. |
@@ -0,0 +1,16 @@ | |||||
--- | |||||
uid: Guides.GuildEvents.GettingUsers | |||||
title: Getting Guild Event Users | |||||
--- | |||||
# Getting Event Users | |||||
You can get a collection of users who are currently interested in the event by calling `GetUsersAsync`. This method works like any other get users method as in it returns an async enumerable. This method also supports pagination by user id. | |||||
```cs | |||||
// get all users and flatten the result into one collection. | |||||
var users = await event.GetUsersAsync().FlattenAsync(); | |||||
// get users around the 613425648685547541 id. | |||||
var aroundUsers = await event.GetUsersAsync(613425648685547541, Direction.Around).FlattenAsync(); | |||||
``` |
@@ -0,0 +1,41 @@ | |||||
--- | |||||
uid: Guides.GuildEvents.Intro | |||||
title: Introduction to Guild Events | |||||
--- | |||||
# Guild Events | |||||
Guild events are a way to host events within a guild. They offer alot of features and flexibility. | |||||
## Getting started with guild events | |||||
You can access any events within a guild by calling `GetEventsAsync` on a guild. | |||||
```cs | |||||
var guildEvents = await guild.GetEventsAsync(); | |||||
``` | |||||
If your working with socket guilds you can just use the `Events` property: | |||||
```cs | |||||
var guildEvents = guild.Events; | |||||
``` | |||||
There are also new gateway events that you can hook to receive guild scheduled events on. | |||||
```cs | |||||
// Fired when a guild event is cancelled. | |||||
client.GuildScheduledEventCancelled += ... | |||||
// Fired when a guild event is completed. | |||||
client.GuildScheduledEventCompleted += ... | |||||
// Fired when a guild event is started. | |||||
client.GuildScheduledEventStarted += ... | |||||
// Fired when a guild event is created. | |||||
client.GuildScheduledEventCreated += ... | |||||
// Fired when a guild event is updated. | |||||
client.GuildScheduledEventUpdated += ... | |||||
``` |
@@ -0,0 +1,23 @@ | |||||
--- | |||||
uid: Guides.GuildEvents.Modifying | |||||
title: Modifying Guild Events | |||||
--- | |||||
# Modifying Events | |||||
You can modify events using the `ModifyAsync` method to modify the event, heres the properties you can modify: | |||||
| Name | Type | Description | | |||||
| ------------ | --------------------------------- | -------------------------------------------- | | |||||
| ChannelId | `ulong?` | Gets or sets the channel id of the event. | | |||||
| string | `string` | Gets or sets the location of this event. | | |||||
| Name | `string` | Gets or sets the name of the event. | | |||||
| PrivacyLevel | `GuildScheduledEventPrivacyLevel` | Gets or sets the privacy level of the event. | | |||||
| StartTime | `DateTimeOffset` | Gets or sets the start time of the event. | | |||||
| EndTime | `DateTimeOffset` | Gets or sets the end time of the event. | | |||||
| Description | `string` | Gets or sets the description of the event. | | |||||
| Type | `GuildScheduledEventType` | Gets or sets the type of the event. | | |||||
| Status | `GuildScheduledEventStatus` | Gets or sets the status of the event. | | |||||
> [!NOTE] | |||||
> All of these properties are optional. |
@@ -1,5 +1,15 @@ | |||||
- name: Introduction | - name: Introduction | ||||
topicUid: Guides.Introduction | topicUid: Guides.Introduction | ||||
- name: "Working with Guild Events" | |||||
items: | |||||
- name: Introduction | |||||
topicUid: Guides.GuildEvents.Intro | |||||
- name: Creating Events | |||||
topicUid: Guides.GuildEvents.Creating | |||||
- name: Getting Event Users | |||||
topicUid: Guides.GuildEvents.GettingUsers | |||||
- name: Modifying Events | |||||
topicUid: Guides.GuildEvents.Modifying | |||||
- name: Working with Slash commands | - name: Working with Slash commands | ||||
items: | items: | ||||
- name: Introduction | - name: Introduction | ||||
@@ -94,6 +94,13 @@ namespace Discord | |||||
/// The maximum number of users that can be gotten per-batch. | /// The maximum number of users that can be gotten per-batch. | ||||
/// </returns> | /// </returns> | ||||
public const int MaxUsersPerBatch = 1000; | public const int MaxUsersPerBatch = 1000; | ||||
/// <summary> | |||||
/// Returns the max users allowed to be in a request for guild event users. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// The maximum number of users that can be gotten per-batch. | |||||
/// </returns> | |||||
public const int MaxGuildEventUsersPerBatch = 100; | |||||
/// <summary> | /// <summary> | ||||
/// Returns the max guilds allowed to be in a request. | /// Returns the max guilds allowed to be in a request. | ||||
/// </summary> | /// </summary> | ||||
@@ -0,0 +1,25 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents the privacy level of a guild scheduled event. | |||||
/// </summary> | |||||
public enum GuildScheduledEventPrivacyLevel | |||||
{ | |||||
/// <summary> | |||||
/// The scheduled event is public and available in discovery. | |||||
/// </summary> | |||||
[Obsolete("This event type isn't supported yet! check back later.", true)] | |||||
Public = 1, | |||||
/// <summary> | |||||
/// The scheduled event is only accessible to guild members. | |||||
/// </summary> | |||||
Private = 2, | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents the status of a guild event. | |||||
/// </summary> | |||||
public enum GuildScheduledEventStatus | |||||
{ | |||||
/// <summary> | |||||
/// The event is scheduled for a set time. | |||||
/// </summary> | |||||
Scheduled = 1, | |||||
/// <summary> | |||||
/// The event has started. | |||||
/// </summary> | |||||
Active = 2, | |||||
/// <summary> | |||||
/// The event was completed. | |||||
/// </summary> | |||||
Completed = 3, | |||||
/// <summary> | |||||
/// The event was canceled. | |||||
/// </summary> | |||||
Cancelled = 4, | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents the type of a guild scheduled event. | |||||
/// </summary> | |||||
public enum GuildScheduledEventType | |||||
{ | |||||
/// <summary> | |||||
/// The event doesn't have a set type. | |||||
/// </summary> | |||||
None = 0, | |||||
/// <summary> | |||||
/// The event is set in a stage channel. | |||||
/// </summary> | |||||
Stage = 1, | |||||
/// <summary> | |||||
/// The event is set in a voice channel. | |||||
/// </summary> | |||||
Voice = 2, | |||||
/// <summary> | |||||
/// The event is set for somewhere externally from discord. | |||||
/// </summary> | |||||
External = 3, | |||||
} | |||||
} |
@@ -0,0 +1,58 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Provides properties that are used to modify an <see cref="IGuildScheduledEvent" /> with the specified changes. | |||||
/// </summary> | |||||
public class GuildScheduledEventsProperties | |||||
{ | |||||
/// <summary> | |||||
/// Gets or sets the channel id of the event. | |||||
/// </summary> | |||||
public Optional<ulong?> ChannelId { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the location of this event. | |||||
/// </summary> | |||||
public Optional<string> Location { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the name of the event. | |||||
/// </summary> | |||||
public Optional<string> Name { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the privacy level of the event. | |||||
/// </summary> | |||||
public Optional<GuildScheduledEventPrivacyLevel> PrivacyLevel { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the start time of the event. | |||||
/// </summary> | |||||
public Optional<DateTimeOffset> StartTime { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the end time of the event. | |||||
/// </summary> | |||||
public Optional<DateTimeOffset> EndTime { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the description of the event. | |||||
/// </summary> | |||||
public Optional<string> Description { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the type of the event. | |||||
/// </summary> | |||||
public Optional<GuildScheduledEventType> Type { get; set; } | |||||
/// <summary> | |||||
/// Gets or sets the status of the event. | |||||
/// </summary> | |||||
public Optional<GuildScheduledEventStatus> Status { get; set; } | |||||
} | |||||
} |
@@ -1056,6 +1056,58 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null); | Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null); | ||||
/// <summary> | |||||
/// Gets a event within this guild. | |||||
/// </summary> | |||||
/// <param name="id">The id of the event.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
Task<IGuildScheduledEvent> GetEventAsync(ulong id, RequestOptions options = null); | |||||
/// <summary> | |||||
/// Gets a collection of events within this guild. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
Task<IReadOnlyCollection<IGuildScheduledEvent>> GetEventsAsync(RequestOptions options = null); | |||||
/// <summary> | |||||
/// Creates an event within this guild. | |||||
/// </summary> | |||||
/// <param name="name">The name of the event.</param> | |||||
/// <param name="privacyLevel">The privacy level of the event.</param> | |||||
/// <param name="startTime">The start time of the event.</param> | |||||
/// <param name="type">The type of the event.</param> | |||||
/// <param name="description">The description of the event.</param> | |||||
/// <param name="endTime">The end time of the event.</param> | |||||
/// <param name="channelId"> | |||||
/// The channel id of the event. | |||||
/// <remarks> | |||||
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/> | |||||
/// in order to use this property. | |||||
/// </remarks> | |||||
/// </param> | |||||
/// <param name="speakers">A collection of speakers for the event.</param> | |||||
/// <param name="location">The location of the event; links are supported</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous create operation. | |||||
/// </returns> | |||||
Task<IGuildScheduledEvent> CreateEventAsync( | |||||
string name, | |||||
DateTimeOffset startTime, | |||||
GuildScheduledEventType type, | |||||
GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, | |||||
string description = null, | |||||
DateTimeOffset? endTime = null, | |||||
ulong? channelId = null, | |||||
string location = null, | |||||
RequestOptions options = null); | |||||
/// <summary> | /// <summary> | ||||
/// Gets this guilds application commands. | /// Gets this guilds application commands. | ||||
/// </summary> | /// </summary> | ||||
@@ -0,0 +1,170 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
/// <summary> | |||||
/// Represents a generic guild scheduled event. | |||||
/// </summary> | |||||
public interface IGuildScheduledEvent : IEntity<ulong> | |||||
{ | |||||
/// <summary> | |||||
/// Gets the guild this event is scheduled in. | |||||
/// </summary> | |||||
IGuild Guild { get; } | |||||
/// <summary> | |||||
/// Gets the optional channel id where this event will be hosted. | |||||
/// </summary> | |||||
ulong? ChannelId { get; } | |||||
/// <summary> | |||||
/// Gets the user who created the event. | |||||
/// </summary> | |||||
IUser Creator { get; } | |||||
/// <summary> | |||||
/// Gets the name of the event. | |||||
/// </summary> | |||||
string Name { get; } | |||||
/// <summary> | |||||
/// Gets the description of the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This field is <see langword="null"/> when the event doesn't have a discription. | |||||
/// </remarks> | |||||
string Description { get; } | |||||
/// <summary> | |||||
/// Gets the start time of the event. | |||||
/// </summary> | |||||
DateTimeOffset StartTime { get; } | |||||
/// <summary> | |||||
/// Gets the optional end time of the event. | |||||
/// </summary> | |||||
DateTimeOffset? EndTime { get; } | |||||
/// <summary> | |||||
/// Gets the privacy level of the event. | |||||
/// </summary> | |||||
GuildScheduledEventPrivacyLevel PrivacyLevel { get; } | |||||
/// <summary> | |||||
/// Gets the status of the event. | |||||
/// </summary> | |||||
GuildScheduledEventStatus Status { get; } | |||||
/// <summary> | |||||
/// Gets the type of the event. | |||||
/// </summary> | |||||
GuildScheduledEventType Type { get; } | |||||
/// <summary> | |||||
/// Gets the optional entity id of the event. The "entity" of the event | |||||
/// can be a stage instance event as is seperate from <see cref="ChannelId"/>. | |||||
/// </summary> | |||||
ulong? EntityId { get; } | |||||
/// <summary> | |||||
/// Gets the location of the event if the <see cref="Type"/> is external. | |||||
/// </summary> | |||||
string Location { get; } | |||||
/// <summary> | |||||
/// Gets the user count of the event. | |||||
/// </summary> | |||||
int? UserCount { get; } | |||||
/// <summary> | |||||
/// Starts the event. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous start operation. | |||||
/// </returns> | |||||
Task StartAsync(RequestOptions options = null); | |||||
/// <summary> | |||||
/// Ends or canceles the event. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous end operation. | |||||
/// </returns> | |||||
Task EndAsync(RequestOptions options = null); | |||||
/// <summary> | |||||
/// Modifies the guild event. | |||||
/// </summary> | |||||
/// <param name="func">The delegate containing the properties to modify the event with.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous modification operation. | |||||
/// </returns> | |||||
Task ModifyAsync(Action<GuildScheduledEventsProperties> func, RequestOptions options = null); | |||||
/// <summary> | |||||
/// Deletes the current event. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous delete operation. | |||||
/// </returns> | |||||
Task DeleteAsync(RequestOptions options = null); | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
/// collection. | |||||
/// </note> | |||||
/// This method will attempt to fetch all users that are interested in the event. | |||||
/// The library will attempt to split up the requests according to and <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. | |||||
/// In other words, if there are 300 users, and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant | |||||
/// is <c>100</c>, the request will be split into 3 individual requests; thus returning 3 individual asynchronous | |||||
/// responses, hence the need of flattening. | |||||
/// </remarks> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(RequestOptions options = null); | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual users as a | |||||
/// collection. | |||||
/// </note> | |||||
/// <note type="warning"> | |||||
/// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual | |||||
/// rate limit, causing your bot to freeze! | |||||
/// </note> | |||||
/// This method will attempt to fetch the number of users specified under <paramref name="limit"/> around | |||||
/// the user <paramref name="fromUserId"/> depending on the <paramref name="dir"/>. The library will | |||||
/// attempt to split up the requests according to your <paramref name="limit"/> and | |||||
/// <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. In other words, should the user request 500 users, | |||||
/// and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant is <c>100</c>, the request will | |||||
/// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need | |||||
/// of flattening. | |||||
/// </remarks> | |||||
/// <param name="fromUserId">The ID of the starting user to get the users from.</param> | |||||
/// <param name="dir">The direction of the users to be gotten from.</param> | |||||
/// <param name="limit">The numbers of users to be gotten from.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null); | |||||
} | |||||
} |
@@ -185,10 +185,14 @@ namespace Discord | |||||
/// </summary> | /// </summary> | ||||
UseApplicationCommands = 0x80_00_00_00, | UseApplicationCommands = 0x80_00_00_00, | ||||
/// <summary> | /// <summary> | ||||
/// Allows for requesting to speak in stage channels. <i>(This permission is under active development and may be changed or removed.)</i>. | |||||
/// Allows for requesting to speak in stage channels. | |||||
/// </summary> | /// </summary> | ||||
RequestToSpeak = 0x01_00_00_00_00, | RequestToSpeak = 0x01_00_00_00_00, | ||||
/// <summary> | /// <summary> | ||||
/// Allows for creating, editing, and deleting guild scheduled events. | |||||
/// </summary> | |||||
ManageEvents = 0x02_00_00_00_00, | |||||
/// <summary> | |||||
/// Allows for deleting and archiving threads, and viewing all private threads. | /// Allows for deleting and archiving threads, and viewing all private threads. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
@@ -1,6 +1,7 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
@@ -87,6 +88,8 @@ namespace Discord | |||||
public bool UseApplicationCommands => Permissions.GetValue(RawValue, GuildPermission.UseApplicationCommands); | public bool UseApplicationCommands => Permissions.GetValue(RawValue, GuildPermission.UseApplicationCommands); | ||||
/// <summary> If <c>true</c>, a user may request to speak in stage channels. </summary> | /// <summary> If <c>true</c>, a user may request to speak in stage channels. </summary> | ||||
public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak); | public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak); | ||||
/// <summary> If <c>true</c>, a user may create, edit, and delete events. </summary> | |||||
public bool ManageEvents => Permissions.GetValue(RawValue, GuildPermission.ManageEvents); | |||||
/// <summary> If <c>true</c>, a user may manage threads in this guild. </summary> | /// <summary> If <c>true</c>, a user may manage threads in this guild. </summary> | ||||
public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); | public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); | ||||
/// <summary> If <c>true</c>, a user may create public threads in this guild. </summary> | /// <summary> If <c>true</c>, a user may create public threads in this guild. </summary> | ||||
@@ -140,6 +143,7 @@ namespace Discord | |||||
bool? manageEmojisAndStickers = null, | bool? manageEmojisAndStickers = null, | ||||
bool? useApplicationCommands = null, | bool? useApplicationCommands = null, | ||||
bool? requestToSpeak = null, | bool? requestToSpeak = null, | ||||
bool? manageEvents = null, | |||||
bool? manageThreads = null, | bool? manageThreads = null, | ||||
bool? createPublicThreads = null, | bool? createPublicThreads = null, | ||||
bool? createPrivateThreads = null, | bool? createPrivateThreads = null, | ||||
@@ -182,6 +186,7 @@ namespace Discord | |||||
Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); | Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); | ||||
Permissions.SetValue(ref value, useApplicationCommands, GuildPermission.UseApplicationCommands); | Permissions.SetValue(ref value, useApplicationCommands, GuildPermission.UseApplicationCommands); | ||||
Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak); | Permissions.SetValue(ref value, requestToSpeak, GuildPermission.RequestToSpeak); | ||||
Permissions.SetValue(ref value, manageEvents, GuildPermission.ManageEvents); | |||||
Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads); | Permissions.SetValue(ref value, manageThreads, GuildPermission.ManageThreads); | ||||
Permissions.SetValue(ref value, createPublicThreads, GuildPermission.CreatePublicThreads); | Permissions.SetValue(ref value, createPublicThreads, GuildPermission.CreatePublicThreads); | ||||
Permissions.SetValue(ref value, createPrivateThreads, GuildPermission.CreatePrivateThreads); | Permissions.SetValue(ref value, createPrivateThreads, GuildPermission.CreatePrivateThreads); | ||||
@@ -227,6 +232,7 @@ namespace Discord | |||||
bool manageEmojisAndStickers = false, | bool manageEmojisAndStickers = false, | ||||
bool useApplicationCommands = false, | bool useApplicationCommands = false, | ||||
bool requestToSpeak = false, | bool requestToSpeak = false, | ||||
bool manageEvents = false, | |||||
bool manageThreads = false, | bool manageThreads = false, | ||||
bool createPublicThreads = false, | bool createPublicThreads = false, | ||||
bool createPrivateThreads = false, | bool createPrivateThreads = false, | ||||
@@ -267,6 +273,7 @@ namespace Discord | |||||
manageEmojisAndStickers: manageEmojisAndStickers, | manageEmojisAndStickers: manageEmojisAndStickers, | ||||
useApplicationCommands: useApplicationCommands, | useApplicationCommands: useApplicationCommands, | ||||
requestToSpeak: requestToSpeak, | requestToSpeak: requestToSpeak, | ||||
manageEvents: manageEvents, | |||||
manageThreads: manageThreads, | manageThreads: manageThreads, | ||||
createPublicThreads: createPublicThreads, | createPublicThreads: createPublicThreads, | ||||
createPrivateThreads: createPrivateThreads, | createPrivateThreads: createPrivateThreads, | ||||
@@ -310,6 +317,7 @@ namespace Discord | |||||
bool? manageEmojisAndStickers = null, | bool? manageEmojisAndStickers = null, | ||||
bool? useApplicationCommands = null, | bool? useApplicationCommands = null, | ||||
bool? requestToSpeak = null, | bool? requestToSpeak = null, | ||||
bool? manageEvents = null, | |||||
bool? manageThreads = null, | bool? manageThreads = null, | ||||
bool? createPublicThreads = null, | bool? createPublicThreads = null, | ||||
bool? createPrivateThreads = null, | bool? createPrivateThreads = null, | ||||
@@ -320,7 +328,7 @@ namespace Discord | |||||
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, | viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, | ||||
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, | readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, | ||||
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, | useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, | ||||
useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, | |||||
useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, | |||||
startEmbeddedActivities); | startEmbeddedActivities); | ||||
/// <summary> | /// <summary> | ||||
@@ -351,6 +359,18 @@ namespace Discord | |||||
return perms; | return perms; | ||||
} | } | ||||
internal void Ensure(GuildPermission permissions) | |||||
{ | |||||
if (!Has(permissions)) | |||||
{ | |||||
var vals = Enum.GetValues(typeof(GuildPermission)).Cast<GuildPermission>(); | |||||
var currentValues = RawValue; | |||||
var missingValues = vals.Where(x => permissions.HasFlag(x) && !Permissions.GetValue(currentValues, x)); | |||||
throw new InvalidOperationException($"Missing required guild permission{(missingValues.Count() > 1 ? "s" : "")} {string.Join(", ", missingValues.Select(x => x.ToString()))} in order to execute this operation."); | |||||
} | |||||
} | |||||
public override string ToString() => RawValue.ToString(); | public override string ToString() => RawValue.ToString(); | ||||
private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; | private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; | ||||
} | } | ||||
@@ -39,13 +39,15 @@ namespace Discord | |||||
DirectMessageReactions = 1 << 13, | DirectMessageReactions = 1 << 13, | ||||
/// <summary> This intent includes TYPING_START </summary> | /// <summary> This intent includes TYPING_START </summary> | ||||
DirectMessageTyping = 1 << 14, | DirectMessageTyping = 1 << 14, | ||||
/// <summary> This intent includes GUILD_SCHEDULED_EVENT_CREATE, GUILD_SCHEDULED_EVENT_UPDATE, GUILD_SCHEDULED_EVENT_DELETE, GUILD_SCHEDULED_EVENT_USER_ADD, GUILD_SCHEDULED_EVENT_USER_REMOVE </summary> | |||||
GuildScheduledEvents = 1 << 16, | |||||
/// <summary> | /// <summary> | ||||
/// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildPresences"/> | /// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildPresences"/> | ||||
/// which are privileged and must be enabled in the Developer Portal. | /// which are privileged and must be enabled in the Developer Portal. | ||||
/// </summary> | /// </summary> | ||||
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | | AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | | ||||
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | | GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | | ||||
DirectMessageReactions | DirectMessageTyping, | |||||
DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents, | |||||
/// <summary> | /// <summary> | ||||
/// This intent includes all of them, including privileged ones. | /// This intent includes all of them, including privileged ones. | ||||
/// </summary> | /// </summary> | ||||
@@ -0,0 +1,43 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API | |||||
{ | |||||
internal class GuildScheduledEvent | |||||
{ | |||||
[JsonProperty("id")] | |||||
public ulong Id { get; set; } | |||||
[JsonProperty("guild_id")] | |||||
public ulong GuildId { get; set; } | |||||
[JsonProperty("channel_id")] | |||||
public Optional<ulong?> ChannelId { get; set; } | |||||
[JsonProperty("creator_id")] | |||||
public Optional<ulong> CreatorId { get; set; } | |||||
[JsonProperty("name")] | |||||
public string Name { get; set; } | |||||
[JsonProperty("description")] | |||||
public Optional<string> Description { get; set; } | |||||
[JsonProperty("scheduled_start_time")] | |||||
public DateTimeOffset ScheduledStartTime { get; set; } | |||||
[JsonProperty("scheduled_end_time")] | |||||
public DateTimeOffset? ScheduledEndTime { get; set; } | |||||
[JsonProperty("privacy_level")] | |||||
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; } | |||||
[JsonProperty("status")] | |||||
public GuildScheduledEventStatus Status { get; set; } | |||||
[JsonProperty("entity_type")] | |||||
public GuildScheduledEventType EntityType { get; set; } | |||||
[JsonProperty("entity_id")] | |||||
public ulong? EntityId { get; set; } | |||||
[JsonProperty("entity_metadata")] | |||||
public GuildScheduledEventEntityMetadata EntityMetadata { get; set; } | |||||
[JsonProperty("creator")] | |||||
public Optional<User> Creator { get; set; } | |||||
[JsonProperty("user_count")] | |||||
public Optional<int> UserCount { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API | |||||
{ | |||||
internal class GuildScheduledEventEntityMetadata | |||||
{ | |||||
[JsonProperty("location")] | |||||
public Optional<string> Location { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API | |||||
{ | |||||
internal class GuildScheduledEventUser | |||||
{ | |||||
[JsonProperty("user")] | |||||
public User User { get; set; } | |||||
[JsonProperty("member")] | |||||
public Optional<GuildMember> Member { get; set; } | |||||
[JsonProperty("guild_scheduled_event_id")] | |||||
public ulong GuildScheduledEventId { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,29 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API.Rest | |||||
{ | |||||
internal class CreateGuildScheduledEventParams | |||||
{ | |||||
[JsonProperty("channel_id")] | |||||
public Optional<ulong> ChannelId { get; set; } | |||||
[JsonProperty("entity_metadata")] | |||||
public Optional<GuildScheduledEventEntityMetadata> EntityMetadata { get; set; } | |||||
[JsonProperty("name")] | |||||
public string Name { get; set; } | |||||
[JsonProperty("privacy_level")] | |||||
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; set; } | |||||
[JsonProperty("scheduled_start_time")] | |||||
public DateTimeOffset StartTime { get; set; } | |||||
[JsonProperty("scheduled_end_time")] | |||||
public Optional<DateTimeOffset> EndTime { get; set; } | |||||
[JsonProperty("description")] | |||||
public Optional<string> Description { get; set; } | |||||
[JsonProperty("entity_type")] | |||||
public GuildScheduledEventType Type { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API.Rest | |||||
{ | |||||
internal class GetEventUsersParams | |||||
{ | |||||
public Optional<int> Limit { get; set; } | |||||
public Optional<Direction> RelativeDirection { get; set; } | |||||
public Optional<ulong> RelativeUserId { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
using Newtonsoft.Json; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord.API.Rest | |||||
{ | |||||
internal class ModifyGuildScheduledEventParams | |||||
{ | |||||
[JsonProperty("channel_id")] | |||||
public Optional<ulong?> ChannelId { get; set; } | |||||
[JsonProperty("entity_metadata")] | |||||
public Optional<GuildScheduledEventEntityMetadata> EntityMetadata { get; set; } | |||||
[JsonProperty("name")] | |||||
public Optional<string> Name { get; set; } | |||||
[JsonProperty("privacy_level")] | |||||
public Optional<GuildScheduledEventPrivacyLevel> PrivacyLevel { get; set; } | |||||
[JsonProperty("scheduled_start_time")] | |||||
public Optional<DateTimeOffset> StartTime { get; set; } | |||||
[JsonProperty("scheduled_end_time")] | |||||
public Optional<DateTimeOffset> EndTime { get; set; } | |||||
[JsonProperty("description")] | |||||
public Optional<string> Description { get; set; } | |||||
[JsonProperty("entity_type")] | |||||
public Optional<GuildScheduledEventType> Type { get; set; } | |||||
[JsonProperty("status")] | |||||
public Optional<GuildScheduledEventStatus> Status { get; set; } | |||||
} | |||||
} |
@@ -1909,6 +1909,105 @@ namespace Discord.API | |||||
} | } | ||||
#endregion | #endregion | ||||
#region Guild Events | |||||
public async Task<GuildScheduledEvent[]> ListGuildScheduledEventsAsync(ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendAsync<GuildScheduledEvent[]>("GET", () => $"guilds/{guildId}/scheduled-events?with_user_count=true", ids, options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildScheduledEvent> GetGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(eventId, 0, nameof(eventId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await NullifyNotFound<GuildScheduledEvent>(SendAsync<GuildScheduledEvent>("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}?with_user_count=true", ids, options: options)).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildScheduledEvent> CreateGuildScheduledEventAsync(CreateGuildScheduledEventParams args, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotNull(args, nameof(args)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendJsonAsync<GuildScheduledEvent>("POST", () => $"guilds/{guildId}/scheduled-events", args, ids, options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildScheduledEvent> ModifyGuildScheduledEventAsync(ModifyGuildScheduledEventParams args, ulong eventId, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(eventId, 0, nameof(eventId)); | |||||
Preconditions.NotNull(args, nameof(args)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendJsonAsync<GuildScheduledEvent>("PATCH", () => $"guilds/{guildId}/scheduled-events/{eventId}", args, ids, options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task DeleteGuildScheduledEventAsync(ulong eventId, ulong guildId, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(eventId, 0, nameof(eventId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
await SendAsync("DELETE", () => $"guilds/{guildId}/scheduled-events/{eventId}", ids, options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildScheduledEventUser[]> GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, int limit = 100, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||||
Preconditions.NotEqual(eventId, 0, nameof(eventId)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
var ids = new BucketIds(guildId: guildId); | |||||
return await SendAsync<GuildScheduledEventUser[]>("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}/users?limit={limit}&with_member=true", ids, options: options).ConfigureAwait(false); | |||||
} | |||||
public async Task<GuildScheduledEventUser[]> GetGuildScheduledEventUsersAsync(ulong eventId, ulong guildId, GetEventUsersParams args, RequestOptions options = null) | |||||
{ | |||||
Preconditions.NotEqual(eventId, 0, nameof(eventId)); | |||||
Preconditions.NotNull(args, nameof(args)); | |||||
Preconditions.AtLeast(args.Limit, 0, nameof(args.Limit)); | |||||
Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit)); | |||||
options = RequestOptions.CreateOrClone(options); | |||||
int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxGuildEventUsersPerBatch); | |||||
ulong? relativeId = args.RelativeUserId.IsSpecified ? args.RelativeUserId.Value : (ulong?)null; | |||||
var relativeDir = args.RelativeDirection.GetValueOrDefault(Direction.Before) switch | |||||
{ | |||||
Direction.After => "after", | |||||
Direction.Around => "around", | |||||
_ => "before", | |||||
}; | |||||
var ids = new BucketIds(guildId: guildId); | |||||
Expression<Func<string>> endpoint; | |||||
if (relativeId != null) | |||||
endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}&{relativeDir}={relativeId}"; | |||||
else | |||||
endpoint = () => $"guilds/{guildId}/scheduled-events/{eventId}/users?with_member=true&limit={limit}"; | |||||
return await SendAsync<GuildScheduledEventUser[]>("GET", endpoint, ids, options: options).ConfigureAwait(false); | |||||
} | |||||
#endregion | |||||
#region Users | #region Users | ||||
public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) | public async Task<User> GetUserAsync(ulong userId, RequestOptions options = null) | ||||
{ | { | ||||
@@ -649,6 +649,235 @@ namespace Discord.Rest | |||||
public static async Task DeleteStickerAsync(BaseDiscordClient client, ulong guildId, ISticker sticker, RequestOptions options = null) | public static async Task DeleteStickerAsync(BaseDiscordClient client, ulong guildId, ISticker sticker, RequestOptions options = null) | ||||
=> await client.ApiClient.DeleteStickerAsync(guildId, sticker.Id, options).ConfigureAwait(false); | => await client.ApiClient.DeleteStickerAsync(guildId, sticker.Id, options).ConfigureAwait(false); | ||||
#endregion | |||||
#endregion | |||||
#region Events | |||||
public static async Task<IReadOnlyCollection<RestUser>> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, int limit = 100, RequestOptions options = null) | |||||
{ | |||||
var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, limit, options).ConfigureAwait(false); | |||||
return models.Select(x => RestUser.Create(client, guildEvent.Guild, x)).ToImmutableArray(); | |||||
} | |||||
public static IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, | |||||
ulong? fromUserId, int? limit, RequestOptions options) | |||||
{ | |||||
return new PagedAsyncEnumerable<RestUser>( | |||||
DiscordConfig.MaxGuildEventUsersPerBatch, | |||||
async (info, ct) => | |||||
{ | |||||
var args = new GetEventUsersParams | |||||
{ | |||||
Limit = info.PageSize, | |||||
RelativeDirection = Direction.After, | |||||
}; | |||||
if (info.Position != null) | |||||
args.RelativeUserId = info.Position.Value; | |||||
var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, args, options).ConfigureAwait(false); | |||||
return models | |||||
.Select(x => RestUser.Create(client, guildEvent.Guild, x)) | |||||
.ToImmutableArray(); | |||||
}, | |||||
nextPage: (info, lastPage) => | |||||
{ | |||||
if (lastPage.Count != DiscordConfig.MaxGuildEventUsersPerBatch) | |||||
return false; | |||||
info.Position = lastPage.Max(x => x.Id); | |||||
return true; | |||||
}, | |||||
start: fromUserId, | |||||
count: limit | |||||
); | |||||
} | |||||
public static IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, | |||||
ulong? fromUserId, Direction dir, int limit, RequestOptions options = null) | |||||
{ | |||||
if (dir == Direction.Around && limit > DiscordConfig.MaxMessagesPerBatch) | |||||
{ | |||||
int around = limit / 2; | |||||
if (fromUserId.HasValue) | |||||
return GetEventUsersAsync(client, guildEvent, fromUserId.Value + 1, Direction.Before, around + 1, options) //Need to include the message itself | |||||
.Concat(GetEventUsersAsync(client, guildEvent, fromUserId, Direction.After, around, options)); | |||||
else //Shouldn't happen since there's no public overload for ulong? and Direction | |||||
return GetEventUsersAsync(client, guildEvent, null, Direction.Before, around + 1, options); | |||||
} | |||||
return new PagedAsyncEnumerable<RestUser>( | |||||
DiscordConfig.MaxGuildEventUsersPerBatch, | |||||
async (info, ct) => | |||||
{ | |||||
var args = new GetEventUsersParams | |||||
{ | |||||
RelativeDirection = dir, | |||||
Limit = info.PageSize | |||||
}; | |||||
if (info.Position != null) | |||||
args.RelativeUserId = info.Position.Value; | |||||
var models = await client.ApiClient.GetGuildScheduledEventUsersAsync(guildEvent.Id, guildEvent.Guild.Id, args, options).ConfigureAwait(false); | |||||
var builder = ImmutableArray.CreateBuilder<RestUser>(); | |||||
foreach (var model in models) | |||||
{ | |||||
builder.Add(RestUser.Create(client, guildEvent.Guild, model)); | |||||
} | |||||
return builder.ToImmutable(); | |||||
}, | |||||
nextPage: (info, lastPage) => | |||||
{ | |||||
if (lastPage.Count != DiscordConfig.MaxGuildEventUsersPerBatch) | |||||
return false; | |||||
if (dir == Direction.Before) | |||||
info.Position = lastPage.Min(x => x.Id); | |||||
else | |||||
info.Position = lastPage.Max(x => x.Id); | |||||
return true; | |||||
}, | |||||
start: fromUserId, | |||||
count: limit | |||||
); | |||||
} | |||||
public static async Task<API.GuildScheduledEvent> ModifyGuildEventAsync(BaseDiscordClient client, Action<GuildScheduledEventsProperties> func, | |||||
IGuildScheduledEvent guildEvent, RequestOptions options = null) | |||||
{ | |||||
var args = new GuildScheduledEventsProperties(); | |||||
func(args); | |||||
if (args.Status.IsSpecified) | |||||
{ | |||||
switch (args.Status.Value) | |||||
{ | |||||
case GuildScheduledEventStatus.Active when guildEvent.Status != GuildScheduledEventStatus.Scheduled: | |||||
case GuildScheduledEventStatus.Completed when guildEvent.Status != GuildScheduledEventStatus.Active: | |||||
case GuildScheduledEventStatus.Cancelled when guildEvent.Status != GuildScheduledEventStatus.Scheduled: | |||||
throw new ArgumentException($"Cannot set event to {args.Status.Value} when events status is {guildEvent.Status}"); | |||||
} | |||||
} | |||||
if (args.Type.IsSpecified) | |||||
{ | |||||
// taken from https://discord.com/developers/docs/resources/guild-scheduled-event#modify-guild-scheduled-event | |||||
switch (args.Type.Value) | |||||
{ | |||||
case GuildScheduledEventType.External: | |||||
if (!args.Location.IsSpecified) | |||||
throw new ArgumentException("Location must be specified for external events."); | |||||
if (!args.EndTime.IsSpecified) | |||||
throw new ArgumentException("End time must be specified for external events."); | |||||
if (!args.ChannelId.IsSpecified) | |||||
throw new ArgumentException("Channel id must be set to null!"); | |||||
if (args.ChannelId.Value != null) | |||||
throw new ArgumentException("Channel id must be set to null!"); | |||||
break; | |||||
} | |||||
} | |||||
var apiArgs = new ModifyGuildScheduledEventParams() | |||||
{ | |||||
ChannelId = args.ChannelId, | |||||
Description = args.Description, | |||||
EndTime = args.EndTime, | |||||
Name = args.Name, | |||||
PrivacyLevel = args.PrivacyLevel, | |||||
StartTime = args.StartTime, | |||||
Status = args.Status, | |||||
Type = args.Type | |||||
}; | |||||
if(args.Location.IsSpecified) | |||||
{ | |||||
apiArgs.EntityMetadata = new API.GuildScheduledEventEntityMetadata() | |||||
{ | |||||
Location = args.Location, | |||||
}; | |||||
} | |||||
return await client.ApiClient.ModifyGuildScheduledEventAsync(apiArgs, guildEvent.Id, guildEvent.Guild.Id, options).ConfigureAwait(false); | |||||
} | |||||
public static async Task<RestGuildEvent> GetGuildEventAsync(BaseDiscordClient client, ulong id, IGuild guild, RequestOptions options = null) | |||||
{ | |||||
var model = await client.ApiClient.GetGuildScheduledEventAsync(id, guild.Id, options).ConfigureAwait(false); | |||||
if (model == null) | |||||
return null; | |||||
return RestGuildEvent.Create(client, guild, model); | |||||
} | |||||
public static async Task<IReadOnlyCollection<RestGuildEvent>> GetGuildEventsAsync(BaseDiscordClient client, IGuild guild, RequestOptions options = null) | |||||
{ | |||||
var models = await client.ApiClient.ListGuildScheduledEventsAsync(guild.Id, options).ConfigureAwait(false); | |||||
return models.Select(x => RestGuildEvent.Create(client, guild, x)).ToImmutableArray(); | |||||
} | |||||
public static async Task<RestGuildEvent> CreateGuildEventAsync(BaseDiscordClient client, IGuild guild, | |||||
string name, | |||||
GuildScheduledEventPrivacyLevel privacyLevel, | |||||
DateTimeOffset startTime, | |||||
GuildScheduledEventType type, | |||||
string description = null, | |||||
DateTimeOffset? endTime = null, | |||||
ulong? channelId = null, | |||||
string location = null, | |||||
RequestOptions options = null) | |||||
{ | |||||
if(location != null) | |||||
{ | |||||
Preconditions.AtMost(location.Length, 100, nameof(location)); | |||||
} | |||||
switch (type) | |||||
{ | |||||
case GuildScheduledEventType.Stage or GuildScheduledEventType.Voice when channelId == null: | |||||
throw new ArgumentException($"{nameof(channelId)} must not be null when type is {type}", nameof(channelId)); | |||||
case GuildScheduledEventType.External when channelId != null: | |||||
throw new ArgumentException($"{nameof(channelId)} must be null when using external event type", nameof(channelId)); | |||||
case GuildScheduledEventType.External when location == null: | |||||
throw new ArgumentException($"{nameof(location)} must not be null when using external event type", nameof(location)); | |||||
case GuildScheduledEventType.External when endTime == null: | |||||
throw new ArgumentException($"{nameof(endTime)} must not be null when using external event type", nameof(endTime)); | |||||
} | |||||
if (startTime <= DateTimeOffset.Now) | |||||
throw new ArgumentOutOfRangeException(nameof(startTime), "The start time for an event cannot be in the past"); | |||||
if (endTime != null && endTime <= startTime) | |||||
throw new ArgumentOutOfRangeException(nameof(endTime), $"{nameof(endTime)} cannot be before the start time"); | |||||
var apiArgs = new CreateGuildScheduledEventParams() | |||||
{ | |||||
ChannelId = channelId ?? Optional<ulong>.Unspecified, | |||||
Description = description ?? Optional<string>.Unspecified, | |||||
EndTime = endTime ?? Optional<DateTimeOffset>.Unspecified, | |||||
Name = name, | |||||
PrivacyLevel = privacyLevel, | |||||
StartTime = startTime, | |||||
Type = type | |||||
}; | |||||
if(location != null) | |||||
{ | |||||
apiArgs.EntityMetadata = new API.GuildScheduledEventEntityMetadata() | |||||
{ | |||||
Location = location | |||||
}; | |||||
} | |||||
var model = await client.ApiClient.CreateGuildScheduledEventAsync(apiArgs, guild.Id, options).ConfigureAwait(false); | |||||
return RestGuildEvent.Create(client, guild, client.CurrentUser, model); | |||||
} | |||||
public static async Task DeleteEventAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent, RequestOptions options = null) | |||||
{ | |||||
await client.ApiClient.DeleteGuildScheduledEventAsync(guildEvent.Id, guildEvent.Guild.Id, options).ConfigureAwait(false); | |||||
} | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -1109,6 +1109,65 @@ namespace Discord.Rest | |||||
=> sticker.DeleteAsync(options); | => sticker.DeleteAsync(options); | ||||
#endregion | #endregion | ||||
#region Guild Events | |||||
/// <summary> | |||||
/// Gets an event within this guild. | |||||
/// </summary> | |||||
/// <param name="id">The snowflake identifier for the event.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
public Task<RestGuildEvent> GetEventAsync(ulong id, RequestOptions options = null) | |||||
=> GuildHelper.GetGuildEventAsync(Discord, id, this, options); | |||||
/// <summary> | |||||
/// Gets all active events within this guild. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
public Task<IReadOnlyCollection<RestGuildEvent>> GetEventsAsync(RequestOptions options = null) | |||||
=> GuildHelper.GetGuildEventsAsync(Discord, this, options); | |||||
/// <summary> | |||||
/// Creates an event within this guild. | |||||
/// </summary> | |||||
/// <param name="name">The name of the event.</param> | |||||
/// <param name="privacyLevel">The privacy level of the event.</param> | |||||
/// <param name="startTime">The start time of the event.</param> | |||||
/// <param name="type">The type of the event.</param> | |||||
/// <param name="description">The description of the event.</param> | |||||
/// <param name="endTime">The end time of the event.</param> | |||||
/// <param name="channelId"> | |||||
/// The channel id of the event. | |||||
/// <remarks> | |||||
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/> | |||||
/// in order to use this property. | |||||
/// </remarks> | |||||
/// </param> | |||||
/// <param name="speakers">A collection of speakers for the event.</param> | |||||
/// <param name="location">The location of the event; links are supported</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous create operation. | |||||
/// </returns> | |||||
public Task<RestGuildEvent> CreateEventAsync( | |||||
string name, | |||||
DateTimeOffset startTime, | |||||
GuildScheduledEventType type, | |||||
GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, | |||||
string description = null, | |||||
DateTimeOffset? endTime = null, | |||||
ulong? channelId = null, | |||||
string location = null, | |||||
RequestOptions options = null) | |||||
=> GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, options); | |||||
#endregion | |||||
#region IGuild | #region IGuild | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
bool IGuild.Available => Available; | bool IGuild.Available => Available; | ||||
@@ -1121,6 +1180,18 @@ namespace Discord.Rest | |||||
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | ||||
/// <inheritdoc /> | |||||
async Task<IGuildScheduledEvent> IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, RequestOptions options) | |||||
=> await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | |||||
=> await GetEventAsync(id, options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | |||||
=> await GetEventsAsync(options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | ||||
=> await GetBansAsync(options).ConfigureAwait(false); | => await GetBansAsync(options).ConfigureAwait(false); | ||||
@@ -0,0 +1,188 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Collections.Immutable; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Model = Discord.API.GuildScheduledEvent; | |||||
namespace Discord.Rest | |||||
{ | |||||
public class RestGuildEvent : RestEntity<ulong>, IGuildScheduledEvent | |||||
{ | |||||
/// <inheritdoc/> | |||||
public IGuild Guild { get; private set; } | |||||
/// <inheritdoc/> | |||||
public ulong? ChannelId { get; private set; } | |||||
/// <inheritdoc/> | |||||
public IUser Creator { get; private set; } | |||||
/// <inheritdoc/> | |||||
public ulong CreatorId { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Name { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Description { get; private set; } | |||||
/// <inheritdoc/> | |||||
public DateTimeOffset StartTime { get; private set; } | |||||
/// <inheritdoc/> | |||||
public DateTimeOffset? EndTime { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventStatus Status { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventType Type { get; private set; } | |||||
/// <inheritdoc/> | |||||
public ulong? EntityId { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Location { get; private set; } | |||||
/// <inheritdoc/> | |||||
public int? UserCount { get; private set; } | |||||
internal RestGuildEvent(BaseDiscordClient client, IGuild guild, ulong id) | |||||
: base(client, id) | |||||
{ | |||||
Guild = guild; | |||||
} | |||||
internal static RestGuildEvent Create(BaseDiscordClient client, IGuild guild, Model model) | |||||
{ | |||||
var entity = new RestGuildEvent(client, guild, model.Id); | |||||
entity.Update(model); | |||||
return entity; | |||||
} | |||||
internal static RestGuildEvent Create(BaseDiscordClient client, IGuild guild, IUser creator, Model model) | |||||
{ | |||||
var entity = new RestGuildEvent(client, guild, model.Id); | |||||
entity.Update(model, creator); | |||||
return entity; | |||||
} | |||||
internal void Update(Model model, IUser creator) | |||||
{ | |||||
Update(model); | |||||
Creator = creator; | |||||
CreatorId = creator.Id; | |||||
} | |||||
internal void Update(Model model) | |||||
{ | |||||
if (model.Creator.IsSpecified) | |||||
{ | |||||
Creator = RestUser.Create(Discord, model.Creator.Value); | |||||
} | |||||
CreatorId = model.CreatorId.ToNullable() ?? 0; // should be changed? | |||||
ChannelId = model.ChannelId.IsSpecified ? model.ChannelId.Value : null; | |||||
Name = model.Name; | |||||
Description = model.Description.GetValueOrDefault(); | |||||
StartTime = model.ScheduledStartTime; | |||||
EndTime = model.ScheduledEndTime; | |||||
PrivacyLevel = model.PrivacyLevel; | |||||
Status = model.Status; | |||||
Type = model.EntityType; | |||||
EntityId = model.EntityId; | |||||
Location = model.EntityMetadata?.Location.GetValueOrDefault(); | |||||
UserCount = model.UserCount.ToNullable(); | |||||
} | |||||
/// <inheritdoc/> | |||||
public Task StartAsync(RequestOptions options = null) | |||||
=> ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); | |||||
/// <inheritdoc/> | |||||
public Task EndAsync(RequestOptions options = null) | |||||
=> ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled | |||||
? GuildScheduledEventStatus.Cancelled | |||||
: GuildScheduledEventStatus.Completed); | |||||
/// <inheritdoc/> | |||||
public Task DeleteAsync(RequestOptions options = null) | |||||
=> GuildHelper.DeleteEventAsync(Discord, this, options); | |||||
/// <inheritdoc/> | |||||
public async Task ModifyAsync(Action<GuildScheduledEventsProperties> func, RequestOptions options = null) | |||||
{ | |||||
var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false); | |||||
Update(model); | |||||
} | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
/// collection. | |||||
/// </note> | |||||
/// This method will attempt to fetch all users that are interested in the event. | |||||
/// The library will attempt to split up the requests according to and <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. | |||||
/// In other words, if there are 300 users, and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant | |||||
/// is <c>100</c>, the request will be split into 3 individual requests; thus returning 3 individual asynchronous | |||||
/// responses, hence the need of flattening. | |||||
/// </remarks> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(RequestOptions options = null) | |||||
=> GuildHelper.GetEventUsersAsync(Discord, this, null, null, options); | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual users as a | |||||
/// collection. | |||||
/// </note> | |||||
/// <note type="warning"> | |||||
/// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual | |||||
/// rate limit, causing your bot to freeze! | |||||
/// </note> | |||||
/// This method will attempt to fetch the number of users specified under <paramref name="limit"/> around | |||||
/// the user <paramref name="fromUserId"/> depending on the <paramref name="dir"/>. The library will | |||||
/// attempt to split up the requests according to your <paramref name="limit"/> and | |||||
/// <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. In other words, should the user request 500 users, | |||||
/// and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant is <c>100</c>, the request will | |||||
/// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need | |||||
/// of flattening. | |||||
/// </remarks> | |||||
/// <param name="fromUserId">The ID of the starting user to get the users from.</param> | |||||
/// <param name="dir">The direction of the users to be gotten from.</param> | |||||
/// <param name="limit">The numbers of users to be gotten from.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null) | |||||
=> GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options); | |||||
#region IGuildScheduledEvent | |||||
/// <inheritdoc/> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(RequestOptions options) | |||||
=> GetUsersAsync(options); | |||||
/// <inheritdoc/> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) | |||||
=> GetUsersAsync(fromUserId, dir, limit, options); | |||||
#endregion | |||||
} | |||||
} |
@@ -4,6 +4,7 @@ using System.Diagnostics; | |||||
using System.Globalization; | using System.Globalization; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.User; | using Model = Discord.API.User; | ||||
using EventUserModel = Discord.API.GuildScheduledEventUser; | |||||
namespace Discord.Rest | namespace Discord.Rest | ||||
{ | { | ||||
@@ -62,6 +63,18 @@ namespace Discord.Rest | |||||
entity.Update(model); | entity.Update(model); | ||||
return entity; | return entity; | ||||
} | } | ||||
internal static RestUser Create(BaseDiscordClient discord, IGuild guild, EventUserModel model) | |||||
{ | |||||
if (model.Member.IsSpecified) | |||||
{ | |||||
var member = model.Member.Value; | |||||
member.User = model.User; | |||||
return RestGuildUser.Create(discord, guild, member); | |||||
} | |||||
else | |||||
return RestUser.Create(discord, model.User); | |||||
} | |||||
internal virtual void Update(Model model) | internal virtual void Update(Model model) | ||||
{ | { | ||||
if (model.Avatar.IsSpecified) | if (model.Avatar.IsSpecified) | ||||
@@ -27,7 +27,7 @@ namespace Discord.Net.Converters | |||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | ||||
{ | { | ||||
throw new NotImplementedException(); | |||||
writer.WriteValue(((DateTimeOffset)value).ToString("O")); | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -28,5 +28,8 @@ namespace Discord.API.Gateway | |||||
[JsonProperty("threads")] | [JsonProperty("threads")] | ||||
public new Channel[] Threads { get; set; } | public new Channel[] Threads { get; set; } | ||||
[JsonProperty("guild_scheduled_events")] | |||||
public GuildScheduledEvent[] GuildScheduledEvents { get; set; } | |||||
} | } | ||||
} | } |
@@ -344,6 +344,61 @@ namespace Discord.WebSocket | |||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>>(); | internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuild, Task>>(); | ||||
#endregion | #endregion | ||||
#region Guild Events | |||||
/// <summary> | |||||
/// Fired when a guild event is created. | |||||
/// </summary> | |||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCreated | |||||
{ | |||||
add { _guildScheduledEventCreated.Add(value); } | |||||
remove { _guildScheduledEventCreated.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCreated = new AsyncEvent<Func<SocketGuildEvent, Task>>(); | |||||
/// <summary> | |||||
/// Fired when a guild event is updated. | |||||
/// </summary> | |||||
public event Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task> GuildScheduledEventUpdated | |||||
{ | |||||
add { _guildScheduledEventUpdated.Add(value); } | |||||
remove { _guildScheduledEventUpdated.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent<Func<Cacheable<SocketGuildEvent, ulong>, SocketGuildEvent, Task>>(); | |||||
/// <summary> | |||||
/// Fired when a guild event is cancelled. | |||||
/// </summary> | |||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCancelled | |||||
{ | |||||
add { _guildScheduledEventCancelled.Add(value); } | |||||
remove { _guildScheduledEventCancelled.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCancelled = new AsyncEvent<Func<SocketGuildEvent, Task>>(); | |||||
/// <summary> | |||||
/// Fired when a guild event is completed. | |||||
/// </summary> | |||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventCompleted | |||||
{ | |||||
add { _guildScheduledEventCompleted.Add(value); } | |||||
remove { _guildScheduledEventCompleted.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventCompleted = new AsyncEvent<Func<SocketGuildEvent, Task>>(); | |||||
/// <summary> | |||||
/// Fired when a guild event is started. | |||||
/// </summary> | |||||
public event Func<SocketGuildEvent, Task> GuildScheduledEventStarted | |||||
{ | |||||
add { _guildScheduledEventStarted.Add(value); } | |||||
remove { _guildScheduledEventStarted.Remove(value); } | |||||
} | |||||
internal readonly AsyncEvent<Func<SocketGuildEvent, Task>> _guildScheduledEventStarted = new AsyncEvent<Func<SocketGuildEvent, Task>>(); | |||||
#endregion | |||||
#region Users | #region Users | ||||
/// <summary> Fired when a user joins a guild. </summary> | /// <summary> Fired when a user joins a guild. </summary> | ||||
public event Func<SocketGuildUser, Task> UserJoined | public event Func<SocketGuildUser, Task> UserJoined | ||||
@@ -486,6 +486,12 @@ namespace Discord.WebSocket | |||||
client.GuildStickerDeleted += (sticker) => _guildStickerDeleted.InvokeAsync(sticker); | client.GuildStickerDeleted += (sticker) => _guildStickerDeleted.InvokeAsync(sticker); | ||||
client.GuildStickerUpdated += (before, after) => _guildStickerUpdated.InvokeAsync(before, after); | client.GuildStickerUpdated += (before, after) => _guildStickerUpdated.InvokeAsync(before, after); | ||||
client.GuildJoinRequestDeleted += (userId, guildId) => _guildJoinRequestDeletedEvent.InvokeAsync(userId, guildId); | client.GuildJoinRequestDeleted += (userId, guildId) => _guildJoinRequestDeletedEvent.InvokeAsync(userId, guildId); | ||||
client.GuildScheduledEventCancelled += (arg) => _guildScheduledEventCancelled.InvokeAsync(arg); | |||||
client.GuildScheduledEventCompleted += (arg) => _guildScheduledEventCompleted.InvokeAsync(arg); | |||||
client.GuildScheduledEventCreated += (arg) => _guildScheduledEventCreated.InvokeAsync(arg); | |||||
client.GuildScheduledEventUpdated += (arg1, arg2) => _guildScheduledEventUpdated.InvokeAsync(arg1, arg2); | |||||
client.GuildScheduledEventStarted += (arg) => _guildScheduledEventStarted.InvokeAsync(arg); | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -2549,19 +2549,91 @@ namespace Discord.WebSocket | |||||
switch (type) | switch (type) | ||||
{ | { | ||||
case "STAGE_INSTANCE_CREATE": | case "STAGE_INSTANCE_CREATE": | ||||
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel); | |||||
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel).ConfigureAwait(false); | |||||
return; | return; | ||||
case "STAGE_INSTANCE_DELETE": | case "STAGE_INSTANCE_DELETE": | ||||
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel); | |||||
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel).ConfigureAwait(false); | |||||
return; | return; | ||||
case "STAGE_INSTANCE_UPDATE": | case "STAGE_INSTANCE_UPDATE": | ||||
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel); | |||||
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel).ConfigureAwait(false); | |||||
return; | return; | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
#endregion | #endregion | ||||
#region Guild Scheduled Events | |||||
case "GUILD_SCHEDULED_EVENT_CREATE": | |||||
{ | |||||
await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildScheduledEvent>(_serializer); | |||||
var guild = State.GetGuild(data.GuildId); | |||||
if (guild == null) | |||||
{ | |||||
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var newEvent = guild.AddOrUpdateEvent(data); | |||||
await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCreated), newEvent).ConfigureAwait(false); | |||||
} | |||||
break; | |||||
case "GUILD_SCHEDULED_EVENT_UPDATE": | |||||
{ | |||||
await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildScheduledEvent>(_serializer); | |||||
var guild = State.GetGuild(data.GuildId); | |||||
if (guild == null) | |||||
{ | |||||
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var before = guild.GetEvent(data.Id); | |||||
var beforeCacheable = new Cacheable<SocketGuildEvent, ulong>(before, data.Id, before != null, () => Task.FromResult((SocketGuildEvent)null)); | |||||
var after = guild.AddOrUpdateEvent(data); | |||||
if((before != null ? before.Status != GuildScheduledEventStatus.Completed : true) && data.Status == GuildScheduledEventStatus.Completed) | |||||
{ | |||||
await TimedInvokeAsync(_guildScheduledEventCompleted, nameof(GuildScheduledEventCompleted), after).ConfigureAwait(false); | |||||
} | |||||
else if((before != null ? before.Status != GuildScheduledEventStatus.Active : false) && data.Status == GuildScheduledEventStatus.Active) | |||||
{ | |||||
await TimedInvokeAsync(_guildScheduledEventStarted, nameof(GuildScheduledEventStarted), after).ConfigureAwait(false); | |||||
} | |||||
else await TimedInvokeAsync(_guildScheduledEventUpdated, nameof(GuildScheduledEventUpdated), beforeCacheable, after).ConfigureAwait(false); | |||||
} | |||||
break; | |||||
case "GUILD_SCHEDULED_EVENT_DELETE": | |||||
{ | |||||
await _gatewayLogger.DebugAsync($"Received Dispatch ({type})").ConfigureAwait(false); | |||||
var data = (payload as JToken).ToObject<GuildScheduledEvent>(_serializer); | |||||
var guild = State.GetGuild(data.GuildId); | |||||
if (guild == null) | |||||
{ | |||||
await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); | |||||
return; | |||||
} | |||||
var guildEvent = guild.RemoveEvent(data.Id) ?? SocketGuildEvent.Create(this, guild, data); | |||||
await TimedInvokeAsync(_guildScheduledEventCancelled, nameof(GuildScheduledEventCancelled), guildEvent).ConfigureAwait(false); | |||||
} | |||||
break; | |||||
#endregion | |||||
#region Ignored (User only) | #region Ignored (User only) | ||||
case "CHANNEL_PINS_ACK": | case "CHANNEL_PINS_ACK": | ||||
await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); | ||||
@@ -20,6 +20,7 @@ using RoleModel = Discord.API.Role; | |||||
using UserModel = Discord.API.User; | using UserModel = Discord.API.User; | ||||
using VoiceStateModel = Discord.API.VoiceState; | using VoiceStateModel = Discord.API.VoiceState; | ||||
using StickerModel = Discord.API.Sticker; | using StickerModel = Discord.API.Sticker; | ||||
using EventModel = Discord.API.GuildScheduledEvent; | |||||
using System.IO; | using System.IO; | ||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
@@ -40,6 +41,7 @@ namespace Discord.WebSocket | |||||
private ConcurrentDictionary<ulong, SocketRole> _roles; | private ConcurrentDictionary<ulong, SocketRole> _roles; | ||||
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | ||||
private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers; | private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers; | ||||
private ConcurrentDictionary<ulong, SocketGuildEvent> _events; | |||||
private ImmutableArray<GuildEmote> _emotes; | private ImmutableArray<GuildEmote> _emotes; | ||||
private AudioClient _audioClient; | private AudioClient _audioClient; | ||||
@@ -364,6 +366,17 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | public IReadOnlyCollection<SocketRole> Roles => _roles.ToReadOnlyCollection(); | ||||
/// <summary> | |||||
/// Gets a collection of all events within this guild. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// This field is based off of caching alone, since there is no events returned on the guild model. | |||||
/// </remarks> | |||||
/// <returns> | |||||
/// A read-only collection of guild events found within this guild. | |||||
/// </returns> | |||||
public IReadOnlyCollection<SocketGuildEvent> Events => _events.ToReadOnlyCollection(); | |||||
internal SocketGuild(DiscordSocketClient client, ulong id) | internal SocketGuild(DiscordSocketClient client, ulong id) | ||||
: base(client, id) | : base(client, id) | ||||
{ | { | ||||
@@ -381,6 +394,8 @@ namespace Discord.WebSocket | |||||
IsAvailable = !(model.Unavailable ?? false); | IsAvailable = !(model.Unavailable ?? false); | ||||
if (!IsAvailable) | if (!IsAvailable) | ||||
{ | { | ||||
if(_events == null) | |||||
_events = new ConcurrentDictionary<ulong, SocketGuildEvent>(); | |||||
if (_channels == null) | if (_channels == null) | ||||
_channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(); | _channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(); | ||||
if (_members == null) | if (_members == null) | ||||
@@ -449,7 +464,16 @@ namespace Discord.WebSocket | |||||
} | } | ||||
_voiceStates = voiceStates; | _voiceStates = voiceStates; | ||||
var events = new ConcurrentDictionary<ulong, SocketGuildEvent>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.GuildScheduledEvents.Length * 1.05)); | |||||
{ | |||||
for (int i = 0; i < model.GuildScheduledEvents.Length; i++) | |||||
{ | |||||
var guildEvent = SocketGuildEvent.Create(Discord, this, model.GuildScheduledEvents[i]); | |||||
events.TryAdd(guildEvent.Id, guildEvent); | |||||
} | |||||
} | |||||
_events = events; | |||||
_syncPromise = new TaskCompletionSource<bool>(); | _syncPromise = new TaskCompletionSource<bool>(); | ||||
_downloaderPromise = new TaskCompletionSource<bool>(); | _downloaderPromise = new TaskCompletionSource<bool>(); | ||||
@@ -1191,6 +1215,115 @@ namespace Discord.WebSocket | |||||
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options); | => GuildHelper.SearchUsersAsync(this, Discord, query, limit, options); | ||||
#endregion | #endregion | ||||
#region Guild Events | |||||
/// <summary> | |||||
/// Gets an event in this guild. | |||||
/// </summary> | |||||
/// <param name="id">The snowflake identifier for the event.</param> | |||||
/// <returns> | |||||
/// An event that is associated with the specified <paramref name="id"/>; <see langword="null"/> if none is found. | |||||
/// </returns> | |||||
public SocketGuildEvent GetEvent(ulong id) | |||||
{ | |||||
if (_events.TryGetValue(id, out SocketGuildEvent value)) | |||||
return value; | |||||
return null; | |||||
} | |||||
internal SocketGuildEvent RemoveEvent(ulong id) | |||||
{ | |||||
if (_events.TryRemove(id, out SocketGuildEvent value)) | |||||
return value; | |||||
return null; | |||||
} | |||||
internal SocketGuildEvent AddOrUpdateEvent(EventModel model) | |||||
{ | |||||
if (_events.TryGetValue(model.Id, out SocketGuildEvent value)) | |||||
value.Update(model); | |||||
else | |||||
{ | |||||
value = SocketGuildEvent.Create(Discord, this, model); | |||||
_events[model.Id] = value; | |||||
} | |||||
return value; | |||||
} | |||||
/// <summary> | |||||
/// Gets an event within this guild. | |||||
/// </summary> | |||||
/// <param name="id">The snowflake identifier for the event.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
public Task<RestGuildEvent> GetEventAsync(ulong id, RequestOptions options = null) | |||||
=> GuildHelper.GetGuildEventAsync(Discord, id, this, options); | |||||
/// <summary> | |||||
/// Gets all active events within this guild. | |||||
/// </summary> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous get operation. | |||||
/// </returns> | |||||
public Task<IReadOnlyCollection<RestGuildEvent>> GetEventsAsync(RequestOptions options = null) | |||||
=> GuildHelper.GetGuildEventsAsync(Discord, this, options); | |||||
/// <summary> | |||||
/// Creates an event within this guild. | |||||
/// </summary> | |||||
/// <param name="name">The name of the event.</param> | |||||
/// <param name="privacyLevel">The privacy level of the event.</param> | |||||
/// <param name="startTime">The start time of the event.</param> | |||||
/// <param name="type">The type of the event.</param> | |||||
/// <param name="description">The description of the event.</param> | |||||
/// <param name="endTime">The end time of the event.</param> | |||||
/// <param name="channelId"> | |||||
/// The channel id of the event. | |||||
/// <remarks> | |||||
/// The event must have a type of <see cref="GuildScheduledEventType.Stage"/> or <see cref="GuildScheduledEventType.Voice"/> | |||||
/// in order to use this property. | |||||
/// </remarks> | |||||
/// </param> | |||||
/// <param name="speakers">A collection of speakers for the event.</param> | |||||
/// <param name="location">The location of the event; links are supported</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A task that represents the asynchronous create operation. | |||||
/// </returns> | |||||
public Task<RestGuildEvent> CreateEventAsync( | |||||
string name, | |||||
DateTimeOffset startTime, | |||||
GuildScheduledEventType type, | |||||
GuildScheduledEventPrivacyLevel privacyLevel = GuildScheduledEventPrivacyLevel.Private, | |||||
string description = null, | |||||
DateTimeOffset? endTime = null, | |||||
ulong? channelId = null, | |||||
string location = null, | |||||
RequestOptions options = null) | |||||
{ | |||||
// requirements taken from https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-permissions-requirements | |||||
switch (type) | |||||
{ | |||||
case GuildScheduledEventType.Stage: | |||||
CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents | GuildPermission.ManageChannels | GuildPermission.MuteMembers | GuildPermission.MoveMembers); | |||||
break; | |||||
case GuildScheduledEventType.Voice: | |||||
CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents | GuildPermission.ViewChannel | GuildPermission.Connect); | |||||
break; | |||||
case GuildScheduledEventType.External: | |||||
CurrentUser.GuildPermissions.Ensure(GuildPermission.ManageEvents); | |||||
break; | |||||
} | |||||
return GuildHelper.CreateGuildEventAsync(Discord, this, name, privacyLevel, startTime, type, description, endTime, channelId, location, options); | |||||
} | |||||
#endregion | |||||
#region Audit logs | #region Audit logs | ||||
/// <summary> | /// <summary> | ||||
/// Gets the specified number of audit log entries for this guild. | /// Gets the specified number of audit log entries for this guild. | ||||
@@ -1625,6 +1758,15 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
async Task<IGuildScheduledEvent> IGuild.CreateEventAsync(string name, DateTimeOffset startTime, GuildScheduledEventType type, GuildScheduledEventPrivacyLevel privacyLevel, string description, DateTimeOffset? endTime, ulong? channelId, string location, RequestOptions options) | |||||
=> await CreateEventAsync(name, startTime, type, privacyLevel, description, endTime, channelId, location, options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IGuildScheduledEvent> IGuild.GetEventAsync(ulong id, RequestOptions options) | |||||
=> await GetEventAsync(id, options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IReadOnlyCollection<IGuildScheduledEvent>> IGuild.GetEventsAsync(RequestOptions options) | |||||
=> await GetEventsAsync(options).ConfigureAwait(false); | |||||
/// <inheritdoc /> | |||||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | ||||
=> await GetBansAsync(options).ConfigureAwait(false); | => await GetBansAsync(options).ConfigureAwait(false); | ||||
/// <inheritdoc/> | /// <inheritdoc/> | ||||
@@ -0,0 +1,216 @@ | |||||
using Discord.Rest; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Collections.Immutable; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
using Model = Discord.API.GuildScheduledEvent; | |||||
namespace Discord.WebSocket | |||||
{ | |||||
/// <summary> | |||||
/// Represents a WebSocket-based guild event. | |||||
/// </summary> | |||||
public class SocketGuildEvent : SocketEntity<ulong>, IGuildScheduledEvent | |||||
{ | |||||
/// <summary> | |||||
/// Gets the guild of the event. | |||||
/// </summary> | |||||
public SocketGuild Guild { get; private set; } | |||||
/// <summary> | |||||
/// Gets the channel of the event. | |||||
/// </summary> | |||||
public SocketGuildChannel Channel { get; private set; } | |||||
/// <summary> | |||||
/// Gets the user who created the event. | |||||
/// </summary> | |||||
public SocketGuildUser Creator { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Name { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Description { get; private set; } | |||||
/// <inheritdoc/> | |||||
public DateTimeOffset StartTime { get; private set; } | |||||
/// <inheritdoc/> | |||||
public DateTimeOffset? EndTime { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventStatus Status { get; private set; } | |||||
/// <inheritdoc/> | |||||
public GuildScheduledEventType Type { get; private set; } | |||||
/// <inheritdoc/> | |||||
public ulong? EntityId { get; private set; } | |||||
/// <inheritdoc/> | |||||
public string Location { get; private set; } | |||||
/// <inheritdoc/> | |||||
public int? UserCount { get; private set; } | |||||
internal SocketGuildEvent(DiscordSocketClient client, SocketGuild guild, ulong id) | |||||
: base(client, id) | |||||
{ | |||||
Guild = guild; | |||||
} | |||||
internal static SocketGuildEvent Create(DiscordSocketClient client, SocketGuild guild, Model model) | |||||
{ | |||||
var entity = new SocketGuildEvent(client, guild, model.Id); | |||||
entity.Update(model); | |||||
return entity; | |||||
} | |||||
internal void Update(Model model) | |||||
{ | |||||
if (model.ChannelId.IsSpecified && model.ChannelId.Value != null) | |||||
{ | |||||
Channel = Guild.GetChannel(model.ChannelId.Value.Value); | |||||
} | |||||
if (model.CreatorId.IsSpecified) | |||||
{ | |||||
var guildUser = Guild.GetUser(model.CreatorId.Value); | |||||
if(guildUser != null) | |||||
{ | |||||
if(model.Creator.IsSpecified) | |||||
guildUser.Update(Discord.State, model.Creator.Value); | |||||
Creator = guildUser; | |||||
} | |||||
else if (guildUser == null && model.Creator.IsSpecified) | |||||
{ | |||||
guildUser = SocketGuildUser.Create(Guild, Discord.State, model.Creator.Value); | |||||
Creator = guildUser; | |||||
} | |||||
} | |||||
Name = model.Name; | |||||
Description = model.Description.GetValueOrDefault(); | |||||
EntityId = model.EntityId; | |||||
Location = model.EntityMetadata?.Location.GetValueOrDefault(); | |||||
Type = model.EntityType; | |||||
PrivacyLevel = model.PrivacyLevel; | |||||
EndTime = model.ScheduledEndTime; | |||||
StartTime = model.ScheduledStartTime; | |||||
Status = model.Status; | |||||
UserCount = model.UserCount.ToNullable(); | |||||
} | |||||
/// <inheritdoc/> | |||||
public Task DeleteAsync(RequestOptions options = null) | |||||
=> GuildHelper.DeleteEventAsync(Discord, this, options); | |||||
/// <inheritdoc/> | |||||
public Task StartAsync(RequestOptions options = null) | |||||
=> ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active); | |||||
/// <inheritdoc/> | |||||
public Task EndAsync(RequestOptions options = null) | |||||
=> ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled | |||||
? GuildScheduledEventStatus.Cancelled | |||||
: GuildScheduledEventStatus.Completed); | |||||
/// <inheritdoc/> | |||||
public async Task ModifyAsync(Action<GuildScheduledEventsProperties> func, RequestOptions options = null) | |||||
{ | |||||
var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false); | |||||
Update(model); | |||||
} | |||||
/// <summary> | |||||
/// Gets a collection of users that are interested in this event. | |||||
/// </summary> | |||||
/// <param name="limit">The amount of users to fetch.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// A read-only collection of users. | |||||
/// </returns> | |||||
public Task<IReadOnlyCollection<RestUser>> GetUsersAsync(int limit = 100, RequestOptions options = null) | |||||
=> GuildHelper.GetEventUsersAsync(Discord, this, limit, options); | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual messages as a | |||||
/// collection. | |||||
/// </note> | |||||
/// This method will attempt to fetch all users that are interested in the event. | |||||
/// The library will attempt to split up the requests according to and <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. | |||||
/// In other words, if there are 300 users, and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant | |||||
/// is <c>100</c>, the request will be split into 3 individual requests; thus returning 3 individual asynchronous | |||||
/// responses, hence the need of flattening. | |||||
/// </remarks> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(RequestOptions options = null) | |||||
=> GuildHelper.GetEventUsersAsync(Discord, this, null, null, options); | |||||
/// <summary> | |||||
/// Gets a collection of N users interested in the event. | |||||
/// </summary> | |||||
/// <remarks> | |||||
/// <note type="important"> | |||||
/// The returned collection is an asynchronous enumerable object; one must call | |||||
/// <see cref="AsyncEnumerableExtensions.FlattenAsync{T}"/> to access the individual users as a | |||||
/// collection. | |||||
/// </note> | |||||
/// <note type="warning"> | |||||
/// Do not fetch too many users at once! This may cause unwanted preemptive rate limit or even actual | |||||
/// rate limit, causing your bot to freeze! | |||||
/// </note> | |||||
/// This method will attempt to fetch the number of users specified under <paramref name="limit"/> around | |||||
/// the user <paramref name="fromUserId"/> depending on the <paramref name="dir"/>. The library will | |||||
/// attempt to split up the requests according to your <paramref name="limit"/> and | |||||
/// <see cref="DiscordConfig.MaxGuildEventUsersPerBatch"/>. In other words, should the user request 500 users, | |||||
/// and the <see cref="Discord.DiscordConfig.MaxGuildEventUsersPerBatch"/> constant is <c>100</c>, the request will | |||||
/// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need | |||||
/// of flattening. | |||||
/// </remarks> | |||||
/// <param name="fromUserId">The ID of the starting user to get the users from.</param> | |||||
/// <param name="dir">The direction of the users to be gotten from.</param> | |||||
/// <param name="limit">The numbers of users to be gotten from.</param> | |||||
/// <param name="options">The options to be used when sending the request.</param> | |||||
/// <returns> | |||||
/// Paged collection of users. | |||||
/// </returns> | |||||
public IAsyncEnumerable<IReadOnlyCollection<RestUser>> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null) | |||||
=> GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options); | |||||
#region IGuildScheduledEvent | |||||
/// <inheritdoc/> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(RequestOptions options) | |||||
=> GetUsersAsync(options); | |||||
/// <inheritdoc/> | |||||
IAsyncEnumerable<IReadOnlyCollection<IUser>> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options) | |||||
=> GetUsersAsync(fromUserId, dir, limit, options); | |||||
/// <inheritdoc/> | |||||
IGuild IGuildScheduledEvent.Guild => Guild; | |||||
/// <inheritdoc/> | |||||
IUser IGuildScheduledEvent.Creator => Creator; | |||||
/// <inheritdoc/> | |||||
ulong? IGuildScheduledEvent.ChannelId => Channel?.Id; | |||||
#endregion | |||||
} | |||||
} |
@@ -94,6 +94,7 @@ namespace Discord | |||||
AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); | AssertFlag(() => new GuildPermissions(manageEmojisAndStickers: true), GuildPermission.ManageEmojisAndStickers); | ||||
AssertFlag(() => new GuildPermissions(useApplicationCommands: true), GuildPermission.UseApplicationCommands); | AssertFlag(() => new GuildPermissions(useApplicationCommands: true), GuildPermission.UseApplicationCommands); | ||||
AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); | AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); | ||||
AssertFlag(() => new GuildPermissions(manageEvents: true), GuildPermission.ManageEvents); | |||||
AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); | AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); | ||||
AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads); | AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads); | ||||
AssertFlag(() => new GuildPermissions(createPrivateThreads: true), GuildPermission.CreatePrivateThreads); | AssertFlag(() => new GuildPermissions(createPrivateThreads: true), GuildPermission.CreatePrivateThreads); | ||||
@@ -170,6 +171,7 @@ namespace Discord | |||||
AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable)); | AssertUtil(GuildPermission.ManageEmojisAndStickers, x => x.ManageEmojisAndStickers, (p, enable) => p.Modify(manageEmojisAndStickers: enable)); | ||||
AssertUtil(GuildPermission.UseApplicationCommands, x => x.UseApplicationCommands, (p, enable) => p.Modify(useApplicationCommands: enable)); | AssertUtil(GuildPermission.UseApplicationCommands, x => x.UseApplicationCommands, (p, enable) => p.Modify(useApplicationCommands: enable)); | ||||
AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable)); | AssertUtil(GuildPermission.RequestToSpeak, x => x.RequestToSpeak, (p, enable) => p.Modify(requestToSpeak: enable)); | ||||
AssertUtil(GuildPermission.ManageEvents, x => x.ManageEvents, (p, enable) => p.Modify(manageEvents: enable)); | |||||
AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable)); | AssertUtil(GuildPermission.ManageThreads, x => x.ManageThreads, (p, enable) => p.Modify(manageThreads: enable)); | ||||
AssertUtil(GuildPermission.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable)); | AssertUtil(GuildPermission.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable)); | ||||
AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable)); | AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable)); | ||||