* 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 | |||
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 | |||
items: | |||
- name: Introduction | |||
@@ -94,6 +94,13 @@ namespace Discord | |||
/// The maximum number of users that can be gotten per-batch. | |||
/// </returns> | |||
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> | |||
/// Returns the max guilds allowed to be in a request. | |||
/// </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> | |||
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> | |||
/// Gets this guilds application commands. | |||
/// </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> | |||
UseApplicationCommands = 0x80_00_00_00, | |||
/// <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> | |||
RequestToSpeak = 0x01_00_00_00_00, | |||
/// <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. | |||
/// </summary> | |||
/// <remarks> | |||
@@ -1,6 +1,7 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
namespace Discord | |||
{ | |||
@@ -87,6 +88,8 @@ namespace Discord | |||
public bool UseApplicationCommands => Permissions.GetValue(RawValue, GuildPermission.UseApplicationCommands); | |||
/// <summary> If <c>true</c>, a user may request to speak in stage channels. </summary> | |||
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> | |||
public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads); | |||
/// <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? useApplicationCommands = null, | |||
bool? requestToSpeak = null, | |||
bool? manageEvents = null, | |||
bool? manageThreads = null, | |||
bool? createPublicThreads = null, | |||
bool? createPrivateThreads = null, | |||
@@ -182,6 +186,7 @@ namespace Discord | |||
Permissions.SetValue(ref value, manageEmojisAndStickers, GuildPermission.ManageEmojisAndStickers); | |||
Permissions.SetValue(ref value, useApplicationCommands, GuildPermission.UseApplicationCommands); | |||
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, createPublicThreads, GuildPermission.CreatePublicThreads); | |||
Permissions.SetValue(ref value, createPrivateThreads, GuildPermission.CreatePrivateThreads); | |||
@@ -227,6 +232,7 @@ namespace Discord | |||
bool manageEmojisAndStickers = false, | |||
bool useApplicationCommands = false, | |||
bool requestToSpeak = false, | |||
bool manageEvents = false, | |||
bool manageThreads = false, | |||
bool createPublicThreads = false, | |||
bool createPrivateThreads = false, | |||
@@ -267,6 +273,7 @@ namespace Discord | |||
manageEmojisAndStickers: manageEmojisAndStickers, | |||
useApplicationCommands: useApplicationCommands, | |||
requestToSpeak: requestToSpeak, | |||
manageEvents: manageEvents, | |||
manageThreads: manageThreads, | |||
createPublicThreads: createPublicThreads, | |||
createPrivateThreads: createPrivateThreads, | |||
@@ -310,6 +317,7 @@ namespace Discord | |||
bool? manageEmojisAndStickers = null, | |||
bool? useApplicationCommands = null, | |||
bool? requestToSpeak = null, | |||
bool? manageEvents = null, | |||
bool? manageThreads = null, | |||
bool? createPublicThreads = null, | |||
bool? createPrivateThreads = null, | |||
@@ -320,7 +328,7 @@ namespace Discord | |||
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, | |||
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, | |||
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, | |||
useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, | |||
useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, | |||
startEmbeddedActivities); | |||
/// <summary> | |||
@@ -351,6 +359,18 @@ namespace Discord | |||
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(); | |||
private string DebuggerDisplay => $"{string.Join(", ", ToList())}"; | |||
} | |||
@@ -39,13 +39,15 @@ namespace Discord | |||
DirectMessageReactions = 1 << 13, | |||
/// <summary> This intent includes TYPING_START </summary> | |||
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> | |||
/// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildPresences"/> | |||
/// which are privileged and must be enabled in the Developer Portal. | |||
/// </summary> | |||
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | | |||
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | | |||
DirectMessageReactions | DirectMessageTyping, | |||
DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents, | |||
/// <summary> | |||
/// This intent includes all of them, including privileged ones. | |||
/// </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 | |||
#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 | |||
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) | |||
=> 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); | |||
#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 | |||
/// <inheritdoc /> | |||
bool IGuild.Available => Available; | |||
@@ -1121,6 +1180,18 @@ namespace Discord.Rest | |||
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 /> | |||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | |||
=> 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.Threading.Tasks; | |||
using Model = Discord.API.User; | |||
using EventUserModel = Discord.API.GuildScheduledEventUser; | |||
namespace Discord.Rest | |||
{ | |||
@@ -62,6 +63,18 @@ namespace Discord.Rest | |||
entity.Update(model); | |||
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) | |||
{ | |||
if (model.Avatar.IsSpecified) | |||
@@ -27,7 +27,7 @@ namespace Discord.Net.Converters | |||
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")] | |||
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>>(); | |||
#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 | |||
/// <summary> Fired when a user joins a guild. </summary> | |||
public event Func<SocketGuildUser, Task> UserJoined | |||
@@ -486,6 +486,12 @@ namespace Discord.WebSocket | |||
client.GuildStickerDeleted += (sticker) => _guildStickerDeleted.InvokeAsync(sticker); | |||
client.GuildStickerUpdated += (before, after) => _guildStickerUpdated.InvokeAsync(before, after); | |||
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 | |||
@@ -2549,19 +2549,91 @@ namespace Discord.WebSocket | |||
switch (type) | |||
{ | |||
case "STAGE_INSTANCE_CREATE": | |||
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel); | |||
await TimedInvokeAsync(_stageStarted, nameof(StageStarted), stageChannel).ConfigureAwait(false); | |||
return; | |||
case "STAGE_INSTANCE_DELETE": | |||
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel); | |||
await TimedInvokeAsync(_stageEnded, nameof(StageEnded), stageChannel).ConfigureAwait(false); | |||
return; | |||
case "STAGE_INSTANCE_UPDATE": | |||
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel); | |||
await TimedInvokeAsync(_stageUpdated, nameof(StageUpdated), before, stageChannel).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
break; | |||
#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) | |||
case "CHANNEL_PINS_ACK": | |||
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 VoiceStateModel = Discord.API.VoiceState; | |||
using StickerModel = Discord.API.Sticker; | |||
using EventModel = Discord.API.GuildScheduledEvent; | |||
using System.IO; | |||
namespace Discord.WebSocket | |||
@@ -40,6 +41,7 @@ namespace Discord.WebSocket | |||
private ConcurrentDictionary<ulong, SocketRole> _roles; | |||
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates; | |||
private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers; | |||
private ConcurrentDictionary<ulong, SocketGuildEvent> _events; | |||
private ImmutableArray<GuildEmote> _emotes; | |||
private AudioClient _audioClient; | |||
@@ -364,6 +366,17 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
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) | |||
: base(client, id) | |||
{ | |||
@@ -381,6 +394,8 @@ namespace Discord.WebSocket | |||
IsAvailable = !(model.Unavailable ?? false); | |||
if (!IsAvailable) | |||
{ | |||
if(_events == null) | |||
_events = new ConcurrentDictionary<ulong, SocketGuildEvent>(); | |||
if (_channels == null) | |||
_channels = new ConcurrentDictionary<ulong, SocketGuildChannel>(); | |||
if (_members == null) | |||
@@ -449,7 +464,16 @@ namespace Discord.WebSocket | |||
} | |||
_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>(); | |||
_downloaderPromise = new TaskCompletionSource<bool>(); | |||
@@ -1191,6 +1215,115 @@ namespace Discord.WebSocket | |||
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options); | |||
#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 | |||
/// <summary> | |||
/// Gets the specified number of audit log entries for this guild. | |||
@@ -1625,6 +1758,15 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
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 /> | |||
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options) | |||
=> await GetBansAsync(options).ConfigureAwait(false); | |||
/// <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(useApplicationCommands: true), GuildPermission.UseApplicationCommands); | |||
AssertFlag(() => new GuildPermissions(requestToSpeak: true), GuildPermission.RequestToSpeak); | |||
AssertFlag(() => new GuildPermissions(manageEvents: true), GuildPermission.ManageEvents); | |||
AssertFlag(() => new GuildPermissions(manageThreads: true), GuildPermission.ManageThreads); | |||
AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads); | |||
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.UseApplicationCommands, x => x.UseApplicationCommands, (p, enable) => p.Modify(useApplicationCommands: 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.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable)); | |||
AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable)); | |||