* 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)); | ||||