diff --git a/docs/guides/guild_events/creating-guild-events.md b/docs/guides/guild_events/creating-guild-events.md
new file mode 100644
index 000000000..64ac0de9b
--- /dev/null
+++ b/docs/guides/guild_events/creating-guild-events.md
@@ -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.
diff --git a/docs/guides/guild_events/getting-event-users.md b/docs/guides/guild_events/getting-event-users.md
new file mode 100644
index 000000000..f4b5388a0
--- /dev/null
+++ b/docs/guides/guild_events/getting-event-users.md
@@ -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();
+```
diff --git a/docs/guides/guild_events/intro.md b/docs/guides/guild_events/intro.md
new file mode 100644
index 000000000..b60a8c70d
--- /dev/null
+++ b/docs/guides/guild_events/intro.md
@@ -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 += ...
+```
diff --git a/docs/guides/guild_events/modifying-events.md b/docs/guides/guild_events/modifying-events.md
new file mode 100644
index 000000000..05e14ec98
--- /dev/null
+++ b/docs/guides/guild_events/modifying-events.md
@@ -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.
diff --git a/docs/guides/toc.yml b/docs/guides/toc.yml
index 603258c33..6bd2d269a 100644
--- a/docs/guides/toc.yml
+++ b/docs/guides/toc.yml
@@ -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
diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs
index 621c8184c..03994a3c7 100644
--- a/src/Discord.Net.Core/DiscordConfig.cs
+++ b/src/Discord.Net.Core/DiscordConfig.cs
@@ -94,6 +94,13 @@ namespace Discord
/// The maximum number of users that can be gotten per-batch.
///
public const int MaxUsersPerBatch = 1000;
+ ///
+ /// Returns the max users allowed to be in a request for guild event users.
+ ///
+ ///
+ /// The maximum number of users that can be gotten per-batch.
+ ///
+ public const int MaxGuildEventUsersPerBatch = 100;
///
/// Returns the max guilds allowed to be in a request.
///
diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs
new file mode 100644
index 000000000..87881104c
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventPrivacyLevel.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents the privacy level of a guild scheduled event.
+ ///
+ public enum GuildScheduledEventPrivacyLevel
+ {
+ ///
+ /// The scheduled event is public and available in discovery.
+ ///
+ [Obsolete("This event type isn't supported yet! check back later.", true)]
+ Public = 1,
+
+ ///
+ /// The scheduled event is only accessible to guild members.
+ ///
+ Private = 2,
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs
new file mode 100644
index 000000000..6e3aa1ab3
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventStatus.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents the status of a guild event.
+ ///
+ public enum GuildScheduledEventStatus
+ {
+ ///
+ /// The event is scheduled for a set time.
+ ///
+ Scheduled = 1,
+
+ ///
+ /// The event has started.
+ ///
+ Active = 2,
+
+ ///
+ /// The event was completed.
+ ///
+ Completed = 3,
+
+ ///
+ /// The event was canceled.
+ ///
+ Cancelled = 4,
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs
new file mode 100644
index 000000000..ad741eee1
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventType.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents the type of a guild scheduled event.
+ ///
+ public enum GuildScheduledEventType
+ {
+ ///
+ /// The event doesn't have a set type.
+ ///
+ None = 0,
+
+ ///
+ /// The event is set in a stage channel.
+ ///
+ Stage = 1,
+
+ ///
+ /// The event is set in a voice channel.
+ ///
+ Voice = 2,
+
+ ///
+ /// The event is set for somewhere externally from discord.
+ ///
+ External = 3,
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs
new file mode 100644
index 000000000..a3fd729e5
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/GuildScheduledEventsProperties.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Provides properties that are used to modify an with the specified changes.
+ ///
+ public class GuildScheduledEventsProperties
+ {
+ ///
+ /// Gets or sets the channel id of the event.
+ ///
+ public Optional ChannelId { get; set; }
+
+ ///
+ /// Gets or sets the location of this event.
+ ///
+ public Optional Location { get; set; }
+
+ ///
+ /// Gets or sets the name of the event.
+ ///
+ public Optional Name { get; set; }
+
+ ///
+ /// Gets or sets the privacy level of the event.
+ ///
+ public Optional PrivacyLevel { get; set; }
+
+ ///
+ /// Gets or sets the start time of the event.
+ ///
+ public Optional StartTime { get; set; }
+ ///
+ /// Gets or sets the end time of the event.
+ ///
+ public Optional EndTime { get; set; }
+
+ ///
+ /// Gets or sets the description of the event.
+ ///
+ public Optional Description { get; set; }
+
+ ///
+ /// Gets or sets the type of the event.
+ ///
+ public Optional Type { get; set; }
+
+ ///
+ /// Gets or sets the status of the event.
+ ///
+ public Optional Status { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index fa76cc360..ebf2ccd4a 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -1056,6 +1056,58 @@ namespace Discord
///
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null);
+ ///
+ /// Gets a event within this guild.
+ ///
+ /// The id of the event.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ Task GetEventAsync(ulong id, RequestOptions options = null);
+
+ ///
+ /// Gets a collection of events within this guild.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ Task> GetEventsAsync(RequestOptions options = null);
+
+ ///
+ /// Creates an event within this guild.
+ ///
+ /// The name of the event.
+ /// The privacy level of the event.
+ /// The start time of the event.
+ /// The type of the event.
+ /// The description of the event.
+ /// The end time of the event.
+ ///
+ /// The channel id of the event.
+ ///
+ /// The event must have a type of or
+ /// in order to use this property.
+ ///
+ ///
+ /// A collection of speakers for the event.
+ /// The location of the event; links are supported
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation.
+ ///
+ Task 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);
+
///
/// Gets this guilds application commands.
///
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
new file mode 100644
index 000000000..e50f4cc2b
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuildScheduledEvent.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ ///
+ /// Represents a generic guild scheduled event.
+ ///
+ public interface IGuildScheduledEvent : IEntity
+ {
+ ///
+ /// Gets the guild this event is scheduled in.
+ ///
+ IGuild Guild { get; }
+
+ ///
+ /// Gets the optional channel id where this event will be hosted.
+ ///
+ ulong? ChannelId { get; }
+
+ ///
+ /// Gets the user who created the event.
+ ///
+ IUser Creator { get; }
+
+ ///
+ /// Gets the name of the event.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the description of the event.
+ ///
+ ///
+ /// This field is when the event doesn't have a discription.
+ ///
+ string Description { get; }
+
+ ///
+ /// Gets the start time of the event.
+ ///
+ DateTimeOffset StartTime { get; }
+
+ ///
+ /// Gets the optional end time of the event.
+ ///
+ DateTimeOffset? EndTime { get; }
+
+ ///
+ /// Gets the privacy level of the event.
+ ///
+ GuildScheduledEventPrivacyLevel PrivacyLevel { get; }
+
+ ///
+ /// Gets the status of the event.
+ ///
+ GuildScheduledEventStatus Status { get; }
+
+ ///
+ /// Gets the type of the event.
+ ///
+ GuildScheduledEventType Type { get; }
+
+ ///
+ /// Gets the optional entity id of the event. The "entity" of the event
+ /// can be a stage instance event as is seperate from .
+ ///
+ ulong? EntityId { get; }
+
+ ///
+ /// Gets the location of the event if the is external.
+ ///
+ string Location { get; }
+
+ ///
+ /// Gets the user count of the event.
+ ///
+ int? UserCount { get; }
+
+ ///
+ /// Starts the event.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous start operation.
+ ///
+ Task StartAsync(RequestOptions options = null);
+ ///
+ /// Ends or canceles the event.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous end operation.
+ ///
+ Task EndAsync(RequestOptions options = null);
+
+ ///
+ /// Modifies the guild event.
+ ///
+ /// The delegate containing the properties to modify the event with.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous modification operation.
+ ///
+ Task ModifyAsync(Action func, RequestOptions options = null);
+
+ ///
+ /// Deletes the current event.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous delete operation.
+ ///
+ Task DeleteAsync(RequestOptions options = null);
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual messages as a
+ /// collection.
+ ///
+ /// 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 .
+ /// In other words, if there are 300 users, and the constant
+ /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous
+ /// responses, hence the need of flattening.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ IAsyncEnumerable> GetUsersAsync(RequestOptions options = null);
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual users as a
+ /// collection.
+ ///
+ ///
+ /// 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!
+ ///
+ /// This method will attempt to fetch the number of users specified under around
+ /// the user depending on the . The library will
+ /// attempt to split up the requests according to your and
+ /// . In other words, should the user request 500 users,
+ /// and the constant is 100, the request will
+ /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need
+ /// of flattening.
+ ///
+ /// The ID of the starting user to get the users from.
+ /// The direction of the users to be gotten from.
+ /// The numbers of users to be gotten from.
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null);
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
index 746981c6e..448fd20b9 100644
--- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
+++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
@@ -185,10 +185,14 @@ namespace Discord
///
UseApplicationCommands = 0x80_00_00_00,
///
- /// Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.).
+ /// Allows for requesting to speak in stage channels.
///
RequestToSpeak = 0x01_00_00_00_00,
///
+ /// Allows for creating, editing, and deleting guild scheduled events.
+ ///
+ ManageEvents = 0x02_00_00_00_00,
+ ///
/// Allows for deleting and archiving threads, and viewing all private threads.
///
///
diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
index 31f74ea22..8a4ad2189 100644
--- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
+++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
@@ -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);
/// If true, a user may request to speak in stage channels.
public bool RequestToSpeak => Permissions.GetValue(RawValue, GuildPermission.RequestToSpeak);
+ /// If true, a user may create, edit, and delete events.
+ public bool ManageEvents => Permissions.GetValue(RawValue, GuildPermission.ManageEvents);
/// If true, a user may manage threads in this guild.
public bool ManageThreads => Permissions.GetValue(RawValue, GuildPermission.ManageThreads);
/// If true, a user may create public threads in this guild.
@@ -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);
///
@@ -351,6 +359,18 @@ namespace Discord
return perms;
}
+ internal void Ensure(GuildPermission permissions)
+ {
+ if (!Has(permissions))
+ {
+ var vals = Enum.GetValues(typeof(GuildPermission)).Cast();
+ 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())}";
}
diff --git a/src/Discord.Net.Core/GatewayIntents.cs b/src/Discord.Net.Core/GatewayIntents.cs
index fb0aac6bc..f2a99e44c 100644
--- a/src/Discord.Net.Core/GatewayIntents.cs
+++ b/src/Discord.Net.Core/GatewayIntents.cs
@@ -39,13 +39,15 @@ namespace Discord
DirectMessageReactions = 1 << 13,
/// This intent includes TYPING_START
DirectMessageTyping = 1 << 14,
+ /// 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
+ GuildScheduledEvents = 1 << 16,
///
/// This intent includes all but and
/// which are privileged and must be enabled in the Developer Portal.
///
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites |
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages |
- DirectMessageReactions | DirectMessageTyping,
+ DirectMessageReactions | DirectMessageTyping | GuildScheduledEvents,
///
/// This intent includes all of them, including privileged ones.
///
diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs
new file mode 100644
index 000000000..338c24dc9
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEvent.cs
@@ -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 ChannelId { get; set; }
+ [JsonProperty("creator_id")]
+ public Optional CreatorId { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ [JsonProperty("description")]
+ public Optional 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 Creator { get; set; }
+ [JsonProperty("user_count")]
+ public Optional UserCount { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs
new file mode 100644
index 000000000..1db38c0ae
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEventEntityMetadata.cs
@@ -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 Location { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs b/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs
new file mode 100644
index 000000000..1b0b93763
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/GuildScheduledEventUser.cs
@@ -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 Member { get; set; }
+ [JsonProperty("guild_scheduled_event_id")]
+ public ulong GuildScheduledEventId { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs
new file mode 100644
index 000000000..a207d3374
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/CreateGuildScheduledEventParams.cs
@@ -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 ChannelId { get; set; }
+ [JsonProperty("entity_metadata")]
+ public Optional 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 EndTime { get; set; }
+ [JsonProperty("description")]
+ public Optional Description { get; set; }
+ [JsonProperty("entity_type")]
+ public GuildScheduledEventType Type { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs b/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs
new file mode 100644
index 000000000..db3ac666e
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/GetEventUsersParams.cs
@@ -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 Limit { get; set; }
+ public Optional RelativeDirection { get; set; }
+ public Optional RelativeUserId { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs
new file mode 100644
index 000000000..3d191a0b3
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildScheduledEventParams.cs
@@ -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 ChannelId { get; set; }
+ [JsonProperty("entity_metadata")]
+ public Optional EntityMetadata { get; set; }
+ [JsonProperty("name")]
+ public Optional Name { get; set; }
+ [JsonProperty("privacy_level")]
+ public Optional PrivacyLevel { get; set; }
+ [JsonProperty("scheduled_start_time")]
+ public Optional StartTime { get; set; }
+ [JsonProperty("scheduled_end_time")]
+ public Optional EndTime { get; set; }
+ [JsonProperty("description")]
+ public Optional Description { get; set; }
+ [JsonProperty("entity_type")]
+ public Optional Type { get; set; }
+ [JsonProperty("status")]
+ public Optional Status { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index ff150e601..262da65be 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -1909,6 +1909,105 @@ namespace Discord.API
}
#endregion
+ #region Guild Events
+
+ public async Task 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("GET", () => $"guilds/{guildId}/scheduled-events?with_user_count=true", ids, options: options).ConfigureAwait(false);
+ }
+
+ public async Task 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(SendAsync("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}?with_user_count=true", ids, options: options)).ConfigureAwait(false);
+ }
+
+ public async Task 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("POST", () => $"guilds/{guildId}/scheduled-events", args, ids, options: options).ConfigureAwait(false);
+ }
+
+ public async Task 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("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 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("GET", () => $"guilds/{guildId}/scheduled-events/{eventId}/users?limit={limit}&with_member=true", ids, options: options).ConfigureAwait(false);
+ }
+
+ public async Task 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> 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("GET", endpoint, ids, options: options).ConfigureAwait(false);
+ }
+
+ #endregion
+
#region Users
public async Task GetUserAsync(ulong userId, RequestOptions options = null)
{
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 51132d513..2cdbbb7b5 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -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> 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> GetEventUsersAsync(BaseDiscordClient client, IGuildScheduledEvent guildEvent,
+ ulong? fromUserId, int? limit, RequestOptions options)
+ {
+ return new PagedAsyncEnumerable(
+ 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> 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(
+ 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();
+ 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 ModifyGuildEventAsync(BaseDiscordClient client, Action 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 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> 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 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.Unspecified,
+ Description = description ?? Optional.Unspecified,
+ EndTime = endTime ?? Optional.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
}
}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 26dc8f3b7..9b0b66633 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -1109,6 +1109,65 @@ namespace Discord.Rest
=> sticker.DeleteAsync(options);
#endregion
+ #region Guild Events
+
+ ///
+ /// Gets an event within this guild.
+ ///
+ /// The snowflake identifier for the event.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ public Task GetEventAsync(ulong id, RequestOptions options = null)
+ => GuildHelper.GetGuildEventAsync(Discord, id, this, options);
+
+ ///
+ /// Gets all active events within this guild.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ public Task> GetEventsAsync(RequestOptions options = null)
+ => GuildHelper.GetGuildEventsAsync(Discord, this, options);
+
+ ///
+ /// Creates an event within this guild.
+ ///
+ /// The name of the event.
+ /// The privacy level of the event.
+ /// The start time of the event.
+ /// The type of the event.
+ /// The description of the event.
+ /// The end time of the event.
+ ///
+ /// The channel id of the event.
+ ///
+ /// The event must have a type of or
+ /// in order to use this property.
+ ///
+ ///
+ /// A collection of speakers for the event.
+ /// The location of the event; links are supported
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation.
+ ///
+ public Task 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
///
bool IGuild.Available => Available;
@@ -1121,6 +1180,18 @@ namespace Discord.Rest
IReadOnlyCollection IGuild.Stickers => Stickers;
+ ///
+ async Task 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);
+
+ ///
+ async Task IGuild.GetEventAsync(ulong id, RequestOptions options)
+ => await GetEventAsync(id, options).ConfigureAwait(false);
+
+ ///
+ async Task> IGuild.GetEventsAsync(RequestOptions options)
+ => await GetEventsAsync(options).ConfigureAwait(false);
+
///
async Task> IGuild.GetBansAsync(RequestOptions options)
=> await GetBansAsync(options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs
new file mode 100644
index 000000000..d3ec11fc6
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEvent.cs
@@ -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, IGuildScheduledEvent
+ {
+ ///
+ public IGuild Guild { get; private set; }
+
+ ///
+ public ulong? ChannelId { get; private set; }
+
+ ///
+ public IUser Creator { get; private set; }
+
+ ///
+ public ulong CreatorId { get; private set; }
+
+ ///
+ public string Name { get; private set; }
+
+ ///
+ public string Description { get; private set; }
+
+ ///
+ public DateTimeOffset StartTime { get; private set; }
+
+ ///
+ public DateTimeOffset? EndTime { get; private set; }
+
+ ///
+ public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; }
+
+ ///
+ public GuildScheduledEventStatus Status { get; private set; }
+
+ ///
+ public GuildScheduledEventType Type { get; private set; }
+
+ ///
+ public ulong? EntityId { get; private set; }
+
+ ///
+ public string Location { get; private set; }
+
+ ///
+ 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();
+ }
+
+ ///
+ public Task StartAsync(RequestOptions options = null)
+ => ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active);
+
+ ///
+ public Task EndAsync(RequestOptions options = null)
+ => ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled
+ ? GuildScheduledEventStatus.Cancelled
+ : GuildScheduledEventStatus.Completed);
+
+ ///
+ public Task DeleteAsync(RequestOptions options = null)
+ => GuildHelper.DeleteEventAsync(Discord, this, options);
+
+ ///
+ public async Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false);
+ Update(model);
+ }
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual messages as a
+ /// collection.
+ ///
+ /// 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 .
+ /// In other words, if there are 300 users, and the constant
+ /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous
+ /// responses, hence the need of flattening.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null)
+ => GuildHelper.GetEventUsersAsync(Discord, this, null, null, options);
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual users as a
+ /// collection.
+ ///
+ ///
+ /// 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!
+ ///
+ /// This method will attempt to fetch the number of users specified under around
+ /// the user depending on the . The library will
+ /// attempt to split up the requests according to your and
+ /// . In other words, should the user request 500 users,
+ /// and the constant is 100, the request will
+ /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need
+ /// of flattening.
+ ///
+ /// The ID of the starting user to get the users from.
+ /// The direction of the users to be gotten from.
+ /// The numbers of users to be gotten from.
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ public IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null)
+ => GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options);
+
+ #region IGuildScheduledEvent
+
+ ///
+ IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(RequestOptions options)
+ => GetUsersAsync(options);
+ ///
+ IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options)
+ => GetUsersAsync(fromUserId, dir, limit, options);
+
+ #endregion
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
index 7304f5f39..872bab392 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
@@ -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)
diff --git a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs
index 0b50cb166..876254fb9 100644
--- a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs
+++ b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs
@@ -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"));
}
}
}
diff --git a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs
index 8470c6d8f..04ee38c0b 100644
--- a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs
+++ b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs
@@ -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; }
}
}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
index 531afad0e..153b320fa 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs
@@ -344,6 +344,61 @@ namespace Discord.WebSocket
internal readonly AsyncEvent, SocketGuild, Task>> _guildJoinRequestDeletedEvent = new AsyncEvent, SocketGuild, Task>>();
#endregion
+ #region Guild Events
+
+ ///
+ /// Fired when a guild event is created.
+ ///
+ public event Func GuildScheduledEventCreated
+ {
+ add { _guildScheduledEventCreated.Add(value); }
+ remove { _guildScheduledEventCreated.Remove(value); }
+ }
+ internal readonly AsyncEvent> _guildScheduledEventCreated = new AsyncEvent>();
+
+ ///
+ /// Fired when a guild event is updated.
+ ///
+ public event Func, SocketGuildEvent, Task> GuildScheduledEventUpdated
+ {
+ add { _guildScheduledEventUpdated.Add(value); }
+ remove { _guildScheduledEventUpdated.Remove(value); }
+ }
+ internal readonly AsyncEvent, SocketGuildEvent, Task>> _guildScheduledEventUpdated = new AsyncEvent, SocketGuildEvent, Task>>();
+
+
+ ///
+ /// Fired when a guild event is cancelled.
+ ///
+ public event Func GuildScheduledEventCancelled
+ {
+ add { _guildScheduledEventCancelled.Add(value); }
+ remove { _guildScheduledEventCancelled.Remove(value); }
+ }
+ internal readonly AsyncEvent> _guildScheduledEventCancelled = new AsyncEvent>();
+
+ ///
+ /// Fired when a guild event is completed.
+ ///
+ public event Func GuildScheduledEventCompleted
+ {
+ add { _guildScheduledEventCompleted.Add(value); }
+ remove { _guildScheduledEventCompleted.Remove(value); }
+ }
+ internal readonly AsyncEvent> _guildScheduledEventCompleted = new AsyncEvent>();
+
+ ///
+ /// Fired when a guild event is started.
+ ///
+ public event Func GuildScheduledEventStarted
+ {
+ add { _guildScheduledEventStarted.Add(value); }
+ remove { _guildScheduledEventStarted.Remove(value); }
+ }
+ internal readonly AsyncEvent> _guildScheduledEventStarted = new AsyncEvent>();
+
+ #endregion
+
#region Users
/// Fired when a user joins a guild.
public event Func UserJoined
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
index 39bcbbd81..cc651cd4d 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -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
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 7460a3aa7..f34106a20 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -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(_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(_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(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(_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);
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index e190f9b23..beaab1cfe 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -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 _roles;
private ConcurrentDictionary _voiceStates;
private ConcurrentDictionary _stickers;
+ private ConcurrentDictionary _events;
private ImmutableArray _emotes;
private AudioClient _audioClient;
@@ -364,6 +366,17 @@ namespace Discord.WebSocket
///
public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection();
+ ///
+ /// Gets a collection of all events within this guild.
+ ///
+ ///
+ /// This field is based off of caching alone, since there is no events returned on the guild model.
+ ///
+ ///
+ /// A read-only collection of guild events found within this guild.
+ ///
+ public IReadOnlyCollection 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();
if (_channels == null)
_channels = new ConcurrentDictionary();
if (_members == null)
@@ -449,7 +464,16 @@ namespace Discord.WebSocket
}
_voiceStates = voiceStates;
-
+ var events = new ConcurrentDictionary(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();
_downloaderPromise = new TaskCompletionSource();
@@ -1191,6 +1215,115 @@ namespace Discord.WebSocket
=> GuildHelper.SearchUsersAsync(this, Discord, query, limit, options);
#endregion
+ #region Guild Events
+
+ ///
+ /// Gets an event in this guild.
+ ///
+ /// The snowflake identifier for the event.
+ ///
+ /// An event that is associated with the specified ; if none is found.
+ ///
+ 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;
+ }
+
+ ///
+ /// Gets an event within this guild.
+ ///
+ /// The snowflake identifier for the event.
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ public Task GetEventAsync(ulong id, RequestOptions options = null)
+ => GuildHelper.GetGuildEventAsync(Discord, id, this, options);
+
+ ///
+ /// Gets all active events within this guild.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous get operation.
+ ///
+ public Task> GetEventsAsync(RequestOptions options = null)
+ => GuildHelper.GetGuildEventsAsync(Discord, this, options);
+
+ ///
+ /// Creates an event within this guild.
+ ///
+ /// The name of the event.
+ /// The privacy level of the event.
+ /// The start time of the event.
+ /// The type of the event.
+ /// The description of the event.
+ /// The end time of the event.
+ ///
+ /// The channel id of the event.
+ ///
+ /// The event must have a type of or
+ /// in order to use this property.
+ ///
+ ///
+ /// A collection of speakers for the event.
+ /// The location of the event; links are supported
+ /// The options to be used when sending the request.
+ ///
+ /// A task that represents the asynchronous create operation.
+ ///
+ public Task 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
///
/// Gets the specified number of audit log entries for this guild.
@@ -1625,6 +1758,15 @@ namespace Discord.WebSocket
///
IReadOnlyCollection IGuild.Stickers => Stickers;
///
+ async Task 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);
+ ///
+ async Task IGuild.GetEventAsync(ulong id, RequestOptions options)
+ => await GetEventAsync(id, options).ConfigureAwait(false);
+ ///
+ async Task> IGuild.GetEventsAsync(RequestOptions options)
+ => await GetEventsAsync(options).ConfigureAwait(false);
+ ///
async Task> IGuild.GetBansAsync(RequestOptions options)
=> await GetBansAsync(options).ConfigureAwait(false);
///
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs
new file mode 100644
index 000000000..6974c0498
--- /dev/null
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuildEvent.cs
@@ -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
+{
+ ///
+ /// Represents a WebSocket-based guild event.
+ ///
+ public class SocketGuildEvent : SocketEntity, IGuildScheduledEvent
+ {
+ ///
+ /// Gets the guild of the event.
+ ///
+ public SocketGuild Guild { get; private set; }
+
+ ///
+ /// Gets the channel of the event.
+ ///
+ public SocketGuildChannel Channel { get; private set; }
+
+ ///
+ /// Gets the user who created the event.
+ ///
+ public SocketGuildUser Creator { get; private set; }
+
+ ///
+ public string Name { get; private set; }
+
+ ///
+ public string Description { get; private set; }
+
+ ///
+ public DateTimeOffset StartTime { get; private set; }
+
+ ///
+ public DateTimeOffset? EndTime { get; private set; }
+
+ ///
+ public GuildScheduledEventPrivacyLevel PrivacyLevel { get; private set; }
+
+ ///
+ public GuildScheduledEventStatus Status { get; private set; }
+
+ ///
+ public GuildScheduledEventType Type { get; private set; }
+
+ ///
+ public ulong? EntityId { get; private set; }
+
+ ///
+ public string Location { get; private set; }
+
+ ///
+ 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();
+ }
+
+ ///
+ public Task DeleteAsync(RequestOptions options = null)
+ => GuildHelper.DeleteEventAsync(Discord, this, options);
+
+ ///
+ public Task StartAsync(RequestOptions options = null)
+ => ModifyAsync(x => x.Status = GuildScheduledEventStatus.Active);
+
+ ///
+ public Task EndAsync(RequestOptions options = null)
+ => ModifyAsync(x => x.Status = Status == GuildScheduledEventStatus.Scheduled
+ ? GuildScheduledEventStatus.Cancelled
+ : GuildScheduledEventStatus.Completed);
+
+ ///
+ public async Task ModifyAsync(Action func, RequestOptions options = null)
+ {
+ var model = await GuildHelper.ModifyGuildEventAsync(Discord, func, this, options).ConfigureAwait(false);
+ Update(model);
+ }
+
+ ///
+ /// Gets a collection of users that are interested in this event.
+ ///
+ /// The amount of users to fetch.
+ /// The options to be used when sending the request.
+ ///
+ /// A read-only collection of users.
+ ///
+ public Task> GetUsersAsync(int limit = 100, RequestOptions options = null)
+ => GuildHelper.GetEventUsersAsync(Discord, this, limit, options);
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual messages as a
+ /// collection.
+ ///
+ /// 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 .
+ /// In other words, if there are 300 users, and the constant
+ /// is 100, the request will be split into 3 individual requests; thus returning 3 individual asynchronous
+ /// responses, hence the need of flattening.
+ ///
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null)
+ => GuildHelper.GetEventUsersAsync(Discord, this, null, null, options);
+
+ ///
+ /// Gets a collection of N users interested in the event.
+ ///
+ ///
+ ///
+ /// The returned collection is an asynchronous enumerable object; one must call
+ /// to access the individual users as a
+ /// collection.
+ ///
+ ///
+ /// 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!
+ ///
+ /// This method will attempt to fetch the number of users specified under around
+ /// the user depending on the . The library will
+ /// attempt to split up the requests according to your and
+ /// . In other words, should the user request 500 users,
+ /// and the constant is 100, the request will
+ /// be split into 5 individual requests; thus returning 5 individual asynchronous responses, hence the need
+ /// of flattening.
+ ///
+ /// The ID of the starting user to get the users from.
+ /// The direction of the users to be gotten from.
+ /// The numbers of users to be gotten from.
+ /// The options to be used when sending the request.
+ ///
+ /// Paged collection of users.
+ ///
+ public IAsyncEnumerable> GetUsersAsync(ulong fromUserId, Direction dir, int limit = DiscordConfig.MaxGuildEventUsersPerBatch, RequestOptions options = null)
+ => GuildHelper.GetEventUsersAsync(Discord, this, fromUserId, dir, limit, options);
+
+ #region IGuildScheduledEvent
+
+ ///
+ IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(RequestOptions options)
+ => GetUsersAsync(options);
+ ///
+ IAsyncEnumerable> IGuildScheduledEvent.GetUsersAsync(ulong fromUserId, Direction dir, int limit, RequestOptions options)
+ => GetUsersAsync(fromUserId, dir, limit, options);
+ ///
+ IGuild IGuildScheduledEvent.Guild => Guild;
+ ///
+ IUser IGuildScheduledEvent.Creator => Creator;
+ ///
+ ulong? IGuildScheduledEvent.ChannelId => Channel?.Id;
+
+ #endregion
+ }
+}
diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
index 00ca05504..f0b0b2db7 100644
--- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
+++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs
@@ -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));