@@ -1,5 +1,24 @@ | |||
# Changelog | |||
## [2.4.0] - 2021-05-22 | |||
### Added | |||
- #1726 Add stickers (91a9063) | |||
- #1753 Webhook message edit & delete functionality (f67cd8e) | |||
- #1757 Add ability to add/remove roles by id (4c9910c) | |||
- #1781 Add GetEmotesAsync to IGuild (df23d57) | |||
- #1801 Add missing property to MESSAGE_REACTION_ADD event (0715d7d) | |||
- #1828 Add methods to interact with reactions without a message object (5b244f2) | |||
- #1830 Add ModifyMessageAsync to IMessageChannel (365a848) | |||
- #1844 Add Discord Certified Moderator user flag (4b8d444) | |||
### Fixed | |||
- #1486 Add type reader when entity type reader exists (c46daaa) | |||
- #1835 Cached message emoji cleanup at MESSAGE_REACTION_REMOVE_EMOJI (8afef82) | |||
### Misc | |||
- #1778 Remove URI check from EmbedBuilder (25b04c4) | |||
- #1800 Fix spelling in SnowflakeUtils.FromSnowflake (6aff419) | |||
## [2.3.1] - 2021-03-10 | |||
### Fixed | |||
- #1761 Deadlock in DiscordShardedClient when Ready is never received (73e5cc2) | |||
@@ -1,7 +1,7 @@ | |||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | |||
<PropertyGroup> | |||
<VersionPrefix>2.3.1</VersionPrefix> | |||
<VersionSuffix></VersionSuffix> | |||
<VersionPrefix>3.0.0</VersionPrefix> | |||
<VersionSuffix>dev</VersionSuffix> | |||
<LangVersion>latest</LangVersion> | |||
<Authors>Discord.Net Contributors</Authors> | |||
<PackageTags>discord;discordapp</PackageTags> | |||
@@ -8,8 +8,6 @@ An unofficial .NET API Wrapper for the Discord client (https://discord.com). | |||
## Documentation | |||
- [Stable](https://discord.foxbot.me/) | |||
- Hosted by @foxbot | |||
- [Nightly](https://docs.stillu.cc/) | |||
- [Latest CI repo](https://github.com/discord-net/docs-static) | |||
@@ -39,4 +39,3 @@ jobs: | |||
steps: | |||
- template: azure/build.yml | |||
- template: azure/deploy.yml | |||
- template: azure/docs.yml |
@@ -28,7 +28,7 @@ public async Task SendRichEmbedAsync() | |||
var embed = new EmbedBuilder | |||
{ | |||
// Embed property can be set within object initializer | |||
Title = "Hello world!" | |||
Title = "Hello world!", | |||
Description = "I am a description set by initializer." | |||
}; | |||
// Or with methods | |||
@@ -134,7 +134,7 @@ If, for whatever reason, you have two commands which are ambiguous to | |||
each other, you may use the @Discord.Commands.PriorityAttribute to | |||
specify which should be tested before the other. | |||
The `Priority` attributes are sorted in ascending order; the higher | |||
The `Priority` attributes are sorted in descending order; the higher | |||
priority will be called first. | |||
### Command Context | |||
@@ -80,15 +80,11 @@ recommended for these operations to be awaited in a | |||
properly established async context whenever possible. | |||
To establish an async context, we will be creating an async main method | |||
in your console application, and rewriting the static main method to | |||
invoke the new async main. | |||
in your console application. | |||
[!code-csharp[Async Context](samples/first-bot/async-context.cs)] | |||
As a result of this, your program will now start and immediately | |||
jump into an async context. This allows us to create a connection | |||
to Discord later on without having to worry about setting up the | |||
correct async implementation. | |||
As a result of this, your program will now start into an async context. | |||
> [!WARNING] | |||
> If your application throws any exceptions within an async context, | |||
@@ -1,7 +1,6 @@ | |||
public class Program | |||
{ | |||
public static void Main(string[] args) | |||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||
public static Task Main(string[] args) => new Program().MainAsync(); | |||
public async Task MainAsync() | |||
{ | |||
@@ -2,8 +2,7 @@ public class Program | |||
{ | |||
private DiscordSocketClient _client; | |||
public static void Main(string[] args) | |||
=> new Program().MainAsync().GetAwaiter().GetResult(); | |||
public static Task Main(string[] args) => new Program().MainAsync(); | |||
public async Task MainAsync() | |||
{ | |||
@@ -10,11 +10,11 @@ using Discord.WebSocket; | |||
class Program | |||
{ | |||
// Program entry point | |||
static void Main(string[] args) | |||
static Task Main(string[] args) | |||
{ | |||
// Call the Program constructor, followed by the | |||
// MainAsync method and wait until it finishes (which should be never). | |||
new Program().MainAsync().GetAwaiter().GetResult(); | |||
return new Program().MainAsync(); | |||
} | |||
private readonly DiscordSocketClient _client; | |||
@@ -9,7 +9,7 @@ namespace _03_sharded_client.Modules | |||
[Command("info")] | |||
public async Task InfoAsync() | |||
{ | |||
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards} shards! | |||
var msg = $@"Hi {Context.User}! There are currently {Context.Client.Shards.Count} shards! | |||
This guild is being served by shard number {Context.Client.GetShardFor(Context.Guild).ShardId}"; | |||
await ReplyAsync(msg); | |||
} | |||
@@ -408,7 +408,7 @@ namespace Discord.Commands | |||
var typeInfo = type.GetTypeInfo(); | |||
if (typeInfo.IsEnum) | |||
return true; | |||
return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.TypeReaderType)); | |||
return _entityTypeReaders.Any(x => type == x.EntityType || typeInfo.ImplementedInterfaces.Contains(x.EntityType)); | |||
} | |||
internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) | |||
{ | |||
@@ -511,7 +511,7 @@ namespace Discord.Commands | |||
await _commandExecutedEvent.InvokeAsync(Optional.Create<CommandInfo>(), context, searchResult).ConfigureAwait(false); | |||
return searchResult; | |||
} | |||
var commands = searchResult.Commands; | |||
var preconditionResults = new Dictionary<CommandMatch, PreconditionResult>(); | |||
@@ -16,7 +16,7 @@ namespace Discord | |||
/// <see href="https://discord.com/developers/docs/reference#api-versioning">Discord API documentation</see> | |||
/// .</para> | |||
/// </returns> | |||
public const int APIVersion = 6; | |||
public const int APIVersion = 9; | |||
/// <summary> | |||
/// Returns the Voice API version Discord.Net uses. | |||
/// </summary> | |||
@@ -43,7 +43,7 @@ namespace Discord | |||
/// <returns> | |||
/// The user agent used in each Discord.Net request. | |||
/// </returns> | |||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; | |||
public static string UserAgent { get; } = $"DiscordBot (https://github.com/discord-net/Discord.Net, v{Version})"; | |||
/// <summary> | |||
/// Returns the base Discord API URL. | |||
/// </summary> | |||
@@ -141,18 +141,6 @@ namespace Discord | |||
/// </remarks> | |||
internal bool DisplayInitialLog { get; set; } = true; | |||
/// <summary> | |||
/// Gets or sets the level of precision of the rate limit reset response. | |||
/// </summary> | |||
/// <remarks> | |||
/// If set to <see cref="RateLimitPrecision.Second"/>, this value will be rounded up to the | |||
/// nearest second. | |||
/// </remarks> | |||
/// <returns> | |||
/// The currently set <see cref="RateLimitPrecision"/>. | |||
/// </returns> | |||
public RateLimitPrecision RateLimitPrecision { get; set; } = RateLimitPrecision.Millisecond; | |||
/// <summary> | |||
/// Gets or sets whether or not rate-limits should use the system clock. | |||
/// </summary> | |||
@@ -257,6 +257,21 @@ namespace Discord | |||
/// </returns> | |||
Task DeleteMessageAsync(IMessage message, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies a message. | |||
/// </summary> | |||
/// <remarks> | |||
/// This method modifies this message with the specified properties. To see an example of this | |||
/// method and what properties are available, please refer to <see cref="MessageProperties"/>. | |||
/// </remarks> | |||
/// <param name="messageId">The snowflake identifier of the message that would be changed.</param> | |||
/// <param name="func">A delegate containing the properties to modify the message 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<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. | |||
/// </summary> | |||
@@ -1,21 +0,0 @@ | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Provides properties that are used to modify the widget of an <see cref="IGuild" /> with the specified changes. | |||
/// </summary> | |||
public class GuildEmbedProperties | |||
{ | |||
/// <summary> | |||
/// Sets whether the widget should be enabled. | |||
/// </summary> | |||
public Optional<bool> Enabled { get; set; } | |||
/// <summary> | |||
/// Sets the channel that the invite should place its users in, if not <c>null</c>. | |||
/// </summary> | |||
public Optional<IChannel> Channel { get; set; } | |||
/// <summary> | |||
/// Sets the channel the invite should place its users in, if not <c>null</c>. | |||
/// </summary> | |||
public Optional<ulong?> ChannelId { get; set; } | |||
} | |||
} |
@@ -28,13 +28,6 @@ namespace Discord | |||
/// </returns> | |||
int AFKTimeout { get; } | |||
/// <summary> | |||
/// Gets a value that indicates whether this guild is embeddable (i.e. can use widget). | |||
/// </summary> | |||
/// <returns> | |||
/// <see langword="true" /> if this guild has a widget enabled; otherwise <see langword="false" />. | |||
/// </returns> | |||
bool IsEmbeddable { get; } | |||
/// <summary> | |||
/// Gets a value that indicates whether this guild has the widget enabled. | |||
/// </summary> | |||
/// <returns> | |||
@@ -132,29 +125,6 @@ namespace Discord | |||
/// </returns> | |||
ulong? AFKChannelId { get; } | |||
/// <summary> | |||
/// Gets the ID of the default channel for this guild. | |||
/// </summary> | |||
/// <remarks> | |||
/// This property retrieves the snowflake identifier of the first viewable text channel for this guild. | |||
/// <note type="warning"> | |||
/// This channel does not guarantee the user can send message to it, as it only looks for the first viewable | |||
/// text channel. | |||
/// </note> | |||
/// </remarks> | |||
/// <returns> | |||
/// A <see langword="ulong"/> representing the snowflake identifier of the default text channel; <c>0</c> if | |||
/// none can be found. | |||
/// </returns> | |||
ulong DefaultChannelId { get; } | |||
/// <summary> | |||
/// Gets the ID of the widget embed channel of this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see langword="ulong"/> representing the snowflake identifier of the embedded channel found within the | |||
/// widget settings of this guild; <see langword="null" /> if none is set. | |||
/// </returns> | |||
ulong? EmbedChannelId { get; } | |||
/// <summary> | |||
/// Gets the ID of the channel assigned to the widget of this guild. | |||
/// </summary> | |||
/// <returns> | |||
@@ -364,16 +334,6 @@ namespace Discord | |||
/// </returns> | |||
Task ModifyAsync(Action<GuildProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies this guild's embed channel. | |||
/// </summary> | |||
/// <param name="func">The delegate containing the properties to modify the guild widget 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> | |||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||
Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies this guild's widget. | |||
/// </summary> | |||
/// <param name="func">The delegate containing the properties to modify the guild widget with.</param> | |||
@@ -592,17 +552,6 @@ namespace Discord | |||
/// </returns> | |||
Task<ITextChannel> GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains the embed channel set | |||
/// within the server's widget settings; <see langword="null" /> if none is set. | |||
/// </returns> | |||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||
Task<IGuildChannel> GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); | |||
/// <summary> | |||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | |||
/// </summary> | |||
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param> | |||
@@ -892,6 +841,15 @@ namespace Discord | |||
/// </returns> | |||
Task<IReadOnlyCollection<IWebhook>> GetWebhooksAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a collection of emotes from 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. The task result contains a read-only collection | |||
/// of emotes found within the guild. | |||
/// </returns> | |||
Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null); | |||
/// <summary> | |||
/// Gets a specific emote from this guild. | |||
/// </summary> | |||
@@ -8,10 +8,10 @@ namespace Discord | |||
/// <summary> | |||
/// The target of the permission is a role. | |||
/// </summary> | |||
Role, | |||
Role = 0, | |||
/// <summary> | |||
/// The target of the permission is a user. | |||
/// </summary> | |||
User | |||
User = 1, | |||
} | |||
} |
@@ -16,14 +16,6 @@ namespace Discord | |||
/// </returns> | |||
bool IsTemporary { get; } | |||
/// <summary> | |||
/// Gets a value that indicates whether the invite has been revoked. | |||
/// </summary> | |||
/// <returns> | |||
/// <c>true</c> if this invite was revoked; otherwise <c>false</c>. | |||
/// </returns> | |||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||
bool IsRevoked { get; } | |||
/// <summary> | |||
/// Gets the time (in seconds) until the invite expires. | |||
/// </summary> | |||
/// <returns> | |||
@@ -12,7 +12,6 @@ namespace Discord | |||
{ | |||
private string _title; | |||
private string _description; | |||
private string _url; | |||
private EmbedImage? _image; | |||
private EmbedThumbnail? _thumbnail; | |||
private List<EmbedFieldBuilder> _fields; | |||
@@ -28,7 +27,7 @@ namespace Discord | |||
/// <summary> | |||
/// Returns the maximum length of description allowed by Discord. | |||
/// </summary> | |||
public const int MaxDescriptionLength = 2048; | |||
public const int MaxDescriptionLength = 4096; | |||
/// <summary> | |||
/// Returns the maximum length of total characters allowed by Discord. | |||
/// </summary> | |||
@@ -70,26 +69,14 @@ namespace Discord | |||
/// <summary> Gets or sets the URL of an <see cref="Embed"/>. </summary> | |||
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||
/// <returns> The URL of the embed.</returns> | |||
public string Url | |||
{ | |||
get => _url; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); | |||
_url = value; | |||
} | |||
} | |||
public string Url { get; set; } | |||
/// <summary> Gets or sets the thumbnail URL of an <see cref="Embed"/>. </summary> | |||
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||
/// <returns> The thumbnail URL of the embed.</returns> | |||
public string ThumbnailUrl | |||
{ | |||
get => _thumbnail?.Url; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ThumbnailUrl)); | |||
_thumbnail = new EmbedThumbnail(value, null, null, null); | |||
} | |||
set => _thumbnail = new EmbedThumbnail(value, null, null, null); | |||
} | |||
/// <summary> Gets or sets the image URL of an <see cref="Embed"/>. </summary> | |||
/// <exception cref="ArgumentException" accessor="set">Url is not a well-formed <see cref="Uri"/>.</exception> | |||
@@ -97,11 +84,7 @@ namespace Discord | |||
public string ImageUrl | |||
{ | |||
get => _image?.Url; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(ImageUrl)); | |||
_image = new EmbedImage(value, null, null, null); | |||
} | |||
set => _image = new EmbedImage(value, null, null, null); | |||
} | |||
/// <summary> Gets or sets the list of <see cref="EmbedFieldBuilder"/> of an <see cref="Embed"/>. </summary> | |||
@@ -553,8 +536,6 @@ namespace Discord | |||
public class EmbedAuthorBuilder | |||
{ | |||
private string _name; | |||
private string _url; | |||
private string _iconUrl; | |||
/// <summary> | |||
/// Gets the maximum author name length allowed by Discord. | |||
/// </summary> | |||
@@ -585,15 +566,7 @@ namespace Discord | |||
/// <returns> | |||
/// The URL of the author field. | |||
/// </returns> | |||
public string Url | |||
{ | |||
get => _url; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(Url)); | |||
_url = value; | |||
} | |||
} | |||
public string Url { get; set; } | |||
/// <summary> | |||
/// Gets or sets the icon URL of the author field. | |||
/// </summary> | |||
@@ -601,15 +574,7 @@ namespace Discord | |||
/// <returns> | |||
/// The icon URL of the author field. | |||
/// </returns> | |||
public string IconUrl | |||
{ | |||
get => _iconUrl; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); | |||
_iconUrl = value; | |||
} | |||
} | |||
public string IconUrl { get; set; } | |||
/// <summary> | |||
/// Sets the name of the author field. | |||
@@ -671,7 +636,6 @@ namespace Discord | |||
public class EmbedFooterBuilder | |||
{ | |||
private string _text; | |||
private string _iconUrl; | |||
/// <summary> | |||
/// Gets the maximum footer length allowed by Discord. | |||
@@ -703,15 +667,7 @@ namespace Discord | |||
/// <returns> | |||
/// The icon URL of the footer field. | |||
/// </returns> | |||
public string IconUrl | |||
{ | |||
get => _iconUrl; | |||
set | |||
{ | |||
if (!value.IsNullOrUri()) throw new ArgumentException(message: "Url must be a well-formed URI.", paramName: nameof(IconUrl)); | |||
_iconUrl = value; | |||
} | |||
} | |||
public string IconUrl { get; set; } | |||
/// <summary> | |||
/// Sets the name of the footer field. | |||
@@ -10,7 +10,7 @@ namespace Discord | |||
public interface IMessage : ISnowflakeEntity, IDeletable | |||
{ | |||
/// <summary> | |||
/// Gets the type of this system message. | |||
/// Gets the type of this message. | |||
/// </summary> | |||
MessageType Type { get; } | |||
/// <summary> | |||
@@ -164,6 +164,14 @@ namespace Discord | |||
/// </summary> | |||
IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions { get; } | |||
/// <summary> | |||
/// Gets all stickers included in this message. | |||
/// </summary> | |||
/// <returns> | |||
/// A read-only collection of sticker objects. | |||
/// </returns> | |||
IReadOnlyCollection<ISticker> Stickers { get; } | |||
/// <summary> | |||
/// Gets the flags related to this message. | |||
/// </summary> | |||
@@ -0,0 +1,67 @@ | |||
using System.Collections.Generic; | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Represents a discord sticker. | |||
/// </summary> | |||
public interface ISticker | |||
{ | |||
/// <summary> | |||
/// Gets the ID of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A snowflake ID associated with this sticker. | |||
/// </returns> | |||
ulong Id { get; } | |||
/// <summary> | |||
/// Gets the ID of the pack of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A snowflake ID associated with the pack of this sticker. | |||
/// </returns> | |||
ulong PackId { get; } | |||
/// <summary> | |||
/// Gets the name of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see langword="string"/> with the name of this sticker. | |||
/// </returns> | |||
string Name { get; } | |||
/// <summary> | |||
/// Gets the description of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see langword="string"/> with the description of this sticker. | |||
/// </returns> | |||
string Description { get; } | |||
/// <summary> | |||
/// Gets the list of tags of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A read-only list with the tags of this sticker. | |||
/// </returns> | |||
IReadOnlyCollection<string> Tags { get; } | |||
/// <summary> | |||
/// Gets the asset hash of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see langword="string"/> with the asset hash of this sticker. | |||
/// </returns> | |||
string Asset { get; } | |||
/// <summary> | |||
/// Gets the preview asset hash of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see langword="string"/> with the preview asset hash of this sticker. | |||
/// </returns> | |||
string PreviewAsset { get; } | |||
/// <summary> | |||
/// Gets the format type of this sticker. | |||
/// </summary> | |||
/// <returns> | |||
/// A <see cref="StickerFormatType"/> with the format type of this sticker. | |||
/// </returns> | |||
StickerFormatType FormatType { get; } | |||
} | |||
} |
@@ -36,18 +36,6 @@ namespace Discord | |||
/// </returns> | |||
Task ModifyAsync(Action<MessageProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Modifies the suppression of this message. | |||
/// </summary> | |||
/// <remarks> | |||
/// This method modifies whether or not embeds in this message are suppressed (hidden). | |||
/// </remarks> | |||
/// <param name="suppressEmbeds">Whether or not embeds in this message should be suppressed.</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 ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null); | |||
/// <summary> | |||
/// Adds this message to its channel's pinned messages. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
@@ -60,9 +60,6 @@ namespace Discord | |||
/// <summary> | |||
/// The message is an inline reply. | |||
/// </summary> | |||
/// <remarks> | |||
/// Only available in API v8. | |||
/// </remarks> | |||
Reply = 19, | |||
} | |||
} |
@@ -0,0 +1,15 @@ | |||
namespace Discord | |||
{ | |||
/// <summary> Defines the types of formats for stickers. </summary> | |||
public enum StickerFormatType | |||
{ | |||
/// <summary> Default value for a sticker format type. </summary> | |||
None = 0, | |||
/// <summary> The sticker format type is png. </summary> | |||
Png = 1, | |||
/// <summary> The sticker format type is apng. </summary> | |||
Apng = 2, | |||
/// <summary> The sticker format type is lottie. </summary> | |||
Lottie = 3, | |||
} | |||
} |
@@ -22,11 +22,6 @@ namespace Discord | |||
/// </summary> | |||
AddReactions = 0x00_00_00_40, | |||
/// <summary> | |||
/// Allows for reading of messages. This flag is obsolete, use <see cref = "ViewChannel" /> instead. | |||
/// </summary> | |||
[Obsolete("Use ViewChannel instead.")] | |||
ReadMessages = ViewChannel, | |||
/// <summary> | |||
/// Allows guild members to view a channel, which includes reading messages in text channels. | |||
/// </summary> | |||
ViewChannel = 0x00_00_04_00, | |||
@@ -45,9 +45,6 @@ namespace Discord | |||
/// <summary> If <c>true</c>, a user may add reactions. </summary> | |||
public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); | |||
/// <summary> If <c>true</c>, a user may join channels. </summary> | |||
[Obsolete("Use ViewChannel instead.")] | |||
public bool ReadMessages => ViewChannel; | |||
/// <summary> If <c>true</c>, a user may view channels. </summary> | |||
public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); | |||
@@ -65,8 +65,6 @@ namespace Discord | |||
/// Allows for viewing of audit logs. | |||
/// </summary> | |||
ViewAuditLog = 0x00_00_00_80, | |||
[Obsolete("Use ViewChannel instead.")] | |||
ReadMessages = ViewChannel, | |||
ViewChannel = 0x00_00_04_00, | |||
SendMessages = 0x00_00_08_00, | |||
/// <summary> | |||
@@ -37,9 +37,6 @@ namespace Discord | |||
/// <summary> If <c>true</c>, a user may view the guild insights. </summary> | |||
public bool ViewGuildInsights => Permissions.GetValue(RawValue, GuildPermission.ViewGuildInsights); | |||
/// <summary> If True, a user may join channels. </summary> | |||
[Obsolete("Use ViewChannel instead.")] | |||
public bool ReadMessages => ViewChannel; | |||
/// <summary> If True, a user may view channels. </summary> | |||
public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); | |||
/// <summary> If True, a user may send messages. </summary> | |||
@@ -90,6 +87,9 @@ namespace Discord | |||
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary> | |||
public GuildPermissions(ulong rawValue) { RawValue = rawValue; } | |||
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value after converting to ulong. </summary> | |||
public GuildPermissions(string rawValue) { RawValue = ulong.Parse(rawValue); } | |||
private GuildPermissions(ulong initialValue, | |||
bool? createInstantInvite = null, | |||
bool? kickMembers = null, | |||
@@ -43,9 +43,6 @@ namespace Discord | |||
/// <summary> If Allowed, a user may add reactions. </summary> | |||
public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); | |||
/// <summary> If Allowed, a user may join channels. </summary> | |||
[Obsolete("Use ViewChannel instead.")] | |||
public PermValue ReadMessages => ViewChannel; | |||
/// <summary> If Allowed, a user may join channels. </summary> | |||
public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel); | |||
/// <summary> If Allowed, a user may send messages. </summary> | |||
public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); | |||
@@ -93,6 +90,13 @@ namespace Discord | |||
DenyValue = denyValue; | |||
} | |||
/// <summary> Creates a new OverwritePermissions with the provided allow and deny packed values after converting to ulong. </summary> | |||
public OverwritePermissions(string allowValue, string denyValue) | |||
{ | |||
AllowValue = ulong.Parse(allowValue); | |||
DenyValue = ulong.Parse(denyValue); | |||
} | |||
private OverwritePermissions(ulong allowValue, ulong denyValue, | |||
PermValue? createInstantInvite = null, | |||
PermValue? manageChannel = null, | |||
@@ -20,6 +20,10 @@ namespace Discord | |||
/// </summary> | |||
IReadOnlyList<ITeamMember> TeamMembers { get; } | |||
/// <summary> | |||
/// Gets the name of this team. | |||
/// </summary> | |||
string Name { get; } | |||
/// <summary> | |||
/// Gets the user identifier that owns this team. | |||
/// </summary> | |||
ulong OwnerUserId { get; } | |||
@@ -113,7 +113,15 @@ namespace Discord | |||
/// A task that represents the asynchronous modification operation. | |||
/// </returns> | |||
Task ModifyAsync(Action<GuildUserProperties> func, RequestOptions options = null); | |||
/// <summary> | |||
/// Adds the specified role to this user in the guild. | |||
/// </summary> | |||
/// <param name="roleId">The role to be added to the user.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous role addition operation. | |||
/// </returns> | |||
Task AddRoleAsync(ulong roleId, RequestOptions options = null); | |||
/// <summary> | |||
/// Adds the specified role to this user in the guild. | |||
/// </summary> | |||
@@ -124,6 +132,15 @@ namespace Discord | |||
/// </returns> | |||
Task AddRoleAsync(IRole role, RequestOptions options = null); | |||
/// <summary> | |||
/// Adds the specified <paramref name="roleIds"/> to this user in the guild. | |||
/// </summary> | |||
/// <param name="roleIds">The roles to be added to the user.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous role addition operation. | |||
/// </returns> | |||
Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null); | |||
/// <summary> | |||
/// Adds the specified <paramref name="roles"/> to this user in the guild. | |||
/// </summary> | |||
/// <param name="roles">The roles to be added to the user.</param> | |||
@@ -133,6 +150,15 @@ namespace Discord | |||
/// </returns> | |||
Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null); | |||
/// <summary> | |||
/// Removes the specified <paramref name="roleId"/> from this user in the guild. | |||
/// </summary> | |||
/// <param name="roleId">The role to be removed from the user.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous role removal operation. | |||
/// </returns> | |||
Task RemoveRoleAsync(ulong roleId, RequestOptions options = null); | |||
/// <summary> | |||
/// Removes the specified <paramref name="role"/> from this user in the guild. | |||
/// </summary> | |||
/// <param name="role">The role to be removed from the user.</param> | |||
@@ -142,6 +168,15 @@ namespace Discord | |||
/// </returns> | |||
Task RemoveRoleAsync(IRole role, RequestOptions options = null); | |||
/// <summary> | |||
/// Removes the specified <paramref name="roleIds"/> from this user in the guild. | |||
/// </summary> | |||
/// <param name="roleIds">The roles to be removed from the user.</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous role removal operation. | |||
/// </returns> | |||
Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null); | |||
/// <summary> | |||
/// Removes the specified <paramref name="roles"/> from this user in the guild. | |||
/// </summary> | |||
/// <param name="roles">The roles to be removed from the user.</param> | |||
@@ -7,10 +7,6 @@ namespace Discord | |||
/// </summary> | |||
public interface IPresence | |||
{ | |||
/// <summary> | |||
/// Gets the activity this user is currently doing. | |||
/// </summary> | |||
IActivity Activity { get; } | |||
/// <summary> | |||
/// Gets the current status of this user. | |||
/// </summary> | |||
@@ -87,7 +87,7 @@ namespace Discord | |||
UserProperties? PublicFlags { get; } | |||
/// <summary> | |||
/// Gets the direct message channel of this user, or create one if it does not already exist. | |||
/// Creates the direct message channel of this user. | |||
/// </summary> | |||
/// <remarks> | |||
/// This method is used to obtain or create a channel used to send a direct message. | |||
@@ -102,7 +102,7 @@ namespace Discord | |||
/// <example> | |||
/// <para>The following example attempts to send a direct message to the target user and logs the incident should | |||
/// it fail.</para> | |||
/// <code region="GetOrCreateDMChannelAsync" language="cs" | |||
/// <code region="CreateDMChannelAsync" language="cs" | |||
/// source="../../../Discord.Net.Examples/Core/Entities/Users/IUser.Examples.cs"/> | |||
/// </example> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
@@ -110,6 +110,6 @@ namespace Discord | |||
/// A task that represents the asynchronous operation for getting or creating a DM channel. The task result | |||
/// contains the DM channel associated with this user. | |||
/// </returns> | |||
Task<IDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null); | |||
Task<IDMChannel> CreateDMChannelAsync(RequestOptions options = null); | |||
} | |||
} |
@@ -22,12 +22,6 @@ namespace Discord | |||
/// </summary> | |||
HypeSquadEvents = 1 << 2, | |||
/// <summary> | |||
/// Flag given to users who have participated in the bug report program. | |||
/// This flag is obsolete, use <see cref="BugHunterLevel1"/> instead. | |||
/// </summary> | |||
[Obsolete("Use BugHunterLevel1 instead.")] | |||
BugHunter = 1 << 3, | |||
/// <summary> | |||
/// Flag given to users who have participated in the bug report program and are level 1. | |||
/// </summary> | |||
BugHunterLevel1 = 1 << 3, | |||
@@ -67,5 +61,9 @@ namespace Discord | |||
/// Flag given to users that developed bots and early verified their accounts. | |||
/// </summary> | |||
EarlyVerifiedBotDeveloper = 1 << 17, | |||
/// <summary> | |||
/// Flag given to users that are discord certified moderators who has give discord's exam. | |||
/// </summary> | |||
DiscordCertifiedModerator = 1 << 18, | |||
} | |||
} |
@@ -27,7 +27,7 @@ namespace Discord | |||
/// <summary> Fills the embed author field with the provided user's full username and avatar URL. </summary> | |||
public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) => | |||
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl()); | |||
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl()); | |||
/// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary> | |||
/// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception> | |||
@@ -1,10 +0,0 @@ | |||
using System; | |||
namespace Discord | |||
{ | |||
internal static class StringExtensions | |||
{ | |||
public static bool IsNullOrUri(this string url) => | |||
string.IsNullOrEmpty(url) || Uri.IsWellFormedUriString(url, UriKind.Absolute); | |||
} | |||
} |
@@ -42,7 +42,7 @@ namespace Discord | |||
RequestOptions options = null, | |||
AllowedMentions allowedMentions = null) | |||
{ | |||
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options, allowedMentions).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -94,7 +94,7 @@ namespace Discord | |||
RequestOptions options = null | |||
) | |||
{ | |||
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -149,7 +149,7 @@ namespace Discord | |||
Embed embed = null, | |||
RequestOptions options = null) | |||
{ | |||
return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
return await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -39,5 +39,16 @@ namespace Discord | |||
DirectMessageReactions = 1 << 13, | |||
/// <summary> This intent includes TYPING_START </summary> | |||
DirectMessageTyping = 1 << 14, | |||
/// <summary> | |||
/// This intent includes all but <see cref="GuildMembers"/> and <see cref="GuildMembers"/> | |||
/// that are privileged must be enabled for the application. | |||
/// </summary> | |||
AllUnprivileged = Guilds | GuildBans | GuildEmojis | GuildIntegrations | GuildWebhooks | GuildInvites | | |||
GuildVoiceStates | GuildMessages | GuildMessageReactions | GuildMessageTyping | DirectMessages | | |||
DirectMessageReactions | DirectMessageTyping, | |||
/// <summary> | |||
/// This intent includes all of them, including privileged ones. | |||
/// </summary> | |||
All = AllUnprivileged | GuildMembers | GuildPresences | |||
} | |||
} |
@@ -1,18 +0,0 @@ | |||
namespace Discord | |||
{ | |||
/// <summary> | |||
/// Specifies the level of precision to request in the rate limit | |||
/// response header. | |||
/// </summary> | |||
public enum RateLimitPrecision | |||
{ | |||
/// <summary> | |||
/// Specifies precision rounded up to the nearest whole second | |||
/// </summary> | |||
Second, | |||
/// <summary> | |||
/// Specifies precision rounded to the nearest millisecond. | |||
/// </summary> | |||
Millisecond | |||
} | |||
} |
@@ -5,8 +5,6 @@ namespace Discord | |||
/// <summary> Specifies the type of token to use with the client. </summary> | |||
public enum TokenType | |||
{ | |||
[Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] | |||
User, | |||
/// <summary> | |||
/// An OAuth2 token type. | |||
/// </summary> | |||
@@ -12,7 +12,7 @@ namespace Discord | |||
/// </summary> | |||
/// <param name="value">The snowflake identifier to resolve.</param> | |||
/// <returns> | |||
/// A <see cref="DateTimeOffset" /> representing the time for when the object is geenrated. | |||
/// A <see cref="DateTimeOffset" /> representing the time for when the object is generated. | |||
/// </returns> | |||
public static DateTimeOffset FromSnowflake(ulong value) | |||
=> DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); | |||
@@ -18,11 +18,11 @@ namespace Discord.Net.Examples.Core.Entities.Users | |||
#endregion | |||
#region GetOrCreateDMChannelAsync | |||
#region CreateDMChannelAsync | |||
public async Task MessageUserAsync(IUser user) | |||
{ | |||
var channel = await user.GetOrCreateDMChannelAsync(); | |||
var channel = await user.CreateDMChannelAsync(); | |||
try | |||
{ | |||
await channel.SendMessageAsync("Awesome stuff!"); | |||
@@ -15,7 +15,7 @@ namespace Discord.Net.Examples.WebSocket | |||
=> client.ReactionAdded += HandleReactionAddedAsync; | |||
public async Task HandleReactionAddedAsync(Cacheable<IUserMessage, ulong> cachedMessage, | |||
ISocketMessageChannel originChannel, SocketReaction reaction) | |||
Cacheable<IMessageChannel, ulong> originChannel, SocketReaction reaction) | |||
{ | |||
var message = await cachedMessage.GetOrDownloadAsync(); | |||
if (message != null && reaction.User.IsSpecified) | |||
@@ -100,16 +100,17 @@ namespace Discord.Net.Examples.WebSocket | |||
public void HookMessageDeleted(BaseSocketClient client) | |||
=> client.MessageDeleted += HandleMessageDelete; | |||
public Task HandleMessageDelete(Cacheable<IMessage, ulong> cachedMessage, ISocketMessageChannel channel) | |||
public async Task HandleMessageDelete(Cacheable<IMessage, ulong> cachedMessage, Cacheable<IMessageChannel, ulong> cachedChannel) | |||
{ | |||
// check if the message exists in cache; if not, we cannot report what was removed | |||
if (!cachedMessage.HasValue) return Task.CompletedTask; | |||
if (!cachedMessage.HasValue) return; | |||
// gets or downloads the channel if it's not in the cache | |||
IMessageChannel channel = await cachedChannel.GetOrDownloadAsync(); | |||
var message = cachedMessage.Value; | |||
Console.WriteLine( | |||
$"A message ({message.Id}) from {message.Author} was removed from the channel {channel.Name} ({channel.Id}):" | |||
+ Environment.NewLine | |||
+ message.Content); | |||
return Task.CompletedTask; | |||
} | |||
#endregion | |||
@@ -23,10 +23,6 @@ namespace Discord.API | |||
public ulong? AFKChannelId { get; set; } | |||
[JsonProperty("afk_timeout")] | |||
public int AFKTimeout { get; set; } | |||
[JsonProperty("embed_enabled")] | |||
public Optional<bool> EmbedEnabled { get; set; } | |||
[JsonProperty("embed_channel_id")] | |||
public Optional<ulong?> EmbedChannelId { get; set; } | |||
[JsonProperty("verification_level")] | |||
public VerificationLevel VerificationLevel { get; set; } | |||
[JsonProperty("default_message_notifications")] | |||
@@ -1,13 +0,0 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
internal class GuildEmbed | |||
{ | |||
[JsonProperty("enabled")] | |||
public bool Enabled { get; set; } | |||
[JsonProperty("channel_id")] | |||
public ulong? ChannelId { get; set; } | |||
} | |||
} |
@@ -6,5 +6,7 @@ namespace Discord.API | |||
{ | |||
[JsonProperty("code")] | |||
public string Code { get; set; } | |||
[JsonProperty("uses")] | |||
public int Uses { get; set; } | |||
} | |||
} |
@@ -58,5 +58,7 @@ namespace Discord.API | |||
public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
[JsonProperty("referenced_message")] | |||
public Optional<Message> ReferencedMessage { get; set; } | |||
[JsonProperty("stickers")] | |||
public Optional<Sticker[]> Stickers { get; set; } | |||
} | |||
} |
@@ -10,8 +10,8 @@ namespace Discord.API | |||
[JsonProperty("type")] | |||
public PermissionTarget TargetType { get; set; } | |||
[JsonProperty("deny"), Int53] | |||
public ulong Deny { get; set; } | |||
public string Deny { get; set; } | |||
[JsonProperty("allow"), Int53] | |||
public ulong Allow { get; set; } | |||
public string Allow { get; set; } | |||
} | |||
} |
@@ -13,8 +13,6 @@ namespace Discord.API | |||
public Optional<ulong> GuildId { get; set; } | |||
[JsonProperty("status")] | |||
public UserStatus Status { get; set; } | |||
[JsonProperty("game")] | |||
public Game Game { get; set; } | |||
[JsonProperty("roles")] | |||
public Optional<ulong[]> Roles { get; set; } | |||
@@ -18,7 +18,7 @@ namespace Discord.API | |||
[JsonProperty("position")] | |||
public int Position { get; set; } | |||
[JsonProperty("permissions"), Int53] | |||
public ulong Permissions { get; set; } | |||
public string Permissions { get; set; } | |||
[JsonProperty("managed")] | |||
public bool Managed { get; set; } | |||
[JsonProperty("tags")] | |||
@@ -0,0 +1,25 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
{ | |||
internal class Sticker | |||
{ | |||
[JsonProperty("id")] | |||
public ulong Id { get; set; } | |||
[JsonProperty("pack_id")] | |||
public ulong PackId { get; set; } | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("description")] | |||
public string Desription { get; set; } | |||
[JsonProperty("tags")] | |||
public Optional<string> Tags { get; set; } | |||
[JsonProperty("asset")] | |||
public string Asset { get; set; } | |||
[JsonProperty("preview_asset")] | |||
public string PreviewAsset { get; set; } | |||
[JsonProperty("format_type")] | |||
public StickerFormatType FormatType { get; set; } | |||
} | |||
} |
@@ -11,6 +11,8 @@ namespace Discord.API | |||
public ulong Id { get; set; } | |||
[JsonProperty("members")] | |||
public TeamMember[] TeamMembers { get; set; } | |||
[JsonProperty("name")] | |||
public string Name { get; set; } | |||
[JsonProperty("owner_user_id")] | |||
public ulong OwnerUserId { get; set; } | |||
} | |||
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API | |||
@@ -14,6 +14,6 @@ namespace Discord.API | |||
[JsonProperty("owner")] | |||
public bool Owner { get; set; } | |||
[JsonProperty("permissions"), Int53] | |||
public ulong Permissions { get; set; } | |||
public string Permissions { get; set; } | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
@@ -7,13 +7,13 @@ namespace Discord.API.Rest | |||
internal class ModifyChannelPermissionsParams | |||
{ | |||
[JsonProperty("type")] | |||
public string Type { get; } | |||
public int Type { get; } | |||
[JsonProperty("allow")] | |||
public ulong Allow { get; } | |||
public string Allow { get; } | |||
[JsonProperty("deny")] | |||
public ulong Deny { get; } | |||
public string Deny { get; } | |||
public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) | |||
public ModifyChannelPermissionsParams(int type, string allow, string deny) | |||
{ | |||
Type = type; | |||
Allow = allow; | |||
@@ -1,4 +1,4 @@ | |||
#pragma warning disable CS1591 | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
@@ -9,7 +9,7 @@ namespace Discord.API.Rest | |||
[JsonProperty("name")] | |||
public Optional<string> Name { get; set; } | |||
[JsonProperty("permissions")] | |||
public Optional<ulong> Permissions { get; set; } | |||
public Optional<string> Permissions { get; set; } | |||
[JsonProperty("color")] | |||
public Optional<uint> Color { get; set; } | |||
[JsonProperty("hoist")] | |||
@@ -0,0 +1,16 @@ | |||
#pragma warning disable CS1591 | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
{ | |||
[JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
internal class ModifyWebhookMessageParams | |||
{ | |||
[JsonProperty("content")] | |||
public Optional<string> Content { get; set; } | |||
[JsonProperty("embeds")] | |||
public Optional<Embed[]> Embeds { get; set; } | |||
[JsonProperty("allowed_mentions")] | |||
public Optional<AllowedMentions> AllowedMentions { get; set; } | |||
} | |||
} |
@@ -1,11 +0,0 @@ | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Rest | |||
{ | |||
[JsonObject(MemberSerialization = MemberSerialization.OptIn)] | |||
internal class SuppressEmbedParams | |||
{ | |||
[JsonProperty("suppress")] | |||
public bool Suppressed { get; set; } | |||
} | |||
} |
@@ -22,7 +22,7 @@ namespace Discord.Rest | |||
return RestToken.Create(client, model); | |||
} | |||
public static async Task<RestChannel> GetChannelAsync(BaseDiscordClient client, | |||
public static async Task<RestChannel> GetChannelAsync(BaseDiscordClient client, | |||
ulong id, RequestOptions options) | |||
{ | |||
var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false); | |||
@@ -50,13 +50,13 @@ namespace Discord.Rest | |||
.Where(x => x.Type == ChannelType.Group) | |||
.Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); | |||
} | |||
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); | |||
return models.Select(RestConnection.Create).ToImmutableArray(); | |||
} | |||
public static async Task<RestInviteMetadata> GetInviteAsync(BaseDiscordClient client, | |||
string inviteId, RequestOptions options) | |||
{ | |||
@@ -65,7 +65,7 @@ namespace Discord.Rest | |||
return RestInviteMetadata.Create(client, null, null, model); | |||
return null; | |||
} | |||
public static async Task<RestGuild> GetGuildAsync(BaseDiscordClient client, | |||
ulong id, bool withCounts, RequestOptions options) | |||
{ | |||
@@ -74,14 +74,6 @@ namespace Discord.Rest | |||
return RestGuild.Create(client, model); | |||
return null; | |||
} | |||
public static async Task<RestGuildEmbed?> GetGuildEmbedAsync(BaseDiscordClient client, | |||
ulong id, RequestOptions options) | |||
{ | |||
var model = await client.ApiClient.GetGuildEmbedAsync(id, options).ConfigureAwait(false); | |||
if (model != null) | |||
return RestGuildEmbed.Create(model); | |||
return null; | |||
} | |||
public static async Task<RestGuildWidget?> GetGuildWidgetAsync(BaseDiscordClient client, | |||
ulong id, RequestOptions options) | |||
{ | |||
@@ -90,7 +82,7 @@ namespace Discord.Rest | |||
return RestGuildWidget.Create(model); | |||
return null; | |||
} | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client, | |||
public static IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(BaseDiscordClient client, | |||
ulong? fromGuildId, int? limit, RequestOptions options) | |||
{ | |||
return new PagedAsyncEnumerable<RestUserGuild>( | |||
@@ -141,7 +133,7 @@ namespace Discord.Rest | |||
var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); | |||
return RestGuild.Create(client, model); | |||
} | |||
public static async Task<RestUser> GetUserAsync(BaseDiscordClient client, | |||
ulong id, RequestOptions options) | |||
{ | |||
@@ -206,5 +198,9 @@ namespace Discord.Rest | |||
} | |||
}; | |||
} | |||
public static Task AddRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) | |||
=> client.ApiClient.AddRoleAsync(guildId, userId, roleId, options); | |||
public static Task RemoveRoleAsync(BaseDiscordClient client, ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) | |||
=> client.ApiClient.RemoveRoleAsync(guildId, userId, roleId, options); | |||
} | |||
} |
@@ -48,20 +48,18 @@ namespace Discord.API | |||
internal IRestClient RestClient { get; private set; } | |||
internal ulong? CurrentUserId { get; set; } | |||
public RateLimitPrecision RateLimitPrecision { get; private set; } | |||
internal bool UseSystemClock { get; set; } | |||
internal JsonSerializer Serializer => _serializer; | |||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, | |||
JsonSerializer serializer = null, RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, bool useSystemClock = true, string clientId = null, string clientSecret = null) | |||
JsonSerializer serializer = null, bool useSystemClock = true, string clientId = null, string clientSecret = null) | |||
{ | |||
_restClientProvider = restClientProvider; | |||
UserAgent = userAgent; | |||
DefaultRetryMode = defaultRetryMode; | |||
_serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; | |||
RateLimitPrecision = rateLimitPrecision; | |||
UseSystemClock = useSystemClock; | |||
RequestQueue = new RequestQueue(); | |||
@@ -82,14 +80,12 @@ namespace Discord.API | |||
RestClient.SetHeader("accept", "*/*"); | |||
RestClient.SetHeader("user-agent", UserAgent); | |||
RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); | |||
RestClient.SetHeader("X-RateLimit-Precision", RateLimitPrecision.ToString().ToLower()); | |||
} | |||
/// <exception cref="ArgumentException">Unknown OAuth token type.</exception> | |||
internal static string GetPrefixedToken(TokenType tokenType, string token) | |||
{ | |||
return tokenType switch | |||
{ | |||
default(TokenType) => token, | |||
TokenType.Bot => $"Bot {token}", | |||
TokenType.Bearer => $"Bearer {token}", | |||
_ => throw new ArgumentException(message: "Unknown OAuth token type.", paramName: nameof(tokenType)), | |||
@@ -600,6 +596,43 @@ namespace Discord.API | |||
var ids = new BucketIds(webhookId: webhookId); | |||
return await SendJsonAsync<Message>("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
/// <exception cref="InvalidOperationException">This operation may only be called with a <see cref="TokenType.Webhook"/> token.</exception> | |||
public async Task ModifyWebhookMessageAsync(ulong webhookId, ulong messageId, ModifyWebhookMessageParams args, RequestOptions options = null) | |||
{ | |||
if (AuthTokenType != TokenType.Webhook) | |||
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); | |||
Preconditions.NotNull(args, nameof(args)); | |||
Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); | |||
Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
if (args.Embeds.IsSpecified) | |||
Preconditions.AtMost(args.Embeds.Value.Length, 10, nameof(args.Embeds), "A max of 10 Embeds are allowed."); | |||
if (args.Content.IsSpecified && args.Content.Value.Length > DiscordConfig.MaxMessageSize) | |||
throw new ArgumentException(message: $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", paramName: nameof(args.Content)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(webhookId: webhookId); | |||
await SendJsonAsync<Message>("PATCH", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="InvalidOperationException">This operation may only be called with a <see cref="TokenType.Webhook"/> token.</exception> | |||
public async Task DeleteWebhookMessageAsync(ulong webhookId, ulong messageId, RequestOptions options = null) | |||
{ | |||
if (AuthTokenType != TokenType.Webhook) | |||
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); | |||
Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); | |||
Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(webhookId: webhookId); | |||
await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}/messages/{messageId}", ids, options: options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public async Task<Message> UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) | |||
{ | |||
@@ -685,16 +718,6 @@ namespace Discord.API | |||
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); | |||
} | |||
public async Task SuppressEmbedAsync(ulong channelId, ulong messageId, Rest.SuppressEmbedParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
Preconditions.NotEqual(messageId, 0, nameof(messageId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(channelId: channelId); | |||
await SendJsonAsync("POST", () => $"channels/{channelId}/messages/{messageId}/suppress-embeds", args, ids, options: options).ConfigureAwait(false); | |||
} | |||
public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(channelId, 0, nameof(channelId)); | |||
@@ -976,7 +999,7 @@ namespace Discord.API | |||
var ids = new BucketIds(guildId: guildId); | |||
string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; | |||
await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); | |||
await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete_message_days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentException"><paramref name="guildId"/> and <paramref name="userId"/> must not be equal to zero.</exception> | |||
public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) | |||
@@ -989,32 +1012,6 @@ namespace Discord.API | |||
await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); | |||
} | |||
//Guild Embeds | |||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||
public async Task<GuildEmbed> GetGuildEmbedAsync(ulong guildId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
try | |||
{ | |||
var ids = new BucketIds(guildId: guildId); | |||
return await SendAsync<GuildEmbed>("GET", () => $"guilds/{guildId}/embed", ids, options: options).ConfigureAwait(false); | |||
} | |||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } | |||
} | |||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||
/// <exception cref="ArgumentNullException"><paramref name="args"/> must not be <see langword="null"/>.</exception> | |||
public async Task<GuildEmbed> ModifyGuildEmbedAsync(ulong guildId, Rest.ModifyGuildEmbedParams args, RequestOptions options = null) | |||
{ | |||
Preconditions.NotNull(args, nameof(args)); | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(guildId: guildId); | |||
return await SendJsonAsync<GuildEmbed>("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); | |||
} | |||
//Guild Widget | |||
/// <exception cref="ArgumentException"><paramref name="guildId"/> must not be equal to zero.</exception> | |||
public async Task<GuildWidget> GetGuildWidgetAsync(ulong guildId, RequestOptions options = null) | |||
@@ -1320,6 +1317,15 @@ namespace Discord.API | |||
} | |||
//Guild emoji | |||
public async Task<IReadOnlyCollection<Emoji>> GetGuildEmotesAsync(ulong guildId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
options = RequestOptions.CreateOrClone(options); | |||
var ids = new BucketIds(guildId: guildId); | |||
return await SendAsync<IReadOnlyCollection<Emoji>>("GET", () => $"guilds/{guildId}/emojis", ids, options: options).ConfigureAwait(false); | |||
} | |||
public async Task<Emoji> GetGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) | |||
{ | |||
Preconditions.NotEqual(guildId, 0, nameof(guildId)); | |||
@@ -16,7 +16,7 @@ namespace Discord.Rest | |||
/// <summary> | |||
/// Gets the logged-in user. | |||
/// </summary> | |||
public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; | |||
public new RestSelfUser CurrentUser { get => base.CurrentUser as RestSelfUser; internal set => base.CurrentUser = value; } | |||
/// <inheritdoc /> | |||
public DiscordRestClient() : this(new DiscordRestConfig()) { } | |||
@@ -86,9 +86,6 @@ namespace Discord.Rest | |||
=> ClientHelper.GetGuildAsync(this, id, false, options); | |||
public Task<RestGuild> GetGuildAsync(ulong id, bool withCounts, RequestOptions options = null) | |||
=> ClientHelper.GetGuildAsync(this, id, withCounts, options); | |||
[Obsolete("This endpoint is deprecated, use GetGuildWidgetAsync instead.")] | |||
public Task<RestGuildEmbed?> GetGuildEmbedAsync(ulong id, RequestOptions options = null) | |||
=> ClientHelper.GetGuildEmbedAsync(this, id, options); | |||
public Task<RestGuildWidget?> GetGuildWidgetAsync(ulong id, RequestOptions options = null) | |||
=> ClientHelper.GetGuildWidgetAsync(this, id, options); | |||
public IAsyncEnumerable<IReadOnlyCollection<RestUserGuild>> GetGuildSummariesAsync(RequestOptions options = null) | |||
@@ -113,7 +110,19 @@ namespace Discord.Rest | |||
=> ClientHelper.GetVoiceRegionAsync(this, id, options); | |||
public Task<RestWebhook> GetWebhookAsync(ulong id, RequestOptions options = null) | |||
=> ClientHelper.GetWebhookAsync(this, id, options); | |||
public Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId) | |||
=> ClientHelper.AddRoleAsync(this, guildId, userId, roleId); | |||
public Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId) | |||
=> ClientHelper.RemoveRoleAsync(this, guildId, userId, roleId); | |||
public Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) | |||
=> MessageHelper.AddReactionAsync(channelId, messageId, emote, this, options); | |||
public Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, RequestOptions options = null) | |||
=> MessageHelper.RemoveReactionAsync(channelId, messageId, userId, emote, this, options); | |||
public Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||
=> MessageHelper.RemoveAllReactionsAsync(channelId, messageId, this, options); | |||
public Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, RequestOptions options = null) | |||
=> MessageHelper.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote, this, options); | |||
//IDiscordClient | |||
/// <inheritdoc /> | |||
async Task<IApplication> IDiscordClient.GetApplicationInfoAsync(RequestOptions options) | |||
@@ -33,8 +33,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -59,8 +59,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -84,8 +84,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -286,6 +286,13 @@ namespace Discord.Rest | |||
return RestUserMessage.Create(client, channel, client.CurrentUser, model); | |||
} | |||
public static async Task<RestUserMessage> ModifyMessageAsync(IMessageChannel channel, ulong messageId, Action<MessageProperties> func, | |||
BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var msgModel = await MessageHelper.ModifyAsync(channel.Id, messageId, client, func, options).ConfigureAwait(false); | |||
return RestUserMessage.Create(client, channel, msgModel.Author.IsSpecified ? RestUser.Create(client, msgModel.Author.Value) : client.CurrentUser, msgModel); | |||
} | |||
public static Task DeleteMessageAsync(IMessageChannel channel, ulong messageId, BaseDiscordClient client, | |||
RequestOptions options) | |||
=> MessageHelper.DeleteAsync(channel.Id, messageId, client, options); | |||
@@ -321,13 +328,13 @@ namespace Discord.Rest | |||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
IUser user, OverwritePermissions perms, RequestOptions options) | |||
{ | |||
var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); | |||
var args = new ModifyChannelPermissionsParams((int)PermissionTarget.User, perms.AllowValue.ToString(), perms.DenyValue.ToString()); | |||
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false); | |||
} | |||
public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
IRole role, OverwritePermissions perms, RequestOptions options) | |||
{ | |||
var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); | |||
var args = new ModifyChannelPermissionsParams((int)PermissionTarget.Role, perms.AllowValue.ToString(), perms.DenyValue.ToString()); | |||
await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false); | |||
} | |||
public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, | |||
@@ -443,8 +450,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
}; | |||
await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); | |||
@@ -135,6 +135,10 @@ namespace Discord.Rest | |||
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public Task TriggerTypingAsync(RequestOptions options = null) | |||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
@@ -93,6 +93,10 @@ namespace Discord.Rest | |||
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentOutOfRangeException">Message content is too long, length must be less or equal to <see cref="DiscordConfig.MaxMessageSize"/>.</exception> | |||
public Task<RestUserMessage> SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null, AllowedMentions allowedMentions = null, MessageReference messageReference = null) | |||
@@ -152,6 +152,10 @@ namespace Discord.Rest | |||
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public Task TriggerTypingAsync(RequestOptions options = null) | |||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
@@ -4,7 +4,6 @@ using System.Collections.Generic; | |||
using System.Collections.Immutable; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using EmbedModel = Discord.API.GuildEmbed; | |||
using WidgetModel = Discord.API.GuildWidget; | |||
using Model = Discord.API.Guild; | |||
using RoleModel = Discord.API.Role; | |||
@@ -81,26 +80,6 @@ namespace Discord.Rest | |||
return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> | |||
public static async Task<EmbedModel> ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, | |||
Action<GuildEmbedProperties> func, RequestOptions options) | |||
{ | |||
if (func == null) throw new ArgumentNullException(nameof(func)); | |||
var args = new GuildEmbedProperties(); | |||
func(args); | |||
var apiArgs = new API.Rest.ModifyGuildEmbedParams | |||
{ | |||
Enabled = args.Enabled | |||
}; | |||
if (args.Channel.IsSpecified) | |||
apiArgs.ChannelId = args.Channel.Value?.Id; | |||
else if (args.ChannelId.IsSpecified) | |||
apiArgs.ChannelId = args.ChannelId.Value; | |||
return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <c>null</c>.</exception> | |||
public static async Task<WidgetModel> ModifyWidgetAsync(IGuild guild, BaseDiscordClient client, | |||
Action<GuildWidgetProperties> func, RequestOptions options) | |||
{ | |||
@@ -205,8 +184,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -233,8 +212,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -258,8 +237,8 @@ namespace Discord.Rest | |||
{ | |||
TargetId = overwrite.TargetId, | |||
TargetType = overwrite.TargetType, | |||
Allow = overwrite.Permissions.AllowValue, | |||
Deny = overwrite.Permissions.DenyValue | |||
Allow = overwrite.Permissions.AllowValue.ToString(), | |||
Deny = overwrite.Permissions.DenyValue.ToString() | |||
}).ToArray() | |||
: Optional.Create<API.Overwrite[]>(), | |||
}; | |||
@@ -304,6 +283,7 @@ namespace Discord.Rest | |||
var vanityModel = await client.ApiClient.GetVanityInviteAsync(guild.Id, options).ConfigureAwait(false); | |||
if (vanityModel == null) throw new InvalidOperationException("This guild does not have a vanity URL."); | |||
var inviteModel = await client.ApiClient.GetInviteAsync(vanityModel.Code, options).ConfigureAwait(false); | |||
inviteModel.Uses = vanityModel.Uses; | |||
return RestInviteMetadata.Create(client, guild, null, inviteModel); | |||
} | |||
@@ -320,7 +300,7 @@ namespace Discord.Rest | |||
Hoist = isHoisted, | |||
Mentionable = isMentionable, | |||
Name = name, | |||
Permissions = permissions?.RawValue ?? Optional.Create<ulong>() | |||
Permissions = permissions?.RawValue.ToString() ?? Optional.Create<string>() | |||
}; | |||
var model = await client.ApiClient.CreateGuildRoleAsync(guild.Id, createGuildRoleParams, options).ConfigureAwait(false); | |||
@@ -496,6 +476,11 @@ namespace Discord.Rest | |||
} | |||
//Emotes | |||
public static async Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var models = await client.ApiClient.GetGuildEmotesAsync(guild.Id, options).ConfigureAwait(false); | |||
return models.Select(x => x.ToEntity()).ToImmutableArray(); | |||
} | |||
public static async Task<GuildEmote> GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) | |||
{ | |||
var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options).ConfigureAwait(false); | |||
@@ -6,7 +6,6 @@ using System.Diagnostics; | |||
using System.Globalization; | |||
using System.Linq; | |||
using System.Threading.Tasks; | |||
using EmbedModel = Discord.API.GuildEmbed; | |||
using WidgetModel = Discord.API.GuildWidget; | |||
using Model = Discord.API.Guild; | |||
@@ -27,8 +26,6 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public int AFKTimeout { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsEmbeddable { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsWidgetEnabled { get; private set; } | |||
/// <inheritdoc /> | |||
public VerificationLevel VerificationLevel { get; private set; } | |||
@@ -42,8 +39,6 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public ulong? AFKChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? EmbedChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? WidgetChannelId { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong? SystemChannelId { get; private set; } | |||
@@ -95,8 +90,6 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); | |||
[Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] | |||
public ulong DefaultChannelId => Id; | |||
/// <inheritdoc /> | |||
public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); | |||
/// <inheritdoc /> | |||
@@ -133,16 +126,12 @@ namespace Discord.Rest | |||
internal void Update(Model model) | |||
{ | |||
AFKChannelId = model.AFKChannelId; | |||
if (model.EmbedChannelId.IsSpecified) | |||
EmbedChannelId = model.EmbedChannelId.Value; | |||
if (model.WidgetChannelId.IsSpecified) | |||
WidgetChannelId = model.WidgetChannelId.Value; | |||
SystemChannelId = model.SystemChannelId; | |||
RulesChannelId = model.RulesChannelId; | |||
PublicUpdatesChannelId = model.PublicUpdatesChannelId; | |||
AFKTimeout = model.AFKTimeout; | |||
if (model.EmbedEnabled.IsSpecified) | |||
IsEmbeddable = model.EmbedEnabled.Value; | |||
if (model.WidgetEnabled.IsSpecified) | |||
IsWidgetEnabled = model.WidgetEnabled.Value; | |||
IconId = model.Icon; | |||
@@ -200,11 +189,6 @@ namespace Discord.Rest | |||
Available = true; | |||
} | |||
internal void Update(EmbedModel model) | |||
{ | |||
EmbedChannelId = model.ChannelId; | |||
IsEmbeddable = model.Enabled; | |||
} | |||
internal void Update(WidgetModel model) | |||
{ | |||
WidgetChannelId = model.ChannelId; | |||
@@ -241,15 +225,6 @@ namespace Discord.Rest | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||
public async Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) | |||
{ | |||
var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); | |||
Update(model); | |||
} | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public async Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | |||
@@ -463,23 +438,6 @@ namespace Discord.Rest | |||
.FirstOrDefault(); | |||
} | |||
/// <summary> | |||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in 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. The task result contains the embed channel set | |||
/// within the server's widget settings; <see langword="null"/> if none is set. | |||
/// </returns> | |||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||
public async Task<RestGuildChannel> GetEmbedChannelAsync(RequestOptions options = null) | |||
{ | |||
var embedId = EmbedChannelId; | |||
if (embedId.HasValue) | |||
return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); | |||
return null; | |||
} | |||
/// <summary> | |||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | |||
/// </summary> | |||
@@ -828,6 +786,9 @@ namespace Discord.Rest | |||
//Emotes | |||
/// <inheritdoc /> | |||
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null) | |||
=> GuildHelper.GetEmotesAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | |||
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); | |||
/// <inheritdoc /> | |||
@@ -934,15 +895,6 @@ namespace Discord.Rest | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
[Obsolete("This endpoint is deprecated, use GetWidgetChannelAsync instead.")] | |||
async Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
return await GetEmbedChannelAsync(options).ConfigureAwait(false); | |||
else | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
async Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | |||
{ | |||
if (mode == CacheMode.AllowDownload) | |||
@@ -1,25 +0,0 @@ | |||
using System.Diagnostics; | |||
using Model = Discord.API.GuildEmbed; | |||
namespace Discord.Rest | |||
{ | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public struct RestGuildEmbed | |||
{ | |||
public bool IsEnabled { get; private set; } | |||
public ulong? ChannelId { get; private set; } | |||
internal RestGuildEmbed(bool isEnabled, ulong? channelId) | |||
{ | |||
ChannelId = channelId; | |||
IsEnabled = isEnabled; | |||
} | |||
internal static RestGuildEmbed Create(Model model) | |||
{ | |||
return new RestGuildEmbed(model.Enabled, model.ChannelId); | |||
} | |||
public override string ToString() => ChannelId?.ToString() ?? "Unknown"; | |||
private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; | |||
} | |||
} |
@@ -8,9 +8,6 @@ namespace Discord.Rest | |||
{ | |||
private long _createdAtTicks; | |||
/// <inheritdoc /> | |||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||
public bool IsRevoked { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsTemporary { get; private set; } | |||
/// <inheritdoc /> | |||
@@ -71,6 +71,48 @@ namespace Discord.Rest | |||
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||
} | |||
public static async Task<Model> ModifyAsync(ulong channelId, ulong msgId, BaseDiscordClient client, Action<MessageProperties> func, | |||
RequestOptions options) | |||
{ | |||
var args = new MessageProperties(); | |||
func(args); | |||
if ((args.Content.IsSpecified && string.IsNullOrEmpty(args.Content.Value)) && (args.Embed.IsSpecified && args.Embed.Value == null)) | |||
Preconditions.NotNullOrEmpty(args.Content.IsSpecified ? args.Content.Value : string.Empty, nameof(args.Content)); | |||
if (args.AllowedMentions.IsSpecified) | |||
{ | |||
AllowedMentions allowedMentions = args.AllowedMentions.Value; | |||
Preconditions.AtMost(allowedMentions?.RoleIds?.Count ?? 0, 100, nameof(allowedMentions.RoleIds), "A max of 100 role Ids are allowed."); | |||
Preconditions.AtMost(allowedMentions?.UserIds?.Count ?? 0, 100, nameof(allowedMentions.UserIds), "A max of 100 user Ids are allowed."); | |||
// check that user flag and user Id list are exclusive, same with role flag and role Id list | |||
if (allowedMentions != null && allowedMentions.AllowedTypes.HasValue) | |||
{ | |||
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Users) && | |||
allowedMentions.UserIds != null && allowedMentions.UserIds.Count > 0) | |||
{ | |||
throw new ArgumentException("The Users flag is mutually exclusive with the list of User Ids.", nameof(allowedMentions)); | |||
} | |||
if (allowedMentions.AllowedTypes.Value.HasFlag(AllowedMentionTypes.Roles) && | |||
allowedMentions.RoleIds != null && allowedMentions.RoleIds.Count > 0) | |||
{ | |||
throw new ArgumentException("The Roles flag is mutually exclusive with the list of Role Ids.", nameof(allowedMentions)); | |||
} | |||
} | |||
} | |||
var apiArgs = new API.Rest.ModifyMessageParams | |||
{ | |||
Content = args.Content, | |||
Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create<API.Embed>(), | |||
Flags = args.Flags.IsSpecified ? args.Flags.Value : Optional.Create<MessageFlags?>(), | |||
AllowedMentions = args.AllowedMentions.IsSpecified ? args.AllowedMentions.Value.ToModel() : Optional.Create<API.AllowedMentions>(), | |||
}; | |||
return await client.ApiClient.ModifyMessageAsync(channelId, msgId, apiArgs, options).ConfigureAwait(false); | |||
} | |||
public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) | |||
=> DeleteAsync(msg.Channel.Id, msg.Id, client, options); | |||
@@ -80,13 +122,9 @@ namespace Discord.Rest | |||
await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); | |||
} | |||
public static async Task SuppressEmbedsAsync(IMessage msg, BaseDiscordClient client, bool suppress, RequestOptions options) | |||
public static async Task AddReactionAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
var apiArgs = new API.Rest.SuppressEmbedParams | |||
{ | |||
Suppressed = suppress | |||
}; | |||
await client.ApiClient.SuppressEmbedAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); | |||
await client.ApiClient.AddReactionAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
} | |||
public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
@@ -94,16 +132,31 @@ namespace Discord.Rest | |||
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveReactionAsync(channelId, messageId, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveReactionAsync(IMessage msg, ulong userId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, userId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveAllReactionsAsync(channelId, messageId, options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveAllReactionsForEmoteAsync(ulong channelId, ulong messageId, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveAllReactionsForEmoteAsync(channelId, messageId, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveAllReactionsForEmoteAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) | |||
{ | |||
await client.ApiClient.RemoveAllReactionsForEmoteAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : UrlEncode(emote.Name), options).ConfigureAwait(false); | |||
@@ -58,6 +58,8 @@ namespace Discord.Rest | |||
public virtual IReadOnlyCollection<RestUser> MentionedUsers => ImmutableArray.Create<RestUser>(); | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>(); | |||
/// <inheritdoc /> | |||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | |||
@@ -69,6 +71,8 @@ namespace Discord.Rest | |||
public MessageReference Reference { get; private set; } | |||
/// <inheritdoc /> | |||
public MessageFlags? Flags { get; private set; } | |||
/// <inheritdoc/> | |||
public MessageType Type { get; private set; } | |||
internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||
: base(discord, id) | |||
@@ -86,6 +90,8 @@ namespace Discord.Rest | |||
} | |||
internal virtual void Update(Model model) | |||
{ | |||
Type = model.Type; | |||
if (model.Timestamp.IsSpecified) | |||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||
@@ -164,8 +170,6 @@ namespace Discord.Rest | |||
/// </returns> | |||
public override string ToString() => Content; | |||
/// <inheritdoc /> | |||
MessageType IMessage.Type => MessageType.Default; | |||
IUser IMessage.Author => Author; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
@@ -173,6 +177,8 @@ namespace Discord.Rest | |||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers; | |||
/// <inheritdoc /> | |||
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); | |||
@@ -9,9 +9,6 @@ namespace Discord.Rest | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class RestSystemMessage : RestMessage, ISystemMessage | |||
{ | |||
/// <inheritdoc /> | |||
public MessageType Type { get; private set; } | |||
internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) | |||
: base(discord, id, channel, author, MessageSource.System) | |||
{ | |||
@@ -25,8 +22,6 @@ namespace Discord.Rest | |||
internal override void Update(Model model) | |||
{ | |||
base.Update(model); | |||
Type = model.Type; | |||
} | |||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; | |||
@@ -21,6 +21,7 @@ namespace Discord.Rest | |||
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | |||
private ImmutableArray<ulong> _roleMentionIds = ImmutableArray.Create<ulong>(); | |||
private ImmutableArray<RestUser> _userMentions = ImmutableArray.Create<RestUser>(); | |||
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>(); | |||
/// <inheritdoc /> | |||
public override bool IsTTS => _isTTS; | |||
@@ -45,6 +46,8 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ITag> Tags => _tags; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Sticker> Stickers => _stickers; | |||
/// <inheritdoc /> | |||
public IUserMessage ReferencedMessage => _referencedMessage; | |||
internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) | |||
@@ -132,6 +135,20 @@ namespace Discord.Rest | |||
IUser refMsgAuthor = MessageHelper.GetAuthor(Discord, guild, refMsg.Author.Value, refMsg.WebhookId.ToNullable()); | |||
_referencedMessage = RestUserMessage.Create(Discord, Channel, refMsgAuthor, refMsg); | |||
} | |||
if (model.Stickers.IsSpecified) | |||
{ | |||
var value = model.Stickers.Value; | |||
if (value.Length > 0) | |||
{ | |||
var stickers = ImmutableArray.CreateBuilder<Sticker>(value.Length); | |||
for (int i = 0; i < value.Length; i++) | |||
stickers.Add(Sticker.Create(value[i])); | |||
_stickers = stickers.ToImmutable(); | |||
} | |||
else | |||
_stickers = ImmutableArray.Create<Sticker>(); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
@@ -147,9 +164,6 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public Task UnpinAsync(RequestOptions options = null) | |||
=> MessageHelper.UnpinAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) | |||
=> MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); | |||
public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
@@ -0,0 +1,48 @@ | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using Model = Discord.API.Sticker; | |||
namespace Discord | |||
{ | |||
/// <inheritdoc cref="ISticker"/> | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class Sticker : ISticker | |||
{ | |||
/// <inheritdoc /> | |||
public ulong Id { get; } | |||
/// <inheritdoc /> | |||
public ulong PackId { get; } | |||
/// <inheritdoc /> | |||
public string Name { get; } | |||
/// <inheritdoc /> | |||
public string Description { get; } | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<string> Tags { get; } | |||
/// <inheritdoc /> | |||
public string Asset { get; } | |||
/// <inheritdoc /> | |||
public string PreviewAsset { get; } | |||
/// <inheritdoc /> | |||
public StickerFormatType FormatType { get; } | |||
internal Sticker(ulong id, ulong packId, string name, string description, string[] tags, string asset, string previewAsset, StickerFormatType formatType) | |||
{ | |||
Id = id; | |||
PackId = packId; | |||
Name = name; | |||
Description = description; | |||
Tags = tags.ToReadOnlyCollection(); | |||
Asset = asset; | |||
PreviewAsset = previewAsset; | |||
FormatType = formatType; | |||
} | |||
internal static Sticker Create(Model model) | |||
{ | |||
return new Sticker(model.Id, model.PackId, model.Name, model.Desription, | |||
model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0], | |||
model.Asset, model.PreviewAsset, model.FormatType); | |||
} | |||
private string DebuggerDisplay => $"{Name} ({Id})"; | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
using System; | |||
using System; | |||
using System.Threading.Tasks; | |||
using Model = Discord.API.Role; | |||
using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; | |||
@@ -24,7 +24,7 @@ namespace Discord.Rest | |||
Hoist = args.Hoist, | |||
Mentionable = args.Mentionable, | |||
Name = args.Name, | |||
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue : Optional.Create<ulong>() | |||
Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue.ToString() : Optional.Create<string>() | |||
}; | |||
var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); | |||
@@ -12,6 +12,8 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
public IReadOnlyList<ITeamMember> TeamMembers { get; private set; } | |||
/// <inheritdoc /> | |||
public string Name { get; private set; } | |||
/// <inheritdoc /> | |||
public ulong OwnerUserId { get; private set; } | |||
private string _iconId; | |||
@@ -30,6 +32,7 @@ namespace Discord.Rest | |||
{ | |||
if (model.Icon.IsSpecified) | |||
_iconId = model.Icon.Value; | |||
Name = model.Name; | |||
OwnerUserId = model.OwnerUserId; | |||
TeamMembers = model.TeamMembers.Select(x => new RestTeamMember(Discord, x)).ToImmutableArray(); | |||
} | |||
@@ -112,17 +112,29 @@ namespace Discord.Rest | |||
public Task KickAsync(string reason = null, RequestOptions options = null) | |||
=> UserHelper.KickAsync(this, Discord, reason, options); | |||
/// <inheritdoc /> | |||
public Task AddRoleAsync(ulong roleId, RequestOptions options = null) | |||
=> AddRolesAsync(new[] { roleId }, options); | |||
/// <inheritdoc /> | |||
public Task AddRoleAsync(IRole role, RequestOptions options = null) | |||
=> AddRolesAsync(new[] { role }, options); | |||
=> AddRoleAsync(role.Id, options); | |||
/// <inheritdoc /> | |||
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) | |||
=> UserHelper.AddRolesAsync(this, Discord, roleIds, options); | |||
/// <inheritdoc /> | |||
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) | |||
=> UserHelper.AddRolesAsync(this, Discord, roles, options); | |||
=> AddRolesAsync(roles.Select(x => x.Id), options); | |||
/// <inheritdoc /> | |||
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) | |||
=> RemoveRolesAsync(new[] { roleId }, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRoleAsync(IRole role, RequestOptions options = null) | |||
=> RemoveRolesAsync(new[] { role }, options); | |||
=> RemoveRoleAsync(role.Id, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) | |||
=> UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) | |||
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options); | |||
=> RemoveRolesAsync(roles.Select(x => x.Id)); | |||
/// <inheritdoc /> | |||
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception> | |||
@@ -79,13 +79,13 @@ namespace Discord.Rest | |||
} | |||
/// <summary> | |||
/// Returns a direct message channel to this user, or create one if it does not already exist. | |||
/// Creates a direct message channel to this user. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains a rest DM channel where the user is the recipient. | |||
/// </returns> | |||
public Task<RestDMChannel> GetOrCreateDMChannelAsync(RequestOptions options = null) | |||
public Task<RestDMChannel> CreateDMChannelAsync(RequestOptions options = null) | |||
=> UserHelper.CreateDMChannelAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
@@ -107,7 +107,7 @@ namespace Discord.Rest | |||
//IUser | |||
/// <inheritdoc /> | |||
async Task<IDMChannel> IUser.GetOrCreateDMChannelAsync(RequestOptions options) | |||
=> await GetOrCreateDMChannelAsync(options).ConfigureAwait(false); | |||
async Task<IDMChannel> IUser.CreateDMChannelAsync(RequestOptions options) | |||
=> await CreateDMChannelAsync(options).ConfigureAwait(false); | |||
} | |||
} |
@@ -59,27 +59,35 @@ namespace Discord.Rest | |||
/// <inheritdoc /> | |||
ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); | |||
/// <inheritdoc /> | |||
Task IGuildUser.KickAsync(string reason, RequestOptions options) => | |||
Task IGuildUser.KickAsync(string reason, RequestOptions options) => | |||
throw new NotSupportedException("Webhook users cannot be kicked."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) => | |||
Task IGuildUser.ModifyAsync(Action<GuildUserProperties> func, RequestOptions options) => | |||
throw new NotSupportedException("Webhook users cannot be modified."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => | |||
Task IGuildUser.AddRoleAsync(ulong role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => | |||
Task IGuildUser.AddRolesAsync(IEnumerable<ulong> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.RemoveRoleAsync(ulong role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<ulong> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
/// <inheritdoc /> | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) => | |||
throw new NotSupportedException("Roles are not supported on webhook users."); | |||
//IVoiceState | |||
@@ -73,16 +73,16 @@ namespace Discord.Rest | |||
return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); | |||
} | |||
public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<IRole> roles, RequestOptions options) | |||
public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<ulong> roleIds, RequestOptions options) | |||
{ | |||
foreach (var role in roles) | |||
await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); | |||
foreach (var roleId in roleIds) | |||
await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); | |||
} | |||
public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<IRole> roles, RequestOptions options) | |||
public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable<ulong> roleIds, RequestOptions options) | |||
{ | |||
foreach (var role in roles) | |||
await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options).ConfigureAwait(false); | |||
foreach (var roleId in roleIds) | |||
await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -73,8 +73,6 @@ namespace Discord.Net.Converters | |||
} | |||
//Enums | |||
if (type == typeof(PermissionTarget)) | |||
return PermissionTargetConverter.Instance; | |||
if (type == typeof(UserStatus)) | |||
return UserStatusConverter.Instance; | |||
if (type == typeof(EmbedType)) | |||
@@ -1,44 +0,0 @@ | |||
using Newtonsoft.Json; | |||
using System; | |||
namespace Discord.Net.Converters | |||
{ | |||
internal class PermissionTargetConverter : JsonConverter | |||
{ | |||
public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); | |||
public override bool CanConvert(Type objectType) => true; | |||
public override bool CanRead => true; | |||
public override bool CanWrite => true; | |||
/// <exception cref="JsonSerializationException">Unknown permission target.</exception> | |||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||
{ | |||
switch ((string)reader.Value) | |||
{ | |||
case "member": | |||
return PermissionTarget.User; | |||
case "role": | |||
return PermissionTarget.Role; | |||
default: | |||
throw new JsonSerializationException("Unknown permission target."); | |||
} | |||
} | |||
/// <exception cref="JsonSerializationException">Invalid permission target.</exception> | |||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||
{ | |||
switch ((PermissionTarget)value) | |||
{ | |||
case PermissionTarget.User: | |||
writer.WriteValue("member"); | |||
break; | |||
case PermissionTarget.Role: | |||
writer.WriteValue("role"); | |||
break; | |||
default: | |||
throw new JsonSerializationException("Invalid permission target."); | |||
} | |||
} | |||
} | |||
} |
@@ -17,8 +17,6 @@ namespace Discord.API.Gateway | |||
public Optional<int[]> ShardingParams { get; set; } | |||
[JsonProperty("presence")] | |||
public Optional<StatusUpdateParams> Presence { get; set; } | |||
[JsonProperty("guild_subscriptions")] | |||
public Optional<bool> GuildSubscriptions { get; set; } | |||
[JsonProperty("intents")] | |||
public Optional<int> Intents { get; set; } | |||
} | |||
@@ -1,4 +1,4 @@ | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json; | |||
namespace Discord.API.Gateway | |||
{ | |||
@@ -10,7 +10,11 @@ namespace Discord.API.Gateway | |||
public ulong MessageId { get; set; } | |||
[JsonProperty("channel_id")] | |||
public ulong ChannelId { get; set; } | |||
[JsonProperty("guild_id")] | |||
public Optional<ulong> GuildId { get; set; } | |||
[JsonProperty("emoji")] | |||
public Emoji Emoji { get; set; } | |||
[JsonProperty("member")] | |||
public Optional<GuildMember> Member { get; set; } | |||
} | |||
} |
@@ -124,11 +124,11 @@ namespace Discord.WebSocket | |||
/// <code language="cs" region="MessageDeleted" | |||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs" /> | |||
/// </example> | |||
public event Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task> MessageDeleted { | |||
public event Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> MessageDeleted { | |||
add { _messageDeletedEvent.Add(value); } | |||
remove { _messageDeletedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _messageDeletedEvent = new AsyncEvent<Func<Cacheable<IMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||
/// <summary> Fired when multiple messages are bulk deleted. </summary> | |||
/// <remarks> | |||
/// <note> | |||
@@ -155,12 +155,12 @@ namespace Discord.WebSocket | |||
/// <see cref="ISocketMessageChannel"/> parameter. | |||
/// </para> | |||
/// </remarks> | |||
public event Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task> MessagesBulkDeleted | |||
public event Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task> MessagesBulkDeleted | |||
{ | |||
add { _messagesBulkDeletedEvent.Add(value); } | |||
remove { _messagesBulkDeletedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>> _messagesBulkDeletedEvent = new AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, ISocketMessageChannel, Task>>(); | |||
internal readonly AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task>> _messagesBulkDeletedEvent = new AsyncEvent<Func<IReadOnlyCollection<Cacheable<IMessage, ulong>>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||
/// <summary> Fired when a message is updated. </summary> | |||
/// <remarks> | |||
/// <para> | |||
@@ -217,23 +217,23 @@ namespace Discord.WebSocket | |||
/// <code language="cs" region="ReactionAdded" | |||
/// source="..\Discord.Net.Examples\WebSocket\BaseSocketClient.Events.Examples.cs"/> | |||
/// </example> | |||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { | |||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionAdded { | |||
add { _reactionAddedEvent.Add(value); } | |||
remove { _reactionAddedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>(); | |||
/// <summary> Fired when a reaction is removed from a message. </summary> | |||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { | |||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task> ReactionRemoved { | |||
add { _reactionRemovedEvent.Add(value); } | |||
remove { _reactionRemovedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, SocketReaction, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, SocketReaction, Task>>(); | |||
/// <summary> Fired when all reactions to a message are cleared. </summary> | |||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task> ReactionsCleared { | |||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task> ReactionsCleared { | |||
add { _reactionsClearedEvent.Add(value); } | |||
remove { _reactionsClearedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _reactionsClearedEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||
/// <summary> | |||
/// Fired when all reactions to a message with a specific emote are removed. | |||
/// </summary> | |||
@@ -250,12 +250,12 @@ namespace Discord.WebSocket | |||
/// The emoji that all reactions had and were removed will be passed into the <see cref="IEmote"/> parameter. | |||
/// </para> | |||
/// </remarks> | |||
public event Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task> ReactionsRemovedForEmote | |||
public event Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task> ReactionsRemovedForEmote | |||
{ | |||
add { _reactionsRemovedForEmoteEvent.Add(value); } | |||
remove { _reactionsRemovedForEmoteEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, ISocketMessageChannel, IEmote, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>> _reactionsRemovedForEmoteEvent = new AsyncEvent<Func<Cacheable<IUserMessage, ulong>, Cacheable<IMessageChannel, ulong>, IEmote, Task>>(); | |||
//Roles | |||
/// <summary> Fired when a role is created. </summary> | |||
@@ -347,11 +347,11 @@ namespace Discord.WebSocket | |||
} | |||
internal readonly AsyncEvent<Func<SocketUser, SocketUser, Task>> _userUpdatedEvent = new AsyncEvent<Func<SocketUser, SocketUser, Task>>(); | |||
/// <summary> Fired when a guild member is updated, or a member presence is updated. </summary> | |||
public event Func<SocketGuildUser, SocketGuildUser, Task> GuildMemberUpdated { | |||
public event Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task> GuildMemberUpdated { | |||
add { _guildMemberUpdatedEvent.Add(value); } | |||
remove { _guildMemberUpdatedEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<SocketGuildUser, SocketGuildUser, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>> _guildMemberUpdatedEvent = new AsyncEvent<Func<Cacheable<SocketGuildUser, ulong>, SocketGuildUser, Task>>(); | |||
/// <summary> Fired when a user joins, leaves, or moves voice channels. </summary> | |||
public event Func<SocketUser, SocketVoiceState, SocketVoiceState, Task> UserVoiceStateUpdated { | |||
add { _userVoiceStateUpdatedEvent.Add(value); } | |||
@@ -372,11 +372,11 @@ namespace Discord.WebSocket | |||
} | |||
internal readonly AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>> _selfUpdatedEvent = new AsyncEvent<Func<SocketSelfUser, SocketSelfUser, Task>>(); | |||
/// <summary> Fired when a user starts typing. </summary> | |||
public event Func<SocketUser, ISocketMessageChannel, Task> UserIsTyping { | |||
public event Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task> UserIsTyping { | |||
add { _userIsTypingEvent.Add(value); } | |||
remove { _userIsTypingEvent.Remove(value); } | |||
} | |||
internal readonly AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>> _userIsTypingEvent = new AsyncEvent<Func<SocketUser, ISocketMessageChannel, Task>>(); | |||
internal readonly AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>> _userIsTypingEvent = new AsyncEvent<Func<Cacheable<IUser, ulong>, Cacheable<IMessageChannel, ulong>, Task>>(); | |||
/// <summary> Fired when a user joins a group channel. </summary> | |||
public event Func<SocketGroupUser, Task> RecipientAdded { | |||
add { _recipientAddedEvent.Add(value); } | |||
@@ -47,7 +47,7 @@ namespace Discord.WebSocket | |||
/// <summary> | |||
/// Gets the current logged-in user. | |||
/// </summary> | |||
public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||
public virtual new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } | |||
/// <summary> | |||
/// Gets a collection of guilds that the user is currently in. | |||
/// </summary> | |||
@@ -70,20 +70,11 @@ namespace Discord.WebSocket | |||
/// A read-only collection of private channels that the user currently partakes in. | |||
/// </returns> | |||
public abstract IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels { get; } | |||
/// <summary> | |||
/// Gets a collection of available voice regions. | |||
/// </summary> | |||
/// <returns> | |||
/// A read-only collection of voice regions that the user has access to. | |||
/// </returns> | |||
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
public abstract IReadOnlyCollection<RestVoiceRegion> VoiceRegions { get; } | |||
internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) | |||
: base(config, client) => BaseConfig = config; | |||
private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
=> new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||
rateLimitPrecision: config.RateLimitPrecision, | |||
useSystemClock: config.UseSystemClock); | |||
/// <summary> | |||
@@ -164,16 +155,6 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public abstract SocketGuild GetGuild(ulong id); | |||
/// <summary> | |||
/// Gets a voice region. | |||
/// </summary> | |||
/// <param name="id">The identifier of the voice region (e.g. <c>eu-central</c> ).</param> | |||
/// <returns> | |||
/// A REST-based voice region associated with the identifier; <c>null</c> if the voice region is not | |||
/// found. | |||
/// </returns> | |||
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
public abstract RestVoiceRegion GetVoiceRegion(string id); | |||
/// <summary> | |||
/// Gets all voice regions. | |||
/// </summary> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
@@ -327,10 +308,14 @@ namespace Discord.WebSocket | |||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
/// <inheritdoc /> | |||
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
{ | |||
return await GetVoiceRegionAsync(id).ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
{ | |||
return await GetVoiceRegionsAsync().ConfigureAwait(false); | |||
} | |||
} | |||
} |
@@ -75,11 +75,6 @@ namespace Discord | |||
nextReconnectDelay = 1000; //Reset delay | |||
await _connectionPromise.Task.ConfigureAwait(false); | |||
} | |||
catch (OperationCanceledException ex) | |||
{ | |||
Cancel(); //In case this exception didn't come from another Error call | |||
await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested).ConfigureAwait(false); | |||
} | |||
catch (Exception ex) | |||
{ | |||
Error(ex); //In case this exception didn't come from another Error call | |||
@@ -143,16 +138,7 @@ namespace Discord | |||
catch (OperationCanceledException) { } | |||
}); | |||
try | |||
{ | |||
await _onConnecting().ConfigureAwait(false); | |||
} | |||
catch (TaskCanceledException ex) | |||
{ | |||
Exception innerEx = ex.InnerException ?? new OperationCanceledException("Failed to connect."); | |||
Error(innerEx); | |||
throw innerEx; | |||
} | |||
await _onConnecting().ConfigureAwait(false); | |||
await _logger.InfoAsync("Connected").ConfigureAwait(false); | |||
State = ConnectionState.Connected; | |||
@@ -36,14 +36,13 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<ISocketPrivateChannel> PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(GetPrivateChannelCount); | |||
public IReadOnlyCollection<DiscordSocketClient> Shards => _shards; | |||
/// <inheritdoc /> | |||
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => _shards[0].VoiceRegions; | |||
/// <summary> | |||
/// Provides access to a REST-only client with a shared state from this client. | |||
/// </summary> | |||
public override DiscordSocketRestClient Rest => _shards[0].Rest; | |||
public override DiscordSocketRestClient Rest => _shards?[0].Rest; | |||
public override SocketSelfUser CurrentUser { get => _shards?.FirstOrDefault(x => x.CurrentUser != null)?.CurrentUser; protected set => throw new InvalidOperationException(); } | |||
/// <summary> Creates a new REST/WebSocket Discord client. </summary> | |||
public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } | |||
@@ -91,8 +90,7 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, | |||
rateLimitPrecision: config.RateLimitPrecision); | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); | |||
internal async Task AcquireIdentifyLockAsync(int shardId, CancellationToken token) | |||
{ | |||
@@ -264,11 +262,6 @@ namespace Discord.WebSocket | |||
return null; | |||
} | |||
/// <inheritdoc /> | |||
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
public override RestVoiceRegion GetVoiceRegion(string id) | |||
=> _shards[0].GetVoiceRegion(id); | |||
/// <inheritdoc /> | |||
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
{ | |||
@@ -339,14 +332,6 @@ namespace Discord.WebSocket | |||
} | |||
return Task.Delay(0); | |||
}; | |||
if (isPrimary) | |||
{ | |||
client.Ready += () => | |||
{ | |||
CurrentUser = client.CurrentUser; | |||
return Task.Delay(0); | |||
}; | |||
} | |||
client.Connected += () => _shardConnectedEvent.InvokeAsync(client); | |||
client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); | |||
@@ -432,11 +417,15 @@ namespace Discord.WebSocket | |||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IVoiceRegion>>(VoiceRegions); | |||
async Task<IReadOnlyCollection<IVoiceRegion>> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) | |||
{ | |||
return await GetVoiceRegionsAsync().ConfigureAwait(false); | |||
} | |||
/// <inheritdoc /> | |||
Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
=> Task.FromResult<IVoiceRegion>(GetVoiceRegion(id)); | |||
async Task<IVoiceRegion> IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) | |||
{ | |||
return await GetVoiceRegionAsync(id).ConfigureAwait(false); | |||
} | |||
internal override void Dispose(bool disposing) | |||
{ | |||
@@ -40,9 +40,8 @@ namespace Discord.API | |||
public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, | |||
string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null, | |||
RateLimitPrecision rateLimitPrecision = RateLimitPrecision.Second, | |||
bool useSystemClock = true) | |||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, rateLimitPrecision, useSystemClock) | |||
: base(restClientProvider, userAgent, defaultRetryMode, serializer, useSystemClock) | |||
{ | |||
_gatewayUrl = url; | |||
if (url != null) | |||
@@ -189,9 +188,9 @@ namespace Discord.API | |||
catch { } | |||
if (ex is GatewayReconnectException) | |||
await WebSocketClient.DisconnectAsync(4000); | |||
await WebSocketClient.DisconnectAsync(4000).ConfigureAwait(false); | |||
else | |||
await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
await WebSocketClient.DisconnectAsync().ConfigureAwait(false); | |||
ConnectionState = ConnectionState.Disconnected; | |||
} | |||
@@ -216,7 +215,7 @@ namespace Discord.API | |||
await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); | |||
} | |||
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, bool guildSubscriptions = true, GatewayIntents? gatewayIntents = null, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) | |||
public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, GatewayIntents gatewayIntents = GatewayIntents.AllUnprivileged, (UserStatus, bool, long?, GameModel)? presence = null, RequestOptions options = null) | |||
{ | |||
options = RequestOptions.CreateOrClone(options); | |||
var props = new Dictionary<string, string> | |||
@@ -234,10 +233,7 @@ namespace Discord.API | |||
options.BucketId = GatewayBucket.Get(GatewayBucketType.Identify).Id; | |||
if (gatewayIntents.HasValue) | |||
msg.Intents = (int)gatewayIntents.Value; | |||
else | |||
msg.GuildSubscriptions = guildSubscriptions; | |||
msg.Intents = (int)gatewayIntents; | |||
if (presence.HasValue) | |||
{ | |||
@@ -43,8 +43,7 @@ namespace Discord.WebSocket | |||
private DateTimeOffset? _statusSince; | |||
private RestApplication _applicationInfo; | |||
private bool _isDisposed; | |||
private bool _guildSubscriptions; | |||
private GatewayIntents? _gatewayIntents; | |||
private GatewayIntents _gatewayIntents; | |||
/// <summary> | |||
/// Provides access to a REST-only client with a shared state from this client. | |||
@@ -72,7 +71,6 @@ namespace Discord.WebSocket | |||
internal WebSocketProvider WebSocketProvider { get; private set; } | |||
internal bool AlwaysDownloadUsers { get; private set; } | |||
internal int? HandlerTimeout { get; private set; } | |||
internal bool? ExclusiveBulkDelete { get; private set; } | |||
internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; | |||
/// <inheritdoc /> | |||
@@ -109,9 +107,6 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public IReadOnlyCollection<SocketGroupChannel> GroupChannels | |||
=> State.PrivateChannels.OfType<SocketGroupChannel>().ToImmutableArray(); | |||
/// <inheritdoc /> | |||
[Obsolete("This property is obsolete, use the GetVoiceRegionsAsync method instead.")] | |||
public override IReadOnlyCollection<RestVoiceRegion> VoiceRegions => GetVoiceRegionsAsync().GetAwaiter().GetResult(); | |||
/// <summary> | |||
/// Initializes a new REST/WebSocket-based Discord client. | |||
@@ -136,11 +131,9 @@ namespace Discord.WebSocket | |||
WebSocketProvider = config.WebSocketProvider; | |||
AlwaysDownloadUsers = config.AlwaysDownloadUsers; | |||
HandlerTimeout = config.HandlerTimeout; | |||
ExclusiveBulkDelete = config.ExclusiveBulkDelete; | |||
State = new ClientState(0, 0); | |||
Rest = new DiscordSocketRestClient(config, ApiClient); | |||
_heartbeatTimes = new ConcurrentQueue<long>(); | |||
_guildSubscriptions = config.GuildSubscriptions; | |||
_gatewayIntents = config.GatewayIntents; | |||
_stateLock = new SemaphoreSlim(1, 1); | |||
@@ -182,8 +175,7 @@ namespace Discord.WebSocket | |||
_largeGuilds = new ConcurrentQueue<ulong>(); | |||
} | |||
private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost, | |||
rateLimitPrecision: config.RateLimitPrecision); | |||
=> new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); | |||
/// <inheritdoc /> | |||
internal override void Dispose(bool disposing) | |||
{ | |||
@@ -201,11 +193,6 @@ namespace Discord.WebSocket | |||
base.Dispose(disposing); | |||
} | |||
/// <inheritdoc /> | |||
internal override async Task OnLoginAsync(TokenType tokenType, string token) | |||
{ | |||
await Rest.OnLoginAsync(tokenType, token); | |||
} | |||
/// <inheritdoc /> | |||
internal override async Task OnLogoutAsync() | |||
{ | |||
@@ -243,7 +230,7 @@ namespace Discord.WebSocket | |||
else | |||
{ | |||
await _gatewayLogger.DebugAsync("Identifying").ConfigureAwait(false); | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
} | |||
} | |||
finally | |||
@@ -302,13 +289,51 @@ namespace Discord.WebSocket | |||
public override SocketChannel GetChannel(ulong id) | |||
=> State.GetChannel(id); | |||
/// <summary> | |||
/// Gets a generic channel from the cache or does a rest request if unavailable. | |||
/// </summary> | |||
/// <example> | |||
/// <code language="cs" title="Example method"> | |||
/// var channel = await _client.GetChannelAsync(381889909113225237); | |||
/// if (channel != null && channel is IMessageChannel msgChannel) | |||
/// { | |||
/// await msgChannel.SendMessageAsync($"{msgChannel} is created at {msgChannel.CreatedAt}"); | |||
/// } | |||
/// </code> | |||
/// </example> | |||
/// <param name="id">The snowflake identifier of the channel (e.g. `381889909113225237`).</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains the channel associated | |||
/// with the snowflake identifier; <c>null</c> when the channel cannot be found. | |||
/// </returns> | |||
public async ValueTask<IChannel> GetChannelAsync(ulong id, RequestOptions options = null) | |||
=> GetChannel(id) ?? (IChannel)await ClientHelper.GetChannelAsync(this, id, options).ConfigureAwait(false); | |||
/// <summary> | |||
/// Gets a user from the cache or does a rest request if unavailable. | |||
/// </summary> | |||
/// <example> | |||
/// <code language="cs" title="Example method"> | |||
/// var user = await _client.GetUserAsync(168693960628371456); | |||
/// if (user != null) | |||
/// Console.WriteLine($"{user} is created at {user.CreatedAt}."; | |||
/// </code> | |||
/// </example> | |||
/// <param name="id">The snowflake identifier of the user (e.g. `168693960628371456`).</param> | |||
/// <param name="options">The options to be used when sending the request.</param> | |||
/// <returns> | |||
/// A task that represents the asynchronous get operation. The task result contains the user associated with | |||
/// the snowflake identifier; <c>null</c> if the user is not found. | |||
/// </returns> | |||
public async ValueTask<IUser> GetUserAsync(ulong id, RequestOptions options = null) | |||
=> await ClientHelper.GetUserAsync(this, id, options).ConfigureAwait(false); | |||
/// <summary> | |||
/// Clears all cached channels from the client. | |||
/// </summary> | |||
public void PurgeChannelCache() => State.PurgeAllChannels(); | |||
/// <summary> | |||
/// Clears cached DM channels from the client. | |||
/// </summary> | |||
public void PurgeDMChannelCache() => State.PurgeDMChannels(); | |||
public void PurgeDMChannelCache() => RemoveDMChannels(); | |||
/// <inheritdoc /> | |||
public override SocketUser GetUser(ulong id) | |||
@@ -322,12 +347,11 @@ namespace Discord.WebSocket | |||
public void PurgeUserCache() => State.PurgeUsers(); | |||
internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) | |||
{ | |||
return state.GetOrAddUser(model.Id, x => | |||
{ | |||
var user = SocketGlobalUser.Create(this, state, model); | |||
user.GlobalUser.AddRef(); | |||
return user; | |||
}); | |||
return state.GetOrAddUser(model.Id, x => SocketGlobalUser.Create(this, state, model)); | |||
} | |||
internal SocketUser GetOrCreateTemporaryUser(ClientState state, Discord.API.User model) | |||
{ | |||
return state.GetUser(model.Id) ?? (SocketUser)SocketUnknownUser.Create(this, state, model); | |||
} | |||
internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) | |||
{ | |||
@@ -335,18 +359,13 @@ namespace Discord.WebSocket | |||
{ | |||
var user = SocketGlobalUser.Create(this, state, model); | |||
user.GlobalUser.AddRef(); | |||
user.Presence = new SocketPresence(UserStatus.Online, null, null, null); | |||
user.Presence = new SocketPresence(UserStatus.Online, null, null); | |||
return user; | |||
}); | |||
} | |||
internal void RemoveUser(ulong id) | |||
=> State.RemoveUser(id); | |||
/// <inheritdoc /> | |||
[Obsolete("This method is obsolete, use GetVoiceRegionAsync instead.")] | |||
public override RestVoiceRegion GetVoiceRegion(string id) | |||
=> GetVoiceRegionAsync(id).GetAwaiter().GetResult(); | |||
/// <inheritdoc /> | |||
public override async ValueTask<IReadOnlyCollection<RestVoiceRegion>> GetVoiceRegionsAsync(RequestOptions options = null) | |||
{ | |||
@@ -469,7 +488,8 @@ namespace Discord.WebSocket | |||
{ | |||
if (CurrentUser == null) | |||
return; | |||
CurrentUser.Presence = new SocketPresence(Status, Activity, null, null); | |||
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | |||
CurrentUser.Presence = new SocketPresence(Status, null, activities); | |||
var presence = BuildCurrentStatus() ?? (UserStatus.Online, false, null, null); | |||
@@ -564,7 +584,7 @@ namespace Discord.WebSocket | |||
await _shardedClient.AcquireIdentifyLockAsync(ShardId, _connection.CancelToken).ConfigureAwait(false); | |||
try | |||
{ | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
} | |||
finally | |||
{ | |||
@@ -572,7 +592,7 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
else | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, guildSubscriptions: _guildSubscriptions, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards, gatewayIntents: _gatewayIntents, presence: BuildCurrentStatus()).ConfigureAwait(false); | |||
} | |||
break; | |||
case GatewayOpCode.Reconnect: | |||
@@ -595,8 +615,10 @@ namespace Discord.WebSocket | |||
var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); | |||
var currentUser = SocketSelfUser.Create(this, state, data.User); | |||
currentUser.Presence = new SocketPresence(Status, Activity, null, null); | |||
var activities = _activity.IsSpecified ? ImmutableList.Create(_activity.Value) : null; | |||
currentUser.Presence = new SocketPresence(Status, null, activities); | |||
ApiClient.CurrentUserId = currentUser.Id; | |||
Rest.CurrentUser = RestSelfUser.Create(this, data.User); | |||
int unavailableGuilds = 0; | |||
for (int i = 0; i < data.Guilds.Length; i++) | |||
{ | |||
@@ -749,7 +771,8 @@ namespace Discord.WebSocket | |||
break; | |||
case "GUILD_SYNC": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); | |||
await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_SYNC)").ConfigureAwait(false); | |||
/*await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); //TODO remove? userbot related | |||
var data = (payload as JToken).ToObject<GuildSyncEvent>(_serializer); | |||
var guild = State.GetGuild(data.Id); | |||
if (guild != null) | |||
@@ -766,7 +789,7 @@ namespace Discord.WebSocket | |||
{ | |||
await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
}*/ | |||
} | |||
break; | |||
case "GUILD_DELETE": | |||
@@ -966,15 +989,15 @@ namespace Discord.WebSocket | |||
var before = user.Clone(); | |||
user.Update(State, data); | |||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); | |||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(before, user.Id, true, () => Task.FromResult((SocketGuildUser)null)); | |||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
if (!guild.HasAllMembers) | |||
await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); | |||
else | |||
await UnknownGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); | |||
return; | |||
user = guild.AddOrUpdateUser(data); | |||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(null, user.Id, false, () => Task.FromResult((SocketGuildUser)null)); | |||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||
} | |||
} | |||
else | |||
@@ -1237,56 +1260,63 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (guild != null && !guild.IsSynced) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (guild != null && !guild.IsSynced) | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
if (channel == null) | |||
{ | |||
if (!data.GuildId.IsSpecified) // assume it is a DM | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
channel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||
} | |||
SocketUser author; | |||
if (guild != null) | |||
else | |||
{ | |||
if (data.WebhookId.IsSpecified) | |||
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
else | |||
author = guild.GetUser(data.Author.Value.Id); | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
SocketUser author; | |||
if (guild != null) | |||
{ | |||
if (data.WebhookId.IsSpecified) | |||
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
author = guild.GetUser(data.Author.Value.Id); | |||
} | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
if (author == null) | |||
if (author == null) | |||
{ | |||
if (guild != null) | |||
{ | |||
if (guild != null) | |||
if (data.Member.IsSpecified) // member isn't always included, but use it when we can | |||
{ | |||
if (data.Member.IsSpecified) // member isn't always included, but use it when we can | |||
{ | |||
data.Member.Value.User = data.Author.Value; | |||
author = guild.AddOrUpdateUser(data.Member.Value); | |||
} | |||
else | |||
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data | |||
data.Member.Value.User = data.Author.Value; | |||
author = guild.AddOrUpdateUser(data.Member.Value); | |||
} | |||
else if (channel is SocketGroupChannel) | |||
author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); | |||
else | |||
{ | |||
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data | |||
} | |||
else if (channel is SocketGroupChannel groupChannel) | |||
author = groupChannel.GetOrAddUser(data.Author.Value); | |||
else | |||
{ | |||
await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var msg = SocketMessage.Create(this, State, author, channel, data); | |||
SocketChannelHelper.AddMessage(channel, this, msg); | |||
await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
var msg = SocketMessage.Create(this, State, author, channel, data); | |||
SocketChannelHelper.AddMessage(channel, this, msg); | |||
await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_UPDATE": | |||
@@ -1294,52 +1324,85 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (guild != null && !guild.IsSynced) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (guild != null && !guild.IsSynced) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
SocketMessage before = null, after = null; | |||
SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); | |||
bool isCached = cachedMsg != null; | |||
if (isCached) | |||
SocketMessage before = null, after = null; | |||
SocketMessage cachedMsg = channel?.GetCachedMessage(data.Id); | |||
bool isCached = cachedMsg != null; | |||
if (isCached) | |||
{ | |||
before = cachedMsg.Clone(); | |||
cachedMsg.Update(State, data); | |||
after = cachedMsg; | |||
} | |||
else | |||
{ | |||
//Edited message isnt in cache, create a detached one | |||
SocketUser author; | |||
if (data.Author.IsSpecified) | |||
{ | |||
before = cachedMsg.Clone(); | |||
cachedMsg.Update(State, data); | |||
after = cachedMsg; | |||
if (guild != null) | |||
{ | |||
if (data.WebhookId.IsSpecified) | |||
author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); | |||
else | |||
author = guild.GetUser(data.Author.Value.Id); | |||
} | |||
else | |||
author = (channel as SocketChannel)?.GetUser(data.Author.Value.Id); | |||
if (author == null) | |||
{ | |||
if (guild != null) | |||
{ | |||
if (data.Member.IsSpecified) // member isn't always included, but use it when we can | |||
{ | |||
data.Member.Value.User = data.Author.Value; | |||
author = guild.AddOrUpdateUser(data.Member.Value); | |||
} | |||
else | |||
author = guild.AddOrUpdateUser(data.Author.Value); // user has no guild-specific data | |||
} | |||
else if (channel is SocketGroupChannel groupChannel) | |||
author = groupChannel.GetOrAddUser(data.Author.Value); | |||
} | |||
} | |||
else | |||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | |||
author = new SocketUnknownUser(this, id: 0); | |||
if (channel == null) | |||
{ | |||
//Edited message isnt in cache, create a detached one | |||
SocketUser author; | |||
if (data.Author.IsSpecified) | |||
if (!data.GuildId.IsSpecified) // assume it is a DM | |||
{ | |||
if (guild != null) | |||
author = guild.GetUser(data.Author.Value.Id); | |||
if (data.Author.IsSpecified) | |||
{ | |||
var dmChannel = CreateDMChannel(data.ChannelId, data.Author.Value, State); | |||
channel = dmChannel; | |||
author = dmChannel.Recipient; | |||
} | |||
else | |||
author = (channel as SocketChannel).GetUser(data.Author.Value.Id); | |||
if (author == null) | |||
author = SocketUnknownUser.Create(this, State, data.Author.Value); | |||
channel = CreateDMChannel(data.ChannelId, author, State); | |||
} | |||
else | |||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | |||
author = new SocketUnknownUser(this, id: 0); | |||
after = SocketMessage.Create(this, State, author, channel, data); | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
} | |||
var cacheableBefore = new Cacheable<IMessage, ulong>(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||
await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
after = SocketMessage.Create(this, State, author, channel, data); | |||
} | |||
var cacheableBefore = new Cacheable<IMessage, ulong>(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||
await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_DELETE": | |||
@@ -1347,26 +1410,22 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Message>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); | |||
bool isCached = msg != null; | |||
var cacheable = new Cacheable<IMessage, ulong>(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id).ConfigureAwait(false)); | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); | |||
} | |||
else | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
SocketMessage msg = null; | |||
if (channel != null) | |||
msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); | |||
var cacheableMsg = new Cacheable<IMessage, ulong>(msg, data.Id, msg != null, () => Task.FromResult((IMessage)null)); | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheableMsg, cacheableChannel).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_REACTION_ADD": | |||
@@ -1374,32 +1433,43 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isCached = cachedMsg != null; | |||
var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var optionalMsg = !isCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
var optionalUser = user is null | |||
? Optional.Create<IUser>() | |||
: Optional.Create(user); | |||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isMsgCached = cachedMsg != null; | |||
IUser user = null; | |||
if (channel != null) | |||
user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||
var optionalMsg = !isMsgCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
cachedMsg?.AddReaction(reaction); | |||
if (data.Member.IsSpecified) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, channel, reaction).ConfigureAwait(false); | |||
if (guild != null) | |||
user = guild.AddOrUpdateUser(data.Member.Value); | |||
} | |||
else | |||
user = GetUser(data.UserId); | |||
var optionalUser = user is null | |||
? Optional.Create<IUser>() | |||
: Optional.Create(user); | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||
}); | |||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||
cachedMsg?.AddReaction(reaction); | |||
await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_REACTION_REMOVE": | |||
@@ -1407,32 +1477,35 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Gateway.Reaction>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isCached = cachedMsg != null; | |||
var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||
var optionalMsg = !isCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var optionalUser = user is null | |||
? Optional.Create<IUser>() | |||
: Optional.Create(user); | |||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isMsgCached = cachedMsg != null; | |||
IUser user = null; | |||
if (channel != null) | |||
user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly).ConfigureAwait(false); | |||
else if (!data.GuildId.IsSpecified) | |||
user = GetUser(data.UserId); | |||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||
var optionalMsg = !isMsgCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
cachedMsg?.RemoveReaction(reaction); | |||
var optionalUser = user is null | |||
? Optional.Create<IUser>() | |||
: Optional.Create(user); | |||
await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, channel, reaction).ConfigureAwait(false); | |||
} | |||
else | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||
}); | |||
var reaction = SocketReaction.Create(data, channel, optionalMsg, optionalUser); | |||
cachedMsg?.RemoveReaction(reaction); | |||
await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheableMsg, cacheableChannel, reaction).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_REACTION_REMOVE_ALL": | |||
@@ -1440,21 +1513,20 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<RemoveAllReactionsEvent>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isMsgCached = cachedMsg != null; | |||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||
{ | |||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isCached = cachedMsg != null; | |||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => (await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false)) as IUserMessage); | |||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||
}); | |||
cachedMsg?.ClearReactions(); | |||
cachedMsg?.ClearReactions(); | |||
await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, channel).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheableMsg, cacheableChannel).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_REACTION_REMOVE_EMOJI": | |||
@@ -1462,70 +1534,55 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_EMOJI)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<API.Gateway.RemoveAllReactionsForEmoteEvent>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isCached = cachedMsg != null; | |||
var optionalMsg = !isCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var cacheable = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage); | |||
var emote = data.Emoji.ToIEmote(); | |||
var cachedMsg = channel?.GetCachedMessage(data.MessageId) as SocketUserMessage; | |||
bool isMsgCached = cachedMsg != null; | |||
cachedMsg?.RemoveAllReactionsForEmoteAsync(emote); | |||
var optionalMsg = !isMsgCached | |||
? Optional.Create<SocketUserMessage>() | |||
: Optional.Create(cachedMsg); | |||
await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheable, channel, emote).ConfigureAwait(false); | |||
} | |||
else | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var cacheableMsg = new Cacheable<IUserMessage, ulong>(cachedMsg, data.MessageId, isMsgCached, async () => | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
return; | |||
} | |||
var channelObj = await cacheableChannel.GetOrDownloadAsync().ConfigureAwait(false); | |||
return await channelObj.GetMessageAsync(data.MessageId).ConfigureAwait(false) as IUserMessage; | |||
}); | |||
var emote = data.Emoji.ToIEmote(); | |||
cachedMsg?.RemoveReactionsForEmote(emote); | |||
await TimedInvokeAsync(_reactionsRemovedForEmoteEvent, nameof(ReactionsRemovedForEmote), cacheableMsg, cacheableChannel, emote).ConfigureAwait(false); | |||
} | |||
break; | |||
case "MESSAGE_DELETE_BULK": | |||
{ | |||
await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); | |||
if (!ExclusiveBulkDelete.HasValue) | |||
{ | |||
await _gatewayLogger.WarningAsync("A bulk delete event has been received, but the event handling behavior has not been set. " + | |||
"To suppress this message, set the ExclusiveBulkDelete configuration property. " + | |||
"This message will appear only once."); | |||
ExclusiveBulkDelete = false; | |||
} | |||
var data = (payload as JToken).ToObject<MessageDeleteBulkEvent>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var cacheableList = new List<Cacheable<IMessage, ulong>>(data.Ids.Length); | |||
foreach (ulong id in data.Ids) | |||
{ | |||
var msg = SocketChannelHelper.RemoveMessage(channel, this, id); | |||
bool isCached = msg != null; | |||
var cacheable = new Cacheable<IMessage, ulong>(msg, id, isCached, async () => await channel.GetMessageAsync(id).ConfigureAwait(false)); | |||
cacheableList.Add(cacheable); | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
if (!ExclusiveBulkDelete ?? false) // this shouldn't happen, but we'll play it safe anyways | |||
await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); | |||
} | |||
await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, channel).ConfigureAwait(false); | |||
} | |||
else | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var cacheableList = new List<Cacheable<IMessage, ulong>>(data.Ids.Length); | |||
foreach (ulong id in data.Ids) | |||
{ | |||
SocketMessage msg = null; | |||
if (channel != null) | |||
msg = SocketChannelHelper.RemoveMessage(channel, this, id); | |||
bool isMsgCached = msg != null; | |||
var cacheableMsg = new Cacheable<IMessage, ulong>(msg, id, isMsgCached, () => Task.FromResult((IMessage)null)); | |||
cacheableList.Add(cacheableMsg); | |||
} | |||
await TimedInvokeAsync(_messagesBulkDeletedEvent, nameof(MessagesBulkDeleted), cacheableList, cacheableChannel).ConfigureAwait(false); | |||
} | |||
break; | |||
@@ -1571,7 +1628,8 @@ namespace Discord.WebSocket | |||
var before = user.Clone(); | |||
user.Update(State, data, true); | |||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); | |||
var cacheableBefore = new Cacheable<SocketGuildUser, ulong>(before, user.Id, true, () => Task.FromResult(user)); | |||
await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), cacheableBefore, user).ConfigureAwait(false); | |||
} | |||
else | |||
{ | |||
@@ -1594,24 +1652,26 @@ namespace Discord.WebSocket | |||
await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); | |||
var data = (payload as JToken).ToObject<TypingStartEvent>(_serializer); | |||
if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) | |||
var channel = GetChannel(data.ChannelId) as ISocketMessageChannel; | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
var guild = (channel as SocketGuildChannel)?.Guild; | |||
if (!(guild?.IsSynced ?? true)) | |||
{ | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); | |||
return; | |||
} | |||
var user = (channel as SocketChannel).GetUser(data.UserId); | |||
if (user == null) | |||
{ | |||
if (guild != null) | |||
user = guild.AddOrUpdateUser(data.Member); | |||
} | |||
if (user != null) | |||
await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); | |||
var cacheableChannel = new Cacheable<IMessageChannel, ulong>(channel, data.ChannelId, channel != null, async () => await GetChannelAsync(data.ChannelId).ConfigureAwait(false) as IMessageChannel); | |||
var user = (channel as SocketChannel)?.GetUser(data.UserId); | |||
if (user == null) | |||
{ | |||
if (guild != null) | |||
user = guild.AddOrUpdateUser(data.Member); | |||
} | |||
var cacheableUser = new Cacheable<IUser, ulong>(user, data.UserId, user != null, async () => await GetUserAsync(data.UserId).ConfigureAwait(false)); | |||
await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), cacheableUser, cacheableChannel).ConfigureAwait(false); | |||
} | |||
break; | |||
@@ -1683,7 +1743,7 @@ namespace Discord.WebSocket | |||
} | |||
else | |||
{ | |||
var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||
var groupChannel = GetChannel(data.ChannelId.Value) as SocketGroupChannel; | |||
if (groupChannel == null) | |||
{ | |||
await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); | |||
@@ -1930,24 +1990,33 @@ namespace Discord.WebSocket | |||
{ | |||
var channel = SocketChannel.CreatePrivate(this, state, model); | |||
state.AddChannel(channel as SocketChannel); | |||
if (channel is SocketDMChannel dm) | |||
dm.Recipient.GlobalUser.DMChannel = dm; | |||
return channel; | |||
} | |||
internal SocketDMChannel CreateDMChannel(ulong channelId, API.User model, ClientState state) | |||
{ | |||
return SocketDMChannel.Create(this, state, channelId, model); | |||
} | |||
internal SocketDMChannel CreateDMChannel(ulong channelId, SocketUser user, ClientState state) | |||
{ | |||
return new SocketDMChannel(this, channelId, user); | |||
} | |||
internal ISocketPrivateChannel RemovePrivateChannel(ulong id) | |||
{ | |||
var channel = State.RemoveChannel(id) as ISocketPrivateChannel; | |||
if (channel != null) | |||
{ | |||
if (channel is SocketDMChannel dmChannel) | |||
dmChannel.Recipient.GlobalUser.DMChannel = null; | |||
foreach (var recipient in channel.Recipients) | |||
recipient.GlobalUser.RemoveRef(this); | |||
} | |||
return channel; | |||
} | |||
internal void RemoveDMChannels() | |||
{ | |||
var channels = State.DMChannels; | |||
State.PurgeDMChannels(); | |||
foreach (var channel in channels) | |||
channel.Recipient.GlobalUser.RemoveRef(this); | |||
} | |||
private async Task GuildAvailableAsync(SocketGuild guild) | |||
{ | |||
@@ -2103,8 +2172,8 @@ namespace Discord.WebSocket | |||
=> await GetApplicationInfoAsync().ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IChannel>(GetChannel(id)); | |||
async Task<IChannel> IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> mode == CacheMode.AllowDownload ? await GetChannelAsync(id, options).ConfigureAwait(false) : GetChannel(id); | |||
/// <inheritdoc /> | |||
Task<IReadOnlyCollection<IPrivateChannel>> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IReadOnlyCollection<IPrivateChannel>>(PrivateChannels); | |||
@@ -2134,8 +2203,8 @@ namespace Discord.WebSocket | |||
=> await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IUser>(GetUser(id)); | |||
async Task<IUser> IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) | |||
=> mode == CacheMode.AllowDownload ? await GetUserAsync(id, options).ConfigureAwait(false) : GetUser(id); | |||
/// <inheritdoc /> | |||
Task<IUser> IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) | |||
=> Task.FromResult<IUser>(GetUser(username, discriminator)); | |||
@@ -111,28 +111,6 @@ namespace Discord.WebSocket | |||
/// </summary> | |||
public int? HandlerTimeout { get; set; } = 3000; | |||
/// <summary> | |||
/// Gets or sets the behavior for <see cref="BaseSocketClient.MessageDeleted"/> on bulk deletes. | |||
/// </summary> | |||
/// <remarks> | |||
/// <para> | |||
/// If <c>true</c>, the <see cref="BaseSocketClient.MessageDeleted"/> event will not be raised for bulk | |||
/// deletes, and only the <see cref="BaseSocketClient.MessagesBulkDeleted"/> will be raised. If <c>false</c> | |||
/// , both events will be raised. | |||
/// </para> | |||
/// <para> | |||
/// If unset, both events will be raised, but a warning will be raised the first time a bulk delete event is | |||
/// received. | |||
/// </para> | |||
/// </remarks> | |||
public bool? ExclusiveBulkDelete { get; set; } = null; | |||
/// <summary> | |||
/// Gets or sets enabling dispatching of guild subscription events e.g. presence and typing events. | |||
/// This is not used if <see cref="GatewayIntents"/> are provided. | |||
/// </summary> | |||
public bool GuildSubscriptions { get; set; } = true; | |||
/// <summary> | |||
/// Gets or sets the maximum identify concurrency. | |||
/// </summary> | |||
@@ -172,14 +150,15 @@ namespace Discord.WebSocket | |||
private int maxWaitForGuildAvailable = 10000; | |||
/// <summary> | |||
/// Gets or sets gateway intents to limit what events are sent from Discord. Allows for more granular control than the <see cref="GuildSubscriptions"/> property. | |||
/// Gets or sets gateway intents to limit what events are sent from Discord. | |||
/// The default is <see cref="GatewayIntents.AllUnprivileged"/>. | |||
/// </summary> | |||
/// <remarks> | |||
/// For more information, please see | |||
/// <see href="https://discord.com/developers/docs/topics/gateway#gateway-intents">GatewayIntents</see> | |||
/// on the official Discord API documentation. | |||
/// </remarks> | |||
public GatewayIntents? GatewayIntents { get; set; } | |||
public GatewayIntents GatewayIntents { get; set; } = GatewayIntents.AllUnprivileged; | |||
/// <summary> | |||
/// Initializes a new instance of the <see cref="DiscordSocketConfig"/> class with the default configuration. | |||
@@ -16,32 +16,27 @@ namespace Discord.WebSocket | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel | |||
{ | |||
private readonly MessageCache _messages; | |||
/// <summary> | |||
/// Gets the recipient of the channel. | |||
/// </summary> | |||
public SocketUser Recipient { get; } | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<SocketMessage> CachedMessages => _messages?.Messages ?? ImmutableArray.Create<SocketMessage>(); | |||
public IReadOnlyCollection<SocketMessage> CachedMessages => ImmutableArray.Create<SocketMessage>(); | |||
/// <summary> | |||
/// Gets a collection that is the current logged-in user and the recipient. | |||
/// </summary> | |||
public new IReadOnlyCollection<SocketUser> Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); | |||
internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) | |||
internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketUser recipient) | |||
: base(discord, id) | |||
{ | |||
Recipient = recipient; | |||
recipient.GlobalUser.AddRef(); | |||
if (Discord.MessageCacheSize > 0) | |||
_messages = new MessageCache(Discord); | |||
} | |||
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) | |||
{ | |||
var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); | |||
var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateTemporaryUser(state, model.Recipients.Value[0])); | |||
entity.Update(state, model); | |||
return entity; | |||
} | |||
@@ -49,6 +44,16 @@ namespace Discord.WebSocket | |||
{ | |||
Recipient.Update(state, model.Recipients.Value[0]); | |||
} | |||
internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, ulong channelId, API.User recipient) | |||
{ | |||
var entity = new SocketDMChannel(discord, channelId, discord.GetOrCreateTemporaryUser(state, recipient)); | |||
entity.Update(state, recipient); | |||
return entity; | |||
} | |||
internal void Update(ClientState state, API.User recipient) | |||
{ | |||
Recipient.Update(state, recipient); | |||
} | |||
/// <inheritdoc /> | |||
public Task CloseAsync(RequestOptions options = null) | |||
@@ -57,7 +62,7 @@ namespace Discord.WebSocket | |||
//Messages | |||
/// <inheritdoc /> | |||
public SocketMessage GetCachedMessage(ulong id) | |||
=> _messages?.Get(id); | |||
=> null; | |||
/// <summary> | |||
/// Gets the message associated with the given <paramref name="id"/>. | |||
/// </summary> | |||
@@ -68,10 +73,7 @@ namespace Discord.WebSocket | |||
/// </returns> | |||
public async Task<IMessage> GetMessageAsync(ulong id, RequestOptions options = null) | |||
{ | |||
IMessage msg = _messages?.Get(id); | |||
if (msg == null) | |||
msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); | |||
return msg; | |||
return await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); | |||
} | |||
/// <summary> | |||
@@ -87,7 +89,7 @@ namespace Discord.WebSocket | |||
/// Paged collection of messages. | |||
/// </returns> | |||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); | |||
/// <summary> | |||
/// Gets a collection of messages in this channel. | |||
/// </summary> | |||
@@ -103,7 +105,7 @@ namespace Discord.WebSocket | |||
/// Paged collection of messages. | |||
/// </returns> | |||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); | |||
/// <summary> | |||
/// Gets a collection of messages in this channel. | |||
/// </summary> | |||
@@ -119,16 +121,16 @@ namespace Discord.WebSocket | |||
/// Paged collection of messages. | |||
/// </returns> | |||
public IAsyncEnumerable<IReadOnlyCollection<IMessage>> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); | |||
=> ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) | |||
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); | |||
=> ImmutableArray.Create<SocketMessage>(); | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); | |||
=> ImmutableArray.Create<SocketMessage>(); | |||
/// <inheritdoc /> | |||
public IReadOnlyCollection<SocketMessage> GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) | |||
=> SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); | |||
=> ImmutableArray.Create<SocketMessage>(); | |||
/// <inheritdoc /> | |||
public Task<IReadOnlyCollection<RestMessage>> GetPinnedMessagesAsync(RequestOptions options = null) | |||
=> ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); | |||
@@ -152,6 +154,10 @@ namespace Discord.WebSocket | |||
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public Task TriggerTypingAsync(RequestOptions options = null) | |||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
@@ -160,9 +166,12 @@ namespace Discord.WebSocket | |||
=> ChannelHelper.EnterTypingState(this, Discord, options); | |||
internal void AddMessage(SocketMessage msg) | |||
=> _messages?.Add(msg); | |||
{ | |||
} | |||
internal SocketMessage RemoveMessage(ulong id) | |||
=> _messages?.Remove(id); | |||
{ | |||
return null; | |||
} | |||
//Users | |||
/// <summary> | |||
@@ -218,13 +227,13 @@ namespace Discord.WebSocket | |||
} | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); | |||
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(limit, options); | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); | |||
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessageId, dir, limit, options); | |||
/// <inheritdoc /> | |||
IAsyncEnumerable<IReadOnlyCollection<IMessage>> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) | |||
=> SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); | |||
=> mode == CacheMode.CacheOnly ? null : GetMessagesAsync(fromMessage.Id, dir, limit, options); | |||
/// <inheritdoc /> | |||
async Task<IReadOnlyCollection<IMessage>> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) | |||
=> await GetPinnedMessagesAsync(options).ConfigureAwait(false); | |||
@@ -180,6 +180,10 @@ namespace Discord.WebSocket | |||
public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public Task TriggerTypingAsync(RequestOptions options = null) | |||
=> ChannelHelper.TriggerTypingAsync(this, Discord, options); | |||
@@ -180,6 +180,10 @@ namespace Discord.WebSocket | |||
public Task DeleteMessagesAsync(IEnumerable<ulong> messageIds, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); | |||
/// <inheritdoc /> | |||
public async Task<IUserMessage> ModifyMessageAsync(ulong messageId, Action<MessageProperties> func, RequestOptions options = null) | |||
=> await ChannelHelper.ModifyMessageAsync(this, messageId, func, Discord, options).ConfigureAwait(false); | |||
/// <inheritdoc /> | |||
public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) | |||
=> ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); | |||
@@ -46,8 +46,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public int AFKTimeout { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsEmbeddable { get; private set; } | |||
/// <inheritdoc /> | |||
public bool IsWidgetEnabled { get; private set; } | |||
/// <inheritdoc /> | |||
public VerificationLevel VerificationLevel { get; private set; } | |||
@@ -84,7 +82,6 @@ namespace Discord.WebSocket | |||
public ulong? ApplicationId { get; internal set; } | |||
internal ulong? AFKChannelId { get; private set; } | |||
internal ulong? EmbedChannelId { get; private set; } | |||
internal ulong? WidgetChannelId { get; private set; } | |||
internal ulong? SystemChannelId { get; private set; } | |||
internal ulong? RulesChannelId { get; private set; } | |||
@@ -198,21 +195,6 @@ namespace Discord.WebSocket | |||
} | |||
} | |||
/// <summary> | |||
/// Gets the embed channel (i.e. the channel set in the guild's widget settings) in this guild. | |||
/// </summary> | |||
/// <returns> | |||
/// A channel set within the server's widget settings; <see langword="null"/> if none is set. | |||
/// </returns> | |||
[Obsolete("This property is deprecated, use WidgetChannel instead.")] | |||
public SocketGuildChannel EmbedChannel | |||
{ | |||
get | |||
{ | |||
var id = EmbedChannelId; | |||
return id.HasValue ? GetChannel(id.Value) : null; | |||
} | |||
} | |||
/// <summary> | |||
/// Gets the widget channel (i.e. the channel set in the guild's widget settings) in this guild. | |||
/// </summary> | |||
/// <returns> | |||
@@ -405,7 +387,8 @@ namespace Discord.WebSocket | |||
for (int i = 0; i < model.Members.Length; i++) | |||
{ | |||
var member = SocketGuildUser.Create(this, state, model.Members[i]); | |||
members.TryAdd(member.Id, member); | |||
if (members.TryAdd(member.Id, member)) | |||
member.GlobalUser.AddRef(); | |||
} | |||
DownloadedMemberCount = members.Count; | |||
@@ -440,16 +423,12 @@ namespace Discord.WebSocket | |||
internal void Update(ClientState state, Model model) | |||
{ | |||
AFKChannelId = model.AFKChannelId; | |||
if (model.EmbedChannelId.IsSpecified) | |||
EmbedChannelId = model.EmbedChannelId.Value; | |||
if (model.WidgetChannelId.IsSpecified) | |||
WidgetChannelId = model.WidgetChannelId.Value; | |||
SystemChannelId = model.SystemChannelId; | |||
RulesChannelId = model.RulesChannelId; | |||
PublicUpdatesChannelId = model.PublicUpdatesChannelId; | |||
AFKTimeout = model.AFKTimeout; | |||
if (model.EmbedEnabled.IsSpecified) | |||
IsEmbeddable = model.EmbedEnabled.Value; | |||
if (model.WidgetEnabled.IsSpecified) | |||
IsWidgetEnabled = model.WidgetEnabled.Value; | |||
IconId = model.Icon; | |||
@@ -504,7 +483,7 @@ namespace Discord.WebSocket | |||
} | |||
_roles = roles; | |||
} | |||
internal void Update(ClientState state, GuildSyncModel model) | |||
/*internal void Update(ClientState state, GuildSyncModel model) //TODO remove? userbot related | |||
{ | |||
var members = new ConcurrentDictionary<ulong, SocketGuildUser>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); | |||
{ | |||
@@ -524,9 +503,9 @@ namespace Discord.WebSocket | |||
_members = members; | |||
var _ = _syncPromise.TrySetResultAsync(true); | |||
/*if (!model.Large) | |||
_ = _downloaderPromise.TrySetResultAsync(true);*/ | |||
} | |||
//if (!model.Large) | |||
// _ = _downloaderPromise.TrySetResultAsync(true); | |||
}*/ | |||
internal void Update(ClientState state, EmojiUpdateModel model) | |||
{ | |||
@@ -548,11 +527,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
[Obsolete("This endpoint is deprecated, use ModifyWidgetAsync instead.")] | |||
public Task ModifyEmbedAsync(Action<GuildEmbedProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyEmbedAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
/// <exception cref="ArgumentNullException"><paramref name="func"/> is <see langword="null"/>.</exception> | |||
public Task ModifyWidgetAsync(Action<GuildWidgetProperties> func, RequestOptions options = null) | |||
=> GuildHelper.ModifyWidgetAsync(this, Discord, func, options); | |||
/// <inheritdoc /> | |||
@@ -869,16 +843,10 @@ namespace Discord.WebSocket | |||
else | |||
{ | |||
member = SocketGuildUser.Create(this, Discord.State, model); | |||
if (member == null) | |||
throw new InvalidOperationException("SocketGuildUser.Create failed to produce a member"); // TODO 2.2rel: delete this | |||
if (member.GlobalUser == null) | |||
throw new InvalidOperationException("Member was created without global user"); // TODO 2.2rel: delete this | |||
member.GlobalUser.AddRef(); | |||
_members[member.Id] = member; | |||
DownloadedMemberCount++; | |||
} | |||
if (member == null) | |||
throw new InvalidOperationException("AddOrUpdateUser failed to produce a user"); // TODO 2.2rel: delete this | |||
return member; | |||
} | |||
internal SocketGuildUser AddOrUpdateUser(PresenceModel model) | |||
@@ -912,6 +880,7 @@ namespace Discord.WebSocket | |||
if (self != null) | |||
_members.TryAdd(self.Id, self); | |||
_downloaderPromise = new TaskCompletionSource<bool>(); | |||
DownloadedMemberCount = _members.Count; | |||
foreach (var member in members) | |||
@@ -1008,6 +977,9 @@ namespace Discord.WebSocket | |||
//Emotes | |||
/// <inheritdoc /> | |||
public Task<IReadOnlyCollection<GuildEmote>> GetEmotesAsync(RequestOptions options = null) | |||
=> GuildHelper.GetEmotesAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task<GuildEmote> GetEmoteAsync(ulong id, RequestOptions options = null) | |||
=> GuildHelper.GetEmoteAsync(this, Discord, id, options); | |||
/// <inheritdoc /> | |||
@@ -1229,10 +1201,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
bool IGuild.Available => true; | |||
/// <inheritdoc /> | |||
ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; | |||
/// <inheritdoc /> | |||
ulong? IGuild.EmbedChannelId => EmbedChannelId; | |||
/// <inheritdoc /> | |||
ulong? IGuild.WidgetChannelId => WidgetChannelId; | |||
/// <inheritdoc /> | |||
ulong? IGuild.SystemChannelId => SystemChannelId; | |||
@@ -1287,10 +1255,6 @@ namespace Discord.WebSocket | |||
Task<ITextChannel> IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<ITextChannel>(DefaultChannel); | |||
/// <inheritdoc /> | |||
[Obsolete("This method is deprecated, use GetWidgetChannelAsync instead.")] | |||
Task<IGuildChannel> IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuildChannel>(EmbedChannel); | |||
/// <inheritdoc /> | |||
Task<IGuildChannel> IGuild.GetWidgetChannelAsync(CacheMode mode, RequestOptions options) | |||
=> Task.FromResult<IGuildChannel>(WidgetChannel); | |||
/// <inheritdoc /> | |||
@@ -49,9 +49,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
int? IInvite.MemberCount => throw new NotImplementedException(); | |||
/// <inheritdoc /> | |||
[Obsolete("This property doesn't exist anymore and shouldn't be used.")] | |||
bool IInviteMetadata.IsRevoked => throw new NotImplementedException(); | |||
/// <inheritdoc /> | |||
public bool IsTemporary { get; private set; } | |||
/// <inheritdoc /> | |||
int? IInviteMetadata.MaxAge { get => MaxAge; } | |||
@@ -61,6 +61,9 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public MessageFlags? Flags { get; private set; } | |||
/// <inheritdoc/> | |||
public MessageType Type { get; private set; } | |||
/// <summary> | |||
/// Returns all attachments included in this message. | |||
/// </summary> | |||
@@ -99,6 +102,8 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<ITag> Tags => ImmutableArray.Create<ITag>(); | |||
/// <inheritdoc /> | |||
public virtual IReadOnlyCollection<Sticker> Stickers => ImmutableArray.Create<Sticker>(); | |||
/// <inheritdoc /> | |||
public IReadOnlyDictionary<IEmote, ReactionMetadata> Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); | |||
/// <inheritdoc /> | |||
@@ -120,6 +125,8 @@ namespace Discord.WebSocket | |||
} | |||
internal virtual void Update(ClientState state, Model model) | |||
{ | |||
Type = model.Type; | |||
if (model.Timestamp.IsSpecified) | |||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||
@@ -183,8 +190,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
IMessageChannel IMessage.Channel => Channel; | |||
/// <inheritdoc /> | |||
MessageType IMessage.Type => MessageType.Default; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IAttachment> IMessage.Attachments => Attachments; | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<IEmbed> IMessage.Embeds => Embeds; | |||
@@ -194,6 +199,8 @@ namespace Discord.WebSocket | |||
IReadOnlyCollection<ulong> IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ulong> IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); | |||
/// <inheritdoc /> | |||
IReadOnlyCollection<ISticker> IMessage.Stickers => Stickers; | |||
internal void AddReaction(SocketReaction reaction) | |||
{ | |||
@@ -9,9 +9,6 @@ namespace Discord.WebSocket | |||
[DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
public class SocketSystemMessage : SocketMessage, ISystemMessage | |||
{ | |||
/// <inheritdoc /> | |||
public MessageType Type { get; private set; } | |||
internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) | |||
: base(discord, id, channel, author, MessageSource.System) | |||
{ | |||
@@ -25,8 +22,6 @@ namespace Discord.WebSocket | |||
internal override void Update(ClientState state, Model model) | |||
{ | |||
base.Update(state, model); | |||
Type = model.Type; | |||
} | |||
private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; | |||
@@ -23,6 +23,7 @@ namespace Discord.WebSocket | |||
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | |||
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>(); | |||
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); | |||
private ImmutableArray<Sticker> _stickers = ImmutableArray.Create<Sticker>(); | |||
/// <inheritdoc /> | |||
public override bool IsTTS => _isTTS; | |||
@@ -47,6 +48,8 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions; | |||
/// <inheritdoc /> | |||
public override IReadOnlyCollection<Sticker> Stickers => _stickers; | |||
/// <inheritdoc /> | |||
public IUserMessage ReferencedMessage => _referencedMessage; | |||
internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) | |||
@@ -158,6 +161,20 @@ namespace Discord.WebSocket | |||
refMsgAuthor = new SocketUnknownUser(Discord, id: 0); | |||
_referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); | |||
} | |||
if (model.Stickers.IsSpecified) | |||
{ | |||
var value = model.Stickers.Value; | |||
if (value.Length > 0) | |||
{ | |||
var stickers = ImmutableArray.CreateBuilder<Sticker>(value.Length); | |||
for (int i = 0; i < value.Length; i++) | |||
stickers.Add(Sticker.Create(value[i])); | |||
_stickers = stickers.ToImmutable(); | |||
} | |||
else | |||
_stickers = ImmutableArray.Create<Sticker>(); | |||
} | |||
} | |||
/// <inheritdoc /> | |||
@@ -172,9 +189,6 @@ namespace Discord.WebSocket | |||
/// <inheritdoc /> | |||
public Task UnpinAsync(RequestOptions options = null) | |||
=> MessageHelper.UnpinAsync(this, Discord, options); | |||
/// <inheritdoc /> | |||
public Task ModifySuppressionAsync(bool suppressEmbeds, RequestOptions options = null) | |||
=> MessageHelper.SuppressEmbedsAsync(this, Discord, suppressEmbeds, options); | |||
public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, | |||
TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) | |||
@@ -12,7 +12,6 @@ namespace Discord.WebSocket | |||
public override string Username { get; internal set; } | |||
public override ushort DiscriminatorValue { get; internal set; } | |||
public override string AvatarId { get; internal set; } | |||
public SocketDMChannel DMChannel { get; internal set; } | |||
internal override SocketPresence Presence { get; set; } | |||
public override bool IsWebhook => false; | |||
@@ -52,7 +51,6 @@ namespace Discord.WebSocket | |||
internal void Update(ClientState state, PresenceModel model) | |||
{ | |||
Presence = SocketPresence.Create(model); | |||
DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); | |||
} | |||
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)"; | |||
@@ -63,7 +63,7 @@ namespace Discord.WebSocket | |||
/// <summary> | |||
/// Returns a collection of roles that the user possesses. | |||
/// </summary> | |||
public IReadOnlyCollection<SocketRole> Roles | |||
public IReadOnlyCollection<SocketRole> Roles | |||
=> _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); | |||
/// <summary> | |||
/// Returns the voice channel the user is in, or <c>null</c> if none. | |||
@@ -125,12 +125,16 @@ namespace Discord.WebSocket | |||
{ | |||
var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
entity.Update(state, model); | |||
if (!model.Roles.IsSpecified) | |||
entity.UpdateRoles(new ulong[0]); | |||
return entity; | |||
} | |||
internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) | |||
{ | |||
var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); | |||
entity.Update(state, model, false); | |||
if (!model.Roles.IsSpecified) | |||
entity.UpdateRoles(new ulong[0]); | |||
return entity; | |||
} | |||
internal void Update(ClientState state, MemberModel model) | |||
@@ -177,17 +181,29 @@ namespace Discord.WebSocket | |||
public Task KickAsync(string reason = null, RequestOptions options = null) | |||
=> UserHelper.KickAsync(this, Discord, reason, options); | |||
/// <inheritdoc /> | |||
public Task AddRoleAsync(ulong roleId, RequestOptions options = null) | |||
=> AddRolesAsync(new[] { roleId }, options); | |||
/// <inheritdoc /> | |||
public Task AddRoleAsync(IRole role, RequestOptions options = null) | |||
=> AddRolesAsync(new[] { role }, options); | |||
=> AddRoleAsync(role.Id, options); | |||
/// <inheritdoc /> | |||
public Task AddRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) | |||
=> UserHelper.AddRolesAsync(this, Discord, roleIds, options); | |||
/// <inheritdoc /> | |||
public Task AddRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) | |||
=> UserHelper.AddRolesAsync(this, Discord, roles, options); | |||
=> AddRolesAsync(roles.Select(x => x.Id), options); | |||
/// <inheritdoc /> | |||
public Task RemoveRoleAsync(ulong roleId, RequestOptions options = null) | |||
=> RemoveRolesAsync(new[] { roleId }, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRoleAsync(IRole role, RequestOptions options = null) | |||
=> RemoveRolesAsync(new[] { role }, options); | |||
=> RemoveRoleAsync(role.Id, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRolesAsync(IEnumerable<ulong> roleIds, RequestOptions options = null) | |||
=> UserHelper.RemoveRolesAsync(this, Discord, roleIds, options); | |||
/// <inheritdoc /> | |||
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) | |||
=> UserHelper.RemoveRolesAsync(this, Discord, roles, options); | |||
=> RemoveRolesAsync(roles.Select(x => x.Id)); | |||
/// <inheritdoc /> | |||
public ChannelPermissions GetPermissions(IGuildChannel channel) | |||